git » jacl.git » commit 98be92d

Fix reading package-qualified syms, handle pipes properly

author Alan Dipert
2019-09-15 06:05:06 UTC
committer Alan Dipert
2019-09-15 06:05:06 UTC
parent 4087774c230abca69be060a292f8b62182a8c120

Fix reading package-qualified syms, handle pipes properly

jacl-tests.js +12 -4
jacl.js +52 -37

diff --git a/jacl-tests.js b/jacl-tests.js
index 38c235c..3def041 100644
--- a/jacl-tests.js
+++ b/jacl-tests.js
@@ -31,9 +31,9 @@ QUnit.test('Symbols', async is => {
   var [rdr, read1] = freshRdr(is);
   var sym;
 
-  sym = await read1('defun ');
-  is.strictEqual(sym.name, 'DEFUN', 'simple symbol name');
-  is.strictEqual(sym.packageName, 'COMMON-LISP', 'simple symbol package');
+  sym = await read1('somesym ');
+  is.strictEqual(sym.name, 'SOMESYM', 'simple symbol name');
+  is.strictEqual(sym.packageName, 'JACL', 'simple symbol package');
 
   sym = await read1('|Alan| ');
   is.strictEqual(sym.name, 'Alan', 'simple symbol name');
@@ -58,6 +58,10 @@ QUnit.test('Symbols', async is => {
 
   testPkg1.intern("some symbol")
   sym = await read1('|TEST-PACKAGE1::some symbol| ');
+  is.strictEqual(sym.name, 'TEST-PACKAGE1::some symbol', 'pipe/qualified symbol');
+  is.strictEqual(sym.packageName, 'JACL', 'pipe/qualified symbol');
+
+  sym = await read1('test-package1::|some symbol| ');
   is.strictEqual(sym.name, 'some symbol', 'pipe/qualified symbol');
   is.strictEqual(sym.packageName, 'TEST-PACKAGE1', 'pipe/qualified symbol');
 
@@ -65,8 +69,12 @@ QUnit.test('Symbols', async is => {
   is.strictEqual(sym.name, 'ANOTHER-SYMBOL', 'symbol created externally');
   is.strictEqual(sym.packageName, 'TEST-PACKAGE1', 'symbol created externally');
 
+  sym = await read1("test-package1|::lol| ");
+  is.strictEqual(sym.name, 'TEST-PACKAGE1::lol', 'pipe without package');
+  is.strictEqual(sym.packageName, 'JACL', 'pipe without package has default package');
+
   is.rejects(
-    read1('|TEST-PACKAGE1:some symbol| ', false),
+    read1('test-package1:|some symbol| ', false),
     /Symbol 'some symbol' not external in package 'TEST-PACKAGE1'/,
     'private symbol reference throws'
   );
diff --git a/jacl.js b/jacl.js
index 5c270e7..c7492b3 100644
--- a/jacl.js
+++ b/jacl.js
@@ -72,46 +72,47 @@ class LispSymbol {
       throw new Error(`Symbol name must not be number: '${name}'`);
     return Package.get(packageName, true).intern(name);
   }
-  static isKeyword(token) {
-    return /^:[^:]+$/.test(token);
-  }
   // Returns a triple of name, package, and whether or not the symbol is
   // external. For example, the toke 'foo:bar' would cause this to be returned:
   // ['bar', 'foo', true]. If the symbol is unqualified, external is null. For
   // example: 'foo' => ['foo', null, null]
   static parseSymbol(token) {
-    const singleMarkerRe = /^([^:]+):([^:]+)$/,
-          doubleMarkerRe = /^([^:]+)::([^:]+)$/;
-
-    let match = token.match(singleMarkerRe);
-    if (match) {
-      let [,pkgName, name] = match;
-      return [name, pkgName, true];
-    }
 
-    match = token.match(doubleMarkerRe);
-    if (match) {
-      let [,pkgName, name] = match;
-      return [name, pkgName, false];
+    if (token.str.length > 2 
+        && token.str[0] === ':'
+        && token.firstPipe !== 0)
+      return [token.str.substring(1), 'KEYWORD', true];
+
+    let accum = '';
+
+    for (let i = 0; i < token.str.length; i++) {
+      if (token.str[i] === ':'
+          && token.str.length > i+2
+          && token.str[i+1] === ':'
+          && (token.firstPipe === null || token.firstPipe >= i+1)) {
+        return [token.str.substring(i+2), accum, false];
+      } else if (token.str[i] === ':'
+          && token.str.length > i+1
+          && token.str[i+1] !== ':'
+          && (token.firstPipe === null || token.firstPipe >= i)) {
+        return [token.str.substring(i+1), accum, true];
+      } else {
+        accum += token.str[i];
+      }
     }
 
-    if (token.indexOf(':') < 0) return [token, null, null];
-
-    throw new Error(`Unknown symbol syntax: '${token}'`);
+    return [accum, null, null];
   }
   static kw(kwName, upcase = true) {
     kwName = upcase ? kwName.toUpperCase() : kwName;
     return LispSymbol.intern('KEYWORD', kwName);
   }
   // intern: whether or not the symbol should be interned
-  static createFromString(str, intern) {
-    if (typeof str !== 'string') throw new Error(`Can only intern strings`);
+  static createFromString(token, intern) {
 
-    if (LispSymbol.isKeyword(str)) {
-      return LispSymbol.intern('KEYWORD', str.substring(1));
-    }
+    let [name, pkgName, singleColon] = LispSymbol.parseSymbol(token);
 
-    let [name, pkgName, singleColon] = LispSymbol.parseSymbol(str);
+    if (pkgName === 'KEYWORD') return LispSymbol.intern('KEYWORD', name);
 
     if (intern) {
       if (pkgName) {
@@ -235,7 +236,7 @@ Package.makePackage('COMMON-LISP-USER', 'CL-USER');
 Package.makePackage('KEYWORD');
 
 const PACKAGE = Package.intern('CL', '*PACKAGE*');
-PACKAGE.value = Package.get('CL');
+PACKAGE.value = Package.get('JACL');
 
 class BufferedStream {
   constructor() {
@@ -330,11 +331,11 @@ class ReadTable {
 
 const READTABLE = Package.intern('CL', '*READTABLE*');
 
-const interpretToken = (str, intern) => {
-  const [isInt, intVal] = readInteger(str);
+const interpretToken = (token, intern) => {
+  const [isInt, intVal] = readInteger(token.str);
   if (isInt) return intVal;
 
-  return LispSymbol.createFromString(str, intern);
+  return LispSymbol.createFromString(token, intern);
 };
 
 const skipWhitespace = async stream => {
@@ -458,16 +459,30 @@ const isConstituent = ch => {
     || /[0-9]/.test(ch);
 }
 
+class Token {
+  constructor(initial = '') {
+    this.str = initial;
+    this.firstPipe = null;
+  }
+  add(ch) {
+    this.str += ch;
+  }
+  sawPipe() {
+    if (this.firstPipe === null) this.firstPipe = this.str.length;
+    return this;
+  }
+}
+
 const readMultiEscaped = async function(stream, token, intern = true) {
   for await(const y of stream) {
     if (isConstituent(y) || READTABLE.val().isTerminating(y) || isWhitespace(y)) {
-      token += y;
+      token.add(y);
       continue;
     } else if (y === '\\') {
-      token += await stream.read();
+      token.add(await stream.read());
       continue;
     } else if (y === '|') {
-      return readSingleEscaped(stream, token);
+      return readSingleEscaped(stream, token.sawPipe());
     } else {
       throw new Error(`Illegal character: '${y}'`);
     }
@@ -485,13 +500,13 @@ const readInteger = token => {
 const readSingleEscaped = async function(stream, token, intern = true) {
   for await(const y of stream) {
     if (isConstituent(y)) {
-      token += y.toUpperCase();
+      token.add(y.toUpperCase());
       continue;
     } else if (y === '\\') {
-      token += await stream.read();
+      token.add(await stream.read());
       continue;
     } else if (y === '|') {
-      return readMultiEscaped(stream, token);
+      return readMultiEscaped(stream, token.sawPipe());
     } else if (READTABLE.val().isTerminating(y) || isWhitespace(y) || y === ')') {
       stream.unread(y);
       return interpretToken(token, intern);
@@ -521,12 +536,12 @@ class Reader {
           continue
         }
       } else if (x === '\\') {
-        let y = await this.stream.read();
+        let y = new Token(await this.stream.read());
         return readSingleEscaped(this.stream, y, internSymbols);
       } else if (x === '|') {
-        return readMultiEscaped(this.stream, '', internSymbols);
+        return readMultiEscaped(this.stream, new Token().sawPipe(), internSymbols);
       } else if (isConstituent(x)) {
-        return readSingleEscaped(this.stream, x.toUpperCase(), internSymbols);
+        return readSingleEscaped(this.stream, new Token(x.toUpperCase()), internSymbols);
       } else {
         throw new Error(`Illegal character: '${x}'`);
       }