const AST = require('./ast') const OsloError = require('./OsloError') const tokenTypes = require('./tokenTypes') module.exports = class Parser { parse(tokenStream) { this.tokenStream = tokenStream 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() } else if (this.tokenStream.peek().value === 'define') { this.tokenStream.eat(tokenTypes.IDENTIFIER) node = this.definition() } else if (this.tokenStream.peek().value === 'if') { this.tokenStream.eat(tokenTypes.IDENTIFIER) node = this.conditional() } else if (this.tokenStream.peek().value === 'let') { this.tokenStream.eat(tokenTypes.IDENTIFIER) node = this.let() } 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, }) } conditional() { return new AST.Conditional({ condition: this.expr(), ifCase: this.expr(), elseCase: this.expr(), }) } definition() { return new AST.Definition({ symbol: this.identifier(), value: this.expr(), }) } identifier() { let token = this.tokenStream.eat(tokenTypes.IDENTIFIER) return new AST.Identifier({ name: token.value, line: token.line, }) } 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(), }) } let() { let bindings = [] this.tokenStream.eat(tokenTypes.OPAREN) while (this.tokenStream.peek().type !== tokenTypes.CPAREN) { this.tokenStream.eat(tokenTypes.OPAREN) bindings.push({ key: new AST.Identifier({ name: this.tokenStream.eat(tokenTypes.IDENTIFIER).value, }), value: this.expr(), }) this.tokenStream.eat(tokenTypes.CPAREN) } this.tokenStream.eat(tokenTypes.CPAREN) return new AST.LetBinding({ bindings: bindings, body: this.expr(), }) } 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, }) } }