git » jacl.git » commit ac8086b

Added &BODY to macro lambda lists

author Alan Dipert
2020-06-23 15:39:31 UTC
committer Alan Dipert
2020-06-23 15:39:31 UTC
parent 008f09bb356456330bcb028bba5bf6b232ba2272

Added &BODY to macro lambda lists

boot.lisp +4 -4
jacl-tests.lisp +2 -2
jacl.js +43 -19

diff --git a/boot.lisp b/boot.lisp
index c18febf..a971d1e 100644
--- a/boot.lisp
+++ b/boot.lisp
@@ -27,14 +27,14 @@
 (\. *package* (|exportSymbol| (\. 'defmacro |name|)))
 (%js "(~{}.setMacro().fvalue = ~{})"
      'defmacro
-     (%lambda nil (env form name params &rest body)
+     (%lambda nil t (env form name params &rest body)
        `(jacl:%progn
           (jacl:%js "(~{}.setMacro().fvalue = ~{})"
             ',name
             ;; TODO Macro lambda list parsing. Also, consider
             ;; incorportating a symbol's package into the name of its
             ;; generated local.
-            (jacl:%lambda nil (jacl::&environment jacl::&whole ,@params)
+            (jacl:%lambda nil t (jacl::&environment jacl::&whole ,@params)
               ,@body))
           ',name)))
 
@@ -43,7 +43,7 @@
   `(jacl:%progn
      (jacl:%js "(~{}.fvalue = ~{})"
        ',name
-       (jacl:%lambda ,name ,params ,@body))
+       (jacl:%lambda ,name nil ,params ,@body))
      ',name))
 
 (defun %export (symbol &optional (package *package*))
@@ -289,7 +289,7 @@
 
 (%export 'lambda)
 (defmacro lambda (params &rest body)
-  `(jacl:%lambda nil ,params ,@body))
+  `(jacl:%lambda nil nil ,params ,@body))
 
 (%export 'list)
 (defun list (&rest objects)
diff --git a/jacl-tests.lisp b/jacl-tests.lisp
index df8425c..0c38608 100644
--- a/jacl-tests.lisp
+++ b/jacl-tests.lisp
@@ -174,7 +174,7 @@
 
 (deftest "Named JACL:%LAMBDA"
   (let* ((invocations 0)
-         (f (jacl:%lambda self (x y)
+         (f (jacl:%lambda self nil (x y)
                           (let ((self nil))
                             (when (< x y)
                               (setq invocations (1+ invocations))
@@ -201,7 +201,7 @@
 ;;          )
 ;;        )))
 
-(defmacro time (memo &rest body)
+(defmacro time (memo &body body)
   (let ((start (gensym))
         (ret (gensym)))
     `(let ((,start (progn
diff --git a/jacl.js b/jacl.js
index 292db18..b37edc3 100644
--- a/jacl.js
+++ b/jacl.js
@@ -1061,7 +1061,15 @@ const parseLambdaList = (list, isMacro) => {
       // :allow-other-keys t etc
       keyAllowOthers: false,
       // Array of [symbol, expr] for [name, init]
-      aux: []
+      aux: [],
+
+      // Macro lambda list only
+      // null or symbol
+      body: null,
+      // null or symbol
+      environment: null,
+      // null or symbol
+      whole: null
     },
     state = 'required';
 
@@ -1074,6 +1082,12 @@ const parseLambdaList = (list, isMacro) => {
             || eqClSym(x, '&REST')
             || eqClSym(x, '&AUX')) {
           state = x.name.substring(1).toLowerCase();
+        } else if (eqClSym(x, '&BODY')) {
+          if (isMacro) {
+            state = x.name.substring(1).toLowerCase();
+          } else {
+            throw new Error(`&BODY may only appear in a macro lambda list`)
+          }
         } else if (eqClSym(x, '&ALLOW-OTHER-KEYS')) {
           throw new Error(`Misplaced ${x.name}`);
         } else if (x instanceof LispSymbol) {
@@ -1115,29 +1129,31 @@ const parseLambdaList = (list, isMacro) => {
           throw new Error(`&OPTIONAL parameter not symbol or valid list`);
         }
         break;
+      case 'body':
       case 'rest':
-        if (sections.rest)
-          throw new Error(`Repeated &REST`);
+        if (sections.body || sections.rest)
+          throw new Error(`Too many &REST/&BODY`);
         if (eqClSym(x, '&ALLOW-OTHER-KEYS'))
-          throw new Error(`Expected variable after &REST, got ${x.name}`);
+          throw new Error(`Expected variable after &${state.toUpperCase()}, got ${x.name}`);
         if (!(x instanceof LispSymbol))
-          throw new Error(`&REST parameter not a symbol`);
-        sections.rest = x;
-        state = 'after-rest';
+          throw new Error(`&${state.toUpperCase()} parameter not a symbol`);
+        sections[state] = x;
+        state = 'after-${state}';
         break;
+      case 'after-body':
       case 'after-rest':
-        if (eqClSym(x, '&OPTIONAL')
+        if (eqClSym(x, '&REST') || eqClSym(x, '&BODY')) {
+          throw new Error(`Too many &REST/&BODY`)
+        } else if (eqClSym(x, '&OPTIONAL')
             || eqClSym(x, '&KEY')
             || eqClSym(x, '&AUX')) {
           state = x.name.substring(1).toLowerCase();
           if (sections[state].length)
             throw new Error(`Duplicate ${x.name}`);
         } else if (eqClSym(x, '&ALLOW-OTHER-KEYS')) {
-          throw new Error(`Misplaced ${x.name} after &REST`);
-        } else if (eqClSym(x, '&REST')) {
-          throw new Error(`Repeated &REST`);
+          throw new Error(`Misplaced ${x.name} after &${state.split("-")[1].toUpperCase()}`);
         } else {
-          throw new Error(`Expected keyword after &REST param`);
+          throw new Error(`Expected keyword after &${state.split("-")[1].toUpperCase()} param`);
         }
         break;
       case 'key':
@@ -1148,7 +1164,7 @@ const parseLambdaList = (list, isMacro) => {
           if (sections.keyAllowOther)
             throw new Error(`Duplicate &ALLOW-OTHER-KEYS`);
           sections.keyAllowOthers = true;
-        } else if (eqClSym(x, '&OPTIONAL') || eqClSym(x, '&REST')) {
+        } else if (eqClSym(x, '&OPTIONAL') || eqClSym(x, '&REST') || eqClSym(x, '&BODY')) {
           throw new Error(`Misplaced ${x.name}`);
         } else if (eqClSym(x, '&AUX')) {
           state = 'aux';
@@ -1185,7 +1201,8 @@ const parseLambdaList = (list, isMacro) => {
           throw new Error(`Repeated &AUX`);
         } else if (eqClSym(x, '&OPTIONAL')
             || eqClSym(x, '&KEY')
-            || eqClSym(x, '&REST')) {
+            || eqClSym(x, '&REST')
+            || eqClSym(x, '&BODY')) {
           throw new Error(`Misplaced ${x.name}`);
         } else if (x instanceof LispSymbol) {
           sections.aux.push({ name: checkValidLocal(x), initform: UNDEFINED });
@@ -1221,6 +1238,8 @@ const analyzeLambdaList = (env, parent, list, isMacro) => {
   }
   if (sections.rest)
     env = env.withLocals([sections.rest])
+  if (sections.body)
+    env = env.withLocals([sections.body])
   for (const spec of sections.key) {
     if (spec.initform !== UNDEFINED) {
       spec.initform = analyze(env, parent, spec.initform);
@@ -1283,7 +1302,7 @@ const analyzeSpecials = new Map([
     return node;
   }],
   [JACLPKG.intern('%LAMBDA'), (env, parent, form) => {
-    let [, name, list, ...body] = form;
+    let [, name, isMacro, list, ...body] = form;
     let declarations = [];
     if (body.length > 0
         && List.isProperList(body[0])
@@ -1297,10 +1316,11 @@ const analyzeSpecials = new Map([
       parent: parent,
       form: form,
       name: name,
-      declarations: declarations
+      declarations: declarations,
+      isMacro: isMacro
     });
     const { lambdaList, initforms, bodyEnv } = analyzeLambdaList(
-      env.withContext('expr'), node, list, false
+      env.withContext('expr'), node, list, isMacro
     );
     node.lambdaList = lambdaList;
     node = merge(node, analyzeBlock(bodyEnv, node, body));
@@ -1906,7 +1926,7 @@ const emitNode = (print, node) => {
       print('){\n');
       // Emit argument length checks
       const min = node.lambdaList.required.length,
-        hasRest = node.lambdaList.rest || node.lambdaList.key.length,
+        hasRest = node.lambdaList.rest || node.lambdaList.body || node.lambdaList.key.length,
         max = hasRest ? false : min + node.lambdaList.optional.length;
       if (min >= 0 && min === max) {
         print(`if (arguments.length !== ${min}) throw new Error('Called with invalid number of arguments: ' + arguments.length);\n`);
@@ -1951,11 +1971,15 @@ const emitNode = (print, node) => {
         print('break;\n');
         print('}\n');
       }
+      // &rest
       const restStart = node.lambdaList.required.length
         + node.lambdaList.optional.length;
-      // &rest
       if (node.lambdaList.rest) {
         print(mungeSym(node.lambdaList.rest, 'local'));
+      } else if (node.lambdaList.body) {
+        print(mungeSym(node.lambdaList.body, 'local'));
+      }
+      if (node.lambdaList.rest || node.lambdaList.body) {
         print('=');
         if (node.declarations.find(x => List.first(x) === JACLPKG.intern('REST-ARRAY'))) {
           print(`Array.prototype.slice.call(arguments, ${restStart});\n`);