author | Alan Dipert
<alan@dipert.org> 2019-10-17 04:39:10 UTC |
committer | Alan Dipert
<alan@dipert.org> 2019-10-17 04:39:10 UTC |
parent | 430e33c8ad5d264532ed536345d06d45bc0c962d |
jacl.js | +87 | -26 |
diff --git a/jacl.js b/jacl.js index 08a8123..7e2745b 100644 --- a/jacl.js +++ b/jacl.js @@ -721,6 +721,15 @@ 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) => { + const [, val] = form + return makeNode("constant", { + env: env, + parent: parent, + form: form, + val: val + }); + }], [JACLPKG.intern("%DOT"), (env, parent, form) => { const [, target, field] = form; if (!(field instanceof LispSymbol || stringy(field))) @@ -857,8 +866,10 @@ 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 = "keyword"; + node.op = "constant"; + node.val = form; } 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.name = form.name; } else if (env.localVals.has(form.name)) { @@ -908,32 +919,60 @@ 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, val: form }); } }; -const emitWrap = (node, print, thunk) => { - if (node.env.context === "return") print("return "); - thunk(); - if (node.env.context !== "sval") print(";\n"); -} +const escapeSingle = name => name.replace(/'/g, "\\'"); + +const constantCode = val => { + if (val instanceof LispSymbol && !val.packageName) { + 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") { + return val.valueOf().toString(); + } else if (val instanceof String || typeof val === 'string') { + return `'${escapeSingle(val)}'`; + } else if (val instanceof LispString) { + return `LispString.fromString('${escapeSingle(val.toString())}')`; + } else if (val === null) { + return 'null'; + } else if (val instanceof Cons) { + const { car, cdr } = val; + return `new Cons(${constantCode(car)}, ${constantCode(cdr)})`; + } else { + throw new Error(`Don't know how to emit constant value ${val}`); + } +}; -const emitNode = (node, print) => { - const { op, env, parent, form } = node; +const emitNode = (print, node) => { + const { op, env: { context }, parent, form } = node; switch (op) { case "js-var": + if (context === "return") print("return "); print(node.name); + if (context !== "sval") print(";\n"); + break; + case "js-field": + if (context === "return") print("return "); + emitNode(print, node.target); + print(`.${node.field}`); + if (context !== "sval") print(";\n"); break; - case "keyword": - // TODO escape symbol name, reconsider how to compile keywords - print(`Package.kw('${form.name}', false)`); + case "constant": + if (context === "return") print("return "); + print(constantCode(node.val)); + if (context !== "sval") print(";\n"); break; case "local": + if (context === "return") print("return "); print(munge(form.name)); + if (context !== "sval") print(";\n"); break; case "global": - // TODO escape packageName, name - print(`Package.get('${form.packageName}').intern('${form.name}')`); + if (context === "return") print("return "); + print(`Package.get('${escapeSingle(form.packageName)}', true).intern('${escapeSingle(form.name)}')`); if (node.slot === 'value') { print(".val()"); } else if (node.slot === 'function') { @@ -941,25 +980,46 @@ const emitNode = (node, print) => { } else { throw new Error(`Unknown global slot: ${node.slot}`); } + if (context !== "sval") print(";\n"); break; case "call": - // TODO + if (context === "return") print("return "); + emitNode(print, node.f); + print("("); + node.args.forEach((arg, i) => { + emitNode(print, arg); + if (i < node.args.length-1) print(","); + }); + print(")"); + if (context !== "sval") print(";\n"); break; + // TODO if + // TODO tagbody + // TODO lambda default: throw new Error(`Unknown op: ${op}`); } }; -const emit = (() => { +class StringBuffer { + constructor(str = "") { + this.str = str; + } + append(str) { + this.str += str; + } + toString() { + return this.str; + } +} + +const emitter = (() => { let sb = ""; return node => { - emitNode(node, x => { - sb += x; - return null; - }); + emitNode(x => { sb += x; return null; }, node); return sb; }; -})(); +}); var buf = new BufferedStream(), rdr = new Reader(buf); @@ -968,11 +1028,12 @@ var buf = new BufferedStream(), for await(const obj of rdr) { console.log("read:", obj); const node = analyze(emptyEnv, null, obj); - console.log(node); - const code = emit(node) - console.log(code); - //console.log("compiled:", compile(obj, null)); - //console.log("evaled:", eval(compile(obj, null))); + 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)); } })()