Add some codegen infrastructure

This commit is contained in:
Sam Vervaeck 2022-09-16 19:43:52 +02:00
parent 8200a0095f
commit f43a66f453
6 changed files with 559 additions and 12 deletions

View file

@ -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()

379
src/cast.ts Normal file
View file

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

82
src/codegen.ts Normal file
View file

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

View file

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

View file

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

61
src/program.ts Normal file
View file

@ -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<string, SourceFile>();
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<SourceFile> {
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));
}