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.

ast-to-js.mjs 2.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. const allowedMathFunctions = new Set([
  2. "abs",
  3. "acos",
  4. "asin",
  5. "atan",
  6. "cos",
  7. "sin",
  8. "tan",
  9. "ceil",
  10. "floor",
  11. "exp",
  12. "log",
  13. "log2",
  14. "log10",
  15. "sqrt",
  16. ]);
  17. export default function astToJs(ast) {
  18. if (typeof ast !== "object" || ast === null) {
  19. throw new Error("Ast node must be an object");
  20. }
  21. switch (ast.kind) {
  22. case "number": {
  23. if (typeof ast.value !== "number") {
  24. throw new Error("Number is of the wrong type");
  25. }
  26. return {
  27. code: `${ast.value}`,
  28. variables: new Set(),
  29. };
  30. }
  31. case "variable": {
  32. if (typeof ast.variable !== "string") {
  33. throw new Error("Variable name not specified");
  34. }
  35. if (!ast.variable.match(/^[a-z][a-z0-9_]*$/)) {
  36. throw new Error(`Invalid variable name: ${ast.variable}`);
  37. }
  38. const name = `var_${ast.variable}`;
  39. return {
  40. code: name,
  41. variables: new Set([name]),
  42. }
  43. }
  44. case "function": {
  45. const { name, argument } = ast;
  46. const { code: argumentCode, variables } = astToJs(argument);
  47. if (typeof name !== "string") {
  48. throw new Error("Function name must be a string");
  49. }
  50. if (!allowedMathFunctions.has(name)) {
  51. throw new Error(`Invalid function: ${name}`);
  52. }
  53. const code = `Math.${name}(${argumentCode})`;
  54. return { code, variables };
  55. }
  56. case "unop": {
  57. const { code: nestedCode, variables } = astToJs(ast.value);
  58. const op =
  59. ast.op === "negate" ? "-" :
  60. ast.op === "invert" ? "~" :
  61. null;
  62. if (op === null) {
  63. throw new Error("Invalid unary operator");
  64. }
  65. const code = `${op}(${nestedCode})`;
  66. return { code, variables };
  67. }
  68. case "binop":
  69. {
  70. const [left, right] = ast.values;
  71. const leftResult = astToJs(left);
  72. const rightResult = astToJs(right);
  73. const op =
  74. ast.op === "add" ? "+" :
  75. ast.op === "subtract" ? "-" :
  76. ast.op === "multiply" ? "*" :
  77. ast.op === "divide" ? "/" :
  78. ast.op === "exponent" ? "**" :
  79. null; // null: never
  80. if (op === null) {
  81. throw new Error("Invalid binary operator");
  82. }
  83. return {
  84. code: `(${leftResult.code} ${op} ${rightResult.code})`,
  85. variables: new Set([...leftResult.variables, ...rightResult.variables]),
  86. }
  87. }
  88. default: {
  89. throw new Error(`Unknown ast kind: ${ast.kind}`);
  90. }
  91. }
  92. }