coed spilt

This commit is contained in:
SimonFJ20 2023-07-24 02:14:02 +02:00
parent b2a59de1b2
commit 27e066f5b1
13 changed files with 387 additions and 399 deletions

View File

@ -1,241 +0,0 @@
import { Expr } from "./parsed";
export type Register = "acc" | "op";
export type Value = number;
export type Location = number;
export type Instruction = ({
type: "push" | "pop",
register: Register,
} | {
type: "load",
register: Register,
value: Value,
} | {
type: "add" | "mul" | "sub" | "div",
left: Register,
right: Register,
dest: Register,
} | {
type: "negate",
src: Register,
dest: Register,
} | {
type: "jump_zero",
register: Register,
location: Location,
} | {
type: "jump",
location: Location,
}) & ({ jumpedTo: true, label: number } | { jumpedTo?: false });
export class Compiler {
public result: Instruction[] = [];
public compileExpr(expr: Expr) {
switch (expr.exprType) {
case "int": {
this.result.push({ type: "load", register: "acc", value: expr.value })
break;
}
case "unary": {
this.compileExpr(expr.subject);
switch (expr.unaryType) {
case "plus": {
break;
}
case "negate": {
this.result.push({ type: "negate", src: "acc", dest: "acc" })
break;
}
}
break;
}
case "binary": {
this.compileExpr(expr.right);
this.result.push({ type: "push", register: "acc" })
this.compileExpr(expr.left);
this.result.push({ type: "pop", register: "op" });
let binaryType: "add" | "sub" | "mul" | "div";
switch (expr.binaryType) {
case "add": {
binaryType = "add";
break;
}
case "subtract": {
binaryType = "sub";
break;
}
case "multiply": {
binaryType = "mul";
break;
}
case "divide": {
binaryType = "div";
break;
}
}
this.result.push({ type: binaryType, left: "acc", right: "op", dest: "acc" });
break;
}
case "if": {
this.compileExpr(expr.condition);
const jumpToFalsyIndex = this.result.length;
this.result.push({ type: "jump_zero", register: "acc", location: 0 });
this.compileExpr(expr.truthy);
const skipFalsyIndex = this.result.length;
this.result.push({ type: "jump", location: 0 });
let jumpToFalsyRef = this.result[jumpToFalsyIndex];
if (jumpToFalsyRef.type !== "jump_zero") throw new Error("unreachable");
jumpToFalsyRef.location = this.result.length;
this.compileExpr(expr.falsy);
let skipFalsyRef = this.result[skipFalsyIndex];
if (skipFalsyRef.type !== "jump") throw new Error("unreachable");
skipFalsyRef.location = this.result.length;
break;
}
case "block": {
this.compileExpr(expr.expr);
break;
}
default: {
const exhaustiveCheck: never = expr;
throw new Error(`Unhandled color case: ${exhaustiveCheck}`);
}
}
}
}
export function locateAndSetJumpedToInstructions(instructions: Instruction[]) {
let nextLabel = 0;
for (const ins of instructions) {
if (ins.type === "jump_zero" || ins.type === "jump") {
instructions[ins.location] = {
...instructions[ins.location],
jumpedTo: true,
label: nextLabel,
};
nextLabel += 1;
}
}
}
export class X86Generator {
public generate(instructions: Instruction[]): string {
return this.asmWithHeaders(instructions.map((ins) => this.generateInstruction(ins)).join(""));
}
private generateInstruction(ins: Instruction): string {
let result = "";
if (ins.jumpedTo) {
}
switch (ins.type) {
case "load": {
result += ` ; load`
result += ` mov ${this.reg64(ins.register)}, ${this.value(ins.value)}\n`;
break;
}
case "push": {
return `
; push
push ${this.reg64(ins.register)}
`;
}
case "pop": {
return `
; pop
pop ${this.reg64(ins.register)}
`;
}
case "negate": {
return `
; neg
mov ${this.reg64(ins.dest)}, ${this.reg64(ins.src)}
neg ${this.reg64(ins.dest)}
`;
}
case "add": {
return `
; add
mov ${this.reg64(ins.dest)}, ${this.reg64(ins.left)}
add ${this.reg64(ins.dest)}, ${this.reg64(ins.right)}
`;
}
case "sub": {
return `
; sub
mov ${this.reg64(ins.dest)}, ${this.reg64(ins.left)}
sub ${this.reg64(ins.dest)}, ${this.reg64(ins.right)}
`;
}
case "mul": {
return `
; mul
mov ${this.reg64(ins.dest)}, ${this.reg64(ins.left)}
imul ${this.reg64(ins.dest)}, ${this.reg64(ins.right)}
`;
}
case "div": {
return `
; div
mov rdi, ${this.reg64(ins.right)}
mov rax, ${this.reg64(ins.left)}
xor rdx, rdx
cqo
idiv rdi
mov ${this.reg64(ins.dest)}, rax
`;
}
case "jump_zero": {
return `
; jump_zero
`
}
}
}
private asmWithHeaders(asm: string) {
return `
bits 64
global _start
_start:
${asm}
exit:
mov rdi, rax
mov rax, 60
syscall
`;
}
private reg64(reg: Register): string {
switch (reg) {
case "acc": return "rax";
case "op": return "rdx";
}
}
private reg32(reg: Register): string {
switch (reg) {
case "acc": return "eax";
case "op": return "edx";
}
}
private value(value: Value): string {
return value.toString();
}
}

