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