mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
171 lines
6.0 KiB
JavaScript
171 lines
6.0 KiB
JavaScript
'use strict';
|
|
var ok = require('assert').ok;
|
|
var path = require('path');
|
|
var CodeGenerator = require('./CodeGenerator');
|
|
var CodeWriter = require('./CodeWriter');
|
|
var createError = require('raptor-util/createError');
|
|
var resolveDep = require('../runtime/dependencies').resolveDep;
|
|
|
|
const FLAG_TRANSFORMER_APPLIED = 'transformerApply';
|
|
|
|
function transformNode(node, context) {
|
|
try {
|
|
context.taglibLookup.forEachNodeTransformer(node, function (transformer) {
|
|
if (node.isDetached()) {
|
|
return; //The node might have been removed from the tree
|
|
}
|
|
if (!node.isTransformerApplied(transformer)) {
|
|
//Check to make sure a transformer of a certain type is only applied once to a node
|
|
node.setTransformerApplied(transformer);
|
|
//Mark the node as have been transformed by the current transformer
|
|
context.setFlag(FLAG_TRANSFORMER_APPLIED);
|
|
//Set the current node
|
|
context._currentNode = node;
|
|
//Set the flag to indicate that a node was transformed
|
|
// node.compiler = this;
|
|
var transformerFunc = transformer.getFunc();
|
|
transformerFunc.call(transformer, node, context); //Have the transformer process the node (NOTE: Just because a node is being processed by the transformer doesn't mean that it has to modify the parse tree)
|
|
}
|
|
});
|
|
} catch (e) {
|
|
throw createError(new Error('Unable to compile template at path "' + context.filename + '". Error: ' + e.message), e);
|
|
}
|
|
}
|
|
|
|
function transformTreeHelper(node, context) {
|
|
transformNode(node, context);
|
|
|
|
/*
|
|
* Now process the child nodes by looping over the child nodes
|
|
* and transforming the subtree recursively
|
|
*
|
|
* NOTE: The length of the childNodes array might change as the tree is being performed.
|
|
* The checks to prevent transformers from being applied multiple times makes
|
|
* sure that this is not a problem.
|
|
*/
|
|
node.forEachChild(function (childNode) {
|
|
transformTreeHelper(childNode, context);
|
|
});
|
|
}
|
|
|
|
function transformTree(rootNode, context) {
|
|
|
|
context.taglibLookup.forEachTemplateTransformer((transformer) => {
|
|
var transformFunc = transformer.getFunc();
|
|
rootNode = transformFunc(rootNode, context) || rootNode;
|
|
});
|
|
|
|
/*
|
|
* The tree is continuously transformed until we go through an entire pass where
|
|
* there were no new nodes that needed to be transformed. This loop makes sure that
|
|
* nodes added by transformers are also transformed.
|
|
*/
|
|
do {
|
|
context.clearFlag(FLAG_TRANSFORMER_APPLIED);
|
|
//Reset the flag to indicate that no transforms were yet applied to any of the nodes for this pass
|
|
transformTreeHelper(rootNode, context); //Run the transforms on the tree
|
|
} while (context.isFlagSet(FLAG_TRANSFORMER_APPLIED));
|
|
|
|
return rootNode;
|
|
}
|
|
|
|
function handleErrors(context) {
|
|
// If there were any errors then compilation failed.
|
|
if (context.hasErrors()) {
|
|
var errors = context.getErrors();
|
|
|
|
var message = 'An error occurred while trying to compile template at path "' + context.filename + '". Error(s) in template:\n';
|
|
for (var i = 0, len = errors.length; i < len; i++) {
|
|
let error = errors[i];
|
|
message += (i + 1) + ') ' + error.toString() + '\n';
|
|
}
|
|
var error = new Error(message);
|
|
error.errors = errors;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
class CompiledTemplate {
|
|
constructor(ast, context, codeGenerator) {
|
|
this.ast = ast;
|
|
this.context = context;
|
|
this.filename = context.filename;
|
|
}
|
|
|
|
get dependencies() {
|
|
var meta = this.context.meta;
|
|
if (meta) {
|
|
var root = path.dirname(this.filename);
|
|
return (meta.deps || []).map(dep => resolveDep(dep, root));
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
get code() {
|
|
// STAGE 3: Generate the code using the final AST
|
|
handleErrors(this.context);
|
|
|
|
// console.log(module.id, 'FINAL AST:' + JSON.stringify(finalAST, null, 4));
|
|
var codeWriter = new CodeWriter(this.context.options, this.context.builder);
|
|
codeWriter.write(this.ast);
|
|
|
|
handleErrors(this.context);
|
|
|
|
// Return the generated code as the compiled output:
|
|
var compiledSrc = codeWriter.getCode();
|
|
return compiledSrc;
|
|
}
|
|
}
|
|
|
|
class Compiler {
|
|
constructor(options, userOptions, inline) {
|
|
ok(options, '"options" is required');
|
|
|
|
this.builder = options.builder;
|
|
this.parser = options.parser;
|
|
|
|
ok(this.builder, '"options.builder" is required');
|
|
ok(this.parser, '"options.parser" is required');
|
|
}
|
|
|
|
compile(src, context) {
|
|
ok(typeof src === 'string', '"src" argument should be a string');
|
|
|
|
var codeGenerator = new CodeGenerator(context);
|
|
|
|
// STAGE 1: Parse the template to produce the initial AST
|
|
var ast = this.parser.parse(src, context);
|
|
context._parsingFinished = true;
|
|
|
|
if (!context.ignoreUnrecognizedTags && context.unrecognizedTags) {
|
|
for(let i=0; i<context.unrecognizedTags.length; i++) {
|
|
let unrecognizedTag = context.unrecognizedTags[i];
|
|
// See if the tag is a macro
|
|
if (!context.isMacro(unrecognizedTag.tagName)) {
|
|
context.addErrorUnrecognizedTag(unrecognizedTag.tagName, unrecognizedTag.node);
|
|
}
|
|
}
|
|
}
|
|
|
|
handleErrors(context);
|
|
|
|
context.root = ast;
|
|
// console.log('ROOT', JSON.stringify(ast, null, 2));
|
|
|
|
// STAGE 2: Transform the initial AST to produce the final AST
|
|
var transformedAST = transformTree(ast, context);
|
|
// console.log('transformedAST', JSON.stringify(ast, null, 2));
|
|
|
|
handleErrors(context);
|
|
|
|
var finalAST = codeGenerator.generateCode(transformedAST);
|
|
|
|
handleErrors(context);
|
|
|
|
return new CompiledTemplate(finalAST, context);
|
|
}
|
|
}
|
|
|
|
module.exports = Compiler;
|