A templating language that looks like Lisp and compiles to HTML
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

compiler.js 2.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. const util = require('util')
  2. module.exports = class Compiler {
  3. constructor(tree, context) {
  4. this.tree = tree
  5. this.context = context
  6. this.pos = 0
  7. this.result = ''
  8. this.selfClosingTags = [
  9. 'area',
  10. 'base',
  11. 'br',
  12. 'col',
  13. 'command',
  14. '!doctype',
  15. '!DOCTYPE',
  16. 'embed',
  17. 'hr',
  18. 'img',
  19. 'input',
  20. 'keygen',
  21. 'link',
  22. 'menuitem',
  23. 'meta',
  24. 'param',
  25. 'source',
  26. 'track',
  27. 'wbr',
  28. ]
  29. this.standardLibrary = {
  30. cond: function(predicate, left, right) {
  31. if (predicate) {
  32. let compiler = new Compiler([left], this.context)
  33. return compiler.compile()
  34. } else {
  35. let compiler = new Compiler([right], this.context)
  36. return compiler.compile()
  37. }
  38. },
  39. }
  40. }
  41. compile() {
  42. this.tree.forEach(node => {
  43. switch (node.constructor.name) {
  44. case 'Application':
  45. this.result += this.application(node)
  46. break;
  47. case 'String':
  48. this.result += node.value;
  49. break;
  50. case 'Identifier':
  51. this.result += this.lookup(node)
  52. break;
  53. }
  54. })
  55. return this.result
  56. }
  57. application(node) {
  58. if (this.standardLibrary[node.functionName.name]) {
  59. return this.standardLibrary[node.functionName.name](...node.args)
  60. }
  61. return this.htmlElement(node)
  62. }
  63. htmlElement(node) {
  64. let result = `<${node.functionName.name}`
  65. node.args.filter(arg => arg.constructor.name === 'Attribute').forEach(arg => {
  66. result += ` ${arg.name}`
  67. let compiler = new Compiler([arg.value], this.context)
  68. let attrValue = compiler.compile()
  69. if (attrValue) {
  70. result += `="${attrValue}"`
  71. }
  72. })
  73. result += '>'
  74. node.args.filter(arg => arg.constructor.name !== 'Attribute').forEach(arg => {
  75. let compiler = new Compiler([arg], this.context)
  76. result += compiler.compile()
  77. })
  78. if (!this.selfClosingTags.includes(node.functionName.name)) {
  79. result += `</${node.functionName.name}>`
  80. }
  81. return result
  82. }
  83. lookup(identifier) {
  84. let result = this.context[identifier.name]
  85. if (!result) {
  86. throw `Undefined variable '${identifier.name}'`
  87. }
  88. return result
  89. }
  90. currentNode() {
  91. return this.tree[this.pos]
  92. }
  93. }