我正在用Common Lisp编写我自己的x86-64汇编程序,它为x86-64的子集生成正确的二进制代码。我使用一个自定义的阅读器宏将汇编代码转换为语法树,它按预期工作。
我试图实现的是允许在汇编代码中使用Lisp代码,这样我就可以将Lisp用作汇编程序的宏语言。我使用#a
作为宏调度字符,#e
作为读取器的结束信号。在读卡器内部,#l
变为Lisp模式,#a
变回汇编模式,#e
(读卡器宏的信号结束)应该在这两种模式下都工作。
我不明白的是如何将评估后的代码的结果输出回输入流(在代码的其余部分之前进行处理),或者如何再次读取Lisp代码输出,以便可以适当地处理Lisp代码的输出(它将是汇编代码)(与汇编代码的其他部分相同)。我怎样才能达到这个目标?
附带说明:这是我的第一个读者宏,所以可能存在设计缺陷。我认为我将Lisp代码读入字符串的方法不一定是最好的方法,如果有一些更短、更惯用的方法的话
以下是我的阅读器宏的简化版本:
(eval when(:编译顶层:加载顶层:执行)(defun获取最后一个字符串(我的字符串)"此函数返回由输入字符串的最后一个字符组成的字符串。"(subseq我的字符串(1-(长度我的字符串)))(defun获取不带最后一个字符的字符串(我的字符串)"此函数返回一个不包含输入字符串最后一个字符的字符串。"(subseq我的字符串0(1-(长度我的字符串)))(defun获取没有无效最后一个字符的字符串(我的字符串最后一个无效字符)如果字符串的最后一个字符无效,则返回不包含该字符的字符串,否则完全返回(循环查找无效最后字符中的无效最后字符do(if(equal(get last character string my string)最后一个字符无效)(setf我的字符串(获取没有最后一个字符的字符串我的字符串)))我的绳子)(将代码转换为字符串(流子字符numarg)"此函数将程序集代码转换为字符串。#l标记为Lisp代码的更改#a标记返回asm#e表示结束。部分基于:http://weitz.de/macros.lisp"(声明(忽略子字符numarg))(让*(最后一个字符无效(列出"'""(")")(当前模式"asm")(这行有代码nil吗)(当前阶段"线路起点")(我的字符串"(list")(lisp代码字符串");;循环通过流。(我的char的循环=(胁迫(list(读取char流t nil t))'字符串)do(cond((等电流模式"asm")(秒(相等的当前阶段"哈希符号读取");;角色是e吗?;;如果是,我们完成了,修复右括号并返回。(秒((等于我的字符"e")(从转换代码返回到字符串(concatenate’string(获取没有无效最后一个字符的字符串(获取没有无效最后一个字符的字符串我的字符串最后一个字符无效)无效的最后一个字符)")"));;角色是l吗?;;如果是,则更改为Lisp模式。((等于我的字符"l");;Lisp代码可以在这里读取和评估吗;;而不把它读成字符串?(progn(设置当前模式"Lisp")(setf这行有代码吗nil)(setf-lisp代码字符串")(设置当前阶段"行首"));;否则,打印错误。(t(错误"在asm模式下#之后未定义控制字符")));;是字符#?;;如果是,则将hash符号标记为read。((等于我的字符"#")(设置当前阶段"哈希符号读取");;字符是换行符吗?((等于我的字符(胁迫(list#\Newline)'字符串))(progn(秒;;这行有noo代码吗?;;如果为true,则不输出任何内容。(这行没有代码)(设置当前阶段"行首");;我们是在指令内部还是在参数内部?;;如果为true,则输出")(或(等电流相位"内部指令")(等电流相位"内部参数")(progn(设置当前阶段"行首")(setf这行有代码吗nil)(设置我的字符串(连接字符串我的字符串"\")));;否则输出)(t(progn(设置当前阶段"行首")(setf这行有代码吗nil)(setf my string(concatenate‘string my string")"))));;我们在评论范围内吗?;;如果是,则不输出任何内容。(等电流相位"内部注释")零);;我们在队伍的最前面吗?(等电流相位"线路起点")(秒;;这行开头有空格吗?;;如果是,则不输出任何内容。((等于我的字符")零);;这是指令的第一个字符吗?;;如果是,则标记此行有代码,将第一个字符标记为已打印、输出"和当前字符。(和(不是(等于我的字符"("))(not(等于我的字符")"))(progn(设置当前阶段"内部指令")(setf这一行t上有代码吗)(设置我的字符串(连接字符串我的字符串"(\"我的字符)))(零));;是性格?;;如果是,则不输出任何内容,开始注释。((等于我的字符";")(设置当前阶段"内部注释");;是字符空间还是逗号?(或(等于我的字符")(等于我的字符",")(秒;;是字符空间还是逗号,最后一个字符是_not_space、逗号还是左括号?;;如果是,输出"和空格。(和(not(equal(get last character string my string)")(not(equal(get last character string my string)",")(not(equal(get last character string my string)"("))(progn(设置当前阶段"在空间中")(设置我的字符串(连接字符串我的字符串"\"))(零));;是否打印指令,这是参数的第一个字符?(和(非(等电流相位"内部指令")(or(equal(get last character string my string)")(等于(获取最后一个字符串我的字符串)","))(秒;;标记我们在参数、输出和当前字符内部。(t(progn(设置当前阶段"内部参数")(setf my string(concatenate‘string my string"\"my char))));;否则输出字符。(t(setf-my string(concatenate‘string my string my char))))((等电流模式"Lisp");;在Lisp模式下,读取文本,直到到达#e或#a并对其进行求值。(秒(相等的当前阶段"哈希符号读取")(秒;;角色是e吗?;;如果是,我们完成了,修复右括号并返回。((等于我的字符"e")(progn(连接字符串"#a"(eval-lisp代码字符串)"#e");这应该有所不同。(从转换代码返回到字符串(concatenate’string(获取没有无效最后一个字符的字符串(获取没有无效最后一个字符的字符串我的字符串最后一个字符无效)无效的最后一个字符)")")));;角色是a吗?;;如果是,请更改为asm模式。((等于我的字符"a")(progn(设置当前模式"asm")(setf这行有代码吗nil)(设置当前阶段"行首")(连接字符串"#a"(eval-lisp代码字符串)"#e");这应该有所不同。;;否则,将#和字符添加到要评估的Lisp代码中。(t(progn(设置为当前阶段")(setf my string(concatenate‘string lisp code string"#"my char))));;是字符#?;;如果是,则将hash符号标记为read。((等于我的字符"#")(设置当前阶段"哈希符号读取");;否则,将该字符添加到要评估的Lisp代码中。(t(setf-my string(concatenate‘string lisp code string my char))))(t(错误"无效当前模式"))))#a是启动自定义读取器的输入。(设置调度宏字符#\###\a#'transform-code-to--string))
下面是一些内部没有Lisp代码的汇编代码示例,适用于:
(defparameter*example-code-x64*#ainc r10;递增寄存器r10。mov r11、r12;将r12的值存储到r11中。#e)
下面是一些内部有Lisp代码的汇编代码,失败(请参阅下面的编译错误)。在本例中,Lisp代码位于汇编代码之后,但应允许使用#a
和#l
作为分隔符来自由混合汇编代码和Lisp代码。
(defparameter*example-code-x64-with-lisp-fails*#ainc r10;递增寄存器r10。mov r11、r12;将r12的值存储到r11中。#l(循环用于(list"inc"dec")中的当前指令do((list"r13"r14"r15"中当前arg的循环)do(princ(concatenate)字符串当前指令"当前参数(胁迫(list#\Newline)'string))))#e)
上面代码的Lisp部分应该在自定义读取器中进行评估,这样它应该产生与下面代码相同的结果:
(defparameter*example-code-x64-with-lisp-fails*#ainc r10;递增寄存器r10。mov r11、r12;将r12的值存储到r11中。inc r13公司inc r14公司含r152013年12月2014年12月2015年12月#e)
但编译失败:
CL-USER>;编译文件"/home/user/code/lisp/lisp asm reader for stackoverflow.lisp"(编写于2014年3月28日下午10:11:29):;;捕获错误:;COMPILE-FILE期间的READ错误:;;值-1不是类型(MOD 4611686018427387901)。;;(表单从第1行、第0列、文件位置0开始);;编译单元中止;捕获1个致命错误条件;捕获1错误条件;编译在0:00:00.004之后中止1编译器注释:/home/user/code/lisp/lisp asm阅读器,适用于stackoverflow。lisp:10487读取错误:COMPILE-FILE期间发生读取错误:值-1不是类型(MOD 4611686018427387901)。(表单从第1行、第0列、文件位置0开始)CL-USER>
从读取器宏中读取lisp代码的惯用方法是调用cl:read。在您的示例中,在消费#L之后调用read将返回其car为循环的列表,并且该列表可以传递给eval。
要收集eval期间创建的输出,可以绑定*标准输出*。因此,一种选择是在阅读器宏中使用类似于以下内容的东西:
(let ((lisp-printed-string
(with-output-to-string (*standard-output*)
(eval (read stream t t t)))))
;; concatenate the lisp printed string onto your
;; hand parsed string here
)
另一种选择是让用户输入一个返回字符串的lisp表单{例如(concatenate"bar"baz")},并收集eval的返回值,而不是其打印输出。