author | Alan Dipert
<alan@dipert.org> 2019-08-28 05:40:14 UTC |
committer | Alan Dipert
<alan@dipert.org> 2019-08-28 05:40:14 UTC |
parent | a5a0917ae0abaf307e3bc0fe36aee2a7222b25a1 |
jacl-tests.js | +6 | -7 |
jacl.js | +71 | -54 |
diff --git a/jacl-tests.js b/jacl-tests.js index 022845d..56bee98 100644 --- a/jacl-tests.js +++ b/jacl-tests.js @@ -5,7 +5,7 @@ QUnit.module('Reader'); const makeRead1 = (rdr, it) => { return async (str, timeout = true) => { const done = timeout ? it.async() : null; - rdr.tokenizer.stream.writeEach(str); + rdr.stream.writeEach(str); const obj = await rdr.read(); if (done) done(); return obj; @@ -13,7 +13,7 @@ const makeRead1 = (rdr, it) => { }; const freshRdr = (is) => { - const rdr = Reader.of(Tokenizer.of(new BufferedStream())); + const rdr = new Reader(new BufferedStream()); return [rdr, makeRead1(rdr, is)]; } @@ -58,7 +58,7 @@ QUnit.test('Symbols', async is => { }); -QUnit.skip('Conses', async is => { +QUnit.test('Conses', async is => { var [rdr, read1] = freshRdr(is); var cons; @@ -101,9 +101,8 @@ QUnit.skip('Conses', async is => { is.deepEqual(Array.from(cons), [1,2], 'convert to pair'); cons = await read1('((1 . 2))'); - console.log(cons); - //is.deepEqual(Array.from(cons), [[1,2]], 'convert to nested pair'); + 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('(1 (2 . 3) 4)'); + is.deepEqual(Array.from(cons), [1,[2,3],4], 'convert to nested array'); }); diff --git a/jacl.js b/jacl.js index 2822796..6dc1a78 100644 --- a/jacl.js +++ b/jacl.js @@ -225,46 +225,55 @@ const READTABLE = Package.intern('CL', '*READTABLE*'); class Token extends String { interpret() { - const [isInt, intVal] = readInteger(this); + const prim = this.valueOf(); + + const [isInt, intVal] = readInteger(prim); if (isInt) return intVal; - return LispSymbol.internFromString(this.valueOf()); + return LispSymbol.internFromString(prim); } static is(x, y) { return (x instanceof Token) && x.valueOf() === y; } } -const LIST_CLOSE_PAREN = new Object(); - const readList = async stream => { - const tok = Tokenizer.of(stream), - rdr = Reader.of(tok); + const rdr = new Reader(stream), + x = await rdr.safeRead(); - const t = await tok.read(); + if (x instanceof DotError) + throw new Error(`Nothing before . in list`); - if (t === LIST_CLOSE_PAREN) return new Values(null); - if (Token.is(t, '.')) throw new Error(`Nothing before . in list`); + if (x instanceof CloseParenError) + return new Values(null); - tok.unread(t); + const car = x, + after = await rdr.safeRead(); - const car = await rdr.read(), - after = await tok.read(); - - if (Token.is(after, '.')) { - const cons = new Cons(car, await rdr.read()); - if (await tok.read() === LIST_CLOSE_PAREN) { + if (after instanceof DotError) { + const y = await rdr.safeRead(); + if (y instanceof Error) + throw new Error(`Nothing after . in list`); + const cons = new Cons(car, y); + if ((await rdr.safeRead()) instanceof CloseParenError) { return new Values(cons); } else { throw new Error(`More than one object after . in list`); } } - if (after === LIST_CLOSE_PAREN) return new Values(new Cons(car)); + if (after instanceof CloseParenError) + return new Values(new Cons(car)); - tok.unread(after); + return new Values(new Cons(car, new Cons(after, (await readList(stream))[0]))); +} - return new Values(new Cons(car, (await readList(stream))[0])); +class DotError extends Error { + constructor() { super('. context error'); } +} + +class CloseParenError extends Error { + constructor() { super('Unmatched )'); } } READTABLE.value = new ReadTable() @@ -285,7 +294,8 @@ READTABLE.value = new ReadTable() } } }) - .setMacro(')', true, async stream => new Values(LIST_CLOSE_PAREN)) + .setMacro('.', false, async stream => { throw new DotError(); }) + .setMacro(')', true, async stream => { throw new CloseParenError(); }) .setMacro('(', true, readList) .setMacro("'", true, async stream => { return new Values(Cons.listOf( @@ -338,23 +348,31 @@ const readSingleEscaped = async function(stream, token) { return readMultiEscaped(stream, token); } else if (READTABLE.val().isTerminating(y) || isWhitespace(y)) { stream.unread(y); - return new Token(token); + return new Token(token).interpret(); } else { throw new Error(`Illegal character: '${y}'`); } } }; - -class Tokenizer { +class Reader { constructor(stream) { this.stream = stream; } static of(stream) { - return new Tokenizer(stream); - } - unread(token) { - this.stream.unreadEach(token); + return new Reader(stream); + } + async safeRead() { + var x; + try { + x = await this.read(); + } catch (e) { + if (e instanceof DotError || e instanceof CloseParenError) { + return e; + } + throw e; + } + return x; } async read() { let macroFun; @@ -389,35 +407,34 @@ class Tokenizer { } } -class Reader { - constructor(tokenizer) { - this.tokenizer = tokenizer; - } - static of(tokenizer) { - return new Reader(tokenizer); - } - async read() { - const obj = await this.tokenizer.read(); - if (obj === LIST_CLOSE_PAREN) { - throw new Error(`Unmatched ')'`); - } else if (obj instanceof Token) { - return obj.interpret(); - } else { - return obj; - } - } - [Symbol.asyncIterator]() { - return { - next: () => this.read().then(obj => { - return { value: obj, done: false }; - }) - } - } -} +//class Reader { +// constructor(tokenizer) { +// this.tokenizer = tokenizer; +// } +// static of(tokenizer) { +// return new Reader(tokenizer); +// } +// async read() { +// const obj = await this.tokenizer.read(); +// if (obj === LIST_CLOSE_PAREN) { +// throw new Error(`Unmatched ')'`); +// } else if (obj instanceof Token) { +// return obj.interpret(); +// } else { +// return obj; +// } +// } +// [Symbol.asyncIterator]() { +// return { +// next: () => this.read().then(obj => { +// return { value: obj, done: false }; +// }) +// } +// } +//} var buf = new BufferedStream(), - tok = new Tokenizer(buf), - rdr = new Reader(tok); + rdr = new Reader(buf); (async function() { for await(const obj of rdr) {