code wiki / (root) / nx_writestyle.nx

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}