git » jacl.git » commit 8b2f22f

Many, many things

author Alan Dipert
2019-09-26 07:32:36 UTC
committer Alan Dipert
2019-09-26 07:32:36 UTC
parent bf88dcff9fa18201cfc1daab2a7cf2a2b0fd284d

Many, many things

jacl-tests.js +0 -6
jacl.js +84 -27
notes.txt +16 -10

diff --git a/jacl-tests.js b/jacl-tests.js
index 3def041..cdd2a58 100644
--- a/jacl-tests.js
+++ b/jacl-tests.js
@@ -167,12 +167,6 @@ QUnit.test('Conses', async is => {
   cons = await read1('(1 . 2)');
   is.deepEqual(Array.from(cons), [1,2], 'convert to pair');
 
-  cons = await read1('((1 . 2))');
-  is.deepEqual(Array.from(cons), [[1,2]], 'convert to nested pair');
-
-  cons = await read1('(1 (2 . 3) 4)');
-  is.deepEqual(Array.from(cons), [1,[2,3],4], 'convert to nested array');
-
   cons = await read1('((((x . y))))');
   is.strictEqual(cons.car.car.car.car.name, 'X', 'very deep pair');
   is.strictEqual(cons.car.car.car.cdr.name, 'Y', 'very deep pair');
diff --git a/jacl.js b/jacl.js
index 2f160b8..9ba7698 100644
--- a/jacl.js
+++ b/jacl.js
@@ -5,9 +5,6 @@ class Cons {
     this.car = car;
     this.cdr = cdr;
   }
-  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);
@@ -23,12 +20,12 @@ class Cons {
           return { done: true };
         } else if (!proper) {
           done = true;
-          return { value: Cons.coerce(ptr.cdr), done: false };
+          return { value: ptr.cdr, done: false };
         } else if (ptr !== null && !(ptr.cdr instanceof Cons || ptr.cdr === null)) {
           proper = false;
-          return { value: Cons.coerce(ptr.car), done: false };
+          return { value: ptr.car, done: false };
         } else {
-          let ret = { value: Cons.coerce(ptr.car), done: false }
+          let ret = { value: ptr.car, done: false }
           ptr = ptr.cdr;
           return ret;
         }
@@ -44,6 +41,7 @@ class LispSymbol {
     this.value = UNDEFINED;
     this.fvalue = UNDEFINED;
     this.stack = [];
+    this.isMacro = false;
   }
   val() {
     if (this.value === UNDEFINED)
@@ -55,6 +53,10 @@ class LispSymbol {
       throw new Error(`Function '${this.name}' unbound`);
     return this.fvalue;
   }
+  setMacro() {
+    this.isMacro = true;
+    return this;
+  }
   pushBinding(v) {
     this.stack.push(this.value);
     this.value = v;
@@ -73,12 +75,12 @@ class LispSymbol {
     return Package.get(packageName, true).intern(name);
   }
   // 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:
+  // external. For example, the token '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) {
 
-    if (token.str.length > 2 
+    if (token.str.length > 2
         && token.str[0] === ':'
         && token.firstPipe !== 0)
       return [token.str.substring(1), 'KEYWORD', true];
@@ -556,27 +558,79 @@ class Reader {
   }
 }
 
-const JSPKG = Package.makePackage('JS');
-
-JSPKG.intern('GLOBAL');
-JSPKG.exportSymbol('GLOBAL');
-
-const compile = (form, env, retctx) => {
-  if (retctx) {
-    if (form instanceof Number || typeof form === 'number') {
-      return form.toString();
-    } else if (form instanceof LispString) {
-      return `(LispString.fromString("${form}"))`;
-    } else if (form instanceof Cons) {
-      const car = form.car;
-      if (car instanceof LispSymbol
-        && car.getPackage().name === 'JS'
-        && car.name === 'GLOBAL') {
-        return form.cdr.car.name;
+const JACLPKG = Package.get('JACL', true);
+
+// Special forms related to interop
+for (const s of ['JS', 'DOT']) {
+  JACLPKG.intern(s);
+  JACLPKG.exportSymbol(s);
+}
+
+// Primitive functions related to interop
+JACLPKG.intern('TO-JS').fvalue = x => {
+  if (x instanceof LispString) {
+    return x.toString();
+  } else {
+    throw new Error(`Don't know how to convert ${x} to a JS value`);
+  }
+};
+JACLPKG.exportSymbol('TO-JS');
+
+JACLPKG.intern('.').setMacro().fvalue = function(topic, ...ops) {
+  if (arguments.length < 2) throw new Error(`\. requires at least two arguments`);
+  return ops.reduce((prev, op) => {
+    if (op instanceof LispSymbol) {
+      return Cons.listOf(JACLPKG.intern('DOT'), prev, op);
+    } else if (op instanceof Cons) {
+      const [method, ...args] = op;
+      return Cons.listOf(Cons.listOf(JACLPKG.intern('DOT'), prev, method), ...args);
+    } else {
+      throw new Error(`Invalid \. syntax: ${op}`);
+    }
+  }, topic);
+};
+JACLPKG.exportSymbol('.');
+
+const isMacroForm = form => {
+  return form instanceof Cons
+    && form.car
+    && form.car instanceof LispSymbol
+    && form.car.isMacro;
+};
+
+const compile = (form, env) => {
+  if (form instanceof Number || typeof form === 'number') {
+    return form.toString();
+  } else if (form instanceof LispString) {
+    return `(LispString.fromString("${form}"))`;
+  } else if (isMacroForm(form)) {
+    while (isMacroForm(form)) {
+      const [sym, ...args] = form;
+      form = sym.fvalue(...args);
+    }
+    return compile(form, env);
+  } else if (form instanceof Cons) {
+    const [op, ...args] = form,
+          [arg1, arg2]  = args;
+    if (JACLPKG.intern('JS') === op) {
+      if (arg1 instanceof LispSymbol) {
+        return arg1.name;
+      } else {
+        return arg1.toString();
       }
+    } else if (JACLPKG.intern('DOT') === op) {
+      return `${compile(arg1, env)}.${arg2.name}`
+    } else if (op instanceof LispSymbol) {
+      return `LispSymbol.intern('${op.packageName}', '${op.name}').func()(` +
+        args.map(x => compile(x, env)).join(',') +
+        `)`;
+    } else {
+      return `${compile(op, env)}(`+
+        args.map(x => compile(x, env)).join(',') +
+        `)`
     }
-  } else {
-    // TODO
+  } else if (form === null) {
+    return 'null';
   }
 };
 
@@ -586,6 +640,9 @@ var buf = new BufferedStream(),
 (async function() {
   for await(const obj of rdr) {
     console.log("read:", obj);
+    console.log("compiled:", compile(obj, null));
+    console.log("evaled:", eval(compile(obj, null)));
   }
 })()
 
+// buf.writeEach(String.raw`(\. (js |window|) (|alert| (to-js "hello, world!")))`)
diff --git a/notes.txt b/notes.txt
index 61ac84d..c8c0051 100644
--- a/notes.txt
+++ b/notes.txt
@@ -1,15 +1,16 @@
 (use-package :js)
 
-; js::global
-; js::window
-; js::dot
-; js::js-string
-; js::enable-js-syntax (for @"js strings")
-; js::+null+
-; js::+true+
-; js::+false+
-; js::+undefined+
-; js::\.
+; jacl:js
+; jacl:dot
+; jacl:to-js
+; jacl:\.
+
+; js:enable-js-syntax (for @"js strings")
+; js:+null+
+; js:+true+
+; js:+false+
+; js:+undefined+
+; js:\.
 
 ; global reference
 ; JS: window
@@ -36,3 +37,8 @@ window
 
 
 
+((dot (global |window|) |alert|)
+ ((dot "hello, world!" |toString|)))
+
+((dot (global |window|) |alert|)
+ (to-js "hello, world!"))