UNPKG

jaycc

Version:

### the Javascript C Compiler

267 lines (219 loc) 6.49 kB
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'); }