init
This commit is contained in:
commit
b2b1533ee6
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
session.vim
|
99
checked.ts
Normal file
99
checked.ts
Normal 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
55
checker.ts
Normal 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
10
deno.jsonc
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"fmt": {
|
||||||
|
"options": {
|
||||||
|
"useTabs":false,
|
||||||
|
"lineWidth": 80,
|
||||||
|
"indentWidth": 4,
|
||||||
|
"singleQuote": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
218
lexer.ts
Normal file
218
lexer.ts
Normal 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
10
main.ts
Normal 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
126
parsed.ts
Normal 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
758
parser.ts
Normal 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
76
token.ts
Normal 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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user