This commit is contained in:
SimonFJ20 2023-04-27 02:32:06 +02:00
commit b2b1533ee6
9 changed files with 1353 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
session.vim

99
checked.ts Normal file
View File

@ -0,0 +1,99 @@
import { AssignType, BinaryType, UnaryType } from "./parsed.ts";
import { Position } from "./token.ts";
export type CheckedExpr =
& {
pos: Position;
}
& ({ exprType: "error"; message: string } | {
exprType: "unit";
} | {
exprType: "id";
value: string;
} | {
exprType: "int";
value: number;
} | {
exprType: "if";
condition: CheckedExpr;
truthy: CheckedExpr;
falsy: CheckedExpr | null;
} | {
exprType: "block";
body: CheckedExpr[];
} | {
exprType: "call";
subject: CheckedExpr;
args: CheckedExpr[];
} | {
exprType: "index";
subject: CheckedExpr;
value: CheckedExpr;
} | {
exprType: "increment" | "decrement";
subject: CheckedExpr;
} | {
exprType: "unary";
unaryType: UnaryType;
subject: CheckedExpr;
} | {
exprType: "binary";
binaryType: BinaryType;
left: CheckedExpr;
right: CheckedExpr;
} | {
exprType: "assign";
assignType: AssignType;
subject: CheckedExpr;
value: CheckedExpr;
} | {
exprType: "let";
param: CheckedParameter;
value: CheckedExpr;
} | {
exprType: "fn";
subject: string;
params: CheckedParameter[];
body: CheckedExpr;
} | {
exprType: "return";
value: CheckedExpr | null;
} | {
exprType: "loop";
body: CheckedExpr;
} | {
exprType: "while";
condition: CheckedExpr;
body: CheckedExpr;
} | {
exprType: "break";
} | {
exprType: "continue";
});
export type CheckedParameter =
& {
pos: Position;
}
& ({ paramType: "error"; message: string } | {
paramType: "value";
subject: string;
valueType: CheckedType | null;
mutable: boolean;
});
export type CheckedType =
& { pos: Position }
& ({ typeType: "error"; message: string } | {
typeType: "unit";
} | {
typeType: "u16";
} | {
typeType: "pointer";
subject: CheckedType;
mutable: boolean;
} | {
typeType: "function";
params: CheckedParameter[];
returnType: CheckedType;
});

55
checker.ts Normal file
View File

@ -0,0 +1,55 @@
import { CheckedExpr, CheckedType } from "./checked.ts";
import { ParsedExpr } from "./parsed.ts";
import { CompileError } from "./token.ts";
export type SymbolValue = {
id: number;
subject: string;
valueType: CheckedType;
};
export class SymbolTable {
private idCounter = 0;
private symbols: { [key: string]: SymbolValue } = {};
public define(subject: string, valueType: CheckedType) {
if (subject in this.symbols) {
throw new Error("redefinition not implemented");
}
const id = this.idCounter;
this.idCounter++;
this.symbols[subject] = { id, subject, valueType };
}
}
export class Checker {
private symbolTable = new SymbolTable();
public errors: CompileError[] = [];
public check(statements: ParsedExpr[]): CheckedExpr[] {
return statements as CheckedExpr[];
}
private searchTopLevelStatements(statements: ParsedExpr[]) {
for (const statement of statements) {
switch (statement.exprType) {
case "fn":
this.searchTopLevelFn(statement);
break;
case "let":
break;
default:
this.errors.push({
pos: statement.pos,
message:
`statement '${statement.exprType}' not supported in top level`,
});
break;
}
}
}
private searchTopLevelFn(statement: ParsedExpr & { exprType: "fn" }) {
}
}

10
deno.jsonc Normal file
View File

@ -0,0 +1,10 @@
{
"fmt": {
"options": {
"useTabs":false,
"lineWidth": 80,
"indentWidth": 4,
"singleQuote": false
}
}
}

218
lexer.ts Normal file
View File

