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/src/index.ts
CHANGED
|
@@ -3,20 +3,27 @@ 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';
|
|
6
|
+
import { parseDocument, isPunctuation } from './parser.js';
|
|
7
|
+
import { ProsodyEngine } from './engine.js';
|
|
8
|
+
import { calliopeEngine } from './calliope/engine.js';
|
|
9
|
+
import { clioEngine } from './clio/engine.js';
|
|
10
|
+
// The FROZEN Clio pipeline + renderers (its own copy of the whole computational
|
|
11
|
+
// tree). When Clio is active the CLI runs entirely through these, so live changes
|
|
12
|
+
// to the regular (Calliope) pipeline — scorer included — cannot affect Clio.
|
|
7
13
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
analyzeReadingDocumentClio,
|
|
15
|
+
renderUnifiedDisplay as clioRenderUnifiedDisplay,
|
|
16
|
+
renderReadingView as clioRenderReadingView,
|
|
17
|
+
renderFullLegend as clioRenderFullLegend,
|
|
18
|
+
parseDocument as clioParseDocument,
|
|
19
|
+
isPunctuation as clioIsPunctuation,
|
|
20
|
+
} from './clio/pipeline.js';
|
|
13
21
|
import {
|
|
14
|
-
buildPhonologicalHierarchy,
|
|
15
22
|
renderHierarchy,
|
|
16
23
|
renderKeyStresses,
|
|
17
24
|
flattenDisplayEntries,
|
|
18
25
|
} from './phonological.js';
|
|
19
|
-
import { extractKeyStresses, scoreMeters, applyStanzaConsensus, applyRhythmLayer } from './scansion.js';
|
|
26
|
+
import { extractKeyStresses, scoreMeters, applyStanzaConsensus, applyRhythmLayer, applyMetricalityLayer } from './scansion.js';
|
|
20
27
|
import { applyRhymeAndForm } from './rhyme.js';
|
|
21
28
|
import {
|
|
22
29
|
scandroidCorralWeird,
|
|
@@ -31,6 +38,7 @@ import {
|
|
|
31
38
|
} from './display.js';
|
|
32
39
|
import type {
|
|
33
40
|
ClsSentence,
|
|
41
|
+
ClsWord,
|
|
34
42
|
MetreName,
|
|
35
43
|
IntonationalUnit,
|
|
36
44
|
StressLevel,
|
|
@@ -43,6 +51,54 @@ import type {
|
|
|
43
51
|
DisplayOptions,
|
|
44
52
|
} from './types.js';
|
|
45
53
|
|
|
54
|
+
// The active prosody engine for this process: Calliope (faithful, default) or
|
|
55
|
+
// Clio (the frozen legacy / alternative parse), chosen by `--clio` or the REPL
|
|
56
|
+
// menu. The exported analysis functions default their `engine` parameter to
|
|
57
|
+
// this, so library callers (tests, trials, benchmark) transparently use the
|
|
58
|
+
// active engine without signature churn.
|
|
59
|
+
let activeEngine: ProsodyEngine = calliopeEngine;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Stanza-level discourse givenness (Wagner Ch.7, the plan's Gap 13) — CAREFUL and
|
|
63
|
+
* cross-line only. A CONTENT word repeated from an EARLIER line of the same stanza
|
|
64
|
+
* is discourse-given, so it may be subordinated relative to a new-information sister
|
|
65
|
+
* (the relativiser reads `discourseGiven`). Constraints that protect single-line
|
|
66
|
+
* scanning and thematically-focused words:
|
|
67
|
+
* • the first line is never marked (no prior context);
|
|
68
|
+
* • a lemma repeated WITHIN a single line is FOCUSED, not given → never marked
|
|
69
|
+
* (Eliot's "Nothing … nothing", a refrain);
|
|
70
|
+
* • only the 2nd-and-later occurrence (across lines) is marked, never the first;
|
|
71
|
+
* • single-line input has no previous line, so nothing is marked — the standout
|
|
72
|
+
* isolated-line feature is untouched.
|
|
73
|
+
* Mutates `discourseGiven` on the words; must run BEFORE the per-line relativisation.
|
|
74
|
+
*/
|
|
75
|
+
function markStanzaGivenness(docPerLine: ClsSentence[][]): void {
|
|
76
|
+
if (docPerLine.length < 2) return; // single line → no givenness
|
|
77
|
+
const key = (w: ClsWord) => w.word.toLowerCase().replace(/['’]/g, '');
|
|
78
|
+
// Lemmas that appear ≥2× within ANY one line are focal (refrain/emphasis) — exempt.
|
|
79
|
+
const focal = new Set<string>();
|
|
80
|
+
for (const sents of docPerLine) {
|
|
81
|
+
const counts = new Map<string, number>();
|
|
82
|
+
for (const sent of sents) for (const w of sent.words) {
|
|
83
|
+
if (!w.isContent) continue;
|
|
84
|
+
const k = key(w);
|
|
85
|
+
counts.set(k, (counts.get(k) ?? 0) + 1);
|
|
86
|
+
}
|
|
87
|
+
for (const [k, n] of counts) if (n >= 2) focal.add(k);
|
|
88
|
+
}
|
|
89
|
+
const seen = new Set<string>(); // content lemmas in PRIOR lines
|
|
90
|
+
for (const sents of docPerLine) {
|
|
91
|
+
const thisLine = new Set<string>();
|
|
92
|
+
for (const sent of sents) for (const w of sent.words) {
|
|
93
|
+
if (!w.isContent) continue;
|
|
94
|
+
const k = key(w);
|
|
95
|
+
if (seen.has(k) && !focal.has(k)) w.discourseGiven = true;
|
|
96
|
+
thisLine.add(k);
|
|
97
|
+
}
|
|
98
|
+
for (const k of thisLine) seen.add(k);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
46
102
|
/**
|
|
47
103
|
* Scan one VERSE LINE (which may parse into several grammatical sentences).
|
|
48
104
|
*
|
|
@@ -56,16 +112,15 @@ import type {
|
|
|
56
112
|
* line's full concatenated syllable stream, with each sentence's IUs preserved
|
|
57
113
|
* as IU boundaries (→ hard caesurae) inside the line.
|
|
58
114
|
*/
|
|
59
|
-
function processLine(sents: ClsSentence[]): LineResult | null {
|
|
115
|
+
function processLine(sents: ClsSentence[], engine: ProsodyEngine = activeEngine): LineResult | null {
|
|
60
116
|
if (sents.length === 0) return null;
|
|
61
117
|
|
|
62
118
|
const iusPerSent: IntonationalUnit[][] = [];
|
|
63
119
|
for (const sent of sents) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
assignRelativeStresses(sent.words, sentIus);
|
|
120
|
+
// The selected engine runs the per-sentence linguistic passes (lexical →
|
|
121
|
+
// hierarchy → compound/nuclear → phrase → relative stress) and returns the
|
|
122
|
+
// prosodic hierarchy. Calliope (default) and Clio differ only here.
|
|
123
|
+
const sentIus = engine.analyzeSentence(sent);
|
|
69
124
|
iusPerSent.push(sentIus);
|
|
70
125
|
}
|
|
71
126
|
|
|
@@ -172,15 +227,19 @@ function applyContinuityRename(results: LineResult[]): void {
|
|
|
172
227
|
}
|
|
173
228
|
}
|
|
174
229
|
|
|
175
|
-
export function analyzeStanzas(text: string, useScandroid = true): LineResult[][] {
|
|
230
|
+
export function analyzeStanzas(text: string, useScandroid = true, engine: ProsodyEngine = activeEngine): LineResult[][] {
|
|
176
231
|
const stanzas = text.split(/\n\s*\n/);
|
|
177
232
|
const results: LineResult[][] = [];
|
|
178
233
|
for (const stanza of stanzas) {
|
|
179
234
|
const lines = stanza.split('\n').filter(l => l.trim() !== '');
|
|
180
235
|
const stanzaResults: LineResult[] = [];
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
236
|
+
// Parse every line first, then mark cross-line discourse givenness BEFORE the
|
|
237
|
+
// per-line stress passes (relativisation reads `discourseGiven`). Only the
|
|
238
|
+
// default (Calliope) relativiser consults it; Clio ignores the flag.
|
|
239
|
+
const docs = lines.map(line => parseDocument(line));
|
|
240
|
+
markStanzaGivenness(docs.map(d => d.sentences));
|
|
241
|
+
for (const doc of docs) {
|
|
242
|
+
const res = processLine(doc.sentences, engine);
|
|
184
243
|
if (res) stanzaResults.push(res);
|
|
185
244
|
}
|
|
186
245
|
// Resolve near-tie lines toward the stanza's dominant meter (explicit, non-
|
|
@@ -204,6 +263,9 @@ export function analyzeStanzas(text: string, useScandroid = true): LineResult[][
|
|
|
204
263
|
applyContinuityRename(all);
|
|
205
264
|
for (const st of results) applyRhythmLayer(st.map(r => r.phonologicalScansion));
|
|
206
265
|
}
|
|
266
|
+
// Prose-likeness hedge (Option 0): advisory, runs after the rhythm layer so it
|
|
267
|
+
// can defer to any accentual/dolnik verdict; annotation-only (metricalityNote).
|
|
268
|
+
applyMetricalityLayer(results.flatMap(st => st.map(r => r.phonologicalScansion)));
|
|
207
269
|
// Rhyme scheme + poetic-form identification spans stanzas (sonnets, terza
|
|
208
270
|
// rima); annotation-only (rhyme/formNote on each line's detail).
|
|
209
271
|
applyRhymeAndForm(results);
|
|
@@ -214,8 +276,8 @@ export function analyzeStanzas(text: string, useScandroid = true): LineResult[][
|
|
|
214
276
|
* Convenience wrapper: analyse a multi‑line text, ignore stanza breaks,
|
|
215
277
|
* return a flat list of LineResult for each line.
|
|
216
278
|
*/
|
|
217
|
-
export function analyzeText(text: string, useScandroid = true): LineResult[] {
|
|
218
|
-
const stanzaResults = analyzeStanzas(text, useScandroid);
|
|
279
|
+
export function analyzeText(text: string, useScandroid = true, engine: ProsodyEngine = activeEngine): LineResult[] {
|
|
280
|
+
const stanzaResults = analyzeStanzas(text, useScandroid, engine);
|
|
219
281
|
return stanzaResults.flat();
|
|
220
282
|
}
|
|
221
283
|
|
|
@@ -224,15 +286,16 @@ export function analyzeText(text: string, useScandroid = true): LineResult[] {
|
|
|
224
286
|
* each original input line alongside its (1+) parsed sentence results so the
|
|
225
287
|
* stress gradient can be projected back over the verbatim text.
|
|
226
288
|
*/
|
|
227
|
-
export function analyzeReadingDocument(text: string): ReadingStanza[] {
|
|
289
|
+
export function analyzeReadingDocument(text: string, engine: ProsodyEngine = activeEngine): ReadingStanza[] {
|
|
228
290
|
const stanzas = text.split(/\n\s*\n/);
|
|
229
291
|
const out: ReadingStanza[] = [];
|
|
230
292
|
for (const stanza of stanzas) {
|
|
231
293
|
const rawLines = stanza.split('\n').filter(l => l.trim() !== '');
|
|
232
294
|
if (rawLines.length === 0) continue;
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
295
|
+
const rawDocs = rawLines.map(raw => ({ raw, doc: parseDocument(raw) }));
|
|
296
|
+
markStanzaGivenness(rawDocs.map(rd => rd.doc.sentences));
|
|
297
|
+
const lines = rawDocs.map(({ raw, doc }) => {
|
|
298
|
+
const res = processLine(doc.sentences, engine);
|
|
236
299
|
return { raw, results: res ? [res] : [] };
|
|
237
300
|
});
|
|
238
301
|
applyStanzaConsensus(lines.flatMap(l => l.results.map(r => r.phonologicalScansion)));
|
|
@@ -246,6 +309,7 @@ export function analyzeReadingDocument(text: string): ReadingStanza[] {
|
|
|
246
309
|
applyContinuityRename(all);
|
|
247
310
|
for (const st of out) applyRhythmLayer(st.lines.flatMap(l => l.results.map(r => r.phonologicalScansion)));
|
|
248
311
|
}
|
|
312
|
+
applyMetricalityLayer(out.flatMap(st => st.lines.flatMap(l => l.results.map(r => r.phonologicalScansion))));
|
|
249
313
|
applyRhymeAndForm(out.map(st => st.lines.flatMap(l => l.results)));
|
|
250
314
|
return out;
|
|
251
315
|
}
|
|
@@ -255,7 +319,10 @@ export function analyzeReadingDocument(text: string): ReadingStanza[] {
|
|
|
255
319
|
function showResults(text: string): void {
|
|
256
320
|
// Use the reading-document analysis so each LineResult keeps its verbatim input
|
|
257
321
|
// line (for the tail Reading Projection) and the stanza-consensus annotation.
|
|
258
|
-
|
|
322
|
+
// Clio routes through its own frozen pipeline + renderer.
|
|
323
|
+
const isClio = activeEngine.name === 'clio';
|
|
324
|
+
const stanzas = (isClio ? analyzeReadingDocumentClio : analyzeReadingDocument)(text);
|
|
325
|
+
const render = isClio ? clioRenderUnifiedDisplay : renderUnifiedDisplay;
|
|
259
326
|
|
|
260
327
|
for (let s = 0; s < stanzas.length; s++) {
|
|
261
328
|
if (stanzas.length > 1) {
|
|
@@ -263,15 +330,16 @@ function showResults(text: string): void {
|
|
|
263
330
|
}
|
|
264
331
|
for (const ln of stanzas[s].lines) {
|
|
265
332
|
for (const res of ln.results) {
|
|
266
|
-
console.log(
|
|
333
|
+
console.log(render(res, ln.raw));
|
|
267
334
|
}
|
|
268
335
|
}
|
|
269
336
|
}
|
|
270
337
|
}
|
|
271
338
|
|
|
272
339
|
function showReadingView(text: string): void {
|
|
273
|
-
const
|
|
274
|
-
|
|
340
|
+
const isClio = activeEngine.name === 'clio';
|
|
341
|
+
const stanzas = (isClio ? analyzeReadingDocumentClio : analyzeReadingDocument)(text);
|
|
342
|
+
console.log((isClio ? clioRenderReadingView : renderReadingView)(stanzas));
|
|
275
343
|
}
|
|
276
344
|
|
|
277
345
|
// ─── MULTI-LINE INPUT (paste-friendly) ──────────────────────────
|
|
@@ -426,6 +494,7 @@ async function replMode(): Promise<void> {
|
|
|
426
494
|
{ title: 'Line-by-Line Analysis (detailed view)', value: 'multiline' },
|
|
427
495
|
{ title: 'Parse & Scan from File (reading view)', value: 'reading-file' },
|
|
428
496
|
{ title: 'Analyze from File (detailed view)', value: 'file' },
|
|
497
|
+
{ title: 'Ask Clio instead (alternative parse) — toggle engine', value: 'engine' },
|
|
429
498
|
{ title: 'Display Legend', value: 'legend' },
|
|
430
499
|
{ title: 'Exit', value: 'exit' },
|
|
431
500
|
],
|
|
@@ -437,8 +506,21 @@ async function replMode(): Promise<void> {
|
|
|
437
506
|
break;
|
|
438
507
|
}
|
|
439
508
|
|
|
509
|
+
if (response.action === 'engine') {
|
|
510
|
+
// Toggle between Calliope (faithful, default) and Clio (the legacy /
|
|
511
|
+
// alternative parse). Clio is Calliope's historian sister — sometimes
|
|
512
|
+
// on point, but not the primary poetic voice.
|
|
513
|
+
activeEngine = activeEngine.name === 'calliope' ? clioEngine : calliopeEngine;
|
|
514
|
+
const label = activeEngine.name === 'clio'
|
|
515
|
+
? 'Clio — legacy / alternative parse'
|
|
516
|
+
: 'Calliope — faithful, default';
|
|
517
|
+
console.log(chalk.dim(`\n Active engine: ${chalk.bold(label)}\n`));
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
|
|
440
521
|
if (response.action === 'legend') {
|
|
441
|
-
|
|
522
|
+
const legend = activeEngine.name === 'clio' ? clioRenderFullLegend : renderFullLegend;
|
|
523
|
+
console.log('\n' + legend() + '\n');
|
|
442
524
|
continue;
|
|
443
525
|
}
|
|
444
526
|
|
|
@@ -494,6 +576,53 @@ async function replMode(): Promise<void> {
|
|
|
494
576
|
}
|
|
495
577
|
}
|
|
496
578
|
|
|
579
|
+
// ─── PARSE-AUDIT DIAGNOSTIC ──────────────────────────────────────
|
|
580
|
+
//
|
|
581
|
+
// `--debug-parse` dumps, per word, the full chain the scansion rests on: POS
|
|
582
|
+
// tag, dependency role + governor, prosodic membership (IU.PP.CP), and the
|
|
583
|
+
// lexical / phrase / relative stress. This is the audit instrument for the
|
|
584
|
+
// POS + dependency + correction layers — read alongside trials/parse_audit.mjs,
|
|
585
|
+
// which tabulates tag/dependency distributions and anomalies over a corpus.
|
|
586
|
+
function debugParse(text: string): void {
|
|
587
|
+
// Clio audits through its own frozen parser; Calliope through the live one.
|
|
588
|
+
const isClio = activeEngine.name === 'clio';
|
|
589
|
+
const parse = isClio ? clioParseDocument : parseDocument;
|
|
590
|
+
const isPunct = isClio ? clioIsPunctuation : isPunctuation;
|
|
591
|
+
const lines = text.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
|
|
592
|
+
for (const line of lines) {
|
|
593
|
+
const doc = parse(line);
|
|
594
|
+
console.log('\n' + chalk.bold.cyan('› ' + line));
|
|
595
|
+
for (const sent of doc.sentences) {
|
|
596
|
+
// Route the audit dump through the active engine so --debug-parse reflects
|
|
597
|
+
// whichever engine (Calliope / Clio) is selected.
|
|
598
|
+
const ius = activeEngine.analyzeSentence(sent);
|
|
599
|
+
const loc = new Map<ClsWord, string>();
|
|
600
|
+
ius.forEach((iu, ii) =>
|
|
601
|
+
iu.phonologicalPhrases.forEach((pp, pi) =>
|
|
602
|
+
pp.cliticGroups.forEach((cg, ci) =>
|
|
603
|
+
cg.tokens.forEach(t => loc.set(t, `${ii + 1}.${pi + 1}.${ci + 1}`)))));
|
|
604
|
+
console.log(chalk.dim(' word POS C/F dep ←governor IU.PP.CP lex phr rel canon name'));
|
|
605
|
+
for (const w of sent.words) {
|
|
606
|
+
if (isPunctuation(w.lexicalClass)) continue;
|
|
607
|
+
const d = w.dependency;
|
|
608
|
+
const gov = d && d.governor ? d.governor.word : '—';
|
|
609
|
+
const cf = w.isContent ? 'C' : 'f';
|
|
610
|
+
const lex = w.syllables.map(s => (s.lexicalStress ?? s.stress)).join('');
|
|
611
|
+
const rel = w.syllables.map(s => s.relativeStress).join('');
|
|
612
|
+
// Calliope substrate (Stage 1): normalised relation + person/place flags.
|
|
613
|
+
const canon = w.canonicalRel ?? '-';
|
|
614
|
+
const name = w.isPersonName && w.isPlaceName ? 'P+C'
|
|
615
|
+
: w.isPersonName ? 'person' : w.isPlaceName ? 'place' : '';
|
|
616
|
+
console.log(' ' + w.word.padEnd(12) + w.lexicalClass.padEnd(6) + ' ' + cf + ' '
|
|
617
|
+
+ (d ? d.dependentType : '?').padEnd(14) + ' ' + String(gov).padEnd(13) + ' '
|
|
618
|
+
+ (loc.get(w) ?? '-').padEnd(9) + ' ' + lex.padEnd(5) + ' '
|
|
619
|
+
+ String(w.phraseStress).padEnd(4) + ' ' + rel.padEnd(5) + ' '
|
|
620
|
+
+ canon.padEnd(10) + ' ' + name);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
497
626
|
// ─── CLI ENTRY POINT ─────────────────────────────────────────────
|
|
498
627
|
|
|
499
628
|
async function main(): Promise<void> {
|
|
@@ -502,7 +631,14 @@ async function main(): Promise<void> {
|
|
|
502
631
|
// syllables stress-coloured, + per-line stress maps) instead of the full dump.
|
|
503
632
|
const reading = rawArgs.includes('--reading') || rawArgs.includes('-r');
|
|
504
633
|
rawArgs = rawArgs.filter(a => a !== '--reading' && a !== '-r');
|
|
505
|
-
|
|
634
|
+
// --debug-parse : dump the per-word POS / dependency / prosody / stress chain.
|
|
635
|
+
const debugParseMode = rawArgs.includes('--debug-parse');
|
|
636
|
+
rawArgs = rawArgs.filter(a => a !== '--debug-parse');
|
|
637
|
+
// --clio : run the frozen legacy / alternative parse engine instead of the
|
|
638
|
+
// default faithful Calliope engine.
|
|
639
|
+
if (rawArgs.includes('--clio')) activeEngine = clioEngine;
|
|
640
|
+
rawArgs = rawArgs.filter(a => a !== '--clio');
|
|
641
|
+
const show = debugParseMode ? debugParse : reading ? showReadingView : showResults;
|
|
506
642
|
|
|
507
643
|
// Explicit arguments take precedence over piped stdin — otherwise running
|
|
508
644
|
// `calliope_ts "some line"` from a script/CI (where stdin is a non-TTY but
|
|
@@ -532,15 +668,23 @@ async function main(): Promise<void> {
|
|
|
532
668
|
await replMode();
|
|
533
669
|
}
|
|
534
670
|
|
|
671
|
+
// Is this module being run directly as the CLI, or imported as a library?
|
|
672
|
+
// A plain `process.argv[1] === fileURLToPath(import.meta.url)` check breaks under
|
|
673
|
+
// `npm install -g`: npm invokes us through a symlink in its bin directory, so
|
|
674
|
+
// process.argv[1] is that symlink's path, not the real dist/index.js. The two
|
|
675
|
+
// never match, `main()` never fires, and the command exits silently. Resolve
|
|
676
|
+
// symlinks on BOTH sides before comparing so the global command actually runs.
|
|
535
677
|
let isMain = false;
|
|
536
678
|
if (process.argv[1]) {
|
|
537
679
|
try {
|
|
538
680
|
isMain = fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
539
|
-
} catch {
|
|
681
|
+
} catch {
|
|
682
|
+
isMain = false;
|
|
683
|
+
}
|
|
540
684
|
}
|
|
541
685
|
if (isMain) {
|
|
542
686
|
main().catch(err => {
|
|
543
687
|
console.error(chalk.red('Fatal error:'), err);
|
|
544
688
|
process.exit(1);
|
|
545
689
|
});
|
|
546
|
-
}
|
|
690
|
+
}
|