UNPKG

ts-fusion-parser

Version:

Parser for Neos Fusion Files

334 lines (333 loc) 16.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Parser = void 0; const lexer_1 = require("./lexer"); const BlockExpressionNode_1 = require("./nodes/BlockExpressionNode"); const LiteralArrayNode_1 = require("./nodes/LiteralArrayNode"); const LiteralNumberNode_1 = require("./nodes/LiteralNumberNode"); const LiteralObjectEntryNode_1 = require("./nodes/LiteralObjectEntryNode"); const LiteralObjectNode_1 = require("./nodes/LiteralObjectNode"); const LiteralStringNode_1 = require("./nodes/LiteralStringNode"); const NotOperationNode_1 = require("./nodes/NotOperationNode"); const ObjectFunctionPathNode_1 = require("./nodes/ObjectFunctionPathNode"); const ObjectNode_1 = require("./nodes/ObjectNode"); const ObjectOffsetAccessPathNode_1 = require("./nodes/ObjectOffsetAccessPathNode"); const ObjectPathNode_1 = require("./nodes/ObjectPathNode"); const OperationNode_1 = require("./nodes/OperationNode"); const TernaryOperationNode_1 = require("./nodes/TernaryOperationNode"); const EelParserError_1 = require("../errors/EelParserError"); const IncompleteObjectPathError_1 = require("../errors/IncompleteObjectPathError"); const CallbackNode_1 = require("./nodes/CallbackNode"); const EmptyEelNode_1 = require("./nodes/EmptyEelNode"); const LiteralBooleanNode_1 = require("./nodes/LiteralBooleanNode"); const LiteralNullNode_1 = require("./nodes/LiteralNullNode"); const SpreadOperationNode_1 = require("./nodes/SpreadOperationNode"); const tokens_1 = require("./tokens"); class Parser { constructor(lexer, positionOffset = 0, options) { this.nodesByType = new Map(); this.options = { allowIncompleteObjectPaths: false, logDebug: false }; this.lexer = lexer; this.positionOffset = positionOffset; if (options) this.options = options; } setPositionOffset(positionOffset) { this.positionOffset = positionOffset; } applyOffset(position) { if (position.begin !== -1) position.begin += this.positionOffset; if (position.end !== -1) position.end += this.positionOffset; return position; } beginPosition() { return { begin: this.lexer.getCursor(), end: -1 }; } endPosition(position) { position.end = this.lexer.getCursor(); return this.applyOffset(position); } parse(checkForEmptyEel = true) { this.parseLazyWhitespace(); if (checkForEmptyEel && this.lexer.lookAhead(tokens_1.RBraceToken)) { this.lexer.consumeLookAhead(); return new EmptyEelNode_1.EmptyEelNode(this.endPosition(this.beginPosition())); } return this.parseExpression(); } parseExpression(parent = undefined) { let object = null; const position = this.beginPosition(); switch (true) { case this.lexer.lookAhead(tokens_1.SpreadToken): this.lexer.consumeLookAhead(); object = this.addNodeToNodesByType(new SpreadOperationNode_1.SpreadOperationNode(this.parseExpression(), this.endPosition(position), parent)); break; case this.lexer.lookAhead(tokens_1.ExclamationMarkToken): this.lexer.consumeLookAhead(); object = this.addNodeToNodesByType(new NotOperationNode_1.NotOperationNode(this.parseExpression(), this.endPosition(position), parent)); break; case this.lexer.lookAhead(tokens_1.FloatToken): case this.lexer.lookAhead(tokens_1.IntegerToken): object = this.addNodeToNodesByType(new LiteralNumberNode_1.LiteralNumberNode(this.lexer.consumeLookAhead().value, this.endPosition(position), parent)); break; case this.lexer.lookAhead(tokens_1.TrueValueToken): case this.lexer.lookAhead(tokens_1.FalseValueToken): object = this.addNodeToNodesByType(new LiteralBooleanNode_1.LiteralBooleanNode(this.lexer.consumeLookAhead().value, this.endPosition(position), parent)); break; case this.lexer.lookAhead(tokens_1.NullValueToken): object = this.addNodeToNodesByType(new LiteralNullNode_1.LiteralNullNode(this.lexer.consumeLookAhead().value, this.endPosition(position), parent)); break; case this.lexer.lookAhead(tokens_1.CallbackSignatureToken): { const signature = this.lexer.consumeLookAhead(); this.parseLazyWhitespace(); const callbackBody = this.parseExpression(); this.parseLazyWhitespace(); object = this.addNodeToNodesByType(new CallbackNode_1.CallbackNode(signature.value, callbackBody, this.endPosition(position), parent)); break; } case this.lexer.lookAhead(tokens_1.LParenToken): this.lexer.consumeLookAhead(); this.parseLazyWhitespace(); object = this.addNodeToNodesByType(new BlockExpressionNode_1.BlockExpressionNode(this.parseExpression(), this.endPosition(position), parent)); this.parseLazyWhitespace(); this.lexer.consume(tokens_1.RParenToken); break; case this.lexer.lookAhead(tokens_1.StringDoubleQuotedToken): case this.lexer.lookAhead(tokens_1.StringSingleQuotedToken): object = this.parseString(parent); break; case this.lexer.lookAhead(tokens_1.ObjectFunctionPathPartToken): case this.lexer.lookAhead(tokens_1.ObjectPathPartToken): object = this.parseObjectExpression(parent); break; case this.lexer.lookAhead(tokens_1.LBraceToken): object = this.parseObjectLiteral(parent); break; case this.lexer.lookAhead(tokens_1.LBracketToken): object = this.parseArrayLiteral(parent); break; } if (object === null) { if (this.options.logDebug) this.lexer.debug(); throw Error("parseExpression"); } const operation = this.parseOperationIfPossible(object); if (operation) return this.addNodeToNodesByType(operation); return object; } parseOperationIfPossible(object) { this.parseLazyWhitespace(); let operationToken = null; const position = this.beginPosition(); switch (true) { case this.lexer.lookAhead(tokens_1.LogicalAndToken): case this.lexer.lookAhead(tokens_1.LogicalOrToken): case this.lexer.lookAhead(tokens_1.IsEqualToken): case this.lexer.lookAhead(tokens_1.IsNotEqualToken): case this.lexer.lookAhead(tokens_1.PlusToken): case this.lexer.lookAhead(tokens_1.MinusToken): case this.lexer.lookAhead(tokens_1.MultiplicationToken): case this.lexer.lookAhead(tokens_1.DivisionToken): case this.lexer.lookAhead(tokens_1.ModuloToken): case this.lexer.lookAhead(tokens_1.LessThanOrEqualToken): case this.lexer.lookAhead(tokens_1.MoreThanOrEqualToken): case this.lexer.lookAhead(tokens_1.LessThanToken): case this.lexer.lookAhead(tokens_1.MoreThanToken): operationToken = this.lexer.consumeLookAhead(); break; case this.lexer.lookAhead(tokens_1.QuestionMarkToken): return this.parseTernaryOperation(object); } if (operationToken !== null) { this.parseLazyWhitespace(); return new OperationNode_1.OperationNode(object, operationToken.value, this.parseExpression(), this.endPosition(position)); } return null; } parseTernaryOperation(object) { const position = this.beginPosition(); this.lexer.consumeLookAhead(); this.parseLazyWhitespace(); const thenPart = this.parseExpression(); this.parseLazyWhitespace(); this.lexer.consume(tokens_1.ColonToken); this.parseLazyWhitespace(); const elsePart = this.parseExpression(); return new TernaryOperationNode_1.TernaryOperationNode(object, thenPart, elsePart, this.endPosition(position)); } parseString(parent = undefined) { const position = this.beginPosition(); switch (true) { case this.lexer.lookAhead(tokens_1.StringDoubleQuotedToken): case this.lexer.lookAhead(tokens_1.StringSingleQuotedToken): return this.addNodeToNodesByType(new LiteralStringNode_1.LiteralStringNode(this.lexer.consumeLookAhead().value, this.endPosition(position), parent)); } if (this.options.logDebug) this.lexer.debug(); throw new EelParserError_1.EelParserError("parseString", this.lexer.getCursor()); } parseObjectExpression(parent = undefined) { const position = this.beginPosition(); const rootPart = this.parseObjectExpressionPart(parent); const parts = [rootPart]; while (this.lexer.lazyConsume(tokens_1.DotToken)) { const positionInCaseOfException = this.beginPosition(); positionInCaseOfException.begin = positionInCaseOfException.begin - 1; // account for consumed DotToken try { parts.push(this.parseObjectExpressionPart(parent)); } catch (error) { if (error instanceof IncompleteObjectPathError_1.IncompleteObjectPathError && this.options.allowIncompleteObjectPaths) { const incompleteObjectPath = new ObjectPathNode_1.ObjectPathNode('.', this.endPosition(positionInCaseOfException), parent); incompleteObjectPath.incomplete = true; parts.push(incompleteObjectPath); } else throw error; } } return this.addNodeToNodesByType(new ObjectNode_1.ObjectNode(parts, this.endPosition(position), parent)); } parseObjectExpressionPart(parent) { switch (true) { case this.lexer.lookAhead(tokens_1.ObjectFunctionPathPartToken): return this.parseObjectFunctionExpressionPart(parent); case this.lexer.lookAhead(tokens_1.ObjectPathPartToken): return this.parseObjectPath(parent); } if (this.options.logDebug) this.lexer.debug(); throw new IncompleteObjectPathError_1.IncompleteObjectPathError("parseObjectExpressionPart: " + this.lexer.getRemainingText(), this.lexer.getCursor()); } parseObjectFunctionExpressionPart(parent = undefined) { const position = this.beginPosition(); const base = this.lexer.consumeLookAhead(); const args = []; this.parseLazyWhitespace(); if (!this.lexer.lookAhead(tokens_1.RParenToken)) { do { this.parseLazyWhitespace(); args.push(this.parseExpression()); } while (this.lexer.lazyConsume(tokens_1.CommaToken)); } this.parseLazyWhitespace(); this.lexer.consume(tokens_1.RParenToken); const node = new ObjectFunctionPathNode_1.ObjectFunctionPathNode(base.value.slice(0, -1), args, this.endPosition(position), parent, this.parseObjectOffsetExpression()); return this.addNodeToNodesByType(node); } parseObjectPath(parent) { const position = this.beginPosition(); const node = new ObjectPathNode_1.ObjectPathNode(this.lexer.consumeLookAhead().value, this.endPosition(position), parent, this.parseObjectOffsetExpression()); return this.addNodeToNodesByType(node); } parseObjectOffsetExpression() { this.parseLazyWhitespace(); if (!this.lexer.lookAhead(tokens_1.LBracketToken)) return undefined; const position = this.beginPosition(); this.lexer.consumeLookAhead(); this.parseLazyWhitespace(); const expression = this.parseExpression(); this.parseLazyWhitespace(); this.lexer.consume(tokens_1.RBracketToken); const node = new ObjectOffsetAccessPathNode_1.ObjectOffsetAccessPathNode(expression, this.endPosition(position), this.parseObjectOffsetExpression()); return this.addNodeToNodesByType(node); } parseObjectLiteral(parent = undefined) { const position = this.beginPosition(); this.lexer.consumeLookAhead(); const entries = []; this.parseLazyWhitespace(); if (!this.lexer.lookAhead(tokens_1.RBraceToken)) { do { this.parseLazyWhitespace(); const entryPosition = this.beginPosition(); const key = this.parseObjectLiteralEntryKey(); this.parseLazyWhitespace(); this.lexer.consume(tokens_1.ColonToken); this.parseLazyWhitespace(); const value = this.parseExpression(); entries.push(this.addNodeToNodesByType(new LiteralObjectEntryNode_1.LiteralObjectEntryNode(key, value, this.endPosition(entryPosition)))); } while (this.lexer.lazyConsume(tokens_1.CommaToken)); } this.parseLazyWhitespace(); this.lexer.consume(tokens_1.RBraceToken); return this.addNodeToNodesByType(new LiteralObjectNode_1.LiteralObjectNode(entries, this.endPosition(position), parent)); } parseObjectLiteralEntryKey() { switch (true) { case this.lexer.lookAhead(tokens_1.StringDoubleQuotedToken): case this.lexer.lookAhead(tokens_1.StringSingleQuotedToken): return this.parseString(); case this.lexer.lookAhead(tokens_1.ObjectPathPartToken): default: return this.parseObjectPath(); } } parseArrayLiteral(parent = undefined) { const position = this.beginPosition(); this.lexer.consumeLookAhead(); const entries = []; this.parseLazyWhitespace(); if (!this.lexer.lookAhead(tokens_1.RBracketToken)) { do { this.parseLazyWhitespace(); entries.push(this.parseExpression()); this.parseLazyWhitespace(); } while (this.lexer.lazyConsume(tokens_1.CommaToken)); this.parseLazyWhitespace(); } this.lexer.consume(tokens_1.RBracketToken); return this.addNodeToNodesByType(new LiteralArrayNode_1.LiteralArrayNode(entries, this.endPosition(position), parent)); } parseLazyWhitespace() { this.lexer.lazyConsume(tokens_1.WhitespaceToken); } handover(parser) { const result = parser.receiveHandover(this.lexer.getRemainingText(), this.lexer.getCursor() + this.positionOffset); this.lexer["cursor"] += result.cursor; return result.nodeOrNodes; } receiveHandover(text, offset) { const currentLexer = this.lexer; const currentPositionOffset = this.positionOffset; this.lexer = new lexer_1.Lexer(text); this.positionOffset = offset; const nodeOrNodes = this.parse(); const result = { nodeOrNodes, cursor: this.lexer["cursor"], nodesByType: this.nodesByType }; this.lexer = currentLexer; this.positionOffset = currentPositionOffset; return result; } logRemaining(cap = undefined) { console.log(">>::" + this.lexer.getRemainingText().substring(0, cap)); } addNodeToNodesByType(node) { var _a; const type = node.constructor; const list = (_a = this.nodesByType.get(type)) !== null && _a !== void 0 ? _a : []; if (list.includes(node)) throw Error(`Did not expect to add already added node <${node.constructor.name}>: ${node.toString()} `); list.push(node); this.nodesByType.set(type, list); return node; } flushNodesByType() { const map = new Map(this.nodesByType); this.nodesByType.clear(); return map; } } exports.Parser = Parser;