@ -0,0 +1,218 @@
import { Position, StaticTokenType, Token, TokenIter } from "./token.ts";
export class Lexer implements TokenIter {
private index = 0;
private line = 1;
private col = 1;
public constructor(private text: string) {}
public next(): Token {
if (this.done()) {
return { pos: this.position(), tokenType: "eof" };
} else if (" \t\r\n".includes(this.current())) {
this.step();
while (" \t\r\n".includes(this.current())) this.step();
return this.next();
} else if (Lexer.idStartChars.includes(this.current())) {
const pos = this.position();
let valueString = this.current();
this.step();
while (Lexer.idChars.includes(this.current())) {
valueString += this.current();
this.step();
}
if (valueString in Lexer.keywordTokenTypes) {
return { pos, tokenType: Lexer.keywordTokenTypes[valueString] };
} else {
return {
pos,
tokenType: "id",
value: valueString,
};
}
} else if (Lexer.intStartChars.includes(this.current())) {
const pos = this.position();
let valueString = this.current();
this.step();
while (Lexer.intChars.includes(this.current())) {
valueString += this.current();
this.step();
}
return {
pos,
tokenType: "int",
value: parseInt(valueString),
};
} else if (this.current() in Lexer.singleStaticTokenTypes) {
const pos = this.position();
const tokenType = Lexer.singleStaticTokenTypes[this.current()];
this.step();
return { pos, tokenType };
} else if (this.currentIs("+")) {
const pos = this.position();
this.step();
if (this.currentIs("+")) {
this.step();
return { pos, tokenType: "plusplus" };
} else if (this.currentIs("=")) {
this.step();
return { pos, tokenType: "plusequal" };
} else {
return { pos, tokenType: "plus" };
}
} else if (this.currentIs("-")) {
const pos = this.position();
this.step();
if (this.currentIs("-")) {
this.step();
return { pos, tokenType: "minusminus" };
} else if (this.currentIs(">")) {
this.step();
return { pos, tokenType: "minusgt" };
} else if (this.currentIs("=")) {
this.step();
return { pos, tokenType: "minusequal" };
} else {
return { pos, tokenType: "minus" };
}
} else if (this.currentIs("&")) {
const pos = this.position();
this.step();
if (this.currentIs("=")) {
this.step();
return { pos, tokenType: "ampersandequal" };
} else {
return { pos, tokenType: "ampersand" };
}
} else if (this.currentIs("|")) {
const pos = this.position();
this.step();
if (this.currentIs("=")) {
this.step();
return { pos, tokenType: "pipeequal" };
} else {
return { pos, tokenType: "pipe" };
}
} else if (this.currentIs("^")) {
const pos = this.position();
this.step();
if (this.currentIs("=")) {
this.step();
return { pos, tokenType: "hatequal" };
} else {
return { pos, tokenType: "hat" };
}
} else if (this.currentIs("=")) {
const pos = this.position();
this.step();
if (this.currentIs("=")) {
this.step();
return { pos, tokenType: "equalequal" };
} else {
return { pos, tokenType: "equal" };
}
} else if (this.currentIs("!")) {
const pos = this.position();
this.step();
if (this.currentIs("=")) {
this.step();
return { pos, tokenType: "exclamationequal" };
} else {
return { pos, tokenType: "exclamation" };
}
} else if (this.currentIs("<")) {
const pos = this.position();
this.step();
if (this.currentIs("=")) {
this.step();
return { pos, tokenType: "ltequal" };
} else {
return { pos, tokenType: "lt" };
}
} else if (this.currentIs(">")) {
const pos = this.position();
this.step();
if (this.currentIs("=")) {
this.step();
return { pos, tokenType: "gtequal" };
} else {
return { pos, tokenType: "gt" };
}
} else {
const pos = this.position();
this.step();
return { pos, tokenType: "invalid" };
}
}
private static intStartChars = "123456789" as const;
private static intChars = "1234567890" as const;
private static idStartChars =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_" as const;
private static idChars = Lexer.idStartChars + Lexer.intChars;
private static singleStaticTokenTypes: { [key: string]: StaticTokenType } =
{
"(": "lparen",
")": "rparen",
"{": "lbrace",
"}": "rbrace",
"[": "lbracket",
"]": "rbracket",
".": "dot",
",": "comma",
":": "colon",
";": "semicolon",
"~": "tilde",
"*": "asterisk",
} as const;
private static keywordTokenTypes: { [key: string]: StaticTokenType } = {
"let": "let",
"mut": "mut",
"not": "not",
"and": "and",
"or": "or",
"fn": "fn",
"return": "return",
"if": "fn",
"else": "else",
"loop": "loop",
"while": "while",
"break": "break",
"continue": "continue",
} as const;
private step() {
this.index++;
if (!this.done()) {
if (this.current() == "\n") {
this.line++;
this.col = 1;
} else {
this.col++;
}
}
}
private position(): Position {
return {
index: this.index,
line: this.line,
col: this.col,
};
}
private currentIs(char: string): boolean {
return !this.done() && this.current() == char;
}
private current(): string {
return this.text[this.index];
}
private done(): boolean {
return this.index >= this.text.length;
}
}

