Как написать расширение макроса в пользовательском LISP в JavaScript - PullRequest
0 голосов
/ 13 мая 2019

У меня есть общий вопрос, как мне поступить и создать правильную функцию или макрос macroexpand.

Это определение моего макроса в интерпретаторе LIPS (вы можете проверить его здесь https://jcubic.github.io/lips/)

function macro_expand(single) {
    return async function(code, args) {
        var env = args['env'] = this;
        async function traverse(node) {
            if (node instanceof Pair && node.car instanceof Symbol) {
                try {
                    var value = env.get(node.car);
                    if (value instanceof Macro && value.defmacro) {
                        var result = await value.invoke(node.cdr, args, true);
                        if (result instanceof Pair) {
                            return result;
                        }
                    }
                } catch (e) {
                    // ignore variables
                }
            }
            var car = node.car;
            if (car instanceof Pair) {
                car = await traverse(car);
            }
            var cdr = node.cdr;
            if (cdr instanceof Pair) {
                cdr = await traverse(cdr);
            }
            var pair = new Pair(car, cdr);
            return pair;
        }
        var new_code = code;
        if (single) {
            return quote((await traverse(code)).car);
        } else {
            while (true) {
                new_code = await traverse(code);
                if (code.toString() === new_code.toString()) {
                    break;
                }
                code = new_code;
            }
            return quote(new_code.car);
        }
    };
}

Проблема в том, что это фиктивный макрос, расширяющий и игнорирующий ошибки переменных, поэтому он не может оценить квазицитатуру макроса, потому что он выдает исключение, что он не может найти переменные. Поэтому я заканчиваю квазицитой внутри моего расширенного списка(ПРИМЕЧАНИЕ: код в последней версии даже не пытается расширить квазицитату, потому что он помечен как нерасширяемый.)функция расширения?

Я тестировал, как biwascheme создает эту функцию, https://www.biwascheme.org/, но она также работает не совсем так, как я ожидаю, что эта функция будет работать:

это расширение:

biwascheme> (define-macro (foo name . body) `(let ((x ,(symbol->string name))) `(print ,x)))
biwascheme> (macroexpand '(foo bar))
=> ((lambda (x) (cons (quote print) (cons x (quote ())))) "bar")
biwascheme> 

Я ожидаю, что он расширится до:

(let ((x "bar")) (quasiquote (print (unquote x))))

Мой возврат LISP:

lips> (define-macro (foo name . body)
          `(let ((x ,(symbol->string name))) `(print ,x)))
;; macroexpand is a macro
lips> (macroexpand (foo bar))
(quasiquote (let ((x (unquote (symbol->string name))))
              (quasiquote (print (unquote x)))))

Даже когда я установил quasiquote как exВозможно, он не расширяет квазицитатуру, потому что не может найти имена, поэтому он генерирует исключение, которое игнорируется macroexpand.

Любой код, даже псевдокод, будет полезен при написании этой функции или макроса в моем LISP.

EDIT :

Я начал обновлять мой код для включения макроса раскрытия в функцию оценки и сделал одно изменение в макросе define-macro.Когда был вызван макрос расширения, он не вызывал код в первый раз, это была проблема.:

До:

var rest = __doc__ ? macro.cdr.cdr : macro.cdr;
if (macro_expand) {
    return rest.car;
}
var pair = rest.reduce(function(result, node) {
    return evaluate(node, { env, dynamic_scope, error });
});

после:

var rest = __doc__ ? macro.cdr.cdr : macro.cdr;
var pair = rest.reduce(function(result, node) {
    return evaluate(node, eval_args);
});
if (macro_expand) {
    return quote(pair);
}

И этотеперь работает без проблем, так что мой макрос expand_macro работает правильно, и вот как вы должны написать macro_expand.

EDIT2 : я дополнительно переработал код, и оказалось, что я нене нужно кодировать macro_exapnd внутри макроса define-macro и только заключать в кавычки пару (флаг удаления данных).

1 Ответ

0 голосов
/ 15 мая 2019

Вот игрушечный макроэкспандер, написанный на Racket, который имеет дело с макросами в стиле CL.В процессе написания этой статьи я использовал макросы Racket и другие средства, так что он сам по себе не был загружен.Очевидно, что такое можно сделать, но было бы гораздо сложнее сделать это.

Цель этого состоит исключительно в том, чтобы продемонстрировать, как может работать простой макроэкспандер: он ни в каком смысле не должен быть чем-топодходит для реального использования.

Специальные формы

