commit b2b1533ee61660d5936303432fc83a5078680527 Author: SimonFJ20 Date: Thu Apr 27 02:32:06 2023 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..882c025 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +session.vim diff --git a/checked.ts b/checked.ts new file mode 100644 index 0000000..6904c7c --- /dev/null +++ b/checked.ts @@ -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; + }); diff --git a/checker.ts b/checker.ts new file mode 100644 index 0000000..8c1c402 --- /dev/null +++ b/checker.ts @@ -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" }) { + } +} + diff --git a/deno.jsonc b/deno.jsonc new file mode 100644 index 0000000..ae1f70d --- /dev/null +++ b/deno.jsonc @@ -0,0 +1,10 @@ +{ + "fmt": { + "options": { + "useTabs":false, + "lineWidth": 80, + "indentWidth": 4, + "singleQuote": false + } + } +} diff --git a/lexer.ts b/lexer.ts new file mode 100644 index 0000000..c424b2e --- /dev/null +++ b/lexer.ts @@ -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; + } +} diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..91ed071 --- /dev/null +++ b/main.ts @@ -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(); diff --git a/parsed.ts b/parsed.ts new file mode 100644 index 0000000..de3c711 --- /dev/null +++ b/parsed.ts @@ -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; + }); diff --git a/parser.ts b/parser.ts new file mode 100644 index 0000000..67001c6 --- /dev/null +++ b/parser.ts @@ -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"; + } +} diff --git a/token.ts b/token.ts new file mode 100644 index 0000000..d33e0ca --- /dev/null +++ b/token.ts @@ -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; +}