@scriptc/runtime 0.0.0 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (416) hide show
  1. package/LICENSE +202 -0
  2. package/package.json +16 -6
  3. package/src/scr_array.c +488 -0
  4. package/src/scr_assert.c +1393 -0
  5. package/src/scr_async.c +2593 -0
  6. package/src/scr_async_dyn.c +607 -0
  7. package/src/scr_bytes.c +1425 -0
  8. package/src/scr_bytes_io.c +161 -0
  9. package/src/scr_child.c +3587 -0
  10. package/src/scr_closure.c +214 -0
  11. package/src/scr_console.c +143 -0
  12. package/src/scr_cycle.c +218 -0
  13. package/src/scr_dc.c +805 -0
  14. package/src/scr_dgram.c +1007 -0
  15. package/src/scr_dyn_handle.c +154 -0
  16. package/src/scr_dyn_invoke.c +644 -0
  17. package/src/scr_error.c +332 -0
  18. package/src/scr_events.c +714 -0
  19. package/src/scr_events_emitter.c +699 -0
  20. package/src/scr_exception.c +242 -0
  21. package/src/scr_fetch.c +1130 -0
  22. package/src/scr_fetch_curl.c +654 -0
  23. package/src/scr_http.c +3482 -0
  24. package/src/scr_http2.c +3030 -0
  25. package/src/scr_inspect.c +803 -0
  26. package/src/scr_inspect_island.c +51 -0
  27. package/src/scr_island.c +9363 -0
  28. package/src/scr_json.c +2151 -0
  29. package/src/scr_lib.c +3612 -0
  30. package/src/scr_loop_epoll.c +241 -0
  31. package/src/scr_loop_kqueue.c +121 -0
  32. package/src/scr_loop_wsapoll.c +208 -0
  33. package/src/scr_map.c +591 -0
  34. package/src/scr_net.c +3017 -0
  35. package/src/scr_net_island.c +458 -0
  36. package/src/scr_number.c +115 -0
  37. package/src/scr_object.c +25 -0
  38. package/src/scr_path.c +1266 -0
  39. package/src/scr_platform.h +119 -0
  40. package/src/scr_readline.c +287 -0
  41. package/src/scr_regex.c +1080 -0
  42. package/src/scr_runtime.h +5046 -0
  43. package/src/scr_stream.c +2877 -0
  44. package/src/scr_string.c +1264 -0
  45. package/src/scr_symbol.c +132 -0
  46. package/src/scr_test.c +662 -0
  47. package/src/scr_tls.c +1520 -0
  48. package/src/scr_union.c +105 -0
  49. package/src/scr_url.c +911 -0
  50. package/src/scr_url_internal.h +29 -0
  51. package/src/scr_url_params.c +561 -0
  52. package/src/scr_watch.c +568 -0
  53. package/src/scr_web.c +1778 -0
  54. package/src/scr_win.c +76 -0
  55. package/src/scr_zlib.c +197 -0
  56. package/src/scr_zlib_island.c +15 -0
  57. package/vendor/README.md +52 -0
  58. package/vendor/curl/COPYRIGHT +534 -0
  59. package/vendor/curl/include/curl/curl.h +3214 -0
  60. package/vendor/curl/include/curl/curlver.h +79 -0
  61. package/vendor/curl/include/curl/easy.h +125 -0
  62. package/vendor/curl/include/curl/header.h +74 -0
  63. package/vendor/curl/include/curl/mprintf.h +52 -0
  64. package/vendor/curl/include/curl/multi.h +460 -0
  65. package/vendor/curl/include/curl/options.h +70 -0
  66. package/vendor/curl/include/curl/stdcheaders.h +35 -0
  67. package/vendor/curl/include/curl/system.h +508 -0
  68. package/vendor/curl/include/curl/typecheck-gcc.h +716 -0
  69. package/vendor/curl/include/curl/urlapi.h +149 -0
  70. package/vendor/curl/include/curl/websockets.h +84 -0
  71. package/vendor/mbedtls/LICENSE +553 -0
  72. package/vendor/mbedtls/include/CMakeLists.txt +22 -0
  73. package/vendor/mbedtls/include/mbedtls/aes.h +631 -0
  74. package/vendor/mbedtls/include/mbedtls/aria.h +343 -0
  75. package/vendor/mbedtls/include/mbedtls/asn1.h +642 -0
  76. package/vendor/mbedtls/include/mbedtls/asn1write.h +390 -0
  77. package/vendor/mbedtls/include/mbedtls/base64.h +82 -0
  78. package/vendor/mbedtls/include/mbedtls/bignum.h +1088 -0
  79. package/vendor/mbedtls/include/mbedtls/block_cipher.h +76 -0
  80. package/vendor/mbedtls/include/mbedtls/build_info.h +194 -0
  81. package/vendor/mbedtls/include/mbedtls/camellia.h +305 -0
  82. package/vendor/mbedtls/include/mbedtls/ccm.h +526 -0
  83. package/vendor/mbedtls/include/mbedtls/chacha20.h +209 -0
  84. package/vendor/mbedtls/include/mbedtls/chachapoly.h +351 -0
  85. package/vendor/mbedtls/include/mbedtls/check_config.h +1149 -0
  86. package/vendor/mbedtls/include/mbedtls/cipher.h +1250 -0
  87. package/vendor/mbedtls/include/mbedtls/cmac.h +246 -0
  88. package/vendor/mbedtls/include/mbedtls/compat-2.x.h +46 -0
  89. package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h +578 -0
  90. package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h +873 -0
  91. package/vendor/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h +359 -0
  92. package/vendor/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h +145 -0
  93. package/vendor/mbedtls/include/mbedtls/config_adjust_ssl.h +91 -0
  94. package/vendor/mbedtls/include/mbedtls/config_adjust_x509.h +35 -0
  95. package/vendor/mbedtls/include/mbedtls/config_psa.h +61 -0
  96. package/vendor/mbedtls/include/mbedtls/constant_time.h +36 -0
  97. package/vendor/mbedtls/include/mbedtls/ctr_drbg.h +596 -0
  98. package/vendor/mbedtls/include/mbedtls/debug.h +156 -0
  99. package/vendor/mbedtls/include/mbedtls/des.h +385 -0
  100. package/vendor/mbedtls/include/mbedtls/dhm.h +972 -0
  101. package/vendor/mbedtls/include/mbedtls/ecdh.h +455 -0
  102. package/vendor/mbedtls/include/mbedtls/ecdsa.h +674 -0
  103. package/vendor/mbedtls/include/mbedtls/ecjpake.h +298 -0
  104. package/vendor/mbedtls/include/mbedtls/ecp.h +1517 -0
  105. package/vendor/mbedtls/include/mbedtls/entropy.h +274 -0
  106. package/vendor/mbedtls/include/mbedtls/error.h +201 -0
  107. package/vendor/mbedtls/include/mbedtls/gcm.h +387 -0
  108. package/vendor/mbedtls/include/mbedtls/hkdf.h +124 -0
  109. package/vendor/mbedtls/include/mbedtls/hmac_drbg.h +434 -0
  110. package/vendor/mbedtls/include/mbedtls/lms.h +440 -0
  111. package/vendor/mbedtls/include/mbedtls/mbedtls_config.h +4446 -0
  112. package/vendor/mbedtls/include/mbedtls/md.h +526 -0
  113. package/vendor/mbedtls/include/mbedtls/md5.h +190 -0
  114. package/vendor/mbedtls/include/mbedtls/memory_buffer_alloc.h +142 -0
  115. package/vendor/mbedtls/include/mbedtls/net_sockets.h +299 -0
  116. package/vendor/mbedtls/include/mbedtls/nist_kw.h +166 -0
  117. package/vendor/mbedtls/include/mbedtls/oid.h +727 -0
  118. package/vendor/mbedtls/include/mbedtls/pem.h +160 -0
  119. package/vendor/mbedtls/include/mbedtls/pk.h +1303 -0
  120. package/vendor/mbedtls/include/mbedtls/pkcs12.h +186 -0
  121. package/vendor/mbedtls/include/mbedtls/pkcs5.h +198 -0
  122. package/vendor/mbedtls/include/mbedtls/pkcs7.h +252 -0
  123. package/vendor/mbedtls/include/mbedtls/platform.h +516 -0
  124. package/vendor/mbedtls/include/mbedtls/platform_time.h +79 -0
  125. package/vendor/mbedtls/include/mbedtls/platform_util.h +247 -0
  126. package/vendor/mbedtls/include/mbedtls/poly1305.h +168 -0
  127. package/vendor/mbedtls/include/mbedtls/private_access.h +20 -0
  128. package/vendor/mbedtls/include/mbedtls/psa_util.h +207 -0
  129. package/vendor/mbedtls/include/mbedtls/ripemd160.h +136 -0
  130. package/vendor/mbedtls/include/mbedtls/rsa.h +1170 -0
  131. package/vendor/mbedtls/include/mbedtls/sha1.h +219 -0
  132. package/vendor/mbedtls/include/mbedtls/sha256.h +200 -0
  133. package/vendor/mbedtls/include/mbedtls/sha3.h +172 -0
  134. package/vendor/mbedtls/include/mbedtls/sha512.h +208 -0
  135. package/vendor/mbedtls/include/mbedtls/ssl.h +5887 -0
  136. package/vendor/mbedtls/include/mbedtls/ssl_cache.h +187 -0
  137. package/vendor/mbedtls/include/mbedtls/ssl_ciphersuites.h +482 -0
  138. package/vendor/mbedtls/include/mbedtls/ssl_cookie.h +106 -0
  139. package/vendor/mbedtls/include/mbedtls/ssl_ticket.h +199 -0
  140. package/vendor/mbedtls/include/mbedtls/threading.h +167 -0
  141. package/vendor/mbedtls/include/mbedtls/timing.h +94 -0
  142. package/vendor/mbedtls/include/mbedtls/version.h +78 -0
  143. package/vendor/mbedtls/include/mbedtls/x509.h +500 -0
  144. package/vendor/mbedtls/include/mbedtls/x509_crl.h +184 -0
  145. package/vendor/mbedtls/include/mbedtls/x509_crt.h +1208 -0
  146. package/vendor/mbedtls/include/mbedtls/x509_csr.h +382 -0
  147. package/vendor/mbedtls/include/psa/build_info.h +20 -0
  148. package/vendor/mbedtls/include/psa/crypto.h +4998 -0
  149. package/vendor/mbedtls/include/psa/crypto_adjust_auto_enabled.h +31 -0
  150. package/vendor/mbedtls/include/psa/crypto_adjust_config_dependencies.h +51 -0
  151. package/vendor/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h +101 -0
  152. package/vendor/mbedtls/include/psa/crypto_adjust_config_synonyms.h +49 -0
  153. package/vendor/mbedtls/include/psa/crypto_builtin_composites.h +214 -0
  154. package/vendor/mbedtls/include/psa/crypto_builtin_key_derivation.h +118 -0
  155. package/vendor/mbedtls/include/psa/crypto_builtin_primitives.h +114 -0
  156. package/vendor/mbedtls/include/psa/crypto_compat.h +230 -0
  157. package/vendor/mbedtls/include/psa/crypto_config.h +145 -0
  158. package/vendor/mbedtls/include/psa/crypto_driver_common.h +44 -0
  159. package/vendor/mbedtls/include/psa/crypto_driver_contexts_composites.h +151 -0
  160. package/vendor/mbedtls/include/psa/crypto_driver_contexts_key_derivation.h +52 -0
  161. package/vendor/mbedtls/include/psa/crypto_driver_contexts_primitives.h +105 -0
  162. package/vendor/mbedtls/include/psa/crypto_extra.h +2145 -0
  163. package/vendor/mbedtls/include/psa/crypto_legacy.h +88 -0
  164. package/vendor/mbedtls/include/psa/crypto_platform.h +102 -0
  165. package/vendor/mbedtls/include/psa/crypto_se_driver.h +1383 -0
  166. package/vendor/mbedtls/include/psa/crypto_sizes.h +1319 -0
  167. package/vendor/mbedtls/include/psa/crypto_struct.h +527 -0
  168. package/vendor/mbedtls/include/psa/crypto_types.h +508 -0
  169. package/vendor/mbedtls/include/psa/crypto_values.h +2782 -0
  170. package/vendor/mbedtls/library/aes.c +2294 -0
  171. package/vendor/mbedtls/library/aesce.c +624 -0
  172. package/vendor/mbedtls/library/aesce.h +136 -0
  173. package/vendor/mbedtls/library/aesni.c +846 -0
  174. package/vendor/mbedtls/library/aesni.h +162 -0
  175. package/vendor/mbedtls/library/alignment.h +704 -0
  176. package/vendor/mbedtls/library/aria.c +969 -0
  177. package/vendor/mbedtls/library/asn1parse.c +468 -0
  178. package/vendor/mbedtls/library/asn1write.c +440 -0
  179. package/vendor/mbedtls/library/base64.c +322 -0
  180. package/vendor/mbedtls/library/base64_internal.h +45 -0
  181. package/vendor/mbedtls/library/bignum.c +2583 -0
  182. package/vendor/mbedtls/library/bignum_core.c +1240 -0
  183. package/vendor/mbedtls/library/bignum_core.h +872 -0
  184. package/vendor/mbedtls/library/bignum_core_invasive.h +38 -0
  185. package/vendor/mbedtls/library/bignum_internal.h +122 -0
  186. package/vendor/mbedtls/library/bignum_mod.c +394 -0
  187. package/vendor/mbedtls/library/bignum_mod.h +452 -0
  188. package/vendor/mbedtls/library/bignum_mod_raw.c +276 -0
  189. package/vendor/mbedtls/library/bignum_mod_raw.h +416 -0
  190. package/vendor/mbedtls/library/bignum_mod_raw_invasive.h +34 -0
  191. package/vendor/mbedtls/library/block_cipher.c +207 -0
  192. package/vendor/mbedtls/library/block_cipher_internal.h +99 -0
  193. package/vendor/mbedtls/library/bn_mul.h +1094 -0
  194. package/vendor/mbedtls/library/camellia.c +1058 -0
  195. package/vendor/mbedtls/library/ccm.c +777 -0
  196. package/vendor/mbedtls/library/chacha20.c +563 -0
  197. package/vendor/mbedtls/library/chacha20_internal.h +23 -0
  198. package/vendor/mbedtls/library/chachapoly.c +487 -0
  199. package/vendor/mbedtls/library/check_crypto_config.h +136 -0
  200. package/vendor/mbedtls/library/cipher.c +1712 -0
  201. package/vendor/mbedtls/library/cipher_invasive.h +28 -0
  202. package/vendor/mbedtls/library/cipher_wrap.c +2482 -0
  203. package/vendor/mbedtls/library/cipher_wrap.h +178 -0
  204. package/vendor/mbedtls/library/cmac.c +1067 -0
  205. package/vendor/mbedtls/library/common.h +487 -0
  206. package/vendor/mbedtls/library/constant_time.c +247 -0
  207. package/vendor/mbedtls/library/constant_time_impl.h +541 -0
  208. package/vendor/mbedtls/library/constant_time_internal.h +579 -0
  209. package/vendor/mbedtls/library/ctr.h +35 -0
  210. package/vendor/mbedtls/library/ctr_drbg.c +1016 -0
  211. package/vendor/mbedtls/library/debug.c +475 -0
  212. package/vendor/mbedtls/library/debug_internal.h +185 -0
  213. package/vendor/mbedtls/library/des.c +1042 -0
  214. package/vendor/mbedtls/library/dhm.c +700 -0
  215. package/vendor/mbedtls/library/ecdh.c +696 -0
  216. package/vendor/mbedtls/library/ecdsa.c +858 -0
  217. package/vendor/mbedtls/library/ecjpake.c +1216 -0
  218. package/vendor/mbedtls/library/ecp.c +3674 -0
  219. package/vendor/mbedtls/library/ecp_curves.c +6125 -0
  220. package/vendor/mbedtls/library/ecp_curves_new.c +9 -0
  221. package/vendor/mbedtls/library/ecp_internal_alt.h +287 -0
  222. package/vendor/mbedtls/library/ecp_invasive.h +305 -0
  223. package/vendor/mbedtls/library/entropy.c +680 -0
  224. package/vendor/mbedtls/library/entropy_poll.c +233 -0
  225. package/vendor/mbedtls/library/entropy_poll.h +64 -0
  226. package/vendor/mbedtls/library/error.c +878 -0
  227. package/vendor/mbedtls/library/gcm.c +1330 -0
  228. package/vendor/mbedtls/library/hkdf.c +161 -0
  229. package/vendor/mbedtls/library/hmac_drbg.c +633 -0
  230. package/vendor/mbedtls/library/lmots.c +789 -0
  231. package/vendor/mbedtls/library/lmots.h +288 -0
  232. package/vendor/mbedtls/library/lms.c +778 -0
  233. package/vendor/mbedtls/library/md.c +1108 -0
  234. package/vendor/mbedtls/library/md5.c +426 -0
  235. package/vendor/mbedtls/library/md_psa.h +26 -0
  236. package/vendor/mbedtls/library/md_wrap.h +46 -0
  237. package/vendor/mbedtls/library/memory_buffer_alloc.c +751 -0
  238. package/vendor/mbedtls/library/mps_common.h +181 -0
  239. package/vendor/mbedtls/library/mps_error.h +89 -0
  240. package/vendor/mbedtls/library/mps_reader.c +538 -0
  241. package/vendor/mbedtls/library/mps_reader.h +366 -0
  242. package/vendor/mbedtls/library/mps_trace.c +112 -0
  243. package/vendor/mbedtls/library/mps_trace.h +154 -0
  244. package/vendor/mbedtls/library/net_sockets.c +694 -0
  245. package/vendor/mbedtls/library/nist_kw.c +729 -0
  246. package/vendor/mbedtls/library/oid.c +1166 -0
  247. package/vendor/mbedtls/library/padlock.c +157 -0
  248. package/vendor/mbedtls/library/padlock.h +111 -0
  249. package/vendor/mbedtls/library/pem.c +554 -0
  250. package/vendor/mbedtls/library/pk.c +1602 -0
  251. package/vendor/mbedtls/library/pk_ecc.c +261 -0
  252. package/vendor/mbedtls/library/pk_internal.h +241 -0
  253. package/vendor/mbedtls/library/pk_wrap.c +1618 -0
  254. package/vendor/mbedtls/library/pk_wrap.h +138 -0
  255. package/vendor/mbedtls/library/pkcs12.c +437 -0
  256. package/vendor/mbedtls/library/pkcs5.c +500 -0
  257. package/vendor/mbedtls/library/pkcs7.c +787 -0
  258. package/vendor/mbedtls/library/pkparse.c +1392 -0
  259. package/vendor/mbedtls/library/pkwrite.c +631 -0
  260. package/vendor/mbedtls/library/pkwrite.h +121 -0
  261. package/vendor/mbedtls/library/platform.c +402 -0
  262. package/vendor/mbedtls/library/platform_util.c +258 -0
  263. package/vendor/mbedtls/library/poly1305.c +492 -0
  264. package/vendor/mbedtls/library/psa_crypto.c +9532 -0
  265. package/vendor/mbedtls/library/psa_crypto_aead.c +646 -0
  266. package/vendor/mbedtls/library/psa_crypto_aead.h +499 -0
  267. package/vendor/mbedtls/library/psa_crypto_cipher.c +747 -0
  268. package/vendor/mbedtls/library/psa_crypto_cipher.h +316 -0
  269. package/vendor/mbedtls/library/psa_crypto_client.c +22 -0
  270. package/vendor/mbedtls/library/psa_crypto_core.h +983 -0
  271. package/vendor/mbedtls/library/psa_crypto_core_common.h +52 -0
  272. package/vendor/mbedtls/library/psa_crypto_driver_wrappers.h +2896 -0
  273. package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.c +256 -0
  274. package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.h +31 -0
  275. package/vendor/mbedtls/library/psa_crypto_ecp.c +594 -0
  276. package/vendor/mbedtls/library/psa_crypto_ecp.h +267 -0
  277. package/vendor/mbedtls/library/psa_crypto_ffdh.c +361 -0
  278. package/vendor/mbedtls/library/psa_crypto_ffdh.h +131 -0
  279. package/vendor/mbedtls/library/psa_crypto_hash.c +470 -0
  280. package/vendor/mbedtls/library/psa_crypto_hash.h +211 -0
  281. package/vendor/mbedtls/library/psa_crypto_invasive.h +92 -0
  282. package/vendor/mbedtls/library/psa_crypto_its.h +131 -0
  283. package/vendor/mbedtls/library/psa_crypto_mac.c +505 -0
  284. package/vendor/mbedtls/library/psa_crypto_mac.h +264 -0
  285. package/vendor/mbedtls/library/psa_crypto_pake.c +571 -0
  286. package/vendor/mbedtls/library/psa_crypto_pake.h +159 -0
  287. package/vendor/mbedtls/library/psa_crypto_random.c +181 -0
  288. package/vendor/mbedtls/library/psa_crypto_random.h +72 -0
  289. package/vendor/mbedtls/library/psa_crypto_random_impl.h +200 -0
  290. package/vendor/mbedtls/library/psa_crypto_rsa.c +714 -0
  291. package/vendor/mbedtls/library/psa_crypto_rsa.h +321 -0
  292. package/vendor/mbedtls/library/psa_crypto_se.c +373 -0
  293. package/vendor/mbedtls/library/psa_crypto_se.h +192 -0
  294. package/vendor/mbedtls/library/psa_crypto_slot_management.c +1137 -0
  295. package/vendor/mbedtls/library/psa_crypto_slot_management.h +344 -0
  296. package/vendor/mbedtls/library/psa_crypto_storage.c +481 -0
  297. package/vendor/mbedtls/library/psa_crypto_storage.h +392 -0
  298. package/vendor/mbedtls/library/psa_its_file.c +254 -0
  299. package/vendor/mbedtls/library/psa_util.c +614 -0
  300. package/vendor/mbedtls/library/psa_util_internal.h +100 -0
  301. package/vendor/mbedtls/library/ripemd160.c +490 -0
  302. package/vendor/mbedtls/library/rsa.c +3121 -0
  303. package/vendor/mbedtls/library/rsa_alt_helpers.c +455 -0
  304. package/vendor/mbedtls/library/rsa_alt_helpers.h +209 -0
  305. package/vendor/mbedtls/library/rsa_internal.h +188 -0
  306. package/vendor/mbedtls/library/sha1.c +480 -0
  307. package/vendor/mbedtls/library/sha256.c +983 -0
  308. package/vendor/mbedtls/library/sha3.c +721 -0
  309. package/vendor/mbedtls/library/sha512.c +1115 -0
  310. package/vendor/mbedtls/library/ssl_cache.c +410 -0
  311. package/vendor/mbedtls/library/ssl_ciphersuites.c +2050 -0
  312. package/vendor/mbedtls/library/ssl_ciphersuites_internal.h +154 -0
  313. package/vendor/mbedtls/library/ssl_client.c +1023 -0
  314. package/vendor/mbedtls/library/ssl_client.h +22 -0
  315. package/vendor/mbedtls/library/ssl_cookie.c +384 -0
  316. package/vendor/mbedtls/library/ssl_debug_helpers.h +85 -0
  317. package/vendor/mbedtls/library/ssl_debug_helpers_generated.c +251 -0
  318. package/vendor/mbedtls/library/ssl_misc.h +3198 -0
  319. package/vendor/mbedtls/library/ssl_msg.c +6619 -0
  320. package/vendor/mbedtls/library/ssl_ticket.c +556 -0
  321. package/vendor/mbedtls/library/ssl_tls.c +10234 -0
  322. package/vendor/mbedtls/library/ssl_tls12_client.c +3688 -0
  323. package/vendor/mbedtls/library/ssl_tls12_server.c +4311 -0
  324. package/vendor/mbedtls/library/ssl_tls13_client.c +3208 -0
  325. package/vendor/mbedtls/library/ssl_tls13_generic.c +1793 -0
  326. package/vendor/mbedtls/library/ssl_tls13_invasive.h +23 -0
  327. package/vendor/mbedtls/library/ssl_tls13_keys.c +1918 -0
  328. package/vendor/mbedtls/library/ssl_tls13_keys.h +668 -0
  329. package/vendor/mbedtls/library/ssl_tls13_server.c +3624 -0
  330. package/vendor/mbedtls/library/threading.c +193 -0
  331. package/vendor/mbedtls/library/threading_internal.h +28 -0
  332. package/vendor/mbedtls/library/timing.c +152 -0
  333. package/vendor/mbedtls/library/version.c +32 -0
  334. package/vendor/mbedtls/library/version_features.c +856 -0
  335. package/vendor/mbedtls/library/x509.c +1776 -0
  336. package/vendor/mbedtls/library/x509_create.c +570 -0
  337. package/vendor/mbedtls/library/x509_crl.c +708 -0
  338. package/vendor/mbedtls/library/x509_crt.c +3311 -0
  339. package/vendor/mbedtls/library/x509_csr.c +648 -0
  340. package/vendor/mbedtls/library/x509_internal.h +101 -0
  341. package/vendor/mbedtls/library/x509write.c +174 -0
  342. package/vendor/mbedtls/library/x509write_crt.c +688 -0
  343. package/vendor/mbedtls/library/x509write_csr.c +336 -0
  344. package/vendor/quickjs-ng/CMakeLists.txt +566 -0
  345. package/vendor/quickjs-ng/LICENSE +24 -0
  346. package/vendor/quickjs-ng/README.md +24 -0
  347. package/vendor/quickjs-ng/api-test.c +1195 -0
  348. package/vendor/quickjs-ng/builtin-array-fromasync.h +119 -0
  349. package/vendor/quickjs-ng/builtin-iterator-zip-keyed.h +332 -0
  350. package/vendor/quickjs-ng/builtin-iterator-zip.h +337 -0
  351. package/vendor/quickjs-ng/cutils.h +1998 -0
  352. package/vendor/quickjs-ng/dtoa.c +1619 -0
  353. package/vendor/quickjs-ng/dtoa.h +87 -0
  354. package/vendor/quickjs-ng/gen/function_source.c +81 -0
  355. package/vendor/quickjs-ng/gen/hello.c +53 -0
  356. package/vendor/quickjs-ng/gen/hello_module.c +106 -0
  357. package/vendor/quickjs-ng/gen/repl.c +3036 -0
  358. package/vendor/quickjs-ng/gen/standalone.c +323 -0
  359. package/vendor/quickjs-ng/gen/test_fib.c +81 -0
  360. package/vendor/quickjs-ng/libregexp-opcode.h +73 -0
  361. package/vendor/quickjs-ng/libregexp.c +3474 -0
  362. package/vendor/quickjs-ng/libregexp.h +101 -0
  363. package/vendor/quickjs-ng/libunicode-table.h +5173 -0
  364. package/vendor/quickjs-ng/libunicode.c +2069 -0
  365. package/vendor/quickjs-ng/libunicode.h +172 -0
  366. package/vendor/quickjs-ng/list.h +107 -0
  367. package/vendor/quickjs-ng/lre-test.c +52 -0
  368. package/vendor/quickjs-ng/qjs.c +762 -0
  369. package/vendor/quickjs-ng/qjsc.c +673 -0
  370. package/vendor/quickjs-ng/quickjs-atom.h +280 -0
  371. package/vendor/quickjs-ng/quickjs-c-atomics.h +54 -0
  372. package/vendor/quickjs-ng/quickjs-libc.c +5037 -0
  373. package/vendor/quickjs-ng/quickjs-libc.h +87 -0
  374. package/vendor/quickjs-ng/quickjs-opcode.h +377 -0
  375. package/vendor/quickjs-ng/quickjs.c +64041 -0
  376. package/vendor/quickjs-ng/quickjs.h +1447 -0
  377. package/vendor/quickjs-ng/run-test262.c +2374 -0
  378. package/vendor/quickjs-ng/unicode_gen.c +3121 -0
  379. package/vendor/quickjs-ng/unicode_gen_def.h +310 -0
  380. package/vendor/ryu/LICENSE-Boost +23 -0
  381. package/vendor/ryu/common.h +114 -0
  382. package/vendor/ryu/d2s.c +509 -0
  383. package/vendor/ryu/d2s_full_table.h +367 -0
  384. package/vendor/ryu/d2s_intrinsics.h +357 -0
  385. package/vendor/ryu/d2s_small_table.h +186 -0
  386. package/vendor/ryu/digit_table.h +35 -0
  387. package/vendor/ryu/ryu.h +46 -0
  388. package/vendor/zlib/LICENSE +22 -0
  389. package/vendor/zlib/adler32.c +164 -0
  390. package/vendor/zlib/compress.c +75 -0
  391. package/vendor/zlib/crc32.c +1049 -0
  392. package/vendor/zlib/crc32.h +9446 -0
  393. package/vendor/zlib/deflate.c +2139 -0
  394. package/vendor/zlib/deflate.h +377 -0
  395. package/vendor/zlib/gzclose.c +23 -0
  396. package/vendor/zlib/gzguts.h +214 -0
  397. package/vendor/zlib/gzlib.c +582 -0
  398. package/vendor/zlib/gzread.c +602 -0
  399. package/vendor/zlib/gzwrite.c +631 -0
  400. package/vendor/zlib/infback.c +628 -0
  401. package/vendor/zlib/inffast.c +320 -0
  402. package/vendor/zlib/inffast.h +11 -0
  403. package/vendor/zlib/inffixed.h +94 -0
  404. package/vendor/zlib/inflate.c +1526 -0
  405. package/vendor/zlib/inflate.h +126 -0
  406. package/vendor/zlib/inftrees.c +299 -0
  407. package/vendor/zlib/inftrees.h +62 -0
  408. package/vendor/zlib/trees.c +1117 -0
  409. package/vendor/zlib/trees.h +128 -0
  410. package/vendor/zlib/uncompr.c +85 -0
  411. package/vendor/zlib/zconf.h +543 -0
  412. package/vendor/zlib/zlib.h +1938 -0
  413. package/vendor/zlib/zutil.c +299 -0
  414. package/vendor/zlib/zutil.h +254 -0
  415. package/README.md +0 -3
  416. package/index.js +0 -2
