calliope-ts 0.0.2 → 0.0.4

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 (160) hide show
  1. package/README.md +32 -20
  2. package/dist/caesura.d.ts +33 -0
  3. package/dist/caesura.d.ts.map +1 -0
  4. package/dist/caesura.js +202 -0
  5. package/dist/calliope/boundaries.d.ts +19 -0
  6. package/dist/calliope/boundaries.d.ts.map +1 -0
  7. package/dist/calliope/boundaries.js +182 -0
  8. package/dist/calliope/bracketing.d.ts +11 -0
  9. package/dist/calliope/bracketing.d.ts.map +1 -0
  10. package/dist/calliope/bracketing.js +416 -0
  11. package/dist/calliope/deps.d.ts +4 -0
  12. package/dist/calliope/deps.d.ts.map +1 -0
  13. package/dist/calliope/deps.js +181 -0
  14. package/dist/calliope/engine.d.ts +3 -0
  15. package/dist/calliope/engine.d.ts.map +1 -0
  16. package/dist/calliope/engine.js +71 -0
  17. package/dist/calliope/feats.d.ts +9 -0
  18. package/dist/calliope/feats.d.ts.map +1 -0
  19. package/dist/calliope/feats.js +45 -0
  20. package/dist/calliope/names.d.ts +7 -0
  21. package/dist/calliope/names.d.ts.map +1 -0
  22. package/dist/calliope/names.js +42 -0
  23. package/dist/calliope/postag.d.ts +8 -0
  24. package/dist/calliope/postag.d.ts.map +1 -0
  25. package/dist/calliope/postag.js +250 -0
  26. package/dist/calliope/prosodic.d.ts +3 -0
  27. package/dist/calliope/prosodic.d.ts.map +1 -0
  28. package/dist/calliope/prosodic.js +275 -0
  29. package/dist/calliope/relstress.d.ts +4 -0
  30. package/dist/calliope/relstress.d.ts.map +1 -0
  31. package/dist/calliope/relstress.js +688 -0
  32. package/dist/calliope/stressrules.d.ts +28 -0
  33. package/dist/calliope/stressrules.d.ts.map +1 -0
  34. package/dist/calliope/stressrules.js +147 -0
  35. package/dist/calliope/syntax.d.ts +38 -0
  36. package/dist/calliope/syntax.d.ts.map +1 -0
  37. package/dist/calliope/syntax.js +234 -0
  38. package/dist/calliope/udpos.d.ts +13 -0
  39. package/dist/calliope/udpos.d.ts.map +1 -0
  40. package/dist/calliope/udpos.js +156 -0
  41. package/dist/clio/caesura.d.ts +27 -0
  42. package/dist/clio/caesura.d.ts.map +1 -0
  43. package/dist/clio/caesura.js +148 -0
  44. package/dist/clio/depfix.d.ts +13 -0
  45. package/dist/clio/depfix.d.ts.map +1 -0
  46. package/dist/clio/depfix.js +84 -0
  47. package/dist/clio/display.d.ts +32 -0
  48. package/dist/clio/display.d.ts.map +1 -0
  49. package/dist/clio/display.js +976 -0
  50. package/dist/clio/engine.d.ts +3 -0
  51. package/dist/clio/engine.d.ts.map +1 -0
  52. package/dist/clio/engine.js +28 -0
  53. package/dist/clio/parser.d.ts +10 -0
  54. package/dist/clio/parser.d.ts.map +1 -0
  55. package/dist/clio/parser.js +696 -0
  56. package/dist/clio/phonological.d.ts +41 -0
  57. package/dist/clio/phonological.d.ts.map +1 -0
  58. package/dist/clio/phonological.js +788 -0
  59. package/dist/clio/phrasestress.d.ts +6 -0
  60. package/dist/clio/phrasestress.d.ts.map +1 -0
  61. package/dist/clio/phrasestress.js +106 -0
  62. package/dist/clio/pipeline.d.ts +11 -0
  63. package/dist/clio/pipeline.d.ts.map +1 -0
  64. package/dist/clio/pipeline.js +147 -0
  65. package/dist/clio/rhyme.d.ts +65 -0
  66. package/dist/clio/rhyme.d.ts.map +1 -0
  67. package/dist/clio/rhyme.js +761 -0
  68. package/dist/clio/scandroid.d.ts +17 -0
  69. package/dist/clio/scandroid.d.ts.map +1 -0
  70. package/dist/clio/scandroid.js +435 -0
  71. package/dist/clio/scansion.d.ts +46 -0
  72. package/dist/clio/scansion.d.ts.map +1 -0
  73. package/dist/clio/scansion.js +1086 -0
  74. package/dist/clio/semantics.d.ts +44 -0
  75. package/dist/clio/semantics.d.ts.map +1 -0
  76. package/dist/clio/semantics.js +139 -0
  77. package/dist/clio/stress.d.ts +83 -0
  78. package/dist/clio/stress.d.ts.map +1 -0
  79. package/dist/clio/stress.js +1737 -0
  80. package/dist/clio/tagfix.d.ts +6 -0
  81. package/dist/clio/tagfix.d.ts.map +1 -0
  82. package/dist/clio/tagfix.js +101 -0
  83. package/dist/display.d.ts +0 -6
  84. package/dist/display.d.ts.map +1 -1
  85. package/dist/display.js +322 -141
  86. package/dist/engine.d.ts +9 -0
  87. package/dist/engine.d.ts.map +1 -0
  88. package/dist/engine.js +12 -0
  89. package/dist/index.d.ts +4 -3
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +169 -26
  92. package/dist/parser.d.ts +3 -3
  93. package/dist/parser.d.ts.map +1 -1
  94. package/dist/parser.js +320 -563
  95. package/dist/phonological.d.ts +1 -1
  96. package/dist/phonological.d.ts.map +1 -1
  97. package/dist/phonological.js +73 -4
  98. package/dist/phrasestress.d.ts +6 -0
  99. package/dist/phrasestress.d.ts.map +1 -0
  100. package/dist/phrasestress.js +106 -0
  101. package/dist/rhyme.d.ts +40 -1
  102. package/dist/rhyme.d.ts.map +1 -1
  103. package/dist/rhyme.js +435 -7
  104. package/dist/scansion.d.ts +9 -0
  105. package/dist/scansion.d.ts.map +1 -1
  106. package/dist/scansion.js +145 -18
  107. package/dist/semantics.d.ts +44 -0
  108. package/dist/semantics.d.ts.map +1 -0
  109. package/dist/semantics.js +139 -0
  110. package/dist/stress.d.ts +127 -4
  111. package/dist/stress.d.ts.map +1 -1
  112. package/dist/stress.js +834 -61
  113. package/dist/types.d.ts +15 -0
  114. package/dist/types.d.ts.map +1 -1
  115. package/package.json +5 -3
  116. package/src/caesura.ts +201 -0
  117. package/src/calliope/boundaries.ts +190 -0
  118. package/src/calliope/bracketing.ts +390 -0
  119. package/src/calliope/deps.ts +160 -0
  120. package/src/calliope/engine.ts +77 -0
  121. package/src/calliope/feats.ts +46 -0
  122. package/src/calliope/names.ts +44 -0
  123. package/src/calliope/postag.ts +253 -0
  124. package/src/calliope/prosodic.ts +262 -0
  125. package/src/calliope/relstress.ts +645 -0
  126. package/src/calliope/stressrules.ts +147 -0
  127. package/src/calliope/syntax.ts +218 -0
  128. package/src/calliope/udpos.ts +152 -0
  129. package/src/calliope_src_contents.md +19049 -0
  130. package/src/clio/caesura.ts +145 -0
  131. package/src/clio/depfix.ts +88 -0
  132. package/src/clio/display.ts +1042 -0
  133. package/src/clio/engine.ts +38 -0
  134. package/src/clio/parser.ts +845 -0
  135. package/src/clio/phonological.ts +849 -0
  136. package/src/clio/phrasestress.ts +108 -0
  137. package/src/clio/pipeline.ts +154 -0
  138. package/src/clio/rhyme.ts +740 -0
  139. package/src/clio/scandroid.ts +434 -0
  140. package/src/clio/scansion.ts +1130 -0
  141. package/src/clio/semantics.ts +134 -0
  142. package/src/clio/stress.ts +1731 -0
  143. package/src/clio/tagfix.ts +104 -0
  144. package/src/display.ts +321 -137
  145. package/src/engine.ts +22 -0
  146. package/src/index.ts +176 -32
  147. package/src/parser.ts +346 -682
  148. package/src/phonological.ts +72 -4
  149. package/src/phrasestress.ts +108 -0
  150. package/src/rhyme.ts +428 -8
  151. package/src/scansion.ts +136 -15
  152. package/src/semantics.ts +134 -0
  153. package/src/stress.ts +794 -64
  154. package/src/types.ts +39 -4
  155. package/tests/DataForHayesLinesOnly.txt +364 -0
  156. package/tests/DataForHayesStressSymbolsRevised.txt +728 -0
  157. package/tests/basic.test.ts +443 -37
  158. package/tests/bench-hayes.mjs +72 -0
  159. package/tests/wagner-stress.test.ts +188 -0
  160. package/vitest.config.ts +0 -15
