calliope-ts 0.0.4 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/README.md +69 -3
  2. package/dist/calliope/bracketing.d.ts.map +1 -1
  3. package/dist/calliope/bracketing.js +59 -5
  4. package/dist/calliope/deps.d.ts +5 -1
  5. package/dist/calliope/deps.d.ts.map +1 -1
  6. package/dist/calliope/deps.js +53 -3
  7. package/dist/calliope/engine.d.ts.map +1 -1
  8. package/dist/calliope/engine.js +6 -1
  9. package/dist/calliope/relstress.d.ts.map +1 -1
  10. package/dist/calliope/relstress.js +411 -15
  11. package/dist/calliope/syntax.d.ts +33 -0
  12. package/dist/calliope/syntax.d.ts.map +1 -1
  13. package/dist/calliope/syntax.js +199 -0
  14. package/dist/data/en_common_words.json +1 -0
  15. package/dist/display.d.ts +15 -1
  16. package/dist/display.d.ts.map +1 -1
  17. package/dist/display.js +112 -10
  18. package/dist/fabbhalle.d.ts +54 -0
  19. package/dist/fabbhalle.d.ts.map +1 -0
  20. package/dist/fabbhalle.js +404 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +86 -33
  23. package/dist/phonological.d.ts.map +1 -1
  24. package/dist/phonological.js +7 -1
  25. package/dist/rewriteEn.d.ts +14 -0
  26. package/dist/rewriteEn.d.ts.map +1 -0
  27. package/dist/rewriteEn.js +392 -0
  28. package/dist/rhyme.d.ts +35 -3
  29. package/dist/rhyme.d.ts.map +1 -1
  30. package/dist/rhyme.js +176 -11
  31. package/dist/russian/accentuator.d.ts +31 -0
  32. package/dist/russian/accentuator.d.ts.map +1 -0
  33. package/dist/russian/accentuator.js +546 -0
  34. package/dist/russian/collocations.d.ts +7 -0
  35. package/dist/russian/collocations.d.ts.map +1 -0
  36. package/dist/russian/collocations.js +38 -0
  37. package/dist/russian/compounds.d.ts +6 -0
  38. package/dist/russian/compounds.d.ts.map +1 -0
  39. package/dist/russian/compounds.js +81 -0
  40. package/dist/russian/download.d.ts +23 -0
  41. package/dist/russian/download.d.ts.map +1 -0
  42. package/dist/russian/download.js +117 -0
  43. package/dist/russian/engine.d.ts +6 -0
  44. package/dist/russian/engine.d.ts.map +1 -0
  45. package/dist/russian/engine.js +1424 -0
  46. package/dist/russian/parser.d.ts +8 -0
  47. package/dist/russian/parser.d.ts.map +1 -0
  48. package/dist/russian/parser.js +92 -0
  49. package/dist/russian/paths.d.ts +55 -0
  50. package/dist/russian/paths.d.ts.map +1 -0
  51. package/dist/russian/paths.js +114 -0
  52. package/dist/russian/rewrite.d.ts +6 -0
  53. package/dist/russian/rewrite.d.ts.map +1 -0
  54. package/dist/russian/rewrite.js +400 -0
  55. package/dist/russian/rhyme.d.ts +18 -0
  56. package/dist/russian/rhyme.d.ts.map +1 -0
  57. package/dist/russian/rhyme.js +230 -0
  58. package/dist/russian/syllabifier.d.ts +14 -0
  59. package/dist/russian/syllabifier.d.ts.map +1 -0
  60. package/dist/russian/syllabifier.js +475 -0
  61. package/dist/russian/types.d.ts +94 -0
  62. package/dist/russian/types.d.ts.map +1 -0
  63. package/dist/russian/types.js +27 -0
  64. package/dist/scandroidNative/constants.d.ts +16 -0
  65. package/dist/scandroidNative/constants.d.ts.map +1 -0
  66. package/dist/scandroidNative/constants.js +60 -0
  67. package/dist/scandroidNative/deduceParameters.d.ts +16 -0
  68. package/dist/scandroidNative/deduceParameters.d.ts.map +1 -0
  69. package/dist/scandroidNative/deduceParameters.js +69 -0
  70. package/dist/scandroidNative/dictionary-data.d.ts +2 -0
  71. package/dist/scandroidNative/dictionary-data.d.ts.map +1 -0
  72. package/dist/scandroidNative/dictionary-data.js +744 -0
  73. package/dist/scandroidNative/dictionary.d.ts +12 -0
  74. package/dist/scandroidNative/dictionary.d.ts.map +1 -0
  75. package/dist/scandroidNative/dictionary.js +50 -0
  76. package/dist/scandroidNative/engine.d.ts +41 -0
  77. package/dist/scandroidNative/engine.d.ts.map +1 -0
  78. package/dist/scandroidNative/engine.js +119 -0
  79. package/dist/scandroidNative/machine.d.ts +93 -0
  80. package/dist/scandroidNative/machine.d.ts.map +1 -0
  81. package/dist/scandroidNative/machine.js +795 -0
  82. package/dist/scandroidNative/positioner.d.ts +32 -0
  83. package/dist/scandroidNative/positioner.d.ts.map +1 -0
  84. package/dist/scandroidNative/positioner.js +208 -0
  85. package/dist/scandroidNative/syllabizer.d.ts +3 -0
  86. package/dist/scandroidNative/syllabizer.d.ts.map +1 -0
  87. package/dist/scandroidNative/syllabizer.js +215 -0
  88. package/dist/scandroidNative/utilities.d.ts +37 -0
  89. package/dist/scandroidNative/utilities.d.ts.map +1 -0
  90. package/dist/scandroidNative/utilities.js +86 -0
  91. package/dist/scansion.d.ts.map +1 -1
  92. package/dist/scansion.js +118 -14
  93. package/dist/stress.d.ts +1 -0
  94. package/dist/stress.d.ts.map +1 -1
  95. package/dist/stress.js +502 -42
  96. package/dist/types.d.ts +3 -0
  97. package/dist/types.d.ts.map +1 -1
  98. package/package.json +27 -6
  99. package/tools/fetch-russian-data.mjs +30 -0
  100. package/webapp/public/app.js +2418 -0
  101. package/webapp/public/assets/GithubLogo.png +0 -0
  102. package/webapp/public/assets/NPM2.png +0 -0
  103. package/webapp/public/assets/fonts/MurenaBold-webfont.woff2 +0 -0
  104. package/webapp/public/assets/fonts/MurenaItalic-webfont.woff2 +0 -0
  105. package/webapp/public/assets/fonts/MurenaRegular-webfont.woff2 +0 -0
  106. package/webapp/public/assets/fonts/Myra_4F_Caps_Bold-webfont.woff2 +0 -0
  107. package/webapp/public/assets/fonts/SinkinSans-700Bold.otf +0 -0
  108. package/webapp/public/assets/fonts/butterflies.ttf +0 -0
  109. package/webapp/public/assets/fonts/midcase_blackline-webfont.woff2 +0 -0
  110. package/webapp/public/assets/fonts/neueral-bold-webfont.woff2 +0 -0
  111. package/webapp/public/assets/fonts/neueral-regular-webfont.woff2 +0 -0
  112. package/webapp/public/assets/fonts/popelka-webfont.woff2 +0 -0
  113. package/webapp/public/assets/fonts/vremenagrotesk-webfont.woff +0 -0
  114. package/webapp/public/assets/fonts/wicky_javick_bold-webfont.woff2 +0 -0
  115. package/webapp/public/assets/logos/Logo_Var_10_Celtic_Knot_SVG.svg +116 -0
  116. package/webapp/public/assets/logos/Logo_Var_11_Graph_SVG.svg +313 -0
  117. package/webapp/public/assets/logos/Logo_Var_12_Limacon_Knot_SVG.svg +3942 -0
  118. package/webapp/public/assets/logos/Logo_Var_13_Crescents_SVG.svg +1030 -0
  119. package/webapp/public/assets/logos/Logo_Var_1_Spiral_SVG.svg +1491 -0
  120. package/webapp/public/assets/logos/Logo_Var_2_Tunnel_SVG.svg +12 -0
  121. package/webapp/public/assets/logos/Logo_Var_3_Triskelion_SVG.svg +10 -0
  122. package/webapp/public/assets/logos/Logo_Var_4_Quinquetra_PNG.png +0 -0
  123. package/webapp/public/assets/logos/Logo_Var_5_Penrose_Pentagram_SVG.svg +37 -0
  124. package/webapp/public/assets/logos/Logo_Var_6_Triskelion2_SVG.svg +10 -0
  125. package/webapp/public/assets/logos/Logo_Var_7_SixfoldSpirals_SVG.svg +20 -0
  126. package/webapp/public/assets/logos/Logo_Var_8_Radial_Labyrinth_SVG.svg +163 -0
  127. package/webapp/public/assets/logos/Logo_Var_9_Radial_Labyrinth_2_SVG.svg +106 -0
  128. package/webapp/public/index.html +316 -0
  129. package/webapp/public/styles.css +1114 -0
  130. package/webapp/server.mjs +680 -0
  131. package/src/caesura.ts +0 -201
  132. package/src/calliope/boundaries.ts +0 -190
  133. package/src/calliope/bracketing.ts +0 -390
  134. package/src/calliope/deps.ts +0 -160
  135. package/src/calliope/engine.ts +0 -77
  136. package/src/calliope/feats.ts +0 -46
  137. package/src/calliope/names.ts +0 -44
  138. package/src/calliope/postag.ts +0 -253
  139. package/src/calliope/prosodic.ts +0 -262
  140. package/src/calliope/relstress.ts +0 -645
  141. package/src/calliope/stressrules.ts +0 -147
  142. package/src/calliope/syntax.ts +0 -218
  143. package/src/calliope/udpos.ts +0 -152
  144. package/src/calliope_src_contents.md +0 -19049
  145. package/src/clio/caesura.ts +0 -145
  146. package/src/clio/depfix.ts +0 -88
  147. package/src/clio/display.ts +0 -1042
  148. package/src/clio/engine.ts +0 -38
  149. package/src/clio/parser.ts +0 -845
  150. package/src/clio/phonological.ts +0 -849
  151. package/src/clio/phrasestress.ts +0 -108
  152. package/src/clio/pipeline.ts +0 -154
  153. package/src/clio/rhyme.ts +0 -740
  154. package/src/clio/scandroid.ts +0 -434
  155. package/src/clio/scansion.ts +0 -1130
  156. package/src/clio/semantics.ts +0 -134
  157. package/src/clio/stress.ts +0 -1731
  158. package/src/clio/tagfix.ts +0 -104
  159. package/src/depfix.ts +0 -88
  160. package/src/display.ts +0 -1138
  161. package/src/engine.ts +0 -22
  162. package/src/index.ts +0 -690
  163. package/src/parser.ts +0 -501
  164. package/src/phonological.ts +0 -917
  165. package/src/phrasestress.ts +0 -108
  166. package/src/rhyme.ts +0 -748
  167. package/src/scandroid.ts +0 -434
  168. package/src/scansion.ts +0 -1174
  169. package/src/semantics.ts +0 -134
  170. package/src/stress.ts +0 -2111
  171. package/src/tagfix.ts +0 -104
  172. package/src/types.ts +0 -265
  173. package/tests/DataForHayesLinesOnly.txt +0 -364
  174. package/tests/DataForHayesStressSymbolsRevised.txt +0 -728
  175. package/tests/basic.test.ts +0 -1102
  176. package/tests/bench-hayes.mjs +0 -72
  177. package/tests/epg64.meter.train.txt +0 -8139
  178. package/tests/litlab-sample-2016.txt +0 -1738
  179. package/tests/prosodic.meter.train.txt +0 -13192
  180. package/tests/wagner-stress.test.ts +0 -188
  181. package/tsconfig.json +0 -29
  182. package/vitest.config.d.ts +0 -2
  183. package/vitest.config.js +0 -14
