code wiki / (root) / nx_scenario.nx

nx_scenario.nx source

↩ module page · 186 lines · 6775 B

1// nx_scenario.nx -- WRITING arc, rung W-SCN-1: the SCENARIO engine. 2// Operator intent: "help provide scenarios" -- situations to write from, to 3// role-play a language lesson in, or to practise a real social interaction. One 4// engine, three uses, because a scenario has the same shape everywhere: 5// kind: language | social | story (which lane consumes it) 6// setting: where/when it happens 7// roles: who is in it 8// goal: what success looks like 9// constraints: (optional) register / tense / "be respectful, read consent" 10// level: (optional) difficulty (A2 / beginner / ...) 11// topic: (optional) vocabulary / theme to keep going 12// 13// The scenario TEXT is operator/lane DATA (the seam: the engine structures, the 14// generation seat expands). This organ PARSES + VALIDATES + EXTRACTS + emits the 15// one-line BRIEF the seat consumes. Pure integer, NO syscalls, NO imports, 16// caller owns buffers (the nx_note shape). Strict WHOLE-KEY matching: a key is 17// only recognized at line start + immediately followed by ':', so the word 18// "goal" appearing inside a value never satisfies the "goal" key. 19// 20// license_tier: ORIGINAL 21// module: nishi-core.write.scenario 22// depends: 23// capability: WRITE_SCENARIO 24 25// index just past the next '\n' at/after i (or n+1 if none) -> next line start 26func sc_next_line(buf: *u8, n: i64, i: i64) -> i64 { 27 var k: i64 = i 28 var go: i64 = 1 29 while go == 1 { 30 if k >= n { go = 0 } 31 else { 32 let c: i64 = buf[k] as i64 33 if c == 10 { go = 0 } else { k = k + 1 } 34 } 35 } 36 return k + 1 37} 38 39// offset where the value of `key` begins (after "key:" + spaces), or -1 if the 40// key is not present as a whole key at any line start. 41func sc_find_field(buf: *u8, n: i64, key: *u8) -> i64 { 42 var i: i64 = 0 43 var result: i64 = 0 - 1 44 var searching: i64 = 1 45 while searching == 1 { 46 if i >= n { searching = 0 } 47 else { 48 // skip leading spaces on this line 49 var j: i64 = i 50 var sg: i64 = 1 51 while sg == 1 { 52 if j < n { 53 let c: i64 = buf[j] as i64 54 if c == 32 { j = j + 1 } else { sg = 0 } 55 } else { sg = 0 } 56 } 57 // compare key chars 58 var m: i64 = 0 59 var matchd: i64 = 1 60 var kdone: i64 = 0 61 while kdone == 0 { 62 let kc: i64 = key[m] as i64 63 if kc == 0 { kdone = 1 } 64 else { 65 if j + m >= n { matchd = 0; kdone = 1 } 66 else { 67 let bc: i64 = buf[j + m] as i64 68 if bc != kc { matchd = 0; kdone = 1 } else { m = m + 1 } 69 } 70 } 71 } 72 if matchd == 1 { 73 if j + m < n { 74 let cc: i64 = buf[j + m] as i64 75 if cc == 58 { // ':' 76 var v: i64 = j + m + 1 77 var vg: i64 = 1 78 while vg == 1 { 79 if v < n { 80 let vc: i64 = buf[v] as i64 81 if vc == 32 { v = v + 1 } else { vg = 0 } 82 } else { vg = 0 } 83 } 84 result = v 85 searching = 0 86 } 87 } 88 } 89 if searching == 1 { i = sc_next_line(buf, n, i) } 90 } 91 } 92 return result 93} 94 95// length of the value beginning at off (to end of line), trailing space/CR trimmed 96func sc_value_len(buf: *u8, n: i64, off: i64) -> i64 { 97 var e: i64 = off 98 var go: i64 = 1 99 while go == 1 { 100 if e >= n { go = 0 } 101 else { 102 let c: i64 = buf[e] as i64 103 if c == 10 { go = 0 } else { e = e + 1 } 104 } 105 } 106 var trim: i64 = 1 107 while trim == 1 { 108 if e > off { 109 let c: i64 = buf[e - 1] as i64 110 if c == 32 { e = e - 1 } else { if c == 13 { e = e - 1 } else { trim = 0 } } 111 } else { trim = 0 } 112 } 113 return e - off 114} 115 116// 1 if key present (whole key), else 0 117func sc_has(buf: *u8, n: i64, key: *u8) -> i64 { 118 if sc_find_field(buf, n, key) >= 0 { return 1 } 119 return 0 120} 121 122// copy the value of key into out (null-terminated, capped at cap-1); len or -1 123func sc_copy_field(buf: *u8, n: i64, key: *u8, out: *u8, cap: i64) -> i64 { 124 let off: i64 = sc_find_field(buf, n, key) 125 if off < 0 { return 0 - 1 } 126 var len: i64 = sc_value_len(buf, n, off) 127 if len > cap - 1 { len = cap - 1 } 128 var i: i64 = 0 129 while i < len { out[i] = buf[off + i]; i = i + 1 } 130 out[len] = 0 as u8 131 return len 132} 133 134// completeness: count of REQUIRED keys missing (0 = a valid scenario). The 135// required schema is the scenario contract (kind/setting/roles/goal); optional 136// keys (constraints/level/topic) do not affect validity. 137func sc_validate(buf: *u8, n: i64) -> i64 { 138 var miss: i64 = 0 139 if sc_has(buf, n, "kind\x00" as *u8) == 0 { miss = miss + 1 } 140 if sc_has(buf, n, "setting\x00" as *u8) == 0 { miss = miss + 1 } 141 if sc_has(buf, n, "roles\x00" as *u8) == 0 { miss = miss + 1 } 142 if sc_has(buf, n, "goal\x00" as *u8) == 0 { miss = miss + 1 } 143 return miss 144} 145 146// append a null-terminated literal to out at p (capped); returns new p 147func sc_app_lit(out: *u8, p: i64, cap: i64, lit: *u8) -> i64 { 148 var i: i64 = 0 149 var q: i64 = p 150 while lit[i] != (0 as u8) { 151 if q < cap - 1 { out[q] = lit[i]; q = q + 1 } 152 i = i + 1 153 } 154 return q 155} 156 157// append the value of key (or '?' if missing) to out at p (capped); returns new p 158func sc_app_field(buf: *u8, n: i64, key: *u8, out: *u8, p: i64, cap: i64) -> i64 { 159 let off: i64 = sc_find_field(buf, n, key) 160 var q: i64 = p 161 if off < 0 { 162 if q < cap - 1 { out[q] = 63 as u8; q = q + 1 } // '?' 163 return q 164 } 165 let len: i64 = sc_value_len(buf, n, off) 166 var i: i64 = 0 167 while i < len { 168 if q < cap - 1 { out[q] = buf[off + i]; q = q + 1 } 169 i = i + 1 170 } 171 return q 172} 173 174// emit the one-line BRIEF the generation seat consumes: 175// "<kind> | <setting> | goal: <goal>" 176// returns the brief length (out null-terminated). 177func sc_emit_brief(buf: *u8, n: i64, out: *u8, cap: i64) -> i64 { 178 var p: i64 = 0 179 p = sc_app_field(buf, n, "kind\x00" as *u8, out, p, cap) 180 p = sc_app_lit(out, p, cap, " | \x00" as *u8) 181 p = sc_app_field(buf, n, "setting\x00" as *u8, out, p, cap) 182 p = sc_app_lit(out, p, cap, " | goal: \x00" as *u8) 183 p = sc_app_field(buf, n, "goal\x00" as *u8, out, p, cap) 184 out[p] = 0 as u8 185 return p 186}