git » jacl.git » commit a13e850

get more packages runtime stuff working

author Alan Dipert
2019-09-06 06:35:08 UTC
committer Alan Dipert
2019-09-06 06:35:08 UTC
parent b68c6c5dfc3ec068befb0a1ab76b4b5c3d59f51f

get more packages runtime stuff working

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);