@@ -28,7 +28,7 @@
28
28
  // utterance nuclear), the symbolic contour is local (one beat per φ) and then
29
29
  // modified (clash resolution), so they legitimately diverge — never forced equal.
30
30
  import { collectPPTokens } from '../phonological.js';
31
- import { isTransitiveFunctionWord, isPronoun, isInherentlyGiven } from './syntax.js';
31
+ import { isTransitiveFunctionWord, isPronoun, isInherentlyGiven, focusAssociateOf, isMarginalModalUse, isCapitalizedFocalPronoun, isPostposedPreposition, isInvertedOperatorAux, isSemiModalHaveTo, } from './syntax.js';
32
32
  const RANK = { x: 0, w: 1, n: 2, m: 3, s: 4 };
33
33
  const LEVELS = ['x', 'w', 'n', 'm', 's'];
34
34
  const CONTENT = /^(NN|NNS|NNP|NNPS|JJ|JJR|JJS|VB|VBD|VBG|VBN|VBP|VBZ|RB|RBR|RBS|CD|UH)$/;
@@ -37,7 +37,13 @@ const QUANTIFIERS = new Set([
37
37
  'all', 'both', 'each', 'every', 'some', 'any', 'many', 'much', 'few', 'no',
38
38
  'most', 'half', 'several', 'either', 'neither', 'enough',
39
39
  ]);
40
- const DEMONSTRATIVES = new Set(['this', 'that', 'these', 'those']);
40
+ // "such" joins the demonstratives for its ATTRIBUTIVE use ("no such roses",
41
+ // "such a night") — a determiner-class weak monosyllable (Quirk's
42
+ // predeterminer; Fabb 2001 on weak monosyllables in iambic verse), whatever
43
+ // the tagger labels it. Its anchor exclusion is gated separately on the
44
+ // attributive relation (see isAttributiveSuch in relativisePP), so the
45
+ // predicative/pronominal use ("such is life") is untouched.
46
+ const DEMONSTRATIVES = new Set(['this', 'that', 'these', 'those', 'such']);
41
47
  // "no" is a NEGATOR, not a reducible determiner: "made with NO loss of time" keeps