package/src/scr_lib.c ADDED
@@ -0,0 +1,3612 @@
1
+ /* Standard library: the process global + synchronous node:fs (scr_runtime.h
2
+ * has the API contract). Everything here is called through compiler-emitted
3
+ * `libCall` IR.
4
+ *
5
+ * - process.argv is ONE interned array, built lazily on first read and
6
+ * retained per read — identity (`process.argv === process.argv`) and
7
+ * mutation persistence match Node's stable process.argv. The atexit
8
+ * cleanup registered by scr_lib_init releases the interned values before
9
+ * the RC audit runs (atexit is LIFO; scr_init registered the audit
10
+ * first), so they never count as leaks.
11
+ * - fs failures THROW through the exception cell (scr_throw_error — the
12
+ * payload is a catchable Error instance whose message is shaped like
13
+ * Node's fs error messages) and return a dummy; the compiler emits
14
+ * pending checks after every fs call (the MAY_THROW_LIB_FNS seed).
15
+ * scr_fs_exists never throws, like Node's existsSync.
16
+ */
17
+ #include "scr_runtime.h"
18
+
19
+ #include <ctype.h>
20
+ #include <dirent.h>
21
+ #include <errno.h>
22
+ #include <fcntl.h>
23
+ #include <limits.h>
24
+ #include <math.h>
25
+ #include <signal.h>
26
+ #include <stdio.h>
27
+ #include <stdlib.h>
28
+ #include <string.h>
29
+ #include <sys/stat.h>
30
+ #include <time.h>
31
+
32
+ #ifdef _WIN32
33
+ /* ── the Windows arm's system surface ─────────────────────────────────
34
+ * mingw-w64's CRT covers most of sync fs (open/read/write/stat/dirent —
35
+ * the seams below note what it lacks: d_type, lstat, two-arg mkdir,
36
+ * mkdtemp, O_SYNC) and the Win32 API covers the process/os surface
37
+ * (GetTempPathA, GetUserNameA, RtlGetVersion, GetConsoleScreenBufferInfo).
38
+ * What has NO arm yet is stubbed honestly at its seam: process.kill and
39
+ * getuid/getgid (needs-design: OpenProcess/TerminateProcess vs Node's
40
+ * uv_kill; no uids exist on Windows — Node omits the members there),
41
+ * setRawMode's raw arm (mechanical: SetConsoleMode).
42
+ * os.networkInterfaces HAS its arm: GetAdaptersAddresses below, libuv's
43
+ * exact row selection. */
44
+ #include <direct.h> /* _mkdir */
45
+ #include <io.h> /* _isatty, _access, open/read/write/close */
46
+ #include <process.h> /* getpid */
47
+ #include <unistd.h> /* mingw-w64 ships one: getcwd, access, isatty, ... */
48
+ #include <winsock2.h> /* BEFORE windows.h (which pulls winsock 1 otherwise) */
49
+ #include <ws2tcpip.h> /* inet_ntop, sockaddr_in6 */
50
+ #include <iphlpapi.h> /* GetAdaptersAddresses (os.networkInterfaces) */
51
+ #include <windows.h>
52
+ #include <lmcons.h> /* UNLEN for GetUserNameA */
53
+
54
+
55
+ /* CRT stat has no symlink view — lstat degrades to stat (divergence: a
56
+ * compiled binary never reports isSymbolicLink() true on Windows; Node
57
+ * does for real symlinks/junctions). Mechanical fix: GetFileAttributesW +
58
+ * FILE_ATTRIBUTE_REPARSE_POINT. */
59
+ #define lstat stat
60
+
61
+ /* Windows has no directory mode bits; the CRT mkdir takes one argument. */
62
+ #define scr_sys_mkdir(p, m) ((void)(m), mkdir(p))
63
+
64
+ /* openSync's "rs"/"sa" flags: no O_SYNC on the CRT — degrade to non-sync
65
+ * opens (Node on Windows maps O_SYNC to FILE_FLAG_WRITE_THROUGH; the
66
+ * difference is durability, not observable output). */
67
+ #define O_SYNC 0
68
+
69
+ #ifndef INET6_ADDRSTRLEN
70
+ #define INET6_ADDRSTRLEN 46 /* ws2tcpip.h's value; only sizes the row bufs */
71
+ #endif
72
+
73
+ #else /* !_WIN32 */
74
+
75
+ #include <arpa/inet.h>
76
+ #include <ifaddrs.h>
77
+ #include <net/if.h>
78
+ #include <netinet/in.h>
79
+ #include <pwd.h>
80
+ #include <sys/ioctl.h>
81
+ #include <sys/socket.h>
82
+ #include <sys/utsname.h>
83
+ #include <termios.h>
84
+ #include <unistd.h>
85
+ #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
86
+ #include <net/if_dl.h>
87
+ #elif defined(__linux__)
88
+ #include <netpacket/packet.h>
89
+ #endif
90
+
91
+ #define scr_sys_mkdir(p, m) mkdir((p), (m))
92
+
93
+ extern char **environ; /* env snapshot (scr_env_pairs) */
94
+
95
+ #endif /* _WIN32 */
96
+
97
+ /* O_BINARY: Windows-only (CRT text mode would translate \n on fd writes);
98
+ * zero elsewhere so the POSIX open flags are unchanged. */
99
+ #ifndef O_BINARY
100
+ #define O_BINARY 0
101
+ #endif
102
+
103
+ /* ── process ─────────────────────────────────────────────────────────── */
104
+
105
+ static int scr_lib_argc = 0;
106
+ static char **scr_lib_argv = NULL;
107
+ static ScrArr *scr_argv_arr = NULL; /* interned process.argv */
108
+ static ScrStr *scr_platform_str = NULL; /* interned process.platform */
109
+ static ScrStr *scr_exec_path_str = NULL; /* interned process.execPath */
110
+ static ScrStr *scr_arch_str = NULL; /* interned process.arch */
111
+ static ScrStr *scr_versions_node_str = NULL; /* interned process.versions.node */
112
+ static ScrStr *scr_versions_openssl_str = NULL; /* interned process.versions.openssl */
113
+
114
+ static void scr_lib_cleanup(void) {
115
+ scr_arr_release(scr_argv_arr);
116
+ scr_argv_arr = NULL;
117
+ scr_str_release(scr_platform_str);
118
+ scr_platform_str = NULL;
119
+ scr_str_release(scr_exec_path_str);
120
+ scr_exec_path_str = NULL;
121
+ scr_str_release(scr_arch_str);
122
+ scr_arch_str = NULL;
123
+ scr_str_release(scr_versions_node_str);
124
+ scr_versions_node_str = NULL;
125
+ scr_str_release(scr_versions_openssl_str);
126
+ scr_versions_openssl_str = NULL;
127
+ }
128
+
129
+ void scr_lib_init(int argc, char **argv) {
130
+ scr_lib_argc = argc;
131
+ scr_lib_argv = argv;
132
+ atexit(scr_lib_cleanup);
133
+ }
134
+
135
+ /* Raw argv accessors for the island's process shim (scr_island.c): the
136
+ * island's process.argv must match the static world's ["scriptc",
137
+ * argv[0], ...] shape exactly, so both build from the same stash. */
138
+ int scr_lib_arg_count(void) { return scr_lib_argc; }
139
+ const char *scr_lib_arg(int i) { return scr_lib_argv[i]; }
140
+
141
+ ScrArr *scr_process_argv(void) {
142
+ if (!scr_argv_arr) {
143
+ /* ["scriptc", argv[0], argv[1], ...]: positions and length line up
144
+ * with Node's [node-path, script-path, ...args]; the argv[0]/argv[1]
145
+ * VALUES diverge (SEMANTICS.md). */
146
+ scr_argv_arr = scr_arr_new(SCR_ELEM_STR, (size_t)scr_lib_argc + 1);
147
+ scr_arr_push_ref(scr_argv_arr, scr_str_new("scriptc", 7));
148
+ for (int i = 0; i < scr_lib_argc; i++) {
149
+ const char *a = scr_lib_argv[i];
150
+ scr_arr_push_ref(scr_argv_arr, scr_str_new(a, strlen(a)));
151
+ }
152
+ }
153
+ return scr_arr_retain(scr_argv_arr);
154
+ }
155
+
156
+ ScrStr *scr_process_platform(void) {
157
+ if (!scr_platform_str) {
158
+ #if defined(__APPLE__)
159
+ scr_platform_str = scr_str_new("darwin", 6);
160
+ #elif defined(__linux__)
161
+ scr_platform_str = scr_str_new("linux", 5);
162
+ #elif defined(_WIN32)
163
+ scr_platform_str = scr_str_new("win32", 5);
164
+ #else
165
+ scr_platform_str = scr_str_new("unknown", 7);
166
+ #endif
167
+ }
168
+ return scr_str_retain(scr_platform_str);
169
+ }
170
+
171
+ /* process.arch — the compiled binary's OWN architecture, spelled the way
172
+ * Node spells its own build's arch. Interned like process.platform. */
173
+ ScrStr *scr_process_arch(void) {
174
+ if (!scr_arch_str) {
175
+ #if defined(__aarch64__) || defined(_M_ARM64)
176
+ scr_arch_str = scr_str_new("arm64", 5);
177
+ #elif defined(__x86_64__) || defined(_M_X64)
178
+ scr_arch_str = scr_str_new("x64", 3);
179
+ #else
180
+ scr_arch_str = scr_str_new("unknown", 7);
181
+ #endif
182
+ }
183
+ return scr_str_retain(scr_arch_str);
184
+ }
185
+
186
+ /* process.versions.node — the runtime's Node COMPATIBILITY TARGET. There
187
+ * is no Node under a compiled binary; this is the version whose semantics
188
+ * SEMANTICS.md verifies the runtime against (divergence 60, the execPath
189
+ * stance: answer for the world that actually exists). */
190
+ #define SCR_NODE_COMPAT_VERSION "24.0.0"
191
+ ScrStr *scr_process_versions_node(void) {
192
+ if (!scr_versions_node_str) {
193
+ scr_versions_node_str =
194
+ scr_str_new(SCR_NODE_COMPAT_VERSION, sizeof(SCR_NODE_COMPAT_VERSION) - 1);
195
+ }
196
+ return scr_str_retain(scr_versions_node_str);
197
+ }
198
+
199
+ /* process.versions.openssl — the compat target's crypto-provider version
200
+ * string (the versions.node stance): Boolean(process.versions.openssl) is
201
+ * Node's own "is crypto available" idiom, and the runtime DOES ship a
202
+ * crypto module (mbedTLS-backed; unsupported members fence at their call
203
+ * sites). Reading undefined here made every hasCrypto test self-skip —
204
+ * measuring nothing (SEMANTICS.md; the divergence entry documents that
205
+ * the string names the compat target's OpenSSL, not a linked library). */
206
+ #define SCR_OPENSSL_COMPAT_VERSION "3.5.5"
207
+ ScrStr *scr_process_versions_openssl(void) {
208
+ if (!scr_versions_openssl_str) {
209
+ scr_versions_openssl_str =
210
+ scr_str_new(SCR_OPENSSL_COMPAT_VERSION, sizeof(SCR_OPENSSL_COMPAT_VERSION) - 1);
211
+ }
212
+ return scr_str_retain(scr_versions_openssl_str);
213
+ }
214
+
215
+ /* process.execPath — the running binary's own resolved absolute path,
216
+ * exactly what Node computes for ITS executable (uv_exepath + realpath):
217
+ * _NSGetExecutablePath on macOS, /proc/self/exe on Linux, argv[0] as the
218
+ * last resort. Interned like process.platform; +1 per read. */
219
+ ScrStr *scr_process_exec_path(void) {
220
+ if (!scr_exec_path_str) {
221
+ char raw[4096];
222
+ raw[0] = '\0';
223
+ #if defined(__APPLE__)
224
+ uint32_t size = sizeof(raw);
225
+ extern int _NSGetExecutablePath(char *buf, uint32_t *bufsize);
226
+ if (_NSGetExecutablePath(raw, &size) != 0) raw[0] = '\0';
227
+ #elif defined(__linux__)
228
+ ssize_t got = readlink("/proc/self/exe", raw, sizeof(raw) - 1);
229
+ if (got > 0) raw[got] = '\0';
230
+ else raw[0] = '\0';
231
+ #elif defined(_WIN32)
232
+ /* uv_exepath's source of truth on Windows; already absolute, and
233
+ * spelled with backslashes like Node's own execPath there. */
234
+ DWORD got = GetModuleFileNameA(NULL, raw, sizeof(raw) - 1);
235
+ raw[got < sizeof(raw) ? got : 0] = '\0';
236
+ #endif
237
+ if (raw[0] == '\0' && scr_lib_argc > 0) {
238
+ snprintf(raw, sizeof(raw), "%s", scr_lib_argv[0]);
239
+ }
240
+ char resolved[PATH_MAX];
241
+ #ifdef _WIN32
242
+ const char *use = _fullpath(resolved, raw, sizeof resolved) != NULL ? resolved : raw;
243
+ #else
244
+ const char *use = realpath(raw, resolved) != NULL ? resolved : raw;
245
+ #endif
246
+ scr_exec_path_str = scr_str_new(use, strlen(use));
247
+ }
248
+ return scr_str_retain(scr_exec_path_str);
249
+ }
250
+
251
+ ScrStr *scr_env_get(const ScrStr *name) {
252
+ /* ScrStr data is NUL-terminated (like the fs paths below). A fresh copy
253
+ * per read: getenv's buffer is not ours to alias, and Node's process.env
254
+ * reads snapshot the value too. Absent → NULL (the compiler's undefined
255
+ * arm), never a throw. */
256
+ #ifdef _WIN32
257
+ /* The WIN32 environment, not the CRT's startup snapshot — libuv's choice
258
+ * too, and the one CreateProcess children inherit. Case-insensitive,
259
+ * like Node's process.env on Windows. */
260
+ DWORD need = GetEnvironmentVariableA(name->data, NULL, 0);
261
+ if (need == 0) return NULL; /* absent (an empty value still needs its NUL) */
262
+ char *buf = malloc(need);
263
+ if (!buf) {
264
+ fputs("scriptc: out of memory\n", stderr);
265
+ abort();
266
+ }
267
+ DWORD got = GetEnvironmentVariableA(name->data, buf, need);
268
+ ScrStr *s = scr_str_new(buf, got);
269
+ free(buf);
270
+ return s;
271
+ #else
272
+ const char *v = getenv(name->data);
273
+ return v ? scr_str_new(v, strlen(v)) : NULL;
274
+ #endif
275
+ }
276
+
277
+ /* process.env.NAME = v — setenv(3): later scr_env_get reads and spawned
278
+ * children (posix_spawn inherits environ) observe the write, like Node.
279
+ * Both args borrowed (NUL-terminated ScrStr data); setenv copies. */
280
+ void scr_env_set(const ScrStr *name, const ScrStr *value) {
281
+ #ifdef _WIN32
282
+ /* The WIN32 environment (see scr_env_get): an empty value stays a
283
+ * present-but-empty variable, and children inherit the write. */
284
+ SetEnvironmentVariableA(name->data, value->data);
285
+ #else
286
+ setenv(name->data, value->data, 1);
287
+ #endif
288
+ }
289
+
290
+ /* `delete process.env.NAME` — unsetenv(3): later reads answer absent and
291
+ * spawned children lose the variable, like Node. Borrowed. */
292
+ void scr_env_unset(const ScrStr *name) {
293
+ #ifdef _WIN32
294
+ SetEnvironmentVariableA(name->data, NULL);
295
+ #else
296
+ unsetenv(name->data);
297
+ #endif
298
+ }
299
+
300
+ /* The whole environment as one fresh string[] of alternating
301
+ * [k0, v0, k1, v1, ...] entries in environ order — the raw material of the
302
+ * compiler's process.env snapshot record (insertion order = environ order,
303
+ * which is Node's own Object.keys(process.env) order). Entries without '='
304
+ * (not producible by setenv) are skipped; the value is everything after
305
+ * the FIRST '='. +1 array. */
306
+ ScrArr *scr_env_pairs(void) {
307
+ ScrArr *out = scr_arr_new(SCR_ELEM_STR, 0);
308
+ #ifdef _WIN32
309
+ /* The WIN32 environment block (see scr_env_get): NUL-separated
310
+ * "K=V" entries, double-NUL terminated. Entries whose first byte is
311
+ * '=' are the hidden per-drive cwd variables ("=C:=..."), which libuv
312
+ * (and so Node's process.env) also skips. */
313
+ char *block = GetEnvironmentStringsA();
314
+ if (block != NULL) {
315
+ for (char *e = block; *e != '\0'; e += strlen(e) + 1) {
316
+ const char *eq = strchr(e, '=');
317
+ if (!eq || eq == e) continue;
318
+ scr_arr_push_ref(out, scr_str_new(e, (size_t)(eq - e)));
319
+ scr_arr_push_ref(out, scr_str_new(eq + 1, strlen(eq + 1)));
320
+ }
321
+ FreeEnvironmentStringsA(block);
322
+ }
323
+ #else
324
+ for (char **e = environ; *e != NULL; e++) {
325
+ const char *eq = strchr(*e, '=');
326
+ if (!eq || eq == *e) continue;
327
+ scr_arr_push_ref(out, scr_str_new(*e, (size_t)(eq - *e)));
328
+ scr_arr_push_ref(out, scr_str_new(eq + 1, strlen(eq + 1)));
329
+ }
330
+ #endif
331
+ return out;
332
+ }
333
+
334
+ /* ── process.pid / process.getuid / process.kill ─────────────────────── */
335
+
336
+ double scr_process_pid(void) { return (double)getpid(); }
337
+
338
+ #ifdef _WIN32
339
+ /* No uids/gids exist on Windows: Node's process object simply has no
340
+ * getuid/getgid members there, so a call is the property-access TypeError
341
+ * below — thrown catchably, exactly what `process.getuid()` does under
342
+ * Windows Node. */
343
+ double scr_process_getuid(void) {
344
+ scr_throw_error_msg(SCR_ERR_TYPE, "process.getuid is not a function", 32);
345
+ return 0;
346
+ }
347
+
348
+ double scr_process_getgid(void) {
349
+ scr_throw_error_msg(SCR_ERR_TYPE, "process.getgid is not a function", 32);
350
+ return 0;
351
+ }
352
+ #else
353
+ double scr_process_getuid(void) { return (double)getuid(); }
354
+
355
+ double scr_process_getgid(void) { return (double)getgid(); }
356
+ #endif
357
+
358
+ /* Node's signal-name table (the names uv exposes), resolved to the HOST's
359
+ * numbers via the POSIX constants. Node accepts names with the SIG prefix
360
+ * only. On Windows the C runtime defines only the ANSI six (INT, ILL,
361
+ * ABRT, FPE, SEGV, TERM) plus SIGBREAK — libuv fills in the numbers below
362
+ * for the handful more it emulates, and Node's os.constants.signals shows
363
+ * exactly that union, so the same defines keep the two tables' Windows
364
+ * rows Node-identical; every other row #ifdefs away like SIGIO/SIGINFO
365
+ * always did. */
366
+ #ifdef _WIN32
367
+ #define SIGHUP 1 /* uv's emulated numbers (uv-win.h) */
368
+ #define SIGQUIT 3
369
+ #define SIGKILL 9
370
+ #define SIGWINCH 28
371
+ #endif
372
+
373
+ static int scr_signal_by_name(const char *name) {
374
+ static const struct { const char *name; int sig; } SIGS[] = {
375
+ {"SIGHUP", SIGHUP}, {"SIGINT", SIGINT}, {"SIGQUIT", SIGQUIT},
376
+ {"SIGILL", SIGILL}, {"SIGABRT", SIGABRT},
377
+ {"SIGIOT", SIGABRT}, {"SIGFPE", SIGFPE},
378
+ {"SIGKILL", SIGKILL}, {"SIGSEGV", SIGSEGV},
379
+ {"SIGTERM", SIGTERM}, {"SIGWINCH", SIGWINCH},
380
+ #ifdef SIGTRAP
381
+ {"SIGTRAP", SIGTRAP},
382
+ #endif
383
+ #ifdef SIGBUS
384
+ {"SIGBUS", SIGBUS},
385
+ #endif
386
+ #ifdef SIGUSR1
387
+ {"SIGUSR1", SIGUSR1}, {"SIGUSR2", SIGUSR2},
388
+ #endif
389
+ #ifdef SIGPIPE
390
+ {"SIGPIPE", SIGPIPE},
391
+ #endif
392
+ #ifdef SIGALRM
393
+ {"SIGALRM", SIGALRM},
394
+ #endif
395
+ #ifdef SIGCHLD
396
+ {"SIGCHLD", SIGCHLD}, {"SIGCONT", SIGCONT},
397
+ {"SIGSTOP", SIGSTOP}, {"SIGTSTP", SIGTSTP}, {"SIGTTIN", SIGTTIN},
398
+ {"SIGTTOU", SIGTTOU}, {"SIGURG", SIGURG}, {"SIGXCPU", SIGXCPU},
399
+ {"SIGXFSZ", SIGXFSZ}, {"SIGVTALRM", SIGVTALRM}, {"SIGPROF", SIGPROF},
400
+ {"SIGSYS", SIGSYS},
401
+ #endif
402
+ #ifdef SIGBREAK
403
+ {"SIGBREAK", SIGBREAK},
404
+ #endif
405
+ #ifdef SIGIO
406
+ {"SIGIO", SIGIO},
407
+ #endif
408
+ #ifdef SIGINFO
409
+ {"SIGINFO", SIGINFO},
410
+ #endif
411
+ };
412
+ for (size_t i = 0; i < sizeof SIGS / sizeof SIGS[0]; i++) {
413
+ if (strcmp(SIGS[i].name, name) == 0) return SIGS[i].sig;
414
+ }
415
+ return -1;
416
+ }
417
+
418
+ /* The table above for other units (scr_child.c's child.kill shares Node's
419
+ * one signal-name story): the resolved number, or -1 for unknown names. */
420
+ int scr_signal_from_name(const ScrStr *signal) {
421
+ return scr_signal_by_name(signal->data);
422
+ }
423
+
424
+ /* The reverse walk, for spawnSync's result.signal: the FIRST name with
425
+ * the number wins (SIGABRT precedes its SIGIOT alias — Node's spelling),
426
+ * NULL for numbers outside the table. Static storage; never freed. */
427
+ const char *scr_signal_name(int sig) {
428
+ static const struct { const char *name; int signo; } SIGS[] = {
429
+ {"SIGHUP", SIGHUP}, {"SIGINT", SIGINT}, {"SIGQUIT", SIGQUIT},
430
+ {"SIGILL", SIGILL},
431
+ #ifdef SIGTRAP
432
+ {"SIGTRAP", SIGTRAP},
433
+ #endif
434
+ {"SIGABRT", SIGABRT},
435
+ #ifdef SIGBUS
436
+ {"SIGBUS", SIGBUS},
437
+ #endif
438
+ {"SIGFPE", SIGFPE}, {"SIGKILL", SIGKILL},
439
+ #ifdef SIGUSR1
440
+ {"SIGUSR1", SIGUSR1},
441
+ #endif
442
+ {"SIGSEGV", SIGSEGV},
443
+ #ifdef SIGUSR2
444
+ {"SIGUSR2", SIGUSR2},
445
+ #endif
446
+ #ifdef SIGPIPE
447
+ {"SIGPIPE", SIGPIPE},
448
+ #endif
449
+ #ifdef SIGALRM
450
+ {"SIGALRM", SIGALRM},
451
+ #endif
452
+ {"SIGTERM", SIGTERM},
453
+ #ifdef SIGCHLD
454
+ {"SIGCHLD", SIGCHLD}, {"SIGCONT", SIGCONT}, {"SIGSTOP", SIGSTOP},
455
+ {"SIGTSTP", SIGTSTP}, {"SIGTTIN", SIGTTIN}, {"SIGTTOU", SIGTTOU},
456
+ {"SIGURG", SIGURG}, {"SIGXCPU", SIGXCPU}, {"SIGXFSZ", SIGXFSZ},
457
+ {"SIGVTALRM", SIGVTALRM}, {"SIGPROF", SIGPROF},
458
+ #endif
459
+ {"SIGWINCH", SIGWINCH},
460
+ #ifdef SIGSYS
461
+ {"SIGSYS", SIGSYS},
462
+ #endif
463
+ #ifdef SIGBREAK
464
+ {"SIGBREAK", SIGBREAK},
465
+ #endif
466
+ #ifdef SIGIO
467
+ {"SIGIO", SIGIO},
468
+ #endif
469
+ #ifdef SIGINFO
470
+ {"SIGINFO", SIGINFO},
471
+ #endif
472
+ };
473
+ for (size_t i = 0; i < sizeof SIGS / sizeof SIGS[0]; i++) {
474
+ if (SIGS[i].signo == sig) return SIGS[i].name;
475
+ }
476
+ return NULL;
477
+ }
478
+
479
+ /* Node validates the pid as an int32 BEFORE kill(2) and throws the
480
+ * ERR_INVALID_ARG_TYPE TypeError with this exact (odd) wording. */
481
+ static bool scr_kill_pid_check(double pid) {
482
+ if (pid >= -2147483648.0 && pid <= 2147483647.0 && pid == (double)(long long)pid) {
483
+ return true; /* NaN fails both range comparisons */
484
+ }
485
+ char num[32];
486
+ scr_f64_to_str(pid, num);
487
+ char msg[96];
488
+ int len = snprintf(msg, sizeof msg,
489
+ "The \"pid\" argument must be of type number. "
490
+ "Received type number (%s)",
491
+ num);
492
+ scr_throw_error_msg(SCR_ERR_TYPE, msg, (size_t)len);
493
+ return false;
494
+ }
495
+
496
+ /* The shared kill(2) tail: signal 0 probes; failure throws Node's terse
497
+ * `kill ESRCH` / `kill EPERM` Error. Returns Node's constant true.
498
+ * Windows: uv_kill's behavior — signal 0 opens the process to probe
499
+ * liveness, anything else is TerminateProcess (no signal exists to
500
+ * deliver; the target dies with exit code 1, exactly Node-on-Windows's
501
+ * process.kill). ESRCH/EPERM map from the open failure. */
502
+ #ifdef _WIN32
503
+ static int scr_win_kill(int pid, int sig) {
504
+ HANDLE h = OpenProcess(
505
+ sig == 0 ? PROCESS_QUERY_LIMITED_INFORMATION : PROCESS_TERMINATE,
506
+ FALSE, (DWORD)pid);
507
+ if (h == NULL) {
508
+ errno = GetLastError() == ERROR_ACCESS_DENIED ? EPERM : ESRCH;
509
+ return -1;
510
+ }
511
+ BOOL ok = sig == 0 ? TRUE : TerminateProcess(h, 1);
512
+ CloseHandle(h);
513
+ if (!ok) {
514
+ errno = EPERM;
515
+ return -1;
516
+ }
517
+ return 0;
518
+ }
519
+ #define scr_sys_kill(pid, sig) scr_win_kill((int)(pid), (sig))
520
+ #else
521
+ #define scr_sys_kill(pid, sig) kill((pid_t)(pid), (sig))
522
+ #endif
523
+
524
+ static bool scr_kill_send(int pid, int sig) {
525
+ if (scr_sys_kill(pid, sig) == 0) return true;
526
+ const char *name = errno == ESRCH ? "ESRCH"
527
+ : errno == EPERM ? "EPERM"
528
+ : errno == EINVAL ? "EINVAL"
529
+ : NULL;
530
+ char msg[32];
531
+ int len;
532
+ if (name) {
533
+ len = snprintf(msg, sizeof msg, "kill %s", name);
534
+ /* Node's errnoException carries code = the errno name. */
535
+ scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
536
+ } else {
537
+ len = snprintf(msg, sizeof msg, "kill E%d", errno);
538
+ scr_throw_error_msg(SCR_ERR_ERROR, msg, (size_t)len);
539
+ }
540
+ return false;
541
+ }
542
+
543
+ bool scr_process_kill(double pid, double signum) {
544
+ if (!scr_kill_pid_check(pid)) return false;
545
+ return scr_kill_send((int)pid, (int)signum);
546
+ }
547
+
548
+ bool scr_process_kill_named(double pid, const ScrStr *signal) {
549
+ if (!scr_kill_pid_check(pid)) return false;
550
+ int sig = scr_signal_by_name(signal->data);
551
+ if (sig < 0) {
552
+ /* Node's ERR_UNKNOWN_SIGNAL TypeError. */
553
+ size_t cap = 16 + signal->len + 1;
554
+ char *msg = malloc(cap);
555
+ if (!msg) {
556
+ fputs("scriptc: out of memory\n", stderr);
557
+ abort();
558
+ }
559
+ int len = snprintf(msg, cap, "Unknown signal: %s", signal->data);
560
+ scr_throw_error_msg(SCR_ERR_TYPE, msg, (size_t)len);
561
+ free(msg);
562
+ return false;
563
+ }
564
+ return scr_kill_send((int)pid, sig);
565
+ }
566
+
567
+ ScrStr *scr_process_cwd(void) {
568
+ char buf[4096];
569
+ if (!getcwd(buf, sizeof buf)) {
570
+ fputs("scriptc: process.cwd() failed\n", stderr);
571
+ abort();
572
+ }
573
+ return scr_str_new(buf, strlen(buf));
574
+ }
575
+
576
+ /* The raw byte writes. stdout goes through the SAME stdio stream and
577
+ * buffer console.log uses (scr_init's setvbuf), so interleaved writes
578
+ * keep source order; stderr is unbuffered, like Node's, and FLUSHES
579
+ * stdout first (the console.error convention) so merged 2>&1 output
580
+ * keeps source order despite stdout's full buffering. The boolean is
581
+ * Node's backpressure signal — a synchronous fwrite has no backpressure,
582
+ * so it is constantly true. */
583
+ bool scr_process_stdout_write(const ScrStr *data) {
584
+ fwrite(data->data, 1, data->len, stdout);
585
+ return true;
586
+ }
587
+
588
+ bool scr_process_stderr_write(const ScrStr *data) {
589
+ fflush(stdout);
590
+ fwrite(data->data, 1, data->len, stderr);
591
+ return true;
592
+ }
593
+
594
+ /* The FIRST-CLASS stream write (`output.write(line)` where output is a
595
+ * WritableStream-typed value — the prefixStream idiom): the value is the
596
+ * stream's fd (1 or 2, minted by the process.stdout/stderr reads), and
597
+ * the write dispatches onto the exact stdout/stderr paths above so
598
+ * buffering and ordering stay identical. */
599
+ bool scr_proc_stream_write(double fd, const ScrStr *data) {
600
+ return (int)fd == 2 ? scr_process_stderr_write(data) : scr_process_stdout_write(data);
601
+ }
602
+
603
+ /* ── os ──────────────────────────────────────────────────────────────
604
+ * os.platform() lowers to scr_process_platform (one implementation).
605
+ * homedir/tmpdir follow libuv's POSIX rules, which Node delegates to.
606
+ */
607
+
608
+ #ifdef _WIN32
609
+ /* uv_os_homedir on Windows: %USERPROFILE% first, then the profile
610
+ * directory API. The env var covers every real session; abort matches
611
+ * the POSIX arm's stance on the unreachable failure. */
612
+ ScrStr *scr_os_homedir(void) {
613
+ const char *home = getenv("USERPROFILE");
614
+ if (home && home[0] != '\0') return scr_str_new(home, strlen(home));
615
+ fputs("scriptc: os.homedir() failed\n", stderr);
616
+ abort();
617
+ }
618
+
619
+ ScrStr *scr_os_user_name(void) {
620
+ char buf[UNLEN + 1];
621
+ DWORD n = sizeof buf;
622
+ if (!GetUserNameA(buf, &n) || n == 0) {
623
+ fputs("scriptc: os.userInfo() failed\n", stderr);
624
+ abort();
625
+ }
626
+ return scr_str_new(buf, n - 1); /* n counts the NUL */
627
+ }
628
+
629
+ ScrStr *scr_os_user_shell(void) {
630
+ /* Node's userInfo().shell is null on Windows; the scriptc surface types
631
+ * it string, so the closest honest spelling is "" (divergence noted in
632
+ * the windows report). */
633
+ return scr_str_new("", 0);
634
+ }
635
+
636
+ ScrStr *scr_os_user_homedir(void) {
637
+ return scr_os_homedir(); /* uv_os_get_passwd reuses uv_os_homedir on win */
638
+ }
639
+
640
+ ScrStr *scr_os_release(void) {
641
+ /* uv_os_uname on Windows: RtlGetVersion (the un-lied-to GetVersionEx),
642
+ * rendered "major.minor.build" — Node answers e.g. "10.0.26100". */
643
+ typedef LONG(WINAPI * RtlGetVersionFn)(PRTL_OSVERSIONINFOW);
644
+ RTL_OSVERSIONINFOW info;
645
+ memset(&info, 0, sizeof info);
646
+ info.dwOSVersionInfoSize = sizeof info;
647
+ HMODULE ntdll = GetModuleHandleA("ntdll.dll");
648
+ RtlGetVersionFn fn =
649
+ ntdll ? (RtlGetVersionFn)(void *)GetProcAddress(ntdll, "RtlGetVersion") : NULL;
650
+ if (fn == NULL || fn(&info) != 0) {
651
+ fputs("scriptc: os.release() failed\n", stderr);
652
+ abort();
653
+ }
654
+ char buf[64];
655
+ int len = snprintf(buf, sizeof buf, "%lu.%lu.%lu", (unsigned long)info.dwMajorVersion,
656
+ (unsigned long)info.dwMinorVersion, (unsigned long)info.dwBuildNumber);
657
+ return scr_str_new(buf, (size_t)len);
658
+ }
659
+
660
+ ScrStr *scr_os_type(void) {
661
+ /* uv_os_uname's sysname on Windows is the constant "Windows_NT". */
662
+ return scr_str_new("Windows_NT", 10);
663
+ }
664
+
665
+ double scr_os_totalmem(void) {
666
+ MEMORYSTATUSEX ms;
667
+ memset(&ms, 0, sizeof ms);
668
+ ms.dwLength = sizeof ms;
669
+ if (!GlobalMemoryStatusEx(&ms)) return 0;
670
+ return (double)ms.ullTotalPhys;
671
+ }
672
+
673
+ ScrStr *scr_os_tmpdir(void) {
674
+ /* GetTempPathA is libuv's source (TMP → TEMP → USERPROFILE → windir),
675
+ * with Node's one-trailing-separator trim. */
676
+ char buf[MAX_PATH + 2];
677
+ DWORD n = GetTempPathA(sizeof buf, buf);
678
+ if (n == 0 || n >= sizeof buf) {
679
+ fputs("scriptc: os.tmpdir() failed\n", stderr);
680
+ abort();
681
+ }
682
+ size_t len = n;
683
+ if (len > 1 && (buf[len - 1] == '\\' || buf[len - 1] == '/')) len--;
684
+ return scr_str_new(buf, len);
685
+ }
686
+ #else
687
+ ScrStr *scr_os_homedir(void) {
688
+ /* uv_os_homedir: $HOME when set (even empty is "set" only if non-NULL;
689
+ * libuv requires non-empty), else the passwd entry. */
690
+ const char *home = getenv("HOME");
691
+ if (home && home[0] != '\0') return scr_str_new(home, strlen(home));
692
+ struct passwd pw;
693
+ struct passwd *result = NULL;
694
+ char buf[8192];
695
+ if (getpwuid_r(getuid(), &pw, buf, sizeof buf, &result) != 0 || !result || !result->pw_dir) {
696
+ fputs("scriptc: os.homedir() failed\n", stderr);
697
+ abort();
698
+ }
699
+ return scr_str_new(result->pw_dir, strlen(result->pw_dir));
700
+ }
701
+
702
+ /* The os.userInfo() field trio — uv_os_get_passwd's slices. One passwd
703
+ * lookup per call (three calls per userInfo record — cheap, no caching
704
+ * to invalidate). Failure aborts: Node throws a system error there, but
705
+ * no compiled program path reaches it for the running uid. */
706
+ static const struct passwd *scr_os_passwd(char *buf, size_t cap, struct passwd *pw) {
707
+ struct passwd *result = NULL;
708
+ if (getpwuid_r(getuid(), pw, buf, cap, &result) != 0 || !result) {
709
+ fputs("scriptc: os.userInfo() failed\n", stderr);
710
+ abort();
711
+ }
712
+ return result;
713
+ }
714
+
715
+ ScrStr *scr_os_user_name(void) {
716
+ struct passwd pw;
717
+ char buf[8192];
718
+ const struct passwd *r = scr_os_passwd(buf, sizeof buf, &pw);
719
+ return scr_str_new(r->pw_name, strlen(r->pw_name));
720
+ }
721
+
722
+ ScrStr *scr_os_user_shell(void) {
723
+ struct passwd pw;
724
+ char buf[8192];
725
+ const struct passwd *r = scr_os_passwd(buf, sizeof buf, &pw);
726
+ const char *sh = r->pw_shell ? r->pw_shell : "";
727
+ return scr_str_new(sh, strlen(sh));
728
+ }
729
+
730
+ ScrStr *scr_os_user_homedir(void) {
731
+ /* The PASSWD home (pw_dir) — Node's userInfo().homedir, distinct from
732
+ * os.homedir()'s $HOME-first cascade. */
733
+ struct passwd pw;
734
+ char buf[8192];
735
+ const struct passwd *r = scr_os_passwd(buf, sizeof buf, &pw);
736
+ return scr_str_new(r->pw_dir, strlen(r->pw_dir));
737
+ }
738
+
739
+ ScrStr *scr_os_release(void) {
740
+ /* uname(2)'s release field — Node's uv_os_uname()-backed answer. */
741
+ struct utsname u;
742
+ if (uname(&u) != 0) {
743
+ fputs("scriptc: os.release() failed\n", stderr);
744
+ abort();
745
+ }
746
+ return scr_str_new(u.release, strlen(u.release));
747
+ }
748
+
749
+ ScrStr *scr_os_type(void) {
750
+ /* uname(2)'s sysname field ("Darwin", "Linux") — Node's os.type(). */
751
+ struct utsname u;
752
+ if (uname(&u) != 0) {
753
+ fputs("scriptc: os.type() failed\n", stderr);
754
+ abort();
755
+ }
756
+ return scr_str_new(u.sysname, strlen(u.sysname));
757
+ }
758
+
759
+ double scr_os_totalmem(void) {
760
+ /* Total physical memory in bytes (sysconf pages × page size — Darwin
761
+ * and Linux both answer _SC_PHYS_PAGES). */
762
+ long pages = sysconf(_SC_PHYS_PAGES);
763
+ long psize = sysconf(_SC_PAGE_SIZE);
764
+ if (pages <= 0 || psize <= 0) return 0;
765
+ return (double)pages * (double)psize;
766
+ }
767
+
768
+ ScrStr *scr_os_tmpdir(void) {
769
+ /* Node's env cascade, with ONE trailing slash trimmed (never down to
770
+ * nothing: "/" stays "/"). */
771
+ const char *dir = getenv("TMPDIR");
772
+ if (!dir || dir[0] == '\0') dir = getenv("TMP");
773
+ if (!dir || dir[0] == '\0') dir = getenv("TEMP");
774
+ if (!dir || dir[0] == '\0') dir = "/tmp";
775
+ size_t len = strlen(dir);
776
+ if (len > 1 && dir[len - 1] == '/') len--;
777
+ return scr_str_new(dir, len);
778
+ }
779
+ #endif /* _WIN32 */
780
+
781
+ /* ── os.networkInterfaces(): the getifaddrs(3) snapshot ────────────────
782
+ * Row selection and field semantics follow libuv (src/unix/bsd-ifaddrs.c),
783
+ * which Node delegates to: an entry contributes a row iff its interface is
784
+ * IFF_UP && IFF_RUNNING, its address is present, and its family is
785
+ * AF_INET/AF_INET6; `internal` is IFF_LOOPBACK; MACs come from the
786
+ * interface's link-level sibling entry (AF_LINK/AF_PACKET, matched by
787
+ * name), all-zeros when there is none; cidr is Node's lib/os.js
788
+ * computation — address/<contiguous netmask prefix>, null when the netmask
789
+ * is non-contiguous (never in practice). Row order is getifaddrs
790
+ * enumeration order, which is also Node's — but Node guarantees no order,
791
+ * so consumers should compare structurally. The emitter walks the snapshot
792
+ * through the accessors below and builds the typed record inline; a
793
+ * getifaddrs failure (effectively unreachable) yields an empty snapshot
794
+ * where Node would throw ERR_SYSTEM_ERROR. */
795
+
796
+ typedef struct ScrIfaddrRow {
797
+ char name[64];
798
+ char address[INET6_ADDRSTRLEN];
799
+ char netmask[INET6_ADDRSTRLEN];
800
+ char cidr[INET6_ADDRSTRLEN + 5]; /* address + "/128" */
801
+ bool has_cidr;
802
+ char mac[18]; /* "aa:bb:cc:dd:ee:ff" */
803
+ bool internal;
804
+ bool ipv6;
805
+ double scopeid;
806
+ } ScrIfaddrRow;
807
+
808
+ struct ScrIfaddrs {
809
+ size_t n;
810
+ ScrIfaddrRow *rows;
811
+ };
812
+
813
+ #ifdef _WIN32
814
+ /* The Windows arm mirrors libuv's uv_interface_addresses (src/win/util.c),
815
+ * which Node delegates to: GetAdaptersAddresses(AF_UNSPEC, INCLUDE_PREFIX
816
+ * + the SKIP_* flags libuv passes), an adapter contributes rows iff
817
+ * OperStatus == IfOperStatusUp and it has a unicast address, the row name
818
+ * is the adapter's FriendlyName (UTF-8), `internal` is the software-
819
+ * loopback interface type, MAC comes from PhysicalAddress (all-zeros when
820
+ * absent — the loopback), the netmask is built from each unicast
821
+ * address's OnLinkPrefixLength, and scopeid is the v6 sockaddr's. cidr is
822
+ * the shared prefix computation below (always contiguous here by
823
+ * construction). A snapshot failure yields the empty dict, the historical
824
+ * stance. */
825
+ ScrIfaddrs *scr_os_ifaddrs(void) {
826
+ ScrIfaddrs *s = calloc(1, sizeof *s);
827
+ if (!s) abort();
828
+ ULONG flags = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST |
829
+ GAA_FLAG_SKIP_DNS_SERVER;
830
+ ULONG size = 16 * 1024;
831
+ IP_ADAPTER_ADDRESSES *adapters = NULL;
832
+ for (int tries = 0; tries < 4; tries++) {
833
+ adapters = realloc(adapters, size);
834
+ if (!adapters) abort();
835
+ ULONG rc = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, adapters, &size);
836
+ if (rc == ERROR_SUCCESS) break;
837
+ if (rc != ERROR_BUFFER_OVERFLOW) {
838
+ free(adapters);
839
+ s->rows = calloc(1, sizeof *s->rows);
840
+ if (!s->rows) abort();
841
+ return s; /* empty snapshot */
842
+ }
843
+ }
844
+ size_t count = 0;
845
+ for (IP_ADAPTER_ADDRESSES *a = adapters; a != NULL; a = a->Next) {
846
+ if (a->OperStatus != IfOperStatusUp || a->FirstUnicastAddress == NULL) continue;
847
+ for (IP_ADAPTER_UNICAST_ADDRESS *u = a->FirstUnicastAddress; u != NULL; u = u->Next) {
848
+ int fam = u->Address.lpSockaddr->sa_family;
849
+ if (fam == AF_INET || fam == AF_INET6) count++;
850
+ }
851
+ }
852
+ s->rows = calloc(count ? count : 1, sizeof *s->rows);
853
+ if (!s->rows) abort();
854
+ for (IP_ADAPTER_ADDRESSES *a = adapters; a != NULL; a = a->Next) {
855
+ if (a->OperStatus != IfOperStatusUp || a->FirstUnicastAddress == NULL) continue;
856
+ char name[64] = "";
857
+ WideCharToMultiByte(CP_UTF8, 0, a->FriendlyName, -1, name, sizeof name - 1, NULL, NULL);
858
+ char mac[18];
859
+ if (a->PhysicalAddressLength == 6) {
860
+ snprintf(mac, sizeof mac, "%02x:%02x:%02x:%02x:%02x:%02x", a->PhysicalAddress[0],
861
+ a->PhysicalAddress[1], a->PhysicalAddress[2], a->PhysicalAddress[3],
862
+ a->PhysicalAddress[4], a->PhysicalAddress[5]);
863
+ } else {
864
+ snprintf(mac, sizeof mac, "00:00:00:00:00:00");
865
+ }
866
+ bool internal = a->IfType == IF_TYPE_SOFTWARE_LOOPBACK;
867
+ for (IP_ADAPTER_UNICAST_ADDRESS *u = a->FirstUnicastAddress; u != NULL; u = u->Next) {
868
+ int fam = u->Address.lpSockaddr->sa_family;
869
+ if (fam != AF_INET && fam != AF_INET6) continue;
870
+ if (s->n == count) break; /* the topology raced the two passes */
871
+ ScrIfaddrRow *row = &s->rows[s->n++];
872
+ snprintf(row->name, sizeof row->name, "%s", name);
873
+ memcpy(row->mac, mac, sizeof mac);
874
+ row->internal = internal;
875
+ unsigned prefix = u->OnLinkPrefixLength;
876
+ if (fam == AF_INET6) {
877
+ const struct sockaddr_in6 *sa = (const struct sockaddr_in6 *)u->Address.lpSockaddr;
878
+ row->ipv6 = true;
879
+ row->scopeid = (double)sa->sin6_scope_id;
880
+ inet_ntop(AF_INET6, (void *)&sa->sin6_addr, row->address, sizeof row->address);
881
+ unsigned char mask[16];
882
+ if (prefix > 128) prefix = 128;
883
+ for (size_t i = 0; i < 16; i++) {
884
+ unsigned bits = prefix > 8 * i ? prefix - 8 * i : 0;
885
+ mask[i] = bits >= 8 ? 0xff : (unsigned char)(0xff00 >> bits);
886
+ }
887
+ inet_ntop(AF_INET6, mask, row->netmask, sizeof row->netmask);
888
+ row->has_cidr = true;
889
+ snprintf(row->cidr, sizeof row->cidr, "%s/%u", row->address, prefix);
890
+ } else {
891
+ const struct sockaddr_in *sa = (const struct sockaddr_in *)u->Address.lpSockaddr;
892
+ inet_ntop(AF_INET, (void *)&sa->sin_addr, row->address, sizeof row->address);
893
+ if (prefix > 32) prefix = 32;
894
+ uint32_t mask = prefix == 0 ? 0 : 0xffffffffu << (32 - prefix);
895
+ struct in_addr m;
896
+ m.s_addr = htonl(mask);
897
+ inet_ntop(AF_INET, (void *)&m, row->netmask, sizeof row->netmask);
898
+ row->has_cidr = true;
899
+ snprintf(row->cidr, sizeof row->cidr, "%s/%u", row->address, prefix);
900
+ }
901
+ }
902
+ }
903
+ free(adapters);
904
+ return s;
905
+ }
906
+ #else
907
+ /* libuv's uv__ifaddr_exclude for the address pass, plus the explicit
908
+ * INET/INET6 family filter (the only families Node's binding reports). */
909
+ static bool scr_ifaddr_row_ok(const struct ifaddrs *ent) {
910
+ if (!((ent->ifa_flags & IFF_UP) && (ent->ifa_flags & IFF_RUNNING))) return false;
911
+ if (ent->ifa_addr == NULL) return false;
912
+ int fam = ent->ifa_addr->sa_family;
913
+ return fam == AF_INET || fam == AF_INET6;
914
+ }
915
+
916
+ /* Node's getCIDR (lib/os.js): the netmask's contiguous 1-bit prefix.
917
+ * Returns -1 for a non-contiguous mask (cidr is then null). */
918
+ static int scr_netmask_prefix(const unsigned char *bytes, size_t len) {
919
+ int ones = 0;
920
+ bool zero_seen = false;
921
+ for (size_t i = 0; i < len; i++) {
922
+ unsigned char b = bytes[i];
923
+ for (int bit = 7; bit >= 0; bit--) {
924
+ if (b & (1u << bit)) {
925
+ if (zero_seen) return -1; /* a 1 after a 0: split mask */
926
+ ones++;
927
+ } else {
928
+ zero_seen = true;
929
+ }
930
+ }
931
+ }
932
+ return ones;
933
+ }
934
+
935
+ ScrIfaddrs *scr_os_ifaddrs(void) {
936
+ ScrIfaddrs *s = calloc(1, sizeof *s);
937
+ if (!s) abort();
938
+ struct ifaddrs *addrs = NULL;
939
+ if (getifaddrs(&addrs) != 0) return s;
940
+ size_t count = 0;
941
+ for (struct ifaddrs *ent = addrs; ent != NULL; ent = ent->ifa_next) {
942
+ if (scr_ifaddr_row_ok(ent)) count++;
943
+ }
944
+ s->rows = calloc(count ? count : 1, sizeof *s->rows);
945
+ if (!s->rows) abort();
946
+ for (struct ifaddrs *ent = addrs; ent != NULL; ent = ent->ifa_next) {
947
+ if (!scr_ifaddr_row_ok(ent)) continue;
948
+ ScrIfaddrRow *row = &s->rows[s->n++];
949
+ snprintf(row->name, sizeof row->name, "%s", ent->ifa_name);
950
+ snprintf(row->mac, sizeof row->mac, "00:00:00:00:00:00");
951
+ row->internal = (ent->ifa_flags & IFF_LOOPBACK) != 0;
952
+ unsigned char maskbytes[16];
953
+ size_t masklen = 0;
954
+ if (ent->ifa_addr->sa_family == AF_INET6) {
955
+ const struct sockaddr_in6 *sa = (const struct sockaddr_in6 *)ent->ifa_addr;
956
+ row->ipv6 = true;
957
+ row->scopeid = (double)sa->sin6_scope_id;
958
+ inet_ntop(AF_INET6, &sa->sin6_addr, row->address, sizeof row->address);
959
+ /* A NULL netmask stays zeroed, exactly libuv's memset — "::"/0. */
960
+ struct in6_addr mask;
961
+ memset(&mask, 0, sizeof mask);
962
+ if (ent->ifa_netmask != NULL) {
963
+ mask = ((const struct sockaddr_in6 *)ent->ifa_netmask)->sin6_addr;
964
+ }
965
+ inet_ntop(AF_INET6, &mask, row->netmask, sizeof row->netmask);
966
+ memcpy(maskbytes, &mask, 16);
967
+ masklen = 16;
968
+ } else {
969
+ const struct sockaddr_in *sa = (const struct sockaddr_in *)ent->ifa_addr;
970
+ inet_ntop(AF_INET, &sa->sin_addr, row->address, sizeof row->address);
971
+ struct in_addr mask;
972
+ memset(&mask, 0, sizeof mask);
973
+ if (ent->ifa_netmask != NULL) {
974
+ mask = ((const struct sockaddr_in *)ent->ifa_netmask)->sin_addr;
975
+ }
976
+ inet_ntop(AF_INET, &mask, row->netmask, sizeof row->netmask);
977
+ memcpy(maskbytes, &mask, 4);
978
+ masklen = 4;
979
+ }
980
+ int prefix = scr_netmask_prefix(maskbytes, masklen);
981
+ if (prefix >= 0) {
982
+ row->has_cidr = true;
983
+ snprintf(row->cidr, sizeof row->cidr, "%s/%d", row->address, prefix);
984
+ }
985
+ }
986
+ /* MAC pass: the link-level sibling entry, matched by interface name —
987
+ * every row of that interface gets its physical address. */
988
+ for (struct ifaddrs *ent = addrs; ent != NULL; ent = ent->ifa_next) {
989
+ if (!((ent->ifa_flags & IFF_UP) && (ent->ifa_flags & IFF_RUNNING))) continue;
990
+ if (ent->ifa_addr == NULL) continue;
991
+ const unsigned char *phys = NULL;
992
+ #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
993
+ if (ent->ifa_addr->sa_family == AF_LINK) {
994
+ const struct sockaddr_dl *sdl = (const struct sockaddr_dl *)ent->ifa_addr;
995
+ if (sdl->sdl_alen == 6) phys = (const unsigned char *)LLADDR(sdl);
996
+ }
997
+ #elif defined(__linux__)
998
+ if (ent->ifa_addr->sa_family == AF_PACKET) {
999
+ const struct sockaddr_ll *sll = (const struct sockaddr_ll *)ent->ifa_addr;
1000
+ if (sll->sll_halen == 6) phys = sll->sll_addr;
1001
+ }
1002
+ #endif
1003
+ if (!phys) continue;
1004
+ for (size_t i = 0; i < s->n; i++) {
1005
+ if (strcmp(s->rows[i].name, ent->ifa_name) != 0) continue;
1006
+ snprintf(s->rows[i].mac, sizeof s->rows[i].mac, "%02x:%02x:%02x:%02x:%02x:%02x",
1007
+ phys[0], phys[1], phys[2], phys[3], phys[4], phys[5]);
1008
+ }
1009
+ }
1010
+ freeifaddrs(addrs);
1011
+ return s;
1012
+ }
1013
+ #endif /* _WIN32 */
1014
+
1015
+ size_t scr_os_ifaddrs_count(const ScrIfaddrs *s) { return s->n; }
1016
+ ScrStr *scr_os_ifaddrs_name(const ScrIfaddrs *s, size_t i) {
1017
+ return scr_str_new(s->rows[i].name, strlen(s->rows[i].name));
1018
+ }
1019
+ ScrStr *scr_os_ifaddrs_address(const ScrIfaddrs *s, size_t i) {
1020
+ return scr_str_new(s->rows[i].address, strlen(s->rows[i].address));
1021
+ }
1022
+ ScrStr *scr_os_ifaddrs_netmask(const ScrIfaddrs *s, size_t i) {
1023
+ return scr_str_new(s->rows[i].netmask, strlen(s->rows[i].netmask));
1024
+ }
1025
+ ScrStr *scr_os_ifaddrs_family(const ScrIfaddrs *s, size_t i) {
1026
+ return s->rows[i].ipv6 ? scr_str_new("IPv6", 4) : scr_str_new("IPv4", 4);
1027
+ }
1028
+ ScrStr *scr_os_ifaddrs_mac(const ScrIfaddrs *s, size_t i) {
1029
+ return scr_str_new(s->rows[i].mac, strlen(s->rows[i].mac));
1030
+ }
1031
+ bool scr_os_ifaddrs_internal(const ScrIfaddrs *s, size_t i) { return s->rows[i].internal; }
1032
+ bool scr_os_ifaddrs_ipv6(const ScrIfaddrs *s, size_t i) { return s->rows[i].ipv6; }
1033
+ /* +1 cidr string, or NULL for the null arm (split netmask). */
1034
+ ScrStr *scr_os_ifaddrs_cidr(const ScrIfaddrs *s, size_t i) {
1035
+ if (!s->rows[i].has_cidr) return NULL;
1036
+ return scr_str_new(s->rows[i].cidr, strlen(s->rows[i].cidr));
1037
+ }
1038
+ double scr_os_ifaddrs_scopeid(const ScrIfaddrs *s, size_t i) { return s->rows[i].scopeid; }
1039
+ void scr_os_ifaddrs_free(ScrIfaddrs *s) {
1040
+ free(s->rows);
1041
+ free(s);
1042
+ }
1043
+
1044
+ /* The events-unit hooks (scr_events.c fills them at install; NULL in
1045
+ * event-free binaries and in the standalone runtime C tests). */
1046
+ void (*scr_process_exit_hook)(double code) = NULL;
1047
+ void (*scr_stdin_destroy_hook)(void) = NULL;
1048
+
1049
+ void scr_process_exit(double code) {
1050
+ /* Node runs 'exit' listeners on explicit process.exit() too — they run
1051
+ * HERE, synchronously, before the teardown-free exit below. The hook is
1052
+ * non-NULL only when the events unit is linked (scr_events.c). */
1053
+ scr_process_in_exit = true;
1054
+ if (scr_process_exit_hook != NULL) scr_process_exit_hook(code);
1055
+ /* _Exit skips atexit handlers on purpose: no further code runs (matching
1056
+ * Node), and the RC audit is meaningless mid-program (live values are
1057
+ * expected). scr_init's flush-at-exit is also skipped — flush here. */
1058
+ fflush(stdout);
1059
+ _Exit((int)code);
1060
+ }
1061
+
1062
+ /* ── the process introspection statics ────────────────────────────────
1063
+ * process.uptime/cpuUsage/threadCpuUsage/resourceUsage/availableMemory/
1064
+ * constrainedMemory — plain reads of the process's own clocks and
1065
+ * counters in Node's units. uptime anchors at load time (a constructor-
1066
+ * attribute monotonic stamp — the binary's own start, which is what
1067
+ * "the current Node.js process" means for a compiled program). */
1068
+ #ifdef _WIN32
1069
+ static double scr_uptime_t0_ms;
1070
+ static void scr_uptime_anchor_init(void) { scr_uptime_t0_ms = (double)GetTickCount64(); }
1071
+ static double scr_uptime_now_ms(void) { return (double)GetTickCount64(); }
1072
+ #else
1073
+ #include <sys/resource.h>
1074
+ #include <sys/time.h>
1075
+ #ifdef __APPLE__
1076
+ #include <mach/mach.h>
1077
+ #endif
1078
+ static double scr_uptime_t0_ms;
1079
+ static double scr_uptime_now_ms(void) {
1080
+ struct timespec ts;
1081
+ clock_gettime(CLOCK_MONOTONIC, &ts);
1082
+ return (double)ts.tv_sec * 1000.0 + (double)ts.tv_nsec / 1e6;
1083
+ }
1084
+ __attribute__((constructor)) static void scr_uptime_anchor_init(void) {
1085
+ scr_uptime_t0_ms = scr_uptime_now_ms();
1086
+ }
1087
+ #endif
1088
+
1089
+ double scr_process_uptime(void) {
1090
+ #ifdef _WIN32
1091
+ if (scr_uptime_t0_ms == 0) scr_uptime_anchor_init();
1092
+ #endif
1093
+ return (scr_uptime_now_ms() - scr_uptime_t0_ms) / 1000.0;
1094
+ }
1095
+
1096
+ #ifdef _WIN32
1097
+ /* GetProcessTimes/GetThreadTimes answer 100ns units; Node reports µs. */
1098
+ static double scr_filetime_us(FILETIME ft) {
1099
+ ULARGE_INTEGER v;
1100
+ v.LowPart = ft.dwLowDateTime;
1101
+ v.HighPart = ft.dwHighDateTime;
1102
+ return (double)(v.QuadPart / 10);
1103
+ }
1104
+ double scr_cpu_user(void) {
1105
+ FILETIME c, e, k, u;
1106
+ if (!GetProcessTimes(GetCurrentProcess(), &c, &e, &k, &u)) return 0;
1107
+ return scr_filetime_us(u);
1108
+ }
1109
+ double scr_cpu_system(void) {
1110
+ FILETIME c, e, k, u;
1111
+ if (!GetProcessTimes(GetCurrentProcess(), &c, &e, &k, &u)) return 0;
1112
+ return scr_filetime_us(k);
1113
+ }
1114
+ double scr_thread_cpu_user(void) {
1115
+ FILETIME c, e, k, u;
1116
+ if (!GetThreadTimes(GetCurrentThread(), &c, &e, &k, &u)) return 0;
1117
+ return scr_filetime_us(u);
1118
+ }
1119
+ double scr_thread_cpu_system(void) {
1120
+ FILETIME c, e, k, u;
1121
+ if (!GetThreadTimes(GetCurrentThread(), &c, &e, &k, &u)) return 0;
1122
+ return scr_filetime_us(k);
1123
+ }
1124
+ #else
1125
+ static double scr_tv_us(struct timeval tv) {
1126
+ return (double)tv.tv_sec * 1e6 + (double)tv.tv_usec;
1127
+ }
1128
+ double scr_cpu_user(void) {
1129
+ struct rusage ru;
1130
+ if (getrusage(RUSAGE_SELF, &ru) != 0) return 0;
1131
+ return scr_tv_us(ru.ru_utime);
1132
+ }
1133
+ double scr_cpu_system(void) {
1134
+ struct rusage ru;
1135
+ if (getrusage(RUSAGE_SELF, &ru) != 0) return 0;
1136
+ return scr_tv_us(ru.ru_stime);
1137
+ }
1138
+ #if defined(RUSAGE_THREAD)
1139
+ double scr_thread_cpu_user(void) {
1140
+ struct rusage ru;
1141
+ if (getrusage(RUSAGE_THREAD, &ru) != 0) return 0;
1142
+ return scr_tv_us(ru.ru_utime);
1143
+ }
1144
+ double scr_thread_cpu_system(void) {
1145
+ struct rusage ru;
1146
+ if (getrusage(RUSAGE_THREAD, &ru) != 0) return 0;
1147
+ return scr_tv_us(ru.ru_stime);
1148
+ }
1149
+ #elif defined(__APPLE__)
1150
+ double scr_thread_cpu_user(void) {
1151
+ thread_basic_info_data_t info;
1152
+ mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
1153
+ if (thread_info(mach_thread_self(), THREAD_BASIC_INFO, (thread_info_t)&info, &count) != KERN_SUCCESS) return 0;
1154
+ return (double)info.user_time.seconds * 1e6 + (double)info.user_time.microseconds;
1155
+ }
1156
+ double scr_thread_cpu_system(void) {
1157
+ thread_basic_info_data_t info;
1158
+ mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
1159
+ if (thread_info(mach_thread_self(), THREAD_BASIC_INFO, (thread_info_t)&info, &count) != KERN_SUCCESS) return 0;
1160
+ return (double)info.system_time.seconds * 1e6 + (double)info.system_time.microseconds;
1161
+ }
1162
+ #else
1163
+ /* No per-thread clock on this platform: the process clocks stand in (a
1164
+ * single-threaded binary's thread IS the process). */
1165
+ double scr_thread_cpu_user(void) { return scr_cpu_user(); }
1166
+ double scr_thread_cpu_system(void) { return scr_cpu_system(); }
1167
+ #endif
1168
+ #endif
1169
+
1170
+ /* The prev-argument validation: Node checks prevValue.user then
1171
+ * prevValue.system and throws the ERR_INVALID_ARG_VALUE RangeError with
1172
+ * the received number, catchably. (The frontend guarantees numbers —
1173
+ * non-number shapes keep compile fences.) */
1174
+ static void scr_cpu_prev_check_field(const char *name, double v) {
1175
+ if (v >= 0 && v <= 1.7976931348623157e308 && v == v) return; /* finite, non-negative */
1176
+ char num[32];
1177
+ size_t nlen = scr_f64_to_str(v, num);
1178
+ num[nlen] = 0;
1179
+ char msg[128];
1180
+ int len = snprintf(msg, sizeof msg, "The property 'prevValue.%s' is invalid. Received %s", name, num);
1181
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_INVALID_ARG_VALUE");
1182
+ }
1183
+
1184
+ void scr_cpu_prev_validate(double user, double system) {
1185
+ scr_cpu_prev_check_field("user", user);
1186
+ if (scr_exc_pending()) return;
1187
+ scr_cpu_prev_check_field("system", system);
1188
+ }
1189
+
1190
+ double scr_cpu_user_diff(double prev) { return scr_cpu_user() - prev; }
1191
+ double scr_cpu_system_diff(double prev) { return scr_cpu_system() - prev; }
1192
+ double scr_thread_cpu_user_diff(double prev) { return scr_thread_cpu_user() - prev; }
1193
+ double scr_thread_cpu_system_diff(double prev) { return scr_thread_cpu_system() - prev; }
1194
+
1195
+ /* process.resourceUsage(): one field by canonical index — Node's names,
1196
+ * order, and units (CPU times in µs; maxRSS in kilobytes — uv divides
1197
+ * Darwin's bytes by 1024; the rest are getrusage's own counters, zero
1198
+ * where the platform never fills them). */
1199
+ double scr_process_rusage(double idx) {
1200
+ #ifdef _WIN32
1201
+ /* uv fills only the CPU times and page-fault/maxRSS slice on Windows;
1202
+ * everything else answers 0 — Node's own shape there. */
1203
+ switch ((int)idx) {
1204
+ case 0: return scr_cpu_user();
1205
+ case 1: return scr_cpu_system();
1206
+ default: return 0;
1207
+ }
1208
+ #else
1209
+ struct rusage ru;
1210
+ if (getrusage(RUSAGE_SELF, &ru) != 0) return 0;
1211
+ switch ((int)idx) {
1212
+ case 0: return scr_tv_us(ru.ru_utime); /* userCPUTime */
1213
+ case 1: return scr_tv_us(ru.ru_stime); /* systemCPUTime */
1214
+ case 2: /* maxRSS (kilobytes) */
1215
+ #ifdef __APPLE__
1216
+ return (double)(ru.ru_maxrss / 1024);
1217
+ #else
1218
+ return (double)ru.ru_maxrss;
1219
+ #endif
1220
+ case 3: return (double)ru.ru_ixrss; /* sharedMemorySize */
1221
+ case 4: return (double)ru.ru_idrss; /* unsharedDataSize */
1222
+ case 5: return (double)ru.ru_isrss; /* unsharedStackSize */
1223
+ case 6: return (double)ru.ru_minflt; /* minorPageFault */
1224
+ case 7: return (double)ru.ru_majflt; /* majorPageFault */
1225
+ case 8: return (double)ru.ru_nswap; /* swappedOut */
1226
+ case 9: return (double)ru.ru_inblock; /* fsRead */
1227
+ case 10: return (double)ru.ru_oublock; /* fsWrite */
1228
+ case 11: return (double)ru.ru_msgsnd; /* ipcSent */
1229
+ case 12: return (double)ru.ru_msgrcv; /* ipcReceived */
1230
+ case 13: return (double)ru.ru_nsignals; /* signalsCount */
1231
+ case 14: return (double)ru.ru_nvcsw; /* voluntaryContextSwitches */
1232
+ case 15: return (double)ru.ru_nivcsw; /* involuntaryContextSwitches */
1233
+ default: return 0;
1234
+ }
1235
+ #endif
1236
+ }
1237
+
1238
+ /* process.availableMemory()/constrainedMemory() — libuv's numbers: the
1239
+ * constrained form answers the cgroup cap where one exists (Linux) and 0
1240
+ * everywhere else; available is the free-ish byte count. */
1241
+ double scr_constrained_memory(void) {
1242
+ #if defined(__linux__)
1243
+ FILE *f = fopen("/sys/fs/cgroup/memory.max", "r"); /* cgroup v2 */
1244
+ if (f == NULL) f = fopen("/sys/fs/cgroup/memory/memory.limit_in_bytes", "r"); /* v1 */
1245
+ if (f == NULL) return 0;
1246
+ char buf[64];
1247
+ size_t n = fread(buf, 1, sizeof buf - 1, f);
1248
+ fclose(f);
1249
+ buf[n] = 0;
1250
+ if (n == 0 || buf[0] == 'm') return 0; /* "max" = unconstrained */
1251
+ double v = strtod(buf, NULL);
1252
+ return v > 0 ? v : 0;
1253
+ #else
1254
+ return 0;
1255
+ #endif
1256
+ }
1257
+
1258
+ double scr_available_memory(void) {
1259
+ #if defined(_WIN32)
1260
+ MEMORYSTATUSEX ms;
1261
+ memset(&ms, 0, sizeof ms);
1262
+ ms.dwLength = sizeof ms;
1263
+ if (!GlobalMemoryStatusEx(&ms)) return 0;
1264
+ return (double)ms.ullAvailPhys;
1265
+ #elif defined(__APPLE__)
1266
+ /* uv_get_available_memory falls back to the free-memory number on
1267
+ * Darwin (vm_statistics' free pages). */
1268
+ vm_statistics64_data_t vm;
1269
+ mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
1270
+ if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&vm, &count) != KERN_SUCCESS) return 0;
1271
+ return (double)vm.free_count * (double)vm_page_size;
1272
+ #elif defined(__linux__)
1273
+ /* /proc/meminfo's MemAvailable — the kernel's own availability estimate
1274
+ * (what uv reads via sysinfo lacks reclaimable cache). */
1275
+ FILE *f = fopen("/proc/meminfo", "r");
1276
+ if (f == NULL) return 0;
1277
+ char line[128];
1278
+ double kb = 0;
1279
+ while (fgets(line, sizeof line, f) != NULL) {
1280
+ if (sscanf(line, "MemAvailable: %lf kB", &kb) == 1) break;
1281
+ }
1282
+ fclose(f);
1283
+ return kb * 1024.0;
1284
+ #else
1285
+ return 0;
1286
+ #endif
1287
+ }
1288
+
1289
+ /* process._exiting — true once the exit sequence began (set above and by
1290
+ * scr_run_exit_listeners in scr_events.c; the flag lives HERE so reading
1291
+ * it never forces the events unit into the link). */
1292
+ bool scr_process_in_exit = false;
1293
+
1294
+ bool scr_process_exiting(void) { return scr_process_in_exit; }
1295
+
1296
+ /* umask(2): mask < 0 reads without setting (set 0, restore — umask has no
1297
+ * read-only form); otherwise sets and answers the previous mask. */
1298
+ double scr_process_umask(double mask) {
1299
+ #ifdef _WIN32
1300
+ /* Node on Windows accepts umask() calls; only the low bits matter. */
1301
+ int prev;
1302
+ if (mask < 0) {
1303
+ _umask_s(0, &prev);
1304
+ int ignored;
1305
+ _umask_s(prev, &ignored);
1306
+ } else {
1307
+ _umask_s((int)mask, &prev);
1308
+ }
1309
+ return (double)prev;
1310
+ #else
1311
+ mode_t prev;
1312
+ if (mask < 0) {
1313
+ prev = umask(0);
1314
+ umask(prev);
1315
+ } else {
1316
+ prev = umask((mode_t)mask);
1317
+ }
1318
+ return (double)prev;
1319
+ #endif
1320
+ }
1321
+
1322
+ void scr_process_chdir(ScrStr *dir) {
1323
+ #ifdef _WIN32
1324
+ if (_chdir(dir->data) != 0) scr_fs_throw(errno, "chdir", dir);
1325
+ #else
1326
+ if (chdir(dir->data) != 0) scr_fs_throw(errno, "chdir", dir);
1327
+ #endif
1328
+ }
1329
+
1330
+ /* net's process-wide happy-eyeballs attempt budget (Node's
1331
+ * getDefaultAutoSelectFamilyAttemptTimeout pair, default 250ms). Lives in
1332
+ * the core unit so the knob never forces scr_net.c into the link. */
1333
+ static double scr_net_autosel_timeout_ms = 250;
1334
+
1335
+ double scr_net_get_autosel_timeout(void) { return scr_net_autosel_timeout_ms; }
1336
+
1337
+ void scr_net_set_autosel_timeout(double ms) { scr_net_autosel_timeout_ms = ms; }
1338
+
1339
+ /* ── fs error formatting ─────────────────────────────────────────────
1340
+ * Node's fs errors read "<ERRNO>: <text>, <syscall> '<path>'"
1341
+ * ("ENOENT: no such file or directory, open 'x'"). The common errnos get
1342
+ * Node's (libuv's) exact lowercase text; anything exotic falls back to
1343
+ * "E<num>: <strerror>, <op> '<path>'" — close enough, and the corpus can
1344
+ * only observe messages through the runtime C tests anyway (the supported
1345
+ * catch form is bindingless).
1346
+ */
1347
+
1348
+ static const char *scr_errno_name(int e, char *fallback, size_t cap) {
1349
+ switch (e) {
1350
+ case ENOENT: return "ENOENT";
1351
+ case EEXIST: return "EEXIST";
1352
+ case EACCES: return "EACCES";
1353
+ case ENOTDIR: return "ENOTDIR";
1354
+ case EISDIR: return "EISDIR";
1355
+ case ENOTEMPTY: return "ENOTEMPTY";
1356
+ case EPERM: return "EPERM";
1357
+ case EBADF: return "EBADF";
1358
+ default:
1359
+ snprintf(fallback, cap, "E%d", e);
1360
+ return fallback;
1361
+ }
1362
+ }
1363
+
1364
+ static const char *scr_errno_text(int e) {
1365
+ switch (e) {
1366
+ case ENOENT: return "no such file or directory";
1367
+ case EEXIST: return "file already exists";
1368
+ case EACCES: return "permission denied";
1369
+ case ENOTDIR: return "not a directory";
1370
+ case EISDIR: return "illegal operation on a directory";
1371
+ case ENOTEMPTY: return "directory not empty";
1372
+ case EPERM: return "operation not permitted";
1373
+ case EBADF: return "bad file descriptor";
1374
+ default: return strerror(e);
1375
+ }
1376
+ }
1377
+
1378
+ /* Node on WINDOWS reports fs error paths ABSOLUTIZED and backslashed
1379
+ * (its fs binding hands the Windows API namespaced absolute paths and the
1380
+ * error keeps that spelling): open("no.bin") fails with "... open
1381
+ * 'C:\cwd\no.bin'". _fullpath reproduces exactly that resolution. POSIX
1382
+ * Node reports the path as given — the passthrough arm. */
1383
+ #ifdef _WIN32
1384
+ static const char *scr_fs_err_path(const ScrStr *path, char buf[PATH_MAX]) {
1385
+ return _fullpath(buf, path->data, PATH_MAX) != NULL ? buf : path->data;
1386
+ }
1387
+ #else
1388
+ static const char *scr_fs_err_path(const ScrStr *path, char buf[PATH_MAX]) {
1389
+ (void)buf;
1390
+ return path->data;
1391
+ }
1392
+ #endif
1393
+
1394
+ /* Exported (scr_runtime.h): scr_bytes.c's fs Buffer forms share it. */
1395
+ void scr_fs_throw(int e, const char *op, const ScrStr *path) {
1396
+ #ifdef _WIN32
1397
+ /* The CRT lands ERROR_ACCESS_DENIED in errno as EACCES; libuv's
1398
+ * uv_translate_sys_error maps the same Win32 error to EPERM, so that
1399
+ * is the code Node throws (a read-only file's write open, chmod on a
1400
+ * held file). Translate at the throw seam so every fs op agrees with
1401
+ * the Windows oracle. */
1402
+ if (e == EACCES) e = EPERM;
1403
+ #endif
1404
+ char namebuf[16];
1405
+ const char *name = scr_errno_name(e, namebuf, sizeof namebuf);
1406
+ const char *text = scr_errno_text(e);
1407
+ char pathbuf[PATH_MAX];
1408
+ const char *shown = scr_fs_err_path(path, pathbuf);
1409
+ size_t cap = strlen(name) + strlen(text) + strlen(op) + strlen(shown) + 8;
1410
+ char *msg = malloc(cap);
1411
+ if (!msg) {
1412
+ fputs("scriptc: out of memory\n", stderr);
1413
+ abort();
1414
+ }
1415
+ int len = snprintf(msg, cap, "%s: %s, %s '%s'", name, text, op, shown);
1416
+ /* A real Error instance (name "Error", message = Node's text) — what a
1417
+ * typed catch's `e instanceof Error` + `e.message` observes in Node —
1418
+ * with `code` stamped to the errno name (the exotic-errno fallback
1419
+ * stamps its "E<num>" spelling; Node would carry the uv name there).
1420
+ * errno/syscall/path stay unrepresented (SEMANTICS.md divergence 13). */
1421
+ scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
1422
+ free(msg);
1423
+ }
1424
+
1425
+ /* ── fs operations ───────────────────────────────────────────────────── */
1426
+
1427
+ ScrStr *scr_fs_read_file(ScrStr *path) {
1428
+ FILE *f = fopen(path->data, "rb");
1429
+ if (!f) {
1430
+ scr_fs_throw(errno, "open", path);
1431
+ return NULL;
1432
+ }
1433
+ size_t cap = 4096, len = 0;
1434
+ char *buf = malloc(cap);
1435
+ if (!buf) {
1436
+ fputs("scriptc: out of memory\n", stderr);
1437
+ abort();
1438
+ }
1439
+ for (;;) {
1440
+ if (cap - len < 2048) {
1441
+ cap *= 2;
1442
+ char *grown = realloc(buf, cap);
1443
+ if (!grown) {
1444
+ fputs("scriptc: out of memory\n", stderr);
1445
+ abort();
1446
+ }
1447
+ buf = grown;
1448
+ }
1449
+ size_t n = fread(buf + len, 1, cap - len, f);
1450
+ len += n;
1451
+ if (n == 0) break;
1452
+ }
1453
+ if (ferror(f)) {
1454
+ int e = errno;
1455
+ fclose(f);
1456
+ free(buf);
1457
+ scr_fs_throw(e, "read", path);
1458
+ return NULL;
1459
+ }
1460
+ fclose(f);
1461
+ ScrStr *s = scr_str_new(buf, len);
1462
+ free(buf);
1463
+ return s;
1464
+ }
1465
+
1466
+ ScrStr *scr_fs_realpath(ScrStr *path) {
1467
+ #ifdef _WIN32
1468
+ /* _fullpath resolves . / .. and drive-relative forms (symlink-free —
1469
+ * the honest Windows approximation); a missing path throws Node's
1470
+ * lstat-spelled ENOENT like the POSIX arm. */
1471
+ char buf[PATH_MAX];
1472
+ if (_fullpath(buf, path->data, sizeof buf) == NULL) {
1473
+ scr_fs_throw(errno ? errno : ENOENT, "lstat", path);
1474
+ return NULL;
1475
+ }
1476
+ if (GetFileAttributesA(buf) == INVALID_FILE_ATTRIBUTES) {
1477
+ scr_fs_throw(ENOENT, "lstat", path);
1478
+ return NULL;
1479
+ }
1480
+ return scr_str_new(buf, strlen(buf));
1481
+ #else
1482
+ /* realpath(3); Node's realpathSync reports failures with the "lstat"
1483
+ * syscall in the message ("ENOENT: no such file or directory, lstat
1484
+ * 'x'") — its own resolution walks lstat by component. */
1485
+ char buf[PATH_MAX];
1486
+ if (realpath(path->data, buf) == NULL) {
1487
+ scr_fs_throw(errno, "lstat", path);
1488
+ return NULL;
1489
+ }
1490
+ return scr_str_new(buf, strlen(buf));
1491
+ #endif
1492
+ }
1493
+
1494
+ static void scr_fs_write_common(ScrStr *path, ScrStr *data, const char *mode) {
1495
+ FILE *f = fopen(path->data, mode);
1496
+ if (!f) {
1497
+ scr_fs_throw(errno, "open", path);
1498
+ return;
1499
+ }
1500
+ if (data->len > 0 && fwrite(data->data, 1, data->len, f) != data->len) {
1501
+ int e = errno;
1502
+ fclose(f);
1503
+ scr_fs_throw(e, "write", path);
1504
+ return;
1505
+ }
1506
+ if (fclose(f) != 0) scr_fs_throw(errno, "close", path);
1507
+ }
1508
+
1509
+ void scr_fs_write_file(ScrStr *path, ScrStr *data) {
1510
+ scr_fs_write_common(path, data, "wb");
1511
+ }
1512
+
1513
+ /* writeFileSync(p, data, { mode }): the mode is open(2)'s O_CREAT
1514
+ * argument — it applies at CREATION only (umask applying), and an
1515
+ * existing file keeps its permissions, exactly Node (which never chmods
1516
+ * here). Same error shapes as the plain form. */
1517
+ void scr_fs_write_file_mode(ScrStr *path, ScrStr *data, double mode) {
1518
+ /* O_BINARY: zero on POSIX; on Windows it keeps the CRT from translating
1519
+ * \n in these byte-exact writes (fopen's "wb" path already does). */
1520
+ int fd = open(path->data, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, (mode_t)mode);
1521
+ if (fd < 0) {
1522
+ scr_fs_throw(errno, "open", path);
1523
+ return;
1524
+ }
1525
+ size_t at = 0;
1526
+ while (at < data->len) {
1527
+ ssize_t wrote = write(fd, data->data + at, data->len - at);
1528
+ if (wrote < 0) {
1529
+ if (errno == EINTR) continue;
1530
+ int e = errno;
1531
+ close(fd);
1532
+ scr_fs_throw(e, "write", path);
1533
+ return;
1534
+ }
1535
+ at += (size_t)wrote;
1536
+ }
1537
+ if (close(fd) != 0) scr_fs_throw(errno, "close", path);
1538
+ }
1539
+
1540
+ void scr_fs_append_file(ScrStr *path, ScrStr *data) {
1541
+ scr_fs_write_common(path, data, "ab");
1542
+ }
1543
+
1544
+ bool scr_fs_exists(ScrStr *path) {
1545
+ /* Like Node's existsSync: any failure (missing, EACCES on a parent, ...)
1546
+ * is simply false — never a throw. */
1547
+ return access(path->data, F_OK) == 0;
1548
+ }
1549
+
1550
+ void scr_fs_mkdir(ScrStr *path) {
1551
+ if (scr_sys_mkdir(path->data, 0777) != 0) scr_fs_throw(errno, "mkdir", path);
1552
+ }
1553
+
1554
+ /* mkdirSync(p, { mode }) non-recursive: mkdir(2) with the explicit mode
1555
+ * (umask applies, exactly as in Node — mode is the syscall argument;
1556
+ * Windows has no directory modes and drops it, like Node there). */
1557
+ void scr_fs_mkdir_mode(ScrStr *path, double mode) {
1558
+ if (scr_sys_mkdir(path->data, (mode_t)mode) != 0) scr_fs_throw(errno, "mkdir", path);
1559
+ }
1560
+
1561
+ void scr_fs_unlink(ScrStr *path) {
1562
+ if (unlink(path->data) != 0) scr_fs_throw(errno, "unlink", path);
1563
+ }
1564
+
1565
+ void scr_fs_chmod(ScrStr *path, double mode) {
1566
+ if (chmod(path->data, (mode_t)mode) != 0) scr_fs_throw(errno, "chmod", path);
1567
+ }
1568
+
1569
+ void scr_fs_chown(ScrStr *path, double uid, double gid) {
1570
+ #ifdef _WIN32
1571
+ /* libuv's uv_fs_chown on Windows is an unconditional no-op success —
1572
+ * Node's chownSync "works" and changes nothing there; same here. */
1573
+ (void)path; (void)uid; (void)gid;
1574
+ #else
1575
+ /* Node passes the ids straight to chown(2); -1 is the POSIX "leave
1576
+ * unchanged" value and rides the same int cast. */
1577
+ if (chown(path->data, (uid_t)(int64_t)uid, (gid_t)(int64_t)gid) != 0) {
1578
+ scr_fs_throw(errno, "chown", path);
1579
+ }
1580
+ #endif
1581
+ }
1582
+
1583
+ /* fs.openSync(path, flags) → the raw fd (as f64) — the pair behind
1584
+ * spawn's fd-stdio form (openSync → stdio: ["ignore", fd, fd] →
1585
+ * closeSync). flags is Node's string grammar; an unknown flag throws
1586
+ * Node's ERR_INVALID_ARG_VALUE TypeError text, an open(2) failure the
1587
+ * usual Node-shaped fs error. Mode is Node's 0666 default (the numeric
1588
+ * third argument is a compile fence). */
1589
+ double scr_fs_open(ScrStr *path, ScrStr *flags) {
1590
+ const char *f = flags->data;
1591
+ int of;
1592
+ if (strcmp(f, "r") == 0) of = O_RDONLY;
1593
+ else if (strcmp(f, "rs") == 0 || strcmp(f, "sr") == 0) of = O_RDONLY | O_SYNC;
1594
+ else if (strcmp(f, "r+") == 0) of = O_RDWR;
1595
+ else if (strcmp(f, "rs+") == 0 || strcmp(f, "sr+") == 0) of = O_RDWR | O_SYNC;
1596
+ else if (strcmp(f, "w") == 0) of = O_TRUNC | O_CREAT | O_WRONLY;
1597
+ else if (strcmp(f, "wx") == 0 || strcmp(f, "xw") == 0) of = O_TRUNC | O_CREAT | O_WRONLY | O_EXCL;
1598
+ else if (strcmp(f, "w+") == 0) of = O_TRUNC | O_CREAT | O_RDWR;
1599
+ else if (strcmp(f, "wx+") == 0 || strcmp(f, "xw+") == 0) of = O_TRUNC | O_CREAT | O_RDWR | O_EXCL;
1600
+ else if (strcmp(f, "a") == 0) of = O_APPEND | O_CREAT | O_WRONLY;
1601
+ else if (strcmp(f, "ax") == 0 || strcmp(f, "xa") == 0) of = O_APPEND | O_CREAT | O_WRONLY | O_EXCL;
1602
+ else if (strcmp(f, "as") == 0 || strcmp(f, "sa") == 0) of = O_APPEND | O_CREAT | O_WRONLY | O_SYNC;
1603
+ else if (strcmp(f, "a+") == 0) of = O_APPEND | O_CREAT | O_RDWR;
1604
+ else if (strcmp(f, "ax+") == 0 || strcmp(f, "xa+") == 0) of = O_APPEND | O_CREAT | O_RDWR | O_EXCL;
1605
+ else if (strcmp(f, "as+") == 0 || strcmp(f, "sa+") == 0) of = O_APPEND | O_CREAT | O_RDWR | O_SYNC;
1606
+ else {
1607
+ char msg[128];
1608
+ int len = snprintf(msg, sizeof msg, "The argument 'flags' is invalid. Received '%s'", f);
1609
+ scr_throw_error_msg(SCR_ERR_TYPE, msg, (size_t)len);
1610
+ return 0;
1611
+ }
1612
+ int fd = open(path->data, of | O_BINARY, 0666);
1613
+ if (fd < 0) {
1614
+ scr_fs_throw(errno, "open", path);
1615
+ return 0;
1616
+ }
1617
+ return (double)fd;
1618
+ }
1619
+
1620
+ /* fs.closeSync(fd) — close(2); failure throws Node's path-less fs error
1621
+ * shape ("EBADF: bad file descriptor, close"). */
1622
+ /* fs.readSync(fd, buffer, offset, length) — the 4-argument buffer form.
1623
+ * Node validates offset/length against the buffer before reading and
1624
+ * throws ERR_OUT_OF_RANGE; here the checks clamp to the same contract and
1625
+ * throw the RangeError shape. Returns the byte count read(2) reports;
1626
+ * errors carry the errno name like the other fd operations. */
1627
+ double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length) {
1628
+ size_t bytelen = buf->len; /* u8 buffers: elem count == byte count */
1629
+ char msg[160];
1630
+ int mlen;
1631
+ /* Node's exact texts: offset validates against MAX_SAFE_INTEGER's range
1632
+ * form, length against the remaining window (validateOffset vs the
1633
+ * buffer-bounds check in fs.readSync). */
1634
+ char numbuf[40];
1635
+ if (offset < 0 || offset > (double)bytelen) {
1636
+ numbuf[scr_f64_to_str(offset, numbuf)] = 0;
1637
+ mlen = snprintf(msg, sizeof msg,
1638
+ "The value of \"offset\" is out of range. It must be >= 0 && <= 9007199254740991. Received %s",
1639
+ numbuf);
1640
+ scr_throw_error_msg(SCR_ERR_RANGE, msg, (size_t)mlen);
1641
+ return 0;
1642
+ }
1643
+ size_t off = (size_t)offset;
1644
+ if (length < 0 || (double)length > (double)(bytelen - off)) {
1645
+ numbuf[scr_f64_to_str(length, numbuf)] = 0;
1646
+ mlen = snprintf(msg, sizeof msg,
1647
+ "The value of \"length\" is out of range. It must be <= %zu. Received %s",
1648
+ bytelen - off, numbuf);
1649
+ scr_throw_error_msg(SCR_ERR_RANGE, msg, (size_t)mlen);
1650
+ return 0;
1651
+ }
1652
+ size_t want = (size_t)length;
1653
+ ssize_t n = read((int)fd, buf->data + off, want);
1654
+ if (n < 0) {
1655
+ int e = errno;
1656
+ char namebuf[16];
1657
+ const char *name = scr_errno_name(e, namebuf, sizeof namebuf);
1658
+ const char *text = scr_errno_text(e);
1659
+ char msg[160];
1660
+ int len = snprintf(msg, sizeof msg, "%s: %s, read", name, text);
1661
+ scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
1662
+ return 0;
1663
+ }
1664
+ return (double)n;
1665
+ }
1666
+
1667
+ void scr_fs_close(double fd) {
1668
+ if (close((int)fd) != 0) {
1669
+ int e = errno;
1670
+ char namebuf[16];
1671
+ const char *name = scr_errno_name(e, namebuf, sizeof namebuf);
1672
+ const char *text = scr_errno_text(e);
1673
+ char msg[160];
1674
+ int len = snprintf(msg, sizeof msg, "%s: %s, close", name, text);
1675
+ scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
1676
+ }
1677
+ }
1678
+
1679
+ /* The two-path fs error shape — Node's copyfile errors quote both ends:
1680
+ * "ENOENT: no such file or directory, copyfile 'src' -> 'dest'". */
1681
+ static void scr_fs_throw2(int e, const char *op, const ScrStr *src, const ScrStr *dest) {
1682
+ char namebuf[16];
1683
+ const char *name = scr_errno_name(e, namebuf, sizeof namebuf);
1684
+ const char *text = scr_errno_text(e);
1685
+ char srcbuf[PATH_MAX], destbuf[PATH_MAX];
1686
+ const char *shown_src = scr_fs_err_path(src, srcbuf);
1687
+ const char *shown_dest = scr_fs_err_path(dest, destbuf);
1688
+ size_t cap = strlen(name) + strlen(text) + strlen(op) + strlen(shown_src) + strlen(shown_dest) + 16;
1689
+ char *msg = malloc(cap);
1690
+ if (!msg) {
1691
+ fputs("scriptc: out of memory\n", stderr);
1692
+ abort();
1693
+ }
1694
+ int len = snprintf(msg, cap, "%s: %s, %s '%s' -> '%s'", name, text, op, shown_src, shown_dest);
1695
+ scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
1696
+ free(msg);
1697
+ }
1698
+
1699
+ /* copyFileSync(src, dest): contents copied into a created-or-truncated
1700
+ * destination carrying the SOURCE's permission bits — libuv's
1701
+ * uv_fs_copyfile behavior behind Node's copyFileSync (umask applies at
1702
+ * creation, like any open(2)). Every failure throws catchably with the
1703
+ * two-path message and the syscall name Node reports ("copyfile"). */
1704
+ void scr_fs_copyfile(ScrStr *src, ScrStr *dest) {
1705
+ int in = open(src->data, O_RDONLY | O_BINARY);
1706
+ if (in < 0) {
1707
+ scr_fs_throw2(errno, "copyfile", src, dest);
1708
+ return;
1709
+ }
1710
+ struct stat st;
1711
+ if (fstat(in, &st) != 0) {
1712
+ int e = errno;
1713
+ close(in);
1714
+ scr_fs_throw2(e, "copyfile", src, dest);
1715
+ return;
1716
+ }
1717
+ int out = open(dest->data, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, st.st_mode & 07777);
1718
+ if (out < 0) {
1719
+ int e = errno;
1720
+ close(in);
1721
+ scr_fs_throw2(e, "copyfile", src, dest);
1722
+ return;
1723
+ }
1724
+ char buf[65536];
1725
+ for (;;) {
1726
+ ssize_t got = read(in, buf, sizeof buf);
1727
+ if (got < 0) {
1728
+ if (errno == EINTR) continue;
1729
+ int e = errno;
1730
+ close(in);
1731
+ close(out);
1732
+ scr_fs_throw2(e, "copyfile", src, dest);
1733
+ return;
1734
+ }
1735
+ if (got == 0) break;
1736
+ ssize_t at = 0;
1737
+ while (at < got) {
1738
+ ssize_t wrote = write(out, buf + at, (size_t)(got - at));
1739
+ if (wrote < 0) {
1740
+ if (errno == EINTR) continue;
1741
+ int e = errno;
1742
+ close(in);
1743
+ close(out);
1744
+ scr_fs_throw2(e, "copyfile", src, dest);
1745
+ return;
1746
+ }
1747
+ at += wrote;
1748
+ }
1749
+ }
1750
+ close(in);
1751
+ if (close(out) != 0) scr_fs_throw2(errno, "copyfile", src, dest);
1752
+ }
1753
+
1754
+ /* renameSync(old, new): rename(2), Node's two-path error shape ("ENOENT:
1755
+ * no such file or directory, rename 'a' -> 'b'"). */
1756
+ void scr_fs_rename(ScrStr *oldpath, ScrStr *newpath) {
1757
+ if (rename(oldpath->data, newpath->data) != 0) {
1758
+ scr_fs_throw2(errno, "rename", oldpath, newpath);
1759
+ }
1760
+ }
1761
+
1762
+ void scr_fs_rm(ScrStr *path) {
1763
+ /* Node's rmSync: lstat first (a missing path reports the lstat syscall),
1764
+ * refuse directories (Node requires `recursive`, which the scriptc
1765
+ * surface doesn't declare — the message wording diverges from Node's
1766
+ * ERR_FS_EISDIR, see SEMANTICS.md), then unlink. */
1767
+ struct stat st;
1768
+ if (lstat(path->data, &st) != 0) {
1769
+ scr_fs_throw(errno, "lstat", path);
1770
+ return;
1771
+ }
1772
+ if (S_ISDIR(st.st_mode)) {
1773
+ scr_fs_throw(EISDIR, "rm", path);
1774
+ return;
1775
+ }
1776
+ if (unlink(path->data) != 0) scr_fs_throw(errno, "unlink", path);
1777
+ }
1778
+
1779
+ void scr_fs_rmdir(ScrStr *path) {
1780
+ if (rmdir(path->data) != 0) scr_fs_throw(errno, "rmdir", path);
1781
+ }
1782
+
1783
+ /* ── fs option forms ─────────────────────────────────────────────────
1784
+ * mkdirSync(p, { recursive: true }), rmSync(p, { recursive, force }),
1785
+ * mkdtempSync(prefix), accessSync(p, mode), and the readFileSync(fd)
1786
+ * forms — the slice real CLIs use. All throw catchably like the rest of
1787
+ * sync fs, with Node's errno/path shapes (verified against Node). */
1788
+
1789
+ /* Node's recursive mkdir algorithm: try mkdir; EEXIST is fine iff the
1790
+ * path is a directory (a file target throws EEXIST at that path); ENOENT
1791
+ * creates the parent first and retries. ENOTDIR past a file reports the
1792
+ * FULL requested path, like Node. `path` is a NUL-terminated mutable
1793
+ * buffer of `len` bytes. */
1794
+ /* The path separators the RECURSIVE walk recognizes: win32 targets take
1795
+ * both slashes (the path module hands out backslashed paths there);
1796
+ * POSIX '/' only — a backslash is an ordinary filename byte. */
1797
+ static bool scr_fs_sep(char c) {
1798
+ #ifdef _WIN32
1799
+ if (c == '\\') return true;
1800
+ #endif
1801
+ return c == '/';
1802
+ }
1803
+
1804
+ static void scr_mkdir_rec(char *path, size_t len, mode_t mode) {
1805
+ if (scr_sys_mkdir(path, mode) == 0) return;
1806
+ int e = errno;
1807
+ struct stat st;
1808
+ if (e == EEXIST) {
1809
+ if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) return;
1810
+ } else if (e == ENOENT) {
1811
+ /* Parent: trim trailing separators, the last component, then the
1812
+ * separator run before it (keep "/" itself). */
1813
+ size_t i = len;
1814
+ while (i > 0 && scr_fs_sep(path[i - 1])) i--;
1815
+ while (i > 0 && !scr_fs_sep(path[i - 1])) i--;
1816
+ while (i > 1 && scr_fs_sep(path[i - 1])) i--;
1817
+ if (i > 0 && i < len) {
1818
+ char saved = path[i];
1819
+ path[i] = 0;
1820
+ #ifdef _WIN32
1821
+ /* POSIX mkdir answers ENOTDIR itself when a path component is a
1822
+ * FILE; the CRT answers ENOENT for that too. Node on Windows still
1823
+ * reports ENOTDIR with the full requested path — recover the
1824
+ * distinction from the parent's stat before recursing. */
1825
+ if (stat(path, &st) == 0 && !S_ISDIR(st.st_mode)) {
1826
+ path[i] = saved;
1827
+ ScrStr *full = scr_str_new(path, len);
1828
+ scr_fs_throw(ENOTDIR, "mkdir", full);
1829
+ scr_str_release(full);
1830
+ return;
1831
+ }
1832
+ #endif
1833
+ scr_mkdir_rec(path, i, mode);
1834
+ path[i] = saved;
1835
+ if (scr_exc_pending()) return;
1836
+ if (scr_sys_mkdir(path, mode) == 0) return;
1837
+ e = errno;
1838
+ if (e == EEXIST && stat(path, &st) == 0 && S_ISDIR(st.st_mode)) return;
1839
+ }
1840
+ }
1841
+ ScrStr *p = scr_str_new(path, len);
1842
+ scr_fs_throw(e, "mkdir", p);
1843
+ scr_str_release(p);
1844
+ }
1845
+
1846
+ void scr_fs_mkdir_recursive(ScrStr *path) {
1847
+ char *buf = malloc(path->len + 1);
1848
+ if (!buf) {
1849
+ fputs("scriptc: out of memory\n", stderr);
1850
+ abort();
1851
+ }
1852
+ memcpy(buf, path->data, path->len + 1); /* ScrStr data is NUL-terminated */
1853
+ scr_mkdir_rec(buf, path->len, 0777);
1854
+ free(buf);
1855
+ }
1856
+
1857
+ /* mkdirSync(p, { recursive: true, mode }): Node passes the mode to every
1858
+ * directory the walk creates (existing ones keep theirs). */
1859
+ void scr_fs_mkdir_recursive_mode(ScrStr *path, double mode) {
1860
+ char *buf = malloc(path->len + 1);
1861
+ if (!buf) {
1862
+ fputs("scriptc: out of memory\n", stderr);
1863
+ abort();
1864
+ }
1865
+ memcpy(buf, path->data, path->len + 1);
1866
+ scr_mkdir_rec(buf, path->len, (mode_t)mode);
1867
+ free(buf);
1868
+ }
1869
+
1870
+ /* First failure of an rm walk, recorded instead of thrown so the retry
1871
+ * form can decide (retryable errno + attempts left → sleep and go again)
1872
+ * before anything reaches the exception cell. `path` is +1 when err != 0;
1873
+ * the throwers release it after scr_fs_throw. */
1874
+ typedef struct {
1875
+ int err;
1876
+ const char *op;
1877
+ ScrStr *path;
1878
+ } ScrRmFail;
1879
+
1880
+ static void scr_rm_fail_set(ScrRmFail *f, int err, const char *op, const char *path, size_t len) {
1881
+ if (f->err != 0) return; /* first failure wins, exactly like the old unwind */
1882
+ f->err = err;
1883
+ f->op = op;
1884
+ f->path = scr_str_new(path, len);
1885
+ }
1886
+
1887
+ /* Post-order tree removal for rmSync's recursive form. Stops at (and
1888
+ * records) the first failure, with the failing path and syscall name. */
1889
+ static void scr_rm_tree_e(const char *path, size_t len, ScrRmFail *f) {
1890
+ struct stat st;
1891
+ if (lstat(path, &st) != 0) {
1892
+ scr_rm_fail_set(f, errno, "lstat", path, len);
1893
+ return;
1894
+ }
1895
+ if (!S_ISDIR(st.st_mode)) {
1896
+ if (unlink(path) != 0) scr_rm_fail_set(f, errno, "unlink", path, len);
1897
+ return;
1898
+ }
1899
+ DIR *d = opendir(path);
1900
+ if (!d) {
1901
+ scr_rm_fail_set(f, errno, "scandir", path, len);
1902
+ return;
1903
+ }
1904
+ const struct dirent *ent;
1905
+ while ((ent = readdir(d)) != NULL) {
1906
+ if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
1907
+ size_t namelen = strlen(ent->d_name);
1908
+ char *child = malloc(len + 1 + namelen + 1);
1909
+ if (!child) {
1910
+ fputs("scriptc: out of memory\n", stderr);
1911
+ abort();
1912
+ }
1913
+ memcpy(child, path, len);
1914
+ child[len] = '/';
1915
+ memcpy(child + len + 1, ent->d_name, namelen + 1);
1916
+ scr_rm_tree_e(child, len + 1 + namelen, f);
1917
+ free(child);
1918
+ if (f->err != 0) {
1919
+ closedir(d);
1920
+ return;
1921
+ }
1922
+ }
1923
+ closedir(d);
1924
+ if (rmdir(path) != 0) scr_rm_fail_set(f, errno, "rmdir", path, len);
1925
+ }
1926
+
1927
+ /* One rm attempt (the shared core of both option forms): lstat dispatch,
1928
+ * force's ENOENT swallow, the non-recursive directory rejection, and the
1929
+ * tree walk — failures recorded in `f`, never thrown. */
1930
+ static void scr_fs_rm_attempt(ScrStr *path, bool recursive, bool force, ScrRmFail *f) {
1931
+ struct stat st;
1932
+ if (lstat(path->data, &st) != 0) {
1933
+ if (force && errno == ENOENT) return; /* Node: force swallows ENOENT */
1934
+ scr_rm_fail_set(f, errno, "lstat", path->data, path->len);
1935
+ return;
1936
+ }
1937
+ if (S_ISDIR(st.st_mode)) {
1938
+ if (!recursive) {
1939
+ /* Node throws ERR_FS_EISDIR here; the EISDIR-prefixed wording is
1940
+ * divergence 13's documented difference. */
1941
+ scr_rm_fail_set(f, EISDIR, "rm", path->data, path->len);
1942
+ return;
1943
+ }
1944
+ scr_rm_tree_e(path->data, path->len, f);
1945
+ return;
1946
+ }
1947
+ if (unlink(path->data) != 0) scr_rm_fail_set(f, errno, "unlink", path->data, path->len);
1948
+ }
1949
+
1950
+ void scr_fs_rm_opts(ScrStr *path, bool recursive, bool force) {
1951
+ ScrRmFail f = {0, NULL, NULL};
1952
+ scr_fs_rm_attempt(path, recursive, force, &f);
1953
+ if (f.err != 0) {
1954
+ scr_fs_throw(f.err, f.op, f.path);
1955
+ scr_str_release(f.path);
1956
+ }
1957
+ }
1958
+
1959
+ /* rmSync(p, { recursive, force, maxRetries, retryDelay }): Node retries
1960
+ * the operation on EBUSY/EMFILE/ENFILE/ENOTEMPTY/EPERM up to maxRetries
1961
+ * times, waiting retryDelay ms LONGER on each try (linear backoff — the
1962
+ * fs.rmSync documented semantics). Everything else throws immediately
1963
+ * with the failing path, exactly like the plain options form. */
1964
+ void scr_fs_rm_opts_retry(ScrStr *path, bool recursive, bool force, double max_retries, double retry_delay) {
1965
+ long tries = max_retries > 0 ? (long)max_retries : 0;
1966
+ double delay_ms = retry_delay > 0 ? retry_delay : 0;
1967
+ ScrRmFail f = {0, NULL, NULL};
1968
+ for (long attempt = 0;; attempt++) {
1969
+ f.err = 0;
1970
+ f.op = NULL;
1971
+ if (f.path) {
1972
+ scr_str_release(f.path);
1973
+ f.path = NULL;
1974
+ }
1975
+ scr_fs_rm_attempt(path, recursive, force, &f);
1976
+ if (f.err == 0) return;
1977
+ bool retryable = f.err == EBUSY || f.err == EMFILE || f.err == ENFILE ||
1978
+ f.err == ENOTEMPTY || f.err == EPERM;
1979
+ if (!retryable || attempt >= tries) break;
1980
+ double ms = delay_ms * (double)(attempt + 1);
1981
+ if (ms > 0) {
1982
+ struct timespec ts;
1983
+ ts.tv_sec = (time_t)(ms / 1000.0);
1984
+ ts.tv_nsec = (long)((ms - (double)ts.tv_sec * 1000.0) * 1000000.0);
1985
+ nanosleep(&ts, NULL);
1986
+ }
1987
+ }
1988
+ scr_fs_throw(f.err, f.op, f.path);
1989
+ scr_str_release(f.path);
1990
+ }
1991
+
1992
+ #ifdef _WIN32
1993
+ /* No mkdtemp in the CRT: libuv's own fallback shape — six random
1994
+ * [a-z0-9] name characters from the CSPRNG, retried on EEXIST. */
1995
+ static char *scr_win_mkdtemp(char *tmpl) {
1996
+ static const char cs[] = "abcdefghijklmnopqrstuvwxyz0123456789";
1997
+ size_t len = strlen(tmpl);
1998
+ for (int tries = 0; tries < 32; tries++) {
1999
+ unsigned char r[6];
2000
+ arc4random_buf(r, sizeof r);
2001
+ for (size_t i = 0; i < 6; i++) tmpl[len - 6 + i] = cs[r[i] % 36];
2002
+ if (mkdir(tmpl) == 0) return tmpl;
2003
+ if (errno != EEXIST) break;
2004
+ }
2005
+ memcpy(tmpl + len - 6, "XXXXXX", 6); /* the error message shows the template */
2006
+ return NULL;
2007
+ }
2008
+ #define mkdtemp scr_win_mkdtemp
2009
+ #endif
2010
+
2011
+ ScrStr *scr_fs_mkdtemp(ScrStr *prefix) {
2012
+ char *tmpl = malloc(prefix->len + 7);
2013
+ if (!tmpl) {
2014
+ fputs("scriptc: out of memory\n", stderr);
2015
+ abort();
2016
+ }
2017
+ memcpy(tmpl, prefix->data, prefix->len);
2018
+ memcpy(tmpl + prefix->len, "XXXXXX", 7);
2019
+ if (!mkdtemp(tmpl)) {
2020
+ /* Node reports the template, X's included: mkdtemp '/nope/x-XXXXXX' */
2021
+ int e = errno;
2022
+ ScrStr *shown = scr_str_new(tmpl, prefix->len + 6);
2023
+ scr_fs_throw(e, "mkdtemp", shown);
2024
+ scr_str_release(shown);
2025
+ free(tmpl);
2026
+ return NULL;
2027
+ }
2028
+ ScrStr *out = scr_str_new(tmpl, prefix->len + 6);
2029
+ free(tmpl);
2030
+ return out;
2031
+ }
2032
+
2033
+ void scr_fs_access(ScrStr *path, double mode) {
2034
+ int m = (int)mode;
2035
+ #ifdef _WIN32
2036
+ /* The CRT access() rejects X_OK (there is no execute bit); Node on
2037
+ * Windows treats X_OK as F_OK, so mask it down to the R/W bits. */
2038
+ m &= 6;
2039
+ #endif
2040
+ if (access(path->data, m) != 0) scr_fs_throw(errno, "access", path);
2041
+ }
2042
+
2043
+ /* The no-path variant of the fs error shape — Node's fd reads report
2044
+ * "EBADF: bad file descriptor, read" with no quoted path. */
2045
+ static void scr_fs_throw_nopath(int e, const char *op) {
2046
+ char namebuf[16];
2047
+ const char *name = scr_errno_name(e, namebuf, sizeof namebuf);
2048
+ const char *text = scr_errno_text(e);
2049
+ char msg[256];
2050
+ int len = snprintf(msg, sizeof msg, "%s: %s, %s", name, text, op);
2051
+ scr_throw_error_msg(SCR_ERR_ERROR, msg, (size_t)len);
2052
+ }
2053
+
2054
+ /* read(2) loop to EOF from the CURRENT position — Node's
2055
+ * readFileSync(fd) semantics for pipes and files alike (the stdin
2056
+ * pattern: readFileSync(0, "utf8")). Returns the malloc'd buffer and
2057
+ * its length, or NULL with the exception pending. */
2058
+ static char *scr_read_fd_all(double fd, size_t *out_len) {
2059
+ size_t cap = 4096, len = 0;
2060
+ char *buf = malloc(cap);
2061
+ if (!buf) {
2062
+ fputs("scriptc: out of memory\n", stderr);
2063
+ abort();
2064
+ }
2065
+ for (;;) {
2066
+ if (cap - len < 2048) {
2067
+ cap *= 2;
2068
+ char *grown = realloc(buf, cap);
2069
+ if (!grown) {
2070
+ fputs("scriptc: out of memory\n", stderr);
2071
+ abort();
2072
+ }
2073
+ buf = grown;
2074
+ }
2075
+ ssize_t n = read((int)fd, buf + len, cap - len);
2076
+ if (n < 0) {
2077
+ if (errno == EINTR) continue;
2078
+ int e = errno;
2079
+ free(buf);
2080
+ scr_fs_throw_nopath(e, "read");
2081
+ return NULL;
2082
+ }
2083
+ if (n == 0) break;
2084
+ len += (size_t)n;
2085
+ }
2086
+ *out_len = len;
2087
+ return buf;
2088
+ }
2089
+
2090
+ ScrStr *scr_fs_read_fd(double fd) {
2091
+ size_t len;
2092
+ char *buf = scr_read_fd_all(fd, &len);
2093
+ if (!buf) return NULL;
2094
+ ScrStr *s = scr_str_new(buf, len);
2095
+ free(buf);
2096
+ return s;
2097
+ }
2098
+
2099
+ ScrBytes *scr_fs_read_fd_bytes(double fd) {
2100
+ size_t len;
2101
+ char *buf = scr_read_fd_all(fd, &len);
2102
+ if (!buf) return NULL;
2103
+ ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)len);
2104
+ if (len > 0) memcpy(b->data, buf, len);
2105
+ free(buf);
2106
+ return b;
2107
+ }
2108
+
2109
+ /* ── Atomics.wait: the synchronous-sleep idiom ───────────────────────
2110
+ * Atomics.wait(int32Array, idx, expected, timeoutMs). scriptc has no
2111
+ * threads: nothing can ever notify a waiter, so the spec's behavior for
2112
+ * every compilable program is exactly "compare, then sleep out the
2113
+ * timeout" — "not-equal" immediately when the element differs from
2114
+ * `expected`, "timed-out" after a real nanosleep otherwise ("ok" is
2115
+ * unreachable; the compiler requires the timeout argument, since an
2116
+ * infinite wait here would be a certain deadlock). The sleep resumes
2117
+ * across EINTR so signals don't shorten it. Timeout semantics follow the
2118
+ * spec: NaN/+Infinity would be infinite (compiler-fenced by requiring
2119
+ * the argument, but a runtime NaN clamps to 0 defensively), negatives
2120
+ * clamp to 0. */
2121
+ ScrStr *scr_atomics_wait(ScrBytes *arr, double idx, double expected, double timeout_ms) {
2122
+ double have = scr_bytes_get(arr, idx); /* traps out-of-range like every access */
2123
+ /* The comparison is on the stored int32 vs ToInt32(expected). */
2124
+ double t = expected;
2125
+ if (t != t || isinf(t)) t = 0;
2126
+ else {
2127
+ t = trunc(t);
2128
+ t = fmod(t, 4294967296.0);
2129
+ if (t < 0) t += 4294967296.0;
2130
+ if (t >= 2147483648.0) t -= 4294967296.0;
2131
+ }
2132
+ if (have != t) return scr_str_new("not-equal", 9);
2133
+ double ms = timeout_ms;
2134
+ if (ms != ms || ms < 0) ms = 0;
2135
+ if (ms > 0) {
2136
+ struct timespec left = {
2137
+ (time_t)(ms / 1000.0),
2138
+ (long)((ms - (double)(time_t)(ms / 1000.0) * 1000.0) * 1e6),
2139
+ };
2140
+ struct timespec rem;
2141
+ while (nanosleep(&left, &rem) != 0 && errno == EINTR) left = rem;
2142
+ }
2143
+ return scr_str_new("timed-out", 9);
2144
+ }
2145
+
2146
+ /* ── the tty probes ──────────────────────────────────────────────────── */
2147
+
2148
+ bool scr_process_is_tty(double fd) { return isatty((int)fd) != 0; }
2149
+
2150
+ /* Terminal width for process.stdout/stderr.columns: ioctl(TIOCGWINSZ) on
2151
+ * the stream's fd, exactly Node's tty.WriteStream source of truth. A
2152
+ * non-TTY stream, or a terminal that refuses the ioctl, answers -1 and
2153
+ * the emitter's union construction turns that into the undefined arm —
2154
+ * Node's missing `.columns` on non-TTY streams. */
2155
+ double scr_process_columns(double fd) {
2156
+ if (!isatty((int)fd)) return -1;
2157
+ #ifdef _WIN32
2158
+ /* The console buffer's window width — libuv's uv_tty_get_winsize. */
2159
+ HANDLE h = (HANDLE)_get_osfhandle((int)fd);
2160
+ CONSOLE_SCREEN_BUFFER_INFO info;
2161
+ if (h == INVALID_HANDLE_VALUE || !GetConsoleScreenBufferInfo(h, &info)) return -1;
2162
+ return (double)(info.srWindow.Right - info.srWindow.Left + 1);
2163
+ #else
2164
+ struct winsize ws;
2165
+ if (ioctl((int)fd, TIOCGWINSZ, &ws) != 0) return -1;
2166
+ return (double)ws.ws_col;
2167
+ #endif
2168
+ }
2169
+
2170
+ /* process.stdin.setRawMode(mode). TTY stdin: libuv's UV_TTY_MODE_RAW
2171
+ * termios flag set — exactly what Node's setRawMode(true) applies — and
2172
+ * setRawMode(false) restores the termios saved at the first raw entry
2173
+ * (libuv's orig_termios), a no-op when raw mode was never entered.
2174
+ * NON-TTY stdin: Node's process.stdin is a Socket with no setRawMode
2175
+ * member at all, so the call throws Node's exact catchable TypeError. */
2176
+ #ifdef _WIN32
2177
+ static DWORD scr_stdin_cooked;
2178
+ static bool scr_stdin_cooked_saved = false;
2179
+
2180
+ void scr_process_stdin_set_raw_mode(bool raw) {
2181
+ if (!isatty(0)) {
2182
+ const char msg[] = "process.stdin.setRawMode is not a function";
2183
+ scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
2184
+ return;
2185
+ }
2186
+ /* libuv's uv_tty_set_mode(UV_TTY_MODE_RAW) console half: drop line
2187
+ * buffering, echo, and Ctrl-C cooking; restore the entry mode on the
2188
+ * way back — the termios save/restore shape, translated. */
2189
+ HANDLE h = (HANDLE)_get_osfhandle(0);
2190
+ DWORD mode;
2191
+ if (h == INVALID_HANDLE_VALUE || !GetConsoleMode(h, &mode)) return;
2192
+ if (raw) {
2193
+ if (!scr_stdin_cooked_saved) {
2194
+ scr_stdin_cooked = mode;
2195
+ scr_stdin_cooked_saved = true;
2196
+ }
2197
+ mode &= ~(DWORD)(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
2198
+ (void)SetConsoleMode(h, mode);
2199
+ } else if (scr_stdin_cooked_saved) {
2200
+ (void)SetConsoleMode(h, scr_stdin_cooked);
2201
+ }
2202
+ }
2203
+ #else
2204
+ static struct termios scr_stdin_cooked;
2205
+ static bool scr_stdin_cooked_saved = false;
2206
+
2207
+ void scr_process_stdin_set_raw_mode(bool raw) {
2208
+ if (!isatty(0)) {
2209
+ const char msg[] = "process.stdin.setRawMode is not a function";
2210
+ scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
2211
+ return;
2212
+ }
2213
+ if (raw) {
2214
+ struct termios t;
2215
+ if (tcgetattr(0, &t) != 0) return;
2216
+ if (!scr_stdin_cooked_saved) {
2217
+ scr_stdin_cooked = t;
2218
+ scr_stdin_cooked_saved = true;
2219
+ }
2220
+ /* libuv uv__tty_make_raw (UV_TTY_MODE_RAW) */
2221
+ t.c_iflag &= (tcflag_t)~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
2222
+ t.c_oflag |= (tcflag_t)ONLCR;
2223
+ t.c_cflag |= (tcflag_t)CS8;
2224
+ t.c_lflag &= (tcflag_t)~(ECHO | ICANON | IEXTEN | ISIG);
2225
+ t.c_cc[VMIN] = 1;
2226
+ t.c_cc[VTIME] = 0;
2227
+ (void)tcsetattr(0, TCSADRAIN, &t);
2228
+ } else if (scr_stdin_cooked_saved) {
2229
+ (void)tcsetattr(0, TCSADRAIN, &scr_stdin_cooked);
2230
+ }
2231
+ }
2232
+ #endif /* _WIN32 */
2233
+
2234
+ /* Node's destroy() tears down the stream: the events unit (scr_events.c)
2235
+ * drops every stdin listener, stops watching fd 0, and ends a running
2236
+ * for-await — nothing fires after, and the loop stops keeping the
2237
+ * process alive for stdin. With the events unit not linked there is
2238
+ * nothing to tear down and the call is a no-op, as before. */
2239
+ void scr_process_stdin_destroy(void) {
2240
+ if (scr_stdin_destroy_hook != NULL) scr_stdin_destroy_hook();
2241
+ }
2242
+
2243
+ /* ── Stats values ────────────────────────────────────────────────────
2244
+ * An immutable snapshot of stat(2) results — the slice the lowered
2245
+ * surface exposes (isFile/isDirectory/size). statSync THROWS like the
2246
+ * other sync fs calls; the promise form rejects (see the fsp section). */
2247
+
2248
+ struct ScrStats {
2249
+ size_t rc;
2250
+ bool is_file;
2251
+ bool is_dir;
2252
+ bool is_symlink; /* lstat only — a followed stat never sees one */
2253
+ double size;
2254
+ double mtime_ms; /* milliseconds with the nanosecond fraction (Node) */
2255
+ };
2256
+
2257
+ ScrStats *scr_stats_retain(ScrStats *s) {
2258
+ if (s->rc != SIZE_MAX) s->rc++;
2259
+ return s;
2260
+ }
2261
+
2262
+ void scr_stats_release(ScrStats *s) {
2263
+ if (!s || s->rc == SIZE_MAX) return;
2264
+ if (--s->rc == 0) free(s);
2265
+ }
2266
+
2267
+ void *scr_stats_retain_v(void *p) { return scr_stats_retain(p); }
2268
+ void scr_stats_release_v(void *p) { scr_stats_release(p); }
2269
+
2270
+ bool scr_stats_is_file(ScrStats *s) { return s->is_file; }
2271
+ bool scr_stats_is_dir(ScrStats *s) { return s->is_dir; }
2272
+ bool scr_stats_is_symlink(ScrStats *s) { return s->is_symlink; }
2273
+ double scr_stats_size(ScrStats *s) { return s->size; }
2274
+ double scr_stats_mtime_ms(ScrStats *s) { return s->mtime_ms; }
2275
+
2276
+ static ScrStats *scr_stats_of(const struct stat *st) {
2277
+ ScrStats *s = malloc(sizeof(ScrStats));
2278
+ if (!s) {
2279
+ fputs("scriptc: out of memory\n", stderr);
2280
+ abort();
2281
+ }
2282
+ s->rc = 1;
2283
+ s->is_file = S_ISREG(st->st_mode);
2284
+ s->is_dir = S_ISDIR(st->st_mode);
2285
+ #if defined(_WIN32)
2286
+ /* CRT stat has no symlink view (lstat above degrades to stat) and no
2287
+ * sub-second mtime — whole seconds where Node reads the FILETIME's
2288
+ * 100ns units (divergence: mtimeMs precision; mechanical fix is
2289
+ * GetFileAttributesEx). */
2290
+ s->is_symlink = false;
2291
+ s->mtime_ms = (double)st->st_mtime * 1000.0;
2292
+ #else
2293
+ s->is_symlink = S_ISLNK(st->st_mode);
2294
+ #if defined(__APPLE__)
2295
+ s->mtime_ms = (double)st->st_mtimespec.tv_sec * 1000.0 +
2296
+ (double)st->st_mtimespec.tv_nsec / 1e6;
2297
+ #else
2298
+ s->mtime_ms = (double)st->st_mtim.tv_sec * 1000.0 +
2299
+ (double)st->st_mtim.tv_nsec / 1e6;
2300
+ #endif
2301
+ #endif
2302
+ s->size = (double)st->st_size;
2303
+ return s;
2304
+ }
2305
+
2306
+ ScrStats *scr_fs_stat(ScrStr *path) {
2307
+ struct stat st;
2308
+ if (stat(path->data, &st) != 0) { /* follows symlinks, like Node's statSync */
2309
+ scr_fs_throw(errno, "stat", path);
2310
+ return NULL;
2311
+ }
2312
+ return scr_stats_of(&st);
2313
+ }
2314
+
2315
+ ScrStats *scr_fs_lstat(ScrStr *path) {
2316
+ struct stat st;
2317
+ if (lstat(path->data, &st) != 0) { /* no follow; Node reports lstat */
2318
+ scr_fs_throw(errno, "lstat", path);
2319
+ return NULL;
2320
+ }
2321
+ return scr_stats_of(&st);
2322
+ }
2323
+
2324
+ ScrArr *scr_fs_readdir(ScrStr *path) {
2325
+ DIR *d = opendir(path->data);
2326
+ if (!d) {
2327
+ scr_fs_throw(errno, "scandir", path); /* Node reports scandir */
2328
+ return NULL;
2329
+ }
2330
+ ScrArr *arr = scr_arr_new(SCR_ELEM_STR, 8);
2331
+ const struct dirent *ent;
2332
+ while ((ent = readdir(d)) != NULL) {
2333
+ if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
2334
+ scr_arr_push_ref(arr, scr_str_new(ent->d_name, strlen(ent->d_name)));
2335
+ }
2336
+ closedir(d);
2337
+ return arr; /* OS order, exactly like Node (unsorted) */
2338
+ }
2339
+
2340
+ /* ── the withFileTypes scandir snapshot ──────────────────────────────
2341
+ * One readdir pass capturing name + entry kind for the emitter's Dirent
2342
+ * assembly (scr_runtime.h has the contract). Kinds are libuv's UV_DIRENT
2343
+ * encoding; DT_UNKNOWN (filesystems that don't fill d_type) falls back
2344
+ * to lstat(2) on the joined path — Node's own getDirents rule (a failed
2345
+ * lstat leaves 0/unknown: every is*() probe answers false, like Node's
2346
+ * Dirent over an UNKNOWN row it could not stat). */
2347
+
2348
+ struct ScrScandir {
2349
+ size_t len, cap;
2350
+ ScrStr **names;
2351
+ unsigned char *kinds;
2352
+ };
2353
+
2354
+ static unsigned char scr_dirent_kind_of_mode(mode_t m) {
2355
+ if (S_ISREG(m)) return 1;
2356
+ if (S_ISDIR(m)) return 2;
2357
+ #ifdef S_ISLNK /* no symlink/socket bits in the CRT's stat */
2358
+ if (S_ISLNK(m)) return 3;
2359
+ #endif
2360
+ if (S_ISFIFO(m)) return 4;
2361
+ #ifdef S_ISSOCK
2362
+ if (S_ISSOCK(m)) return 5;
2363
+ #endif
2364
+ if (S_ISCHR(m)) return 6;
2365
+ if (S_ISBLK(m)) return 7;
2366
+ return 0;
2367
+ }
2368
+
2369
+ ScrScandir *scr_fs_scandir(ScrStr *path) {
2370
+ DIR *d = opendir(path->data);
2371
+ if (!d) {
2372
+ scr_fs_throw(errno, "scandir", path); /* Node reports scandir */
2373
+ return NULL;
2374
+ }
2375
+ ScrScandir *s = malloc(sizeof *s);
2376
+ if (!s) {
2377
+ fputs("scriptc: out of memory\n", stderr);
2378
+ abort();
2379
+ }
2380
+ s->len = 0;
2381
+ s->cap = 8;
2382
+ s->names = malloc(s->cap * sizeof *s->names);
2383
+ s->kinds = malloc(s->cap);
2384
+ if (!s->names || !s->kinds) {
2385
+ fputs("scriptc: out of memory\n", stderr);
2386
+ abort();
2387
+ }
2388
+ const struct dirent *ent;
2389
+ while ((ent = readdir(d)) != NULL) {
2390
+ if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
2391
+ unsigned char kind;
2392
+ #ifdef DT_REG
2393
+ switch (ent->d_type) {
2394
+ case DT_REG: kind = 1; break;
2395
+ case DT_DIR: kind = 2; break;
2396
+ case DT_LNK: kind = 3; break;
2397
+ case DT_FIFO: kind = 4; break;
2398
+ case DT_SOCK: kind = 5; break;
2399
+ case DT_CHR: kind = 6; break;
2400
+ case DT_BLK: kind = 7; break;
2401
+ default: { /* DT_UNKNOWN: the lstat fallback */
2402
+ char buf[4096];
2403
+ int w = snprintf(buf, sizeof buf, "%s/%s", path->data, ent->d_name);
2404
+ struct stat st;
2405
+ kind = (w > 0 && (size_t)w < sizeof buf && lstat(buf, &st) == 0)
2406
+ ? scr_dirent_kind_of_mode(st.st_mode)
2407
+ : 0;
2408
+ break;
2409
+ }
2410
+ }
2411
+ #else
2412
+ { /* mingw dirent has no d_type: every row takes the stat fallback
2413
+ * (lstat degrades to stat on Windows — see the include seam). */
2414
+ char buf[4096];
2415
+ int w = snprintf(buf, sizeof buf, "%s/%s", path->data, ent->d_name);
2416
+ struct stat st;
2417
+ kind = (w > 0 && (size_t)w < sizeof buf && lstat(buf, &st) == 0)
2418
+ ? scr_dirent_kind_of_mode(st.st_mode)
2419
+ : 0;
2420
+ }
2421
+ #endif
2422
+ if (s->len == s->cap) {
2423
+ s->cap *= 2;
2424
+ s->names = realloc(s->names, s->cap * sizeof *s->names);
2425
+ s->kinds = realloc(s->kinds, s->cap);
2426
+ if (!s->names || !s->kinds) {
2427
+ fputs("scriptc: out of memory\n", stderr);
2428
+ abort();
2429
+ }
2430
+ }
2431
+ s->names[s->len] = scr_str_new(ent->d_name, strlen(ent->d_name));
2432
+ s->kinds[s->len] = kind;
2433
+ s->len++;
2434
+ }
2435
+ closedir(d);
2436
+ return s;
2437
+ }
2438
+
2439
+ size_t scr_fs_scandir_count(const ScrScandir *s) { return s->len; }
2440
+
2441
+ ScrStr *scr_fs_scandir_name(const ScrScandir *s, size_t i) {
2442
+ return scr_str_retain(s->names[i]);
2443
+ }
2444
+
2445
+ double scr_fs_scandir_type(const ScrScandir *s, size_t i) { return (double)s->kinds[i]; }
2446
+
2447
+ void scr_fs_scandir_free(ScrScandir *s) {
2448
+ for (size_t i = 0; i < s->len; i++) scr_str_release(s->names[i]);
2449
+ free(s->names);
2450
+ free(s->kinds);
2451
+ free(s);
2452
+ }
2453
+
2454
+ /* ── node:crypto (the string-producing slice) ────────────────────────
2455
+ * Buffers aren't representable, so the lowered surface is exactly the
2456
+ * string-producing forms: randomUUID(), and the COMPOSED pattern
2457
+ * randomBytes(n).toString("hex"|"base64") — one libCall, the Buffer never
2458
+ * escapes. Randomness comes from arc4random_buf (the CSPRNG both macOS
2459
+ * and modern glibc provide). */
2460
+
2461
+ /* ── the scalar Math statics ─────────────────────────────────────────
2462
+ * Math.min/max at two arguments: the ECMA folds — C's fmin/fmax are NOT
2463
+ * these (they return the non-NaN operand where JS lets NaN poison, and
2464
+ * leave the ±0 order unspecified). Math.random(): a uniform double in
2465
+ * [0,1) at the spec's 53-bit granularity, drawn from arc4random_buf —
2466
+ * the same CSPRNG behind the crypto lowerings. Same distribution as
2467
+ * Node's, necessarily a different sequence (SEMANTICS.md 62); range and
2468
+ * granularity are pinned differentially by invariant, not by bytes. */
2469
+
2470
+ double scr_math_min(double a, double b) {
2471
+ if (isnan(a) || isnan(b)) return (double)NAN;
2472
+ if (a == 0.0 && b == 0.0) return signbit(a) ? a : b; /* -0 wins */
2473
+ return a < b ? a : b;
2474
+ }
2475
+
2476
+ double scr_math_max(double a, double b) {
2477
+ if (isnan(a) || isnan(b)) return (double)NAN;
2478
+ if (a == 0.0 && b == 0.0) return signbit(a) ? b : a; /* +0 wins */
2479
+ return a > b ? a : b;
2480
+ }
2481
+
2482
+ /* Math.round: ECMA half-toward-+Infinity. NOT C round() (half away from
2483
+ * zero: round(-1.5) is -2 where JS answers -1) and NOT floor(x + 0.5)
2484
+ * (the float ADD drifts at the epsilon boundary: 0.49999999999999994 +
2485
+ * 0.5 == 1.0 in doubles where the exact sum is below one — JS answers
2486
+ * 0). x - floor(x) is EXACT for doubles (Sterbenz), so the fraction
2487
+ * comparison decides losslessly; results in (-0.5, 0] keep the sign (JS:
2488
+ * Math.round(-0.3) is -0). */
2489
+ double scr_math_round(double x) {
2490
+ if (isnan(x) || isinf(x) || x == 0.0) return x;
2491
+ double f = floor(x);
2492
+ double diff = x - f;
2493
+ double r = diff < 0.5 ? f : f + 1.0;
2494
+ return (r == 0.0 && x < 0.0) ? -0.0 : r;
2495
+ }
2496
+
2497
+ double scr_math_random(void) {
2498
+ uint64_t r;
2499
+ arc4random_buf(&r, sizeof r);
2500
+ /* The top 53 bits scaled by 2^-53: every representable k/2^53 in [0,1)
2501
+ * is equally likely — V8's own construction. */
2502
+ return (double)(r >> 11) * 0x1.0p-53;
2503
+ }
2504
+
2505
+ ScrStr *scr_crypto_random_uuid(void) {
2506
+ unsigned char b[16];
2507
+ arc4random_buf(b, sizeof b);
2508
+ b[6] = (unsigned char)((b[6] & 0x0f) | 0x40); /* version 4 */
2509
+ b[8] = (unsigned char)((b[8] & 0x3f) | 0x80); /* variant 10xx */
2510
+ char out[37];
2511
+ static const char hex[] = "0123456789abcdef";
2512
+ size_t o = 0;
2513
+ for (size_t i = 0; i < 16; i++) {
2514
+ if (i == 4 || i == 6 || i == 8 || i == 10) out[o++] = '-';
2515
+ out[o++] = hex[b[i] >> 4];
2516
+ out[o++] = hex[b[i] & 0x0f];
2517
+ }
2518
+ return scr_str_new(out, 36);
2519
+ }
2520
+
2521
+ /* randomBytes(n).toString(enc): n truncates like Node's (1.5 → 1 byte);
2522
+ * out-of-range n THROWS Node's RangeError verbatim. enc is "hex" or
2523
+ * "base64" (the compiler fences other encodings at the call site). */
2524
+ ScrStr *scr_crypto_random_string(double n, ScrStr *enc) {
2525
+ if (!(n >= 0 && n <= 2147483647)) {
2526
+ char num[32];
2527
+ size_t numlen = scr_f64_to_str(n, num);
2528
+ char msg[128];
2529
+ int mlen = snprintf(msg, sizeof msg,
2530
+ "The value of \"size\" is out of range. It must be >= 0 && <= 2147483647. Received %.*s",
2531
+ (int)numlen, num);
2532
+ scr_throw_error_msg(SCR_ERR_RANGE, msg, (size_t)mlen);
2533
+ return NULL;
2534
+ }
2535
+ size_t size = (size_t)n;
2536
+ unsigned char *bytes = malloc(size ? size : 1);
2537
+ if (!bytes) {
2538
+ fputs("scriptc: out of memory\n", stderr);
2539
+ abort();
2540
+ }
2541
+ arc4random_buf(bytes, size);
2542
+ ScrStr *out;
2543
+ if (enc->len == 3 && memcmp(enc->data, "hex", 3) == 0) {
2544
+ static const char hex[] = "0123456789abcdef";
2545
+ char *buf = malloc(size * 2 + 1);
2546
+ if (!buf) {
2547
+ fputs("scriptc: out of memory\n", stderr);
2548
+ abort();
2549
+ }
2550
+ for (size_t i = 0; i < size; i++) {
2551
+ buf[i * 2] = hex[bytes[i] >> 4];
2552
+ buf[i * 2 + 1] = hex[bytes[i] & 0x0f];
2553
+ }
2554
+ out = scr_str_new(buf, size * 2);
2555
+ free(buf);
2556
+ } else {
2557
+ /* base64, standard alphabet, '=' padded — Buffer.toString("base64"). */
2558
+ static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2559
+ size_t outlen = (size + 2) / 3 * 4;
2560
+ char *buf = malloc(outlen + 1);
2561
+ if (!buf) {
2562
+ fputs("scriptc: out of memory\n", stderr);
2563
+ abort();
2564
+ }
2565
+ size_t o = 0;
2566
+ for (size_t i = 0; i < size; i += 3) {
2567
+ unsigned v = (unsigned)bytes[i] << 16;
2568
+ if (i + 1 < size) v |= (unsigned)bytes[i + 1] << 8;
2569
+ if (i + 2 < size) v |= (unsigned)bytes[i + 2];
2570
+ buf[o++] = b64[(v >> 18) & 63];
2571
+ buf[o++] = b64[(v >> 12) & 63];
2572
+ buf[o++] = i + 1 < size ? b64[(v >> 6) & 63] : '=';
2573
+ buf[o++] = i + 2 < size ? b64[v & 63] : '=';
2574
+ }
2575
+ out = scr_str_new(buf, o);
2576
+ free(buf);
2577
+ }
2578
+ free(bytes);
2579
+ return out;
2580
+ }
2581
+
2582
+ /* ── SHA-256 (FIPS 180-4) — the composed createHash chain ────────────
2583
+ * createHash("sha256").update(data).digest("hex") fuses into one call in
2584
+ * the compiler (the Hash handle never materializes), so the runtime
2585
+ * surface is just hash-these-bytes-to-hex. Straightforward FIPS 180-4
2586
+ * implementation; the differential corpus pins it against Node's own
2587
+ * digests. */
2588
+
2589
+ static const uint32_t scr_sha256_k[64] = {
2590
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
2591
+ 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
2592
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
2593
+ 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
2594
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
2595
+ 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
2596
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
2597
+ 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
2598
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
2599
+ 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
2600
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
2601
+
2602
+ static uint32_t scr_sha256_rotr(uint32_t x, unsigned n) {
2603
+ return (x >> n) | (x << (32 - n));
2604
+ }
2605
+
2606
+ static void scr_sha256_block(uint32_t h[8], const unsigned char *p) {
2607
+ uint32_t w[64];
2608
+ for (int i = 0; i < 16; i++) {
2609
+ w[i] = ((uint32_t)p[i * 4] << 24) | ((uint32_t)p[i * 4 + 1] << 16) |
2610
+ ((uint32_t)p[i * 4 + 2] << 8) | (uint32_t)p[i * 4 + 3];
2611
+ }
2612
+ for (int i = 16; i < 64; i++) {
2613
+ uint32_t s0 = scr_sha256_rotr(w[i - 15], 7) ^ scr_sha256_rotr(w[i - 15], 18) ^ (w[i - 15] >> 3);
2614
+ uint32_t s1 = scr_sha256_rotr(w[i - 2], 17) ^ scr_sha256_rotr(w[i - 2], 19) ^ (w[i - 2] >> 10);
2615
+ w[i] = w[i - 16] + s0 + w[i - 7] + s1;
2616
+ }
2617
+ uint32_t a = h[0], b = h[1], c = h[2], d = h[3];
2618
+ uint32_t e = h[4], f = h[5], g = h[6], hh = h[7];
2619
+ for (int i = 0; i < 64; i++) {
2620
+ uint32_t s1 = scr_sha256_rotr(e, 6) ^ scr_sha256_rotr(e, 11) ^ scr_sha256_rotr(e, 25);
2621
+ uint32_t ch = (e & f) ^ (~e & g);
2622
+ uint32_t t1 = hh + s1 + ch + scr_sha256_k[i] + w[i];
2623
+ uint32_t s0 = scr_sha256_rotr(a, 2) ^ scr_sha256_rotr(a, 13) ^ scr_sha256_rotr(a, 22);
2624
+ uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
2625
+ uint32_t t2 = s0 + maj;
2626
+ hh = g; g = f; f = e; e = d + t1;
2627
+ d = c; c = b; b = a; a = t1 + t2;
2628
+ }
2629
+ h[0] += a; h[1] += b; h[2] += c; h[3] += d;
2630
+ h[4] += e; h[5] += f; h[6] += g; h[7] += hh;
2631
+ }
2632
+
2633
+ /* Final block(s) shared shape: the 0x80 terminator, zero padding, 64-bit
2634
+ * big-endian bit length (FIPS 180-4 — SHA-1 and SHA-256 pad alike). */
2635
+ static size_t scr_sha256_digest(const unsigned char *data, size_t len, unsigned char out[32]) {
2636
+ uint32_t h[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
2637
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
2638
+ size_t i = 0;
2639
+ for (; i + 64 <= len; i += 64) scr_sha256_block(h, data + i);
2640
+ unsigned char tail[128];
2641
+ size_t rem = len - i;
2642
+ memcpy(tail, data + i, rem);
2643
+ tail[rem] = 0x80;
2644
+ size_t pad = (rem + 1 + 8 <= 64) ? 64 : 128;
2645
+ memset(tail + rem + 1, 0, pad - rem - 1 - 8);
2646
+ uint64_t bits = (uint64_t)len * 8;
2647
+ for (int b = 0; b < 8; b++) tail[pad - 1 - b] = (unsigned char)(bits >> (8 * b));
2648
+ scr_sha256_block(h, tail);
2649
+ if (pad == 128) scr_sha256_block(h, tail + 64);
2650
+ for (int j = 0; j < 8; j++) {
2651
+ for (int b = 0; b < 4; b++) out[j * 4 + b] = (unsigned char)(h[j] >> (24 - 8 * b));
2652
+ }
2653
+ return 32;
2654
+ }
2655
+
2656
+ /* ── SHA-1 (FIPS 180-4) — the RFC 6455 Sec-WebSocket-Accept hash ────── */
2657
+
2658
+ static void scr_sha1_block(uint32_t h[5], const unsigned char *p) {
2659
+ uint32_t w[80];
2660
+ for (int i = 0; i < 16; i++) {
2661
+ w[i] = ((uint32_t)p[i * 4] << 24) | ((uint32_t)p[i * 4 + 1] << 16) |
2662
+ ((uint32_t)p[i * 4 + 2] << 8) | (uint32_t)p[i * 4 + 3];
2663
+ }
2664
+ for (int i = 16; i < 80; i++) {
2665
+ uint32_t x = w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16];
2666
+ w[i] = (x << 1) | (x >> 31);
2667
+ }
2668
+ uint32_t a = h[0], b = h[1], c = h[2], d = h[3], e = h[4];
2669
+ for (int i = 0; i < 80; i++) {
2670
+ uint32_t f, k;
2671
+ if (i < 20) { f = (b & c) | (~b & d); k = 0x5a827999; }
2672
+ else if (i < 40) { f = b ^ c ^ d; k = 0x6ed9eba1; }
2673
+ else if (i < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8f1bbcdc; }
2674
+ else { f = b ^ c ^ d; k = 0xca62c1d6; }
2675
+ uint32_t t = ((a << 5) | (a >> 27)) + f + e + k + w[i];
2676
+ e = d; d = c; c = ((b << 30) | (b >> 2)); b = a; a = t;
2677
+ }
2678
+ h[0] += a; h[1] += b; h[2] += c; h[3] += d; h[4] += e;
2679
+ }
2680
+
2681
+ static size_t scr_sha1_digest(const unsigned char *data, size_t len, unsigned char out[32]) {
2682
+ uint32_t h[5] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0};
2683
+ size_t i = 0;
2684
+ for (; i + 64 <= len; i += 64) scr_sha1_block(h, data + i);
2685
+ unsigned char tail[128];
2686
+ size_t rem = len - i;
2687
+ memcpy(tail, data + i, rem);
2688
+ tail[rem] = 0x80;
2689
+ size_t pad = (rem + 1 + 8 <= 64) ? 64 : 128;
2690
+ memset(tail + rem + 1, 0, pad - rem - 1 - 8);
2691
+ uint64_t bits = (uint64_t)len * 8;
2692
+ for (int b = 0; b < 8; b++) tail[pad - 1 - b] = (unsigned char)(bits >> (8 * b));
2693
+ scr_sha1_block(h, tail);
2694
+ if (pad == 128) scr_sha1_block(h, tail + 64);
2695
+ for (int j = 0; j < 5; j++) {
2696
+ for (int b = 0; b < 4; b++) out[j * 4 + b] = (unsigned char)(h[j] >> (24 - 8 * b));
2697
+ }
2698
+ return 20;
2699
+ }
2700
+
2701
+ /* The digest's encoding: "hex" or "base64" (compiler-fenced literals). */
2702
+ static ScrStr *scr_digest_encode(const unsigned char *d, size_t n, const ScrStr *enc) {
2703
+ if (enc->len == 3 && memcmp(enc->data, "hex", 3) == 0) {
2704
+ static const char hex[] = "0123456789abcdef";
2705
+ char buf[64];
2706
+ for (size_t i = 0; i < n; i++) {
2707
+ buf[i * 2] = hex[d[i] >> 4];
2708
+ buf[i * 2 + 1] = hex[d[i] & 0x0f];
2709
+ }
2710
+ return scr_str_new(buf, n * 2);
2711
+ }
2712
+ /* base64, standard alphabet, '=' padded — Buffer.toString("base64"). */
2713
+ static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2714
+ char buf[48];
2715
+ size_t o = 0;
2716
+ for (size_t i = 0; i < n; i += 3) {
2717
+ unsigned v = (unsigned)d[i] << 16;
2718
+ if (i + 1 < n) v |= (unsigned)d[i + 1] << 8;
2719
+ if (i + 2 < n) v |= (unsigned)d[i + 2];
2720
+ buf[o++] = b64[(v >> 18) & 63];
2721
+ buf[o++] = b64[(v >> 12) & 63];
2722
+ buf[o++] = i + 1 < n ? b64[(v >> 6) & 63] : '=';
2723
+ buf[o++] = i + 2 < n ? b64[v & 63] : '=';
2724
+ }
2725
+ return scr_str_new(buf, o);
2726
+ }
2727
+
2728
+ /* ── MD5 (RFC 1321) — island npm code only (the static frontend fences
2729
+ * every non-SHA algorithm literal; published packages hash cache keys and
2730
+ * etags with md5, so the island's createHash carries it). ─────────── */
2731
+
2732
+ static const uint32_t scr_md5_k[64] = {
2733
+ 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a,
2734
+ 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
2735
+ 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340,
2736
+ 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
2737
+ 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8,
2738
+ 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
2739
+ 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa,
2740
+ 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
2741
+ 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92,
2742
+ 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
2743
+ 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391};
2744
+ static const unsigned char scr_md5_r[64] = {
2745
+ 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
2746
+ 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
2747
+ 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
2748
+ 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21};
2749
+
2750
+ static void scr_md5_block(uint32_t h[4], const unsigned char *p) {
2751
+ uint32_t m[16];
2752
+ for (int i = 0; i < 16; i++) {
2753
+ m[i] = (uint32_t)p[i * 4] | ((uint32_t)p[i * 4 + 1] << 8) |
2754
+ ((uint32_t)p[i * 4 + 2] << 16) | ((uint32_t)p[i * 4 + 3] << 24);
2755
+ }
2756
+ uint32_t a = h[0], b = h[1], c = h[2], d = h[3];
2757
+ for (int i = 0; i < 64; i++) {
2758
+ uint32_t f, g;
2759
+ if (i < 16) { f = (b & c) | (~b & d); g = (uint32_t)i; }
2760
+ else if (i < 32) { f = (d & b) | (~d & c); g = (5u * i + 1) & 15; }
2761
+ else if (i < 48) { f = b ^ c ^ d; g = (3u * i + 5) & 15; }
2762
+ else { f = c ^ (b | ~d); g = (7u * i) & 15; }
2763
+ uint32_t t = d;
2764
+ d = c;
2765
+ c = b;
2766
+ uint32_t x = a + f + scr_md5_k[i] + m[g];
2767
+ b = b + ((x << scr_md5_r[i]) | (x >> (32 - scr_md5_r[i])));
2768
+ a = t;
2769
+ }
2770
+ h[0] += a; h[1] += b; h[2] += c; h[3] += d;
2771
+ }
2772
+
2773
+ static size_t scr_md5_digest(const unsigned char *data, size_t len, unsigned char out[32]) {
2774
+ uint32_t h[4] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476};
2775
+ size_t i = 0;
2776
+ for (; i + 64 <= len; i += 64) scr_md5_block(h, data + i);
2777
+ unsigned char tail[128];
2778
+ size_t rem = len - i;
2779
+ memcpy(tail, data + i, rem);
2780
+ tail[rem] = 0x80;
2781
+ size_t pad = (rem + 1 + 8 <= 64) ? 64 : 128;
2782
+ memset(tail + rem + 1, 0, pad - rem - 1 - 8);
2783
+ uint64_t bits = (uint64_t)len * 8;
2784
+ for (int b = 0; b < 8; b++) tail[pad - 8 + b] = (unsigned char)(bits >> (8 * b));
2785
+ scr_md5_block(h, tail);
2786
+ if (pad == 128) scr_md5_block(h, tail + 64);
2787
+ for (int j = 0; j < 4; j++) {
2788
+ for (int b = 0; b < 4; b++) out[j * 4 + b] = (unsigned char)(h[j] >> (8 * b));
2789
+ }
2790
+ return 16;
2791
+ }
2792
+
2793
+ /* One-shot digest by algorithm name — the island crypto shim's bridge
2794
+ * (createHash concatenates its update() chunks JS-side). Returns the
2795
+ * digest length, 0 for an unknown algorithm. */
2796
+ size_t scr_crypto_digest_raw(const char *alg, const unsigned char *data, size_t len,
2797
+ unsigned char out[32]) {
2798
+ if (strcmp(alg, "sha256") == 0) return scr_sha256_digest(data, len, out);
2799
+ if (strcmp(alg, "sha1") == 0) return scr_sha1_digest(data, len, out);
2800
+ if (strcmp(alg, "md5") == 0) return scr_md5_digest(data, len, out);
2801
+ return 0;
2802
+ }
2803
+
2804
+ /* HMAC (RFC 2104) over the same digests — block size 64 for all three. */
2805
+ size_t scr_crypto_hmac_raw(const char *alg, const unsigned char *key, size_t keylen,
2806
+ const unsigned char *data, size_t len, unsigned char out[32]) {
2807
+ unsigned char kblock[64];
2808
+ unsigned char kd[32];
2809
+ if (keylen > 64) {
2810
+ size_t kn = scr_crypto_digest_raw(alg, key, keylen, kd);
2811
+ if (kn == 0) return 0;
2812
+ memset(kblock, 0, 64);
2813
+ memcpy(kblock, kd, kn);
2814
+ } else {
2815
+ memset(kblock, 0, 64);
2816
+ memcpy(kblock, key, keylen);
2817
+ }
2818
+ unsigned char *inner = malloc(64 + len);
2819
+ if (!inner) return 0;
2820
+ for (int i = 0; i < 64; i++) inner[i] = kblock[i] ^ 0x36;
2821
+ memcpy(inner + 64, data, len);
2822
+ unsigned char ih[32];
2823
+ size_t in = scr_crypto_digest_raw(alg, inner, 64 + len, ih);
2824
+ free(inner);
2825
+ if (in == 0) return 0;
2826
+ unsigned char outer[96];
2827
+ for (int i = 0; i < 64; i++) outer[i] = kblock[i] ^ 0x5c;
2828
+ memcpy(outer + 64, ih, in);
2829
+ return scr_crypto_digest_raw(alg, outer, 64 + in, out);
2830
+ }
2831
+
2832
+ static ScrStr *scr_hash_digest_raw(const ScrStr *alg, const unsigned char *data, size_t len,
2833
+ const ScrStr *enc) {
2834
+ unsigned char d[32];
2835
+ /* sha1 or sha256 — the compiler fences every other algorithm literal. */
2836
+ size_t n = (alg->len == 4 && memcmp(alg->data, "sha1", 4) == 0)
2837
+ ? scr_sha1_digest(data, len, d)
2838
+ : scr_sha256_digest(data, len, d);
2839
+ return scr_digest_encode(d, n, enc);
2840
+ }
2841
+
2842
+ /* Strings hash their UTF-8 bytes (Node's default input encoding — ScrStr
2843
+ * storage IS utf8, so the bytes are the string's own). Borrowed; +1. */
2844
+ ScrStr *scr_crypto_hash_digest_str(ScrStr *alg, ScrStr *data, ScrStr *enc) {
2845
+ return scr_hash_digest_raw(alg, (const unsigned char *)data->data, data->len, enc);
2846
+ }
2847
+
2848
+ ScrStr *scr_crypto_hash_digest_bytes(ScrStr *alg, ScrBytes *data, ScrStr *enc) {
2849
+ return scr_hash_digest_raw(alg, data->data, data->len * scr_bytes_elem_size(data->elem), enc);
2850
+ }
2851
+
2852
+ /* The composed `new crypto.X509Certificate(data).fingerprint` read, fused
2853
+ * by the compiler (no certificate handle exists). Node's .fingerprint IS
2854
+ * the SHA-1 of the certificate's DER bytes, uppercase colon-separated —
2855
+ * pinned against Node. Accepts what Node's constructor accepts from the
2856
+ * fs.readFileSync idiom: PEM (the armor's base64 body decodes to DER,
2857
+ * Node-leniently — whitespace skipped) or raw DER (a leading SEQUENCE
2858
+ * tag). Anything else throws Node's exact PEM error (Error, code
2859
+ * ERR_OSSL_PEM_NO_START_LINE). Input borrowed; result +1. */
2860
+ static ScrStr *scr_x509_fingerprint_raw(const uint8_t *in, size_t n);
2861
+
2862
+ ScrStr *scr_crypto_x509_fingerprint(ScrBytes *data) {
2863
+ return scr_x509_fingerprint_raw(data->data, data->len * scr_bytes_elem_size(data->elem));
2864
+ }
2865
+
2866
+ /* The string-input form (readFileSync(path, "utf-8") — PEM text; ScrStr
2867
+ * storage IS the bytes). */
2868
+ ScrStr *scr_crypto_x509_fingerprint_str(ScrStr *pem) {
2869
+ return scr_x509_fingerprint_raw((const uint8_t *)pem->data, pem->len);
2870
+ }
2871
+
2872
+ /* PEM armor → DER (or raw-DER passthrough), the shared front half of
2873
+ * every X509Certificate member. On success `*der`/`*der_len` hold the
2874
+ * certificate bytes and `*decoded` the malloc'd base64 buffer to free
2875
+ * (NULL for raw-DER input); unparseable input throws Node's exact PEM
2876
+ * error and answers false with the exception pending. */
2877
+ static bool scr_x509_der(const uint8_t *in, size_t n, const uint8_t **der, size_t *der_len,
2878
+ uint8_t **decoded) {
2879
+ static const char begin[] = "-----BEGIN CERTIFICATE-----";
2880
+ static const char end[] = "-----END CERTIFICATE-----";
2881
+ *der = NULL;
2882
+ *der_len = 0;
2883
+ *decoded = NULL;
2884
+ /* PEM: base64-decode the armor's body. */
2885
+ const uint8_t *b = NULL;
2886
+ for (size_t i = 0; i + sizeof begin - 1 <= n; i++) {
2887
+ if (memcmp(in + i, begin, sizeof begin - 1) == 0) {
2888
+ b = in + i + sizeof begin - 1;
2889
+ break;
2890
+ }
2891
+ }
2892
+ if (b != NULL) {
2893
+ const uint8_t *stop = in + n;
2894
+ for (const uint8_t *p = b; p + sizeof end - 1 <= in + n; p++) {
2895
+ if (memcmp(p, end, sizeof end - 1) == 0) {
2896
+ stop = p;
2897
+ break;
2898
+ }
2899
+ }
2900
+ uint8_t *out = malloc(((size_t)(stop - b) / 4 + 1) * 3 + 3);
2901
+ if (!out) {
2902
+ fputs("scriptc: out of memory\n", stderr);
2903
+ abort();
2904
+ }
2905
+ size_t o = 0;
2906
+ unsigned acc = 0;
2907
+ int have = 0;
2908
+ for (const uint8_t *p = b; p < stop; p++) {
2909
+ int v;
2910
+ uint8_t c = *p;
2911
+ if (c >= 'A' && c <= 'Z') v = c - 'A';
2912
+ else if (c >= 'a' && c <= 'z') v = c - 'a' + 26;
2913
+ else if (c >= '0' && c <= '9') v = c - '0' + 52;
2914
+ else if (c == '+') v = 62;
2915
+ else if (c == '/') v = 63;
2916
+ else continue; /* whitespace, '=' */
2917
+ acc = (acc << 6) | (unsigned)v;
2918
+ if (++have == 4) {
2919
+ out[o++] = (uint8_t)(acc >> 16);
2920
+ out[o++] = (uint8_t)(acc >> 8);
2921
+ out[o++] = (uint8_t)acc;
2922
+ acc = 0;
2923
+ have = 0;
2924
+ }
2925
+ }
2926
+ if (have == 2) out[o++] = (uint8_t)(acc >> 4);
2927
+ else if (have == 3) {
2928
+ out[o++] = (uint8_t)(acc >> 10);
2929
+ out[o++] = (uint8_t)(acc >> 2);
2930
+ }
2931
+ *decoded = out;
2932
+ *der = out;
2933
+ *der_len = o;
2934
+ return true;
2935
+ }
2936
+ if (n > 0 && in[0] == 0x30) {
2937
+ /* Raw DER: the certificate SEQUENCE. */
2938
+ *der = in;
2939
+ *der_len = n;
2940
+ return true;
2941
+ }
2942
+ scr_throw_error_msg_code(SCR_ERR_ERROR,
2943
+ "error:0480006C:PEM routines::no start line",
2944
+ 42, "ERR_OSSL_PEM_NO_START_LINE");
2945
+ return false; /* exception pending */
2946
+ }
2947
+
2948
+ static ScrStr *scr_x509_fingerprint_raw(const uint8_t *in, size_t n) {
2949
+ const uint8_t *der;
2950
+ size_t der_len;
2951
+ uint8_t *decoded;
2952
+ if (!scr_x509_der(in, n, &der, &der_len, &decoded)) return NULL;
2953
+ unsigned char digest[32];
2954
+ size_t dn = scr_sha1_digest(der, der_len, digest);
2955
+ free(decoded);
2956
+ char buf[64];
2957
+ static const char hex[] = "0123456789ABCDEF";
2958
+ size_t o = 0;
2959
+ for (size_t i = 0; i < dn; i++) {
2960
+ if (i > 0) buf[o++] = ':';
2961
+ buf[o++] = hex[digest[i] >> 4];
2962
+ buf[o++] = hex[digest[i] & 0x0f];
2963
+ }
2964
+ return scr_str_new(buf, o);
2965
+ }
2966
+
2967
+ /* ── the certificate's Validity window (validFrom / validTo) ─────────────
2968
+ *
2969
+ * A minimal DER walk to TBSCertificate.validity: Certificate ::= SEQUENCE
2970
+ * { tbsCertificate SEQUENCE { version [0] OPTIONAL, serialNumber INTEGER,
2971
+ * signature SEQUENCE, issuer, validity SEQUENCE { notBefore, notAfter },
2972
+ * ... } }. Each Time is UTCTime (YYMMDDHHMMSSZ; RFC 5280's 50-year pivot)
2973
+ * or GeneralizedTime (YYYYMMDDHHMMSSZ), rendered in Node's exact
2974
+ * ASN1_TIME_print shape: "Jul 1 00:00:00 2026 GMT" (%2d space-padded
2975
+ * day). Truncated/non-Zulu encodings (RFC 5280 forbids them in certs)
2976
+ * and walk failures answer OpenSSL's "Bad time value". */
2977
+
2978
+ /* Reads one DER TL header at p (before end): tag to *tag, content length
2979
+ * to *len, and answers the content pointer (NULL on malformed/overlong). */
2980
+ static const uint8_t *scr_der_tl(const uint8_t *p, const uint8_t *end, uint8_t *tag, size_t *len) {
2981
+ if (p == NULL || end - p < 2) return NULL;
2982
+ *tag = p[0];
2983
+ uint8_t l0 = p[1];
2984
+ const uint8_t *content = p + 2;
2985
+ size_t l;
2986
+ if (l0 < 0x80) {
2987
+ l = l0;
2988
+ } else {
2989
+ size_t nb = l0 & 0x7f;
2990
+ if (nb == 0 || nb > sizeof(size_t) || end - content < (ptrdiff_t)nb) return NULL;
2991
+ l = 0;
2992
+ for (size_t i = 0; i < nb; i++) l = (l << 8) | content[i];
2993
+ content += nb;
2994
+ }
2995
+ if ((size_t)(end - content) < l) return NULL;
2996
+ *tag = p[0];
2997
+ *len = l;
2998
+ return content;
2999
+ }
3000
+
3001
+ /* Formats one Time element (UTCTime/GeneralizedTime content bytes) in
3002
+ * ASN1_TIME_print's shape into buf; answers the length (0 = bad value). */
3003
+ static size_t scr_x509_time_print(uint8_t tag, const uint8_t *t, size_t n, char buf[32]) {
3004
+ static const char *mon[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
3005
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
3006
+ int year, mo, day, hh, mm, ss;
3007
+ const uint8_t *d = t;
3008
+ size_t need = tag == 0x17 ? 13 : 15; /* ...HHMMSSZ, Zulu only */
3009
+ if (n != need || t[n - 1] != 'Z') return 0;
3010
+ for (size_t i = 0; i + 1 < n; i++) {
3011
+ if (t[i] < '0' || t[i] > '9') return 0;
3012
+ }
3013
+ #define SCR_DER_2D(p) (((p)[0] - '0') * 10 + ((p)[1] - '0'))
3014
+ if (tag == 0x17) {
3015
+ year = SCR_DER_2D(d);
3016
+ year += year < 50 ? 2000 : 1900; /* RFC 5280's UTCTime pivot */
3017
+ d += 2;
3018
+ } else {
3019
+ year = SCR_DER_2D(d) * 100;
3020
+ d += 2;
3021
+ year += SCR_DER_2D(d);
3022
+ d += 2;
3023
+ }
3024
+ mo = SCR_DER_2D(d); d += 2;
3025
+ day = SCR_DER_2D(d); d += 2;
3026
+ hh = SCR_DER_2D(d); d += 2;
3027
+ mm = SCR_DER_2D(d); d += 2;
3028
+ ss = SCR_DER_2D(d);
3029
+ #undef SCR_DER_2D
3030
+ if (mo < 1 || mo > 12) return 0;
3031
+ int r = snprintf(buf, 32, "%s %2d %02d:%02d:%02d %d GMT", mon[mo - 1], day, hh, mm, ss, year);
3032
+ return r > 0 ? (size_t)r : 0;
3033
+ }
3034
+
3035
+ static ScrStr *scr_x509_validity_raw(const uint8_t *in, size_t n, bool want_to) {
3036
+ const uint8_t *der;
3037
+ size_t der_len;
3038
+ uint8_t *decoded;
3039
+ if (!scr_x509_der(in, n, &der, &der_len, &decoded)) return NULL;
3040
+ static const char bad[] = "Bad time value";
3041
+ const uint8_t *end = der + der_len;
3042
+ uint8_t tag;
3043
+ size_t len;
3044
+ char buf[32];
3045
+ size_t blen = 0;
3046
+ /* Certificate SEQUENCE → tbsCertificate SEQUENCE */
3047
+ const uint8_t *p = scr_der_tl(der, end, &tag, &len);
3048
+ if (p && tag == 0x30) {
3049
+ end = p + len;
3050
+ p = scr_der_tl(p, end, &tag, &len);
3051
+ } else {
3052
+ p = NULL;
3053
+ }
3054
+ if (p && tag == 0x30) {
3055
+ const uint8_t *tbs_end = p + len;
3056
+ /* [0] version (optional), serialNumber, signature, issuer */
3057
+ const uint8_t *q = p;
3058
+ for (int skip = 0; skip < 4 && q != NULL; skip++) {
3059
+ const uint8_t *c = scr_der_tl(q, tbs_end, &tag, &len);
3060
+ if (!c) { q = NULL; break; }
3061
+ if (skip == 0 && tag != 0xA0) {
3062
+ /* no version field: this element is already serialNumber */
3063
+ skip++;
3064
+ }
3065
+ q = c + len;
3066
+ }
3067
+ /* validity SEQUENCE { notBefore, notAfter } */
3068
+ const uint8_t *v = scr_der_tl(q, tbs_end, &tag, &len);
3069
+ if (v && tag == 0x30) {
3070
+ const uint8_t *v_end = v + len;
3071
+ const uint8_t *t1 = scr_der_tl(v, v_end, &tag, &len);
3072
+ if (t1 && (tag == 0x17 || tag == 0x18)) {
3073
+ if (!want_to) {
3074
+ blen = scr_x509_time_print(tag, t1, len, buf);
3075
+ } else {
3076
+ const uint8_t *t2 = scr_der_tl(t1 + len, v_end, &tag, &len);
3077
+ if (t2 && (tag == 0x17 || tag == 0x18)) {
3078
+ blen = scr_x509_time_print(tag, t2, len, buf);
3079
+ }
3080
+ }
3081
+ }
3082
+ }
3083
+ }
3084
+ free(decoded);
3085
+ if (blen == 0) return scr_str_new(bad, sizeof bad - 1);
3086
+ return scr_str_new(buf, blen);
3087
+ }
3088
+
3089
+ ScrStr *scr_crypto_x509_valid_from(ScrBytes *data) {
3090
+ return scr_x509_validity_raw(data->data, data->len * scr_bytes_elem_size(data->elem), false);
3091
+ }
3092
+
3093
+ ScrStr *scr_crypto_x509_valid_from_str(ScrStr *pem) {
3094
+ return scr_x509_validity_raw((const uint8_t *)pem->data, pem->len, false);
3095
+ }
3096
+
3097
+ ScrStr *scr_crypto_x509_valid_to(ScrBytes *data) {
3098
+ return scr_x509_validity_raw(data->data, data->len * scr_bytes_elem_size(data->elem), true);
3099
+ }
3100
+
3101
+ ScrStr *scr_crypto_x509_valid_to_str(ScrStr *pem) {
3102
+ return scr_x509_validity_raw((const uint8_t *)pem->data, pem->len, true);
3103
+ }
3104
+
3105
+ /* ── String surface (fromCharCode / lastIndexOf) ─────────────────────── */
3106
+
3107
+ /* Forward declaration — scr_to_uint32 lives with the bitwise operators
3108
+ * below (ToUint16 is its low 16 bits, per spec). */
3109
+ static uint32_t scr_to_uint32(double d);
3110
+
3111
+ /* String.fromCharCode core over n UTF-16 code units read through
3112
+ * `unit(src, i)` (already ToUint16'd): combine adjacent surrogate pairs,
3113
+ * substitute U+FFFD for lone surrogates (divergence 1's storage policy —
3114
+ * Node writing a lone surrogate to stdout produces the same replacement
3115
+ * bytes), UTF-8 encode. */
3116
+ static ScrStr *scr_str_from_units(size_t n, uint32_t (*unit)(void *, size_t), void *src) {
3117
+ char *out = malloc(n * 3 + 1); /* worst case: 3 bytes per UTF-16 unit */
3118
+ if (!out) {
3119
+ fputs("scriptc: out of memory\n", stderr);
3120
+ abort();
3121
+ }
3122
+ size_t o = 0;
3123
+ for (size_t i = 0; i < n; i++) {
3124
+ uint32_t cp = unit(src, i);
3125
+ if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < n) {
3126
+ uint32_t lo = unit(src, i + 1);
3127
+ if (lo >= 0xDC00 && lo <= 0xDFFF) {
3128
+ cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
3129
+ i++;
3130
+ } else {
3131
+ cp = 0xFFFD;
3132
+ }
3133
+ } else if (cp >= 0xD800 && cp <= 0xDFFF) {
3134
+ cp = 0xFFFD;
3135
+ }
3136
+ if (cp <= 0x7F) {
3137
+ out[o++] = (char)cp;
3138
+ } else if (cp <= 0x7FF) {
3139
+ out[o++] = (char)(0xC0 | (cp >> 6));
3140
+ out[o++] = (char)(0x80 | (cp & 0x3F));
3141
+ } else if (cp <= 0xFFFF) {
3142
+ out[o++] = (char)(0xE0 | (cp >> 12));
3143
+ out[o++] = (char)(0x80 | ((cp >> 6) & 0x3F));
3144
+ out[o++] = (char)(0x80 | (cp & 0x3F));
3145
+ } else {
3146
+ out[o++] = (char)(0xF0 | (cp >> 18));
3147
+ out[o++] = (char)(0x80 | ((cp >> 12) & 0x3F));
3148
+ out[o++] = (char)(0x80 | ((cp >> 6) & 0x3F));
3149
+ out[o++] = (char)(0x80 | (cp & 0x3F));
3150
+ }
3151
+ }
3152
+ ScrStr *s = scr_str_new(out, o);
3153
+ free(out);
3154
+ return s;
3155
+ }
3156
+
3157
+ static uint32_t scr_fcc_arr_unit(void *src, size_t i) {
3158
+ return scr_to_uint32(scr_arr_get_f64((ScrArr *)src, (double)i)) & 0xFFFFu;
3159
+ }
3160
+
3161
+ /* String.fromCharCode(...codes) over ONE packed f64[]. */
3162
+ ScrStr *scr_str_from_char_code(ScrArr *codes) {
3163
+ return scr_str_from_units(codes->len, scr_fcc_arr_unit, codes);
3164
+ }
3165
+
3166
+ static uint32_t scr_fcc_bytes_unit(void *src, size_t i) {
3167
+ return scr_to_uint32(scr_bytes_get((const ScrBytes *)src, (double)i)) & 0xFFFFu;
3168
+ }
3169
+
3170
+ /* String.fromCharCode(...bytes) — a spread typed array/Buffer source (the
3171
+ * magic-number ASCII probe: String.fromCharCode(...data.slice(4, 8))).
3172
+ * u8 elements are plain code units; u32/f32 elements ride the same
3173
+ * ToUint16 the packed-array form applies. */
3174
+ ScrStr *scr_str_from_char_code_bytes(ScrBytes *codes) {
3175
+ return scr_str_from_units(codes->len, scr_fcc_bytes_unit, codes);
3176
+ }
3177
+
3178
+ /* UTF-16 unit count of the UTF-8 prefix ending at byte offset `end`
3179
+ * (ScrStr storage is well-formed, so lead bytes decide the advance). */
3180
+ static size_t scr_lib_u16_units(const char *s, size_t end) {
3181
+ size_t units = 0;
3182
+ for (size_t i = 0; i < end;) {
3183
+ unsigned char b = (unsigned char)s[i];
3184
+ size_t adv = b < 0x80 ? 1 : b < 0xE0 ? 2 : b < 0xF0 ? 3 : 4;
3185
+ units += adv == 4 ? 2 : 1;
3186
+ i += adv;
3187
+ }
3188
+ return units;
3189
+ }
3190
+
3191
+ /* lastIndexOf(needle), the one-argument form: last occurrence as a UTF-16
3192
+ * index, -1 when absent; the empty needle finds the length (per spec's
3193
+ * clamped +Infinity fromIndex). A byte-wise reverse scan is boundary-safe:
3194
+ * a well-formed needle's first byte is never a continuation byte. */
3195
+ double scr_str_last_index_of(ScrStr *s, ScrStr *needle) {
3196
+ if (needle->len == 0) return (double)scr_lib_u16_units(s->data, s->len);
3197
+ if (needle->len > s->len) return -1.0;
3198
+ for (size_t i = s->len - needle->len + 1; i-- > 0;) {
3199
+ if (memcmp(s->data + i, needle->data, needle->len) == 0) {
3200
+ return (double)scr_lib_u16_units(s->data, i);
3201
+ }
3202
+ }
3203
+ return -1.0;
3204
+ }
3205
+
3206
+ /* ── Date, the composed slice ──────────────────────────────────────────
3207
+ * Date values have no representation — the compiled surface is exactly
3208
+ * Date.now() and the composed new Date(ms?).toISOString(). */
3209
+
3210
+ double scr_date_now(void) {
3211
+ struct timespec ts;
3212
+ clock_gettime(CLOCK_REALTIME, &ts);
3213
+ /* Node's Date.now() is integer milliseconds. */
3214
+ return floor((double)ts.tv_sec * 1000.0 + (double)ts.tv_nsec / 1e6);
3215
+ }
3216
+
3217
+ /* Node's Date.prototype.toISOString over a millisecond time value:
3218
+ * TimeClip's ToInteger truncation, proleptic Gregorian civil-from-days
3219
+ * (Howard Hinnant's algorithm), YYYY-MM-DDTHH:mm:ss.sssZ with expanded
3220
+ * ±YYYYYY years outside 0–9999, and Node's "Invalid time value"
3221
+ * RangeError on NaN / out-of-range values — verified against Node over
3222
+ * epoch, negative, fractional, boundary (±8.64e15), and expanded-year
3223
+ * inputs. */
3224
+ ScrStr *scr_date_to_iso(double ms) {
3225
+ if (!(fabs(ms) <= 8640000000000000.0)) { /* NaN and out of range */
3226
+ scr_throw_error_msg(SCR_ERR_RANGE, "Invalid time value", 18);
3227
+ return NULL;
3228
+ }
3229
+ double t = trunc(ms);
3230
+ double dayd = floor(t / 86400000.0);
3231
+ long long msday = (long long)(t - dayd * 86400000.0);
3232
+ long long z = (long long)dayd + 719468;
3233
+ long long era = (z >= 0 ? z : z - 146096) / 146097;
3234
+ unsigned long long doe = (unsigned long long)(z - era * 146097);
3235
+ unsigned long long yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
3236
+ long long y = (long long)yoe + era * 400;
3237
+ unsigned long long doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
3238
+ unsigned long long mp = (5 * doy + 2) / 153;
3239
+ unsigned long long d = doy - (153 * mp + 2) / 5 + 1;
3240
+ unsigned long long m = mp < 10 ? mp + 3 : mp - 9;
3241
+ if (m <= 2) y += 1;
3242
+ int hh = (int)(msday / 3600000), mi = (int)(msday / 60000 % 60),
3243
+ ss = (int)(msday / 1000 % 60), sss = (int)(msday % 1000);
3244
+ char buf[40];
3245
+ int len;
3246
+ if (y < 0) {
3247
+ len = snprintf(buf, sizeof buf, "-%06lld-%02llu-%02lluT%02d:%02d:%02d.%03dZ", -y, m, d, hh, mi, ss, sss);
3248
+ } else if (y > 9999) {
3249
+ len = snprintf(buf, sizeof buf, "+%06lld-%02llu-%02lluT%02d:%02d:%02d.%03dZ", y, m, d, hh, mi, ss, sss);
3250
+ } else {
3251
+ len = snprintf(buf, sizeof buf, "%04lld-%02llu-%02lluT%02d:%02d:%02d.%03dZ", y, m, d, hh, mi, ss, sss);
3252
+ }
3253
+ return scr_str_new(buf, (size_t)len);
3254
+ }
3255
+
3256
+ /* ── new Date(dateString).getTime() ──────────────────────────────────────
3257
+ *
3258
+ * The BOUNDED date-string parse (documented divergence — V8's parser
3259
+ * accepts far more): two grammars answer a time value, everything else is
3260
+ * NaN (Node's invalid-date getTime).
3261
+ *
3262
+ * 1. The ASN1_TIME_print shape X509Certificate.validFrom/validTo answer
3263
+ * ("Jul 1 00:00:00 2026 GMT", "Jul 17 17:52:11 2026 GMT") — the
3264
+ * portless cert-expiry read. Month names match case-insensitively;
3265
+ * one or two spaces precede the day (%2d's padding).
3266
+ * 2. ECMA's own date-time string format with an EXPLICIT offset:
3267
+ * YYYY[-MM[-DD]] date-only forms (UTC, per the spec) and full
3268
+ * date-times YYYY-MM-DDTHH:mm[:ss[.sss]] ending in Z or ±HH:MM.
3269
+ * Offset-less date-times are LOCAL time in JS; that arm answers NaN
3270
+ * here rather than guessing a zone (the divergence note).
3271
+ */
3272
+
3273
+ /* Howard Hinnant's days_from_civil (the to_iso walk inverted). */
3274
+ static double scr_days_from_civil(long long y, int m, int d) {
3275
+ y -= m <= 2;
3276
+ long long era = (y >= 0 ? y : y - 399) / 400;
3277
+ unsigned long long yoe = (unsigned long long)(y - era * 400);
3278
+ unsigned long long doy = (unsigned long long)((153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1);
3279
+ unsigned long long doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
3280
+ return (double)(era * 146097 + (long long)doe - 719468);
3281
+ }
3282
+
3283
+ static double scr_date_ms_of(long long y, int mo, int d, int hh, int mi, int ss, int ms) {
3284
+ /* V8 accepts days 1..31 in every month and ROLLS OVER past the month's
3285
+ * end (Feb 30 → Mar 2) — days_from_civil extrapolates linearly, so the
3286
+ * rollover falls out; day 0 and 32+ are NaN, like V8. */
3287
+ if (mo < 1 || mo > 12 || d < 1 || d > 31) return NAN;
3288
+ if (hh > 24 || mi > 59 || ss > 59 || (hh == 24 && (mi || ss || ms))) return NAN;
3289
+ double t = scr_days_from_civil(y, mo, d) * 86400000.0 +
3290
+ hh * 3600000.0 + mi * 60000.0 + ss * 1000.0 + ms;
3291
+ if (fabs(t) > 8640000000000000.0) return NAN;
3292
+ return t;
3293
+ }
3294
+
3295
+ static bool scr_date_digits(const char **p, const char *end, int n, int *out) {
3296
+ int v = 0;
3297
+ if (end - *p < n) return false;
3298
+ for (int i = 0; i < n; i++) {
3299
+ char c = (*p)[i];
3300
+ if (c < '0' || c > '9') return false;
3301
+ v = v * 10 + (c - '0');
3302
+ }
3303
+ *p += n;
3304
+ *out = v;
3305
+ return true;
3306
+ }
3307
+
3308
+ double scr_date_parse_get_time(ScrStr *s) {
3309
+ const char *p = s->data;
3310
+ const char *end = s->data + s->len;
3311
+ static const char *mon[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
3312
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
3313
+ /* Grammar 1: "MMM [D]D HH:MM:SS YYYY GMT" (ASN1_TIME_print). */
3314
+ if (end - p > 3 && ((p[0] >= 'A' && p[0] <= 'Z') || (p[0] >= 'a' && p[0] <= 'z'))) {
3315
+ int mo = 0;
3316
+ for (int i = 0; i < 12; i++) {
3317
+ if (tolower((unsigned char)p[0]) == tolower((unsigned char)mon[i][0]) &&
3318
+ tolower((unsigned char)p[1]) == tolower((unsigned char)mon[i][1]) &&
3319
+ tolower((unsigned char)p[2]) == tolower((unsigned char)mon[i][2])) {
3320
+ mo = i + 1;
3321
+ break;
3322
+ }
3323
+ }
3324
+ if (mo == 0) return NAN;
3325
+ p += 3;
3326
+ if (p < end && *p == ' ') p++;
3327
+ if (p < end && *p == ' ') p++; /* %2d's padding space */
3328
+ int d = 0, hh, mi, ss, y;
3329
+ if (!scr_date_digits(&p, end, 1, &d)) return NAN;
3330
+ int d2;
3331
+ if (p < end && *p >= '0' && *p <= '9' && scr_date_digits(&p, end, 1, &d2)) d = d * 10 + d2;
3332
+ if (p >= end || *p++ != ' ') return NAN;
3333
+ if (!scr_date_digits(&p, end, 2, &hh) || p >= end || *p++ != ':') return NAN;
3334
+ if (!scr_date_digits(&p, end, 2, &mi) || p >= end || *p++ != ':') return NAN;
3335
+ if (!scr_date_digits(&p, end, 2, &ss) || p >= end || *p++ != ' ') return NAN;
3336
+ if (!scr_date_digits(&p, end, 4, &y)) return NAN;
3337
+ if (end - p != 4 || memcmp(p, " GMT", 4) != 0) return NAN;
3338
+ return scr_date_ms_of(y, mo, d, hh, mi, ss, 0);
3339
+ }
3340
+ /* Grammar 2: ECMA's date-time string format. */
3341
+ {
3342
+ int y, mo = 1, d = 1, hh = 0, mi = 0, ss = 0, ms = 0;
3343
+ bool sign_year = p < end && (*p == '+' || *p == '-');
3344
+ long long yy;
3345
+ if (sign_year) {
3346
+ int y6;
3347
+ bool neg = *p == '-';
3348
+ p++;
3349
+ if (!scr_date_digits(&p, end, 6, &y6)) return NAN;
3350
+ yy = neg ? -(long long)y6 : y6;
3351
+ } else {
3352
+ if (!scr_date_digits(&p, end, 4, &y)) return NAN;
3353
+ yy = y;
3354
+ }
3355
+ if (p < end && *p == '-') {
3356
+ p++;
3357
+ if (!scr_date_digits(&p, end, 2, &mo)) return NAN;
3358
+ if (p < end && *p == '-') {
3359
+ p++;
3360
+ if (!scr_date_digits(&p, end, 2, &d)) return NAN;
3361
+ }
3362
+ }
3363
+ if (p == end) return scr_date_ms_of(yy, mo, d, 0, 0, 0, 0); /* date-only: UTC */
3364
+ if (*p++ != 'T') return NAN;
3365
+ if (!scr_date_digits(&p, end, 2, &hh) || p >= end || *p++ != ':') return NAN;
3366
+ if (!scr_date_digits(&p, end, 2, &mi)) return NAN;
3367
+ if (p < end && *p == ':') {
3368
+ p++;
3369
+ if (!scr_date_digits(&p, end, 2, &ss)) return NAN;
3370
+ if (p < end && *p == '.') {
3371
+ p++;
3372
+ if (!scr_date_digits(&p, end, 3, &ms)) return NAN;
3373
+ }
3374
+ }
3375
+ if (p == end) return NAN; /* offset-less date-time: LOCAL in JS — the divergence */
3376
+ double off = 0;
3377
+ if (*p == 'Z') {
3378
+ p++;
3379
+ } else if (*p == '+' || *p == '-') {
3380
+ bool neg = *p == '-';
3381
+ int oh, om;
3382
+ p++;
3383
+ if (!scr_date_digits(&p, end, 2, &oh) || p >= end || *p++ != ':') return NAN;
3384
+ if (!scr_date_digits(&p, end, 2, &om)) return NAN;
3385
+ off = (oh * 60 + om) * 60000.0;
3386
+ if (neg) off = -off;
3387
+ } else {
3388
+ return NAN;
3389
+ }
3390
+ if (p != end) return NAN;
3391
+ double t = scr_date_ms_of(yy, mo, d, hh, mi, ss, ms);
3392
+ return isnan(t) ? t : t - off;
3393
+ }
3394
+ }
3395
+
3396
+ /* Date.UTC(year, month, date, hours, minutes, seconds, ms) — the spec's
3397
+ * MakeDay/MakeTime/TimeClip pipeline over ALREADY-NUMBER arguments (tsc
3398
+ * pins them; the frontend completes omitted trailing arguments with the
3399
+ * spec's defaults: month 0, date 1, time parts 0). ToIntegerOrInfinity is
3400
+ * trunc on finite values; any non-finite part is NaN. Years 0–99 map to
3401
+ * 1900+year (the spec's MakeFullYear), out-of-range months ROLL into the
3402
+ * year (Date.UTC(2017, 13) is Feb 2018) and any integer date offsets from
3403
+ * day 1 of that month — days_from_civil extrapolates linearly, so both
3404
+ * rollovers fall out. V8 bounds MakeDay's year to ±1e6 (kMaxYear/kMinYear,
3405
+ * date.h) before TimeClip can see the result, and Node answers NaN past
3406
+ * it — matched here, and it keeps days_from_civil's long long exact.
3407
+ * Never throws. */
3408
+ double scr_date_utc(double y, double mo, double d,
3409
+ double h, double mi, double s, double ms) {
3410
+ if (!isfinite(y) || !isfinite(mo) || !isfinite(d) || !isfinite(h) ||
3411
+ !isfinite(mi) || !isfinite(s) || !isfinite(ms)) {
3412
+ return NAN;
3413
+ }
3414
+ y = trunc(y);
3415
+ mo = trunc(mo);
3416
+ d = trunc(d);
3417
+ h = trunc(h);
3418
+ mi = trunc(mi);
3419
+ s = trunc(s);
3420
+ ms = trunc(ms);
3421
+ if (y >= 0 && y <= 99) y += 1900;
3422
+ double ym = y + floor(mo / 12.0);
3423
+ int mn = (int)(mo - floor(mo / 12.0) * 12.0); /* 0..11 */
3424
+ if (fabs(ym) > 1000000.0) return NAN; /* V8's MakeDay year bound */
3425
+ double days = scr_days_from_civil((long long)ym, mn + 1, 1) + (d - 1.0);
3426
+ double t = days * 86400000.0 + h * 3600000.0 + mi * 60000.0 + s * 1000.0 + ms;
3427
+ if (fabs(t) > 8640000000000000.0) return NAN; /* TimeClip */
3428
+ return t == 0 ? 0 : t; /* normalize -0 (TimeClip's +0) */
3429
+ }
3430
+
3431
+ /* ── Number statics ────────────────────────────────────────────────────
3432
+ * JS-exact: the ES2015 Number statics never coerce (unlike the global
3433
+ * isNaN/isFinite), and the compiler routes only number-typed arguments
3434
+ * here, so plain C predicates are the whole story. */
3435
+
3436
+ bool scr_num_is_finite(double x) { return isfinite(x) != 0; }
3437
+
3438
+ /* Number.prototype.toExponential() with fractionDigits UNDEFINED — the
3439
+ * spec's "as many digits as necessary": the shortest correctly-rounded
3440
+ * mantissa that round-trips, formatted d[.ddd]e±X with no zero-padding of
3441
+ * the exponent ("7e+0", "1.5e-7"). NaN → "NaN", ±Infinity → its text,
3442
+ * ±0 → "0e+0" (the spec's x = +0 arm — the sign of -0 is dropped because
3443
+ * -0 < 0 is false). Verified differentially against Node. */
3444
+ ScrStr *scr_num_to_exponential(double x) {
3445
+ if (isnan(x)) return scr_str_new("NaN", 3);
3446
+ if (isinf(x)) return x < 0 ? scr_str_new("-Infinity", 9) : scr_str_new("Infinity", 8);
3447
+ if (x == 0) return scr_str_new("0e+0", 4);
3448
+ char buf[64];
3449
+ int len = 0;
3450
+ for (int prec = 0; prec <= 17; prec++) {
3451
+ len = snprintf(buf, sizeof buf, "%.*e", prec, x);
3452
+ if (strtod(buf, NULL) == x) break;
3453
+ }
3454
+ /* Normalize C's exponent ("e+05" / "e-123") to JS's ("e+5"/"e-123"):
3455
+ * drop leading zeros after the sign, keeping at least one digit. */
3456
+ char out[64];
3457
+ int o = 0;
3458
+ const char *e = memchr(buf, 'e', (size_t)len);
3459
+ const char *p = buf;
3460
+ while (p < e) out[o++] = *p++;
3461
+ out[o++] = 'e';
3462
+ p++; /* past 'e' */
3463
+ out[o++] = *p++; /* the sign — %e always emits one */
3464
+ while (*p == '0' && p[1] >= '0' && p[1] <= '9') p++;
3465
+ while (p < buf + len) out[o++] = *p++;
3466
+ return scr_str_new(out, (size_t)o);
3467
+ }
3468
+
3469
+ /* Number.prototype.toFixed() with fractionDigits UNDEFINED (= 0 digits) —
3470
+ * the spec's pick of the integer n closest to x with ties toward the
3471
+ * LARGER n on the magnitude ((2.5).toFixed() = "3", (-2.5).toFixed() =
3472
+ * "-3" — printf's half-even would answer "2"), the "-0" result for
3473
+ * negative fractions rounding to zero, and ToString fallback at
3474
+ * |x| ≥ 1e21 (NaN and ±Infinity ride that arm's texts via ToString too,
3475
+ * matching the spec's early answers). */
3476
+ ScrStr *scr_num_to_fixed0(double x) {
3477
+ if (isnan(x)) return scr_str_new("NaN", 3);
3478
+ if (fabs(x) >= 1e21) return scr_f64_to_scrstr(x);
3479
+ double a = fabs(x);
3480
+ double fl = floor(a);
3481
+ double n = (a - fl >= 0.5) ? fl + 1 : fl; /* exact: a < 2^70, and below
3482
+ * 2^52 the fraction is exact;
3483
+ * at/above it a - fl == 0 */
3484
+ ScrStr *digits = scr_f64_to_scrstr(n); /* n < 1e21 → never exponent form */
3485
+ if (!(x < 0)) return digits;
3486
+ size_t len = digits->len;
3487
+ char buf[64];
3488
+ buf[0] = '-';
3489
+ memcpy(buf + 1, digits->data, len);
3490
+ ScrStr *r = scr_str_new(buf, len + 1);
3491
+ scr_str_release(digits);
3492
+ return r;
3493
+ }
3494
+
3495
+ bool scr_num_is_nan(double x) { return isnan(x) != 0; }
3496
+
3497
+ bool scr_num_is_integer(double x) { return isfinite(x) && trunc(x) == x; }
3498
+
3499
+ bool scr_num_is_safe_integer(double x) {
3500
+ return isfinite(x) && trunc(x) == x && fabs(x) <= 9007199254740991.0;
3501
+ }
3502
+
3503
+ /* ── bitwise operators ─────────────────────────────────────────────────
3504
+ * JS-exact (scr_runtime.h has the contract). ToUint32 is the primitive —
3505
+ * ToInt32 and the Int32-typed results are the same 32 bits reinterpreted
3506
+ * as two's complement, spelled portably (no implementation-defined
3507
+ * narrowing casts, no UB shifts of signed values).
3508
+ */
3509
+
3510
+ static uint32_t scr_to_uint32(double d) {
3511
+ if (!isfinite(d)) return 0; /* NaN, +Infinity, -Infinity */
3512
+ double t = trunc(d);
3513
+ t = fmod(t, 4294967296.0); /* exact for doubles; result in (-2^32, 2^32) */
3514
+ if (t < 0) t += 4294967296.0;
3515
+ return (uint32_t)t;
3516
+ }
3517
+
3518
+ /* The 32 bits as a SIGNED (Int32) JS number. */
3519
+ static double scr_bits_as_int32(uint32_t u) {
3520
+ return u >= UINT32_C(0x80000000)
3521
+ ? (double)(int32_t)(u - UINT32_C(0x80000000)) + (double)INT32_MIN
3522
+ : (double)u;
3523
+ }
3524
+
3525
+ double scr_bit_and(double a, double b) {
3526
+ return scr_bits_as_int32(scr_to_uint32(a) & scr_to_uint32(b));
3527
+ }
3528
+
3529
+ double scr_bit_or(double a, double b) {
3530
+ return scr_bits_as_int32(scr_to_uint32(a) | scr_to_uint32(b));
3531
+ }
3532
+
3533
+ double scr_bit_xor(double a, double b) {
3534
+ return scr_bits_as_int32(scr_to_uint32(a) ^ scr_to_uint32(b));
3535
+ }
3536
+
3537
+ double scr_bit_shl(double a, double b) {
3538
+ return scr_bits_as_int32(scr_to_uint32(a) << (scr_to_uint32(b) & 31u));
3539
+ }
3540
+
3541
+ double scr_bit_shr(double a, double b) {
3542
+ uint32_t u = scr_to_uint32(a);
3543
+ uint32_t s = scr_to_uint32(b) & 31u;
3544
+ uint32_t r = u >> s;
3545
+ if ((u & UINT32_C(0x80000000)) != 0 && s != 0) {
3546
+ r |= ~(UINT32_C(0xffffffff) >> s); /* arithmetic shift: sign-fill */
3547
+ }
3548
+ return scr_bits_as_int32(r);
3549
+ }
3550
+
3551
+ double scr_bit_ushr(double a, double b) {
3552
+ /* The one Uint32-typed result: (-1 >>> 0) === 4294967295. */
3553
+ return (double)(scr_to_uint32(a) >> (scr_to_uint32(b) & 31u));
3554
+ }
3555
+
3556
+ double scr_bit_not(double a) {
3557
+ return scr_bits_as_int32(~scr_to_uint32(a));
3558
+ }
3559
+
3560
+ /* ── checked catch-binding cast (`e as C`) ────────────────────────────
3561
+ * The caught analog of the dyn boundary's checked casts: an OBJ payload
3562
+ * inside the class's preorder interval extracts (retained, +1); every
3563
+ * other payload throws a catchable TypeError naming the class. Node's
3564
+ * `as` is erasure — the runtime check is the documented trust-but-verify
3565
+ * stance for dynamic values, extended to exception payloads. */
3566
+ void *scr_caught_check_obj(const ScrCaught *c, size_t pre, size_t post,
3567
+ const char *cls) {
3568
+ if (scr_caught_instanceof(c, pre, post)) return c->retain_fn(c->payload);
3569
+ char msg[160];
3570
+ int len = snprintf(msg, sizeof msg,
3571
+ "caught value is not an instance of %s (checked cast)",
3572
+ cls);
3573
+ scr_throw_error_msg(SCR_ERR_TYPE, msg, (size_t)len);
3574
+ return NULL; /* callers are compiler-emitted pending checks */
3575
+ }
3576
+
3577
+ /* ── Set → array drain ([...set]) ─────────────────────────────────────
3578
+ * The live entries in insertion order (tombstones skipped — the forEach
3579
+ * walk folded into one call; no user code runs mid-drain, so the
3580
+ * live-iteration rules are moot). Borrows the set; the array is +1,
3581
+ * string elements retained into it by the iter_key read. */
3582
+ ScrArr *scr_set_to_arr_f64(const ScrMap *s) {
3583
+ size_t n = (size_t)scr_map_iter_count(s);
3584
+ ScrArr *out = scr_arr_new(SCR_ELEM_F64, (size_t)scr_map_size(s));
3585
+ for (size_t i = 0; i < n; i++) {
3586
+ if (!scr_map_iter_live(s, (double)i)) continue;
3587
+ scr_arr_push_f64(out, scr_map_iter_key_f64(s, (double)i));
3588
+ }
3589
+ return out;
3590
+ }
3591
+
3592
+ ScrArr *scr_set_to_arr_str(const ScrMap *s) {
3593
+ size_t n = (size_t)scr_map_iter_count(s);
3594
+ ScrArr *out = scr_arr_new(SCR_ELEM_STR, (size_t)scr_map_size(s));
3595
+ for (size_t i = 0; i < n; i++) {
3596
+ if (!scr_map_iter_live(s, (double)i)) continue;
3597
+ scr_arr_push_ref(out, scr_map_iter_key_str(s, (double)i));
3598
+ }
3599
+ return out;
3600
+ }
3601
+
3602
+ ScrArr *scr_set_to_arr_ref(const ScrMap *s) {
3603
+ size_t n = (size_t)scr_map_iter_count(s);
3604
+ /* The elements' adapters ride from the set (no trace: handle elements
3605
+ * are acyclic by the set's own construction rule). */
3606
+ ScrArr *out = scr_arr_new_ref(s->key_retain, s->key_release, NULL, (size_t)scr_map_size(s));
3607
+ for (size_t i = 0; i < n; i++) {
3608
+ if (!scr_map_iter_live(s, (double)i)) continue;
3609
+ scr_arr_push_ref(out, scr_map_iter_key_ref(s, (double)i));
3610
+ }
3611
+ return out;
3612
+ }