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}