View File

@ -1,60 +0,0 @@
@preprocessor typescript
@{%
import { Expr } from "./parsed";
%}
expr -> term {% id %}
term -> term _ "+" _ factor
{% ([left, _0, _1, _2, right]): Expr =>
({ exprType: "binary", binaryType: "add", left, right }) %}
| term _ "-" _ factor
{% ([left, _0, _1, _2, right]): Expr =>
({ exprType: "binary", binaryType: "subtract", left, right }) %}
| factor {% id %}
factor -> factor _ "*" _ unary
{% ([left, _0, _1, _2, right]): Expr =>
({ exprType: "binary", binaryType: "multiply", left, right }) %}
| factor _ "/" _ unary
{% ([left, _0, _1, _2, right]): Expr =>
({ exprType: "binary", binaryType: "divide", left, right }) %}
| unary {% id %}
unary -> "+" _ unary
{% ([_0, _1, subject]): Expr =>
({ exprType: "unary", unaryType: "plus", subject }) %}
| "-" _ unary
{% ([_0, _1, subject]): Expr =>
({ exprType: "unary", unaryType: "negate", subject }) %}
| operand {% id %}
operand -> int {% id %}
| group {% id %}
| block {% id %}
| if {% id %}
int -> [0-9]:+
{% ([token]): Expr =>
({ exprType: "int", value: parseInt(token.join("")) }) %}
group -> "(" _ expr _ ")" {% (v): Expr => v[2] %}
block -> "{" _ expr _ "}" {% (v): Expr => ({ exprType: "block", expr: v[2] }) %}
if -> "if" __ expr _ block _ "else" _ block
{%
(v): Expr => ({
exprType: "if",
condition: v[2],
truthy: v[4],
falsy: v[8]
})
%}
_ -> __:?
__ -> ws:+
ws -> [ \t\r\n]

View File

@ -1,7 +1,8 @@
{
"license": "MIT",
"scripts": {
"start": "npm run build && ts-node main.ts",
"build": "nearleyc grammar.ne -o grammar.out.ts"
"start": "npm run build && ts-node src/main.ts",
"build": "nearleyc src/grammar.ne -o src/grammar.out.ts"
},
"dependencies": {
"@types/moo": "^0.5.5",
@ -10,6 +11,7 @@
"moo": "^0.5.2",
"nearley": "^2.20.1",
"ts-node": "^10.9.1",
"ts-results": "^3.3.0",
"typescript": "^5.1.6"
}
}

103
src/compiler.ts Normal file
View File

