123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- const Error = require('./Error')
- 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())
- if (this.tokenStream.error) {
- return {
- error: this.tokenStream.error,
- }
- }
- }
- 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.error) return
-
- 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.error) return
-
- 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()
- } else {
- let token = this.tokenStream.peek()
- this.tokenStream.error = new Error({
- line: token.line,
- message: `Encountered an unexpected ${token.type} while looking for a string or 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
- }
- }
|