@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
|
@@ -372,12 +372,21 @@ export class BoutiquesParser implements Frontend {
|
|
|
372
372
|
case InputTypePrimitive.Integer: {
|
|
373
373
|
const node: Int = { kind: "int", attrs: {} };
|
|
374
374
|
if (isNumber(btInput.minimum)) {
|
|
375
|
-
|
|
376
|
-
|
|
375
|
+
// Smallest valid integer: for a fractional bound, `>= 5.7` allows 6
|
|
376
|
+
// (ceil) and `> 5.7` allows 6 (floor + 1); both differ from a plain
|
|
377
|
+
// floor.
|
|
378
|
+
node.attrs.minValue =
|
|
379
|
+
btInput["exclusive-minimum"] === true
|
|
380
|
+
? Math.floor(btInput.minimum) + 1
|
|
381
|
+
: Math.ceil(btInput.minimum);
|
|
377
382
|
}
|
|
378
383
|
if (isNumber(btInput.maximum)) {
|
|
379
|
-
|
|
380
|
-
|
|
384
|
+
// Largest valid integer: `<= 5.7` allows 5 (floor) and `< 5.7` allows
|
|
385
|
+
// 5 (ceil - 1).
|
|
386
|
+
node.attrs.maxValue =
|
|
387
|
+
btInput["exclusive-maximum"] === true
|
|
388
|
+
? Math.ceil(btInput.maximum) - 1
|
|
389
|
+
: Math.floor(btInput.maximum);
|
|
381
390
|
}
|
|
382
391
|
if (meta) node.meta = meta;
|
|
383
392
|
return node;
|
|
@@ -424,7 +433,10 @@ export class BoutiquesParser implements Frontend {
|
|
|
424
433
|
return null;
|
|
425
434
|
}
|
|
426
435
|
const node = this.parseDescriptor(nested);
|
|
427
|
-
|
|
436
|
+
// Merge (not overwrite) so the subcommand's own meta - notably its
|
|
437
|
+
// `output-files`, attached by `parseDescriptor` - survives; the input's
|
|
438
|
+
// meta (name/doc) layers on top.
|
|
439
|
+
if (node && meta) node.meta = { ...node.meta, ...meta };
|
|
428
440
|
return node;
|
|
429
441
|
}
|
|
430
442
|
|
|
@@ -553,12 +565,19 @@ export class BoutiquesParser implements Frontend {
|
|
|
553
565
|
}
|
|
554
566
|
|
|
555
567
|
// Hoist metadata (doc, default) to outermost wrapper so backends find it
|
|
556
|
-
// on the binding node. Keep name on inner for solver's findDeepName
|
|
568
|
+
// on the binding node. Keep `name` on inner for the solver's findDeepName,
|
|
569
|
+
// and keep `outputs` there too: outputs are a scope concept declared on the
|
|
570
|
+
// subcommand struct, not a property of the list/flag wrapper - hoisting them
|
|
571
|
+
// onto a repeat would move the output scope off the struct and make backends
|
|
572
|
+
// (which resolve outputs against the struct) silently drop them.
|
|
557
573
|
if (node !== inner && inner.meta) {
|
|
558
|
-
const { name, ...rest } = inner.meta;
|
|
574
|
+
const { name, outputs, ...rest } = inner.meta;
|
|
559
575
|
if (Object.keys(rest).length > 0) {
|
|
560
576
|
node.meta = { ...node.meta, ...rest };
|
|
561
|
-
|
|
577
|
+
const kept: NodeMeta = {};
|
|
578
|
+
if (name) kept.name = name;
|
|
579
|
+
if (outputs) kept.outputs = outputs;
|
|
580
|
+
inner.meta = Object.keys(kept).length > 0 ? kept : undefined;
|
|
562
581
|
}
|
|
563
582
|
}
|
|
564
583
|
|
|
@@ -38,6 +38,19 @@ export const canonicalize: Pass = {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// Identity for dedup: structure plus the meta that makes two structurally
|
|
42
|
+
// identical union arms semantically distinct - the variant tag (@type
|
|
43
|
+
// discriminant), name, and attached outputs. Deduping on structure alone
|
|
44
|
+
// would silently drop a distinct arm (e.g. two sub-commands with the same
|
|
45
|
+
// inner shape but different @type), making its variant unreachable.
|
|
46
|
+
function identityKey(node: Expr): string {
|
|
47
|
+
const m = node.meta;
|
|
48
|
+
const metaKey = m
|
|
49
|
+
? [m.name ?? "", m.variantTag ?? "", m.outputs ? JSON.stringify(m.outputs) : ""].join("|")
|
|
50
|
+
: "";
|
|
51
|
+
return `${structuralHash(node)}#${metaKey}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
41
54
|
function sortKey(node: Expr): string {
|
|
42
55
|
const name = node.meta?.name ?? "";
|
|
43
56
|
return `${node.kind}:${name}:${structuralHash(node)}`;
|
|
@@ -51,13 +64,13 @@ export const canonicalize: Pass = {
|
|
|
51
64
|
// Sort alternatives
|
|
52
65
|
const sorted = [...children].sort((a, b) => sortKey(a).localeCompare(sortKey(b)));
|
|
53
66
|
|
|
54
|
-
// Deduplicate
|
|
67
|
+
// Deduplicate identical arms (structure + discriminating meta).
|
|
55
68
|
const seen = new Set<string>();
|
|
56
69
|
const alts: Expr[] = [];
|
|
57
70
|
for (const child of sorted) {
|
|
58
|
-
const
|
|
59
|
-
if (!seen.has(
|
|
60
|
-
seen.add(
|
|
71
|
+
const key = identityKey(child);
|
|
72
|
+
if (!seen.has(key)) {
|
|
73
|
+
seen.add(key);
|
|
61
74
|
alts.push(child);
|
|
62
75
|
} else {
|
|
63
76
|
changed = true;
|
|
@@ -67,7 +80,7 @@ export const canonicalize: Pass = {
|
|
|
67
80
|
// Check if order changed
|
|
68
81
|
if (
|
|
69
82
|
alts.length !== children.length ||
|
|
70
|
-
alts.some((alt, i) =>
|
|
83
|
+
alts.some((alt, i) => identityKey(alt) !== identityKey(children[i]!))
|
|
71
84
|
) {
|
|
72
85
|
changed = true;
|
|
73
86
|
}
|
|
@@ -127,15 +127,30 @@ export const simplify: Pass = {
|
|
|
127
127
|
node.attrs.join === ""
|
|
128
128
|
) {
|
|
129
129
|
changed = true;
|
|
130
|
-
prev
|
|
130
|
+
// Replace with a fresh node - `prev` is the original literal object
|
|
131
|
+
// (the `literal` case returns `node` unchanged), so mutating it in
|
|
132
|
+
// place would corrupt the caller's IR and double-append on rerun.
|
|
133
|
+
nodes[nodes.length - 1] = {
|
|
134
|
+
...prev,
|
|
135
|
+
attrs: { ...prev.attrs, str: prev.attrs.str + child.attrs.str },
|
|
136
|
+
};
|
|
131
137
|
} else {
|
|
132
138
|
nodes.push(child);
|
|
133
139
|
}
|
|
134
140
|
}
|
|
135
141
|
|
|
136
|
-
// seq(T) -> T, carrying the seq's meta down onto the child.
|
|
142
|
+
// seq(T) -> T, carrying the seq's meta down onto the child. Skip the
|
|
143
|
+
// collapse when the sequence declares outputs and the sole child is a
|
|
144
|
+
// literal: outputs need a scope binding, which the solver only forces
|
|
145
|
+
// on sequences (not on literals, which are never bound). Collapsing a
|
|
146
|
+
// parameterless output-bearing command (e.g. `seq(lit("run"))` with an
|
|
147
|
+
// output-file) onto its literal would orphan the output. A field child
|
|
148
|
+
// (str/path) does get bound, so those still collapse.
|
|
137
149
|
if (nodes.length === 1) {
|
|
138
150
|
const child = nodes[0]!;
|
|
151
|
+
if (node.meta?.outputs?.length && child.kind === "literal") {
|
|
152
|
+
return { ...node, attrs: { ...node.attrs, nodes } };
|
|
153
|
+
}
|
|
139
154
|
changed = true;
|
|
140
155
|
const mergedMeta = mergeMeta(node.meta, child.meta);
|
|
141
156
|
return mergedMeta ? { ...child, meta: mergedMeta } : child;
|
package/src/solver/solver.ts
CHANGED
|
@@ -45,11 +45,11 @@ function isBooleanLiteralPair(variants: Array<{ type: BoundType }>): boolean {
|
|
|
45
45
|
return false;
|
|
46
46
|
}
|
|
47
47
|
const [a, b] = variants.map((v) => (v.type.kind === "literal" ? v.type.value : null));
|
|
48
|
+
// `literalFromNode` canonicalizes clean-int literals, so "0"/"1" arrive as the
|
|
49
|
+
// numbers 0/1 (never strings); only "false"/"true" survive as strings.
|
|
48
50
|
return (
|
|
49
51
|
(a === 0 && b === 1) ||
|
|
50
52
|
(a === 1 && b === 0) ||
|
|
51
|
-
(a === "0" && b === "1") ||
|
|
52
|
-
(a === "1" && b === "0") ||
|
|
53
53
|
(a === "false" && b === "true") ||
|
|
54
54
|
(a === "true" && b === "false")
|
|
55
55
|
);
|