将特殊字符写入方案中的输出端口(编译器设计)

writing special characters to output port in scheme (compiler design)

我有一个家庭作业要在 chez 方案中为 chez 方案(到 x64 程序集)创建一个编译器。
到目前为止,我的技术是创建一个巨大的字符串,然后将其显示在一个文件中。例如,假设我的输出汇编程序存储在变量 'str' 中——我将其写入文件的方法是:

(define out (open-output-port "file-name"))  
(display str out)  

我的字符串由 \n 分隔,它标志着每行的结尾。例如:最终 'str' 的一部分可能如下所示:

"mov rax, rdx\n  
LoopStart:\n  
cmp rdx,0\n"  

等等...(汇编代码)
这是通过对方案中的每一行代码使用(多次)调用字符串追加来实现的。
它一直工作正常,直到我意识到我无法将 "special" 字符输出到程序集文件 - 例如 \n - 它被解释为最终文件中的新行,即使我希望能够在我的最终装配中产生这种字符组合。 \t、#\newline 和显示函数将 "consume".
的任何其他特殊字符也是如此 我看到还有另一个方案函数没有 "consume" 这些特殊字符 - write.
write 的问题在于,例如:

(write out "blablalba \n\n blabla")  

将在文件 "out" 中生成以下文本: "blabla \n\n blabla"
(包括括号!!!)
我想找到一种能够在没有括号但也没有 "consuming" 特殊字符的情况下编写字符串的方法。
任何帮助将不胜感激,
谢谢,埃拉德

~~~~~~~~~~~~~编辑~~~~~~~~~~~~~~~~~~~~~
好的,首先感谢您的回复。 不过,我会尽量更准确地回答我的问题,
我想要的是能够编译这行代码,例如:

    (char? "\n")

我的编译器应该模仿 Chez 方案(我没有使用球拍!)当然还有 Chez returns #f。
但是在我的编译器中,我首先扫描代码中的任何常量 - 我当然找到了这个字符串,但是由于显示功能,输出的代码将有一个换行符而不是字符串文字 '\n'.
因此,正如建议的那样,我认为我需要做的就是找到一种方法将字符串文字中的 '\' 字符替换为 '\\' (因为 NASM 语法实际上不应该包含这些特殊字符,除了字符串文字)。
有没有简单的方法来解决这个问题?我可以使用 string->list 然后搜索这些特殊情况(我认为大约有 5 - \n,\r,\t,现在想不起来了)
无论如何,我会尝试写它,然后我会post在这里。谢谢

~~~~~~~~~~~~第二次编辑~~~~~~~~~~~~~~~~~~~~~`
好的,现在我知道我需要做点别的了,
我需要能够转动这个字符串:

    "abc\nabc\n\tabc"

对此:

    "'abc',10,'abc',10,9,'abc'"  

这是因为我确实在它前面有一个 "db" 前缀,这就是你应该如何在汇编中定义这个字符串文字。

如何在方案中执行此操作?

在我看来,您希望能够发出包含例如 \n 的字符串。例如,要创建一个包含字符串

的文本文件

abc\ndef

对吗?

如果是这样,那么答案很简单:只需 "display" 一个包含所需字符的字符串。事实上,我相信你对你的程序有点误解;挑战不在于让显示器做你想做的事,而是在于指定你想要输出的字符串。

让我们从代码开始:

#lang racket

(define str "abc\ndef")

(define out (open-output-file "/tmp/file-name"))  
(display str out)  

您应该会发现输出文件包含八个字符:一个 'a'、一个 'b'、一个 'c'、一个反斜杠、一个 'n'、一个 'd'、'e' 和 'f'.

这是(也许)令人惊讶的事情:文件中的字符串与 str 定义的字符串完全相同。查看此内容的一种方法是检查 string-length,或将字符串映射到列表:

(string-length str) ;; -> 8
(string->list str) ;; -> '(#\a #\b #\c #\ #\n #\d #\e #\f)

所以实际上,您真正的挑战与 'display' 没有任何关系,它只是将字符转储到端口;你的问题是在第一种情况下构建字符串,这可以通过多种不同的方式来完成。您可以使用引号字符,就像我在这个示例中所做的那样,或者您可以使用 string 函数,它将字符列表转换为字符串,或者您可以使用 "here-strings"... 或任何数字其他技术。

在您使用的 x86 Intel 语法中,您永远不需要文字 \" 字符,这使您可以轻松地将其包装在 double-quoted 字符串中,而无需使用 \ 转义在 asm.

中获得文字 \

事实上,YASM 甚至不支持具有 C-style 转义处理的字符文字,因此您只需要 \ 作为具有文字 \ 的字符常量的一部分。 NASM 确实支持 C-style 在反引号内转义,但无论哪种方式,您始终可以改用数字常量。

例如而不是

push  `abc\n`        ; NASM syntax for a numeric constant that will have the ASCII codes for  a, b, c, \n in that order in memory
mov   rsi, rsp   ; buf
mov   eax, 1     ; sys_write
mov   edi, 1     ; stdout
mov   edx, 4     ; length
syscall          ; x86-64 Linux sys_write(1, "abc\n", 4)
...

您可以发出与

相同的 machine-code 字节
; YASM syntax, or NASM without any C-style escapes
push   ('abc' <<8) | 0xa     ; 0xa = ASCII code for newline
...

所以在这种情况下,将字符文字与数字文字混合使用是很尴尬的,而不是仅使用 push 0x0a636261(x86 是 little-endian)。

作为 db pseudo-instruction:

的参数要简单得多
    lea   rdi, [rel hello_string]
    call  printf        ; puts would be more efficient, but for example purposes I don't want a function that adds a newline on its own.
    ...

section .rodata
  hello_string: db 'Hello World!', 0xa, 0   ; null-terminated string
  ; instead of 'Hello World!\n[=12=]'

Multi-byte 带引号的字符串作为 db 的多个参数。我假设您在方案中的 double-quoted 字符串中使用的 Intel 语法风格与那些足够相似。

我上面所说的一切也适用于 GNU as:您可以根据需要在 .ascii 之后使用 .byte,或者使用立即数的数字常量。

这里有几件事需要澄清:

"\n" 是一个长度为 1 的字符串,其中包含换行符。 "\n" 是一个长度为 2 的字符串,其中包含一个反斜杠和一个 n。 display 不转换字符串中的任何内容,它只打印字符串中的内容。因此,如果您有一个包含反斜杠和 n 的字符串,display 将打印它。

当您有一个包含反斜杠和 n 的文件并将该文件读入字符串时,您将得到字符串 "\n",而不是字符串 "\n"。如果文件包含一个实际的换行符,则后者是您将得到的。

因此,如果您有一个内容为 "abc\n" 的文件,并且您想将其转换为 db 'abc\n',则无需对 \n 执行任何操作。将文件读入字符串后,您将得到字符串 "\"abc\n\""(其中包含一个引号、字母 abc、一个反斜杠、一个 n 和另一个引号)。然后将双引号替换为单引号,并在前面添加一个 db,您将得到 "db 'abc\n'"。你打印它,你会得到 db 'abc\n' 就是这样。

假设您的目标语法是 nasm,就是这样。 db 'abc\n' 是有效的 nasm 语法并且会做你想做的。 db 'abc', 10 也是有效的语法并且做同样的事情,但只要您的输入语法只包含 nasm 支持的转义序列,就不需要将它们转换为字节。