git » jacl.git » commit f543038

Add first # dispatch char and macro, #: for uninterned syms

author Alan Dipert
2019-08-18 12:54:00 UTC
committer Alan Dipert
2019-08-18 12:54:00 UTC
parent 465be05a933bb6ce1c609f3fa916e068e19d9b55

Add first # dispatch char and macro, #: for uninterned syms

jacl.js +69 -23

diff --git a/jacl.js b/jacl.js
index 3bf804c..c2f815c 100644
--- a/jacl.js
+++ b/jacl.js
@@ -37,11 +37,10 @@ class Cons {
   }
 }
 
-// TODO uninterned symbol construction
 class LispSymbol {
   constructor(name, packageName) {
     this.name = name;
-    this.package = packageName;
+    this.packageName = packageName;
     this.value = UNDEFINED;
     this.fvalue = UNDEFINED;
     this.stack = [];
@@ -70,29 +69,46 @@ class LispSymbol {
       throw new Error(`Symbol name must not be number: '${name}'`);
     return Package.get(packageName, true).intern(name);
   }
-  static fromString(token) {
-    if (/^:[^:]+$/.test(token)) {
-      return LispSymbol.intern('KEYWORD', token.substring(1));
-    }
-
+  static parseQualified(token) {
     const singleMarkerRe = /^([^:]+):([^:]+)$/,
           doubleMarkerRe = /^([^:]+)::([^:]+)$/;
 
-    let match = token.match(singleMarkerRe);
-    if (match) {
-      return LispSymbol.intern(...match.slice(1))
+    for (const re of [singleMarkerRe, doubleMarkerRe]) {
+      const match = token.match(re);
+      if (match) return {
+        packageName: match[1],
+        name: match[2]
+      }
     }
-
-    match = token.match(doubleMarkerRe);
-    if (match) {
-      return LispSymbol.intern(...match.slice(1));
+  }
+  static parse(token) {
+    if (/^:[^:]+$/.test(token)) {
+      return {
+        packageName: 'KEYWORD',
+        name: token.slice(1)
+      };
     }
 
-    if (token.indexOf(':') < 0) {
-      return PACKAGE.val().intern(token);
-    }
+    const qualified = LispSymbol.parseQualified(token);
+    if (qualified) return qualified;
+
+    if (token.indexOf(':') < 0)
+      return { packageName: null, name: token };
 
-    throw new Error(`Unknown symbol syntax: '${token}'`);
+    return null;
+  }
+  static fromString(token, intern = true) {
+    const parsed = LispSymbol.parse(token);
+    if (!parsed) throw new Error(`Failed to parse symbol: ${token}`);
+    const {packageName, name} = parsed;
+
+    if (intern) {
+      const pkg = packageName ? Package.get(packageName, true) : PACKAGE.val();
+      return pkg.intern(name);
+    } else {
+      if (packageName) throw new Error(`Symbol contains package marker`);
+      return new LispSymbol(name, null);
+    }
   }
 }
 
@@ -197,19 +213,34 @@ class BufferedStream {
 class ReadTable {
   constructor() {
     this.macros = new Map();
-    this.terminatingMacros = new Set();
+    this.terminatingChars = new Set();
+    this.dispatchChars = new Map();
+  }
+  makeDispatch(ch, isTerminating) {
+    this.dispatchChars.set(ch, new Map());
+    if (isTerminating) this.terminatingChars.add(ch);
+    return this;
+  }
+  setDispatch(dispCh, ch, fun) {
+    if (!this.dispatchChars.has(dispCh))
+      throw new Error(`Unknown dispatch char: '${dispCh}'`);
+    this.dispatchChars.get(dispCh).set(ch, fun);
+    return this;
   }
   setMacro(ch, isTerminating, fun) {
     this.macros.set(ch, fun);
-    if (isTerminating) this.terminatingMacros.add(ch);
+    if (isTerminating) this.terminatingChars.add(ch);
     return this;
   }
   isTerminating(ch) {
-    return this.terminatingMacros.has(ch);
+    return this.terminatingChars.has(ch);
   }
   getMacro(ch) {
     return this.macros.has(ch) ? this.macros.get(ch) : null;
   }
+  getDispatchChar(ch) {
+    return this.dispatchChars.has(ch) ? this.dispatchChars.get(ch) : null;
+  }
   clone() {
     throw new Error(`Not implemented`);
   }
@@ -283,6 +314,11 @@ READTABLE.value = new ReadTable()
       Package.intern('CL', 'QUOTE'),
       await Reader.of(Tokenizer.of(stream)).read()
     ));
+  })
+  .makeDispatch('#', false)
+  .setDispatch('#', ':', async stream => {
+    const token = await Tokenizer.of(stream).read();
+    return new Values(LispSymbol.fromString(token, false));
   });
 
 const isWhitespace = ch => ' \t\n\r\b'.indexOf(ch) > -1;
@@ -346,7 +382,7 @@ class Tokenizer {
     this.stream.unreadEach(token);
   }
   async read() {
-    let macroFun;
+    let macroFun, dispatchChars;
     for await(const x of this.stream) {
       if (isWhitespace(x)) {
         continue;
@@ -355,7 +391,17 @@ class Tokenizer {
         if (vals.length) {
           return vals[0];
         } else {
-          continue
+          continue;
+        }
+      } else if (dispatchChars = READTABLE.val().getDispatchChar(x)) {
+        const dispCh = await this.stream.read();
+        if (!dispatchChars.has(dispCh))
+          throw new Error(`Unknown char '${dispCh}' for dispatch macro '${x}'`);
+        const vals = await dispatchChars.get(dispCh)(this.stream);
+        if (vals.length) {
+          return vals[0];
+        } else {
+          continue;
         }
       } else if (x === '\\') {
         let y = await this.stream.read();