calliope-ts 0.0.2 → 0.0.3
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 +3 -13
- 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/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/dist/engine.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// engine.ts — the prosody-engine abstraction (type-only; no runtime deps, so
|
|
2
|
+
// the concrete engines can import this without a cycle).
|
|
3
|
+
//
|
|
4
|
+
// Two engines produce the SAME shape — a per-sentence prosodic hierarchy with
|
|
5
|
+
// lexical/phrase/relative stress populated on the words:
|
|
6
|
+
// • "calliope" — the faithful, default, syntax-driven rebuild (Match-Theory
|
|
7
|
+
// hierarchy + Scenario A–O relation-keyed stress);
|
|
8
|
+
// • "clio" — a frozen snapshot of the prior pipeline, the legacy /
|
|
9
|
+
// alternative parse, selectable via the CLI.
|
|
10
|
+
// Everything downstream (metrical scoring, rhyme/form, display, synopsis) is
|
|
11
|
+
// shared and engine-agnostic.
|
|
12
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { ProsodyEngine } from './engine.js';
|
|
2
3
|
import { type ReadingStanza } from './display.js';
|
|
3
4
|
import type { LineResult } from './types.js';
|
|
4
|
-
export declare function analyzeStanzas(text: string, useScandroid?: boolean): LineResult[][];
|
|
5
|
+
export declare function analyzeStanzas(text: string, useScandroid?: boolean, engine?: ProsodyEngine): LineResult[][];
|
|
5
6
|
/**
|
|
6
7
|
* Convenience wrapper: analyse a multi‑line text, ignore stanza breaks,
|
|
7
8
|
* return a flat list of LineResult for each line.
|
|
8
9
|
*/
|
|
9
|
-
export declare function analyzeText(text: string, useScandroid?: boolean): LineResult[];
|
|
10
|
+
export declare function analyzeText(text: string, useScandroid?: boolean, engine?: ProsodyEngine): LineResult[];
|
|
10
11
|
/**
|
|
11
12
|
* Analyse a document for the reading view: like analyzeStanzas, but retains
|
|
12
13
|
* each original input line alongside its (1+) parsed sentence results so the
|
|
13
14
|
* stress gradient can be projected back over the verbatim text.
|
|
14
15
|
*/
|
|
15
|
-
export declare function analyzeReadingDocument(text: string): ReadingStanza[];
|
|
16
|
+
export declare function analyzeReadingDocument(text: string, engine?: ProsodyEngine): ReadingStanza[];
|
|
16
17
|
export declare const ML_IDLE_MS = 120;
|
|
17
18
|
export type MLEvent = {
|
|
18
19
|
kind: 'char';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA0B5C,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAOV,UAAU,EAMX,MAAM,YAAY,CAAC;AAkLpB,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,UAAO,EAAE,MAAM,GAAE,aAA4B,GAAG,UAAU,EAAE,EAAE,CA2CtH;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,UAAO,EAAE,MAAM,GAAE,aAA4B,GAAG,UAAU,EAAE,CAGjH;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,aAA4B,GAAG,aAAa,EAAE,CA0B1G;AAwCD,eAAO,MAAM,UAAU,MAAM,CAAC;AAE9B,MAAM,MAAM,OAAO,GACf;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AAEpB,MAAM,WAAW,OAAO;IAAG,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;CAAE;AAC7E,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAExD,wBAAgB,UAAU,IAAI,OAAO,CAAoD;AAMzF;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,GAAG,QAAQ,CAoCrE"}
|
package/dist/index.js
CHANGED
|
@@ -3,13 +3,73 @@ import * as fs from 'fs';
|
|
|
3
3
|
import * as readline from 'readline';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import { parseDocument } from './parser.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
6
|
+
import { parseDocument, isPunctuation } from './parser.js';
|
|
7
|
+
import { calliopeEngine } from './calliope/engine.js';
|
|
8
|
+
import { clioEngine } from './clio/engine.js';
|
|
9
|
+
// The FROZEN Clio pipeline + renderers (its own copy of the whole computational
|
|
10
|
+
// tree). When Clio is active the CLI runs entirely through these, so live changes
|
|
11
|
+
// to the regular (Calliope) pipeline — scorer included — cannot affect Clio.
|
|
12
|
+
import { analyzeReadingDocumentClio, renderUnifiedDisplay as clioRenderUnifiedDisplay, renderReadingView as clioRenderReadingView, renderFullLegend as clioRenderFullLegend, parseDocument as clioParseDocument, isPunctuation as clioIsPunctuation, } from './clio/pipeline.js';
|
|
13
|
+
import { renderHierarchy, renderKeyStresses, } from './phonological.js';
|
|
14
|
+
import { extractKeyStresses, scoreMeters, applyStanzaConsensus, applyRhythmLayer, applyMetricalityLayer } from './scansion.js';
|
|
10
15
|
import { applyRhymeAndForm } from './rhyme.js';
|
|
11
16
|
import { scandroidCorralWeird, scandroidMaximizeNormal, stressToMarks, } from './scandroid.js';
|
|
12
17
|
import { renderUnifiedDisplay, renderFullLegend, renderReadingView, } from './display.js';
|
|
18
|
+
// The active prosody engine for this process: Calliope (faithful, default) or
|
|
19
|
+
// Clio (the frozen legacy / alternative parse), chosen by `--clio` or the REPL
|
|
20
|
+
// menu. The exported analysis functions default their `engine` parameter to
|
|
21
|
+
// this, so library callers (tests, trials, benchmark) transparently use the
|
|
22
|
+
// active engine without signature churn.
|
|
23
|
+
let activeEngine = calliopeEngine;
|
|
24
|
+
/**
|
|
25
|
+
* Stanza-level discourse givenness (Wagner Ch.7, the plan's Gap 13) — CAREFUL and
|
|
26
|
+
* cross-line only. A CONTENT word repeated from an EARLIER line of the same stanza
|
|
27
|
+
* is discourse-given, so it may be subordinated relative to a new-information sister
|
|
28
|
+
* (the relativiser reads `discourseGiven`). Constraints that protect single-line
|
|
29
|
+
* scanning and thematically-focused words:
|
|
30
|
+
* • the first line is never marked (no prior context);
|
|
31
|
+
* • a lemma repeated WITHIN a single line is FOCUSED, not given → never marked
|
|
32
|
+
* (Eliot's "Nothing … nothing", a refrain);
|
|
33
|
+
* • only the 2nd-and-later occurrence (across lines) is marked, never the first;
|
|
34
|
+
* • single-line input has no previous line, so nothing is marked — the standout
|
|
35
|
+
* isolated-line feature is untouched.
|
|
36
|
+
* Mutates `discourseGiven` on the words; must run BEFORE the per-line relativisation.
|
|
37
|
+
*/
|
|
38
|
+
function markStanzaGivenness(docPerLine) {
|
|
39
|
+
if (docPerLine.length < 2)
|
|
40
|
+
return; // single line → no givenness
|
|
41
|
+
const key = (w) => w.word.toLowerCase().replace(/['’]/g, '');
|
|
42
|
+
// Lemmas that appear ≥2× within ANY one line are focal (refrain/emphasis) — exempt.
|
|
43
|
+
const focal = new Set();
|
|
44
|
+
for (const sents of docPerLine) {
|
|
45
|
+
const counts = new Map();
|
|
46
|
+
for (const sent of sents)
|
|
47
|
+
for (const w of sent.words) {
|
|
48
|
+
if (!w.isContent)
|
|
49
|
+
continue;
|
|
50
|
+
const k = key(w);
|
|
51
|
+
counts.set(k, (counts.get(k) ?? 0) + 1);
|
|
52
|
+
}
|
|
53
|
+
for (const [k, n] of counts)
|
|
54
|
+
if (n >= 2)
|
|
55
|
+
focal.add(k);
|
|
56
|
+
}
|
|
57
|
+
const seen = new Set(); // content lemmas in PRIOR lines
|
|
58
|
+
for (const sents of docPerLine) {
|
|
59
|
+
const thisLine = new Set();
|
|
60
|
+
for (const sent of sents)
|
|
61
|
+
for (const w of sent.words) {
|
|
62
|
+
if (!w.isContent)
|
|
63
|
+
continue;
|
|
64
|
+
const k = key(w);
|
|
65
|
+
if (seen.has(k) && !focal.has(k))
|
|
66
|
+
w.discourseGiven = true;
|
|
67
|
+
thisLine.add(k);
|
|
68
|
+
}
|
|
69
|
+
for (const k of thisLine)
|
|
70
|
+
seen.add(k);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
13
73
|
/**
|
|
14
74
|
* Scan one VERSE LINE (which may parse into several grammatical sentences).
|
|
15
75
|
*
|
|
@@ -23,16 +83,15 @@ import { renderUnifiedDisplay, renderFullLegend, renderReadingView, } from './di
|
|
|
23
83
|
* line's full concatenated syllable stream, with each sentence's IUs preserved
|
|
24
84
|
* as IU boundaries (→ hard caesurae) inside the line.
|
|
25
85
|
*/
|
|
26
|
-
function processLine(sents) {
|
|
86
|
+
function processLine(sents, engine = activeEngine) {
|
|
27
87
|
if (sents.length === 0)
|
|
28
88
|
return null;
|
|
29
89
|
const iusPerSent = [];
|
|
30
90
|
for (const sent of sents) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
assignRelativeStresses(sent.words, sentIus);
|
|
91
|
+
// The selected engine runs the per-sentence linguistic passes (lexical →
|
|
92
|
+
// hierarchy → compound/nuclear → phrase → relative stress) and returns the
|
|
93
|
+
// prosodic hierarchy. Calliope (default) and Clio differ only here.
|
|
94
|
+
const sentIus = engine.analyzeSentence(sent);
|
|
36
95
|
iusPerSent.push(sentIus);
|
|
37
96
|
}
|
|
38
97
|
// Merge the sentences' streams into the line-level scansion domain.
|
|
@@ -132,15 +191,19 @@ function applyContinuityRename(results) {
|
|
|
132
191
|
d.consensusMeter = undefined;
|
|
133
192
|
}
|
|
134
193
|
}
|
|
135
|
-
export function analyzeStanzas(text, useScandroid = true) {
|
|
194
|
+
export function analyzeStanzas(text, useScandroid = true, engine = activeEngine) {
|
|
136
195
|
const stanzas = text.split(/\n\s*\n/);
|
|
137
196
|
const results = [];
|
|
138
197
|
for (const stanza of stanzas) {
|
|
139
198
|
const lines = stanza.split('\n').filter(l => l.trim() !== '');
|
|
140
199
|
const stanzaResults = [];
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
200
|
+
// Parse every line first, then mark cross-line discourse givenness BEFORE the
|
|
201
|
+
// per-line stress passes (relativisation reads `discourseGiven`). Only the
|
|
202
|
+
// default (Calliope) relativiser consults it; Clio ignores the flag.
|
|
203
|
+
const docs = lines.map(line => parseDocument(line));
|
|
204
|
+
markStanzaGivenness(docs.map(d => d.sentences));
|
|
205
|
+
for (const doc of docs) {
|
|
206
|
+
const res = processLine(doc.sentences, engine);
|
|
144
207
|
if (res)
|
|
145
208
|
stanzaResults.push(res);
|
|
146
209
|
}
|
|
@@ -166,6 +229,9 @@ export function analyzeStanzas(text, useScandroid = true) {
|
|
|
166
229
|
for (const st of results)
|
|
167
230
|
applyRhythmLayer(st.map(r => r.phonologicalScansion));
|
|
168
231
|
}
|
|
232
|
+
// Prose-likeness hedge (Option 0): advisory, runs after the rhythm layer so it
|
|
233
|
+
// can defer to any accentual/dolnik verdict; annotation-only (metricalityNote).
|
|
234
|
+
applyMetricalityLayer(results.flatMap(st => st.map(r => r.phonologicalScansion)));
|
|
169
235
|
// Rhyme scheme + poetic-form identification spans stanzas (sonnets, terza
|
|
170
236
|
// rima); annotation-only (rhyme/formNote on each line's detail).
|
|
171
237
|
applyRhymeAndForm(results);
|
|
@@ -175,8 +241,8 @@ export function analyzeStanzas(text, useScandroid = true) {
|
|
|
175
241
|
* Convenience wrapper: analyse a multi‑line text, ignore stanza breaks,
|
|
176
242
|
* return a flat list of LineResult for each line.
|
|
177
243
|
*/
|
|
178
|
-
export function analyzeText(text, useScandroid = true) {
|
|
179
|
-
const stanzaResults = analyzeStanzas(text, useScandroid);
|
|
244
|
+
export function analyzeText(text, useScandroid = true, engine = activeEngine) {
|
|
245
|
+
const stanzaResults = analyzeStanzas(text, useScandroid, engine);
|
|
180
246
|
return stanzaResults.flat();
|
|
181
247
|
}
|
|
182
248
|
/**
|
|
@@ -184,16 +250,17 @@ export function analyzeText(text, useScandroid = true) {
|
|
|
184
250
|
* each original input line alongside its (1+) parsed sentence results so the
|
|
185
251
|
* stress gradient can be projected back over the verbatim text.
|
|
186
252
|
*/
|
|
187
|
-
export function analyzeReadingDocument(text) {
|
|
253
|
+
export function analyzeReadingDocument(text, engine = activeEngine) {
|
|
188
254
|
const stanzas = text.split(/\n\s*\n/);
|
|
189
255
|
const out = [];
|
|
190
256
|
for (const stanza of stanzas) {
|
|
191
257
|
const rawLines = stanza.split('\n').filter(l => l.trim() !== '');
|
|
192
258
|
if (rawLines.length === 0)
|
|
193
259
|
continue;
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
260
|
+
const rawDocs = rawLines.map(raw => ({ raw, doc: parseDocument(raw) }));
|
|
261
|
+
markStanzaGivenness(rawDocs.map(rd => rd.doc.sentences));
|
|
262
|
+
const lines = rawDocs.map(({ raw, doc }) => {
|
|
263
|
+
const res = processLine(doc.sentences, engine);
|
|
197
264
|
return { raw, results: res ? [res] : [] };
|
|
198
265
|
});
|
|
199
266
|
applyStanzaConsensus(lines.flatMap(l => l.results.map(r => r.phonologicalScansion)));
|
|
@@ -208,6 +275,7 @@ export function analyzeReadingDocument(text) {
|
|
|
208
275
|
for (const st of out)
|
|
209
276
|
applyRhythmLayer(st.lines.flatMap(l => l.results.map(r => r.phonologicalScansion)));
|
|
210
277
|
}
|
|
278
|
+
applyMetricalityLayer(out.flatMap(st => st.lines.flatMap(l => l.results.map(r => r.phonologicalScansion))));
|
|
211
279
|
applyRhymeAndForm(out.map(st => st.lines.flatMap(l => l.results)));
|
|
212
280
|
return out;
|
|
213
281
|
}
|
|
@@ -215,21 +283,25 @@ export function analyzeReadingDocument(text) {
|
|
|
215
283
|
function showResults(text) {
|
|
216
284
|
// Use the reading-document analysis so each LineResult keeps its verbatim input
|
|
217
285
|
// line (for the tail Reading Projection) and the stanza-consensus annotation.
|
|
218
|
-
|
|
286
|
+
// Clio routes through its own frozen pipeline + renderer.
|
|
287
|
+
const isClio = activeEngine.name === 'clio';
|
|
288
|
+
const stanzas = (isClio ? analyzeReadingDocumentClio : analyzeReadingDocument)(text);
|
|
289
|
+
const render = isClio ? clioRenderUnifiedDisplay : renderUnifiedDisplay;
|
|
219
290
|
for (let s = 0; s < stanzas.length; s++) {
|
|
220
291
|
if (stanzas.length > 1) {
|
|
221
292
|
console.log('\n' + chalk.bold('═══ Stanza ' + (s + 1) + ' ═══'));
|
|
222
293
|
}
|
|
223
294
|
for (const ln of stanzas[s].lines) {
|
|
224
295
|
for (const res of ln.results) {
|
|
225
|
-
console.log(
|
|
296
|
+
console.log(render(res, ln.raw));
|
|
226
297
|
}
|
|
227
298
|
}
|
|
228
299
|
}
|
|
229
300
|
}
|
|
230
301
|
function showReadingView(text) {
|
|
231
|
-
const
|
|
232
|
-
|
|
302
|
+
const isClio = activeEngine.name === 'clio';
|
|
303
|
+
const stanzas = (isClio ? analyzeReadingDocumentClio : analyzeReadingDocument)(text);
|
|
304
|
+
console.log((isClio ? clioRenderReadingView : renderReadingView)(stanzas));
|
|
233
305
|
}
|
|
234
306
|
// ─── MULTI-LINE INPUT (paste-friendly) ──────────────────────────
|
|
235
307
|
// Goal: the user pastes a whole poem (stanza breaks and all) and presses Enter
|
|
@@ -396,6 +468,7 @@ async function replMode() {
|
|
|
396
468
|
{ title: 'Line-by-Line Analysis (detailed view)', value: 'multiline' },
|
|
397
469
|
{ title: 'Parse & Scan from File (reading view)', value: 'reading-file' },
|
|
398
470
|
{ title: 'Analyze from File (detailed view)', value: 'file' },
|
|
471
|
+
{ title: 'Ask Clio instead (alternative parse) — toggle engine', value: 'engine' },
|
|
399
472
|
{ title: 'Display Legend', value: 'legend' },
|
|
400
473
|
{ title: 'Exit', value: 'exit' },
|
|
401
474
|
],
|
|
@@ -405,8 +478,20 @@ async function replMode() {
|
|
|
405
478
|
console.log(chalk.dim('\nGoodbye.\n'));
|
|
406
479
|
break;
|
|
407
480
|
}
|
|
481
|
+
if (response.action === 'engine') {
|
|
482
|
+
// Toggle between Calliope (faithful, default) and Clio (the legacy /
|
|
483
|
+
// alternative parse). Clio is Calliope's historian sister — sometimes
|
|
484
|
+
// on point, but not the primary poetic voice.
|
|
485
|
+
activeEngine = activeEngine.name === 'calliope' ? clioEngine : calliopeEngine;
|
|
486
|
+
const label = activeEngine.name === 'clio'
|
|
487
|
+
? 'Clio — legacy / alternative parse'
|
|
488
|
+
: 'Calliope — faithful, default';
|
|
489
|
+
console.log(chalk.dim(`\n Active engine: ${chalk.bold(label)}\n`));
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
408
492
|
if (response.action === 'legend') {
|
|
409
|
-
|
|
493
|
+
const legend = activeEngine.name === 'clio' ? clioRenderFullLegend : renderFullLegend;
|
|
494
|
+
console.log('\n' + legend() + '\n');
|
|
410
495
|
continue;
|
|
411
496
|
}
|
|
412
497
|
if (response.action === 'scan') {
|
|
@@ -462,6 +547,50 @@ async function replMode() {
|
|
|
462
547
|
}
|
|
463
548
|
}
|
|
464
549
|
}
|
|
550
|
+
// ─── PARSE-AUDIT DIAGNOSTIC ──────────────────────────────────────
|
|
551
|
+
//
|
|
552
|
+
// `--debug-parse` dumps, per word, the full chain the scansion rests on: POS
|
|
553
|
+
// tag, dependency role + governor, prosodic membership (IU.PP.CP), and the
|
|
554
|
+
// lexical / phrase / relative stress. This is the audit instrument for the
|
|
555
|
+
// POS + dependency + correction layers — read alongside trials/parse_audit.mjs,
|
|
556
|
+
// which tabulates tag/dependency distributions and anomalies over a corpus.
|
|
557
|
+
function debugParse(text) {
|
|
558
|
+
// Clio audits through its own frozen parser; Calliope through the live one.
|
|
559
|
+
const isClio = activeEngine.name === 'clio';
|
|
560
|
+
const parse = isClio ? clioParseDocument : parseDocument;
|
|
561
|
+
const isPunct = isClio ? clioIsPunctuation : isPunctuation;
|
|
562
|
+
const lines = text.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
|
|
563
|
+
for (const line of lines) {
|
|
564
|
+
const doc = parse(line);
|
|
565
|
+
console.log('\n' + chalk.bold.cyan('› ' + line));
|
|
566
|
+
for (const sent of doc.sentences) {
|
|
567
|
+
// Route the audit dump through the active engine so --debug-parse reflects
|
|
568
|
+
// whichever engine (Calliope / Clio) is selected.
|
|
569
|
+
const ius = activeEngine.analyzeSentence(sent);
|
|
570
|
+
const loc = new Map();
|
|
571
|
+
ius.forEach((iu, ii) => iu.phonologicalPhrases.forEach((pp, pi) => pp.cliticGroups.forEach((cg, ci) => cg.tokens.forEach(t => loc.set(t, `${ii + 1}.${pi + 1}.${ci + 1}`)))));
|
|
572
|
+
console.log(chalk.dim(' word POS C/F dep ←governor IU.PP.CP lex phr rel canon name'));
|
|
573
|
+
for (const w of sent.words) {
|
|
574
|
+
if (isPunctuation(w.lexicalClass))
|
|
575
|
+
continue;
|
|
576
|
+
const d = w.dependency;
|
|
577
|
+
const gov = d && d.governor ? d.governor.word : '—';
|
|
578
|
+
const cf = w.isContent ? 'C' : 'f';
|
|
579
|
+
const lex = w.syllables.map(s => (s.lexicalStress ?? s.stress)).join('');
|
|
580
|
+
const rel = w.syllables.map(s => s.relativeStress).join('');
|
|
581
|
+
// Calliope substrate (Stage 1): normalised relation + person/place flags.
|
|
582
|
+
const canon = w.canonicalRel ?? '-';
|
|
583
|
+
const name = w.isPersonName && w.isPlaceName ? 'P+C'
|
|
584
|
+
: w.isPersonName ? 'person' : w.isPlaceName ? 'place' : '';
|
|
585
|
+
console.log(' ' + w.word.padEnd(12) + w.lexicalClass.padEnd(6) + ' ' + cf + ' '
|
|
586
|
+
+ (d ? d.dependentType : '?').padEnd(14) + ' ' + String(gov).padEnd(13) + ' '
|
|
587
|
+
+ (loc.get(w) ?? '-').padEnd(9) + ' ' + lex.padEnd(5) + ' '
|
|
588
|
+
+ String(w.phraseStress).padEnd(4) + ' ' + rel.padEnd(5) + ' '
|
|
589
|
+
+ canon.padEnd(10) + ' ' + name);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
465
594
|
// ─── CLI ENTRY POINT ─────────────────────────────────────────────
|
|
466
595
|
async function main() {
|
|
467
596
|
let rawArgs = process.argv.slice(2);
|
|
@@ -469,7 +598,15 @@ async function main() {
|
|
|
469
598
|
// syllables stress-coloured, + per-line stress maps) instead of the full dump.
|
|
470
599
|
const reading = rawArgs.includes('--reading') || rawArgs.includes('-r');
|
|
471
600
|
rawArgs = rawArgs.filter(a => a !== '--reading' && a !== '-r');
|
|
472
|
-
|
|
601
|
+
// --debug-parse : dump the per-word POS / dependency / prosody / stress chain.
|
|
602
|
+
const debugParseMode = rawArgs.includes('--debug-parse');
|
|
603
|
+
rawArgs = rawArgs.filter(a => a !== '--debug-parse');
|
|
604
|
+
// --clio : run the frozen legacy / alternative parse engine instead of the
|
|
605
|
+
// default faithful Calliope engine.
|
|
606
|
+
if (rawArgs.includes('--clio'))
|
|
607
|
+
activeEngine = clioEngine;
|
|
608
|
+
rawArgs = rawArgs.filter(a => a !== '--clio');
|
|
609
|
+
const show = debugParseMode ? debugParse : reading ? showReadingView : showResults;
|
|
473
610
|
// Explicit arguments take precedence over piped stdin — otherwise running
|
|
474
611
|
// `calliope_ts "some line"` from a script/CI (where stdin is a non-TTY but
|
|
475
612
|
// empty) silently analysed the empty pipe and ignored the argument.
|
|
@@ -495,6 +632,12 @@ async function main() {
|
|
|
495
632
|
// No arguments: launch interactive REPL
|
|
496
633
|
await replMode();
|
|
497
634
|
}
|
|
635
|
+
// Is this module being run directly as the CLI, or imported as a library?
|
|
636
|
+
// A plain `process.argv[1] === fileURLToPath(import.meta.url)` check breaks under
|
|
637
|
+
// `npm install -g`: npm invokes us through a symlink in its bin directory, so
|
|
638
|
+
// process.argv[1] is that symlink's path, not the real dist/index.js. The two
|
|
639
|
+
// never match, `main()` never fires, and the command exits silently. Resolve
|
|
640
|
+
// symlinks on BOTH sides before comparing so the global command actually runs.
|
|
498
641
|
let isMain = false;
|
|
499
642
|
if (process.argv[1]) {
|
|
500
643
|
try {
|
package/dist/parser.d.ts
CHANGED
|
@@ -2,9 +2,9 @@ import { ClsDocument } from './types.js';
|
|
|
2
2
|
export declare function isPunctuation(tag: string): boolean;
|
|
3
3
|
export declare function isQuoteTag(tag: string): boolean;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* output from McAleese’s original Calliope implementation.
|
|
5
|
+
* Lowercase the first alphabetic character of every line. Kept available but
|
|
6
|
+
* NOT called by default — empirically net-negative with UDPipe (see parseDocument).
|
|
8
7
|
*/
|
|
8
|
+
export declare function lowerLineInitials(text: string): string;
|
|
9
9
|
export declare function parseDocument(text: string): ClsDocument;
|
|
10
10
|
//# sourceMappingURL=parser.d.ts.map
|
package/dist/parser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAmBA,OAAO,EACL,WAAW,EAKZ,MAAM,YAAY,CAAC;AAkCpB,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAElD;AASD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE/C;AAMD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKtD;AAmJD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAsJvD"}
|