git » jacl.git » commit e4b264c

Printing symbols, roundtripping pipes etc

author Alan Dipert
2019-08-14 19:44:24 UTC
committer Alan Dipert
2019-08-14 19:44:24 UTC
parent bb8491fe18383ab5b48978ef567ab4b04b72877f

Printing symbols, roundtripping pipes etc

jacl.js +66 -17

diff --git a/jacl.js b/jacl.js
index 0f718af..00a7378 100644
--- a/jacl.js
+++ b/jacl.js
@@ -8,6 +8,12 @@ class Cons {
   static coerce(x) {
     return x instanceof Cons ? Array.from(x) : x;
   }
+  static listOf(...xs) {
+    let list = null;
+    for(let i = xs.length-1; i >= 0; i--)
+      list = new Cons(xs[i], list);
+    return list;
+  }
   [Symbol.iterator]() {
     let ptr = this, proper = true, done = false;
     return {
@@ -37,6 +43,16 @@ class LispSymbol {
     this.value = UNDEFINED;
     this.fvalue = UNDEFINED;
     this.stack = [];
+    this.printPiped = true;
+    this.packageSingle = true;
+  }
+  setPrintPiped(x) {
+    this.printPiped = x;
+    return this;
+  }
+  setPackageSingle(x) {
+    this.packageSingle = x;
+    return this;
   }
   val() {
     if (this.value === UNDEFINED)
@@ -62,25 +78,46 @@ class LispSymbol {
       throw new Error(`Symbol name must not be number: '${name}'`);
     return Package.get(packageName, true).intern(name);
   }
-  static fromString(token) {
+  static fromString(token, printPiped = true) {
     if (/^:[^:]+$/.test(token)) {
-      return LispSymbol.intern('KEYWORD', token.substring(1));
+      return LispSymbol.intern('KEYWORD', token.substring(1)).setPrintPiped(printPiped);
     }
 
     const singleMarkerRe = /^([^:]+):([^:]+)$/;
     const doubleMarkerRe = /^([^:]+)::([^:]+)$/;
 
-    for (const re of [singleMarkerRe, doubleMarkerRe]) {
-      const match = token.match(re);
-      if (match) return LispSymbol.intern(...match.slice(1));
+    let match = token.match(singleMarkerRe);
+    if (match) {
+      return LispSymbol
+        .intern(...match.slice(1))
+        .setPrintPiped(printPiped);
+    }
+
+    match = token.match(doubleMarkerRe);
+    if (match) {
+      return LispSymbol.intern(...match.slice(1))
+        .setPrintPiped(printPiped)
+        .setPackageSingle(false);
     }
 
     if (token.indexOf(':') < 0) {
-      return PACKAGE.val().intern(token);
+      return PACKAGE.val().intern(token).setPrintPiped(printPiped);
     }
 
     throw new Error(`Unknown symbol syntax: '${token}'`);
   }
+  toString() {
+    let str = ''
+    if (this.printPiped) str += '|';
+    if (this.package && this.package != PACKAGE.value.name) {
+      str += this.package;
+      if (this.package && this.packageSingle) str += ':';
+      if (this.package && !this.packageSingle) str += '::';
+    }
+    str += this.name;
+    if (this.printPiped) str += '|';
+    return str;
+  }
 }
 
 class LispString extends Array {
@@ -197,9 +234,13 @@ class ReadTable {
 const READTABLE = Package.get('CL').intern('*READTABLE*');
 
 class Token extends String {
+  constructor(printPiped, str) {
+    super(str);
+    this.printPiped = printPiped;
+  }
   // TODO figure out implications of jacl:undefined/jacl:true/jacl:false etc here
   interpret() {
-    return readInteger(this) || LispSymbol.fromString(this)
+    return readInteger(this) || LispSymbol.fromString(this, this.printPiped)
   }
   static is(x, y) {
     return (x instanceof Token) && x.valueOf() === y;
@@ -210,7 +251,7 @@ const LIST_CLOSE_PAREN = new Object();
 
 const readList = async stream => {
   const tok = Tokenizer.of(stream),
-    rdr = Reader.of(tok);
+        rdr = Reader.of(tok);
 
   let t = await tok.read();
 
@@ -219,8 +260,8 @@ const readList = async stream => {
 
   tok.unread(t);
 
-  let car = await rdr.read(),
-    after = await tok.read();
+  let car   = await rdr.read(),
+      after = await tok.read();
 
   if (Token.is(after, '.')) {
     let cons = new Cons(car, await rdr.read());
@@ -257,7 +298,13 @@ READTABLE.value = new ReadTable()
     }
   })
   .setMacro(')', true, async stream => new Values(LIST_CLOSE_PAREN))
-  .setMacro('(', true, readList);
+  .setMacro('(', true, readList)
+  .setMacro("'", true, async stream => {
+    return new Values(Cons.listOf(
+      Package.get('CL').intern('QUOTE'),
+      await Reader.of(Tokenizer.of(stream)).read()
+    ));
+  });
 
 const isWhitespace = ch => ' \t\n\r\b'.indexOf(ch) > -1;
 
@@ -278,13 +325,14 @@ const isConstituent = ch => {
     || /[0-9]/.test(ch);
 }
 
-const readMultiEscaped = async function(stream, token) {
+const readMultiEscaped = async function(stream, token, printPiped) {
   for await(const y of stream) {
     if (isConstituent(y) || READTABLE.val().isTerminating(y) || isWhitespace(y)) {
       token += y;
       continue;
     } else if (y === '\\') {
       token += await stream.read();
+      printPiped = true;
       continue;
     } else if (y === '|') {
       return readSingleEscaped(stream, token);
@@ -300,19 +348,20 @@ const readInteger = token => {
   }
 };
 
-const readSingleEscaped = async function(stream, token) {
+const readSingleEscaped = async function(stream, token, printPiped) {
   for await(const y of stream) {
     if (isConstituent(y)) {
       token += y.toUpperCase();
       continue;
     } else if (y === '\\') {
       token += await stream.read();
+      printPiped = true;
       continue;
     } else if (y === '|') {
       return readMultiEscaped(stream, token);
     } else if (READTABLE.val().isTerminating(y) || isWhitespace(y)) {
       stream.unread(y);
-      return new Token(token);
+      return new Token(printPiped, token);
     } else {
       throw new Error(`Illegal character: '${y}'`);
     }
@@ -344,11 +393,11 @@ class Tokenizer {
         }
       } else if (x === '\\') {
         let y = await this.stream.read();
-        return readSingleEscaped(this.stream, y);
+        return readSingleEscaped(this.stream, y, true);
       } else if (x === '|') {
-        return readMultiEscaped(this.stream, '');
+        return readMultiEscaped(this.stream, '', true);
       } else if (isConstituent(x)) {
-        return readSingleEscaped(this.stream, x.toUpperCase());
+        return readSingleEscaped(this.stream, x.toUpperCase(), false);
       } else {
         throw new Error(`Illegal character: '${x}'`);
       }