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 { 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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { 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
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