10
main.ts Normal file
View File

@ -0,0 +1,10 @@
async function readFromStdin() {
const buffer = new Uint8Array(8192);
const n = await Deno.stdin.read(buffer);
return new TextDecoder().decode(buffer.subarray(0, n!));
}
async function main() {
}
main();

126
parsed.ts Normal file
View File

@ -0,0 +1,126 @@
import { Position } from "./token.ts";
export type UnaryType =
| "negate"
| "log_not"
| "bit_not"
| "addressof"
| "dereference";
export type BinaryType =
| "add"
| "subtract"
| "log_and"
| "log_or"
| "bit_and"
| "bit_or"
| "bit_xor"
| "equal"
| "inequal"
| "lt"
| "gt"
| "ltequal"
| "gtequal";
export type AssignType =
| "equal"
| "add"
| "subtract"
| "and"
| "or"
| "xor";
export type ParsedExpr =
& {
pos: Position;
}
& ({ exprType: "error"; message: string } | {
exprType: "unit";
} | {
exprType: "id";
value: string;
} | {
exprType: "int";
value: number;
} | {
exprType: "if";
condition: ParsedExpr;
truthy: ParsedExpr;
falsy: ParsedExpr | null;
} | {
exprType: "block";
body: ParsedExpr[];
} | {
exprType: "call";
subject: ParsedExpr;
args: ParsedExpr[];
} | {
exprType: "index";
subject: ParsedExpr;
value: ParsedExpr;
} | {
exprType: "increment" | "decrement";
subject: ParsedExpr;
} | {
exprType: "unary";
unaryType: UnaryType;
subject: ParsedExpr;
} | {
exprType: "binary";
binaryType: BinaryType;
left: ParsedExpr;
right: ParsedExpr;
} | {
exprType: "assign";
assignType: AssignType;
subject: ParsedExpr;
value: ParsedExpr;
} | {
exprType: "let";
param: ParsedParameter;
value: ParsedExpr;
} | {
exprType: "fn";
subject: string;
params: ParsedParameter[];
returnType: ParsedType | null;
body: ParsedExpr;
} | {
exprType: "return";
value: ParsedExpr | null;
} | {
exprType: "loop";
body: ParsedExpr;
} | {
exprType: "while";
condition: ParsedExpr;
body: ParsedExpr;
} | {
exprType: "break";
} | {
exprType: "continue";
});
export type ParsedParameter =
& {
pos: Position;
}
& ({ paramType: "error"; message: string } | {
paramType: "value";
subject: string;
valueType: ParsedType | null;
mutable: boolean;
});
export type ParsedType =
& { pos: Position }
& ({ typeType: "error"; message: string } | {
typeType: "unit";
} | {
typeType: "id";
value: string;
} | {
typeType: "pointer";
subject: ParsedType;
mutable: boolean;
});

758
parser.ts Normal file
View File

