git » jacl.git » commit 83433da

tagbody/go WIP

author Alan Dipert
2019-11-11 14:10:10 UTC
committer Alan Dipert
2019-11-11 14:10:10 UTC
parent b04a1d962af3f66c8c0fcb256d7d5cccd8f9f3e1

tagbody/go WIP

jacl.js +49 -15

diff --git a/jacl.js b/jacl.js
index c058183..f595457 100644
--- a/jacl.js
+++ b/jacl.js
@@ -1192,11 +1192,16 @@ const analyzeSpecials = new Map([
     }
     tags.set(currentTag, currentStmts);
 
-    const node = makeNode('tagbody', { env: env, parent: parent, form: form });
+    const node = makeNode('tagbody', { 
+      env: env, 
+      parent: parent, 
+      form: form,
+      id: env.newId()
+    });
 
     const childEnv = env
       .withContext('statement')
-      .withTags(Array.from(tags.keys()).filter(x => x !== null));
+      .withTags(node.id, Array.from(tags.keys()).filter(x => x !== null));
 
     // Second pass: analyze statements
     const ana = analyze.bind(null, childEnv, node);
@@ -1217,7 +1222,13 @@ const analyzeSpecials = new Map([
       env: env,
       parent: parent,
       form: form,
-      tagName: tagName
+      tagName: tagName,
+      // TODO If the %GO is closed over in a lambda/flet/labels then it needs
+      // to throw an exception. If a tagbody occurs in sval/return context then
+      // it needs to throw an exception also, to escape emitted IIFEs?
+      // Otherwise, it can set its enclosing tagbody's target and continue ot
+      // its loop.
+      tagbodyId: env.tags.get(tagName)
     });
   }],
   [JACLPKG.intern('%IF'), (env, parent, form) => {
@@ -1290,6 +1301,11 @@ const analyzeSymbol = (env, parent, form) => {
   return node;
 };
 
+const makeCounter = () => {
+  let i = 0;
+  return () => i++;
+};
+
 class Env {
   constructor(init = true) {
     if (init) Env.init(this);
@@ -1297,15 +1313,18 @@ class Env {
   }
   static init(env) {
     env.locals = new Set();
-    env.tags = new Set();
+    // sym => tagbodyName
+    env.tags = new Map();
     env.context = 'sval';
+    env.counter = makeCounter();
     return env;
   }
   clone() {
     const newEnv = new Env(false);
     newEnv.locals = new Set(this.locals);
-    newEnv.tags = new Set(this.tags);
+    newEnv.tags = new Map(this.tags);
     newEnv.context = this.context;
+    newEnv.counter = this.counter;
     return newEnv;
   }
   withLocals(syms) {
@@ -1313,16 +1332,19 @@ class Env {
     newEnv.locals = new Set([...this.locals, ...syms]);
     return newEnv;
   }
-  withTags(syms) {
+  newId() {
+    return this.counter();
+  }
+  withTags(id, tags) {
     const newEnv = this.clone();
-    newEnv.tags = new Set([...this.tags, ...syms]);
+    newEnv.tags = new Map([...this.tags, ...tags.map(tag => [tag, id])]);
     return newEnv;
   }
   hasLocal(sym) {
     return this.locals.has(sym);
   }
-  hasTag(sym) {
-    return this.tags.has(sym);
+  hasTag(tag) {
+    return this.tags.has(tag);
   }
   withContext(context) {
     const newEnv = this.clone();
@@ -1658,16 +1680,28 @@ const emitNode = (print, node) => {
         print('\n');
       }
       break;
-    case 'tagbody':
+    case 'tagbody': {
       if (context === 'return') print('return ');
-      if (context === 'statement') {
-        // TODO
+      if (context === 'sval' || context === 'return') {
+        throw new Error('TODO tagbody IIFE');
       } else {
+        for (const stmt of node.prelude) {
+          emitNode(print, stmt);
+        }
+        if (node.tags.size) {
+          const firstTag = () => {
+            const name = node.tags.entries().next().value[0];
+            return typeof name === 'number' ? name : `'${escapeSingle(name)}'`;
+          };
+          print(`tagbody_${node.id}_to=${firstTag()};\n`);
+          print(`tagbody_${node.id}:while(true){\n`);
+          print(`break;`);
+          print('}\n');
+        }
       }
-      if (context !== 'sval') print(';\n');
+      //if (context !== 'sval') print(';\n');
       break;
-    // TODO lambda
-    default:
+    } default:
       throw new Error(`Unknown op: ${op}`);
   }
 };