author | Alan Dipert
<alan@dipert.org> 2019-08-14 19:44:24 UTC |
committer | Alan Dipert
<alan@dipert.org> 2019-08-14 19:44:24 UTC |
parent | bb8491fe18383ab5b48978ef567ab4b04b72877f |
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}'`); }