'use strict'; var isArray = Array.isArray; var Node = require('./ast/Node'); var Literal = require('./ast/Literal'); var Identifier = require('./ast/Identifier'); var ok = require('assert').ok; var Container = require('./ast/Container'); class Slot { constructor(generator) { this._content = null; this._start = generator._code.length; this._currentIndent = generator._currentIndent; this._inFunction = generator.inFunction; this._statement = false; } setContent(content, options) { this._content = content; if (options && options.statement) { this._statement = true; } } generateCode(generator) { if (!this._content) { return; } generator._currentIndent = this._currentIndent; generator.inFunction = this._inFunction; var capture = generator._beginCaptureCode(); generator.generateCode(this._content); var slotCode = capture.end(); if (!slotCode) { return; } if (this._statement) { slotCode += '\n' + this._currentIndent; } var oldCode = generator._code; var beforeCode = oldCode.substring(0, this._start); var afterCode = oldCode.substring(this._start); generator._code = beforeCode + slotCode + afterCode; } } class Generator { constructor(options) { this.root = null; this._indentStr = options.indent != null ? options.indent : ' '; this._indentSize = this._indentStr.length; this._code = ''; this._currentIndent = ''; this.inFunction = false; this._bufferedWrites = null; this.builder = options.builder; this.walker = options.walker; this.outputType = options.output || 'html'; this._generatorCodeFuncName = 'generate' + this.outputType.charAt(0).toUpperCase() + this.outputType.substring(1) + 'Code'; this._slots = []; } createSlot() { var slot = new Slot(this); this._slots.push(slot); return slot; } addStaticVar(name, value) { ok(this.root, 'Invalid state'); var templateRoot = this.root; if (templateRoot.type !== 'TemplateRoot') { throw new Error('Root node is not of type "TemplateRoot". Actual ' + JSON.stringify(templateRoot)); } templateRoot.addStaticVar(name, value); } generateCode(node, options) { var isRoot = this.root == null; if (isRoot) { this.root = node; } if (options) { if (options.onError) { this._onError = options.onError; } } let generator = this; ok(node, '"node" is required'); if (typeof node === 'string') { this.write(node); return; } else if (isArray(node) || (node instanceof Container)) { node.forEach(this.generateCode, this); return; } var generateCodeFunc = node.generateCode; if (!generateCodeFunc) { generateCodeFunc = node[this._generatorCodeFuncName]; if (!generateCodeFunc) { throw new Error('Missing generator method for node of type "' + node.type + '". Node: ' + node); } } var oldCurrentNode = this._currentNode; this._currentNode = node; var resultNode = generateCodeFunc.call(node, this); if (resultNode) { // The generateCode function can optionally return either of the following: // - An AST node // - An array/cointainer of AST nodes this.generateCode(resultNode); } this._currentNode = oldCurrentNode; if (isRoot) { while(this._slots.length) { let slots = this._slots; this._slots = []; for (let i=slots.length-1; i>=0; i--) { let slot = slots[i]; slot.generateCode(generator); } } } return this._code; } generateBlock(body) { if (!body) { this.write('{}'); return; } if (typeof body === 'function') { body = body(); } if (!isArray(body) && !(body instanceof Container)) { throw new Error('Invalid body'); } if (body.length === 0) { this.write('{}'); return; } this.write('{\n') .incIndent(); var oldCodeLength = this._code.length; this.generateStatements(body); if (this._bufferedWrites) { if (this._code.length !== oldCodeLength) { this._code += '\n'; } this.flushBufferedWrites(); } this.decIndent() .writeLineIndent() .write('}'); } generateStatements(nodes) { ok(nodes, '"nodes" expected'); var firstStatement = true; nodes.forEach((node) => { if (node instanceof Node) { node.statement = true; } var startCodeLen = this._code.length; var currentIndent = this._currentIndent; if (!firstStatement) { this._write('\n'); } if (!this._code.endsWith(currentIndent)) { this.writeLineIndent(); } var startPos = this._code.length; this.generateCode(node); if (this._code.length === startPos) { this._code = this._code.slice(0, startCodeLen); return; } if (!this._code.endsWith('\n')) { this._code += ';\n'; } firstStatement = false; }); } _beginCaptureCode() { var oldCode = this._code; this._code = ''; return { generator: this, end() { var newCode = this.generator._code; this.generator._code = oldCode; return newCode; } }; } addWriteLiteral(value) { let lastWrite = this._bufferedWrites ? this._bufferedWrites[this._bufferedWrites.length-1] : null; if (lastWrite instanceof Literal) { lastWrite.value += value; return; } var output = new Literal({value}); if (!this._bufferedWrites) { this._bufferedWrites = [output]; } else { this._bufferedWrites.push(output); } } addWrite(output) { if (output instanceof Literal) { let lastWrite = this._bufferedWrites ? this._bufferedWrites[this._bufferedWrites.length-1] : null; if (lastWrite instanceof Literal) { lastWrite.value += output.value; return; } } if (!this._bufferedWrites) { this._bufferedWrites = [output]; } else { this._bufferedWrites.push(output); } } flushBufferedWrites(addSeparator) { var bufferedWrites = this._bufferedWrites; if (!bufferedWrites) { return; } this._bufferedWrites = null; if (!addSeparator && !this._code.endsWith(this._currentIndent)) { this.writeLineIndent(); } var len = bufferedWrites.length; for (var i=0; i