From f43a66f453d902e546ce4d33c7e9280f6fdd9ab3 Mon Sep 17 00:00:00 2001 From: Sam Vervaeck Date: Fri, 16 Sep 2022 19:43:52 +0200 Subject: [PATCH] Add some codegen infrastructure --- src/bin/bolt.ts | 21 +-- src/cast.ts | 379 +++++++++++++++++++++++++++++++++++++++++++++ src/codegen.ts | 82 ++++++++++ src/cst.ts | 12 ++ src/diagnostics.ts | 16 +- src/program.ts | 61 ++++++++ 6 files changed, 559 insertions(+), 12 deletions(-) create mode 100644 src/cast.ts create mode 100644 src/codegen.ts create mode 100644 src/program.ts diff --git a/src/bin/bolt.ts b/src/bin/bolt.ts index f558eaa27..3de5fe8ab 100644 --- a/src/bin/bolt.ts +++ b/src/bin/bolt.ts @@ -14,6 +14,7 @@ import { Checker } from "../checker" import { SourceFile, TextFile } from "../cst" import { parseSourceFile } from ".." import { Analyser } from "../analysis" +import { Program } from "../program" function debug(value: any) { console.error(util.inspect(value, { colors: true, depth: Infinity })); @@ -37,18 +38,18 @@ yargs const filename = path.resolve(cwd, args.file); const diagnostics = new ConsoleDiagnostics(); - const text = fs.readFileSync(filename, 'utf8') - const file = new TextFile(filename, text); - const sourceFile = parseSourceFile(file, diagnostics); - if (sourceFile === null) { + const program = new Program([ filename ]); + if (program.diagnostics.hasError) { + process.exit(1); + } + program.check(); + if (program.diagnostics.hasError) { + process.exit(1); + } + program.emit(); + if (program.diagnostics.hasError) { process.exit(1); } - //debug(sourceFile.toJSON()); - const analyser = new Analyser(); - analyser.addSourceFile(sourceFile); - const checker = new Checker(analyser, diagnostics); - checker.check(sourceFile); - } ) .help() diff --git a/src/cast.ts b/src/cast.ts new file mode 100644 index 000000000..8546f505d --- /dev/null +++ b/src/cast.ts @@ -0,0 +1,379 @@ + +import type stream from "stream" +import { IndentWriter } from "./util"; + +export const enum CNodeKind { + + // Types + BuiltinType, + + // Statements + ExprStmt, + RetStmt, + + // Expressions + CallExpr, + RefExpr, + ConstExpr, + + // Declarations + TypeDecl, + VarDecl, + FuncDecl, + + // Directives + IncDir, + + // Other nodes + Program, + +} + +export const enum CBuiltinTypeKind { + Char, + Short, + Int, + Long, + LongLong, + UnsignedChar, + UnsignedShort, + UnsignedInt, + UnsignedLong, + UnsignedLongLong, +} + +abstract class CNodeBase { + public abstract readonly kind: CNodeKind; +} + +export class CBuiltinType extends CNodeBase { + + public readonly kind = CNodeKind.BuiltinType; + + public constructor( + public typeKind: CBuiltinTypeKind, + ) { + super(); + } + +} + +export type CType + = CBuiltinType + +export class CRefExpr extends CNodeBase { + + public readonly kind = CNodeKind.RefExpr; + + public constructor( + public name: string + ) { + super(); + } + +} + +export class CCallExpr extends CNodeBase { + + public readonly kind = CNodeKind.CallExpr; + + public constructor( + public operator: CExpr, + public args: CExpr[], + ) { + super(); + } + +} + +export class CConstExpr extends CNodeBase { + + public readonly kind = CNodeKind.ConstExpr; + + public constructor( + public value: bigint | string | boolean, + ) { + super(); + } + +} + +export type CExpr + = CRefExpr + | CCallExpr + | CConstExpr + ; + +export class CRetStmt extends CNodeBase { + + public readonly kind = CNodeKind.RetStmt; + + public constructor( + public value: CExpr | null, + ) { + super(); + } + +} + +export class CExprStmt extends CNodeBase { + + public readonly kind = CNodeKind.ExprStmt; + + public constructor( + public expr: CExpr, + ) { + super(); + } + +} + +export type CStmt + = CExprStmt + | CRetStmt; + +export class CTypeDecl extends CNodeBase { + + public readonly kind = CNodeKind.TypeDecl; + + public constructor( + public name: string, + public type: CType, + ) { + super(); + } + +} + +export class CFuncDecl extends CNodeBase { + + public readonly kind = CNodeKind.FuncDecl; + + public constructor( + public returnType: CType, + public name: string, + public params: Array<[CType, string]>, + public body: CStmt[] | null, + ) { + super(); + } + +} + +export class CVarDecl extends CNodeBase { + + public readonly kind = CNodeKind.VarDecl; + + public constructor( + public isExtern: boolean, + public type: CType, + public name: string, + ) { + super(); + } + +} + +export type CDecl + = CTypeDecl + | CVarDecl + | CFuncDecl + +export class CIncDir extends CNodeBase { + + public readonly kind = CNodeKind.IncDir; + + public constructor( + public filePath: string, + public isSystem = false, + ) { + super(); + } + +} + +export type CDir + = CIncDir; + +export class CProgram extends CNodeBase { + + public readonly kind = CNodeKind.Program; + + public constructor( + public elements: (CDecl | CDir)[], + ) { + super(); + } +} + +export type CNode + = CDecl + | CDir + | CStmt + | CExpr + | CType + | CProgram + +export class CEmitter { + + private writer: IndentWriter; + + public constructor( + public stream: stream.Writable, + ) { + this.writer = new IndentWriter(stream); + } + + public emit(node: CNode): void { + + switch (node.kind) { + + case CNodeKind.Program: + { + for (const element of node.elements) { + this.emit(element); + } + break; + } + + case CNodeKind.IncDir: + { + this.writer.write('#include '); + this.writer.write(node.isSystem ? '<' : '"'); + this.writer.write(node.filePath); + this.writer.write(node.isSystem ? '>' : '"'); + this.writer.write('\n\n'); + break; + } + + case CNodeKind.BuiltinType: + { + switch (node.typeKind) { + case CBuiltinTypeKind.Char: + this.writer.write('char'); + break; + case CBuiltinTypeKind.Short: + this.writer.write('short'); + break; + case CBuiltinTypeKind.Int: + this.writer.write('int'); + break; + case CBuiltinTypeKind.Long: + this.writer.write('long'); + break; + case CBuiltinTypeKind.LongLong: + this.writer.write('long long'); + break; + case CBuiltinTypeKind.UnsignedChar: + this.writer.write('unsigned char'); + break; + case CBuiltinTypeKind.UnsignedShort: + this.writer.write('unsigned short'); + break; + case CBuiltinTypeKind.UnsignedInt: + this.writer.write('unsigned int'); + break; + case CBuiltinTypeKind.UnsignedLong: + this.writer.write('unsigned long'); + break; + case CBuiltinTypeKind.UnsignedLongLong: + this.writer.write('unsigned long long'); + break; + } + break; + } + + case CNodeKind.FuncDecl: + { + this.emit(node.returnType); + this.writer.write(' ' + node.name + '('); + let count = 0; + for (const [type, name] of node.params) { + this.emit(type); + this.writer.write(' ' + name); + if (count++ > 0) { + this.writer.write(', '); + } + } + this.writer.write(') {\n'); + this.writer.indent(); + if (node.body !== null) { + for (const element of node.body) { + this.emit(element); + } + } + this.writer.dedent(); + this.writer.write('}\n\n'); + break; + } + + case CNodeKind.ExprStmt: + this.emit(node.expr); + this.writer.write(';\n'); + break; + + case CNodeKind.RetStmt: + { + this.writer.write('return'); + if (node.value !== null) { + this.writer.write(' '); + this.emit(node.value); + } + this.writer.write(';\n'); + break; + } + + case CNodeKind.RefExpr: + this.writer.write(node.name); + break; + + case CNodeKind.CallExpr: + { + this.emit(node.operator); + this.writer.write('('); + let count = 0; + for (const arg of node.args) { + this.emit(arg); + if (count++ > 0) { + this.writer.write(', '); + } + } + this.writer.write(')'); + break; + } + + case CNodeKind.ConstExpr: + { + if (typeof(node.value) === 'string') { + this.writer.write('"'); + for (const ch of node.value) { + switch (ch) { + case '\b': this.writer.write('\\b'); break; + case '\f': this.writer.write('\\f'); break; + case '\n': this.writer.write('\\n'); break; + case '\r': this.writer.write('\\r'); break; + case '\t': this.writer.write('\\t'); break; + case '\v': this.writer.write('\\v'); break; + case '\0': this.writer.write('\\0'); break; + case '\'': this.writer.write('\\\''); break; + case '"': this.writer.write('\\"'); break; + default: this.writer.write(ch); break; + } + } + this.writer.write('"'); + } else if (typeof(node.value) === 'bigint') { + this.writer.write(node.value.toString()); + } else { + throw new Error(`Unexpected type of value in CConstExpr`); + } + break; + } + + default: + throw new Error(`Unexpected ${node.constructor.name}`); + + } + + } + +} diff --git a/src/codegen.ts b/src/codegen.ts new file mode 100644 index 000000000..12292f34c --- /dev/null +++ b/src/codegen.ts @@ -0,0 +1,82 @@ + +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/cst.ts b/src/cst.ts index eb3bf3403..e6fbaef6e 100644 --- a/src/cst.ts +++ b/src/cst.ts @@ -3,6 +3,10 @@ import type { InferContext, Kind, Scheme, Type, TypeEnv } from "./checker" export type TextSpan = [number, number]; +export type Value + = bigint + | string + export class TextPosition { public constructor( @@ -574,6 +578,10 @@ export class Integer extends TokenBase { super(startPos); } + public getValue(): Value { + return this.value; + } + public get text(): string { switch (this.radix) { case 16: @@ -602,6 +610,10 @@ export class StringLiteral extends TokenBase { super(startPos); } + public getValue(): Value { + return this.contents; + } + public get text(): string { let out = '"'; for (const ch of this.contents) { diff --git a/src/diagnostics.ts b/src/diagnostics.ts index faaad0643..e5a827586 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -359,6 +359,8 @@ export type Diagnostic | KindMismatchDiagnostic export interface Diagnostics { + readonly hasError: boolean; + readonly hasFatal: boolean; add(diagnostic: Diagnostic): void; } @@ -386,10 +388,20 @@ export class DiagnosticStore { } export class ConsoleDiagnostics { + + private writer = new IndentWriter(process.stderr); + + public hasError = false; + public hasFatal = false; public add(diagnostic: Diagnostic): void { - const writer = new IndentWriter(process.stderr); - diagnostic.format(writer); + diagnostic.format(this.writer); + if (diagnostic.level >= Level.Error) { + this.hasError = true; + } + if (diagnostic.level >= Level.Fatal) { + this.hasFatal = true; + } } } diff --git a/src/program.ts b/src/program.ts new file mode 100644 index 000000000..072b8bd31 --- /dev/null +++ b/src/program.ts @@ -0,0 +1,61 @@ +import path from "path"; +import fs from "fs" +import { parseSourceFile } from "."; +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"; + +export class Program { + + private sourceFilesByPath = new Map(); + + private analyser = new Analyser(); + + public constructor( + public fileNames: string[], + public diagnostics: Diagnostics = new ConsoleDiagnostics(), + ) { + for (const fileName of fileNames) { + const realPath = path.resolve(fileName); + const text = fs.readFileSync(realPath, 'utf-8'); + const file = new TextFile(fileName, text); + const sourceFile = parseSourceFile(file, diagnostics); + if (sourceFile !== null) { + this.sourceFilesByPath.set(realPath, sourceFile); + this.analyser.addSourceFile(sourceFile); + } + } + } + + public getSourceFiles(): Iterable { + return this.sourceFilesByPath.values(); + } + + public check(): void { + const checker = new Checker(this.analyser, this.diagnostics); + for (const sourceFile of this.getSourceFiles()) { + checker.check(sourceFile); + } + } + + public emit(): void { + 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)); + } + } + +} + +function stripExtension(filepath: string): string { + const basename = path.basename(filepath); + const i = basename.lastIndexOf('.'); + if (i === -1) { + return filepath; + } + return path.join(path.dirname(filepath), basename.substring(0, i)); +}