@@ -0,0 +1,27 @@
1
+ import { ClsWord, IntonationalUnit } from '../types.js';
2
+ export type CaesuraKind = 'hard' | 'soft';
3
+ /**
4
+ * Caesura positions for a line, keyed by the syllable index AFTER which the pause
5
+ * falls (= number of syllables to its left).
6
+ * • 'hard' — an overt break at an Intonational-Unit boundary.
7
+ * • 'soft' — a single INFERRED medial caesura for a punctuation-free line.
8
+ * Candidates are the boundaries just before a phrase/clause onset
9
+ * (PHRASE_ONSET_POS); the one nearest the line's midpoint that lies in the
10
+ * central third AND coincides with a foot boundary wins — so it is medial,
11
+ * never mid-foot, and consistent across structurally-parallel lines. Read in
12
+ * LINEAR order (robust to clitic-group reordering); needs a line of ≥ 8
13
+ * syllables.
14
+ */
15
+ export declare function computeCaesurae(words: ClsWord[], ius: IntonationalUnit[], scansion?: string): Map<number, CaesuraKind>;
16
+ /**
17
+ * The words that immediately PRECEDE a caesura in a line — i.e. each word whose
18
+ * cumulative syllable count lands exactly on a caesura position. Used by the
19
+ * rhyme layer for pre-caesural internal-rhyme detection. Returned in linear
20
+ * (reading) order; the caesura kind is paired so callers can weight hard vs
21
+ * inferred breaks if they wish.
22
+ */
23
+ export declare function preCaesuralWords(words: ClsWord[], ius: IntonationalUnit[], scansion?: string): {
24
+ word: ClsWord;
25
+ kind: CaesuraKind;
26
+ }[];
27
+ //# sourceMappingURL=caesura.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caesura.d.ts","sourceRoot":"","sources":["../../src/clio/caesura.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGxD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,CAAC;AAyC1C;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,gBAAgB,EAAE,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAoDtH;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,gBAAgB,EAAE,EAAE,QAAQ,CAAC,EAAE,MAAM,GAC3D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,EAAE,CAYxC"}
@@ -0,0 +1,148 @@
1
+ // caesura.ts — Caesura placement, shared by the display and rhyme layers.
2
+ //
3
+ // A caesura is the line's medial pause. Two kinds are distinguished:
4
+ // • 'hard' — an overt break at an Intonational-Unit boundary (comma, dash,
5
+ // colon, semicolon …): the punctuation projection.
6
+ // • 'soft' — a single INFERRED medial caesura for a punctuation-free line
7
+ // (Kiparsky 1975, via McAleese: "phonological phrasing determines the
8
+ // location of caesurae in verse").
9
+ //
10
+ // Extracted from display.ts (2026-06-13) so the rhyme layer can find the words
11
+ // that immediately precede caesurae (for pre-caesural internal-rhyme detection)
12
+ // without importing the display module. Pure analysis — no colour/chalk here.
13
+ import { isPunctuation } from './parser.js';
14
+ /** Foot-boundary syllable indices from a scansion string (cumulative syllable
15
+ * count after each foot; silent beats '-' are not syllables). */
16
+ function footBoundarySet(scansion) {
17
+ const set = new Set();
18
+ let c = 0;
19
+ for (const foot of scansion.split('|')) {
20
+ for (const ch of foot)
21
+ if ('xwnms'.includes(ch))
22
+ c++;
23
+ set.add(c);
24
+ }
25
+ return set;
26
+ }
27
+ // A new phonological/syntactic phrase opens at these POS tags: prepositions &
28
+ // subordinators (IN), infinitival "to" (TO), coordinators (CC), wh/relativizers,
29
+ // verb-particles (RP), and the predicate's verb/modal. The major medial caesura
30
+ // of a line falls immediately BEFORE such a word — far more reliably read off the
31
+ // (robust) POS tags than off FinNLP's (noisy) phonological-phrase grouping, which
32
+ // mis-bracketed e.g. "The epic | feast…". Determiners/articles are excluded (they
33
+ // continue a phrase a preposition already opened: "as | one empty bag").
34
+ const PHRASE_ONSET_POS = new Set([
35
+ 'IN', 'TO', 'CC', 'WDT', 'WP', 'WP$', 'WRB', 'RP',
36
+ 'VB', 'VBD', 'VBG', 'VBN', 'VBP', 'VBZ', 'MD',
37
+ ]);
38
+ // Directional/spatial adverbs that open a phrase ("snowed-in OUT of many routes",
39
+ // "drifting DOWN to sleep"). FinNLP often tags these RB rather than RP/IN, so the
40
+ // POS set alone misses them; a small curated lemma list recovers them with low
41
+ // over-fire risk (a generic RB like "very"/"quickly" is NOT a phrase onset).
42
+ const DIRECTIONAL_ONSET = new Set([
43
+ 'out', 'in', 'up', 'down', 'off', 'away', 'back', 'forth', 'over',
44
+ 'around', 'along', 'through', 'apart', 'aside', 'onward', 'onwards',
45
+ ]);
46
+ /** True if a word opens a new phonological/syntactic phrase (a caesura candidate). */
47
+ function isPhraseOnset(w) {
48
+ if (PHRASE_ONSET_POS.has(w.lexicalClass))
49
+ return true;
50
+ return w.lexicalClass === 'RB' && DIRECTIONAL_ONSET.has(w.word.toLowerCase());
51
+ }
52
+ /**
53
+ * Caesura positions for a line, keyed by the syllable index AFTER which the pause
54
+ * falls (= number of syllables to its left).
55
+ * • 'hard' — an overt break at an Intonational-Unit boundary.
56
+ * • 'soft' — a single INFERRED medial caesura for a punctuation-free line.
57
+ * Candidates are the boundaries just before a phrase/clause onset
58
+ * (PHRASE_ONSET_POS); the one nearest the line's midpoint that lies in the
59
+ * central third AND coincides with a foot boundary wins — so it is medial,
60
+ * never mid-foot, and consistent across structurally-parallel lines. Read in
61
+ * LINEAR order (robust to clitic-group reordering); needs a line of ≥ 8
62
+ * syllables.
63
+ */
64
+ export function computeCaesurae(words, ius, scansion) {
65
+ const caes = new Map();
66
+ // Caesurae must sit on FOOT BOUNDARIES (when the scansion is known). The line's
67
+ // metrical feet ARE the scansion's own segmentation, so a break that falls mid-
68
+ // foot has no metrical reality — it merely fragments a foot (often into a lone
69
+ // monosyllable: "But ‖ Oh ‖ ye…") and it made the reading view disagree with the
70
+ // detailed "Feet:" view, which already marks foot-edge breaks only. Punctuation
71
+ // is a caesura CANDIDATE, never an override: an IU boundary that does not land on
72
+ // a foot edge is dropped (the soft-caesura fallback below then offers one medial,
73
+ // foot-aligned break if the line is long enough).
74
+ const footEdges = scansion ? footBoundarySet(scansion) : null;
75
+ const iuOf = new Map();
76
+ for (let i = 0; i < ius.length; i++) {
77
+ for (const pp of ius[i].phonologicalPhrases) {
78
+ for (const cg of pp.cliticGroups)
79
+ for (const tok of cg.tokens)
80
+ iuOf.set(tok, i);
81
+ }
82
+ }
83
+ let cum = 0;
84
+ let prevIu;
85
+ let prevWasContentful = false;
86
+ const onsetPositions = [];
87
+ for (const w of words) {
88
+ if (isPunctuation(w.lexicalClass) || w.syllables.length === 0)
89
+ continue;
90
+ const iu = iuOf.get(w);
91
+ if (prevIu !== undefined && iu !== undefined && iu !== prevIu
92
+ && (!footEdges || footEdges.has(cum))) {
93
+ caes.set(cum, 'hard'); // IU boundary landing on a foot edge → hard caesura
94
+ }
95
+ // A phrase-onset word that is NOT the line's first word opens a candidate
96
+ // caesura immediately before it.
97
+ if (prevWasContentful && isPhraseOnset(w))
98
+ onsetPositions.push(cum);
99
+ cum += w.syllables.length;
100
+ prevIu = iu;
101
+ prevWasContentful = true;
102
+ }
103
+ const total = cum;
104
+ // Infer ONE medial caesura only when the line carries no overt (hard) break.
105
+ if (caes.size === 0 && total >= 8 && onsetPositions.length > 0) {
106
+ const mid = total / 2;
107
+ const lo = Math.max(2, Math.ceil(total / 3));
108
+ const hi = Math.floor((2 * total) / 3);
109
+ let best = -1, bestDist = Infinity;
110
+ for (const c of onsetPositions) {
111
+ if (c < lo || c > hi)
112
+ continue; // medial third only
113
+ if (footEdges && !footEdges.has(c))
114
+ continue; // align to a foot boundary
115
+ const d = Math.abs(c - mid);
116
+ if (d < bestDist) {
117
+ bestDist = d;
118
+ best = c;
119
+ }
120
+ }
121
+ if (best > 0)
122
+ caes.set(best, 'soft');
123
+ }
124
+ return caes;
125
+ }
126
+ /**
127
+ * The words that immediately PRECEDE a caesura in a line — i.e. each word whose
128
+ * cumulative syllable count lands exactly on a caesura position. Used by the
129
+ * rhyme layer for pre-caesural internal-rhyme detection. Returned in linear
130
+ * (reading) order; the caesura kind is paired so callers can weight hard vs
131
+ * inferred breaks if they wish.
132
+ */
133
+ export function preCaesuralWords(words, ius, scansion) {
134
+ const caes = computeCaesurae(words, ius, scansion);
135
+ if (caes.size === 0)
136
+ return [];
137
+ const out = [];
138
+ let cum = 0;
139
+ for (const w of words) {
140
+ if (isPunctuation(w.lexicalClass) || w.syllables.length === 0)
141
+ continue;
142
+ cum += w.syllables.length;
143
+ const kind = caes.get(cum);
144
+ if (kind)
145
+ out.push({ word: w, kind });
146
+ }
147
+ return out;
148
+ }
@@ -0,0 +1,13 @@
1
+ interface FinDepNode {
2
+ label: string;
3
+ type: string;
4
+ parent: number;
5
+ }
6
+ /**
7
+ * Repair systematic en-parse attachment errors. Returns a new deps array
8
+ * (same shape as en-parse's `toArray` output); on any failure returns the
9
+ * input unchanged.
10
+ */
11
+ export declare function applyDepFixes(tokens: string[], tags: string[], deps: FinDepNode[]): FinDepNode[];
12
+ export {};
13
+ //# sourceMappingURL=depfix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"depfix.d.ts","sourceRoot":"","sources":["../../src/clio/depfix.ts"],"names":[],"mappings":"AAiBA,UAAU,UAAU;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAqCD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,UAAU,EAAE,CAwBhG"}
@@ -0,0 +1,84 @@
1
+ // depfix.ts — Post-parse dependency repair via DepEdit rules (the `depedits`
2
+ // npm package, the maintainer's TypeScript port of DepEdit).
3
+ //
4
+ // Runs AFTER en-parse, complementing the pre-parse tag corrections in
5
+ // tagfix.ts: tagfix repairs what the tagger got wrong before the tree is
6
+ // built; this pass repairs systematic attachment errors en-parse makes even
7
+ // with correct tags. Rules are written in DepEdit's declarative format
8
+ // (definitions ⟶ relations ⟶ actions, tab-separated) over en-parse's own
9
+ // label space (DOBJ/NSUBJ/DEP/…), so the round-trip is lossless and every
10
+ // rule is independently testable.
11
+ //
12
+ // The rule set is deliberately small and evidence-based — each rule cites the
13
+ // observed failure it corrects. `depedits` is ESM-only; it is loaded lazily
14
+ // and failures degrade gracefully (the unrepaired parse is still a parse).
15
+ import { createRequire } from 'module';
16
+ // Observed failure (probe: "I had quit the programming paradigm"): en-parse
17
+ // attaches BOTH nouns of a noun compound to the verb as parallel objects
18
+ // ("programming ←DOBJ← quit", "paradigm ←DOBJ← quit"), and leaves the
19
+ // determiner dangling on the first noun as generic DEP. The repairs:
20
+ // 1. Two adjacent common nouns sharing a governor with the same object
21
+ // relation → the first is a compound modifier (AMOD) of the second.
22
+ // 2. A determiner left as DEP on a noun that has become a modifier →
23
+ // re-attach it as DET to that noun's head (the true NP head).
24
+ const CALLIOPE_DEP_FIXES = [
25
+ 'xpos=/NNS?/&func=/DOBJ|IOBJ/;xpos=/NNS?/&func=/DOBJ|IOBJ/;xpos=/VB.*/\t#3>#1;#3>#2;#1.#2\t#2>#1;#1:func=AMOD',
26
+ 'xpos=/DT/&func=/DEP|EXT/;xpos=/NNS?/&func=/AMOD/;xpos=/NNS?.*/\t#2>#1;#3>#2\t#3>#1;#1:func=DET',
27
+ ].join('\n');
28
+ let engine;
29
+ function loadEngine() {
30
+ if (engine !== undefined)
31
+ return engine;
32
+ try {
33
+ // This package compiles to ESM, where bare `require` does not exist, and
34
+ // the parse path is synchronous, so dynamic import() is not an option:
35
+ // createRequire gives a sync loader, and since `depedits` is itself
36
+ // ESM-only this resolves via Node's require(esm) (≥20.17 / ≥22.12). On
37
+ // older runtimes it throws and the repair pass degrades to a no-op (the
38
+ // unrepaired parse is still a parse).
39
+ const req = createRequire(import.meta.url);
40
+ const { DepEditEngine } = req('depedits');
41
+ const e = new DepEditEngine();
42
+ e.loadIniString(CALLIOPE_DEP_FIXES);
43
+ engine = e;
44
+ }
45
+ catch {
46
+ engine = null;
47
+ }
48
+ return engine;
49
+ }
50
+ /**
51
+ * Repair systematic en-parse attachment errors. Returns a new deps array
52
+ * (same shape as en-parse's `toArray` output); on any failure returns the
53
+ * input unchanged.
54
+ */
55
+ export function applyDepFixes(tokens, tags, deps) {
56
+ const e = loadEngine();
57
+ if (!e || tokens.length === 0 || deps.length !== tokens.length)
58
+ return deps;
59
+ try {
60
+ const conllu = tokens.map((tok, i) => {
61
+ const head = deps[i].parent >= 0 ? deps[i].parent + 1 : 0;
62
+ const safe = tok.replace(/\s/g, '_') || '_';
63
+ return `${i + 1}\t${safe}\t${safe}\t_\t${tags[i] || '_'}\t_\t${head}\t${deps[i].label || 'DEP'}\t_\t_`;
64
+ }).join('\n') + '\n\n';
65
+ const out = e.process(conllu);
66
+ const fixed = deps.map(d => ({ ...d }));
67
+ for (const row of out.split('\n')) {
68
+ const cols = row.split('\t');
69
+ if (cols.length < 10)
70
+ continue;
71
+ const idx = parseInt(cols[0], 10) - 1;
72
+ if (!(idx >= 0 && idx < fixed.length))
73
+ continue;
74
+ const head = parseInt(cols[6], 10);
75
+ fixed[idx].parent = Number.isFinite(head) ? head - 1 : fixed[idx].parent;
76
+ if (cols[7] && cols[7] !== '_')
77
+ fixed[idx].label = cols[7];
78
+ }
79
+ return fixed;
80
+ }
81
+ catch {
82
+ return deps;
83
+ }
84
+ }
@@ -0,0 +1,32 @@
1
+ import { ClsWord, LineResult } from '../types.js';
2
+ export declare function renderUnifiedDisplay(result: LineResult, rawLine?: string): string;
3
+ export declare function renderLegend(): string;
4
+ /**
5
+ * The long-form legend triggered from the main menu's "Display Legend" option:
6
+ * the compact legend PLUS full Penn POS-tag and grammatical-dependency glossaries.
7
+ * (These glossaries are deliberately NOT part of the compact in-output legend.)
8
+ */
9
+ export declare function renderFullLegend(): string;
10
+ /** One input line with its (1+) parsed sentence results. */
11
+ export interface ReadingLine {
12
+ raw: string;
13
+ results: LineResult[];
14
+ }
15
+ /** A stanza: a run of consecutive non-blank input lines. */
16
+ export interface ReadingStanza {
17
+ lines: ReadingLine[];
18
+ }
19
+ /**
20
+ * Project per-syllable stress colours back onto the original line, preserving
21
+ * capitalisation, punctuation, spacing and any extrametrical fragments the
22
+ * pipeline dropped (e.g. possessive "'s"). Word-like tokens are coloured;
23
+ * everything between them (spaces, punctuation, dashes) is emitted verbatim.
24
+ *
25
+ * Alignment is tolerant: it matches each token to the next parsed word by
26
+ * normalised form (equal, or token starts with the word — handling "cat's"),
27
+ * with a small look-ahead resync so a stray/unsyllabified token never derails
28
+ * the rest of the line. No original character is ever dropped.
29
+ */
30
+ export declare function projectStressOntoLine(rawLine: string, words: ClsWord[]): string;
31
+ export declare function renderReadingView(stanzas: ReadingStanza[]): string;
32
+ //# sourceMappingURL=display.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"display.d.ts","sourceRoot":"","sources":["../../src/clio/display.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,OAAO,EAMP,UAAU,EAGX,MAAM,aAAa,CAAC;AAuJrB,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAsQjF;AAMD,wBAAgB,YAAY,IAAI,MAAM,CAUrC;AAuHD;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAsBzC;AAqCD,4DAA4D;AAC5D,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAmCD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,CAuC/E;AAuOD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,MAAM,CA0FlE"}