"use strict"; const isArray = Array.isArray; const Node = require("./ast/Node"); const Literal = require("./ast/Literal"); const Identifier = require("./ast/Identifier"); const HtmlElement = require("./ast/HtmlElement"); const Html = require("./ast/Html"); const ok = require("assert").ok; const Container = require("./ast/Container"); const createError = require("raptor-util/createError"); class GeneratorEvent { constructor(node, codegen) { this.node = node; this.codegen = codegen; this.isBefore = true; this.builder = codegen.builder; this.context = codegen.context; this.insertedNodes = null; } insertCode(newCode) { if (!this.insertedNodes) { this.insertedNodes = []; } this.insertedNodes = this.insertedNodes.concat(newCode); } } class FinalNodes { constructor() { this.nodes = []; this.nodes._finalNode = true; // Mark the array as a collection of final nodes this.lastNode = null; } push(node) { if (!node) { return; } if (node instanceof Html) { if (this.lastNode instanceof Html) { this.lastNode.append(node); return; } } if (node.setFinalNode) { node.setFinalNode(true); } this.lastNode = node; this.nodes.push(node); } } class CodeGenerator { constructor(context) { this.root = null; this._code = ""; this.currentIndent = ""; this.inFunction = false; this._doneListeners = []; this.builder = context.builder; this.context = context; ok(this.builder, '"this.builder" is required'); this._codegenCodeMethodName = "generate" + context.outputType.toUpperCase() + "Code"; } addVar(name, value) { return this.context.addVar(name, value); } addStaticVar(name, value) { return this.context.addStaticVar(name, value); } addStaticCode(code) { this.context.addStaticCode(code); } addDependency(path, type, options) { this.context.addDependency(path, type, options); } pushMeta(key, value, unique) { this.context.pushMeta(key, value, unique); } setMeta(key, value) { this.context.setMeta(key, value); } getEscapeXmlAttrVar() { return this.context.getEscapeXmlAttrVar(); } importModule(varName, path) { return this.context.importModule(varName, path); } _invokeCodeGenerator(func, node, isMethod) { try { if (isMethod) { return func.call(node, this); } else { return func.call(node, node, this); } } catch (err) { var errorMessage = "Generating code for "; if (node instanceof HtmlElement) { errorMessage += "<" + (node.tagName || node.tagNameExpression) + "> tag"; } else { errorMessage += node.type + " node"; } if (node.pos) { errorMessage += " (" + this.context.getPosInfo(node.pos) + ")"; } errorMessage += " failed. Error: " + err; throw createError(errorMessage, err /* cause */); } } _generateCode(node, finalNodes) { if (isArray(node)) { node.forEach(child => { this._generateCode(child, finalNodes); }); return; } else if (node instanceof Container) { node.forEach(child => { if (child.container === node) { this._generateCode(child, finalNodes); } }); return; } if (node == null) { return; } if ( typeof node === "string" || node._finalNode || !(node instanceof Node) ) { finalNodes.push(node); return; } if (node._normalizeChildTextNodes) { node._normalizeChildTextNodes(this.context); } let oldCurrentNode = this._currentNode; this._currentNode = node; var beforeAfterEvent = new GeneratorEvent(node, this); var isWhitespacePreserved = node.isPreserveWhitespace(); if (isWhitespacePreserved) { this.context.beginPreserveWhitespace(); } beforeAfterEvent.isBefore = true; beforeAfterEvent.node.emit("beforeGenerateCode", beforeAfterEvent); this.context.emit( "beforeGenerateCode:" + beforeAfterEvent.node.type, beforeAfterEvent ); this.context.emit("beforeGenerateCode", beforeAfterEvent); if (beforeAfterEvent.insertedNodes) { this._generateCode(beforeAfterEvent.insertedNodes, finalNodes); beforeAfterEvent.insertedNodes = null; } let codeGeneratorFunc; let generatedCode; if (node.getCodeGenerator) { codeGeneratorFunc = node.getCodeGenerator(this.outputType); if (codeGeneratorFunc) { node.setCodeGenerator(null); generatedCode = this._invokeCodeGenerator( codeGeneratorFunc, node, false ); if (generatedCode === null) { node = null; } else if ( generatedCode !== undefined && generatedCode !== node ) { node = null; this._generateCode(generatedCode, finalNodes); } } } if (node != null) { codeGeneratorFunc = node.generateCode; if (!codeGeneratorFunc) { codeGeneratorFunc = node[this._codegenCodeMethodName]; } if (codeGeneratorFunc) { generatedCode = this._invokeCodeGenerator( codeGeneratorFunc, node, true ); if (generatedCode === undefined || generatedCode === node) { finalNodes.push(node); } else if (generatedCode === null) { // If nothing was returned then don't generate any code } else { this._generateCode(generatedCode, finalNodes); } } else { finalNodes.push(node); } } beforeAfterEvent.isBefore = false; beforeAfterEvent.node.emit("afterGenerateCode", beforeAfterEvent); this.context.emit( "afterGenerateCode:" + beforeAfterEvent.node.type, beforeAfterEvent ); this.context.emit("afterGenerateCode", beforeAfterEvent); if (beforeAfterEvent.insertedNodes) { this._generateCode(beforeAfterEvent.insertedNodes, finalNodes); beforeAfterEvent.insertedNodes = null; } if (isWhitespacePreserved) { this.context.endPreserveWhitespace(); } this._currentNode = oldCurrentNode; } generateCode(node) { if (!node) { return null; } if (node._finalNode) { return node; } let finalNodes = new FinalNodes(); var isList = typeof node.forEach === "function"; this._generateCode(node, finalNodes); finalNodes = finalNodes.nodes; if (!isList) { if (finalNodes.length === 0) { return null; } else if (finalNodes.length === 1) { return finalNodes[0]; } } return finalNodes; } isLiteralNode(node) { return node instanceof Literal; } isIdentifierNode(node) { return node instanceof Identifier; } isPreserveWhitespaceEnabled() { return false; } addError(message, code) { ok('"message" is required'); let node = this._currentNode; if (typeof message === "object") { let errorInfo = message; errorInfo.node = node; this.context.addError(errorInfo); } else { this.context.addError({ node, message, code }); } } onDone(listenerFunc) { this._doneListeners.push(listenerFunc); } getRequirePath(targetFilename) { return this.context.getRequirePath(targetFilename); } resolvePath(pathExpression) { return this.context.resolvePath(pathExpression); } } module.exports = CodeGenerator;