387 lines
7.3 KiB
TypeScript
387 lines
7.3 KiB
TypeScript
|
|
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;
|
|
|
|
public emit(file: stream.Writable): void {
|
|
const emitter = new CEmitter(file);
|
|
emitter.emit(this as any);
|
|
}
|
|
|
|
}
|
|
|
|
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}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|