Add some codegen infrastructure
This commit is contained in:
parent
8200a0095f
commit
f43a66f453
6 changed files with 559 additions and 12 deletions
|
@ -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
379
src/cast.ts
Normal 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
82
src/codegen.ts
Normal 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}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
12
src/cst.ts
12
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) {
|
||||
|
|
|
@ -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
61
src/program.ts
Normal 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));
|
||||
}
|
Loading…
Reference in a new issue