nx_writestyle.nx source
↩ module page · 164 lines · 6824 B
1// nx_writestyle.nx -- WRITING arc, rung W-R1: the content-agnostic prose-SIGNAL
2// detector. Extends W-R0 (nx_writecraft) with the craft signals an editor flags,
3// computed deterministically so they are exact + reproducible + exceed-able vs
4// Hemingway Editor / ProWritingAid (highlights -> here, quantitative counts).
5//
6// CONTENT-AGNOSTIC by design: it scores any register (the SFW lane and the
7// operator's local-LLM explicit lane run the SAME organ). The dialogue ratio +
8// these signals also feed the register classifier / routing seam and the
9// companion + image-gen ingestion point (per the 2026-06-12 fiction-ingestion
10// spec). No register lexicon lives here -- that arrives later as DATA (rule 25),
11// operator/lane-provided, never hardcoded.
12//
13// Signals (out[], filled by ws_signals):
14// out[0] = adverbs -- words ending "-ly" (len >= WS_ADVERB_MINLEN)
15// out[1] = passive -- a be-verb followed within WS_PASS_WINDOW words
16// by an "-ed" word (one per be-verb)
17// out[2] = weak -- intensifier/filler words (data list below)
18// out[3] = dialogue_milli -- chars inside double-quotes * 1000 / total chars
19// out[4] = longest_sentence -- words in the longest sentence
20// out[5] = hard_sentences -- sentences with >= WS_HARD_SENT_WORDS words
21// out[6], out[7] -- SCRATCH (be-verb window, current-sentence words)
22// -- caller must allocate out with >= 8 i64.
23// returns out[0] (adverb count).
24//
25// A word can satisfy more than one signal ("really" = adverb AND weak); that is
26// intentional. Thresholds are heuristic consts (move to conf/store as DATA next).
27//
28// Pure integer, NO syscalls. Reuses nx_writecraft for char classification (DRY).
29//
30// license_tier: ORIGINAL
31// module: nishi-core.write.style
32// depends: nishi-core.write.craft
33// capability: WRITE_PROSE_SIGNALS
34import "nx_writecraft.nx"
35
36const WS_ADVERB_MINLEN: i64 = 4
37const WS_PASS_WINDOW: i64 = 3
38const WS_HARD_SENT_WORDS: i64 = 14
39
40// 1 if t[a..b) equals the null-terminated lit, case-insensitively
41func ws_streq_ci(t: *u8, a: i64, b: i64, lit: *u8) -> i64 {
42 var i: i64 = a
43 var j: i64 = 0
44 while i < b {
45 if lit[j] == (0 as u8) { return 0 } // lit ran out first
46 if wc_lower(t[i] as i64) != wc_lower(lit[j] as i64) { return 0 }
47 i = i + 1
48 j = j + 1
49 }
50 if lit[j] != (0 as u8) { return 0 } // lit longer than word
51 return 1
52}
53
54// 1 if the word t[a..b) ends in the two lowercased chars c1,c2
55func ws_ends2(t: *u8, a: i64, b: i64, c1: i64, c2: i64) -> i64 {
56 if b - a < 2 { return 0 }
57 if wc_lower(t[b - 2] as i64) != c1 { return 0 }
58 if wc_lower(t[b - 1] as i64) != c2 { return 0 }
59 return 1
60}
61
62func ws_is_beverb(t: *u8, a: i64, b: i64) -> i64 {
63 if ws_streq_ci(t, a, b, "is\x00" as *u8) == 1 { return 1 }
64 if ws_streq_ci(t, a, b, "are\x00" as *u8) == 1 { return 1 }
65 if ws_streq_ci(t, a, b, "was\x00" as *u8) == 1 { return 1 }
66 if ws_streq_ci(t, a, b, "were\x00" as *u8) == 1 { return 1 }
67 if ws_streq_ci(t, a, b, "be\x00" as *u8) == 1 { return 1 }
68 if ws_streq_ci(t, a, b, "been\x00" as *u8) == 1 { return 1 }
69 if ws_streq_ci(t, a, b, "being\x00" as *u8) == 1 { return 1 }
70 if ws_streq_ci(t, a, b, "am\x00" as *u8) == 1 { return 1 }
71 return 0
72}
73
74func ws_is_weak(t: *u8, a: i64, b: i64) -> i64 {
75 if ws_streq_ci(t, a, b, "very\x00" as *u8) == 1 { return 1 }
76 if ws_streq_ci(t, a, b, "really\x00" as *u8) == 1 { return 1 }
77 if ws_streq_ci(t, a, b, "just\x00" as *u8) == 1 { return 1 }
78 if ws_streq_ci(t, a, b, "quite\x00" as *u8) == 1 { return 1 }
79 if ws_streq_ci(t, a, b, "rather\x00" as *u8) == 1 { return 1 }
80 if ws_streq_ci(t, a, b, "actually\x00" as *u8) == 1 { return 1 }
81 if ws_streq_ci(t, a, b, "basically\x00" as *u8) == 1 { return 1 }
82 if ws_streq_ci(t, a, b, "literally\x00" as *u8) == 1 { return 1 }
83 return 0
84}
85
86// evaluate one word t[a..b); accumulate into out[0],out[1],out[2]; out[6]=be-verb
87// window, out[7]=current-sentence word count
88func ws_eval_word(t: *u8, a: i64, b: i64, out: *i64) -> i64 {
89 out[7] = out[7] + 1 // current-sentence words
90 if ws_ends2(t, a, b, 108, 121) == 1 { // "-ly"
91 if b - a >= WS_ADVERB_MINLEN { out[0] = out[0] + 1 }
92 }
93 if ws_is_weak(t, a, b) == 1 { out[2] = out[2] + 1 }
94 if out[6] > 0 { // inside a be-verb window
95 if ws_ends2(t, a, b, 101, 100) == 1 { // "-ed" -> passive
96 out[1] = out[1] + 1
97 out[6] = 0
98 } else {
99 out[6] = out[6] - 1
100 }
101 }
102 if ws_is_beverb(t, a, b) == 1 { out[6] = WS_PASS_WINDOW }
103 return 0
104}
105
106// fill out[0..5] (out must hold >= 8 i64); returns adverb count
107func ws_signals(t: *u8, n: i64, out: *i64) -> i64 {
108 out[0] = 0
109 out[1] = 0
110 out[2] = 0
111 out[3] = 0
112 out[4] = 0
113 out[5] = 0
114 out[6] = 0 // be-verb window
115 out[7] = 0 // current-sentence words
116 var dia: i64 = 0
117 var inside: i64 = 0
118 var wstart: i64 = 0 - 1
119 var prev_term: i64 = 0
120 var i: i64 = 0
121 while i < n {
122 let c: i64 = t[i] as i64
123 if c == 34 { // double-quote toggles dialogue
124 inside = 1 - inside
125 } else {
126 if inside == 1 { dia = dia + 1 }
127 }
128 var inword: i64 = 0
129 if wc_is_alpha(c) == 1 {
130 inword = 1
131 } else {
132 if c == 39 { if wstart >= 0 { inword = 1 } } // apostrophe inside a word
133 }
134 if inword == 1 {
135 if wstart < 0 { wstart = i }
136 } else {
137 if wstart >= 0 {
138 ws_eval_word(t, wstart, i, out)
139 wstart = 0 - 1
140 }
141 }
142 var term: i64 = 0
143 if c == 46 { term = 1 }
144 if c == 33 { term = 1 }
145 if c == 63 { term = 1 }
146 if term == 1 {
147 if prev_term == 0 { // end of a sentence
148 if out[7] > out[4] { out[4] = out[7] }
149 if out[7] >= WS_HARD_SENT_WORDS { out[5] = out[5] + 1 }
150 out[7] = 0
151 }
152 }
153 prev_term = term
154 i = i + 1
155 }
156 if wstart >= 0 { ws_eval_word(t, wstart, n, out) } // trailing word
157 if out[7] > 0 { // trailing sentence
158 if out[7] > out[4] { out[4] = out[7] }
159 if out[7] >= WS_HARD_SENT_WORDS { out[5] = out[5] + 1 }
160 out[7] = 0
161 }
162 if n > 0 { out[3] = (dia * 1000) / n } else { out[3] = 0 }
163 return out[0]
164}