git » jacl.git » commit 50bf8f9

&key is... done? also &allow-other-keys

author Alan Dipert
2019-11-04 06:36:48 UTC
committer Alan Dipert
2019-11-04 06:36:48 UTC
parent 296de103afd0d6f427ec3a58dd5cb63188591782

&key is... done? also &allow-other-keys

jacl.js +34 -4

diff --git a/jacl.js b/jacl.js
index d6c7f2b..220236b 100644
--- a/jacl.js
+++ b/jacl.js
@@ -335,7 +335,7 @@ for (const [k,v] of CLFUNCS) {
 }
 
 // Lambda list keywords
-for (const kw of ['&OPTIONAL', '&REST', '&KEY', '&AUX']) {
+for (const kw of ['&OPTIONAL', '&REST', '&KEY', '&ALLOW-OTHER-KEYS', '&AUX']) {
   CLPKG.intern(kw);
   CLPKG.exportSymbol(kw);
 }
@@ -892,6 +892,9 @@ const parseLambdaList = list => {
       key: [],
       // Array of &key svar names (symbols)
       keySvars: [],
+      // Bool, whether other keys allowed (can still be set at runtime by passing
+      // :allow-other-keys t etc
+      keyAllowOthers: false,
       // Array of [symbol, expr] for [name, init]
       aux: []
     },
@@ -906,6 +909,8 @@ const parseLambdaList = list => {
             || eqClSym(x, '&REST')
             || eqClSym(x, '&AUX')) {
           state = x.name.substring(1).toLowerCase();
+        } else if (eqClSym(x, '&ALLOW-OTHER-KEYS')) {
+          throw new Error(`Misplaced ${x.name}`);
         } else if (x instanceof LispSymbol) {
           sections.required.push(checkValidLocal(x));
         } else {
@@ -919,6 +924,8 @@ const parseLambdaList = list => {
                    || eqClSym(x, '&REST')
                    || eqClSym(x, '&AUX')) {
           state = x.name.substring(1).toLowerCase();
+        } else if (eqClSym(x, '&ALLOW-OTHER-KEYS')) {
+          throw new Error(`Misplaced ${x.name} after &OPTIONAL`);
         } else if (x instanceof LispSymbol) {
           sections.optional.push({
             name: checkValidLocal(x),
@@ -946,6 +953,8 @@ const parseLambdaList = list => {
       case 'rest':
         if (sections.rest)
           throw new Error(`Repeated &REST`);
+        if (eqClSym(x, '&ALLOW-OTHER-KEYS'))
+          throw new Error(`Expected variable after &REST, got ${x.name}`);
         if (!(x instanceof LispSymbol))
           throw new Error(`&REST parameter not a symbol`);
         sections.rest = x;
@@ -958,6 +967,8 @@ const parseLambdaList = list => {
           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`);
         } else {
@@ -968,6 +979,10 @@ const parseLambdaList = list => {
         // TODO (keyword var) e.g. ((:foo foo))
         if (eqClSym(x, '&KEY')) {
           throw new Error(`Repeated &KEY`);
+        } else if (eqClSym(x, '&ALLOW-OTHER-KEYS')) {
+          if (sections.keyAllowOther)
+            throw new Error(`Duplicate &ALLOW-OTHER-KEYS`);
+          sections.keyAllowOthers = true;
         } else if (eqClSym(x, '&OPTIONAL') || eqClSym(x, '&REST')) {
           throw new Error(`Misplaced ${x.name}`);
         } else if (eqClSym(x, '&AUX')) {
@@ -1464,7 +1479,7 @@ const emitNode = (print, node) => {
       if (min >= 0 && min === max) {
         print(`if (arguments.length !== ${min}) throw new Error('Called with invalid number of arguments: ' + arguments.length);\n`);
       } else {
-        if (min >= 0) {
+        if (min > 0) {
           print(`if (arguments.length < ${min}) throw new Error('Called with too few arguments: ' + arguments.length);\n`);
         }
         if (max) {
@@ -1535,11 +1550,26 @@ const emitNode = (print, node) => {
         }
         print(';\n');
         print(`if((arguments.length-${restStart})%2)throw new Error('Odd number of &key arguments');\n`);
-        print('var keyVals = {};\n');
+        print('var keyVals={};\n');
+        print('var knownKeys=[');
+        print(node.lambdaList.key.map(({key}) => `'${escapeSingle(key.name)}'`).join(','));
+        print('];\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`);
+        // Duplicate keys are ignored
+        print(`if(!keyVals.hasOwnProperty(arguments[i].name)){\n`);
         print('keyVals[arguments[i].name]=arguments[i+1];\n');
-        print('}\n');
+        print('}}\n');
+        const allowOtherKw = escapeSingle(LispSymbol.kw('ALLOW-OTHER-KEYS', false).name);
+        // Unspecified keys are allowed either by including the keyword 
+        // &allow-other-keys after the &key section or by supplying the keyword
+        // param :allow-other-keys with a logically true value
+        print(`if(!${node.lambdaList.keyAllowOthers}&&!(keyVals.hasOwnProperty('${allowOtherKw}')&&keyVals['${allowOtherKw}']!==null)){\n`);
+          print('console.log("WTF");\n');
+        print('for(var prop in keyVals){\n');
+        print(`if(keyVals.hasOwnProperty(prop)){\n`);
+        print(`if(!knownKeys.includes(prop))throw new Error('Unknown keyword argument: '+prop);\n`);
+        print('}}}\n');
         for (const kspec of node.lambdaList.key) {
           print(`if(keyVals.hasOwnProperty('${escapeSingle(kspec.key.name)}')){\n`);
           print(mungeSym(kspec.name, 'local'));