cookeylang-ts
Version:
A dynamic, interpreted language.
473 lines (472 loc) • 18.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Parser = void 0;
const base_1 = require("./expr/base");
const Stmt = __importStar(require("./expr/stmt"));
const Expr = __importStar(require("./expr/expr"));
const token_1 = require("./token");
class Parser {
constructor(tokens, file) {
this.i = 0;
this.tokens = [];
this.hasError = false;
this.file = file;
this.tokens = tokens;
}
init() {
let stmts = [];
while (this.isValid()) {
stmts.push(this.decl());
}
return stmts;
}
decl() {
if (this.match(token_1.TType.CLASS))
return this.classDecl();
if (this.match(token_1.TType.FUNCTION))
return this.funcDecl("function");
if (this.match(token_1.TType.VAR, token_1.TType.FINAL))
return this.varDecl();
return this.stmt();
}
classDecl() {
let name = this.consume(token_1.TType.IDENTIFIER, "Expected class name after 'class' keyword.");
this.consume(token_1.TType.LEFT_BRACE, "Expected '{' after class name.");
let methods = [];
while (this.peek().type != token_1.TType.RIGHT_BRACE && this.isValid()) {
methods.push(this.funcDecl("method"));
}
this.consume(token_1.TType.RIGHT_BRACE, "Expected '}' after class body.");
return new Stmt.ClassDecl(name, methods);
}
funcDecl(type) {
let name = this.consume(token_1.TType.IDENTIFIER, `Expected ${type} name.`);
this.consume(token_1.TType.LEFT_PAREN, `Expected '(' after ${type} name.`);
let params = [];
if (this.peek().type != token_1.TType.RIGHT_PAREN) {
do {
params.push(this.consume(token_1.TType.IDENTIFIER, "Expected parameter name."));
} while (this.match(token_1.TType.COMMA));
}
this.consume(token_1.TType.RIGHT_PAREN, "Expected ')' after parameters.");
let body;
if (this.match(token_1.TType.LEFT_BRACE))
body = this.block();
else
body = [this.stmt()];
return new Stmt.FuncDecl(name, params, body);
}
varDecl() {
let mut = this.previous();
let name = this.consume(token_1.TType.IDENTIFIER, "Expected a variable name.");
let value = null;
if (this.match(token_1.TType.EQ))
value = this.expression();
this.consume(token_1.TType.SEMI, "Expected a ';' after variable declaration.");
if (value == null && mut.type == token_1.TType.FINAL)
this.error("Constants require a value");
return new Stmt.VarDecl(mut, name, value);
}
stmt() {
if (this.match(token_1.TType.IF))
return this.ifStmt();
if (this.match(token_1.TType.WHILE))
return this.whileStmt();
if (this.match(token_1.TType.DO))
return this.doWhileStmt();
if (this.match(token_1.TType.FOR))
return this.forStmt();
if (this.match(token_1.TType.EXIT))
return this.exitStmt();
if (this.match(token_1.TType.RET))
return this.retStmt();
if (this.match(token_1.TType.LEFT_BRACE))
return new Stmt.Block(this.block());
return this.exprStmt();
}
exprStmt() {
let expr = this.expression();
this.consume(token_1.TType.SEMI, "Expected ';' after expression");
return new Stmt.ExprStmt(expr);
}
ifStmt() {
this.consume(token_1.TType.LEFT_PAREN, "Expected '(' after if keyword.");
let condition = this.expression();
this.consume(token_1.TType.RIGHT_PAREN, "Expected ')' after if condition.");
let thenBr = this.stmt();
let elseBr = null;
if (this.match(token_1.TType.EL))
elseBr = this.stmt();
return new Stmt.IfStmt(condition, thenBr, elseBr);
}
whileStmt() {
this.consume(token_1.TType.LEFT_PAREN, "Expected '(' after while keyword.");
let condition = this.expression();
this.consume(token_1.TType.RIGHT_PAREN, "Expected ')' after condition.");
let body = this.stmt();
return new Stmt.WhileStmt(condition, body);
}
doWhileStmt() {
let body = this.stmt();
this.consume(token_1.TType.WHILE, "Expected 'while' after statement.");
this.consume(token_1.TType.LEFT_PAREN, "Expected '(' after 'while' keyword.");
let condition = this.expression();
this.consume(token_1.TType.RIGHT_PAREN, "Expected ')' after expression.");
this.consume(token_1.TType.SEMI, "Expected ';' after ')'");
return new Stmt.Block([body, new Stmt.WhileStmt(condition, body)]);
}
forStmt() {
this.consume(token_1.TType.LEFT_PAREN, "Expected '(' after 'for' keyword.");
let initializer;
if (this.match(token_1.TType.SEMI))
initializer = null;
else if (this.match(token_1.TType.VAR))
initializer = this.varDecl();
else
initializer = this.exprStmt();
let condition = null;
if (!this.match(token_1.TType.SEMI))
condition = this.expression();
this.consume(token_1.TType.SEMI, "Expected ';' after for loop condition.");
let increment = null;
if (!this.match(token_1.TType.SEMI))
increment = this.expression();
this.consume(token_1.TType.RIGHT_PAREN, "Expected ')' after for loop clauses.");
let body = this.stmt();
if (increment)
body = new Stmt.Block([body, new Stmt.ExprStmt(increment)]);
if (condition == null)
condition = new Expr.Literal(body.lineData, true);
body = new Stmt.WhileStmt(condition, body);
if (initializer)
body = new Stmt.Block([initializer, body]);
return body;
}
exitStmt() {
let num = new Expr.Literal(this.previous(), 0);
if (!this.match(token_1.TType.SEMI))
num = this.expression();
this.consume(token_1.TType.SEMI, "Expected ';' after exit.");
return new Stmt.ExitStmt(num);
}
retStmt() {
let ret = this.previous();
let value = new Expr.Literal(ret, null);
if (this.peek().type != token_1.TType.SEMI) {
value = this.expression();
}
this.consume(token_1.TType.SEMI, "Expected ';' after ret statement.");
return new Stmt.RetStmt(ret, value);
}
block() {
let stmts = [];
while (this.isValid() && this.peek().type != token_1.TType.RIGHT_BRACE) {
stmts.push(this.decl());
}
this.consume(token_1.TType.RIGHT_BRACE, "Expected '}' after block.");
return stmts;
}
expression() {
return this.lambda();
}
lambda() {
if (this.match(token_1.TType.LAMBDA)) {
let lineData = this.previous();
let params = [];
if (this.match(token_1.TType.LEFT_PAREN)) {
let params = [];
if (this.peek().type != token_1.TType.RIGHT_PAREN) {
do {
params.push(this.consume(token_1.TType.IDENTIFIER, "Expected parameter name."));
} while (this.match(token_1.TType.COMMA));
}
this.consume(token_1.TType.RIGHT_PAREN, "Expected ')' after lamda parameters.");
this.consume(token_1.TType.COL, "Expected ':' after parameters.");
}
else
this.consume(token_1.TType.COL, "Expected ':' after lambda keyword.");
let body;
if (this.match(token_1.TType.LEFT_BRACE))
body = this.block();
else
body = [new Stmt.RetStmt(lineData, this.expression())];
return new Expr.Lambda(lineData, params, body);
}
return this.ternary();
}
ternary() {
let expr = this.assignment();
if (this.match(token_1.TType.QUE)) {
let thenBr = this.expression();
this.consume(token_1.TType.COL, "Expected ':' after ternary branch.");
let elseBr = this.ternary();
expr = new Stmt.IfStmt(expr, thenBr, elseBr);
}
return expr;
}
assignment() {
let expr = this.logicOr();
if (this.match(token_1.TType.EQ)) {
let value = this.assignment();
if (expr instanceof Expr.Variable) {
let name = expr.name;
return new Expr.Assign(name, value);
}
this.error("Invalid assignment target.");
}
if (this.match(token_1.TType.PLUS_EQ, token_1.TType.MINUS_EQ, token_1.TType.TIMES_EQ, token_1.TType.DIVIDE_EQ, token_1.TType.POWER_EQ, token_1.TType.MODULO_EQ)) {
let op = this.previous();
switch (op.type) {
case token_1.TType.PLUS_EQ:
op.type = token_1.TType.PLUS;
break;
case token_1.TType.MINUS_EQ:
op.type = token_1.TType.MINUS;
break;
case token_1.TType.TIMES_EQ:
op.type = token_1.TType.TIMES;
break;
case token_1.TType.DIVIDE_EQ:
op.type = token_1.TType.DIVIDE;
break;
case token_1.TType.POWER_EQ:
op.type = token_1.TType.POWER;
break;
case token_1.TType.MODULO_EQ:
op.type = token_1.TType.MODULO;
break;
}
let value = this.assignment();
return this.desugarAssign(op, expr, op.type, value);
}
if (this.match(token_1.TType.PLUS_PLUS, token_1.TType.MINUS_MINUS)) {
let op = this.previous();
switch (op.type) {
case token_1.TType.PLUS_PLUS:
op.type = token_1.TType.PLUS;
break;
case token_1.TType.MINUS_MINUS:
op.type = token_1.TType.MINUS;
break;
}
return this.desugarAssign(op, expr, op.type, 1);
}
return expr;
}
logicOr() {
let expr = this.logicAnd();
while (this.match(token_1.TType.OR)) {
let op = this.previous();
let right = this.logicAnd();
expr = new Expr.Logic(expr, op, right);
}
return expr;
}
logicAnd() {
let expr = this.equality();
while (this.match(token_1.TType.AND)) {
let op = this.previous();
let right = this.equality();
expr = new Expr.Logic(expr, op, right);
}
return expr;
}
equality() {
let expr = this.comparison();
while (this.match(token_1.TType.EQ_EQ, token_1.TType.BANG_EQ)) {
let op = this.previous();
let right = this.comparison();
expr = new Expr.Binary(expr, op, right);
}
return expr;
}
comparison() {
let expr = this.addition();
while (this.match(token_1.TType.GREATER, token_1.TType.GREATER_EQ, token_1.TType.LESS, token_1.TType.LESS_EQ)) {
let op = this.previous();
let right = this.addition();
expr = new Expr.Binary(expr, op, right);
}
return expr;
}
addition() {
let expr = this.multiplication();
while (this.match(token_1.TType.PLUS, token_1.TType.MINUS)) {
let op = this.previous();
let right = this.multiplication();
expr = new Expr.Binary(expr, op, right);
}
return expr;
}
multiplication() {
let expr = this.power();
while (this.match(token_1.TType.DIVIDE, token_1.TType.TIMES, token_1.TType.MODULO)) {
let op = this.previous();
let right = this.power();
expr = new Expr.Binary(expr, op, right);
}
return expr;
}
power() {
let expr = this.unary();
while (this.match(token_1.TType.POWER)) {
let op = this.previous();
let right = this.unary();
expr = new Expr.Binary(expr, op, right);
}
return expr;
}
unary() {
if (this.match(token_1.TType.BANG_EQ, token_1.TType.PLUS, token_1.TType.MINUS)) {
let op = this.previous();
let right = this.unary();
return new Expr.Unary(op, right);
}
if (this.match(token_1.TType.PLUS_PLUS, token_1.TType.MINUS_MINUS)) {
let lineData = this.previous();
let right = this.unary();
let op = lineData.type;
switch (op) {
case token_1.TType.PLUS_PLUS:
op = token_1.TType.PLUS;
break;
case token_1.TType.MINUS_MINUS:
op = token_1.TType.MINUS;
break;
}
return this.desugarAssign(lineData, right, op, 1);
}
return this.call();
}
call() {
let expr = this.primary();
while (true) {
if (this.match(token_1.TType.LEFT_PAREN)) {
expr = this.finishCall(expr);
}
else {
break;
}
}
return expr;
}
finishCall(callee) {
let args = [];
if (this.peek().type != token_1.TType.RIGHT_PAREN) {
do {
args.push(this.expression());
} while (this.match(token_1.TType.COMMA));
}
let paren = this.consume(token_1.TType.RIGHT_PAREN, "Expected ')' after arguments.");
return new Expr.Call(callee, paren, args);
}
primary() {
if (this.match(token_1.TType.FALSE))
return new Expr.Literal(this.previous(), false);
if (this.match(token_1.TType.TRUE))
return new Expr.Literal(this.previous(), true);
if (this.match(token_1.TType.NAV))
return new Expr.Literal(this.previous(), null);
if (this.match(token_1.TType.NUMBER, token_1.TType.STRING)) {
return new Expr.Literal(this.previous(), this.previous().value);
}
if (this.match(token_1.TType.LEFT_PAREN)) {
let expr = this.expression();
this.consume(token_1.TType.RIGHT_PAREN, "Expected a ')' after expression.");
return new Expr.Grouping(expr);
}
if (this.match(token_1.TType.IDENTIFIER))
return new Expr.Variable(this.previous());
this.error(`Unexpected token ${token_1.TType[this.peek().type]}.`);
return new base_1.Base(token_1.defualtToken);
}
isValid() {
return this.i < this.tokens.length && this.peek().type != token_1.TType.END;
}
match(...tokens) {
for (const token of tokens) {
if (this.isValid() && this.peek().type == token) {
this.advance();
return true;
}
}
return false;
}
advance() {
if (this.isValid())
this.i++;
return this.previous();
}
previous() {
return this.tokens[this.i - 1];
}
peek() {
return this.tokens[this.i];
}
consume(type, message) {
if (this.match(type))
return this.previous();
this.error(message);
}
error(message) {
this.hasError = true;
console.log(`<${this.file}> [ ${this.tokens[this.i].line} : ${this.tokens[this.i].col} ] ${message}`);
this.synchronize();
}
synchronize() {
this.advance();
while (this.isValid()) {
if (this.previous().type == token_1.TType.SEMI)
return;
switch (this.peek().type) {
case token_1.TType.CLASS:
case token_1.TType.FUNCTION:
case token_1.TType.VAR:
case token_1.TType.FINAL:
case token_1.TType.DELETEVARIABLE:
case token_1.TType.FOR:
case token_1.TType.WHILE:
case token_1.TType.FOREACH:
case token_1.TType.FORREP:
case token_1.TType.SWITCH:
case token_1.TType.IF:
case token_1.TType.RET:
case token_1.TType.EXIT:
case token_1.TType.BREAK:
return;
}
this.advance();
}
}
desugarAssign(lineData, name, type, value) {
let op = new token_1.Token(lineData.line, lineData.col, lineData.file, type, "");
let val;
if (!(value instanceof Expr.Literal))
val = new Expr.Literal(lineData, value);
else
val = value;
if (name instanceof Expr.Variable)
return new Expr.Assign(name.name, new Expr.Binary(name, op, val));
this.error("Invalid assignment target.");
}
}
exports.Parser = Parser;