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.

parse.mjs 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. /*
  2. Operation precedences:
  3. 0: exponentiation
  4. 1: implicit multiplication
  5. 2: multiplication/division
  6. 3: unary operators
  7. 4: addition/subtraction
  8. */
  9. // We could probably have lexed directly into the parse token, but
  10. // this way we keep a bit more separation of church & state
  11. const lexTokenToParseToken = (token) => {
  12. if (token === undefined) {
  13. return { kind: "EOF" };
  14. }
  15. switch (token.kind) {
  16. case "operator": {
  17. switch (token.value) {
  18. case "invert": return { kind: "UNOP", value: token.value, precedence: 3, isOp: true };
  19. case "subtract": return { kind: "MAYBE_UNOP", value: token.value, unopValue: "negate", precedence: 4, isOp: true };
  20. case "exponent": return { kind: "BINOP", value: token.value, precedence: 0, isOp: true, };
  21. case "multiply": return { kind: "BINOP", value: token.value, precedence: 2, isOp: true, };
  22. case "divide": return { kind: "BINOP", value: token.value, precedence: 2, isOp: true, };
  23. case "add": return { kind: "BINOP", value: token.value, precedence: 4, isOp: true, };
  24. default: throw new Error(`Unknown operator ${token.value}`);
  25. }
  26. }
  27. case "open-paren": return { kind: "EXPR_START" };
  28. case "close-paren": return { kind: "EXPR_END" };
  29. case "number": return { kind: "VALUE", ast: { kind: "number", value: token.value } };
  30. case "variable": return { kind: "VALUE", ast: { kind: "variable", variable: token.value } };
  31. default: throw new Error(`Unknown token kind ${token.kind}`);
  32. }
  33. }
  34. const mightBeUnop = (token) => {
  35. return (
  36. token.kind === "UNOP"
  37. || token.kind === "MAYBE_UNOP"
  38. || token.kind === "FUNCTION"
  39. || token.kind === "IMPLICIT_MULTIPLICATION"
  40. );
  41. }
  42. const mightBeBinop = (token) => {
  43. return (
  44. token.kind === "BINOP"
  45. || token.kind === "MAYBE_UNOP"
  46. );
  47. }
  48. const parseOne = (stack, lookahead) => {
  49. // If we can reduce a parenthetical expression, we want to
  50. if (stack[0]?.kind === "EXPR_END") {
  51. const [_end, value, _start, ...rest] = stack;
  52. if (stack[1]?.kind !== "VALUE" || stack[2]?.kind !== "EXPR_START") {
  53. throw new Error("Received unexpected close parenthesis");
  54. }
  55. return ["reduce", [value, ...rest]];
  56. }
  57. // Otherwise, all of our reductions occur on values
  58. if (stack[0]?.kind === "VALUE") {
  59. // We have some special cases for two adjacent values, when the first one is
  60. // a pure variable (function call) or number (term multiplication)
  61. if (lookahead.kind === "VALUE" || lookahead.kind === "EXPR_START") {
  62. if (stack[0].ast.kind === "variable") {
  63. const [token, ...rest] = stack;
  64. const newToken = {
  65. kind: "FUNCTION",
  66. isOp: true,
  67. precedence: 3,
  68. name: token.ast.variable
  69. }
  70. const newStack = [newToken, ...rest];
  71. return ["reduce", newStack];
  72. }
  73. if (stack[0].ast.kind === "number") {
  74. const [token, ...rest] = stack;
  75. const newToken = {
  76. kind: "IMPLICIT_MULTIPLICATION",
  77. isOp: true,
  78. precedence: 1,
  79. value: token.ast.value
  80. }
  81. const newStack = [newToken, ...rest];
  82. return ["reduce", newStack];
  83. }
  84. }
  85. // If we have an operator on our stack, we want to reduce it unless the upcoming
  86. // operator has a stronger precedence
  87. if (stack[1]?.isOp) {
  88. const shouldShift = mightBeBinop(lookahead) && lookahead.precedence < stack[1].precedence;
  89. if (shouldShift) {
  90. return ["shift"];
  91. }
  92. const isBinop = mightBeBinop(stack[1]) && stack[2]?.kind === "VALUE";
  93. if (isBinop) {
  94. const [right, binop, left, ...rest] = stack;
  95. // Binops are easy
  96. const newValue = {
  97. kind: "VALUE",
  98. ast: {
  99. kind: "binop",
  100. op: binop.value,
  101. values: [
  102. left.ast,
  103. right.ast,
  104. ]
  105. }
  106. };
  107. return ["reduce", [newValue, ...rest]]
  108. }
  109. const isUnop = mightBeUnop(stack[1]);
  110. if (!isUnop) {
  111. throw new Error("Unexpected operator");
  112. }
  113. const [value, unop, ...rest] = stack;
  114. let newToken;
  115. // Handle each of the different types of unary operators
  116. switch (unop.kind) {
  117. case "UNOP":
  118. case "MAYBE_UNOP": {
  119. const op = unop.kind === "MAYBE_UNOP" ? unop.unopValue : unop.value;
  120. newToken = {
  121. kind: "VALUE",
  122. ast: {
  123. kind: "unop",
  124. op,
  125. value: value.ast
  126. }
  127. };
  128. break;
  129. }
  130. case "FUNCTION": {
  131. newToken = {
  132. kind: "VALUE",
  133. ast: {
  134. kind: "function",
  135. name: unop.name,
  136. argument: value.ast,
  137. }
  138. };
  139. break;
  140. }
  141. case "IMPLICIT_MULTIPLICATION": {
  142. newToken = {
  143. kind: "VALUE",
  144. ast: {
  145. kind: "binop",
  146. op: "multiply",
  147. values: [
  148. { kind: "number", value: unop.value },
  149. value.ast,
  150. ]
  151. }
  152. };
  153. break
  154. }
  155. default: {
  156. throw new Error(`Unknown unary operator: ${unop.kind}`);
  157. }
  158. }
  159. return ["reduce", [newToken, ...rest]];
  160. }
  161. }
  162. // Otherwise, we have no reductions to do, so we shift in a new token
  163. return ["shift"];
  164. }
  165. export default (tokens) => {
  166. const queue = [...tokens];
  167. let stack = [];
  168. const maxIter = 1000;
  169. let iter = 0;
  170. while (queue.length > 0 || stack.length > 1) {
  171. // I haven't proven that this terminates so uh
  172. // Hopefully this will keep me from nuking anyone's chrome
  173. if (iter >= maxIter) {
  174. throw new Error("Timeout");
  175. }
  176. iter++;
  177. const lookahead = lexTokenToParseToken(queue[0]);
  178. const action = parseOne(stack, lookahead);
  179. if (window.DEBUG) {
  180. console.log([...stack], lookahead, action);
  181. }
  182. switch (action[0]) {
  183. case "shift": {
  184. if (lookahead.kind === "EOF") {
  185. throw new Error("Attempting to shift EOF, which indicates a malformed program");
  186. }
  187. queue.shift();
  188. stack = [lookahead, ...stack]
  189. break;
  190. }
  191. case "reduce": {
  192. stack = action[1];
  193. }
  194. }
  195. }
  196. // If we parsed correctly, we should be left with a single value
  197. // representing our final result
  198. if (stack[0]?.kind !== "VALUE") {
  199. throw new Error("Parser did not return a value");
  200. }
  201. return stack[0].ast;
  202. }