@ -0,0 +1,103 @@
import { Instruction } from "./ir";
import { Expr } from "./parsed";
import { assertExhaustive } from "./utils";
export class Compiler {
public result: Instruction[] = [];
public compileExpr(expr: Expr) {
switch (expr.exprType) {
case "int": {
this.result.push({ type: "load", register: "acc", value: expr.value })
break;
}
case "unary": {
this.compileExpr(expr.subject);
switch (expr.unaryType) {
case "plus": {
break;
}
case "negate": {
this.result.push({ type: "negate", src: "acc", dest: "acc" })
break;
}
}
break;
}
case "binary": {
this.compileExpr(expr.right);
this.result.push({ type: "push", register: "acc" })
this.compileExpr(expr.left);
this.result.push({ type: "pop", register: "op" });
let binaryType: "add" | "sub" | "mul" | "div";
switch (expr.binaryType) {
case "add": {
binaryType = "add";
break;
}
case "subtract": {
binaryType = "sub";
break;
}
case "multiply": {
binaryType = "mul";
break;
}
case "divide": {
binaryType = "div";
break;
}
}
this.result.push({ type: binaryType, left: "acc", right: "op", dest: "acc" });
break;
}
case "if": {
this.compileExpr(expr.condition);
const jumpToFalsyIndex = this.result.length;
this.result.push({ type: "jump_zero", register: "acc", location: 0 });
this.compileExpr(expr.truthy);
const skipFalsyIndex = this.result.length;
this.result.push({ type: "jump", location: 0 });
let jumpToFalsyRef = this.result[jumpToFalsyIndex];
if (jumpToFalsyRef.type !== "jump_zero") throw new Error("unreachable");
jumpToFalsyRef.location = this.result.length;
this.compileExpr(expr.falsy);
let skipFalsyRef = this.result[skipFalsyIndex];
if (skipFalsyRef.type !== "jump") throw new Error("unreachable");
skipFalsyRef.location = this.result.length;
break;
}
case "block": {
this.compileExpr(expr.expr);
break;
}
default: {
assertExhaustive(expr);
}
}
}
}
export function locateAndSetJumpedToInstructions(instructions: Instruction[]) {
let nextLabel = 0;
for (const ins of instructions) {
if (ins.type === "jump_zero" || ins.type === "jump") {
instructions[ins.location] = {
...instructions[ins.location],
jumpedTo: true,
label: nextLabel,
};
nextLabel += 1;
}
}
}

63
src/grammar.ne Normal file
View File

@ -0,0 +1,63 @@
@preprocessor typescript
@{%
import { Expr } from "./parsed";
const expr = (e: Expr): Expr => e;
%}
expr -> term {% id %}
term -> term _ "+" _ factor
{% ([left, _0, _1, _2, right]) =>
expr({ exprType: "binary", binaryType: "add", left, right }) %}
| term _ "-" _ factor
{% ([left, _0, _1, _2, right]) =>
expr({ exprType: "binary", binaryType: "subtract", left, right }) %}
| factor {% id %}
factor -> factor _ "*" _ unary
{% ([left, _0, _1, _2, right]) =>
expr({ exprType: "binary", binaryType: "multiply", left, right }) %}
| factor _ "/" _ unary
{% ([left, _0, _1, _2, right]) =>
expr({ exprType: "binary", binaryType: "divide", left, right }) %}
| unary {% id %}
unary -> "+" _ unary
{% ([_0, _1, subject]) =>
expr({ exprType: "unary", unaryType: "plus", subject }) %}
| "-" _ unary
{% ([_0, _1, subject]) =>
expr({ exprType: "unary", unaryType: "negate", subject }) %}
| operand {% id %}
operand -> int {% id %}
| group {% id %}
| block {% id %}
| if {% id %}
int -> [0-9]:+
{% ([token]) =>
expr({ exprType: "int", value: parseInt(token.join("")) }) %}
group -> "(" _ expr _ ")" {% v => expr(v[2]) %}
block -> "{" _ expr _ "}" {% v => expr({ exprType: "block", expr: v[2] }) %}
if -> "if" __ expr _ block _ "else" _ block
{%
v => expr({
exprType: "if",
condition: v[2],
truthy: v[4],
falsy: v[8]
})
%}
_ -> __:?
__ -> ws:+
ws -> [ \t\r\n]

28
src/ir.ts Normal file
View File

