UNPKG

ts-fusion-parser

Version:

Parser for Neos Fusion Files

298 lines (297 loc) 13.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Parser = void 0; const Comment_1 = require("../../common/Comment"); const ParserError_1 = require("../../common/ParserError"); const lexer_1 = require("../eel/lexer"); const parser_1 = require("../eel/parser"); const lexer_2 = require("./lexer"); const InlineEelNode_1 = require("./nodes/InlineEelNode"); const TagAttributeNode_1 = require("./nodes/TagAttributeNode"); const TagNameNode_1 = require("./nodes/TagNameNode"); const TagNode_1 = require("./nodes/TagNode"); const TagSpreadEelAttributeNode_1 = require("./nodes/TagSpreadEelAttributeNode"); const TextNode_1 = require("./nodes/TextNode"); const tokens_1 = require("./tokens"); class Parser { constructor(lexer, positionOffset = 0, options) { this.nodesByType = new Map(); this.options = { allowUnclosedTags: false }; this.lexer = lexer; this.positionOffset = positionOffset; if (options) this.options = options; } applyOffset(position) { if (position.begin !== -1) position.begin += this.positionOffset; if (position.end !== -1) position.end += this.positionOffset; return position; } parse() { return [...this.parseTextsOrTags()]; } *parseText(parent = undefined) { const position = { begin: -1, end: -1 }; let currentText = ''; let currentTextPosition = { begin: this.lexer.getCursor(), end: -1 }; while (!this.lexer.isEOF() && (this.lexer.lookAhead(tokens_1.CharacterToken) || this.lexer.lookAhead(tokens_1.EscapedCharacterToken))) { const charToken = this.lexer.consumeLookAhead(); if ((new tokens_1.AttributeEelBeginToken).regex.test(charToken.value)) { if (currentText !== '') { currentTextPosition.end = this.lexer.getCursor() - 1; const textNode = new TextNode_1.TextNode(this.applyOffset(currentTextPosition), currentText, parent); this.addNodeToNodesByType(textNode); yield textNode; currentTextPosition = { begin: this.lexer.getCursor(), end: -1 }; currentText = ''; } const eelBegin = charToken; const eelParser = this.buildEelParser(); const result = this.handover(eelParser); const eelEnd = this.lexer.consume(tokens_1.AttributeEelEndToken); const position = { begin: eelBegin.position.begin, end: eelEnd.position.end }; const inlineEelNode = new InlineEelNode_1.InlineEelNode(this.applyOffset(position), result); this.addNodeToNodesByType(inlineEelNode); yield inlineEelNode; } else { currentText += charToken.value; } if (position.begin === -1) position.begin = charToken.position.begin; position.end = charToken.position.end; } if (currentText !== '') { currentTextPosition.end = this.lexer.getCursor() - 1; const textNode = new TextNode_1.TextNode(this.applyOffset(currentTextPosition), currentText, parent); this.addNodeToNodesByType(textNode); yield textNode; } } parseJavascript(parent = undefined) { let text = ""; const position = { begin: -1, end: -1 }; while (!this.lexer.isEOF() && !this.lexer.lookAhead(tokens_1.ScriptEndToken) && this.lexer.lookAhead(tokens_1.AnyCharacterToken)) { const charToken = this.lexer.consumeLookAhead(); text += charToken.value; if (position.begin === -1) position.begin = charToken.position.begin; position.end = charToken.position.end; } const textNode = new TextNode_1.TextNode(this.applyOffset(position), text, parent); this.addNodeToNodesByType(textNode); return textNode; } *parseTextsOrTags(parent = undefined) { while (!this.lexer.isEOF()) { this.parseLazyWhitespace(); switch (true) { case this.lexer.lookAhead(tokens_1.CharacterToken): yield* this.parseText(parent); break; case this.lexer.lookAhead(tokens_1.TagBeginToken): yield this.parseTag(parent); break; case this.lexer.lookAhead(tokens_1.CommentToken): yield* this.parseComment(); break; default: return; } } } *parseComment() { const commentValueRegex = /^<!--([.]*?)-->$/gm; const token = this.lexer.consume(tokens_1.CommentToken); const commentValueRegexResult = commentValueRegex.exec(token.value); if (commentValueRegexResult) { const comment = new Comment_1.Comment(commentValueRegexResult[1], token.value.includes("\n"), '<!--', this.applyOffset(token.position)); this.addNodeToNodesByType(comment); yield comment; } } parseTag(parent = undefined) { const token = this.lexer.consume(tokens_1.TagBeginToken); const nameNode = TagNameNode_1.TagNameNode.From(token); this.lexer.tagStack.push(nameNode.toString()); const position = { ...token.position }; this.parseLazyWhitespace(); const attributes = []; try { while (!this.lexer.lookAhead(tokens_1.TagCloseToken) && !this.lexer.lookAhead(tokens_1.TagSelfCloseToken)) { this.parseLazyWhitespace(); const isSpreadEelAttribute = this.lexer.lookAhead(tokens_1.AttributeEelBeginToken); const attribute = isSpreadEelAttribute ? this.parseSpreadEelAttribute() : this.parseTagAttribute(); this.addNodeToNodesByType(attribute); attributes.push(attribute); this.parseLazyWhitespace(); } } catch (error) { if (!this.options.allowUnclosedTags) { if (error instanceof Error) { throw new ParserError_1.ParserError(error.message, this.lexer.getCursor()); } else throw error; } const endToken = new tokens_1.TagSelfCloseToken(); endToken["value"] = ">"; endToken.position = { begin: position.begin + 1, end: position.end }; position.end = endToken.position.end; this.lexer.tagStack.pop(); const tagNode = new TagNode_1.TagNode(this.applyOffset(position), nameNode.toString(), nameNode, attributes, [], TagNameNode_1.TagNameNode.From(endToken), true, parent); tagNode["artificiallyClosed"] = true; this.addNodeToNodesByType(tagNode); return tagNode; } const endToken = this.lexer.consumeLookAhead(); this.parseLazyWhitespace(); if (endToken instanceof tokens_1.TagSelfCloseToken) { position.end = endToken.position.end; this.lexer.tagStack.pop(); const tagNode = new TagNode_1.TagNode(this.applyOffset(position), nameNode.toString(), nameNode, attributes, [], TagNameNode_1.TagNameNode.From(endToken), true, parent); this.addNodeToNodesByType(tagNode); return tagNode; } const isScript = nameNode.toString() === "script"; const content = isScript ? [this.parseJavascript()] : [...this.parseTextsOrTags()]; this.parseLazyWhitespace(); if (this.lexer.isEOF()) { const endToken = new tokens_1.TagSelfCloseToken(); endToken["value"] = ">"; endToken.position = { begin: position.end + 1, end: position.end + 2 }; position.end = endToken.position.end; this.lexer.tagStack.pop(); const endNode = TagNameNode_1.TagNameNode.From(endToken); const tagNode = new TagNode_1.TagNode(this.applyOffset(position), nameNode.toString(), nameNode, attributes, [], endNode, true, parent); tagNode["artificiallyClosed"] = true; this.addNodeToNodesByType(tagNode); return tagNode; } const closingTagToken = this.lexer.consume(tokens_1.TagEndToken); position.end = closingTagToken.position.end; closingTagToken.position = this.applyOffset(closingTagToken.position); this.lexer.tagStack.pop(); const tagNode = new TagNode_1.TagNode(this.applyOffset(position), nameNode.toString(), nameNode, attributes, content, TagNameNode_1.TagNameNode.From(closingTagToken), false, parent); this.addNodeToNodesByType(tagNode); return tagNode; } parseSpreadEelAttribute() { const eelBegin = this.lexer.consumeLookAhead(); const eelParser = this.buildEelParser(); const result = this.handover(eelParser); const eelEnd = this.lexer.consume(tokens_1.AttributeEelEndToken); const position = { begin: eelBegin.position.begin, end: eelEnd.position.end }; const eel = this.lexer.getSnippet(position.begin + 1, position.end - 1); return new TagSpreadEelAttributeNode_1.TagSpreadEelAttributeNode(this.applyOffset(position), Array.isArray(result) ? result : [result], eel); } parseTagAttribute() { this.parseLazyWhitespace(); // TODO: check if TagAttribute begins with `"`. If so, parse it as a string instead of using the token regex const name = this.lexer.consume(tokens_1.AttributeNameToken); const position = name.position; let value; if (this.lexer.lazyConsume(tokens_1.AttributeValueAssignToken)) { switch (true) { case this.lexer.lookAhead(tokens_1.WordToken): case this.lexer.lookAhead(tokens_1.AttributeStringValueToken): value = this.lexer.consumeLookAhead(); break; case this.lexer.lookAhead(tokens_1.AttributeEelBeginToken): { const eelBegin = this.lexer.consumeLookAhead(); const eelParser = this.buildEelParser(); const result = this.handover(eelParser); // console.log("result", result) const eelEnd = this.lexer.consume(tokens_1.AttributeEelEndToken); value = { value: result, position: { begin: eelBegin.position.begin, end: eelEnd.position.end } }; break; } } } if (value) { position.end = value.position.end; } return new TagAttributeNode_1.TagAttributeNode(this.applyOffset(position), name.value, value ? value.value : null); } parseLazyWhitespace() { this.lexer.lazyConsume(tokens_1.WhitespaceToken); } handover(parser, parent = undefined) { const text = this.lexer.getRemainingText(); const result = parser.receiveHandover(text, this.lexer["cursor"] + this.positionOffset); this.addNodesFromHandoverResult(result, parent); this.lexer["cursor"] += result.cursor; return Array.isArray(result.nodeOrNodes) ? result.nodeOrNodes : [result.nodeOrNodes]; } addNodesFromHandoverResult(result, parent = undefined) { var _a; if (Array.isArray(result.nodeOrNodes)) { for (const node of result.nodeOrNodes) { node["parent"] = parent; } } else { result.nodeOrNodes["parent"] = parent; } for (const [type, nodes] of result.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); } } receiveHandover(text) { const currentLexer = this.lexer; this.lexer = new lexer_2.Lexer(text); const nodeOrNodes = this.parse(); const result = { nodeOrNodes: nodeOrNodes, cursor: this.lexer["cursor"], nodesByType: this.nodesByType }; this.lexer = currentLexer; 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 : []; list.push(node); this.nodesByType.set(type, list); } flushNodesByType() { const map = new Map(this.nodesByType); this.nodesByType.clear(); return map; } buildEelParser() { return new parser_1.Parser(new lexer_1.Lexer(""), undefined, this.options.eelParserOptions); } } exports.Parser = Parser;