git » jacl.git » commit 296de10

&key progress, still not error on unknown key

author Alan Dipert
2019-11-04 05:41:17 UTC
committer Alan Dipert
2019-11-04 05:41:17 UTC
parent 88007b81682bc209e3131047a4b168210086c8c5

&key progress, still not error on unknown key

jacl.js +64 -19

diff --git a/jacl.js b/jacl.js
index edc1a45..d6c7f2b 100644
--- a/jacl.js
+++ b/jacl.js
@@ -888,8 +888,10 @@ const parseLambdaList = list => {
       optionalSvars: [],
       // null or symbol
       rest: null,
-      // Map of keyword => [symbol, expr, symbol] for key => [name, init, svar]
-      key: new Map(),
+      // Array of [symbol, expr, symbol] for [key, name, init, svar]
+      key: [],
+      // Array of &key svar names (symbols)
+      keySvars: [],
       // Array of [symbol, expr] for [name, init]
       aux: []
     },
@@ -972,23 +974,28 @@ const parseLambdaList = list => {
           state = 'aux';
         } else if (x instanceof LispSymbol) {
           const key = LispSymbol.intern('KEYWORD', x.name);
-          if (sections.key.has(key))
-            throw new Error(`Duplicate key: ${key.name}`);
-          sections.key.set(key, {
+          sections.key.push({
+            key: key,
             name: checkValidLocal(x),
             initform: UNDEFINED,
             svar: UNDEFINED
           });
         } else if (Cons.isProperList(x)) {
-          throw new Error(`TODO`);
-          //const len = Cons.length(x);
-          //if (len === 0 || len > 3) 
-          //  throw new Error(`&KEY parameter list wrong size`);
-          //sections.key.push({
-          //  name: checkValidLocal(x.car),
-          //  initform: len > 1 ? Cons.nth(x, 1) : UNDEFINED,
-          //  svar: len === 3 ? checkValidLocal(Cons.nth(x, 2)) : UNDEFINED
-          //});
+          const len = Cons.length(x);
+          if (len === 0 || len > 3) 
+            throw new Error(`&KEY parameter list wrong size`);
+          const key = LispSymbol.intern('KEYWORD', x.car.name);
+          let svar = UNDEFINED;
+          if (len === 3) {
+            svar = checkValidLocal(Cons.nth(x, 2));
+            sections.keySvars.push(svar);
+          }
+          sections.key.push({
+            key: key,
+            name: checkValidLocal(x.car),
+            initform: len > 1 ? Cons.nth(x, 1) : UNDEFINED,
+            svar: svar
+          });
         } else {
           throw new Error(`&KEY parameter not symbol or valid list`);
         }
@@ -1452,7 +1459,8 @@ const emitNode = (print, node) => {
       print('){\n');
       // Emit argument length checks
       const min = node.lambdaList.required.length,
-        max = node.lambdaList.rest ? false : min + node.lambdaList.optional.length;
+        hasRest = node.lambdaList.rest || 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`);
       } else {
@@ -1463,9 +1471,8 @@ const emitNode = (print, node) => {
           print(`if (arguments.length > ${max}) throw new Error('Called with too many arguments: ' + arguments.length);\n`);
         }
       }
-      // &optional stuff
+      // &optional
       if (node.lambdaList.optional.length) {
-        // Speculatively bind &optional svars to true
         if (node.lambdaList.optionalSvars.length) {
           print('var ');
           for (let i = 0; i < node.lambdaList.optionalSvars.length; i++) {
@@ -1510,8 +1517,46 @@ const emitNode = (print, node) => {
         print(`Cons.fromArray(Array.prototype.slice.call(arguments, ${restStart}));\n`);
       }
       // &key
-      if (node.lambdaList.key) {
-        // TODO
+      if (node.lambdaList.key.length) {
+        if (node.lambdaList.keySvars.length) {
+          print('var ');
+          for (let i = 0; i < node.lambdaList.keySvars.length; i++) {
+            print(mungeSym(node.lambdaList.keySvars[i], 'local'));
+            print('=true');
+            if (i < node.lambdaList.keySvars.length-1) print(',');
+          }
+          print(';\n');
+        }
+        print('var ');
+        for (let i = 0; i < node.lambdaList.key.length; i++) {
+          print(mungeSym(node.lambdaList.key[i].name, 'local'));
+          print('=null')
+          if (i < node.lambdaList.key.length-1) print(',');
+        }
+        print(';\n');
+        print(`if((arguments.length-${restStart})%2)throw new Error('Odd number of &key arguments');\n`);
+        print('var keyVals = {};\n');
+        print(`for(var i=${restStart}; i<arguments.length; i+=2){\n`);
+        print(`if (!(arguments[i] instanceof LispSymbol) || arguments[i].packageName!=='KEYWORD')throw new Error('Not a keyword: '+arguments[i]);\n`);
+        print('keyVals[arguments[i].name]=arguments[i+1];\n');
+        print('}\n');
+        for (const kspec of node.lambdaList.key) {
+          print(`if(keyVals.hasOwnProperty('${escapeSingle(kspec.key.name)}')){\n`);
+          print(mungeSym(kspec.name, 'local'));
+          print(`=keyVals['${escapeSingle(kspec.key.name)}'];\n`);
+          if (kspec.initform !== UNDEFINED) {
+            print('}else{\n');
+            print(mungeSym(kspec.name, 'local'));
+            print('=');
+            emitNode(print, kspec.initform);
+            print(';\n');
+            if (kspec.svar !== UNDEFINED) {
+              print(mungeSym(kspec.svar, 'local'));
+              print('=false;\n');
+            }
+          }
+          print('}\n');
+        }
       }
       emitBlock(print, node.statements, node.ret);
       print('})');