Refactor codegen into interchangeable passes

This commit is contained in:
Sam Vervaeck 2022-09-18 21:37:24 +02:00
parent 404ac83101
commit 6af1fabb88
8 changed files with 359 additions and 91 deletions

View file

@ -14,7 +14,7 @@ import { Checker } from "../checker"
import { SourceFile, TextFile } from "../cst" import { SourceFile, TextFile } from "../cst"
import { parseSourceFile } from ".." import { parseSourceFile } from ".."
import { Analyser } from "../analysis" import { Analyser } from "../analysis"
import { Program } from "../program" import { Program, TargetType } from "../program"
function debug(value: any) { function debug(value: any) {
console.error(util.inspect(value, { colors: true, depth: Infinity })); console.error(util.inspect(value, { colors: true, depth: Infinity }));
@ -37,7 +37,6 @@ yargs
const cwd = args.C; const cwd = args.C;
const filename = path.resolve(cwd, args.file); const filename = path.resolve(cwd, args.file);
const diagnostics = new ConsoleDiagnostics();
const program = new Program([ filename ]); const program = new Program([ filename ]);
if (program.diagnostics.hasError) { if (program.diagnostics.hasError) {
process.exit(1); process.exit(1);
@ -46,7 +45,7 @@ yargs
if (program.diagnostics.hasError) { if (program.diagnostics.hasError) {
process.exit(1); process.exit(1);
} }
program.emit(); program.emit({ type: TargetType.JS });
if (program.diagnostics.hasError) { if (program.diagnostics.hasError) {
process.exit(1); process.exit(1);
} }

View file

@ -1,82 +0,0 @@
import { CBuiltinType, CBuiltinTypeKind, CCallExpr, CConstExpr, CDecl, CDir, CExpr, CExprStmt, CFuncDecl, CIncDir, CProgram, CRefExpr, CStmt } from "./cast";
import { Expression, SourceFile, Syntax, SyntaxKind } from "./cst";
import { assert } from "./util";
interface Context {
body: CStmt[];
}
export function generateCode(sourceFile: SourceFile): CProgram {
const intType = new CBuiltinType(CBuiltinTypeKind.Int);
const decls: (CDecl | CDir)[] = [];
decls.push(new CIncDir("runtime.h"));
const mainBody: CStmt[] = [];
decls.push(
new CFuncDecl(
intType,
'main',
[],
mainBody
)
);
visit(sourceFile, { body: mainBody });
return new CProgram(decls);
function visit(node: Syntax, context: Context): void {
switch (node.kind) {
case SyntaxKind.SourceFile:
{
for (const element of node.elements) {
visit(element, context);
}
break;
}
case SyntaxKind.ExpressionStatement:
{
context.body.push(
new CExprStmt(
visitExpression(node.expression, context)
)
);
break;
}
case SyntaxKind.LetDeclaration:
{
// TODO
break;
}
}
}
function visitExpression(node: Expression, context: Context): CExpr {
switch (node.kind) {
case SyntaxKind.ReferenceExpression:
assert(node.modulePath.length === 0);
return new CRefExpr(node.name.text);
case SyntaxKind.CallExpression:
const operator = visitExpression(node.func, context);
const args = node.args.map(arg => visitExpression(arg, context));
return new CCallExpr(operator, args);
case SyntaxKind.ConstantExpression:
return new CConstExpr(node.token.getValue());
default:
throw new Error(`Unexpected ${node}`);
}
}
}

196
src/js.ts Normal file
View file

@ -0,0 +1,196 @@
import type stream from "stream"
import { IndentWriter } from "./util";
export const enum JSNodeKind {
// Patterns
BindPattern,
ArrayPattern,
ObjectPattern,
// Expressions
ReferenceExpression,
CallExpression,
FunctionExpression,
MemberExpression,
IndexExpression,
// Statements
ExpressionStatement,
ReturnStatement,
// Declarations
FunctionDeclaration,
VariableDeclaration,
// Other nodes
Program,
}
abstract class JSNodeBase {
public abstract readonly kind: JSNodeKind;
public emit(out: stream.Writable): void {
const emitter = new JSEmitter(out);
emitter.emit(this as unknown as JSNode);
}
}
export class JSBindPattern extends JSNodeBase {
public readonly kind = JSNodeKind.BindPattern;
public constructor(
public name: string,
) {
super();
}
}
export type JSPattern
= JSBindPattern
export class JSReferenceExpression extends JSNodeBase {
public kind = JSNodeKind.ReferenceExpression;
public constructor(
public name: string,
) {
super();
}
}
export class JSCallExpression extends JSNodeBase {
public readonly kind = JSNodeKind.CallExpression;
public constructor(
public operator: JSExpression,
public args: JSExpression[],
) {
super();
}
}
export type JSExpression
= JSReferenceExpression
| JSCallExpression
export class JSExpressionStatement extends JSNodeBase {
public readonly kind = JSNodeKind.ExpressionStatement;
public constructor(
public expression: JSExpression,
) {
super();
}
}
export class JSReturnStatement extends JSNodeBase {
public readonly kind = JSNodeKind.ReturnStatement;
public constructor(
public value: JSExpression | null,
) {
super();
}
}
export type JSStatement
= JSExpressionStatement
| JSReturnStatement
export const enum JSDeclarationFlags {
IsExported = 1,
}
export type JSFunctionElement
= JSDeclaration
| JSStatement
export class JSFunctionDeclaration extends JSNodeBase {
public readonly kind = JSNodeKind.FunctionDeclaration;
public constructor(
public flags: JSDeclarationFlags,
public name: string,
public params: JSPattern[],
public body: JSFunctionElement[],
) {
super();
}
}
export enum JSVarType {
Var,
Const,
Let,
}
export class JSVariableDeclaration extends JSNodeBase {
public readonly kind = JSNodeKind.VariableDeclaration;
public constructor(
public flags: JSDeclarationFlags,
public varType: JSVarType,
public pattern: JSPattern,
public value: JSExpression | null,
) {
super();
}
}
export type JSDeclaration
= JSFunctionDeclaration
| JSVariableDeclaration
export type JSSourceElement
= JSStatement
| JSDeclaration
export class JSProgram extends JSNodeBase {
public readonly kind = JSNodeKind.Program;
public constructor(
public elements: JSSourceElement[],
) {
super();
}
}
export type JSNode
= JSStatement
| JSDeclaration
| JSExpression
| JSPattern
| JSProgram
export class JSEmitter {
private writer: IndentWriter;
public constructor(out: stream.Writable) {
this.writer = new IndentWriter(out);
}
public emit(node: JSNode) {
}
}

90
src/passes/BoltToC.ts Normal file
View file

@ -0,0 +1,90 @@
import { CBuiltinType, CBuiltinTypeKind, CCallExpr, CConstExpr, CDecl, CDir, CExpr, CExprStmt, CFuncDecl, CIncDir, CNode, CProgram, CRefExpr, CStmt } from "../c";
import { Expression, Syntax, SyntaxKind } from "../cst";
import { Pass } from "../types";
import { assert } from "../util";
interface Context {
body: CStmt[];
}
class BoltToC implements Pass<Syntax, CNode> {
public apply(input: Syntax): CNode {
assert(input.kind === SyntaxKind.SourceFile);
const intType = new CBuiltinType(CBuiltinTypeKind.Int);
const decls: (CDecl | CDir)[] = [];
decls.push(new CIncDir("runtime.h"));
const mainBody: CStmt[] = [];
decls.push(
new CFuncDecl(
intType,
'main',
[],
mainBody
)
);
visit(input, { body: mainBody });
return new CProgram(decls);
function visit(node: Syntax, context: Context): void {
switch (node.kind) {
case SyntaxKind.SourceFile:
{
for (const element of node.elements) {
visit(element, context);
}
break;
}
case SyntaxKind.ExpressionStatement:
{
context.body.push(
new CExprStmt(
visitExpression(node.expression, context)
)
);
break;
}
case SyntaxKind.LetDeclaration:
{
// TODO
break;
}
}
}
function visitExpression(node: Expression, context: Context): CExpr {
switch (node.kind) {
case SyntaxKind.ReferenceExpression:
assert(node.modulePath.length === 0);
return new CRefExpr(node.name.text);
case SyntaxKind.CallExpression:
const operator = visitExpression(node.func, context);
const args = node.args.map(arg => visitExpression(arg, context));
return new CCallExpr(operator, args);
case SyntaxKind.ConstantExpression:
return new CConstExpr(node.token.getValue());
default:
throw new Error(`Unexpected ${node}`);
}
}
}
}
export default BoltToC;

14
src/passes/BoltToJS.ts Normal file
View file

@ -0,0 +1,14 @@
import { Syntax } from "../cst";
import { JSNode, JSProgram } from "../js";
import { Pass } from "../types";
export class BoltToJS implements Pass<Syntax, JSNode> {
public apply(input: Syntax): JSNode {
return new JSProgram([]);
}
}
export default BoltToJS;

View file

@ -5,8 +5,39 @@ import { SourceFile, TextFile } from "./cst";
import { ConsoleDiagnostics, Diagnostics } from "./diagnostics"; import { ConsoleDiagnostics, Diagnostics } from "./diagnostics";
import { Checker } from "./checker"; import { Checker } from "./checker";
import { Analyser } from "./analysis"; import { Analyser } from "./analysis";
import { generateCode } from "./codegen"; import { Newable, Pass } from "./types";
import { CEmitter } from "./cast"; import BoltToC from "./passes/BoltToC";
import BoltToJS from "./passes/BoltToJS";
type AnyPass = Pass<any, any>;
export enum TargetType {
C,
JS,
WebAssembly,
LLVM,
}
interface TargetSpec {
type: TargetType;
}
export class PassManager {
private registeredPasses: AnyPass[] = [];
public add(pass: Newable<AnyPass>) {
this.registeredPasses.push(new pass());
}
public apply<In>(input: In): unknown {
for (const pass of this.registeredPasses) {
input = pass.apply(input);
}
return input;
}
}
export class Program { export class Program {
@ -41,11 +72,23 @@ export class Program {
} }
} }
public emit(): void { public emit(target: TargetSpec): void {
let suffix;
const passes = new PassManager();
switch (target.type) {
case TargetType.C:
suffix = '.c';
passes.add(BoltToC);
break;
case TargetType.JS:
suffix = '.js'
passes.add(BoltToJS);
break;
}
for (const [filePath, sourceFile] of this.sourceFilesByPath) { for (const [filePath, sourceFile] of this.sourceFilesByPath) {
const file = fs.createWriteStream(stripExtension(filePath) + '.c', 'utf-8'); const code = passes.apply(sourceFile) as any;
const emitter = new CEmitter(file); const file = fs.createWriteStream(stripExtension(filePath) + suffix, 'utf-8');
emitter.emit(generateCode(sourceFile)); code.emit(file);
} }
} }

8
src/types.ts Normal file
View file

@ -0,0 +1,8 @@
export interface Pass<In, Out> {
apply(input: In): Out;
}
export interface Newable<T> {
new (...args: any[]): T;
}