Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: JS bridge — proper export stripping + byte length for QuickJS eval

Strip all `export` keywords (both `export { ... };` and `export function`)
by filling with spaces instead of deleting (preserves line numbers).
Use UTF-8 byte length instead of char length for C interop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+27 -19
+27 -19
fedac/native/cl/js-bridge.lisp
··· 192 192 (unless code 193 193 (format *error-output* "[js-bridge] Piece not found: ~A~%" path) 194 194 (return-from js-load-piece nil)) 195 - ;; Wrap piece code so `export { ... }` becomes a no-op. 196 - ;; We eval a script that replaces `export` with a harmless function, 197 - ;; then evals the piece code, then exposes lifecycle functions. 198 - (let ((wrapper (format nil "~ 199 - // Make export a no-op for module compatibility~%~ 200 - var __orig_code = true;~%~ 201 - ~A~%~ 195 + ;; Strip export statement and eval as global script. 196 + ;; Use byte length (not char length) for C interop. 197 + (let* ((stripped (let ((pos (search "export " code))) 198 + (if pos 199 + ;; Replace all `export ` with empty to handle: 200 + ;; `export { boot, paint, act, sim };` and 201 + ;; `export function boot(...)` etc. 202 + (let ((s (copy-seq code)) 203 + (p pos)) 204 + (loop while p do 205 + ;; Find closing ; for `export {` 206 + (if (and (< (+ p 7) (length s)) 207 + (char= (char s (+ p 7)) #\{)) 208 + ;; export { ... }; → blank the whole thing 209 + (let ((semi (position #\; s :start p))) 210 + (when semi 211 + (fill s #\Space :start p :end (1+ semi)))) 212 + ;; export function/const/let → just blank "export " 213 + (fill s #\Space :start p :end (min (+ p 7) (length s)))) 214 + (setf p (search "export " s :start2 (1+ p)))) 215 + s) 216 + code))) 217 + (wrapper (format nil "~A~%~ 202 218 // Expose lifecycle functions as globals~%~ 203 219 if (typeof boot === 'function') globalThis.__piece_boot = boot;~%~ 204 220 if (typeof paint === 'function') globalThis.__piece_paint = paint;~%~ 205 221 if (typeof act === 'function') globalThis.__piece_act = act;~%~ 206 222 if (typeof sim === 'function') globalThis.__piece_sim = sim;~%~ 207 223 if (typeof leave === 'function') globalThis.__piece_leave = leave;~%" 208 - ;; Remove the export line via simple string search 209 - (let ((s code) 210 - (pos (search "export {" code))) 211 - (if pos 212 - ;; Find the semicolon after the closing brace 213 - (let ((end (position #\; s :start pos))) 214 - (if end 215 - (concatenate 'string (subseq s 0 pos) (subseq s (1+ end))) 216 - (subseq s 0 pos))) 217 - s))))) 218 - (let ((rc (ac-native.quickjs:qjs-eval *ctx* wrapper (length wrapper) 219 - path 0))) 224 + stripped)) 225 + (bytes (sb-ext:string-to-octets wrapper :external-format :utf-8))) 226 + (let ((rc (ac-native.quickjs:qjs-eval *ctx* wrapper (length bytes) 227 + path 0))) ; 0 = JS_EVAL_TYPE_GLOBAL 220 228 (when (= rc -1) 221 229 (format *error-output* "[js-bridge] Failed to load ~A~%" path) 222 230 (return-from js-load-piece nil))))