293 lines
7 KiB
JavaScript
293 lines
7 KiB
JavaScript
import {parse} from "./parser";
|
|
import {
|
|
BANG,
|
|
BANG_EQUAL,
|
|
EQUAL_EQUAL,
|
|
GREATER,
|
|
GREATER_EQUAL,
|
|
LESS,
|
|
LESS_EQUAL,
|
|
MINUS,
|
|
PLUS,
|
|
SLASH,
|
|
STAR
|
|
} from "./scanner";
|
|
import {atan, cos, random, sin, tan} from "./functions";
|
|
|
|
export function interpret(init_env, code) {
|
|
const log_console = document.getElementById("log");
|
|
let {canvas, tx, ty, tangle, unit, width, height} = init_env;
|
|
init_env.PI = Math.PI;
|
|
const UP = "UP";
|
|
const DOWN = "DOWN";
|
|
const LEFT = "LEFT";
|
|
const RIGHT = "RIGHT";
|
|
const BOTH = "BOTH";
|
|
|
|
Object.assign(init_env, {UP, DOWN, LEFT, RIGHT, BOTH});
|
|
const x0 = width / 2;
|
|
const y0 = height / 4;
|
|
|
|
let THIS = {
|
|
current_environment: init_env,
|
|
|
|
execute: (statement) => {
|
|
statement.accept(THIS);
|
|
},
|
|
|
|
visitBlockStatement: (statements) => {
|
|
THIS.executeBlock(statements, {enclosing: THIS.current_environment});
|
|
},
|
|
|
|
visitParametrizedBlock: (argList, statements) => {
|
|
let args = argList.map(THIS.evaluate);
|
|
let start, end;
|
|
if (args.length === 2) {
|
|
start = args[0];
|
|
end = args[1];
|
|
} else {
|
|
start = 0;
|
|
end = args[0];
|
|
}
|
|
let previous = THIS.current_environment;
|
|
THIS.current_environment = {enclosing: previous};
|
|
try {
|
|
for (let i = start; i < end; i++) {
|
|
THIS.current_environment.it = i;
|
|
for (let i = 0; i < statements.length; i++) {
|
|
THIS.execute(statements[i]);
|
|
}
|
|
}
|
|
} finally {
|
|
THIS.current_environment = previous;
|
|
}
|
|
},
|
|
|
|
visitExpressionStatement: (expression) => {
|
|
THIS.evaluate(expression);
|
|
},
|
|
|
|
visitVariableStatement: (name, initializer) => {
|
|
let value = undefined;
|
|
if (initializer) {
|
|
value = THIS.evaluate(initializer);
|
|
}
|
|
THIS.current_environment[name.lexeme] = value;
|
|
},
|
|
|
|
visitPrintStatement: (expression) => {
|
|
let value = THIS.evaluate(expression);
|
|
log_console.innerText += THIS.stringify(value) +"\n";
|
|
log_console.scrollTop = log_console.scrollHeight;
|
|
},
|
|
|
|
visitCallStatement: (fun, argList) => {
|
|
let args = argList.map(THIS.evaluate);
|
|
switch (fun) {
|
|
case "start":
|
|
THIS.start(...args);
|
|
break;
|
|
case "go":
|
|
THIS.go(...args);
|
|
break;
|
|
case "turn":
|
|
THIS.turn(...args);
|
|
break;
|
|
case "left":
|
|
THIS.left(...args);
|
|
break;
|
|
case "right":
|
|
THIS.right(...args);
|
|
break;
|
|
case "pillars":
|
|
THIS.pillars(...args);
|
|
break;
|
|
case "moving_pillars":
|
|
THIS.moving_pillars(...args);
|
|
break;
|
|
case "staircase":
|
|
THIS.staircase(...args);
|
|
break;
|
|
default:
|
|
throw "Unknown function: " + fun;
|
|
}
|
|
},
|
|
|
|
visitCallFunction: (name, argList) => {
|
|
let args = argList.map(THIS.evaluate);
|
|
switch (name) {
|
|
case "random":
|
|
return random(...args);
|
|
case "cos":
|
|
return cos(...args);
|
|
case "sin":
|
|
return sin(...args);
|
|
case "tan":
|
|
return tan(...args);
|
|
case "atan":
|
|
return atan(...args);
|
|
}
|
|
},
|
|
|
|
start: (x, y) => {
|
|
tx = x * unit;
|
|
ty = y * unit;
|
|
tangle = 0;
|
|
canvas.moveTo(x0 + tx, y0 + ty);
|
|
},
|
|
|
|
go: (distance) => {
|
|
tx += distance * unit * Math.cos(tangle);
|
|
ty += distance * unit * Math.sin(tangle);
|
|
canvas.lineTo(x0 + tx, y0 + ty);
|
|
},
|
|
|
|
turn: (degrees) => {
|
|
tangle += degrees * Math.PI / 180;
|
|
},
|
|
|
|
left: (degrees) => {
|
|
THIS.turn(-degrees);
|
|
},
|
|
|
|
right: (degrees) => {
|
|
THIS.turn(degrees);
|
|
},
|
|
|
|
pillars: (number, length, shift = 0, direction = UP) => {
|
|
for (let i = 0; i < number; i++) {
|
|
THIS.turn(-90);
|
|
THIS.go(direction === DOWN ? -length : length);
|
|
THIS.turn(90);
|
|
THIS.go(1);
|
|
THIS.turn(90);
|
|
if (direction === BOTH) {
|
|
length += shift;
|
|
}
|
|
THIS.go(direction === DOWN ? -length : length);
|
|
THIS.turn(-90);
|
|
THIS.go(1);
|
|
length += shift;
|
|
}
|
|
},
|
|
|
|
moving_pillars: (n, length, shift = 0, direction = DOWN) => {
|
|
let length2 = length;
|
|
length += shift;
|
|
for (let i = 0; i < n; i++) {
|
|
THIS.turn(-90);
|
|
THIS.go(direction === UP ? -length2 : length2);
|
|
THIS.turn(90);
|
|
THIS.go(1);
|
|
THIS.turn(90);
|
|
THIS.go(direction === UP ? -length : length);
|
|
THIS.turn(-90);
|
|
THIS.go(1);
|
|
}
|
|
},
|
|
|
|
staircase: (number_of_steps, size, direction = DOWN) => {
|
|
const angle = direction === DOWN ? 90 : -90;
|
|
for (let i = 0; i < number_of_steps; i++) {
|
|
THIS.go(size);
|
|
THIS.turn(angle);
|
|
THIS.go(size);
|
|
THIS.turn(-angle);
|
|
}
|
|
},
|
|
|
|
executeBlock: (statements, environment) => {
|
|
let previous = THIS.current_environment;
|
|
try {
|
|
THIS.current_environment = environment;
|
|
for (let i = 0; i < statements.length; i++) {
|
|
THIS.execute(statements[i]);
|
|
}
|
|
} finally {
|
|
THIS.current_environment = previous;
|
|
}
|
|
},
|
|
|
|
visitBinaryExpr: (operator, l, r) => {
|
|
let left = THIS.evaluate(l);
|
|
let right = THIS.evaluate(r);
|
|
|
|
switch (operator.type) {
|
|
case MINUS:
|
|
return left - right;
|
|
case SLASH:
|
|
return left / right;
|
|
case STAR:
|
|
return left * right;
|
|
case PLUS:
|
|
if (typeof left === 'number' && typeof right === 'number') {
|
|
return left + right;
|
|
} else {
|
|
return THIS.stringify(left) + THIS.stringify(right);
|
|
}
|
|
case GREATER:
|
|
return left > right;
|
|
case GREATER_EQUAL:
|
|
return left >= right;
|
|
case LESS:
|
|
return left < right;
|
|
case LESS_EQUAL:
|
|
return left <= right;
|
|
case BANG_EQUAL:
|
|
return left !== right;
|
|
case EQUAL_EQUAL:
|
|
return left === right;
|
|
}
|
|
throw "?";
|
|
},
|
|
|
|
visitGroupingExpr: (expr) => {
|
|
return THIS.evaluate(expr);
|
|
},
|
|
|
|
visitLiteralExpr: (value) => {
|
|
return value;
|
|
},
|
|
|
|
visitUnaryExpr: (operator, right_expr) => {
|
|
let right = THIS.evaluate(right_expr);
|
|
|
|
switch (operator.type) {
|
|
case MINUS:
|
|
return -right;
|
|
case BANG:
|
|
return !right;
|
|
default:
|
|
return undefined;
|
|
}
|
|
},
|
|
|
|
visitVariableExpr: (name) => {
|
|
return THIS.current_environment[name.lexeme];
|
|
},
|
|
|
|
visitAssignExpr: (name, value_expr) => {
|
|
let value = THIS.evaluate(value_expr);
|
|
THIS.current_environment[name] = value;
|
|
return value;
|
|
},
|
|
|
|
evaluate: (expr) => {
|
|
return expr.accept(THIS);
|
|
},
|
|
|
|
stringify: (value) => {
|
|
if (value === undefined || value === null) {
|
|
return "nil";
|
|
}
|
|
return value.toString();
|
|
}
|
|
};
|
|
|
|
const statements = parse(code);
|
|
for (let i = 0; i < statements.length; i++) {
|
|
// console.log(statements[i]);
|
|
THIS.execute(statements[i]);
|
|
}
|
|
}
|
|
|