UNPKG

ts-fusion-parser

Version:

Parser for Neos Fusion Files

599 lines (598 loc) 27 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ObjectTreeParser = void 0; const Comment_1 = require("../common/Comment"); const NodePosition_1 = require("../common/NodePosition"); const ParserError_1 = require("../common/ParserError"); const lexer_1 = require("../dsl/eel/lexer"); const parser_1 = require("../dsl/eel/parser"); const lexer_2 = require("./lexer"); const AssignedObjectPath_1 = require("./nodes/AssignedObjectPath"); const Block_1 = require("./nodes/Block"); const BoolValue_1 = require("./nodes/BoolValue"); const DslExpressionValue_1 = require("./nodes/DslExpressionValue"); const EelExpressionValue_1 = require("./nodes/EelExpressionValue"); const FloatValue_1 = require("./nodes/FloatValue"); const FusionFile_1 = require("./nodes/FusionFile"); const FusionObjectValue_1 = require("./nodes/FusionObjectValue"); const IncludeStatement_1 = require("./nodes/IncludeStatement"); const IncompletePathSegment_1 = require("./nodes/IncompletePathSegment"); const IntValue_1 = require("./nodes/IntValue"); const MetaPathSegment_1 = require("./nodes/MetaPathSegment"); const NullValue_1 = require("./nodes/NullValue"); const ObjectPath_1 = require("./nodes/ObjectPath"); const ObjectStatement_1 = require("./nodes/ObjectStatement"); const PathSegment_1 = require("./nodes/PathSegment"); const PropertyDocumentationDefinition_1 = require("./nodes/PropertyDocumentationDefinition"); const PrototypeDocumentationDefinition_1 = require("./nodes/PrototypeDocumentationDefinition"); const PrototypePathSegment_1 = require("./nodes/PrototypePathSegment"); const StatementList_1 = require("./nodes/StatementList"); const StringValue_1 = require("./nodes/StringValue"); const ValueAssignment_1 = require("./nodes/ValueAssignment"); const ValueCopy_1 = require("./nodes/ValueCopy"); const ValueUnset_1 = require("./nodes/ValueUnset"); const token_1 = require("./token"); const stripSlashes = (str) => str.replace(/\\./g, (match) => (new Function(`return "${match}"`))() || match); const stripCSlashes = stripSlashes; class ObjectTreeParser { constructor(lexer, contextPathAndFilename, options) { this.lexer = lexer; this.contextPathAndFilename = contextPathAndFilename; this.nodesByType = new Map(); this.ignoredErrors = []; this.options = { ignoreErrors: false, allowIncompleteObjectStatements: false }; this.lexer = lexer; this.contextPathAndFilename = contextPathAndFilename; if (options) this.options = options; } static parse(sourceCode, contextPathAndFilename = undefined, options) { const lexer = new lexer_2.Lexer(sourceCode); const parser = new ObjectTreeParser(lexer, contextPathAndFilename, options); return parser.parseFusionFile(); } consume() { return this.lexer.consumeLookahead(); } /** * Accepts a token of a given type. * The Lexer will look up the regex for the token and try to match it on the current string. * First match wins. * * @param {number} tokenType * @return bool */ accept(tokenType, debug = false) { const token = this.lexer.getCachedLookaheadOrTryToGenerateLookaheadForTokenAndGetLookahead(tokenType, debug); if (token === null) { return false; } return token.getType() === tokenType; } /** * Expects a token of a given type. * The Lexer will look up the regex for the token and try to match it on the current string. * First match wins. * * @param {number} tokenType * @return Token * @throws ParserUnexpectedCharException */ expect(tokenType) { const token = this.lexer.getCachedLookaheadOrTryToGenerateLookaheadForTokenAndGetLookahead(tokenType); if (token === null || token.getType() !== tokenType) { throw Error(`Expected token: '${token_1.Token.typeToString(tokenType)}'.`); // // throw new ParserUnexpectedCharException("Expected token: 'tokenReadable'.", 1646988824); } return this.lexer.consumeLookahead(); } /** * Checks, if the token type matches the current, if so consume it and return true. * @param {number} tokenType * @return bool|null */ lazyExpect(tokenType) { const token = this.lexer.getCachedLookaheadOrTryToGenerateLookaheadForTokenAndGetLookahead(tokenType); if (token === null || token.getType() !== tokenType) { return false; } this.lexer.consumeLookahead(); return true; } lazyBigGap() { const comments = []; while (true) { switch (true) { case this.accept(token_1.Token.SPACE): case this.accept(token_1.Token.NEWLINE): this.consume(); break; case this.accept(token_1.Token.PROPERTY_DOCUMENTATION_DEFINITION): return comments; case this.accept(token_1.Token.PROTOTYPE_DOCUMENTATION_DEFINITION): return comments; case this.accept(token_1.Token.SLASH_COMMENT): case this.accept(token_1.Token.HASH_COMMENT): case this.accept(token_1.Token.MULTILINE_COMMENT): comments.push(this.parseComment()); break; default: return comments; } } } lazySmallGap() { while (true) { switch (true) { case this.accept(token_1.Token.SPACE): this.consume(); break; case this.accept(token_1.Token.PROPERTY_DOCUMENTATION_DEFINITION): return; case this.accept(token_1.Token.PROTOTYPE_DOCUMENTATION_DEFINITION): return; case this.accept(token_1.Token.SLASH_COMMENT): case this.accept(token_1.Token.HASH_COMMENT): return this.parseComment(); default: return; } } } parsePropertyDocumentationDefinition(parent) { const position = this.createPosition(); const rawContent = this.consume().getValue(); position.begin -= rawContent.length; return new PropertyDocumentationDefinition_1.PropertyDocumentationDefinition(rawContent, this.endPosition(position), parent); } parsePrototypeDocumentationDefinition(parent) { const position = this.createPosition(); const rawContent = this.consume().getValue(); position.begin -= rawContent.length; return new PrototypeDocumentationDefinition_1.PrototypeDocumentationDefinition(rawContent, this.endPosition(position), parent); } parseComment() { let type = undefined; let prefix = undefined; if (this.accept(token_1.Token.SLASH_COMMENT)) { type = token_1.Token.SLASH_COMMENT; prefix = "//"; } else if (this.accept(token_1.Token.HASH_COMMENT)) { type = token_1.Token.HASH_COMMENT; prefix = "#"; } else if (this.accept(token_1.Token.MULTILINE_COMMENT)) { type = token_1.Token.MULTILINE_COMMENT; prefix = "/*"; } else throw new Error('Expected Comment'); const position = this.createPosition(); const rawComment = this.consume().getValue(); position.begin -= rawComment.length; const comment = new Comment_1.Comment(rawComment.replace(prefix, ""), type === token_1.Token.MULTILINE_COMMENT, prefix, this.endPosition(position)); this.addNodeToNodesByType(comment); return comment; } parseFusionFile() { // try { const file = new FusionFile_1.FusionFile(this.parseStatementList(null, null, "file"), this.contextPathAndFilename); file.nodesByType = this.flushNodesByType(); file.errors = this.ignoredErrors; return file; // } catch (e) { // throw e; // } catch (ParserUnexpectedCharException e) { // throw this.prepareParserException(new ParserException()) // .setCode(e.getCode()) // .setMessageCreator (MessageLinePart nextLine) use (e) { // return "Unexpected char {nextLine.charPrint()}. {e.getMessage()}"; // }) // .setPrevious(e) // .build(); // } catch (Fusion\Exception e) { // throw this.prepareParserException(new ParserException()) // .setCode(e.getCode()) // .setMessage('Exception while parsing: ' . e.getMessage()) // .setHideColumnInformation() // .setPrevious(e) // .build(); // } } /** * StatementList * = ( Statement )* * * @param ?int stopLookahead When this tokenType is encountered the loop will be stopped */ parseStatementList(parent, stopLookahead = null, debugName = "") { const position = this.createPosition(); const statements = []; const comments = []; comments.push(...this.lazyBigGap()); while (this.accept(token_1.Token.EOF) === false && (stopLookahead === null || this.accept(stopLookahead) === false)) { let statement; try { statement = this.parseStatement(null); this.addNodeToNodesByType(statement); statements.push(statement); // TODO: save order of statements and comments => check again why this is needed => probably for printing/prettier comments.push(...this.lazyBigGap()); } catch (e) { if (!this.options.ignoreErrors) { throw e; } else { this.ignoredErrors.push(e); } break; } } // TODO: positioning of StatementList begins to late const statementList = new StatementList_1.StatementList(statements, comments, this.endPosition(position), parent); this.addNodeToNodesByType(statementList); return statementList; } /** * Statement * = IncludeStatement / ObjectStatement */ parseStatement(parent) { // watch out for the order, its regex matching and first one wins. if (this.accept(token_1.Token.PROPERTY_DOCUMENTATION_DEFINITION)) { return this.parsePropertyDocumentationDefinition(parent); } if (this.accept(token_1.Token.PROTOTYPE_DOCUMENTATION_DEFINITION)) { return this.parsePrototypeDocumentationDefinition(parent); } if (this.accept(token_1.Token.INCLUDE)) return this.parseIncludeStatement(parent); if (this.accept(token_1.Token.PROTOTYPE_START)) return this.parseObjectStatement(parent); if (this.accept(token_1.Token.OBJECT_PATH_PART)) { this.lexer.advanceCursor(-1 * this.lexer.consumeLookahead().getValue().length); return this.parseObjectStatement(parent); } if (this.accept(token_1.Token.META_PATH_START) || this.accept(token_1.Token.STRING_SINGLE_QUOTED) || this.accept(token_1.Token.STRING_DOUBLE_QUOTED)) { return this.parseObjectStatement(parent); } if (!this.options.ignoreErrors) console.log("parseStatement"); if (!this.options.ignoreErrors) this.lexer.debug(); throw new ParserError_1.ParserError("Error while parsing statement", this.lexer.getCursor()); } /** * IncludeStatement * = INCLUDE ( STRING / CHAR / FILE_PATTERN ) EndOfStatement */ parseIncludeStatement(parent) { const position = this.createPosition(); this.expect(token_1.Token.INCLUDE); this.lazyExpect(token_1.Token.SPACE); let filePattern; switch (true) { case this.accept(token_1.Token.STRING_DOUBLE_QUOTED): case this.accept(token_1.Token.STRING_SINGLE_QUOTED): const stringWrapped = this.consume().getValue(); filePattern = stringWrapped.substring(1, stringWrapped.length - 1); break; case this.accept(token_1.Token.FILE_PATTERN): filePattern = this.consume().getValue(); break; default: throw new Error('Expected file pattern in quotes or [a-zA-Z0-9.*:/_-]'); // // throw new ParserUnexpectedCharException('Expected file pattern in quotes or [a-zA-Z0-9.*:/_-]', 1646988832); } this.parseEndOfStatement("parseIncludeStatement"); return new IncludeStatement_1.IncludeStatement(filePattern, this.endPosition(position), parent); } /** * ObjectStatement * = ObjectPath ( ValueAssignment / ValueUnset / ValueCopy )? ( Block / EndOfStatement ) */ parseObjectStatement(parent) { const position = this.createPosition(); const currentPath = this.parseObjectPath(null); this.addNodeToNodesByType(currentPath); this.lazyExpect(token_1.Token.SPACE); const cursorAfterObjectPath = this.lexer.getCursor(); let operation = null; switch (true) { case this.accept(token_1.Token.ASSIGNMENT): operation = this.parseValueAssignment(null); break; case this.accept(token_1.Token.UNSET): operation = this.parseValueUnset(null); break; case this.accept(token_1.Token.COPY): operation = this.parseValueCopy(null); break; } if (operation !== null) { this.addNodeToNodesByType(operation); } this.lazyExpect(token_1.Token.SPACE); if (this.accept(token_1.Token.LBRACE)) { const block = this.parseBlock(null); return new ObjectStatement_1.ObjectStatement(currentPath, operation, block, cursorAfterObjectPath, this.endPosition(position), parent); } if (operation === null) { if (this.options.ignoreErrors || this.options.allowIncompleteObjectStatements) { const newPosition = { begin: this.lexer.getCursor(), end: this.lexer.getCursor() }; // TODO: do not reuse "newPosition" => check why operation = new ValueAssignment_1.ValueAssignment(new NullValue_1.NullValue(newPosition), newPosition, null); } else { throw Error("operation is null"); } } if (!(operation instanceof ValueAssignment_1.ValueAssignment && operation.pathValue instanceof EelExpressionValue_1.EelExpressionValue)) { this.parseEndOfStatement("parseObjectStatement"); } return new ObjectStatement_1.ObjectStatement(currentPath, operation, undefined, cursorAfterObjectPath, this.endPosition(position), parent); } /** * ObjectPath * = PathSegment ( '.' PathSegment )* * */ parseObjectPath(parent) { const position = this.createPosition(); const segments = []; do { const segment = this.parsePathSegment(); this.addNodeToNodesByType(segment); segments.push(segment); } while (this.lazyExpect(token_1.Token.DOT)); position.end = segments[segments.length - 1].position.end; return new ObjectPath_1.ObjectPath(position, parent, ...segments); } /** * PathSegment * = ( PROTOTYPE_START FUSION_OBJECT_NAME ')' / OBJECT_PATH_PART / '@' OBJECT_PATH_PART / STRING / CHAR ) */ parsePathSegment() { let position = this.createPosition(); switch (true) { case this.accept(token_1.Token.PROTOTYPE_START): this.consume(); position = this.createPosition(); let prototypeName; try { prototypeName = this.expect(token_1.Token.FUSION_OBJECT_NAME).getValue(); } catch (error) { throw error; // // throw this.prepareParserException(new ParserException()) // // .setCode(1646991578) // // .setMessageCreator([MessageCreator.class, 'forPathSegmentPrototypeName']) // // .build(); } position = this.endPosition(position); this.expect(token_1.Token.RPAREN); return new PrototypePathSegment_1.PrototypePathSegment(prototypeName, position); case this.accept(token_1.Token.OBJECT_PATH_PART): const pathKey = this.consume().getValue(); position = this.createPosition(); position.begin -= pathKey.length; return new PathSegment_1.PathSegment(pathKey, this.endPosition(position)); case this.accept(token_1.Token.META_PATH_START): const nodePosition = this.createPosition(); this.consume(); const metaPathSegmentKey = this.expect(token_1.Token.OBJECT_PATH_PART).getValue(); return new MetaPathSegment_1.MetaPathSegment(metaPathSegmentKey, this.endPosition(nodePosition)); case this.accept(token_1.Token.STRING_DOUBLE_QUOTED): case this.accept(token_1.Token.STRING_SINGLE_QUOTED): const stringWrapped = this.consume().getValue(); const quotedPathKey = stringWrapped.substring(1, stringWrapped.length - 1); position.begin -= quotedPathKey.length; return new PathSegment_1.PathSegment(quotedPathKey, this.endPosition(position)); case this.accept(token_1.Token.SPACE): case this.accept(token_1.Token.NEWLINE): if (this.options.ignoreErrors) return new IncompletePathSegment_1.IncompletePathSegment("", this.endPosition(this.createPosition())); } throw Error("Could not parse segment"); // // throw this.prepareParserException(new ParserException()) // // .setCode(1635708755) // // .setMessageCreator([MessageCreator.class, 'forParsePathSegment']) // // .build(); } parseValueAssignment(parent) { const position = this.createPosition(); this.expect(token_1.Token.ASSIGNMENT); this.lazyExpect(token_1.Token.SPACE); const value = this.parsePathValue(); this.addNodeToNodesByType(value); return new ValueAssignment_1.ValueAssignment(value, this.endPosition(position), parent); } /** * PathValue * = ( CHAR / STRING / DSL_EXPRESSION / FusionObject / EelExpression ) */ parsePathValue() { var _a; // watch out for the order, its regex matching and first one wins. // sorted by likelyhood const position = this.createPosition(); let stringContent; switch (true) { case this.accept(token_1.Token.STRING_SINGLE_QUOTED): const charWrapped = this.consume().getValue(); stringContent = charWrapped.substring(1, charWrapped.length - 1); return new StringValue_1.StringValue(stripSlashes(stringContent), this.endPosition(position)); case this.accept(token_1.Token.STRING_DOUBLE_QUOTED): const stringWrapped = this.consume().getValue(); stringContent = stringWrapped.substring(1, stringWrapped.length - 1); return new StringValue_1.StringValue(stripCSlashes(stringContent), this.endPosition(position)); case this.accept(token_1.Token.DSL_EXPRESSION_START): return this.parseDslExpression(); case this.accept(token_1.Token.FLOAT): return new FloatValue_1.FloatValue(parseFloat(this.consume().getValue()), this.endPosition(position)); case this.accept(token_1.Token.INTEGER): return new IntValue_1.IntValue(parseInt(this.consume().getValue()), this.endPosition(position)); case this.accept(token_1.Token.TRUE_VALUE): this.consume(); return new BoolValue_1.BoolValue(true, this.endPosition(position)); case this.accept(token_1.Token.FALSE_VALUE): this.consume(); return new BoolValue_1.BoolValue(false, this.endPosition(position)); case this.accept(token_1.Token.NULL_VALUE): this.consume(); return new NullValue_1.NullValue(this.endPosition(position)); case this.accept(token_1.Token.FUSION_OBJECT_NAME): const nodePosition = this.createPosition(); const value = this.consume().getValue(); nodePosition.begin -= value.length; return new FusionObjectValue_1.FusionObjectValue(value, this.endPosition(nodePosition)); case this.accept(token_1.Token.EEL_EXPRESSION_START): this.consume(); const eelPosition = this.createPosition(); const eelLexer = new lexer_1.Lexer(this.lexer.getRemainingCode()); const eelParser = new parser_1.Parser(eelLexer, this.lexer.getCursor(), this.options.eelParserOptions); const eelNode = eelParser.parse(); for (const [type, nodes] of eelParser.nodesByType.entries()) { const list = (_a = this.nodesByType.get(type)) !== null && _a !== void 0 ? _a : []; for (const node of nodes) { list.push(node); } this.nodesByType.set(type, list); } this.lexer.advanceCursor(eelLexer.getCursor() + 1); const code = this.lexer.getCode().slice(eelNode.position.begin, eelNode.position.end); return new EelExpressionValue_1.EelExpressionValue(code, this.endPosition(eelPosition, -1), eelNode); } if (!this.options.ignoreErrors) console.log("parsePathValue"); if (!this.options.ignoreErrors) this.lexer.debug(); throw new ParserError_1.ParserError("Could not parse value", this.lexer.getCursor()); // // throw this.prepareParserException(new ParserException()) // // .setCode(1646988841) // // .setMessageCreator([MessageCreator.class, 'forParsePathValue']) // // .build(); } parseDslExpression() { var _a; const dslIdentifier = this.expect(token_1.Token.DSL_EXPRESSION_START).getValue(); const position = this.createPosition(); position.begin -= dslIdentifier.length; let dslCode = ''; try { dslCode = this.expect(token_1.Token.DSL_EXPRESSION_CONTENT).getValue(); } catch (error) { if (this.options.ignoreErrors) this.ignoredErrors.push(error); else throw error; } dslCode = dslCode.substring(1, dslCode.length - 1); const dslExpression = new DslExpressionValue_1.DslExpressionValue(dslIdentifier, dslCode, this.endPosition(position), this.options.afxParserOptions); const nodesByType = dslExpression.parse(); for (const [type, nodes] of nodesByType.entries()) { const list = (_a = this.nodesByType.get(type)) !== null && _a !== void 0 ? _a : []; for (const node of nodes) { list.push(node); } this.nodesByType.set(type, list); } return dslExpression; } parseValueUnset(parent) { const position = this.createPosition(); this.expect(token_1.Token.UNSET); return new ValueUnset_1.ValueUnset(this.endPosition(position), parent); } parseValueCopy(parent) { const position = this.createPosition(); this.expect(token_1.Token.COPY); this.lazyExpect(token_1.Token.SPACE); const sourcePath = this.parseAssignedObjectPath(null); return new ValueCopy_1.ValueCopy(sourcePath, this.endPosition(position), parent); } parseAssignedObjectPath(parent) { const position = this.createPosition(); const isRelative = this.lazyExpect(token_1.Token.DOT); return new AssignedObjectPath_1.AssignedObjectPath(this.endPosition(position), parent, this.parseObjectPath(null), Boolean(isRelative)); } /** * Block: * = '{' StatementList? '}' */ parseBlock(parent, debugName = "") { this.expect(token_1.Token.LBRACE); const blockPosition = this.createPosition(); blockPosition.begin -= 1; this.parseEndOfStatement("parseBlock"); const statementList = this.parseStatementList(null, token_1.Token.RBRACE, debugName); try { this.expect(token_1.Token.RBRACE); } catch (error) { if (this.options.ignoreErrors) { this.ignoredErrors.push(error); } else { throw error; } } const block = new Block_1.Block(parent, statementList, this.endPosition(blockPosition)); this.addNodeToNodesByType(block); return block; } /** * EndOfStatement * = ( EOF / NEWLINE ) */ parseEndOfStatement(debugFrom = '') { this.lazySmallGap(); if (this.accept(token_1.Token.EOF)) { return; } if (this.accept(token_1.Token.NEWLINE)) { this.consume(); return; } if (!this.options.ignoreErrors) console.log("parseEndOfStatement"); if (!this.options.ignoreErrors) this.lexer.debug(); throw Error("parsed EOF from: " + debugFrom); // // throw this.prepareParserException(new ParserException()) // // .setCode(1635878683) // // .setMessageCreator([MessageCreator.class, 'forParseEndOfStatement']) // // .build(); } createPosition() { return new NodePosition_1.NodePosition(this.lexer.getCursor()); } endPosition(position, offset = 0) { position.end = this.lexer.getCursor() + offset; return position; } addNodeToNodesByType(node) { var _a; const type = node.constructor; const list = (_a = this.nodesByType.get(type)) !== null && _a !== void 0 ? _a : []; list.push(node); this.nodesByType.set(type, list); } flushNodesByType() { const map = new Map(this.nodesByType); this.nodesByType.clear(); return map; } } exports.ObjectTreeParser = ObjectTreeParser;