push
This commit is contained in:
Executable
+24
@@ -0,0 +1,24 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="/css/common.css">
|
||||
<link rel="stylesheet" href="/css/calc.css">
|
||||
<script id="program" language="json" type="{{ content-type }}">
|
||||
{{ program }}
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
window.addEventListener("load", () => {
|
||||
import("/js/calc.mjs");
|
||||
})
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="body">
|
||||
<h1 class="title" id="name">Loading</h1>
|
||||
<div id="input"></div>
|
||||
<span id="output"></span>
|
||||
<span id="error"></span>
|
||||
<button id="report">Show me your math!</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
.body #input {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.body #input > input {
|
||||
justify-self: center;
|
||||
outline: none;
|
||||
padding: 16px;
|
||||
margin: 16px;
|
||||
font-size: 24px;
|
||||
border: 2px solid #2a2a2a;
|
||||
width: 128px;
|
||||
height: 64px;
|
||||
border-radius: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.body #output {
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.body #output:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.body #output::before {
|
||||
content: "Result:";
|
||||
color: #2a2a2a;
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
|
||||
.body #error {
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
color: #e21c1c;
|
||||
}
|
||||
|
||||
.body #error:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.body #error::before {
|
||||
content: "Error:";
|
||||
color: #e94e4e;
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
body {
|
||||
font-family: 'Oxygen';
|
||||
}
|
||||
|
||||
.body {
|
||||
display: flex;
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.body > * {
|
||||
margin: 8px 0px;
|
||||
}
|
||||
|
||||
.body .title {
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
.body #name {
|
||||
max-width: 400px;
|
||||
font-size: 24px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.body #program {
|
||||
resize: vertical;
|
||||
}
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="/css/common.css">
|
||||
<link rel="stylesheet" href="/css/main.css">
|
||||
<script type="module">
|
||||
window.addEventListener("load", () => {
|
||||
import("/js/main.mjs");
|
||||
})
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="body">
|
||||
<h1 class="title">Upload your Program to Run</h1>
|
||||
<input id="name" placeholder="New Program"/>
|
||||
<textarea id="program" style="min-height: 200px;">
|
||||
(-b + sqrt(b^2 - 4a*c)) / 2a
|
||||
</textarea>
|
||||
<div>
|
||||
Choose a program type:
|
||||
<select id="content-type">
|
||||
<option value="application/x-yaca-ast">AST</option>
|
||||
<option value="application/x-yaca-code" selected>Code</option>
|
||||
</select>
|
||||
</div>
|
||||
<button id="upload">Upload</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
const allowedMathFunctions = new Set([
|
||||
"abs",
|
||||
"acos",
|
||||
"asin",
|
||||
"atan",
|
||||
"cos",
|
||||
"sin",
|
||||
"tan",
|
||||
"ceil",
|
||||
"floor",
|
||||
"exp",
|
||||
"log",
|
||||
"log2",
|
||||
"log10",
|
||||
"sqrt",
|
||||
]);
|
||||
|
||||
export default function astToJs(ast) {
|
||||
if (typeof ast !== "object" || ast === null) {
|
||||
throw new Error("Ast node must be an object");
|
||||
}
|
||||
|
||||
switch (ast.kind) {
|
||||
case "number": {
|
||||
if (typeof ast.value !== "number") {
|
||||
throw new Error("Number is of the wrong type");
|
||||
}
|
||||
|
||||
return {
|
||||
code: `${ast.value}`,
|
||||
variables: new Set(),
|
||||
};
|
||||
}
|
||||
case "variable": {
|
||||
if (typeof ast.variable !== "string") {
|
||||
throw new Error("Variable name not specified");
|
||||
}
|
||||
|
||||
if (!ast.variable.match(/^[a-z][a-z0-9_]*$/)) {
|
||||
throw new Error(`Invalid variable name: ${ast.variable}`);
|
||||
}
|
||||
|
||||
const name = `var_${ast.variable}`;
|
||||
|
||||
return {
|
||||
code: name,
|
||||
variables: new Set([name]),
|
||||
}
|
||||
}
|
||||
case "function": {
|
||||
const { name, argument } = ast;
|
||||
const { code: argumentCode, variables } = astToJs(argument);
|
||||
|
||||
if (typeof name !== "string") {
|
||||
throw new Error("Function name must be a string");
|
||||
}
|
||||
|
||||
if (!allowedMathFunctions.has(name)) {
|
||||
throw new Error(`Invalid function: ${name}`);
|
||||
}
|
||||
|
||||
const code = `Math.${name}(${argumentCode})`;
|
||||
|
||||
return { code, variables };
|
||||
}
|
||||
case "unop": {
|
||||
const { code: nestedCode, variables } = astToJs(ast.value);
|
||||
const op =
|
||||
ast.op === "negate" ? "-" :
|
||||
ast.op === "invert" ? "~" :
|
||||
null;
|
||||
|
||||
if (op === null) {
|
||||
throw new Error("Invalid unary operator");
|
||||
}
|
||||
|
||||
const code = `${op}(${nestedCode})`;
|
||||
return { code, variables };
|
||||
}
|
||||
case "binop":
|
||||
{
|
||||
const [left, right] = ast.values;
|
||||
const leftResult = astToJs(left);
|
||||
const rightResult = astToJs(right);
|
||||
const op =
|
||||
ast.op === "add" ? "+" :
|
||||
ast.op === "subtract" ? "-" :
|
||||
ast.op === "multiply" ? "*" :
|
||||
ast.op === "divide" ? "/" :
|
||||
ast.op === "exponent" ? "**" :
|
||||
null; // null: never
|
||||
|
||||
if (op === null) {
|
||||
throw new Error("Invalid binary operator");
|
||||
}
|
||||
|
||||
return {
|
||||
code: `(${leftResult.code} ${op} ${rightResult.code})`,
|
||||
variables: new Set([...leftResult.variables, ...rightResult.variables]),
|
||||
}
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unknown ast kind: ${ast.kind}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
import astToJs from "/js/ast-to-js.mjs";
|
||||
import evalCode from "/js/eval-code.mjs";
|
||||
import lex from "/js/lex.mjs";
|
||||
import parse from "/js/parse.mjs";
|
||||
|
||||
const $ = document.querySelector.bind(document);
|
||||
const nameEl = $("#name");
|
||||
const errorEl = $("#error");
|
||||
const reportEl = $("#report");
|
||||
|
||||
const astProgram = $("#program");
|
||||
const program = JSON.parse(astProgram.textContent);
|
||||
|
||||
nameEl.innerText = `Running: ${program.name}`;
|
||||
|
||||
reportEl.addEventListener("click", () => {
|
||||
const file = location.pathname.split("/").slice(-1)[0];
|
||||
reportEl.disabled = true;
|
||||
reportEl.innerText = "Reported!";
|
||||
fetch(`/report`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({ file })
|
||||
});
|
||||
})
|
||||
|
||||
try {
|
||||
let ast;
|
||||
if (astProgram.type === "application/x-yaca-code") {
|
||||
const tokens = lex(program.code);
|
||||
ast = parse(tokens);
|
||||
} else {
|
||||
ast = JSON.parse(program.code);
|
||||
}
|
||||
|
||||
const jsProgram = astToJs(ast);
|
||||
evalCode(jsProgram);
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
const msg = e instanceof Error ? e.message : "Something went wrong";
|
||||
errorEl.innerText = msg;
|
||||
}
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
const $ = document.querySelector.bind(document);
|
||||
|
||||
const prepareInputs = (variables, onChange) => {
|
||||
const inputEl = $("#input");
|
||||
|
||||
for (const node of [...inputEl.childNodes]) {
|
||||
node.remove();
|
||||
}
|
||||
|
||||
for (const variable of variables) {
|
||||
const input = document.createElement("input");
|
||||
input.setAttribute("type", "text");
|
||||
input.setAttribute("placeholder", `${variable.slice(4)}`);
|
||||
input.setAttribute("value", "");
|
||||
input.addEventListener("keyup", () => {
|
||||
let value = Number(input.value);
|
||||
if (isNaN(value)) {
|
||||
value = 0;
|
||||
}
|
||||
|
||||
onChange(variable, value);
|
||||
});
|
||||
inputEl.appendChild(input);
|
||||
}
|
||||
}
|
||||
|
||||
const updateOutput = (result) => {
|
||||
// Float truncation
|
||||
if (typeof result === "number" && Math.floor(result) !== result) {
|
||||
result = result.toFixed(2);
|
||||
}
|
||||
|
||||
const $ = document.querySelector.bind(document);
|
||||
const outputEl = $("#output");
|
||||
|
||||
outputEl.innerText = result;
|
||||
}
|
||||
|
||||
|
||||
export default ({ code, variables }) => {
|
||||
const varList = [...variables].sort((a, b) => a.localeCompare(b));
|
||||
const fn = new Function(...varList, `return (${code});`);
|
||||
const values = new Map(varList.map((val) => [val, 0]));
|
||||
|
||||
const refresh = () => {
|
||||
const result = fn(...varList.map((val) => values.get(val)));
|
||||
|
||||
if (isNaN(result)) {
|
||||
throw new Error("Output was not a number")
|
||||
}
|
||||
|
||||
$("#error").innerText = "";
|
||||
updateOutput(result);
|
||||
}
|
||||
|
||||
const onChange = (variable, value) => {
|
||||
values.set(variable, value);
|
||||
refresh();
|
||||
}
|
||||
|
||||
prepareInputs(varList, onChange);
|
||||
|
||||
refresh();
|
||||
}
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
export default (source) => {
|
||||
let index = 0;
|
||||
const tokens = [];
|
||||
|
||||
while (index < source.length) {
|
||||
const token = source[index];
|
||||
index += 1;
|
||||
|
||||
switch (token) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\r':
|
||||
break;
|
||||
case "~":
|
||||
case "+":
|
||||
case "-":
|
||||
case "*":
|
||||
case "/":
|
||||
case "^": {
|
||||
const op =
|
||||
token === "~" ? "invert" :
|
||||
token === "+" ? "add" :
|
||||
token === "-" ? "subtract" :
|
||||
token === "*" ? "multiply" :
|
||||
token === "/" ? "divide" :
|
||||
token === "^" ? "exponent" :
|
||||
null;
|
||||
|
||||
tokens.push({
|
||||
kind: "operator",
|
||||
value: op,
|
||||
});
|
||||
break
|
||||
}
|
||||
case "(": {
|
||||
tokens.push({
|
||||
kind: "open-paren",
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ")": {
|
||||
tokens.push({
|
||||
kind: "close-paren",
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (token.match(/^[0-9\.]$/)) {
|
||||
let currentToken = token;
|
||||
|
||||
while (index < source.length && source[index].match(/^[0-9\.]$/)) {
|
||||
currentToken += source[index];
|
||||
index += 1;
|
||||
}
|
||||
|
||||
const value = Number(currentToken);
|
||||
tokens.push({
|
||||
kind: "number",
|
||||
value,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
if (token.match(/^[a-z]$/)) {
|
||||
let currentToken = token;
|
||||
|
||||
while (index < source.length && source[index].match(/^[a-z0-9_]$/)) {
|
||||
currentToken += source[index];
|
||||
index += 1;
|
||||
}
|
||||
|
||||
tokens.push({
|
||||
kind: "variable",
|
||||
value: currentToken,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
throw new Error(`Syntax error: Unexpected "${token}"`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
Executable
+98
@@ -0,0 +1,98 @@
|
||||
const $ = document.querySelector.bind(document);
|
||||
const nameEl = $("#name");
|
||||
const uploadEl = $("#upload");
|
||||
const programEl = $("#program");
|
||||
const contentTypeEl = $("#content-type");
|
||||
|
||||
uploadEl.addEventListener("click", async () => {
|
||||
const code = programEl.value;
|
||||
// We use logical or instead of nullish coalescing because
|
||||
// [input] values are always coerced to a string
|
||||
const name = nameEl.value || nameEl.getAttribute("placeholder");
|
||||
const type = contentTypeEl.value;
|
||||
|
||||
const res = await fetch("/upload", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ type, program: { name, code } })
|
||||
});
|
||||
|
||||
const url = await res.text();
|
||||
window.location.href = url;
|
||||
});
|
||||
|
||||
const staticAst = `{
|
||||
"kind": "binop",
|
||||
"op": "divide",
|
||||
"values": [
|
||||
{
|
||||
"kind": "binop",
|
||||
"op": "add",
|
||||
"values": [
|
||||
{
|
||||
"kind": "unop",
|
||||
"op": "negate",
|
||||
"value": { "kind": "variable", "variable": "b"}
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "sqrt",
|
||||
"argument": {
|
||||
"kind": "binop",
|
||||
"op": "subtract",
|
||||
"values": [
|
||||
{
|
||||
"kind": "binop",
|
||||
"op": "exponent",
|
||||
"values": [
|
||||
{ "kind": "variable", "variable": "b"},
|
||||
{ "kind": "number", "value": 2}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "binop",
|
||||
"op": "multiply",
|
||||
"values": [
|
||||
{ "kind": "number", "value": 4 },
|
||||
{
|
||||
"kind": "binop",
|
||||
"op": "multiply",
|
||||
"values": [
|
||||
{ "kind": "variable", "variable": "a" },
|
||||
{ "kind": "variable", "variable": "c" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "binop",
|
||||
"op": "multiply",
|
||||
"values": [
|
||||
{ "kind": "number", "value": 2 },
|
||||
{ "kind": "variable", "variable": "a" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}`;
|
||||
|
||||
const staticCode = `(-b + sqrt(b^2 - 4a*c)) / 2a`;
|
||||
|
||||
const resetTextArea = () => {
|
||||
const contentType = contentTypeEl.value;
|
||||
|
||||
if (contentType === "application/x-yaca-ast") {
|
||||
programEl.value = staticAst;
|
||||
} else {
|
||||
programEl.value = staticCode;
|
||||
}
|
||||
}
|
||||
|
||||
contentTypeEl.addEventListener("change", resetTextArea);
|
||||
resetTextArea();
|
||||
Executable
+234
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
Operation precedences:
|
||||
0: exponentiation
|
||||
1: implicit multiplication
|
||||
2: multiplication/division
|
||||
3: unary operators
|
||||
4: addition/subtraction
|
||||
*/
|
||||
|
||||
|
||||
// We could probably have lexed directly into the parse token, but
|
||||
// this way we keep a bit more separation of church & state
|
||||
const lexTokenToParseToken = (token) => {
|
||||
if (token === undefined) {
|
||||
return { kind: "EOF" };
|
||||
}
|
||||
|
||||
switch (token.kind) {
|
||||
case "operator": {
|
||||
switch (token.value) {
|
||||
case "invert": return { kind: "UNOP", value: token.value, precedence: 3, isOp: true };
|
||||
case "subtract": return { kind: "MAYBE_UNOP", value: token.value, unopValue: "negate", precedence: 4, isOp: true };
|
||||
case "exponent": return { kind: "BINOP", value: token.value, precedence: 0, isOp: true, };
|
||||
case "multiply": return { kind: "BINOP", value: token.value, precedence: 2, isOp: true, };
|
||||
case "divide": return { kind: "BINOP", value: token.value, precedence: 2, isOp: true, };
|
||||
case "add": return { kind: "BINOP", value: token.value, precedence: 4, isOp: true, };
|
||||
default: throw new Error(`Unknown operator ${token.value}`);
|
||||
}
|
||||
}
|
||||
case "open-paren": return { kind: "EXPR_START" };
|
||||
case "close-paren": return { kind: "EXPR_END" };
|
||||
case "number": return { kind: "VALUE", ast: { kind: "number", value: token.value } };
|
||||
case "variable": return { kind: "VALUE", ast: { kind: "variable", variable: token.value } };
|
||||
default: throw new Error(`Unknown token kind ${token.kind}`);
|
||||
}
|
||||
}
|
||||
|
||||
const mightBeUnop = (token) => {
|
||||
return (
|
||||
token.kind === "UNOP"
|
||||
|| token.kind === "MAYBE_UNOP"
|
||||
|| token.kind === "FUNCTION"
|
||||
|| token.kind === "IMPLICIT_MULTIPLICATION"
|
||||
);
|
||||
}
|
||||
|
||||
const mightBeBinop = (token) => {
|
||||
return (
|
||||
token.kind === "BINOP"
|
||||
|| token.kind === "MAYBE_UNOP"
|
||||
);
|
||||
}
|
||||
|
||||
const parseOne = (stack, lookahead) => {
|
||||
// If we can reduce a parenthetical expression, we want to
|
||||
if (stack[0]?.kind === "EXPR_END") {
|
||||
const [_end, value, _start, ...rest] = stack;
|
||||
|
||||
if (stack[1]?.kind !== "VALUE" || stack[2]?.kind !== "EXPR_START") {
|
||||
throw new Error("Received unexpected close parenthesis");
|
||||
}
|
||||
|
||||
return ["reduce", [value, ...rest]];
|
||||
}
|
||||
|
||||
// Otherwise, all of our reductions occur on values
|
||||
if (stack[0]?.kind === "VALUE") {
|
||||
// We have some special cases for two adjacent values, when the first one is
|
||||
// a pure variable (function call) or number (term multiplication)
|
||||
if (lookahead.kind === "VALUE" || lookahead.kind === "EXPR_START") {
|
||||
if (stack[0].ast.kind === "variable") {
|
||||
const [token, ...rest] = stack;
|
||||
const newToken = {
|
||||
kind: "FUNCTION",
|
||||
isOp: true,
|
||||
precedence: 3,
|
||||
name: token.ast.variable
|
||||
}
|
||||
|
||||
const newStack = [newToken, ...rest];
|
||||
return ["reduce", newStack];
|
||||
}
|
||||
|
||||
if (stack[0].ast.kind === "number") {
|
||||
const [token, ...rest] = stack;
|
||||
const newToken = {
|
||||
kind: "IMPLICIT_MULTIPLICATION",
|
||||
isOp: true,
|
||||
precedence: 1,
|
||||
value: token.ast.value
|
||||
}
|
||||
|
||||
const newStack = [newToken, ...rest];
|
||||
return ["reduce", newStack];
|
||||
}
|
||||
}
|
||||
|
||||
// If we have an operator on our stack, we want to reduce it unless the upcoming
|
||||
// operator has a stronger precedence
|
||||
if (stack[1]?.isOp) {
|
||||
const shouldShift = mightBeBinop(lookahead) && lookahead.precedence < stack[1].precedence;
|
||||
if (shouldShift) {
|
||||
return ["shift"];
|
||||
}
|
||||
|
||||
const isBinop = mightBeBinop(stack[1]) && stack[2]?.kind === "VALUE";
|
||||
|
||||
if (isBinop) {
|
||||
const [right, binop, left, ...rest] = stack;
|
||||
|
||||
// Binops are easy
|
||||
const newValue = {
|
||||
kind: "VALUE",
|
||||
ast: {
|
||||
kind: "binop",
|
||||
op: binop.value,
|
||||
values: [
|
||||
left.ast,
|
||||
right.ast,
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
return ["reduce", [newValue, ...rest]]
|
||||
}
|
||||
|
||||
const isUnop = mightBeUnop(stack[1]);
|
||||
if (!isUnop) {
|
||||
throw new Error("Unexpected operator");
|
||||
}
|
||||
|
||||
const [value, unop, ...rest] = stack;
|
||||
let newToken;
|
||||
|
||||
// Handle each of the different types of unary operators
|
||||
switch (unop.kind) {
|
||||
case "UNOP":
|
||||
case "MAYBE_UNOP": {
|
||||
const op = unop.kind === "MAYBE_UNOP" ? unop.unopValue : unop.value;
|
||||
newToken = {
|
||||
kind: "VALUE",
|
||||
ast: {
|
||||
kind: "unop",
|
||||
op,
|
||||
value: value.ast
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "FUNCTION": {
|
||||
newToken = {
|
||||
kind: "VALUE",
|
||||
ast: {
|
||||
kind: "function",
|
||||
name: unop.name,
|
||||
argument: value.ast,
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "IMPLICIT_MULTIPLICATION": {
|
||||
newToken = {
|
||||
kind: "VALUE",
|
||||
ast: {
|
||||
kind: "binop",
|
||||
op: "multiply",
|
||||
values: [
|
||||
{ kind: "number", value: unop.value },
|
||||
value.ast,
|
||||
]
|
||||
}
|
||||
};
|
||||
break
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unknown unary operator: ${unop.kind}`);
|
||||
}
|
||||
}
|
||||
|
||||
return ["reduce", [newToken, ...rest]];
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, we have no reductions to do, so we shift in a new token
|
||||
return ["shift"];
|
||||
}
|
||||
|
||||
|
||||
export default (tokens) => {
|
||||
const queue = [...tokens];
|
||||
let stack = [];
|
||||
|
||||
const maxIter = 1000;
|
||||
let iter = 0;
|
||||
|
||||
while (queue.length > 0 || stack.length > 1) {
|
||||
// I haven't proven that this terminates so uh
|
||||
// Hopefully this will keep me from nuking anyone's chrome
|
||||
if (iter >= maxIter) {
|
||||
throw new Error("Timeout");
|
||||
}
|
||||
iter++;
|
||||
|
||||
const lookahead = lexTokenToParseToken(queue[0]);
|
||||
const action = parseOne(stack, lookahead);
|
||||
|
||||
if (window.DEBUG) {
|
||||
console.log([...stack], lookahead, action);
|
||||
}
|
||||
|
||||
switch (action[0]) {
|
||||
case "shift": {
|
||||
if (lookahead.kind === "EOF") {
|
||||
throw new Error("Attempting to shift EOF, which indicates a malformed program");
|
||||
}
|
||||
|
||||
queue.shift();
|
||||
stack = [lookahead, ...stack]
|
||||
break;
|
||||
}
|
||||
case "reduce": {
|
||||
stack = action[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we parsed correctly, we should be left with a single value
|
||||
// representing our final result
|
||||
if (stack[0]?.kind !== "VALUE") {
|
||||
throw new Error("Parser did not return a value");
|
||||
}
|
||||
|
||||
return stack[0].ast;
|
||||
}
|
||||
Reference in New Issue
Block a user