const util = require('util') module.exports = class Compiler { constructor(tree, context) { this.tree = tree this.context = context this.pos = 0 this.result = '' this.selfClosingTags = [ 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr', ] this.standardLibrary = { cond: function(predicate, left, right) { if (predicate) { let compiler = new Compiler([left], this.context) return compiler.compile() } else { let compiler = new Compiler([right], this.context) return compiler.compile() } }, } } compile() { this.tree.forEach(node => { switch (node.constructor.name) { case 'Application': this.result += this.application(node) break; case 'String': this.result += node.value; break; case 'Identifier': this.result += this.lookup(node) break; } }) return this.result } application(node) { if (this.standardLibrary[node.functionName.name]) { return this.standardLibrary[node.functionName.name](...node.args) } return this.htmlElement(node) } htmlElement(node) { let result = `<${node.functionName.name}` node.args.filter(arg => arg.constructor.name === 'Attribute').forEach(arg => { result += ` ${arg.name}=` let compiler = new Compiler([arg.value], this.context) result += `"${compiler.compile()}"` }) result += '>' node.args.filter(arg => arg.constructor.name !== 'Attribute').forEach(arg => { let compiler = new Compiler([arg], this.context) result += compiler.compile() }) if (!this.selfClosingTags.includes(node.functionName.name)) { result += `` } return result } lookup(identifier) { let result = this.context[identifier.name] if (!result) { throw `Undefined variable '${identifier.name}'` } return result } currentNode() { return this.tree[this.pos] } }