nx_game_page_sink_t221.nx source
↩ module page · 153 lines · 7188 B
1// ORIGINAL proprietary. Owned serialization dependency for the existing game-page emitter.
2// See knowledge/gates/page-owned-sink-design-t221.md. Primitive qualification only;
3// the current page template and its raw-buffer callers are not migrated yet.
4import "nx_buf_dyn.nx"
5import "nx_itoa_lib.nx"
6import "nx_base64.nx"
7
8struct GpeSink { bytes:*NxBufOwned, error:i64, max_bytes:i64, scratch:*u8, scratch_cap:i64, }
9const GPE_SINK_RECORD_BYTES:i64=__size_of(GpeSink)+NX_BO_BYTES
10const GPE_SINK_I64_MIN:i64=0-NX_BO_I64_MAX-1
11const GPE_SINK_BASE64_INPUT_QUANTUM:i64=3
12const GPE_SINK_BASE64_OUTPUT_QUANTUM:i64=4
13
14// One allocation owns both descriptors; byte storage is exclusively NxBufOwned's.
15func gpe_sink_new(max_bytes:i64)->*GpeSink{
16 if max_bytes<0{return 0 as *GpeSink}
17 let s:*GpeSink=sys_mmap_try(GPE_SINK_RECORD_BYTES) as *GpeSink
18 if (s as i64)<=0{return 0 as *GpeSink}
19 s.bytes=(s as i64+__size_of(GpeSink)) as *NxBufOwned
20 s.bytes.buf=0 as *u8;s.bytes.len=0;s.bytes.cap=0
21 s.error=0;s.max_bytes=max_bytes;s.scratch=0 as *u8;s.scratch_cap=0;return s
22}
23func gpe_sink_fail(s:*GpeSink,rc:i64)->i64{if s.error==0{s.error=rc};return s.error}
24func gpe_sink_ready(s:*GpeSink)->i64{
25 if (s as i64)<=0{return NX_BO_INVALID}
26 if s.error<0{return s.error}
27 let valid:i64=nx_bo_state(s.bytes)
28 if valid<0{return gpe_sink_fail(s,valid)}
29 if s.max_bytes<0{return gpe_sink_fail(s,NX_BO_INVALID)}
30 return 0
31}
32func gpe_sink_room(s:*GpeSink,extra:i64)->i64{
33 let ready:i64=gpe_sink_ready(s);if ready<0{return ready}
34 if extra<0{return gpe_sink_fail(s,NX_BO_INVALID)}
35 if extra>NX_BO_I64_MAX-s.bytes.len{return gpe_sink_fail(s,NX_BO_RANGE)}
36 if s.max_bytes>0&&extra>s.max_bytes-s.bytes.len{return gpe_sink_fail(s,NX_BO_CAPACITY)}
37 return 0
38}
39// Source storage must remain live and immutable for the duration of the call.
40func gpe_sink_source(s:*GpeSink,src:*u8,n:i64)->i64{
41 let ready:i64=gpe_sink_ready(s);if ready<0{return ready}
42 if n<0{return gpe_sink_fail(s,NX_BO_INVALID)};if n==0{return 0}
43 let sp:i64=src as i64
44 if sp<=0{return gpe_sink_fail(s,NX_BO_INVALID)}
45 if n>NX_BO_I64_MAX-sp{return gpe_sink_fail(s,NX_BO_RANGE)}
46 let owner:i64=s as i64
47 if sp<owner+GPE_SINK_RECORD_BYTES&&owner<sp+n{return gpe_sink_fail(s,NX_BO_ALIAS)}
48 let output:i64=s.bytes.buf as i64
49 if s.bytes.cap>0&&sp<output+s.bytes.cap&&output<sp+n{return gpe_sink_fail(s,NX_BO_ALIAS)}
50 return 0
51}
52func gpe_sink_span(s:*GpeSink,src:*u8,n:i64)->i64{
53 let source:i64=gpe_sink_source(s,src,n);if source<0{return source}
54 let rc:i64=nx_bo_append(s.bytes,src,n,s.max_bytes)
55 if rc<0{return gpe_sink_fail(s,rc)};return s.bytes.len
56}
57func gpe_sink_byte(s:*GpeSink,value:i64)->i64{
58 let ready:i64=gpe_sink_ready(s);if ready<0{return ready}
59 if value<0||value>255{return gpe_sink_fail(s,NX_BO_INVALID)}
60 let room:i64=gpe_sink_room(s,1);if room<0{return room}
61 let allocated:i64=gpe_sink_scratch_new(s,1);if allocated<0{return allocated}
62 s.scratch[0]=value as u8;return gpe_sink_scratch_commit(s,1)
63}
64// This bounded string adapter requires a NUL inside the caller-declared source span.
65func gpe_sink_text(s:*GpeSink,text:*u8,available:i64)->i64{
66 let source:i64=gpe_sink_source(s,text,available);if source<0{return source}
67 var n:i64=0
68 while n<available{if text[n]==(0 as u8){return gpe_sink_span(s,text,n)};n=n+1}
69 return gpe_sink_fail(s,NX_BO_INVALID)
70}
71func gpe_sink_num(s:*GpeSink,value:i64)->i64{
72 let ready:i64=gpe_sink_ready(s);if ready<0{return ready}
73 // The existing shared integer formatter deliberately clamps MIN; retain its exact representation here.
74 if value==GPE_SINK_I64_MIN{return gpe_sink_span(s,"-9223372036854775808",20)}
75 // Retained native qualification found address-taken stack scratch corrupted by later locals.
76 // Use the sink's owned scratch until that compiler path is separately repaired and qualified.
77 let allocated:i64=gpe_sink_scratch_new(s,NXI_BUF);if allocated<0{return allocated}
78 let n:i64=nxi_buf(s.scratch,0,value)
79 return gpe_sink_scratch_commit(s,n)
80}
81func gpe_sink_scratch_release(s:*GpeSink)->i64{
82 if s.scratch_cap==0{return 0}
83 let rc:i64=sys_munmap_direct(s.scratch,s.scratch_cap)
84 if rc<0{return gpe_sink_fail(s,NX_BO_RELEASE)}
85 s.scratch=0 as *u8;s.scratch_cap=0;return 0
86}
87func gpe_sink_scratch_new(s:*GpeSink,capacity:i64)->i64{
88 let ready:i64=gpe_sink_ready(s);if ready<0{return ready}
89 if capacity<=0||s.scratch_cap!=0{return gpe_sink_fail(s,NX_BO_INVALID)}
90 let p:*u8=sys_mmap_try(capacity)
91 if (p as i64)<=0{return gpe_sink_fail(s,NX_BO_ALLOCATION)}
92 s.scratch=p;s.scratch_cap=capacity;return 0
93}
94func gpe_sink_scratch_commit(s:*GpeSink,n:i64)->i64{
95 if n<0||n>s.scratch_cap{return gpe_sink_fail(s,NX_BO_RANGE)}
96 let wrote:i64=gpe_sink_span(s,s.scratch,n)
97 let released:i64=gpe_sink_scratch_release(s)
98 if wrote<0{return wrote};if released<0{return released};return s.bytes.len
99}
100// Compatibility with rk_js_str: escape LF, quote and backslash; omit CR.
101// Encoding/semantic validation remains the input owner's responsibility.
102func gpe_sink_js(s:*GpeSink,src:*u8,n:i64)->i64{
103 let source:i64=gpe_sink_source(s,src,n);if source<0{return source}
104 var size:i64=2;var i:i64=0
105 while i<n{
106 let c:i64=src[i] as i64;var extra:i64=1
107 if c==13{extra=0}else{if c==10||c==34||c==92{extra=2}}
108 if extra>NX_BO_I64_MAX-size{return gpe_sink_fail(s,NX_BO_RANGE)}
109 size=size+extra;i=i+1
110 }
111 let room:i64=gpe_sink_room(s,size);if room<0{return room}
112 let allocated:i64=gpe_sink_scratch_new(s,size);if allocated<0{return allocated}
113 let dest:*u8=s.scratch;var p:i64=0;dest[p]=34 as u8;p=p+1;i=0
114 while i<n{
115 let c:i64=src[i] as i64
116 if c==10{dest[p]=92 as u8;dest[p+1]=110 as u8;p=p+2}
117 else{if c==34||c==92{dest[p]=92 as u8;dest[p+1]=c as u8;p=p+2}
118 else{if c!=13{dest[p]=c as u8;p=p+1}}}
119 i=i+1
120 }
121 dest[p]=34 as u8;p=p+1
122 return gpe_sink_scratch_commit(s,p)
123}
124func gpe_sink_base64_size(n:i64)->i64{
125 if n<0{return NX_BO_INVALID}
126 var groups:i64=n/GPE_SINK_BASE64_INPUT_QUANTUM
127 if n%GPE_SINK_BASE64_INPUT_QUANTUM!=0{groups=groups+1}
128 if groups>NX_BO_I64_MAX/GPE_SINK_BASE64_OUTPUT_QUANTUM{return NX_BO_RANGE}
129 return groups*GPE_SINK_BASE64_OUTPUT_QUANTUM
130}
131func gpe_sink_base64(s:*GpeSink,src:*u8,n:i64)->i64{
132 let source:i64=gpe_sink_source(s,src,n);if source<0{return source}
133 let size:i64=gpe_sink_base64_size(n);if size<0{return gpe_sink_fail(s,size)}
134 let room:i64=gpe_sink_room(s,size);if room<0{return room}
135 if size==0{return s.bytes.len}
136 let allocated:i64=gpe_sink_scratch_new(s,size);if allocated<0{return allocated}
137 let wrote:i64=b64_encode(src,n,s.scratch)
138 if wrote!=size{gpe_sink_fail(s,NX_BO_RANGE);gpe_sink_scratch_release(s);return s.error}
139 return gpe_sink_scratch_commit(s,wrote)
140}
141// Release is repeatable and retries any retained scratch cleanup; error remains latched.
142func gpe_sink_release(s:*GpeSink)->i64{
143 if (s as i64)<=0{return NX_BO_INVALID}
144 let scratch:i64=gpe_sink_scratch_release(s)
145 let bytes:i64=nx_bo_release(s.bytes)
146 if bytes<0{gpe_sink_fail(s,bytes)}
147 if scratch<0{return scratch};return bytes
148}
149// Consumes the handle only on success; caller must clear its pointer after success.
150func gpe_sink_destroy(s:*GpeSink)->i64{
151 let rc:i64=gpe_sink_release(s);if rc<0{return rc}
152 return sys_munmap_direct(s as *u8,GPE_SINK_RECORD_BYTES)
153}