git » jacl.git » commit 8a17940

sundry analyzer progress. start tracking parent node in prep for transforms

author Alan Dipert
2019-10-16 20:44:56 UTC
committer Alan Dipert
2019-10-16 20:44:56 UTC
parent 7c324d679c8caa151b5dfcdeb5f93fe122466f9a

sundry analyzer progress. start tracking parent node in prep for transforms

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