ts-fusion-parser
Version:
Parser for Neos Fusion Files
334 lines (333 loc) • 16.2 kB
JavaScript
"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;