author | Alan Dipert
<alan@dipert.org> 2019-09-15 06:05:06 UTC |
committer | Alan Dipert
<alan@dipert.org> 2019-09-15 06:05:06 UTC |
parent | 4087774c230abca69be060a292f8b62182a8c120 |
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}'`); }