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.
- package/README.md +32 -20
- package/dist/caesura.d.ts +33 -0
- package/dist/caesura.d.ts.map +1 -0
- package/dist/caesura.js +202 -0
- package/dist/calliope/boundaries.d.ts +19 -0
- package/dist/calliope/boundaries.d.ts.map +1 -0
- package/dist/calliope/boundaries.js +182 -0
- package/dist/calliope/bracketing.d.ts +11 -0
- package/dist/calliope/bracketing.d.ts.map +1 -0
- package/dist/calliope/bracketing.js +416 -0
- package/dist/calliope/deps.d.ts +4 -0
- package/dist/calliope/deps.d.ts.map +1 -0
- package/dist/calliope/deps.js +181 -0
- package/dist/calliope/engine.d.ts +3 -0
- package/dist/calliope/engine.d.ts.map +1 -0
- package/dist/calliope/engine.js +71 -0
- package/dist/calliope/feats.d.ts +9 -0
- package/dist/calliope/feats.d.ts.map +1 -0
- package/dist/calliope/feats.js +45 -0
- package/dist/calliope/names.d.ts +7 -0
- package/dist/calliope/names.d.ts.map +1 -0
- package/dist/calliope/names.js +42 -0
- package/dist/calliope/postag.d.ts +8 -0
- package/dist/calliope/postag.d.ts.map +1 -0
- package/dist/calliope/postag.js +250 -0
- package/dist/calliope/prosodic.d.ts +3 -0
- package/dist/calliope/prosodic.d.ts.map +1 -0
- package/dist/calliope/prosodic.js +275 -0
- package/dist/calliope/relstress.d.ts +4 -0
- package/dist/calliope/relstress.d.ts.map +1 -0
- package/dist/calliope/relstress.js +688 -0
- package/dist/calliope/stressrules.d.ts +28 -0
- package/dist/calliope/stressrules.d.ts.map +1 -0
- package/dist/calliope/stressrules.js +147 -0
- package/dist/calliope/syntax.d.ts +38 -0
- package/dist/calliope/syntax.d.ts.map +1 -0
- package/dist/calliope/syntax.js +234 -0
- package/dist/calliope/udpos.d.ts +13 -0
- package/dist/calliope/udpos.d.ts.map +1 -0
- package/dist/calliope/udpos.js +156 -0
- package/dist/clio/caesura.d.ts +27 -0
- package/dist/clio/caesura.d.ts.map +1 -0
- package/dist/clio/caesura.js +148 -0
- package/dist/clio/depfix.d.ts +13 -0
- package/dist/clio/depfix.d.ts.map +1 -0
- package/dist/clio/depfix.js +84 -0
- package/dist/clio/display.d.ts +32 -0
- package/dist/clio/display.d.ts.map +1 -0
- package/dist/clio/display.js +976 -0
- package/dist/clio/engine.d.ts +3 -0
- package/dist/clio/engine.d.ts.map +1 -0
- package/dist/clio/engine.js +28 -0
- package/dist/clio/parser.d.ts +10 -0
- package/dist/clio/parser.d.ts.map +1 -0
- package/dist/clio/parser.js +696 -0
- package/dist/clio/phonological.d.ts +41 -0
- package/dist/clio/phonological.d.ts.map +1 -0
- package/dist/clio/phonological.js +788 -0
- package/dist/clio/phrasestress.d.ts +6 -0
- package/dist/clio/phrasestress.d.ts.map +1 -0
- package/dist/clio/phrasestress.js +106 -0
- package/dist/clio/pipeline.d.ts +11 -0
- package/dist/clio/pipeline.d.ts.map +1 -0
- package/dist/clio/pipeline.js +147 -0
- package/dist/clio/rhyme.d.ts +65 -0
- package/dist/clio/rhyme.d.ts.map +1 -0
- package/dist/clio/rhyme.js +761 -0
- package/dist/clio/scandroid.d.ts +17 -0
- package/dist/clio/scandroid.d.ts.map +1 -0
- package/dist/clio/scandroid.js +435 -0
- package/dist/clio/scansion.d.ts +46 -0
- package/dist/clio/scansion.d.ts.map +1 -0
- package/dist/clio/scansion.js +1086 -0
- package/dist/clio/semantics.d.ts +44 -0
- package/dist/clio/semantics.d.ts.map +1 -0
- package/dist/clio/semantics.js +139 -0
- package/dist/clio/stress.d.ts +83 -0
- package/dist/clio/stress.d.ts.map +1 -0
- package/dist/clio/stress.js +1737 -0
- package/dist/clio/tagfix.d.ts +6 -0
- package/dist/clio/tagfix.d.ts.map +1 -0
- package/dist/clio/tagfix.js +101 -0
- package/dist/display.d.ts +0 -6
- package/dist/display.d.ts.map +1 -1
- package/dist/display.js +322 -141
- package/dist/engine.d.ts +9 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +12 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +169 -26
- package/dist/parser.d.ts +3 -3
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +320 -563
- package/dist/phonological.d.ts +1 -1
- package/dist/phonological.d.ts.map +1 -1
- package/dist/phonological.js +73 -4
- package/dist/phrasestress.d.ts +6 -0
- package/dist/phrasestress.d.ts.map +1 -0
- package/dist/phrasestress.js +106 -0
- package/dist/rhyme.d.ts +40 -1
- package/dist/rhyme.d.ts.map +1 -1
- package/dist/rhyme.js +435 -7
- package/dist/scansion.d.ts +9 -0
- package/dist/scansion.d.ts.map +1 -1
- package/dist/scansion.js +145 -18
- package/dist/semantics.d.ts +44 -0
- package/dist/semantics.d.ts.map +1 -0
- package/dist/semantics.js +139 -0
- package/dist/stress.d.ts +127 -4
- package/dist/stress.d.ts.map +1 -1
- package/dist/stress.js +834 -61
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -3
- package/src/caesura.ts +201 -0
- package/src/calliope/boundaries.ts +190 -0
- package/src/calliope/bracketing.ts +390 -0
- package/src/calliope/deps.ts +160 -0
- package/src/calliope/engine.ts +77 -0
- package/src/calliope/feats.ts +46 -0
- package/src/calliope/names.ts +44 -0
- package/src/calliope/postag.ts +253 -0
- package/src/calliope/prosodic.ts +262 -0
- package/src/calliope/relstress.ts +645 -0
- package/src/calliope/stressrules.ts +147 -0
- package/src/calliope/syntax.ts +218 -0
- package/src/calliope/udpos.ts +152 -0
- package/src/calliope_src_contents.md +19049 -0
- package/src/clio/caesura.ts +145 -0
- package/src/clio/depfix.ts +88 -0
- package/src/clio/display.ts +1042 -0
- package/src/clio/engine.ts +38 -0
- package/src/clio/parser.ts +845 -0
- package/src/clio/phonological.ts +849 -0
- package/src/clio/phrasestress.ts +108 -0
- package/src/clio/pipeline.ts +154 -0
- package/src/clio/rhyme.ts +740 -0
- package/src/clio/scandroid.ts +434 -0
- package/src/clio/scansion.ts +1130 -0
- package/src/clio/semantics.ts +134 -0
- package/src/clio/stress.ts +1731 -0
- package/src/clio/tagfix.ts +104 -0
- package/src/display.ts +321 -137
- package/src/engine.ts +22 -0
- package/src/index.ts +176 -32
- package/src/parser.ts +346 -682
- package/src/phonological.ts +72 -4
- package/src/phrasestress.ts +108 -0
- package/src/rhyme.ts +428 -8
- package/src/scansion.ts +136 -15
- package/src/semantics.ts +134 -0
- package/src/stress.ts +794 -64
- package/src/types.ts +39 -4
- package/tests/DataForHayesLinesOnly.txt +364 -0
- package/tests/DataForHayesStressSymbolsRevised.txt +728 -0
- package/tests/basic.test.ts +443 -37
- package/tests/bench-hayes.mjs +72 -0
- package/tests/wagner-stress.test.ts +188 -0
- package/vitest.config.ts +0 -15
package/README.md
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
|
-
# Calliope TS
|
|
1
|
+
# Calliope TS 0.0.4
|
|
2
2
|
|
|
3
|
-
**Calliope TS** is a phonological poetry
|
|
3
|
+
**Calliope TS** is a phonological poetry-scansion toolkit for TypeScript / Node.js. Hand it a poem (or a single line), and it will tell you — and *show* you — how that verse actually moves: which syllables carry weight and how much, where the beats fall, what meter each line is in and how confidently, what rhymes with what, and what form the stanzas add up to.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
Hand to this muse a song or verse,
|
|
7
|
-
And she might show you how it curves,
|
|
8
|
-
Which syllables hold weight in time,
|
|
9
|
-
How much of it, and where a rhyme,
|
|
10
|
-
Which meter dances through each line,
|
|
11
|
-
And where should land each beat
|
|
12
|
-
Your voice might drawl, or music drawn...
|
|
13
|
-
She even knows poetic form!
|
|
14
|
-
|
|
15
|
-
The premise of this toolkit is that rhythm, and therefore also meter, is much more reliably *recovered from pronunciation* than from text as such. What?! But there's no Text-to-Speech here?! (Sorry!) A lapse? A paradox? Nah. Instead, Calliope parses a stanza or verse, reproducing roughly how an attentive (but silent) reader might do so: first, working out how the words are likely to be said aloud — deduced by following contextual patterns, cohering a syntax, wielding phonotactics, evading constraints, gathering prosody, accounting for the ways phrases clump or bounce, for how a voice might subtly perk over clause-ends, and all the rest of it; and only thereafter scanning for what sort of rhythms a phrase such as that might be spectrally reaching for, or settling into. The scholars dwelling on Generative Metrics, a liminal little field somewhere in-between linguistics, phonetics, and literary studies, call this sort of approach **phonological scansion**, and the bulk of this README is about what that means and how the toolkit puts it to work.
|
|
5
|
+
The premise is that meter is something you *recover from pronunciation*, not something you stamp onto spelling. So Calliope reads a line roughly the way an attentive reader does: it first works out how the words would be said aloud — dictionary stress, the way phrases clump, where the voice peaks at a clause-end — and only then looks for the regular beat that saying settles into. This approach has a name in the linguistics literature, **phonological scansion**, and the bulk of this README is about what that means and how the toolkit puts it to work.
|
|
16
6
|
|
|
17
7
|
```
|
|
18
8
|
$ calliope-ts --reading exile.txt
|
|
@@ -89,11 +79,15 @@ cat poem.txt | calliope-ts --reading
|
|
|
89
79
|
|
|
90
80
|
# 5. Interactive menu (run without typing arguments in a terminal each time)
|
|
91
81
|
calliope-ts
|
|
82
|
+
|
|
83
|
+
# 6. Run the alternative "Clio" parse (powered by the legacy FinNLP suite)
|
|
84
|
+
calliope-ts --clio poem.txt
|
|
92
85
|
```
|
|
93
86
|
|
|
94
87
|
The interactive menu offers: multi-line paste-and-scan (reading view),
|
|
95
88
|
single-line detailed analysis, line-by-line analysis, file input in either
|
|
96
|
-
view,
|
|
89
|
+
view, a **legend** explaining every symbol and colour, and an option to
|
|
90
|
+
"Ask Clio instead" for the alternative parse.
|
|
97
91
|
|
|
98
92
|
There are two display modes:
|
|
99
93
|
|
|
@@ -178,7 +172,7 @@ Bruce Hayes himself (2005) and Gareth McAleese (2007/2008) built the first faith
|
|
|
178
172
|
The current pipeline has eight stages.
|
|
179
173
|
|
|
180
174
|
**1. Grammatical parsing.**
|
|
181
|
-
The line is tokenized, part-of-speech tagged, and dependency-parsed — that is, the engine works out which word grammatically governs which (subject of what verb, object of what preposition) — using the FinNLP family of libraries (`lexed`, `en-pos`, `en-parse`). Two correction layers sit inside this stage,
|
|
175
|
+
The line is tokenized, part-of-speech tagged, and dependency-parsed — that is, the engine works out which word grammatically governs which (subject of what verb, object of what preposition) — using `udpipe-node` (our Node/JS/WASM port of UDPipe), now generating Universal Dependencies (UD) trees and morphological features. (For legacy comparison, the toolkit's built-in "Clio" alternate mechanics persist in using the FinNLP family of libraries (`lexed`, `en-pos`, `en-parse`) instead). A conversion layer seamlessly translates UD tags into the Penn Treebank tags our prosody expects. Two correction layers sit inside this stage,
|
|
182
176
|
First, because poetry tends to break part-of-speech and grammatical dependency taggers in predictable ways, a *tag-repair* pass fixes systematic errors before the dependency tree is built (this appropriately accounts for rare exotics and awkward/shifty commonplaces alike: from archaic forms like *thou/thy/doth/wherefore*, to the pronoun *I*, to perfect-tense participles like *had quit*).
|
|
183
177
|
Then we leverage a *tree-repair* pass (using the [depedits](https://www.npmjs.com/package/depedits) rule engine, our TypeScript port of the DepEdit library, originally in Python), which fixes systematic phrasal role attachment errors (e.g. noun compounds parsed as double objects, and the like). Hyphenated compounds and contractions (like *we'll*, *don't*, archaic *fix'd*, etc) are re-merged into single metrical words.
|
|
184
178
|
|
|
@@ -219,6 +213,10 @@ import {
|
|
|
219
213
|
analyzeStanzas, // poem text → LineResult[][] (per stanza, per line)
|
|
220
214
|
analyzeText, // poem text → LineResult[] (flat)
|
|
221
215
|
analyzeReadingDocument, // poem text → ReadingStanza[] (keeps verbatim lines)
|
|
216
|
+
// The legacy/alternate "Clio" engine equivalents (using FinNLP):
|
|
217
|
+
analyzeStanzasClio,
|
|
218
|
+
analyzeTextClio,
|
|
219
|
+
analyzeReadingDocumentClio,
|
|
222
220
|
} from 'calliope-ts';
|
|
223
221
|
```
|
|
224
222
|
|
|
@@ -362,7 +360,7 @@ Calliope TS is developed by **Aleksey Calvin** / [SilverAgePoets.com](https://ww
|
|
|
362
360
|
---
|
|
363
361
|
|
|
364
362
|
**Limitations to know about.**
|
|
365
|
-
- English only (the dictionary and
|
|
363
|
+
- English only (the dictionary and phonological rules are English; while the UDPipe backend natively supports multilingual parsing, other languages will produce nonsense rather than errors).
|
|
366
364
|
- Stress-doublet words (*rebel*, *content*, names like *Hugo*) are read with their dictionary stress; a correction is on the roadmap.
|
|
367
365
|
- Rare or foreign proper names fall back to rule-based stress guesses.
|
|
368
366
|
|
|
@@ -384,8 +382,22 @@ node trials/corpus_benchmark.mjs # litlab / prosodic / epg64 meter corpora
|
|
|
384
382
|
|
|
385
383
|
## License and credits
|
|
386
384
|
|
|
387
|
-
Apache-2.0. © Aleksey Calvin Tsukanov / SilverAgePoets.com.
|
|
388
|
-
My email: alekseycalvin@gmail.com
|
|
389
|
-
|
|
390
|
-
Methodological and conceptual debts:
|
|
385
|
+
Apache-2.0. © Aleksey Calvin Tsukanov / SilverAgePoets.com. <br>
|
|
386
|
+
My email: alekseycalvin@gmail.com <br>
|
|
387
|
+
|
|
388
|
+
Methodological and conceptual debts:
|
|
389
|
+
- Michael Wagner (Prosody and Recursion, MIT, 2005) (for further clarifying the inter-relational nuances of prosody and syntax).
|
|
390
|
+
- Manfred Krifka (2001/2002) (for so poignantly elucidating NSR and CSR beyond SPE).
|
|
391
|
+
- Bruce Hayes (1982/1984/1995/1996 with Abigail Kaun/2005) (the phonological scansion procedure as such, extrametricality insights, text-setting methodologies, MaxEnt OT, and who knows what else);
|
|
392
|
+
- Gareth McAleese (for a single 2008 paper, for detailing the original Calliope implementation, for exhibiting a remarkable field-spanning purview, an uncanny industriousness, and an uncommon – perhaps a tad obsessive – dedication to testing, refining, fusing, and extending all sorts of methodologies in a single-minded pursuit of bringing constraint-based computational scansion far beyond the best documented practices and results at that time; and for so obviously succeeding, if only to seemingly vanish from the field as abruptly and unreservedly as he entered and absorbed it*);
|
|
393
|
+
- Charles O. Hartman (Scandroid);
|
|
394
|
+
- Claire Moore Cantwell (morphological/phonological tagging algorithms),
|
|
395
|
+
- Austin Pursley (implementing finer-grained rhyme-matching heuristics over a corpus),
|
|
396
|
+
- Allison Parrish (Pronouncing-py and being a real life computational poet hero),
|
|
397
|
+
- Derek Attridge (beat/offbeat rhythm theory and insightful writings on the English dolnik);
|
|
398
|
+
- M. L. Gasparov (dolnik/taktovik taxonomy);
|
|
399
|
+
- the compilers of the CMU Pronouncing Dictionary;
|
|
400
|
+
- the makers of Prosodic (Heuser et al, for establishing an admirable state-of-the-art to compare against, differentiate from, and hopefully surpass in due time, in select ways),
|
|
401
|
+
- Milan Straka and the UDPipe project (for the robust neural parsing architecture now driving the core mechanics),
|
|
402
|
+
- as well as broader generative-metrics, constraint-based metrics, and OT traditions, including Kiparsky, Prince & Smolensky, Groves, Blumenfeld, Lilja, Chomsky & Halle, Fabb & Halle (rule-based grid scansion theory), Einarsson (Metremic theory), Russom (Universalist metrics), K. M. Ryan (gradient syllable weight), big daddy Jakobson who had once roped the whole world with subtle strings and often hung out with Mayakovsky, and many others. <br>
|
|
391
403
|
*Gareth McAleese: If you're reading this, please do email me!
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ClsWord, IntonationalUnit } from './types.js';
|
|
2
|
+
export type CaesuraKind = 'hard' | 'soft';
|
|
3
|
+
/** A caesura with its graded boundary strength (0..1, NSBR-scaled), so the display
|
|
4
|
+
* can colour the mark by how strong the underlying prosodic break is. */
|
|
5
|
+
export interface CaesuraInfo {
|
|
6
|
+
kind: CaesuraKind;
|
|
7
|
+
strength: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Caesura positions for a line, keyed by the syllable index AFTER which the pause
|
|
11
|
+
* falls (= number of syllables to its left).
|
|
12
|
+
* • 'hard' — an overt break at an Intonational-Unit boundary.
|
|
13
|
+
* • 'soft' — a single INFERRED medial caesura for a punctuation-free line.
|
|
14
|
+
* Candidates are the boundaries just before a phrase/clause onset
|
|
15
|
+
* (PHRASE_ONSET_POS); the one nearest the line's midpoint that lies in the
|
|
16
|
+
* central third AND coincides with a foot boundary wins — so it is medial,
|
|
17
|
+
* never mid-foot, and consistent across structurally-parallel lines. Read in
|
|
18
|
+
* LINEAR order (robust to clitic-group reordering); needs a line of ≥ 8
|
|
19
|
+
* syllables.
|
|
20
|
+
*/
|
|
21
|
+
export declare function computeCaesurae(words: ClsWord[], ius: IntonationalUnit[], scansion?: string): Map<number, CaesuraInfo>;
|
|
22
|
+
/**
|
|
23
|
+
* The words that immediately PRECEDE a caesura in a line — i.e. each word whose
|
|
24
|
+
* cumulative syllable count lands exactly on a caesura position. Used by the
|
|
25
|
+
* rhyme layer for pre-caesural internal-rhyme detection. Returned in linear
|
|
26
|
+
* (reading) order; the caesura kind is paired so callers can weight hard vs
|
|
27
|
+
* inferred breaks if they wish.
|
|
28
|
+
*/
|
|
29
|
+
export declare function preCaesuralWords(words: ClsWord[], ius: IntonationalUnit[], scansion?: string): {
|
|
30
|
+
word: ClsWord;
|
|
31
|
+
kind: CaesuraKind;
|
|
32
|
+
}[];
|
|
33
|
+
//# sourceMappingURL=caesura.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caesura.d.ts","sourceRoot":"","sources":["../src/caesura.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAIvD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,CAAC;AAE1C;0EAC0E;AAC1E,MAAM,WAAW,WAAW;IAAG,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;CAAE;AAyCrE;;;;;;;;;;;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,CAuGtH;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"}
|
package/dist/caesura.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
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
|
+
import { computeBoundaries } from './calliope/boundaries.js';
|
|
15
|
+
/** Foot-boundary syllable indices from a scansion string (cumulative syllable
|
|
16
|
+
* count after each foot; silent beats '-' are not syllables). */
|
|
17
|
+
function footBoundarySet(scansion) {
|
|
18
|
+
const set = new Set();
|
|
19
|
+
let c = 0;
|
|
20
|
+
for (const foot of scansion.split('|')) {
|
|
21
|
+
for (const ch of foot)
|
|
22
|
+
if ('xwnms'.includes(ch))
|
|
23
|
+
c++;
|
|
24
|
+
set.add(c);
|
|
25
|
+
}
|
|
26
|
+
return set;
|
|
27
|
+
}
|
|
28
|
+
// A new phonological/syntactic phrase opens at these POS tags: prepositions &
|
|
29
|
+
// subordinators (IN), infinitival "to" (TO), coordinators (CC), wh/relativizers,
|
|
30
|
+
// verb-particles (RP), and the predicate's verb/modal. The major medial caesura
|
|
31
|
+
// of a line falls immediately BEFORE such a word — far more reliably read off the
|
|
32
|
+
// (robust) POS tags than off FinNLP's (noisy) phonological-phrase grouping, which
|
|
33
|
+
// mis-bracketed e.g. "The epic | feast…". Determiners/articles are excluded (they
|
|
34
|
+
// continue a phrase a preposition already opened: "as | one empty bag").
|
|
35
|
+
const PHRASE_ONSET_POS = new Set([
|
|
36
|
+
'IN', 'TO', 'CC', 'WDT', 'WP', 'WP$', 'WRB', 'RP',
|
|
37
|
+
'VB', 'VBD', 'VBG', 'VBN', 'VBP', 'VBZ', 'MD',
|
|
38
|
+
]);
|
|
39
|
+
// Directional/spatial adverbs that open a phrase ("snowed-in OUT of many routes",
|
|
40
|
+
// "drifting DOWN to sleep"). FinNLP often tags these RB rather than RP/IN, so the
|
|
41
|
+
// POS set alone misses them; a small curated lemma list recovers them with low
|
|
42
|
+
// over-fire risk (a generic RB like "very"/"quickly" is NOT a phrase onset).
|
|
43
|
+
const DIRECTIONAL_ONSET = new Set([
|
|
44
|
+
'out', 'in', 'up', 'down', 'off', 'away', 'back', 'forth', 'over',
|
|
45
|
+
'around', 'along', 'through', 'apart', 'aside', 'onward', 'onwards',
|
|
46
|
+
]);
|
|
47
|
+
/** True if a word opens a new phonological/syntactic phrase (a caesura candidate). */
|
|
48
|
+
function isPhraseOnset(w) {
|
|
49
|
+
if (PHRASE_ONSET_POS.has(w.lexicalClass))
|
|
50
|
+
return true;
|
|
51
|
+
return w.lexicalClass === 'RB' && DIRECTIONAL_ONSET.has(w.word.toLowerCase());
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Caesura positions for a line, keyed by the syllable index AFTER which the pause
|
|
55
|
+
* falls (= number of syllables to its left).
|
|
56
|
+
* • 'hard' — an overt break at an Intonational-Unit boundary.
|
|
57
|
+
* • 'soft' — a single INFERRED medial caesura for a punctuation-free line.
|
|
58
|
+
* Candidates are the boundaries just before a phrase/clause onset
|
|
59
|
+
* (PHRASE_ONSET_POS); the one nearest the line's midpoint that lies in the
|
|
60
|
+
* central third AND coincides with a foot boundary wins — so it is medial,
|
|
61
|
+
* never mid-foot, and consistent across structurally-parallel lines. Read in
|
|
62
|
+
* LINEAR order (robust to clitic-group reordering); needs a line of ≥ 8
|
|
63
|
+
* syllables.
|
|
64
|
+
*/
|
|
65
|
+
export function computeCaesurae(words, ius, scansion) {
|
|
66
|
+
const caes = new Map();
|
|
67
|
+
// Graded boundary strength (Wagner Ch.4–5) keyed by syllable index, so each
|
|
68
|
+
// caesura carries the strength of its underlying ϕ/ι break — used both to COLOUR
|
|
69
|
+
// the mark and to gate strong-ϕ hard caesurae on a line-relative threshold.
|
|
70
|
+
const bounds = computeBoundaries(words, ius);
|
|
71
|
+
const strengthAt = new Map();
|
|
72
|
+
for (const b of bounds.phi) {
|
|
73
|
+
const prev = strengthAt.get(b.syllableIndex) ?? 0;
|
|
74
|
+
if (b.strength > prev)
|
|
75
|
+
strengthAt.set(b.syllableIndex, b.strength);
|
|
76
|
+
}
|
|
77
|
+
const strOf = (syl) => strengthAt.get(syl) ?? 0;
|
|
78
|
+
const setHard = (syl) => caes.set(syl, { kind: 'hard', strength: Math.max(0.6, strOf(syl)) });
|
|
79
|
+
// Caesurae must sit on FOOT BOUNDARIES (when the scansion is known). The line's
|
|
80
|
+
// metrical feet ARE the scansion's own segmentation, so a break that falls mid-
|
|
81
|
+
// foot has no metrical reality — it merely fragments a foot (often into a lone
|
|
82
|
+
// monosyllable: "But ‖ Oh ‖ ye…") and it made the reading view disagree with the
|
|
83
|
+
// detailed "Feet:" view, which already marks foot-edge breaks only. Punctuation
|
|
84
|
+
// is a caesura CANDIDATE, never an override: an IU boundary that does not land on
|
|
85
|
+
// a foot edge is dropped (the soft-caesura fallback below then offers one medial,
|
|
86
|
+
// foot-aligned break if the line is long enough).
|
|
87
|
+
const footEdges = scansion ? footBoundarySet(scansion) : null;
|
|
88
|
+
const iuOf = new Map();
|
|
89
|
+
for (let i = 0; i < ius.length; i++) {
|
|
90
|
+
for (const pp of ius[i].phonologicalPhrases) {
|
|
91
|
+
for (const cg of pp.cliticGroups)
|
|
92
|
+
for (const tok of cg.tokens)
|
|
93
|
+
iuOf.set(tok, i);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
let cum = 0;
|
|
97
|
+
let prevIu;
|
|
98
|
+
let prevWasContentful = false;
|
|
99
|
+
const onsetPositions = [];
|
|
100
|
+
for (const w of words) {
|
|
101
|
+
if (isPunctuation(w.lexicalClass) || w.syllables.length === 0) {
|
|
102
|
+
// A COMMA is an overt medial pause — the canonical caesura marker — but in
|
|
103
|
+
// the Match-Theory hierarchy it is only a ϕ (minor) break, NOT an ι boundary
|
|
104
|
+
// (dash/colon/semicolon DO project an ι, so those are caught by the IU test
|
|
105
|
+
// below). So detect a comma here directly: a comma after a contentful word,
|
|
106
|
+
// landing on a foot edge, is a hard caesura. (Without this, the comma→ϕ
|
|
107
|
+
// reclassification silently dropped every comma caesura, taking pre-caesural
|
|
108
|
+
// and caesural internal-rhyme detection with it.)
|
|
109
|
+
if (prevWasContentful && (w.lexicalClass === ',' || w.word === ',')
|
|
110
|
+
&& (!footEdges || footEdges.has(cum))) {
|
|
111
|
+
setHard(cum);
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const iu = iuOf.get(w);
|
|
116
|
+
if (prevIu !== undefined && iu !== undefined && iu !== prevIu
|
|
117
|
+
&& (!footEdges || footEdges.has(cum))) {
|
|
118
|
+
setHard(cum); // IU boundary landing on a foot edge → hard caesura
|
|
119
|
+
}
|
|
120
|
+
// A phrase-onset word that is NOT the line's first word opens a candidate
|
|
121
|
+
// caesura immediately before it.
|
|
122
|
+
if (prevWasContentful && isPhraseOnset(w))
|
|
123
|
+
onsetPositions.push(cum);
|
|
124
|
+
cum += w.syllables.length;
|
|
125
|
+
prevIu = iu;
|
|
126
|
+
prevWasContentful = true;
|
|
127
|
+
}
|
|
128
|
+
const total = cum;
|
|
129
|
+
// STRONG-ϕ hard caesura (Wagner Ch.5): a ϕ boundary whose NSBR-scaled strength
|
|
130
|
+
// clears a line-relative threshold — a major medial break after a long branching
|
|
131
|
+
// phrase — is a hard caesura even without punctuation, provided it lands on a foot
|
|
132
|
+
// edge and is medial. Threshold is relative (strength is already line-normalised),
|
|
133
|
+
// so a comma after a short phrase does NOT trigger one.
|
|
134
|
+
if (total >= 8) {
|
|
135
|
+
const lo = Math.max(2, Math.ceil(total / 3));
|
|
136
|
+
const hi = Math.floor((2 * total) / 3);
|
|
137
|
+
for (const b of bounds.phi) {
|
|
138
|
+
const c = b.syllableIndex;
|
|
139
|
+
if (c < lo || c > hi)
|
|
140
|
+
continue;
|
|
141
|
+
if (footEdges && !footEdges.has(c))
|
|
142
|
+
continue;
|
|
143
|
+
if (caes.has(c))
|
|
144
|
+
continue;
|
|
145
|
+
if (b.strength >= 0.75)
|
|
146
|
+
caes.set(c, { kind: 'hard', strength: b.strength });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Infer ONE medial caesura only when the line carries no overt (hard) break.
|
|
150
|
+
// Prefer the STRONGEST candidate boundary, tie-broken by nearness to the midpoint.
|
|
151
|
+
if (caes.size === 0 && total >= 8 && onsetPositions.length > 0) {
|
|
152
|
+
const mid = total / 2;
|
|
153
|
+
const lo = Math.max(2, Math.ceil(total / 3));
|
|
154
|
+
const hi = Math.floor((2 * total) / 3);
|
|
155
|
+
let best = -1, bestStr = -1, bestDist = Infinity;
|
|
156
|
+
for (const c of onsetPositions) {
|
|
157
|
+
if (c < lo || c > hi)
|
|
158
|
+
continue; // medial third only
|
|
159
|
+
if (footEdges && !footEdges.has(c))
|
|
160
|
+
continue; // align to a foot boundary
|
|
161
|
+
const str = strOf(c);
|
|
162
|
+
const d = Math.abs(c - mid);
|
|
163
|
+
if (str > bestStr + 1e-6 || (Math.abs(str - bestStr) <= 1e-6 && d < bestDist)) {
|
|
164
|
+
bestStr = str;
|
|
165
|
+
bestDist = d;
|
|
166
|
+
best = c;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (best > 0)
|
|
170
|
+
caes.set(best, { kind: 'soft', strength: Math.max(0.25, bestStr) });
|
|
171
|
+
}
|
|
172
|
+
// A caesura is a MEDIAL pause: a line-terminal comma/IU close lands at the last
|
|
173
|
+
// syllable (cum == total) and is the line break's own boundary, not a caesura.
|
|
174
|
+
// Dropping it here keeps every consumer consistent — the detailed view used to
|
|
175
|
+
// print a spurious trailing '‖' the reading view (correctly) never showed, and
|
|
176
|
+
// the rhyme layer would have double-counted the end word as "pre-caesural".
|
|
177
|
+
caes.delete(total);
|
|
178
|
+
return caes;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* The words that immediately PRECEDE a caesura in a line — i.e. each word whose
|
|
182
|
+
* cumulative syllable count lands exactly on a caesura position. Used by the
|
|
183
|
+
* rhyme layer for pre-caesural internal-rhyme detection. Returned in linear
|
|
184
|
+
* (reading) order; the caesura kind is paired so callers can weight hard vs
|
|
185
|
+
* inferred breaks if they wish.
|
|
186
|
+
*/
|
|
187
|
+
export function preCaesuralWords(words, ius, scansion) {
|
|
188
|
+
const caes = computeCaesurae(words, ius, scansion);
|
|
189
|
+
if (caes.size === 0)
|
|
190
|
+
return [];
|
|
191
|
+
const out = [];
|
|
192
|
+
let cum = 0;
|
|
193
|
+
for (const w of words) {
|
|
194
|
+
if (isPunctuation(w.lexicalClass) || w.syllables.length === 0)
|
|
195
|
+
continue;
|
|
196
|
+
cum += w.syllables.length;
|
|
197
|
+
const info = caes.get(cum);
|
|
198
|
+
if (info)
|
|
199
|
+
out.push({ word: w, kind: info.kind });
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ClsWord, IntonationalUnit } from '../types.js';
|
|
2
|
+
export interface BoundaryInfo {
|
|
3
|
+
level: 'kappa' | 'phi' | 'iota';
|
|
4
|
+
strength: number;
|
|
5
|
+
raw: number;
|
|
6
|
+
syllableIndex: number;
|
|
7
|
+
}
|
|
8
|
+
export interface LineBoundaries {
|
|
9
|
+
/** ϕ boundaries in document order: phi[k] is the boundary OPENING the k-th ϕ.
|
|
10
|
+
* phi[0] is the line's left edge (strength 0 — not a real internal break). */
|
|
11
|
+
phi: BoundaryInfo[];
|
|
12
|
+
/** ι boundaries in document order, same convention. */
|
|
13
|
+
iota: BoundaryInfo[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Compute graded boundary strength for every ϕ and ι boundary in a line.
|
|
17
|
+
*/
|
|
18
|
+
export declare function computeBoundaries(words: ClsWord[], ius: IntonationalUnit[]): LineBoundaries;
|
|
19
|
+
//# sourceMappingURL=boundaries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boundaries.d.ts","sourceRoot":"","sources":["../../src/calliope/boundaries.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAExD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B;mFAC+E;IAC/E,GAAG,EAAE,YAAY,EAAE,CAAC;IACpB,uDAAuD;IACvD,IAAI,EAAE,YAAY,EAAE,CAAC;CACtB;AAuFD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,gBAAgB,EAAE,GAAG,cAAc,CAyD3F"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// calliope/boundaries.ts — graded prosodic boundary strength (Wagner 2005 Ch. 4–5).
|
|
2
|
+
//
|
|
3
|
+
// The labelled κ/ϕ/ι hierarchy is the SKELETON; this module adds the relational
|
|
4
|
+
// FLESH Wagner argues the grid actually encodes — a graded boundary strength rather
|
|
5
|
+
// than a categorical label. Each ϕ/ι boundary gets a strength scaled RELATIVE to the
|
|
6
|
+
// line's other boundaries (the NSBR, Normalized Scopally-determined Boundary Rank,
|
|
7
|
+
// Ch. 5 §5.3): the strongest boundary in the line is 1.0, the rest fall below it.
|
|
8
|
+
//
|
|
9
|
+
// The raw strength of a boundary between left unit A and right unit B combines:
|
|
10
|
+
// • the categorical level (ι ≫ ϕ ≫ κ) — the SBR base rank;
|
|
11
|
+
// • punctuation coincidence — a comma/dash/colon at the boundary makes it stronger;
|
|
12
|
+
// • the LENGTH of the preceding constituent (a boundary after a long, branching
|
|
13
|
+
// phrase is stronger — Wagner Ch. 5 look-back);
|
|
14
|
+
// • clause separation — a boundary between subject|predicate or main|subordinate
|
|
15
|
+
// clause (their dependency LCA is at/near the root) is stronger;
|
|
16
|
+
// • associative coordination — a boundary internal to a same-coordinator series is
|
|
17
|
+
// WEAKER (equal-rank, flat).
|
|
18
|
+
//
|
|
19
|
+
// Strength feeds (a) the colored bracket rendering (display.ts) and (b) caesura
|
|
20
|
+
// placement (caesura.ts): a hard caesura needs a boundary whose strength clears a
|
|
21
|
+
// line-relative threshold, not an absolute one.
|
|
22
|
+
function isPunct(w) {
|
|
23
|
+
return /^[^A-Za-z0-9]+$/.test(w.lexicalClass) || w.syllables.length === 0;
|
|
24
|
+
}
|
|
25
|
+
/** Dependency depth of a word (number of governors up to the root). */
|
|
26
|
+
function depthOf(w, memo) {
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
let d = 0;
|
|
29
|
+
let cur = w;
|
|
30
|
+
while (cur) {
|
|
31
|
+
if (memo.has(cur)) {
|
|
32
|
+
d += memo.get(cur);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
if (seen.has(cur))
|
|
36
|
+
break; // cycle guard
|
|
37
|
+
seen.add(cur);
|
|
38
|
+
const g = cur.dependency?.governor;
|
|
39
|
+
if (!g || g === cur || isPunct(g))
|
|
40
|
+
break;
|
|
41
|
+
d++;
|
|
42
|
+
cur = g;
|
|
43
|
+
}
|
|
44
|
+
memo.set(w, d);
|
|
45
|
+
return d;
|
|
46
|
+
}
|
|
47
|
+
/** The syntactic head of a ϕ — the lowest-depth (closest to root) content token. */
|
|
48
|
+
function phraseHead(tokens, memo) {
|
|
49
|
+
let best = null;
|
|
50
|
+
let bestD = Infinity;
|
|
51
|
+
for (const t of tokens) {
|
|
52
|
+
if (isPunct(t))
|
|
53
|
+
continue;
|
|
54
|
+
const d = depthOf(t, memo);
|
|
55
|
+
if (d < bestD) {
|
|
56
|
+
bestD = d;
|
|
57
|
+
best = t;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return best;
|
|
61
|
+
}
|
|
62
|
+
/** Lowest-common-ancestor depth of two words in the dependency tree. */
|
|
63
|
+
function lcaDepth(a, b, memo) {
|
|
64
|
+
const anc = new Map();
|
|
65
|
+
let cur = a;
|
|
66
|
+
let d = 0;
|
|
67
|
+
const seen = new Set();
|
|
68
|
+
while (cur && !seen.has(cur)) {
|
|
69
|
+
anc.set(cur, d++);
|
|
70
|
+
seen.add(cur);
|
|
71
|
+
const g = cur.dependency?.governor;
|
|
72
|
+
if (!g || g === cur || isPunct(g))
|
|
73
|
+
break;
|
|
74
|
+
cur = g;
|
|
75
|
+
}
|
|
76
|
+
cur = b;
|
|
77
|
+
const seen2 = new Set();
|
|
78
|
+
while (cur && !seen2.has(cur)) {
|
|
79
|
+
if (anc.has(cur))
|
|
80
|
+
return depthOf(cur, memo);
|
|
81
|
+
seen2.add(cur);
|
|
82
|
+
const g = cur.dependency?.governor;
|
|
83
|
+
if (!g || g === cur || isPunct(g))
|
|
84
|
+
break;
|
|
85
|
+
cur = g;
|
|
86
|
+
}
|
|
87
|
+
return 0; // disjoint subtrees / different roots → treat as top-level (depth 0)
|
|
88
|
+
}
|
|
89
|
+
/** Is there a comma / dash / terminal punctuation token between A and B in surface
|
|
90
|
+
* order, and is it a strong (ι-class) one? */
|
|
91
|
+
function punctBetween(words, a, b) {
|
|
92
|
+
let comma = false, strong = false;
|
|
93
|
+
for (const w of words) {
|
|
94
|
+
if (w.absoluteIndex <= a.absoluteIndex || w.absoluteIndex >= b.absoluteIndex)
|
|
95
|
+
continue;
|
|
96
|
+
if (!isPunct(w))
|
|
97
|
+
continue;
|
|
98
|
+
if (w.word === ',' || w.lexicalClass === ',')
|
|
99
|
+
comma = true;
|
|
100
|
+
if (/^[.!?:;…]$/.test(w.word) || /^[.!?:;-]$/.test(w.lexicalClass) ||
|
|
101
|
+
w.lexicalClass === '-LRB-' || w.lexicalClass === '-RRB-')
|
|
102
|
+
strong = true;
|
|
103
|
+
}
|
|
104
|
+
return { comma, strong };
|
|
105
|
+
}
|
|
106
|
+
function sylCount(tokens) {
|
|
107
|
+
let n = 0;
|
|
108
|
+
for (const t of tokens)
|
|
109
|
+
n += t.syllables.length;
|
|
110
|
+
return n;
|
|
111
|
+
}
|
|
112
|
+
/** Flatten the ϕ of an ι into token lists. */
|
|
113
|
+
function phiTokenLists(iu) {
|
|
114
|
+
return iu.phonologicalPhrases.map(pp => pp.cliticGroups.flatMap(cg => cg.tokens).filter(t => !isPunct(t)));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Compute graded boundary strength for every ϕ and ι boundary in a line.
|
|
118
|
+
*/
|
|
119
|
+
export function computeBoundaries(words, ius) {
|
|
120
|
+
const memo = new Map();
|
|
121
|
+
const maxDepth = Math.max(1, ...words.filter(w => !isPunct(w)).map(w => depthOf(w, memo)));
|
|
122
|
+
// Flatten ϕ across all ι in document order, remembering each ϕ's ι index.
|
|
123
|
+
const flatPhi = [];
|
|
124
|
+
ius.forEach((iu, iuIdx) => {
|
|
125
|
+
phiTokenLists(iu).forEach((tokens, ppIdx) => {
|
|
126
|
+
if (tokens.length)
|
|
127
|
+
flatPhi.push({ tokens, iuIdx, ppIdx });
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
let cumSyl = 0;
|
|
131
|
+
const phi = [];
|
|
132
|
+
const iota = [];
|
|
133
|
+
for (let k = 0; k < flatPhi.length; k++) {
|
|
134
|
+
const cur = flatPhi[k];
|
|
135
|
+
const prev = k > 0 ? flatPhi[k - 1] : null;
|
|
136
|
+
const isIotaBoundary = !!prev && cur.iuIdx !== prev.iuIdx;
|
|
137
|
+
let raw = 0;
|
|
138
|
+
if (prev) {
|
|
139
|
+
const aHead = phraseHead(prev.tokens, memo); // left phrase head
|
|
140
|
+
const bHead = phraseHead(cur.tokens, memo); // right phrase head
|
|
141
|
+
const aLast = prev.tokens[prev.tokens.length - 1];
|
|
142
|
+
const bFirst = cur.tokens[0];
|
|
143
|
+
// base rank by level
|
|
144
|
+
raw += isIotaBoundary ? 3.0 : 1.0;
|
|
145
|
+
// punctuation coincidence
|
|
146
|
+
const p = punctBetween(words, aLast, bFirst);
|
|
147
|
+
if (p.strong)
|
|
148
|
+
raw += 2.0;
|
|
149
|
+
else if (p.comma)
|
|
150
|
+
raw += 1.2;
|
|
151
|
+
// length of the preceding constituent (look-back): longer → stronger
|
|
152
|
+
raw += Math.min(1.5, sylCount(prev.tokens) / 6);
|
|
153
|
+
// clause separation: a shallow dependency LCA (near the root) → strong
|
|
154
|
+
if (aHead && bHead) {
|
|
155
|
+
const lca = lcaDepth(aHead, bHead, memo);
|
|
156
|
+
raw += 1.2 * (1 - Math.min(1, lca / maxDepth)); // shallow LCA → +clause bonus
|
|
157
|
+
}
|
|
158
|
+
// associative coordination weakening: same coordinator across the boundary
|
|
159
|
+
if (startsCoordinator(cur.tokens) && !p.comma && !p.strong)
|
|
160
|
+
raw -= 0.4;
|
|
161
|
+
}
|
|
162
|
+
const info = {
|
|
163
|
+
level: isIotaBoundary ? 'iota' : 'phi',
|
|
164
|
+
strength: 0, // filled after normalisation
|
|
165
|
+
raw: Math.max(0, raw),
|
|
166
|
+
syllableIndex: cumSyl,
|
|
167
|
+
};
|
|
168
|
+
phi.push(info);
|
|
169
|
+
if (isIotaBoundary)
|
|
170
|
+
iota.push(info);
|
|
171
|
+
cumSyl += sylCount(cur.tokens);
|
|
172
|
+
}
|
|
173
|
+
// NSBR normalisation: scale to the strongest boundary in the line (0..1).
|
|
174
|
+
const maxRaw = Math.max(0, ...phi.map(b => b.raw));
|
|
175
|
+
for (const b of phi)
|
|
176
|
+
b.strength = maxRaw > 0 ? b.raw / maxRaw : 0;
|
|
177
|
+
return { phi, iota };
|
|
178
|
+
}
|
|
179
|
+
function startsCoordinator(tokens) {
|
|
180
|
+
const t = tokens[0];
|
|
181
|
+
return !!t && (t.lexicalClass === 'CC' || t.canonicalRel === 'CC');
|
|
182
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ClsSentence, ClsWord } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Run the cyclic Compound + Nuclear Stress Rules (Wagner-extended) over the
|
|
4
|
+
* sentence's dependency tree and write each word's `phraseStress` (1 = strongest).
|
|
5
|
+
*/
|
|
6
|
+
export declare function computePhraseStress(sent: ClsSentence): void;
|
|
7
|
+
/**
|
|
8
|
+
* Partition the sentence's words into ϕ-domains over the dependency tree.
|
|
9
|
+
*/
|
|
10
|
+
export declare function computePhiDomains(sent: ClsSentence): Map<ClsWord, number>;
|
|
11
|
+
//# sourceMappingURL=bracketing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bracketing.d.ts","sourceRoot":"","sources":["../../src/calliope/bracketing.ts"],"names":[],"mappings":"AAsCA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AA4HnD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAmF3D;AA4GD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,GAAG,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CA6BzE"}
|