jaycc
Version:
### the Javascript C Compiler
267 lines (219 loc) • 6.49 kB
text/typescript
let Version:string;
Version = '1.0.0';
import type { AST } from './ast.js';
import { FileLexer, type Token } from './lexer.js';
import { Parser } from './parser.js';
import { architecture, CodeGen, state } from './codegen.js';
import type { State } from './codegen.js';
import { open, readFile, writeFile } from 'node:fs/promises';
import type { FileHandle } from 'node:fs/promises';
import { execFileSync } from 'node:child_process';
interface Config {
env: string;
asm: string;
linker: string;
arch: architecture;
stopat: Stage;
}
enum Stage {
Linker,
Assembler,
CodeGenerator
}
let config:(arch:architecture)=>Config;
config = (arch:architecture): Config => ({
env: '/usr/bin/env',
asm: 'nasm',
linker: 'ld',
arch,
stopat: Stage.Linker
});
interface Icompiler {
constructor:Function;
lex:()=>FileLexer;
parse:()=>AST;
codegen:()=>string;
link:(asmfile:string)=>void;
compile:()=>Promise<boolean>;
replaceext:(file:string,ext:string)=>string;
inputfile:string;
outputfile:string;
ast?:AST;
tokens?:Token[];
parser?:Parser;
state:State;
lexer?:FileLexer;
sourcefile?:string;
code?:CodeGen;
asm?:string;
config:Config;
}
class Compiler implements Icompiler {
public inputfile:string;
public outputfile:string;
public tokens?:Token[];
public ast?:AST;
public parser?:Parser;
public state:State;
public lexer?:FileLexer;
public sourcefile?:string;
public code?:CodeGen;
public asm?:string;
public config:Config;
public constructor(inputfile:string, cfg:Config, outputfile?:string) {
this.state = state();
this.inputfile = inputfile;
this.outputfile = outputfile
?? this.replaceext(this.inputfile, '');
this.config = cfg;
}
public replaceext(file:string, ext:string): string {
let regexp:RegExp;
let ret:string;
regexp = /\..+$/;
ret = file
.replace(regexp, ext);
return ret;
}
public async compile(): Promise<boolean> {
let fd:FileHandle;
let asmfile:string;
let ret:undefined|void;
fd = await open(this.inputfile, 'r');
if (!fd) {
throw new Error('Unable to open file: ' + this.inputfile);
return false as never;
}
this.sourcefile = await readFile(fd, {encoding:'utf8',flag:'r'});
fd.close();
if (!this.sourcefile) {
throw new Error('Unable to read input file');
return false as never;
}
this.lexer = this.lex();
this.tokens = this.lexer.tokens;
this.ast = this.parse();
this.asm = this.codegen();
asmfile = this.replaceext(this.inputfile, '.asm');
fd = await open(asmfile, 'w');
if (!fd) {
throw new Error('Unable to open file: ' + asmfile);
return false as never;
}
ret = await writeFile(fd, this.asm, {encoding:'utf8',mode:0o644,flag:'w'});
if (ret !== undefined) {
throw new Error('Unable to write to file: ' + asmfile);
return false as never;
}
fd.close();
if (this.config.stopat == Stage.CodeGenerator)
return true;
else
await this.link(asmfile);
return true;
}
public lex(): FileLexer {
return new FileLexer(this.sourcefile??'');
}
public parse(): AST {
this.parser = new Parser(this.tokens??[]);
return this.parser.parse();
}
public codegen(): string {
this.code = new CodeGen(this.state, this.ast, this.config.arch);
return this.code.emit;
}
public link(asmfile:string): void {
let objectfile:string;
try {
execFileSync(this.config.env,
[this.config.asm, '-f', 'elf', asmfile]
);
} catch(err:unknown) {
if (err && err instanceof Error) {
throw err;
return void 0 as never;
}
}
if (this.config.stopat == Stage.Assembler)
return void 0;
objectfile = this.replaceext(this.inputfile, '.o');
try {
execFileSync(this.config.env,
[this.config.linker, objectfile, '-o', this.outputfile]
);
} catch (err:unknown) {
if (err && err instanceof Error) {
throw err;
return void 0 as never;
}
}
}
}
let configuration:Config;
let comp:Compiler;
let retval:boolean;
let basename:(file:string)=>string;
let args:string[];
let argc:number;
let dashs:boolean;
let dashc:boolean;
let dasho:string;
let input:string;
let usage:()=>never;
let shver:()=>never;
shver = (): never => {
console.log(`jcc version ${Version} (the javascript c compiler)`);
process.exit(0);
return true as never;
}
usage = (): never => {
console.warn('Usage: jcc [-v] inputfile.c -o outputfile [ -s | -c ]');
process.exit(1);
return false as never;
}
basename = (file:string): string => {
let x:string;
let regexp:RegExp;
regexp = /[^/]+$/;
x = file.replace(regexp, '');
return file
.replace(x, '');
}
args = process.argv;
if (basename(args[0]) == 'node')
args.shift();
if (!args.length)
usage();
else
args.shift();
argc = args.length;
if ((argc) && (args[0] == '-v'))
shver();
if ((argc < 3) || (argc > 4))
usage();
if (argc == 4) {
dashs = (args[3] == '-s') ? true : false;
dashc = (args[3] == '-c') ? true : false;
}
else
dashs = dashc = false;
if (args[1] != '-o')
usage();
input = args[0];
dasho = args[2];
configuration = config(architecture.ia32);
if (dashs)
configuration.stopat = Stage.CodeGenerator;
else if (dashc)
configuration.stopat = Stage.Assembler;
retval=false;
try {
comp = new Compiler(input, configuration, dasho);
retval = await comp.compile();
} catch(err:unknown) {
if (err && (typeof err === 'object') && (err instanceof Error))
console.error(err);
else
console.error('Compilation failed');
}