@ -0,0 +1,28 @@
export type Register = "acc" | "op";
export type Value = number;
export type Location = number;
export type Instruction = ({
type: "push" | "pop",
register: Register,
} | {
type: "load",
register: Register,
value: Value,
} | {
type: "add" | "mul" | "sub" | "div",
left: Register,
right: Register,
dest: Register,
} | {
type: "negate",
src: Register,
dest: Register,
} | {
type: "jump_zero",
register: Register,
location: Location,
} | {
type: "jump",
location: Location,
}) & ({ jumpedTo: true, label: number } | { jumpedTo?: false });

View File

@ -1,8 +1,8 @@
import { Parser, Grammar } from "nearley"
import compiledGrammar from "./grammar.out"
import { Compiler, Instruction, Register, Value, X86Generator as X8664Generator, locateAndSetJumpedToInstructions } from "./compiler";
import { Compiler, locateAndSetJumpedToInstructions } from "./compiler";
import fs from "fs/promises";
import { exec } from "child_process";
import { parse } from "./parser";
import { X8664Generator } from "./x86_64_generator";
function executeCommand(command: string) {
return new Promise<void>((resolve, reject) => {
@ -22,16 +22,13 @@ function executeCommand(command: string) {
}
async function main(args: string[]) {
const parser = new Parser(Grammar.fromCompiled(compiledGrammar));
const input = args[2];
if (input === null)
throw new Error("input fucked")
parser.feed(input);
const ast = parser.results[0];
const ast = parse(input).unwrap();
console.log(JSON.stringify(ast, null, 4))

20
src/parser.ts Normal file
View File

@ -0,0 +1,20 @@
import { Grammar, Parser } from "nearley";
import { Err, Ok, Result } from "ts-results";
import compiledGrammar from "./grammar.out"
import { Expr } from "./parsed";
export function parse(text: string): Result<Expr, string> {
const parser = new Parser(Grammar.fromCompiled(compiledGrammar));
try {
parser.feed(text);
} catch (parseError) {
console.log(parseError)
}
const result = parser.results?.at(0);
if (!result)
return Err("failed to parse");
if (parser.results.length > 1)
return Err("ambigous parse result");
return Ok(result);
}

12
src/utils.ts Normal file
View File

@ -0,0 +1,12 @@
export function assertExhaustive(matchedItem?: never): never {
throw new Error(`unexhaustive match, unmatched value: ${matchedItem}`);
}
export function toString(value: unknown): string {
const stringified = String(value);
if (stringified === "[object Object]")
return JSON.stringify(value, null, 4)
return stringified;
}

148
src/x86_64_generator.ts Normal file
View File

@ -0,0 +1,148 @@
import { Instruction, Register, Value } from "./ir";
import { assertExhaustive } from "./utils";
export class X8664Generator {
private result = "";
public generate(instructions: Instruction[]): string {
this.result = "";
for (const ins of instructions) {
this.generateInstruction(ins, instructions);
}
return this.asmWithHeaders(this.result);
}
private generateInstruction(ins: Instruction, instructions: Instruction[]) {
let result = "";
if (ins.jumpedTo)
result += `.L${ins.label}:\n`;
switch (ins.type) {
case "load":
this.add(`
; load
mov ${this.reg64(ins.register)}, ${this.value(ins.value)}
`);
break;
case "push":
this.add(`
; push
push ${this.reg64(ins.register)}
`);
case "pop":
this.add(`
; pop
pop ${this.reg64(ins.register)}
`);
break;
case "negate":
this.add(`
; neg
mov ${this.reg64(ins.dest)}, ${this.reg64(ins.src)}
neg ${this.reg64(ins.dest)}
`);
break;
case "add":
this.add(`
; add
mov ${this.reg64(ins.dest)}, ${this.reg64(ins.left)}
add ${this.reg64(ins.dest)}, ${this.reg64(ins.right)}
`);
break;
case "sub":
this.add(`
; sub
mov ${this.reg64(ins.dest)}, ${this.reg64(ins.left)}
sub ${this.reg64(ins.dest)}, ${this.reg64(ins.right)}
`);
break;
case "mul":
this.add(`
; mul
mov ${this.reg64(ins.dest)}, ${this.reg64(ins.left)}
imul ${this.reg64(ins.dest)}, ${this.reg64(ins.right)}
`);
break;
case "div":
this.add(`
; div
mov rdi, ${this.reg64(ins.right)}
mov rax, ${this.reg64(ins.left)}
xor rdx, rdx
cqo
idiv rdi
mov ${this.reg64(ins.dest)}, rax
`);
break;
case "jump_zero":
this.add(`
; jump_zero
cmp ${ins.register}
jz .L${(() => {
const dest = instructions[ins.location];
if (!dest.jumpedTo)
throw new Error("impossible");
return dest.label;
})()}
`);
break;
case "jump":
this.add(`
; jump
jz .L${(() => {
const dest = instructions[ins.location];
if (!dest.jumpedTo)
throw new Error("impossible");
return dest.label;
})()}
`);
break;
default:
assertExhaustive(ins);
}
return result;
}
private add(lines: string) {
this.result += lines
.trim()
.split("\n")
.map(s => s.replace(/^( )+/, " "))
.join("\n")
+ "\n";
}
private asmWithHeaders(asm: string) {
return `
bits 64
global _start
_start:
\t${asm}
exit:
\tmov rdi, rax
\tmov rax, 60
\tsyscall
`.replace(/ /g, "").replace(/\t/g, " ");
}
private reg64(reg: Register): string {
switch (reg) {
case "acc": return "rax";
case "op": return "rdx";
}
}
private reg32(reg: Register): string {
switch (reg) {
case "acc": return "eax";
case "op": return "edx";
}
}
private value(value: Value): string {
return value.toString();
}
}

View File

@ -1,89 +0,0 @@
Arguments:
/usr/bin/node /usr/bin/yarn add ts-done
PATH:
/home/pieter/.cargo/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/home/pieter/.cargo/bin:/home/pieter/.yarn/bin:/home/pieter/.cabal/bin:/home/pieter/.ghcup/bin
Yarn version:
1.22.19
Node version:
20.4.0
Platform:
linux x64
Trace:
Error: https://registry.yarnpkg.com/ts-done: Not found
at params.callback [as _callback] (/usr/lib/node_modules/yarn/lib/cli.js:66145:18)
at self.callback (/usr/lib/node_modules/yarn/lib/cli.js:140890:22)
at Request.emit (node:events:512:28)
at Request.<anonymous> (/usr/lib/node_modules/yarn/lib/cli.js:141862:10)
at Request.emit (node:events:512:28)
at IncomingMessage.<anonymous> (/usr/lib/node_modules/yarn/lib/cli.js:141784:12)
at Object.onceWrapper (node:events:626:28)
at IncomingMessage.emit (node:events:524:35)
at endReadableNT (node:internal/streams/readable:1378:12)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21)
npm manifest:
{
"scripts": {
"build": "nearleyc grammar.ne -o grammar.out.js"
},
"dependencies": {
"moo": "^0.5.2",
"nearley": "^2.20.1"
}
}
yarn manifest:
No manifest
Lockfile:
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
commander@^2.19.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
discontinuous-range@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a"
integrity sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==
moo@^0.5.0, moo@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.2.tgz#f9fe82473bc7c184b0d32e2215d3f6e67278733c"
integrity sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==
nearley@^2.20.1:
version "2.20.1"
resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.20.1.tgz#246cd33eff0d012faf197ff6774d7ac78acdd474"
integrity sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==
dependencies:
commander "^2.19.0"
moo "^0.5.0"
railroad-diagrams "^1.0.0"
randexp "0.4.6"
railroad-diagrams@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e"
integrity sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==
randexp@0.4.6:
version "0.4.6"
resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3"
integrity sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==
dependencies:
discontinuous-range "1.0.0"
ret "~0.1.10"
ret@~0.1.10:
version "0.1.15"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==

View File

@ -154,6 +154,11 @@ ts-node@^10.9.1:
v8-compile-cache-lib "^3.0.1"
yn "3.1.1"
ts-results@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/ts-results/-/ts-results-3.3.0.tgz#68623a6c18e65556287222dab76498a28154922f"
integrity sha512-FWqxGX2NHp5oCyaMd96o2y2uMQmSu8Dey6kvyuFdRJ2AzfmWo3kWa4UsPlCGlfQ/qu03m09ZZtppMoY8EMHuiA==
typescript@^5.1.6:
version "5.1.6"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274"