如何在阅读器宏中评估Lisp代码?

时间:2014-03-28 20:48:24

标签: lisp common-lisp reader-macro

我在Common Lisp中编写自己的x86-64汇编程序,它为x86-64的子集生成正确的二进制代码。我使用自定义阅读器宏将汇编代码转换为语法树,并按预期工作。

我试图完成的是允许在汇编代码中使用Lisp代码,这样我就可以使用 Lisp作为汇编语言的宏语言。我使用#a作为宏调度字符,#e为读者发出信号。内部阅读器#l更改为Lisp模式,#a更改回组装模式,#e(表示阅读器宏的信号结束)应该在两种模式下都有效。

我不明白的是如何将评估代码的结果输出回输入流(在其余代码之前处理),或者如何获取Lisp代码输出再次阅读,以便可以适当地处理Lisp代码的输出(它将是汇编代码)(与汇编代码的其余部分相同)。我怎样才能实现这一目标?

旁注:这是我的第一个读者宏,因此可能存在设计缺陷。我认为我将Lisp代码读入字符串的方法不一定是最好的方法,如果有一些更短,更惯用的方法。

这是我的阅读器宏的简化版本:

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defun get-last-character-string (my-string)
    "This function returns a string consisting of the last character of the input string."
    (subseq my-string (1- (length my-string))))

  (defun get-string-without-last-character (my-string) 
    "This function returns a string without the last character of the input string."
    (subseq my-string 0 (1- (length my-string))))

  (defun get-string-without-invalid-last-character (my-string invalid-last-characters)
    "If the last character of the string is invalid, the string is returned without it, otherwise completely." 
    (loop for invalid-last-character in invalid-last-characters
          do (if (equal (get-last-character-string my-string) invalid-last-character)
               (setf my-string (get-string-without-last-character my-string))))
    my-string)

  (defun transform-code-to-string (stream sub-char numarg)
    "This function converts assembly code into a string.
     #l marks change to Lisp code. #a marks return to asm. #e marks end.
     Partially based on: http://weitz.de/macros.lisp"
    (declare (ignore sub-char numarg))
    (let*
      ((invalid-last-characters (list "'" " " "(" ")"))
       (current-mode "asm")
       (is-there-code-on-this-line nil)
       (current-phase "beginning-of-line")
       (my-string "(list ")
       (lisp-code-string ""))
      ;; loop through stream.
      (loop for my-char = (coerce (list (read-char stream t nil t)) 'string)
            do (cond
                 ((equal current-mode "asm")
                  (cond
                    ((equal current-phase "hash-sign-read")
                     ;; is character e ?
                     ;; if yes, we're done, fix closing parentheses and return.
                     (cond
                       ((equal my-char "e")
                        (return-from transform-code-to-string
                                     (concatenate 'string (get-string-without-invalid-last-character
                                                            (get-string-without-invalid-last-character
                                                              my-string invalid-last-characters)
                                                            invalid-last-characters) "))")))
                       ;; is character l ?
                       ;; if yes, change to Lisp mode.
                       ((equal my-char "l")
                        ;; could Lisp code could be read and evaluated here
                        ;; without reading it into a string?
                        (progn
                          (setf current-mode "Lisp") 
                          (setf is-there-code-on-this-line nil)
                          (setf lisp-code-string "")
                          (setf current-phase "beginning-of-line")))
                       ;; otherwise, print error.
                       (t (error "in asm mode undefined control character after #"))))
                    ;; is character # ?
                    ;; if yes, mark hash sign read.
                    ((equal my-char "#")
                     (setf current-phase "hash-sign-read"))
                    ;; is character newline?
                    ((equal my-char (coerce (list #\Newline) 'string))
                     (progn
                       (cond
                         ;; is there _no_ code on this line?
                         ;; if true, do not output anything.
                         ((not is-there-code-on-this-line)
                          (setf current-phase "beginning-of-line"))
                         ;; are we inside instruction or inside a parameter?
                         ;; if true, output ")
                         ((or (equal current-phase "inside-instruction")
                              (equal current-phase "inside-parameters"))
                          (progn
                            (setf current-phase "beginning-of-line")
                            (setf is-there-code-on-this-line nil)
                            (setf my-string (concatenate 'string my-string "\")"))))
                         ;; otherwise output )
                         (t (progn
                              (setf current-phase "beginning-of-line")
                              (setf is-there-code-on-this-line nil)
                              (setf my-string (concatenate 'string my-string ")")))))))
                    ;; are we inside a comment?
                    ;; if yes, don't output anything.
                    ((equal current-phase "inside-comment")
                     nil)
                    ;; are we in the beginning of the line?
                    ((equal current-phase "beginning-of-line")
                     (cond
                       ;; is this a space in the beginning of the line?
                       ;; if yes, do not output anything.
                       ((equal my-char " ")
                        nil)
                       ;; is this the first character of instruction and not ( or ) ?
                       ;; if yes, mark there is code on this line, mark first character as printed, output " and current character.
                       ((and
                          (not (equal my-char "("))
                          (not (equal my-char ")")))
                        (progn
                          (setf current-phase "inside-instruction")
                          (setf is-there-code-on-this-line t)
                          (setf my-string (concatenate 'string my-string "'(\"" my-char))))
                       (t nil)))
                    ;; is character ; ?
                    ;; if yes, don't output anything, begin comment.
                    ((equal my-char ";")
                     (setf current-phase "inside-comment"))
                    ;; is character space or comma?
                    ((or (equal my-char " ")
                         (equal my-char ","))
                     (cond
                       ;; is character space or comma, and last character was _not_ space, comma or opening parenthesis?
                       ;; if yes, output " and space.
                       ((and
                          (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
                          (setf current-phase "in-space")
                          (setf my-string (concatenate 'string my-string "\" "))))
                       (t nil)))
                    ;; is instruction printed and this is the 1st character of a parameter?
                    ((and
                       (not (equal current-phase "inside-instruction"))
                       (or (equal (get-last-character-string my-string) " ")
                           (equal (get-last-character-string my-string) ",")))
                     (cond
                       ;; mark we're inside parameters, output " and current character.
                       (t (progn
                            (setf current-phase "inside-parameters")
                            (setf my-string (concatenate 'string my-string "\"" my-char))))))
                    ;; otherwise output the character.
                    (t (setf my-string (concatenate 'string my-string my-char)))))
                 ((equal current-mode "Lisp")
                  ;; in Lisp mode, read text until #e or #a is reached and eval it.
                  (cond
                    ((equal current-phase "hash-sign-read")
                     (cond
                       ;; is character e ?
                       ;; if yes, we're done, fix closing parentheses and return.
                       ((equal my-char "e")
                        (progn
                          (concatenate 'string "#a" (eval lisp-code-string) "#e") ; this should be something different.
                          (return-from transform-code-to-string
                                       (concatenate 'string (get-string-without-invalid-last-character
                                                              (get-string-without-invalid-last-character
                                                                my-string invalid-last-characters)
                                                              invalid-last-characters) "))"))))
                       ;; is character a ?
                       ;; if yes, change to asm mode.
                       ((equal my-char "a")
                        (progn
                          (setf current-mode "asm")
                          (setf is-there-code-on-this-line nil)
                          (setf current-phase "beginning-of-line")
                          (concatenate 'string "#a" (eval lisp-code-string) "#e") ; this should be something different.
                          ;; otherwise, add # and the character to the Lisp code to be evaluated.
                          (t (progn
                               (setf current-phase "")
                               (setf my-string (concatenate 'string lisp-code-string "#" my-char))))))
                       ;; is character # ?
                       ;; if yes, mark hash sign read.
                       ((equal my-char "#")
                        (setf current-phase "hash-sign-read"))
                       ;; otherwise add the character to the Lisp code to be evaluated.
                       (t (setf my-string (concatenate 'string lisp-code-string my-char)))))
                    (t (error "invalid current mode"))))))

      ;;; #a is the input which starts the custom reader.
      (set-dispatch-macro-character #\# #\a #'transform-code-to-string))

这里有一些示例汇编代码,里面没有Lisp代码,有效:

(defparameter *example-code-x64*
  #a
  inc r10     ; increment register r10.
  mov r11,r12 ; store value of r12 into r11.
  #e)

这里有一些内置Lisp代码的汇编代码,失败(请参阅下面的编译错误)。在这一个中,Lisp代码在汇编代码之后,但是应该允许使用#a#l作为分隔符自由混合汇编和Lisp代码。

(defparameter *example-code-x64-with-lisp-fails*
  #a
  inc r10     ; increment register r10.
  mov r11,r12 ; store value of r12 into r11.
  #l
  (loop for current-instruction in (list "inc" "dec")
        do (loop for current-arg in (list "r13" "r14" "r15")
                 do (princ (concatenate 'string
                                        current-instruction
                                        " "
                                        current-arg
                                        (coerce (list #\Newline) 'string)))))
  #e)

上述代码的Lisp部分应该在自定义阅读器中进行评估,以便它应该产生与下面代码相同的结果:

(defparameter *example-code-x64-with-lisp-fails*
  #a
  inc r10     ; increment register r10.
  mov r11,r12 ; store value of r12 into r11.
  inc r13
  inc r14
  inc r15
  dec r13
  dec r14
  dec r15
  #e)

但是编译失败了:

CL-USER> ; compiling file "/home/user/code/lisp/lisp-asm-reader-for-stackoverflow.lisp" (written 28 MAR 2014 10:11:29 PM):
; 
; caught ERROR:
;   READ error during COMPILE-FILE:
;   
;     The value -1 is not of type (MOD 4611686018427387901).
;   
;     (in form starting at line: 1, column: 0, file-position: 0)
; 
; compilation unit aborted
;   caught 1 fatal ERROR condition
;   caught 1 ERROR condition
; compilation aborted after 0:00:00.004

1 compiler notes:

/home/user/code/lisp/lisp-asm-reader-for-stackoverflow.lisp:10487
  read-error: READ error during COMPILE-FILE:

  The value -1 is not of type (MOD 4611686018427387901).

  (in form starting at line: 1, column: 0, file-position: 0)

CL-USER>

1 个答案:

答案 0 :(得分:5)

从读取器宏中读取lisp代码的惯用方法是调用cl:read。在您的示例中,在使用#L之后调用read将返回其car为循环的列表,并且该列表可以传递给eval。

要收集在eval期间创建的输出,您可以绑定* standard-output *。因此,选择是在阅读器宏中使用类似于以下内容的内容:

(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表单{例如(连接" bar"" baz")},并收集eval的返回值而不是其打印输出。

相关问题