File size: 6,311 Bytes
59f93ff
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// semantics.ts — Prominence signals mined from the dependency parse + POS.
//
// Dependency roles ARE the semantic layer: NSUBJ=agent, DOBJ/IOBJ=patient/
// recipient, OBL/ADVMOD/ADVCL=peripheral, PRP$=possessor, INTJ/DISCOURSE=
// address.  A flat POS floor crushes function words that these configurations
// reveal to be prominent — a STRANDED preposition ("what are you waiting FOR"),
// a CONTRASTIVE possessive ("thy choice, not mine"), a VOCATIVE.  These
// detectors recover that prominence from observable structure only (no semantic
// guessing, no cross-poem givenness).  They are consumed by the relativiser
// (stress.ts) as targeted floor RAISES, and by the nuclear pass (Phase 4).

import { ClsWord } from './types.js';
import { isPunctuation } from './parser.js';

/** True if some other token has `word` as its dependency governor (i.e. word
 *  has a complement/dependent of its own). */
function hasDependent(word: ClsWord, words: ClsWord[]): boolean {
  for (const w of words) {
    if (w !== word && w.dependency && w.dependency.governor === word) return true;
  }
  return false;
}

/** Last non-punctuation token index in the sentence at/after `from`? */
function isClauseFinal(word: ClsWord, words: ClsWord[]): boolean {
  const idx = words.indexOf(word);
  if (idx < 0) return false;
  for (let k = idx + 1; k < words.length; k++) {
    if (!isPunctuation(words[k].lexicalClass)) return false;
  }
  return true;
}

/**
 * A STRANDED preposition: an IN preposition whose complement has been extracted
 * (wh-movement / relativisation / topicalisation), so it governs no object and
 * sits clause-finally — "what are you waiting FOR", "…what you stare AT".  Such
 * a preposition bears stress (it is not the reducible proclitic of "in the
 * house").  Conservative: IN only (infinitival TO is excluded — "I want to go"
 * is not stranding), no dependent, and clause-final (the canonical strand site).
 */
export function isStrandedPreposition(word: ClsWord, words: ClsWord[]): boolean {
  if (word.lexicalClass !== 'IN') return false;
  if (hasDependent(word, words)) return false;       // has a complement → ordinary preposition
  return isClauseFinal(word, words);
}

/** Absolute / elliptical possessive pronouns used as the contrasted element. */
const ABSOLUTE_POSSESSIVES = new Set([
  'mine', 'thine', 'yours', 'hers', 'ours', 'theirs', 'his',
]);
const CONTRAST_MARKERS = new Set(['not', 'but', 'nor']);

/**
 * A CONTRASTIVE possessive: a possessive determiner (PRP$: thy/my/your/her…)
 * in the elliptical contrast frame "X's … not/but MINE" — the contrast lifts
 * the possessor out of reduction ("it was THY choice, not mine").  Tight by
 * construction: requires a contrast marker (not/but/nor) adjacent to an
 * absolute possessive somewhere in the clause, so an ordinary unfocused
 * possessive ("I lost my way") is left alone.
 */
export function isContrastivePossessive(word: ClsWord, words: ClsWord[]): boolean {
  if (word.lexicalClass !== 'PRP$') return false;
  for (let i = 0; i + 1 < words.length; i++) {
    const a = words[i].word.toLowerCase();
    const b = words[i + 1].word.toLowerCase().replace(/['’]/g, '');
    if (CONTRAST_MARKERS.has(a) && ABSOLUTE_POSSESSIVES.has(b)) return true;
  }
  return false;
}

/** Finite auxiliaries / modals whose appearance before a subject pronoun marks
 *  subject-aux inversion. */
const INVERSION_AUX = new Set(['MD', 'VBP', 'VBZ', 'VBD']);

/**
 * A fronted DEICTIC LOCATIVE "there"/"here" in locative inversion — "THERE
 * could I marvel", "HERE could I rest".  FinNLP mis-tags the fronted locative
 * as existential (EX / expl) or reduces it as a discourse adverb, flattening it
 * to 'w'; but a fronted locative that triggers subject-aux inversion (an
 * aux/modal immediately followed by a subject pronoun) is a stressed deictic
 * focus, NOT the reduced existential of "there IS a house" (no inversion) or
 * the presentational "there LIVED a king" (verb + NP, no inversion).
 */
export function isDeicticLocative(word: ClsWord, words: ClsWord[]): boolean {
  const lemma = word.word.toLowerCase().replace(/['’]/g, '');
  if (lemma !== 'there' && lemma !== 'here') return false;
  const idx = words.indexOf(word);
  // must be the first non-punctuation token (fronted)
  let first = -1;
  for (let i = 0; i < words.length; i++) {
    if (!isPunctuation(words[i].lexicalClass)) { first = i; break; }
  }
  if (idx !== first) return false;
  // subject-aux inversion: <there/here> <aux|modal> <subject pronoun>
  const aux = words[idx + 1];
  const subj = words[idx + 2];
  return !!(aux && subj && INVERSION_AUX.has(aux.lexicalClass) && subj.lexicalClass === 'PRP');
}

/**
 * Imperative clause: the ROOT is a base-form verb (VB) with no overt subject
 * (no NSUBJ dependent) — "Tell me…", "Do not go…".  Used by the nuclear pass:
 * the accent falls on the verb / its object, not on a (dropped) subject, and an
 * imperative-clause vocative is a direct address.
 */
export function isImperativeClause(words: ClsWord[]): boolean {
  const root = words.find(w => w.dependency && w.dependency.dependentType === 'root');
  if (!root) return false;
  if (root.lexicalClass !== 'VB' && root.lexicalClass !== 'VBP') return false;
  for (const w of words) {
    if (w.dependency && w.dependency.governor === root
        && /nsubj/i.test(w.dependency.dependentType)) return false;
  }
  return true;
}

/**
 * A VOCATIVE (direct address): a noun tagged DISCOURSE/INTJ/DEP and set off by
 * adjacent punctuation (a comma or "!"), in a clause that is imperative or
 * subject-less — "Sing, O GODDESS…", "blow, BUGLE, blow".  Conservative: the
 * noun must be comma/!-adjacent so an ordinary argument noun is not swept in.
 */
export function isVocative(word: ClsWord, words: ClsWord[]): boolean {
  if (!/^(NN|NNS|NNP|NNPS)$/.test(word.lexicalClass)) return false;
  const role = word.dependency?.dependentType ?? '';
  if (!/discourse|intj|dep|vocative/i.test(role)) return false;
  const idx = words.indexOf(word);
  const prev = idx > 0 ? words[idx - 1] : null;
  const next = idx + 1 < words.length ? words[idx + 1] : null;
  const commaAdjacent =
    (prev && /^[,!]$/.test(prev.word)) || (next && /^[,!]$/.test(next.word));
  return !!commaAdjacent && isImperativeClause(words);
}