@miguelduran14/knowflow 0.1.1

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.
Files changed (192) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +182 -0
  3. package/dist/advisories.d.ts +16 -0
  4. package/dist/advisories.js +364 -0
  5. package/dist/advisories.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +104 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/copy-resolver.d.ts +22 -0
  10. package/dist/copy-resolver.js +74 -0
  11. package/dist/copy-resolver.js.map +1 -0
  12. package/dist/dataflow.d.ts +2 -0
  13. package/dist/dataflow.js +88 -0
  14. package/dist/dataflow.js.map +1 -0
  15. package/dist/dossier.d.ts +20 -0
  16. package/dist/dossier.js +331 -0
  17. package/dist/dossier.js.map +1 -0
  18. package/dist/explain/claude.d.ts +14 -0
  19. package/dist/explain/claude.js +91 -0
  20. package/dist/explain/claude.js.map +1 -0
  21. package/dist/explain/explain.d.ts +72 -0
  22. package/dist/explain/explain.js +126 -0
  23. package/dist/explain/explain.js.map +1 -0
  24. package/dist/explain/facts.d.ts +23 -0
  25. package/dist/explain/facts.js +266 -0
  26. package/dist/explain/facts.js.map +1 -0
  27. package/dist/explain/fake.d.ts +14 -0
  28. package/dist/explain/fake.js +27 -0
  29. package/dist/explain/fake.js.map +1 -0
  30. package/dist/explain/openai.d.ts +42 -0
  31. package/dist/explain/openai.js +102 -0
  32. package/dist/explain/openai.js.map +1 -0
  33. package/dist/explain/provider.d.ts +19 -0
  34. package/dist/explain/provider.js +2 -0
  35. package/dist/explain/provider.js.map +1 -0
  36. package/dist/explain/sse.d.ts +10 -0
  37. package/dist/explain/sse.js +44 -0
  38. package/dist/explain/sse.js.map +1 -0
  39. package/dist/flow-parser.d.ts +10 -0
  40. package/dist/flow-parser.js +503 -0
  41. package/dist/flow-parser.js.map +1 -0
  42. package/dist/glossary.d.ts +54 -0
  43. package/dist/glossary.js +340 -0
  44. package/dist/glossary.js.map +1 -0
  45. package/dist/index.d.ts +17 -0
  46. package/dist/index.js +16 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/inventory.d.ts +7 -0
  49. package/dist/inventory.js +453 -0
  50. package/dist/inventory.js.map +1 -0
  51. package/dist/lines.d.ts +15 -0
  52. package/dist/lines.js +53 -0
  53. package/dist/lines.js.map +1 -0
  54. package/dist/linker.d.ts +17 -0
  55. package/dist/linker.js +56 -0
  56. package/dist/linker.js.map +1 -0
  57. package/dist/mermaid.d.ts +27 -0
  58. package/dist/mermaid.js +133 -0
  59. package/dist/mermaid.js.map +1 -0
  60. package/dist/parser.d.ts +19 -0
  61. package/dist/parser.js +442 -0
  62. package/dist/parser.js.map +1 -0
  63. package/dist/pic.d.ts +29 -0
  64. package/dist/pic.js +197 -0
  65. package/dist/pic.js.map +1 -0
  66. package/dist/references.d.ts +24 -0
  67. package/dist/references.js +906 -0
  68. package/dist/references.js.map +1 -0
  69. package/dist/source-lines.d.ts +56 -0
  70. package/dist/source-lines.js +114 -0
  71. package/dist/source-lines.js.map +1 -0
  72. package/dist/types.d.ts +499 -0
  73. package/dist/types.js +2 -0
  74. package/dist/types.js.map +1 -0
  75. package/gui/dist/assets/ChainCanvas-C37KyhnE.js +1 -0
  76. package/gui/dist/assets/FlowCanvas-BRklWBCp.js +3 -0
  77. package/gui/dist/assets/RobotProfessor-BtTDTBDq.js +4220 -0
  78. package/gui/dist/assets/fira-code-cyrillic-400-normal-UC0NFL4U.woff2 +0 -0
  79. package/gui/dist/assets/fira-code-cyrillic-400-normal-dc1q3VD7.woff +0 -0
  80. package/gui/dist/assets/fira-code-cyrillic-500-normal-Bu7VCL72.woff +0 -0
  81. package/gui/dist/assets/fira-code-cyrillic-500-normal-CX35h3Mg.woff2 +0 -0
  82. package/gui/dist/assets/fira-code-cyrillic-600-normal-BD56RyRT.woff +0 -0
  83. package/gui/dist/assets/fira-code-cyrillic-600-normal-CPRdbepc.woff2 +0 -0
  84. package/gui/dist/assets/fira-code-cyrillic-ext-400-normal-Buh61xzB.woff +0 -0
  85. package/gui/dist/assets/fira-code-cyrillic-ext-400-normal-txZ9Fk_1.woff2 +0 -0
  86. package/gui/dist/assets/fira-code-cyrillic-ext-500-normal-CYRspHgm.woff2 +0 -0
  87. package/gui/dist/assets/fira-code-cyrillic-ext-500-normal-CwtLmSFD.woff +0 -0
  88. package/gui/dist/assets/fira-code-cyrillic-ext-600-normal-B9wE0zmr.woff2 +0 -0
  89. package/gui/dist/assets/fira-code-cyrillic-ext-600-normal-DldcyWhZ.woff +0 -0
  90. package/gui/dist/assets/fira-code-greek-400-normal-B2Gh_Y8s.woff2 +0 -0
  91. package/gui/dist/assets/fira-code-greek-400-normal-DobhaxvF.woff +0 -0
  92. package/gui/dist/assets/fira-code-greek-500-normal-CVO0IRNt.woff2 +0 -0
  93. package/gui/dist/assets/fira-code-greek-500-normal-DEXIWiey.woff +0 -0
  94. package/gui/dist/assets/fira-code-greek-600-normal-D_h3X2X5.woff +0 -0
  95. package/gui/dist/assets/fira-code-greek-600-normal-Dxp52_By.woff2 +0 -0
  96. package/gui/dist/assets/fira-code-greek-ext-400-normal-DR7mBgIM.woff2 +0 -0
  97. package/gui/dist/assets/fira-code-greek-ext-400-normal-DSPUheWg.woff +0 -0
  98. package/gui/dist/assets/fira-code-greek-ext-500-normal-BO3iq16n.woff2 +0 -0
  99. package/gui/dist/assets/fira-code-greek-ext-500-normal-CMiNLXER.woff +0 -0
  100. package/gui/dist/assets/fira-code-greek-ext-600-normal-C-cQDoOk.woff +0 -0
  101. package/gui/dist/assets/fira-code-greek-ext-600-normal-DtBpFRLc.woff2 +0 -0
  102. package/gui/dist/assets/fira-code-latin-400-normal-C-QZfXAs.woff +0 -0
  103. package/gui/dist/assets/fira-code-latin-400-normal-DGosTW8U.woff2 +0 -0
  104. package/gui/dist/assets/fira-code-latin-500-normal-B6gioGu8.woff2 +0 -0
  105. package/gui/dist/assets/fira-code-latin-500-normal-aMLC2AMm.woff +0 -0
  106. package/gui/dist/assets/fira-code-latin-600-normal-BmSSHshu.woff +0 -0
  107. package/gui/dist/assets/fira-code-latin-600-normal-CvYZpPHg.woff2 +0 -0
  108. package/gui/dist/assets/fira-code-latin-ext-400-normal-DG73JjOf.woff +0 -0
  109. package/gui/dist/assets/fira-code-latin-ext-400-normal-KSMg0QLl.woff2 +0 -0
  110. package/gui/dist/assets/fira-code-latin-ext-500-normal-CaANqXjI.woff +0 -0
  111. package/gui/dist/assets/fira-code-latin-ext-500-normal-Crd9e_oI.woff2 +0 -0
  112. package/gui/dist/assets/fira-code-latin-ext-600-normal-0PFQL1eg.woff +0 -0
  113. package/gui/dist/assets/fira-code-latin-ext-600-normal-Cb7jxoAi.woff2 +0 -0
  114. package/gui/dist/assets/fira-code-symbols2-400-normal-_QEZCEwe.woff +0 -0
  115. package/gui/dist/assets/fira-code-symbols2-500-normal-DwsgbIjQ.woff +0 -0
  116. package/gui/dist/assets/fira-code-symbols2-600-normal-Dvq6IQE5.woff +0 -0
  117. package/gui/dist/assets/fira-sans-cyrillic-300-normal-BO7tmGom.woff2 +0 -0
  118. package/gui/dist/assets/fira-sans-cyrillic-300-normal-BtNmWoqn.woff +0 -0
  119. package/gui/dist/assets/fira-sans-cyrillic-400-normal-CKu2TFQr.woff2 +0 -0
  120. package/gui/dist/assets/fira-sans-cyrillic-400-normal-D9eUeF-O.woff +0 -0
  121. package/gui/dist/assets/fira-sans-cyrillic-500-normal-CXfldnQ8.woff2 +0 -0
  122. package/gui/dist/assets/fira-sans-cyrillic-500-normal-Daski1k2.woff +0 -0
  123. package/gui/dist/assets/fira-sans-cyrillic-600-normal-CJ5P9t7t.woff +0 -0
  124. package/gui/dist/assets/fira-sans-cyrillic-600-normal-JSBvCpTs.woff2 +0 -0
  125. package/gui/dist/assets/fira-sans-cyrillic-700-normal-Bl-HczEO.woff2 +0 -0
  126. package/gui/dist/assets/fira-sans-cyrillic-700-normal-KyW5QH4H.woff +0 -0
  127. package/gui/dist/assets/fira-sans-cyrillic-ext-300-normal-BRn8Tsce.woff2 +0 -0
  128. package/gui/dist/assets/fira-sans-cyrillic-ext-300-normal-DOtRq9zH.woff +0 -0
  129. package/gui/dist/assets/fira-sans-cyrillic-ext-400-normal-Cycyekte.woff2 +0 -0
  130. package/gui/dist/assets/fira-sans-cyrillic-ext-400-normal-Dd5dApAy.woff +0 -0
  131. package/gui/dist/assets/fira-sans-cyrillic-ext-500-normal-Cixivk4R.woff +0 -0
  132. package/gui/dist/assets/fira-sans-cyrillic-ext-500-normal-Dkk5LlrU.woff2 +0 -0
  133. package/gui/dist/assets/fira-sans-cyrillic-ext-600-normal-BKgjRriM.woff +0 -0
  134. package/gui/dist/assets/fira-sans-cyrillic-ext-600-normal-CE6oUZE1.woff2 +0 -0
  135. package/gui/dist/assets/fira-sans-cyrillic-ext-700-normal-BfO9TR-w.woff2 +0 -0
  136. package/gui/dist/assets/fira-sans-cyrillic-ext-700-normal-Gcpdxicj.woff +0 -0
  137. package/gui/dist/assets/fira-sans-greek-300-normal-D3JfF3y9.woff2 +0 -0
  138. package/gui/dist/assets/fira-sans-greek-300-normal-DSgcXl_1.woff +0 -0
  139. package/gui/dist/assets/fira-sans-greek-400-normal-BFCFncbp.woff2 +0 -0
  140. package/gui/dist/assets/fira-sans-greek-400-normal-DQ8sF22y.woff +0 -0
  141. package/gui/dist/assets/fira-sans-greek-500-normal-C9KT8sAy.woff +0 -0
  142. package/gui/dist/assets/fira-sans-greek-500-normal-Cy-TUuxE.woff2 +0 -0
  143. package/gui/dist/assets/fira-sans-greek-600-normal-BlqpLOpF.woff2 +0 -0
  144. package/gui/dist/assets/fira-sans-greek-600-normal-DfHu91YF.woff +0 -0
  145. package/gui/dist/assets/fira-sans-greek-700-normal-BVfcrbV7.woff2 +0 -0
  146. package/gui/dist/assets/fira-sans-greek-700-normal-CJu31rC4.woff +0 -0
  147. package/gui/dist/assets/fira-sans-greek-ext-300-normal-CodlqOxk.woff +0 -0
  148. package/gui/dist/assets/fira-sans-greek-ext-300-normal-DVDwbKuK.woff2 +0 -0
  149. package/gui/dist/assets/fira-sans-greek-ext-400-normal-ByG7UNQ2.woff2 +0 -0
  150. package/gui/dist/assets/fira-sans-greek-ext-400-normal-ClCfNE6D.woff +0 -0
  151. package/gui/dist/assets/fira-sans-greek-ext-500-normal-Bem4H9fH.woff +0 -0
  152. package/gui/dist/assets/fira-sans-greek-ext-500-normal-nq-cjB3m.woff2 +0 -0
  153. package/gui/dist/assets/fira-sans-greek-ext-600-normal-DPxZoeHL.woff +0 -0
  154. package/gui/dist/assets/fira-sans-greek-ext-600-normal-fS0evUH3.woff2 +0 -0
  155. package/gui/dist/assets/fira-sans-greek-ext-700-normal-C1m6bE4Q.woff2 +0 -0
  156. package/gui/dist/assets/fira-sans-greek-ext-700-normal-C8J5qYMq.woff +0 -0
  157. package/gui/dist/assets/fira-sans-latin-300-normal-B2LrTgQS.woff2 +0 -0
  158. package/gui/dist/assets/fira-sans-latin-300-normal-Dwkm-oZ7.woff +0 -0
  159. package/gui/dist/assets/fira-sans-latin-400-normal-66xCkunx.woff +0 -0
  160. package/gui/dist/assets/fira-sans-latin-400-normal-a0AnQzuD.woff2 +0 -0
  161. package/gui/dist/assets/fira-sans-latin-500-normal-Bkfzcwmr.woff +0 -0
  162. package/gui/dist/assets/fira-sans-latin-500-normal-Bvbxc8ch.woff2 +0 -0
  163. package/gui/dist/assets/fira-sans-latin-600-normal-CXKlxLG9.woff2 +0 -0
  164. package/gui/dist/assets/fira-sans-latin-600-normal-DtSgd811.woff +0 -0
  165. package/gui/dist/assets/fira-sans-latin-700-normal-CRhwpWTq.woff2 +0 -0
  166. package/gui/dist/assets/fira-sans-latin-700-normal-DgqLm3Fi.woff +0 -0
  167. package/gui/dist/assets/fira-sans-latin-ext-300-normal-CAROBYR0.woff +0 -0
  168. package/gui/dist/assets/fira-sans-latin-ext-300-normal-GNF4Ac3G.woff2 +0 -0
  169. package/gui/dist/assets/fira-sans-latin-ext-400-normal-BCUwhuGB.woff +0 -0
  170. package/gui/dist/assets/fira-sans-latin-ext-400-normal-BLG_mAEf.woff2 +0 -0
  171. package/gui/dist/assets/fira-sans-latin-ext-500-normal-CFRf4jcC.woff +0 -0
  172. package/gui/dist/assets/fira-sans-latin-ext-500-normal-OO1QnNpY.woff2 +0 -0
  173. package/gui/dist/assets/fira-sans-latin-ext-600-normal-DLMQsaV4.woff +0 -0
  174. package/gui/dist/assets/fira-sans-latin-ext-600-normal-DTQBTt1m.woff2 +0 -0
  175. package/gui/dist/assets/fira-sans-latin-ext-700-normal-cfTxGFzJ.woff +0 -0
  176. package/gui/dist/assets/fira-sans-latin-ext-700-normal-g2Yrd4J5.woff2 +0 -0
  177. package/gui/dist/assets/fira-sans-vietnamese-300-normal-BO7He0nF.woff2 +0 -0
  178. package/gui/dist/assets/fira-sans-vietnamese-300-normal-CeyOCBN-.woff +0 -0
  179. package/gui/dist/assets/fira-sans-vietnamese-400-normal-DUVG35gE.woff +0 -0
  180. package/gui/dist/assets/fira-sans-vietnamese-400-normal-ljDTQFsx.woff2 +0 -0
  181. package/gui/dist/assets/fira-sans-vietnamese-500-normal-ChEXCnJy.woff2 +0 -0
  182. package/gui/dist/assets/fira-sans-vietnamese-500-normal-UV7M6ot_.woff +0 -0
  183. package/gui/dist/assets/fira-sans-vietnamese-600-normal-Bxjaclsw.woff +0 -0
  184. package/gui/dist/assets/fira-sans-vietnamese-600-normal-Dh2KAvU8.woff2 +0 -0
  185. package/gui/dist/assets/fira-sans-vietnamese-700-normal-DFi88MGY.woff +0 -0
  186. package/gui/dist/assets/fira-sans-vietnamese-700-normal-j5oLGLJY.woff2 +0 -0
  187. package/gui/dist/assets/index-CwQzep9r.js +413 -0
  188. package/gui/dist/assets/index-DUFjnEFT.css +1 -0
  189. package/gui/dist/assets/theme-BnuhLJ6X.css +1 -0
  190. package/gui/dist/assets/theme-G7EkTZY2.js +30 -0
  191. package/gui/dist/index.html +13 -0
  192. package/package.json +62 -0
