git » jacl.git » commit cc7d304

locals cleanup

author Alan Dipert
2019-10-23 04:18:49 UTC
committer Alan Dipert
2019-10-23 04:18:49 UTC
parent c920199a3af9f96ff322c2d196aaef33a1ed2497

locals cleanup

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));
   }
 })()