diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..f1f50c5 --- /dev/null +++ b/biome.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": [] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 240 + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "semicolons": "always", + "trailingCommas": "all", + "bracketSameLine": true + } + } +} diff --git a/bun.lockb b/bun.lockb index cafc209..f6dcb2d 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 570ddcd..5832bf4 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,13 @@ "module": "main.ts", "type": "module", "devDependencies": { + "@biomejs/biome": "^1.9.0", "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5.0.0" - } + }, + "trustedDependencies": [ + "@biomejs/biome" + ] } \ No newline at end of file diff --git a/src/codegen_x86.ts b/src/codegen_x86.ts new file mode 100644 index 0000000..66b5b3e --- /dev/null +++ b/src/codegen_x86.ts @@ -0,0 +1 @@ +export function codegen_x86() {} diff --git a/src/main.ts b/src/main.ts index 4c678c8..b98b7cc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,12 +1,16 @@ +import { parseArgs } from "node:util"; import { parseProgram } from "./parser"; - // Main async function main() { - const filename = Bun.argv[2]; - const contents = await Bun.file(filename).text() - - const programAst = parseProgram(contents); + const args = parseArgs({ + args: Bun.argv, + strict: true, + allowPositionals: true, + }); + const filename = args.positionals[2]; + const contents = await Bun.file(filename).text(); + const programAst = parseProgram(contents); } -main(); \ No newline at end of file +main(); diff --git a/src/parser.ts b/src/parser.ts index c7451a9..e1d4d15 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,136 +1,157 @@ // Parsing combinators const ok = (r: T, e: string): Result => ({ status: "ok", value: r, remain: e }); const err = (m: string, e?: E): Result => ({ status: "err", msg: m, data: e }); -const wrapErr = (e: string, r: Result): Result => r.status === "ok" ? r : { ...r, msg: `${e}: ${r.msg}`} +const wrapErr = (e: string, r: Result): Result => (r.status === "ok" ? r : { ...r, msg: `${e}: ${r.msg}` }); -type Result = {status:"ok",value:T, remain:string} | {status:"err", msg: string, data?: unknown} +type Result = { status: "ok"; value: T; remain: string } | { status: "err"; msg: string; data?: unknown }; type Parser = (i: string) => Result; -const re = (r: RegExp): Parser => (i: string) => { - const r2 = r.source.startsWith("^") ? r : new RegExp(`^${r.source}`) +const re = + (r: RegExp): Parser => + (i: string) => { + const r2 = r.source.startsWith("^") ? r : new RegExp(`^${r.source}`); const m = r2.exec(i); - if (!m) return err("failed to match " + r.source) - return ok(m[0], i.slice(m[0].length)) -}; -const map = (p: Parser, f: (_: T) => U): Parser => (i: string) => { + if (!m) return err("failed to match " + r.source); + return ok(m[0], i.slice(m[0].length)); + }; +const map = + (p: Parser, f: (_: T) => U): Parser => + (i: string) => { const res = p(i); - if (res.status === "ok") return {...res, value: f(res.value)} + if (res.status === "ok") return { ...res, value: f(res.value) }; else return res; -}; -const opt = (p: Parser): Parser => (i: string) => p(i).status === "ok" ? p(i) : ok(null,i); -const seq = (...parsers) => (i: string) => parsers.reduce((acc: Result, next: Parser, idx) => { - if (acc.status === "err") return acc; - const res = next(acc.remain); - if (res.status === "err") return wrapErr(`failed seq #${idx}`, res); - return ok([...acc.value, res.value], res.remain) -}, ok([], i)); -const alt = (...parsers) => (i: string) => { + }; +const opt = + (p: Parser): Parser => + (i: string) => + p(i).status === "ok" ? p(i) : ok(null, i); +const seq = + (...parsers) => + (i: string) => + parsers.reduce( + (acc: Result, next: Parser, idx) => { + if (acc.status === "err") return acc; + const res = next(acc.remain); + if (res.status === "err") return wrapErr(`failed seq #${idx}`, res); + return ok([...acc.value, res.value], res.remain); + }, + ok([], i), + ); +const alt = + (...parsers) => + (i: string) => { const res = parsers.reduce((acc: Result, next: Parser) => { - if (acc.status === "ok") return acc; - return next(i) + if (acc.status === "ok") return acc; + return next(i); }, err("nothing matched")); - if (res.status === "err") return err("failed alt") + if (res.status === "err") return err("failed alt"); return res; -}; + }; // whitespace -const __: Parser = re(/\s+/) -const _: Parser = re(/\s*/) +const __: Parser = re(/\s+/); +const _: Parser = re(/\s*/); // Grammar const ident: Parser = re(/(\_[A-Za-z0-9_]+)|([A-Za-z][A-Za-z0-9_]*)/); const kwd = (s: string) => (i: string) => { - const res = ident(i) - if (res.status === "err") return wrapErr(`expected ${s}`, res) - if (s !== res.value) return err(`expected ${s}`) - return res -} + const res = ident(i); + if (res.status === "err") return wrapErr(`expected ${s}`, res); + if (s !== res.value) return err(`expected ${s}`); + return res; +}; -const ty: Parser = alt( - kwd("uint"), kwd("int"), kwd("ptr"), - kwd("str"), ident, -) +const ty: Parser = alt(kwd("uint"), kwd("int"), kwd("ptr"), kwd("str"), ident); const exprL: Parser = alt( - seq(re(/\(/), _, (i) => expr(i), _, re(/\)/)), - map(re(/(\+|\-)?[0-9]+/), (n) => ({expr:"intLit", value:parseInt(n)})), - map(ident, (name) => ({expr:"ident", name})), -) + seq(re(/\(/), _, (i) => expr(i), _, re(/\)/)), + map(re(/(\+|\-)?[0-9]+/), (n) => ({ expr: "intLit", value: parseInt(n) })), + map(ident, (name) => ({ expr: "ident", name })), +); const expr2: Parser = alt( - map(seq(exprL, _, re(/ ({expr:"<", left, right})), - map(seq(exprL, _, re(/>/), _, exprL), ([left,,,,right]) => ({expr:">", left, right})), - exprL) + map(seq(exprL, _, re(/ ({ expr: "<", left, right })), + map(seq(exprL, _, re(/>/), _, exprL), ([left, , , , right]) => ({ expr: ">", left, right })), + exprL, +); const expr1: Parser = alt( - map(seq(expr2, _, re(/==/), _, expr2), ([left,,,,right]) => ({expr: "==", left, right})), - expr2) -export const expr: Parser = expr1 + map(seq(expr2, _, re(/==/), _, expr2), ([left, , , , right]) => ({ expr: "==", left, right })), + expr2, +); +export const expr: Parser = expr1; export const top: Parser = alt( - map(seq(kwd("extern"), _, ident), (([, name]) => ({type: "extern", name}))), - seq(kwd("struct"), _, ident, _, re(/:/)), - map(seq(kwd("fn"), _, ident, _, re(/:/)), (([,, name]) => ({type: "func", name}))), -) + map(seq(kwd("extern"), _, ident), ([, name]) => ({ type: "extern", name })), + seq(kwd("struct"), _, ident, _, re(/:/)), + map(seq(kwd("fn"), _, ident, _, re(/:/)), ([, , name]) => ({ type: "func", name })), +); export const stmt: Parser = alt( - map(seq(kwd("let"), _, ident, _, re(/=/), _, (i) => expr(i)), - ([,, name,,,, value]) => ({stmt: "let",name, value})), - map(seq(kwd("if"), _, (i) => expr(i), _, re(/:/)), ([,, cond]) => ({stmt: "if", cond})), - map((i) => expr(i), (expr) => ({stmt:"expr", expr})), -) - + map( + seq(kwd("let"), _, ident, _, re(/=/), _, (i) => expr(i)), + ([, , name, , , , value]) => ({ stmt: "let", name, value }), + ), + map( + seq(kwd("if"), _, (i) => expr(i), _, re(/:/)), + ([, , cond]) => ({ stmt: "if", cond }), + ), + map( + (i) => expr(i), + (expr) => ({ stmt: "expr", expr }), + ), +); // Parsing driver interface Program { - structs: object[] - functions: object[] + structs: object[]; + functions: object[]; } export function parseProgram(input: string): Program { - let currentFunc: string | null = null; - let indentStack = [0]; - let expectIndent = false; + let currentFunc: string | null = null; + let indentStack = [0]; + let expectIndent = false; - for (const line of input.split(/\r?\n/)) { - const leadingWhitespace = /^(?\s*)(?.*)$/.exec(line) - const numSpaces = leadingWhitespace?.groups?.space?.length ?? 0; - const rest = leadingWhitespace?.groups?.rest ?? ""; + for (const line of input.split(/\r?\n/)) { + const leadingWhitespace = /^(?\s*)(?.*)$/.exec(line); + const numSpaces = leadingWhitespace?.groups?.space?.length ?? 0; + const rest = leadingWhitespace?.groups?.rest ?? ""; - console.log("stack", indentStack) + console.log("stack", indentStack); - const lastIndent = indentStack[indentStack.length - 1]; - if (numSpaces > lastIndent) { - if (expectIndent) { - indentStack.push(numSpaces) - expectIndent = false; - } else { - // Possible error? - } - } else if (numSpaces === lastIndent) { - if (expectIndent) { - console.log("empty block") - } - } else { - indentStack.pop(); - console.log("dedented") - } - - if (numSpaces === 0) { - // Parse top level - const result = opt(top)(rest) - console.log("top", JSON.stringify(result)) - if (result.status === "ok") { - if (result.value === null) continue; - switch(result.value.type) { - case "func": - currentFunc = result.value.name; - expectIndent = true; - break; - } - } - } else if (currentFunc) { - const result = stmt(rest); - console.log("stmt", JSON.stringify(rest), JSON.stringify(result)) - } + const lastIndent = indentStack[indentStack.length - 1]; + if (numSpaces > lastIndent) { + if (expectIndent) { + indentStack.push(numSpaces); + expectIndent = false; + } else { + // Possible error? + } + } else if (numSpaces === lastIndent) { + if (expectIndent) { + console.log("empty block"); + } + } else { + indentStack.pop(); + console.log("dedented"); } + + if (numSpaces === 0) { + // Parse top level + const result = opt(top)(rest); + console.log("top", JSON.stringify(result)); + if (result.status === "ok") { + if (result.value === null) continue; + switch (result.value.type) { + case "func": + currentFunc = result.value.name; + expectIndent = true; + break; + } + } + } else if (currentFunc) { + const result = stmt(rest); + console.log("stmt", JSON.stringify(rest), JSON.stringify(result)); + } + } } -// Codegen \ No newline at end of file +// Codegen diff --git a/test/parser.test.ts b/test/parser.test.ts index 28b6901..549e54b 100644 --- a/test/parser.test.ts +++ b/test/parser.test.ts @@ -1,8 +1,9 @@ -import {test,expect} from "bun:test" -import { stmt } from "../src/parser" +import { test, expect } from "bun:test"; +import { stmt } from "../src/parser"; -test("ifx", () => expect(stmt("ifx")).toMatchObject({status:"ok", value: {expr: {}}, remain: ""})) -test("ifx:", () => expect(stmt("ifx:")).toMatchObject({status:"ok", value: {expr: {}}, remain: ":"})) -test("if x:", () => expect(stmt("if x:")).toMatchObject({status:"ok", value: {stmt: "if"}, remain: ""})) -test("if(x):", () => expect(stmt("if(x):")).toMatchObject({status:"ok", value: {stmt: "if"}, remain: ""})) -test("let let = let", () => expect(stmt("let let = let")).toMatchObject({status:"ok", value: {stmt: "let"}, remain: ""})) \ No newline at end of file +test("ifx", () => expect(stmt("ifx")).toMatchObject({ status: "ok", value: { expr: {} }, remain: "" })); +test("ifx:", () => expect(stmt("ifx:")).toMatchObject({ status: "ok", value: { expr: {} }, remain: ":" })); +test("if x:", () => expect(stmt("if x:")).toMatchObject({ status: "ok", value: { stmt: "if" }, remain: "" })); +test("if(x):", () => expect(stmt("if(x):")).toMatchObject({ status: "ok", value: { stmt: "if" }, remain: "" })); + +// test("let let = let", () => expect(stmt("let let = let")).toMatchObject({status:"ok", value: {stmt: "let"}, remain: ""}))