2021-12-31 21:38:26 +00:00
|
|
|
import dbg from "debug";
|
|
|
|
import * as FS from "fs";
|
|
|
|
import Color from "chalk";
|
|
|
|
import * as Path from "path";
|
|
|
|
import tokenize, { TokenizerError } from "./tokenizer";
|
|
|
|
import parse, { Parsed, ParserError } from "./parser";
|
2022-01-01 18:47:34 +00:00
|
|
|
import get_ir, { IR, IRError } from "./ir";
|
2021-12-31 21:38:26 +00:00
|
|
|
import compile, { CompileTarget } from "./compile";
|
2022-01-05 21:16:17 +00:00
|
|
|
import {
|
|
|
|
ESMTypescriptTarget,
|
|
|
|
NodeJSTypescriptTarget,
|
|
|
|
} from "./targets/typescript";
|
|
|
|
import { CSharpTarget } from "./targets/csharp";
|
2022-07-16 21:22:16 +00:00
|
|
|
import { RustTarget } from "./targets/rust";
|
2021-12-31 21:38:26 +00:00
|
|
|
|
2022-01-01 18:47:34 +00:00
|
|
|
class CatchedError extends Error {}
|
|
|
|
|
2021-12-31 21:38:26 +00:00
|
|
|
const log = dbg("app");
|
|
|
|
|
2022-01-07 08:45:08 +00:00
|
|
|
export const Targets = new Map<string, typeof CompileTarget>();
|
2021-12-31 21:38:26 +00:00
|
|
|
|
2022-01-05 21:16:17 +00:00
|
|
|
Targets.set("ts-esm", ESMTypescriptTarget);
|
|
|
|
Targets.set("ts-node", NodeJSTypescriptTarget);
|
|
|
|
Targets.set("c#", CSharpTarget as typeof CompileTarget);
|
2022-07-16 21:22:16 +00:00
|
|
|
Targets.set("rust", RustTarget as typeof CompileTarget);
|
2021-12-31 21:38:26 +00:00
|
|
|
|
|
|
|
function indexToLineAndCol(src: string, index: number) {
|
|
|
|
let line = 1;
|
|
|
|
let col = 1;
|
|
|
|
for (let i = 0; i < index; i++) {
|
|
|
|
if (src.charAt(i) === "\n") {
|
|
|
|
line++;
|
|
|
|
col = 1;
|
|
|
|
} else {
|
|
|
|
col++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return { line, col };
|
|
|
|
}
|
|
|
|
|
|
|
|
const fileCache = new Map<string, string>();
|
|
|
|
function getFile(name: string) {
|
|
|
|
if (fileCache.has(name)) return fileCache.get(name);
|
|
|
|
else {
|
|
|
|
try {
|
|
|
|
const data = FS.readFileSync(name, "utf-8");
|
|
|
|
fileCache.set(name, data);
|
|
|
|
return data;
|
|
|
|
} catch (err) {
|
|
|
|
printError(new Error(`Cannot open file ${name};`), null, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
function printError(err: Error, file: string | null, idx: number) {
|
|
|
|
let loc = { line: 0, col: 0 };
|
|
|
|
if (file != null) {
|
|
|
|
const data = getFile(file);
|
|
|
|
if (data) loc = indexToLineAndCol(data, idx);
|
|
|
|
}
|
|
|
|
|
|
|
|
console.error(`${Color.red("ERROR: at")} ${file}:${loc.line}:${loc.col}`);
|
|
|
|
console.error(" ", err.message);
|
|
|
|
log(err.stack);
|
|
|
|
}
|
|
|
|
|
|
|
|
export type Target = {
|
|
|
|
type: string;
|
|
|
|
output: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type CompileOptions = {
|
|
|
|
input: string;
|
|
|
|
targets: Target[];
|
|
|
|
emitDefinitions?: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
type ProcessContext = {
|
|
|
|
options: CompileOptions;
|
|
|
|
processedFiles: Set<string>;
|
|
|
|
};
|
|
|
|
|
2022-01-05 21:16:17 +00:00
|
|
|
function processFile(
|
|
|
|
ctx: ProcessContext,
|
|
|
|
file: string,
|
|
|
|
root = false
|
|
|
|
): Parsed | null {
|
2021-12-31 21:38:26 +00:00
|
|
|
file = Path.resolve(file);
|
|
|
|
if (ctx.processedFiles.has(file)) {
|
|
|
|
log("Skipping file %s since it has already be processed", file);
|
|
|
|
return null;
|
|
|
|
}
|
2022-01-01 01:25:11 +00:00
|
|
|
ctx.processedFiles.add(file);
|
2021-12-31 21:38:26 +00:00
|
|
|
log("Processing file %s", file);
|
|
|
|
|
|
|
|
const content = getFile(file);
|
|
|
|
if (!content) throw new Error("Could not open file " + file);
|
|
|
|
try {
|
|
|
|
log("Tokenizing %s", file);
|
|
|
|
const tokens = tokenize(content);
|
|
|
|
log("Parsing %s", file);
|
|
|
|
const parsed = parse(tokens, file);
|
|
|
|
|
|
|
|
log("Resolving imports of %s", file);
|
|
|
|
let resolved = parsed
|
|
|
|
.map((statement) => {
|
|
|
|
if (statement.type == "import") {
|
|
|
|
const base = Path.dirname(file);
|
2022-01-05 21:16:17 +00:00
|
|
|
const resolved = Path.resolve(
|
|
|
|
Path.join(base, statement.path + ".jrpc")
|
|
|
|
);
|
2021-12-31 21:38:26 +00:00
|
|
|
return processFile(ctx, resolved);
|
|
|
|
} else {
|
|
|
|
return statement;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.filter((e) => !!e)
|
|
|
|
.flat(1) as Parsed;
|
|
|
|
return resolved;
|
|
|
|
} catch (err) {
|
|
|
|
if (err instanceof TokenizerError) {
|
|
|
|
printError(err, file, err.index);
|
2022-01-05 21:16:17 +00:00
|
|
|
if (!root) throw new CatchedError();
|
2021-12-31 21:38:26 +00:00
|
|
|
} else if (err instanceof ParserError) {
|
|
|
|
printError(err, file, err.token.startIdx);
|
2022-01-05 21:16:17 +00:00
|
|
|
if (!root) throw new CatchedError();
|
|
|
|
} else if (root && err instanceof CatchedError) {
|
2022-01-01 18:47:34 +00:00
|
|
|
return null;
|
2021-12-31 21:38:26 +00:00
|
|
|
} else {
|
2022-01-05 21:16:17 +00:00
|
|
|
throw err;
|
2021-12-31 21:38:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default function startCompile(options: CompileOptions) {
|
|
|
|
const ctx = {
|
|
|
|
options,
|
|
|
|
processedFiles: new Set(),
|
|
|
|
} as ProcessContext;
|
|
|
|
|
|
|
|
let ir: IR | undefined = undefined;
|
2022-01-05 21:16:17 +00:00
|
|
|
if (options.input.endsWith(".json")) {
|
2021-12-31 21:38:26 +00:00
|
|
|
ir = JSON.parse(FS.readFileSync(options.input, "utf-8"));
|
|
|
|
} else {
|
2022-01-01 18:47:34 +00:00
|
|
|
const parsed = processFile(ctx, options.input, true);
|
|
|
|
|
2022-01-05 21:16:17 +00:00
|
|
|
if (!parsed) process.exit(1); // Errors should have already been emitted
|
2022-01-01 18:47:34 +00:00
|
|
|
try {
|
2022-01-05 21:16:17 +00:00
|
|
|
ir = get_ir(parsed);
|
|
|
|
} catch (err) {
|
|
|
|
if (err instanceof IRError) {
|
|
|
|
printError(
|
|
|
|
err,
|
|
|
|
err.statement.location.file,
|
|
|
|
err.statement.location.idx
|
|
|
|
);
|
2022-01-01 18:47:34 +00:00
|
|
|
process.exit(1);
|
|
|
|
} else {
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
}
|
2021-12-31 21:38:26 +00:00
|
|
|
}
|
|
|
|
|
2022-01-05 21:16:17 +00:00
|
|
|
if (!ir) throw new Error("Error compiling: Cannot get IR");
|
2021-12-31 21:38:26 +00:00
|
|
|
|
2022-01-05 21:16:17 +00:00
|
|
|
if (options.emitDefinitions) {
|
|
|
|
FS.writeFileSync(
|
|
|
|
options.emitDefinitions,
|
|
|
|
JSON.stringify(ir, undefined, 3)
|
|
|
|
);
|
2021-12-31 21:38:26 +00:00
|
|
|
}
|
|
|
|
|
2022-01-05 21:16:17 +00:00
|
|
|
if (options.targets.length <= 0) {
|
2022-01-01 00:39:15 +00:00
|
|
|
console.log(Color.yellow("WARNING:"), "No targets selected!");
|
|
|
|
}
|
|
|
|
|
2022-01-05 21:16:17 +00:00
|
|
|
options.targets.forEach((target) => {
|
|
|
|
const tg = Targets.get(target.type) as any;
|
|
|
|
if (!tg) {
|
2021-12-31 21:38:26 +00:00
|
|
|
console.log(Color.red("ERROR:"), "Target not supported!");
|
|
|
|
return;
|
|
|
|
}
|
2022-01-05 21:16:17 +00:00
|
|
|
compile(ir, new tg(target.output, ir.options));
|
|
|
|
});
|
2021-12-31 21:38:26 +00:00
|
|
|
}
|