capdag 1.222.603 → 1.226.626

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.
@@ -590,6 +590,18 @@ function buildStylesheet() {
590
590
  'target-arrow-shape': 'tee',
591
591
  },
592
592
  },
593
+ {
594
+ // A convergence (fan-in) edge: a second producer feeding a cap's
595
+ // non-main argument. Dotted + diamond-tail to read as a merging
596
+ // side-input distinct from the solid main backbone edge.
597
+ selector: 'edge.strand-convergence',
598
+ style: {
599
+ 'line-style': 'dotted',
600
+ 'width': 2,
601
+ 'source-arrow-shape': 'diamond',
602
+ 'source-arrow-color': machineEdgeColor || 'data(color)',
603
+ },
604
+ },
593
605
  {
594
606
  selector: 'edge.faded',
595
607
  style: { 'opacity': fadedEdgeOpacity },
@@ -762,6 +774,28 @@ function validateStrandStep(step, path) {
762
774
  if (typeof body.output_is_sequence !== 'boolean') {
763
775
  throw new Error(`CapFabRenderer: ${path}.step_type.Cap.output_is_sequence must be a boolean`);
764
776
  }
777
+ // `inputs` (capdag CapInput list) carries the data-flow graph: the main
778
+ // input plus any convergence inputs. Optional on legacy payloads, but
779
+ // when present it must be well-formed — the fan-in edges are drawn from
780
+ // it, so a malformed entry is a hard error, not a silent skip.
781
+ if (body.inputs !== undefined) {
782
+ assertArray(body.inputs, `${path}.step_type.Cap.inputs`);
783
+ body.inputs.forEach((input, inputIdx) => {
784
+ const inputPath = `${path}.step_type.Cap.inputs[${inputIdx}]`;
785
+ assertObject(input, inputPath);
786
+ assertString(input.arg_urn, `${inputPath}.arg_urn`);
787
+ // serde: unit variant → the string "StrandInput"; struct variant →
788
+ // { Step: { token_id } }. Anything else is malformed.
789
+ if (input.source === 'StrandInput') return;
790
+ if (input.source && typeof input.source === 'object' && input.source.Step) {
791
+ assertString(input.source.Step.token_id, `${inputPath}.source.Step.token_id`);
792
+ return;
793
+ }
794
+ throw new Error(
795
+ `CapFabRenderer: ${inputPath}.source must be "StrandInput" or { Step: { token_id } }`
796
+ );
797
+ });
798
+ }
765
799
  } else {
766
800
  assertString(body.media_def, `${path}.step_type.${variant}.media_def`);
767
801
  }
@@ -1309,6 +1343,17 @@ function buildStrandGraphData(data) {
1309
1343
  return outerExit;
1310
1344
  }
1311
1345
 
1346
+ // Map every step's stable token_id to its output node id. A cap
1347
+ // step's node (`step_${i}`) IS its `to_spec` output, so a convergence
1348
+ // input naming a producer by token resolves to that producer's output
1349
+ // node — the value flowing into the fan-in arg.
1350
+ const tokenToNodeId = new Map();
1351
+ data.steps.forEach((step, i) => {
1352
+ if (step && typeof step.token_id === 'string' && step.token_id.length > 0) {
1353
+ tokenToNodeId.set(step.token_id, `step_${i}`);
1354
+ }
1355
+ });
1356
+
1312
1357
  data.steps.forEach((step, i) => {
1313
1358
  const variant = Object.keys(step.step_type)[0];
1314
1359
  const nodeId = `step_${i}`;
@@ -1330,8 +1375,43 @@ function buildStrandGraphData(data) {
1330
1375
  if (cardinality !== '1\u21921') {
1331
1376
  label = `${label} (${cardinality})`;
1332
1377
  }
1378
+ // Resolve this cap's actual data-flow producers from `body.inputs`
1379
+ // (capdag CapInput serde shape): each `source` is the string
1380
+ // "StrandInput" (fed by the strand's input anchor → the input slot
1381
+ // node) or `{ Step: { token_id } }` (fed by a producing cap → that
1382
+ // step's output node). This is the authoritative topology; step
1383
+ // ORDER is not — the realizer emits edges greedily in dependency
1384
+ // order, so `prevNodeId` (the previously-emitted step) need not be
1385
+ // one of this cap's inputs at all.
1386
+ const capInputs = Array.isArray(body.inputs) ? body.inputs : [];
1387
+ const producerNodeIds = [];
1388
+ for (const input of capInputs) {
1389
+ const src = input && input.source;
1390
+ if (src === 'StrandInput') {
1391
+ producerNodeIds.push(inputSlotId);
1392
+ continue;
1393
+ }
1394
+ const producerToken = src && typeof src === 'object' && src.Step && src.Step.token_id;
1395
+ if (typeof producerToken !== 'string' || producerToken.length === 0) continue;
1396
+ const producerNodeId = tokenToNodeId.get(producerToken);
1397
+ if (producerNodeId) producerNodeIds.push(producerNodeId);
1398
+ }
1399
+
1400
+ // Pick the source of the single labeled "backbone" edge (the one
1401
+ // carrying the cap title + cardinality). Inside a ForEach body — or
1402
+ // when the cap declares no inputs — keep the linear `prevNodeId`
1403
+ // thread so the ForEach boundary handling and its (1→n) entry marker
1404
+ // are preserved exactly. Otherwise anchor the backbone on a REAL
1405
+ // input: `prevNodeId` if it is genuinely one, else the first actual
1406
+ // producer — so a non-input step ordered just before this cap can
1407
+ // never become a spurious backbone edge.
1408
+ const isForeachContext = isForeachEntry || insideForEachBody !== null;
1409
+ let backboneSource = prevNodeId;
1410
+ if (!isForeachContext && producerNodeIds.length > 0 && !producerNodeIds.includes(prevNodeId)) {
1411
+ backboneSource = producerNodeIds[0];
1412
+ }
1333
1413
  addEdge(
1334
- prevNodeId,
1414
+ backboneSource,
1335
1415
  nodeId,
1336
1416
  label,
1337
1417
  body.title,
@@ -1340,6 +1420,25 @@ function buildStrandGraphData(data) {
1340
1420
  { foreachEntry: isForeachEntry }
1341
1421
  );
1342
1422
 
1423
+ // Convergence (fan-in): draw an edge from every other producer that
1424
+ // isn't the backbone source, so a cap fed by several producers renders
1425
+ // as a DAG, not a chain. Deduplicated against the backbone edge and
1426
+ // against a self-loop.
1427
+ const drawnSources = new Set([backboneSource, nodeId]);
1428
+ for (const producerNodeId of producerNodeIds) {
1429
+ if (drawnSources.has(producerNodeId)) continue;
1430
+ drawnSources.add(producerNodeId);
1431
+ addEdge(
1432
+ producerNodeId,
1433
+ nodeId,
1434
+ '',
1435
+ body.title,
1436
+ body.cap_urn,
1437
+ 'strand-convergence',
1438
+ {}
1439
+ );
1440
+ }
1441
+
1343
1442
  if (insideForEachBody !== null) {
1344
1443
  if (bodyEntry === null) bodyEntry = nodeId;
1345
1444
  bodyExit = nodeId;
package/capdag.js CHANGED
@@ -6388,7 +6388,9 @@ async function scanChannelRoot(scanRoot, expectedSlug, identity, discovered) {
6388
6388
  // [alias cap:in="...";op=...;out="..."] — header (defines a cap with alias)
6389
6389
  // [src -> alias -> dst] — wiring (connects nodes via cap)
6390
6390
  // [(a, b) -> alias -> dst] — fan-in wiring
6391
- // [src -> LOOP alias -> dst] — loop wiring (ForEach semantics)
6391
+ //
6392
+ // Per-item map (`is_loop`) is a derived cardinality property, never authored
6393
+ // syntax — there is no LOOP marker in the grammar.
6392
6394
  // ============================================================================
6393
6395
 
6394
6396
  /**
@@ -6504,7 +6506,9 @@ class MachineEdge {
6504
6506
  */
6505
6507
  toString() {
6506
6508
  const sources = this.sources.map(s => s.toString()).join(', ');
6507
- const loopPrefix = this.isLoop ? 'LOOP ' : '';
6509
+ // Debug-only marker for a per-item map edge. Not notation syntax (the
6510
+ // `LOOP` keyword is retired); `is_loop` is a derived cardinality property.
6511
+ const loopPrefix = this.isLoop ? 'map ' : '';
6508
6512
  return `(${sources}) -${loopPrefix}${this.capUrn}-> ${this.target}`;
6509
6513
  }
6510
6514
  }
@@ -6703,13 +6707,13 @@ class Machine {
6703
6707
  const targetKey = edge.target.toString();
6704
6708
  const targetName = nodeNames.get(targetKey);
6705
6709
 
6706
- const loopPrefix = edge.isLoop ? 'LOOP ' : '';
6707
-
6710
+ // `is_loop` is NOT emitted into notation text: it is a derived cardinality
6711
+ // property, not authored syntax (the `LOOP` keyword is retired).
6708
6712
  if (sources.length === 1) {
6709
- output += `[${sources[0]} -> ${loopPrefix}${alias} -> ${targetName}]`;
6713
+ output += `[${sources[0]} -> ${alias} -> ${targetName}]`;
6710
6714
  } else {
6711
6715
  const group = sources.join(', ');
6712
- output += `[(${group}) -> ${loopPrefix}${alias} -> ${targetName}]`;
6716
+ output += `[(${group}) -> ${alias} -> ${targetName}]`;
6713
6717
  }
6714
6718
  }
6715
6719
 
@@ -6756,13 +6760,13 @@ class Machine {
6756
6760
  const targetKey = edge.target.toString();
6757
6761
  const targetName = nodeNames.get(targetKey);
6758
6762
 
6759
- const loopPrefix = edge.isLoop ? 'LOOP ' : '';
6760
-
6763
+ // `is_loop` is NOT emitted into notation text: it is a derived cardinality
6764
+ // property, not authored syntax (the `LOOP` keyword is retired).
6761
6765
  if (sources.length === 1) {
6762
- lines.push(`[${sources[0]} -> ${loopPrefix}${alias} -> ${targetName}]`);
6766
+ lines.push(`[${sources[0]} -> ${alias} -> ${targetName}]`);
6763
6767
  } else {
6764
6768
  const group = sources.join(', ');
6765
- lines.push(`[(${group}) -> ${loopPrefix}${alias} -> ${targetName}]`);
6769
+ lines.push(`[(${group}) -> ${alias} -> ${targetName}]`);
6766
6770
  }
6767
6771
  }
6768
6772
 
@@ -6814,13 +6818,13 @@ class Machine {
6814
6818
  const targetKey = edge.target.toString();
6815
6819
  const targetName = nodeNames.get(targetKey);
6816
6820
 
6817
- const loopPrefix = edge.isLoop ? 'LOOP ' : '';
6818
-
6821
+ // `is_loop` is NOT emitted into notation text: it is a derived cardinality
6822
+ // property, not authored syntax (the `LOOP` keyword is retired).
6819
6823
  if (sources.length === 1) {
6820
- lines.push(`${open}${sources[0]} -> ${loopPrefix}${alias} -> ${targetName}${close}`);
6824
+ lines.push(`${open}${sources[0]} -> ${alias} -> ${targetName}${close}`);
6821
6825
  } else {
6822
6826
  const group = sources.join(', ');
6823
- lines.push(`${open}(${group}) -> ${loopPrefix}${alias} -> ${targetName}${close}`);
6827
+ lines.push(`${open}(${group}) -> ${alias} -> ${targetName}${close}`);
6824
6828
  }
6825
6829
  }
6826
6830
 
@@ -6896,13 +6900,14 @@ class Machine {
6896
6900
 
6897
6901
  const sources = edge.sources.map(s => nodeNames.get(s.toString()));
6898
6902
  const targetName = nodeNames.get(edge.target.toString());
6899
- const loopPrefix = edge.isLoop ? 'LOOP ' : '';
6900
6903
 
6904
+ // `is_loop` is NOT emitted into notation text: it is a derived cardinality
6905
+ // property, not authored syntax (the `LOOP` keyword is retired).
6901
6906
  if (sources.length === 1) {
6902
- lines.push(`${open}${sources[0]} -> ${loopPrefix}${token} -> ${targetName}${close}`);
6907
+ lines.push(`${open}${sources[0]} -> ${token} -> ${targetName}${close}`);
6903
6908
  } else {
6904
6909
  const group = sources.join(', ');
6905
- lines.push(`${open}(${group}) -> ${loopPrefix}${token} -> ${targetName}${close}`);
6910
+ lines.push(`${open}(${group}) -> ${token} -> ${targetName}${close}`);
6906
6911
  }
6907
6912
  }
6908
6913
 
@@ -6990,7 +6995,8 @@ class Machine {
6990
6995
  * - Leaf targets: stadium-shaped nodes (rounded)
6991
6996
  * - Intermediate nodes: rectangular
6992
6997
  * - Edge labels: op= tag value (or full cap URN if no op)
6993
- * - LOOP edges: dotted line style with "LOOP" prefix on label
6998
+ * - Per-item map edges (`is_loop`): dotted line style `is_loop` is a
6999
+ * derived render property, never the retired `LOOP` keyword
6994
7000
  * - Node labels: derived MediaUrn type
6995
7001
  *
6996
7002
  * @returns {string} Mermaid flowchart definition
@@ -7045,8 +7051,9 @@ class Machine {
7045
7051
  const srcName = nodeNames.get(srcKey);
7046
7052
 
7047
7053
  if (edge.isLoop) {
7048
- // Dotted line for LOOP edges
7049
- lines.push(` ${srcName} -. "LOOP ${label}" .-> ${targetName}`);
7054
+ // Dotted line renders the derived per-item map (`is_loop`) property.
7055
+ // No "LOOP" text — that keyword is retired.
7056
+ lines.push(` ${srcName} -. "${label}" .-> ${targetName}`);
7050
7057
  } else {
7051
7058
  lines.push(` ${srcName} -- "${label}" --> ${targetName}`);
7052
7059
  }
@@ -7164,7 +7171,6 @@ function _parseMachineInternal(input) {
7164
7171
  sources: stmt.sources,
7165
7172
  capAlias: stmt.capAlias,
7166
7173
  target: stmt.target,
7167
- isLoop: stmt.isLoop,
7168
7174
  position: i,
7169
7175
  location: stmt.location,
7170
7176
  sourceLocations: stmt.sourceLocations,
@@ -7286,7 +7292,13 @@ function _parseMachineInternal(input) {
7286
7292
  assignOrCheckNode(wiring.target, capOutMedia, nodeMedia, wiring.position,
7287
7293
  wiring.targetLocation || wiring.location);
7288
7294
 
7289
- edges.push(new MachineEdge(sourceUrns, capUrn, capOutMedia, wiring.isLoop));
7295
+ // `is_loop` is a derived cardinality property, not authored syntax (the
7296
+ // `LOOP` keyword is retired). The pure-JS parse path has no cap definitions
7297
+ // — it derives media only from the cap URN's in=/out= specs — so it cannot
7298
+ // evaluate the ForEach rule (a sequence source feeding a scalar-input cap).
7299
+ // Editors obtain the derived `is_loop` from the engine server's resolved
7300
+ // graph; here it defaults to false.
7301
+ edges.push(new MachineEdge(sourceUrns, capUrn, capOutMedia, false));
7290
7302
  }
7291
7303
 
7292
7304
  return {
package/capdag.test.js CHANGED
@@ -3308,14 +3308,25 @@ function test6294_Machine_fanInSecondaryUnassignedGetsWildcard() {
3308
3308
  assertEqual(g.edges()[0].sources[1].toString(), 'media:');
3309
3309
  }
3310
3310
 
3311
- // TEST6306: Machine loop edge
3312
- function test6306_Machine_loopEdge() {
3313
- const g = Machine.fromString(
3314
- '[p2t cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"]' +
3315
- '[pages -> LOOP p2t -> texts]'
3311
+ // TEST6306: The retired LOOP keyword is no longer grammar. A wiring that still
3312
+ // writes `LOOP <cap>` before the cap alias no longer parses — `LOOP` is now an
3313
+ // ordinary alias, so `pages -> LOOP p2t -> texts` is two aliases in the cap
3314
+ // position with no arrow between them, which is a syntax error.
3315
+ function test6306_Machine_loopKeywordIsNotGrammar() {
3316
+ assertThrowsWithCode(
3317
+ () => Machine.fromString(
3318
+ '[p2t cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"]' +
3319
+ '[pages -> LOOP p2t -> texts]'
3320
+ ),
3321
+ MachineSyntaxErrorCodes.PARSE_ERROR
3322
+ );
3323
+
3324
+ // `LOOP` on its own in the cap position parses fine — it is just an alias —
3325
+ // but resolves to nothing, proving it carries no special meaning.
3326
+ assertThrowsWithCode(
3327
+ () => Machine.fromString('[pages -> LOOP -> texts]'),
3328
+ MachineSyntaxErrorCodes.UNDEFINED_ALIAS
3316
3329
  );
3317
- assertEqual(g.edgeCount(), 1);
3318
- assertEqual(g.edges()[0].isLoop, true);
3319
3330
  }
3320
3331
 
3321
3332
  // TEST6308: Machine undefined alias fails
@@ -3416,14 +3427,16 @@ function test6331_Machine_lineBasedTwoStepChain() {
3416
3427
  assertEqual(g.edgeCount(), 2);
3417
3428
  }
3418
3429
 
3419
- // TEST6334: Machine line based loop
3420
- function test6334_Machine_lineBasedLoop() {
3421
- const g = Machine.fromString(
3422
- 'p2t cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"\n' +
3423
- 'pages -> LOOP p2t -> texts'
3430
+ // TEST6334: The retired LOOP keyword is not grammar in line-based mode either —
3431
+ // `pages -> LOOP p2t -> texts` is a syntax error, same as the bracketed form.
3432
+ function test6334_Machine_lineBasedLoopKeywordIsNotGrammar() {
3433
+ assertThrowsWithCode(
3434
+ () => Machine.fromString(
3435
+ 'p2t cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"\n' +
3436
+ 'pages -> LOOP p2t -> texts'
3437
+ ),
3438
+ MachineSyntaxErrorCodes.PARSE_ERROR
3424
3439
  );
3425
- assertEqual(g.edgeCount(), 1);
3426
- assertEqual(g.edges()[0].isLoop, true);
3427
3440
  }
3428
3441
 
3429
3442
  // TEST6337: Machine line based fan in
@@ -3835,8 +3848,12 @@ function test6415_Machine_roundtripFanOut() {
3835
3848
  'Fan-out round-trip failed: ' + notation);
3836
3849
  }
3837
3850
 
3838
- // TEST6417: Machine roundtrip loop edge
3839
- function test6417_Machine_roundtripLoopEdge() {
3851
+ // TEST6417: A per-item map (`is_loop`) edge serializes WITHOUT any LOOP marker —
3852
+ // `is_loop` is a derived cardinality property, not authored notation text. The
3853
+ // pure-JS parse path has no cap definitions to re-derive cardinality, so the
3854
+ // reparsed edge has `isLoop === false`; editors get the derived value from the
3855
+ // engine, not from re-parsing.
3856
+ function test6417_Machine_loopEdgeSerializesWithoutLoopText() {
3840
3857
  const original = new Machine([new MachineEdge(
3841
3858
  [MediaUrn.fromString('media:disbound-page;enc=utf-8')],
3842
3859
  CapUrn.fromString('cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"'),
@@ -3844,9 +3861,17 @@ function test6417_Machine_roundtripLoopEdge() {
3844
3861
  true
3845
3862
  )]);
3846
3863
  const notation = original.toMachineNotation();
3864
+ assert(!notation.includes('LOOP'), 'serialized notation must not contain the retired LOOP keyword');
3865
+
3847
3866
  const reparsed = Machine.fromString(notation);
3848
- assert(original.isEquivalent(reparsed), 'Loop round-trip failed');
3849
- assertEqual(reparsed.edges()[0].isLoop, true, 'isLoop must be preserved');
3867
+ assertEqual(reparsed.edgeCount(), 1);
3868
+ assertEqual(reparsed.edges()[0].isLoop, false,
3869
+ 'pure-JS parse path cannot derive cardinality — isLoop defaults to false');
3870
+ // The structural payload (sources, cap, target) round-trips intact.
3871
+ assert(reparsed.edges()[0].capUrn.isEquivalent(original.edges()[0].capUrn),
3872
+ 'cap URN must round-trip');
3873
+ assert(reparsed.edges()[0].target.isEquivalent(original.edges()[0].target),
3874
+ 'target media must round-trip');
3850
3875
  }
3851
3876
 
3852
3877
  // TEST6419: Machine serialization is deterministic
@@ -4203,16 +4228,21 @@ function test6462_Machine_toMermaid_linearChain() {
4203
4228
  assert(mermaid.includes('(['), 'Should have stadium shape nodes');
4204
4229
  }
4205
4230
 
4206
- // TEST6463: Machine to mermaid loop edge
4231
+ // TEST6463: Mermaid renders a per-item map (`is_loop`) edge with a dotted line
4232
+ // — `is_loop` is a kept render property — but emits NO "LOOP" text, since that
4233
+ // keyword is retired. The loop edge is built programmatically because the
4234
+ // grammar no longer has any way to author one.
4207
4235
  function test6463_Machine_toMermaid_loopEdge() {
4208
- const machine = Machine.fromString(
4209
- '[p2t cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"]' +
4210
- '[pages -> LOOP p2t -> texts]'
4211
- );
4236
+ const machine = new Machine([new MachineEdge(
4237
+ [MediaUrn.fromString('media:disbound-page;enc=utf-8')],
4238
+ CapUrn.fromString('cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"'),
4239
+ MediaUrn.fromString('media:enc=utf-8;ext=txt'),
4240
+ true
4241
+ )]);
4212
4242
  const mermaid = machine.toMermaid();
4213
- assert(mermaid.includes('LOOP'), 'Should include LOOP label');
4214
- assert(mermaid.includes('-.'), 'Should use dotted line for LOOP');
4215
- assert(mermaid.includes('.->'), 'Should use dotted arrow for LOOP');
4243
+ assert(!mermaid.includes('LOOP'), 'Must not emit the retired LOOP label');
4244
+ assert(mermaid.includes('-.'), 'Should use dotted line for the per-item map edge');
4245
+ assert(mermaid.includes('.->'), 'Should use dotted arrow for the per-item map edge');
4216
4246
  }
4217
4247
 
4218
4248
  // TEST6464: Machine to mermaid empty graph
@@ -6749,7 +6779,7 @@ async function runTests() {
6749
6779
  runTest('MACHINE:fan_out', test6290_Machine_fanOut);
6750
6780
  runTest('MACHINE:fan_in_secondary_assigned_by_prior_wiring', test6292_Machine_fanInSecondaryAssignedByPriorWiring);
6751
6781
  runTest('MACHINE:fan_in_secondary_unassigned_gets_wildcard', test6294_Machine_fanInSecondaryUnassignedGetsWildcard);
6752
- runTest('MACHINE:loop_edge', test6306_Machine_loopEdge);
6782
+ runTest('MACHINE:loop_keyword_is_not_grammar', test6306_Machine_loopKeywordIsNotGrammar);
6753
6783
  runTest('MACHINE:undefined_alias_fails', test6308_Machine_undefinedAliasFails);
6754
6784
  runTest('MACHINE:node_alias_collision', test6310_Machine_nodeAliasCollision);
6755
6785
  runTest('MACHINE:conflicting_media_types_fail', test6312_Machine_conflictingMediaTypesFail);
@@ -6762,7 +6792,7 @@ async function runTests() {
6762
6792
  console.log('\n--- machine/parser.rs (line-based) ---');
6763
6793
  runTest('MACHINE:line_based_simple_chain', test6327_Machine_lineBasedSimpleChain);
6764
6794
  runTest('MACHINE:line_based_two_step_chain', test6331_Machine_lineBasedTwoStepChain);
6765
- runTest('MACHINE:line_based_loop', test6334_Machine_lineBasedLoop);
6795
+ runTest('MACHINE:line_based_loop_keyword_is_not_grammar', test6334_Machine_lineBasedLoopKeywordIsNotGrammar);
6766
6796
  runTest('MACHINE:line_based_fan_in', test6337_Machine_lineBasedFanIn);
6767
6797
  runTest('MACHINE:mixed_bracketed_and_line_based', test6341_Machine_mixedBracketedAndLineBased);
6768
6798
  runTest('MACHINE:line_based_equivalent_to_bracketed', test6345_Machine_lineBasedEquivalentToBracketed);
@@ -6797,7 +6827,7 @@ async function runTests() {
6797
6827
  runTest('MACHINE:roundtrip_single_edge', test6410_Machine_roundtripSingleEdge);
6798
6828
  runTest('MACHINE:roundtrip_two_edge_chain', test6413_Machine_roundtripTwoEdgeChain);
6799
6829
  runTest('MACHINE:roundtrip_fan_out', test6415_Machine_roundtripFanOut);
6800
- runTest('MACHINE:roundtrip_loop_edge', test6417_Machine_roundtripLoopEdge);
6830
+ runTest('MACHINE:loop_edge_serializes_without_loop_text', test6417_Machine_loopEdgeSerializesWithoutLoopText);
6801
6831
  runTest('MACHINE:serialization_is_deterministic', test6419_Machine_serializationIsDeterministic);
6802
6832
  runTest('MACHINE:reordered_edges_produce_same_notation', test6421_Machine_reorderedEdgesProduceSameNotation);
6803
6833
  runTest('MACHINE:multiline_serialize_format', test6429_Machine_multilineSerializeFormat);
package/machine-parser.js CHANGED
@@ -170,14 +170,13 @@ function peg$parse(input, options) {
170
170
  const peg$c2 = "(";
171
171
  const peg$c3 = ",";
172
172
  const peg$c4 = ")";
173
- const peg$c5 = "LOOP";
174
- const peg$c6 = "-";
175
- const peg$c7 = ">";
176
- const peg$c8 = "cap:";
177
- const peg$c9 = "\r\n";
178
- const peg$c10 = "\"";
179
- const peg$c11 = "\\\"";
180
- const peg$c12 = "\\\\";
173
+ const peg$c5 = "-";
174
+ const peg$c6 = ">";
175
+ const peg$c7 = "cap:";
176
+ const peg$c8 = "\r\n";
177
+ const peg$c9 = "\"";
178
+ const peg$c10 = "\\\"";
179
+ const peg$c11 = "\\\\";
181
180
 
182
181
  const peg$r0 = /^[a-zA-Z_]/;
183
182
  const peg$r1 = /^[a-zA-Z0-9_\-]/;
@@ -189,21 +188,20 @@ function peg$parse(input, options) {
189
188
  const peg$e2 = peg$literalExpectation("(", false);
190
189
  const peg$e3 = peg$literalExpectation(",", false);
191
190
  const peg$e4 = peg$literalExpectation(")", false);
192
- const peg$e5 = peg$literalExpectation("LOOP", false);
193
- const peg$e6 = peg$literalExpectation("-", false);
194
- const peg$e7 = peg$literalExpectation(">", false);
195
- const peg$e8 = peg$classExpectation([["a", "z"], ["A", "Z"], "_"], false, false, false);
196
- const peg$e9 = peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "_", "-"], false, false, false);
197
- const peg$e10 = peg$literalExpectation("cap:", false);
198
- const peg$e11 = peg$anyExpectation();
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");
191
+ const peg$e5 = peg$literalExpectation("-", false);
192
+ const peg$e6 = peg$literalExpectation(">", false);
193
+ const peg$e7 = peg$classExpectation([["a", "z"], ["A", "Z"], "_"], false, false, false);
194
+ const peg$e8 = peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "_", "-"], false, false, false);
195
+ const peg$e9 = peg$literalExpectation("cap:", false);
196
+ const peg$e10 = peg$anyExpectation();
197
+ const peg$e11 = peg$otherExpectation("newline");
198
+ const peg$e12 = peg$literalExpectation("\r\n", false);
199
+ const peg$e13 = peg$classExpectation(["\n", "\r"], false, false, false);
200
+ const peg$e14 = peg$literalExpectation("\"", false);
201
+ const peg$e15 = peg$literalExpectation("\\\"", false);
202
+ const peg$e16 = peg$literalExpectation("\\\\", false);
203
+ const peg$e17 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false, false);
204
+ const peg$e18 = peg$otherExpectation("required whitespace");
207
205
 
208
206
  function peg$f0(stmts) { return stmts; }
209
207
  function peg$f1(inner) { return inner; }
@@ -211,18 +209,16 @@ function peg$parse(input, options) {
211
209
  function peg$f3(a, c) {
212
210
  return { type: 'header', alias: a.value, capUrn: c.value, location: location(), aliasLocation: a.location, capUrnLocation: c.location };
213
211
  }
214
- function peg$f4(s, lc, t) {
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 };
212
+ function peg$f4(s, c, t) {
213
+ return { type: 'wiring', sources: s.values, capAlias: c.value, target: t.value, location: location(), sourceLocations: s.locations, capAliasLocation: c.location, targetLocation: t.location };
216
214
  }
217
215
  function peg$f5(a) { return { values: [a.value], locations: [a.location] }; }
218
216
  function peg$f6(first, a) { return a; }
219
217
  function peg$f7(first, rest) {
220
218
  return { values: [first.value, ...rest.map(r => r.value)], locations: [first.location, ...rest.map(r => r.location)] };
221
219
  }
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
+ function peg$f8(a) { return { value: a, location: location() }; }
221
+ function peg$f9(c) { return { value: c, location: location() }; }
226
222
  let peg$currPos = options.peg$currPos | 0;
227
223
  let peg$savedPos = peg$currPos;
228
224
  const peg$posDetailsCache = [{ line: 1, column: 1 }];
@@ -515,7 +511,7 @@ function peg$parse(input, options) {
515
511
  s3 = peg$parsearrow();
516
512
  if (s3 !== peg$FAILED) {
517
513
  s4 = peg$parse_();
518
- s5 = peg$parseloop_cap_loc();
514
+ s5 = peg$parsealias_loc();
519
515
  if (s5 !== peg$FAILED) {
520
516
  s6 = peg$parse_();
521
517
  s7 = peg$parsearrow();
@@ -673,70 +669,27 @@ function peg$parse(input, options) {
673
669
  return s0;
674
670
  }
675
671
 
676
- function peg$parseloop_cap_loc() {
677
- let s0, s1, s2, s3;
678
-
679
- s0 = peg$currPos;
680
- if (input.substr(peg$currPos, 4) === peg$c5) {
681
- s1 = peg$c5;
682
- peg$currPos += 4;
683
- } else {
684
- s1 = peg$FAILED;
685
- if (peg$silentFails === 0) { peg$fail(peg$e5); }
686
- }
687
- if (s1 !== peg$FAILED) {
688
- s2 = peg$parse__();
689
- if (s2 !== peg$FAILED) {
690
- s3 = peg$parsealias_loc();
691
- if (s3 !== peg$FAILED) {
692
- peg$savedPos = s0;
693
- s0 = peg$f8(s3);
694
- } else {
695
- peg$currPos = s0;
696
- s0 = peg$FAILED;
697
- }
698
- } else {
699
- peg$currPos = s0;
700
- s0 = peg$FAILED;
701
- }
702
- } else {
703
- peg$currPos = s0;
704
- s0 = peg$FAILED;
705
- }
706
- if (s0 === peg$FAILED) {
707
- s0 = peg$currPos;
708
- s1 = peg$parsealias_loc();
709
- if (s1 !== peg$FAILED) {
710
- peg$savedPos = s0;
711
- s1 = peg$f9(s1);
712
- }
713
- s0 = s1;
714
- }
715
-
716
- return s0;
717
- }
718
-
719
672
  function peg$parsearrow() {
720
673
  let s0, s1, s2;
721
674
 
722
675
  s0 = peg$currPos;
723
676
  s1 = [];
724
677
  if (input.charCodeAt(peg$currPos) === 45) {
725
- s2 = peg$c6;
678
+ s2 = peg$c5;
726
679
  peg$currPos++;
727
680
  } else {
728
681
  s2 = peg$FAILED;
729
- if (peg$silentFails === 0) { peg$fail(peg$e6); }
682
+ if (peg$silentFails === 0) { peg$fail(peg$e5); }
730
683
  }
731
684
  if (s2 !== peg$FAILED) {
732
685
  while (s2 !== peg$FAILED) {
733
686
  s1.push(s2);
734
687
  if (input.charCodeAt(peg$currPos) === 45) {
735
- s2 = peg$c6;
688
+ s2 = peg$c5;
736
689
  peg$currPos++;
737
690
  } else {
738
691
  s2 = peg$FAILED;
739
- if (peg$silentFails === 0) { peg$fail(peg$e6); }
692
+ if (peg$silentFails === 0) { peg$fail(peg$e5); }
740
693
  }
741
694
  }
742
695
  } else {
@@ -744,11 +697,11 @@ function peg$parse(input, options) {
744
697
  }
745
698
  if (s1 !== peg$FAILED) {
746
699
  if (input.charCodeAt(peg$currPos) === 62) {
747
- s2 = peg$c7;
700
+ s2 = peg$c6;
748
701
  peg$currPos++;
749
702
  } else {
750
703
  s2 = peg$FAILED;
751
- if (peg$silentFails === 0) { peg$fail(peg$e7); }
704
+ if (peg$silentFails === 0) { peg$fail(peg$e6); }
752
705
  }
753
706
  if (s2 !== peg$FAILED) {
754
707
  s1 = [s1, s2];
@@ -772,7 +725,7 @@ function peg$parse(input, options) {
772
725
  s1 = peg$parsealias();
773
726
  if (s1 !== peg$FAILED) {
774
727
  peg$savedPos = s0;
775
- s1 = peg$f10(s1);
728
+ s1 = peg$f8(s1);
776
729
  }
777
730
  s0 = s1;
778
731
 
@@ -789,7 +742,7 @@ function peg$parse(input, options) {
789
742
  peg$currPos++;
790
743
  } else {
791
744
  s2 = peg$FAILED;
792
- if (peg$silentFails === 0) { peg$fail(peg$e8); }
745
+ if (peg$silentFails === 0) { peg$fail(peg$e7); }
793
746
  }
794
747
  if (s2 !== peg$FAILED) {
795
748
  s3 = [];
@@ -798,7 +751,7 @@ function peg$parse(input, options) {
798
751
  peg$currPos++;
799
752
  } else {
800
753
  s4 = peg$FAILED;
801
- if (peg$silentFails === 0) { peg$fail(peg$e9); }
754
+ if (peg$silentFails === 0) { peg$fail(peg$e8); }
802
755
  }
803
756
  while (s4 !== peg$FAILED) {
804
757
  s3.push(s4);
@@ -807,7 +760,7 @@ function peg$parse(input, options) {
807
760
  peg$currPos++;
808
761
  } else {
809
762
  s4 = peg$FAILED;
810
- if (peg$silentFails === 0) { peg$fail(peg$e9); }
763
+ if (peg$silentFails === 0) { peg$fail(peg$e8); }
811
764
  }
812
765
  }
813
766
  s2 = [s2, s3];
@@ -832,7 +785,7 @@ function peg$parse(input, options) {
832
785
  s1 = peg$parsecap_urn();
833
786
  if (s1 !== peg$FAILED) {
834
787
  peg$savedPos = s0;
835
- s1 = peg$f11(s1);
788
+ s1 = peg$f9(s1);
836
789
  }
837
790
  s0 = s1;
838
791
 
@@ -844,12 +797,12 @@ function peg$parse(input, options) {
844
797
 
845
798
  s0 = peg$currPos;
846
799
  s1 = peg$currPos;
847
- if (input.substr(peg$currPos, 4) === peg$c8) {
848
- s2 = peg$c8;
800
+ if (input.substr(peg$currPos, 4) === peg$c7) {
801
+ s2 = peg$c7;
849
802
  peg$currPos += 4;
850
803
  } else {
851
804
  s2 = peg$FAILED;
852
- if (peg$silentFails === 0) { peg$fail(peg$e10); }
805
+ if (peg$silentFails === 0) { peg$fail(peg$e9); }
853
806
  }
854
807
  if (s2 !== peg$FAILED) {
855
808
  s3 = [];
@@ -912,7 +865,7 @@ function peg$parse(input, options) {
912
865
  peg$currPos++;
913
866
  } else {
914
867
  s3 = peg$FAILED;
915
- if (peg$silentFails === 0) { peg$fail(peg$e11); }
868
+ if (peg$silentFails === 0) { peg$fail(peg$e10); }
916
869
  }
917
870
  if (s3 !== peg$FAILED) {
918
871
  s1 = [s1, s2, s3];
@@ -938,12 +891,12 @@ function peg$parse(input, options) {
938
891
  let s0, s1;
939
892
 
940
893
  peg$silentFails++;
941
- if (input.substr(peg$currPos, 2) === peg$c9) {
942
- s0 = peg$c9;
894
+ if (input.substr(peg$currPos, 2) === peg$c8) {
895
+ s0 = peg$c8;
943
896
  peg$currPos += 2;
944
897
  } else {
945
898
  s0 = peg$FAILED;
946
- if (peg$silentFails === 0) { peg$fail(peg$e13); }
899
+ if (peg$silentFails === 0) { peg$fail(peg$e12); }
947
900
  }
948
901
  if (s0 === peg$FAILED) {
949
902
  s0 = input.charAt(peg$currPos);
@@ -951,13 +904,13 @@ function peg$parse(input, options) {
951
904
  peg$currPos++;
952
905
  } else {
953
906
  s0 = peg$FAILED;
954
- if (peg$silentFails === 0) { peg$fail(peg$e14); }
907
+ if (peg$silentFails === 0) { peg$fail(peg$e13); }
955
908
  }
956
909
  }
957
910
  peg$silentFails--;
958
911
  if (s0 === peg$FAILED) {
959
912
  s1 = peg$FAILED;
960
- if (peg$silentFails === 0) { peg$fail(peg$e12); }
913
+ if (peg$silentFails === 0) { peg$fail(peg$e11); }
961
914
  }
962
915
 
963
916
  return s0;
@@ -968,39 +921,39 @@ function peg$parse(input, options) {
968
921
 
969
922
  s0 = peg$currPos;
970
923
  if (input.charCodeAt(peg$currPos) === 34) {
971
- s1 = peg$c10;
924
+ s1 = peg$c9;
972
925
  peg$currPos++;
973
926
  } else {
974
927
  s1 = peg$FAILED;
975
- if (peg$silentFails === 0) { peg$fail(peg$e15); }
928
+ if (peg$silentFails === 0) { peg$fail(peg$e14); }
976
929
  }
977
930
  if (s1 !== peg$FAILED) {
978
931
  s2 = [];
979
- if (input.substr(peg$currPos, 2) === peg$c11) {
980
- s3 = peg$c11;
932
+ if (input.substr(peg$currPos, 2) === peg$c10) {
933
+ s3 = peg$c10;
981
934
  peg$currPos += 2;
982
935
  } else {
983
936
  s3 = peg$FAILED;
984
- if (peg$silentFails === 0) { peg$fail(peg$e16); }
937
+ if (peg$silentFails === 0) { peg$fail(peg$e15); }
985
938
  }
986
939
  if (s3 === peg$FAILED) {
987
- if (input.substr(peg$currPos, 2) === peg$c12) {
988
- s3 = peg$c12;
940
+ if (input.substr(peg$currPos, 2) === peg$c11) {
941
+ s3 = peg$c11;
989
942
  peg$currPos += 2;
990
943
  } else {
991
944
  s3 = peg$FAILED;
992
- if (peg$silentFails === 0) { peg$fail(peg$e17); }
945
+ if (peg$silentFails === 0) { peg$fail(peg$e16); }
993
946
  }
994
947
  if (s3 === peg$FAILED) {
995
948
  s3 = peg$currPos;
996
949
  s4 = peg$currPos;
997
950
  peg$silentFails++;
998
951
  if (input.charCodeAt(peg$currPos) === 34) {
999
- s5 = peg$c10;
952
+ s5 = peg$c9;
1000
953
  peg$currPos++;
1001
954
  } else {
1002
955
  s5 = peg$FAILED;
1003
- if (peg$silentFails === 0) { peg$fail(peg$e15); }
956
+ if (peg$silentFails === 0) { peg$fail(peg$e14); }
1004
957
  }
1005
958
  peg$silentFails--;
1006
959
  if (s5 === peg$FAILED) {
@@ -1015,7 +968,7 @@ function peg$parse(input, options) {
1015
968
  peg$currPos++;
1016
969
  } else {
1017
970
  s5 = peg$FAILED;
1018
- if (peg$silentFails === 0) { peg$fail(peg$e11); }
971
+ if (peg$silentFails === 0) { peg$fail(peg$e10); }
1019
972
  }
1020
973
  if (s5 !== peg$FAILED) {
1021
974
  s4 = [s4, s5];
@@ -1032,31 +985,31 @@ function peg$parse(input, options) {
1032
985
  }
1033
986
  while (s3 !== peg$FAILED) {
1034
987
  s2.push(s3);
1035
- if (input.substr(peg$currPos, 2) === peg$c11) {
1036
- s3 = peg$c11;
988
+ if (input.substr(peg$currPos, 2) === peg$c10) {
989
+ s3 = peg$c10;
1037
990
  peg$currPos += 2;
1038
991
  } else {
1039
992
  s3 = peg$FAILED;
1040
- if (peg$silentFails === 0) { peg$fail(peg$e16); }
993
+ if (peg$silentFails === 0) { peg$fail(peg$e15); }
1041
994
  }
1042
995
  if (s3 === peg$FAILED) {
1043
- if (input.substr(peg$currPos, 2) === peg$c12) {
1044
- s3 = peg$c12;
996
+ if (input.substr(peg$currPos, 2) === peg$c11) {
997
+ s3 = peg$c11;
1045
998
  peg$currPos += 2;
1046
999
  } else {
1047
1000
  s3 = peg$FAILED;
1048
- if (peg$silentFails === 0) { peg$fail(peg$e17); }
1001
+ if (peg$silentFails === 0) { peg$fail(peg$e16); }
1049
1002
  }
1050
1003
  if (s3 === peg$FAILED) {
1051
1004
  s3 = peg$currPos;
1052
1005
  s4 = peg$currPos;
1053
1006
  peg$silentFails++;
1054
1007
  if (input.charCodeAt(peg$currPos) === 34) {
1055
- s5 = peg$c10;
1008
+ s5 = peg$c9;
1056
1009
  peg$currPos++;
1057
1010
  } else {
1058
1011
  s5 = peg$FAILED;
1059
- if (peg$silentFails === 0) { peg$fail(peg$e15); }
1012
+ if (peg$silentFails === 0) { peg$fail(peg$e14); }
1060
1013
  }
1061
1014
  peg$silentFails--;
1062
1015
  if (s5 === peg$FAILED) {
@@ -1071,7 +1024,7 @@ function peg$parse(input, options) {
1071
1024
  peg$currPos++;
1072
1025
  } else {
1073
1026
  s5 = peg$FAILED;
1074
- if (peg$silentFails === 0) { peg$fail(peg$e11); }
1027
+ if (peg$silentFails === 0) { peg$fail(peg$e10); }
1075
1028
  }
1076
1029
  if (s5 !== peg$FAILED) {
1077
1030
  s4 = [s4, s5];
@@ -1088,11 +1041,11 @@ function peg$parse(input, options) {
1088
1041
  }
1089
1042
  }
1090
1043
  if (input.charCodeAt(peg$currPos) === 34) {
1091
- s3 = peg$c10;
1044
+ s3 = peg$c9;
1092
1045
  peg$currPos++;
1093
1046
  } else {
1094
1047
  s3 = peg$FAILED;
1095
- if (peg$silentFails === 0) { peg$fail(peg$e15); }
1048
+ if (peg$silentFails === 0) { peg$fail(peg$e14); }
1096
1049
  }
1097
1050
  if (s3 !== peg$FAILED) {
1098
1051
  s1 = [s1, s2, s3];
@@ -1119,7 +1072,7 @@ function peg$parse(input, options) {
1119
1072
  peg$currPos++;
1120
1073
  } else {
1121
1074
  s1 = peg$FAILED;
1122
- if (peg$silentFails === 0) { peg$fail(peg$e18); }
1075
+ if (peg$silentFails === 0) { peg$fail(peg$e17); }
1123
1076
  }
1124
1077
  while (s1 !== peg$FAILED) {
1125
1078
  s0.push(s1);
@@ -1128,7 +1081,7 @@ function peg$parse(input, options) {
1128
1081
  peg$currPos++;
1129
1082
  } else {
1130
1083
  s1 = peg$FAILED;
1131
- if (peg$silentFails === 0) { peg$fail(peg$e18); }
1084
+ if (peg$silentFails === 0) { peg$fail(peg$e17); }
1132
1085
  }
1133
1086
  }
1134
1087
  peg$silentFails--;
@@ -1146,7 +1099,7 @@ function peg$parse(input, options) {
1146
1099
  peg$currPos++;
1147
1100
  } else {
1148
1101
  s1 = peg$FAILED;
1149
- if (peg$silentFails === 0) { peg$fail(peg$e18); }
1102
+ if (peg$silentFails === 0) { peg$fail(peg$e17); }
1150
1103
  }
1151
1104
  if (s1 !== peg$FAILED) {
1152
1105
  while (s1 !== peg$FAILED) {
@@ -1156,7 +1109,7 @@ function peg$parse(input, options) {
1156
1109
  peg$currPos++;
1157
1110
  } else {
1158
1111
  s1 = peg$FAILED;
1159
- if (peg$silentFails === 0) { peg$fail(peg$e18); }
1112
+ if (peg$silentFails === 0) { peg$fail(peg$e17); }
1160
1113
  }
1161
1114
  }
1162
1115
  } else {
@@ -1165,7 +1118,7 @@ function peg$parse(input, options) {
1165
1118
  peg$silentFails--;
1166
1119
  if (s0 === peg$FAILED) {
1167
1120
  s1 = peg$FAILED;
1168
- if (peg$silentFails === 0) { peg$fail(peg$e19); }
1121
+ if (peg$silentFails === 0) { peg$fail(peg$e18); }
1169
1122
  }
1170
1123
 
1171
1124
  return s0;
package/machine.pegjs CHANGED
@@ -13,7 +13,6 @@
13
13
  // extract cap:in="media:ext=pdf";extract;out="media:txt;textable"
14
14
  // doc -> extract -> text
15
15
  // (thumbnail, model_spec) -> describe -> description
16
- // pages -> LOOP p2t -> texts
17
16
  //
18
17
  // Both forms can be freely mixed in the same program.
19
18
 
@@ -29,9 +28,12 @@ header = a:alias_loc __ c:cap_urn_loc {
29
28
  return { type: 'header', alias: a.value, capUrn: c.value, location: location(), aliasLocation: a.location, capUrnLocation: c.location };
30
29
  }
31
30
 
32
- // Wiring: source -> loop_cap -> target
33
- wiring = s:source_loc _ arrow _ lc:loop_cap_loc _ arrow _ t:alias_loc {
34
- 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 };
31
+ // Wiring: source -> cap -> target
32
+ //
33
+ // The cap position is a plain alias. There is no LOOP marker: per-item map
34
+ // (`is_loop`) is a derived cardinality property, never authored syntax.
35
+ wiring = s:source_loc _ arrow _ c:alias_loc _ arrow _ t:alias_loc {
36
+ return { type: 'wiring', sources: s.values, capAlias: c.value, target: t.value, location: location(), sourceLocations: s.locations, capAliasLocation: c.location, targetLocation: t.location };
35
37
  }
36
38
 
37
39
  source_loc = group_loc / single_alias_loc
@@ -42,9 +44,6 @@ group_loc = "(" _ first:alias_loc rest:("," _ a:alias_loc { return a; })+ _ ")"
42
44
  return { values: [first.value, ...rest.map(r => r.value)], locations: [first.location, ...rest.map(r => r.location)] };
43
45
  }
44
46
 
45
- loop_cap_loc = "LOOP" __ a:alias_loc { return { alias: a.value, isLoop: true, location: a.location }; }
46
- / a:alias_loc { return { alias: a.value, isLoop: false, location: a.location }; }
47
-
48
47
  arrow = "-"+ ">"
49
48
 
50
49
  // Alias with location tracking
package/package.json CHANGED
@@ -40,5 +40,5 @@
40
40
  "pretest": "npm run build:parser",
41
41
  "test": "node capdag.test.js"
42
42
  },
43
- "version": "1.222.603"
43
+ "version": "1.226.626"
44
44
  }