Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 0 additions & 27 deletions .github/workflows/codeql.yml

This file was deleted.

72 changes: 0 additions & 72 deletions .github/workflows/test.yml

This file was deleted.

46 changes: 26 additions & 20 deletions src/cli.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,10 +9,15 @@
* If -o is omitted, writes to stdout.
*/

import { parseArgs } from "node:util"
import { readFileSync, writeFileSync, existsSync } from "node:fs"
import { resolve } from "node:path"
import { configure, reset, transliterate, filesystemStrategy } from "./index.js"
import { parseArgs } from "node:util";
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { resolve } from "node:path";
import {
configure,
reset,
transliterate,
filesystemStrategy,
} from "./index.js";

const { values } = parseArgs({
options: {
Expand All@@ -23,7 +28,7 @@ const { values } = parseArgs({
help: { type: "boolean", short: "h" },
},
strict: true,
})
});

if (values.help || !values["system-code"]) {
process.stdout.write(
Expand All@@ -36,36 +41,37 @@ Options:
--maps-dir Directory containing <systemCode>.json IR files
-h, --help Show this help
`,
)
process.exit(values.help ? 0 : 1)
);
process.exit(values.help ? 0 : 1);
}

const mapsDir = values["maps-dir"]
? resolve(process.cwd(), values["maps-dir"])
: undefined
: undefined;

if (mapsDir && !existsSync(mapsDir)) {
process.stderr.write(`Error: maps directory not found: ${mapsDir}\n`)
process.exit(2)
process.stderr.write(`Error: maps directory not found: ${mapsDir}\n`);
process.exit(2);
}

reset()
reset();
if (mapsDir) {
configure({ strategies: [filesystemStrategy(mapsDir)] })
configure({ strategies: [filesystemStrategy(mapsDir)] });
}

const inputText = values.input
? readFileSync(resolve(process.cwd(), values.input), "utf8")
: readFileSync(0, "utf8")
const inputText =
values.input && values.input !== "-"
? readFileSync(resolve(process.cwd(), values.input), "utf8")
: readFileSync(0, "utf8");

try {
const result = transliterate(values["system-code"]!, inputText)
const result = transliterate(values["system-code"]!, inputText);
if (values.output) {
writeFileSync(resolve(process.cwd(), values.output), result + "\n")
writeFileSync(resolve(process.cwd(), values.output), result + "\n");
} else {
process.stdout.write(result + "\n")
process.stdout.write(result + "\n");
}
} catch (e) {
process.stderr.write(`Error: ${(e as Error).message}\n`)
process.exit(1)
process.stderr.write(`Error: ${(e as Error).message}\n`);
process.exit(1);
}
58 changes: 30 additions & 28 deletions src/detector.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,43 +7,45 @@
* Levenshtein distance to `output`, return ranked candidates.
*/

import type { DetectionResult, DetectOptions, SystemCode } from "./types.js"
import type { MapLoader } from "./loader.js"
import { executeStage } from "./runtime/interpreter.js"
import { InterscriptError } from "./errors.js"
import type { DetectionResult, DetectOptions, SystemCode } from "./types.js";
import type { MapLoader } from "./loader.js";
import { executeStage } from "./runtime/interpreter.js";
import { InterscriptError } from "./errors.js";

/**
* Compute Levenshtein edit distance between two strings.
* Classic dynamic programming, O(m·n) time and O(min(m,n)) space.
*/
export function levenshtein(a: string, b: string): number {
if (a === b) return 0
if (a.length === 0) return b.length
if (b.length === 0) return a.length
if (a === b) return 0;
if (a.length === 0) return b.length;
if (b.length === 0) return a.length;

let prev = new Array<number>(b.length + 1)
let curr = new Array<number>(b.length + 1)
for (let j = 0; j <= b.length; j++) prev[j] = j
let prev = new Array<number>(b.length + 1);
let curr = new Array<number>(b.length + 1);
for (let j = 0; j <= b.length; j++) prev[j] = j;

for (let i = 1; i <= a.length; i++) {
curr[0] = i
curr[0] = i;
for (let j = 1; j <= b.length; j++) {
const cost = a[i - 1] === b[j - 1] ? 0 : 1
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
curr[j] = Math.min(
prev[j]! + 1, // deletion
curr[j - 1]! + 1, // insertion
prev[j - 1]! + cost, // substitution
)
);
}
;[prev, curr] = [curr, prev]
[prev, curr] = [curr, prev];
}
return prev[b.length]!
return prev[b.length]!;
}

/** Convert a glob (`*` wildcard) into a RegExp. */
function globToRegExp(pattern: string): RegExp {
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")
return new RegExp(`^${escaped}$`)
const escaped = pattern
.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
.replace(/\*/g, ".*");
return new RegExp(`^${escaped}$`);
}

/**
Expand All@@ -58,26 +60,26 @@ export function detectInMaps(
opts: DetectOptions = {},
knownMaps?: Iterable<SystemCode>,
): DetectionResult[] {
const candidates: DetectionResult[] = []
const filter = opts.mapPattern ? globToRegExp(opts.mapPattern) : null
const systems = knownMaps ?? loader.loadedMaps()
const candidates: DetectionResult[] = [];
const filter = opts.mapPattern ? globToRegExp(opts.mapPattern) : null;
const systems = knownMaps ?? loader.loadedMaps();

for (const systemCode of systems) {
if (filter && !filter.test(systemCode)) continue
if (filter && !filter.test(systemCode)) continue;

let transliterated: string
let transliterated: string;
try {
const map = loader.load(systemCode)
transliterated = executeStage(map, "main", input, loader)
const map = loader.load(systemCode);
transliterated = executeStage(map, "main", input, loader);
} catch (e) {
if (e instanceof InterscriptError) continue
throw e
if (e instanceof InterscriptError) continue;
throw e;
}
candidates.push({
mapName: systemCode,
distance: levenshtein(transliterated, output),
})
});
}

return candidates.sort((a, b) => a.distance - b.distance)
return candidates.sort((a, b) => a.distance - b.distance);
}
20 changes: 10 additions & 10 deletions src/errors.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,20 +7,20 @@
*/

export class InterscriptError extends Error {
override readonly cause?: unknown
override readonly cause?: unknown;

constructor(message: string, options?: { cause?: unknown }) {
super(message, options)
this.name = new.target.name
this.cause = options?.cause
super(message, options);
this.name = new.target.name;
this.cause = options?.cause;
}
}

export class MapNotFoundError extends InterscriptError {
readonly systemCode: string
readonly systemCode: string;
constructor(systemCode: string) {
super(`Map not found: ${systemCode}`)
this.systemCode = systemCode
super(`Map not found: ${systemCode}`);
this.systemCode = systemCode;
}
}

Expand All@@ -29,9 +29,9 @@ export class SystemConversionError extends InterscriptError {}
export class MapLogicError extends InterscriptError {}

export class DependencyMissingError extends InterscriptError {
readonly dependency: string
readonly dependency: string;
constructor(dependency: string) {
super(`Map dependency missing: ${dependency}`)
this.dependency = dependency
super(`Map dependency missing: ${dependency}`);
this.dependency = dependency;
}
}
Loading
Loading