@ -0,0 +1,758 @@
import {
BinaryType,
ParsedExpr,
ParsedParameter,
ParsedType,
} from "./parsed.ts";
import {
CompileError,
Position,
Token,
TokenIter,
TokenType,
} from "./token.ts";
export class Parser {
private current: Token;
public errors: CompileError[] = [];
public constructor(private lexer: TokenIter) {
this.current = lexer.next();
}
public parse(): ParsedExpr[] {
while (this.currentIs("semicolon")) this.step();
const statements: ParsedExpr[] = [];
while (!this.done()) {
while (this.currentIs("semicolon")) this.step();
}
return statements;
}
private checkAndSkipStatementLinebreak() {
if (!this.currentIs("semicolon")) {
this.errors.push({
pos: this.position(),
message:
`expected ';' after statement, got '${this.current.tokenType}'`,
});
} else {
this.step();
}
}
public parseStatement(): ParsedExpr {
if (this.currentIs("fn")) {
return this.parseFn();
} else if (this.currentIs("if")) {
return this.parseIf();
} else if (this.currentIs("loop")) {
return this.parseLoop();
} else if (this.currentIs("while")) {
return this.parseWhile();
} else if (this.currentIs("return")) {
const statement = this.parseReturn();
this.checkAndSkipStatementLinebreak();
return statement;
} else if (this.currentIs("break")) {
const statement = this.parseBreak();
this.checkAndSkipStatementLinebreak();
return statement;
} else if (this.currentIs("continue")) {
const statement = this.parseContinue();
this.checkAndSkipStatementLinebreak();
return statement;
} else if (this.currentIs("let")) {
const statement = this.parseLet();
this.checkAndSkipStatementLinebreak();
return statement;
} else {
const statement = this.parseAssign();
this.checkAndSkipStatementLinebreak();
return statement;
}
}
private parseFn(): ParsedExpr {
const pos = this.position();
this.step();
if (this.done() || this.current.tokenType != "id") {
return this.errorExpr(
`expected 'Id' after 'fn' as function name, got '${this.current.tokenType}'`,
);
}
const subject = this.current.value;
this.step();
if (!this.currentIs("lparen")) {
return this.errorExpr(
`expected '(' before function-parameters, got '${this.current.tokenType}'`,
);
}
this.step();
const params: ParsedParameter[] = [];
params.push(this.parseParam());
while (this.currentIs("comma")) {
this.step();
if (this.done() || this.currentIs("rparen")) break;
params.push(this.parseParam());
}
if (!this.currentIs("rparen")) {
return this.errorExpr(
`expected ')' after function-parameters, got '${this.current.tokenType}'`,
);
}
this.step();
let returnType: ParsedType | null = null;
if (this.currentIs("minusgt")) {
this.step();
returnType = this.parseType();
}
if (!this.currentIs("lbrace")) {
return this.errorExpr(
`expected '{' after 'loop', got '${this.current.tokenType}'`,
);
}
const body = this.parseBlock();
return {
pos,
exprType: "fn",
subject,
params,
returnType,
body,
};
}
private parseLoop(): ParsedExpr {
const pos = this.position();
this.step();
if (!this.currentIs("lbrace")) {
return this.errorExpr(
`expected '{' after 'loop', got '${this.current.tokenType}'`,
);
}
const body = this.parseBlock();
return {
pos,
exprType: "loop",
body,
};
}
private parseWhile(): ParsedExpr {
const pos = this.position();
this.step();
const condition = this.parseExpr();
if (!this.currentIs("lbrace")) {
return this.errorExpr(
`expected '{' after while-condition, got '${this.current.tokenType}'`,
);
}
const body = this.parseBlock();
return {
pos,
exprType: "while",
condition,
body,
};
}
private parseReturn(): ParsedExpr {
const pos = this.position();
this.step();
let value: ParsedExpr | null = null;
if (!this.done() && this.current.tokenType != "semicolon") {
value = this.parseExpr();
}
return { pos, exprType: "return", value };
}
private parseBreak(): ParsedExpr {
const pos = this.position();
this.step();
return { pos, exprType: "break" };
}
private parseContinue(): ParsedExpr {
const pos = this.position();
this.step();
return { pos, exprType: "continue" };
}
private parseLet(): ParsedExpr {
const pos = this.position();
this.step();
const param = this.parseParam();
if (!this.currentIs("equal")) {
return this.errorExpr(
`expected '=' after let-parameter, got '${this.current.tokenType}'`,
);
}
this.step();
const value = this.parseExpr();
return {
pos,
exprType: "let",
param,
value,
};
}
private parseParam(): ParsedParameter {
const pos = this.position();
const mutable = this.currentIs("mut");
if (mutable) this.step();
if (this.done() || this.current.tokenType != "id") {
return this.errorParam(
`expected 'id' in parameter, got '${this.current.tokenType}'`,
);
}
const subject = this.current.value;
this.step();
let valueType: ParsedType | null = null;
if (this.currentIs("colon")) {
this.step();
valueType = this.parseType();
}
return {
pos,
paramType: "value",
subject,
valueType,
mutable,
};
}
private parseAssign(): ParsedExpr {
const pos = this.position();
const subject = this.parseExpr();
if (this.currentIs("equal")) {
this.step();
const value = this.parseExpr();
return {
pos,
exprType: "assign",
assignType: "equal",
subject,
value,
};
} else if (this.currentIs("plusequal")) {
this.step();
const value = this.parseExpr();
return {
pos,
exprType: "assign",
assignType: "add",
subject,
value,
};
} else if (this.currentIs("minusequal")) {
this.step();
const value = this.parseExpr();
return {
pos,
exprType: "assign",
assignType: "subtract",
subject,
value,
};
} else if (this.currentIs("ampersandequal")) {
this.step();
const value = this.parseExpr();
return {
pos,
exprType: "assign",
assignType: "and",
subject,
value,
};
} else if (this.currentIs("pipeequal")) {
this.step();
const value = this.parseExpr();
return {
pos,
exprType: "assign",
assignType: "or",
subject,
value,
};
} else if (this.currentIs("hatequal")) {
this.step();
const value = this.parseExpr();
return {
pos,
exprType: "assign",
assignType: "xor",
subject,
value,
};
} else {
return subject;
}
}
public parseExpr(): ParsedExpr {
return this.parseLogOr();
}
private binaryExpr(
binaryType: BinaryType,
left: ParsedExpr,
right: ParsedExpr,
pos: Position,
): ParsedExpr {
return {
pos,
exprType: "binary",
binaryType,
left,
right,
};
}
private parseLogOr(): ParsedExpr {
const pos = this.position();
let left = this.parseLogAnd();
while (!this.done()) {
if (this.currentIs("or")) {
this.step();
const right = this.parseLogAnd();
left = this.binaryExpr("log_or", left, right, pos);
} else {
break;
}
}
return left;
}
private parseLogAnd(): ParsedExpr {
const pos = this.position();
let left = this.parseBitOr();
while (!this.done()) {
if (this.currentIs("and")) {
this.step();
const right = this.parseBitOr();
left = this.binaryExpr("log_and", left, right, pos);
} else {
break;
}
}
return left;
}
private parseBitOr(): ParsedExpr {
const pos = this.position();
let left = this.parseBitXor();
while (!this.done()) {
if (this.currentIs("pipe")) {
this.step();
const right = this.parseBitXor();
left = this.binaryExpr("bit_or", left, right, pos);
} else {
break;
}
}
return left;
}
private parseBitXor(): ParsedExpr {
const pos = this.position();
let left = this.parseBitAnd();
while (!this.done()) {
if (this.currentIs("hat")) {
this.step();
const right = this.parseBitAnd();
left = this.binaryExpr("bit_xor", left, right, pos);
} else {
break;
}
}
return left;
}
private parseBitAnd(): ParsedExpr {
const pos = this.position();
let left = this.parseEquality();
while (!this.done()) {
if (this.currentIs("ampersand")) {
this.step();
const right = this.parseEquality();
left = this.binaryExpr("bit_and", left, right, pos);
} else {
break;
}
}
return left;
}
private parseEquality(): ParsedExpr {
const pos = this.position();
let left = this.parseComparison();
while (!this.done()) {
if (this.currentIs("equalequal")) {
this.step();
const right = this.parseComparison();
left = this.binaryExpr("equal", left, right, pos);
} else if (this.currentIs("exclamationequal")) {
this.step();
const right = this.parseComparison();
left = this.binaryExpr("inequal", left, right, pos);
} else {
break;
}
}
return left;
}
private parseComparison(): ParsedExpr {
const pos = this.position();
const left = this.parseAddSubtract();
if (this.currentIs("lt")) {
this.step();
const right = this.parseAddSubtract();
return this.binaryExpr("lt", left, right, pos);
} else if (this.currentIs("gt")) {
this.step();
const right = this.parseAddSubtract();
return this.binaryExpr("gt", left, right, pos);
} else if (this.currentIs("ltequal")) {
this.step();
const right = this.parseAddSubtract();
return this.binaryExpr("ltequal", left, right, pos);
} else if (this.currentIs("gtequal")) {
this.step();
const right = this.parseAddSubtract();
return this.binaryExpr("gtequal", left, right, pos);
} else {
return left;
}
}
private parseAddSubtract(): ParsedExpr {
const pos = this.position();
let left = this.parseUnary();
while (!this.done()) {
if (this.currentIs("plus")) {
this.step();
const right = this.parseUnary();
left = this.binaryExpr("add", left, right, pos);
} else if (this.currentIs("minus")) {
this.step();
const right = this.parseUnary();
left = this.binaryExpr("subtract", left, right, pos);
} else {
break;
}
}
return left;
}
private parseUnary(): ParsedExpr {
if (this.currentIs("minus")) {
const pos = this.position();
this.step();
return {
pos,
exprType: "unary",
unaryType: "negate",
subject: this.parseUnary(),
};
} else if (this.currentIs("not")) {
const pos = this.position();
this.step();
return {
pos,
exprType: "unary",
unaryType: "log_not",
subject: this.parseUnary(),
};
} else if (this.currentIs("tilde")) {
const pos = this.position();
this.step();
return {
pos,
exprType: "unary",
unaryType: "bit_not",
subject: this.parseUnary(),
};
} else if (this.currentIs("ampersand")) {
const pos = this.position();
this.step();
return {
pos,
exprType: "unary",
unaryType: "addressof",
subject: this.parseUnary(),
};
} else if (this.currentIs("asterisk")) {
const pos = this.position();
this.step();
return {
pos,
exprType: "unary",
unaryType: "dereference",
subject: this.parseUnary(),
};
} else {
return this.parseCallIndex();
}
}
private parseCallIndex(): ParsedExpr {
const pos = this.position();
let subject = this.parseOperand();
while (!this.done()) {
if (this.currentIs("lparen")) {
this.step();
const args: ParsedExpr[] = [];
if (!this.done() && this.current.tokenType != "rparen") {
args.push(this.parseExpr());
while (this.currentIs("comma")) {
this.step();
if (this.done() || this.currentIs("rparen")) {
break;
}
args.push(this.parseExpr());
}
}
if (!this.currentIs("rparen")) {
return this.errorExpr(
`expected ')', got '${this.current.tokenType}'`,
);
}
this.step();
subject = {
pos,
exprType: "call",
subject,
args,
};
} else if (this.currentIs("lbracket")) {
this.step();
const value = this.parseExpr();
if (!this.currentIs("rbracket")) {
return this.errorExpr(
`expected ']', got '${this.current.tokenType}'`,
);
}
this.step();
subject = {
pos,
exprType: "index",
subject,
value,
};
} else {
break;
}
}
return subject;
}
private parseOperand(): ParsedExpr {
if (this.current.tokenType == "id") {
const pos = this.position();
const value = this.current.value;
this.step();
return {
pos,
exprType: "id",
value,
};
} else if (this.current.tokenType == "int") {
const pos = this.position();
const value = this.current.value;
this.step();
return {
pos,
exprType: "int",
value,
};
} else if (this.currentIs("lparen")) {
const pos = this.position();
this.step();
if (this.currentIs("rparen")) {
this.step();
return { pos, exprType: "unit" };
}
const subject = this.parseExpr();
if (!this.currentIs("rparen")) {
return this.errorExpr(
`expected ')', got '${this.current.tokenType}'`,
);
}
this.step();
subject.pos = pos;
return subject;
} else if (this.currentIs("if")) {
return this.parseIf();
} else if (this.currentIs("lbrace")) {
return this.parseBlock();
} else {
return this.errorExpr(
`expected operand, got '${this.current.tokenType}'`,
);
}
}
private parseIf(): ParsedExpr {
const pos = this.position();
this.step();
const condition = this.parseExpr();
if (!this.currentIs("lbrace")) {
return this.errorExpr(
`expected '{' after if-condition, got '${this.current.tokenType}'`,
);
}
const truthy = this.parseBlock();
let falsy: ParsedExpr | null = null;
if (this.currentIs("else")) {
this.step();
if (!this.currentIs("lbrace")) {
return this.errorExpr(
`expected '{' after 'else', got '${this.current.tokenType}'`,
);
}
falsy = this.parseBlock();
}
return {
pos,
exprType: "if",
condition,
truthy,
falsy,
};
}
private parseBlock(): ParsedExpr {
const pos = this.position();
this.step();
while (this.currentIs("semicolon")) this.step();
const body: ParsedExpr[] = [];
while (!this.done() && this.current.tokenType != "rbrace") {
body.push(this.parseStatement());
while (this.currentIs("semicolon")) this.step();
}
if (!this.currentIs("rbrace")) {
return this.errorExpr(
`expected '}' after block, got '${this.current.tokenType}'`,
);
}
this.step();
return {
pos,
exprType: "block",
body,
};
}
private parseType(): ParsedType {
return this.parsePointerType();
}
private parsePointerType(): ParsedType {
if (this.currentIs("asterisk")) {
const pos = this.position();
this.step();
let mutable = false;
if (this.currentIs("mut")) {
mutable = true;
this.step();
}
const subject = this.parseTypeOperand();
return {
pos,
typeType: "pointer",
subject,
mutable,
};
} else {
return this.parseTypeOperand();
}
}
private parseTypeOperand(): ParsedType {
if (this.current.tokenType == "id") {
const pos = this.position();
const value = this.current.value;
this.step();
return {
pos,
typeType: "id",
value,
};
} else if (this.currentIs("lparen")) {
const pos = this.position();
this.step();
if (this.currentIs("rparen")) {
this.step();
return { pos, typeType: "unit" };
}
const subject = this.parseType();
if (!this.currentIs("rparen")) {
return this.errorType(
`expected ')', got '${this.current.tokenType}'`,
);
}
this.step();
subject.pos = pos;
return subject;
} else {
return this.errorType(
`expected type, got '${this.current.tokenType}'`,
);
}
}
private errorExpr(
message: string,
pos: Position = this.position(),
): ParsedExpr {
this.errors.push({ pos, message });
return {
pos,
exprType: "error",
message,
};
}
private errorParam(
message: string,
pos: Position = this.position(),
): ParsedParameter {
this.errors.push({ pos, message });
return {
pos,
paramType: "error",
message,
};
}
private errorType(
message: string,
pos: Position = this.position(),
): ParsedType {
this.errors.push({ pos, message });
return {
pos,
typeType: "error",
message,
};
}
private position() {
return this.current.pos;
}
private step() {
this.current = this.lexer.next();
}
private currentIs(tokenType: TokenType): boolean {
return this.current.tokenType == tokenType;
}
private done(): boolean {
return this.current.tokenType == "eof";
}
}

