harmonyc 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/cli/cli.d.ts +2 -0
  2. package/cli/cli.js +32 -0
  3. package/cli/run.d.ts +2 -0
  4. package/cli/run.js +21 -0
  5. package/cli/watch.d.ts +1 -0
  6. package/cli/watch.js +35 -0
  7. package/code_generator/VitestGenerator.d.ts +36 -0
  8. package/code_generator/VitestGenerator.js +240 -0
  9. package/code_generator/outFile.d.ts +20 -0
  10. package/code_generator/outFile.js +60 -0
  11. package/code_generator/test_phrases.d.ts +11 -0
  12. package/code_generator/test_phrases.js +27 -0
  13. package/compiler/compile.d.ts +16 -0
  14. package/compiler/compile.js +50 -0
  15. package/compiler/compiler.d.ts +8 -0
  16. package/compiler/compiler.js +39 -0
  17. package/filenames/filenames.d.ts +3 -0
  18. package/filenames/filenames.js +17 -0
  19. package/model/Router.d.ts +22 -0
  20. package/model/Router.js +54 -0
  21. package/model/model.d.ts +225 -0
  22. package/model/model.js +526 -0
  23. package/optimizations/autoLabel/autoLabel.d.ts +2 -0
  24. package/optimizations/autoLabel/autoLabel.js +19 -0
  25. package/package.json +1 -4
  26. package/parser/lexer.d.ts +21 -0
  27. package/parser/lexer.js +123 -0
  28. package/parser/lexer_rules.d.ts +33 -0
  29. package/parser/lexer_rules.js +74 -0
  30. package/parser/parser.d.ts +18 -0
  31. package/parser/parser.js +76 -0
  32. package/phrases_assistant/phrases_assistant.d.ts +13 -0
  33. package/phrases_assistant/phrases_assistant.js +146 -0
  34. package/util/indent.d.ts +1 -0
  35. package/util/indent.js +5 -0
  36. package/util/iterators.d.ts +1 -0
  37. package/util/iterators.js +5 -0
  38. package/util/xmur3.d.ts +1 -0
  39. package/util/xmur3.js +12 -0
  40. package/vitest/index.d.ts +11 -0
  41. package/vitest/index.js +101 -0
