module.exports = class Compiler { constructor(tree, context) { this.tree = tree this.context = context this.result = '' } compile() { const selfClosingTags = [ 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr', ] this.tree.forEach(node => { if (node.type === 'functionCall') { const attributes = node.args.map( arg => `${arg.attributeName}="${this.compileAttribute( arg.attributeValue, )}"`, ) const compiler = new Compiler(node.subtree, this.context) const content = compiler.compile() this.result += `<${node.functionName}${ attributes.length ? ' ' : '' }${attributes.join(' ')}>` if (content) { this.result += content } if (!selfClosingTags.includes(node.functionName)) { this.result += `` } } else if (node.type === 'string') { this.result += node.content } else if (node.type === 'identifier') { this.result += this.lookup(node.name) } else if (node.type === 'each') { const symbol = node.symbol.value const subject = this.lookup(node.subject.name) subject.forEach(item => { let context = {} context[symbol] = item const compiler = new Compiler([node.body], context) this.result += compiler.compile() }) } }) return this.result.trim() } compileAttribute(attribute) { if (attribute.type === 'string') { return attribute.content } else if (attribute.type === 'identifier') { return this.lookup(attribute.name) } } lookup(name) { return this.context[name] } }