This commit is contained in:
Michael Zhang 2023-10-04 14:10:46 -05:00
commit 3b84ad4a83
13 changed files with 396 additions and 0 deletions

View file

@ -0,0 +1,26 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile
{
"name": "Existing Dockerfile",
"build": {
// Sets the run context to one level up instead of the .devcontainer folder.
"context": "..",
// Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
"dockerfile": "../Dockerfile"
},
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {}
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Uncomment the next line to run commands after the container is created.
// "postCreateCommand": "cat /etc/os-release",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "devcontainer"
}

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/target
/build
node_modules

7
.prettierrc.json Normal file
View file

@ -0,0 +1,7 @@
{
"useTabs": false,
"tabWidth": 2,
"singleQuote": false,
"trailingComma": "all",
"printWidth": 100
}

4
Dockerfile Normal file
View file

@ -0,0 +1,4 @@
FROM oven/bun:alpine
RUN apk add \
git clang make lld

2
Makefile Normal file
View file

@ -0,0 +1,2 @@
watch:
bun --watch src/index.ts

0
bootstrap/gc.boot Normal file
View file

6
bootstrap/runtime.boot Normal file
View file

@ -0,0 +1,6 @@
extern main;
extern helloge;
pub fn _start() {
main();
}

BIN
bun.lockb Executable file

Binary file not shown.

7
package.json Normal file
View file

@ -0,0 +1,7 @@
{
"dependencies": { "chalk": "^5.3.0", "cmd-ts": "^0.13.0" },
"devDependencies": {
"bun-types": "^1.0.4-canary.20231004T140131",
"prettier": "^3.0.3"
}
}

25
src/index.ts Normal file
View file

@ -0,0 +1,25 @@
import { BunFile } from "bun";
import { Type, command, run, positional, boolean, option, flag } from "cmd-ts";
import { parseProgram, program } from "./parser";
const File: Type<string, BunFile> = {
from: async (str) => Bun.file(str),
};
const app = command({
name: "",
args: {
lib: flag({ long: "lib", description: "Compile this in library mode (no main function)" }),
file: positional({ type: File, displayName: "file", description: "Source code" }),
},
handler: async ({ lib, file }) => {
// Read stream to file
const source = await file.text();
// Parse source
const parseResult = parseProgram(source);
// console.log("tree", parseResult);
},
});
run(app, Bun.argv.slice(2));

170
src/parser.ts Normal file
View file

@ -0,0 +1,170 @@
import { Result, getLinesAround } from "./util";
import chalk from "chalk";
export interface ParseInput {
source: string;
position: number;
}
export interface ParseSuccess<T> {
output: T;
start: number;
end: number;
}
export interface ParseError {
expected?: string;
start: number;
}
export interface Parser<Out> {
(_: ParseInput): Result<ParseSuccess<Out>, ParseError>;
}
export const exact: (_: string) => Parser<string> = (needle: string) => (input: ParseInput) => {
const start = input.position;
const end = start + needle.length;
const sliced = input.source.slice(start, end);
return sliced === needle
? Result.ok({ output: sliced, start, end })
: Result.err({ expected: `The string ${JSON.stringify(needle)}`, start });
};
export const regex: (_: RegExp) => Parser<string> = (re: RegExp) => (input: ParseInput) => {
const start = input.position;
const reY = new RegExp(re, [...new Set([...re.flags.split(""), "y", "m"])].join(""));
reY.lastIndex = start;
const result = input.source.match(reY);
if (!result) return Result.err({ expected: `String that matches ${reY}`, start });
return Result.ok({ output: result[0], start, end: start + result[0].length });
};
export const optWhitespace = regex(/\s*/m);
export const whitespace = regex(/\s+/m);
export const surroundWs = <T>(parser: Parser<T>) =>
first(second(optWhitespace, parser), optWhitespace);
export const map =
<T, U>(parser: Parser<T>, func: (_: T) => U) =>
(input: ParseInput) =>
parser(input).map(({ output, ...result }) => ({ output: func(output), ...result }));
export const pair: <Out1, Out2>(p1: Parser<Out1>, p2: Parser<Out2>) => Parser<[Out1, Out2]> =
(p1, p2) => (input: ParseInput) =>
p1(input).andThen(({ output: out1, end }) =>
p2({ ...input, position: end }).map(({ output: out2, end }) => ({
output: [out1, out2],
start: input.position,
end: end,
})),
);
export const first: <Out1, Out2>(p1: Parser<Out1>, p2: Parser<Out2>) => Parser<Out1> = (p1, p2) =>
map(pair(p1, p2), ([out1]) => out1);
export const firstWs: <Out1, Out2>(p1: Parser<Out1>, p2: Parser<Out2>) => Parser<Out1> = (p1, p2) =>
first(first(p1, whitespace), p2);
export const second: <Out1, Out2>(p1: Parser<Out1>, p2: Parser<Out2>) => Parser<Out2> = (p1, p2) =>
map(pair(p1, p2), ([_, out2]) => out2);
export const secondWs: <Out1, Out2>(p1: Parser<Out1>, p2: Parser<Out2>) => Parser<Out2> = (
p1,
p2,
) => second(second(p1, whitespace), p2);
export const sep: <T, D>(itemParser: Parser<T>, delimParser: Parser<D>) => Parser<T[]> =
(itemParser, delimParser) => (input: ParseInput) => {
const items = [];
let position = input.position;
let first = true;
while (true) {
if (first) {
first = false;
} else {
const result = delimParser({ ...input, position });
if (result.status === "err") break;
const { end } = result.unwrap();
position = end;
}
const result = itemParser({ ...input, position });
if (result.status === "err") break;
const { output, end } = result.unwrap();
position = end;
items.push(output);
}
return Result.ok({ output: items, start: input.position, end: position });
};
export const repeatWs: <T>(parser: Parser<T>) => Parser<T[]> = (parser) => sep(parser, whitespace);
export const alt =
<T>(...parsers: Parser<T>[]) =>
(input: ParseInput) =>
parsers.reduceRight(
(prevValue: Result<ParseSuccess<T>, ParseError>, nextValue: Parser<T>) =>
prevValue.orElse(() => nextValue(input)),
Result.err({ start: input.position }),
);
// Language-specific stuff
export const ident = regex(/[_a-zA-Z][_a-zA-Z0-9]+/);
export const extern = map(first(secondWs(exact("extern"), ident), exact(";")), (name) => ({
kind: "externItem",
name,
}));
export const item = alt(extern);
export const program = surroundWs(repeatWs(item));
export function parseProgram(input: string) {
const result = program({ source: input, position: 0 });
if (result.status === "err") {
const { expected, start } = result.unwrapErr();
const expectedMsg = expected ? `Expected: ${expected}` : "";
printError(input, start, expectedMsg);
}
const { output, end } = result.unwrap();
if (end !== input.length) {
printError(input, end, "wtf is this bro");
}
return output;
}
function printError(input: string, at: number, msg: string) {
const linesAround = getLinesAround(input, at);
if (linesAround.status === "err") throw new Error(msg);
const { lines, focusedLineIdx, startLineNum, col } = linesAround.unwrap();
const maxLen = Math.max(
startLineNum.toString().length,
(startLineNum + lines.length).toString().length,
);
console.log(chalk.red("Error parsing program:"));
lines.forEach((line, idx) => {
const absLine = idx + startLineNum;
const absLineStr = absLine.toString().padStart(maxLen, " ");
console.log(chalk.blue(`${absLineStr} | `) + line);
if (idx === focusedLineIdx) {
const emptyLineStr = "".padStart(maxLen, " ");
const errorPointer = new Array(col).fill(0).map((_) => " ") + "^";
console.log(chalk.blue(`${emptyLineStr} | `) + chalk.red(errorPointer + " " + msg));
}
});
console.log();
throw new Error();
}