package/cli/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/cli/cli.js ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ import { compileFiles } from "../compiler/compiler.js";
3
+ import { parseArgs } from 'node:util';
4
+ import { watchFiles } from "./watch.js";
5
+ import { run, runWatch } from "./run.js";
6
+ const args = parseArgs({
7
+ options: {
8
+ help: { type: 'boolean' },
9
+ watch: { type: 'boolean', short: 'w' },
10
+ run: { type: 'boolean', short: 'r' },
11
+ },
12
+ allowPositionals: true,
13
+ });
14
+ if (args.positionals.length === 0 || args.values.help) {
15
+ console.error('Usage: harmonyc <input files glob pattern>');
16
+ process.exit(1);
17
+ }
18
+ ;
19
+ (async () => {
20
+ if (args.values.watch) {
21
+ const outFns = await watchFiles(args.positionals);
22
+ if (args.values.run) {
23
+ runWatch(outFns);
24
+ }
25
+ }
26
+ else {
27
+ const { outFns } = await compileFiles(args.positionals);
28
+ if (args.values.run) {
29
+ run(outFns);
30
+ }
31
+ }
32
+ })();
package/cli/run.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function run(patterns: string[]): void;
2
+ export declare function runWatch(patterns: string[]): void;
package/cli/run.js ADDED
@@ -0,0 +1,21 @@
1
+ import { exec, spawn } from 'child_process';
2
+ function runCommand(patterns) {
3
+ return `npx vitest run ${args(patterns)}`;
4
+ }
5
+ function runWatchCommand(patterns) {
6
+ return `npx vitest ${args(patterns)}`;
7
+ }
8
+ function args(patterns) {
9
+ return patterns.map((s) => JSON.stringify(s)).join(' ');
10
+ }
11
+ export function run(patterns) {
12
+ var _a, _b;
13
+ const cmd = runCommand(patterns);
14
+ const p = exec(cmd, { cwd: process.cwd() });
15
+ (_a = p.stdout) === null || _a === void 0 ? void 0 : _a.pipe(process.stdout);
16
+ (_b = p.stderr) === null || _b === void 0 ? void 0 : _b.pipe(process.stderr);
17
+ }
18
+ export function runWatch(patterns) {
19
+ const cmd = runWatchCommand(patterns);
20
+ spawn(cmd, { cwd: process.cwd(), stdio: 'inherit', shell: true });
21
+ }
package/cli/watch.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function watchFiles(patterns: string[]): Promise<string[]>;
package/cli/watch.js ADDED
@@ -0,0 +1,35 @@
1
+ import Watcher from 'watcher';
2
+ import { compileFile, compileFiles } from "../compiler/compiler.js";
3
+ export async function watchFiles(patterns) {
4
+ const { fns, outFns } = await compileFiles(patterns);
5
+ for (const file of fns) {
6
+ const watcher = new Watcher(file, { debounce: 20, ignoreInitial: true });
7
+ watcher.on('all', async () => {
8
+ var _a;
9
+ try {
10
+ await compileFile(file);
11
+ }
12
+ catch (e) {
13
+ process.stdout.write(`\n`);
14
+ console.log((_a = e.message) !== null && _a !== void 0 ? _a : e);
15
+ process.stdout.write(`\n`);
16
+ }
17
+ logger.log(`Compiled ${file}`);
18
+ });
19
+ }
20
+ return outFns;
21
+ }
22
+ const logger = {
23
+ last: '',
24
+ n: 0,
25
+ log(msg) {
26
+ if (msg === this.last) {
27
+ process.stdout.write(`\r${msg} ${++this.n}x`);
28
+ }
29
+ else {
30
+ process.stdout.write(`\r${msg}`);
31
+ this.last = msg;
32
+ this.n = 1;
33
+ }
34
+ },
35
+ };
@@ -0,0 +1,36 @@
1
+ import { CompilerOptions } from '../compiler/compile.ts';
2
+ import { Action, CodeGenerator, ErrorResponse, Feature, Phrase, PhraseMethod, Response, SaveToVariable, SetVariable, Test, TestGroup } from '../model/model.ts';
3
+ import { OutFile } from './outFile.ts';
4
+ export declare class VitestGenerator implements CodeGenerator {
5
+ private tf;
6
+ private sourceFileName;
7
+ private opts;
8
+ static error(message: string, stack: string): string;
9
+ framework: string;
10
+ phraseFns: Map<string, Phrase>;
11
+ currentFeatureName: string;
12
+ featureClassName: string;
13
+ phraseMethods: PhraseMethod[];
14
+ constructor(tf: OutFile, sourceFileName: string, opts: CompilerOptions);
15
+ feature(feature: Feature): void;
16
+ testGroup(g: TestGroup): void;
17
+ featureVars: Map<string, string>;
18
+ resultCount: number;
19
+ test(t: Test): void;
20
+ errorStep(action: Action, errorResponse: ErrorResponse): void;
21
+ extraArgs: string[];
22
+ step(action: Action, responses: Response[]): void;
23
+ private declareFeatureVariables;
24
+ phrase(p: Phrase): void;
25
+ setVariable(action: SetVariable): void;
26
+ saveToVariable(s: SaveToVariable, what?: string): void;
27
+ stringLiteral(text: string, { withVariables }: {
28
+ withVariables: boolean;
29
+ }): string;
30
+ codeLiteral(src: string): string;
31
+ private paramName;
32
+ stringParamDeclaration(index: number): string;
33
+ variantParamDeclaration(index: number): string;
34
+ functionName(phrase: Phrase): string;
35
+ argPlaceholder(i: number): string;
36
+ }
@@ -0,0 +1,240 @@
1
+ import { basename } from 'path';
2
+ import { xyzab } from "../compiler/compile.js";
3
+ import { Arg, Response, Word, } from "../model/model.js";
4
+ const X = 'X'.codePointAt(0);
5
+ const A = 'A'.codePointAt(0);
6
+ export class VitestGenerator {
7
+ static error(message, stack) {
8
+ return `const e = new SyntaxError(${str(message)});
9
+ e.stack = undefined;
10
+ throw e;
11
+ ${stack ? `/* ${stack} */` : ''}`;
12
+ }
13
+ constructor(tf, sourceFileName, opts) {
14
+ this.tf = tf;
15
+ this.sourceFileName = sourceFileName;
16
+ this.opts = opts;
17
+ this.framework = 'vitest';
18
+ this.phraseFns = new Map();
19
+ this.currentFeatureName = '';
20
+ this.phraseMethods = [];
21
+ this.resultCount = 0;
22
+ this.extraArgs = [];
23
+ }
24
+ feature(feature) {
25
+ const phrasesModule = './' + basename(this.sourceFileName.replace(/\.harmony$/, '.phrases.js'));
26
+ const fn = (this.featureClassName =
27
+ this.currentFeatureName =
28
+ pascalCase(feature.name) + 'Phrases');
29
+ this.phraseFns = new Map();
30
+ // test file
31
+ if (this.framework === 'vitest') {
32
+ this.tf.print(`import { describe, test, expect } from "vitest";`);
33
+ }
34
+ if (feature.tests.length === 0) {
35
+ this.tf.print('');
36
+ this.tf.print(`describe.todo(${str(feature.name)});`);
37
+ return;
38
+ }
39
+ this.tf.print(`import ${fn} from ${str(phrasesModule)};`);
40
+ this.tf.print(``);
41
+ for (const item of feature.testGroups) {
42
+ item.toCode(this);
43
+ }
44
+ this.tf.print(``);
45
+ for (const ph of this.phraseFns.keys()) {
46
+ const p = this.phraseFns.get(ph);
47
+ const parameters = p.args.map((a, i) => {
48
+ const declaration = a.toDeclaration(this, i);
49
+ const parts = declaration.split(': ');
50
+ return {
51
+ name: parts[0],
52
+ type: parts[1] || 'any',
53
+ };
54
+ });
55
+ if (p instanceof Response) {
56
+ parameters.push({
57
+ name: 'res',
58
+ type: 'any',
59
+ });
60
+ }
61
+ this.phraseMethods.push({
62
+ name: ph,
63
+ parameters,
64
+ });
65
+ }
66
+ }
67
+ testGroup(g) {
68
+ this.tf.print(`describe(${str(g.label.text)}, () => {`, g.label.start, g.label.text);
69
+ this.tf.indent(() => {
70
+ for (const item of g.items) {
71
+ item.toCode(this);
72
+ }
73
+ });
74
+ this.tf.print('});');
75
+ }
76
+ test(t) {
77
+ var _a;
78
+ this.resultCount = 0;
79
+ this.featureVars = new Map();
80
+ // avoid shadowing this import name
81
+ this.featureVars.set(new Object(), this.currentFeatureName);
82
+ this.tf.print(`test(${str(t.name)}, async (context) => {`, (_a = t.lastStrain) === null || _a === void 0 ? void 0 : _a.start, t.testNumber);
83
+ this.tf.indent(() => {
84
+ this.tf.print(`context.task.meta.phrases ??= [];`);
85
+ for (const step of t.steps) {
86
+ step.toCode(this);
87
+ }
88
+ });
89
+ this.tf.print('});');
90
+ }
91
+ errorStep(action, errorResponse) {
92
+ this.declareFeatureVariables([action]);
93
+ this.tf.print(`await expect(async () => {`);
94
+ this.tf.indent(() => {
95
+ action.toCode(this);
96
+ this.tf.print(`context.task.meta.phrases.push(${str(errorResponse.toSingleLineString())});`);
97
+ });
98
+ this.tf.print(`}).rejects.toThrow(${(errorResponse === null || errorResponse === void 0 ? void 0 : errorResponse.message) !== undefined
99
+ ? str(errorResponse.message.text)
100
+ : ''});`);
101
+ }
102
+ step(action, responses) {
103
+ this.declareFeatureVariables([action, ...responses]);
104
+ if (responses.length === 0) {
105
+ action.toCode(this);
106
+ return;
107
+ }
108
+ if (action.isEmpty) {
109
+ for (const response of responses) {
110
+ response.toCode(this);
111
+ }
112
+ return;
113
+ }
114
+ const res = `r${this.resultCount++ || ''}`;
115
+ this.tf.print(`const ${res} =`);
116
+ this.tf.indent(() => {
117
+ action.toCode(this);
118
+ try {
119
+ this.extraArgs = [res];
120
+ for (const response of responses) {
121
+ response.toCode(this);
122
+ }
123
+ }
124
+ finally {
125
+ this.extraArgs = [];
126
+ }
127
+ });
128
+ }
129
+ declareFeatureVariables(phrases) {
130
+ for (const p of phrases) {
131
+ const feature = p.feature.name;
132
+ let f = this.featureVars.get(feature);
133
+ if (!f) {
134
+ f = toId(feature, abbrev, this.featureVars);
135
+ this.tf.print(`const ${f} = new ${pascalCase(feature)}Phrases(context);`);
136
+ }
137
+ }
138
+ }
139
+ phrase(p) {
140
+ const phrasefn = this.functionName(p);
141
+ if (!this.phraseFns.has(phrasefn))
142
+ this.phraseFns.set(phrasefn, p);
143
+ const f = this.featureVars.get(p.feature.name);
144
+ const args = p.args.map((a) => a.toCode(this));
145
+ args.push(...this.extraArgs);
146
+ if (p instanceof Response && p.parts.length === 1 && p.saveToVariable) {
147
+ return this.saveToVariable(p.saveToVariable);
148
+ }
149
+ const name = p.toSingleLineString();
150
+ this.tf.print(`(context.task.meta.phrases.push(${str(p.toString())}),`);
151
+ if (p instanceof Response && p.saveToVariable) {
152
+ this.saveToVariable(p.saveToVariable, '');
153
+ }
154
+ this.tf.printn(`await ${f}.`);
155
+ this.tf.write(`${phrasefn}(${args.join(', ')})`, p.start, name);
156
+ this.tf.write(`);`);
157
+ this.tf.nl();
158
+ }
159
+ setVariable(action) {
160
+ this.tf.print(`(context.task.meta.variables ??= {})[${str(action.variableName)}] = ${action.value.toCode(this)};`);
161
+ }
162
+ saveToVariable(s, what = this.extraArgs[0] + ';') {
163
+ this.tf.print(`(context.task.meta.variables ??= {})[${str(s.variableName)}] = ${what}`.trimEnd());
164
+ }
165
+ stringLiteral(text, { withVariables }) {
166
+ if (withVariables && text.match(/\$\{/)) {
167
+ return templateStr(text).replace(/\\\$\{([^\s}]+)\}/g, (_, x) => `\${context.task.meta.variables?.[${str(x)}]}`);
168
+ }
169
+ return str(text);
170
+ }
171
+ codeLiteral(src) {
172
+ return src.replace(/\$\{([^\s}]+)\}/g, (_, x) => `context.task.meta.variables?.[${str(x)}]`);
173
+ }
174
+ paramName(index) {
175
+ return xyzab(index);
176
+ }
177
+ stringParamDeclaration(index) {
178
+ return `${this.paramName(index)}: string`;
179
+ }
180
+ variantParamDeclaration(index) {
181
+ return `${this.paramName(index)}: any`;
182
+ }
183
+ functionName(phrase) {
184
+ const { kind } = phrase;
185
+ let argIndex = -1;
186
+ return ((kind === 'response' ? 'Then_' : 'When_') +
187
+ (phrase.parts
188
+ .flatMap((c) => c instanceof Word
189
+ ? words(c.text).filter((x) => x)
190
+ : c instanceof Arg
191
+ ? [this.argPlaceholder(++argIndex)]
192
+ : [])
193
+ .join('_') || ''));
194
+ }
195
+ argPlaceholder(i) {
196
+ return typeof this.opts.argumentPlaceholder === 'function'
197
+ ? this.opts.argumentPlaceholder(i)
198
+ : this.opts.argumentPlaceholder;
199
+ }
200
+ }
201
+ function str(s) {
202
+ if (s.includes('\n'))
203
+ return '\n' + templateStr(s);
204
+ let r = JSON.stringify(s);
205
+ return r;
206
+ }
207
+ function templateStr(s) {
208
+ return '`' + s.replace(/([`$\\])/g, '\\$1') + '`';
209
+ }
210
+ function capitalize(s) {
211
+ return s.charAt(0).toUpperCase() + s.slice(1);
212
+ }
213
+ function toId(s, transform, previous) {
214
+ if (previous.has(s))
215
+ return previous.get(s);
216
+ let base = transform(s);
217
+ let id = base;
218
+ if ([...previous.values()].includes(id)) {
219
+ let i = 1;
220
+ while ([...previous.values()].includes(id + i))
221
+ i++;
222
+ id = base + i;
223
+ }
224
+ previous.set(s, id);
225
+ return id;
226
+ }
227
+ function words(s) {
228
+ return s.split(/[^0-9\p{L}]+/gu);
229
+ }
230
+ function pascalCase(s) {
231
+ return words(s).map(capitalize).join('');
232
+ }
233
+ function underscore(s) {
234
+ return words(s).join('_');
235
+ }
236
+ function abbrev(s) {
237
+ return words(s)
238
+ .map((x) => x.charAt(0).toUpperCase())
239
+ .join('');
240
+ }
@@ -0,0 +1,20 @@
1
+ import { SourceNode } from 'source-map-js';
2
+ import { Location } from '../model/model.ts';
3
+ export declare class OutFile {
4
+ name: string;
5
+ sourceFile: string;
6
+ level: number;
7
+ sm: SourceNode;
8
+ indentSpaces: number;
9
+ constructor(name: string, sourceFile: string);
10
+ indent(fn: () => void): void;
11
+ private get currentIndent();
12
+ clear(): void;
13
+ printn(line: string, start?: Location, name?: string, end?: Location): void;
14
+ print(line: string, start?: Location, name?: string, end?: Location): void;
15
+ write(line: string, start?: Location, name?: string, end?: Location): void;
16
+ nl(): void;
17
+ get sourceMap(): import("source-map-js").RawSourceMap;
18
+ get valueWithoutSourceMap(): string;
19
+ get value(): string;
20
+ }
@@ -0,0 +1,60 @@
1
+ import { SourceNode } from 'source-map-js';
2
+ export class OutFile {
3
+ constructor(name, sourceFile) {
4
+ this.name = name;
5
+ this.sourceFile = sourceFile;
6
+ this.level = 0;
7
+ this.indentSpaces = 2;
8
+ this.sm = new SourceNode(0, 0, sourceFile);
9
+ }
10
+ indent(fn) {
11
+ this.level++;
12
+ try {
13
+ fn();
14
+ }
15
+ finally {
16
+ this.level--;
17
+ }
18
+ }
19
+ get currentIndent() {
20
+ return ' '.repeat(this.level * this.indentSpaces);
21
+ }
22
+ clear() {
23
+ this.sm = new SourceNode(0, 0, this.sourceFile);
24
+ }
25
+ printn(line, start, name, end) {
26
+ this.write(this.currentIndent + line, start, name, end);
27
+ }
28
+ print(line, start, name, end) {
29
+ this.write(this.currentIndent + line + '\n', start, name, end);
30
+ }
31
+ write(line, start, name, end) {
32
+ const chunk = line;
33
+ if (start) {
34
+ this.sm.add(new SourceNode(start.line, start.column, this.sourceFile, chunk, name));
35
+ }
36
+ else {
37
+ this.sm.add(new SourceNode(null, null, null, chunk));
38
+ }
39
+ if (end) {
40
+ this.sm.add(new SourceNode(end.line, end.column, this.sourceFile));
41
+ }
42
+ }
43
+ nl() {
44
+ this.write('\n');
45
+ }
46
+ get sourceMap() {
47
+ return this.sm.toStringWithSourceMap({ file: this.name }).map.toJSON();
48
+ }
49
+ get valueWithoutSourceMap() {
50
+ return this.sm.toString();
51
+ }
52
+ get value() {
53
+ const { code, map } = this.sm.toStringWithSourceMap({ file: this.name });
54
+ let res = code;
55
+ res +=
56
+ `\n//# sour` + // not for this file ;)
57
+ `ceMappingURL=data:application/json,${encodeURIComponent(map.toString())}`;
58
+ return res;
59
+ }
60
+ }
@@ -0,0 +1,11 @@
1
+ export declare class TestPhrases {
2
+ private context;
3
+ constructor(context: any);
4
+ When_goodbye(): void;
5
+ When_hello(): string;
6
+ When_greet_X(name: string): void;
7
+ Then_X_is_Y(x: string, y: string): Promise<void>;
8
+ Then_last_char(s: string): string;
9
+ Then_last_char_of_greeting(): any;
10
+ Then_X(s: string, r: string): void;
11
+ }
@@ -0,0 +1,27 @@
1
+ import { expect } from 'vitest';
2
+ export class TestPhrases {
3
+ constructor(context) {
4
+ this.context = context;
5
+ }
6
+ When_goodbye() {
7
+ throw new Error('Goodbye, World!');
8
+ }
9
+ When_hello() {
10
+ return (this.context.task.meta.greeting = 'Hello!');
11
+ }
12
+ When_greet_X(name) {
13
+ this.context.task.meta.greeting = `Hello, ${name}!`;
14
+ }
15
+ async Then_X_is_Y(x, y) {
16
+ expect(x).toBe(y);
17
+ }
18
+ Then_last_char(s) {
19
+ return s.slice(-1);
20
+ }
21
+ Then_last_char_of_greeting() {
22
+ return this.context.task.meta.greeting.slice(-1);
23
+ }
24
+ Then_X(s, r) {
25
+ expect(s).toBe(r);
26
+ }
27
+ }
@@ -0,0 +1,16 @@
1
+ import { OutFile } from '../code_generator/outFile.ts';
2
+ export interface CompilerOptions {
3
+ argumentPlaceholder: string | ((index: number) => string);
4
+ }
5
+ export interface CompiledFeature {
6
+ name: string;
7
+ code: Record<string, string>;
8
+ }
9
+ export declare function XYZAB(index: number): string;
10
+ export declare function xyzab(index: number): string;
11
+ export declare const DEFAULT_COMPILER_OPTIONS: CompilerOptions;
12
+ export declare function compileFeature(fileName: string, src: string, opts?: Partial<CompilerOptions>): {
13
+ outFile: OutFile;
14
+ phraseMethods: import("../model/model.ts").PhraseMethod[];
15
+ featureClassName: string;
16
+ };
@@ -0,0 +1,50 @@
1
+ import { basename } from 'node:path';
2
+ import { VitestGenerator } from "../code_generator/VitestGenerator.js";
3
+ import { OutFile } from "../code_generator/outFile.js";
4
+ import { base, testFileName } from "../filenames/filenames.js";
5
+ import { Feature } from "../model/model.js";
6
+ import { autoLabel } from "../optimizations/autoLabel/autoLabel.js";
7
+ import { parse } from "../parser/parser.js";
8
+ const X = 'X'.codePointAt(0);
9
+ const A = 'A'.codePointAt(0);
10
+ const x = 'x'.codePointAt(0);
11
+ const a = 'a'.codePointAt(0);
12
+ export function XYZAB(index) {
13
+ return String.fromCodePoint(A + ((X - A + index) % 26));
14
+ }
15
+ export function xyzab(index) {
16
+ return String.fromCodePoint(a + ((x - a + index) % 26));
17
+ }
18
+ export const DEFAULT_COMPILER_OPTIONS = {
19
+ argumentPlaceholder: XYZAB,
20
+ };
21
+ export function compileFeature(fileName, src, opts = {}) {
22
+ const feature = new Feature(basename(base(fileName)));
23
+ try {
24
+ feature.root = parse(src);
25
+ }
26
+ catch (e) {
27
+ if (e.pos && e.errorMessage) {
28
+ e.message =
29
+ e.stack = `Error in ${fileName}:${e.pos.rowBegin}:${e.pos.columnBegin}: ${e.errorMessage}`;
30
+ }
31
+ else {
32
+ e.stack = `Error in ${fileName}: ${e.stack}`;
33
+ }
34
+ throw e;
35
+ }
36
+ feature.root.setFeature(feature);
37
+ autoLabel(feature.root);
38
+ const testFn = testFileName(fileName);
39
+ const testFile = new OutFile(testFn, fileName);
40
+ const cg = new VitestGenerator(testFile, fileName, {
41
+ ...DEFAULT_COMPILER_OPTIONS,
42
+ ...opts,
43
+ });
44
+ feature.toCode(cg);
45
+ return {
46
+ outFile: testFile,
47
+ phraseMethods: cg.phraseMethods,
48
+ featureClassName: cg.featureClassName,
49
+ };
50
+ }
@@ -0,0 +1,8 @@
1
+ export declare function compileFiles(pattern: string | string[]): Promise<{
2
+ fns: string[];
3
+ outFns: string[];
4
+ }>;
5
+ export declare function compileFile(fn: string): Promise<{
6
+ outFile: import("../code_generator/outFile.ts").OutFile;
7
+ } | undefined>;
8
+ export declare function preprocess(src: string): string;
@@ -0,0 +1,39 @@
1
+ import glob from 'fast-glob';
2
+ import { readFileSync, writeFileSync } from 'fs';
3
+ import { resolve } from 'path';
4
+ import { compileFeature } from "./compile.js";
5
+ export async function compileFiles(pattern) {
6
+ var _a;
7
+ const fns = await glob(pattern);
8
+ if (!fns.length)
9
+ throw new Error(`No files found for pattern: ${String(pattern)}`);
10
+ const results = await Promise.allSettled(fns.map((fn) => compileFile(fn)));
11
+ const compiled = results.flatMap((r) => r.status === 'fulfilled' ? [r.value] : []);
12
+ const errored = results.flatMap((r) => r.status === 'rejected' && r.reason ? [r.reason] : []);
13
+ for (const error of errored) {
14
+ console.error((_a = error.message) !== null && _a !== void 0 ? _a : error);
15
+ }
16
+ console.log(`Compiled ${compiled.length} file${compiled.length === 1 ? '' : 's'}.`);
17
+ const features = compiled.filter((f) => f !== undefined);
18
+ return { fns, outFns: features.map((f) => f.outFile.name) };
19
+ }
20
+ export async function compileFile(fn) {
21
+ fn = resolve(fn);
22
+ const src = preprocess(readFileSync(fn, 'utf8').toString());
23
+ try {
24
+ const { outFile } = compileFeature(fn, src);
25
+ writeFileSync(outFile.name, outFile.value);
26
+ return { outFile };
27
+ }
28
+ catch (e) {
29
+ return undefined;
30
+ }
31
+ }
32
+ export function preprocess(src) {
33
+ // strip BOM
34
+ if (src.charCodeAt(0) === 0xfeff) {
35
+ src = src.slice(1);
36
+ }
37
+ src = src.replace(/\r\n?/g, '\n');
38
+ return src;
39
+ }
@@ -0,0 +1,3 @@
1
+ export declare function base(fn: string): string;
2
+ export declare function testFileName(fn: string): string;
3
+ export declare function phrasesFileName(fn: string): string;
@@ -0,0 +1,17 @@
1
+ import glob from 'fast-glob';
2
+ const { globSync, convertPathToPattern } = glob;
3
+ export function base(fn) {
4
+ return fn.replace(/\.harmony(\.\w+)?$/i, '');
5
+ }
6
+ export function testFileName(fn) {
7
+ return base(fn) + '.test.mjs';
8
+ }
9
+ export function phrasesFileName(fn) {
10
+ const baseFn = base(fn);
11
+ const pattern = convertPathToPattern(baseFn);
12
+ const existing = globSync(`${pattern}.phrases.{tsx,jsx,ts,js}`);
13
+ if (existing.length) {
14
+ return existing.sort().at(-1);
15
+ }
16
+ return `${baseFn}.phrases.ts`;
17
+ }
@@ -0,0 +1,22 @@
1
+ import type { Branch } from './model.ts';
2
+ export declare class Router<N> {
3
+ outs: N[];
4
+ index: number;
5
+ random: () => number;
6
+ started: Set<N>;
7
+ covered: Set<N>;
8
+ constructor(outs: N[], seed?: string);
9
+ next(): N;
10
+ get incompleteCount(): number;
11
+ }
12
+ type N = Branch;
13
+ export declare class Routers {
14
+ private root;
15
+ routers: Map<Branch, Router<Branch>>;
16
+ constructor(root: N);
17
+ discover(branch: N): void;
18
+ get(branch: N): Router<Branch>;
19
+ nextWalk(): Branch[];
20
+ getIncompleteCount(): number;
21
+ }
22
+ export {};