Refactor codegen into interchangeable passes
This commit is contained in:
parent
404ac83101
commit
6af1fabb88
8 changed files with 359 additions and 91 deletions
|
@ -14,7 +14,7 @@ import { Checker } from "../checker"
|
|||
import { SourceFile, TextFile } from "../cst"
|
||||
import { parseSourceFile } from ".."
|
||||
import { Analyser } from "../analysis"
|
||||
import { Program } from "../program"
|
||||
import { Program, TargetType } from "../program"
|
||||
|
||||
function debug(value: any) {
|
||||
console.error(util.inspect(value, { colors: true, depth: Infinity }));
|
||||
|
@ -37,7 +37,6 @@ yargs
|
|||
const cwd = args.C;
|
||||
const filename = path.resolve(cwd, args.file);
|
||||
|
||||
const diagnostics = new ConsoleDiagnostics();
|
||||
const program = new Program([ filename ]);
|
||||
if (program.diagnostics.hasError) {
|
||||
process.exit(1);
|
||||
|
@ -46,7 +45,7 @@ yargs
|
|||
if (program.diagnostics.hasError) {
|
||||
process.exit(1);
|
||||
}
|
||||
program.emit();
|
||||
program.emit({ type: TargetType.JS });
|
||||
if (program.diagnostics.hasError) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
|
@ -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
196
src/js.ts
Normal 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
90
src/passes/BoltToC.ts
Normal 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
14
src/passes/BoltToJS.ts
Normal 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;
|
||||
|
|
@ -5,8 +5,39 @@ import { SourceFile, TextFile } from "./cst";
|
|||
import { ConsoleDiagnostics, Diagnostics } from "./diagnostics";
|
||||
import { Checker } from "./checker";
|
||||
import { Analyser } from "./analysis";
|
||||
import { generateCode } from "./codegen";
|
||||
import { CEmitter } from "./cast";
|
||||
import { Newable, Pass } from "./types";
|
||||
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 {
|
||||
|
||||
|
@ -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) {
|
||||
const file = fs.createWriteStream(stripExtension(filePath) + '.c', 'utf-8');
|
||||
const emitter = new CEmitter(file);
|
||||
emitter.emit(generateCode(sourceFile));
|
||||
const code = passes.apply(sourceFile) as any;
|
||||
const file = fs.createWriteStream(stripExtension(filePath) + suffix, 'utf-8');
|
||||
code.emit(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
8
src/types.ts
Normal file
8
src/types.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
export interface Pass<In, Out> {
|
||||
apply(input: In): Out;
|
||||
}
|
||||
|
||||
export interface Newable<T> {
|
||||
new (...args: any[]): T;
|
||||
}
|
Loading…
Reference in a new issue