const AST = require('./ast') const Error = require('./Error') const tokenTypes = require('./tokenTypes') module.exports = class Parser { constructor(tokenStream) { this.tokenStream = tokenStream } parse() { let tree = [] while (this.tokenStream.peek().type !== tokenTypes.EOF) { let expr = this.expr() tree.push(expr) if (this.tokenStream.error) { return { error: this.tokenStream.error, } } } return tree } expr() { let token = this.tokenStream.peek() switch (token.type) { case tokenTypes.ATTRIBUTE: return this.attribute() case tokenTypes.BOOLEAN: return this.bool() case tokenTypes.IDENTIFIER: return this.identifier() case tokenTypes.NUMBER: return this.number() case tokenTypes.QUOTE: return this.string() case tokenTypes.SYMBOL: return this.symbol() case tokenTypes.OPAREN: return this.form() default: this.tokenStream.error = `Unexpected ${token.type} on line ${ token.line }` break } } form() { this.tokenStream.eat(tokenTypes.OPAREN) let node if (this.tokenStream.peek().value === 'lambda') { this.tokenStream.eat(tokenTypes.IDENTIFIER) node = this.lambda(true) } else { node = new AST.Application() node.function = this.expr() node.args = [] while (this.tokenStream.peek().type !== tokenTypes.CPAREN) { node.args.push(this.expr()) } } this.tokenStream.eat(tokenTypes.CPAREN) return node } attribute() { return new AST.Attribute({ name: this.tokenStream.eat(tokenTypes.ATTRIBUTE).value, value: this.expr(), }) } bool() { return new AST.Boolean({ value: this.tokenStream.eat(tokenTypes.BOOLEAN).value, }) } lambda() { let parameters = [] this.tokenStream.eat(tokenTypes.OPAREN) while (this.tokenStream.peek().type !== tokenTypes.CPAREN) { parameters.push( new AST.Identifier({ name: this.tokenStream.eat(tokenTypes.IDENTIFIER).value, }), ) } this.tokenStream.eat(tokenTypes.CPAREN) return new AST.Lambda({ parameters: parameters, body: this.form(), }) } identifier() { return new AST.Identifier({ name: this.tokenStream.eat(tokenTypes.IDENTIFIER).value, }) } number() { return new AST.Number({ value: this.tokenStream.eat(tokenTypes.NUMBER).value, }) } string() { this.tokenStream.eat(tokenTypes.QUOTE) let value = '' if (this.tokenStream.peek().type === tokenTypes.LITERAL) { value = this.tokenStream.eat(tokenTypes.LITERAL).value } let node = new AST.String({ value: value, }) this.tokenStream.eat(tokenTypes.QUOTE) return node } symbol() { return new AST.Symbol({ value: this.tokenStream.eat(tokenTypes.SYMBOL).value, }) } }