xxxxxxxxxx
165
function setup() {
createCanvas(400, 400);
expressions = [
"10",
"1 + 2",
"3 - 2",
"2 * 3",
"4 / 2",
"1 + 2 + 3",
"3 - 2 - 1",
"1 * 2 * 3",
"3 / 2 / 1",
"5 + 4 - 3 * 2 / 1",
"1 * 2 + 3 / 4",
"1 * (2 + 3) / 4",
"1 * (2 + 3 / (4 - 5))"
];
background("black");
fill("white");
noStroke();
textAlign(RIGHT);
const size = 16;
textSize(size - 2);
expressions.forEach((expr, i) => {
const res = run(expr);
const test = eval(expr);
const pass = res == test ? "PASSED" : "FAILED";
console.log(pass);
console.log();
text(`${expr} = ${res} ${pass}`,
width * 0.7,
height /2 - expressions.length * size / 2 + i * size
);
});
}
function run(expr) {
console.log(expr);
const tokens = lexer(expr);
console.log(tokens);
// ast abstract syntax tree
const ast = parser(tokens);
console.log(ast);
const res = exec(ast);
console.log(res);
return res;
}
function exec(node) {
const { kind } = node;
switch (kind) {
case "op":
const { op, left, right } = node;
switch (op) {
case "+": return exec(left) + exec(right);
case "-": return exec(left) - exec(right);
case "*": return exec(left) * exec(right);
case "/": return exec(left) / exec(right);
default: return null;
}
case "number": {
const { val } = node;
return +val;
}
case "brackets": {
const { body } = node;
return exec(body);
}
default: return null;
}
}
function parser(tokens) {
let i = 0;
const current = () => {
return tokens[i];
}
const accept = (kind) => {
const token = current();
if (!token || token.kind !== kind)
return false;
i++;
return true;
}
const expect = (kind) => {
const token = current();
if (!accept(kind))
throw new Error(`Expected: ${kind}, Got: ${token && token.kind}, Index: ${i}`);
return token.val;
}
const number = () => {
if (accept("bracketOpen")) {
const body = expression();
expect("bracketClose");
return { kind: "brackets", body }
}
const val = expect("number");
return { kind: "number", val };
}
const term = (left) => {
left = left ?? number();
const token = current();
if (!accept("termOp"))
return left;
const right = number();
return term({ kind: "op", op: token.val, left, right });
}
const expression = (left) => {
left = left ?? term();
const token = current();
if (!accept("expressionOp"))
return left;
const right = term();
return expression({ kind: "op", op: token.val, left, right });
}
return expression();
}
function removeWhiteSpace(script) {
return script.replace(/\s/g, "");
}
function lexer(script) {
script = removeWhiteSpace(script);
const rules = [
{ match: /^[0-9]+/, kind: "number" },
{ match: /^[\+\-]/, kind: "expressionOp" },
{ match: /^[\*\/]/, kind: "termOp" },
{ match: /^\(/, kind: "bracketOpen" },
{ match: /^\)/, kind: "bracketClose" },
];
const tokens = [];
loop: while (script.length) {
for (const rule of rules) {
const match = script.match(rule.match);
if (match) {
const { kind } = rule;
const val = match[0];
tokens.push({ kind, val });
script = script.slice(val.length, script.length);
continue loop;
}
}
break;
}
return tokens;
}