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.

resolver.js 4.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. const AST = require("./ast");
  2. module.exports = class Resolver {
  3. constructor() {
  4. this.tree = [];
  5. this.context = {};
  6. this.stdlib = {
  7. if: (predicate, left, right) => {
  8. const resolvedPredicate = this.resolveNode(predicate);
  9. this.typecheck(resolvedPredicate, AST.Boolean);
  10. if (resolvedPredicate && resolvedPredicate.value === true) {
  11. return this.resolveNode(left);
  12. } else if (resolvedPredicate && resolvedPredicate.value === false) {
  13. return this.resolveNode(right);
  14. }
  15. },
  16. list: args => {
  17. let elements = [];
  18. args.forEach(arg => {
  19. elements.push(this.resolveNode(arg));
  20. });
  21. return new AST.List({
  22. elements: elements
  23. });
  24. },
  25. quote: value => {
  26. return new AST.Symbol({ value: value });
  27. },
  28. htmlElement: node => {
  29. let resolvedArgs = [];
  30. node.args.forEach(arg => {
  31. resolvedArgs.push(this.resolveNode(arg));
  32. });
  33. return new AST.Application({
  34. functionName: node.functionName,
  35. args: resolvedArgs
  36. });
  37. },
  38. "=": (left, right) => {
  39. this.typecheck(right, left.constructor);
  40. if (left.constructor.name === "List") {
  41. if (left.elements.length !== right.elements.length) {
  42. return new AST.Boolean({ value: false });
  43. }
  44. let equal = true;
  45. left.elements.forEach((el, i) => {
  46. this.typecheck(el.value, right.elements[i].constructor);
  47. if (el.value !== right.elements[i].value) {
  48. equal = false;
  49. }
  50. });
  51. return new AST.Boolean({ value: equal });
  52. }
  53. return new AST.Boolean({ value: left.value === right.value });
  54. },
  55. ">": (left, right) => {
  56. this.typecheck(left, AST.Number) && this.typecheck(right, AST.Number);
  57. return new AST.Boolean({ value: left.value > right.value });
  58. },
  59. "<": (left, right) => {
  60. this.typecheck(left, AST.Number) && this.typecheck(right, AST.Number);
  61. return new AST.Boolean({ value: left.value < right.value });
  62. },
  63. ">=": (left, right) => {
  64. this.typecheck(left, AST.Number) && this.typecheck(right, AST.Number);
  65. return new AST.Boolean({ value: left.value >= right.value });
  66. },
  67. "<=": (left, right) => {
  68. this.typecheck(left, AST.Number) && this.typecheck(right, AST.Number);
  69. return new AST.Boolean({ value: left.value <= right.value });
  70. },
  71. and: args => {
  72. let and = true;
  73. args.forEach(arg => {
  74. this.typecheck(arg, AST.Boolean);
  75. if (arg.value === false) {
  76. and = false;
  77. }
  78. });
  79. return new AST.Boolean({ value: and });
  80. },
  81. or: args => {
  82. let or = false;
  83. args.forEach(arg => {
  84. this.typecheck(arg, AST.Boolean);
  85. if (arg.value === true) {
  86. or = true;
  87. }
  88. });
  89. return new AST.Boolean({ value: or });
  90. },
  91. lambda: function(parameter, body) {
  92. console.log(parameter, body);
  93. }
  94. };
  95. }
  96. resolve(tree, context) {
  97. this.context = context;
  98. tree.forEach(node => {
  99. this.tree.push(this.resolveNode(node));
  100. });
  101. return this.tree;
  102. }
  103. resolveNode(node, context) {
  104. if (!context) {
  105. context = this.context;
  106. }
  107. switch (node.constructor.name) {
  108. case "Boolean":
  109. case "Number":
  110. case "String":
  111. return node;
  112. case "Attribute":
  113. return new AST.Attribute({
  114. name: node.name,
  115. value: this.resolveNode(node.value)
  116. });
  117. case "Identifier":
  118. return this.lookup(node.name);
  119. case "Application":
  120. if (this.stdlib.hasOwnProperty(node.functionName.name)) {
  121. const f = this.stdlib[node.functionName.name];
  122. if (["list", "and", "or"].includes(node.functionName.name)) {
  123. return f(node.args);
  124. }
  125. let resolvedArgs = [];
  126. if (node.functionName.name === "quote") {
  127. resolvedArgs = node.args;
  128. } else {
  129. node.args.forEach(arg => {
  130. resolvedArgs.push(this.resolveNode(arg));
  131. });
  132. }
  133. return f(...resolvedArgs);
  134. }
  135. return this.stdlib.htmlElement(node);
  136. }
  137. }
  138. lookup(name) {
  139. console.log(name);
  140. const result = this.context[name];
  141. return this.wrap(result);
  142. }
  143. wrap(value) {
  144. switch (value.constructor.name) {
  145. case "String":
  146. return new AST.String({ value: value });
  147. case "Number":
  148. return new AST.Number({ value: value });
  149. case "Array":
  150. return new AST.List({ elements: value.map(this.wrap) });
  151. }
  152. }
  153. typecheck(value, type) {
  154. console.log(value);
  155. console.log(type);
  156. if (value.constructor.name !== type.name) {
  157. throw `Type error: expected a ${type.name} but got a ${
  158. value.constructor.name
  159. }`;
  160. }
  161. }
  162. };