author | Alan Dipert
<alan@dipert.org> 2019-10-23 04:18:49 UTC |
committer | Alan Dipert
<alan@dipert.org> 2019-10-23 04:18:49 UTC |
parent | c920199a3af9f96ff322c2d196aaef33a1ed2497 |
jacl.js | +121 | -99 |
diff --git a/jacl.js b/jacl.js index abc0ab4..cc33b44 100644 --- a/jacl.js +++ b/jacl.js @@ -531,7 +531,7 @@ READTABLE.value = new ReadTable() await new Reader(stream).read() )); }) - .setMacro(",", true, async stream => { + .setMacro(',', true, async stream => { const ch = await stream.read(); if (ch === '@') { return new Values(Cons.listOf( @@ -819,13 +819,13 @@ const makeNode = (op, ...objs) => merge({ op: op }, ...objs); const analyzeBlock = (env, parent, forms) => { let stmts = forms.slice(0, forms.length-1) - .map(x => analyze(env.withContext("statement"), parent, x)), + .map(x => analyze(env.withContext('statement'), parent, x)), ret; if (forms.length <= 1) { ret = analyze(env, parent, forms[0]); } else { ret = analyze( - env.withContext(env.context === "statement" ? "statement" : "return"), + env.withContext(env.context === 'statement' ? 'statement' : 'return'), parent, forms.slice(forms.length-1)[0] ) @@ -839,31 +839,32 @@ const isTag = x => x instanceof LispSymbol || x instanceof Number; const asTagName = x => x instanceof LispSymbol ? x.name : x.valueOf(); const analyzeSpecials = new Map([ - [JACLPKG.intern("%QUOTE"), (env, parent, form) => { - return makeNode("quote", { env: env, parent: parent, form: form }); + [JACLPKG.intern('%QUOTE'), (env, parent, form) => { + return makeNode('quote', { env: env, parent: parent, form: form }); }], - [JACLPKG.intern("%DOT"), (env, parent, form) => { + [JACLPKG.intern('%DOT'), (env, parent, form) => { const [, target, field] = form; if (!(field instanceof LispSymbol || stringy(field))) throw new Error(`%DOT field must be a symbol, JS string, or Lisp string`) - const node = makeNode("js-field", { + const node = makeNode('js-field', { env: env, parent: parent, form: form, field: field instanceof LispSymbol ? field.name : field.toString() }); - node.target = analyze(env.withContext("sval"), node, target); + node.target = analyze(env.withContext('sval'), node, target); return node; }], - [JACLPKG.intern("%CALL"), (env, parent, form) => { - env = env.withContext("sval"); + [JACLPKG.intern('%CALL'), (env, parent, form) => { + env = env.withContext('sval'); const [, func, ...args] = form; - const node = makeNode("call", { env: env, parent: parent, form: form }); + const node = makeNode('call', { env: env, parent: parent, form: form }); node.f = analyze(env, node, func); node.args = args.map(analyze.bind(null, env, node)) return node; }], - [JACLPKG.intern("%LAMBDA"), (env, parent, form) => { + // TODO majorly broken because of Env overhaul + [JACLPKG.intern('%LAMBDA'), (env, parent, form) => { const [, arglist, ...exprs] = form, args = (arglist === null ? [] : Array.from(arglist)) .map(x => { @@ -879,10 +880,11 @@ const analyzeSpecials = new Map([ throw new Error(`&REST must be followed by argument symbol`); } - const bodyEnv = env.withContext("return"); + // TODO locals are now stored as syms + const bodyEnv = env.withContext('return').withLocals(argNames); argNames.forEach(s => bodyEnv.localVals.add(s)); - const node = makeNode("lambda", { + const node = makeNode('lambda', { env: env, parent: parent, form: form, @@ -894,17 +896,31 @@ const analyzeSpecials = new Map([ Object.assign(node, analyzeBlock(bodyEnv, node, exprs)); return node; }], + [JACLPKG.intern('%LET'), (env, parent, form) => { + const [, bindings, ...body] = form; + const node = makeNode('let', { + env: env, + parent: parent, + form: form + }); + node.bindings = Array.from(bindings).map(([name, expr]) => { + return [name, analyze(env.withContext('sval'), node, expr)]; + }); + return merge( + node, + analyzeBlock(env.withLocals(node.bindings.map(x => x[0])), node, body) + ); + }], [JACLPKG.intern('%SET'), (env, parent, form) => { const [, target, val] = form; if (!(target instanceof LispSymbol)) throw new Error(`Can't assign to non-symbol`); - const valExpr = analyze(env.withContext("sval"), null, val); + const valExpr = analyze(env.withContext('sval'), null, val); let op; - if (target.packageName === PACKAGE.val().name - && env.hasLocal(target.name)) { + if (env.hasLocal(target)) { // Local op = 'set-local'; } else { @@ -920,7 +936,7 @@ const analyzeSpecials = new Map([ return node; }], - [JACLPKG.intern("%TAGBODY"), (env, parent, form) => { + [JACLPKG.intern('%TAGBODY'), (env, parent, form) => { const [, ...tagsStmts] = form; // Map from tag names (string or int) to arrays of statements // Any statements that appear before a tag are stored with the null tag @@ -929,7 +945,7 @@ const analyzeSpecials = new Map([ // after the tag // First pass: gather tags and statements, don't analyze - const newEnv = env.withContext("statement"); + const newEnv = env.withContext('statement'); const tags = new Map(); let currentTag = null, currentStmts = []; for (const x of tagsStmts) { @@ -945,7 +961,7 @@ const analyzeSpecials = new Map([ if (currentTag !== null) newEnv.tagbodyTags.add(currentTag); tags.set(currentTag, currentStmts); - const node = makeNode("tagbody", { env: env, parent: parent, form: form }); + const node = makeNode('tagbody', { env: env, parent: parent, form: form }); // Second pass: analyze statements const ana = analyze.bind(null, newEnv, node); @@ -956,26 +972,26 @@ const analyzeSpecials = new Map([ return node; }], - [JACLPKG.intern("%GO"), (env, parent, form) => { + [JACLPKG.intern('%GO'), (env, parent, form) => { const [, tag] = form; if (!isTag(tag)) throw new Error(`Invalid GO tag`); const tagName = asTagName(tag); if (!env.tagbodyTags.has(tagName)) throw new Error(`Non-existent GO tag: ${tagName}`); - return makeNode("go", { + return makeNode('go', { env: env, parent: parent, form: form, tagName: tagName }); }], - [JACLPKG.intern("%IF"), (env, parent, form) => { + [JACLPKG.intern('%IF'), (env, parent, form) => { if ([...form].length < 4) throw new Error(`IF requires at least 3 args`); const [, pred, expr0, expr1] = form; - const testNode = analyze(env.withContext("sval"), null, pred), + const testNode = analyze(env.withContext('sval'), null, pred), thenNode = analyze(env, null, expr0), elseNode = analyze(env, null, expr1); - const node = makeNode("if", { + const node = makeNode('if', { env: env, parent: parent, form: form, @@ -991,14 +1007,14 @@ const analyzeSpecials = new Map([ const parseCall = (env, parent, form) => { const [func, ...args] = form; - let node = makeNode("call", { env: env, parent: parent, form: form }); - node.args = args.map(analyze.bind(null, env.withContext("sval"), node)); + let node = makeNode('call', { env: env, parent: parent, form: form }); + node.args = args.map(analyze.bind(null, env.withContext('sval'), node)); if (isLambdaForm(func)) { node.f = analyze(env, parent, func); } else if (func instanceof LispSymbol && env.localFuns.has(func.name)) { - node.f = makeNode("local", { env: env, parent: node, form: func }); + node.f = makeNode('local', { env: env, parent: node, form: func }); } else if (func instanceof LispSymbol) { - node.f = makeNode("global", { env: env, parent: node, form: func, slot: "function" }); + node.f = makeNode('global', { env: env, parent: node, form: func, slot: 'function' }); } return node; }; @@ -1022,16 +1038,16 @@ const analyzeList = (env, parent, form) => { const analyzeSymbol = (env, parent, form) => { const node = makeNode(null, { env: env, parent: parent, form: form }); if (form.packageName === 'KEYWORD') { - node.op = "constant"; + node.op = 'constant'; } else if (form.packageName === 'JS' && !form.getPackage().isExported(form.name)) { // TODO Explode if the name isn't a valid JS identifier - node.op = "js-var"; + node.op = 'js-var'; node.name = form.name; - } else if (env.localVals.has(form.name)) { - node.op = "local"; + } else if (env.hasLocal(form)) { + node.op = 'local'; } else { - node.op = "global"; - node.slot = "value"; + node.op = 'global'; + node.slot = 'value'; } return node; }; @@ -1045,28 +1061,27 @@ class Env { return this; } static init(env) { - env.localFuns = new Set(); - env.localVals = new Set(); - env.tagbodyTags = new Set(); - env.context = "sval"; + env.locals = new Set(); + env.context = 'sval'; return env; } clone() { const newEnv = new Env(false); - newEnv.localFuns = new Set(this.localFuns); - newEnv.localVals = new Set(this.localVals); - newEnv.tagbodyTags = new Set(this.tagbodyTags); + newEnv.locals = new Set(this.locals); newEnv.context = this.context; return newEnv; } + withLocals(syms) { + const newEnv = this.clone(); + newEnv.locals = new Set([...this.locals, ...syms]); + console.log(newEnv.locals); + return newEnv; + } hasLocal(sym) { - return this.localVals.has(sym.name); + return this.locals.has(sym); } withContext(context) { - const newEnv = new Env(false); - newEnv.localFuns = this.localFuns; - newEnv.localVals = this.localVals; - newEnv.tagbodyTags = this.tagbodyTags; + const newEnv = this.clone(); newEnv.context = context; return newEnv; } @@ -1080,7 +1095,7 @@ const analyze = (env, parent, form) => { } else if (form instanceof Cons) { return analyzeList(env, parent, form); } else { - return makeNode("constant", { env: env, parent: parent, form: form }); + return makeNode('constant', { env: env, parent: parent, form: form }); } }; @@ -1091,7 +1106,7 @@ const constantCode = val => { return `new LispSymbol('${escapeSingle(val.name)}', null)`; } else if (val instanceof LispSymbol) { return `Package.get('${escapeSingle(val.packageName)}', true).intern('${escapeSingle(val.name)}')`; - } else if (val instanceof Number || typeof val === "number") { + } else if (val instanceof Number || typeof val === 'number') { return val.valueOf().toString(); } else if (val instanceof String || typeof val === 'string') { return `'${escapeSingle(val)}'`; @@ -1112,92 +1127,99 @@ const truth = x => x != null && x !== false; const emitNode = (print, node) => { const { op, env: { context }, parent, form } = node; switch (op) { - case "js-var": - if (context === "return") print("return "); + case 'js-var': + if (context === 'return') print('return '); print(node.name); - if (context !== "sval") print(";\n"); + if (context !== 'sval') print(';\n'); break; - case "js-field": - if (context === "return") print("return "); + case 'js-field': + if (context === 'return') print('return '); emitNode(print, node.target); print(`.${node.field}`); - if (context !== "sval") print(";\n"); + if (context !== 'sval') print(';\n'); break; - case "quote": - if (context === "return") print("return "); + case 'quote': + if (context === 'return') print('return '); print(constantCode(form.cdr.car)); - if (context !== "sval") print(";\n"); + if (context !== 'sval') print(';\n'); break; - case "constant": - if (context === "return") print("return "); + case 'constant': + if (context === 'return') print('return '); print(constantCode(form)); - if (context !== "sval") print(";\n"); + if (context !== 'sval') print(';\n'); break; - case "local": - if (context === "return") print("return "); + case 'local': + if (context === 'return') print('return '); print(munge(form.name)); - if (context !== "sval") print(";\n"); + if (context !== 'sval') print(';\n'); break; - case "global": - if (context === "return") print("return "); + case 'global': + if (context === 'return') print('return '); if (form.packageName === 'COMMON-LISP' && form.name === 'NIL') { - print("null"); + print('null'); } else { print(`Package.get('${escapeSingle(form.packageName)}', true).intern('${escapeSingle(form.name)}')`); if (node.slot === 'value') { - print(".val()"); + print('.val()'); } else if (node.slot === 'function') { - print(".func()"); + print('.func()'); } else { throw new Error(`Unknown global slot: ${node.slot}`); } } - if (context !== "sval") print(";\n"); + if (context !== 'sval') print(';\n'); + break; + case 'set-global': + if (context === 'return') print('return '); + print(`Package.get('${escapeSingle(node.targetSym.packageName)}').intern('${escapeSingle(node.targetSym.name)}').value=`); + emitNode(print, node.val); + if (context !== 'sval') print(';\n'); + break; break; - case "set": - if (context === "return") print("return "); + case 'set': + if (context === 'return') print('return '); emitNode(print, node.target); print('='); emitNode(print, node.val); - if (context !== "sval") print(";\n"); + if (context !== 'sval') print(';\n'); break; - case "call": - if (context === "return") print("return "); + case 'call': + if (context === 'return') print('return '); emitNode(print, node.f); - print("("); + print('('); node.args.forEach((arg, i) => { emitNode(print, arg); - if (i < node.args.length-1) print(","); + if (i < node.args.length-1) print(','); }); - print(")"); - if (context !== "sval") print(";\n"); + print(')'); + if (context !== 'sval') print(';\n'); break; - case "if": - if (context === "sval" || context === "return") { - print("(truth("); + case 'if': + if (context === 'sval' || context === 'return') { + print('(truth('); emitNode(print, node.testNode); - print(")?") + print(')?') emitNode(print, node.thenNode); - print(":"); + print(':'); emitNode(print, node.elseNode); - print(")"); + print(')'); } else { - print("if("); + print('if('); emitNode(print, node.testNode); - print(")\n\t"); + print(')\n\t'); emitNode(print, node.thenNode); - print("else\n\t"); + print('else\n\t'); emitNode(print, node.elseNode); - print("\n"); + print('\n'); } break; - case "tagbody": - if (context === "return") print("return "); - if (context === "statement") { + case 'tagbody': + if (context === 'return') print('return '); + if (context === 'statement') { // TODO } else { } - if (context !== "sval") print(";\n"); + if (context !== 'sval') print(';\n'); break; // TODO lambda default: @@ -1206,7 +1228,7 @@ const emitNode = (print, node) => { }; class StringBuffer { - constructor(str = "") { + constructor(str = '') { this.str = str; } append(str) { @@ -1218,7 +1240,7 @@ class StringBuffer { } const emitter = (() => { - let sb = ""; + let sb = ''; return node => { emitNode(x => { sb += x; return null; }, node); return sb; @@ -1230,14 +1252,14 @@ var buf = new BufferedStream(), (async function() { for await(const obj of rdr) { - console.log("read:", obj); + console.log('read:', obj); const node = analyze(emptyEnv, null, obj); - console.log("analyzed:", node); + console.log('analyzed:', node); const sb = new StringBuffer(); emitNode(sb.append.bind(sb), node); const code = sb.toString(); - console.log("generated:", code); - console.log("evaled:", eval(code)); + console.log('generated:', code); + console.log('evaled:', eval(code)); } })()