nx_grammar.nx source
↩ module page · 127 lines · 4547 B
1// nx_grammar.nx -- WRITING arc, rung W-R2g: MECHANICAL grammar checks.
2// Content-agnostic, deterministic, dictionary-FREE. Honest scope: these are the
3// mechanical checks an editor catches by pattern, NOT full ML/Grammarly-class
4// grammar -- dictionary spell-check + suggestion ranking is a later sub-arc. This
5// fills the lone honest gap in the W-EXC-1 census with a real, exact organ.
6//
7// Checks (out[], filled by gr_check):
8// out[0] = doubled words -- the same word twice in a row ("the the")
9// out[1] = a/an misuse -- "a" before a vowel, or "an" before a consonant
10// (letter heuristic; a/e/i/o/u are vowels, not y/h)
11// out[2] = cap-after-period -- a lowercase word right after . ! or ?
12// returns the total error count.
13//
14// Pure integer, NO syscalls. Reuses nx_writecraft for char classification (DRY).
15//
16// license_tier: ORIGINAL
17// module: nishi-core.write.grammar
18// depends: nishi-core.write.craft
19// capability: WRITE_MECHANICAL_GRAMMAR
20import "nx_writecraft.nx"
21
22// 1 if c (any case) is an article-vowel a/e/i/o/u (NOT y, NOT h)
23func gr_is_art_vowel(c: i64) -> i64 {
24 let l: i64 = wc_lower(c)
25 if l == 97 { return 1 }
26 if l == 101 { return 1 }
27 if l == 105 { return 1 }
28 if l == 111 { return 1 }
29 if l == 117 { return 1 }
30 return 0
31}
32
33// 1 if t[a..b) equals null-terminated lit, case-insensitively
34func gr_eq_lit(t: *u8, a: i64, b: i64, lit: *u8) -> i64 {
35 var i: i64 = a
36 var j: i64 = 0
37 while i < b {
38 if lit[j] == (0 as u8) { return 0 }
39 if wc_lower(t[i] as i64) != wc_lower(lit[j] as i64) { return 0 }
40 i = i + 1
41 j = j + 1
42 }
43 if lit[j] != (0 as u8) { return 0 }
44 return 1
45}
46
47// 1 if the two word ranges are equal case-insensitively
48func gr_words_eq(t: *u8, a1: i64, b1: i64, a2: i64, b2: i64) -> i64 {
49 if b1 - a1 != b2 - a2 { return 0 }
50 var i: i64 = 0
51 while i < b1 - a1 {
52 if wc_lower(t[a1 + i] as i64) != wc_lower(t[a2 + i] as i64) { return 0 }
53 i = i + 1
54 }
55 return 1
56}
57
58func gr_check(t: *u8, n: i64, out: *i64) -> i64 {
59 var doubled: i64 = 0
60 var aan: i64 = 0
61 var caperr: i64 = 0
62 var pstart: i64 = 0 - 1 // previous word range
63 var pend: i64 = 0 - 1
64 var prev_a: i64 = 0 // previous word was "a"
65 var prev_an: i64 = 0 // previous word was "an"
66 var prev_term: i64 = 0 // a sentence terminator since the last word
67 var wstart: i64 = 0 - 1
68 var i: i64 = 0
69 while i < n {
70 let c: i64 = t[i] as i64
71 var inword: i64 = 0
72 if wc_is_alpha(c) == 1 {
73 inword = 1
74 } else {
75 if c == 39 { if wstart >= 0 { inword = 1 } }
76 }
77 if inword == 1 {
78 if wstart < 0 {
79 wstart = i
80 if prev_term == 1 { // first word after . ! ?
81 if c >= 97 { if c <= 122 { caperr = caperr + 1 } }
82 }
83 prev_term = 0
84 }
85 } else {
86 if wstart >= 0 {
87 // doubled-word
88 if pstart >= 0 {
89 if gr_words_eq(t, pstart, pend, wstart, i) == 1 { doubled = doubled + 1 }
90 }
91 // a/an vs this word's first letter
92 if prev_a == 1 {
93 if gr_is_art_vowel(t[wstart] as i64) == 1 { aan = aan + 1 }
94 }
95 if prev_an == 1 {
96 if gr_is_art_vowel(t[wstart] as i64) == 0 { aan = aan + 1 }
97 }
98 prev_a = 0
99 prev_an = 0
100 if gr_eq_lit(t, wstart, i, "a\x00" as *u8) == 1 { prev_a = 1 }
101 if gr_eq_lit(t, wstart, i, "an\x00" as *u8) == 1 { prev_an = 1 }
102 pstart = wstart
103 pend = i
104 wstart = 0 - 1
105 }
106 if c == 46 { prev_term = 1 }
107 if c == 33 { prev_term = 1 }
108 if c == 63 { prev_term = 1 }
109 }
110 i = i + 1
111 }
112 if wstart >= 0 { // trailing word
113 if pstart >= 0 {
114 if gr_words_eq(t, pstart, pend, wstart, n) == 1 { doubled = doubled + 1 }
115 }
116 if prev_a == 1 {
117 if gr_is_art_vowel(t[wstart] as i64) == 1 { aan = aan + 1 }
118 }
119 if prev_an == 1 {
120 if gr_is_art_vowel(t[wstart] as i64) == 0 { aan = aan + 1 }
121 }
122 }
123 out[0] = doubled
124 out[1] = aan
125 out[2] = caperr
126 return doubled + aan + caperr
127}