code wiki / _hdl_build / nx_galx_shell_jslint.nx

nx_galx_shell_jslint.nx source

↩ module page · 192 lines · 8356 B

1// nx_galx_shell_jslint.nx -- SOVEREIGN JS syntax sanity guard for the gallery shell's inline <script>. 2// 3// WHY: the gallery JS broke (operator: "its not working", browser: "Uncaught SyntaxError: Invalid or unexpected 4// token") because an HTML attribute with single quotes (href='#') was embedded inside a single-quoted JS string 5// (lb.innerHTML='...href='#'...') -> the string terminated early and the '#' was a stray token. There is NO JS 6// engine in this sovereign environment, so nothing caught it. This is a tiny string state-machine that catches 7// exactly that class (premature string termination -> stray token after a string) plus unbalanced strings/ 8// brackets. NOT a full JS parser -- a focused, no-false-positive guard for hand-written shell JS. 9// 10// LIMITATION (documented): regex literals are treated as division; this is balance-neutral and produces no false 11// positive for regexes without embedded quotes (the gallery's only regex is /[<&>]/g). A regex literal containing 12// a quote would need real regex handling -- add it the day the shell needs one. 13// license_tier: ORIGINAL 14import "nx_syscalls.nx" 15const K_MAGIC_2048: i64 = 2048 16 17// Scan a JS source [0..n). Returns the byte offset of the first syntax problem, or -1 if clean. 18// States: 0=CODE 1=SQ('...') 2=DQ("...") 3=TMPL(`...`) 4=line-comment 5=block-comment. 19func jslint_scan_script(s: *u8, n: i64) -> i64 { 20 var st: i64 = 0 21 var depth: i64 = 0 // ()[]{} nesting 22 var jcs: i64 = 0 // 1 = we just closed a string; the next non-space CODE char must be an operator/punct 23 var i: i64 = 0 24 while i < n { 25 let c: i64 = s[i] as i64 26 if st == 0 { 27 if c == 47 { // '/' 28 if i + 1 < n { 29 let d: i64 = s[i+1] as i64 30 if d == 47 { st = 4; i = i + 2; jcs = 0; continue } 31 if d == 42 { st = 5; i = i + 2; jcs = 0; continue } 32 } 33 jcs = 0; i = i + 1; continue // division operator 34 } 35 if c == 39 { st = 1; jcs = 0; i = i + 1; continue } 36 if c == 34 { st = 2; jcs = 0; i = i + 1; continue } 37 if c == 96 { st = 3; jcs = 0; i = i + 1; continue } 38 if c == 40 { depth = depth + 1; jcs = 0; i = i + 1; continue } // ( 39 if c == 91 { depth = depth + 1; jcs = 0; i = i + 1; continue } // [ 40 if c == 123 { depth = depth + 1; jcs = 0; i = i + 1; continue } // { 41 if c == 41 { depth = depth - 1; if depth < 0 { return i } jcs = 0; i = i + 1; continue } // ) 42 if c == 93 { depth = depth - 1; if depth < 0 { return i } jcs = 0; i = i + 1; continue } // ] 43 if c == 125 { depth = depth - 1; if depth < 0 { return i } jcs = 0; i = i + 1; continue } // } 44 if c == 32 { i = i + 1; continue } // keep jcs across whitespace 45 if c == 9 { i = i + 1; continue } 46 if c == 10 { i = i + 1; continue } 47 if c == 13 { i = i + 1; continue } 48 if jcs == 1 { 49 // stray identifier-start / digit / '#' / '@' immediately after a string with no operator = the bug 50 if c >= 65 { if c <= 90 { return i } } 51 if c >= 97 { if c <= 122 { return i } } 52 if c == 95 { return i } 53 if c == 36 { return i } 54 if c == 35 { return i } 55 if c == 64 { return i } 56 if c >= 48 { if c <= 57 { return i } } 57 } 58 jcs = 0; i = i + 1; continue 59 } 60 if st == 1 { 61 if c == 92 { i = i + 2; continue } 62 if c == 39 { st = 0; jcs = 1; i = i + 1; continue } 63 if c == 10 { return i } // newline inside a '...' string = unterminated 64 i = i + 1; continue 65 } 66 if st == 2 { 67 if c == 92 { i = i + 2; continue } 68 if c == 34 { st = 0; jcs = 1; i = i + 1; continue } 69 if c == 10 { return i } 70 i = i + 1; continue 71 } 72 if st == 3 { 73 if c == 92 { i = i + 2; continue } 74 if c == 96 { st = 0; jcs = 1; i = i + 1; continue } 75 i = i + 1; continue 76 } 77 if st == 4 { 78 if c == 10 { st = 0 } 79 i = i + 1; continue 80 } 81 if st == 5 { 82 if c == 42 { if i + 1 < n { if s[i+1] == (47 as u8) { st = 0; i = i + 2; continue } } } 83 i = i + 1; continue 84 } 85 i = i + 1 86 } 87 if st != 0 { return n } // unterminated string or comment 88 if depth != 0 { return n } // unbalanced ()[]{} 89 return 0 - 1 90} 91 92// Locate the FIRST <script>...</script> body in html[0..n). Returns body length (>=0) and sets *out_start to the 93// body offset; returns -1 if no <script> found. 94func jslint_find_script(html: *u8, n: i64, out_start: *i64) -> i64 { 95 var i: i64 = 0 96 while i + 8 <= n { 97 if html[i]==(60 as u8) { if html[i+1]==(115 as u8) { if html[i+2]==(99 as u8) { if html[i+3]==(114 as u8) { 98 if html[i+4]==(105 as u8) { if html[i+5]==(112 as u8) { if html[i+6]==(116 as u8) { 99 // found "<script"; skip to the next '>' 100 var j: i64 = i + 7 101 while j < n { if html[j]==(62 as u8) { j = j + 1; break } j = j + 1 } 102 let bs: i64 = j 103 // find "</script" 104 var k: i64 = bs 105 while k + 8 <= n { 106 if html[k]==(60 as u8) { if html[k+1]==(47 as u8) { if html[k+2]==(115 as u8) { if html[k+3]==(99 as u8) { 107 out_start[0] = bs 108 return k - bs 109 } } } } 110 k = k + 1 111 } 112 return 0 - 1 113 } } } } } } } 114 i = i + 1 115 } 116 return 0 - 1 117} 118 119// Returns 1 if html[0..n) is a COMPLETE page: starts with "<!doctype" (any case) AND the last non-space run is 120// "</html>". Catches a truncated / short-write shell (a blank or half-rendered gallery = a hidden failure). 121func shell_complete(html: *u8, n: i64) -> i64 { 122 if n < 16 { return 0 } 123 // find "<!doctype" within the first 2048 bytes (skips any HTTP headers the serve prepends to the body) 124 let dt: *u8 = "<!doctype" as *u8 125 var ds: i64 = 0 - 1 126 var p: i64 = 0 127 var plim: i64 = n - 9 128 if plim > K_MAGIC_2048 { plim = K_MAGIC_2048 } 129 while p <= plim { 130 var m: i64 = 1 131 var q: i64 = 0 132 while q < 9 { 133 var a: i64 = html[p+q] as i64 134 if a >= 65 { if a <= 90 { a = a + 32 } } 135 if a != (dt[q] as i64) { m = 0; q = 9 } 136 q = q + 1 137 } 138 if m == 1 { ds = p; p = plim + 1 } else { p = p + 1 } 139 } 140 if ds < 0 { return 0 } 141 var e: i64 = n 142 var go: i64 = 1 143 while go == 1 { 144 if e <= 0 { go = 0 } else { 145 let c: i64 = html[e-1] as i64 146 var ws: i64 = 0 147 if c == 32 { ws = 1 } 148 if c == 10 { ws = 1 } 149 if c == 13 { ws = 1 } 150 if c == 9 { ws = 1 } 151 if ws == 1 { e = e - 1 } else { go = 0 } 152 } 153 } 154 if e < 7 { return 0 } 155 let ht: *u8 = "</html>" as *u8 156 var j: i64 = 0 157 while j < 7 { 158 var a2: i64 = html[e-7+j] as i64 159 if a2 >= 65 { if a2 <= 90 { a2 = a2 + 32 } } 160 if a2 != (ht[j] as i64) { return 0 } 161 j = j + 1 162 } 163 return 1 164} 165 166// Returns 1 if the FIRST <style>...</style> block has balanced { } (0 if no style block or unbalanced braces). 167func shell_style_balanced(html: *u8, n: i64) -> i64 { 168 var i: i64 = 0 169 var bs: i64 = 0 - 1 170 while i + 6 <= n { 171 if html[i]==(60 as u8) { if html[i+1]==(115 as u8) { if html[i+2]==(116 as u8) { if html[i+3]==(121 as u8) { if html[i+4]==(108 as u8) { if html[i+5]==(101 as u8) { 172 var j: i64 = i + 6 173 while j < n { if html[j]==(62 as u8) { j = j + 1; break } j = j + 1 } 174 bs = j 175 i = n 176 } } } } } } 177 i = i + 1 178 } 179 if bs < 0 { return 0 } 180 var depth: i64 = 0 181 var k: i64 = bs 182 while k + 7 <= n { 183 if html[k]==(60 as u8) { if html[k+1]==(47 as u8) { if html[k+2]==(115 as u8) { if html[k+3]==(116 as u8) { 184 if depth == 0 { return 1 } 185 return 0 186 } } } } 187 if html[k]==(123 as u8) { depth = depth + 1 } 188 if html[k]==(125 as u8) { depth = depth - 1; if depth < 0 { return 0 } } 189 k = k + 1 190 } 191 return 0 192}