125
src/util.ts Normal file
View file

@ -0,0 +1,125 @@
export class Result<T, E> {
private constructor(
public status: "ok" | "err",
private okValue: T | undefined,
private errValue: E | undefined,
) {}
toString(): string {
switch (this.status) {
case "ok":
return `Ok(${this.okValue!.toString()})`;
case "err":
return `Err(${this.errValue!.toString()})`;
}
}
static ok<T, E>(value: T): Result<T, E> {
return new Result("ok", value, undefined!);
}
static err<T, E>(value: E): Result<T, E> {
return new Result("err", undefined!, value);
}
unwrap(): T {
switch (this.status) {
case "ok":
return this.okValue!;
case "err":
throw new Error("unwrap");
}
}
unwrapErr(): E {
switch (this.status) {
case "ok":
throw new Error("unwrap");
case "err":
return this.errValue!;
}
}
map<U>(func: (_: T) => U): Result<U, E> {
switch (this.status) {
case "ok":
return Result.ok(func(this.okValue!));
case "err":
return Result.err(this.errValue!);
}
}
mapErr<U>(func: (_: E) => U): Result<T, U> {
switch (this.status) {
case "ok":
return Result.ok(this.okValue!);
case "err":
return Result.err(func(this.errValue!));
}
}
andThen<U>(func: (_: T) => Result<U, E>): Result<U, E> {
switch (this.status) {
case "ok":
return func(this.okValue!);
case "err":
return Result.err(this.errValue!);
}
}
orElse<U>(func: (_: E) => Result<U, E>): Result<U, E> {
switch (this.status) {
case "ok":
return Result.ok(this.okValue!);
case "err":
return func(this.errValue!);
}
}
}
export interface LinesAround {
lines: string[];
startLineNum: number;
focusedLineIdx: number;
col: number;
}
export function getLinesAround(source: string, at: number): Result<LinesAround, undefined> {
let pos = 0;
let lineIdx = 0;
let focusedLineIdx;
let col;
let allLines = [];
while (true) {
const re = /\r?\n/;
const nextNewline = source.slice(pos).match(re);
if (!nextNewline) break;
const line = source.slice(pos, pos + nextNewline.index);
allLines.push(line);
const start = pos;
const end = start + line.length;
if (at >= start && at < end) {
focusedLineIdx = lineIdx;
col = at - start;
}
pos += line.length + nextNewline.length;
lineIdx += 1;
}
if (focusedLineIdx === undefined || col === undefined) return Result.err(undefined);
const before = Math.max(0, focusedLineIdx - 1);
const after = Math.min(allLines.length - 1, focusedLineIdx + 1);
return Result.ok({
lines: allLines.slice(before, after + 1),
focusedLineIdx: focusedLineIdx - before,
startLineNum: before + 1,
col,
});
}

21
tsconfig.json Normal file
View file

@ -0,0 +1,21 @@
{
"compilerOptions": {
"lib": ["ESNext"],
"module": "esnext",
"target": "esnext",
"moduleResolution": "bundler",
"noEmit": true,
"allowImportingTsExtensions": true,
"moduleDetection": "force",
"strict": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"composite": true,
"downlevelIteration": true,
"allowSyntheticDefaultImports": true,
"types": ["bun-types"]
}
}