76
token.ts Normal file
View File

@ -0,0 +1,76 @@
export type Position = { index: number; line: number; col: number };
export type CompileError = { pos: Position; message: string };
export type DynamicTokenType = "id" | "int";
export type StaticTokenType =
| "eof"
| "invalid"
| "lparen"
| "rparen"
| "lbrace"
| "rbrace"
| "lbracket"
| "rbracket"
| "dot"
| "comma"
| "colon"
| "semicolon"
| "plus"
| "plusplus"
| "plusequal"
| "minus"
| "minusminus"
| "minusequal"
| "minusgt"
| "asterisk"
| "tilde"
| "ampersand"
| "ampersandequal"
| "pipe"
| "pipeequal"
| "hat"
| "hatequal"
| "equal"
| "equalequal"
| "exclamation"
| "exclamationequal"
| "lt"
| "gt"
| "ltequal"
| "gtequal"
| "not"
| "and"
| "or"
| "let"
| "mut"
| "fn"
| "return"
| "if"
| "else"
| "loop"
| "while"
| "break"
| "continue";
export type TokenType = DynamicTokenType | StaticTokenType;
export type Token =
& {
pos: Position;
}
& (
| { tokenType: "id"; value: string }
| {
tokenType: "int";
value: number;
}
| {
tokenType: StaticTokenType;
}
);
export interface TokenIter {
next(): Token;
}