From 6af1fabb88142d04bee223d9436c9ca3e2c245fb Mon Sep 17 00:00:00 2001 From: Sam Vervaeck Date: Sun, 18 Sep 2022 21:37:24 +0200 Subject: [PATCH] Refactor codegen into interchangeable passes --- src/bin/bolt.ts | 5 +- src/{cast.ts => c.ts} | 0 src/codegen.ts | 82 ----------------- src/js.ts | 196 +++++++++++++++++++++++++++++++++++++++++ src/passes/BoltToC.ts | 90 +++++++++++++++++++ src/passes/BoltToJS.ts | 14 +++ src/program.ts | 55 ++++++++++-- src/types.ts | 8 ++ 8 files changed, 359 insertions(+), 91 deletions(-) rename src/{cast.ts => c.ts} (100%) delete mode 100644 src/codegen.ts create mode 100644 src/js.ts create mode 100644 src/passes/BoltToC.ts create mode 100644 src/passes/BoltToJS.ts create mode 100644 src/types.ts diff --git a/src/bin/bolt.ts b/src/bin/bolt.ts index 3de5fe8ab..108d81190 100644 --- a/src/bin/bolt.ts +++ b/src/bin/bolt.ts @@ -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); } diff --git a/src/cast.ts b/src/c.ts similarity index 100% rename from src/cast.ts rename to src/c.ts diff --git a/src/codegen.ts b/src/codegen.ts deleted file mode 100644 index 12292f34c..000000000 --- a/src/codegen.ts +++ /dev/null @@ -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}`); - } - } - -} - diff --git a/src/js.ts b/src/js.ts new file mode 100644 index 000000000..d700c5946 --- /dev/null +++ b/src/js.ts @@ -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) { + } + +} diff --git a/src/passes/BoltToC.ts b/src/passes/BoltToC.ts new file mode 100644 index 000000000..a3239f607 --- /dev/null +++ b/src/passes/BoltToC.ts @@ -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 { + + 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; diff --git a/src/passes/BoltToJS.ts b/src/passes/BoltToJS.ts new file mode 100644 index 000000000..8b6d7be47 --- /dev/null +++ b/src/passes/BoltToJS.ts @@ -0,0 +1,14 @@ +import { Syntax } from "../cst"; +import { JSNode, JSProgram } from "../js"; +import { Pass } from "../types"; + +export class BoltToJS implements Pass { + + public apply(input: Syntax): JSNode { + return new JSProgram([]); + } + +} + +export default BoltToJS; + diff --git a/src/program.ts b/src/program.ts index 072b8bd31..e47bbdc9f 100644 --- a/src/program.ts +++ b/src/program.ts @@ -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; + +export enum TargetType { + C, + JS, + WebAssembly, + LLVM, +} + +interface TargetSpec { + type: TargetType; +} + +export class PassManager { + + private registeredPasses: AnyPass[] = []; + + public add(pass: Newable) { + this.registeredPasses.push(new pass()); + } + + public apply(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); } } diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..5fd35e6cb --- /dev/null +++ b/src/types.ts @@ -0,0 +1,8 @@ + +export interface Pass { + apply(input: In): Out; +} + +export interface Newable { + new (...args: any[]): T; +}