git » jacl.git » commit 7aefd8d

improve emission, add jacl::%quote

author Alan Dipert
2019-10-17 04:39:10 UTC
committer Alan Dipert
2019-10-17 04:39:10 UTC
parent 430e33c8ad5d264532ed536345d06d45bc0c962d

improve emission, add jacl::%quote

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