const Node = require('./node') const tokenTypes = require('./tokenTypes') module.exports = class Parser { constructor(tokenStream) { this.tokenStream = tokenStream } parse() { let tree = [] while (this.tokenStream.peek().type !== tokenTypes.EOF) { tree.push(this.expr()) } return tree } expr() { let node this.tokenStream.eat(tokenTypes.OPAREN) while ( this.tokenStream.peek().type !== tokenTypes.CPAREN && this.tokenStream.peek().type !== tokenTypes.EOF ) { if (this.tokenStream.peek().type === tokenTypes.LITERAL) { node = this.element() } else if (this.tokenStream.peek().type === tokenTypes.KEYWORD) { node = this.keyword() } } this.tokenStream.eat(tokenTypes.CPAREN) return node } element() { let elementNode = new Node({ type: 'functionCall', args: [], subtree: [], functionName: this.tokenStream.eat(tokenTypes.LITERAL).value, }) while ( ![tokenTypes.CPAREN, tokenTypes.EOF].includes( this.tokenStream.peek().type, ) ) { if (this.tokenStream.peek().type === tokenTypes.ATTRIBUTE) { elementNode.args.push(this.attribute()) } else if (this.tokenStream.peek().type === tokenTypes.QUOTE) { elementNode.subtree.push(this.quotedString()) } else if (this.tokenStream.peek().type === tokenTypes.OPAREN) { elementNode.subtree.push(this.expr()) } else if (this.tokenStream.peek().type === tokenTypes.LITERAL) { elementNode.subtree.push(this.identifier()) } } return elementNode } attribute() { let attributeNode = new Node() attributeNode.attributeName = this.tokenStream.eat( tokenTypes.ATTRIBUTE, ).value if (this.tokenStream.peek().type === tokenTypes.QUOTE) { attributeNode.attributeValue = this.quotedString() } else if (this.tokenStream.peek().type === tokenTypes.LITERAL) { attributeNode.attributeValue = this.identifier() } return attributeNode } identifier() { const identifier = this.tokenStream.eat(tokenTypes.LITERAL) return new Node({ type: 'identifier', name: identifier.value, }) } quotedString() { return new Node({ type: 'string', content: this.string(), }) } keyword() { const keyword = this.tokenStream.eat(tokenTypes.KEYWORD) if (keyword.value === 'each') { const subject = this.identifier() const symbol = this.tokenStream.eat(tokenTypes.SYMBOL) const body = this.expr() return new Node({ type: 'each', symbol: symbol, body: body, subject: subject, }) } } string() { this.tokenStream.eat(tokenTypes.QUOTE) let stringValue = this.tokenStream.eat(tokenTypes.LITERAL).value this.tokenStream.eat(tokenTypes.QUOTE) return stringValue } }