cookeylang
Version:
A dynamic, interpreted language.
601 lines (460 loc) • 15.9 kB
JavaScript
const error = require("./error");
function parser(tokens) {
let index = 0;
// Helpers
function peek() { return tokens[index] || {}; }
function consume(name, expect) {
if (tokens[index].name != name) error(tokens[index].line, expect);
index++;
return tokens[index];
}
function match(...types) {
for (const type of types) {
if (check(type)) {
index++;
return true;
}
}
return false;
}
function check(type) {
if (isAtEnd()) return false;
return peek().name == type;
}
function isAtEnd() {
return peek().name == "EOF";
}
function peek() {
return tokens[index];
}
function previous() {
return tokens[index - 1];
}
// Parsing
function declaration() {
// We will use the same for class methods as well, so that's what kind is.
if (match("CLASS")) return classDeclaration();
if (match("FUNCTION")) return funcDeclaration("function");
if (match("VAR", "FINAL")) return varDeclaration();
return statement();
}
function classDeclaration() {
let name = peek();
consume("IDENTIFIER", "Expect class name.");
let superclass = null;
if (match("EXTENDS")) {
consume("IDENTIFIER", "Expect superclass name.");
superclass = { type: "variable", line: previous().line, name: previous().value };
}
consume("LEFT_BRACE", "Expect '{' before class body.");
let methods = []
while (!check("RIGHT_BRACE") && !isAtEnd()) {
methods.push(funcDeclaration("method"));
}
consume("RIGHT_BRACE", "Expect '}' after class body.");
return { type: "class", line: name.line, name, superclass, methods };
}
function funcDeclaration(kind) {
let name = peek();
consume("IDENTIFIER", "Expect " + kind + " name.")
let line = name.line;
consume("LEFT_PAREN", "Expect '(' after " + kind + " name.");
let params = [];
if (!check("RIGHT_PAREN")) {
do {
params.push(peek());
consume("IDENTIFIER", "Expect parameter name.")
} while (match("COMMA"));
}
consume("RIGHT_PAREN", "Expect ')' after parameters.");
consume("LEFT_BRACE", "Expect '{' before " + kind + " body.");
let body = block();
return { type: "funcdef", line, name, params, body }
}
function varDeclaration() {
let type = previous().value;
let line = previous().line;
consume("IDENTIFIER", "Expected an identifier.");
let name = previous().value;
if (peek().name != "EQ" && type.name == "FINAL") error(peek().line, "Constants require a value.");
let value = null;
if (match("EQ")) {
value = expression();
}
consume("SEMI", "Expect ';' after variable declaration.");
return { type: "vardef", line, mut: type, name, value }
}
function statement() {
if (match("IF")) return ifStatement();
if (match("RET")) return returnStatement();
if (match("WHILE")) return whileStatement();
if (match("DO")) return doWhileStatement();
if (match("FOR")) return forStatement();
if (match("FORREP")) return forrepStatement();
if (match("SWITCH")) return switchCaseStatement();
if (match("BREAK")) return breakStatement();
if (match("DELETEVARIABLE")) return deleteVariableStatement();
if (match("EXIT")) return exitStatement();
if (match("LEFT_BRACE")) return { type: "block", line: previous().line, block: block() };
return expressionStatement();
}
function ifStatement() {
consume("LEFT_PAREN", "Expect '(' after if statement.");
let line = previous().line;
let condition = expression();
consume("RIGHT_PAREN", "Expect ')' after if statement.");
let ifTrue = statement();
let ifFalse = null;
if (match("EL")) {
ifFalse = statement();
}
return { type: "ifstatement", line, condition, ifTrue, ifFalse };
}
function returnStatement() {
let keyword = previous();
let line = peek().line;
let value = null;
if (!check("SEMI")) {
value = expression();
}
consume("SEMI", "Expect ';' after return value.");
return { type: "return", line, keyword, value };
}
function doWhileStatement() {
let body = statement();
consume("WHILE", "Expect 'while' condition.");
consume("LEFT_PAREN", "Expect '(' after 'while'.");
let condition = expression();
consume("RIGHT_PAREN", "Expect ')' after condition.");
consume("SEMI", "Expect ';' after condition.")
return { type: "dowhile", line: condition.line, condition, body };
}
function whileStatement() {
consume("LEFT_PAREN", "Expect '(' after 'while'.");
let condition = expression();
consume("RIGHT_PAREN", "Expect ')' after condition.");
let body = statement();
return { type: "whileloop", line: condition.line, condition, body };
}
function forStatement() {
consume("LEFT_PAREN", "Expect '(' after 'for'.")
let line = previous().line;
let initializer;
if (match("SEMI")) {
initializer = null;
} else if (match("VAR", "FINAL")) {
initializer = varDeclaration();
} else {
initializer = expressionStatement();
}
let condition = null;
if (!check("SEMI")) {
condition = expression();
}
consume("SEMI", "Expect ',' after loop condition.");
let increment = null;
if (!check("RIGHT_PAREN")) {
increment = expression();
}
consume("RIGHT_PAREN", "Expect ')' after for clauses.");
let body = statement();
if (increment != null) {
body = { type: "block", line, block: [
body,
{ type: "expr", line, expr: increment }
] };
}
if (condition == null) condition = { type: "literal", value: true, line };
body = { type: "whileloop", line, condition, body };
if (initializer != null) {
body = { type: "block", line, block: [
initializer, body
] };
}
return body;
}
function switchCaseStatement() {
consume("LEFT_PAREN", "Expect '(' after switch")
let condition = expression();
consume("RIGHT_PAREN", "Expect ')' after condition")
consume("LEFT_BRACE", "Expect '{' after switch value");
let checks = []
while (!check("RIGHT_BRACE") && !isAtEnd()) {
checks.push(parameterExpressions());
}
consume("RIGHT_BRACE", "Expect '}' after switch body.");
return { type: "switchstatement", line: condition.line, condition, checks };
}
function parameterExpressions() {
let line = peek().line;
if (match("CASE")) {
consume("LEFT_PAREN", "Expect '(' after 'case'");
let expr = [];
if (!check("RIGHT_PAREN")) {
do {
expr.push(expression());
} while (match("COMMA"));
} else error(line, "Expected conditions in 'case' statement.");
consume("RIGHT_PAREN", "Expect ')' after conditions.");
let body = statement();
return { type: "case", expr, body };
}
if (match("DEFAULT")) {
let body = statement();
return { type: "default", body };
}
error(line, "Expected 'case' or 'default', got " + peek().name);
}
function forrepStatement() {
consume("LEFT_PAREN", "Expect '(' after 'forrep' loop.");
let line = previous().line;
let params = [];
if (!check("RIGHT_PAREN")) {
do {
params.push(expression());
} while (match("COMMA"));
}
consume("RIGHT_PAREN", "Expect ')' after arguments.");
let body = statement();
return { type: "forrep", params, line, body };
}
function breakStatement() {
consume("SEMI", "Expect ';' after break statement.");
return { type: "breakloop", line: previous().line }
}
function deleteVariableStatement() {
let variable = peek();
consume("IDENTIFIER", "Expect variable name.");
consume("SEMI", "Expect ';' after variable.");
return { type: "deletevariable", line: variable.line, variable };
}
function exitStatement() {
let line = previous().line;
let num = { type: "literal", line, value: 0 }
if (!check("SEMI")) {
num = expression();
}
consume("SEMI", "Expect ';' after exit statement.")
return { line, type: "exit", num }
}
function block() {
let statements = [];
while (!check("RIGHT_BRACE") && !isAtEnd()) {
statements.push(declaration());
}
consume("RIGHT_BRACE", "Expect '}' after block.");
return statements;
}
function expressionStatement() {
let line = peek().line;
let expr = expression();
consume("SEMI", "Expected ';' after expression.");
return { line, type: "expr", expr }
}
function expression() {
if (match("LAMBDA")) {
consume("LEFT_PAREN", "Expect '(' after lambda statement.")
let line = previous().line;
let params = [];
if (!check("RIGHT_PAREN")) {
do {
params.push(peek());
consume("IDENTIFIER", "Expect parameter name.")
} while (match("COMMA"));
}
consume("RIGHT_PAREN", "Expect ')' after lambda parameters.");
consume("COL", "Expect ':' after right parentheses.");
let retVal = expression();
return { type: "lambda", line, params, retVal }
}
return ternary();
}
function ternary() {
let expr = postfix();
let line = expr.line;
if (match("QUE")) {
let ifTrue = postfix();
consume("COL", "Expect ':' after ternary statement.");
let ifFalse = ternary();
expr = { type: "ternarystatement", line, condition: expr, ifTrue, ifFalse };
}
return expr;
}
function postfix() {
let expr = assignment();
let line = expr.line;
if (match("PLUS_PLUS", "MINUS_MINUS")) {
let operator = previous().name;
expr = { type: "postfix", line, name: expr.name, operator };
}
return expr;
}
function assignment() {
let expr = or();
if (match("EQ", "PLUS_EQ", "MINUS_EQ", "DIVIDE_EQ", "TIMES_EQ", "POWER_EQ", "MODULO_EQ")) {
let equals = previous();
let value = expression();
if (expr.type == "variable") {
let name = expr.name;
return { line: equals.line, type: "assign", name, operator: equals.name, value };
} else if (expr.type == "accessor") {
let accessor = expr;
return { type: "accessorset", line: equals.line, obj: accessor.obj, name: accessor.name, value };
}
error(equals.line, "Invalid assignment target.");
}
return expr;
}
function or() {
let expr = and();
let line = expr.line;
while (match("OR")) {
let operator = previous();
let right = and();
expr = { type: "logical", line, left: expr, operator: operator.name, right }
}
return expr;
}
function and() {
let expr = equality();
let line = expr.line;
while (match("AND")) {
let operator = previous();
let right = equality();
expr = { type: "logical", line, left: expr, operator, right };
}
return expr;
}
function equality() {
let expr = comparison();
while (match("BANG_EQ", "EQ_EQ")) {
let line = previous().line;
let operator = previous().value;
let right = comparison();
expr = { line, type: "binary", left: expr, operator, right };
}
return expr;
}
function comparison() {
let expr = addition();
while (match("GREATER", "GREATER_EQ", "LESS", "LESS_EQ")) {
let line = previous().line;
let operator = previous().value;
let right = addition();
expr = { line, type: "binary", left: expr, operator, right };
}
return expr;
}
function addition() {
let expr = multiplication();
while (match("MINUS", "PLUS")) {
let line = previous().line;
let operator = previous().value;
let right = multiplication();
expr = { line, type: "binary", left: expr, operator, right };
}
return expr;
}
function multiplication() {
let expr = power();
while (match("DIVIDE", "TIMES", "MODULO")) {
let line = previous().line;
let operator = previous().value;
let right = power();
expr = { line, type: "binary", left: expr, operator, right };
}
return expr;
}
function power() {
let expr = unary();
while (match("POWER")) {
let line = previous().line;
let operator = previous().value;
let right = unary();
expr = { line, type: "binary", left: expr, operator, right };
}
return expr;
}
function unary() {
if (match("BANG", "MINUS")) {
let line = previous().line;
let operator = previous().value;
let right = unary();
return { line, type: "unary", operator, right };
}
return type_change();
}
function type_change() {
if (match("AT")) {
let line = previous().line;
let newtype = peek();
consume("STRING", "Expected type name in string.");
consume("COL", "Expected ':' after type name")
let expr = type_change();
return { type: "typechange", line, newtype, expr };
}
return call();
}
function call() {
let expr = primary();
while (true) {
if (match("LEFT_PAREN")) {
expr = finishCall(expr);
} else if (match("DOT")) {
let name = peek();
consume("IDENTIFIER", "Expect property name after '.'.");
expr = { type: "accessor", obj: expr, name, line: name.line }
} else {
break;
}
}
return expr;
}
function finishCall(callee) {
let args = [];
if (!check("RIGHT_PAREN")) {
do {
args.push(expression());
} while (match("COMMA"));
}
let paren = peek();
consume("RIGHT_PAREN", "Expect ')' after arguments.");
return { type: "call", line: paren.line, call: callee, args };
}
function primary() {
if (match("FALSE")) return { type: "literal", value: false, line: previous().line };
if (match("TRUE")) return { type: "literal", value: true, line: previous().line };
if (match("NAV")) return { type: "literal", value: null, line: previous().line };
if (match("NUMBER", "STRING")) {
return { type: "literal", value: previous().value, line: previous().line };
}
if (match("THIS")) return { type: "this", line: previous().line };
if (match("SUPERCLASS")) {
let keyword = previous();
consume("DOT", "Expect '.' after 'superClass'.");
let method = peek();
consume("IDENTIFIER", "Expect superclass method name.");
return { type: "superclass", keyword, method, line: method.line };
}
if (match("IDENTIFIER")) {
return { type: "variable", name: previous().value, line: previous().line };
}
if (match("LEFT_PAREN")) {
let expr = expression();
consume("RIGHT_PAREN", "Expected ')' after expression.");
return { type: "grouping", expr, line: previous().line };
}
if (match("PIPE")) {
let expr = expression();
consume("PIPE", "Expected '|' after expression.");
return { type: "absolute", expr, line: previous().line };
}
error(peek().line, "Unexpected token '" + peek().value + "'");
}
let statements = [];
while(!isAtEnd()) {
statements.push(declaration())
}
return statements;
}
module.exports = parser;