git » jacl.git » commit 2980fa7

added %prog, fleshed out %tagbody and abandoned efficient approach for now

author Alan Dipert
2019-11-16 05:45:14 UTC
committer Alan Dipert
2019-11-16 05:45:14 UTC
parent b16f9df808dc005edaecb82bc912ed783b2c32fd

added %prog, fleshed out %tagbody and abandoned efficient approach for now

jacl.js +77 -21

diff --git a/jacl.js b/jacl.js
index d73b76f..0546e8b 100644
--- a/jacl.js
+++ b/jacl.js
@@ -1,6 +1,13 @@
 // Sentinel used in a few places to indicate absence of a user-provided value
 const UNDEFINED = new Object();
 
+class TagEx extends Error {
+  constructor(name) {
+    super(`Unknown GO tag: ${name}`);
+    this.name = name;
+  }
+}
+
 class Cons {
   constructor(car, cdr = null) {
     this.car = car;
@@ -762,6 +769,8 @@ const SPECIAL_FORMS = [
   '%CALL',
   '%DOT',
   '%LAMBDA',
+  '%LET',
+  '%PROG',
   '%SET',
   '%TAGBODY',
   '%GO'
@@ -858,8 +867,6 @@ 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, parent, forms) => {
   let stmts = forms.slice(0, forms.length-1)
                .map(x => analyze(env.withContext('stmt'), parent, x)),
@@ -1152,6 +1159,15 @@ const analyzeSpecials = new Map([
       analyzeBlock(env.withLocals(node.bindings.map(x => x[0])), node, body)
     );
   }],
+  [JACLPKG.intern('%PROG'), (env, parent, form) => {
+    const [, ...body] = form;
+    const node = makeNode('prog', {
+      env: env,
+      parent: parent,
+      form: form
+    });
+    return merge(node, analyzeBlock(env, node, body));
+  }],
   [JACLPKG.intern('%SET'), (env, parent, form) => {
     const [, target, val] = form;
     if (!(target instanceof LispSymbol))
@@ -1693,38 +1709,78 @@ const emitNode = (print, node) => {
       break;
     case 'tagbody': {
       if (context === 'return') print('return ');
-      if (context === 'expr' || context === 'return') {
-        throw new Error('TODO tagbody IIFE');
-      } else {
-        for (const stmt of node.prelude) {
-          emitNode(print, stmt);
+      if (context === 'return' || context === 'expr') {
+        print('(function()\n');
+      }
+        print('{\n');
+        if (node.tags.size) {
+          print(`var tagbody_${node.id}_to;\n`);
+          print(`var tagbody_${node.id}_tags=[`);
+          print(Array.from(node.tags, ([tag]) => formatTag(tag)).join(','));
+          print(`];\n`);
+        }
+        if (node.prelude.length && node.tags.size) {
+          print(`try{\n`);
+            for (const stmt of node.prelude) {
+              emitNode(print, stmt);
+            }
+          print(`}catch(e){\n`);
+            print(`if((e instanceof TagEx) && tagbody_${node.id}_tags.indexOf(e.name) >= 0){\n`);
+              print(`tagbody_${node.id}_to=e.name;\n`);
+            print(`}else{\n`);
+              print(`throw e;\n`);
+            print(`}\n`);
+          print(`}\n`);
+        } else if (node.prelude.length) {
+          for (const stmt of node.prelude) {
+            emitNode(print, stmt);
+          }
         }
         if (node.tags.size) {
           const firstTag = formatTag(node.tags.entries().next().value[0]);
+          print(`if (tagbody_${node.id}_to===undefined){\n`);
           print(`tagbody_${node.id}_to=${firstTag};\n`);
+          print(`}\n`);
           print(`tagbody_${node.id}:while(true){\n`);
-            print(`switch(tagbody_${node.id}_to){\n`);
-            for (const [tag, stmts] of node.tags) {
-              print(`case ${formatTag(tag)}:\n`);
-              for (const stmt of stmts) emitNode(print, stmt);
-            }
-            print(`default:\nbreak tagbody_${node.id};\n`);
-            print('}\n');
+            print(`try{\n`);
+              print(`switch(tagbody_${node.id}_to){\n`);
+                for (const [tag, stmts] of node.tags) {
+                  print(`case ${formatTag(tag)}:\n`);
+                  for (const stmt of stmts) emitNode(print, stmt);
+                }
+                print(`default:\nbreak tagbody_${node.id};\n`);
+              print('}\n');
+            print(`}catch(e){\n`);
+              print(`if((e instanceof TagEx) && tagbody_${node.id}_tags.indexOf(e.name) >= 0){\n`);
+                print(`tagbody_${node.id}_to=e.name;\n`);
+                print(`continue tagbody_${node.id};\n`);
+              print(`}else{\n`);
+                print(`throw e;\n`);
+              print(`}\n`);
+            print(`}\n`);
           print('}\n');
         }
+      if (context === 'return' || context === 'expr') {
+        print('return null;\n})()');
+      } else {
+        print('}\n');
       }
-      //if (context !== 'expr') print(';\n');
       break;
     } case 'go': {
       if (context === 'return') print('return ');
-      if (context === 'expr' || context === 'return') {
-        throw new Error('TODO go IIFE');
+      if (context === 'return' || context == 'expr') {
+        print(`(function(){throw new TagEx(${formatTag(node.tagName)});})()`);
       } else {
-        print('{\n');
-        print(`tagbody_${node.tagbodyId}_to=${formatTag(node.tagName)};\n`);
-        print(`continue tagbody_${node.tagbodyId};\n`);
-        print('}\n');
+        print(`throw new TagEx(${formatTag(node.tagName)})`);
       }
+      if (context !== 'expr') print(';\n');
+      break;
+    } case 'prog': {
+      if (context === 'expr') print('(function()');
+      print('{');
+      emitBlock(print, node.statements, node.ret);
+      print('}');
+      if (context === 'expr') print(')()');
       break;
     } default:
       throw new Error(`Unknown op: ${op}`);