author | Alan Dipert
<alan@dipert.org> 2019-10-16 20:44:56 UTC |
committer | Alan Dipert
<alan@dipert.org> 2019-10-16 20:44:56 UTC |
parent | 7c324d679c8caa151b5dfcdeb5f93fe122466f9a |
jacl.js | +65 | -136 |
diff --git a/jacl.js b/jacl.js index 214bf8c..76e7c62 100644 --- a/jacl.js +++ b/jacl.js @@ -651,7 +651,7 @@ JACLPKG.intern('.').setMacro().fvalue = function(topic, ...ops) { const [method, ...args] = op; return Cons.listOf( JACLPKG.intern('%CALL'), - Cons.listOf(JACLPKG.intern('%DOT'), prev, method), + Cons.listOf(JACLPKG.intern('%DOT'), prev, method), ...args ); } else { @@ -693,19 +693,22 @@ const isLambdaForm = form => { ); } -const merge = (...objs) => Object.assign({}, ...objs); +const merge = (...objs) => Object.assign(Object.create(null), ...objs); + +const makeNode = (op, ...objs) => merge({ op: op }, ...objs); //(def specials '#{if def fn* do let* loop recur new set! ns}) -const analyzeBlock = (env, forms) => { +const analyzeBlock = (env, parent, forms) => { let stmts = forms.slice(0, forms.length-1) - .map(x => analyze(env.withContext("statement"), x)), + .map(x => analyze(env.withContext("statement"), parent, x)), ret; if (forms.length <= 1) { - ret = analyze(env, forms[0]); + ret = analyze(env, parent, forms[0]); } else { ret = analyze( env.withContext(env.context === "statement" ? "statement" : "return"), + parent, forms.slice(forms.length-1)[0] ) } @@ -718,30 +721,28 @@ 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("%DOT"), (env, 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`) - return { - op: "js-field", + const node = makeNode("js-field", { env: env, + parent: parent, form: form, - target: analyze(env.withContext("sval"), target), field: field instanceof LispSymbol ? field.name : field.toString() - }; + }); + node.target = analyze(env.withContext("sval"), node, target); + return node; }], - [JACLPKG.intern("%CALL"), (env, form) => { + [JACLPKG.intern("%CALL"), (env, parent, form) => { env = env.withContext("sval"); const [, func, ...args] = form; - return { - op: "call", - env: env, - form: form, - f: analyze(env, func), - args: args.map(analyze.bind(null, env)) - } + 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, form) => { + [JACLPKG.intern("%LAMBDA"), (env, parent, form) => { const [, arglist, ...exprs] = form, args = (arglist === null ? [] : Array.from(arglist)) .map(x => { @@ -760,25 +761,26 @@ const analyzeSpecials = new Map([ const bodyEnv = env.withContext("return"); argNames.forEach(s => bodyEnv.localVals.add(s)); - return merge({ - op: "lambda", + const node = makeNode("lambda", { env: env, + parent: parent, form: form, isVariadic: isVariadic, restArgName: isVariadic ? args[restIdx+1] : null, minArgs: argNames.length - (isVariadic ? 1 : 0), argNames: argNames, - }, - analyzeBlock(bodyEnv, exprs)); + }); + Object.assign(node, analyzeBlock(bodyEnv, node, exprs)); + return node; }], - [JACLPKG.intern("%TAGBODY"), (env, 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 // Tags/statements are processed in multiple passes because any parent tag // can be jumped to from any child even if the child appears syntactically // after the tag - + // First pass: gather tags and statements, don't analyze const newEnv = env.clone().withContext("statement"); const tags = new Map(); @@ -796,77 +798,77 @@ 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 }); + // Second pass: analyze statements - const ana = analyze.bind(null, newEnv); + const ana = analyze.bind(null, newEnv, node); for (const [tag, stmts] of tags) tags.set(tag, stmts.map(ana)); + node.prelude = tags.get(null); + tags.delete(null); + node.tags = tags; - return { - op: "tagbody", - env: env, - form: form, - tags: tags - }; + return node; }], - [JACLPKG.intern("%GO"), (env, 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 { - op: "go", + return makeNode("go", { env: env, + parent: parent, form: form, tagName: tagName - }; + }); }] ]); -const parseCall = (env, form) => { +const parseCall = (env, parent, form) => { const [func, ...args] = form; - let f; + let node = makeNode("call", { env: env, parent: parent, form: form }); + node.args = args.map(analyze.bind(null, env.withContext("sval"), node)); if (isLambdaForm(func)) { - f = analyze(env, func); + node.f = analyze(env, parent, func); } else if (func instanceof LispSymbol && env.localFuns.has(func.name)) { - f = { env: env, form: func, op: "local", name: munge(func.name) }; + node.f = makeNode("local", { env: env, parent: node, form: func, name: munge(func.name) }); } else if (func instanceof LispSymbol) { - f = { env: env, form: func, op: "global", slot: "function" }; - } - return { - env: env, - op: "call", - f: f, - args: args.map(analyze.bind(null, env.withContext("sval"))) - }; + node.f = makeNode("global", { env: env, parent: node, form: func, slot: "function" }); + } + return node; }; -const analyzeList = (env, form) => { +const analyzeList = (env, parent, form) => { if (isMacroForm(form)) { while (isMacroForm(form)) { const [sym, ...args] = form; form = sym.func()(...args); } - return analyze(env, form); + return analyze(env, parent, form); } else if (analyzeSpecials.has(form.car)) { - return analyzeSpecials.get(form.car)(env, form); + return analyzeSpecials.get(form.car)(env, parent, form); } else if (isLambdaForm(form.car) || form.car instanceof LispSymbol) { - return parseCall(env, form); + return parseCall(env, parent, form); } else { throw new Error(`Invalid call`) } }; -const analyzeSymbol = (env, form) => { - ret = x => merge({ env: env, form: form }, x); +const analyzeSymbol = (env, parent, form) => { + const node = makeNode(null, { env: env, parent: parent, form: form }); if (form.packageName === 'KEYWORD') { - return ret({ op: "constant" }); + node.op = "constant"; } else if (form.packageName === 'JS' && !form.getPackage().isExported(form.name)) { - return ret({ op: "js-var", name: form.name }); + node.op = "js-var"; + node.name = form.name; } else if (env.localVals.has(form.name)) { - return ret({ op: "local", name: munge(form.name) }); + node.op = "local"; + node.name = munge(form.name); } else { - return ret({ op: "global", slot: "value" }); + node.op = "global"; + node.slot = "value"; } + return node; }; class Env { @@ -901,86 +903,13 @@ class Env { const emptyEnv = new Env(); -const analyze = (env, form) => { +const analyze = (env, parent, form) => { if (form instanceof LispSymbol) { - return analyzeSymbol(env, form); + return analyzeSymbol(env, parent, form); } else if (form instanceof Cons) { - return analyzeList(env, form); + return analyzeList(env, parent, form); } else { - return { op: "constant", env: env, form: form }; - } -}; - -const compileLambda = ([, args, ...body], env) => { - env = new Set(...args, env); - let bodyStr = ''; - if (body.length == 1) { - bodyStr = `return ${compile(body[0], env)};`; - } else if (body.length > 1) { - bodyStr += body.slice(0, -1).map(x => compile(x, env)).join(';'); - bodyStr += `return ${compile(body[body.length-1], env)};`; - } - return `(function(${args.map(munge).join(',')}){${bodyStr}})` -}; - -const compile = (form, env) => { - if (form instanceof Number || typeof form === 'number') { - return form.toString(); - } else if (form instanceof String || typeof form === 'string') { - return JSON.stringify(form); - } else if (form instanceof LispString) { - return `(LispString.fromString("${form}"))`; - } else if (form instanceof LispSymbol && form.packageName !== null) { - if (form.packageName === 'COMMON-LISP' && CLCONSTS.has(form.name)) { - return CLCONSTS.get(form.name); - } else if (form.packageName === 'JS' && JSCONSTS.has(form.name)) { - return JSCONSTS.get(form.name); - } else if (form.packageName === 'JS' && !JSCONSTS.has(form.name)) { - return form.name; - } else { - return `LispSymbol.intern(${JSON.stringify(form.packageName)}, ${JSON.stringify(form.name)}).val()`; - } - } else if (isMacroForm(form)) { - while (isMacroForm(form)) { - const [sym, ...args] = form; - form = sym.fvalue(...args); - } - return compile(form, env); - } else if (form instanceof Cons) { - let [op, ...args] = form, - [arg1, arg2] = args; - if (JACLPKG.intern('%DOT') === op) { - return `${compile(arg1, env)}.${arg2.name}` - } else if (JACLPKG.intern('%CALL') === op) { - let [, func, ...args] = form; - return `${compile(func, env)}(` + - args.map(x => compile(x, env)).join(',') + - `)`; - } else if (CLPKG.intern('QUOTE') === op) { - if (arg1 instanceof LispSymbol) { - return `LispSymbol.intern(${JSON.stringify(arg1.packageName)}, ${JSON.stringify(arg1.name)})`; - } else if (arg1 instanceof Cons) { - let [car, cdr] = [arg1.car, arg1.cdr].map(x => { - return compile(Cons.listOf(CLPKG.intern('QUOTE'), x)); - }); - return `(new Cons(${car},${cdr}))`; - } else { - return compile(arg1, env); - } - return compile(arg1, env); - } else if (JACLPKG.intern('%LAMBDA') === op) { - return compileLambda(form, env); - } else if (op instanceof LispSymbol) { - return `LispSymbol.intern('${op.packageName}', '${op.name}').func()(` + - args.map(x => compile(x, env)).join(',') + - `)`; - } else if (isLambdaForm(op)) { - return `${compile(op, env)}(${args.map(x => compile(x, env)).join(',')})` - } else { - throw new Error(`Illegal function call`); - } - } else if (form === null) { - return 'null'; + return makeNode("constant", { env: env, parent: parent, form: form }); } }; @@ -990,7 +919,7 @@ var buf = new BufferedStream(), (async function() { for await(const obj of rdr) { console.log("read:", obj); - console.log(analyze(emptyEnv, obj)); + console.log(analyze(emptyEnv, null, obj)); //console.log("compiled:", compile(obj, null)); //console.log("evaled:", eval(compile(obj, null))); }