capdag 0.106.243 → 0.109.248
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/cap-graph-renderer.js +283 -168
- package/capdag.test.js +154 -60
- package/machine-parser.js +126 -64
- package/package.json +1 -1
package/cap-graph-renderer.js
CHANGED
|
@@ -663,22 +663,38 @@ function classifyStrandCapSteps(steps) {
|
|
|
663
663
|
return { capStepIndices, capFlags };
|
|
664
664
|
}
|
|
665
665
|
|
|
666
|
-
// Build the strand graph
|
|
667
|
-
// (`
|
|
668
|
-
//
|
|
666
|
+
// Build the strand graph by mirroring capdag's plan builder
|
|
667
|
+
// (`capdag/src/planner/plan_builder.rs::build_plan_from_path`). The plan
|
|
668
|
+
// builder is the authoritative source of truth for how strand steps
|
|
669
|
+
// translate into a DAG of nodes and edges:
|
|
669
670
|
//
|
|
670
|
-
// *
|
|
671
|
-
//
|
|
672
|
-
//
|
|
673
|
-
//
|
|
671
|
+
// * Node IDs are positional: `input_slot`, `step_0`, `step_1`, …,
|
|
672
|
+
// `output`. They are NOT media URN strings — URN comparisons for
|
|
673
|
+
// graph topology are wrong because the planner connects steps by
|
|
674
|
+
// the order-theoretic `conformsTo` relation, not by string equality.
|
|
675
|
+
// * `prev_node_id` is a single running pointer, only advanced by Cap
|
|
676
|
+
// steps. ForEach steps mark the start of a body span without
|
|
677
|
+
// advancing prev; the body's first Cap still connects to whatever
|
|
678
|
+
// was before the ForEach.
|
|
679
|
+
// * Cap inside a ForEach body connects from `prev_node_id` like any
|
|
680
|
+
// other cap, AND tracks `body_entry` (first cap in body) and
|
|
681
|
+
// `body_exit` (most recent cap in body).
|
|
682
|
+
// * Collect after a ForEach body creates a ForEach node with
|
|
683
|
+
// boundaries, an iteration edge to body_entry, a Collect node, and
|
|
684
|
+
// a collection edge from body_exit to Collect. prev_node_id becomes
|
|
685
|
+
// the Collect node.
|
|
686
|
+
// * Standalone Collect (no enclosing ForEach) creates a Collect node
|
|
687
|
+
// consuming prev_node_id directly.
|
|
688
|
+
// * Unclosed ForEach with no body caps is a terminal unwrap — the
|
|
689
|
+
// ForEach node is skipped; prev_node_id stays as-is.
|
|
690
|
+
// * Unclosed ForEach WITH body caps gets a ForEach node, iteration
|
|
691
|
+
// edge to body_entry, and prev_node_id becomes body_exit.
|
|
674
692
|
//
|
|
675
|
-
//
|
|
676
|
-
//
|
|
677
|
-
//
|
|
678
|
-
//
|
|
679
|
-
//
|
|
680
|
-
// `target_spec` differs from the last cap step's `to_spec` (the Collect
|
|
681
|
-
// shape transition).
|
|
693
|
+
// Node labels come from the `media_display_names` map keyed by the
|
|
694
|
+
// step's canonical URN (or source_spec/target_spec for the boundary
|
|
695
|
+
// nodes). ForEach and Collect nodes display "for each" / "collect".
|
|
696
|
+
// Cap edges carry the cap title plus cardinality marker when either
|
|
697
|
+
// input or output is a sequence.
|
|
682
698
|
function buildStrandGraphData(data) {
|
|
683
699
|
validateStrandPayload(data);
|
|
684
700
|
|
|
@@ -686,143 +702,197 @@ function buildStrandGraphData(data) {
|
|
|
686
702
|
const sourceSpec = canonicalMediaUrn(data.source_spec);
|
|
687
703
|
const targetSpec = canonicalMediaUrn(data.target_spec);
|
|
688
704
|
|
|
689
|
-
|
|
705
|
+
// Look up a display name for a media URN via the host-supplied map.
|
|
706
|
+
// Uses `MediaUrn.isEquivalent` so tag-order variation doesn't defeat
|
|
707
|
+
// the lookup — URNs are compared semantically, never as raw strings.
|
|
708
|
+
const MediaUrn = requireHostDependency('MediaUrn');
|
|
709
|
+
const displayEntries = [];
|
|
690
710
|
for (const [urn, display] of Object.entries(mediaDisplayNames)) {
|
|
691
|
-
if (typeof display
|
|
692
|
-
|
|
711
|
+
if (typeof display !== 'string' || display.length === 0) continue;
|
|
712
|
+
try {
|
|
713
|
+
displayEntries.push({ media: MediaUrn.fromString(urn), display });
|
|
714
|
+
} catch (_) {
|
|
715
|
+
// Skip entries with unparseable URN keys — the host payload is
|
|
716
|
+
// trusted, but malformed keys are not fatal.
|
|
693
717
|
}
|
|
694
718
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
const displayName = canonicalDisplayLookup.get(canonicalUrn);
|
|
700
|
-
nodesMap.set(canonicalUrn, {
|
|
701
|
-
id: canonicalUrn,
|
|
702
|
-
label: displayName !== undefined ? displayName : mediaNodeLabel(canonicalUrn),
|
|
703
|
-
fullUrn: canonicalUrn,
|
|
704
|
-
});
|
|
719
|
+
function displayNameFor(canonicalUrn) {
|
|
720
|
+
const candidate = MediaUrn.fromString(canonicalUrn);
|
|
721
|
+
for (const entry of displayEntries) {
|
|
722
|
+
if (candidate.isEquivalent(entry.media)) return entry.display;
|
|
705
723
|
}
|
|
706
|
-
return canonicalUrn;
|
|
724
|
+
return mediaNodeLabel(canonicalUrn);
|
|
707
725
|
}
|
|
708
726
|
|
|
709
|
-
|
|
710
|
-
ensureNode(targetSpec);
|
|
711
|
-
|
|
712
|
-
const { capStepIndices } = classifyStrandCapSteps(data.steps);
|
|
713
|
-
const firstCapIdx = capStepIndices.length > 0 ? capStepIndices[0] : -1;
|
|
714
|
-
const lastCapIdx = capStepIndices.length > 0 ? capStepIndices[capStepIndices.length - 1] : -1;
|
|
715
|
-
const hasLeadingForEach = data.steps.some((s, i) =>
|
|
716
|
-
Object.keys(s.step_type)[0] === 'ForEach' && i < (firstCapIdx === -1 ? Infinity : firstCapIdx));
|
|
717
|
-
const hasTrailingCollect = data.steps.some((s, i) =>
|
|
718
|
-
Object.keys(s.step_type)[0] === 'Collect' && i > (lastCapIdx === -1 ? -Infinity : lastCapIdx));
|
|
719
|
-
|
|
727
|
+
const nodes = [];
|
|
720
728
|
const edges = [];
|
|
721
|
-
|
|
729
|
+
const nodeIds = new Set();
|
|
722
730
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
edges.push({
|
|
733
|
-
id: `strand-edge-${capEdgeIdx}`,
|
|
734
|
-
source: sourceSpec,
|
|
735
|
-
target: firstCapFrom,
|
|
736
|
-
title: label || 'fan-out',
|
|
737
|
-
label,
|
|
738
|
-
cardinality: '',
|
|
739
|
-
capUrn: '',
|
|
740
|
-
color: edgeHueColor(capEdgeIdx),
|
|
741
|
-
});
|
|
742
|
-
capEdgeIdx++;
|
|
743
|
-
}
|
|
731
|
+
function addNode(id, label, fullUrn, nodeClass) {
|
|
732
|
+
if (nodeIds.has(id)) return;
|
|
733
|
+
nodeIds.add(id);
|
|
734
|
+
nodes.push({
|
|
735
|
+
id,
|
|
736
|
+
label,
|
|
737
|
+
fullUrn: fullUrn || '',
|
|
738
|
+
nodeClass: nodeClass || '',
|
|
739
|
+
});
|
|
744
740
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
741
|
+
let edgeCounter = 0;
|
|
742
|
+
function addEdge(source, target, label, title, fullUrn, edgeClass) {
|
|
743
|
+
edges.push({
|
|
744
|
+
id: `strand-edge-${edgeCounter}`,
|
|
745
|
+
source,
|
|
746
|
+
target,
|
|
747
|
+
label: label || '',
|
|
748
|
+
title: title || '',
|
|
749
|
+
fullUrn: fullUrn || '',
|
|
750
|
+
edgeClass: edgeClass || '',
|
|
751
|
+
color: edgeHueColor(edgeCounter),
|
|
752
|
+
});
|
|
753
|
+
edgeCounter++;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Entry node — the strand's source media spec.
|
|
757
|
+
const inputSlotId = 'input_slot';
|
|
758
|
+
addNode(inputSlotId, displayNameFor(sourceSpec), sourceSpec, 'strand-source');
|
|
759
|
+
|
|
760
|
+
let prevNodeId = inputSlotId;
|
|
761
|
+
|
|
762
|
+
// Track ForEach body membership. `insideForEachBody = { index, nodeId }`
|
|
763
|
+
// records which ForEach step we're inside and the id we'll give its
|
|
764
|
+
// eventual node. `bodyEntry`/`bodyExit` track the first and most
|
|
765
|
+
// recent Cap step inside that body.
|
|
766
|
+
let insideForEachBody = null;
|
|
767
|
+
let bodyEntry = null;
|
|
768
|
+
let bodyExit = null;
|
|
769
|
+
|
|
770
|
+
// Finalize an outer ForEach body when a nested ForEach starts before
|
|
771
|
+
// the outer's Collect. Mirrors plan_builder.rs:238-289.
|
|
772
|
+
function finalizeOuterForEach(outerForEach, outerEntry, outerExit) {
|
|
773
|
+
const outerForEachInput = outerForEach.index === 0
|
|
774
|
+
? inputSlotId
|
|
775
|
+
: `step_${outerForEach.index - 1}`;
|
|
776
|
+
// Create the ForEach node + direct edge from its input + iteration
|
|
777
|
+
// edge into the body's first cap.
|
|
778
|
+
addNode(outerForEach.nodeId, 'for each', '', 'strand-foreach');
|
|
779
|
+
addEdge(outerForEachInput, outerForEach.nodeId, 'for each', 'for each', '', 'strand-iteration');
|
|
780
|
+
addEdge(outerForEach.nodeId, outerEntry, '', '', '', 'strand-iteration');
|
|
781
|
+
return outerExit;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
data.steps.forEach((step, i) => {
|
|
755
785
|
const variant = Object.keys(step.step_type)[0];
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
786
|
+
const nodeId = `step_${i}`;
|
|
787
|
+
|
|
788
|
+
if (variant === 'Cap') {
|
|
789
|
+
const body = step.step_type.Cap;
|
|
790
|
+
const toCanonical = canonicalMediaUrn(step.to_spec);
|
|
791
|
+
addNode(nodeId, displayNameFor(toCanonical), toCanonical, 'strand-cap');
|
|
792
|
+
|
|
793
|
+
let label = body.title;
|
|
794
|
+
const cardinality = cardinalityLabel(body.input_is_sequence, body.output_is_sequence);
|
|
795
|
+
if (cardinality !== '1\u21921') {
|
|
796
|
+
label = `${label} (${cardinality})`;
|
|
797
|
+
}
|
|
798
|
+
addEdge(prevNodeId, nodeId, label, body.title, body.cap_urn, 'strand-cap-edge');
|
|
799
|
+
|
|
800
|
+
if (insideForEachBody !== null) {
|
|
801
|
+
if (bodyEntry === null) bodyEntry = nodeId;
|
|
802
|
+
bodyExit = nodeId;
|
|
803
|
+
}
|
|
762
804
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
if (cardinality !== '1\u21921') {
|
|
766
|
-
label = `${label} (${cardinality})`;
|
|
805
|
+
prevNodeId = nodeId;
|
|
806
|
+
return;
|
|
767
807
|
}
|
|
768
808
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
809
|
+
if (variant === 'ForEach') {
|
|
810
|
+
// If we're already inside a ForEach body when another ForEach
|
|
811
|
+
// starts, finalize the outer one first.
|
|
812
|
+
if (insideForEachBody !== null) {
|
|
813
|
+
const outer = insideForEachBody;
|
|
814
|
+
const entry = bodyEntry !== null ? bodyEntry : prevNodeId;
|
|
815
|
+
const exit = bodyExit !== null ? bodyExit : prevNodeId;
|
|
816
|
+
if (bodyEntry === null) {
|
|
817
|
+
// Outer ForEach with no body caps is an illegal nesting; the
|
|
818
|
+
// plan builder throws. Mirror that.
|
|
819
|
+
throw new Error(
|
|
820
|
+
`CapGraphRenderer strand: nested ForEach at step[${i}] but outer ForEach at step[${outer.index}] has no body caps`
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
prevNodeId = finalizeOuterForEach(outer, entry, exit);
|
|
824
|
+
bodyEntry = null;
|
|
825
|
+
bodyExit = null;
|
|
826
|
+
}
|
|
827
|
+
insideForEachBody = { index: i, nodeId };
|
|
828
|
+
bodyEntry = null;
|
|
829
|
+
bodyExit = null;
|
|
830
|
+
// Do NOT advance prevNodeId — the body's first cap will connect
|
|
831
|
+
// to whatever was before the ForEach.
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
781
834
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
835
|
+
if (variant === 'Collect') {
|
|
836
|
+
if (insideForEachBody !== null) {
|
|
837
|
+
const outer = insideForEachBody;
|
|
838
|
+
const entry = bodyEntry !== null ? bodyEntry : prevNodeId;
|
|
839
|
+
const exit = bodyExit !== null ? bodyExit : prevNodeId;
|
|
840
|
+
const outerForEachInput = outer.index === 0
|
|
841
|
+
? inputSlotId
|
|
842
|
+
: `step_${outer.index - 1}`;
|
|
843
|
+
|
|
844
|
+
addNode(outer.nodeId, 'for each', '', 'strand-foreach');
|
|
845
|
+
addEdge(outerForEachInput, outer.nodeId, 'for each', 'for each', '', 'strand-iteration');
|
|
846
|
+
addEdge(outer.nodeId, entry, '', '', '', 'strand-iteration');
|
|
847
|
+
|
|
848
|
+
addNode(nodeId, 'collect', '', 'strand-collect');
|
|
849
|
+
addEdge(exit, nodeId, 'collect', 'collect', '', 'strand-collection');
|
|
850
|
+
|
|
851
|
+
insideForEachBody = null;
|
|
852
|
+
bodyEntry = null;
|
|
853
|
+
bodyExit = null;
|
|
854
|
+
prevNodeId = nodeId;
|
|
855
|
+
} else {
|
|
856
|
+
// Standalone Collect — scalar → list-of-one. Mirrors
|
|
857
|
+
// plan_builder.rs:333-355.
|
|
858
|
+
addNode(nodeId, 'collect', '', 'strand-collect');
|
|
859
|
+
addEdge(prevNodeId, nodeId, 'collect', 'collect', '', 'strand-collection');
|
|
860
|
+
prevNodeId = nodeId;
|
|
861
|
+
}
|
|
862
|
+
return;
|
|
801
863
|
}
|
|
802
|
-
}
|
|
803
864
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
// degenerate strands (e.g. identity).
|
|
807
|
-
if (firstCapIdx === -1 && sourceSpec !== targetSpec) {
|
|
808
|
-
edges.push({
|
|
809
|
-
id: `strand-edge-${capEdgeIdx}`,
|
|
810
|
-
source: sourceSpec,
|
|
811
|
-
target: targetSpec,
|
|
812
|
-
title: '',
|
|
813
|
-
label: '',
|
|
814
|
-
cardinality: '',
|
|
815
|
-
capUrn: '',
|
|
816
|
-
color: edgeHueColor(capEdgeIdx),
|
|
817
|
-
});
|
|
818
|
-
}
|
|
865
|
+
throw new Error(`CapGraphRenderer strand: unknown step_type variant '${variant}' at step[${i}]`);
|
|
866
|
+
});
|
|
819
867
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
868
|
+
// Handle unclosed ForEach after the walk. Mirrors plan_builder.rs:362-428.
|
|
869
|
+
if (insideForEachBody !== null) {
|
|
870
|
+
const outer = insideForEachBody;
|
|
871
|
+
const hasBodyEntry = bodyEntry !== null;
|
|
872
|
+
if (hasBodyEntry) {
|
|
873
|
+
const entry = bodyEntry;
|
|
874
|
+
const exit = bodyExit;
|
|
875
|
+
const outerForEachInput = outer.index === 0
|
|
876
|
+
? inputSlotId
|
|
877
|
+
: `step_${outer.index - 1}`;
|
|
878
|
+
addNode(outer.nodeId, 'for each', '', 'strand-foreach');
|
|
879
|
+
addEdge(outerForEachInput, outer.nodeId, 'for each', 'for each', '', 'strand-iteration');
|
|
880
|
+
addEdge(outer.nodeId, entry, '', '', '', 'strand-iteration');
|
|
881
|
+
prevNodeId = exit;
|
|
882
|
+
}
|
|
883
|
+
// hasBodyEntry === false is a terminal unwrap — skip the ForEach
|
|
884
|
+
// node entirely, prev_node_id stays as-is.
|
|
885
|
+
insideForEachBody = null;
|
|
886
|
+
bodyEntry = null;
|
|
887
|
+
bodyExit = null;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Final output node. Mirrors plan_builder.rs:430-432.
|
|
891
|
+
const outputId = 'output';
|
|
892
|
+
addNode(outputId, displayNameFor(targetSpec), targetSpec, 'strand-target');
|
|
893
|
+
addEdge(prevNodeId, outputId, '', '', '', 'strand-cap-edge');
|
|
894
|
+
|
|
895
|
+
return { nodes, edges, sourceSpec, targetSpec };
|
|
826
896
|
}
|
|
827
897
|
|
|
828
898
|
function strandCytoscapeElements(built) {
|
|
@@ -833,9 +903,7 @@ function strandCytoscapeElements(built) {
|
|
|
833
903
|
label: node.label,
|
|
834
904
|
fullUrn: node.fullUrn,
|
|
835
905
|
},
|
|
836
|
-
classes: node.
|
|
837
|
-
: node.id === built.targetSpec ? 'strand-target'
|
|
838
|
-
: '',
|
|
906
|
+
classes: node.nodeClass || '',
|
|
839
907
|
}));
|
|
840
908
|
const edgeElements = built.edges.map(edge => ({
|
|
841
909
|
group: 'edges',
|
|
@@ -845,10 +913,10 @@ function strandCytoscapeElements(built) {
|
|
|
845
913
|
target: edge.target,
|
|
846
914
|
label: edge.label,
|
|
847
915
|
title: edge.title,
|
|
848
|
-
|
|
849
|
-
fullUrn: edge.capUrn,
|
|
916
|
+
fullUrn: edge.fullUrn,
|
|
850
917
|
color: edge.color,
|
|
851
918
|
},
|
|
919
|
+
classes: edge.edgeClass || '',
|
|
852
920
|
}));
|
|
853
921
|
return nodeElements.concat(edgeElements);
|
|
854
922
|
}
|
|
@@ -870,6 +938,39 @@ function findCapStepIndexByUrn(steps, targetUrnString) {
|
|
|
870
938
|
return -1;
|
|
871
939
|
}
|
|
872
940
|
|
|
941
|
+
// Remove nodes and edges belonging to the ForEach body's interior cap
|
|
942
|
+
// steps from a strand backbone. In run mode, these are replaced by
|
|
943
|
+
// per-body replicas; keeping the prototype chain alongside the
|
|
944
|
+
// replicas produces a confusing double-render. The ForEach and Collect
|
|
945
|
+
// nodes themselves, plus their iteration/collection edges, stay.
|
|
946
|
+
function stripBodyInteriorFromStrandBackbone(built, steps, foreachStepIdx, collectStepIdx) {
|
|
947
|
+
const bodyEnd = collectStepIdx >= 0 ? collectStepIdx : steps.length;
|
|
948
|
+
// Collect the positional IDs of body-interior cap steps (the caps
|
|
949
|
+
// strictly between ForEach and Collect).
|
|
950
|
+
const interiorIds = new Set();
|
|
951
|
+
for (let i = foreachStepIdx + 1; i < bodyEnd; i++) {
|
|
952
|
+
if (Object.keys(steps[i].step_type)[0] === 'Cap') {
|
|
953
|
+
interiorIds.add(`step_${i}`);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
if (interiorIds.size === 0) return built;
|
|
957
|
+
|
|
958
|
+
const keptNodes = built.nodes.filter(n => !interiorIds.has(n.id));
|
|
959
|
+
const keptEdges = built.edges.filter(e =>
|
|
960
|
+
!interiorIds.has(e.source) && !interiorIds.has(e.target));
|
|
961
|
+
|
|
962
|
+
// After stripping, the ForEach node and the Collect node (or the
|
|
963
|
+
// output node if no Collect) may become disconnected — the body
|
|
964
|
+
// replicas will bridge them. That's fine; cytoscape's ELK layout
|
|
965
|
+
// handles disconnected subgraphs.
|
|
966
|
+
return {
|
|
967
|
+
nodes: keptNodes,
|
|
968
|
+
edges: keptEdges,
|
|
969
|
+
sourceSpec: built.sourceSpec,
|
|
970
|
+
targetSpec: built.targetSpec,
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
|
|
873
974
|
function buildRunGraphData(data) {
|
|
874
975
|
validateRunPayload(data);
|
|
875
976
|
|
|
@@ -879,11 +980,14 @@ function buildRunGraphData(data) {
|
|
|
879
980
|
const strandInput = Object.assign({}, data.resolved_strand, {
|
|
880
981
|
media_display_names: data.media_display_names,
|
|
881
982
|
});
|
|
882
|
-
const
|
|
983
|
+
const strandBuiltRaw = buildStrandGraphData(strandInput);
|
|
883
984
|
|
|
884
985
|
// Locate the ForEach/Collect span in the backbone for body-replica
|
|
885
|
-
// placement.
|
|
886
|
-
//
|
|
986
|
+
// placement. The strand builder uses positional node IDs mirroring
|
|
987
|
+
// the plan builder (`step_0`, `step_1`, …). The ForEach node at
|
|
988
|
+
// step index i has id `step_i`; body replicas fan out from that
|
|
989
|
+
// ForEach node and (when a Collect closes the body) merge into the
|
|
990
|
+
// Collect node at `step_j`.
|
|
887
991
|
const steps = data.resolved_strand.steps;
|
|
888
992
|
let foreachStepIdx = -1;
|
|
889
993
|
let collectStepIdx = -1;
|
|
@@ -894,6 +998,17 @@ function buildRunGraphData(data) {
|
|
|
894
998
|
}
|
|
895
999
|
const hasForeach = foreachStepIdx >= 0;
|
|
896
1000
|
|
|
1001
|
+
// In run mode we want per-body replicas to REPLACE the prototype cap
|
|
1002
|
+
// chain inside the ForEach body, not sit alongside it. Strip the
|
|
1003
|
+
// backbone nodes and edges that correspond to body-interior Cap
|
|
1004
|
+
// steps and their direct edges, keeping only the input slot, the
|
|
1005
|
+
// ForEach node, the Collect node (if any), the output node, and any
|
|
1006
|
+
// caps OUTSIDE the body span. Body replicas will connect from the
|
|
1007
|
+
// ForEach node and merge at the Collect node.
|
|
1008
|
+
const strandBuilt = hasForeach
|
|
1009
|
+
? stripBodyInteriorFromStrandBackbone(strandBuiltRaw, steps, foreachStepIdx, collectStepIdx)
|
|
1010
|
+
: strandBuiltRaw;
|
|
1011
|
+
|
|
897
1012
|
// Filter and bound the outcomes.
|
|
898
1013
|
const allOutcomes = data.body_outcomes.slice().sort((a, b) => a.body_index - b.body_index);
|
|
899
1014
|
const successes = allOutcomes.filter(o => o.success);
|
|
@@ -903,10 +1018,8 @@ function buildRunGraphData(data) {
|
|
|
903
1018
|
const hiddenSuccessCount = successes.length - visibleSuccess.length;
|
|
904
1019
|
const hiddenFailureCount = failures.length - visibleFailure.length;
|
|
905
1020
|
|
|
906
|
-
//
|
|
907
|
-
//
|
|
908
|
-
// ForEach) and extends through either all body caps (success) or up to
|
|
909
|
-
// the failed_cap (failure).
|
|
1021
|
+
// Collect the Cap steps inside the ForEach body. Each body replica
|
|
1022
|
+
// chains through these caps.
|
|
910
1023
|
const bodyCapSteps = [];
|
|
911
1024
|
const bodyStart = hasForeach ? foreachStepIdx + 1 : 0;
|
|
912
1025
|
const bodyEnd = collectStepIdx >= 0 ? collectStepIdx : steps.length;
|
|
@@ -916,19 +1029,24 @@ function buildRunGraphData(data) {
|
|
|
916
1029
|
}
|
|
917
1030
|
}
|
|
918
1031
|
|
|
919
|
-
//
|
|
920
|
-
//
|
|
1032
|
+
// Body replicas fan out from the ForEach node. If no ForEach, fall
|
|
1033
|
+
// back to the input_slot. When the strand closes the body with a
|
|
1034
|
+
// Collect, replicas merge into that Collect node; otherwise
|
|
1035
|
+
// (unclosed ForEach) they merge into the `output` node.
|
|
1036
|
+
const anchorNodeId = hasForeach ? `step_${foreachStepIdx}` : 'input_slot';
|
|
1037
|
+
const mergeNodeId = collectStepIdx >= 0 ? `step_${collectStepIdx}` : 'output';
|
|
1038
|
+
|
|
921
1039
|
const replicaNodes = [];
|
|
922
1040
|
const replicaEdges = [];
|
|
923
1041
|
|
|
924
|
-
function buildBodyReplica(outcome
|
|
1042
|
+
function buildBodyReplica(outcome) {
|
|
925
1043
|
const success = outcome.success;
|
|
926
1044
|
const successClass = success ? 'body-success' : 'body-failure';
|
|
927
1045
|
const edgeClass = success ? 'body-success' : 'body-failure';
|
|
928
1046
|
const colorVar = success ? '--graph-body-edge-success' : '--graph-body-edge-failure';
|
|
929
1047
|
|
|
930
|
-
//
|
|
931
|
-
//
|
|
1048
|
+
// Trace end: failures stop at failed_cap. `CapUrn.isEquivalent`
|
|
1049
|
+
// is used for the match — never string equality.
|
|
932
1050
|
let traceEnd = bodyCapSteps.length;
|
|
933
1051
|
if (!success && typeof outcome.failed_cap === 'string' && outcome.failed_cap.length > 0) {
|
|
934
1052
|
const CapUrn = requireHostDependency('CapUrn');
|
|
@@ -941,12 +1059,9 @@ function buildRunGraphData(data) {
|
|
|
941
1059
|
}
|
|
942
1060
|
}
|
|
943
1061
|
}
|
|
944
|
-
if (traceEnd === 0) return;
|
|
1062
|
+
if (traceEnd === 0) return;
|
|
945
1063
|
|
|
946
|
-
|
|
947
|
-
// ForEach's from_spec (fan-out from the same source node).
|
|
948
|
-
const anchorCanonical = canonicalMediaUrn(bodyCapSteps[0].step.from_spec);
|
|
949
|
-
let prevBodyNodeId = anchorCanonical;
|
|
1064
|
+
let prevBodyNodeId = anchorNodeId;
|
|
950
1065
|
const bodyKey = `body-${outcome.body_index}`;
|
|
951
1066
|
const titleLabel = typeof outcome.title === 'string' && outcome.title.length > 0
|
|
952
1067
|
? outcome.title
|
|
@@ -973,7 +1088,7 @@ function buildRunGraphData(data) {
|
|
|
973
1088
|
id: `${bodyKey}-e-${i}`,
|
|
974
1089
|
source: prevBodyNodeId,
|
|
975
1090
|
target: replicaNodeId,
|
|
976
|
-
label: '',
|
|
1091
|
+
label: i === 0 ? body.title : '',
|
|
977
1092
|
title: body.title,
|
|
978
1093
|
fullUrn: body.cap_urn,
|
|
979
1094
|
color: `var(${colorVar})`,
|
|
@@ -984,16 +1099,17 @@ function buildRunGraphData(data) {
|
|
|
984
1099
|
prevBodyNodeId = replicaNodeId;
|
|
985
1100
|
}
|
|
986
1101
|
|
|
987
|
-
//
|
|
988
|
-
//
|
|
989
|
-
|
|
990
|
-
|
|
1102
|
+
// Successful bodies merge their replica tail back into the Collect
|
|
1103
|
+
// node (or `output` if the ForEach is unclosed) so the graph
|
|
1104
|
+
// visibly fans in. Failed bodies do NOT merge — the trace
|
|
1105
|
+
// terminates at the failed cap.
|
|
1106
|
+
if (success) {
|
|
991
1107
|
replicaEdges.push({
|
|
992
1108
|
group: 'edges',
|
|
993
1109
|
data: {
|
|
994
1110
|
id: `${bodyKey}-merge`,
|
|
995
1111
|
source: prevBodyNodeId,
|
|
996
|
-
target:
|
|
1112
|
+
target: mergeNodeId,
|
|
997
1113
|
label: '',
|
|
998
1114
|
title: 'collect',
|
|
999
1115
|
fullUrn: '',
|
|
@@ -1005,14 +1121,13 @@ function buildRunGraphData(data) {
|
|
|
1005
1121
|
}
|
|
1006
1122
|
}
|
|
1007
1123
|
|
|
1008
|
-
visibleSuccess.forEach((o
|
|
1009
|
-
visibleFailure.forEach((o
|
|
1124
|
+
visibleSuccess.forEach((o) => buildBodyReplica(o));
|
|
1125
|
+
visibleFailure.forEach((o) => buildBodyReplica(o));
|
|
1010
1126
|
|
|
1011
1127
|
// Build success and failure "show more" nodes when there are hidden
|
|
1012
|
-
// outcomes. Anchored at the
|
|
1128
|
+
// outcomes. Anchored at the ForEach node (or input_slot if none).
|
|
1013
1129
|
const showMoreNodes = [];
|
|
1014
1130
|
if (hasForeach && bodyCapSteps.length > 0) {
|
|
1015
|
-
const anchorCanonical = canonicalMediaUrn(bodyCapSteps[0].step.from_spec);
|
|
1016
1131
|
if (hiddenSuccessCount > 0) {
|
|
1017
1132
|
const nodeId = 'show-more-success';
|
|
1018
1133
|
showMoreNodes.push({
|
|
@@ -1030,7 +1145,7 @@ function buildRunGraphData(data) {
|
|
|
1030
1145
|
group: 'edges',
|
|
1031
1146
|
data: {
|
|
1032
1147
|
id: 'show-more-success-edge',
|
|
1033
|
-
source:
|
|
1148
|
+
source: anchorNodeId,
|
|
1034
1149
|
target: nodeId,
|
|
1035
1150
|
label: '',
|
|
1036
1151
|
title: '',
|
|
@@ -1057,7 +1172,7 @@ function buildRunGraphData(data) {
|
|
|
1057
1172
|
group: 'edges',
|
|
1058
1173
|
data: {
|
|
1059
1174
|
id: 'show-more-failure-edge',
|
|
1060
|
-
source:
|
|
1175
|
+
source: anchorNodeId,
|
|
1061
1176
|
target: nodeId,
|
|
1062
1177
|
label: '',
|
|
1063
1178
|
title: '',
|
package/capdag.test.js
CHANGED
|
@@ -4030,86 +4030,178 @@ function testRenderer_classifyStrandCapSteps_nestedForks() {
|
|
|
4030
4030
|
assert(!capFlags.get(5).prevForEach && capFlags.get(5).nextCollect, 'cap3 outer exit');
|
|
4031
4031
|
}
|
|
4032
4032
|
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
//
|
|
4040
|
-
//
|
|
4041
|
-
//
|
|
4033
|
+
// Helper: find an edge with the given source/target ids.
|
|
4034
|
+
function findEdge(edges, source, target) {
|
|
4035
|
+
return edges.find(e => e.source === source && e.target === target);
|
|
4036
|
+
}
|
|
4037
|
+
|
|
4038
|
+
function testRenderer_buildStrandGraphData_singleCapPlain() {
|
|
4039
|
+
// Minimal strand with one plain 1→1 cap. Plan builder produces:
|
|
4040
|
+
// input_slot → step_0 (cap) → output
|
|
4041
|
+
// (two edges, three nodes). No cardinality marker in the cap label
|
|
4042
|
+
// because input_is_sequence == output_is_sequence == false.
|
|
4043
|
+
const payload = {
|
|
4044
|
+
source_spec: 'media:a',
|
|
4045
|
+
target_spec: 'media:b',
|
|
4046
|
+
steps: [
|
|
4047
|
+
makeCapStep('cap:in="media:a";op=x;out="media:b"', 'x', 'media:a', 'media:b', false, false),
|
|
4048
|
+
],
|
|
4049
|
+
};
|
|
4050
|
+
const built = rendererBuildStrandGraphData(payload);
|
|
4051
|
+
const nodeIds = built.nodes.map(n => n.id).sort();
|
|
4052
|
+
assertEqual(JSON.stringify(nodeIds), JSON.stringify(['input_slot', 'output', 'step_0']),
|
|
4053
|
+
'nodes are input_slot + step_0 + output (positional ids)');
|
|
4054
|
+
assertEqual(built.edges.length, 2, 'two edges: input_slot→step_0 and step_0→output');
|
|
4055
|
+
const capEdge = findEdge(built.edges, 'input_slot', 'step_0');
|
|
4056
|
+
assert(capEdge !== undefined, 'cap edge from input_slot to step_0 exists');
|
|
4057
|
+
assertEqual(capEdge.label, 'x', 'plain cap edge label is the cap title with no cardinality marker');
|
|
4058
|
+
const outEdge = findEdge(built.edges, 'step_0', 'output');
|
|
4059
|
+
assert(outEdge !== undefined, 'output edge from step_0 to output exists');
|
|
4060
|
+
}
|
|
4061
|
+
|
|
4062
|
+
function testRenderer_buildStrandGraphData_sequenceShowsCardinality() {
|
|
4063
|
+
// A cap with input_is_sequence=true MUST emit "(n→1)" on its edge
|
|
4064
|
+
// label.
|
|
4065
|
+
const payload = {
|
|
4066
|
+
source_spec: 'media:a;list',
|
|
4067
|
+
target_spec: 'media:b',
|
|
4068
|
+
steps: [
|
|
4069
|
+
makeCapStep('cap:in="media:a;list";op=x;out="media:b"', 'x', 'media:a;list', 'media:b', true, false),
|
|
4070
|
+
],
|
|
4071
|
+
};
|
|
4072
|
+
const built = rendererBuildStrandGraphData(payload);
|
|
4073
|
+
const capEdge = findEdge(built.edges, 'input_slot', 'step_0');
|
|
4074
|
+
assert(capEdge !== undefined, 'cap edge exists');
|
|
4075
|
+
assert(capEdge.label.includes('(n\u21921)'),
|
|
4076
|
+
`cap edge label must include (n\u21921) marker; got: ${capEdge.label}`);
|
|
4077
|
+
}
|
|
4078
|
+
|
|
4079
|
+
function testRenderer_buildStrandGraphData_foreachCollectSpan() {
|
|
4080
|
+
// Strand: [ForEach, Cap, Collect]. Plan builder produces:
|
|
4081
|
+
// input_slot (source) →direct→ step_1 (cap) — cap emits its own
|
|
4082
|
+
// direct edge from prev
|
|
4083
|
+
// input_slot →direct→ step_0 (foreach) — created when Collect
|
|
4084
|
+
// step_0 →iteration→ step_1 — iteration edge
|
|
4085
|
+
// step_1 →collection→ step_2 (collect) — collection edge
|
|
4086
|
+
// step_2 →direct→ output — output connector
|
|
4087
|
+
//
|
|
4088
|
+
// (six nodes: input_slot, step_0, step_1, step_2, output; five
|
|
4089
|
+
// edges.) ForEach and Collect are REAL nodes in the graph, not
|
|
4090
|
+
// labels on cap edges — they're distinct processing units in the
|
|
4091
|
+
// plan. This mirrors capdag's plan_builder.rs exactly.
|
|
4042
4092
|
const payload = {
|
|
4043
4093
|
source_spec: 'media:pdf;list',
|
|
4044
|
-
target_spec: 'media:txt',
|
|
4094
|
+
target_spec: 'media:txt;list',
|
|
4045
4095
|
steps: [
|
|
4046
4096
|
makeForEachStep('media:pdf;list'),
|
|
4047
|
-
makeCapStep('cap:in="media:pdf";op=extract;out="media:txt"', 'extract
|
|
4097
|
+
makeCapStep('cap:in="media:pdf";op=extract;out="media:txt"', 'extract', 'media:pdf', 'media:txt', false, false),
|
|
4098
|
+
makeCollectStep('media:txt'),
|
|
4048
4099
|
],
|
|
4049
4100
|
};
|
|
4050
4101
|
const built = rendererBuildStrandGraphData(payload);
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4102
|
+
const nodeIds = built.nodes.map(n => n.id).sort();
|
|
4103
|
+
assertEqual(JSON.stringify(nodeIds),
|
|
4104
|
+
JSON.stringify(['input_slot', 'output', 'step_0', 'step_1', 'step_2']),
|
|
4105
|
+
'positional nodes for source, foreach, cap, collect, output');
|
|
4106
|
+
|
|
4107
|
+
// The five edges the plan builder would produce:
|
|
4108
|
+
assert(findEdge(built.edges, 'input_slot', 'step_1') !== undefined,
|
|
4109
|
+
'cap direct edge input_slot→step_1 (prev wasn\'t advanced by ForEach)');
|
|
4110
|
+
assert(findEdge(built.edges, 'input_slot', 'step_0') !== undefined,
|
|
4111
|
+
'foreach input edge input_slot→step_0');
|
|
4112
|
+
assert(findEdge(built.edges, 'step_0', 'step_1') !== undefined,
|
|
4113
|
+
'iteration edge step_0→step_1 (body entry)');
|
|
4114
|
+
assert(findEdge(built.edges, 'step_1', 'step_2') !== undefined,
|
|
4115
|
+
'collection edge step_1→step_2 (body exit → collect)');
|
|
4116
|
+
assert(findEdge(built.edges, 'step_2', 'output') !== undefined,
|
|
4117
|
+
'output edge step_2→output');
|
|
4118
|
+
|
|
4119
|
+
// ForEach and Collect nodes carry their canonical labels.
|
|
4120
|
+
const foreachNode = built.nodes.find(n => n.id === 'step_0');
|
|
4121
|
+
assertEqual(foreachNode.label, 'for each', 'ForEach node labeled "for each"');
|
|
4122
|
+
const collectNode = built.nodes.find(n => n.id === 'step_2');
|
|
4123
|
+
assertEqual(collectNode.label, 'collect', 'Collect node labeled "collect"');
|
|
4124
|
+
}
|
|
4125
|
+
|
|
4126
|
+
function testRenderer_buildStrandGraphData_standaloneCollect() {
|
|
4127
|
+
// Strand with a standalone Collect (no enclosing ForEach). Plan
|
|
4128
|
+
// builder creates a Collect node consuming prev directly — plain
|
|
4129
|
+
// direct edge, no iteration/collection semantics.
|
|
4066
4130
|
const payload = {
|
|
4067
4131
|
source_spec: 'media:a',
|
|
4068
|
-
target_spec: 'media:
|
|
4132
|
+
target_spec: 'media:b;list',
|
|
4069
4133
|
steps: [
|
|
4070
|
-
makeCapStep('cap:in="media:a";op=x;out="media:
|
|
4071
|
-
makeCollectStep('media:
|
|
4134
|
+
makeCapStep('cap:in="media:a";op=x;out="media:b"', 'x', 'media:a', 'media:b', false, false),
|
|
4135
|
+
makeCollectStep('media:b'),
|
|
4072
4136
|
],
|
|
4073
4137
|
};
|
|
4074
4138
|
const built = rendererBuildStrandGraphData(payload);
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
//
|
|
4087
|
-
//
|
|
4139
|
+
assert(findEdge(built.edges, 'input_slot', 'step_0') !== undefined,
|
|
4140
|
+
'cap edge input_slot → step_0');
|
|
4141
|
+
assert(findEdge(built.edges, 'step_0', 'step_1') !== undefined,
|
|
4142
|
+
'standalone collect edge step_0 → step_1 (Collect node)');
|
|
4143
|
+
assert(findEdge(built.edges, 'step_1', 'output') !== undefined,
|
|
4144
|
+
'output edge step_1 → output');
|
|
4145
|
+
const collectNode = built.nodes.find(n => n.id === 'step_1');
|
|
4146
|
+
assertEqual(collectNode.label, 'collect', 'Collect node labeled "collect"');
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
function testRenderer_buildStrandGraphData_unclosedForEachBody() {
|
|
4150
|
+
// Strand: [Cap_a, ForEach, Cap_b] with no closing Collect. The plan
|
|
4151
|
+
// builder's "unclosed ForEach" branch creates a ForEach node
|
|
4152
|
+
// connecting Cap_a to Cap_b via iteration, with prev becoming the
|
|
4153
|
+
// body exit (Cap_b).
|
|
4088
4154
|
const payload = {
|
|
4089
4155
|
source_spec: 'media:a',
|
|
4090
|
-
target_spec: 'media:
|
|
4156
|
+
target_spec: 'media:c',
|
|
4091
4157
|
steps: [
|
|
4092
|
-
makeCapStep('cap:in="media:a";op=
|
|
4158
|
+
makeCapStep('cap:in="media:a";op=a;out="media:b"', 'a', 'media:a', 'media:b', false, false),
|
|
4159
|
+
makeForEachStep('media:b'),
|
|
4160
|
+
makeCapStep('cap:in="media:b";op=b;out="media:c"', 'b', 'media:b', 'media:c', false, false),
|
|
4093
4161
|
],
|
|
4094
4162
|
};
|
|
4095
4163
|
const built = rendererBuildStrandGraphData(payload);
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4164
|
+
// Cap_a connects from input_slot.
|
|
4165
|
+
assert(findEdge(built.edges, 'input_slot', 'step_0') !== undefined,
|
|
4166
|
+
'cap_a edge input_slot → step_0');
|
|
4167
|
+
// Cap_b still connects directly from step_0 (the ForEach didn't
|
|
4168
|
+
// advance prev). This mirrors plan_builder.
|
|
4169
|
+
assert(findEdge(built.edges, 'step_0', 'step_2') !== undefined,
|
|
4170
|
+
'cap_b direct edge step_0 → step_2');
|
|
4171
|
+
// ForEach node at step_1 with direct edge from step_0 and iteration
|
|
4172
|
+
// edge to step_2.
|
|
4173
|
+
assert(findEdge(built.edges, 'step_0', 'step_1') !== undefined,
|
|
4174
|
+
'foreach input edge step_0 → step_1');
|
|
4175
|
+
assert(findEdge(built.edges, 'step_1', 'step_2') !== undefined,
|
|
4176
|
+
'iteration edge step_1 → step_2 (body entry)');
|
|
4177
|
+
// Output connects from step_2 (body exit).
|
|
4178
|
+
assert(findEdge(built.edges, 'step_2', 'output') !== undefined,
|
|
4179
|
+
'output edge step_2 → output');
|
|
4180
|
+
}
|
|
4181
|
+
|
|
4182
|
+
function testRenderer_buildStrandGraphData_nestedForEachThrows() {
|
|
4183
|
+
// Nested ForEach without an intervening body cap in the outer
|
|
4184
|
+
// ForEach is an illegal nesting per plan_builder. The renderer
|
|
4185
|
+
// must throw the same error to surface the issue rather than
|
|
4186
|
+
// render a malformed graph.
|
|
4102
4187
|
const payload = {
|
|
4103
|
-
source_spec: 'media:a;list',
|
|
4104
|
-
target_spec: 'media:
|
|
4188
|
+
source_spec: 'media:a;list;list',
|
|
4189
|
+
target_spec: 'media:a',
|
|
4105
4190
|
steps: [
|
|
4106
|
-
|
|
4191
|
+
makeForEachStep('media:a;list;list'),
|
|
4192
|
+
makeForEachStep('media:a;list'),
|
|
4193
|
+
makeCapStep('cap:in="media:a";op=x;out="media:a"', 'x', 'media:a', 'media:a', false, false),
|
|
4107
4194
|
],
|
|
4108
4195
|
};
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4196
|
+
let threw = false;
|
|
4197
|
+
try {
|
|
4198
|
+
rendererBuildStrandGraphData(payload);
|
|
4199
|
+
} catch (e) {
|
|
4200
|
+
threw = true;
|
|
4201
|
+
assert(e.message.includes('nested ForEach'),
|
|
4202
|
+
'error must name the nested-ForEach violation');
|
|
4203
|
+
}
|
|
4204
|
+
assert(threw, 'nested ForEach without outer body cap must throw');
|
|
4113
4205
|
}
|
|
4114
4206
|
|
|
4115
4207
|
function testRenderer_validateStrandPayload_missingSourceSpec() {
|
|
@@ -4702,10 +4794,12 @@ async function runTests() {
|
|
|
4702
4794
|
runTest('RENDERER: validateStrandStep_booleanIsSequence', testRenderer_validateStrandStep_requiresBooleanIsSequence);
|
|
4703
4795
|
runTest('RENDERER: classifyStrandCapSteps_simple', testRenderer_classifyStrandCapSteps_capFlags);
|
|
4704
4796
|
runTest('RENDERER: classifyStrandCapSteps_nested', testRenderer_classifyStrandCapSteps_nestedForks);
|
|
4705
|
-
runTest('RENDERER:
|
|
4706
|
-
runTest('RENDERER: buildStrand_collectFixupEdge', testRenderer_buildStrandGraphData_collectFixupEdge);
|
|
4707
|
-
runTest('RENDERER: buildStrand_plainCapNoMarker', testRenderer_buildStrandGraphData_plainCapNoMarker);
|
|
4797
|
+
runTest('RENDERER: buildStrand_singleCapPlain', testRenderer_buildStrandGraphData_singleCapPlain);
|
|
4708
4798
|
runTest('RENDERER: buildStrand_sequenceShowsCardinality', testRenderer_buildStrandGraphData_sequenceShowsCardinality);
|
|
4799
|
+
runTest('RENDERER: buildStrand_foreachCollectSpan', testRenderer_buildStrandGraphData_foreachCollectSpan);
|
|
4800
|
+
runTest('RENDERER: buildStrand_standaloneCollect', testRenderer_buildStrandGraphData_standaloneCollect);
|
|
4801
|
+
runTest('RENDERER: buildStrand_unclosedForEachBody', testRenderer_buildStrandGraphData_unclosedForEachBody);
|
|
4802
|
+
runTest('RENDERER: buildStrand_nestedForEachThrows', testRenderer_buildStrandGraphData_nestedForEachThrows);
|
|
4709
4803
|
runTest('RENDERER: validateStrand_missingSourceSpec', testRenderer_validateStrandPayload_missingSourceSpec);
|
|
4710
4804
|
|
|
4711
4805
|
console.log('\n--- cap-graph-renderer run builder ---');
|
package/machine-parser.js
CHANGED
|
@@ -174,13 +174,15 @@ function peg$parse(input, options) {
|
|
|
174
174
|
const peg$c6 = "-";
|
|
175
175
|
const peg$c7 = ">";
|
|
176
176
|
const peg$c8 = "cap:";
|
|
177
|
-
const peg$c9 = "\"
|
|
178
|
-
const peg$c10 = "
|
|
179
|
-
const peg$c11 = "
|
|
177
|
+
const peg$c9 = "\r\n";
|
|
178
|
+
const peg$c10 = "\"";
|
|
179
|
+
const peg$c11 = "\\\"";
|
|
180
|
+
const peg$c12 = "\\\\";
|
|
180
181
|
|
|
181
182
|
const peg$r0 = /^[a-zA-Z_]/;
|
|
182
183
|
const peg$r1 = /^[a-zA-Z0-9_\-]/;
|
|
183
|
-
const peg$r2 = /^[
|
|
184
|
+
const peg$r2 = /^[\n\r]/;
|
|
185
|
+
const peg$r3 = /^[ \t\r\n]/;
|
|
184
186
|
|
|
185
187
|
const peg$e0 = peg$literalExpectation("[", false);
|
|
186
188
|
const peg$e1 = peg$literalExpectation("]", false);
|
|
@@ -194,29 +196,33 @@ function peg$parse(input, options) {
|
|
|
194
196
|
const peg$e9 = peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "_", "-"], false, false, false);
|
|
195
197
|
const peg$e10 = peg$literalExpectation("cap:", false);
|
|
196
198
|
const peg$e11 = peg$anyExpectation();
|
|
197
|
-
const peg$e12 = peg$
|
|
198
|
-
const peg$e13 = peg$literalExpectation("
|
|
199
|
-
const peg$e14 = peg$
|
|
200
|
-
const peg$e15 = peg$
|
|
201
|
-
const peg$e16 = peg$
|
|
199
|
+
const peg$e12 = peg$otherExpectation("newline");
|
|
200
|
+
const peg$e13 = peg$literalExpectation("\r\n", false);
|
|
201
|
+
const peg$e14 = peg$classExpectation(["\n", "\r"], false, false, false);
|
|
202
|
+
const peg$e15 = peg$literalExpectation("\"", false);
|
|
203
|
+
const peg$e16 = peg$literalExpectation("\\\"", false);
|
|
204
|
+
const peg$e17 = peg$literalExpectation("\\\\", false);
|
|
205
|
+
const peg$e18 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false, false);
|
|
206
|
+
const peg$e19 = peg$otherExpectation("required whitespace");
|
|
202
207
|
|
|
203
208
|
function peg$f0(stmts) { return stmts; }
|
|
204
209
|
function peg$f1(inner) { return inner; }
|
|
205
|
-
function peg$f2(
|
|
210
|
+
function peg$f2(inner) { return inner; }
|
|
211
|
+
function peg$f3(a, c) {
|
|
206
212
|
return { type: 'header', alias: a.value, capUrn: c.value, location: location(), aliasLocation: a.location, capUrnLocation: c.location };
|
|
207
213
|
}
|
|
208
|
-
function peg$
|
|
214
|
+
function peg$f4(s, lc, t) {
|
|
209
215
|
return { type: 'wiring', sources: s.values, capAlias: lc.alias, isLoop: lc.isLoop, target: t.value, location: location(), sourceLocations: s.locations, capAliasLocation: lc.location, targetLocation: t.location };
|
|
210
216
|
}
|
|
211
|
-
function peg$
|
|
212
|
-
function peg$
|
|
213
|
-
function peg$
|
|
217
|
+
function peg$f5(a) { return { values: [a.value], locations: [a.location] }; }
|
|
218
|
+
function peg$f6(first, a) { return a; }
|
|
219
|
+
function peg$f7(first, rest) {
|
|
214
220
|
return { values: [first.value, ...rest.map(r => r.value)], locations: [first.location, ...rest.map(r => r.location)] };
|
|
215
221
|
}
|
|
216
|
-
function peg$
|
|
217
|
-
function peg$
|
|
218
|
-
function peg$
|
|
219
|
-
function peg$
|
|
222
|
+
function peg$f8(a) { return { alias: a.value, isLoop: true, location: a.location }; }
|
|
223
|
+
function peg$f9(a) { return { alias: a.value, isLoop: false, location: a.location }; }
|
|
224
|
+
function peg$f10(a) { return { value: a, location: location() }; }
|
|
225
|
+
function peg$f11(c) { return { value: c, location: location() }; }
|
|
220
226
|
let peg$currPos = options.peg$currPos | 0;
|
|
221
227
|
let peg$savedPos = peg$currPos;
|
|
222
228
|
const peg$posDetailsCache = [{ line: 1, column: 1 }];
|
|
@@ -444,6 +450,18 @@ function peg$parse(input, options) {
|
|
|
444
450
|
peg$currPos = s0;
|
|
445
451
|
s0 = peg$FAILED;
|
|
446
452
|
}
|
|
453
|
+
if (s0 === peg$FAILED) {
|
|
454
|
+
s0 = peg$currPos;
|
|
455
|
+
s1 = peg$parseinner();
|
|
456
|
+
if (s1 !== peg$FAILED) {
|
|
457
|
+
s2 = peg$parse_();
|
|
458
|
+
peg$savedPos = s0;
|
|
459
|
+
s0 = peg$f2(s1);
|
|
460
|
+
} else {
|
|
461
|
+
peg$currPos = s0;
|
|
462
|
+
s0 = peg$FAILED;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
447
465
|
|
|
448
466
|
return s0;
|
|
449
467
|
}
|
|
@@ -470,7 +488,7 @@ function peg$parse(input, options) {
|
|
|
470
488
|
s3 = peg$parsecap_urn_loc();
|
|
471
489
|
if (s3 !== peg$FAILED) {
|
|
472
490
|
peg$savedPos = s0;
|
|
473
|
-
s0 = peg$
|
|
491
|
+
s0 = peg$f3(s1, s3);
|
|
474
492
|
} else {
|
|
475
493
|
peg$currPos = s0;
|
|
476
494
|
s0 = peg$FAILED;
|
|
@@ -506,7 +524,7 @@ function peg$parse(input, options) {
|
|
|
506
524
|
s9 = peg$parsealias_loc();
|
|
507
525
|
if (s9 !== peg$FAILED) {
|
|
508
526
|
peg$savedPos = s0;
|
|
509
|
-
s0 = peg$
|
|
527
|
+
s0 = peg$f4(s1, s5, s9);
|
|
510
528
|
} else {
|
|
511
529
|
peg$currPos = s0;
|
|
512
530
|
s0 = peg$FAILED;
|
|
@@ -549,7 +567,7 @@ function peg$parse(input, options) {
|
|
|
549
567
|
s1 = peg$parsealias_loc();
|
|
550
568
|
if (s1 !== peg$FAILED) {
|
|
551
569
|
peg$savedPos = s0;
|
|
552
|
-
s1 = peg$
|
|
570
|
+
s1 = peg$f5(s1);
|
|
553
571
|
}
|
|
554
572
|
s0 = s1;
|
|
555
573
|
|
|
@@ -585,7 +603,7 @@ function peg$parse(input, options) {
|
|
|
585
603
|
s8 = peg$parsealias_loc();
|
|
586
604
|
if (s8 !== peg$FAILED) {
|
|
587
605
|
peg$savedPos = s5;
|
|
588
|
-
s5 = peg$
|
|
606
|
+
s5 = peg$f6(s3, s8);
|
|
589
607
|
} else {
|
|
590
608
|
peg$currPos = s5;
|
|
591
609
|
s5 = peg$FAILED;
|
|
@@ -610,7 +628,7 @@ function peg$parse(input, options) {
|
|
|
610
628
|
s8 = peg$parsealias_loc();
|
|
611
629
|
if (s8 !== peg$FAILED) {
|
|
612
630
|
peg$savedPos = s5;
|
|
613
|
-
s5 = peg$
|
|
631
|
+
s5 = peg$f6(s3, s8);
|
|
614
632
|
} else {
|
|
615
633
|
peg$currPos = s5;
|
|
616
634
|
s5 = peg$FAILED;
|
|
@@ -634,7 +652,7 @@ function peg$parse(input, options) {
|
|
|
634
652
|
}
|
|
635
653
|
if (s6 !== peg$FAILED) {
|
|
636
654
|
peg$savedPos = s0;
|
|
637
|
-
s0 = peg$
|
|
655
|
+
s0 = peg$f7(s3, s4);
|
|
638
656
|
} else {
|
|
639
657
|
peg$currPos = s0;
|
|
640
658
|
s0 = peg$FAILED;
|
|
@@ -672,7 +690,7 @@ function peg$parse(input, options) {
|
|
|
672
690
|
s3 = peg$parsealias_loc();
|
|
673
691
|
if (s3 !== peg$FAILED) {
|
|
674
692
|
peg$savedPos = s0;
|
|
675
|
-
s0 = peg$
|
|
693
|
+
s0 = peg$f8(s3);
|
|
676
694
|
} else {
|
|
677
695
|
peg$currPos = s0;
|
|
678
696
|
s0 = peg$FAILED;
|
|
@@ -690,7 +708,7 @@ function peg$parse(input, options) {
|
|
|
690
708
|
s1 = peg$parsealias_loc();
|
|
691
709
|
if (s1 !== peg$FAILED) {
|
|
692
710
|
peg$savedPos = s0;
|
|
693
|
-
s1 = peg$
|
|
711
|
+
s1 = peg$f9(s1);
|
|
694
712
|
}
|
|
695
713
|
s0 = s1;
|
|
696
714
|
}
|
|
@@ -754,7 +772,7 @@ function peg$parse(input, options) {
|
|
|
754
772
|
s1 = peg$parsealias();
|
|
755
773
|
if (s1 !== peg$FAILED) {
|
|
756
774
|
peg$savedPos = s0;
|
|
757
|
-
s1 = peg$
|
|
775
|
+
s1 = peg$f10(s1);
|
|
758
776
|
}
|
|
759
777
|
s0 = s1;
|
|
760
778
|
|
|
@@ -814,7 +832,7 @@ function peg$parse(input, options) {
|
|
|
814
832
|
s1 = peg$parsecap_urn();
|
|
815
833
|
if (s1 !== peg$FAILED) {
|
|
816
834
|
peg$savedPos = s0;
|
|
817
|
-
s1 = peg$
|
|
835
|
+
s1 = peg$f11(s1);
|
|
818
836
|
}
|
|
819
837
|
s0 = s1;
|
|
820
838
|
|
|
@@ -856,7 +874,7 @@ function peg$parse(input, options) {
|
|
|
856
874
|
}
|
|
857
875
|
|
|
858
876
|
function peg$parsecap_urn_body() {
|
|
859
|
-
let s0, s1, s2;
|
|
877
|
+
let s0, s1, s2, s3;
|
|
860
878
|
|
|
861
879
|
s0 = peg$parsequoted_value();
|
|
862
880
|
if (s0 === peg$FAILED) {
|
|
@@ -878,16 +896,31 @@ function peg$parse(input, options) {
|
|
|
878
896
|
s1 = peg$FAILED;
|
|
879
897
|
}
|
|
880
898
|
if (s1 !== peg$FAILED) {
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
899
|
+
s2 = peg$currPos;
|
|
900
|
+
peg$silentFails++;
|
|
901
|
+
s3 = peg$parseNL();
|
|
902
|
+
peg$silentFails--;
|
|
903
|
+
if (s3 === peg$FAILED) {
|
|
904
|
+
s2 = undefined;
|
|
884
905
|
} else {
|
|
906
|
+
peg$currPos = s2;
|
|
885
907
|
s2 = peg$FAILED;
|
|
886
|
-
if (peg$silentFails === 0) { peg$fail(peg$e11); }
|
|
887
908
|
}
|
|
888
909
|
if (s2 !== peg$FAILED) {
|
|
889
|
-
|
|
890
|
-
|
|
910
|
+
if (input.length > peg$currPos) {
|
|
911
|
+
s3 = input.charAt(peg$currPos);
|
|
912
|
+
peg$currPos++;
|
|
913
|
+
} else {
|
|
914
|
+
s3 = peg$FAILED;
|
|
915
|
+
if (peg$silentFails === 0) { peg$fail(peg$e11); }
|
|
916
|
+
}
|
|
917
|
+
if (s3 !== peg$FAILED) {
|
|
918
|
+
s1 = [s1, s2, s3];
|
|
919
|
+
s0 = s1;
|
|
920
|
+
} else {
|
|
921
|
+
peg$currPos = s0;
|
|
922
|
+
s0 = peg$FAILED;
|
|
923
|
+
}
|
|
891
924
|
} else {
|
|
892
925
|
peg$currPos = s0;
|
|
893
926
|
s0 = peg$FAILED;
|
|
@@ -901,44 +934,73 @@ function peg$parse(input, options) {
|
|
|
901
934
|
return s0;
|
|
902
935
|
}
|
|
903
936
|
|
|
937
|
+
function peg$parseNL() {
|
|
938
|
+
let s0, s1;
|
|
939
|
+
|
|
940
|
+
peg$silentFails++;
|
|
941
|
+
if (input.substr(peg$currPos, 2) === peg$c9) {
|
|
942
|
+
s0 = peg$c9;
|
|
943
|
+
peg$currPos += 2;
|
|
944
|
+
} else {
|
|
945
|
+
s0 = peg$FAILED;
|
|
946
|
+
if (peg$silentFails === 0) { peg$fail(peg$e13); }
|
|
947
|
+
}
|
|
948
|
+
if (s0 === peg$FAILED) {
|
|
949
|
+
s0 = input.charAt(peg$currPos);
|
|
950
|
+
if (peg$r2.test(s0)) {
|
|
951
|
+
peg$currPos++;
|
|
952
|
+
} else {
|
|
953
|
+
s0 = peg$FAILED;
|
|
954
|
+
if (peg$silentFails === 0) { peg$fail(peg$e14); }
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
peg$silentFails--;
|
|
958
|
+
if (s0 === peg$FAILED) {
|
|
959
|
+
s1 = peg$FAILED;
|
|
960
|
+
if (peg$silentFails === 0) { peg$fail(peg$e12); }
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
return s0;
|
|
964
|
+
}
|
|
965
|
+
|
|
904
966
|
function peg$parsequoted_value() {
|
|
905
967
|
let s0, s1, s2, s3, s4, s5;
|
|
906
968
|
|
|
907
969
|
s0 = peg$currPos;
|
|
908
970
|
if (input.charCodeAt(peg$currPos) === 34) {
|
|
909
|
-
s1 = peg$
|
|
971
|
+
s1 = peg$c10;
|
|
910
972
|
peg$currPos++;
|
|
911
973
|
} else {
|
|
912
974
|
s1 = peg$FAILED;
|
|
913
|
-
if (peg$silentFails === 0) { peg$fail(peg$
|
|
975
|
+
if (peg$silentFails === 0) { peg$fail(peg$e15); }
|
|
914
976
|
}
|
|
915
977
|
if (s1 !== peg$FAILED) {
|
|
916
978
|
s2 = [];
|
|
917
|
-
if (input.substr(peg$currPos, 2) === peg$
|
|
918
|
-
s3 = peg$
|
|
979
|
+
if (input.substr(peg$currPos, 2) === peg$c11) {
|
|
980
|
+
s3 = peg$c11;
|
|
919
981
|
peg$currPos += 2;
|
|
920
982
|
} else {
|
|
921
983
|
s3 = peg$FAILED;
|
|
922
|
-
if (peg$silentFails === 0) { peg$fail(peg$
|
|
984
|
+
if (peg$silentFails === 0) { peg$fail(peg$e16); }
|
|
923
985
|
}
|
|
924
986
|
if (s3 === peg$FAILED) {
|
|
925
|
-
if (input.substr(peg$currPos, 2) === peg$
|
|
926
|
-
s3 = peg$
|
|
987
|
+
if (input.substr(peg$currPos, 2) === peg$c12) {
|
|
988
|
+
s3 = peg$c12;
|
|
927
989
|
peg$currPos += 2;
|
|
928
990
|
} else {
|
|
929
991
|
s3 = peg$FAILED;
|
|
930
|
-
if (peg$silentFails === 0) { peg$fail(peg$
|
|
992
|
+
if (peg$silentFails === 0) { peg$fail(peg$e17); }
|
|
931
993
|
}
|
|
932
994
|
if (s3 === peg$FAILED) {
|
|
933
995
|
s3 = peg$currPos;
|
|
934
996
|
s4 = peg$currPos;
|
|
935
997
|
peg$silentFails++;
|
|
936
998
|
if (input.charCodeAt(peg$currPos) === 34) {
|
|
937
|
-
s5 = peg$
|
|
999
|
+
s5 = peg$c10;
|
|
938
1000
|
peg$currPos++;
|
|
939
1001
|
} else {
|
|
940
1002
|
s5 = peg$FAILED;
|
|
941
|
-
if (peg$silentFails === 0) { peg$fail(peg$
|
|
1003
|
+
if (peg$silentFails === 0) { peg$fail(peg$e15); }
|
|
942
1004
|
}
|
|
943
1005
|
peg$silentFails--;
|
|
944
1006
|
if (s5 === peg$FAILED) {
|
|
@@ -970,31 +1032,31 @@ function peg$parse(input, options) {
|
|
|
970
1032
|
}
|
|
971
1033
|
while (s3 !== peg$FAILED) {
|
|
972
1034
|
s2.push(s3);
|
|
973
|
-
if (input.substr(peg$currPos, 2) === peg$
|
|
974
|
-
s3 = peg$
|
|
1035
|
+
if (input.substr(peg$currPos, 2) === peg$c11) {
|
|
1036
|
+
s3 = peg$c11;
|
|
975
1037
|
peg$currPos += 2;
|
|
976
1038
|
} else {
|
|
977
1039
|
s3 = peg$FAILED;
|
|
978
|
-
if (peg$silentFails === 0) { peg$fail(peg$
|
|
1040
|
+
if (peg$silentFails === 0) { peg$fail(peg$e16); }
|
|
979
1041
|
}
|
|
980
1042
|
if (s3 === peg$FAILED) {
|
|
981
|
-
if (input.substr(peg$currPos, 2) === peg$
|
|
982
|
-
s3 = peg$
|
|
1043
|
+
if (input.substr(peg$currPos, 2) === peg$c12) {
|
|
1044
|
+
s3 = peg$c12;
|
|
983
1045
|
peg$currPos += 2;
|
|
984
1046
|
} else {
|
|
985
1047
|
s3 = peg$FAILED;
|
|
986
|
-
if (peg$silentFails === 0) { peg$fail(peg$
|
|
1048
|
+
if (peg$silentFails === 0) { peg$fail(peg$e17); }
|
|
987
1049
|
}
|
|
988
1050
|
if (s3 === peg$FAILED) {
|
|
989
1051
|
s3 = peg$currPos;
|
|
990
1052
|
s4 = peg$currPos;
|
|
991
1053
|
peg$silentFails++;
|
|
992
1054
|
if (input.charCodeAt(peg$currPos) === 34) {
|
|
993
|
-
s5 = peg$
|
|
1055
|
+
s5 = peg$c10;
|
|
994
1056
|
peg$currPos++;
|
|
995
1057
|
} else {
|
|
996
1058
|
s5 = peg$FAILED;
|
|
997
|
-
if (peg$silentFails === 0) { peg$fail(peg$
|
|
1059
|
+
if (peg$silentFails === 0) { peg$fail(peg$e15); }
|
|
998
1060
|
}
|
|
999
1061
|
peg$silentFails--;
|
|
1000
1062
|
if (s5 === peg$FAILED) {
|
|
@@ -1026,11 +1088,11 @@ function peg$parse(input, options) {
|
|
|
1026
1088
|
}
|
|
1027
1089
|
}
|
|
1028
1090
|
if (input.charCodeAt(peg$currPos) === 34) {
|
|
1029
|
-
s3 = peg$
|
|
1091
|
+
s3 = peg$c10;
|
|
1030
1092
|
peg$currPos++;
|
|
1031
1093
|
} else {
|
|
1032
1094
|
s3 = peg$FAILED;
|
|
1033
|
-
if (peg$silentFails === 0) { peg$fail(peg$
|
|
1095
|
+
if (peg$silentFails === 0) { peg$fail(peg$e15); }
|
|
1034
1096
|
}
|
|
1035
1097
|
if (s3 !== peg$FAILED) {
|
|
1036
1098
|
s1 = [s1, s2, s3];
|
|
@@ -1053,20 +1115,20 @@ function peg$parse(input, options) {
|
|
|
1053
1115
|
peg$silentFails++;
|
|
1054
1116
|
s0 = [];
|
|
1055
1117
|
s1 = input.charAt(peg$currPos);
|
|
1056
|
-
if (peg$
|
|
1118
|
+
if (peg$r3.test(s1)) {
|
|
1057
1119
|
peg$currPos++;
|
|
1058
1120
|
} else {
|
|
1059
1121
|
s1 = peg$FAILED;
|
|
1060
|
-
if (peg$silentFails === 0) { peg$fail(peg$
|
|
1122
|
+
if (peg$silentFails === 0) { peg$fail(peg$e18); }
|
|
1061
1123
|
}
|
|
1062
1124
|
while (s1 !== peg$FAILED) {
|
|
1063
1125
|
s0.push(s1);
|
|
1064
1126
|
s1 = input.charAt(peg$currPos);
|
|
1065
|
-
if (peg$
|
|
1127
|
+
if (peg$r3.test(s1)) {
|
|
1066
1128
|
peg$currPos++;
|
|
1067
1129
|
} else {
|
|
1068
1130
|
s1 = peg$FAILED;
|
|
1069
|
-
if (peg$silentFails === 0) { peg$fail(peg$
|
|
1131
|
+
if (peg$silentFails === 0) { peg$fail(peg$e18); }
|
|
1070
1132
|
}
|
|
1071
1133
|
}
|
|
1072
1134
|
peg$silentFails--;
|
|
@@ -1080,21 +1142,21 @@ function peg$parse(input, options) {
|
|
|
1080
1142
|
peg$silentFails++;
|
|
1081
1143
|
s0 = [];
|
|
1082
1144
|
s1 = input.charAt(peg$currPos);
|
|
1083
|
-
if (peg$
|
|
1145
|
+
if (peg$r3.test(s1)) {
|
|
1084
1146
|
peg$currPos++;
|
|
1085
1147
|
} else {
|
|
1086
1148
|
s1 = peg$FAILED;
|
|
1087
|
-
if (peg$silentFails === 0) { peg$fail(peg$
|
|
1149
|
+
if (peg$silentFails === 0) { peg$fail(peg$e18); }
|
|
1088
1150
|
}
|
|
1089
1151
|
if (s1 !== peg$FAILED) {
|
|
1090
1152
|
while (s1 !== peg$FAILED) {
|
|
1091
1153
|
s0.push(s1);
|
|
1092
1154
|
s1 = input.charAt(peg$currPos);
|
|
1093
|
-
if (peg$
|
|
1155
|
+
if (peg$r3.test(s1)) {
|
|
1094
1156
|
peg$currPos++;
|
|
1095
1157
|
} else {
|
|
1096
1158
|
s1 = peg$FAILED;
|
|
1097
|
-
if (peg$silentFails === 0) { peg$fail(peg$
|
|
1159
|
+
if (peg$silentFails === 0) { peg$fail(peg$e18); }
|
|
1098
1160
|
}
|
|
1099
1161
|
}
|
|
1100
1162
|
} else {
|
|
@@ -1103,7 +1165,7 @@ function peg$parse(input, options) {
|
|
|
1103
1165
|
peg$silentFails--;
|
|
1104
1166
|
if (s0 === peg$FAILED) {
|
|
1105
1167
|
s1 = peg$FAILED;
|
|
1106
|
-
if (peg$silentFails === 0) { peg$fail(peg$
|
|
1168
|
+
if (peg$silentFails === 0) { peg$fail(peg$e19); }
|
|
1107
1169
|
}
|
|
1108
1170
|
|
|
1109
1171
|
return s0;
|
package/package.json
CHANGED