mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
337 lines
8.6 KiB
JavaScript
337 lines
8.6 KiB
JavaScript
"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;
|