@adobe/htlengine
Version:
Javascript Based HTL (Sightly) parser
195 lines (178 loc) • 6.44 kB
JavaScript
/*
* Copyright 2018 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
const OutText = require('../parser/commands/OutText');
const VariableBinding = require('../parser/commands/VariableBinding');
const FunctionBlock = require('../parser/commands/FunctionBlock');
const FunctionCall = require('../parser/commands/FunctionCall');
const Conditional = require('../parser/commands/Conditional');
const Loop = require('../parser/commands/Loop');
const OutputVariable = require('../parser/commands/OutputVariable');
const CreateElement = require('../parser/commands/CreateElement');
const PushElement = require('../parser/commands/PushElement');
const PopElement = require('../parser/commands/PopElement');
const Doctype = require('../parser/commands/Doctype');
const Comment = require('../parser/commands/Comment');
const AddAttribute = require('../parser/commands/AddAttribute');
const ExpressionFormatter = require('./ExpressionFormatter');
const DomHandler = require('./DomHandler');
module.exports = class JSCodeGenVisitor {
constructor() {
this._result = '';
this._templates = '';
this._indentLevel = 0;
this._lastIndentLevel = 0;
this._inFunctionBlock = false;
this._indents = [];
this._codeLine = 0;
this._templateLine = 0;
this._dom = new DomHandler(this);
}
withIndent(delim) {
this._indents = [delim];
this._indent = delim;
for (let i = 0; i < 50; i += 1) {
this._indents[i + 1] = this._indents[i] + delim;
}
return this;
}
withSourceMap(enabled) {
this._mappings = enabled ? [] : null;
return this;
}
indent() {
this._indent = this._indents[++this._indentLevel] || ''; // eslint-disable-line no-plusplus
return this;
}
outdent() {
this._indent = this._indents[--this._indentLevel] || ''; // eslint-disable-line no-plusplus
return this;
}
setIndent(n) {
this._indentLevel = n;
this._indent = this._indents[n] || '';
return this;
}
process(commands) {
this._dom.beginDocument();
commands.forEach((c) => {
c.accept(this);
});
this._dom.endDocument();
return this;
}
out(msg) {
if (this._indent) {
if (this._inFunctionBlock) {
this._templates += `${this._indent + msg}\n`;
this._templateLine += 1;
} else {
this._result += `${this._indent + msg}\n`;
this._codeLine += 1;
}
} else if (this._inFunctionBlock) {
this._templates += msg;
} else {
this._result += msg;
}
}
get code() {
return this._result;
}
get templates() {
return this._templates;
}
get mappings() {
return this._mappings;
}
_addMapping(location) {
if (!location) {
return;
}
const lastmapping = this._mappings[this._mappings.length - 1];
if (lastmapping && lastmapping.originalLine === location.line) {
// skip multiple mappings for the same original line, as
// IDEs wouldn't probably know how to distinguish them
return;
}
this._mappings.push({
inFunctionBlock: this._inFunctionBlock,
originalLine: location.line,
originalColumn: location.column,
generatedLine: this._inFunctionBlock ? this._templateLine : this._codeLine,
generatedColumn: 0,
});
}
visit(cmd) {
if (this._mappings) {
this._addMapping(cmd.location);
}
if (cmd instanceof OutText) {
this._dom.outText(cmd);
} else if (cmd instanceof VariableBinding.Start) {
const exp = ExpressionFormatter.format(cmd.expression);
this.out(`const ${cmd.variableName} = ${exp};`);
} else if (cmd instanceof VariableBinding.Global) {
const exp = ExpressionFormatter.format(cmd.expression);
this.out(`const ${ExpressionFormatter.escapeVariable(cmd.variableName)} = ${exp};`);
} else if (cmd instanceof VariableBinding.End) {
// nop
} else if (cmd instanceof FunctionBlock.Start) {
if (this._inFunctionBlock) {
throw new Error('Template cannot be defined in another template');
}
this._inFunctionBlock = true;
this._lastIndentLevel = this._indentLevel;
this.setIndent(0);
this._dom.functionStart(cmd);
} else if (cmd instanceof FunctionBlock.End) {
this._dom.functionEnd(cmd);
this._inFunctionBlock = false;
this.setIndent(this._lastIndentLevel);
} else if (cmd instanceof FunctionCall) {
this._dom.functionCall(cmd);
} else if (cmd instanceof Conditional.Start) {
const exp = ExpressionFormatter.format(cmd.expression);
const neg = cmd.negate ? '!' : '';
this.out(`if (${neg}${exp}) {`);
this.indent();
} else if (cmd instanceof Conditional.End) {
this.outdent();
this.out('}');
} else if (cmd instanceof OutputVariable) {
this._dom.outVariable(cmd.variableName);
} else if (cmd instanceof Loop.Init) {
const exp = ExpressionFormatter.format(cmd.expression);
this.out(`const ${cmd.variableName} = $.col.init(${exp});`);
} else if (cmd instanceof Loop.Start) {
this.out(`for (const ${cmd.indexVariable} of $.col.keys(${cmd.listVariable})) {`);
this.indent();
this.out(`const ${cmd.itemVariable} = $.col.get(${cmd.listVariable}, ${cmd.indexVariable});`);
} else if (cmd instanceof Loop.End) {
this.outdent();
this.out('}');
} else if (cmd instanceof CreateElement) {
this._dom.createElement(cmd);
} else if (cmd instanceof PushElement) {
this._dom.pushElement(cmd);
} else if (cmd instanceof PopElement) {
this._dom.popElement(cmd);
} else if (cmd instanceof AddAttribute) {
this._dom.addAttribute(cmd);
} else if (cmd instanceof Doctype) {
this._dom.doctype(cmd);
} else if (cmd instanceof Comment) {
this._dom.comment(cmd);
} else {
throw new Error(`unknown command: ${cmd}`);
}
}
};