author | Alan Dipert
<alan@dipert.org> 2019-08-18 12:54:00 UTC |
committer | Alan Dipert
<alan@dipert.org> 2019-08-18 12:54:00 UTC |
parent | 465be05a933bb6ce1c609f3fa916e068e19d9b55 |
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();