352 lines
7.9 KiB
JavaScript
352 lines
7.9 KiB
JavaScript
import "./scanner";
|
|
import {
|
|
scan,
|
|
error,
|
|
BANG,
|
|
BANG_EQUAL,
|
|
EOF,
|
|
EQUAL,
|
|
EQUAL_EQUAL,
|
|
FALSE,
|
|
GREATER,
|
|
GREATER_EQUAL,
|
|
IDENTIFIER,
|
|
LEFT_BRACE,
|
|
LEFT_PAREN,
|
|
LESS,
|
|
LESS_EQUAL,
|
|
MINUS,
|
|
NUMBER,
|
|
PLUS,
|
|
PRINT,
|
|
RIGHT_BRACE,
|
|
RIGHT_PAREN,
|
|
SEMICOLON,
|
|
SLASH,
|
|
STAR,
|
|
STRING,
|
|
TRUE,
|
|
VAR,
|
|
START,
|
|
COMMA,
|
|
GO,
|
|
STAIRCASE,
|
|
PILLARS,
|
|
MOVING_PILLARS,
|
|
LEFT,
|
|
RIGHT,
|
|
TURN,
|
|
REPEAT
|
|
} from "./scanner";
|
|
|
|
export function parse(code) {
|
|
const tokens = scan(code);
|
|
// console.log(tokens);
|
|
let current = 0;
|
|
|
|
const parse = () => {
|
|
let statements = [];
|
|
|
|
while (!is_at_end()) {
|
|
try {
|
|
statements.push(declaration());
|
|
} catch (e) {
|
|
console.log(e);
|
|
current = tokens.length - 1; // stop compiling
|
|
}
|
|
}
|
|
return statements;
|
|
}
|
|
|
|
const declaration = () => {
|
|
if (match(VAR)) {
|
|
return varDeclaration();
|
|
}
|
|
return statement();
|
|
}
|
|
|
|
const varDeclaration = () => {
|
|
const name = consume(IDENTIFIER, "Expected a variable name");
|
|
let initializer;
|
|
if (match(EQUAL)) {
|
|
initializer = expression();
|
|
}
|
|
consume(SEMICOLON, "Expected semicolon");
|
|
return {
|
|
class: "var",
|
|
name: name,
|
|
initializer: initializer,
|
|
accept: (visitor) => visitor.visitVariableStatement(name, initializer)
|
|
};
|
|
}
|
|
|
|
const statement = () => {
|
|
if (match(PRINT)) {
|
|
return printStatement();
|
|
}
|
|
if (match(START)) {
|
|
return callStatement("start");
|
|
}
|
|
if (match(GO)) {
|
|
return callStatement("go");
|
|
}
|
|
if (match(LEFT)) {
|
|
return callStatement("left");
|
|
}
|
|
if (match(RIGHT)) {
|
|
return callStatement("right");
|
|
}
|
|
if (match(TURN)) {
|
|
return callStatement("turn");
|
|
}
|
|
if (match(STAIRCASE)) {
|
|
return callStatement("staircase");
|
|
}
|
|
if (match(PILLARS)) {
|
|
return callStatement("pillars");
|
|
}
|
|
if (match(MOVING_PILLARS)) {
|
|
return callStatement("moving_pillars");
|
|
}
|
|
if (match(REPEAT)) {
|
|
return call_block();
|
|
}
|
|
if (match(LEFT_BRACE)) {
|
|
const blockStatement = block();
|
|
return {class: "block", accept: (visitor) => visitor.visitBlockStatement(blockStatement)};
|
|
}
|
|
|
|
return expressionStatement();
|
|
}
|
|
|
|
const expression = () => {
|
|
return assignment();
|
|
}
|
|
|
|
const arg_expressions = () => {
|
|
consume(LEFT_PAREN, "Expect '('");
|
|
let exprs = [expression()];
|
|
while (match(COMMA)) {
|
|
exprs.push(expression());
|
|
}
|
|
consume(RIGHT_PAREN, "Expect ')'");
|
|
return exprs;
|
|
}
|
|
|
|
const assignment = () => {
|
|
let expr = equality();
|
|
if (match(EQUAL)) {
|
|
let equals = previous();
|
|
let value = assignment();
|
|
|
|
if (expr.class === 'Variable') {
|
|
let name = expr.name;
|
|
return {
|
|
class: "assign",
|
|
var_name: name,
|
|
init_expr: value,
|
|
accept: (visitor) => visitor.visitAssignExpr(name, value)
|
|
};
|
|
}
|
|
|
|
throw error(equals, "Invalid assignment target.");
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
const printStatement = () => {
|
|
const value = expression();
|
|
consume(SEMICOLON, "Expected semicolon");
|
|
return {class: "print", value: value, accept: (visitor) => visitor.visitPrintStatement(value)};
|
|
}
|
|
|
|
const callStatement = (name) => {
|
|
const args = arg_expressions();
|
|
consume(SEMICOLON, "Expected semicolon");
|
|
return {class: "call", accept: (visitor) => visitor.visitCallStatement(name, args)};
|
|
}
|
|
|
|
const call_block = () => {
|
|
const args = arg_expressions();
|
|
consume(LEFT_BRACE, "Expect block");
|
|
const action = block();
|
|
return {class: "block", accept: (visitor) => visitor.visitParametrizedBlock(args, action)};
|
|
}
|
|
|
|
const expressionStatement = () => {
|
|
const value = expression();
|
|
consume(SEMICOLON, "Expected semicolon");
|
|
return {
|
|
class: "expressionStatement",
|
|
expression: value,
|
|
accept: (visitor) => visitor.visitExpressionStatement(value)
|
|
};
|
|
}
|
|
|
|
const block = () => {
|
|
let statements = [];
|
|
while (!check(RIGHT_BRACE) && !is_at_end()) {
|
|
statements.push(declaration());
|
|
}
|
|
consume(RIGHT_BRACE, "Expect '}' after block.");
|
|
return statements;
|
|
}
|
|
|
|
const equality = () => {
|
|
let expr = comparison();
|
|
while (match(BANG_EQUAL, EQUAL_EQUAL)) {
|
|
let operator = previous();
|
|
let right = comparison();
|
|
const left = expr;
|
|
expr = {
|
|
class: "binaryExpr",
|
|
operator: operator,
|
|
left: expr,
|
|
right: right,
|
|
accept: (visitor) => visitor.visitBinaryExpr(operator, left, right)
|
|
};
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
const comparison = () => {
|
|
let expr = term();
|
|
while (match(GREATER, GREATER_EQUAL, LESS, LESS_EQUAL)) {
|
|
let operator = previous();
|
|
let right = term();
|
|
const left = expr;
|
|
expr = {
|
|
class: "binaryExpr",
|
|
operator: operator,
|
|
left: expr,
|
|
right: right,
|
|
accept: (visitor) => visitor.visitBinaryExpr(operator, left, right)
|
|
};
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
const term = () => {
|
|
let expr = factor();
|
|
while (match(MINUS, PLUS)) {
|
|
let operator = previous();
|
|
let right = factor();
|
|
const left = expr;
|
|
expr = {
|
|
class: "binaryExpr",
|
|
operator: operator,
|
|
left: expr,
|
|
right: right,
|
|
accept: (visitor) => visitor.visitBinaryExpr(operator, left, right)
|
|
};
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
const factor = () => {
|
|
let expr = unary();
|
|
while (match(SLASH, STAR)) {
|
|
let operator = previous();
|
|
let right = unary();
|
|
const left = expr;
|
|
expr = {
|
|
class: "binaryExpr",
|
|
operator: operator,
|
|
left: expr,
|
|
right: right,
|
|
accept: (visitor) => visitor.visitBinaryExpr(operator, left, right)
|
|
};
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
const unary = () => {
|
|
if (match(BANG, MINUS)) {
|
|
let operator = previous();
|
|
let right = unary();
|
|
return {
|
|
class: "unaryExpr",
|
|
operator: operator,
|
|
right: right,
|
|
accept: (visitor) => visitor.visitUnaryExpr(operator, right)
|
|
};
|
|
}
|
|
return primary();
|
|
}
|
|
|
|
const primary = () => {
|
|
if (match(FALSE)) {
|
|
return {class: "boolean", value: false, accept: (visitor) => visitor.visitLiteralExpr(false)};
|
|
}
|
|
if (match(TRUE)) {
|
|
return {class: "boolean", value: true, accept: (visitor) => visitor.visitLiteralExpr(true)};
|
|
}
|
|
if (check(NUMBER)) {
|
|
advance();
|
|
let number = parseFloat(previous().literal);
|
|
return {class: "number", value: number, accept: (visitor) => visitor.visitLiteralExpr(number)};
|
|
}
|
|
if (check(STRING)) {
|
|
advance();
|
|
let string = previous().literal;
|
|
return {class: "string", value: string, accept: (visitor) => visitor.visitLiteralExpr(string)};
|
|
}
|
|
if (match(IDENTIFIER)) {
|
|
let identifier = previous();
|
|
return {class: "Variable", identifier: identifier, accept: (visitor) => visitor.visitVariableExpr(identifier)};
|
|
}
|
|
if (match(LEFT_PAREN)) {
|
|
let expr = expression();
|
|
consume(RIGHT_PAREN, "Expect ')' after expression.");
|
|
return {class: "groupExpr", expression: expr, accept: (visitor) => visitor.visitGroupingExpr(expr)};
|
|
}
|
|
throw error(peek(), "Expect expression.");
|
|
}
|
|
|
|
const consume = (type, message) => {
|
|
if (check(type)) {
|
|
return advance();
|
|
}
|
|
throw error(peek(), message);
|
|
}
|
|
|
|
const match = (...types) => {
|
|
for (let i = 0; i < types.length; i++) {
|
|
if (check(types[i])) {
|
|
advance();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const check = (type) => {
|
|
// console.log(peek().type+"==="+type);
|
|
if (is_at_end()) {
|
|
return false;
|
|
}
|
|
return peek().type === type;
|
|
}
|
|
|
|
const advance = () => {
|
|
if (!is_at_end()) {
|
|
current++;
|
|
}
|
|
return previous();
|
|
}
|
|
|
|
const is_at_end = () => {
|
|
return peek().type === EOF;
|
|
}
|
|
|
|
const peek = () => {
|
|
return tokens[current];
|
|
}
|
|
|
|
const previous = () => {
|
|
return tokens[current - 1];
|
|
}
|
|
|
|
return parse();
|
|
}
|