author | Alan Dipert
<alan@dipert.org> 2019-08-14 16:45:53 UTC |
committer | Alan Dipert
<alan@dipert.org> 2019-08-14 16:45:53 UTC |
parent | 61fdc40193cc88c8e0cb7514bf10873b1296d6e6 |
jacl.js | +45 | -33 |
diff --git a/jacl.js b/jacl.js index dccb9cd..7eb4eda 100644 --- a/jacl.js +++ b/jacl.js @@ -1,6 +1,4 @@ -const KEYS = { - UNBOUND: Symbol() -}; +const UNDEFINED = new Object(); class Cons { constructor(car, cdr = null) { @@ -36,17 +34,17 @@ class LispSymbol { constructor(name, packageName) { this.name = name; this.package = packageName; - this.value = KEYS.UNBOUND; - this.fvalue = KEYS.UNBOUND; + this.value = UNDEFINED; + this.fvalue = UNDEFINED; this.stack = []; } val() { - if (this.value === KEYS.UNBOUND) + if (this.value === UNDEFINED) throw new Error(`Variable '${this.name}' unbound`); return this.value; } func() { - if (this.fvalue === KEYS.UNBOUND) + if (this.fvalue === UNDEFINED) throw new Error(`Function '${this.name}' unbound`); return this.fvalue; } @@ -151,7 +149,9 @@ class BufferedStream { for (const x of xs) this.write(x); } unreadEach(xs) { - for (const x of xs) this.unread(x); + for (let i = xs.length; i--; i >= 0) { + this.unread(xs[i]); + } } unread(obj) { if (this.resolveQueue.length) { @@ -201,32 +201,38 @@ class Token extends String { interpret() { return readInteger(this) || LispSymbol.fromString(this) } + static is(x, y) { + return (x instanceof Token) && x.valueOf() === y; + } } -const CLOSE_PAREN = new Object(); +const LIST_CLOSE_PAREN = new Object(); +// TODO broken const readList = async stream => { - let x = await (new Tokenizer(stream)).nextToken() - if (x === CLOSE_PAREN) { + let tok = Tokenizer.of(stream), + rdr = Reader.of(tok), + x = await tok.read(); + if (x === LIST_CLOSE_PAREN) { return new Values(null); + } else if (Token.is(x, '.')) { + throw new Error(`Nothing before . in list`); } else { - if (x instanceof Token) { - stream.unreadEach(x); - x = await (new Reader(new Tokenizer(stream))).read(); + tok.unread(x); + let car = await rdr.read(), + maybeDot = await tok.read(); + if (Token.is(maybeDot, '.')) { + let cons = new Values(new Cons(car, await rdr.read())), + maybeClose = await tok.read(); + if (maybeClose === LIST_CLOSE_PAREN) { + return cons; + } else { + throw new Error(`More than one object after . in list`); + } + } else { + return new Values(new Cons(car, (await readList(stream))[0])); } - return new Values(new Cons(x, (await readList(stream))[0])); - } - //while (true) { - // const x = await rdr.read(); - // if (x === CLOSE_PAREN) { - // return new Values(head); - // } else if (cons !== null) { - // cons.cdr = new Cons(x); - // cons = cons.cdr; - // } else { - // cons = head = new Cons(x); - // } - //} + } } READTABLE.value = new ReadTable() @@ -247,7 +253,7 @@ READTABLE.value = new ReadTable() } } }) - .setMacro(')', true, async stream => new Values(CLOSE_PAREN)) + .setMacro(')', true, async stream => new Values(LIST_CLOSE_PAREN)) .setMacro('(', true, readList); const isWhitespace = ch => ' \t\n\r\b'.indexOf(ch) > -1; @@ -315,10 +321,13 @@ class Tokenizer { constructor(stream) { this.stream = stream; } + static of(stream) { + return new Tokenizer(stream); + } unread(token) { - this.stream.writeEach(token); + this.stream.unreadEach(token); } - async nextToken() { + async read() { let macroFun; for await(const x of this.stream) { if (isWhitespace(x)) { @@ -344,7 +353,7 @@ class Tokenizer { } [Symbol.asyncIterator]() { return { - next: () => this.nextToken().then(obj => { + next: () => this.read().then(obj => { return { value: obj, done: false }; }) } @@ -355,9 +364,12 @@ class Reader { constructor(tokenizer) { this.tokenizer = tokenizer; } + static of(tokenizer) { + return new Reader(tokenizer); + } async read() { - const obj = await this.tokenizer.nextToken(); - if (obj === CLOSE_PAREN) { + const obj = await this.tokenizer.read(); + if (obj === LIST_CLOSE_PAREN) { throw new Error(`Unmatched ')'`); } else if (obj instanceof Token) { return obj.interpret();