libpetri 1.8.5 → 2.0.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/README.md +47 -0
- package/dist/{chunk-B2D5DMTO.js → chunk-4L6JVKH4.js} +165 -8
- package/dist/chunk-4L6JVKH4.js.map +1 -0
- package/dist/chunk-H62Z76FY.js +1346 -0
- package/dist/chunk-H62Z76FY.js.map +1 -0
- package/dist/chunk-SXK2Z45Z.js +50 -0
- package/dist/chunk-SXK2Z45Z.js.map +1 -0
- package/dist/debug/index.d.ts +50 -3
- package/dist/debug/index.js +64 -6
- package/dist/debug/index.js.map +1 -1
- package/dist/doclet/index.d.ts +152 -31
- package/dist/doclet/index.js +458 -57
- package/dist/doclet/index.js.map +1 -1
- package/dist/doclet/resources/petrinet-diagrams.css +384 -7
- package/dist/doclet/resources/petrinet-diagrams.js +7214 -114
- package/dist/dot-exporter-PMHOQHZ3.js +8 -0
- package/dist/{event-store-BnyHh3TF.d.ts → event-store-2zkXeQkd.d.ts} +1 -1
- package/dist/export/index.d.ts +16 -2
- package/dist/export/index.js +1 -1
- package/dist/index.d.ts +41 -5
- package/dist/index.js +1221 -35
- package/dist/index.js.map +1 -1
- package/dist/petri-net-BDrj4XZE.d.ts +1461 -0
- package/dist/render-P6GROU7J.js +16 -0
- package/dist/render-P6GROU7J.js.map +1 -0
- package/dist/verification/index.d.ts +3 -144
- package/dist/verification/index.js +30 -1214
- package/dist/verification/index.js.map +1 -1
- package/dist/viewer/index.d.ts +196 -0
- package/dist/viewer/index.js +442 -0
- package/dist/viewer/index.js.map +1 -0
- package/dist/viewer/viewer-static.iife.js +3 -0
- package/dist/viewer/viewer.css +503 -0
- package/dist/viewer/viewer.iife.js +7219 -0
- package/package.json +17 -8
- package/dist/chunk-B2D5DMTO.js.map +0 -1
- package/dist/chunk-VQ4XMJTD.js +0 -107
- package/dist/chunk-VQ4XMJTD.js.map +0 -1
- package/dist/dot-exporter-3CVCH6J4.js +0 -8
- package/dist/petri-net-D-GN9g_D.d.ts +0 -570
- /package/dist/{dot-exporter-3CVCH6J4.js.map → dot-exporter-PMHOQHZ3.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
|
+
SmtVerifier,
|
|
2
3
|
allPlaces,
|
|
3
4
|
and,
|
|
4
5
|
andPlaces,
|
|
5
6
|
enumerateBranches,
|
|
6
7
|
forwardInput,
|
|
8
|
+
isProven,
|
|
9
|
+
isViolated,
|
|
7
10
|
outPlace,
|
|
8
11
|
timeout,
|
|
9
12
|
timeoutPlace,
|
|
10
13
|
xor,
|
|
11
14
|
xorPlaces
|
|
12
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-H62Z76FY.js";
|
|
16
|
+
import {
|
|
17
|
+
eventTransitionName,
|
|
18
|
+
isFailureEvent
|
|
19
|
+
} from "./chunk-SXK2Z45Z.js";
|
|
13
20
|
import {
|
|
14
21
|
MAX_DURATION_MS,
|
|
15
22
|
deadline,
|
|
@@ -126,9 +133,10 @@ function consumptionCount(spec, available) {
|
|
|
126
133
|
|
|
127
134
|
// src/core/transition-action.ts
|
|
128
135
|
function passthrough() {
|
|
129
|
-
return
|
|
130
|
-
};
|
|
136
|
+
return PASSTHROUGH;
|
|
131
137
|
}
|
|
138
|
+
var PASSTHROUGH = async () => {
|
|
139
|
+
};
|
|
132
140
|
function transform(fn) {
|
|
133
141
|
return async (ctx) => {
|
|
134
142
|
const result = fn(ctx);
|
|
@@ -165,13 +173,13 @@ function produce(place2, value) {
|
|
|
165
173
|
}
|
|
166
174
|
function withTimeout(action, timeoutMs, timeoutPlace2, timeoutValue) {
|
|
167
175
|
return (ctx) => {
|
|
168
|
-
return new Promise((
|
|
176
|
+
return new Promise((resolve2, reject) => {
|
|
169
177
|
let completed = false;
|
|
170
178
|
const timer = setTimeout(() => {
|
|
171
179
|
if (!completed) {
|
|
172
180
|
completed = true;
|
|
173
181
|
ctx.output(timeoutPlace2, timeoutValue);
|
|
174
|
-
|
|
182
|
+
resolve2();
|
|
175
183
|
}
|
|
176
184
|
}, timeoutMs);
|
|
177
185
|
action(ctx).then(
|
|
@@ -179,7 +187,7 @@ function withTimeout(action, timeoutMs, timeoutPlace2, timeoutValue) {
|
|
|
179
187
|
if (!completed) {
|
|
180
188
|
completed = true;
|
|
181
189
|
clearTimeout(timer);
|
|
182
|
-
|
|
190
|
+
resolve2();
|
|
183
191
|
}
|
|
184
192
|
},
|
|
185
193
|
(err) => {
|
|
@@ -627,6 +635,423 @@ function findForwardInputs(out) {
|
|
|
627
635
|
}
|
|
628
636
|
}
|
|
629
637
|
|
|
638
|
+
// src/core/compose-bindings.ts
|
|
639
|
+
var COMPOSE_BINDINGS_KEY = /* @__PURE__ */ Symbol("ComposeBindings.internal");
|
|
640
|
+
var ComposeBindings = class {
|
|
641
|
+
_portBindings = /* @__PURE__ */ new Map();
|
|
642
|
+
_channelBindings = /* @__PURE__ */ new Map();
|
|
643
|
+
/**
|
|
644
|
+
* @internal Use {@link PetriNetBuilder.compose} — instances are produced
|
|
645
|
+
* by the host builder and supplied to the caller's callback.
|
|
646
|
+
*/
|
|
647
|
+
constructor(key) {
|
|
648
|
+
if (key !== COMPOSE_BINDINGS_KEY) {
|
|
649
|
+
throw new Error(
|
|
650
|
+
"Use PetriNetBuilder.compose(instance, b => ...) \u2014 ComposeBindings is not directly constructible"
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Binds the named interface port to the given caller place per **MOD-020**.
|
|
656
|
+
*
|
|
657
|
+
* The port name is the **original** (pre-prefix) name declared in the
|
|
658
|
+
* subnet's `Interface`. The typed `<T>` parameter ensures the caller
|
|
659
|
+
* place's token type matches the interface port's token type at compile
|
|
660
|
+
* time per [MOD-022]; TypeScript does not validate the type at runtime
|
|
661
|
+
* because generics are erased.
|
|
662
|
+
*
|
|
663
|
+
* @throws when `portName` is already bound on this builder
|
|
664
|
+
*/
|
|
665
|
+
bindPort(portName, callerPlace) {
|
|
666
|
+
if (this._portBindings.has(portName)) {
|
|
667
|
+
throw new Error(`Port '${portName}' is already bound`);
|
|
668
|
+
}
|
|
669
|
+
this._portBindings.set(portName, callerPlace);
|
|
670
|
+
return this;
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Records a synchronous channel binding per **MOD-021**: at compose time,
|
|
674
|
+
* the instance-side renamed channel transition is merged with
|
|
675
|
+
* `callerTransition` into a single transition in the resulting flat net.
|
|
676
|
+
*
|
|
677
|
+
* **Status**: channel composition is implemented in task #13. Recording a
|
|
678
|
+
* channel binding here is permitted, but `compose(...)` currently raises
|
|
679
|
+
* an `Error` when any channel binding is present.
|
|
680
|
+
*
|
|
681
|
+
* @throws when `channelName` is already bound on this builder
|
|
682
|
+
*/
|
|
683
|
+
bindChannel(channelName, callerTransition) {
|
|
684
|
+
if (this._channelBindings.has(channelName)) {
|
|
685
|
+
throw new Error(`Channel '${channelName}' is already bound`);
|
|
686
|
+
}
|
|
687
|
+
this._channelBindings.set(channelName, callerTransition);
|
|
688
|
+
return this;
|
|
689
|
+
}
|
|
690
|
+
/** Returns an unmodifiable view of the recorded port bindings. */
|
|
691
|
+
portBindings() {
|
|
692
|
+
return this._portBindings;
|
|
693
|
+
}
|
|
694
|
+
/** Returns an unmodifiable view of the recorded channel bindings. */
|
|
695
|
+
channelBindings() {
|
|
696
|
+
return this._channelBindings;
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
function __createComposeBindings() {
|
|
700
|
+
return new ComposeBindings(COMPOSE_BINDINGS_KEY);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// src/core/internal/subnet-rewriter.ts
|
|
704
|
+
function renamePlace(orig, prefix) {
|
|
705
|
+
return place(prefix + "/" + orig.name);
|
|
706
|
+
}
|
|
707
|
+
function renameNet(body, prefix, placeRemap, transitionRemap) {
|
|
708
|
+
placeRemap.clear();
|
|
709
|
+
transitionRemap.clear();
|
|
710
|
+
for (const orig of body.places) {
|
|
711
|
+
placeRemap.set(orig.name, renamePlace(orig, prefix));
|
|
712
|
+
}
|
|
713
|
+
const builder = PetriNet.builder(prefix + "/" + body.name);
|
|
714
|
+
for (const renamedPlace of placeRemap.values()) {
|
|
715
|
+
builder.place(renamedPlace);
|
|
716
|
+
}
|
|
717
|
+
for (const t of body.transitions) {
|
|
718
|
+
const renamed = rewriteTransition(t, prefix, placeRemap);
|
|
719
|
+
transitionRemap.set(t.name, renamed);
|
|
720
|
+
builder.transition(renamed);
|
|
721
|
+
}
|
|
722
|
+
return builder.build();
|
|
723
|
+
}
|
|
724
|
+
function rewriteTransition(t, prefix, placeRemap) {
|
|
725
|
+
return rebuildWithName(t, prefix + "/" + t.name, placeRemap);
|
|
726
|
+
}
|
|
727
|
+
function substitutePlaces(t, remap) {
|
|
728
|
+
return rebuildWithName(t, t.name, remap);
|
|
729
|
+
}
|
|
730
|
+
function rebuildWithName(t, name, remap) {
|
|
731
|
+
const builder = Transition.builder(name).timing(t.timing).priority(t.priority).action(t.action);
|
|
732
|
+
if (t.inputSpecs.length > 0) {
|
|
733
|
+
const rewrittenInputs = new Array(t.inputSpecs.length);
|
|
734
|
+
for (let i = 0; i < t.inputSpecs.length; i++) {
|
|
735
|
+
rewrittenInputs[i] = rewriteIn(t.inputSpecs[i], remap);
|
|
736
|
+
}
|
|
737
|
+
builder.inputs(...rewrittenInputs);
|
|
738
|
+
}
|
|
739
|
+
if (t.outputSpec !== null) {
|
|
740
|
+
builder.outputs(rewriteOut(t.outputSpec, remap));
|
|
741
|
+
}
|
|
742
|
+
for (let i = 0; i < t.inhibitors.length; i++) {
|
|
743
|
+
builder.inhibitor(rewriteInhibitor(t.inhibitors[i], remap).place);
|
|
744
|
+
}
|
|
745
|
+
for (let i = 0; i < t.reads.length; i++) {
|
|
746
|
+
builder.read(rewriteRead(t.reads[i], remap).place);
|
|
747
|
+
}
|
|
748
|
+
for (let i = 0; i < t.resets.length; i++) {
|
|
749
|
+
builder.reset(rewriteReset(t.resets[i], remap).place);
|
|
750
|
+
}
|
|
751
|
+
return builder.build();
|
|
752
|
+
}
|
|
753
|
+
function rewriteIn(spec, remap) {
|
|
754
|
+
switch (spec.type) {
|
|
755
|
+
case "one":
|
|
756
|
+
return one(resolve(spec.place, remap), spec.guard);
|
|
757
|
+
case "exactly":
|
|
758
|
+
return exactly(spec.count, resolve(spec.place, remap), spec.guard);
|
|
759
|
+
case "all":
|
|
760
|
+
return all(resolve(spec.place, remap), spec.guard);
|
|
761
|
+
case "at-least":
|
|
762
|
+
return atLeast(spec.minimum, resolve(spec.place, remap), spec.guard);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
function rewriteOut(out, remap) {
|
|
766
|
+
switch (out.type) {
|
|
767
|
+
case "place":
|
|
768
|
+
return outPlace(resolve(out.place, remap));
|
|
769
|
+
case "forward-input":
|
|
770
|
+
return forwardInput(resolve(out.from, remap), resolve(out.to, remap));
|
|
771
|
+
case "and": {
|
|
772
|
+
const children = out.children;
|
|
773
|
+
const rewritten = new Array(children.length);
|
|
774
|
+
for (let i = 0; i < children.length; i++) {
|
|
775
|
+
rewritten[i] = rewriteOut(children[i], remap);
|
|
776
|
+
}
|
|
777
|
+
return { type: "and", children: rewritten };
|
|
778
|
+
}
|
|
779
|
+
case "xor": {
|
|
780
|
+
const children = out.children;
|
|
781
|
+
const rewritten = new Array(children.length);
|
|
782
|
+
for (let i = 0; i < children.length; i++) {
|
|
783
|
+
rewritten[i] = rewriteOut(children[i], remap);
|
|
784
|
+
}
|
|
785
|
+
return { type: "xor", children: rewritten };
|
|
786
|
+
}
|
|
787
|
+
case "timeout":
|
|
788
|
+
return timeout(out.afterMs, rewriteOut(out.child, remap));
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
function rewriteInhibitor(inh, remap) {
|
|
792
|
+
return { type: "inhibitor", place: resolve(inh.place, remap) };
|
|
793
|
+
}
|
|
794
|
+
function rewriteRead(rd, remap) {
|
|
795
|
+
return { type: "read", place: resolve(rd.place, remap) };
|
|
796
|
+
}
|
|
797
|
+
function rewriteReset(rs, remap) {
|
|
798
|
+
return { type: "reset", place: resolve(rs.place, remap) };
|
|
799
|
+
}
|
|
800
|
+
function resolve(p, remap) {
|
|
801
|
+
const replaced = remap.get(p.name);
|
|
802
|
+
return replaced !== void 0 ? replaced : p;
|
|
803
|
+
}
|
|
804
|
+
function mergeTransitions(caller, instance, mergedName) {
|
|
805
|
+
if (mergedName === void 0 || mergedName === null || mergedName.length === 0) {
|
|
806
|
+
throw new Error("mergeTransitions: mergedName must be a non-empty string");
|
|
807
|
+
}
|
|
808
|
+
const mergedTiming = mergeTimings(caller.timing, instance.timing, mergedName);
|
|
809
|
+
const mergedPriority = pickPriority(caller.priority, instance.priority);
|
|
810
|
+
const mergedAction = composeActions(caller.action, instance.action);
|
|
811
|
+
const builder = Transition.builder(mergedName).timing(mergedTiming).priority(mergedPriority);
|
|
812
|
+
if (mergedAction !== void 0) {
|
|
813
|
+
builder.action(mergedAction);
|
|
814
|
+
}
|
|
815
|
+
const unionedInputs = unionArcs(caller.inputSpecs, instance.inputSpecs, keyOfIn);
|
|
816
|
+
if (unionedInputs.length > 0) {
|
|
817
|
+
builder.inputs(...unionedInputs);
|
|
818
|
+
}
|
|
819
|
+
const mergedOutput = mergeOutputs(caller.outputSpec, instance.outputSpec);
|
|
820
|
+
if (mergedOutput !== null) {
|
|
821
|
+
builder.outputs(mergedOutput);
|
|
822
|
+
}
|
|
823
|
+
for (const inh of unionArcs(
|
|
824
|
+
caller.inhibitors,
|
|
825
|
+
instance.inhibitors,
|
|
826
|
+
keyOfInhibitor
|
|
827
|
+
)) {
|
|
828
|
+
builder.inhibitor(inh.place);
|
|
829
|
+
}
|
|
830
|
+
for (const rd of unionArcs(caller.reads, instance.reads, keyOfRead)) {
|
|
831
|
+
builder.read(rd.place);
|
|
832
|
+
}
|
|
833
|
+
for (const rs of unionArcs(caller.resets, instance.resets, keyOfReset)) {
|
|
834
|
+
builder.reset(rs.place);
|
|
835
|
+
}
|
|
836
|
+
return builder.build();
|
|
837
|
+
}
|
|
838
|
+
function mergeTimings(caller, instance, channelName) {
|
|
839
|
+
if (caller.type === "immediate" && instance.type === "immediate") {
|
|
840
|
+
return { type: "immediate" };
|
|
841
|
+
}
|
|
842
|
+
if (caller.type === "immediate") return instance;
|
|
843
|
+
if (instance.type === "immediate") return caller;
|
|
844
|
+
if (timingsEqual(caller, instance)) return caller;
|
|
845
|
+
throw new Error(
|
|
846
|
+
`Channel composition '${channelName}': conflicting non-Immediate timings \u2014 caller-side ${describeTiming(caller)} vs instance-side ${describeTiming(instance)}. Resolve explicitly by aligning the timings on either side (MOD-021).`
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
function pickPriority(callerPriority, _instancePriority) {
|
|
850
|
+
return callerPriority;
|
|
851
|
+
}
|
|
852
|
+
function composeActions(caller, instance) {
|
|
853
|
+
const callerIsPassthrough = caller === void 0 || isPassthrough(caller);
|
|
854
|
+
const instanceIsPassthrough = instance === void 0 || isPassthrough(instance);
|
|
855
|
+
if (callerIsPassthrough && instanceIsPassthrough) return void 0;
|
|
856
|
+
if (callerIsPassthrough) return instance;
|
|
857
|
+
if (instanceIsPassthrough) return caller;
|
|
858
|
+
return async (ctx) => {
|
|
859
|
+
await caller(ctx);
|
|
860
|
+
await instance(ctx);
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
function mergeOutputs(caller, instance) {
|
|
864
|
+
if (caller === null && instance === null) return null;
|
|
865
|
+
if (caller === null) return instance;
|
|
866
|
+
if (instance === null) return caller;
|
|
867
|
+
return and(caller, instance);
|
|
868
|
+
}
|
|
869
|
+
function unionArcs(caller, instance, keyOf) {
|
|
870
|
+
if (caller.length === 0 && instance.length === 0) return [];
|
|
871
|
+
const seen = /* @__PURE__ */ new Map();
|
|
872
|
+
for (let i2 = 0; i2 < caller.length; i2++) {
|
|
873
|
+
const arc = caller[i2];
|
|
874
|
+
const key = keyOf(arc);
|
|
875
|
+
if (!seen.has(key)) seen.set(key, arc);
|
|
876
|
+
}
|
|
877
|
+
for (let i2 = 0; i2 < instance.length; i2++) {
|
|
878
|
+
const arc = instance[i2];
|
|
879
|
+
const key = keyOf(arc);
|
|
880
|
+
if (!seen.has(key)) seen.set(key, arc);
|
|
881
|
+
}
|
|
882
|
+
const result = new Array(seen.size);
|
|
883
|
+
let i = 0;
|
|
884
|
+
for (const arc of seen.values()) result[i++] = arc;
|
|
885
|
+
return result;
|
|
886
|
+
}
|
|
887
|
+
function timingsEqual(a, b) {
|
|
888
|
+
if (a.type !== b.type) return false;
|
|
889
|
+
switch (a.type) {
|
|
890
|
+
case "immediate":
|
|
891
|
+
return true;
|
|
892
|
+
case "deadline":
|
|
893
|
+
return a.byMs === b.byMs;
|
|
894
|
+
case "delayed":
|
|
895
|
+
return a.afterMs === b.afterMs;
|
|
896
|
+
case "window": {
|
|
897
|
+
const w = b;
|
|
898
|
+
return a.earliestMs === w.earliestMs && a.latestMs === w.latestMs;
|
|
899
|
+
}
|
|
900
|
+
case "exact":
|
|
901
|
+
return a.atMs === b.atMs;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
function describeTiming(t) {
|
|
905
|
+
switch (t.type) {
|
|
906
|
+
case "immediate":
|
|
907
|
+
return "Immediate";
|
|
908
|
+
case "deadline":
|
|
909
|
+
return `Deadline(byMs=${t.byMs})`;
|
|
910
|
+
case "delayed":
|
|
911
|
+
return `Delayed(afterMs=${t.afterMs})`;
|
|
912
|
+
case "window":
|
|
913
|
+
return `Window(earliestMs=${t.earliestMs}, latestMs=${t.latestMs})`;
|
|
914
|
+
case "exact":
|
|
915
|
+
return `Exact(atMs=${t.atMs})`;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
var PASSTHROUGH_REF = passthrough();
|
|
919
|
+
function isPassthrough(action) {
|
|
920
|
+
return action === PASSTHROUGH_REF;
|
|
921
|
+
}
|
|
922
|
+
function keyOfIn(arc) {
|
|
923
|
+
switch (arc.type) {
|
|
924
|
+
case "one":
|
|
925
|
+
return arc.guard === void 0 ? `one|${arc.place.name}` : `one|${arc.place.name}|g:${guardKey(arc.guard)}`;
|
|
926
|
+
case "exactly":
|
|
927
|
+
return arc.guard === void 0 ? `exactly|${arc.place.name}|${arc.count}` : `exactly|${arc.place.name}|${arc.count}|g:${guardKey(arc.guard)}`;
|
|
928
|
+
case "all":
|
|
929
|
+
return arc.guard === void 0 ? `all|${arc.place.name}` : `all|${arc.place.name}|g:${guardKey(arc.guard)}`;
|
|
930
|
+
case "at-least":
|
|
931
|
+
return arc.guard === void 0 ? `atLeast|${arc.place.name}|${arc.minimum}` : `atLeast|${arc.place.name}|${arc.minimum}|g:${guardKey(arc.guard)}`;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
function keyOfInhibitor(arc) {
|
|
935
|
+
return `inh|${arc.place.name}`;
|
|
936
|
+
}
|
|
937
|
+
function keyOfRead(arc) {
|
|
938
|
+
return `read|${arc.place.name}`;
|
|
939
|
+
}
|
|
940
|
+
function keyOfReset(arc) {
|
|
941
|
+
return `reset|${arc.place.name}`;
|
|
942
|
+
}
|
|
943
|
+
var GUARD_KEYS = /* @__PURE__ */ new WeakMap();
|
|
944
|
+
var guardKeyCounter = 0;
|
|
945
|
+
function guardKey(g) {
|
|
946
|
+
let k = GUARD_KEYS.get(g);
|
|
947
|
+
if (k === void 0) {
|
|
948
|
+
k = String(++guardKeyCounter);
|
|
949
|
+
GUARD_KEYS.set(g, k);
|
|
950
|
+
}
|
|
951
|
+
return k;
|
|
952
|
+
}
|
|
953
|
+
function applyFusion(transitions, fusionMap) {
|
|
954
|
+
const rewritten = /* @__PURE__ */ new Set();
|
|
955
|
+
for (const t of transitions) {
|
|
956
|
+
rewritten.add(substitutePlaces(t, fusionMap));
|
|
957
|
+
}
|
|
958
|
+
return rewritten;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// src/core/fusion-set.ts
|
|
962
|
+
var FUSION_SET_KEY = /* @__PURE__ */ Symbol("FusionSet.internal");
|
|
963
|
+
var FusionSet = class _FusionSet {
|
|
964
|
+
name;
|
|
965
|
+
members;
|
|
966
|
+
/** @internal Use {@link FusionSet.builder} or {@link FusionSet.of} to create instances. */
|
|
967
|
+
constructor(key, name, members) {
|
|
968
|
+
if (key !== FUSION_SET_KEY) {
|
|
969
|
+
throw new Error("Use FusionSet.builder() or FusionSet.of() to create instances");
|
|
970
|
+
}
|
|
971
|
+
this.name = name;
|
|
972
|
+
this.members = members;
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Returns the canonical member — by convention, the first declared member.
|
|
976
|
+
* The canonical place's identity survives into the resulting flat net.
|
|
977
|
+
*/
|
|
978
|
+
get canonical() {
|
|
979
|
+
return this.members[0];
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Returns all members **except** the canonical member, in declaration order.
|
|
983
|
+
* These are the places that get substituted away at
|
|
984
|
+
* {@link import('./petri-net.js').PetriNetBuilder.build} time.
|
|
985
|
+
*
|
|
986
|
+
* For a single-member (degenerate) set, returns an empty array.
|
|
987
|
+
*/
|
|
988
|
+
nonCanonical() {
|
|
989
|
+
if (this.members.length <= 1) return [];
|
|
990
|
+
return this.members.slice(1);
|
|
991
|
+
}
|
|
992
|
+
toString() {
|
|
993
|
+
return `FusionSet[${this.name}, canonical=${this.canonical.name}, members=${this.members.length}]`;
|
|
994
|
+
}
|
|
995
|
+
// ============================================================
|
|
996
|
+
// Static factories
|
|
997
|
+
// ============================================================
|
|
998
|
+
/** Returns a fresh {@link FusionSetBuilder} with the given human-readable name. */
|
|
999
|
+
static builder(name) {
|
|
1000
|
+
return new FusionSetBuilder(name);
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Convenience factory: builds a fusion set whose first member is `first`
|
|
1004
|
+
* (the canonical) and whose remaining members are `rest`, all sharing the
|
|
1005
|
+
* type `<T>`.
|
|
1006
|
+
*
|
|
1007
|
+
* The varargs form ensures static-type homogeneity at the call site (the
|
|
1008
|
+
* TypeScript compiler checks every `rest` entry is a `Place<T>`).
|
|
1009
|
+
*/
|
|
1010
|
+
static of(name, first, ...rest) {
|
|
1011
|
+
const members = [first];
|
|
1012
|
+
for (const p of rest) members.push(p);
|
|
1013
|
+
return new _FusionSet(FUSION_SET_KEY, name, Object.freeze(members));
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
var FusionSetBuilder = class {
|
|
1017
|
+
_name;
|
|
1018
|
+
_members = [];
|
|
1019
|
+
constructor(name) {
|
|
1020
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
1021
|
+
throw new Error("FusionSet.builder: name must be a non-empty string");
|
|
1022
|
+
}
|
|
1023
|
+
this._name = name;
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Appends a member to the fusion set. The first member becomes the canonical
|
|
1027
|
+
* place per the convention documented on {@link FusionSet}.
|
|
1028
|
+
*
|
|
1029
|
+
* The `<T>` parameter is for compile-time guidance only: callers writing
|
|
1030
|
+
* typed code at the same call site benefit from the compiler checking
|
|
1031
|
+
* `Place<T>` at each member.
|
|
1032
|
+
*/
|
|
1033
|
+
member(place2) {
|
|
1034
|
+
if (place2 === void 0 || place2 === null) {
|
|
1035
|
+
throw new Error(`FusionSet '${this._name}': member place must not be null`);
|
|
1036
|
+
}
|
|
1037
|
+
this._members.push(place2);
|
|
1038
|
+
return this;
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Builds the immutable {@link FusionSet}. Validates that the set has at
|
|
1042
|
+
* least one member; the empty set is rejected as malformed per **MOD-060**.
|
|
1043
|
+
* Single-member sets are accepted as a structurally degenerate no-op.
|
|
1044
|
+
*/
|
|
1045
|
+
build() {
|
|
1046
|
+
if (this._members.length === 0) {
|
|
1047
|
+
throw new Error(
|
|
1048
|
+
`FusionSet '${this._name}': must have at least one member (MOD-060)`
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
return new FusionSet(FUSION_SET_KEY, this._name, Object.freeze(this._members.slice()));
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
|
|
630
1055
|
// src/core/petri-net.ts
|
|
631
1056
|
var PETRI_NET_KEY = /* @__PURE__ */ Symbol("PetriNet.internal");
|
|
632
1057
|
var PetriNet = class _PetriNet {
|
|
@@ -673,6 +1098,7 @@ var PetriNetBuilder = class {
|
|
|
673
1098
|
_name;
|
|
674
1099
|
_places = /* @__PURE__ */ new Set();
|
|
675
1100
|
_transitions = /* @__PURE__ */ new Set();
|
|
1101
|
+
_fusionSets = [];
|
|
676
1102
|
constructor(name) {
|
|
677
1103
|
this._name = name;
|
|
678
1104
|
}
|
|
@@ -711,8 +1137,182 @@ var PetriNetBuilder = class {
|
|
|
711
1137
|
for (const t of transitions) this.transition(t);
|
|
712
1138
|
return this;
|
|
713
1139
|
}
|
|
1140
|
+
compose(instance, arg) {
|
|
1141
|
+
if (typeof arg === "function") {
|
|
1142
|
+
const bindings = __createComposeBindings();
|
|
1143
|
+
arg(bindings);
|
|
1144
|
+
return this.composeInternal(instance, bindings.portBindings(), bindings.channelBindings());
|
|
1145
|
+
}
|
|
1146
|
+
const portMappings = arg instanceof Map ? arg : new Map(Object.entries(arg));
|
|
1147
|
+
return this.composeInternal(instance, portMappings, /* @__PURE__ */ new Map());
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* @internal Shared compose implementation: validates port and channel
|
|
1151
|
+
* bindings, builds the place-substitution map, walks every renamed-body
|
|
1152
|
+
* transition through {@link substitutePlaces}, applies channel merges per
|
|
1153
|
+
* [MOD-021], and adds the resulting transitions to this builder.
|
|
1154
|
+
*
|
|
1155
|
+
* ## Channel-merge flow ([MOD-021])
|
|
1156
|
+
*
|
|
1157
|
+
* 1. Collect rewritten instance transitions into a working `Map<string,
|
|
1158
|
+
* Transition>` keyed by prefixed transition name (deferred — not yet
|
|
1159
|
+
* added to the builder's transition set).
|
|
1160
|
+
* 2. For each channel binding, resolve the renamed instance-side
|
|
1161
|
+
* transition through `instance.channel(channelName)`, then look up its
|
|
1162
|
+
* rewritten counterpart in the working map by name.
|
|
1163
|
+
* 3. Replace the working-map entry with a {@link mergeTransitions} result
|
|
1164
|
+
* that fuses caller-side + instance-side; remove the rewritten
|
|
1165
|
+
* instance-side entry. Also replace (or add) the caller-side
|
|
1166
|
+
* transition in this builder's transition set with the same merged
|
|
1167
|
+
* result, indexed under the caller's name slot.
|
|
1168
|
+
* 4. Add the surviving (un-merged) entries to this builder.
|
|
1169
|
+
*
|
|
1170
|
+
* The deferral matters: writing the rewritten instance transitions to the
|
|
1171
|
+
* builder eagerly would force a second "remove-then-replace" pass to
|
|
1172
|
+
* apply the channel merges, complicating the place-collection invariants.
|
|
1173
|
+
* Collecting first and merging second keeps the builder's transition set
|
|
1174
|
+
* finalized exactly once.
|
|
1175
|
+
*
|
|
1176
|
+
* Keying the working map by prefixed transition name (rather than by
|
|
1177
|
+
* Transition reference) is also robust against a prior
|
|
1178
|
+
* {@link Instance.bindActions} call that may have rebuilt the renamed-body
|
|
1179
|
+
* transitions, breaking identity equality between the body and the
|
|
1180
|
+
* channel-handle map — but the prefixed names remain stable.
|
|
1181
|
+
*/
|
|
1182
|
+
composeInternal(instance, portMappings, channelBindings) {
|
|
1183
|
+
const iface = instance.def.iface;
|
|
1184
|
+
const mergeMap = /* @__PURE__ */ new Map();
|
|
1185
|
+
for (const [portName, callerPlace] of portMappings) {
|
|
1186
|
+
const port = iface.port(portName);
|
|
1187
|
+
if (port === void 0) {
|
|
1188
|
+
const knownPorts = [];
|
|
1189
|
+
for (const p of iface.ports.values()) knownPorts.push(p.name);
|
|
1190
|
+
throw new Error(
|
|
1191
|
+
`compose: no port named '${portName}' on subnet '${instance.def.name}' (instance prefix '${instance.prefix}'). Known ports: [${knownPorts.join(", ")}]`
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
const ifacePlace = instance.port(portName);
|
|
1195
|
+
mergeMap.set(ifacePlace.name, callerPlace);
|
|
1196
|
+
}
|
|
1197
|
+
const rewrittenByName = /* @__PURE__ */ new Map();
|
|
1198
|
+
for (const t of instance.renamedBody.transitions) {
|
|
1199
|
+
rewrittenByName.set(t.name, substitutePlaces(t, mergeMap));
|
|
1200
|
+
}
|
|
1201
|
+
for (const [channelName, callerTrans] of channelBindings) {
|
|
1202
|
+
let instanceRenamedChannel;
|
|
1203
|
+
try {
|
|
1204
|
+
instanceRenamedChannel = instance.channel(channelName);
|
|
1205
|
+
} catch (cause) {
|
|
1206
|
+
const knownChannels = [];
|
|
1207
|
+
for (const c of iface.channels.values()) knownChannels.push(c.name);
|
|
1208
|
+
const err = new Error(
|
|
1209
|
+
`compose: no channel named '${channelName}' on subnet '${instance.def.name}' (instance prefix '${instance.prefix}'). Known channels: [${knownChannels.join(", ")}]`
|
|
1210
|
+
);
|
|
1211
|
+
err.cause = cause;
|
|
1212
|
+
throw err;
|
|
1213
|
+
}
|
|
1214
|
+
const rewrittenInstanceChannel = rewrittenByName.get(instanceRenamedChannel.name);
|
|
1215
|
+
if (rewrittenInstanceChannel === void 0) {
|
|
1216
|
+
throw new Error(
|
|
1217
|
+
`compose: channel '${channelName}' resolved to a transition '${instanceRenamedChannel.name}' that is not present in the renamed body. This indicates a SubnetDef invariant violation.`
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
const merged = mergeTransitions(callerTrans, rewrittenInstanceChannel, callerTrans.name);
|
|
1221
|
+
rewrittenByName.delete(instanceRenamedChannel.name);
|
|
1222
|
+
this._transitions.delete(callerTrans);
|
|
1223
|
+
this.transition(merged);
|
|
1224
|
+
}
|
|
1225
|
+
for (const rewritten of rewrittenByName.values()) {
|
|
1226
|
+
this.transition(rewritten);
|
|
1227
|
+
}
|
|
1228
|
+
return this;
|
|
1229
|
+
}
|
|
1230
|
+
fuse(...args) {
|
|
1231
|
+
if (args.length === 1 && typeof args[0] === "function") {
|
|
1232
|
+
const declarer = args[0];
|
|
1233
|
+
const fb = FusionSet.builder(this._name + "-fusion");
|
|
1234
|
+
declarer(fb);
|
|
1235
|
+
this._fusionSets.push(fb.build());
|
|
1236
|
+
return this;
|
|
1237
|
+
}
|
|
1238
|
+
for (const s of args) {
|
|
1239
|
+
if (s === void 0 || s === null) {
|
|
1240
|
+
throw new Error("fuse: fusion set must not be null");
|
|
1241
|
+
}
|
|
1242
|
+
this._fusionSets.push(s);
|
|
1243
|
+
}
|
|
1244
|
+
return this;
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Builds the immutable {@link PetriNet}, applying fusion resolution (per
|
|
1248
|
+
* **MOD-061**) AFTER all transition/composition accumulation:
|
|
1249
|
+
*
|
|
1250
|
+
* 1. Detect overlapping fusion sets — a single place declared in two sets
|
|
1251
|
+
* is rejected with an `Error`.
|
|
1252
|
+
* 2. Build the `non-canonical → canonical` substitution map across all
|
|
1253
|
+
* sets, keyed by non-canonical place name (matching the rewriter's
|
|
1254
|
+
* Map<string, Place<unknown>> convention — TypeScript Place identity is
|
|
1255
|
+
* name-based per `runtime/compiled-net.ts`).
|
|
1256
|
+
* 3. Walk every transition through {@link applyFusion} to rewrite arc place
|
|
1257
|
+
* references.
|
|
1258
|
+
* 4. Re-derive the place set from the rewritten transitions plus any
|
|
1259
|
+
* caller-declared standalone places, dropping non-canonical members.
|
|
1260
|
+
* Caller-declared standalone places that happen to be non-canonical
|
|
1261
|
+
* members are also dropped.
|
|
1262
|
+
*
|
|
1263
|
+
* If no fusion sets were registered, the build is the trivial
|
|
1264
|
+
* `new PetriNet(...)` — the fusion machinery has no per-build cost when
|
|
1265
|
+
* unused.
|
|
1266
|
+
*
|
|
1267
|
+
* @throws when two fusion sets share a place
|
|
1268
|
+
*/
|
|
714
1269
|
build() {
|
|
715
|
-
|
|
1270
|
+
if (this._fusionSets.length === 0) {
|
|
1271
|
+
return new PetriNet(PETRI_NET_KEY, this._name, this._places, this._transitions);
|
|
1272
|
+
}
|
|
1273
|
+
return this.buildWithFusion();
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* @internal Fusion-resolution pass per **MOD-061**. Split out from
|
|
1277
|
+
* {@link build} so the no-fusion fast path stays trivial.
|
|
1278
|
+
*/
|
|
1279
|
+
buildWithFusion() {
|
|
1280
|
+
const ownership = /* @__PURE__ */ new Map();
|
|
1281
|
+
for (const set of this._fusionSets) {
|
|
1282
|
+
for (const member of set.members) {
|
|
1283
|
+
const prior = ownership.get(member.name);
|
|
1284
|
+
if (prior !== void 0 && prior !== set) {
|
|
1285
|
+
throw new Error(
|
|
1286
|
+
`Fusion overlap: place '${member.name}' appears in two fusion sets ('${prior.name}' and '${set.name}'). A place may appear in at most one fusion set (MOD-060).`
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
ownership.set(member.name, set);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
const fusionMap = /* @__PURE__ */ new Map();
|
|
1293
|
+
const nonCanonicalNames = /* @__PURE__ */ new Set();
|
|
1294
|
+
for (const set of this._fusionSets) {
|
|
1295
|
+
const canonical = set.canonical;
|
|
1296
|
+
for (const nc of set.nonCanonical()) {
|
|
1297
|
+
fusionMap.set(nc.name, canonical);
|
|
1298
|
+
nonCanonicalNames.add(nc.name);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
const rewrittenTransitions = applyFusion(this._transitions, fusionMap);
|
|
1302
|
+
const rebuiltPlaces = /* @__PURE__ */ new Set();
|
|
1303
|
+
for (const p of this._places) {
|
|
1304
|
+
if (!nonCanonicalNames.has(p.name)) {
|
|
1305
|
+
rebuiltPlaces.add(p);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
for (const t of rewrittenTransitions) {
|
|
1309
|
+
for (const spec of t.inputSpecs) rebuiltPlaces.add(spec.place);
|
|
1310
|
+
for (const p of t.outputPlaces()) rebuiltPlaces.add(p);
|
|
1311
|
+
for (const inh of t.inhibitors) rebuiltPlaces.add(inh.place);
|
|
1312
|
+
for (const r of t.reads) rebuiltPlaces.add(r.place);
|
|
1313
|
+
for (const r of t.resets) rebuiltPlaces.add(r.place);
|
|
1314
|
+
}
|
|
1315
|
+
return new PetriNet(PETRI_NET_KEY, this._name, rebuiltPlaces, rewrittenTransitions);
|
|
716
1316
|
}
|
|
717
1317
|
};
|
|
718
1318
|
function rebuildWithAction(t, action) {
|
|
@@ -735,6 +1335,602 @@ function rebuildWithAction(t, action) {
|
|
|
735
1335
|
return builder.build();
|
|
736
1336
|
}
|
|
737
1337
|
|
|
1338
|
+
// src/core/subnet.ts
|
|
1339
|
+
function closedSubnet(net) {
|
|
1340
|
+
return { kind: "closed", net };
|
|
1341
|
+
}
|
|
1342
|
+
function openSubnet(def) {
|
|
1343
|
+
return { kind: "open", def };
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
// src/core/interface.ts
|
|
1347
|
+
var INTERFACE_KEY = /* @__PURE__ */ Symbol("Interface.internal");
|
|
1348
|
+
var Interface = class {
|
|
1349
|
+
ports;
|
|
1350
|
+
channels;
|
|
1351
|
+
/** @internal Use {@link Interface.builder} to create instances. */
|
|
1352
|
+
constructor(key, ports, channels) {
|
|
1353
|
+
if (key !== INTERFACE_KEY) throw new Error("Use Interface.builder() to create instances");
|
|
1354
|
+
this.ports = ports;
|
|
1355
|
+
this.channels = channels;
|
|
1356
|
+
}
|
|
1357
|
+
/** Looks up a port by name. Returns undefined when absent. */
|
|
1358
|
+
port(name) {
|
|
1359
|
+
return this.ports.get(name);
|
|
1360
|
+
}
|
|
1361
|
+
/** Looks up a channel by name. Returns undefined when absent. */
|
|
1362
|
+
channel(name) {
|
|
1363
|
+
return this.channels.get(name);
|
|
1364
|
+
}
|
|
1365
|
+
/**
|
|
1366
|
+
* Looks up a port by name and returns its underlying place, narrowed to
|
|
1367
|
+
* `Place<T>`. Returns undefined when the port does not exist.
|
|
1368
|
+
*
|
|
1369
|
+
* Note: TypeScript erases generics at runtime, so unlike Java's
|
|
1370
|
+
* `portPlaceAs(name, Class<T>)` this method cannot verify the token type at
|
|
1371
|
+
* runtime — the caller is trusted to supply the correct `T`. Per **MOD-022**,
|
|
1372
|
+
* type compatibility is enforced at compile time only in TypeScript.
|
|
1373
|
+
*/
|
|
1374
|
+
placeAs(name) {
|
|
1375
|
+
const p = this.ports.get(name);
|
|
1376
|
+
if (p === void 0) return void 0;
|
|
1377
|
+
return p.place;
|
|
1378
|
+
}
|
|
1379
|
+
static builder() {
|
|
1380
|
+
return new InterfaceBuilder();
|
|
1381
|
+
}
|
|
1382
|
+
};
|
|
1383
|
+
var InterfaceBuilder = class {
|
|
1384
|
+
_ports = /* @__PURE__ */ new Map();
|
|
1385
|
+
_channels = /* @__PURE__ */ new Map();
|
|
1386
|
+
/** Add a pre-built port (rejects duplicate names). */
|
|
1387
|
+
port(port) {
|
|
1388
|
+
if (this._ports.has(port.name)) {
|
|
1389
|
+
throw new Error(`Duplicate port name: '${port.name}'`);
|
|
1390
|
+
}
|
|
1391
|
+
this._ports.set(port.name, port);
|
|
1392
|
+
return this;
|
|
1393
|
+
}
|
|
1394
|
+
/** Add an input port (advisory direction). */
|
|
1395
|
+
inputPort(name, place2) {
|
|
1396
|
+
return this.port({ name, direction: "input", place: place2 });
|
|
1397
|
+
}
|
|
1398
|
+
/** Add an output port (advisory direction). */
|
|
1399
|
+
outputPort(name, place2) {
|
|
1400
|
+
return this.port({ name, direction: "output", place: place2 });
|
|
1401
|
+
}
|
|
1402
|
+
/** Add an in-out port (advisory direction). */
|
|
1403
|
+
inoutPort(name, place2) {
|
|
1404
|
+
return this.port({ name, direction: "inout", place: place2 });
|
|
1405
|
+
}
|
|
1406
|
+
channel(channelOrName, transition) {
|
|
1407
|
+
const ch = typeof channelOrName === "string" ? { name: channelOrName, transition } : channelOrName;
|
|
1408
|
+
if (this._channels.has(ch.name)) {
|
|
1409
|
+
throw new Error(`Duplicate channel name: '${ch.name}'`);
|
|
1410
|
+
}
|
|
1411
|
+
this._channels.set(ch.name, ch);
|
|
1412
|
+
return this;
|
|
1413
|
+
}
|
|
1414
|
+
/** @internal Bulk-add ports already validated by the caller. */
|
|
1415
|
+
portsAll(ports) {
|
|
1416
|
+
for (const p of ports) this.port(p);
|
|
1417
|
+
return this;
|
|
1418
|
+
}
|
|
1419
|
+
/** @internal Bulk-add channels already validated by the caller. */
|
|
1420
|
+
channelsAll(channels) {
|
|
1421
|
+
for (const c of channels) this.channel(c);
|
|
1422
|
+
return this;
|
|
1423
|
+
}
|
|
1424
|
+
build() {
|
|
1425
|
+
return new Interface(
|
|
1426
|
+
INTERFACE_KEY,
|
|
1427
|
+
new Map(this._ports),
|
|
1428
|
+
new Map(this._channels)
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
|
|
1433
|
+
// src/core/instance.ts
|
|
1434
|
+
var INSTANCE_KEY = /* @__PURE__ */ Symbol("Instance.internal");
|
|
1435
|
+
var Instance = class {
|
|
1436
|
+
prefix;
|
|
1437
|
+
def;
|
|
1438
|
+
renamedBody;
|
|
1439
|
+
portHandles;
|
|
1440
|
+
channelHandles;
|
|
1441
|
+
params;
|
|
1442
|
+
/**
|
|
1443
|
+
* @internal Use {@link SubnetDef.instantiate} (or the internal factory
|
|
1444
|
+
* {@link __createInstance}) to create instances.
|
|
1445
|
+
*/
|
|
1446
|
+
constructor(key, prefix, def, renamedBody, portHandles, channelHandles, params) {
|
|
1447
|
+
if (key !== INSTANCE_KEY) {
|
|
1448
|
+
throw new Error("Use SubnetDef.instantiate() to create Instance values");
|
|
1449
|
+
}
|
|
1450
|
+
this.prefix = prefix;
|
|
1451
|
+
this.def = def;
|
|
1452
|
+
this.renamedBody = renamedBody;
|
|
1453
|
+
this.portHandles = portHandles;
|
|
1454
|
+
this.channelHandles = channelHandles;
|
|
1455
|
+
this.params = params;
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* Returns the renamed {@link Place} corresponding to the named port.
|
|
1459
|
+
*
|
|
1460
|
+
* The port name is the **original** (pre-prefix) name as declared in the
|
|
1461
|
+
* subnet's `Interface`. Per **MOD-022**, TypeScript enforces token-type
|
|
1462
|
+
* compatibility at compile time only; this method does not validate `T`
|
|
1463
|
+
* at runtime. A missing name raises an `Error`.
|
|
1464
|
+
*
|
|
1465
|
+
* @throws when the port name is unknown
|
|
1466
|
+
*/
|
|
1467
|
+
port(name) {
|
|
1468
|
+
const p = this.portHandles.get(name);
|
|
1469
|
+
if (p === void 0) {
|
|
1470
|
+
throw new Error(`No port named '${name}' in instance '${this.prefix}'`);
|
|
1471
|
+
}
|
|
1472
|
+
return p;
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Returns the renamed {@link Transition} corresponding to the named channel.
|
|
1476
|
+
*
|
|
1477
|
+
* @throws when the channel name is unknown
|
|
1478
|
+
*/
|
|
1479
|
+
channel(name) {
|
|
1480
|
+
const t = this.channelHandles.get(name);
|
|
1481
|
+
if (t === void 0) {
|
|
1482
|
+
throw new Error(`No channel named '${name}' in instance '${this.prefix}'`);
|
|
1483
|
+
}
|
|
1484
|
+
return t;
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Returns the debug-UI descriptor for this instance per **MOD-041**.
|
|
1488
|
+
*/
|
|
1489
|
+
descriptor() {
|
|
1490
|
+
const transitions = [];
|
|
1491
|
+
for (const t of this.renamedBody.transitions) transitions.push(t.name);
|
|
1492
|
+
const exposedPlaces = [];
|
|
1493
|
+
for (const p of this.portHandles.values()) exposedPlaces.push(p.name);
|
|
1494
|
+
return {
|
|
1495
|
+
prefix: this.prefix,
|
|
1496
|
+
defName: this.def.name,
|
|
1497
|
+
transitions,
|
|
1498
|
+
exposedPlaces,
|
|
1499
|
+
params: this.params,
|
|
1500
|
+
parentPrefix: null
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Produces a derived instance whose specified transitions (named by their
|
|
1505
|
+
* **original**, pre-prefix names) carry the supplied actions per **MOD-030**.
|
|
1506
|
+
*
|
|
1507
|
+
* For each entry `[originalName, action]`, the renamed body is searched for
|
|
1508
|
+
* the transition whose name equals `prefix + "/" + originalName`. The
|
|
1509
|
+
* resulting instance shares the original `def`, `prefix`, `params`, and
|
|
1510
|
+
* port/channel handle topology — only the renamed body is rebuilt with new
|
|
1511
|
+
* actions. Per **MOD-030**, calling `bindActions` on one instance does NOT
|
|
1512
|
+
* affect the actions held by other instances of the same `def`.
|
|
1513
|
+
*
|
|
1514
|
+
* Unrecognised original names raise an `Error` so typos surface eagerly.
|
|
1515
|
+
*/
|
|
1516
|
+
bindActions(actionsByOriginalName) {
|
|
1517
|
+
const prefixedByName = /* @__PURE__ */ new Map();
|
|
1518
|
+
const renamedNames = /* @__PURE__ */ new Set();
|
|
1519
|
+
for (const t of this.renamedBody.transitions) {
|
|
1520
|
+
renamedNames.add(t.name);
|
|
1521
|
+
}
|
|
1522
|
+
for (const originalName of Object.keys(actionsByOriginalName)) {
|
|
1523
|
+
const prefixed = this.prefix + "/" + originalName;
|
|
1524
|
+
if (!renamedNames.has(prefixed)) {
|
|
1525
|
+
throw new Error(
|
|
1526
|
+
`Instance.bindActions: no transition '${originalName}' (resolved as '${prefixed}') in instance '${this.prefix}' of subnet '${this.def.name}'`
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
prefixedByName.set(prefixed, actionsByOriginalName[originalName]);
|
|
1530
|
+
}
|
|
1531
|
+
const reboundBody = this.renamedBody.bindActionsWithResolver((name) => {
|
|
1532
|
+
const action = prefixedByName.get(name);
|
|
1533
|
+
if (action !== void 0) return action;
|
|
1534
|
+
for (const t of this.renamedBody.transitions) {
|
|
1535
|
+
if (t.name === name) return t.action;
|
|
1536
|
+
}
|
|
1537
|
+
throw new Error(`Instance.bindActions: resolver invoked with unknown name '${name}'`);
|
|
1538
|
+
});
|
|
1539
|
+
const reboundChannelHandles = /* @__PURE__ */ new Map();
|
|
1540
|
+
if (this.channelHandles.size > 0) {
|
|
1541
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1542
|
+
for (const t of reboundBody.transitions) {
|
|
1543
|
+
byName.set(t.name, t);
|
|
1544
|
+
}
|
|
1545
|
+
for (const [name, oldT] of this.channelHandles) {
|
|
1546
|
+
const refreshed = byName.get(oldT.name);
|
|
1547
|
+
if (refreshed === void 0) {
|
|
1548
|
+
throw new Error(
|
|
1549
|
+
`Instance.bindActions: channel '${name}' transition '${oldT.name}' not found in rebound body`
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
reboundChannelHandles.set(name, refreshed);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
return __createInstance(
|
|
1556
|
+
this.prefix,
|
|
1557
|
+
this.def,
|
|
1558
|
+
reboundBody,
|
|
1559
|
+
this.portHandles,
|
|
1560
|
+
reboundChannelHandles,
|
|
1561
|
+
this.params
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
function __createInstance(prefix, def, renamedBody, portHandles, channelHandles, params) {
|
|
1566
|
+
return new Instance(
|
|
1567
|
+
INSTANCE_KEY,
|
|
1568
|
+
prefix,
|
|
1569
|
+
def,
|
|
1570
|
+
renamedBody,
|
|
1571
|
+
portHandles,
|
|
1572
|
+
channelHandles,
|
|
1573
|
+
params
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
// src/verification/verification-harness.ts
|
|
1578
|
+
function buildVerificationResult(syntheticNet, perProperty) {
|
|
1579
|
+
const frozen = new Map(perProperty);
|
|
1580
|
+
return {
|
|
1581
|
+
syntheticNet,
|
|
1582
|
+
perProperty: frozen,
|
|
1583
|
+
allProven() {
|
|
1584
|
+
for (const r of frozen.values()) {
|
|
1585
|
+
if (!isProven(r)) return false;
|
|
1586
|
+
}
|
|
1587
|
+
return true;
|
|
1588
|
+
},
|
|
1589
|
+
anyViolated() {
|
|
1590
|
+
for (const r of frozen.values()) {
|
|
1591
|
+
if (isViolated(r)) return true;
|
|
1592
|
+
}
|
|
1593
|
+
return false;
|
|
1594
|
+
}
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
function normaliseGenerators(generators) {
|
|
1598
|
+
if (generators instanceof Map) return new Map(generators);
|
|
1599
|
+
return new Map(Object.entries(generators));
|
|
1600
|
+
}
|
|
1601
|
+
function normaliseProperties(properties) {
|
|
1602
|
+
if (Array.isArray(properties)) return [...properties];
|
|
1603
|
+
return [...properties];
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
// src/core/subnet-def.ts
|
|
1607
|
+
var SUBNET_DEF_KEY = /* @__PURE__ */ Symbol("SubnetDef.internal");
|
|
1608
|
+
var SubnetDef = class _SubnetDef {
|
|
1609
|
+
name;
|
|
1610
|
+
body;
|
|
1611
|
+
iface;
|
|
1612
|
+
/** @internal Use {@link SubnetDef.builder} or {@link SubnetDef.fromNet} to create instances. */
|
|
1613
|
+
constructor(key, name, body, iface) {
|
|
1614
|
+
if (key !== SUBNET_DEF_KEY) {
|
|
1615
|
+
throw new Error("Use SubnetDef.builder() or SubnetDef.fromNet() to create instances");
|
|
1616
|
+
}
|
|
1617
|
+
this.name = name;
|
|
1618
|
+
this.body = body;
|
|
1619
|
+
this.iface = iface;
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Produces a renamed module instance per **MOD-010**, **MOD-011**, **MOD-012**,
|
|
1623
|
+
* and **MOD-030**.
|
|
1624
|
+
*
|
|
1625
|
+
* The rename pass walks every place and transition of the body net,
|
|
1626
|
+
* substituting each name with `prefix + "/" + originalName`, and rebuilds
|
|
1627
|
+
* every arc with rewritten place references. Transition timing, priority,
|
|
1628
|
+
* and action are carried through by reference (action sharing per
|
|
1629
|
+
* [MOD-030]). The renamed body is itself a structurally valid `PetriNet`
|
|
1630
|
+
* per [CORE-040]; per-instance state isolation per [MOD-012] is a
|
|
1631
|
+
* structural consequence of distinct prefixed names.
|
|
1632
|
+
*
|
|
1633
|
+
* ## Prefix validation
|
|
1634
|
+
*
|
|
1635
|
+
* The `"/"` character is reserved as the prefix separator (per [MOD-010]).
|
|
1636
|
+
* User-supplied prefixes MUST NOT contain `"/"`; nested instantiation is
|
|
1637
|
+
* performed by the future `PetriNetBuilder.compose(...)` mechanism (per
|
|
1638
|
+
* [MOD-013]). A prefix containing `"/"` raises an `Error`.
|
|
1639
|
+
*
|
|
1640
|
+
* @param prefix the rename prefix (non-empty, must not contain `"/"`)
|
|
1641
|
+
* @param params the parameter value carried through to `Instance.params`
|
|
1642
|
+
* (may be omitted when `P` is `void`)
|
|
1643
|
+
* @throws when `prefix` is empty or contains `"/"`
|
|
1644
|
+
*/
|
|
1645
|
+
instantiate(prefix, params) {
|
|
1646
|
+
validatePrefix(prefix);
|
|
1647
|
+
const placeRemap = /* @__PURE__ */ new Map();
|
|
1648
|
+
const transitionRemap = /* @__PURE__ */ new Map();
|
|
1649
|
+
const renamedBody = renameNet(this.body, prefix, placeRemap, transitionRemap);
|
|
1650
|
+
const portHandles = /* @__PURE__ */ new Map();
|
|
1651
|
+
for (const port of this.iface.ports.values()) {
|
|
1652
|
+
const renamed = placeRemap.get(port.place.name);
|
|
1653
|
+
if (renamed === void 0) {
|
|
1654
|
+
throw new Error(
|
|
1655
|
+
`Port '${port.name}' references place '${port.place.name}' that was not found in the renamed body. This indicates a SubnetDef invariant violation.`
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
portHandles.set(port.name, renamed);
|
|
1659
|
+
}
|
|
1660
|
+
const channelHandles = /* @__PURE__ */ new Map();
|
|
1661
|
+
for (const channel of this.iface.channels.values()) {
|
|
1662
|
+
const renamed = transitionRemap.get(channel.transition.name);
|
|
1663
|
+
if (renamed === void 0) {
|
|
1664
|
+
throw new Error(
|
|
1665
|
+
`Channel '${channel.name}' references transition '${channel.transition.name}' that was not found in the renamed body. This indicates a SubnetDef invariant violation.`
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1668
|
+
channelHandles.set(channel.name, renamed);
|
|
1669
|
+
}
|
|
1670
|
+
return __createInstance(
|
|
1671
|
+
prefix,
|
|
1672
|
+
this,
|
|
1673
|
+
renamedBody,
|
|
1674
|
+
portHandles,
|
|
1675
|
+
channelHandles,
|
|
1676
|
+
params
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1679
|
+
/**
|
|
1680
|
+
* Verifies safety properties of this subnet definition **in isolation** per
|
|
1681
|
+
* **MOD-051**, by wrapping it in a synthetic enclosing net where each input
|
|
1682
|
+
* port is fed by an {@link EnvironmentPlace} (token-source per the harness
|
|
1683
|
+
* generator) and each output port is observed via a synthetic place. The
|
|
1684
|
+
* standard {@link SmtVerifier} (per [MOD-050]) is invoked once per property
|
|
1685
|
+
* declared in the harness; the resulting per-property outcomes are
|
|
1686
|
+
* aggregated into a {@link VerificationResult}.
|
|
1687
|
+
*
|
|
1688
|
+
* ## Synthetic-net construction
|
|
1689
|
+
*
|
|
1690
|
+
* The synthetic enclosing net is built by:
|
|
1691
|
+
* 1. Instantiating this `SubnetDef` with the prefix `"sut"` (system-under-test)
|
|
1692
|
+
* and `harness.params`.
|
|
1693
|
+
* 2. For each input or in-out port on the interface, looking up the
|
|
1694
|
+
* harness generator by port name, allocating a synthetic
|
|
1695
|
+
* {@link Place}`<unknown>` of the same conceptual token type as the
|
|
1696
|
+
* port, wrapping it in an {@link EnvironmentPlace}, and binding the
|
|
1697
|
+
* port to that synthetic place via
|
|
1698
|
+
* {@link import('./petri-net.js').PetriNetBuilder.compose}. The supplier
|
|
1699
|
+
* is invoked once at construction time to materialize the seed token —
|
|
1700
|
+
* its presence is what bounds the input behavior under analysis. **If
|
|
1701
|
+
* the harness map is missing a generator for a required input or in-out
|
|
1702
|
+
* port, an `Error` is thrown.**
|
|
1703
|
+
* 3. For each output or in-out port, allocating a synthetic observation
|
|
1704
|
+
* {@link Place}`<unknown>` and binding the port to it via the same
|
|
1705
|
+
* `compose(...)` call. The verifier inspects this place's reachability
|
|
1706
|
+
* / marking through the standard property APIs ({@link SmtProperty}).
|
|
1707
|
+
* 4. Building the resulting flat {@link PetriNet} per [MOD-023] (the
|
|
1708
|
+
* verifier is composition-unaware per [MOD-050]).
|
|
1709
|
+
*
|
|
1710
|
+
* ## Per-property invocation
|
|
1711
|
+
*
|
|
1712
|
+
* Each {@link SmtProperty} in the harness is verified independently against
|
|
1713
|
+
* the same synthetic net. The synthetic environment places are passed
|
|
1714
|
+
* through to the verifier so that places driven by the harness generators
|
|
1715
|
+
* are treated under the verifier's environment-analysis semantics rather
|
|
1716
|
+
* than as ordinary sink places.
|
|
1717
|
+
*
|
|
1718
|
+
* @param harness the verification harness — supplies parameters, input-port
|
|
1719
|
+
* token generators, and the property set
|
|
1720
|
+
* @returns a {@link VerificationResult} aggregating per-property
|
|
1721
|
+
* {@link SmtVerificationResult}s
|
|
1722
|
+
* @throws when an input or in-out port is missing a harness generator
|
|
1723
|
+
*/
|
|
1724
|
+
async verify(harness) {
|
|
1725
|
+
if (harness === null || harness === void 0) {
|
|
1726
|
+
throw new Error("SubnetDef.verify: harness must not be null/undefined");
|
|
1727
|
+
}
|
|
1728
|
+
const sut = this.instantiate("sut", harness.params);
|
|
1729
|
+
const generators = normaliseGenerators(harness.portInputGenerators);
|
|
1730
|
+
const properties = normaliseProperties(harness.properties);
|
|
1731
|
+
const envPlaces = [];
|
|
1732
|
+
const portMappings = /* @__PURE__ */ new Map();
|
|
1733
|
+
for (const port of this.iface.ports.values()) {
|
|
1734
|
+
const portName = port.name;
|
|
1735
|
+
switch (port.direction) {
|
|
1736
|
+
case "input": {
|
|
1737
|
+
const generator = generators.get(portName);
|
|
1738
|
+
if (generator === void 0) {
|
|
1739
|
+
throw new Error(
|
|
1740
|
+
`verify: harness is missing an input generator for port '${portName}' on subnet '${this.name}' (MOD-051)`
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
const seed = generator();
|
|
1744
|
+
if (seed === null || seed === void 0) {
|
|
1745
|
+
throw new Error(
|
|
1746
|
+
`verify: input generator for port '${portName}' on subnet '${this.name}' produced null/undefined`
|
|
1747
|
+
);
|
|
1748
|
+
}
|
|
1749
|
+
const synth = place(`harness_in_${portName}`);
|
|
1750
|
+
envPlaces.push(environmentPlace(synth.name));
|
|
1751
|
+
portMappings.set(portName, synth);
|
|
1752
|
+
break;
|
|
1753
|
+
}
|
|
1754
|
+
case "output": {
|
|
1755
|
+
const synth = place(`harness_out_${portName}`);
|
|
1756
|
+
portMappings.set(portName, synth);
|
|
1757
|
+
break;
|
|
1758
|
+
}
|
|
1759
|
+
case "inout": {
|
|
1760
|
+
const generator = generators.get(portName);
|
|
1761
|
+
if (generator === void 0) {
|
|
1762
|
+
throw new Error(
|
|
1763
|
+
`verify: harness is missing an input generator for in-out port '${portName}' on subnet '${this.name}' (MOD-051)`
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
const seed = generator();
|
|
1767
|
+
if (seed === null || seed === void 0) {
|
|
1768
|
+
throw new Error(
|
|
1769
|
+
`verify: input generator for in-out port '${portName}' on subnet '${this.name}' produced null/undefined`
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1772
|
+
const synth = place(`harness_io_${portName}`);
|
|
1773
|
+
envPlaces.push(environmentPlace(synth.name));
|
|
1774
|
+
portMappings.set(portName, synth);
|
|
1775
|
+
break;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
const syntheticNet = PetriNet.builder("verify_" + this.name).compose(sut, portMappings).build();
|
|
1780
|
+
const perProperty = /* @__PURE__ */ new Map();
|
|
1781
|
+
for (const property of properties) {
|
|
1782
|
+
const verifier = SmtVerifier.forNet(syntheticNet).property(property);
|
|
1783
|
+
if (envPlaces.length > 0) {
|
|
1784
|
+
verifier.environmentPlaces(...envPlaces);
|
|
1785
|
+
}
|
|
1786
|
+
const result = await verifier.verify();
|
|
1787
|
+
perProperty.set(property, result);
|
|
1788
|
+
}
|
|
1789
|
+
return buildVerificationResult(syntheticNet, perProperty);
|
|
1790
|
+
}
|
|
1791
|
+
// ============================================================
|
|
1792
|
+
// Static factories
|
|
1793
|
+
// ============================================================
|
|
1794
|
+
static builder(name) {
|
|
1795
|
+
return new SubnetDefBuilder(name);
|
|
1796
|
+
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Retrofit utility per **MOD-014**: wraps an existing closed {@link PetriNet}
|
|
1799
|
+
* plus an {@link Interface} into an unparameterised `SubnetDef<void>`.
|
|
1800
|
+
*
|
|
1801
|
+
* Validation per **MOD-014** / **MOD-006** is enforced before the result is
|
|
1802
|
+
* constructed:
|
|
1803
|
+
* - Every port's underlying `Place` must be present in `net.places`.
|
|
1804
|
+
* - Every channel's underlying `Transition` must be present in `net.transitions`.
|
|
1805
|
+
* - Port and channel name uniqueness is re-validated defensively (the
|
|
1806
|
+
* `Interface` builder already enforces this; hand-built `Interface`
|
|
1807
|
+
* values bypass that path).
|
|
1808
|
+
*
|
|
1809
|
+
* The resulting subnet definition is unparameterised (parameter type is `void`).
|
|
1810
|
+
*
|
|
1811
|
+
* @throws when a port place is not in `net.places`, a channel transition is
|
|
1812
|
+
* not in `net.transitions`, or port/channel names are not unique.
|
|
1813
|
+
*/
|
|
1814
|
+
static fromNet(net, iface) {
|
|
1815
|
+
const bodyPlaces = net.places;
|
|
1816
|
+
const bodyTransitions = net.transitions;
|
|
1817
|
+
const seenPortNames = /* @__PURE__ */ new Set();
|
|
1818
|
+
for (const port of iface.ports.values()) {
|
|
1819
|
+
if (seenPortNames.has(port.name)) {
|
|
1820
|
+
throw new Error(
|
|
1821
|
+
`fromNet: duplicate port name '${port.name}' on interface for net '${net.name}' (MOD-006)`
|
|
1822
|
+
);
|
|
1823
|
+
}
|
|
1824
|
+
seenPortNames.add(port.name);
|
|
1825
|
+
if (!bodyPlaces.has(port.place)) {
|
|
1826
|
+
throw new Error(
|
|
1827
|
+
`fromNet: port '${port.name}' references place '${port.place.name}' which is not in net '${net.name}' (MOD-014/MOD-006)`
|
|
1828
|
+
);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
const seenChannelNames = /* @__PURE__ */ new Set();
|
|
1832
|
+
for (const channel of iface.channels.values()) {
|
|
1833
|
+
if (seenChannelNames.has(channel.name)) {
|
|
1834
|
+
throw new Error(
|
|
1835
|
+
`fromNet: duplicate channel name '${channel.name}' on interface for net '${net.name}' (MOD-006)`
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
seenChannelNames.add(channel.name);
|
|
1839
|
+
if (!bodyTransitions.has(channel.transition)) {
|
|
1840
|
+
throw new Error(
|
|
1841
|
+
`fromNet: channel '${channel.name}' references transition '${channel.transition.name}' which is not in net '${net.name}' (MOD-014/MOD-006)`
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
return new _SubnetDef(SUBNET_DEF_KEY, net.name, net, iface);
|
|
1846
|
+
}
|
|
1847
|
+
};
|
|
1848
|
+
var SubnetDefBuilder = class {
|
|
1849
|
+
_name;
|
|
1850
|
+
_bodyBuilder;
|
|
1851
|
+
_ports = [];
|
|
1852
|
+
_channels = [];
|
|
1853
|
+
constructor(name) {
|
|
1854
|
+
this._name = name;
|
|
1855
|
+
this._bodyBuilder = PetriNet.builder(name);
|
|
1856
|
+
}
|
|
1857
|
+
// -------- body construction (delegates to PetriNet.Builder) --------
|
|
1858
|
+
transition(transition) {
|
|
1859
|
+
this._bodyBuilder.transition(transition);
|
|
1860
|
+
return this;
|
|
1861
|
+
}
|
|
1862
|
+
transitions(...transitions) {
|
|
1863
|
+
this._bodyBuilder.transitions(...transitions);
|
|
1864
|
+
return this;
|
|
1865
|
+
}
|
|
1866
|
+
place(place2) {
|
|
1867
|
+
this._bodyBuilder.place(place2);
|
|
1868
|
+
return this;
|
|
1869
|
+
}
|
|
1870
|
+
// -------- interface declarations --------
|
|
1871
|
+
inputPort(name, place2) {
|
|
1872
|
+
this._ports.push({ name, direction: "input", place: place2 });
|
|
1873
|
+
return this;
|
|
1874
|
+
}
|
|
1875
|
+
outputPort(name, place2) {
|
|
1876
|
+
this._ports.push({ name, direction: "output", place: place2 });
|
|
1877
|
+
return this;
|
|
1878
|
+
}
|
|
1879
|
+
inoutPort(name, place2) {
|
|
1880
|
+
this._ports.push({ name, direction: "inout", place: place2 });
|
|
1881
|
+
return this;
|
|
1882
|
+
}
|
|
1883
|
+
channel(name, transition) {
|
|
1884
|
+
this._channels.push({ name, transition });
|
|
1885
|
+
return this;
|
|
1886
|
+
}
|
|
1887
|
+
// -------- build & validate (MOD-006) --------
|
|
1888
|
+
build() {
|
|
1889
|
+
const built = this._bodyBuilder.build();
|
|
1890
|
+
const bodyPlaces = built.places;
|
|
1891
|
+
const bodyTransitions = built.transitions;
|
|
1892
|
+
const portNames = /* @__PURE__ */ new Set();
|
|
1893
|
+
for (const port of this._ports) {
|
|
1894
|
+
if (portNames.has(port.name)) {
|
|
1895
|
+
throw new Error(`Subnet '${this._name}': duplicate port name '${port.name}'`);
|
|
1896
|
+
}
|
|
1897
|
+
portNames.add(port.name);
|
|
1898
|
+
if (!bodyPlaces.has(port.place)) {
|
|
1899
|
+
throw new Error(
|
|
1900
|
+
`Subnet '${this._name}': port '${port.name}' references place '${port.place.name}' which is not in the body`
|
|
1901
|
+
);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
const channelNames = /* @__PURE__ */ new Set();
|
|
1905
|
+
for (const channel of this._channels) {
|
|
1906
|
+
if (channelNames.has(channel.name)) {
|
|
1907
|
+
throw new Error(`Subnet '${this._name}': duplicate channel name '${channel.name}'`);
|
|
1908
|
+
}
|
|
1909
|
+
channelNames.add(channel.name);
|
|
1910
|
+
if (!bodyTransitions.has(channel.transition)) {
|
|
1911
|
+
throw new Error(
|
|
1912
|
+
`Subnet '${this._name}': channel '${channel.name}' references transition '${channel.transition.name}' which is not in the body`
|
|
1913
|
+
);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
const ifaceBuilder = Interface.builder();
|
|
1917
|
+
ifaceBuilder.portsAll(this._ports);
|
|
1918
|
+
ifaceBuilder.channelsAll(this._channels);
|
|
1919
|
+
const iface = ifaceBuilder.build();
|
|
1920
|
+
return new SubnetDef(SUBNET_DEF_KEY, this._name, built, iface);
|
|
1921
|
+
}
|
|
1922
|
+
};
|
|
1923
|
+
function validatePrefix(prefix) {
|
|
1924
|
+
if (typeof prefix !== "string" || prefix.length === 0) {
|
|
1925
|
+
throw new Error("SubnetDef.instantiate: prefix must be a non-empty string");
|
|
1926
|
+
}
|
|
1927
|
+
if (prefix.indexOf("/") >= 0) {
|
|
1928
|
+
throw new Error(
|
|
1929
|
+
`SubnetDef.instantiate: prefix must not contain '/' (reserved as the prefix separator per MOD-010); use compose(...) for nested instantiation. Got: '${prefix}'`
|
|
1930
|
+
);
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
|
|
738
1934
|
// src/runtime/marking.ts
|
|
739
1935
|
var EMPTY_TOKENS = Object.freeze([]);
|
|
740
1936
|
var Marking = class _Marking {
|
|
@@ -1045,26 +2241,6 @@ function intersects(snapshot, mask) {
|
|
|
1045
2241
|
return false;
|
|
1046
2242
|
}
|
|
1047
2243
|
|
|
1048
|
-
// src/event/net-event.ts
|
|
1049
|
-
function eventTransitionName(event) {
|
|
1050
|
-
switch (event.type) {
|
|
1051
|
-
case "transition-enabled":
|
|
1052
|
-
case "transition-clock-restarted":
|
|
1053
|
-
case "transition-started":
|
|
1054
|
-
case "transition-completed":
|
|
1055
|
-
case "transition-failed":
|
|
1056
|
-
case "transition-timed-out":
|
|
1057
|
-
case "action-timed-out":
|
|
1058
|
-
case "log-message":
|
|
1059
|
-
return event.transitionName;
|
|
1060
|
-
default:
|
|
1061
|
-
return null;
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
function isFailureEvent(event) {
|
|
1065
|
-
return event.type === "transition-failed" || event.type === "transition-timed-out" || event.type === "action-timed-out";
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
2244
|
// src/event/event-store.ts
|
|
1069
2245
|
function filterEvents(store, predicate) {
|
|
1070
2246
|
return store.events().filter(predicate);
|
|
@@ -1346,11 +2522,11 @@ var BitmapNetExecutor = class {
|
|
|
1346
2522
|
throw new Error(`Place ${envPlace.place.name} is not registered as an environment place`);
|
|
1347
2523
|
}
|
|
1348
2524
|
if (this.closed || this.draining) return false;
|
|
1349
|
-
return new Promise((
|
|
2525
|
+
return new Promise((resolve2, reject) => {
|
|
1350
2526
|
this.externalQueue.push({
|
|
1351
2527
|
place: envPlace.place,
|
|
1352
2528
|
token,
|
|
1353
|
-
resolve,
|
|
2529
|
+
resolve: resolve2,
|
|
1354
2530
|
reject
|
|
1355
2531
|
});
|
|
1356
2532
|
this.wakeUp();
|
|
@@ -1832,8 +3008,8 @@ var BitmapNetExecutor = class {
|
|
|
1832
3008
|
promises.push(Promise.race(arr));
|
|
1833
3009
|
}
|
|
1834
3010
|
if (!this.closed) {
|
|
1835
|
-
promises.push(new Promise((
|
|
1836
|
-
this.wakeUpResolve =
|
|
3011
|
+
promises.push(new Promise((resolve2) => {
|
|
3012
|
+
this.wakeUpResolve = resolve2;
|
|
1837
3013
|
}));
|
|
1838
3014
|
const timerMs = this.millisUntilNextTimedTransition();
|
|
1839
3015
|
if (timerMs > 0 && timerMs < Infinity) {
|
|
@@ -2442,11 +3618,11 @@ var PrecompiledNetExecutor = class {
|
|
|
2442
3618
|
throw new Error(`Place ${envPlace.place.name} is not registered as an environment place`);
|
|
2443
3619
|
}
|
|
2444
3620
|
if (this.closed || this.draining) return false;
|
|
2445
|
-
return new Promise((
|
|
3621
|
+
return new Promise((resolve2, reject) => {
|
|
2446
3622
|
this.externalQueue.push({
|
|
2447
3623
|
place: envPlace.place,
|
|
2448
3624
|
token,
|
|
2449
|
-
resolve,
|
|
3625
|
+
resolve: resolve2,
|
|
2450
3626
|
reject
|
|
2451
3627
|
});
|
|
2452
3628
|
this.wakeUp();
|
|
@@ -3051,8 +4227,8 @@ var PrecompiledNetExecutor = class {
|
|
|
3051
4227
|
}
|
|
3052
4228
|
}
|
|
3053
4229
|
if (!this.closed) {
|
|
3054
|
-
promises.push(new Promise((
|
|
3055
|
-
this.wakeUpResolve =
|
|
4230
|
+
promises.push(new Promise((resolve2) => {
|
|
4231
|
+
this.wakeUpResolve = resolve2;
|
|
3056
4232
|
}));
|
|
3057
4233
|
const timerMs = this.millisUntilNextTimedTransition();
|
|
3058
4234
|
if (timerMs > 0 && timerMs < Infinity) {
|
|
@@ -3156,7 +4332,13 @@ var TimeoutSentinel2 = class extends Error {
|
|
|
3156
4332
|
export {
|
|
3157
4333
|
BitmapNetExecutor,
|
|
3158
4334
|
CompiledNet,
|
|
4335
|
+
ComposeBindings,
|
|
4336
|
+
FusionSet,
|
|
4337
|
+
FusionSetBuilder,
|
|
3159
4338
|
InMemoryEventStore,
|
|
4339
|
+
Instance,
|
|
4340
|
+
Interface,
|
|
4341
|
+
InterfaceBuilder,
|
|
3160
4342
|
MAX_DURATION_MS,
|
|
3161
4343
|
Marking,
|
|
3162
4344
|
OutViolationError,
|
|
@@ -3164,6 +4346,8 @@ export {
|
|
|
3164
4346
|
PetriNetBuilder,
|
|
3165
4347
|
PrecompiledNet,
|
|
3166
4348
|
PrecompiledNetExecutor,
|
|
4349
|
+
SubnetDef,
|
|
4350
|
+
SubnetDefBuilder,
|
|
3167
4351
|
TokenInput,
|
|
3168
4352
|
TokenOutput,
|
|
3169
4353
|
Transition,
|
|
@@ -3176,6 +4360,7 @@ export {
|
|
|
3176
4360
|
arcPlace,
|
|
3177
4361
|
atLeast,
|
|
3178
4362
|
clearBit,
|
|
4363
|
+
closedSubnet,
|
|
3179
4364
|
consumptionCount,
|
|
3180
4365
|
containsAll,
|
|
3181
4366
|
deadline,
|
|
@@ -3204,6 +4389,7 @@ export {
|
|
|
3204
4389
|
matchesGuard,
|
|
3205
4390
|
noopEventStore,
|
|
3206
4391
|
one,
|
|
4392
|
+
openSubnet,
|
|
3207
4393
|
outPlace,
|
|
3208
4394
|
outputArc,
|
|
3209
4395
|
passthrough,
|