author | Alan Dipert
<alan@dipert.org> 2019-11-04 06:36:48 UTC |
committer | Alan Dipert
<alan@dipert.org> 2019-11-04 06:36:48 UTC |
parent | 296de103afd0d6f427ec3a58dd5cb63188591782 |
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'));