author | Alan Dipert
<alan@dipert.org> 2019-09-06 06:35:08 UTC |
committer | Alan Dipert
<alan@dipert.org> 2019-09-06 06:35:08 UTC |
parent | b68c6c5dfc3ec068befb0a1ab76b4b5c3d59f51f |
jacl-tests.js | +20 | -4 |
jacl.js | +42 | -9 |
diff --git a/jacl-tests.js b/jacl-tests.js index f92abb3..b2f0521 100644 --- a/jacl-tests.js +++ b/jacl-tests.js @@ -41,17 +41,33 @@ QUnit.test('Symbols', async is => { sym = await read1(String.raw`\alan `); is.strictEqual(sym.name, 'aLAN', 'simple symbol with escape'); - Package.makePackage('TEST-PACKAGE'); + const testPkg = Package.makePackage('TEST-PACKAGE'), + plusOne = testPkg.intern("1+"); - sym = await read1('test-package::+ '); - is.strictEqual(sym.name, '+', 'exported symbol'); + testPkg.exportSymbol("1+"); + sym = await read1('test-package:1+ '); + is.strictEqual(sym.name, '1+', 'exported symbol'); is.strictEqual(sym.packageName, 'TEST-PACKAGE', 'exported symbol'); - // TODO external vs internal + is.rejects( + read1('test-package:omg ', false), + /Symbol 'OMG' not found in package 'TEST-PACKAGE'/, + 'non-existent symbol not found' + ); + [rdr, read1] = freshRdr(is); + + testPkg.intern("some symbol") sym = await read1('|TEST-PACKAGE::some symbol| '); is.strictEqual(sym.name, 'some symbol', 'pipe/qualified symbol'); is.strictEqual(sym.packageName, 'TEST-PACKAGE', 'pipe/qualified symbol'); + is.rejects( + read1('|TEST-PACKAGE:some symbol| ', false), + /Symbol 'some symbol' not external in package 'TEST-PACKAGE'/, + 'private symbol reference throws' + ); + [rdr, read1] = freshRdr(is); + sym = await read1(':some-lil-kw '); is.strictEqual(sym.name, 'SOME-LIL-KW', 'kw name'); is.strictEqual(sym.packageName, 'KEYWORD', 'kw package'); diff --git a/jacl.js b/jacl.js index c35348f..12bc52d 100644 --- a/jacl.js +++ b/jacl.js @@ -76,20 +76,31 @@ class LispSymbol { static isKeyword(token) { return /^:[^:]+$/.test(token); } - static getPackageAndName(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) return match.slice(1); + if (match) { + let [,pkgName, name] = match; + return [name, pkgName, true]; + } match = token.match(doubleMarkerRe); - if (match) return match.slice(1); + if (match) { + let [,pkgName, name] = match; + return [name, pkgName, false]; + } - if (token.indexOf(':') < 0) return [null, token]; + if (token.indexOf(':') < 0) return [token, null, null]; throw new Error(`Unknown symbol syntax: '${token}'`); } + // intern: whether or not the symbol should be interned static createFromString(str, intern) { if (typeof str !== 'string') throw new Error(`Can only intern strings`); @@ -97,11 +108,19 @@ class LispSymbol { return LispSymbol.intern('KEYWORD', str.substring(1)); } - let [pkgName, name] = LispSymbol.getPackageAndName(str); + let [name, pkgName, isExternal] = LispSymbol.parseSymbol(str); if (intern) { - if (!pkgName) pkgName = PACKAGE.val().name; - return LispSymbol.intern(pkgName, name); + if (pkgName) { + const pkg = Package.get(pkgName, true); + if (!pkg.hasSymbol(name)) + throw new Error(`Symbol '${name}' not found in package '${pkgName}'`); + if (isExternal && !pkg.isExported(name)) + throw new Error(`Symbol '${name}' not external in package '${pkgName}'`); + return pkg.intern(name); + } else { + return PACKAGE.val().intern(name); + } } else { return new LispSymbol(name, null); } @@ -124,8 +143,13 @@ const PACKAGES = new Map(); class Package { constructor(name) { this.name = name; + // Map of name strings to LispSymbol objects contained in this package + // Note that names may refer to symbols from different packages if they are + // imported this.symbols = new Map(); - this.exports = new Map(); + // Set of names (keys of this.symbols) that are exported + this.exports = new Set(); + // List of packages, the symbols of which to also search this.use = []; } intern(name) { @@ -134,9 +158,18 @@ class Package { sym = new LispSymbol(name, this.name); this.symbols.set(name, sym); } - if (this.name === 'KEYWORD') this.exports.set(name, sym); + if (this.name === 'KEYWORD') this.exports.add(name); return sym; } + exportSymbol(name) { + this.exports.add(name); + } + hasSymbol(name) { + return this.symbols.has(name); + } + isExported(name) { + return this.exports.has(name); + } static intern(packageName, name) { const pkg = Package.get(packageName, true); return pkg.intern(name);