LIPS-scheme / lips

Scheme based powerful lisp interpreter in JavaScript
https://lips.js.org
Other
416 stars 34 forks source link

Hygienic macro syntax-rules #43

Open jcubic opened 4 years ago

jcubic commented 4 years ago

Hygienic macro syntax-rules

List of issues

jcubic commented 4 years ago

This macro don't work:

(define-syntax for
  (syntax-rules (in as)
    ((for element in list body ...)
     (map (lambda (element)
            body ...)
          list))
    ((for list as element body ...)
     (for element in list body ...))))

(for '(0 1 2 3 4) as i
     (print i))

Because identifiers are not used to match the pattern. The code try to match first pattern so element is a list.

jcubic commented 4 years ago

for macro is working.

jcubic commented 4 years ago

Example that don't work (taken from SRFI 46)

(let-syntax
    ((f (syntax-rules ()
          ((f ?e)
           (let-syntax
               ((g (syntax-rules ::: ()
                     ((g (??x ?e) (??y :::))
                      '((??x) ?e (??y) :::)))))
             (g (1 2) (3 4)))))))
  (f :::))

it should return ((1) 2 (3) (4)) but it return ((#:??x) 2 (3) (4))

jcubic commented 3 years ago

SRFI 46 example works.

jcubic commented 3 years ago

Two examples that should work:

(define-syntax foo (syntax-rules () ((_ x ...) #(x ...))))
(define-syntax foo (syntax-rules () ((_ #(x y ...)) x)))

There is need to add vector support to syntax-rules.

jcubic commented 3 years ago

Another use case nested ellipsis from practical-scheme.net.

(define-syntax ellipsis-test
  (syntax-rules ()
    [(_ (a (b c ...) ...) ...)
     '((a ...)
       (((a b) ...) ...)
       ((((a b c) ...) ...) ...))]))

(ellipsis-test (1 (2 3 4) (5 6)) (7 (8 9 10 11)))
;; ⇒ ((1 7)
;;    (((1 2) (1 5)) ((7 8)))
;;    ((((1 2 3) (1 2 4)) ((1 5 6))) (((7 8 9) (7 8 10) (7 8 11)))))

NOTE: this is not part of R7RS, can be implemented later.

jcubic commented 3 years ago

While debugging SRFI-197 macro, found another example that doesn't work:

(define-syntax foo
  (syntax-rules ()
    ((_)
     (let ()
       (define-syntax %foo
         (syntax-rules (foo bar)
           ((_ (foo))
            (display "foo"))
           ((_ x)
            (display 'x))))
       (%foo (10))))))

(foo)

should print (10). The problem here are collisions of _ ==> foo in outer syntax that maps into nested _ which is also the name of the identifier. Changing the identifier from foo to foo_ or nested _ to __ solves the issue.

This one case can be fixed by not matching the first expression in the pattern to identifiers.

Also this:

(define-syntax foo
  (syntax-rules ()
    ((_)
     (let ()
       (define-syntax %foo
         (syntax-rules (foo bar)
           ((__ (foo))
            (print '(foo)))
           ((__ x)
            (print 'x))))
       (%foo (foo)))))

prints: (#:foo) instead of (foo).

jcubic commented 8 months ago

The problem with SRFI-210 is not about hygiene:

This works fine:

(define-syntax foo
  (syntax-rules ()
    ((_ () ((operand1 arg1) ...))
     (let ((arg1 operand1) ...)
       (list arg1 ...)))
    ((_ (operand1 operand2 ...) (temp ...))
     (foo (operand2 ...) (temp ... (operand1 arg1))))))

(pprint (macroexpand (foo (10 20) ())))
;; (#:let ((#:arg1 10)
;;         (#:arg1 20))
;;      (#:list #:arg1 #:arg1))
(print (foo (10 20) ()))
;; => (10 20)
jcubic commented 8 months ago

The problem was that identifiers in syntax-rules were checking the scope if they were not shadowed, but this was wrong. So the code was removed and the unit test updated.

jcubic commented 8 months ago

Another problem found from SRFI-210 is this macro:

(define-syntax foo
  (syntax-rules ()
    ((_ (arg more ...))
     (letrec-syntax ((aux (syntax-rules ::: ()
                            ((aux () ((operand1 arg1) :::))
                             (let ((arg1 operand1) :::)
                               (list arg1 :::)))
                            ((aux (operand1 operand2 :::) (temp :::))
                             (aux (operand2 :::) (temp ::: (operand1 arg1)))))))
       (aux (arg more ...) ())))))

(print (foo (10 20)))
;; ==> (20 20)

Same code with single a syntax-rules works fine.

jcubic commented 8 months ago

This is the limitation of renaming syntax-rules, I don't think I can fix this. Inside nested syntax rules everything got renamed including nested syntax-rules so arg1 is renamed before the macro is expended and it got one value.

The whole syntax-rules need to be refactored into a proper scope based system.