@@ -0,0 +1,906 @@
1
+ import { cleanLines, extractProgramIds, matchHeader } from './source-lines.js';
2
+ /**
3
+ * Where-used por campo: para cada data-name del esquema, dónde se LEE y
4
+ * dónde se ESCRIBE en la PROCEDURE DIVISION, anclado a párrafo y línea.
5
+ *
6
+ * Misma disciplina que el resto del motor (ADR-0003): se reconoce un
7
+ * subconjunto de verbos con reparto de roles bien definido (MOVE, COMPUTE,
8
+ * la aritmética, SET, INITIALIZE, STRING/UNSTRING, INSPECT, READ/RETURN
9
+ * INTO, WRITE/REWRITE FROM, PERFORM VARYING, los contextos de condición) y
10
+ * los host variables de EXEC SQL/CICS. Todo lo demás se MARCA, no se
11
+ * inventa:
12
+ * - un argumento BY REFERENCE de una CALL, un MOVE CORRESPONDING, un
13
+ * host variable de SQL dinámico o un homónimo del esquema → `uncertain`;
14
+ * - una ocurrencia real de un campo en un verbo que el motor no modela
15
+ * todavía → `unclassified` (se toca ahí, pero no se afirma el rol);
16
+ * - un nombre en posición de operando que no casa con el esquema →
17
+ * `unknownNames` (puede estar en un copybook ausente).
18
+ *
19
+ * El rol se decide por la SENTENCIA completa, no por la línea: una MOVE
20
+ * puede repartirse en varias líneas y el operando destino está tras el TO.
21
+ * Por eso se acumula el texto hasta el punto y se trocea por verbos, con
22
+ * un mapa offset→línea para anclar cada token a su línea real del fuente.
23
+ */
24
+ export function collectReferences(source, data) {
25
+ const cleaned = cleanLines(source);
26
+ const procIdx = cleaned.findIndex(l => PROCEDURE_HDR_RE.test(l.masked));
27
+ const fragment = procIdx === -1;
28
+ // Sin esquema no hay contra qué resolver los nombres: se devuelve vacío
29
+ // (la GUI siempre tiene `data` cuando tiene fuente).
30
+ if (!data || data.records.length === 0) {
31
+ return { fields: [], assignments: [], unknownNames: [], fragment };
32
+ }
33
+ // ── Índice del esquema: nombres conocidos, mapa 88→padre y homónimos ──
34
+ const nameCounts = new Map();
35
+ const parentOf88 = new Map();
36
+ const bump = (u) => {
37
+ nameCounts.set(u, (nameCounts.get(u) ?? 0) + 1);
38
+ };
39
+ const walk = (f) => {
40
+ if (f.type !== 'unresolved-copy') {
41
+ const u = f.name.toUpperCase();
42
+ bump(u);
43
+ for (const cv of f.conditionValues ?? [])
44
+ parentOf88.set(cv.name.toUpperCase(), u);
45
+ for (const g of f.renamesGroups ?? [])
46
+ bump(g.name.toUpperCase());
47
+ }
48
+ for (const c of f.children)
49
+ walk(c);
50
+ };
51
+ for (const r of data.records)
52
+ walk(r);
53
+ const known = new Set(nameCounts.keys());
54
+ const ambiguousNames = new Set([...nameCounts].filter(([, n]) => n > 1).map(([k]) => k));
55
+ // Nombres declarados con `INDEXED BY`: son registros especiales de
56
+ // COBOL, no campos del esquema (sin PIC ni offset), así que no están en
57
+ // `known`. Se recogen para no acusarlos como nombres desconocidos ni
58
+ // fabricarles referencias — igual que un `SET IX UP BY 1` no es una
59
+ // escritura de un dato.
60
+ const indexNames = new Set();
61
+ for (const l of cleaned) {
62
+ for (const m of l.masked.matchAll(/(?<![\w-])INDEXED\s+BY\s+((?:[A-Za-z][\w-]*\s*)+)/gi)) {
63
+ for (const n of m[1].trim().split(/\s+/))
64
+ indexNames.add(n.toUpperCase());
65
+ }
66
+ }
67
+ /** Resuelve un token a un campo del esquema (o a su padre, si es un 88). */
68
+ const resolve = (u) => {
69
+ if (known.has(u))
70
+ return { name: u, ambiguous: ambiguousNames.has(u) };
71
+ const parent = parentOf88.get(u);
72
+ if (parent)
73
+ return { name: parent, via88: u, ambiguous: ambiguousNames.has(parent) };
74
+ return undefined;
75
+ };
76
+ const unknownNames = new Set();
77
+ const all = [];
78
+ const assignments = [];
79
+ const dedup = new Set();
80
+ // Estado del recorrido, mutado por el bucle principal.
81
+ const ids = extractProgramIds(cleaned);
82
+ const implicitEntry = ids[0]?.name ?? 'MAIN';
83
+ const nestedFrom = ids[1]?.line;
84
+ let paragraph;
85
+ let inProcedure = false;
86
+ let procHeaderOpen = false;
87
+ // Fragmento puro (sin ninguna cabecera de división): se analiza entero
88
+ // como cuerpo de PROCEDURE. Si hay OTRA división pero no PROCEDURE, no
89
+ // hay nada que analizar (no es un fragmento de procedimiento).
90
+ if (procIdx === -1 && !cleaned.some(l => OTHER_DIV_RE.test(l.masked)))
91
+ inProcedure = true;
92
+ /** Un token resuelto como una ocurrencia, o undefined si no casa. */
93
+ const oneRef = (token, kind, offset, uncertain = false) => {
94
+ const r = resolve(token.toUpperCase());
95
+ if (!r)
96
+ return undefined;
97
+ return {
98
+ name: r.name,
99
+ kind,
100
+ offset,
101
+ ...(uncertain || r.ambiguous ? { uncertain: true } : {}),
102
+ ...(r.via88 ? { via88: r.via88 } : {}),
103
+ ...(r.ambiguous ? { ambiguous: true } : {}),
104
+ };
105
+ };
106
+ /**
107
+ * Todos los identificadores de `text` que resuelven a un campo, como
108
+ * ocurrencias de `kind`. Los que no resuelven y parecen un nombre se
109
+ * apuntan en `unknownNames` (salvo que `collectUnknown` sea false: los
110
+ * verbos no modelados no deben ensuciar esa lista).
111
+ *
112
+ * Lo que va entre paréntesis —subíndice (`WS-EL(WS-I)`) o longitud de
113
+ * modificación de referencia (`WS-X(1:WS-L)`)— NO es el operando en sí:
114
+ * son índices/longitudes que se LEEN. Se extraen aparte, marcados
115
+ * `subscript`, para que `MOVE A TO B(I)` cuente como escritura de B y
116
+ * lectura de I (antes I se contaba como escritura).
117
+ */
118
+ const refsIn = (text, base, kind, opts = {}) => {
119
+ const out = [];
120
+ // Identificadores FUERA de paréntesis → rol `kind`.
121
+ for (const m of maskParens(text).matchAll(IDENT_RE)) {
122
+ const tok = m[0];
123
+ const u = tok.toUpperCase();
124
+ if (NON_NAME.has(u))
125
+ continue;
126
+ const at = base + (m.index ?? 0);
127
+ const o = oneRef(tok, kind, at, opts.uncertain ?? false);
128
+ if (o)
129
+ out.push(o);
130
+ else if (opts.collectUnknown !== false && tok.length > 1 && !indexNames.has(u))
131
+ unknownNames.add(u);
132
+ }
133
+ // Identificadores DENTRO de paréntesis → siempre lectura, `subscript`.
134
+ for (const [lo, hi] of parenRanges(text)) {
135
+ for (const m of text.slice(lo, hi).matchAll(IDENT_RE)) {
136
+ const tok = m[0];
137
+ if (NON_NAME.has(tok.toUpperCase()))
138
+ continue;
139
+ const o = oneRef(tok, 'read', base + lo + (m.index ?? 0), opts.uncertain ?? false);
140
+ if (o) {
141
+ o.subscript = true;
142
+ out.push(o);
143
+ }
144
+ }
145
+ }
146
+ return out;
147
+ };
148
+ // ── Handlers por verbo — cada uno devuelve las ocurrencias del chunk ──
149
+ // `chunk` es el texto enmascarado de la sentencia; `base` su offset
150
+ // absoluto en el buffer. Las funciones de recorte trabajan sobre el
151
+ // enmascarado (literales en blanco) para no confundir un `TO` dentro de
152
+ // una cadena con la palabra clave.
153
+ const moveRefs = (chunk, base) => {
154
+ const corr = CORRESPONDING_RE.test(chunk);
155
+ const toI = kw(chunk, 'TO');
156
+ if (toI < 0)
157
+ return refsIn(chunk, base, 'unclassified', { collectUnknown: false });
158
+ const opt = corr ? { uncertain: true } : {};
159
+ return [
160
+ ...refsIn(chunk.slice(0, toI), base, 'read', opt),
161
+ ...refsIn(chunk.slice(toI + 2), base + toI + 2, 'write', opt),
162
+ ];
163
+ };
164
+ const computeRefs = (chunk, base) => {
165
+ const eqI = chunk.indexOf('=');
166
+ if (eqI < 0)
167
+ return refsIn(chunk, base, 'unclassified', { collectUnknown: false });
168
+ return [
169
+ ...refsIn(chunk.slice(0, eqI), base, 'write'),
170
+ ...refsIn(chunk.slice(eqI + 1), base + eqI + 1, 'read'),
171
+ ];
172
+ };
173
+ const arithRefs = (verb, chunk, base) => {
174
+ const linkKws = verb === 'ADD' ? ['TO'] : verb === 'SUBTRACT' ? ['FROM'] : verb === 'MULTIPLY' ? ['BY'] : ['INTO', 'BY'];
175
+ const linkI = kwAny(chunk, linkKws);
176
+ const givI = kw(chunk, 'GIVING');
177
+ const remI = kw(chunk, 'REMAINDER');
178
+ if (givI >= 0) {
179
+ // Todo lo anterior a GIVING (fuentes + el operando TO/FROM/BY/INTO)
180
+ // se lee; el/los operando(s) tras GIVING (y REMAINDER) se escriben.
181
+ const out = refsIn(chunk.slice(0, givI), base, 'read');
182
+ const wEnd = remI > givI ? remI : chunk.length;
183
+ out.push(...refsIn(chunk.slice(givI + 6, wEnd), base + givI + 6, 'write'));
184
+ if (remI >= 0)
185
+ out.push(...refsIn(chunk.slice(remI + 9), base + remI + 9, 'write'));
186
+ return out;
187
+ }
188
+ if (linkI >= 0) {
189
+ const linkLen = /^[A-Za-z]+/.exec(chunk.slice(linkI))?.[0].length ?? 2;
190
+ // Sin GIVING, el operando tras TO/FROM/BY/INTO se lee Y se escribe.
191
+ return [
192
+ ...refsIn(chunk.slice(0, linkI), base, 'read'),
193
+ ...refsIn(chunk.slice(linkI + linkLen), base + linkI + linkLen, 'read-write'),
194
+ ];
195
+ }
196
+ return refsIn(chunk, base, 'unclassified', { collectUnknown: false });
197
+ };
198
+ const setRefs = (chunk, base) => {
199
+ const ud = /(?<![\w-])(?:UP|DOWN)\s+BY(?![\w-])/i.exec(chunk);
200
+ if (ud) {
201
+ const byEnd = ud.index + ud[0].length;
202
+ return [
203
+ ...refsIn(chunk.slice(0, ud.index), base, 'read-write'),
204
+ ...refsIn(chunk.slice(byEnd), base + byEnd, 'read'),
205
+ ];
206
+ }
207
+ const toI = kw(chunk, 'TO');
208
+ if (toI < 0)
209
+ return refsIn(chunk, base, 'unclassified', { collectUnknown: false });
210
+ const tgt = refsIn(chunk.slice(0, toI), base, 'write');
211
+ const val = chunk.slice(toI + 2);
212
+ // SET <88> TO TRUE/FALSE: el 88 resuelve a su campo padre (write via88);
213
+ // no hay valor legible que leer.
214
+ if (/(?<![\w-])(?:TRUE|FALSE)(?![\w-])/i.test(val))
215
+ return tgt;
216
+ return [...tgt, ...refsIn(val, base + toI + 2, 'read')];
217
+ };
218
+ const initRefs = (chunk, base) => {
219
+ const rI = kw(chunk, 'REPLACING');
220
+ const out = refsIn(chunk.slice(0, rI >= 0 ? rI : chunk.length), base, 'write');
221
+ if (rI >= 0)
222
+ out.push(...refsIn(chunk.slice(rI), base + rI, 'read'));
223
+ return out;
224
+ };
225
+ const sideRefs = (chunk, base, stopKw, kind) => {
226
+ const i = kw(chunk, stopKw);
227
+ return refsIn(i >= 0 ? chunk.slice(0, i) : chunk, base, kind);
228
+ };
229
+ /** Marca ocurrencias como auxiliares (POINTER/TALLYING): fuera de aristas. */
230
+ const markAux = (raws) => {
231
+ for (const r of raws)
232
+ r.aux = true;
233
+ return raws;
234
+ };
235
+ const stringRefs = (chunk, base) => {
236
+ const intoI = kw(chunk, 'INTO');
237
+ if (intoI < 0)
238
+ return refsIn(chunk, base, 'unclassified', { collectUnknown: false });
239
+ const out = refsIn(chunk.slice(0, intoI), base, 'read'); // fuentes + operando DELIMITED BY
240
+ const rest = chunk.slice(intoI + 4);
241
+ const rb = base + intoI + 4;
242
+ const ptrI = kw(rest, 'POINTER');
243
+ const ovI = kwAny(rest, ['ON', 'NOT', 'END-STRING']);
244
+ const wEnd = ptrI >= 0 ? ptrI : ovI >= 0 ? ovI : rest.length;
245
+ out.push(...refsIn(rest.slice(0, wEnd), rb, 'write'));
246
+ if (ptrI >= 0) {
247
+ const pEnd = ovI > ptrI ? ovI : rest.length;
248
+ out.push(...markAux(refsIn(rest.slice(ptrI + 7, pEnd), rb + ptrI + 7, 'read-write')));
249
+ }
250
+ return out;
251
+ };
252
+ const unstringRefs = (chunk, base) => {
253
+ const intoI = kw(chunk, 'INTO');
254
+ if (intoI < 0)
255
+ return refsIn(chunk, base, 'unclassified', { collectUnknown: false });
256
+ const out = refsIn(chunk.slice(0, intoI), base, 'read'); // UNSTRING <origen> [DELIMITED BY <d>]
257
+ const rest = chunk.slice(intoI + 4);
258
+ const rb = base + intoI + 4;
259
+ const ptrI = kw(rest, 'POINTER');
260
+ const endI = kwAny(rest, ['ON', 'NOT', 'END-UNSTRING']);
261
+ const wEnd = ptrI >= 0 ? ptrI : endI >= 0 ? endI : rest.length;
262
+ // destinos + operandos de DELIMITER IN / COUNT IN / TALLYING IN
263
+ out.push(...refsIn(rest.slice(0, wEnd), rb, 'write'));
264
+ if (ptrI >= 0) {
265
+ const seg = rest.slice(ptrI + 7, endI > ptrI ? endI : rest.length);
266
+ const tI = kw(seg, 'TALLYING');
267
+ if (tI >= 0) {
268
+ out.push(...markAux(refsIn(seg.slice(0, tI), rb + ptrI + 7, 'read-write')));
269
+ out.push(...markAux(refsIn(seg.slice(tI), rb + ptrI + 7 + tI, 'write')));
270
+ }
271
+ else {
272
+ out.push(...markAux(refsIn(seg, rb + ptrI + 7, 'read-write')));
273
+ }
274
+ }
275
+ return out;
276
+ };
277
+ const inspectRefs = (chunk, base) => {
278
+ const opI = kwAny(chunk, ['TALLYING', 'REPLACING', 'CONVERTING']);
279
+ const modifies = /(?<![\w-])(?:REPLACING|CONVERTING)(?![\w-])/i.test(chunk);
280
+ // INSPECT <x> ... — INSPECT queda fuera (NON_NAME); x se lee, y además
281
+ // se escribe si hay REPLACING/CONVERTING.
282
+ const out = refsIn(chunk.slice(0, opI >= 0 ? opI : chunk.length), base, modifies ? 'read-write' : 'read');
283
+ if (opI < 0)
284
+ return out;
285
+ const rest = chunk.slice(opI);
286
+ const rb = base + opI;
287
+ if (/^\s*TALLYING(?![\w-])/i.test(rest)) {
288
+ const forI = kw(rest, 'FOR');
289
+ out.push(...refsIn(rest.slice(0, forI >= 0 ? forI : rest.length), rb, 'write')); // contador
290
+ if (forI >= 0)
291
+ out.push(...refsIn(rest.slice(forI), rb + forI, 'read')); // ... BEFORE/AFTER INITIAL <x>
292
+ }
293
+ else {
294
+ out.push(...refsIn(rest, rb, 'read')); // REPLACING/CONVERTING ... : operandos identificador se leen
295
+ }
296
+ return out;
297
+ };
298
+ const readRefs = (chunk, base) => {
299
+ const out = [];
300
+ const intoI = kw(chunk, 'INTO');
301
+ if (intoI >= 0) {
302
+ const seg = chunk.slice(intoI + 4);
303
+ const end = kwAny(seg, ['KEY', 'AT', 'INVALID', 'NOT', 'WITH', 'END-READ']);
304
+ out.push(...refsIn(seg.slice(0, end >= 0 ? end : seg.length), base + intoI + 4, 'write'));
305
+ }
306
+ const keyI = kw(chunk, 'KEY');
307
+ if (keyI >= 0) {
308
+ const seg = chunk.slice(keyI + 3);
309
+ const end = kwAny(seg, ['AT', 'INVALID', 'NOT', 'END-READ']);
310
+ out.push(...refsIn(seg.slice(0, end >= 0 ? end : seg.length), base + keyI + 3, 'read'));
311
+ }
312
+ return out;
313
+ };
314
+ const returnRefs = (chunk, base) => {
315
+ const intoI = kw(chunk, 'INTO');
316
+ if (intoI < 0)
317
+ return [];
318
+ const seg = chunk.slice(intoI + 4);
319
+ const end = kwAny(seg, ['AT', 'NOT', 'END-RETURN']);
320
+ return refsIn(seg.slice(0, end >= 0 ? end : seg.length), base + intoI + 4, 'write');
321
+ };
322
+ const writeRefs = (chunk, base) => {
323
+ const fromI = kw(chunk, 'FROM');
324
+ // Primer identificador tras WRITE/REWRITE/RELEASE = el registro → se escribe.
325
+ const out = refsIn(chunk.slice(0, fromI >= 0 ? fromI : chunk.length), base, 'write');
326
+ if (fromI >= 0) {
327
+ const seg = chunk.slice(fromI + 4);
328
+ const end = kwAny(seg, ['BEFORE', 'AFTER', 'AT', 'INVALID', 'NOT', 'END-WRITE', 'END-REWRITE']);
329
+ out.push(...refsIn(seg.slice(0, end >= 0 ? end : seg.length), base + fromI + 4, 'read'));
330
+ }
331
+ return out;
332
+ };
333
+ const performRefs = (chunk, base) => {
334
+ const out = [];
335
+ const vI = kw(chunk, 'VARYING');
336
+ if (vI >= 0) {
337
+ const seg = chunk.slice(vI);
338
+ const sb = base + vI;
339
+ for (const m of seg.matchAll(/(?<![\w-])(?:VARYING|AFTER)\s+([A-Za-z][\w-]*)/gi)) {
340
+ const nameStart = (m.index ?? 0) + m[0].length - m[1].length;
341
+ const o = oneRef(m[1], 'write', sb + nameStart);
342
+ if (o)
343
+ out.push(o);
344
+ }
345
+ for (const m of seg.matchAll(/(?<![\w-])(?:FROM|BY)\s+([A-Za-z][\w-]*)/gi)) {
346
+ const nameStart = (m.index ?? 0) + m[0].length - m[1].length;
347
+ const o = oneRef(m[1], 'read', sb + nameStart);
348
+ if (o)
349
+ out.push(o);
350
+ }
351
+ }
352
+ const uI = kw(chunk, 'UNTIL');
353
+ if (uI >= 0) {
354
+ const seg = chunk.slice(uI + 5);
355
+ const end = kw(seg, 'END-PERFORM');
356
+ out.push(...refsIn(seg.slice(0, end >= 0 ? end : seg.length), base + uI + 5, 'read'));
357
+ }
358
+ const tm = /(?<![\w-])([A-Za-z][\w-]*)\s+TIMES(?![\w-])/i.exec(chunk);
359
+ if (tm) {
360
+ const o = oneRef(tm[1], 'read', base + (tm.index ?? 0));
361
+ if (o)
362
+ out.push(o);
363
+ }
364
+ return out;
365
+ };
366
+ const callRefs = (chunk, base) => {
367
+ const out = [];
368
+ const uI = kw(chunk, 'USING');
369
+ const rI = kw(chunk, 'RETURNING');
370
+ if (uI >= 0) {
371
+ const argPart = chunk.slice(uI + 5, rI > uI ? rI : chunk.length);
372
+ const ab = base + uI + 5;
373
+ // BY REFERENCE (por defecto) → el llamado puede devolver valor:
374
+ // read-write + uncertain. BY CONTENT/VALUE → solo lectura.
375
+ let byRef = true;
376
+ for (const m of argPart.matchAll(/(?<![\w-])(?:BY\s+(REFERENCE|CONTENT|VALUE)|([A-Za-z][\w-]*))(?![\w-])/gi)) {
377
+ if (m[1]) {
378
+ byRef = m[1].toUpperCase() === 'REFERENCE';
379
+ continue;
380
+ }
381
+ const tok = m[2];
382
+ if (!tok || NON_NAME.has(tok.toUpperCase()))
383
+ continue;
384
+ const o = oneRef(tok, byRef ? 'read-write' : 'read', ab + (m.index ?? 0), byRef);
385
+ if (o)
386
+ out.push(o);
387
+ }
388
+ }
389
+ if (rI >= 0) {
390
+ const seg = chunk.slice(rI + 9);
391
+ const mm = /^\s*([A-Za-z][\w-]*)/.exec(seg);
392
+ if (mm) {
393
+ const o = oneRef(mm[1], 'write', base + rI + 9 + mm[0].length - mm[1].length);
394
+ if (o)
395
+ out.push(o);
396
+ }
397
+ }
398
+ return out;
399
+ };
400
+ const goRefs = (chunk, base) => {
401
+ const m = /(?<![\w-])DEPENDING\s+ON\s+([A-Za-z][\w-]*)/i.exec(chunk);
402
+ if (!m)
403
+ return [];
404
+ const o = oneRef(m[1], 'read', base + (m.index ?? 0) + m[0].length - m[1].length);
405
+ return o ? [o] : [];
406
+ };
407
+ const dispatch = (verb, chunk, base) => {
408
+ switch (verb) {
409
+ case 'MOVE':
410
+ return moveRefs(chunk, base);
411
+ case 'COMPUTE':
412
+ return computeRefs(chunk, base);
413
+ case 'ADD':
414
+ case 'SUBTRACT':
415
+ case 'MULTIPLY':
416
+ case 'DIVIDE':
417
+ return arithRefs(verb, chunk, base);
418
+ case 'SET':
419
+ return setRefs(chunk, base);
420
+ case 'INITIALIZE':
421
+ return initRefs(chunk, base);
422
+ case 'ACCEPT':
423
+ return sideRefs(chunk, base, 'FROM', 'write');
424
+ case 'DISPLAY':
425
+ return sideRefs(chunk, base, 'UPON', 'read');
426
+ case 'STRING':
427
+ return stringRefs(chunk, base);
428
+ case 'UNSTRING':
429
+ return unstringRefs(chunk, base);
430
+ case 'INSPECT':
431
+ return inspectRefs(chunk, base);
432
+ case 'READ':
433
+ return readRefs(chunk, base);
434
+ case 'RETURN':
435
+ return returnRefs(chunk, base);
436
+ case 'WRITE':
437
+ case 'REWRITE':
438
+ case 'RELEASE':
439
+ return writeRefs(chunk, base);
440
+ case 'PERFORM':
441
+ return performRefs(chunk, base);
442
+ case 'IF':
443
+ case 'EVALUATE':
444
+ case 'WHEN':
445
+ return refsIn(chunk, base, 'read');
446
+ case 'CALL':
447
+ return callRefs(chunk, base);
448
+ case 'GO':
449
+ return goRefs(chunk, base);
450
+ default:
451
+ return refsIn(chunk, base, 'unclassified', { collectUnknown: false });
452
+ }
453
+ };
454
+ // ── Emisión de referencias ──────────────────────────────────────────
455
+ const emitRef = (o, verb, snippet, line) => {
456
+ const fr = {
457
+ name: o.name,
458
+ kind: o.kind,
459
+ verb,
460
+ paragraph: paragraph ?? implicitEntry,
461
+ line,
462
+ snippet,
463
+ ...(paragraph === undefined ? { paragraphImplicit: true } : {}),
464
+ ...(o.uncertain ? { uncertain: true } : {}),
465
+ ...(o.via88 ? { via88: o.via88 } : {}),
466
+ ...(o.ambiguous ? { ambiguous: true } : {}),
467
+ };
468
+ const key = `${fr.name}|${fr.kind}|${fr.line}|${fr.verb}`;
469
+ if (dedup.has(key))
470
+ return;
471
+ dedup.add(key);
472
+ all.push(fr);
473
+ };
474
+ const makeLineAt = (map) => (offset) => {
475
+ let ln = map[0]?.line ?? 0;
476
+ for (const e of map) {
477
+ if (e.start <= offset)
478
+ ln = e.line;
479
+ else
480
+ break;
481
+ }
482
+ return ln;
483
+ };
484
+ /**
485
+ * Aristas de propagación de valor de una sentencia: cada operando fuente
486
+ * (lectura, salvo subíndices y auxiliares) fluye a cada operando destino
487
+ * (escritura, salvo auxiliares). Solo para verbos que COPIAN o DERIVAN un
488
+ * valor (`EDGE_VERBS`) — no IF/EVALUATE (condición), READ (origen externo,
489
+ * el fichero) ni PERFORM.
490
+ */
491
+ const emitEdges = (verb, label, occs, snippet, lineAt) => {
492
+ if (!EDGE_VERBS.has(verb))
493
+ return;
494
+ const sources = occs.filter(o => (o.kind === 'read' || o.kind === 'read-write') && !o.subscript && !o.aux);
495
+ const sinks = occs.filter(o => (o.kind === 'write' || o.kind === 'read-write') && !o.aux);
496
+ for (const s of sources) {
497
+ for (const t of sinks) {
498
+ if (s.name === t.name)
499
+ continue;
500
+ assignments.push({
501
+ from: s.name,
502
+ to: t.name,
503
+ verb: label,
504
+ paragraph: paragraph ?? implicitEntry,
505
+ line: lineAt(t.offset),
506
+ snippet,
507
+ kind: 'statement',
508
+ ...(paragraph === undefined ? { paragraphImplicit: true } : {}),
509
+ ...(s.uncertain || t.uncertain ? { uncertain: true } : {}),
510
+ });
511
+ }
512
+ }
513
+ };
514
+ const processSentence = (masked, body, map) => {
515
+ const lineAt = makeLineAt(map);
516
+ const marks = [];
517
+ for (const m of masked.matchAll(VERB_SPLIT_RE))
518
+ marks.push({ at: m.index ?? 0, verb: m[1].toUpperCase() });
519
+ for (let i = 0; i < marks.length; i++) {
520
+ const start = marks[i].at;
521
+ const end = i + 1 < marks.length ? marks[i + 1].at : masked.length;
522
+ const verb = marks[i].verb;
523
+ const occs = dispatch(verb, masked.slice(start, end), start);
524
+ if (occs.length === 0)
525
+ continue;
526
+ const snippet = collapse(body.slice(start, end));
527
+ const label = verb === 'GO' ? 'GO TO' : verb;
528
+ for (const o of occs)
529
+ emitRef(o, label, snippet, lineAt(o.offset));
530
+ emitEdges(verb, label, occs, snippet, lineAt);
531
+ }
532
+ };
533
+ // ── EXEC SQL / CICS ─────────────────────────────────────────────────
534
+ const sqlRefs = (masked, base) => {
535
+ const vm = /(?<![\w-])EXEC\s+SQL\s+([A-Za-z-]+)/i.exec(masked);
536
+ const verb = vm ? vm[1].toUpperCase() : '';
537
+ if (verb === 'INCLUDE')
538
+ return [];
539
+ const dynamic = verb === 'PREPARE' || verb === 'EXECUTE';
540
+ // Rangos de host-vars de un INTO (SELECT ... INTO / FETCH ... INTO),
541
+ // NO el "INSERT INTO <tabla>".
542
+ const intoRanges = [];
543
+ for (const m of masked.matchAll(/(?<![\w-])INTO(?![\w-])/gi)) {
544
+ const idx = m.index ?? 0;
545
+ if (/INSERT\s*$/i.test(masked.slice(Math.max(0, idx - 12), idx)))
546
+ continue;
547
+ const after = masked.slice(idx + 4);
548
+ const stop = /(?<![\w-])(?:FROM|WHERE|ORDER|GROUP|HAVING|FOR|FETCH|USING|SET|VALUES|END-EXEC)(?![\w-])/i.exec(after);
549
+ intoRanges.push([idx + 4, idx + 4 + (stop ? (stop.index ?? after.length) : after.length)]);
550
+ }
551
+ const out = [];
552
+ for (const m of masked.matchAll(/:([A-Za-z][A-Za-z0-9_-]*)\s*(?::\s*([A-Za-z][A-Za-z0-9_-]*))?/g)) {
553
+ const idx = m.index ?? 0;
554
+ const inInto = intoRanges.some(([s, e]) => idx >= s && idx < e);
555
+ const kind = inInto ? 'write' : 'read';
556
+ const host = oneRef(m[1], kind, base + idx + 1, dynamic);
557
+ if (host)
558
+ out.push(host);
559
+ if (m[2]) {
560
+ const ind = oneRef(m[2], kind, base + idx + m[0].lastIndexOf(m[2]), true); // variable indicadora
561
+ if (ind)
562
+ out.push(ind);
563
+ }
564
+ }
565
+ return out;
566
+ };
567
+ const cicsRefs = (masked, base) => {
568
+ const out = [];
569
+ for (const m of masked.matchAll(/(?<![\w-])([A-Za-z][\w-]*)\s*\(\s*([A-Za-z][\w-]*)\s*\)/g)) {
570
+ const key = m[1].toUpperCase();
571
+ const op = m[2];
572
+ const offset = base + (m.index ?? 0) + m[0].lastIndexOf(op);
573
+ let kind;
574
+ let uncertain = false;
575
+ if (key === 'INTO' || key === 'SET')
576
+ kind = 'write';
577
+ else if (key === 'FROM')
578
+ kind = 'read';
579
+ else if (key === 'RESP' || key === 'RESP2')
580
+ kind = 'write';
581
+ else if (key === 'LENGTH' || key === 'FLENGTH' || key === 'KEYLENGTH') {
582
+ kind = 'read-write';
583
+ uncertain = true;
584
+ }
585
+ else
586
+ kind = 'read'; // FILE, DATASET, MAP, RIDFLD, QUEUE… con operando data-name: se lee
587
+ const o = oneRef(op, kind, offset, uncertain);
588
+ if (o)
589
+ out.push(o);
590
+ }
591
+ return out;
592
+ };
593
+ // ── Buffer de sentencia (se acumula hasta el punto) ─────────────────
594
+ let bufMasked = '';
595
+ let bufBody = '';
596
+ let bufMap = [];
597
+ const addLine = (l) => {
598
+ bufMap.push({ start: bufMasked.length, line: l.line });
599
+ bufMasked += l.masked + '\n';
600
+ bufBody += l.body + '\n';
601
+ };
602
+ const drainSentences = () => {
603
+ for (;;) {
604
+ const m = PERIOD_RE.exec(bufMasked);
605
+ if (!m)
606
+ return;
607
+ const cut = m.index;
608
+ processSentence(bufMasked.slice(0, cut), bufBody.slice(0, cut), bufMap);
609
+ const rem = cut + 1;
610
+ bufMasked = bufMasked.slice(rem);
611
+ bufBody = bufBody.slice(rem);
612
+ bufMap = bufMap.map(e => ({ start: Math.max(0, e.start - rem), line: e.line }));
613
+ }
614
+ };
615
+ const flushBuffer = () => {
616
+ if (bufMasked.trim() !== '')
617
+ processSentence(bufMasked, bufBody, bufMap);
618
+ bufMasked = '';
619
+ bufBody = '';
620
+ bufMap = [];
621
+ };
622
+ // ── Buffer de bloque EXEC ──────────────────────────────────────────
623
+ let execMasked = '';
624
+ let execBody = '';
625
+ let execMap = [];
626
+ let execOpen = false;
627
+ const addExecLine = (l) => {
628
+ execMap.push({ start: execMasked.length, line: l.line });
629
+ execMasked += l.masked + '\n';
630
+ execBody += l.body + '\n';
631
+ };
632
+ const finishExec = () => {
633
+ const isCics = /(?<![\w-])EXEC\s+CICS(?![\w-])/i.test(execMasked);
634
+ const occs = isCics ? cicsRefs(execMasked, 0) : sqlRefs(execMasked, 0);
635
+ if (occs.length > 0) {
636
+ const lineAt = makeLineAt(execMap);
637
+ const vm = /(?<![\w-])EXEC\s+SQL\s+([A-Za-z-]+)/i.exec(execMasked);
638
+ const label = isCics ? 'EXEC CICS' : vm ? `EXEC SQL ${vm[1].toUpperCase()}` : 'EXEC SQL';
639
+ const snippet = collapse(execBody);
640
+ for (const o of occs)
641
+ emitRef(o, label, snippet, lineAt(o.offset));
642
+ }
643
+ execMasked = '';
644
+ execBody = '';
645
+ execMap = [];
646
+ };
647
+ // ── Bucle principal ────────────────────────────────────────────────
648
+ for (const l of cleaned) {
649
+ if (END_PROGRAM_RE.test(l.masked))
650
+ break;
651
+ if (nestedFrom !== undefined && l.line >= nestedFrom)
652
+ break;
653
+ if (!inProcedure) {
654
+ if (PROCEDURE_HDR_RE.test(l.masked)) {
655
+ inProcedure = true;
656
+ // "PROCEDURE DIVISION [USING ...]." puede seguir en varias líneas
657
+ // hasta su punto: nada de eso son sentencias.
658
+ procHeaderOpen = !/\.\s*$/.test(l.masked);
659
+ }
660
+ continue;
661
+ }
662
+ if (procHeaderOpen) {
663
+ if (/\.\s*$/.test(l.masked))
664
+ procHeaderOpen = false;
665
+ continue;
666
+ }
667
+ if (execOpen) {
668
+ addExecLine(l);
669
+ if (END_EXEC_RE.test(l.masked)) {
670
+ finishExec();
671
+ execOpen = false;
672
+ }
673
+ continue;
674
+ }
675
+ const hdr = matchHeader(l.masked);
676
+ if (hdr) {
677
+ flushBuffer();
678
+ paragraph = hdr.name;
679
+ continue;
680
+ }
681
+ if (EXEC_START_RE.test(l.masked)) {
682
+ flushBuffer();
683
+ addExecLine(l);
684
+ if (END_EXEC_RE.test(l.masked))
685
+ finishExec();
686
+ else
687
+ execOpen = true;
688
+ continue;
689
+ }
690
+ addLine(l);
691
+ drainSentences();
692
+ }
693
+ flushBuffer();
694
+ // ── Agrupar por nombre ────────────────────────────────────────────
695
+ const byName = new Map();
696
+ for (const fr of all) {
697
+ let u = byName.get(fr.name);
698
+ if (!u) {
699
+ u = { name: fr.name, reads: [], writes: [], unclassified: [] };
700
+ byName.set(fr.name, u);
701
+ }
702
+ if (fr.kind === 'read')
703
+ u.reads.push(fr);
704
+ else if (fr.kind === 'write')
705
+ u.writes.push(fr);
706
+ else if (fr.kind === 'read-write') {
707
+ u.reads.push(fr);
708
+ u.writes.push(fr);
709
+ }
710
+ else
711
+ u.unclassified.push(fr);
712
+ }
713
+ const byLine = (a, b) => a.line - b.line;
714
+ const fields = [...byName.values()]
715
+ .map(u => ({
716
+ name: u.name,
717
+ reads: u.reads.sort(byLine),
718
+ writes: u.writes.sort(byLine),
719
+ unclassified: u.unclassified.sort(byLine),
720
+ }))
721
+ .sort((a, b) => a.name.localeCompare(b.name));
722
+ // Aristas de solape REDEFINES: escribir un alias cambia los bytes del
723
+ // otro, así que el valor se propaga en ambos sentidos. No es una
724
+ // asignación (es una reinterpretación de memoria) → `uncertain` + kind
725
+ // `redefines`, sin línea. El esquema ya da el dato (`SchemaField.redefines`).
726
+ const seenRedef = new Set();
727
+ const walkRedef = (f) => {
728
+ if (f.type !== 'unresolved-copy' && f.redefines) {
729
+ const a = f.name.toUpperCase();
730
+ const b = f.redefines.toUpperCase();
731
+ if (known.has(a) && known.has(b) && a !== b) {
732
+ for (const [from, to] of [
733
+ [a, b],
734
+ [b, a],
735
+ ]) {
736
+ const k = `${from}->${to}`;
737
+ if (seenRedef.has(k))
738
+ continue;
739
+ seenRedef.add(k);
740
+ assignments.push({
741
+ from,
742
+ to,
743
+ verb: 'REDEFINES',
744
+ line: 0,
745
+ snippet: `${a} REDEFINES ${b}`,
746
+ uncertain: true,
747
+ kind: 'redefines',
748
+ });
749
+ }
750
+ }
751
+ }
752
+ for (const c of f.children)
753
+ walkRedef(c);
754
+ };
755
+ for (const rec of data.records)
756
+ walkRedef(rec);
757
+ // Dedupe de aristas de sentencia (una misma línea puede repetir el par).
758
+ const edgeSeen = new Set();
759
+ const dedupedEdges = assignments.filter(e => {
760
+ const k = `${e.from}|${e.to}|${e.verb}|${e.line}|${e.kind ?? ''}`;
761
+ if (edgeSeen.has(k))
762
+ return false;
763
+ edgeSeen.add(k);
764
+ return true;
765
+ });
766
+ return { fields, assignments: dedupedEdges, unknownNames: [...unknownNames].sort(), fragment };
767
+ }
768
+ // ── Utilidades y constantes de módulo ────────────────────────────────
769
+ const PROCEDURE_HDR_RE = /^\s*PROCEDURE\s+DIVISION/i;
770
+ const OTHER_DIV_RE = /^\s*(?:IDENTIFICATION|ID|ENVIRONMENT|DATA)\s+DIVISION/i;
771
+ const END_PROGRAM_RE = /^\s*END\s+PROGRAM\b/i;
772
+ const EXEC_START_RE = /(?<![\w-])EXEC\s+(?:SQL|CICS)(?![\w-])/i;
773
+ const END_EXEC_RE = /(?<![\w-])END-EXEC(?![\w-])/i;
774
+ // Un punto cierra la sentencia; el lookahead evita el punto decimal de un
775
+ // literal numérico (MOVE 1.5 TO ...).
776
+ const PERIOD_RE = /\.(?![0-9])/;
777
+ const IDENT_RE = /[A-Za-z][A-Za-z0-9-]*/g;
778
+ const CORRESPONDING_RE = /(?<![\w-])(?:CORRESPONDING|CORR)(?![\w-])/i;
779
+ // Verbos y palabras de control que arrancan (o delimitan) una sentencia:
780
+ // se usan para trocear la sentencia en statements. Incluye verbos que no
781
+ // se modelan (SEARCH, SORT, OPEN…) para que actúen de frontera y su
782
+ // contenido caiga en el handler por defecto (`unclassified`).
783
+ const VERB_SPLIT_RE = /(?<![\w-])(MOVE|COMPUTE|ADD|SUBTRACT|MULTIPLY|DIVIDE|SET|INITIALIZE|ACCEPT|DISPLAY|STRING|UNSTRING|INSPECT|REWRITE|READ|RETURN|WRITE|RELEASE|PERFORM|EVALUATE|WHEN|IF|CALL|GO|SEARCH|SORT|MERGE|UNLOCK|START|DELETE|CANCEL|OPEN|CLOSE)(?![\w-])/gi;
784
+ // Verbos que COPIAN o DERIVAN un valor: de ellos salen aristas de
785
+ // propagación (fuente→destino). READ/RETURN quedan fuera a propósito —
786
+ // su origen es externo (el fichero), no otro campo; el "usos" ya los
787
+ // muestra. IF/EVALUATE/PERFORM tampoco: no mueven datos.
788
+ const EDGE_VERBS = new Set([
789
+ 'MOVE',
790
+ 'COMPUTE',
791
+ 'ADD',
792
+ 'SUBTRACT',
793
+ 'MULTIPLY',
794
+ 'DIVIDE',
795
+ 'SET',
796
+ 'STRING',
797
+ 'UNSTRING',
798
+ 'WRITE',
799
+ 'REWRITE',
800
+ 'RELEASE',
801
+ ]);
802
+ /** Índice del primer `kw` (palabra completa, case-insensitive) en `t`, o -1. */
803
+ function kw(t, k) {
804
+ const m = new RegExp(`(?<![\\w-])${k}(?![\\w-])`, 'i').exec(t);
805
+ return m ? m.index : -1;
806
+ }
807
+ /** El menor índice entre varias palabras clave, o -1 si ninguna aparece. */
808
+ function kwAny(t, ks) {
809
+ let best = -1;
810
+ for (const k of ks) {
811
+ const i = kw(t, k);
812
+ if (i >= 0 && (best < 0 || i < best))
813
+ best = i;
814
+ }
815
+ return best;
816
+ }
817
+ /** `text` con el contenido de todo paréntesis reemplazado por espacios
818
+ * (misma longitud): deja fuera subíndices y modificación de referencia. */
819
+ function maskParens(text) {
820
+ let depth = 0;
821
+ let out = '';
822
+ for (const ch of text) {
823
+ if (ch === '(') {
824
+ depth++;
825
+ out += ' ';
826
+ }
827
+ else if (ch === ')') {
828
+ if (depth > 0)
829
+ depth--;
830
+ out += ' ';
831
+ }
832
+ else {
833
+ out += depth > 0 ? ' ' : ch;
834
+ }
835
+ }
836
+ return out;
837
+ }
838
+ /** Rangos `[inicio, fin)` del contenido de cada paréntesis de primer nivel. */
839
+ function parenRanges(text) {
840
+ const ranges = [];
841
+ let depth = 0;
842
+ let start = -1;
843
+ for (let i = 0; i < text.length; i++) {
844
+ const ch = text[i];
845
+ if (ch === '(') {
846
+ if (depth === 0)
847
+ start = i + 1;
848
+ depth++;
849
+ }
850
+ else if (ch === ')') {
851
+ if (depth > 0)
852
+ depth--;
853
+ if (depth === 0 && start >= 0) {
854
+ ranges.push([start, i]);
855
+ start = -1;
856
+ }
857
+ }
858
+ }
859
+ return ranges;
860
+ }
861
+ /** Colapsa a una línea y recorta — el snippet de contexto de la ficha. */
862
+ function collapse(text) {
863
+ const one = text.replace(/\s+/g, ' ').trim();
864
+ return one.length > 140 ? one.slice(0, 137) + '…' : one;
865
+ }
866
+ /**
867
+ * Palabras reservadas y estructurales que aparecen en posición de operando
868
+ * y NO son data-names: se excluyen tanto de las referencias como de
869
+ * `unknownNames`. No hace falta que sea exhaustiva — el camino principal
870
+ * se protege casando cada token contra los nombres reales del esquema;
871
+ * esta lista sobre todo evita ruido en `unknownNames` y ubica bien los
872
+ * repartos de rol (dónde corta TO/GIVING/INTO…).
873
+ */
874
+ const NON_NAME = new Set([
875
+ // Verbos
876
+ 'MOVE', 'COMPUTE', 'ADD', 'SUBTRACT', 'MULTIPLY', 'DIVIDE', 'SET', 'INITIALIZE', 'ACCEPT', 'DISPLAY',
877
+ 'STRING', 'UNSTRING', 'INSPECT', 'READ', 'RETURN', 'WRITE', 'REWRITE', 'RELEASE', 'PERFORM', 'EVALUATE',
878
+ 'WHEN', 'IF', 'ELSE', 'CALL', 'GO', 'GOBACK', 'STOP', 'EXIT', 'CONTINUE', 'SEARCH', 'SORT', 'MERGE',
879
+ 'UNLOCK', 'START', 'DELETE', 'CANCEL', 'OPEN', 'CLOSE', 'NEXT', 'SENTENCE', 'RUN',
880
+ // Estructura de sentencia
881
+ 'TO', 'FROM', 'BY', 'INTO', 'GIVING', 'USING', 'RETURNING', 'THRU', 'THROUGH', 'ROUNDED', 'REMAINDER',
882
+ 'DEPENDING', 'ON', 'VARYING', 'UNTIL', 'TIMES', 'CORRESPONDING', 'CORR', 'OF', 'IN', 'IS', 'ARE', 'THEN',
883
+ 'END-IF', 'END-EVALUATE', 'END-PERFORM', 'END-STRING', 'END-UNSTRING', 'END-CALL', 'END-READ', 'END-WRITE',
884
+ 'END-REWRITE', 'END-DELETE', 'END-START', 'END-ADD', 'END-SUBTRACT', 'END-MULTIPLY', 'END-DIVIDE',
885
+ 'END-COMPUTE', 'END-SEARCH', 'END-RETURN', 'END-ACCEPT', 'END-DISPLAY', 'END-EXEC',
886
+ // Condiciones y relaciones
887
+ 'NOT', 'AND', 'OR', 'EQUAL', 'EQUALS', 'GREATER', 'LESS', 'THAN', 'EXCEEDS', 'ZERO', 'ZEROS', 'ZEROES',
888
+ 'ZEROED', 'SPACE', 'SPACES', 'HIGH-VALUE', 'HIGH-VALUES', 'LOW-VALUE', 'LOW-VALUES', 'QUOTE', 'QUOTES',
889
+ 'NULL', 'NULLS', 'TRUE', 'FALSE', 'OTHER', 'ALSO', 'ANY', 'POSITIVE', 'NEGATIVE', 'NUMERIC', 'ALPHABETIC',
890
+ 'ALPHABETIC-LOWER', 'ALPHABETIC-UPPER', 'OMITTED', 'ADDRESS',
891
+ // Cláusulas
892
+ 'DELIMITED', 'DELIMITER', 'COUNT', 'POINTER', 'TALLYING', 'REPLACING', 'CONVERTING', 'FOR', 'WITH',
893
+ 'MODE', 'KEY', 'LENGTH', 'REFERENCE', 'CONTENT', 'VALUE', 'SIZE', 'ERROR', 'OVERFLOW', 'EXCEPTION',
894
+ 'AT', 'INVALID', 'END', 'EOP', 'END-OF-PAGE', 'ADVANCING', 'NO', 'PAGE', 'LINE', 'LINES', 'UP', 'DOWN',
895
+ 'INITIAL', 'CHARACTER', 'CHARACTERS', 'ALL', 'LEADING', 'TRAILING', 'FIRST', 'BEFORE', 'AFTER',
896
+ 'STANDARD', 'OPTIONAL', 'PRIOR', 'LOCK', 'REWIND', 'REEL', 'UNIT', 'REMOVAL', 'DATA', 'RECORD',
897
+ // SQL / CICS que puedan colarse como token suelto
898
+ 'EXEC', 'SQL', 'CICS', 'SELECT', 'INSERT', 'UPDATE', 'FETCH', 'DECLARE', 'CURSOR', 'ORDER', 'GROUP',
899
+ 'HAVING', 'WHERE', 'VALUES', 'RESP', 'RESP2', 'INDICATOR', 'CIC',
900
+ // FUNCTION y funciones intrínsecas (FUNCTION MOD (X 4), FUNCTION MAX (...))
901
+ 'FUNCTION', 'MOD', 'REM', 'MAX', 'MIN', 'SUM', 'MEAN', 'MEDIAN', 'RANGE', 'MIDRANGE', 'ABS', 'SQRT',
902
+ 'NUMVAL', 'NUMVAL-C', 'INTEGER', 'INTEGER-PART', 'INTEGER-OF-DATE', 'DATE-OF-INTEGER', 'RANDOM',
903
+ 'CURRENT-DATE', 'WHEN-COMPILED', 'ORD', 'ORD-MAX', 'ORD-MIN', 'CHAR', 'UPPER-CASE', 'LOWER-CASE',
904
+ 'REVERSE', 'TRIM', 'LENGTH-AN', 'STORED-CHAR-LENGTH', 'FACTORIAL', 'LOG', 'LOG10', 'EXP', 'EXP10',
905
+ ]);
906
+ //# sourceMappingURL=references.js.map