Прежде всего нам нужно иметь дело со специальными формами.Специальные формы - это вещи, которые имеют магическую семантику.Этот расширитель имеет очень простое представление о том, как они работают:

  • специальная форма - составная форма, первый элемент которой является специальным оператором;
  • каждый элемент остальной части формыэто либо какая-то особая вещь, которая не раскрывается, либо расширяется нормально, что делается с помощью выражения expr в определении;
  • способ, которым это делается, с помощью довольно бессмысленного сопоставителя паттернов, которыйвероятно, достаточно только потому, что существует небольшое количество специальных форм, о которых экспандер знает.

Итак, вот как определяются специальные формы, и определение трех из них:

(define special-patterns (make-hasheqv))

(define (special-pattern? op)
  (and (symbol? op)
       (hash-has-key? special-patterns op)))

(define (special-pattern op)
  (hash-ref special-patterns op))

(define-syntax-rule (define-special-pattern (op spec ...))
  (hash-set! special-patterns 'op '(op spec ...)))

(define-special-pattern (quote thing))
(define-special-pattern (lambda args expr ...))
(define-special-pattern (define thing expr ...))
(define-special-pattern (set! thing expr))

Теперь мы можем спросить, является ли что-то особой формой (специальным шаблоном в коде) и извлечь ее шаблон:

