Как оценить код Лисп внутри макроса читателя?
Я пишу свой собственный ассемблер x86-64 в Common Lisp, и он производит правильный двоичный код для подмножества x86-64. Я использую пользовательский макрос для чтения, чтобы преобразовать ассемблерный код в синтаксическое дерево, и он работает как положено.
Я пытаюсь разрешить использование кода на Lisp внутри ассемблерного кода, чтобы я мог использовать Lisp в качестве макроязыка для моего ассемблера. я использую #a
в качестве символа макроса и #e
сигнализировать конец для читателя. Читатель внутри #l
переключается в режим Lisp и #a
вернуться в режим сборки, #e
(сигнализировать о завершении для читателя макроса) должен работать в обоих режимах.
Чего я не понимаю, так это как вывести результаты вычисленного кода обратно во входной поток (для обработки до остальной части кода) или как иначе получить выходные данные кода на Лиспе для повторного чтения, чтобы вывод Лисп-кода (это будет ассемблерный код) может быть обработан соответствующим образом (так же, как и остальная часть ассемблерного кода). Как я могу достичь этой цели?
Заметка: это мой первый читательский макрос, поэтому могут быть недостатки дизайна. Я думаю, что мой подход к чтению кода Lisp в строку не обязательно является лучшим, если есть какой-то более короткий и более идиоматический способ сделать это.
Вот упрощенная версия моего макроса читателя:
(eval-when (: compile-toplevel: load-toplevel: execute) (defun get-last-character-string (my-string) "Эта функция возвращает строку, состоящую из последнего символа входной строки." (subseq my-string (1- (длина my-string))))) (defun get-string-Without-Last-Symbol (my-string) Msgstr "Эта функция возвращает строку без последнего символа входной строки." (subseq my-string 0 (1- (длина my-string)))) (defun получить строку-без-недопустимый-последний-символ (моя-строка неверно-последний-символы) "Если последний символ строки недействителен, строка возвращается без него, в противном случае полностью". (цикл для неверного последнего символа в неверных последних символах do (if (равно (get-last-symbol-string-my-string) неверно-последний-символ) (setf my-string (получить-строку-без-последнего символа my-string)))) моя строка) (defun transform-code-to-string (stream sub-char numarg) Msgstr "Эта функция преобразует ассемблерный код в строку. #l пометки меняются на код Lisp. #a отметки возвращаются в asm. #e отмечает конец. Частично основано на: http://weitz.de/macros.lisp" (объявить (игнорировать sub-char numarg)) (позволять* ((invalid-last-символы (список "'" " " "(" ")")) (текущий режим "asm") (есть-там-код-на-этой строке ноль) (текущая фаза "начало строки") (моя строка "(список") (lisp-code-string ""));; цикл через поток. (цикл для строки my-char = (coerce (list (read-char stream t nil t)) ') делать ((равный ток-режим "asm") (конд ((равная текущая фаза "хэш-знак-чтение");; это персонаж е?;; если да, то все готово, исправьте закрывающие скобки и вернитесь. (конд ((равно my-char "e") (возврат из кода преобразования в строку (объединить 'строку (получить-строку-без-недействительный-последний-символ (Получить-строку-без недопустимого-последнего-символа my-string invalid-last-символы) invalid-last-символы) "))")));; это персонаж л?;; если да, перейдите в режим Lisp. ((равно my-char "l");; может ли Лисп код можно прочитать и оценить здесь;; не читая это в строку? (progn (установить текущий режим "Лисп") (setf is-there-code-on-this-line nil) (setf lisp-code-string "") (установить текущую фазу "начало строки")));; в противном случае ошибка печати. (t (ошибка "в режиме asm неопределенный управляющий символ после #"))));; персонаж №?;; если да, отметьте прочитанный хеш-знак. ((равно my-char "#") (установить текущую фазу "хэш-знак-чтение");; символ новой строки? ((равно my-char (coerce (list #\Newline) 'строка)) (progn (конд;; есть ли _no_ код в этой строке?;; если это правда, ничего не выводить. ((не есть-там-код-на-этой строке) (установить текущую фазу "начало строки");; мы внутри инструкции или внутри параметра?;; если true, выведите ") ((или (равный ток-фаза "внутренняя инструкция") (равный ток-фаза "внутренние параметры")) (progn (установить текущую фазу "начало строки") (setf is-there-code-on-this-line nil) (setf my-string (объединить 'string my-string "\")"))));; иначе выводится) (т (прогноз (установить текущую фазу "начало строки") (setf is-there-code-on-this-line nil) (setf my-string (объединить 'string my-string ")")))))));; мы внутри комментария?;; если да, ничего не выводить. ((равно текущая фаза "внутренний комментарий") ноль);; мы в начале строки? ((равный ток-фаза "начало линии") (конд;; это пробел в начале строки?;; если да, ничего не выводить. ((равно my-char "") ноль);; это первый символ инструкции, а не (или)?;; если да, отметьте код в этой строке, отметьте первый символ как напечатанный, вывод "и текущий символ. ((а также (нет (равно my-char "(")) (нет (равно my-char ")"))) (progn (установить текущую фазу "внутренняя инструкция") (setf is-there-code-on-this-line t) (setf my-string (объединить 'string my-string "' (\"" my-char)))) (т ноль)));; это характер;?;; если да, ничего не выводить, начните комментировать. ((равно my-char ";") (setf current-phase "inside-comment"));; такое пространство символов или запятая? ((или (равно my-char "") (равно my-char ",")) (конд;; пробел или запятая, а последний символ был _не_ пробел, запятая или открывающая скобка?;; если да, выведите "и пробел". ((а также (нет (равно (get-last-character-string my-string) "")) (нет (равно (get-last-character-string my-string) ",")) (нет (равно (get-last-character-string my-string) "("))) (progn (установить текущую фазу "в космосе") (setf my-string (объединить 'string my-string "\" ")))) (т ноль)));; напечатана инструкция и это 1-й символ параметра? ((а также (нет (равно ток-фаза "внутренняя инструкция")) (или (равно (get-last-character-string my-string) "") (равно (get-last-character-string my-string) ","))) (конд;; отметьте, что мы внутри параметров, вывода "и текущий символ. (т (прогноз (установить текущую фазу "внутренние параметры") (setf my-string (объединить 'string my-string "\"" my-char))))));; в противном случае выведите символ. (t (setf my-string (объединить 'строку my-string my-char))))) ((равен текущему режиму "Лисп");; в режиме Lisp читайте текст, пока не достигнете #e или #a, и оцените его. (конд ((равная текущая фаза "хэш-знак-чтение") (конд;; это персонаж е?;; если да, то все готово, исправьте закрывающие скобки и вернитесь. ((равно my-char "e") (progn (объединить 'string "#a" (eval lisp-code-string) "#e"); это должно быть что-то другое. (возврат из кода преобразования в строку (объединить 'строку (получить-строку-без-недействительный-последний-символ (Получить-строку-без недопустимого-последнего-символа my-string invalid-last-символы) недопустимые последние символы) "))"))));; это персонаж?;; если да, перейдите в режим asm. ((равно my-char "a") (progn (setf current-mode "asm") (setf is-there-code-on-this-line nil) (установить текущую фазу "начало строки") (объединить 'string "#a" (eval lisp-code-string) "#e"); это должно быть что-то другое.;; в противном случае добавьте # и символ в код Lisp для оценки. (т (прогноз (setf current-phase "") (setf my-string (объединить 'строка lisp-code-string "#" my-char))))));; персонаж №?;; если да, отметьте прочитанный хеш-знак. ((равно my-char "#") (установить текущую фазу "хэш-знак-чтение");; в противном случае добавьте символ в код Lisp для оценки. (t (setf my-string (объединить 'строку lisp-code-string my-char))))) (t (ошибка "Недопустимый текущий режим")))))) ;;; #a - это ввод, который запускает пользовательский ридер. (set-dispatch-macro-Character #\# #\a #'transform-code-to-string))
Вот пример кода сборки без кода Lisp внутри, работает:
(defparameter * example-code-x64 * #a inc r10; инкрементный регистр r10. MOV R11, R12; сохранить значение r12 в r11. #e)
А вот некоторый ассемблерный код с Лисп-кодом внутри, терпит неудачу (см. Ошибку компиляции ниже). В этом коде Лиспе после кода ассемблера, но сборку и код Лиспа должно быть разрешено свободно смешивать с помощью #a
а также #l
в качестве разделителей.
(defparameter * example-code-x64-with-lisp-fails * #a inc r10; инкрементный регистр r10. MOV R11, R12; сохранить значение r12 в r11. #l (цикл для текущей инструкции в (список "inc" "dec") do (цикл для текущего аргумента в (список "r13" "r14" "r15") do (princ (concateate 'string) ток-инструкция "" ток-Arg (принудительно (list # \ Newline) 'строка))))) #e)
Часть Lisp вышеприведенного кода должна быть оценена в специальном считывателе, чтобы он давал результаты, идентичные приведенному ниже:
(defparameter * example-code-x64-with-lisp-fails * #a inc r10; инкрементный регистр r10. MOV R11, R12; сохранить значение r12 в r11. inc r13 inc r14 inc r15 декабрь r13 декабрь r14 декабрь 15 #e)
Но вместо этого компиляция не удалась:
CL-USER>; файл компиляции "/home/user/code/lisp/lisp-asm-reader-for-stackru.lisp" (записано 28 марта 2014 г. в 10:11:29):;; поймал ОШИБКУ:; Ошибка чтения во время COMPILE-FILE:;; Значение -1 не относится к типу (MOD 4611686018427387901).;; (в форме, начинающейся со строки: 1, столбец: 0, позиция файла: 0);; блок компиляции прерван; поймал 1 смертельное состояние ОШИБКА; поймал 1 ОШИБКА состояние; сборник прерван после 0: 00: 00.004 1 примечание компилятора: /home/user/code/lisp/lisp-asm-reader-for-stackru.lisp:10487 ошибка чтения: ошибка чтения во время COMPILE-FILE: Значение -1 не относится к типу (MOD 4611686018427387901). (в форме, начинающейся со строки: 1, столбец: 0, позиция файла: 0) CL-USER>
1 ответ
Идиоматический способ чтения кода lisp из макроса читателя - вызвать cl:read. В вашем примере вызов read после использования #L вернет список, чья машина является loop, и этот список может быть передан в 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, которая возвращает строку {например (concatenate "bar" "baz")}, и собирает возвращаемое значение eval вместо его печатного вывода.