anko / eslisp

un-opinionated S-expression syntax and macro system for JavaScript
ISC License
528 stars 31 forks source link

Recursive macros #56

Closed vuolen closed 4 years ago

vuolen commented 4 years ago

I might be missing something, but a simple recursive macro call

(macro test (lambda () (test)))
(test)

leads to the error

/home/lennu/code/lci/node_modules/eslisp/lib/cli.js:103
      throw err;
      ^

ReferenceError: Error evaluating macro `test` (called at line 27, column 0): test is not defined
    at ctor$.eval (eval at <anonymous> (/home/lennu/code/lci/node_modules/eslisp/lib/built-in-macros.js:610:16), <anonymous>:2:5)
    at /home/lennu/code/lci/node_modules/eslisp/lib/compile.js:184:35
    at listToEstree (/home/lennu/code/lci/node_modules/eslisp/lib/compile.js:191:6)
    at astToEstree (/home/lennu/code/lci/node_modules/eslisp/lib/compile.js:250:25)
    at /home/lennu/code/lci/node_modules/eslisp/lib/translate.js:35:14
    at /home/lennu/code/lci/node_modules/prelude-ls/lib/List.js:158:21
    at /home/lennu/code/lci/node_modules/prelude-ls/lib/List.js:161:4
    at /home/lennu/code/lci/node_modules/prelude-ls/lib/List.js:665:42
    at module.exports (/home/lennu/code/lci/node_modules/eslisp/lib/translate.js:36:7)
    at toEstree (/home/lennu/code/lci/node_modules/eslisp/lib/index.js:9:10)

Calling arguments.callee works, however it is neither recommended or elegant. Is there any alternative?

anko commented 4 years ago

Compiling a macro that calls itself as part of its definition like that would always recurse infinitely: To compile the function that implements the macro test, the compiler would need to know what code (test) returns, so it would need to call the macro test to find out, so it would need to compile the function that implements the macro test, et cetera, recurring infinitely. This is why I made macros only be in scope strictly after their definition.

What you can do instead is have the macro return code that calls it:

(macro test
 (lambda (x)
  ; If we got an argument, return it.
  ; If we got NO argument, return a call to this macro, with a default.
  (if x (return x)
        (return '(test defaultArgument)))))

(test)
(test notTheDefault)

Output JS:

defaultArgument;
notTheDefault;

It works because the macro isn't called in its definition, but it instead returns code that calls it.


Or if the macro requires fancier recursive computation on its arguments, it might be easier to just define a recursive function inside the macro definition (like factorial here), and call that:

(macro !
 (lambda (thing)

  (var factorial
   (lambda (x)
    (if (== x 1)
     (return x)
     (return (* x (factorial (- x 1)))))))

  (return ((. this atom) (factorial ((. this evaluate) thing))))))

(! 5)
(! (+ 3 2))

Output JS:

120;
120;

Does that answer your question?

vuolen commented 4 years ago

Apologies for my simplified example. Of course it would cause an infinite loop if it worked, my intention was to just recreate the error message.

However, you managed to answer the actual question in hand. Thank you!