author | Alan Dipert
<alan@dipert.org> 2019-09-26 07:32:36 UTC |
committer | Alan Dipert
<alan@dipert.org> 2019-09-26 07:32:36 UTC |
parent | bf88dcff9fa18201cfc1daab2a7cf2a2b0fd284d |
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!"))