42
48
  // a beat on "no". Listed here (ahead of the rel==='DET' → x floor in functionLevel)
43
49
  // so the negative determiner is never flattened to the clitic tier.
@@ -50,6 +56,13 @@ const BE_FORMS = new Set(['be', 'am', 'is', 'are', 'was', 'were', 'been', 'being
50
56
  // to a single proclitic syllable — never a stressed content peak (en-pos mis-tags
51
57
  // the capitalised "'Tis" as NNP). Apostrophes already stripped by bare().
52
58
  const APHAERESIS = new Set(['tis', 'twas', 'twere', 'twill', 'twould', 'tisnt']);
59
+ // Archaic interrogative wh-adverbs — a closed grammatical class the tagger
60
+ // routinely mislabels (Coleridge's "wherefore" comes back IN): they carry the
61
+ // wh n-tier like their modern counterparts (why/where/whence…).
62
+ const WH_ADV_LEMMAS = new Set([
63
+ 'wherefore', 'whence', 'whither', 'whereby', 'wherein', 'whereof',
64
+ 'whereto', 'whereat', 'whereupon',
65
+ ]);
53
66
  function bare(w) {
54
67
  return w.word.toLowerCase().replace(/['’]/g, '');
55
68
  }
@@ -60,6 +73,17 @@ function isContent(w) {
60
73
  function isReducedVerb(w, words) {
61
74
  if (isAphaeresis(w))
62
75
  return true;
76
+ // Marginal-modal dare/need with a BARE infinitive ("dare he aspire", "dare
77
+ // seize the fire") — the auxiliary usage (it is what licenses the inversion):
78
+ // reduces like a modal; the beat belongs to the infinitive it governs.
79
+ if (words && isMarginalModalUse(w, words))
80
+ return true;
81
+ // Semi-modal "have to" (necessity — "do I have to bless", "we had to
82
+ // laugh"): the reduced ("hafta") functor use; the beat belongs to its
83
+ // infinitive. Lexical "have" (possession, causative, existential) never
84
+ // matches — the gate is the complement's shape, not the lemma.
85
+ if (words && isSemiModalHaveTo(w, words))
86
+ return true;
63
87
  const rel = w.canonicalRel ?? '';
64
88
  // "to be" (infinitive main verb) and ROOT be-forms ("Where WAS I?",
65
89
  // "I have BEEN") carry a beat — NOT reduced. Must check BEFORE the AUX
@@ -89,6 +113,30 @@ function functionLevel(w, words) {
89
113
  const lemma = bare(w);
90
114
  const pos = w.lexicalClass;
91
115
  const rel = w.canonicalRel ?? '';
116
+ // RHYME-FELLOW positive override (set by the stanza pass in index.ts): a
117
+ // line-final function MONOSYLLABLE that strong-rhymes with a content
118
+ // line-final elsewhere in the stanza carries the rhyme's beat — "Now
119
+ // WHEREfore STOPP'ST thou ME?" (me ~ three). 'n', not 's': a real beat,
120
+ // still graded below the clause's content anchors.
121
+ if (w.rhymeFocal && w.syllables.length === 1)
122
+ return 'n';
123
+ // wh-words FIRST — even in the determiner slot ("WHAT immortal hand or eye"):
124
+ // Krifka (13), wh-words and quantifiers as arguments take accents like content
125
+ // words ("Éverybody has escáped"); an interrogative determiner is the focus
126
+ // exponent of its question and never floors to the article tier. The archaic
127
+ // wh-adverbs ("Now WHEREfore stopp'st thou me?") are routinely mis-tagged IN,
128
+ // so the closed lemma class backs the POS test up.
129
+ if (/^(WDT|WP|WP\$|WRB)$/.test(pos) || WH_ADV_LEMMAS.has(lemma)) {
130
+ // …but a wh-word immediately followed by its POSTPOSED preposition ("what
131
+ // for", "who with", "where from") defers to the preposition's strong form
132
+ // — the orphaned functor is the accent exponent of the inverted PP ("what
133
+ // FOR is this form"). Keeping both at 'n' would only feed the n-n clash
134
+ // rule, whose lexical-stress tiebreak would demote the preposition back.
135
+ const nxt = words.find(x => x.absoluteIndex === w.absoluteIndex + 1 && x.syllables.length > 0);
136
+ if (nxt && isPostposedPreposition(nxt, words))
137
+ return 'w';
138
+ return 'n';
139
+ }
92
140
  // A quantifier / demonstrative in an ATTRIBUTIVE determiner slot ("each street",
93
141
  // "this cat", "that day") is a reduced determiner (x), NOT a standalone n-tier
94
142
  // quantifier. Only the determiner USE reduces — a predeterminer ("ALL the",
@@ -97,9 +145,14 @@ function functionLevel(w, words) {
97
145
  // preposition's beat ("through", "in") outrank an interior "each".
98
146
  if (rel === 'DET' && pos !== 'PDT' && !NEGATORS.has(lemma))
99
147
  return 'x';
100
- // n — quantifiers, demonstratives, wh-words, negators (carry real stress)
101
- if (pos === 'PDT' || QUANTIFIERS.has(lemma) || DEMONSTRATIVES.has(lemma) ||
102
- NEGATORS.has(lemma) || /^(WDT|WP|WP\$|WRB)$/.test(pos))
148
+ // n — quantifiers, demonstratives, negators (carry real stress) — but NOT
149
+ // when the token is a subordinating MARK: "that" in "that I were clay" and
150
+ // "so" in "so that I seem" are complementisers (functors with clausal
151
+ // complements, Wagner §6.5.5), not demonstratives, and floor with the other
152
+ // complementisers below.
153
+ if (rel !== 'COMPMARK' && rel !== 'ADVMARK' &&
154
+ (pos === 'PDT' || QUANTIFIERS.has(lemma) || DEMONSTRATIVES.has(lemma) ||
155
+ NEGATORS.has(lemma)))
103
156
  return 'n';
104
157
  // "out of" is a compound preposition with fixed stress on "out" (OUT of, not
105
158
  // out OF). "out" carries the directional content; "of" is the grammatical
@@ -113,26 +166,59 @@ function functionLevel(w, words) {
113
166
  if (next && bare(next) === 'of')
114
167
  return 'n';
115
168
  }
169
+ // A POSTPOSED preposition — orphaned from its complement, which stands
170
+ // immediately to its LEFT as a wh-word ("what FOR is this form" = "for
171
+ // what"; "who WITH?", "where FROM?") — cannot procliticize rightward and
172
+ // takes its STRONG form (Selkirk 1996: function words are weak only where
173
+ // they can cliticize onto a following host). Accent-capable 'n', so the
174
+ // meter realises the beat; a preposition with a genuine rightward
175
+ // complement never matches ("who for the WORLD…" stays reduced below).
176
+ if (isPostposedPreposition(w, words))
177
+ return 'n';
116
178
  // A STRANDED / intransitive preposition ("what are you waiting FOR", "she walked
117
179
  // IN") is NOT the reducible proclitic of "in the house" — Wagner §6.5.5: only
118
180
  // transitive functors have the weak allomorph. It keeps an overt beat ('w'), so
119
181
  // the metrical fitter can promote it; a TRANSITIVE preposition floors at 'x'.
120
- if (pos === 'IN' || pos === 'TO' || rel === 'CASE') {
182
+ // POSSESSIVE pronouns and the clitic 's also carry the CASE relation but are
183
+ // not prepositions — they take the pronoun tier below, not the clitic floor
184
+ // (the CASE catch-all had crushed "For His Civility"'s His to 'x').
185
+ if (pos === 'IN' || pos === 'TO' ||
186
+ (rel === 'CASE' && pos !== 'PRP$' && pos !== 'PRP' && pos !== 'POS')) {
121
187
  return isTransitiveFunctionWord(w, words) ? 'x' : 'w';
122
188
  }
123
- // x — pure clitics: articles, coordinators, complementisers
124
- if (ARTICLES.has(lemma) || pos === 'CC' || rel === 'CC' || rel === 'COMPMARK')
189
+ // x — pure clitics: articles, coordinators, complementisers, adverbial marks
190
+ // (wh-adverb marks "when", "where" keep the overt 'w' tier below: they
191
+ // carry framing content and verse freely promotes them)
192
+ if (ARTICLES.has(lemma) || pos === 'CC' || rel === 'CC' || rel === 'COMPMARK' ||
193
+ (rel === 'ADVMARK' && !/^(WDT|WP|WP\$|WRB)$/.test(pos)))
125
194
  return 'x';
195
+ // A CAPITALIZED mid-sentence pronoun ("For His Civility", "but just
196
+ // Ourselves") is the poet's typographic focus mark — narrow focus per
197
+ // Krifka (78): it carries real (promotable) stress, not the given-pronoun w.
198
+ if (isCapitalizedFocalPronoun(w, words))
199
+ return 'n';
126
200
  // w — possessives, pronouns, auxiliaries, modals, leftover determiners
127
201
  return 'w';
128
202
  }
129
- /** The peak (highest lexical-stress) syllable of a word. */
203
+ /** The peak (highest lexical-stress) syllable of a word.
204
+ *
205
+ * TIES (the lexicon's double-stressed entries — "outside" 1·1) resolve by the
206
+ * English diatone: the nominal/adjectival member of such a pair is LEFT-stressed
207
+ * (the OUTside of the house) while the prepositional/adverbial/particle member is
208
+ * RIGHT-stressed (she waited outSIDE; outSIDE the swallows roundelay) — so a tie
209
+ * takes the LAST tied syllable when the word is tagged as an adposition/particle,
210
+ * or is a preposition-built adverb (out·side, with·in, through·out, be·yond…).
211
+ * The compound indefinites (SOMEwhere, ANYthing) are left-stressed and do not
212
+ * match the particle-prefix test, so they keep the default first-syllable tie. */
213
+ const PREP_PREFIX = /^(out|in|up|down|with|through|be|a(?=bove|bout|round|cross|long|mid|gainst))/;
130
214
  function peakSyllable(w) {
215
+ const rightwardTies = /^(IN|TO|RP)$/.test(w.lexicalClass) ||
216
+ (/^RB/.test(w.lexicalClass) && PREP_PREFIX.test(w.word.toLowerCase()));
131
217
  let best = null;
132
218
  let bestV = -1;
133
219
  for (const s of w.syllables) {
134
220
  const v = s.lexicalStress ?? s.stress;
135
- if (v > bestV) {
221
+ if (v > bestV || (rightwardTies && v === bestV)) {
136
222
  bestV = v;
137
223
  best = s;
138
224
  }
@@ -212,8 +298,21 @@ function relativisePP(toks, words) {
212
298
  // subordinated relative to its sister (Wagner Ch.7) — but only as an ANCHOR
213
299
  // demotion, and never when it would empty the φ of content. A word given a higher
214
300
  // (worse) effective phrase-stress is less likely to win the φ nuclear.
301
+ // The line's FINAL syllable-bearing word: the RHYME position. Verse-final
302
+ // position is focal by construction (the rhyme is the form's own contrast
303
+ // device — Krifka: focus overrides givenness deaccenting), so givenness
304
+ // demotion never applies there: Shakespeare's "…more red than her lips'
305
+ // RED" keeps its rhyme-carrying beat even though "red" is discourse-given.
306
+ // Deaccented rhymes remain possible — as the marked, wrenched case the
307
+ // meter layer prices — but the RELATIVISER does not impose them.
308
+ const lineFinal = [...words]
309
+ .filter(x => x.syllables.length > 0)
310
+ .sort((a, b) => a.absoluteIndex - b.absoluteIndex)
311
+ .pop() ?? null;
215
312
  const ps = (w) => {
216
313
  const base = w.phraseStress || Infinity;
314
+ if (w === lineFinal)
315
+ return base; // rhyme position: never given-demoted
217
316
  // A given content word — discourse-given (repeated from an earlier line), an
218
317
  // inherently-given indefinite pronoun ("something", Wagner §7.2.3), or
219
318
  // anaphorically given via a shared-head coordinate structure ("young blood
@@ -233,7 +332,12 @@ function relativisePP(toks, words) {
233
332
  const promotePronoun = (w) => {
234
333
  if (w.lexicalClass !== 'PRP' || w.word.includes("'") || w.word.includes("’"))
235
334
  return false;
236
- if (w.phraseStress !== 1)
335
+ // ≤ 2, not === 1: the utterance-Newman pass (bracketing.ts) demotes an
336
+ // EARLIER co-equal of the utterance nuclear by one — a subject pronoun that
337
+ // matched a branching VP ("I have no right" — I co-equal with "right")
338
+ // lands at 2 yet is still the syntactically prominent pronoun this
339
+ // inversion test wants.
340
+ if (w.phraseStress > 2)
237
341
  return false;
238
342
  const idx = toks.indexOf(w);
239
343
  if (idx !== 0)
@@ -254,10 +358,115 @@ function relativisePP(toks, words) {
254
358
  return true;
255
359
  return false;
256
360
  };
361
+ // Subordinating marks are functors regardless of POS (the RB-tagged "so" of
362
+ // "so that I seem" must not anchor a beat over its clause's verb).
363
+ const isMark = (w) => {
364
+ const r = w.canonicalRel ?? '';
365
+ return r === 'COMPMARK' || r === 'ADVMARK';
366
+ };
367
+ // A pure negator ("not") is a functor over its predicate (negation takes the
368
+ // VP as complement); it keeps its n-tier stress — real, promotable — but must
369
+ // not claim a beat-anchor away from the verb it negates ("could not STOP for
370
+ // DEATH", not "could NOT stop"). Restored below if the φ has nothing else.
371
+ const isNegatorTok = (w) => NEGATORS.has(bare(w));
372
+ // Association with Focus (Krifka §4.4.6): a premodifying "just/only/even"
373
+ // F-marks its associate — the PARTICLE reduces (functor) and a PRONOUN
374
+ // associate is promoted to anchor status ("held but just OURSELVES": the
375
+ // exclusive reading accents the pronoun, never the particle).
376
+ const isFocusParticleTok = (w) => focusAssociateOf(w, words) !== null;
377
+ // A clause-initial temporal deictic directly introducing a wh-word ("Now
378
+ // wherefore stopp'st thou me?", "Now, what shall we do?") is a DISCOURSE
379
+ // MARKER, not a time adverb — Krifka §4.5.4 groups situational deictics with
380
+ // the non-accentable expressions in exactly this transitional use. It cedes
381
+ // its beat to the wh-focus it introduces (kept at 'n': real, promotable).
382
+ // The genuinely temporal use ("Now sleeps the crimson petal") is untouched —
383
+ // the gate is the following wh-word, not the lemma.
384
+ const isDiscourseDeictic = (w) => {
385
+ if (!/^(now|then)$/.test(bare(w)) || !/^RB/.test(w.lexicalClass))
386
+ return false;
387
+ if (toks.indexOf(w) !== 0)
388
+ return false;
389
+ const nxt = words.find(x => x.absoluteIndex === w.absoluteIndex + 1 && x.syllables.length > 0);
390
+ return !!nxt && (/^(WDT|WP|WP\$|WRB)$/.test(nxt.lexicalClass) || WH_ADV_LEMMAS.has(bare(nxt)));
391
+ };
392
+ // A sentence-initial INFERENTIAL CONNECTIVE set off by a comma ("So, may
393
+ // then each moment…", "Well, I declare", "Now, where was I?") is the
394
+ // discourse-marker use (Schiffrin) — transitional, prosodically reduced,
395
+ // never the φ anchor. The gate is the FULL configuration: first word of
396
+ // the sentence + immediately followed by a comma/dash + the closed
397
+ // inferential class. The accent-taking clause-initial adverbs stay out:
398
+ // spatial presentationals ("Here, take this"), concessives ("Still, she
399
+ // persisted"), narrative-temporal "Then," (freely stressed) are not listed;
400
+ // degree/manner "so" ("so much my own") fails the comma test.
401
+ const CONNECTIVES = new Set(['so', 'now', 'well', 'why', 'anyway']);
402
+ const isDiscourseConnective = (w) => {
403
+ if (!CONNECTIVES.has(bare(w)) || !/^(RB|UH|IN)/.test(w.lexicalClass))
404
+ return false;
405
+ const firstAlpha = words
406
+ .filter(x => /[A-Za-z]/.test(x.word))
407
+ .sort((a, b) => a.absoluteIndex - b.absoluteIndex)[0];
408
+ if (firstAlpha !== w)
409
+ return false;
410
+ const nxt = words.find(x => x.absoluteIndex === w.absoluteIndex + 1);
411
+ return !!nxt && nxt.syllables.length === 0 && /^[,—–-]+$/.test(nxt.word);
412
+ };
413
+ // "then/now" wedged between an INVERTED operator auxiliary and its delayed
414
+ // subject ("So, may then each moment drip off" — "may(beat) then(weak)") is
415
+ // the inferential use: the aux-to-subject gap of an inversion is a proclitic
416
+ // zone, and the operator aux, not the deictic, carries the inversion's
417
+ // accent. Normal-order "I would then go" (subject already passed) and
418
+ // clause-initial narrative "Then," are untouched.
419
+ const isPostOperatorDeictic = (w) => {
420
+ if (!/^(now|then)$/.test(bare(w)) || !/^RB/.test(w.lexicalClass))
421
+ return false;
422
+ const prev = words
423
+ .filter(x => x.syllables.length > 0 && x.absoluteIndex < w.absoluteIndex)
424
+ .sort((a, b) => b.absoluteIndex - a.absoluteIndex)[0];
425
+ if (!prev || prev.absoluteIndex !== w.absoluteIndex - 1)
426
+ return false;
427
+ return isInvertedOperatorAux(prev, words);
428
+ };
429
+ // Attributive "such" ("no such roses see I…") is determiner-class and must
430
+ // not out-anchor the noun it modifies — which cross-line givenness demotion
431
+ // would otherwise let it do (L5's "roses" made L6's "roses" given, handing
432
+ // the φ nuclear to "such" and wrecking the iambic grid). The gate is the
433
+ // attributive relation to a FOLLOWING governor; predicative/pronominal
434
+ // "such" ("such is life") never matches and keeps content status.
435
+ const isAttributiveSuch = (w) => {
436
+ if (bare(w) !== 'such')
437
+ return false;
438
+ const rel = w.canonicalRel ?? '';
439
+ if (rel !== 'AMOD' && rel !== 'DET')
440
+ return false;
441
+ const gov = w.dependency?.governor;
442
+ return !!gov && gov.absoluteIndex > w.absoluteIndex;
443
+ };
444
+ const focusAssociates = new Set();
445
+ for (const t of toks) {
446
+ const a = focusAssociateOf(t, words);
447
+ if (a)
448
+ focusAssociates.add(a);
449
+ }
450
+ // A pronoun made FOCAL — by a focus particle, or by the poet's mid-sentence
451
+ // capital ("Ourselves", "His") — sheds its inherent givenness (Wagner §7.2.3
452
+ // is a default, not a sentence): it anchors like content.
453
+ const isFocalPronoun = (w) => isPronoun(w) && w.syllables.length > 0 &&
454
+ (focusAssociates.has(w) || isCapitalizedFocalPronoun(w, words));
257
455
  // The set the gradient is measured against: content words (non-reduced) first;
258
456
  // for a content-less φ fall back to its prominent function words ("for THAT",
259
457
  // "to YOU"), then to any content, then to anything — so every φ has an anchor.
260
- let anchors = toks.filter(w => promotePronoun(w) || (isContent(w) && !isReducedVerb(w, words)));
458
+ // An inverted operator aux ("So, MAY then each moment…", "HAD I known") is a
459
+ // positive anchor — the operator carries the inversion's accent even though
460
+ // it is neither content nor a promoted pronoun.
461
+ let anchors = toks.filter(w => promotePronoun(w) || isFocalPronoun(w) ||
462
+ isInvertedOperatorAux(w, words) ||
463
+ (isContent(w) && !isReducedVerb(w, words) && !isMark(w) &&
464
+ !isNegatorTok(w) && !isFocusParticleTok(w) && !isDiscourseDeictic(w) &&
465
+ !isDiscourseConnective(w) && !isPostOperatorDeictic(w) &&
466
+ !isAttributiveSuch(w)));
467
+ if (anchors.length === 0) {
468
+ anchors = toks.filter(w => isContent(w) && !isReducedVerb(w, words) && !isMark(w));
469
+ }
261
470
  if (anchors.length === 0)
262
471
  anchors = toks.filter(w => !isReducedVerb(w, words) && functionLevel(w, words) !== 'x');
263
472
  if (anchors.length === 0)
@@ -291,6 +500,14 @@ function relativisePP(toks, words) {
291
500
  if (BE_FORMS.has(bare(w))) {
292
501
  const i = toks.indexOf(w);
293
502
  const prev = i > 0 ? toks[i - 1] : null;
503
+ // EXISTENTIAL be ("There WAS an Old Man with a beard"): after the
504
+ // expletive THERE (EX tag) the be-form is the verb of EXISTENCE — the
505
+ // assertion itself — not a copula linking a subject to a predicate.
506
+ // It keeps the overt-weak tier (promotable to the beat verse wants),
507
+ // never the clitic floor. Deictic-locative "THERE was…" is tagged RB
508
+ // and takes a different path entirely, so it is untouched.
509
+ if (prev && prev.lexicalClass === 'EX')
510
+ return 'w';
294
511
  const followingContent = toks.slice(i + 1).find(x => isContent(x));
295
512
  if (prev && !isContent(prev) && followingContent &&
296
513
  /^(JJ|NN)/.test(followingContent.lexicalClass))
@@ -298,6 +515,19 @@ function relativisePP(toks, words) {
298
515
  }
299
516
  return 'w';
300
517
  }
518
+ // A premodifying focus particle ("just/only/even" = exclusives) carries
519
+ // real-but-subordinate stress: 'n' — its associate holds the beat. The
520
+ // user-facing reading: "held but just(n) OURSELVES(s)".
521
+ if (isFocusParticleTok(w))
522
+ return 'n';
523
+ // Discourse-marker "now/then" before a wh-word: real-but-subordinate 'n'.
524
+ if (isDiscourseDeictic(w))
525
+ return 'n';
526
+ // Inferential connective ("So, …") and the deictic wedged inside an
527
+ // inversion ("may then each moment"): overt-weak — the beat belongs to
528
+ // the operator / the clause it introduces.
529
+ if (isDiscourseConnective(w) || isPostOperatorDeictic(w))
530
+ return 'w';
301
531
  // Function word → its category gradient; a POLYSYLLABIC one floors its peak at
302
532
  // 'w' so its stressed syllable outranks the reduced one (be·CAUSE, u·PON).
303
533
  let tier = functionLevel(w, words);
@@ -346,6 +576,79 @@ function relativisePP(toks, words) {
346
576
  break; // hit content before any prep
347
577
  }
348
578
  }
579
+ // Interrogative focus fronting (Krifka §4.6: focus introduces the phrase
580
+ // boundary and its accent). A φ-initial preposition heading a wh-containing
581
+ // PP is the left edge of a FRONTED FOCUS and carries a genuine light beat:
582
+ // "IN what distant deeps", "ON what wings". The licence is either an overt
583
+ // question mark closing the clause, or the pied-piped wh-PP standing at the
584
+ // very START of the utterance — wh-fronting is itself the interrogative /
585
+ // exclamative construction, and a verse line often carries the "?" on a later
586
+ // line. (A mid-sentence relative "…the house in which he lived" is φ-initial
587
+ // but not utterance-initial, so it stays reduced.) The interrogative
588
+ // DETERMINER just after the preposition defers to the noun it determines
589
+ // ("on WHAT wings" ↛ ; "ON what WINGS" ✓), while a free-standing wh
590
+ // ("WHAT the hand") keeps its own 'n'.
591
+ {
592
+ const lastIdx = toks[toks.length - 1].absoluteIndex;
593
+ const isQuestionClause = (() => {
594
+ const after = words
595
+ .filter(w => w.absoluteIndex > lastIdx)
596
+ .sort((a, b) => a.absoluteIndex - b.absoluteIndex);
597
+ for (const w of after) {
598
+ if (w.word === '?')
599
+ return true;
600
+ if (/^[.!;:]+$/.test(w.word) || w.word === '…')
601
+ return false;
602
+ }
603
+ return false;
604
+ })();
605
+ const firstAlpha = words
606
+ .filter(w => /[A-Za-z]/.test(w.word))
607
+ .sort((a, b) => a.absoluteIndex - b.absoluteIndex)[0];
608
+ const isUtteranceInitial = !!firstAlpha && toks[0] === firstAlpha;
609
+ const isWh = (x) => /^(WDT|WP|WP\$|WRB)$/.test(x.lexicalClass);
610
+ if ((isQuestionClause || isUtteranceInitial) && toks.length >= 2) {
611
+ const p = toks[0];
612
+ if ((p.lexicalClass === 'IN' || p.lexicalClass === 'TO') && p !== nuclear &&
613
+ (isWh(toks[1]) || (toks.length > 2 && isWh(toks[2])))) {
614
+ levels[0] = 'n';
615
+ // wh functioning as a determiner (a nominal follows it in the φ) defers
616
+ if (isWh(toks[1]) && toks.length > 2 && /^(JJ|NN)/.test(toks[2].lexicalClass)) {
617
+ levels[1] = 'w';
618
+ }
619
+ }
620
+ }
621
+ }
622
+ // Left-edge beat for a φ-INITIAL DISYLLABIC subordinator ("BeCAUSE I could
623
+ // not stop for Death", "BeCAUSE at least the past were passed away"). The
624
+ // subordinator opens its own clause-φ; its stressable syllable carries the
625
+ // light φ-initial beat ("beginnings free") exactly as a φ-initial preposition
626
+ // does — promotable by the fitter, never a forced ictus. The raise lands on
627
+ // the lexically stressed syllable; for a zero-contour entry ("because" 0·0)
628
+ // English subordinators of this shape are end-stressed (be·CAUSE, un·TIL,
629
+ // al·THOUGH), so the tie falls to the FINAL syllable. Monosyllabic marks
630
+ // ("if", "since") and mid-φ subordinators are untouched.
631
+ if (toks.length >= 2) {
632
+ const first = toks[0];
633
+ const rel0 = first.canonicalRel ?? '';
634
+ if ((rel0 === 'COMPMARK' || rel0 === 'ADVMARK') && first !== nuclear &&
635
+ first.syllables.length === 2 &&
636
+ (levels[0] === 'x' || levels[0] === 'w')) {
637
+ const [s1, s2] = first.syllables;
638
+ const l1 = s1.lexicalStress ?? s1.stress ?? 0;
639
+ const l2 = s2.lexicalStress ?? s2.stress ?? 0;
640
+ // Paint explicitly (the zero contour would misplace paintWord's peak).
641
+ if (l1 > l2) {
642
+ s1.relativeStress = 'n';
643
+ s2.relativeStress = 'x';
644
+ }
645
+ else {
646
+ s1.relativeStress = 'x';
647
+ s2.relativeStress = 'n';
648
+ }
649
+ levels[0] = '__painted__'; // sentinel: skip paintWord below
650
+ }
651
+ }
349
652
  // Givenness escape (Wagner §6.1.3, maintainer's no-flat-run-on directive): when a
350
653
  // φ's nuclear is an inherently-given PRONOUN immediately preceded by a transitive
351
654
  // PREPOSITION ("of HIM", "to THEE"), do NOT leave them on a par or stress the
@@ -362,7 +665,11 @@ function relativisePP(toks, words) {
362
665
  levels[pi] = 'n'; // preposition takes the differentiating beat
363
666
  }
364
667
  }
365
- toks.forEach((w, i) => paintWord(w, levels[i]));
668
+ toks.forEach((w, i) => {
669
+ if (levels[i] === '__painted__')
670
+ return; // subordinator raise painted directly
671
+ paintWord(w, levels[i]);
672
+ });
366
673
  // NOTE: the clash invariant (no adjacent equal n/m/s) is enforced once, globally,
367
674
  // by resolveStressClashes over TRUE adjacent syllables in computeRelativeStress —
368
675
  // not here. A per-word peak comparison would wrongly treat two polysyllables'
@@ -385,6 +692,11 @@ function isCompoundPair(a, b) {
385
692
  return true;
386
693
  if ((b.canonicalRel ?? '') === 'NOMD' && b.dependency?.governor === a)
387
694
  return true;
695
+ // A POSSESSIVE noun (nmod:poss → canonical CASE: "love's sway") is a phrasal
696
+ // modifier, NOT a compound member — the NSR right-stresses it (love's SWAY).
697
+ // Without this the N+N adjacency fallback fore-stressed possessives.
698
+ if ((a.canonicalRel ?? '') === 'CASE' || (b.canonicalRel ?? '') === 'CASE')
699
+ return false;
388
700
  return CLASH_NOUN.test(a.lexicalClass) && CLASH_NOUN.test(b.lexicalClass) &&
389
701
  Math.abs(a.absoluteIndex - b.absoluteIndex) === 1;
390
702
  }
@@ -422,10 +734,53 @@ function isCompoundPair(a, b) {
422
734
  */
423
735
  function resolveStressClashes(words, sylWord) {
424
736
  const syls = [];
737
+ // An OVERT punctuation boundary (comma, dash, IU stop) between two words is a
738
+ // genuine prosodic pause: beats on opposite sides keep BEAT status (never
739
+ // crushed below 'm' — the old cross-comma cascade stripped "blood" two
740
+ // tiers), but they still GRADE — two nuclear strengths cannot abut even
741
+ // across a pause, and a comma list thins its medial members to subordinate
742
+ // beats ("BLOOD, bone(m), MARrow…"). Quotes/brackets are
743
+ // phrasing-transparent and do not block.
744
+ const boundaryAfter = new Set(); // syllable index i: boundary between i and i+1
425
745
  for (const w of [...words].sort((a, b) => a.absoluteIndex - b.absoluteIndex)) {
746
+ if (w.syllables.length === 0) {
747
+ if (/^[,;:.!?…()—–-]+$/.test(w.word) && syls.length > 0)
748
+ boundaryAfter.add(syls.length - 1);
749
+ continue;
750
+ }
426
751
  for (const s of w.syllables)
427
752
  syls.push(s);
428
753
  }
754
+ // Alternation over a CLASH RUN (beat deletion at the weak/medial position):
755
+ // three or more consecutive beat-level syllables (≥ m) resolve by demoting
756
+ // the MEDIAL members and keeping both edges — "my young soul sick" →
757
+ // young(m) soul(n) sick(s), where the old pairwise-left cascade stripped the
758
+ // first two beats in sequence. A run may now CROSS overt boundaries: a
759
+ // medial that abuts a comma/dash stays a real (subordinate) beat — demoted
760
+ // only to 'm', so "As I am BLOOD, bone, MARrow…" grades s·m·s instead of
761
+ // holding three co-equal nuclei — while a boundary-free medial thins to 'n'
762
+ // as before. Pairs (exactly two) fall through to the pairwise logic below,
763
+ // which knows about compounds, phrase stress, and boundaries.
764
+ {
765
+ let i = 0;
766
+ while (i < syls.length) {
767
+ if (RANK[syls[i].relativeStress ?? 'w'] < RANK.m) {
768
+ i++;
769
+ continue;
770
+ }
771
+ let j = i;
772
+ while (j + 1 < syls.length &&
773
+ RANK[syls[j + 1].relativeStress ?? 'w'] >= RANK.m)
774
+ j++;
775
+ if (j - i >= 2) {
776
+ for (let k = i + 1; k < j; k++) {
777
+ const bounded = boundaryAfter.has(k - 1) || boundaryAfter.has(k);
778
+ syls[k].relativeStress = bounded ? 'm' : 'n';
779
+ }
780
+ }
781
+ i = j + 1;
782
+ }
783
+ }
429
784
  // Lexical stress value of a syllable (2=primary, 1=secondary, 0=unstressed).
430
785
  // Used to break n-n ties: a syllable with HIGHER lexical stress has greater
431
786
  // phonological integrity (Hayes' stress-maximum principle) and is protected
@@ -442,6 +797,24 @@ function resolveStressClashes(words, sylWord) {
442
797
  const a = syls[i], b = syls[i + 1];
443
798
  const ra = RANK[a.relativeStress ?? 'w'];
444
799
  const rb = RANK[b.relativeStress ?? 'w'];
800
+ if (boundaryAfter.has(i)) {
801
+ // Across a comma/dash the pause separates the beats, so subordinate
802
+ // combinations (m·s, s·m, m·m) stand — but two NUCLEAR strengths still
803
+ // cannot abut even across the pause: the less prominent (higher
804
+ // phrase-stress) of an s·s pair grades to 'm' (still a beat), a tie
805
+ // resolving rightward toward the nuclear (Newman).
806
+ if (ra === RANK.s && rb === RANK.s) {
807
+ const wa = sylWord.get(a), wb = sylWord.get(b);
808
+ const pa = wa?.phraseStress || Infinity;
809
+ const pb = wb?.phraseStress || Infinity;
810
+ if (pa < pb)
811
+ b.relativeStress = 'm';
812
+ else
813
+ a.relativeStress = 'm';
814
+ changed = true;
815
+ }
816
+ continue;
817
+ }
445
818
  if (ra >= RANK.m && rb >= RANK.m) {
446
819
  const wa = sylWord.get(a), wb = sylWord.get(b);
447
820
  let demoteA;
@@ -550,6 +923,14 @@ function promoteWeakTroughs(words) {
550
923
  }
551
924
  }
552
925
  const isW = (i) => i >= 0 && i < syls.length && syls[i].relativeStress === 'w';
926
+ // A flank counts as weak when it is 'w' OR 'x' — a clitic flank is weaker
927
+ // still, and refusing it silenced real troughs ("And(x) I(w) had(w) put" —
928
+ // "I" is the alternation's natural light beat; "Be(x)cause(w) I(w)…" — the
929
+ // subordinator's stressed syllable likewise). The CANDIDATE must still be an
930
+ // overt 'w' (a clitic itself never promotes), and the polysyllable-toneless
931
+ // exclusion below still applies.
932
+ const isWeakFlank = (i) => i >= 0 && i < syls.length &&
933
+ (syls[i].relativeStress === 'w' || syls[i].relativeStress === 'x');
553
934
  const isLexicallyToneless = (i) => {
554
935
  const s = syls[i];
555
936
  if ((s.lexicalStress ?? s.stress) !== 0)
@@ -559,12 +940,27 @@ function promoteWeakTroughs(words) {
559
940
  };
560
941
  let lastProm = -2;
561
942
  for (let i = 1; i < syls.length - 1; i++) {
562
- if (!isW(i) || !isW(i - 1) || !isW(i + 1))
563
- continue; // medial of a ≥3 literal-w run
943
+ if (!isW(i) || !isWeakFlank(i - 1) || !isWeakFlank(i + 1))
944
+ continue; // 'w' amid weakness
564
945
  if (isLexicallyToneless(i))
565
946
  continue; // no fabricated secondaries
566
947
  if (i - 1 === lastProm)
567
948
  continue; // keep promotions non-adjacent
949
+ // Argument over functor at the alternation level (Wagner §6.2.2): when a
950
+ // REDUCED VERB (auxiliary / semi-modal — a functor) and a PRONOUN (an
951
+ // argument) are both eligible neighbours in the same trough, the
952
+ // left-greedy scan must not hand the beat to the functor — "do I have to
953
+ // bless" wants do(w) I(n) have(w), not do(n) I(w) have(n). The reduced
954
+ // verb yields to an immediately-following eligible pronoun.
955
+ if (i + 2 < syls.length) {
956
+ const wHere = sylWord.get(syls[i]);
957
+ const wNext = sylWord.get(syls[i + 1]);
958
+ if (wHere && wNext && wHere !== wNext &&
959
+ isReducedVerb(wHere, words) && isPronoun(wNext) &&
960
+ isW(i + 1) && !isLexicallyToneless(i + 1) && isWeakFlank(i + 2)) {
961
+ continue; // the pronoun promotes instead
962
+ }
963
+ }
568
964
  syls[i].relativeStress = 'n';
569
965
  lastProm = i;
570
966
  }
@@ -12,6 +12,39 @@ export declare function isInherentlyGiven(w: ClsWord): boolean;
12
12
  * whose post-nominal PP modifier cannot be hosted inside the head's own group and
13
13
  * so needs its OWN ϕ (the phrasing break the ear hears after "Something …"). */
14
14
  export declare function isLightNominalHead(w: ClsWord): boolean;
15
+ export declare function focusAssociateOf(w: ClsWord, words: ClsWord[]): ClsWord | null;
16
+ export declare function isMarginalModalUse(w: ClsWord, words: ClsWord[]): boolean;
17
+ /** A CAPITALIZED personal/possessive pronoun in mid-sentence ("but just
18
+ * Ourselves", "For His Civility" — Dickinson's reverential capitals).
19
+ * English capitalizes pronouns only sentence-initially and in "I", so a
20
+ * capital here is the poet's deliberate typographic FOCUS mark: the pronoun
21
+ * is referential/contrastive — Wagner's "functor role" ceded to a full
22
+ * referring use, Krifka (78): an accented pronoun signals narrow focus. */
23
+ export declare function isCapitalizedFocalPronoun(w: ClsWord, words: ClsWord[]): boolean;
24
+ /** A POSTPOSED preposition: orphaned from its complement, which stands
25
+ * immediately to its LEFT as a wh-word — the inverted pied-piping of "what
26
+ * FOR is this form" (= "for what"), and the bare "Who WITH?", "Where FROM?".
27
+ * Such a preposition cannot procliticize (no rightward complement to lean on)
28
+ * and takes its STRONG form (Selkirk 1996: function words are weak only where
29
+ * they can cliticize onto a following host) — the same phonology as clause-
30
+ * final stranding, caught here in the wh-adjacent inversion the transitivity
31
+ * test misses. Subordinators never match ("what if…" is a mark, not case),
32
+ * and neither does a preposition with a genuine rightward complement
33
+ * ("who for the WORLD would…", "when in ROME…" stay reduced). */
34
+ export declare function isPostposedPreposition(w: ClsWord, words: ClsWord[]): boolean;
35
+ /** Subject–auxiliary INVERSION without interrogative force: the aux/modal
36
+ * precedes its clause's subject in a sentence that is NOT a question —
37
+ * optative "So, may then each moment drip off…" / "May she rest", negative
38
+ * inversion "Never did I see", conditional inversion "Had I known". The
39
+ * inverted auxiliary is the overt exponent of the operator that triggered the
40
+ * inversion (wish / negation / condition — Krifka: illocutionary and polarity
41
+ * operators are accentable focus exponents), so it is NOT the reduced given-
42
+ * auxiliary of a plain declarative: it anchors a light beat ("HAD I but
43
+ * known", "never DID I see"). In a QUESTION the inversion is discharged by
44
+ * the interrogative operator instead (wh-word or final "?") and the aux stays
45
+ * reduced ("won't you GUIDE me…?", "do I have to BLESS?"). */
46
+ export declare function isInvertedOperatorAux(w: ClsWord, words: ClsWord[]): boolean;
47
+ export declare function isSemiModalHaveTo(w: ClsWord, words: ClsWord[]): boolean;
15
48
  export declare function verbHasArgChild(verb: ClsWord, children: Map<number, ClsWord[]>): boolean;
16
49
  export declare function isUnaccusativeOrPassive(subj: ClsWord, verb: ClsWord): boolean;
17
50
  /** The preposition lemma governing an oblique noun: its `case`/`CASE` dependent. */
@@ -1 +1 @@
1
- {"version":3,"file":"syntax.d.ts","sourceRoot":"","sources":["../../src/calliope/syntax.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAQtC,wBAAgB,OAAO,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAE3C;AACD,wBAAgB,MAAM,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAsC;AACjF,wBAAgB,MAAM,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAsC;AAGjF;;;gDAGgD;AAChD,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAG7C;AAaD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAIrD;AAED;;iFAEiF;AACjF,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAEtD;AAMD,wBAAgB,eAAe,CAC7B,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,GAC9C,OAAO,CAQT;AASD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAI7E;AAmBD,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,GAAG,IAAI,CAQzE;AAED;2EAC2E;AAC3E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAKzE;AAED;iFACiF;AACjF,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAYrF;AAED;+EAC+E;AAC/E,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAStE;AAkBD;;qEAEqE;AACrE,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAqB9E;AAGD,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AAEnD;;;gFAGgF;AAChF,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,WAAW,CAcnD"}
1
+ {"version":3,"file":"syntax.d.ts","sourceRoot":"","sources":["../../src/calliope/syntax.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAQtC,wBAAgB,OAAO,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAE3C;AACD,wBAAgB,MAAM,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAsC;AACjF,wBAAgB,MAAM,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAsC;AAGjF;;;gDAGgD;AAChD,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAG7C;AAaD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAIrD;AAED;;iFAEiF;AACjF,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAEtD;AAWD,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,GAAG,IAAI,CAW7E;AAUD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAkBxE;AAED;;;;;4EAK4E;AAC5E,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAuB/E;AAED;;;;;;;;;kEASkE;AAClE,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAoB5E;AAED;;;;;;;;;;+DAU+D;AAC/D,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAiB3E;AAeD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAkBvE;AAMD,wBAAgB,eAAe,CAC7B,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,GAC9C,OAAO,CAQT;AASD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAI7E;AAmBD,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,GAAG,IAAI,CAQzE;AAED;2EAC2E;AAC3E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAKzE;AAED;iFACiF;AACjF,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAYrF;AAED;+EAC+E;AAC/E,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAStE;AAkBD;;qEAEqE;AACrE,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAqB9E;AAGD,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AAEnD;;;gFAGgF;AAChF,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,WAAW,CAcnD"}