@styx-api/core 0.7.0 → 0.8.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.
- package/dist/index.cjs +430 -356
- package/dist/index.d.cts +1 -34
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -34
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +431 -352
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/backend/argtype/__fixtures__/microsyntax.argtype +0 -2
- package/src/backend/argtype/emit.ts +2 -51
- package/src/backend/boutiques/boutiques.ts +55 -47
- package/src/backend/field-defaults.ts +40 -0
- package/src/backend/find-struct-node.ts +7 -0
- package/src/backend/index.ts +1 -9
- package/src/backend/nipype/emit.ts +11 -3
- package/src/backend/python/arg-builder.ts +2 -27
- package/src/backend/python/emit.ts +18 -6
- package/src/backend/python/outputs-emit.ts +27 -42
- package/src/backend/python/python.ts +46 -22
- package/src/backend/python/validate-emit.ts +4 -1
- package/src/backend/resolve-output-tokens.ts +0 -76
- package/src/backend/typescript/arg-builder.ts +2 -24
- package/src/backend/typescript/emit.ts +7 -4
- package/src/backend/typescript/outputs-emit.ts +15 -39
- package/src/backend/typescript/typemap.ts +9 -1
- package/src/backend/typescript/typescript.ts +41 -18
- package/src/backend/typescript/validate-emit.ts +4 -1
- package/src/backend/union-variants.ts +41 -1
- package/src/frontend/argdump/parser.ts +20 -19
- package/src/frontend/argtype/__fixtures__/afni-3dtstat.argtype +41 -0
- package/src/frontend/argtype/__fixtures__/bet.argtype +0 -2
- package/src/frontend/argtype/__fixtures__/fsl-flirt.argtype +50 -0
- package/src/frontend/argtype/__fixtures__/wb-command-sub.argtype +39 -0
- package/src/frontend/argtype/ast.ts +14 -1
- package/src/frontend/argtype/frontmatter.ts +49 -5
- package/src/frontend/argtype/lexer.ts +20 -0
- package/src/frontend/argtype/lower.ts +115 -75
- package/src/frontend/argtype/parser-frontend.ts +2 -2
- package/src/frontend/argtype/parser.ts +70 -24
- package/src/frontend/boutiques/destruct-template.ts +24 -17
- package/src/frontend/boutiques/parser.ts +27 -8
- package/src/ir/passes/canonicalize.ts +18 -5
- package/src/ir/passes/simplify.ts +17 -2
- package/src/solver/solver.ts +2 -2
|
@@ -270,10 +270,12 @@ export class ArgdumpParser implements Frontend {
|
|
|
270
270
|
|
|
271
271
|
if (isArgparseMarker(nargs)) {
|
|
272
272
|
if (nargs.__argparse__ === "REMAINDER") {
|
|
273
|
-
// REMAINDER -> rep(
|
|
273
|
+
// REMAINDER -> rep(node), preserving the terminal's meta (name/doc/
|
|
274
|
+
// default) so the positional's `dest` name survives instead of being
|
|
275
|
+
// dropped for a solver-derived placeholder.
|
|
274
276
|
const rep: Repeat = {
|
|
275
277
|
kind: "repeat",
|
|
276
|
-
attrs: { node
|
|
278
|
+
attrs: { node, countMin: 0 },
|
|
277
279
|
};
|
|
278
280
|
return rep;
|
|
279
281
|
}
|
|
@@ -700,32 +702,33 @@ export class ArgdumpParser implements Frontend {
|
|
|
700
702
|
return null;
|
|
701
703
|
}
|
|
702
704
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
705
|
+
// A single choice needs no `alternative` wrapper, but must still honor the
|
|
706
|
+
// subparsers' required-ness (a lone-choice non-required subparser is still
|
|
707
|
+
// optional) rather than being returned bare as a mandatory node.
|
|
708
|
+
const node: Expr =
|
|
709
|
+
alts.length === 1
|
|
710
|
+
? alts[0]!
|
|
711
|
+
: ({ kind: "alternative", attrs: { alts } } satisfies Alternative);
|
|
708
712
|
|
|
709
713
|
const isRequired = action.subparsers_required === true || action.required === true;
|
|
710
|
-
|
|
711
|
-
const opt: Optional = { kind: "optional", attrs: { node: alt } };
|
|
714
|
+
const meta = this.buildNodeMeta(action);
|
|
712
715
|
|
|
713
|
-
|
|
716
|
+
if (!isRequired) {
|
|
717
|
+
const opt: Optional = { kind: "optional", attrs: { node } };
|
|
714
718
|
if (meta) opt.meta = meta;
|
|
715
|
-
|
|
716
719
|
return opt;
|
|
717
720
|
}
|
|
718
721
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
722
|
+
// Required: hang the subparsers meta on the alternative wrapper when there is
|
|
723
|
+
// one; a single bare arm keeps its own sub-command meta instead of having it
|
|
724
|
+
// clobbered by the action's dest name.
|
|
725
|
+
if (node.kind === "alternative" && meta) node.meta = meta;
|
|
726
|
+
return node;
|
|
723
727
|
}
|
|
724
728
|
|
|
725
729
|
// Mutual exclusion
|
|
726
730
|
|
|
727
731
|
private applyMutualExclusion(
|
|
728
|
-
actionsByDest: Map<string, AdAction>,
|
|
729
732
|
groups: unknown[],
|
|
730
733
|
nodes: Expr[],
|
|
731
734
|
nodesByDest: Map<string, Expr>,
|
|
@@ -825,7 +828,6 @@ export class ArgdumpParser implements Frontend {
|
|
|
825
828
|
|
|
826
829
|
const positionals: Expr[] = [];
|
|
827
830
|
const optionals: Expr[] = [];
|
|
828
|
-
const actionsByDest = new Map<string, AdAction>();
|
|
829
831
|
const nodesByDest = new Map<string, Expr>();
|
|
830
832
|
|
|
831
833
|
for (const rawAction of actions) {
|
|
@@ -836,7 +838,6 @@ export class ArgdumpParser implements Frontend {
|
|
|
836
838
|
|
|
837
839
|
const dest = rawAction.dest;
|
|
838
840
|
if (isString(dest)) {
|
|
839
|
-
actionsByDest.set(dest, rawAction);
|
|
840
841
|
nodesByDest.set(dest, node);
|
|
841
842
|
}
|
|
842
843
|
|
|
@@ -853,7 +854,7 @@ export class ArgdumpParser implements Frontend {
|
|
|
853
854
|
// Apply mutual exclusion groups
|
|
854
855
|
const mutexGroups = descriptor.mutually_exclusive_groups;
|
|
855
856
|
if (isArray(mutexGroups) && mutexGroups.length > 0) {
|
|
856
|
-
allNodes = this.applyMutualExclusion(
|
|
857
|
+
allNodes = this.applyMutualExclusion(mutexGroups, allNodes, nodesByDest);
|
|
857
858
|
}
|
|
858
859
|
|
|
859
860
|
return { kind: "sequence", attrs: { nodes: allNodes } };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
exe: "3dTstat"
|
|
3
|
+
version: "24.2.06"
|
|
4
|
+
authors:
|
|
5
|
+
- "RW Cox"
|
|
6
|
+
urls:
|
|
7
|
+
- "https://afni.nimh.nih.gov/"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
/// Compute voxelwise statistics across the time axis of a 3D+time dataset.
|
|
11
|
+
///
|
|
12
|
+
/// All of AFNI is named with a leading digit (3d*, 1d*, 2d*), so the tool
|
|
13
|
+
/// label has to be quoted - a bare identifier cannot start with a digit.
|
|
14
|
+
"3dTstat": seq(
|
|
15
|
+
set(
|
|
16
|
+
/// Output dataset prefix (the view and extension are added automatically).
|
|
17
|
+
opt("-prefix", prefix: str = "stat"),
|
|
18
|
+
|
|
19
|
+
/// Compute the mean across time (the default when no statistic is given).
|
|
20
|
+
mean: opt("-mean"),
|
|
21
|
+
|
|
22
|
+
/// Compute the standard deviation across time.
|
|
23
|
+
stdev: opt("-stdev"),
|
|
24
|
+
|
|
25
|
+
/// Compute the median across time.
|
|
26
|
+
median: opt("-median"),
|
|
27
|
+
|
|
28
|
+
/// Percentiles to compute, emitted as a single colon-joined argument
|
|
29
|
+
/// (for example `25:50:75`).
|
|
30
|
+
opt("-percentile", percentiles: rep(int.min(0).max(100)).join(":")),
|
|
31
|
+
|
|
32
|
+
/// Number of leading time points to ignore.
|
|
33
|
+
opt("-ignore", ignore_count: int.min(0)),
|
|
34
|
+
),
|
|
35
|
+
|
|
36
|
+
/// Input 3D+time dataset.
|
|
37
|
+
input: path,
|
|
38
|
+
).output(
|
|
39
|
+
/// The statistics dataset header written by `-prefix`.
|
|
40
|
+
statfile: `{prefix}+orig.HEAD`,
|
|
41
|
+
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
exe: "flirt"
|
|
3
|
+
version: "6.0.4"
|
|
4
|
+
authors:
|
|
5
|
+
- "FMRIB Analysis Group, University of Oxford"
|
|
6
|
+
urls:
|
|
7
|
+
- "https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FLIRT"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
/// FMRIB's Linear Image Registration Tool (affine intra-modal registration).
|
|
11
|
+
flirt: seq(
|
|
12
|
+
/// Input volume to register.
|
|
13
|
+
opt("-in", infile: path),
|
|
14
|
+
|
|
15
|
+
/// Reference volume that defines the target space.
|
|
16
|
+
opt("-ref", reference: path),
|
|
17
|
+
|
|
18
|
+
set(
|
|
19
|
+
/// Registered output volume.
|
|
20
|
+
opt("-out", outbase: str).output(outvol: `{outbase}.nii.gz`),
|
|
21
|
+
|
|
22
|
+
/// Text file the estimated affine transform is written to.
|
|
23
|
+
opt("-omat", outmatrix: str),
|
|
24
|
+
|
|
25
|
+
/// Number of histogram bins used for the cost function.
|
|
26
|
+
opt("-bins", bins: int.min(1) = 256),
|
|
27
|
+
|
|
28
|
+
/// Degrees of freedom for the transform (6 rigid, 12 full affine).
|
|
29
|
+
opt("-dof", dof: int.min(3).max(12) = 12),
|
|
30
|
+
|
|
31
|
+
/// Cost function to optimize.
|
|
32
|
+
opt("-cost", cost: alt(
|
|
33
|
+
"mutualinfo",
|
|
34
|
+
"corratio",
|
|
35
|
+
"normcorr",
|
|
36
|
+
"normmi",
|
|
37
|
+
"leastsq",
|
|
38
|
+
) = "corratio"),
|
|
39
|
+
|
|
40
|
+
/// Final interpolation applied when writing `-out`. The union carries a
|
|
41
|
+
/// default variant (`trilinear`), which the frontend keeps as the node
|
|
42
|
+
/// default.
|
|
43
|
+
opt("-interp", interp: alt(
|
|
44
|
+
"trilinear",
|
|
45
|
+
"nearestneighbour",
|
|
46
|
+
"sinc",
|
|
47
|
+
"spline",
|
|
48
|
+
) = "trilinear"),
|
|
49
|
+
),
|
|
50
|
+
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
exe: "wb_command"
|
|
3
|
+
version: "1.5.0"
|
|
4
|
+
authors:
|
|
5
|
+
- "Washington University School of Medicine"
|
|
6
|
+
urls:
|
|
7
|
+
- "https://www.humanconnectome.org/software/connectome-workbench"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
/// Reduce a volume file along the map axis to a single output map.
|
|
11
|
+
///
|
|
12
|
+
/// The executable is `wb_command` and the subcommand is `-volume-reduce`, so
|
|
13
|
+
/// the tool id (`volume_reduce`) deliberately differs from the exe.
|
|
14
|
+
volume_reduce: seq(
|
|
15
|
+
"-volume-reduce",
|
|
16
|
+
|
|
17
|
+
/// The input volume.
|
|
18
|
+
volume_in: path,
|
|
19
|
+
|
|
20
|
+
/// The reduction operation applied across the maps.
|
|
21
|
+
operation: alt(
|
|
22
|
+
"MAX",
|
|
23
|
+
"MIN",
|
|
24
|
+
"MEAN",
|
|
25
|
+
"SUM",
|
|
26
|
+
"STDEV",
|
|
27
|
+
"VARIANCE",
|
|
28
|
+
"TSNR",
|
|
29
|
+
),
|
|
30
|
+
|
|
31
|
+
/// The output volume.
|
|
32
|
+
volume_out: str,
|
|
33
|
+
|
|
34
|
+
/// Exclude outliers by standard deviation before reducing.
|
|
35
|
+
opt("-exclude-outliers", sigma_below: float.min(0), sigma_above: float.min(0)),
|
|
36
|
+
).output(
|
|
37
|
+
/// The reduced output volume.
|
|
38
|
+
reduced: `{volume_out}`,
|
|
39
|
+
)
|
|
@@ -15,9 +15,21 @@ export type Combinator = "seq" | "set" | "opt" | "rep" | "alt" | "any";
|
|
|
15
15
|
/** A terminal keyword. */
|
|
16
16
|
export type Terminal = "int" | "float" | "str" | "path";
|
|
17
17
|
|
|
18
|
+
/** 1-based source position of a construct, for diagnostics. Points at the node's
|
|
19
|
+
* primary token (a terminal/combinator keyword, a literal token, or the opening
|
|
20
|
+
* paren of a group), which is where a misplaced modifier or bad decoration is
|
|
21
|
+
* most usefully reported. */
|
|
22
|
+
export interface SourceSpan {
|
|
23
|
+
line: number;
|
|
24
|
+
column: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
18
27
|
export interface AstNode {
|
|
19
28
|
kind: "literal" | "terminal" | "comb" | "ref";
|
|
20
29
|
|
|
30
|
+
/** Source position of the node's primary token, if known. */
|
|
31
|
+
span?: SourceSpan;
|
|
32
|
+
|
|
21
33
|
/** kind === "literal": the fixed token string. */
|
|
22
34
|
value?: string;
|
|
23
35
|
/** kind === "terminal": which terminal. */
|
|
@@ -36,7 +48,8 @@ export interface AstNode {
|
|
|
36
48
|
title?: string;
|
|
37
49
|
/** Description: a `///` block (minus the title), or `.description("...")`. */
|
|
38
50
|
description?: string;
|
|
39
|
-
/** `= value` sugar / `.default(...)
|
|
51
|
+
/** `= value` sugar / `.default(...)`. Meaningful on a terminal and on
|
|
52
|
+
* `opt`/`alt`/`rep`; dropped with a warning on a `seq`/`set` struct. */
|
|
40
53
|
default?: string | number;
|
|
41
54
|
/** `.min(n)` (int/float only). */
|
|
42
55
|
min?: number;
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Frontmatter handling for argtype documents.
|
|
3
3
|
*
|
|
4
4
|
* A document may begin with a `---` fenced block of YAML-ish metadata (`exe`,
|
|
5
|
-
* `version`, `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* mappings. That covers every key the lowering step
|
|
9
|
-
* exotic is read as a raw string and ignored downstream.
|
|
5
|
+
* `version`, `container`, `authors`, `stdout`, ...). Rather than pull in a full
|
|
6
|
+
* YAML dependency, this parses the small subset the format uses: scalar
|
|
7
|
+
* `key: value`, block sequences (`- item`), inline flow sequences (`[a, b]`),
|
|
8
|
+
* and one level of nested mappings. That covers every key the lowering step
|
|
9
|
+
* consumes; anything more exotic is read as a raw string and ignored downstream.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
export interface SplitResult {
|
|
@@ -174,7 +174,51 @@ function unescapeScalar(s: string): string {
|
|
|
174
174
|
);
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
/** Split a flow-sequence body (`a, "b, c", [d, e]`) on top-level commas, keeping
|
|
178
|
+
* commas inside quotes or a nested `[...]` intact. Empty segments (from a leading,
|
|
179
|
+
* trailing, or doubled comma, or an empty `[]` body) are dropped, matching YAML;
|
|
180
|
+
* a quoted empty string keeps its quotes and so survives. */
|
|
181
|
+
function splitFlow(body: string): string[] {
|
|
182
|
+
const parts: string[] = [];
|
|
183
|
+
let depth = 0;
|
|
184
|
+
let inQuote: string | null = null;
|
|
185
|
+
let cur = "";
|
|
186
|
+
for (let i = 0; i < body.length; i++) {
|
|
187
|
+
const ch = body[i]!;
|
|
188
|
+
if (inQuote === '"' && ch === "\\") {
|
|
189
|
+
cur += ch + (body[i + 1] ?? "");
|
|
190
|
+
i++;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (inQuote) {
|
|
194
|
+
if (ch === inQuote) inQuote = null;
|
|
195
|
+
cur += ch;
|
|
196
|
+
} else if (ch === '"' || ch === "'") {
|
|
197
|
+
inQuote = ch;
|
|
198
|
+
cur += ch;
|
|
199
|
+
} else if (ch === "[") {
|
|
200
|
+
depth++;
|
|
201
|
+
cur += ch;
|
|
202
|
+
} else if (ch === "]") {
|
|
203
|
+
depth--;
|
|
204
|
+
cur += ch;
|
|
205
|
+
} else if (ch === "," && depth === 0) {
|
|
206
|
+
parts.push(cur);
|
|
207
|
+
cur = "";
|
|
208
|
+
} else {
|
|
209
|
+
cur += ch;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
parts.push(cur);
|
|
213
|
+
return parts.map((p) => p.trim()).filter((p) => p !== "");
|
|
214
|
+
}
|
|
215
|
+
|
|
177
216
|
function parseScalar(text: string): unknown {
|
|
217
|
+
// Inline flow sequence: `[a, b, c]` (valid YAML the block-list parser would
|
|
218
|
+
// otherwise read as a bare string). `[]` is the empty list.
|
|
219
|
+
if (text.startsWith("[") && text.endsWith("]") && text.length >= 2) {
|
|
220
|
+
return splitFlow(text.slice(1, -1)).map(parseScalar);
|
|
221
|
+
}
|
|
178
222
|
if (text.startsWith('"') && text.endsWith('"') && text.length >= 2) {
|
|
179
223
|
return unescapeScalar(text.slice(1, -1));
|
|
180
224
|
}
|
|
@@ -195,6 +195,26 @@ export function lex(source: string): LexResult {
|
|
|
195
195
|
});
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
|
+
// A plain integer run abutting a letter/underscore (`3dTstat`, all of
|
|
199
|
+
// AFNI's `3d*` tools) is a digit-led identifier: not a valid number and
|
|
200
|
+
// not a valid bare identifier. Consume the whole run and emit the fix
|
|
201
|
+
// directly rather than splitting into `number` + `ident` (which surfaces
|
|
202
|
+
// as a baffling "expected a definition"); recover as an `ident` so the
|
|
203
|
+
// rest still parses. Only a pure integer run qualifies: a decimal,
|
|
204
|
+
// signed, or malformed-exponent `num` is a broken number, not a name, so
|
|
205
|
+
// it keeps its own number/error path instead of being quoted as an ident.
|
|
206
|
+
if (/^\d+$/.test(num) && isIdentStart(peek())) {
|
|
207
|
+
let rest = "";
|
|
208
|
+
while (i < source.length && isIdentPart(peek())) rest += advance();
|
|
209
|
+
const full = num + rest;
|
|
210
|
+
errors.push({
|
|
211
|
+
message: `Identifier cannot start with a digit; quote it as "${full}"`,
|
|
212
|
+
line: startLine,
|
|
213
|
+
column: startCol,
|
|
214
|
+
});
|
|
215
|
+
push("ident", full, startLine, startCol);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
198
218
|
push("number", num, startLine, startCol);
|
|
199
219
|
continue;
|
|
200
220
|
}
|