> (special-pattern? 'lambda)
#t
> (special-pattern 'lambda)
'(lambda args expr ...)

Обратите внимание, что такие вещи, как if, не являются специальными операторами для макроэкспандера, дажехотя на самом деле они особенные: в такой форме, как (if test then else), все подчиненные формы должны быть расширены, поэтому у макроэкспандера нет причин знать о них.Это только такие вещи, как lambda, где некоторые субформы должны быть не расширены, о которых нужно знать макроэкспандеру.

Определения макросов

Макросы являются составными формами, первый элемент которыхраспознается как именование макроса.Для каждого такого макроса есть функция макроэкспандера, которая будет отвечать за расширение формы: этой функции передается вся форма.Существует немного синтаксиса, то есть define-macro, который оборачивает эту функцию аналогично тому, как defmacro делает в CL (но нет поддержки &whole, поддержки деструктуризации arglist или чего-либо еще).

(define macros (make-hasheqv))

(define (macro? op)
  (and (symbol? op)
       (hash-has-key? macros op)))

(define (macro op)
  (hash-ref macros op))

(define-syntax-rule (define-macro (m arg ... . tail) form ...)
  (hash-set! macros 'm (lambda (whole)
                         (apply (lambda (arg ... . tail) form ...)
                                (rest whole)))))

С помощью этого мы можем определить простой макрос: вот четыре определения для let.

Прежде всего, это самое элементарное: здесь даже не используется define-macro но это то, во что она превращается: внешняя функция получает всю форму, а затем вызывает внутреннюю часть, которая не является именем макроса.Затем внутренняя функция кропотливо превращает (let ((x y) ...) ...) в ((lambda (x ...) ...) y ...), что является правильным расширением для let.(Обратите внимание, что все это не относится к CL * (let (x) ...)).

(hash-set! macros 'let
           ;; this is what define-macro turns into
           (lambda (whole)
             (apply (lambda (bindings . body)
                      (cons (cons 'lambda
                                  (cons (map first bindings) body))
                            (map second bindings)))
                    (rest whole))))

Теперь вот что, но использование define-macro для уменьшения боли:

(define-macro (let bindings . body)
  ;; Really primitive version
  (cons (cons 'lambda (cons (map first bindings) body))
        (map second bindings)))

И другая версия с использованиемlist*, чтобы сделать вещи немного менее ужасными:

(define-macro (let bindings . body)
  ;; without backquote, but usung list* to make it a bit
  ;; less painful
  (list* (list* 'lambda (map first bindings) body)
         (map second bindings)))

И, наконец, версия, использующая обратную цитату ( aka квазицитат).

(define-macro (let bindings . body)
  ;; with backquote
  `((lambda ,(map first bindings) ,@body)
    ,@(map second bindings)))

Вот версиямакроопределения для prog1, которое нарушается из-за нарушения гигиены:

(define-macro (prog1 form . forms)
  ;; Broken
  `(let ([r ,form])
     ,@forms
     r))

И вот как вам нужно написать его, чтобы быть более гигиеничным (хотя он все еще негигиеничен по несколько экстремальным стандартам Схемы):

(define-macro (prog1 form . forms)
  ;; Working
  (let ([rn (string->uninterned-symbol "r")])
    `(let ([,rn ,form])
       ,@forms
       ,rn)))

Обратите внимание, что этот макрос превращается в другой макрос : он расширяется до let: расширитель должен иметь дело с этим (и он делает).

Макроэкспандер

Макроэкспандер состоит из двух функций: expand-macros - это то, что фактически выполняет расширение, и отправляется на expand-special для специальных форм.

Вот expand-macros:

(define (expand-macros form)
  ;; expanding a form
  (if (cons? form)
      ;; only compound forms are even considered
      (let ([op (first form)])
        (cond [(macro? op)
               ;; it's a macro: call the macro function & recurse on the result
               (expand-macros ((macro op) form))]
              [(special-pattern? op)
               ;; it's special: use the special expander
               (expand-special form)]
              [else
               ;; just expand every element.
               (map expand-macros form)]))
      form))

Примечания по этому поводу:

  • только соединениеформы могут быть макро-формами;
  • это lisp-1, поэтому машины составных форм оцениваются полностью нормально и могут быть макро-формами: ((let (...) ...) ...) отлично;
  • макросы расширеныдо тех пор, пока не останется ничего сделать.

Вот expand-special: это намного сложнее, чем expand-macro и, вероятно, глючит: он пытается сопоставить определение специальной формы с формой, которую ей дали.

(define (expand-special form)
  ;; expand a special thing based on a pattern.
  (match-let* ([(cons op body) form]
               [(cons pop pbody) (special-pattern op)])
    (unless (eqv? op pop)
      (error 'expand-special "~s is not ~s" pop op))
    (let pattern-loop ([accum (list op)]
                       [tail body]
                       [ptail pbody]
                       [context 'expr])
      (cond [(null? tail)
             (unless (or (null? ptail)
                         (eqv? (first ptail) '...))
               (error 'expand-special "~s is not enough forms for ~s"
                      body op))
             (reverse accum)]
            [(null? ptail)
             (error 'expand-special "~s is too many forms for ~s"
                    body op)]
            [else
             (match-let* ([(cons btf btr) tail]
                          [(cons ptf ptr) ptail]
                          [ellipsis? (eqv? ptf '...)]
                          [ctx (if ellipsis? context ptf)]
                          [ptt (if ellipsis? ptail ptr)])
               (pattern-loop (cons (if (eqv? ctx 'expr)
                                       (expand-macros btf)
                                       btf)
                                   accum)
                             btr ptt ctx))]))))

Неприятный бит здесь - это обработка многоточия (...), которое используется в сопоставителе для обозначения «здесь больше вещей»: я не могу вспомнить, может ли оно иметь дело с многоточием, которое не является последним в шаблон, но я подозреваю, что сильно нет. Обратите внимание, что хотя базовая система макросов также использует многоточие, они не связаны: это просто полагается на то, что ... является допустимым именем символа.

Обратите внимание, что это возвращается в expand-macros, где это необходимо, конечно.

С учетом этих определений мы теперь можем расширить некоторые макросы:

> (expand-macros '(let ((x y)) x))
'((lambda (x) x) y)
> (expand-macros '(prog1 a b))
'((lambda (r) b r) a)

Обратите внимание, что принтер Racket не печатает специально, а r выше.

С помощью простой утилиты трассировки вы можете определить отслеживаемую версию макроэкспандера:

> (expand-macros '(let ([x 1]) (prog1 x (display "1"))))
[expand-macros (let ((x 1)) (prog1 x (display "1")))
 [expand-macros ((lambda (x) (prog1 x (display "1"))) 1)
  [expand-macros (lambda (x) (prog1 x (display "1")))
   [expand-special (lambda (x) (prog1 x (display "1")))
    [expand-macros (prog1 x (display "1"))
     [expand-macros (let ((r x)) (display "1") r)
      [expand-macros ((lambda (r) (display "1") r) x)
       [expand-macros (lambda (r) (display "1") r)
        [expand-special (lambda (r) (display "1") r)
         [expand-macros (display "1")
          [expand-macros display
           -> display]
          [expand-macros "1"
           -> "1"]
          -> (display "1")]
         [expand-macros r
          -> r]
         -> (lambda (r) (display "1") r)]
        -> (lambda (r) (display "1") r)]
       [expand-macros x
        -> x]
       -> ((lambda (r) (display "1") r) x)]
      -> ((lambda (r) (display "1") r) x)]
     -> ((lambda (r) (display "1") r) x)]
    -> (lambda (x) ((lambda (r) (display "1") r) x))]
   -> (lambda (x) ((lambda (r) (display "1") r) x))]
  [expand-macros 1
   -> 1]
  -> ((lambda (x) ((lambda (r) (display "1") r) x)) 1)]
 -> ((lambda (x) ((lambda (r) (display "1") r) x)) 1)]
'((lambda (x) ((lambda (r) (display "1") r) x)) 1)

Доступна версия этого кода здесь .

...