diff --git a/package.json b/package.json index 210791e..3478d04 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hibas123/jrpcgen", - "version": "1.1.4", + "version": "1.1.5", "main": "lib/index.js", "license": "MIT", "packageManager": "yarn@3.1.1", @@ -24,6 +24,9 @@ "templates/CSharp/*.csproj", "templates/Rust/Cargo.toml", "templates/Rust/src/lib.rs", + "templates/Dart/service_client.dart", + "templates/Dart/base.dart", + "templates/Dart/pubspec.yaml", "examples/*.jrpcproj", "src/**", "tsconfig.json" diff --git a/src/ir.ts b/src/ir.ts index 31672f7..7549d0d 100644 --- a/src/ir.ts +++ b/src/ir.ts @@ -316,7 +316,10 @@ export default function get_ir(parsed: Parsed): IR { builtin.push("bytes"); } } else { - throw new IRError(statement, "Invalid statement!"); + throw new IRError( + statement, + "Invalid statement: " + (statement as any).type + ); } }); diff --git a/src/process.ts b/src/process.ts index dcebc2a..e5f1949 100644 --- a/src/process.ts +++ b/src/process.ts @@ -2,6 +2,8 @@ import dbg from "debug"; import * as FS from "fs"; import Color from "chalk"; import * as Path from "path"; +import * as Https from "https"; +import * as Http from "http"; import tokenize, { TokenizerError } from "./tokenizer"; import parse, { Parsed, ParserError } from "./parser"; import get_ir, { IR, IRError } from "./ir"; @@ -14,6 +16,7 @@ import { CSharpTarget } from "./targets/csharp"; import { RustTarget } from "./targets/rust"; import { ZIGTarget } from "./targets/zig"; import { DartTarget } from "./targets/dart"; +import { URL } from "url"; class CatchedError extends Error {} @@ -43,26 +46,63 @@ function indexToLineAndCol(src: string, index: number) { return { line, col }; } +function resolve(base: string, ...parts: string[]) { + if (base.startsWith("http://") || base.startsWith("https://")) { + let u = new URL(base); + for (const part of parts) { + u = new URL(part, u); + } + return u.href; + } else { + return Path.resolve(base, ...parts); + } +} + +async function fetchFileFromURL(url: string) { + return new Promise((yes, no) => { + const makeReq = url.startsWith("https://") ? Https.request : Http.request; + const req = makeReq(url, (res) => { + let chunks: Buffer[] = []; + res.on("data", (chunk) => { + chunks.push(Buffer.from(chunk)); + }); + res.on("error", no); + res.on("end", () => yes(Buffer.concat(chunks).toString("utf-8"))); + }); + + req.on("error", no); + req.end(); + }); +} + const fileCache = new Map(); -function getFile(name: string) { + +async 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; + if (name.startsWith("http://") || name.startsWith("https://")) { + const data = await fetchFileFromURL(name); + fileCache.set(name, data); + return data; + } else { + const data = FS.readFileSync(name, "utf-8"); + fileCache.set(name, data); + return data; + } } catch (err) { - printError(new Error(`Cannot open file ${name};`), null, 0); + log(err); + await printError(new Error(`Cannot open file ${name};`), null, 0); } } return undefined; } -function printError(err: Error, file: string | null, idx: number) { +async 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); + if (data) loc = indexToLineAndCol(await data, idx); } console.error(`${Color.red("ERROR: at")} ${file}:${loc.line}:${loc.col}`); @@ -86,21 +126,21 @@ type ProcessContext = { processedFiles: Set; }; -function processFile( +async function processFile( ctx: ProcessContext, file: string, root = false -): Parsed | null { - file = Path.resolve(file); +): Promise { + file = resolve(file); if (ctx.processedFiles.has(file)) { - log("Skipping file %s since it has already be processed", file); + log("Skipping file %s since it has already been processed", file); return null; } ctx.processedFiles.add(file); log("Processing file %s", file); - const content = getFile(file); - if (!content) throw new Error("Could not open file " + file); + const content = await getFile(file); + if (content == undefined) throw new Error("Could not open file " + file); try { log("Tokenizing %s", file); const tokens = tokenize(content); @@ -108,27 +148,29 @@ function processFile( 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); - const resolved = Path.resolve( - Path.join(base, statement.path + ".jrpc") - ); - return processFile(ctx, resolved); + let resolved: Parsed = []; + for (const statement of parsed) { + if (statement.type == "import") { + let res: string; + if (file.startsWith("http://") || file.startsWith("https://")) { + res = resolve(file, statement.path + ".jrpc"); } else { - return statement; + const base = Path.dirname(file); + res = resolve(base, statement.path + ".jrpc"); } - }) - .filter((e) => !!e) - .flat(1) as Parsed; - return resolved; + resolved.push(...((await processFile(ctx, res)) || [])); + } else { + resolved.push(statement); + } + } + + return resolved.filter((e) => !!e).flat(1) as Parsed; } catch (err) { if (err instanceof TokenizerError) { - printError(err, file, err.index); + await printError(err, file, err.index); if (!root) throw new CatchedError(); } else if (err instanceof ParserError) { - printError(err, file, err.token.startIdx); + await printError(err, file, err.token.startIdx); if (!root) throw new CatchedError(); } else if (root && err instanceof CatchedError) { return null; @@ -138,7 +180,7 @@ function processFile( } } -export default function startCompile(options: CompileOptions) { +export default async function startCompile(options: CompileOptions) { const ctx = { options, processedFiles: new Set(), @@ -148,14 +190,14 @@ export default function startCompile(options: CompileOptions) { if (options.input.endsWith(".json")) { ir = JSON.parse(FS.readFileSync(options.input, "utf-8")); } else { - const parsed = processFile(ctx, options.input, true); + const parsed = await processFile(ctx, options.input, true); if (!parsed) process.exit(1); // Errors should have already been emitted try { ir = get_ir(parsed); } catch (err) { if (err instanceof IRError) { - printError( + await printError( err, err.statement.location.file, err.statement.location.idx