@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.
- package/LICENSE +202 -0
- package/package.json +16 -6
- package/src/scr_array.c +488 -0
- package/src/scr_assert.c +1393 -0
- package/src/scr_async.c +2593 -0
- package/src/scr_async_dyn.c +607 -0
- package/src/scr_bytes.c +1425 -0
- package/src/scr_bytes_io.c +161 -0
- package/src/scr_child.c +3587 -0
- package/src/scr_closure.c +214 -0
- package/src/scr_console.c +143 -0
- package/src/scr_cycle.c +218 -0
- package/src/scr_dc.c +805 -0
- package/src/scr_dgram.c +1007 -0
- package/src/scr_dyn_handle.c +154 -0
- package/src/scr_dyn_invoke.c +644 -0
- package/src/scr_error.c +332 -0
- package/src/scr_events.c +714 -0
- package/src/scr_events_emitter.c +699 -0
- package/src/scr_exception.c +242 -0
- package/src/scr_fetch.c +1130 -0
- package/src/scr_fetch_curl.c +654 -0
- package/src/scr_http.c +3482 -0
- package/src/scr_http2.c +3030 -0
- package/src/scr_inspect.c +803 -0
- package/src/scr_inspect_island.c +51 -0
- package/src/scr_island.c +9363 -0
- package/src/scr_json.c +2151 -0
- package/src/scr_lib.c +3612 -0
- package/src/scr_loop_epoll.c +241 -0
- package/src/scr_loop_kqueue.c +121 -0
- package/src/scr_loop_wsapoll.c +208 -0
- package/src/scr_map.c +591 -0
- package/src/scr_net.c +3017 -0
- package/src/scr_net_island.c +458 -0
- package/src/scr_number.c +115 -0
- package/src/scr_object.c +25 -0
- package/src/scr_path.c +1266 -0
- package/src/scr_platform.h +119 -0
- package/src/scr_readline.c +287 -0
- package/src/scr_regex.c +1080 -0
- package/src/scr_runtime.h +5046 -0
- package/src/scr_stream.c +2877 -0
- package/src/scr_string.c +1264 -0
- package/src/scr_symbol.c +132 -0
- package/src/scr_test.c +662 -0
- package/src/scr_tls.c +1520 -0
- package/src/scr_union.c +105 -0
- package/src/scr_url.c +911 -0
- package/src/scr_url_internal.h +29 -0
- package/src/scr_url_params.c +561 -0
- package/src/scr_watch.c +568 -0
- package/src/scr_web.c +1778 -0
- package/src/scr_win.c +76 -0
- package/src/scr_zlib.c +197 -0
- package/src/scr_zlib_island.c +15 -0
- package/vendor/README.md +52 -0
- package/vendor/curl/COPYRIGHT +534 -0
- package/vendor/curl/include/curl/curl.h +3214 -0
- package/vendor/curl/include/curl/curlver.h +79 -0
- package/vendor/curl/include/curl/easy.h +125 -0
- package/vendor/curl/include/curl/header.h +74 -0
- package/vendor/curl/include/curl/mprintf.h +52 -0
- package/vendor/curl/include/curl/multi.h +460 -0
- package/vendor/curl/include/curl/options.h +70 -0
- package/vendor/curl/include/curl/stdcheaders.h +35 -0
- package/vendor/curl/include/curl/system.h +508 -0
- package/vendor/curl/include/curl/typecheck-gcc.h +716 -0
- package/vendor/curl/include/curl/urlapi.h +149 -0
- package/vendor/curl/include/curl/websockets.h +84 -0
- package/vendor/mbedtls/LICENSE +553 -0
- package/vendor/mbedtls/include/CMakeLists.txt +22 -0
- package/vendor/mbedtls/include/mbedtls/aes.h +631 -0
- package/vendor/mbedtls/include/mbedtls/aria.h +343 -0
- package/vendor/mbedtls/include/mbedtls/asn1.h +642 -0
- package/vendor/mbedtls/include/mbedtls/asn1write.h +390 -0
- package/vendor/mbedtls/include/mbedtls/base64.h +82 -0
- package/vendor/mbedtls/include/mbedtls/bignum.h +1088 -0
- package/vendor/mbedtls/include/mbedtls/block_cipher.h +76 -0
- package/vendor/mbedtls/include/mbedtls/build_info.h +194 -0
- package/vendor/mbedtls/include/mbedtls/camellia.h +305 -0
- package/vendor/mbedtls/include/mbedtls/ccm.h +526 -0
- package/vendor/mbedtls/include/mbedtls/chacha20.h +209 -0
- package/vendor/mbedtls/include/mbedtls/chachapoly.h +351 -0
- package/vendor/mbedtls/include/mbedtls/check_config.h +1149 -0
- package/vendor/mbedtls/include/mbedtls/cipher.h +1250 -0
- package/vendor/mbedtls/include/mbedtls/cmac.h +246 -0
- package/vendor/mbedtls/include/mbedtls/compat-2.x.h +46 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h +578 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h +873 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h +359 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h +145 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_ssl.h +91 -0
- package/vendor/mbedtls/include/mbedtls/config_adjust_x509.h +35 -0
- package/vendor/mbedtls/include/mbedtls/config_psa.h +61 -0
- package/vendor/mbedtls/include/mbedtls/constant_time.h +36 -0
- package/vendor/mbedtls/include/mbedtls/ctr_drbg.h +596 -0
- package/vendor/mbedtls/include/mbedtls/debug.h +156 -0
- package/vendor/mbedtls/include/mbedtls/des.h +385 -0
- package/vendor/mbedtls/include/mbedtls/dhm.h +972 -0
- package/vendor/mbedtls/include/mbedtls/ecdh.h +455 -0
- package/vendor/mbedtls/include/mbedtls/ecdsa.h +674 -0
- package/vendor/mbedtls/include/mbedtls/ecjpake.h +298 -0
- package/vendor/mbedtls/include/mbedtls/ecp.h +1517 -0
- package/vendor/mbedtls/include/mbedtls/entropy.h +274 -0
- package/vendor/mbedtls/include/mbedtls/error.h +201 -0
- package/vendor/mbedtls/include/mbedtls/gcm.h +387 -0
- package/vendor/mbedtls/include/mbedtls/hkdf.h +124 -0
- package/vendor/mbedtls/include/mbedtls/hmac_drbg.h +434 -0
- package/vendor/mbedtls/include/mbedtls/lms.h +440 -0
- package/vendor/mbedtls/include/mbedtls/mbedtls_config.h +4446 -0
- package/vendor/mbedtls/include/mbedtls/md.h +526 -0
- package/vendor/mbedtls/include/mbedtls/md5.h +190 -0
- package/vendor/mbedtls/include/mbedtls/memory_buffer_alloc.h +142 -0
- package/vendor/mbedtls/include/mbedtls/net_sockets.h +299 -0
- package/vendor/mbedtls/include/mbedtls/nist_kw.h +166 -0
- package/vendor/mbedtls/include/mbedtls/oid.h +727 -0
- package/vendor/mbedtls/include/mbedtls/pem.h +160 -0
- package/vendor/mbedtls/include/mbedtls/pk.h +1303 -0
- package/vendor/mbedtls/include/mbedtls/pkcs12.h +186 -0
- package/vendor/mbedtls/include/mbedtls/pkcs5.h +198 -0
- package/vendor/mbedtls/include/mbedtls/pkcs7.h +252 -0
- package/vendor/mbedtls/include/mbedtls/platform.h +516 -0
- package/vendor/mbedtls/include/mbedtls/platform_time.h +79 -0
- package/vendor/mbedtls/include/mbedtls/platform_util.h +247 -0
- package/vendor/mbedtls/include/mbedtls/poly1305.h +168 -0
- package/vendor/mbedtls/include/mbedtls/private_access.h +20 -0
- package/vendor/mbedtls/include/mbedtls/psa_util.h +207 -0
- package/vendor/mbedtls/include/mbedtls/ripemd160.h +136 -0
- package/vendor/mbedtls/include/mbedtls/rsa.h +1170 -0
- package/vendor/mbedtls/include/mbedtls/sha1.h +219 -0
- package/vendor/mbedtls/include/mbedtls/sha256.h +200 -0
- package/vendor/mbedtls/include/mbedtls/sha3.h +172 -0
- package/vendor/mbedtls/include/mbedtls/sha512.h +208 -0
- package/vendor/mbedtls/include/mbedtls/ssl.h +5887 -0
- package/vendor/mbedtls/include/mbedtls/ssl_cache.h +187 -0
- package/vendor/mbedtls/include/mbedtls/ssl_ciphersuites.h +482 -0
- package/vendor/mbedtls/include/mbedtls/ssl_cookie.h +106 -0
- package/vendor/mbedtls/include/mbedtls/ssl_ticket.h +199 -0
- package/vendor/mbedtls/include/mbedtls/threading.h +167 -0
- package/vendor/mbedtls/include/mbedtls/timing.h +94 -0
- package/vendor/mbedtls/include/mbedtls/version.h +78 -0
- package/vendor/mbedtls/include/mbedtls/x509.h +500 -0
- package/vendor/mbedtls/include/mbedtls/x509_crl.h +184 -0
- package/vendor/mbedtls/include/mbedtls/x509_crt.h +1208 -0
- package/vendor/mbedtls/include/mbedtls/x509_csr.h +382 -0
- package/vendor/mbedtls/include/psa/build_info.h +20 -0
- package/vendor/mbedtls/include/psa/crypto.h +4998 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_auto_enabled.h +31 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_config_dependencies.h +51 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h +101 -0
- package/vendor/mbedtls/include/psa/crypto_adjust_config_synonyms.h +49 -0
- package/vendor/mbedtls/include/psa/crypto_builtin_composites.h +214 -0
- package/vendor/mbedtls/include/psa/crypto_builtin_key_derivation.h +118 -0
- package/vendor/mbedtls/include/psa/crypto_builtin_primitives.h +114 -0
- package/vendor/mbedtls/include/psa/crypto_compat.h +230 -0
- package/vendor/mbedtls/include/psa/crypto_config.h +145 -0
- package/vendor/mbedtls/include/psa/crypto_driver_common.h +44 -0
- package/vendor/mbedtls/include/psa/crypto_driver_contexts_composites.h +151 -0
- package/vendor/mbedtls/include/psa/crypto_driver_contexts_key_derivation.h +52 -0
- package/vendor/mbedtls/include/psa/crypto_driver_contexts_primitives.h +105 -0
- package/vendor/mbedtls/include/psa/crypto_extra.h +2145 -0
- package/vendor/mbedtls/include/psa/crypto_legacy.h +88 -0
- package/vendor/mbedtls/include/psa/crypto_platform.h +102 -0
- package/vendor/mbedtls/include/psa/crypto_se_driver.h +1383 -0
- package/vendor/mbedtls/include/psa/crypto_sizes.h +1319 -0
- package/vendor/mbedtls/include/psa/crypto_struct.h +527 -0
- package/vendor/mbedtls/include/psa/crypto_types.h +508 -0
- package/vendor/mbedtls/include/psa/crypto_values.h +2782 -0
- package/vendor/mbedtls/library/aes.c +2294 -0
- package/vendor/mbedtls/library/aesce.c +624 -0
- package/vendor/mbedtls/library/aesce.h +136 -0
- package/vendor/mbedtls/library/aesni.c +846 -0
- package/vendor/mbedtls/library/aesni.h +162 -0
- package/vendor/mbedtls/library/alignment.h +704 -0
- package/vendor/mbedtls/library/aria.c +969 -0
- package/vendor/mbedtls/library/asn1parse.c +468 -0
- package/vendor/mbedtls/library/asn1write.c +440 -0
- package/vendor/mbedtls/library/base64.c +322 -0
- package/vendor/mbedtls/library/base64_internal.h +45 -0
- package/vendor/mbedtls/library/bignum.c +2583 -0
- package/vendor/mbedtls/library/bignum_core.c +1240 -0
- package/vendor/mbedtls/library/bignum_core.h +872 -0
- package/vendor/mbedtls/library/bignum_core_invasive.h +38 -0
- package/vendor/mbedtls/library/bignum_internal.h +122 -0
- package/vendor/mbedtls/library/bignum_mod.c +394 -0
- package/vendor/mbedtls/library/bignum_mod.h +452 -0
- package/vendor/mbedtls/library/bignum_mod_raw.c +276 -0
- package/vendor/mbedtls/library/bignum_mod_raw.h +416 -0
- package/vendor/mbedtls/library/bignum_mod_raw_invasive.h +34 -0
- package/vendor/mbedtls/library/block_cipher.c +207 -0
- package/vendor/mbedtls/library/block_cipher_internal.h +99 -0
- package/vendor/mbedtls/library/bn_mul.h +1094 -0
- package/vendor/mbedtls/library/camellia.c +1058 -0
- package/vendor/mbedtls/library/ccm.c +777 -0
- package/vendor/mbedtls/library/chacha20.c +563 -0
- package/vendor/mbedtls/library/chacha20_internal.h +23 -0
- package/vendor/mbedtls/library/chachapoly.c +487 -0
- package/vendor/mbedtls/library/check_crypto_config.h +136 -0
- package/vendor/mbedtls/library/cipher.c +1712 -0
- package/vendor/mbedtls/library/cipher_invasive.h +28 -0
- package/vendor/mbedtls/library/cipher_wrap.c +2482 -0
- package/vendor/mbedtls/library/cipher_wrap.h +178 -0
- package/vendor/mbedtls/library/cmac.c +1067 -0
- package/vendor/mbedtls/library/common.h +487 -0
- package/vendor/mbedtls/library/constant_time.c +247 -0
- package/vendor/mbedtls/library/constant_time_impl.h +541 -0
- package/vendor/mbedtls/library/constant_time_internal.h +579 -0
- package/vendor/mbedtls/library/ctr.h +35 -0
- package/vendor/mbedtls/library/ctr_drbg.c +1016 -0
- package/vendor/mbedtls/library/debug.c +475 -0
- package/vendor/mbedtls/library/debug_internal.h +185 -0
- package/vendor/mbedtls/library/des.c +1042 -0
- package/vendor/mbedtls/library/dhm.c +700 -0
- package/vendor/mbedtls/library/ecdh.c +696 -0
- package/vendor/mbedtls/library/ecdsa.c +858 -0
- package/vendor/mbedtls/library/ecjpake.c +1216 -0
- package/vendor/mbedtls/library/ecp.c +3674 -0
- package/vendor/mbedtls/library/ecp_curves.c +6125 -0
- package/vendor/mbedtls/library/ecp_curves_new.c +9 -0
- package/vendor/mbedtls/library/ecp_internal_alt.h +287 -0
- package/vendor/mbedtls/library/ecp_invasive.h +305 -0
- package/vendor/mbedtls/library/entropy.c +680 -0
- package/vendor/mbedtls/library/entropy_poll.c +233 -0
- package/vendor/mbedtls/library/entropy_poll.h +64 -0
- package/vendor/mbedtls/library/error.c +878 -0
- package/vendor/mbedtls/library/gcm.c +1330 -0
- package/vendor/mbedtls/library/hkdf.c +161 -0
- package/vendor/mbedtls/library/hmac_drbg.c +633 -0
- package/vendor/mbedtls/library/lmots.c +789 -0
- package/vendor/mbedtls/library/lmots.h +288 -0
- package/vendor/mbedtls/library/lms.c +778 -0
- package/vendor/mbedtls/library/md.c +1108 -0
- package/vendor/mbedtls/library/md5.c +426 -0
- package/vendor/mbedtls/library/md_psa.h +26 -0
- package/vendor/mbedtls/library/md_wrap.h +46 -0
- package/vendor/mbedtls/library/memory_buffer_alloc.c +751 -0
- package/vendor/mbedtls/library/mps_common.h +181 -0
- package/vendor/mbedtls/library/mps_error.h +89 -0
- package/vendor/mbedtls/library/mps_reader.c +538 -0
- package/vendor/mbedtls/library/mps_reader.h +366 -0
- package/vendor/mbedtls/library/mps_trace.c +112 -0
- package/vendor/mbedtls/library/mps_trace.h +154 -0
- package/vendor/mbedtls/library/net_sockets.c +694 -0
- package/vendor/mbedtls/library/nist_kw.c +729 -0
- package/vendor/mbedtls/library/oid.c +1166 -0
- package/vendor/mbedtls/library/padlock.c +157 -0
- package/vendor/mbedtls/library/padlock.h +111 -0
- package/vendor/mbedtls/library/pem.c +554 -0
- package/vendor/mbedtls/library/pk.c +1602 -0
- package/vendor/mbedtls/library/pk_ecc.c +261 -0
- package/vendor/mbedtls/library/pk_internal.h +241 -0
- package/vendor/mbedtls/library/pk_wrap.c +1618 -0
- package/vendor/mbedtls/library/pk_wrap.h +138 -0
- package/vendor/mbedtls/library/pkcs12.c +437 -0
- package/vendor/mbedtls/library/pkcs5.c +500 -0
- package/vendor/mbedtls/library/pkcs7.c +787 -0
- package/vendor/mbedtls/library/pkparse.c +1392 -0
- package/vendor/mbedtls/library/pkwrite.c +631 -0
- package/vendor/mbedtls/library/pkwrite.h +121 -0
- package/vendor/mbedtls/library/platform.c +402 -0
- package/vendor/mbedtls/library/platform_util.c +258 -0
- package/vendor/mbedtls/library/poly1305.c +492 -0
- package/vendor/mbedtls/library/psa_crypto.c +9532 -0
- package/vendor/mbedtls/library/psa_crypto_aead.c +646 -0
- package/vendor/mbedtls/library/psa_crypto_aead.h +499 -0
- package/vendor/mbedtls/library/psa_crypto_cipher.c +747 -0
- package/vendor/mbedtls/library/psa_crypto_cipher.h +316 -0
- package/vendor/mbedtls/library/psa_crypto_client.c +22 -0
- package/vendor/mbedtls/library/psa_crypto_core.h +983 -0
- package/vendor/mbedtls/library/psa_crypto_core_common.h +52 -0
- package/vendor/mbedtls/library/psa_crypto_driver_wrappers.h +2896 -0
- package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.c +256 -0
- package/vendor/mbedtls/library/psa_crypto_driver_wrappers_no_static.h +31 -0
- package/vendor/mbedtls/library/psa_crypto_ecp.c +594 -0
- package/vendor/mbedtls/library/psa_crypto_ecp.h +267 -0
- package/vendor/mbedtls/library/psa_crypto_ffdh.c +361 -0
- package/vendor/mbedtls/library/psa_crypto_ffdh.h +131 -0
- package/vendor/mbedtls/library/psa_crypto_hash.c +470 -0
- package/vendor/mbedtls/library/psa_crypto_hash.h +211 -0
- package/vendor/mbedtls/library/psa_crypto_invasive.h +92 -0
- package/vendor/mbedtls/library/psa_crypto_its.h +131 -0
- package/vendor/mbedtls/library/psa_crypto_mac.c +505 -0
- package/vendor/mbedtls/library/psa_crypto_mac.h +264 -0
- package/vendor/mbedtls/library/psa_crypto_pake.c +571 -0
- package/vendor/mbedtls/library/psa_crypto_pake.h +159 -0
- package/vendor/mbedtls/library/psa_crypto_random.c +181 -0
- package/vendor/mbedtls/library/psa_crypto_random.h +72 -0
- package/vendor/mbedtls/library/psa_crypto_random_impl.h +200 -0
- package/vendor/mbedtls/library/psa_crypto_rsa.c +714 -0
- package/vendor/mbedtls/library/psa_crypto_rsa.h +321 -0
- package/vendor/mbedtls/library/psa_crypto_se.c +373 -0
- package/vendor/mbedtls/library/psa_crypto_se.h +192 -0
- package/vendor/mbedtls/library/psa_crypto_slot_management.c +1137 -0
- package/vendor/mbedtls/library/psa_crypto_slot_management.h +344 -0
- package/vendor/mbedtls/library/psa_crypto_storage.c +481 -0
- package/vendor/mbedtls/library/psa_crypto_storage.h +392 -0
- package/vendor/mbedtls/library/psa_its_file.c +254 -0
- package/vendor/mbedtls/library/psa_util.c +614 -0
- package/vendor/mbedtls/library/psa_util_internal.h +100 -0
- package/vendor/mbedtls/library/ripemd160.c +490 -0
- package/vendor/mbedtls/library/rsa.c +3121 -0
- package/vendor/mbedtls/library/rsa_alt_helpers.c +455 -0
- package/vendor/mbedtls/library/rsa_alt_helpers.h +209 -0
- package/vendor/mbedtls/library/rsa_internal.h +188 -0
- package/vendor/mbedtls/library/sha1.c +480 -0
- package/vendor/mbedtls/library/sha256.c +983 -0
- package/vendor/mbedtls/library/sha3.c +721 -0
- package/vendor/mbedtls/library/sha512.c +1115 -0
- package/vendor/mbedtls/library/ssl_cache.c +410 -0
- package/vendor/mbedtls/library/ssl_ciphersuites.c +2050 -0
- package/vendor/mbedtls/library/ssl_ciphersuites_internal.h +154 -0
- package/vendor/mbedtls/library/ssl_client.c +1023 -0
- package/vendor/mbedtls/library/ssl_client.h +22 -0
- package/vendor/mbedtls/library/ssl_cookie.c +384 -0
- package/vendor/mbedtls/library/ssl_debug_helpers.h +85 -0
- package/vendor/mbedtls/library/ssl_debug_helpers_generated.c +251 -0
- package/vendor/mbedtls/library/ssl_misc.h +3198 -0
- package/vendor/mbedtls/library/ssl_msg.c +6619 -0
- package/vendor/mbedtls/library/ssl_ticket.c +556 -0
- package/vendor/mbedtls/library/ssl_tls.c +10234 -0
- package/vendor/mbedtls/library/ssl_tls12_client.c +3688 -0
- package/vendor/mbedtls/library/ssl_tls12_server.c +4311 -0
- package/vendor/mbedtls/library/ssl_tls13_client.c +3208 -0
- package/vendor/mbedtls/library/ssl_tls13_generic.c +1793 -0
- package/vendor/mbedtls/library/ssl_tls13_invasive.h +23 -0
- package/vendor/mbedtls/library/ssl_tls13_keys.c +1918 -0
- package/vendor/mbedtls/library/ssl_tls13_keys.h +668 -0
- package/vendor/mbedtls/library/ssl_tls13_server.c +3624 -0
- package/vendor/mbedtls/library/threading.c +193 -0
- package/vendor/mbedtls/library/threading_internal.h +28 -0
- package/vendor/mbedtls/library/timing.c +152 -0
- package/vendor/mbedtls/library/version.c +32 -0
- package/vendor/mbedtls/library/version_features.c +856 -0
- package/vendor/mbedtls/library/x509.c +1776 -0
- package/vendor/mbedtls/library/x509_create.c +570 -0
- package/vendor/mbedtls/library/x509_crl.c +708 -0
- package/vendor/mbedtls/library/x509_crt.c +3311 -0
- package/vendor/mbedtls/library/x509_csr.c +648 -0
- package/vendor/mbedtls/library/x509_internal.h +101 -0
- package/vendor/mbedtls/library/x509write.c +174 -0
- package/vendor/mbedtls/library/x509write_crt.c +688 -0
- package/vendor/mbedtls/library/x509write_csr.c +336 -0
- package/vendor/quickjs-ng/CMakeLists.txt +566 -0
- package/vendor/quickjs-ng/LICENSE +24 -0
- package/vendor/quickjs-ng/README.md +24 -0
- package/vendor/quickjs-ng/api-test.c +1195 -0
- package/vendor/quickjs-ng/builtin-array-fromasync.h +119 -0
- package/vendor/quickjs-ng/builtin-iterator-zip-keyed.h +332 -0
- package/vendor/quickjs-ng/builtin-iterator-zip.h +337 -0
- package/vendor/quickjs-ng/cutils.h +1998 -0
- package/vendor/quickjs-ng/dtoa.c +1619 -0
- package/vendor/quickjs-ng/dtoa.h +87 -0
- package/vendor/quickjs-ng/gen/function_source.c +81 -0
- package/vendor/quickjs-ng/gen/hello.c +53 -0
- package/vendor/quickjs-ng/gen/hello_module.c +106 -0
- package/vendor/quickjs-ng/gen/repl.c +3036 -0
- package/vendor/quickjs-ng/gen/standalone.c +323 -0
- package/vendor/quickjs-ng/gen/test_fib.c +81 -0
- package/vendor/quickjs-ng/libregexp-opcode.h +73 -0
- package/vendor/quickjs-ng/libregexp.c +3474 -0
- package/vendor/quickjs-ng/libregexp.h +101 -0
- package/vendor/quickjs-ng/libunicode-table.h +5173 -0
- package/vendor/quickjs-ng/libunicode.c +2069 -0
- package/vendor/quickjs-ng/libunicode.h +172 -0
- package/vendor/quickjs-ng/list.h +107 -0
- package/vendor/quickjs-ng/lre-test.c +52 -0
- package/vendor/quickjs-ng/qjs.c +762 -0
- package/vendor/quickjs-ng/qjsc.c +673 -0
- package/vendor/quickjs-ng/quickjs-atom.h +280 -0
- package/vendor/quickjs-ng/quickjs-c-atomics.h +54 -0
- package/vendor/quickjs-ng/quickjs-libc.c +5037 -0
- package/vendor/quickjs-ng/quickjs-libc.h +87 -0
- package/vendor/quickjs-ng/quickjs-opcode.h +377 -0
- package/vendor/quickjs-ng/quickjs.c +64041 -0
- package/vendor/quickjs-ng/quickjs.h +1447 -0
- package/vendor/quickjs-ng/run-test262.c +2374 -0
- package/vendor/quickjs-ng/unicode_gen.c +3121 -0
- package/vendor/quickjs-ng/unicode_gen_def.h +310 -0
- package/vendor/ryu/LICENSE-Boost +23 -0
- package/vendor/ryu/common.h +114 -0
- package/vendor/ryu/d2s.c +509 -0
- package/vendor/ryu/d2s_full_table.h +367 -0
- package/vendor/ryu/d2s_intrinsics.h +357 -0
- package/vendor/ryu/d2s_small_table.h +186 -0
- package/vendor/ryu/digit_table.h +35 -0
- package/vendor/ryu/ryu.h +46 -0
- package/vendor/zlib/LICENSE +22 -0
- package/vendor/zlib/adler32.c +164 -0
- package/vendor/zlib/compress.c +75 -0
- package/vendor/zlib/crc32.c +1049 -0
- package/vendor/zlib/crc32.h +9446 -0
- package/vendor/zlib/deflate.c +2139 -0
- package/vendor/zlib/deflate.h +377 -0
- package/vendor/zlib/gzclose.c +23 -0
- package/vendor/zlib/gzguts.h +214 -0
- package/vendor/zlib/gzlib.c +582 -0
- package/vendor/zlib/gzread.c +602 -0
- package/vendor/zlib/gzwrite.c +631 -0
- package/vendor/zlib/infback.c +628 -0
- package/vendor/zlib/inffast.c +320 -0
- package/vendor/zlib/inffast.h +11 -0
- package/vendor/zlib/inffixed.h +94 -0
- package/vendor/zlib/inflate.c +1526 -0
- package/vendor/zlib/inflate.h +126 -0
- package/vendor/zlib/inftrees.c +299 -0
- package/vendor/zlib/inftrees.h +62 -0
- package/vendor/zlib/trees.c +1117 -0
- package/vendor/zlib/trees.h +128 -0
- package/vendor/zlib/uncompr.c +85 -0
- package/vendor/zlib/zconf.h +543 -0
- package/vendor/zlib/zlib.h +1938 -0
- package/vendor/zlib/zutil.c +299 -0
- package/vendor/zlib/zutil.h +254 -0
- package/README.md +0 -3
- package/index.js +0 -2
package/src/scr_stream.c
ADDED
|
@@ -0,0 +1,2877 @@
|
|
|
1
|
+
/* node:stream — the runtime-provided stream classes behind the
|
|
2
|
+
* options-object constructor forms (`new Readable({ read() {} })` et al).
|
|
3
|
+
* This translation unit links ONLY into binaries whose IR touches the
|
|
4
|
+
* stream surface (moduleUsesStream — the scr_events_emitter.c gating
|
|
5
|
+
* precedent); stream-free binaries keep their exact link line.
|
|
6
|
+
*
|
|
7
|
+
* ONE layout for all five classes (ScrStream: the ScrEmitter prefix plus
|
|
8
|
+
* a state block), so upcasts among them and to ScrEmitter are pointer
|
|
9
|
+
* reinterprets. Events dispatch through the embedded emitter registry —
|
|
10
|
+
* scr_emitter_emit's variadic core — with the frontend's PER-BASE forced
|
|
11
|
+
* tuples guaranteeing every 'data' listener reads one ScrBytes*, every
|
|
12
|
+
* 'pipe'/'unpipe' listener one ScrStream*, and the lifecycle events
|
|
13
|
+
* nothing.
|
|
14
|
+
*
|
|
15
|
+
* EVENT TIMING. Node schedules most stream emissions on process.nextTick;
|
|
16
|
+
* here those deferrals ride a FIFO tick queue drained at the top of every
|
|
17
|
+
* loop turn (scr_loop_set_stream — the station before events/net/timers,
|
|
18
|
+
* the closest to nextTick; ticks scheduled from user code on the main
|
|
19
|
+
* stack run when the loop starts, i.e. after all synchronous code — the
|
|
20
|
+
* nextTick shape). The implemented orderings follow lib/internal/streams:
|
|
21
|
+
* - on('data') starts flowing on a TICK (resume_): synchronous code
|
|
22
|
+
* after the registration runs before the first 'data'.
|
|
23
|
+
* - push() while flowing with an empty buffer emits 'data'
|
|
24
|
+
* SYNCHRONOUSLY (addChunk's direct-emit fast path).
|
|
25
|
+
* - 'readable' is emitted on a tick (emitReadable_), collapsed while
|
|
26
|
+
* one is already scheduled (emittedReadable).
|
|
27
|
+
* - push(null) → 'end' fires on a tick once the buffer drains, then
|
|
28
|
+
* autoDestroy (default) destroys and 'close' follows on the NEXT
|
|
29
|
+
* tick.
|
|
30
|
+
* - write() calls the user write synchronously; a SYNCHRONOUS
|
|
31
|
+
* completion defers afterWrite (the user cb + 'drain') to a tick
|
|
32
|
+
* (Node's onwrite sync guard), an async completion runs it directly.
|
|
33
|
+
* - end() → (buffer drains) → _final → 'prefinish' (sync) → 'finish'
|
|
34
|
+
* on a tick → autoDestroy → 'close' on the next tick.
|
|
35
|
+
* - destroy(err) calls _destroy synchronously; 'error' then 'close'
|
|
36
|
+
* fire on ticks (error first, close after — Node's order).
|
|
37
|
+
* An unhandled 'error' event throws its payload through the exception
|
|
38
|
+
* cell (scr_emitter_emit_error) — the loop's dispatch surfaces it as the
|
|
39
|
+
* uncaught crash, Node's contract.
|
|
40
|
+
*
|
|
41
|
+
* DOCUMENTED DIVERGENCES (SEMANTICS.md, the stream block): tick-vs-
|
|
42
|
+
* microtask interleaving (Node runs nextTick callbacks before promise
|
|
43
|
+
* jobs; this queue drains after the fiber ready-queue), pipe() without
|
|
44
|
+
* registry-visible listeners (listenerCount('data') does not count the
|
|
45
|
+
* C-side pipe hookup), and Transform completing writes without read-side
|
|
46
|
+
* backpressure. */
|
|
47
|
+
#include "scr_runtime.h"
|
|
48
|
+
|
|
49
|
+
#include <stdio.h>
|
|
50
|
+
#include <stdlib.h>
|
|
51
|
+
#include <string.h>
|
|
52
|
+
|
|
53
|
+
/* Node v24's stream default (lib/internal/streams/state.js — 64 KiB
|
|
54
|
+
* since nodejs/node#52037, EXCEPT win32, where the very same line keeps
|
|
55
|
+
* the old 16 KiB: `process.platform === 'win32' ? 16 * 1024 : 64 * 1024`.
|
|
56
|
+
* The triple decides at compile time, like the path/EOL bindings). */
|
|
57
|
+
#ifdef _WIN32
|
|
58
|
+
#define SCR_STREAM_DEFAULT_HWM 16384
|
|
59
|
+
#else
|
|
60
|
+
#define SCR_STREAM_DEFAULT_HWM 65536
|
|
61
|
+
#endif
|
|
62
|
+
|
|
63
|
+
static void scr_stream_oom(void) {
|
|
64
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
65
|
+
abort();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* ── state ────────────────────────────────────────────────────────────── */
|
|
69
|
+
|
|
70
|
+
typedef struct ScrWEntry {
|
|
71
|
+
ScrBytes *chunk; /* owned */
|
|
72
|
+
ScrClosure *cb; /* owned or NULL (the user's write(chunk, cb) callback) */
|
|
73
|
+
} ScrWEntry;
|
|
74
|
+
|
|
75
|
+
struct ScrStreamState {
|
|
76
|
+
bool has_r, has_w;
|
|
77
|
+
bool passthrough; /* identity transform (PassThrough) */
|
|
78
|
+
bool is_transform; /* the writable side routes through transform/flush */
|
|
79
|
+
bool allow_half_open;
|
|
80
|
+
bool auto_destroy, emit_close;
|
|
81
|
+
|
|
82
|
+
/* shared lifecycle */
|
|
83
|
+
bool destroyed, destroy_calling, error_emitted, close_emitted, error_scheduled, close_scheduled;
|
|
84
|
+
/* a parked for-await consumed the error as its rejection: the 'error'
|
|
85
|
+
* EVENT is suppressed unless a user listener exists (Node's iterator
|
|
86
|
+
* registers its own handler, so no unhandled-'error' crash occurs) */
|
|
87
|
+
bool next_err_consumed;
|
|
88
|
+
ScrError *errored; /* owned */
|
|
89
|
+
|
|
90
|
+
/* readable side. The buffer holds owned ScrBytes* chunks — or, once an
|
|
91
|
+
* encoding is set (setEncoding / the encoding option), owned ScrStr*
|
|
92
|
+
* entries: pushes decode through the StringDecoder machinery, lengths
|
|
93
|
+
* count JS string units (Node's encoded accounting), and head_off
|
|
94
|
+
* counts CHARS consumed of buf[0] instead of bytes. */
|
|
95
|
+
struct {
|
|
96
|
+
size_t hwm, length;
|
|
97
|
+
void **buf; /* queue of owned chunks; buf[0] read from head_off */
|
|
98
|
+
size_t n, cap, head_off;
|
|
99
|
+
int flowing; /* -1 null, 0 paused, 1 flowing */
|
|
100
|
+
bool reading, in_read_sync, ended, end_emitted, end_scheduled;
|
|
101
|
+
bool need_readable, emitted_readable, readable_listening;
|
|
102
|
+
bool resume_scheduled, maybe_more_scheduled;
|
|
103
|
+
bool encoded; /* string-chunk mode (decoder active) */
|
|
104
|
+
ScrStr *enc; /* owned canonical encoding name, or NULL */
|
|
105
|
+
double dec_pending; /* scr_strdec packed pending state */
|
|
106
|
+
/* Readable.from mode: entries are whole OBJECTS (each counts 1
|
|
107
|
+
* toward length/hwm and delivers undivided — Node's objectMode
|
|
108
|
+
* accounting for the from() surface; hwm is 1). */
|
|
109
|
+
bool object_entries;
|
|
110
|
+
/* The parked for-await next() promise (at most one — the loop awaits
|
|
111
|
+
* each chunk before asking again). next_dyn picks the resolution
|
|
112
|
+
* shape: a dyn boxed by tag (the JS lane), or bytes. */
|
|
113
|
+
ScrPromise *next_waiter;
|
|
114
|
+
bool next_dyn;
|
|
115
|
+
ScrClosure *read_cb;
|
|
116
|
+
ScrStreamReadInv read_inv;
|
|
117
|
+
} r;
|
|
118
|
+
|
|
119
|
+
/* writable side */
|
|
120
|
+
struct {
|
|
121
|
+
size_t hwm, length, inflight_len;
|
|
122
|
+
ScrWEntry *q;
|
|
123
|
+
size_t n, cap, head;
|
|
124
|
+
int corked;
|
|
125
|
+
bool writing, wsync, need_drain, after_scheduled;
|
|
126
|
+
bool ending, finished, prefinished, final_called, finish_scheduled;
|
|
127
|
+
ScrClosure *inflight_cb; /* owned or NULL */
|
|
128
|
+
ScrClosure *write_cb;
|
|
129
|
+
ScrStreamChunkInv write_inv;
|
|
130
|
+
ScrClosure *final_cb;
|
|
131
|
+
ScrStreamPlainInv final_inv;
|
|
132
|
+
ScrClosure **end_cbs; /* owned end(cb) callbacks, fired at 'finish' */
|
|
133
|
+
size_t end_cbs_n, end_cbs_cap;
|
|
134
|
+
} w;
|
|
135
|
+
|
|
136
|
+
/* transform */
|
|
137
|
+
ScrClosure *transform_cb;
|
|
138
|
+
ScrStreamChunkInv transform_inv;
|
|
139
|
+
ScrClosure *flush_cb;
|
|
140
|
+
ScrStreamPlainInv flush_inv;
|
|
141
|
+
bool flush_called;
|
|
142
|
+
|
|
143
|
+
/* destroy */
|
|
144
|
+
ScrClosure *destroy_cb;
|
|
145
|
+
ScrStreamErrInv destroy_inv;
|
|
146
|
+
|
|
147
|
+
/* finished()/pipeline() watchers: fired once at the terminal point
|
|
148
|
+
* (the 'close' tick — or a registration-time tick when already closed)
|
|
149
|
+
* with the stream's finish status (NULL / the error / premature close).
|
|
150
|
+
* A watcher's presence marks lifecycle errors HANDLED (Node's eos and
|
|
151
|
+
* pipeline both register 'error' listeners, so no unhandled crash). */
|
|
152
|
+
struct {
|
|
153
|
+
ScrClosure **cb;
|
|
154
|
+
ScrStreamErrInv *inv;
|
|
155
|
+
size_t n, cap;
|
|
156
|
+
} fin;
|
|
157
|
+
|
|
158
|
+
/* pipes (src side): owned destination refs */
|
|
159
|
+
struct {
|
|
160
|
+
ScrStream **dst;
|
|
161
|
+
bool *end;
|
|
162
|
+
size_t n, cap;
|
|
163
|
+
} pipes;
|
|
164
|
+
size_t await_drain; /* pipe destinations we are waiting on */
|
|
165
|
+
|
|
166
|
+
/* pipe sources awaiting THIS stream's drain (dst side): owned refs */
|
|
167
|
+
struct {
|
|
168
|
+
ScrStream **src;
|
|
169
|
+
size_t n, cap;
|
|
170
|
+
} drain_srcs;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/* ── vtables and RC ───────────────────────────────────────────────────── */
|
|
174
|
+
|
|
175
|
+
/* The five runtime vtables start with the EMPTY preorder interval
|
|
176
|
+
* (pre 1 > post 0): main() stamps the real interval exactly when the
|
|
177
|
+
* class def rides the module, and an unstamped vt must never claim a
|
|
178
|
+
* user class whose preorder number happens to collide (scr_stream_is
|
|
179
|
+
* below tests interval membership for compiler-emitted subclasses). */
|
|
180
|
+
static void scr_stream_release_direct(void *obj);
|
|
181
|
+
ScrVt scr_readable_vt = {1, 0, &scr_stream_release_direct};
|
|
182
|
+
ScrVt scr_writable_vt = {1, 0, &scr_stream_release_direct};
|
|
183
|
+
ScrVt scr_duplex_vt = {1, 0, &scr_stream_release_direct};
|
|
184
|
+
ScrVt scr_transform_vt = {1, 0, &scr_stream_release_direct};
|
|
185
|
+
ScrVt scr_passthrough_vt = {1, 0, &scr_stream_release_direct};
|
|
186
|
+
|
|
187
|
+
static bool scr_vt_within(const ScrVt *vt, const ScrVt *cls) {
|
|
188
|
+
return cls->pre <= vt->pre && vt->pre <= cls->post;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* True for the five runtime classes AND compiler-emitted user subclasses
|
|
192
|
+
* (their structs embed the full ScrStream prefix, and their vtable's
|
|
193
|
+
* preorder number sits inside a stream class's stamped interval — the
|
|
194
|
+
* instanceof machinery). Duplex/Transform/PassThrough intervals nest
|
|
195
|
+
* inside Readable's, so two tests cover the forest. */
|
|
196
|
+
static bool scr_stream_is(const ScrEmitter *em) {
|
|
197
|
+
const ScrVt *vt = em->vt;
|
|
198
|
+
if (vt == &scr_readable_vt || vt == &scr_writable_vt || vt == &scr_duplex_vt ||
|
|
199
|
+
vt == &scr_transform_vt || vt == &scr_passthrough_vt) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
return scr_vt_within(vt, &scr_readable_vt) || scr_vt_within(vt, &scr_writable_vt) ||
|
|
203
|
+
scr_vt_within(vt, &scr_duplex_vt) || scr_vt_within(vt, &scr_transform_vt) ||
|
|
204
|
+
scr_vt_within(vt, &scr_passthrough_vt);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* Readable-buffer entry accessors: bytes chunks, or strings once an
|
|
208
|
+
* encoding is set (lengths then count JS string units, Node's encoded
|
|
209
|
+
* accounting). */
|
|
210
|
+
static size_t scr_stream_entry_len(const ScrStreamState *st, void *e) {
|
|
211
|
+
if (st->r.object_entries) return 1; /* objectMode-style accounting */
|
|
212
|
+
return st->r.encoded ? (size_t)scr_str_utf16_len((ScrStr *)e) : ((ScrBytes *)e)->len;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
static void scr_stream_entry_release(const ScrStreamState *st, void *e) {
|
|
216
|
+
if (st->r.encoded) scr_str_release((ScrStr *)e);
|
|
217
|
+
else scr_bytes_release((ScrBytes *)e);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
static void scr_stream_state_drop(ScrStreamState *st, bool gc) {
|
|
221
|
+
if (!st) return;
|
|
222
|
+
if (st->errored) scr_error_release(st->errored);
|
|
223
|
+
for (size_t i = 0; i < st->r.n; i++) scr_stream_entry_release(st, st->r.buf[i]);
|
|
224
|
+
free(st->r.buf);
|
|
225
|
+
if (st->r.enc) scr_str_release(st->r.enc);
|
|
226
|
+
if (!gc && st->r.next_waiter) scr_promise_release(st->r.next_waiter);
|
|
227
|
+
if (!gc) {
|
|
228
|
+
if (st->r.read_cb) scr_closure_release(st->r.read_cb);
|
|
229
|
+
if (st->w.write_cb) scr_closure_release(st->w.write_cb);
|
|
230
|
+
if (st->w.final_cb) scr_closure_release(st->w.final_cb);
|
|
231
|
+
if (st->w.inflight_cb) scr_closure_release(st->w.inflight_cb);
|
|
232
|
+
if (st->transform_cb) scr_closure_release(st->transform_cb);
|
|
233
|
+
if (st->flush_cb) scr_closure_release(st->flush_cb);
|
|
234
|
+
if (st->destroy_cb) scr_closure_release(st->destroy_cb);
|
|
235
|
+
for (size_t i = 0; i < st->w.end_cbs_n; i++) scr_closure_release(st->w.end_cbs[i]);
|
|
236
|
+
for (size_t i = 0; i < st->fin.n; i++) scr_closure_release(st->fin.cb[i]);
|
|
237
|
+
for (size_t i = 0; i < st->pipes.n; i++) scr_stream_release(st->pipes.dst[i]);
|
|
238
|
+
for (size_t i = 0; i < st->drain_srcs.n; i++) scr_stream_release(st->drain_srcs.src[i]);
|
|
239
|
+
}
|
|
240
|
+
for (size_t i = st->w.head; i < st->w.n; i++) {
|
|
241
|
+
scr_bytes_release(st->w.q[i].chunk);
|
|
242
|
+
if (!gc && st->w.q[i].cb) scr_closure_release(st->w.q[i].cb);
|
|
243
|
+
}
|
|
244
|
+
free(st->w.q);
|
|
245
|
+
free(st->w.end_cbs);
|
|
246
|
+
free(st->fin.cb);
|
|
247
|
+
free(st->fin.inv);
|
|
248
|
+
free(st->pipes.dst);
|
|
249
|
+
free(st->pipes.end);
|
|
250
|
+
free(st->drain_srcs.src);
|
|
251
|
+
free(st);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* The compiler-emitted subclass RC/trace entry points: a user `extends
|
|
255
|
+
* Readable` struct embeds the ScrStream prefix (vt, registry, display
|
|
256
|
+
* name, state pointer), so its emitted teardown/trace delegate the STATE
|
|
257
|
+
* here (the registry rides scr_emitter_reg_* like any emitter subclass).
|
|
258
|
+
* NULL-safe: an exception before super(options) leaves st unset. */
|
|
259
|
+
void scr_stream_st_release(ScrStreamState *st) { scr_stream_state_drop(st, false); }
|
|
260
|
+
void scr_stream_st_gcfree(ScrStreamState *st) { scr_stream_state_drop(st, true); }
|
|
261
|
+
|
|
262
|
+
void scr_stream_st_trace(ScrStreamState *st, ScrTraceVisit visit, void *ctx) {
|
|
263
|
+
if (!st) return;
|
|
264
|
+
if (st->r.next_waiter) visit(st->r.next_waiter, ctx);
|
|
265
|
+
if (st->r.read_cb) visit(st->r.read_cb, ctx);
|
|
266
|
+
if (st->w.write_cb) visit(st->w.write_cb, ctx);
|
|
267
|
+
if (st->w.final_cb) visit(st->w.final_cb, ctx);
|
|
268
|
+
if (st->w.inflight_cb) visit(st->w.inflight_cb, ctx);
|
|
269
|
+
if (st->transform_cb) visit(st->transform_cb, ctx);
|
|
270
|
+
if (st->flush_cb) visit(st->flush_cb, ctx);
|
|
271
|
+
if (st->destroy_cb) visit(st->destroy_cb, ctx);
|
|
272
|
+
for (size_t i = 0; i < st->w.end_cbs_n; i++) visit(st->w.end_cbs[i], ctx);
|
|
273
|
+
for (size_t i = 0; i < st->fin.n; i++) visit(st->fin.cb[i], ctx);
|
|
274
|
+
for (size_t i = st->w.head; i < st->w.n; i++) {
|
|
275
|
+
if (st->w.q[i].cb) visit(st->w.q[i].cb, ctx);
|
|
276
|
+
}
|
|
277
|
+
for (size_t i = 0; i < st->pipes.n; i++) visit(st->pipes.dst[i], ctx);
|
|
278
|
+
for (size_t i = 0; i < st->drain_srcs.n; i++) visit(st->drain_srcs.src[i], ctx);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
void scr_stream_trace(void *obj, ScrTraceVisit visit, void *ctx) {
|
|
282
|
+
ScrStream *s = obj;
|
|
283
|
+
scr_emitter_reg_trace(s->reg, visit, ctx);
|
|
284
|
+
scr_stream_st_trace(s->st, visit, ctx);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
static void scr_stream_gcfree(void *obj) {
|
|
288
|
+
ScrStream *s = obj;
|
|
289
|
+
scr_emitter_reg_gcfree(s->reg);
|
|
290
|
+
scr_stream_state_drop(s->st, true);
|
|
291
|
+
scr_obj_free_note();
|
|
292
|
+
scr_cyc_free(obj);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
static void scr_stream_release_direct(void *obj) {
|
|
296
|
+
ScrStream *s = obj;
|
|
297
|
+
if (--s->rc == 0) {
|
|
298
|
+
scr_cyc_on_dead(s);
|
|
299
|
+
scr_emitter_reg_drop(s->reg);
|
|
300
|
+
scr_stream_state_drop(s->st, false);
|
|
301
|
+
scr_obj_free_note();
|
|
302
|
+
scr_cyc_free(s);
|
|
303
|
+
} else {
|
|
304
|
+
scr_cyc_on_release(s);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
ScrStream *scr_stream_retain(ScrStream *s) {
|
|
309
|
+
if (s && s->rc != SIZE_MAX) {
|
|
310
|
+
s->rc++;
|
|
311
|
+
scr_cyc_mark_live(s);
|
|
312
|
+
}
|
|
313
|
+
return s;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
void scr_stream_release(ScrStream *s) {
|
|
317
|
+
if (!s || s->rc == SIZE_MAX) return;
|
|
318
|
+
s->vt->release(s);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
void *scr_stream_retain_v(void *s) { return scr_stream_retain(s); }
|
|
322
|
+
void scr_stream_release_v(void *s) { scr_stream_release(s); }
|
|
323
|
+
|
|
324
|
+
/* ── the tick queue (the nextTick stand-in) ───────────────────────────── */
|
|
325
|
+
|
|
326
|
+
typedef enum {
|
|
327
|
+
SCR_ST_RESUME, /* resume_: read(0) kick, 'resume', flow */
|
|
328
|
+
SCR_ST_READABLE, /* emitReadable_: 'readable', then flow */
|
|
329
|
+
SCR_ST_END, /* endReadableNT: 'end', autoDestroy/half-open */
|
|
330
|
+
SCR_ST_MAYBE_MORE, /* maybeReadMore_: refill toward hwm */
|
|
331
|
+
SCR_ST_AFTER_WRITE, /* deferred afterWrite (sync completion) */
|
|
332
|
+
SCR_ST_FINISH, /* 'finish', end cbs, autoDestroy */
|
|
333
|
+
SCR_ST_ERROR, /* 'error' with the owned payload */
|
|
334
|
+
SCR_ST_CLOSE, /* 'close' (when emitClose) */
|
|
335
|
+
SCR_ST_END_W, /* half-open: end the writable side */
|
|
336
|
+
SCR_ST_WCB_ERR, /* a failed write's user cb (called plain) */
|
|
337
|
+
SCR_ST_NEXT_EOF, /* a parked for-await's EOF sentinel, AFTER 'end' */
|
|
338
|
+
SCR_ST_FIN, /* notify finished()/pipeline watchers (already-
|
|
339
|
+
* terminal registration) */
|
|
340
|
+
} ScrStreamTickOp;
|
|
341
|
+
|
|
342
|
+
typedef struct ScrStreamTick {
|
|
343
|
+
ScrStream *s; /* owned */
|
|
344
|
+
ScrStreamTickOp op;
|
|
345
|
+
ScrError *err; /* owned or NULL (SCR_ST_ERROR) */
|
|
346
|
+
ScrClosure *cb; /* owned or NULL (SCR_ST_AFTER_WRITE / SCR_ST_WCB_ERR) */
|
|
347
|
+
struct ScrStreamTick *next;
|
|
348
|
+
} ScrStreamTick;
|
|
349
|
+
|
|
350
|
+
static ScrStreamTick *scr_st_head = NULL;
|
|
351
|
+
static ScrStreamTick *scr_st_tail = NULL;
|
|
352
|
+
|
|
353
|
+
static void scr_st_tick(ScrStream *s, ScrStreamTickOp op, ScrError *err /*moves*/,
|
|
354
|
+
ScrClosure *cb /*moves*/) {
|
|
355
|
+
ScrStreamTick *t = calloc(1, sizeof *t);
|
|
356
|
+
if (!t) scr_stream_oom();
|
|
357
|
+
t->s = scr_stream_retain(s);
|
|
358
|
+
t->op = op;
|
|
359
|
+
t->err = err;
|
|
360
|
+
t->cb = cb;
|
|
361
|
+
if (scr_st_tail) scr_st_tail->next = t;
|
|
362
|
+
else scr_st_head = t;
|
|
363
|
+
scr_st_tail = t;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
static bool scr_stream_ticks_pending(void) { return scr_st_head != NULL; }
|
|
367
|
+
|
|
368
|
+
/* ── emit helpers ─────────────────────────────────────────────────────── */
|
|
369
|
+
|
|
370
|
+
static bool scr_stream_emit0(ScrStream *s, const char *name) {
|
|
371
|
+
ScrStr *n = scr_str_new(name, strlen(name));
|
|
372
|
+
bool had = scr_emitter_emit((ScrEmitter *)s, n);
|
|
373
|
+
scr_str_release(n);
|
|
374
|
+
return had;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
static void scr_stream_emit_stream(ScrStream *s, const char *name, ScrStream *payload) {
|
|
378
|
+
ScrStr *n = scr_str_new(name, strlen(name));
|
|
379
|
+
scr_emitter_emit((ScrEmitter *)s, n, payload);
|
|
380
|
+
scr_str_release(n);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/* 'error': unhandled throws the payload (Node's crash), via the emitter's
|
|
384
|
+
* error contract. Borrows err. */
|
|
385
|
+
static void scr_stream_emit_error(ScrStream *s, ScrError *err) {
|
|
386
|
+
ScrStr *n = scr_str_new("error", 5);
|
|
387
|
+
scr_emitter_emit_error((ScrEmitter *)s, n, err);
|
|
388
|
+
scr_str_release(n);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
static ScrError *scr_stream_mkerr(const char *code, const char *msg) {
|
|
392
|
+
ScrStr *m = scr_str_new(msg, strlen(msg));
|
|
393
|
+
ScrError *e = scr_error_new(SCR_ERR_ERROR, m);
|
|
394
|
+
scr_str_release(m);
|
|
395
|
+
scr_error_set_code(e, code);
|
|
396
|
+
return e;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/* ── forward decls ────────────────────────────────────────────────────── */
|
|
400
|
+
|
|
401
|
+
static void scr_stream_flow(ScrStream *s);
|
|
402
|
+
static void scr_stream_emit_readable_now(ScrStream *s);
|
|
403
|
+
static void scr_stream_maybe_read_more(ScrStream *s);
|
|
404
|
+
static void scr_stream_finish_maybe(ScrStream *s);
|
|
405
|
+
static void scr_stream_clear_buffer(ScrStream *s);
|
|
406
|
+
static void scr_stream_do_destroy(ScrStream *s, ScrError *err /*borrowed*/);
|
|
407
|
+
static void scr_stream_notify_finished(ScrStream *s);
|
|
408
|
+
static void *scr_stream_read_n(ScrStream *s, double size);
|
|
409
|
+
static void scr_stream_settle_next(ScrStream *s);
|
|
410
|
+
static ScrStream *scr_stream_alloc(const ScrVt *vt, const char *cls, bool has_r, bool has_w,
|
|
411
|
+
double rhwm, double whwm, bool auto_destroy,
|
|
412
|
+
bool emit_close, bool allow_half_open);
|
|
413
|
+
|
|
414
|
+
/* errorOrDestroy: autoDestroy (the default) destroys with the error;
|
|
415
|
+
* otherwise the error emits on a tick. Borrows err. */
|
|
416
|
+
static void scr_stream_error_or_destroy(ScrStream *s, ScrError *err) {
|
|
417
|
+
ScrStreamState *st = s->st;
|
|
418
|
+
if (st->destroyed) return; /* err stays the caller's borrow */
|
|
419
|
+
if (st->auto_destroy) {
|
|
420
|
+
scr_stream_do_destroy(s, err);
|
|
421
|
+
} else {
|
|
422
|
+
if (!st->errored) st->errored = scr_error_retain(err);
|
|
423
|
+
if (!st->error_scheduled) {
|
|
424
|
+
st->error_scheduled = true;
|
|
425
|
+
scr_st_tick(s, SCR_ST_ERROR, scr_error_retain(err), NULL);
|
|
426
|
+
}
|
|
427
|
+
scr_stream_settle_next(s); /* a parked for-await rejects with the error */
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/* ── readable internals ───────────────────────────────────────────────── */
|
|
432
|
+
|
|
433
|
+
static void scr_stream_rbuf_push(ScrStreamState *st, void *chunk /*moves*/, bool front) {
|
|
434
|
+
if (st->r.n == st->r.cap) {
|
|
435
|
+
st->r.cap = st->r.cap ? st->r.cap * 2 : 4;
|
|
436
|
+
st->r.buf = realloc(st->r.buf, st->r.cap * sizeof *st->r.buf);
|
|
437
|
+
if (!st->r.buf) scr_stream_oom();
|
|
438
|
+
}
|
|
439
|
+
if (front) {
|
|
440
|
+
/* unshift: head_off must be zero (only buf[0] is partially consumed,
|
|
441
|
+
* and unshift with a part-read head keeps the remainder intact by
|
|
442
|
+
* materializing it first — the caller slices). */
|
|
443
|
+
memmove(st->r.buf + 1, st->r.buf, st->r.n * sizeof *st->r.buf);
|
|
444
|
+
st->r.buf[0] = chunk;
|
|
445
|
+
} else {
|
|
446
|
+
st->r.buf[st->r.n] = chunk;
|
|
447
|
+
}
|
|
448
|
+
st->r.n++;
|
|
449
|
+
st->r.length += scr_stream_entry_len(st, chunk);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/* Takes n units (n <= length; bytes, or chars once encoded) off the
|
|
453
|
+
* buffer head as one +1 Buffer / string. */
|
|
454
|
+
static void *scr_stream_rbuf_take(ScrStreamState *st, size_t n) {
|
|
455
|
+
void *first = st->r.n > 0 ? st->r.buf[0] : NULL;
|
|
456
|
+
if (first && st->r.head_off == 0 && scr_stream_entry_len(st, first) == n) {
|
|
457
|
+
/* whole-chunk fast path: hand the buffered chunk out as-is */
|
|
458
|
+
memmove(st->r.buf, st->r.buf + 1, (st->r.n - 1) * sizeof *st->r.buf);
|
|
459
|
+
st->r.n--;
|
|
460
|
+
st->r.length -= n;
|
|
461
|
+
return first;
|
|
462
|
+
}
|
|
463
|
+
if (st->r.encoded) {
|
|
464
|
+
/* string mode: slice by JS string units (Node's fromList over
|
|
465
|
+
* strings), concatenating across entries. */
|
|
466
|
+
ScrStr *out = NULL;
|
|
467
|
+
size_t at = 0;
|
|
468
|
+
while (at < n) {
|
|
469
|
+
ScrStr *head = st->r.buf[0];
|
|
470
|
+
size_t head_len = (size_t)scr_str_utf16_len(head);
|
|
471
|
+
size_t avail = head_len - st->r.head_off;
|
|
472
|
+
size_t take = avail < n - at ? avail : n - at;
|
|
473
|
+
ScrStr *piece = scr_str_slice(head, (double)st->r.head_off, (double)(st->r.head_off + take));
|
|
474
|
+
if (out == NULL) {
|
|
475
|
+
out = piece;
|
|
476
|
+
} else {
|
|
477
|
+
ScrStr *joined = scr_str_concat(out, piece);
|
|
478
|
+
scr_str_release(out);
|
|
479
|
+
scr_str_release(piece);
|
|
480
|
+
out = joined;
|
|
481
|
+
}
|
|
482
|
+
at += take;
|
|
483
|
+
st->r.head_off += take;
|
|
484
|
+
if (st->r.head_off == head_len) {
|
|
485
|
+
scr_str_release(head);
|
|
486
|
+
memmove(st->r.buf, st->r.buf + 1, (st->r.n - 1) * sizeof *st->r.buf);
|
|
487
|
+
st->r.n--;
|
|
488
|
+
st->r.head_off = 0;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
st->r.length -= n;
|
|
492
|
+
return out;
|
|
493
|
+
}
|
|
494
|
+
ScrBytes *out = scr_bytes_new(SCR_BYTES_U8, (double)n);
|
|
495
|
+
size_t at = 0;
|
|
496
|
+
while (at < n) {
|
|
497
|
+
ScrBytes *head = st->r.buf[0];
|
|
498
|
+
size_t avail = head->len - st->r.head_off;
|
|
499
|
+
size_t take = avail < n - at ? avail : n - at;
|
|
500
|
+
memcpy(out->data + at, head->data + st->r.head_off, take);
|
|
501
|
+
at += take;
|
|
502
|
+
st->r.head_off += take;
|
|
503
|
+
if (st->r.head_off == head->len) {
|
|
504
|
+
scr_bytes_release(head);
|
|
505
|
+
memmove(st->r.buf, st->r.buf + 1, (st->r.n - 1) * sizeof *st->r.buf);
|
|
506
|
+
st->r.n--;
|
|
507
|
+
st->r.head_off = 0;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
st->r.length -= n;
|
|
511
|
+
return out;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/* emit('data', chunk) + pipe delivery. Borrows chunk (a Buffer, or a
|
|
515
|
+
* string once encoded). The emit ABI carries BOTH payload slots — the
|
|
516
|
+
* bytes chunk and the string chunk, exactly one non-NULL — and the
|
|
517
|
+
* compiler's stream-data listener thunks dispatch on which (typed
|
|
518
|
+
* listeners unwrap their declared side; dyn listeners box by tag). */
|
|
519
|
+
static void scr_stream_emit_data(ScrStream *s, void *chunk) {
|
|
520
|
+
ScrStreamState *st = s->st;
|
|
521
|
+
ScrBytes *b = st->r.encoded ? NULL : (ScrBytes *)chunk;
|
|
522
|
+
ScrStr *str = st->r.encoded ? (ScrStr *)chunk : NULL;
|
|
523
|
+
ScrStr *n = scr_str_new("data", 4);
|
|
524
|
+
scr_emitter_emit((ScrEmitter *)s, n, b, str);
|
|
525
|
+
scr_str_release(n);
|
|
526
|
+
if (scr_exc_pending()) return;
|
|
527
|
+
/* pipe delivery, with backpressure: a full destination pauses us until
|
|
528
|
+
* its 'drain'. Encoded sources write the string (utf8 bytes at the
|
|
529
|
+
* destination — Node writes the decoded string through). */
|
|
530
|
+
for (size_t i = 0; i < st->pipes.n;) {
|
|
531
|
+
ScrStream *dst = st->pipes.dst[i];
|
|
532
|
+
if (dst->st->destroyed) {
|
|
533
|
+
/* Node's pipe cleanup: a destroyed destination unhooks (the
|
|
534
|
+
* onerror/onclose → unpipe path) and pauses the source — writing
|
|
535
|
+
* after destroy would drop the error silently and spin an infinite
|
|
536
|
+
* synchronous flow over an endless source. */
|
|
537
|
+
scr_stream_release(scr_stream_unpipe(s, dst));
|
|
538
|
+
if (scr_exc_pending()) return;
|
|
539
|
+
continue; /* the pipes array shifted down over i */
|
|
540
|
+
}
|
|
541
|
+
bool ok = b ? scr_stream_write(dst, b, NULL) : scr_stream_write_str(dst, str, NULL);
|
|
542
|
+
if (scr_exc_pending()) return;
|
|
543
|
+
i++;
|
|
544
|
+
if (!ok && !dst->st->destroyed) {
|
|
545
|
+
ScrStreamState *dstt = dst->st;
|
|
546
|
+
/* register once */
|
|
547
|
+
bool already = false;
|
|
548
|
+
for (size_t j = 0; j < dstt->drain_srcs.n; j++) {
|
|
549
|
+
if (dstt->drain_srcs.src[j] == s) already = true;
|
|
550
|
+
}
|
|
551
|
+
if (!already) {
|
|
552
|
+
if (dstt->drain_srcs.n == dstt->drain_srcs.cap) {
|
|
553
|
+
dstt->drain_srcs.cap = dstt->drain_srcs.cap ? dstt->drain_srcs.cap * 2 : 2;
|
|
554
|
+
dstt->drain_srcs.src = realloc(dstt->drain_srcs.src, dstt->drain_srcs.cap * sizeof *dstt->drain_srcs.src);
|
|
555
|
+
if (!dstt->drain_srcs.src) scr_stream_oom();
|
|
556
|
+
}
|
|
557
|
+
dstt->drain_srcs.src[dstt->drain_srcs.n++] = scr_stream_retain(s);
|
|
558
|
+
st->await_drain++;
|
|
559
|
+
st->r.flowing = 0;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
static void scr_stream_emit_readable_nt(ScrStream *s) {
|
|
566
|
+
ScrStreamState *st = s->st;
|
|
567
|
+
st->r.need_readable = false; /* Node's emitReadable clears the ask */
|
|
568
|
+
if (!st->r.emitted_readable) {
|
|
569
|
+
st->r.emitted_readable = true;
|
|
570
|
+
scr_st_tick(s, SCR_ST_READABLE, NULL, NULL);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
static void scr_stream_end_readable(ScrStream *s) {
|
|
575
|
+
ScrStreamState *st = s->st;
|
|
576
|
+
if (st->r.end_emitted || st->r.end_scheduled) return;
|
|
577
|
+
st->r.end_scheduled = true;
|
|
578
|
+
scr_st_tick(s, SCR_ST_END, NULL, NULL);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/* The user _read call (reading guard + sync flag). Absent read cb means
|
|
582
|
+
* ERR_METHOD_NOT_IMPLEMENTED, Node's contract. */
|
|
583
|
+
static void scr_stream_call_read(ScrStream *s) {
|
|
584
|
+
ScrStreamState *st = s->st;
|
|
585
|
+
if (st->r.read_cb == NULL && st->r.object_entries) {
|
|
586
|
+
/* Readable.from: fully seeded at construction — _read is a no-op */
|
|
587
|
+
st->r.in_read_sync = false;
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
if (st->r.read_cb == NULL && !st->is_transform && !st->passthrough) {
|
|
591
|
+
ScrError *e = scr_stream_mkerr("ERR_METHOD_NOT_IMPLEMENTED", "The _read() method is not implemented");
|
|
592
|
+
scr_stream_error_or_destroy(s, e);
|
|
593
|
+
scr_error_release(e);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
if (st->r.read_cb == NULL) {
|
|
597
|
+
/* transform reads are push-driven; the attempt still clears the
|
|
598
|
+
* initial sync flag (Node's Transform._read ran) */
|
|
599
|
+
st->r.in_read_sync = false;
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
st->r.reading = true;
|
|
603
|
+
st->r.in_read_sync = true;
|
|
604
|
+
st->r.read_inv(st->r.read_cb, s, (double)st->r.hwm);
|
|
605
|
+
st->r.in_read_sync = false;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/* readableAddChunk. Borrows chunk (retains its own ref). Returns the
|
|
609
|
+
* below-hwm answer. Encoded streams decode the bytes through the
|
|
610
|
+
* StringDecoder first (Node decodes at push); an empty decode (a partial
|
|
611
|
+
* multi-byte tail held pending) adds nothing but still clears the
|
|
612
|
+
* reading flag. */
|
|
613
|
+
static bool scr_stream_add_chunk(ScrStream *s, ScrBytes *chunk, bool front) {
|
|
614
|
+
ScrStreamState *st = s->st;
|
|
615
|
+
if (!st->has_r) {
|
|
616
|
+
ScrError *e = scr_stream_mkerr("ERR_STREAM_PUSH_AFTER_EOF", "push on a Writable (no readable side)");
|
|
617
|
+
scr_stream_error_or_destroy(s, e);
|
|
618
|
+
scr_error_release(e);
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
if (st->r.ended && !front) {
|
|
622
|
+
ScrError *e = scr_stream_mkerr("ERR_STREAM_PUSH_AFTER_EOF", "stream.push() after EOF");
|
|
623
|
+
scr_stream_error_or_destroy(s, e);
|
|
624
|
+
scr_error_release(e);
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
if (st->destroyed) return false;
|
|
628
|
+
if (!front) st->r.reading = false;
|
|
629
|
+
void *entry;
|
|
630
|
+
size_t entry_len;
|
|
631
|
+
if (st->r.encoded) {
|
|
632
|
+
ScrStr *dec = scr_strdec_write(st->r.enc, st->r.dec_pending, chunk);
|
|
633
|
+
st->r.dec_pending = scr_strdec_next(st->r.enc, st->r.dec_pending, chunk);
|
|
634
|
+
entry_len = (size_t)scr_str_utf16_len(dec);
|
|
635
|
+
if (entry_len == 0) {
|
|
636
|
+
scr_str_release(dec);
|
|
637
|
+
if (!front) scr_stream_maybe_read_more(s);
|
|
638
|
+
return !st->r.ended && (st->r.length < st->r.hwm || st->r.length == 0);
|
|
639
|
+
}
|
|
640
|
+
entry = dec; /* owned */
|
|
641
|
+
} else {
|
|
642
|
+
entry = scr_bytes_retain(chunk);
|
|
643
|
+
entry_len = chunk->len;
|
|
644
|
+
}
|
|
645
|
+
if (entry_len > 0 &&
|
|
646
|
+
st->r.flowing == 1 && st->r.length == 0 && !st->r.in_read_sync && !front &&
|
|
647
|
+
(scr_emitter_has((ScrEmitter *)s, "data") || st->pipes.n > 0)) {
|
|
648
|
+
/* the direct-emit fast path: flowing, empty buffer, async push */
|
|
649
|
+
if (st->r.emitted_readable) st->r.emitted_readable = false;
|
|
650
|
+
scr_stream_emit_data(s, entry);
|
|
651
|
+
scr_stream_entry_release(st, entry);
|
|
652
|
+
if (scr_exc_pending()) return false;
|
|
653
|
+
} else {
|
|
654
|
+
scr_stream_rbuf_push(st, entry, front);
|
|
655
|
+
if (st->r.need_readable) scr_stream_emit_readable_nt(s);
|
|
656
|
+
}
|
|
657
|
+
if (!front) scr_stream_maybe_read_more(s);
|
|
658
|
+
scr_stream_settle_next(s); /* a parked for-await consumes buffered content */
|
|
659
|
+
return !st->r.ended && (st->r.length < st->r.hwm || st->r.length == 0);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
static void scr_stream_maybe_read_more(ScrStream *s) {
|
|
663
|
+
ScrStreamState *st = s->st;
|
|
664
|
+
if (st->r.maybe_more_scheduled || st->r.reading || st->r.ended || st->destroyed) return;
|
|
665
|
+
if (!(st->r.length < st->r.hwm && (st->r.flowing == 1 || st->r.need_readable))) return;
|
|
666
|
+
st->r.maybe_more_scheduled = true;
|
|
667
|
+
scr_st_tick(s, SCR_ST_MAYBE_MORE, NULL, NULL);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/* howMuchToRead + fromList + the doRead discipline: readable.read().
|
|
671
|
+
* Answers a +1 Buffer — or a +1 string once encoded (lengths and the
|
|
672
|
+
* size argument then count JS string units, Node's encoded accounting). */
|
|
673
|
+
static void *scr_stream_read_n(ScrStream *s, double size) {
|
|
674
|
+
ScrStreamState *st = s->st;
|
|
675
|
+
bool absent = size < 0;
|
|
676
|
+
size_t n = 0;
|
|
677
|
+
if (!absent) {
|
|
678
|
+
n = (size_t)size;
|
|
679
|
+
if (n > st->r.hwm) {
|
|
680
|
+
/* Node grows hwm to the next power of two above n */
|
|
681
|
+
size_t h = st->r.hwm;
|
|
682
|
+
while (h < n) h *= 2;
|
|
683
|
+
st->r.hwm = h;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (!absent && n != 0) st->r.emitted_readable = false;
|
|
687
|
+
if (!absent && n == 0 && st->r.need_readable &&
|
|
688
|
+
(st->r.length >= st->r.hwm || st->r.ended)) {
|
|
689
|
+
if (st->r.length == 0 && st->r.ended) scr_stream_end_readable(s);
|
|
690
|
+
else scr_stream_emit_readable_nt(s);
|
|
691
|
+
return NULL;
|
|
692
|
+
}
|
|
693
|
+
/* howMuchToRead */
|
|
694
|
+
size_t want;
|
|
695
|
+
if (st->r.object_entries) {
|
|
696
|
+
/* objectMode-style: one whole entry per read, whatever n says */
|
|
697
|
+
want = st->r.length > 0 ? 1 : 0;
|
|
698
|
+
} else if (absent) {
|
|
699
|
+
want = st->r.flowing == 1 && st->r.n > 0
|
|
700
|
+
? scr_stream_entry_len(st, st->r.buf[0]) - st->r.head_off
|
|
701
|
+
: st->r.length;
|
|
702
|
+
} else {
|
|
703
|
+
want = n <= st->r.length ? n : (st->r.ended ? st->r.length : 0);
|
|
704
|
+
}
|
|
705
|
+
if (want == 0 && st->r.ended) {
|
|
706
|
+
if (st->r.length == 0) scr_stream_end_readable(s);
|
|
707
|
+
return NULL;
|
|
708
|
+
}
|
|
709
|
+
bool do_read = st->r.need_readable || st->r.length == 0 ||
|
|
710
|
+
st->r.length - want < st->r.hwm;
|
|
711
|
+
if (st->r.ended || st->r.reading || st->destroyed || st->errored) do_read = false;
|
|
712
|
+
if (do_read) {
|
|
713
|
+
if (st->r.length == 0) st->r.need_readable = true;
|
|
714
|
+
scr_stream_call_read(s);
|
|
715
|
+
if (scr_exc_pending()) return NULL;
|
|
716
|
+
/* a synchronous push may have refilled */
|
|
717
|
+
if (!st->r.reading) {
|
|
718
|
+
if (st->r.object_entries) {
|
|
719
|
+
want = st->r.length > 0 ? 1 : 0;
|
|
720
|
+
} else if (absent) {
|
|
721
|
+
want = st->r.flowing == 1 && st->r.n > 0
|
|
722
|
+
? scr_stream_entry_len(st, st->r.buf[0]) - st->r.head_off
|
|
723
|
+
: st->r.length;
|
|
724
|
+
} else {
|
|
725
|
+
want = n <= st->r.length ? n : (st->r.ended ? st->r.length : 0);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
void *ret = want > 0 && want <= st->r.length ? scr_stream_rbuf_take(st, want) : NULL;
|
|
730
|
+
if (ret == NULL) {
|
|
731
|
+
st->r.need_readable = st->r.length <= st->r.hwm;
|
|
732
|
+
} else if (st->r.flowing == 1 && st->r.length == 0) {
|
|
733
|
+
st->r.need_readable = true;
|
|
734
|
+
}
|
|
735
|
+
if (st->r.length == 0) {
|
|
736
|
+
if (!st->r.ended) st->r.need_readable = true;
|
|
737
|
+
if (st->r.ended && !absent && n != want) scr_stream_end_readable(s);
|
|
738
|
+
if (st->r.ended && absent) scr_stream_end_readable(s);
|
|
739
|
+
}
|
|
740
|
+
if (ret != NULL && !st->error_emitted && !st->close_emitted) {
|
|
741
|
+
scr_stream_emit_data(s, ret);
|
|
742
|
+
if (scr_exc_pending()) {
|
|
743
|
+
scr_stream_entry_release(st, ret);
|
|
744
|
+
return NULL;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return ret;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
static void scr_stream_flow(ScrStream *s) {
|
|
751
|
+
ScrStreamState *st = s->st;
|
|
752
|
+
while (st->r.flowing == 1 && !scr_exc_pending()) {
|
|
753
|
+
void *b = scr_stream_read_n(s, -1);
|
|
754
|
+
if (b == NULL) break;
|
|
755
|
+
scr_stream_entry_release(st, b);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
static void scr_stream_resume_kick(ScrStream *s) {
|
|
760
|
+
ScrStreamState *st = s->st;
|
|
761
|
+
if (st->r.resume_scheduled) return;
|
|
762
|
+
st->r.resume_scheduled = true;
|
|
763
|
+
scr_st_tick(s, SCR_ST_RESUME, NULL, NULL);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/* ── the dyn-flavored completion callbacks (the JS lane) ──────────────── */
|
|
767
|
+
|
|
768
|
+
/* The JS lane's option callbacks and underscore methods declare their
|
|
769
|
+
* parameters implicitly-any: the compiler-emitted invoke thunks box the
|
|
770
|
+
* chunk/encoding/error into dyn, and the COMPLETION callback arrives as
|
|
771
|
+
* a callable dyn function whose glue lands here — arguments arrive as
|
|
772
|
+
* borrowed DOM values: the error is null/undefined (none), an
|
|
773
|
+
* error-shaped object ({message}), or a string; transform/flush data is
|
|
774
|
+
* bytes, a string, or absent. clo->caps[0] boxes the retained stream. */
|
|
775
|
+
|
|
776
|
+
static ScrError *scr_stream_dyn_err(ScrDyn *const *args, size_t argc) {
|
|
777
|
+
if (argc < 1) return NULL;
|
|
778
|
+
const ScrDyn *a = args[0];
|
|
779
|
+
if (a->kind == SCR_DYN_UNDEF || a->kind == SCR_DYN_NULL) return NULL;
|
|
780
|
+
if (a->kind == SCR_DYN_STR) {
|
|
781
|
+
ScrError *e = scr_error_new(SCR_ERR_ERROR, a->v.str);
|
|
782
|
+
return e;
|
|
783
|
+
}
|
|
784
|
+
if (a->kind == SCR_DYN_OBJ) {
|
|
785
|
+
ScrDyn *m = scr_dyn_obj_get((ScrDyn *)a, "message", 7);
|
|
786
|
+
ScrStr *msg = m && m->kind == SCR_DYN_STR ? scr_str_retain(m->v.str) : scr_str_new("", 0);
|
|
787
|
+
ScrError *e = scr_error_new(SCR_ERR_ERROR, msg);
|
|
788
|
+
scr_str_release(msg);
|
|
789
|
+
return e;
|
|
790
|
+
}
|
|
791
|
+
/* any other truthy value: Node would wrap it — approximate with "" */
|
|
792
|
+
ScrStr *empty = scr_str_new("", 0);
|
|
793
|
+
ScrError *e = scr_error_new(SCR_ERR_ERROR, empty);
|
|
794
|
+
scr_str_release(empty);
|
|
795
|
+
return e;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
static ScrStream *scr_stream_dyn_recv(ScrClosure *clo) {
|
|
799
|
+
return scr_box_get_ref(clo->caps[0]);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
static ScrDyn *scr_stream_done_dyn_ret(void) { return scr_dyn_retain(scr_dyn_undefined()); }
|
|
803
|
+
|
|
804
|
+
ScrDyn *scr_stream_done_dyn_w(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
|
|
805
|
+
ScrStream *s = scr_stream_dyn_recv(clo);
|
|
806
|
+
scr_stream_write_done(s, scr_stream_dyn_err(args, argc));
|
|
807
|
+
scr_stream_release(s);
|
|
808
|
+
return scr_stream_done_dyn_ret();
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
ScrDyn *scr_stream_done_dyn_f(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
|
|
812
|
+
ScrStream *s = scr_stream_dyn_recv(clo);
|
|
813
|
+
scr_stream_final_done(s, scr_stream_dyn_err(args, argc));
|
|
814
|
+
scr_stream_release(s);
|
|
815
|
+
return scr_stream_done_dyn_ret();
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
ScrDyn *scr_stream_done_dyn_d(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
|
|
819
|
+
ScrStream *s = scr_stream_dyn_recv(clo);
|
|
820
|
+
scr_stream_destroy_done(s, scr_stream_dyn_err(args, argc));
|
|
821
|
+
scr_stream_release(s);
|
|
822
|
+
return scr_stream_done_dyn_ret();
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
static void scr_stream_dyn_data(ScrDyn *const *args, size_t argc,
|
|
826
|
+
ScrBytes **data, ScrStr **data_str) {
|
|
827
|
+
*data = NULL;
|
|
828
|
+
*data_str = NULL;
|
|
829
|
+
if (argc < 2) return;
|
|
830
|
+
const ScrDyn *a = args[1];
|
|
831
|
+
if (a->kind == SCR_DYN_BYTES) *data = scr_dyn_bytes_copy_out(a);
|
|
832
|
+
else if (a->kind == SCR_DYN_STR) *data_str = scr_str_retain(a->v.str);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
ScrDyn *scr_stream_done_dyn_t(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
|
|
836
|
+
ScrStream *s = scr_stream_dyn_recv(clo);
|
|
837
|
+
ScrBytes *data;
|
|
838
|
+
ScrStr *data_str;
|
|
839
|
+
scr_stream_dyn_data(args, argc, &data, &data_str);
|
|
840
|
+
scr_stream_transform_done(s, scr_stream_dyn_err(args, argc), data, data_str);
|
|
841
|
+
scr_stream_release(s);
|
|
842
|
+
return scr_stream_done_dyn_ret();
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
ScrDyn *scr_stream_done_dyn_l(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
|
|
846
|
+
ScrStream *s = scr_stream_dyn_recv(clo);
|
|
847
|
+
ScrBytes *data;
|
|
848
|
+
ScrStr *data_str;
|
|
849
|
+
scr_stream_dyn_data(args, argc, &data, &data_str);
|
|
850
|
+
scr_stream_flush_done(s, scr_stream_dyn_err(args, argc), data, data_str);
|
|
851
|
+
scr_stream_release(s);
|
|
852
|
+
return scr_stream_done_dyn_ret();
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/* Checked-dynamic chunks (the JS lane's push/write of any-typed values):
|
|
856
|
+
* dispatch by DOM tag — bytes copy out, strings convert utf8, null takes
|
|
857
|
+
* the null path (EOF for push; ERR_STREAM_NULL_VALUES for write), and
|
|
858
|
+
* anything else throws the write TypeError (Node would throw
|
|
859
|
+
* ERR_INVALID_ARG_TYPE — approximated by the same shape). Borrow d. */
|
|
860
|
+
bool scr_stream_push_dyn(ScrStream *s, const ScrDyn *d) {
|
|
861
|
+
switch (d->kind) {
|
|
862
|
+
case SCR_DYN_BYTES: {
|
|
863
|
+
ScrBytes *b = scr_dyn_bytes_copy_out(d);
|
|
864
|
+
bool r = scr_stream_push(s, b);
|
|
865
|
+
scr_bytes_release(b);
|
|
866
|
+
return r;
|
|
867
|
+
}
|
|
868
|
+
case SCR_DYN_STR:
|
|
869
|
+
return scr_stream_push_str(s, d->v.str);
|
|
870
|
+
case SCR_DYN_NULL:
|
|
871
|
+
return scr_stream_push_null(s);
|
|
872
|
+
default: {
|
|
873
|
+
static const char msg[] = "May not write null values to stream";
|
|
874
|
+
scr_throw_error_msg_code(SCR_ERR_TYPE, msg, sizeof msg - 1, "ERR_STREAM_NULL_VALUES");
|
|
875
|
+
return false;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
bool scr_stream_write_dyn(ScrStream *s, const ScrDyn *d, ScrClosure *cb) {
|
|
881
|
+
switch (d->kind) {
|
|
882
|
+
case SCR_DYN_BYTES: {
|
|
883
|
+
ScrBytes *b = scr_dyn_bytes_copy_out(d);
|
|
884
|
+
bool r = scr_stream_write(s, b, cb);
|
|
885
|
+
scr_bytes_release(b);
|
|
886
|
+
return r;
|
|
887
|
+
}
|
|
888
|
+
case SCR_DYN_STR:
|
|
889
|
+
return scr_stream_write_str(s, d->v.str, cb);
|
|
890
|
+
default:
|
|
891
|
+
if (cb) scr_closure_release(cb);
|
|
892
|
+
return scr_stream_write_null(s);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/* ── the for-await surface (readable.nextChunk) ───────────────────────── */
|
|
897
|
+
|
|
898
|
+
/* Resolve the parked waiter with a taken chunk (moves), the EOF
|
|
899
|
+
* sentinel, or dyn-boxed content per next_dyn. */
|
|
900
|
+
static void scr_stream_next_fulfill(ScrStreamState *st, ScrPromise *w /*moves*/, void *chunk /*moves or NULL=EOF*/) {
|
|
901
|
+
if (st->r.next_dyn) {
|
|
902
|
+
ScrDyn *d;
|
|
903
|
+
if (chunk == NULL) {
|
|
904
|
+
d = scr_dyn_retain(scr_dyn_undefined()); /* the EOF sentinel */
|
|
905
|
+
} else if (st->r.encoded) {
|
|
906
|
+
d = scr_dyn_new_str((ScrStr *)chunk);
|
|
907
|
+
scr_str_release((ScrStr *)chunk);
|
|
908
|
+
} else {
|
|
909
|
+
d = scr_dyn_new_buffer_copy((ScrBytes *)chunk);
|
|
910
|
+
scr_bytes_release((ScrBytes *)chunk);
|
|
911
|
+
}
|
|
912
|
+
scr_promise_fulfill_ref(w, d, scr_dyn_retain_v, scr_dyn_release_v, NULL);
|
|
913
|
+
} else {
|
|
914
|
+
ScrBytes *b = chunk ? (ScrBytes *)chunk : scr_bytes_new(SCR_BYTES_U8, 0);
|
|
915
|
+
scr_promise_fulfill_ref(w, b, scr_bytes_retain_v, scr_bytes_release_v, NULL);
|
|
916
|
+
}
|
|
917
|
+
scr_promise_release(w);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/* Settle the parked waiter if the stream's state can answer now: an
|
|
921
|
+
* error rejects, buffered content fulfills (one whole entry in from
|
|
922
|
+
* mode, the whole buffer otherwise — Node's iterator read()), EOF
|
|
923
|
+
* fulfills the sentinel. Called from push/eof/destroy transitions. */
|
|
924
|
+
static void scr_stream_settle_next(ScrStream *s) {
|
|
925
|
+
ScrStreamState *st = s->st;
|
|
926
|
+
if (!st->r.next_waiter) return;
|
|
927
|
+
if (st->errored) {
|
|
928
|
+
ScrPromise *w = st->r.next_waiter;
|
|
929
|
+
st->r.next_waiter = NULL;
|
|
930
|
+
st->next_err_consumed = true;
|
|
931
|
+
scr_throw_obj(scr_error_retain(st->errored), &scr_error_retain_v, &scr_error_release_v,
|
|
932
|
+
scr_error_trace_arg());
|
|
933
|
+
scr_promise_reject_pending(w);
|
|
934
|
+
scr_promise_release(w);
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
if (st->r.length > 0) {
|
|
938
|
+
ScrPromise *w = st->r.next_waiter;
|
|
939
|
+
st->r.next_waiter = NULL;
|
|
940
|
+
void *chunk = scr_stream_rbuf_take(st, st->r.object_entries ? 1 : st->r.length);
|
|
941
|
+
scr_stream_next_fulfill(st, w, chunk);
|
|
942
|
+
if (st->r.ended && st->r.length == 0) scr_stream_end_readable(s);
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
if (st->r.ended || st->destroyed) {
|
|
946
|
+
if (st->r.ended && !st->r.end_emitted) {
|
|
947
|
+
/* Node's iterator completes AFTER 'end' (and its autoDestroy):
|
|
948
|
+
* queue the sentinel BEHIND the pending 'end' tick, so code after
|
|
949
|
+
* the loop reads readableEnded/destroyed true. */
|
|
950
|
+
scr_stream_end_readable(s);
|
|
951
|
+
scr_st_tick(s, SCR_ST_NEXT_EOF, NULL, NULL);
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
ScrPromise *w = st->r.next_waiter;
|
|
955
|
+
st->r.next_waiter = NULL;
|
|
956
|
+
scr_stream_next_fulfill(st, w, NULL);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
static ScrPromise *scr_stream_next_chunk_impl(ScrStream *s, bool dyn) {
|
|
961
|
+
ScrStreamState *st = s->st;
|
|
962
|
+
ScrPromise *p = scr_promise_new();
|
|
963
|
+
if (!st->has_r) {
|
|
964
|
+
st->r.next_dyn = dyn;
|
|
965
|
+
scr_stream_next_fulfill(st, scr_promise_retain(p), NULL);
|
|
966
|
+
return p;
|
|
967
|
+
}
|
|
968
|
+
if (st->r.next_waiter) {
|
|
969
|
+
/* the desugared loop awaits each chunk before asking again */
|
|
970
|
+
static const char msg[] = "concurrent for-await iteration of one stream is not supported yet";
|
|
971
|
+
scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
|
|
972
|
+
scr_promise_reject_pending(p);
|
|
973
|
+
return p;
|
|
974
|
+
}
|
|
975
|
+
st->r.next_dyn = dyn;
|
|
976
|
+
st->r.next_waiter = scr_promise_retain(p);
|
|
977
|
+
/* answer from the current state, or kick a _read and park */
|
|
978
|
+
scr_stream_settle_next(s);
|
|
979
|
+
if (st->r.next_waiter && !st->r.reading && !st->r.ended && !st->destroyed) {
|
|
980
|
+
st->r.need_readable = true;
|
|
981
|
+
scr_stream_call_read(s);
|
|
982
|
+
if (scr_exc_pending()) {
|
|
983
|
+
ScrPromise *w = st->r.next_waiter;
|
|
984
|
+
st->r.next_waiter = NULL;
|
|
985
|
+
scr_promise_reject_pending(w);
|
|
986
|
+
scr_promise_release(w);
|
|
987
|
+
return p;
|
|
988
|
+
}
|
|
989
|
+
/* a synchronous push may have answered already */
|
|
990
|
+
scr_stream_settle_next(s);
|
|
991
|
+
}
|
|
992
|
+
return p;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
ScrPromise *scr_stream_next_chunk(ScrStream *s) {
|
|
996
|
+
ScrStreamState *st = s->st;
|
|
997
|
+
if (st->has_r && st->r.encoded) {
|
|
998
|
+
/* the typed lane's chunk is a Buffer; string chunks would confuse */
|
|
999
|
+
ScrPromise *p = scr_promise_new();
|
|
1000
|
+
static const char msg[] =
|
|
1001
|
+
"for-await over a stream with an encoding set is not supported yet in "
|
|
1002
|
+
"typed code (chunks are Buffers here)";
|
|
1003
|
+
scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
|
|
1004
|
+
scr_promise_reject_pending(p);
|
|
1005
|
+
return p;
|
|
1006
|
+
}
|
|
1007
|
+
return scr_stream_next_chunk_impl(s, false);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
ScrPromise *scr_stream_next_chunk_dyn(ScrStream *s) {
|
|
1011
|
+
return scr_stream_next_chunk_impl(s, true);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
/* ── Readable.from (array-seeded object-entry streams) ────────────────── */
|
|
1015
|
+
|
|
1016
|
+
/* +1 stream fully seeded from the array's elements (strings or Buffers —
|
|
1017
|
+
* one WHOLE entry per element, Node's objectMode delivery; hwm 1) and
|
|
1018
|
+
* already EOF'd. Borrows arr. */
|
|
1019
|
+
ScrStream *scr_stream_from_arr(ScrArr *arr, bool strings) {
|
|
1020
|
+
ScrStream *s = scr_stream_alloc(&scr_readable_vt, "Readable", true, false,
|
|
1021
|
+
1, -1, true, true, true);
|
|
1022
|
+
ScrStreamState *st = s->st;
|
|
1023
|
+
st->r.object_entries = true;
|
|
1024
|
+
st->r.encoded = strings;
|
|
1025
|
+
if (strings) st->r.enc = scr_str_new("utf8", 4);
|
|
1026
|
+
for (size_t i = 0; i < arr->len; i++) {
|
|
1027
|
+
scr_stream_rbuf_push(st, scr_arr_get_ref(arr, (double)i), false);
|
|
1028
|
+
}
|
|
1029
|
+
scr_stream_push_null(s);
|
|
1030
|
+
return s;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/* ── the registration hook ('data'/'readable' listeners kick flow) ────── */
|
|
1034
|
+
|
|
1035
|
+
static void scr_stream_on_listener(ScrEmitter *em, ScrStr *name) {
|
|
1036
|
+
if (!scr_stream_is(em)) return;
|
|
1037
|
+
ScrStream *s = (ScrStream *)em;
|
|
1038
|
+
ScrStreamState *st = s->st;
|
|
1039
|
+
if (!st || !st->has_r) return;
|
|
1040
|
+
if (name->len == 4 && memcmp(name->data, "data", 4) == 0) {
|
|
1041
|
+
st->r.readable_listening = scr_emitter_has(em, "readable");
|
|
1042
|
+
if (st->r.flowing != 0) {
|
|
1043
|
+
st->r.flowing = 1;
|
|
1044
|
+
scr_stream_resume_kick(s);
|
|
1045
|
+
}
|
|
1046
|
+
} else if (name->len == 8 && memcmp(name->data, "readable", 8) == 0) {
|
|
1047
|
+
if (!st->r.end_emitted && !st->r.readable_listening) {
|
|
1048
|
+
st->r.readable_listening = true;
|
|
1049
|
+
st->r.need_readable = true;
|
|
1050
|
+
st->r.flowing = 0;
|
|
1051
|
+
st->r.emitted_readable = false;
|
|
1052
|
+
if (st->r.length > 0) {
|
|
1053
|
+
scr_stream_emit_readable_nt(s);
|
|
1054
|
+
} else if (!st->r.reading) {
|
|
1055
|
+
/* nReadingNextTick: a read(0) kick on a tick */
|
|
1056
|
+
scr_st_tick(s, SCR_ST_MAYBE_MORE, NULL, NULL);
|
|
1057
|
+
st->r.maybe_more_scheduled = true;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/* ── writable internals ───────────────────────────────────────────────── */
|
|
1064
|
+
|
|
1065
|
+
static void scr_stream_wq_push(ScrStreamState *st, ScrBytes *chunk /*moves*/, ScrClosure *cb /*moves*/) {
|
|
1066
|
+
if (st->w.n == st->w.cap) {
|
|
1067
|
+
st->w.cap = st->w.cap ? st->w.cap * 2 : 4;
|
|
1068
|
+
st->w.q = realloc(st->w.q, st->w.cap * sizeof *st->w.q);
|
|
1069
|
+
if (!st->w.q) scr_stream_oom();
|
|
1070
|
+
}
|
|
1071
|
+
st->w.q[st->w.n].chunk = chunk;
|
|
1072
|
+
st->w.q[st->w.n].cb = cb;
|
|
1073
|
+
st->w.n++;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
static void scr_stream_do_write(ScrStream *s, ScrBytes *chunk /*moves*/, ScrClosure *cb /*moves*/) {
|
|
1077
|
+
ScrStreamState *st = s->st;
|
|
1078
|
+
st->w.writing = true;
|
|
1079
|
+
st->w.wsync = true;
|
|
1080
|
+
st->w.inflight_len = chunk->len;
|
|
1081
|
+
st->w.inflight_cb = cb;
|
|
1082
|
+
if (st->passthrough && st->transform_cb == NULL) {
|
|
1083
|
+
/* identity: push through, complete synchronously */
|
|
1084
|
+
scr_stream_add_chunk(s, chunk, false);
|
|
1085
|
+
scr_bytes_release(chunk);
|
|
1086
|
+
if (!scr_exc_pending()) scr_stream_write_done(s, NULL);
|
|
1087
|
+
st->w.wsync = false;
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
if (st->is_transform || st->passthrough) {
|
|
1091
|
+
if (st->transform_cb == NULL) {
|
|
1092
|
+
scr_bytes_release(chunk);
|
|
1093
|
+
ScrError *e = scr_stream_mkerr("ERR_METHOD_NOT_IMPLEMENTED", "The _transform() method is not implemented");
|
|
1094
|
+
st->w.wsync = false;
|
|
1095
|
+
scr_stream_error_or_destroy(s, e);
|
|
1096
|
+
scr_error_release(e);
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
st->transform_inv(st->transform_cb, s, chunk);
|
|
1100
|
+
scr_bytes_release(chunk);
|
|
1101
|
+
st->w.wsync = false;
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
if (st->w.write_cb == NULL) {
|
|
1105
|
+
scr_bytes_release(chunk);
|
|
1106
|
+
ScrError *e = scr_stream_mkerr("ERR_METHOD_NOT_IMPLEMENTED", "The _write() method is not implemented");
|
|
1107
|
+
st->w.wsync = false;
|
|
1108
|
+
scr_stream_error_or_destroy(s, e);
|
|
1109
|
+
scr_error_release(e);
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
st->w.write_inv(st->w.write_cb, s, chunk);
|
|
1113
|
+
scr_bytes_release(chunk);
|
|
1114
|
+
st->w.wsync = false;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
/* afterWrite — Node's exact order: 'drain' first (when the queue is
|
|
1118
|
+
* empty), THEN the user's write(chunk, cb) callback, then finishMaybe.
|
|
1119
|
+
* The buffered queue was already restarted by write_done (Node's onwrite
|
|
1120
|
+
* calls clearBuffer before afterWrite). cb moves. */
|
|
1121
|
+
static void scr_stream_after_write(ScrStream *s, ScrClosure *cb) {
|
|
1122
|
+
ScrStreamState *st = s->st;
|
|
1123
|
+
if (st->w.length == 0 && st->w.need_drain && !st->w.ending && !st->destroyed) {
|
|
1124
|
+
st->w.need_drain = false;
|
|
1125
|
+
scr_stream_emit0(s, "drain");
|
|
1126
|
+
if (scr_exc_pending()) {
|
|
1127
|
+
if (cb) scr_closure_release(cb);
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
/* pipe sources waiting on this drain resume now */
|
|
1131
|
+
while (st->drain_srcs.n > 0) {
|
|
1132
|
+
ScrStream *src = st->drain_srcs.src[--st->drain_srcs.n];
|
|
1133
|
+
if (src->st && src->st->await_drain > 0 && --src->st->await_drain == 0 && !src->st->destroyed) {
|
|
1134
|
+
src->st->r.flowing = 1;
|
|
1135
|
+
scr_stream_resume_kick(src);
|
|
1136
|
+
}
|
|
1137
|
+
scr_stream_release(src);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
if (cb) {
|
|
1141
|
+
((void (*)(ScrClosure *))cb->fn)(cb);
|
|
1142
|
+
scr_closure_release(cb);
|
|
1143
|
+
if (scr_exc_pending()) return;
|
|
1144
|
+
}
|
|
1145
|
+
scr_stream_finish_maybe(s);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
static void scr_stream_clear_buffer(ScrStream *s) {
|
|
1149
|
+
ScrStreamState *st = s->st;
|
|
1150
|
+
while (!st->w.writing && st->w.head < st->w.n && st->w.corked == 0 &&
|
|
1151
|
+
!st->destroyed && !scr_exc_pending()) {
|
|
1152
|
+
ScrWEntry e = st->w.q[st->w.head++];
|
|
1153
|
+
if (st->w.head == st->w.n) {
|
|
1154
|
+
st->w.head = 0;
|
|
1155
|
+
st->w.n = 0;
|
|
1156
|
+
}
|
|
1157
|
+
scr_stream_do_write(s, e.chunk, e.cb);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/* prefinish + 'finish' scheduling once the writable side has fully
|
|
1162
|
+
* drained after end(). */
|
|
1163
|
+
static void scr_stream_finish_maybe(ScrStream *s) {
|
|
1164
|
+
ScrStreamState *st = s->st;
|
|
1165
|
+
if (!st->w.ending || st->w.writing || st->w.head < st->w.n || st->w.finished ||
|
|
1166
|
+
st->destroyed || st->w.finish_scheduled) {
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
if (!st->w.prefinished) {
|
|
1170
|
+
if ((st->is_transform || st->passthrough) && !st->flush_called) {
|
|
1171
|
+
st->flush_called = true;
|
|
1172
|
+
if (st->flush_cb) {
|
|
1173
|
+
st->flush_inv(st->flush_cb, s);
|
|
1174
|
+
return; /* flush_done resumes the finish path */
|
|
1175
|
+
}
|
|
1176
|
+
/* no flush: EOF the readable side now */
|
|
1177
|
+
scr_stream_push_null(s);
|
|
1178
|
+
if (scr_exc_pending()) return;
|
|
1179
|
+
}
|
|
1180
|
+
if (st->w.final_cb && !st->w.final_called) {
|
|
1181
|
+
st->w.final_called = true;
|
|
1182
|
+
st->w.final_inv(st->w.final_cb, s);
|
|
1183
|
+
return; /* final_done resumes the finish path */
|
|
1184
|
+
}
|
|
1185
|
+
st->w.prefinished = true;
|
|
1186
|
+
scr_stream_emit0(s, "prefinish");
|
|
1187
|
+
if (scr_exc_pending()) return;
|
|
1188
|
+
}
|
|
1189
|
+
st->w.finish_scheduled = true;
|
|
1190
|
+
scr_st_tick(s, SCR_ST_FINISH, NULL, NULL);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
/* ── construction ─────────────────────────────────────────────────────── */
|
|
1194
|
+
|
|
1195
|
+
static ScrStreamState *scr_stream_state_new(bool has_r, bool has_w,
|
|
1196
|
+
double rhwm, double whwm, bool auto_destroy,
|
|
1197
|
+
bool emit_close, bool allow_half_open) {
|
|
1198
|
+
ScrStreamState *st = calloc(1, sizeof *st);
|
|
1199
|
+
if (!st) scr_stream_oom();
|
|
1200
|
+
st->has_r = has_r;
|
|
1201
|
+
st->has_w = has_w;
|
|
1202
|
+
st->auto_destroy = auto_destroy;
|
|
1203
|
+
st->emit_close = emit_close;
|
|
1204
|
+
st->allow_half_open = allow_half_open;
|
|
1205
|
+
st->r.hwm = rhwm >= 0 ? (size_t)rhwm : SCR_STREAM_DEFAULT_HWM;
|
|
1206
|
+
st->w.hwm = whwm >= 0 ? (size_t)whwm : SCR_STREAM_DEFAULT_HWM;
|
|
1207
|
+
st->r.flowing = -1;
|
|
1208
|
+
/* Node's ReadableState.sync starts TRUE: pushes before the first _read
|
|
1209
|
+
* attempt take the buffered path (never the direct 'data' emit). */
|
|
1210
|
+
st->r.in_read_sync = true;
|
|
1211
|
+
return st;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
static ScrStream *scr_stream_alloc(const ScrVt *vt, const char *cls, bool has_r, bool has_w,
|
|
1215
|
+
double rhwm, double whwm, bool auto_destroy,
|
|
1216
|
+
bool emit_close, bool allow_half_open) {
|
|
1217
|
+
ScrStream *s = scr_cyc_alloc(sizeof *s, &scr_stream_trace, &scr_stream_gcfree);
|
|
1218
|
+
s->rc = 1;
|
|
1219
|
+
s->vt = vt;
|
|
1220
|
+
s->reg = NULL;
|
|
1221
|
+
s->cls = cls;
|
|
1222
|
+
s->st = scr_stream_state_new(has_r, has_w, rhwm, whwm, auto_destroy, emit_close, allow_half_open);
|
|
1223
|
+
scr_obj_alloc_note();
|
|
1224
|
+
return s;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
ScrStream *scr_stream_new_readable(double hwm, bool auto_destroy, bool emit_close,
|
|
1228
|
+
ScrClosure *read, ScrStreamReadInv read_inv,
|
|
1229
|
+
ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
|
|
1230
|
+
ScrStream *s = scr_stream_alloc(&scr_readable_vt, "Readable", true, false,
|
|
1231
|
+
hwm, -1, auto_destroy, emit_close, true);
|
|
1232
|
+
s->st->r.read_cb = read;
|
|
1233
|
+
s->st->r.read_inv = read_inv;
|
|
1234
|
+
s->st->destroy_cb = destroy;
|
|
1235
|
+
s->st->destroy_inv = destroy_inv;
|
|
1236
|
+
return s;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
ScrStream *scr_stream_new_writable(double hwm, bool auto_destroy, bool emit_close,
|
|
1240
|
+
ScrClosure *write, ScrStreamChunkInv write_inv,
|
|
1241
|
+
ScrClosure *final_cb, ScrStreamPlainInv final_inv,
|
|
1242
|
+
ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
|
|
1243
|
+
ScrStream *s = scr_stream_alloc(&scr_writable_vt, "Writable", false, true,
|
|
1244
|
+
-1, hwm, auto_destroy, emit_close, true);
|
|
1245
|
+
s->st->w.write_cb = write;
|
|
1246
|
+
s->st->w.write_inv = write_inv;
|
|
1247
|
+
s->st->w.final_cb = final_cb;
|
|
1248
|
+
s->st->w.final_inv = final_inv;
|
|
1249
|
+
s->st->destroy_cb = destroy;
|
|
1250
|
+
s->st->destroy_inv = destroy_inv;
|
|
1251
|
+
return s;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
ScrStream *scr_stream_new_duplex(double rhwm, double whwm, bool auto_destroy,
|
|
1255
|
+
bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
|
|
1256
|
+
ScrClosure *read, ScrStreamReadInv read_inv,
|
|
1257
|
+
ScrClosure *write, ScrStreamChunkInv write_inv,
|
|
1258
|
+
ScrClosure *final_cb, ScrStreamPlainInv final_inv,
|
|
1259
|
+
ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
|
|
1260
|
+
ScrStream *s = scr_stream_alloc(&scr_duplex_vt, "Duplex", readable_side, writable_side,
|
|
1261
|
+
rhwm, whwm, auto_destroy, emit_close, allow_half_open);
|
|
1262
|
+
if (!readable_side) {
|
|
1263
|
+
/* Node: a { readable: false } duplex reads as an already-ended half */
|
|
1264
|
+
s->st->r.ended = true;
|
|
1265
|
+
s->st->r.end_emitted = true;
|
|
1266
|
+
}
|
|
1267
|
+
if (!writable_side) {
|
|
1268
|
+
s->st->w.ending = true;
|
|
1269
|
+
s->st->w.finished = true;
|
|
1270
|
+
}
|
|
1271
|
+
s->st->r.read_cb = read;
|
|
1272
|
+
s->st->r.read_inv = read_inv;
|
|
1273
|
+
s->st->w.write_cb = write;
|
|
1274
|
+
s->st->w.write_inv = write_inv;
|
|
1275
|
+
s->st->w.final_cb = final_cb;
|
|
1276
|
+
s->st->w.final_inv = final_inv;
|
|
1277
|
+
s->st->destroy_cb = destroy;
|
|
1278
|
+
s->st->destroy_inv = destroy_inv;
|
|
1279
|
+
return s;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
static ScrStream *scr_stream_new_transformish(const ScrVt *vt, const char *cls, bool passthrough,
|
|
1283
|
+
double rhwm, double whwm, bool auto_destroy, bool emit_close, bool allow_half_open,
|
|
1284
|
+
ScrClosure *transform, ScrStreamChunkInv transform_inv,
|
|
1285
|
+
ScrClosure *flush, ScrStreamPlainInv flush_inv,
|
|
1286
|
+
ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
|
|
1287
|
+
ScrStream *s = scr_stream_alloc(vt, cls, true, true, rhwm, whwm,
|
|
1288
|
+
auto_destroy, emit_close, allow_half_open);
|
|
1289
|
+
s->st->is_transform = !passthrough;
|
|
1290
|
+
s->st->passthrough = passthrough;
|
|
1291
|
+
/* Node's Transform constructor: the readable half starts with
|
|
1292
|
+
* sync=false and needReadable=true (transformed data direct-emits). */
|
|
1293
|
+
s->st->r.in_read_sync = false;
|
|
1294
|
+
s->st->r.need_readable = true;
|
|
1295
|
+
s->st->transform_cb = transform;
|
|
1296
|
+
s->st->transform_inv = transform_inv;
|
|
1297
|
+
s->st->flush_cb = flush;
|
|
1298
|
+
s->st->flush_inv = flush_inv;
|
|
1299
|
+
s->st->destroy_cb = destroy;
|
|
1300
|
+
s->st->destroy_inv = destroy_inv;
|
|
1301
|
+
return s;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
ScrStream *scr_stream_new_transform(double rhwm, double whwm, bool auto_destroy,
|
|
1305
|
+
bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
|
|
1306
|
+
ScrClosure *transform, ScrStreamChunkInv transform_inv,
|
|
1307
|
+
ScrClosure *flush, ScrStreamPlainInv flush_inv,
|
|
1308
|
+
ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
|
|
1309
|
+
(void)readable_side;
|
|
1310
|
+
(void)writable_side;
|
|
1311
|
+
return scr_stream_new_transformish(&scr_transform_vt, "Transform", false,
|
|
1312
|
+
rhwm, whwm, auto_destroy, emit_close, allow_half_open,
|
|
1313
|
+
transform, transform_inv, flush, flush_inv, destroy, destroy_inv);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
ScrStream *scr_stream_new_passthrough(double rhwm, double whwm, bool auto_destroy,
|
|
1317
|
+
bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
|
|
1318
|
+
ScrClosure *transform, ScrStreamChunkInv transform_inv,
|
|
1319
|
+
ScrClosure *flush, ScrStreamPlainInv flush_inv,
|
|
1320
|
+
ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
|
|
1321
|
+
(void)readable_side;
|
|
1322
|
+
(void)writable_side;
|
|
1323
|
+
ScrStream *s = scr_stream_new_transformish(&scr_passthrough_vt, "PassThrough",
|
|
1324
|
+
transform == NULL, rhwm, whwm, auto_destroy, emit_close, allow_half_open,
|
|
1325
|
+
transform, transform_inv, flush, flush_inv, destroy, destroy_inv);
|
|
1326
|
+
if (transform != NULL) s->st->is_transform = true;
|
|
1327
|
+
return s;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
/* ── subclass initialization (the emitted super(options) call) ────────── */
|
|
1331
|
+
|
|
1332
|
+
/* A compiler-emitted `extends Readable` subclass allocates its own struct
|
|
1333
|
+
* (vt stamped with the SUBCLASS vtable, display name stamped, registry
|
|
1334
|
+
* and state NULL — the emitter-subclass story plus one slot); super(
|
|
1335
|
+
* options) lands here to build the stream state. The callback closures
|
|
1336
|
+
* move; overridden underscore methods arrive as compiler-synthesized
|
|
1337
|
+
* wrapper closures dispatching through the vt, so a further-derived
|
|
1338
|
+
* override wins even when an inherited constructor runs the init. */
|
|
1339
|
+
|
|
1340
|
+
void scr_stream_init_readable(ScrStream *s, double hwm, bool auto_destroy, bool emit_close,
|
|
1341
|
+
ScrClosure *read, ScrStreamReadInv read_inv,
|
|
1342
|
+
ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
|
|
1343
|
+
ScrStreamState *st = scr_stream_state_new(true, false, hwm, -1, auto_destroy, emit_close, true);
|
|
1344
|
+
s->st = st;
|
|
1345
|
+
st->r.read_cb = read;
|
|
1346
|
+
st->r.read_inv = read_inv;
|
|
1347
|
+
st->destroy_cb = destroy;
|
|
1348
|
+
st->destroy_inv = destroy_inv;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
void scr_stream_init_writable(ScrStream *s, double hwm, bool auto_destroy, bool emit_close,
|
|
1352
|
+
ScrClosure *write, ScrStreamChunkInv write_inv,
|
|
1353
|
+
ScrClosure *final_cb, ScrStreamPlainInv final_inv,
|
|
1354
|
+
ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
|
|
1355
|
+
ScrStreamState *st = scr_stream_state_new(false, true, -1, hwm, auto_destroy, emit_close, true);
|
|
1356
|
+
s->st = st;
|
|
1357
|
+
st->w.write_cb = write;
|
|
1358
|
+
st->w.write_inv = write_inv;
|
|
1359
|
+
st->w.final_cb = final_cb;
|
|
1360
|
+
st->w.final_inv = final_inv;
|
|
1361
|
+
st->destroy_cb = destroy;
|
|
1362
|
+
st->destroy_inv = destroy_inv;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
void scr_stream_init_duplex(ScrStream *s, double rhwm, double whwm, bool auto_destroy,
|
|
1366
|
+
bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
|
|
1367
|
+
ScrClosure *read, ScrStreamReadInv read_inv,
|
|
1368
|
+
ScrClosure *write, ScrStreamChunkInv write_inv,
|
|
1369
|
+
ScrClosure *final_cb, ScrStreamPlainInv final_inv,
|
|
1370
|
+
ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
|
|
1371
|
+
ScrStreamState *st = scr_stream_state_new(readable_side, writable_side, rhwm, whwm,
|
|
1372
|
+
auto_destroy, emit_close, allow_half_open);
|
|
1373
|
+
s->st = st;
|
|
1374
|
+
if (!readable_side) {
|
|
1375
|
+
st->r.ended = true;
|
|
1376
|
+
st->r.end_emitted = true;
|
|
1377
|
+
}
|
|
1378
|
+
if (!writable_side) {
|
|
1379
|
+
st->w.ending = true;
|
|
1380
|
+
st->w.finished = true;
|
|
1381
|
+
}
|
|
1382
|
+
st->r.read_cb = read;
|
|
1383
|
+
st->r.read_inv = read_inv;
|
|
1384
|
+
st->w.write_cb = write;
|
|
1385
|
+
st->w.write_inv = write_inv;
|
|
1386
|
+
st->w.final_cb = final_cb;
|
|
1387
|
+
st->w.final_inv = final_inv;
|
|
1388
|
+
st->destroy_cb = destroy;
|
|
1389
|
+
st->destroy_inv = destroy_inv;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
static void scr_stream_init_transformish(ScrStream *s, bool passthrough,
|
|
1393
|
+
double rhwm, double whwm, bool auto_destroy, bool emit_close, bool allow_half_open,
|
|
1394
|
+
ScrClosure *transform, ScrStreamChunkInv transform_inv,
|
|
1395
|
+
ScrClosure *flush, ScrStreamPlainInv flush_inv,
|
|
1396
|
+
ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
|
|
1397
|
+
ScrStreamState *st = scr_stream_state_new(true, true, rhwm, whwm,
|
|
1398
|
+
auto_destroy, emit_close, allow_half_open);
|
|
1399
|
+
s->st = st;
|
|
1400
|
+
st->is_transform = !passthrough;
|
|
1401
|
+
st->passthrough = passthrough;
|
|
1402
|
+
/* the Transform constructor's readable-half stance (see new_transformish) */
|
|
1403
|
+
st->r.in_read_sync = false;
|
|
1404
|
+
st->r.need_readable = true;
|
|
1405
|
+
st->transform_cb = transform;
|
|
1406
|
+
st->transform_inv = transform_inv;
|
|
1407
|
+
st->flush_cb = flush;
|
|
1408
|
+
st->flush_inv = flush_inv;
|
|
1409
|
+
st->destroy_cb = destroy;
|
|
1410
|
+
st->destroy_inv = destroy_inv;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
void scr_stream_init_transform(ScrStream *s, double rhwm, double whwm, bool auto_destroy,
|
|
1414
|
+
bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
|
|
1415
|
+
ScrClosure *transform, ScrStreamChunkInv transform_inv,
|
|
1416
|
+
ScrClosure *flush, ScrStreamPlainInv flush_inv,
|
|
1417
|
+
ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
|
|
1418
|
+
(void)readable_side;
|
|
1419
|
+
(void)writable_side;
|
|
1420
|
+
scr_stream_init_transformish(s, false, rhwm, whwm, auto_destroy, emit_close, allow_half_open,
|
|
1421
|
+
transform, transform_inv, flush, flush_inv, destroy, destroy_inv);
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
void scr_stream_init_passthrough(ScrStream *s, double rhwm, double whwm, bool auto_destroy,
|
|
1425
|
+
bool emit_close, bool allow_half_open, bool readable_side, bool writable_side,
|
|
1426
|
+
ScrClosure *transform, ScrStreamChunkInv transform_inv,
|
|
1427
|
+
ScrClosure *flush, ScrStreamPlainInv flush_inv,
|
|
1428
|
+
ScrClosure *destroy, ScrStreamErrInv destroy_inv) {
|
|
1429
|
+
(void)readable_side;
|
|
1430
|
+
(void)writable_side;
|
|
1431
|
+
scr_stream_init_transformish(s, transform == NULL, rhwm, whwm, auto_destroy, emit_close,
|
|
1432
|
+
allow_half_open, transform, transform_inv, flush, flush_inv, destroy, destroy_inv);
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
/* ── the underscore-method assignment surface ─────────────────────────
|
|
1436
|
+
* `r._read = fn` / `w._write = fn` (and _final/_destroy/_transform/
|
|
1437
|
+
* _flush) AFTER construction: Node assigns an own property that shadows
|
|
1438
|
+
* the prototype method, and the machinery calls through it from then on.
|
|
1439
|
+
* Here the same slot the option callbacks fill swaps its closure — the
|
|
1440
|
+
* old callback releases, the new one (+1, moves) takes the slot with its
|
|
1441
|
+
* compiler-emitted invoke thunk. In-flight operations already dispatched
|
|
1442
|
+
* through the OLD callback complete against it (they hold no further
|
|
1443
|
+
* reference); the next _read/_write/... dispatch uses the new one, which
|
|
1444
|
+
* is Node's own timing. A NULL state block (assignment before a
|
|
1445
|
+
* subclass's super()) cannot be reached from lowered code — super()
|
|
1446
|
+
* lowers before any statement that could see `this`. */
|
|
1447
|
+
void scr_stream_set_read(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamReadInv inv) {
|
|
1448
|
+
ScrStreamState *st = s->st;
|
|
1449
|
+
if (st->r.read_cb) scr_closure_release(st->r.read_cb);
|
|
1450
|
+
st->r.read_cb = cb;
|
|
1451
|
+
st->r.read_inv = inv;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
void scr_stream_set_write(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamChunkInv inv) {
|
|
1455
|
+
ScrStreamState *st = s->st;
|
|
1456
|
+
if (st->w.write_cb) scr_closure_release(st->w.write_cb);
|
|
1457
|
+
st->w.write_cb = cb;
|
|
1458
|
+
st->w.write_inv = inv;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
void scr_stream_set_final(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamPlainInv inv) {
|
|
1462
|
+
ScrStreamState *st = s->st;
|
|
1463
|
+
if (st->w.final_cb) scr_closure_release(st->w.final_cb);
|
|
1464
|
+
st->w.final_cb = cb;
|
|
1465
|
+
st->w.final_inv = inv;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
void scr_stream_set_destroy(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamErrInv inv) {
|
|
1469
|
+
ScrStreamState *st = s->st;
|
|
1470
|
+
if (st->destroy_cb) scr_closure_release(st->destroy_cb);
|
|
1471
|
+
st->destroy_cb = cb;
|
|
1472
|
+
st->destroy_inv = inv;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
void scr_stream_set_transform(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamChunkInv inv) {
|
|
1476
|
+
ScrStreamState *st = s->st;
|
|
1477
|
+
if (st->transform_cb) scr_closure_release(st->transform_cb);
|
|
1478
|
+
st->transform_cb = cb;
|
|
1479
|
+
st->transform_inv = inv;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
void scr_stream_set_flush(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamPlainInv inv) {
|
|
1483
|
+
ScrStreamState *st = s->st;
|
|
1484
|
+
if (st->flush_cb) scr_closure_release(st->flush_cb);
|
|
1485
|
+
st->flush_cb = cb;
|
|
1486
|
+
st->flush_inv = inv;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
/* ── dyn options (a checked-dynamic options record at construction) ─────
|
|
1490
|
+
*
|
|
1491
|
+
* The JS lane's `constructor(options) { super(options); }` forwarding and
|
|
1492
|
+
* `new Readable(dynVar)`: the record's SHAPE is runtime data, so the
|
|
1493
|
+
* option walk happens here — scalar options read with Node's own rules
|
|
1494
|
+
* (highWaterMark wins over the sided keys; autoDestroy/emitClose/
|
|
1495
|
+
* allowHalfOpen and the Duplex side toggles compare `!== false`; unknown
|
|
1496
|
+
* keys are ignored exactly like Node), and callback slots holding
|
|
1497
|
+
* callable values (Node's `typeof === 'function'` guard — anything else
|
|
1498
|
+
* leaves the slot to the prototype/fallback) bind as closures whose one
|
|
1499
|
+
* cap boxes the dyn callable; the invs below build the DOM arguments the
|
|
1500
|
+
* way the compiler-emitted dyn thunks do (chunk as Buffer-flavored bytes,
|
|
1501
|
+
* encoding 'buffer', the error via the boundary encoding, the completion
|
|
1502
|
+
* callback as a callable dyn over the *_done glue). Options that Node's
|
|
1503
|
+
* base WOULD consume but have no lowering here (objectMode truthy,
|
|
1504
|
+
* decodeStrings:false, non-utf8 defaultEncoding, signal, construct,
|
|
1505
|
+
* writev, and read/write/final on the transformish bases) throw the loud
|
|
1506
|
+
* unsupported Error — the compile-time fence's runtime twin. */
|
|
1507
|
+
|
|
1508
|
+
/* The boxed dyn callable (+1 — scr_box_get_ref retains; callers release
|
|
1509
|
+
* after the call). */
|
|
1510
|
+
static ScrDyn *scr_stream_dynopt_target(ScrClosure *cb) {
|
|
1511
|
+
return scr_box_get_ref(cb->caps[0]);
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
static ScrDyn *scr_stream_dynopt_done(ScrStream *s, ScrDynThunk glue, uint32_t arity, const char *sig) {
|
|
1515
|
+
ScrClosure *c = scr_closure_new((void *)glue, 1);
|
|
1516
|
+
c->caps[0] = scr_box_new_obj(&scr_stream_retain_v, &scr_stream_release_v, &scr_stream_trace);
|
|
1517
|
+
scr_box_set_ref(c->caps[0], scr_stream_retain(s));
|
|
1518
|
+
return scr_dyn_new_func(c, glue, arity, sig, "callback");
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
static void scr_stream_dynopt_read_inv(ScrClosure *cb, ScrStream *s, double size) {
|
|
1522
|
+
(void)s;
|
|
1523
|
+
ScrDyn *fn = scr_stream_dynopt_target(cb);
|
|
1524
|
+
ScrDyn *a = scr_dyn_new_num(size);
|
|
1525
|
+
ScrDyn *r = scr_dyn_call(fn, &a, 1, "read");
|
|
1526
|
+
scr_dyn_release(a);
|
|
1527
|
+
scr_dyn_release(fn);
|
|
1528
|
+
if (r != NULL) scr_dyn_release(r);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
static void scr_stream_dynopt_chunk3(ScrClosure *cb, ScrStream *s, ScrBytes *chunk,
|
|
1532
|
+
ScrDynThunk glue, const char *what) {
|
|
1533
|
+
ScrDyn *args[3];
|
|
1534
|
+
args[0] = scr_dyn_new_buffer_copy(chunk);
|
|
1535
|
+
ScrStr *enc = scr_str_new("buffer", 6);
|
|
1536
|
+
args[1] = scr_dyn_new_str(enc);
|
|
1537
|
+
scr_str_release(enc);
|
|
1538
|
+
args[2] = scr_stream_dynopt_done(s, glue, 1, "(error)");
|
|
1539
|
+
ScrDyn *fn = scr_stream_dynopt_target(cb);
|
|
1540
|
+
ScrDyn *r = scr_dyn_call(fn, args, 3, what);
|
|
1541
|
+
for (int i = 0; i < 3; i++) scr_dyn_release(args[i]);
|
|
1542
|
+
scr_dyn_release(fn);
|
|
1543
|
+
if (r != NULL) scr_dyn_release(r);
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
static void scr_stream_dynopt_write_inv(ScrClosure *cb, ScrStream *s, ScrBytes *chunk) {
|
|
1547
|
+
scr_stream_dynopt_chunk3(cb, s, chunk, &scr_stream_done_dyn_w, "write");
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
static void scr_stream_dynopt_transform_inv(ScrClosure *cb, ScrStream *s, ScrBytes *chunk) {
|
|
1551
|
+
ScrDyn *args[3];
|
|
1552
|
+
args[0] = scr_dyn_new_buffer_copy(chunk);
|
|
1553
|
+
ScrStr *enc = scr_str_new("buffer", 6);
|
|
1554
|
+
args[1] = scr_dyn_new_str(enc);
|
|
1555
|
+
scr_str_release(enc);
|
|
1556
|
+
args[2] = scr_stream_dynopt_done(s, &scr_stream_done_dyn_t, 2, "(error,data)");
|
|
1557
|
+
ScrDyn *fn = scr_stream_dynopt_target(cb);
|
|
1558
|
+
ScrDyn *r = scr_dyn_call(fn, args, 3, "transform");
|
|
1559
|
+
for (int i = 0; i < 3; i++) scr_dyn_release(args[i]);
|
|
1560
|
+
scr_dyn_release(fn);
|
|
1561
|
+
if (r != NULL) scr_dyn_release(r);
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
static void scr_stream_dynopt_final_inv(ScrClosure *cb, ScrStream *s) {
|
|
1565
|
+
ScrDyn *fn = scr_stream_dynopt_target(cb);
|
|
1566
|
+
ScrDyn *a = scr_stream_dynopt_done(s, &scr_stream_done_dyn_f, 1, "(error)");
|
|
1567
|
+
ScrDyn *r = scr_dyn_call(fn, &a, 1, "final");
|
|
1568
|
+
scr_dyn_release(a);
|
|
1569
|
+
scr_dyn_release(fn);
|
|
1570
|
+
if (r != NULL) scr_dyn_release(r);
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
static void scr_stream_dynopt_flush_inv(ScrClosure *cb, ScrStream *s) {
|
|
1574
|
+
ScrDyn *fn = scr_stream_dynopt_target(cb);
|
|
1575
|
+
ScrDyn *a = scr_stream_dynopt_done(s, &scr_stream_done_dyn_l, 2, "(error,data)");
|
|
1576
|
+
ScrDyn *r = scr_dyn_call(fn, &a, 1, "flush");
|
|
1577
|
+
scr_dyn_release(a);
|
|
1578
|
+
scr_dyn_release(fn);
|
|
1579
|
+
if (r != NULL) scr_dyn_release(r);
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
static void scr_stream_dynopt_destroy_inv(ScrClosure *cb, ScrStream *s, ScrError *err) {
|
|
1583
|
+
ScrDyn *args[2];
|
|
1584
|
+
args[0] = err != NULL ? scr_dyn_from_error(err) : scr_dyn_new_null();
|
|
1585
|
+
args[1] = scr_stream_dynopt_done(s, &scr_stream_done_dyn_d, 1, "(error)");
|
|
1586
|
+
ScrDyn *fn = scr_stream_dynopt_target(cb);
|
|
1587
|
+
ScrDyn *r = scr_dyn_call(fn, args, 2, "destroy");
|
|
1588
|
+
scr_dyn_release(args[0]);
|
|
1589
|
+
scr_dyn_release(args[1]);
|
|
1590
|
+
scr_dyn_release(fn);
|
|
1591
|
+
if (r != NULL) scr_dyn_release(r);
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
static ScrClosure *scr_stream_dynopt_clo(ScrDyn *fn, void *inv) {
|
|
1595
|
+
ScrClosure *c = scr_closure_new(inv, 1);
|
|
1596
|
+
c->caps[0] = scr_box_new_obj(&scr_dyn_retain_v, &scr_dyn_release_v, NULL);
|
|
1597
|
+
scr_box_set_ref(c->caps[0], scr_dyn_retain(fn));
|
|
1598
|
+
return c;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
typedef struct {
|
|
1602
|
+
double rhwm, whwm;
|
|
1603
|
+
bool auto_destroy, emit_close, allow_half_open;
|
|
1604
|
+
bool readable_side, writable_side;
|
|
1605
|
+
ScrStr *enc; /* owned canonical name, or NULL */
|
|
1606
|
+
/* BORROWED callable fields (owned by the options record). */
|
|
1607
|
+
ScrDyn *read, *write, *final_fn, *destroy, *transform, *flush;
|
|
1608
|
+
} ScrStreamDynOpts;
|
|
1609
|
+
|
|
1610
|
+
/* Node's `=== false` comparisons (autoDestroy/emitClose/allowHalfOpen and
|
|
1611
|
+
* the Duplex side toggles: `options.x !== false` — 0 does NOT disable). */
|
|
1612
|
+
static bool scr_stream_dynopt_false(const ScrDyn *v) {
|
|
1613
|
+
return v != NULL && v->kind == SCR_DYN_BOOL && !v->v.b;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
static double scr_stream_dynopt_hwm(const ScrDyn *o, const char *key, size_t klen) {
|
|
1617
|
+
ScrDyn *v = scr_dyn_obj_get((ScrDyn *)o, key, klen);
|
|
1618
|
+
if (v == NULL || v->kind != SCR_DYN_NUM || v->v.num < 0) return -1;
|
|
1619
|
+
return v->v.num;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
static ScrDyn *scr_stream_dynopt_fn(const ScrDyn *o, const char *key, size_t klen) {
|
|
1623
|
+
ScrDyn *v = scr_dyn_obj_get((ScrDyn *)o, key, klen);
|
|
1624
|
+
return v != NULL && v->kind == SCR_DYN_FUNC ? v : NULL; /* Node's typeof guard */
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
static bool scr_stream_dynopt_truthy(const ScrDyn *o, const char *key, size_t klen) {
|
|
1628
|
+
ScrDyn *v = scr_dyn_obj_get((ScrDyn *)o, key, klen);
|
|
1629
|
+
return v != NULL && scr_dyn_truthy(v);
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
static bool scr_stream_dynopt_unsupported(const char *msg, size_t len) {
|
|
1633
|
+
scr_throw_error_msg(SCR_ERR_ERROR, msg, len);
|
|
1634
|
+
return false;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
/* The runtime twin of the frontend's literal-encoding fold (BUF_ENCODINGS):
|
|
1638
|
+
* canonicalizes or throws Node's ERR_UNKNOWN_ENCODING TypeError. */
|
|
1639
|
+
static ScrStr *scr_stream_dynopt_encoding(const ScrStr *raw) {
|
|
1640
|
+
static const struct { const char *from; const char *to; } map[] = {
|
|
1641
|
+
{"utf8", "utf8"}, {"utf-8", "utf8"}, {"hex", "hex"}, {"base64", "base64"},
|
|
1642
|
+
{"base64url", "base64url"}, {"latin1", "latin1"}, {"binary", "latin1"},
|
|
1643
|
+
{"ascii", "ascii"}, {"utf16le", "utf16le"}, {"utf-16le", "utf16le"},
|
|
1644
|
+
{"ucs2", "utf16le"}, {"ucs-2", "utf16le"},
|
|
1645
|
+
};
|
|
1646
|
+
for (size_t i = 0; i < sizeof map / sizeof map[0]; i++) {
|
|
1647
|
+
size_t n = strlen(map[i].from);
|
|
1648
|
+
if (raw->len == n && memcmp(raw->data, map[i].from, n) == 0) {
|
|
1649
|
+
return scr_str_new(map[i].to, strlen(map[i].to));
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
char msg[128];
|
|
1653
|
+
int len = snprintf(msg, sizeof msg, "Unknown encoding: %.*s",
|
|
1654
|
+
(int)(raw->len < 64 ? raw->len : 64), raw->data);
|
|
1655
|
+
scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)(len < 0 ? 0 : len), "ERR_UNKNOWN_ENCODING");
|
|
1656
|
+
return NULL;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
/* Parses a dyn options record for one base ('r','w','d','t','p'); false
|
|
1660
|
+
* with the exception pending when a consumed-but-unlowered option rides. */
|
|
1661
|
+
static bool scr_stream_parse_dyn_opts(const ScrDyn *opts, char base, ScrStreamDynOpts *out) {
|
|
1662
|
+
out->rhwm = -1;
|
|
1663
|
+
out->whwm = -1;
|
|
1664
|
+
out->auto_destroy = true;
|
|
1665
|
+
out->emit_close = true;
|
|
1666
|
+
out->allow_half_open = true;
|
|
1667
|
+
out->readable_side = true;
|
|
1668
|
+
out->writable_side = true;
|
|
1669
|
+
out->enc = NULL;
|
|
1670
|
+
out->read = out->write = out->final_fn = out->destroy = out->transform = out->flush = NULL;
|
|
1671
|
+
if (opts == NULL || opts->kind != SCR_DYN_OBJ) return true; /* Node's `if (options)` */
|
|
1672
|
+
const bool duplexish = base == 'd' || base == 't' || base == 'p';
|
|
1673
|
+
const bool transformish = base == 't' || base == 'p';
|
|
1674
|
+
const bool r_sided = base != 'w';
|
|
1675
|
+
|
|
1676
|
+
if (scr_stream_dynopt_truthy(opts, "objectMode", 10) ||
|
|
1677
|
+
scr_stream_dynopt_truthy(opts, "readableObjectMode", 18) ||
|
|
1678
|
+
scr_stream_dynopt_truthy(opts, "writableObjectMode", 18)) {
|
|
1679
|
+
static const char m[] = "objectMode streams are not supported yet (chunks are Buffers or utf8 strings)";
|
|
1680
|
+
return scr_stream_dynopt_unsupported(m, sizeof m - 1);
|
|
1681
|
+
}
|
|
1682
|
+
if (scr_stream_dynopt_false(scr_dyn_obj_get((ScrDyn *)opts, "decodeStrings", 13))) {
|
|
1683
|
+
static const char m[] = "decodeStrings: false is not supported yet (chunks always decode to Buffers)";
|
|
1684
|
+
return scr_stream_dynopt_unsupported(m, sizeof m - 1);
|
|
1685
|
+
}
|
|
1686
|
+
{
|
|
1687
|
+
ScrDyn *v = scr_dyn_obj_get((ScrDyn *)opts, "signal", 6);
|
|
1688
|
+
if (v != NULL && v->kind != SCR_DYN_NULL && v->kind != SCR_DYN_UNDEF) {
|
|
1689
|
+
static const char m[] = "the stream option 'signal' is not supported yet (AbortSignal-driven destruction has no lowering)";
|
|
1690
|
+
return scr_stream_dynopt_unsupported(m, sizeof m - 1);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
if (scr_stream_dynopt_fn(opts, "construct", 9) != NULL) {
|
|
1694
|
+
static const char m[] = "the stream option 'construct' is not supported yet (deferred construction has no lowering)";
|
|
1695
|
+
return scr_stream_dynopt_unsupported(m, sizeof m - 1);
|
|
1696
|
+
}
|
|
1697
|
+
if (scr_stream_dynopt_fn(opts, "writev", 6) != NULL) {
|
|
1698
|
+
static const char m[] = "the stream option 'writev' is not supported yet (writes deliver one chunk at a time)";
|
|
1699
|
+
return scr_stream_dynopt_unsupported(m, sizeof m - 1);
|
|
1700
|
+
}
|
|
1701
|
+
{
|
|
1702
|
+
ScrDyn *v = scr_dyn_obj_get((ScrDyn *)opts, "defaultEncoding", 15);
|
|
1703
|
+
if (v != NULL && v->kind == SCR_DYN_STR &&
|
|
1704
|
+
!(v->v.str->len == 4 && memcmp(v->v.str->data, "utf8", 4) == 0) &&
|
|
1705
|
+
!(v->v.str->len == 5 && memcmp(v->v.str->data, "utf-8", 5) == 0)) {
|
|
1706
|
+
static const char m[] = "the stream option 'defaultEncoding' is not supported yet (utf8, the default, is the write-side string encoding)";
|
|
1707
|
+
return scr_stream_dynopt_unsupported(m, sizeof m - 1);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
if (transformish) {
|
|
1711
|
+
/* Node's Transform would install these UNDER the transform machinery
|
|
1712
|
+
* (Duplex consumes read/write/final before Transform overrides). */
|
|
1713
|
+
if (scr_stream_dynopt_fn(opts, "read", 4) != NULL ||
|
|
1714
|
+
scr_stream_dynopt_fn(opts, "write", 5) != NULL ||
|
|
1715
|
+
scr_stream_dynopt_fn(opts, "final", 5) != NULL) {
|
|
1716
|
+
static const char m[] = "a read/write/final option on a Transform is not supported yet (it would replace the transform composition)";
|
|
1717
|
+
return scr_stream_dynopt_unsupported(m, sizeof m - 1);
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
/* highWaterMark wins over the sided keys (Node's getHighWaterMark). */
|
|
1722
|
+
{
|
|
1723
|
+
double hwm = scr_stream_dynopt_hwm(opts, "highWaterMark", 13);
|
|
1724
|
+
double rh = duplexish ? scr_stream_dynopt_hwm(opts, "readableHighWaterMark", 21) : -1;
|
|
1725
|
+
double wh = duplexish ? scr_stream_dynopt_hwm(opts, "writableHighWaterMark", 21) : -1;
|
|
1726
|
+
out->rhwm = hwm >= 0 ? hwm : rh;
|
|
1727
|
+
out->whwm = hwm >= 0 ? hwm : wh;
|
|
1728
|
+
}
|
|
1729
|
+
out->auto_destroy = !scr_stream_dynopt_false(scr_dyn_obj_get((ScrDyn *)opts, "autoDestroy", 11));
|
|
1730
|
+
out->emit_close = !scr_stream_dynopt_false(scr_dyn_obj_get((ScrDyn *)opts, "emitClose", 9));
|
|
1731
|
+
if (duplexish) {
|
|
1732
|
+
out->allow_half_open = !scr_stream_dynopt_false(scr_dyn_obj_get((ScrDyn *)opts, "allowHalfOpen", 13));
|
|
1733
|
+
out->readable_side = !scr_stream_dynopt_false(scr_dyn_obj_get((ScrDyn *)opts, "readable", 8));
|
|
1734
|
+
out->writable_side = !scr_stream_dynopt_false(scr_dyn_obj_get((ScrDyn *)opts, "writable", 8));
|
|
1735
|
+
}
|
|
1736
|
+
if (r_sided) {
|
|
1737
|
+
ScrDyn *v = scr_dyn_obj_get((ScrDyn *)opts, "encoding", 8);
|
|
1738
|
+
if (v != NULL && v->kind == SCR_DYN_STR) {
|
|
1739
|
+
out->enc = scr_stream_dynopt_encoding(v->v.str);
|
|
1740
|
+
if (out->enc == NULL) return false;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
out->destroy = scr_stream_dynopt_fn(opts, "destroy", 7);
|
|
1744
|
+
if (transformish) {
|
|
1745
|
+
out->transform = scr_stream_dynopt_fn(opts, "transform", 9);
|
|
1746
|
+
out->flush = scr_stream_dynopt_fn(opts, "flush", 5);
|
|
1747
|
+
} else {
|
|
1748
|
+
if (base != 'w') out->read = scr_stream_dynopt_fn(opts, "read", 4);
|
|
1749
|
+
if (base != 'r') {
|
|
1750
|
+
out->write = scr_stream_dynopt_fn(opts, "write", 5);
|
|
1751
|
+
out->final_fn = scr_stream_dynopt_fn(opts, "final", 5);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
return true;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
/* One slot: an options callback SHADOWS the fallback (Node's instance-
|
|
1758
|
+
* property-over-prototype rule); the unused fallback releases. */
|
|
1759
|
+
static ScrClosure *scr_stream_dynopt_pick(ScrDyn *opt, void *dyn_inv, ScrClosure *fb, void *fb_inv, void **inv_out) {
|
|
1760
|
+
if (opt != NULL) {
|
|
1761
|
+
if (fb != NULL) scr_closure_release(fb);
|
|
1762
|
+
*inv_out = dyn_inv;
|
|
1763
|
+
return scr_stream_dynopt_clo(opt, dyn_inv);
|
|
1764
|
+
}
|
|
1765
|
+
*inv_out = fb != NULL ? fb_inv : NULL;
|
|
1766
|
+
return fb;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
static void scr_stream_dynopt_apply_enc(ScrStream *s, ScrStr *enc) {
|
|
1770
|
+
if (enc == NULL) return;
|
|
1771
|
+
ScrStream *chain = scr_stream_set_encoding(s, enc);
|
|
1772
|
+
scr_stream_release(chain);
|
|
1773
|
+
scr_str_release(enc);
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
ScrStream *scr_stream_new_readable_dyn(ScrDyn *opts) {
|
|
1777
|
+
ScrStreamDynOpts o;
|
|
1778
|
+
if (!scr_stream_parse_dyn_opts(opts, 'r', &o)) return NULL;
|
|
1779
|
+
void *rinv = NULL, *dinv = NULL;
|
|
1780
|
+
ScrClosure *read = scr_stream_dynopt_pick(o.read, (void *)&scr_stream_dynopt_read_inv, NULL, NULL, &rinv);
|
|
1781
|
+
ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, NULL, NULL, &dinv);
|
|
1782
|
+
ScrStream *s = scr_stream_new_readable(o.rhwm, o.auto_destroy, o.emit_close,
|
|
1783
|
+
read, (ScrStreamReadInv)rinv, destroy, (ScrStreamErrInv)dinv);
|
|
1784
|
+
scr_stream_dynopt_apply_enc(s, o.enc);
|
|
1785
|
+
return s;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
ScrStream *scr_stream_new_writable_dyn(ScrDyn *opts) {
|
|
1789
|
+
ScrStreamDynOpts o;
|
|
1790
|
+
if (!scr_stream_parse_dyn_opts(opts, 'w', &o)) return NULL;
|
|
1791
|
+
void *winv = NULL, *finv = NULL, *dinv = NULL;
|
|
1792
|
+
ScrClosure *write = scr_stream_dynopt_pick(o.write, (void *)&scr_stream_dynopt_write_inv, NULL, NULL, &winv);
|
|
1793
|
+
ScrClosure *final_cb = scr_stream_dynopt_pick(o.final_fn, (void *)&scr_stream_dynopt_final_inv, NULL, NULL, &finv);
|
|
1794
|
+
ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, NULL, NULL, &dinv);
|
|
1795
|
+
return scr_stream_new_writable(o.whwm, o.auto_destroy, o.emit_close,
|
|
1796
|
+
write, (ScrStreamChunkInv)winv, final_cb, (ScrStreamPlainInv)finv, destroy, (ScrStreamErrInv)dinv);
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
ScrStream *scr_stream_new_duplex_dyn(ScrDyn *opts) {
|
|
1800
|
+
ScrStreamDynOpts o;
|
|
1801
|
+
if (!scr_stream_parse_dyn_opts(opts, 'd', &o)) return NULL;
|
|
1802
|
+
void *rinv = NULL, *winv = NULL, *finv = NULL, *dinv = NULL;
|
|
1803
|
+
ScrClosure *read = scr_stream_dynopt_pick(o.read, (void *)&scr_stream_dynopt_read_inv, NULL, NULL, &rinv);
|
|
1804
|
+
ScrClosure *write = scr_stream_dynopt_pick(o.write, (void *)&scr_stream_dynopt_write_inv, NULL, NULL, &winv);
|
|
1805
|
+
ScrClosure *final_cb = scr_stream_dynopt_pick(o.final_fn, (void *)&scr_stream_dynopt_final_inv, NULL, NULL, &finv);
|
|
1806
|
+
ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, NULL, NULL, &dinv);
|
|
1807
|
+
ScrStream *s = scr_stream_new_duplex(o.rhwm, o.whwm, o.auto_destroy, o.emit_close,
|
|
1808
|
+
o.allow_half_open, o.readable_side, o.writable_side,
|
|
1809
|
+
read, (ScrStreamReadInv)rinv, write, (ScrStreamChunkInv)winv,
|
|
1810
|
+
final_cb, (ScrStreamPlainInv)finv, destroy, (ScrStreamErrInv)dinv);
|
|
1811
|
+
scr_stream_dynopt_apply_enc(s, o.enc);
|
|
1812
|
+
return s;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
static ScrStream *scr_stream_new_transformish_dyn(ScrDyn *opts, char base) {
|
|
1816
|
+
ScrStreamDynOpts o;
|
|
1817
|
+
if (!scr_stream_parse_dyn_opts(opts, base, &o)) return NULL;
|
|
1818
|
+
void *tinv = NULL, *linv = NULL, *dinv = NULL;
|
|
1819
|
+
ScrClosure *transform = scr_stream_dynopt_pick(o.transform, (void *)&scr_stream_dynopt_transform_inv, NULL, NULL, &tinv);
|
|
1820
|
+
ScrClosure *flush = scr_stream_dynopt_pick(o.flush, (void *)&scr_stream_dynopt_flush_inv, NULL, NULL, &linv);
|
|
1821
|
+
ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, NULL, NULL, &dinv);
|
|
1822
|
+
ScrStream *s = base == 't'
|
|
1823
|
+
? scr_stream_new_transform(o.rhwm, o.whwm, o.auto_destroy, o.emit_close,
|
|
1824
|
+
o.allow_half_open, o.readable_side, o.writable_side,
|
|
1825
|
+
transform, (ScrStreamChunkInv)tinv, flush, (ScrStreamPlainInv)linv,
|
|
1826
|
+
destroy, (ScrStreamErrInv)dinv)
|
|
1827
|
+
: scr_stream_new_passthrough(o.rhwm, o.whwm, o.auto_destroy, o.emit_close,
|
|
1828
|
+
o.allow_half_open, o.readable_side, o.writable_side,
|
|
1829
|
+
transform, (ScrStreamChunkInv)tinv, flush, (ScrStreamPlainInv)linv,
|
|
1830
|
+
destroy, (ScrStreamErrInv)dinv);
|
|
1831
|
+
scr_stream_dynopt_apply_enc(s, o.enc);
|
|
1832
|
+
return s;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
ScrStream *scr_stream_new_transform_dyn(ScrDyn *opts) {
|
|
1836
|
+
return scr_stream_new_transformish_dyn(opts, 't');
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
ScrStream *scr_stream_new_passthrough_dyn(ScrDyn *opts) {
|
|
1840
|
+
return scr_stream_new_transformish_dyn(opts, 'p');
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
void scr_stream_init_readable_dyn(ScrStream *s, ScrDyn *opts,
|
|
1844
|
+
ScrClosure *read_fb, ScrStreamReadInv read_fb_inv,
|
|
1845
|
+
ScrClosure *destroy_fb, ScrStreamErrInv destroy_fb_inv) {
|
|
1846
|
+
ScrStreamDynOpts o;
|
|
1847
|
+
if (!scr_stream_parse_dyn_opts(opts, 'r', &o)) {
|
|
1848
|
+
if (read_fb != NULL) scr_closure_release(read_fb);
|
|
1849
|
+
if (destroy_fb != NULL) scr_closure_release(destroy_fb);
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
void *rinv = NULL, *dinv = NULL;
|
|
1853
|
+
ScrClosure *read = scr_stream_dynopt_pick(o.read, (void *)&scr_stream_dynopt_read_inv, read_fb, (void *)read_fb_inv, &rinv);
|
|
1854
|
+
ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, destroy_fb, (void *)destroy_fb_inv, &dinv);
|
|
1855
|
+
scr_stream_init_readable(s, o.rhwm, o.auto_destroy, o.emit_close,
|
|
1856
|
+
read, (ScrStreamReadInv)rinv, destroy, (ScrStreamErrInv)dinv);
|
|
1857
|
+
scr_stream_dynopt_apply_enc(s, o.enc);
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
void scr_stream_init_writable_dyn(ScrStream *s, ScrDyn *opts,
|
|
1861
|
+
ScrClosure *write_fb, ScrStreamChunkInv write_fb_inv,
|
|
1862
|
+
ScrClosure *final_fb, ScrStreamPlainInv final_fb_inv,
|
|
1863
|
+
ScrClosure *destroy_fb, ScrStreamErrInv destroy_fb_inv) {
|
|
1864
|
+
ScrStreamDynOpts o;
|
|
1865
|
+
if (!scr_stream_parse_dyn_opts(opts, 'w', &o)) {
|
|
1866
|
+
if (write_fb != NULL) scr_closure_release(write_fb);
|
|
1867
|
+
if (final_fb != NULL) scr_closure_release(final_fb);
|
|
1868
|
+
if (destroy_fb != NULL) scr_closure_release(destroy_fb);
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
void *winv = NULL, *finv = NULL, *dinv = NULL;
|
|
1872
|
+
ScrClosure *write = scr_stream_dynopt_pick(o.write, (void *)&scr_stream_dynopt_write_inv, write_fb, (void *)write_fb_inv, &winv);
|
|
1873
|
+
ScrClosure *final_cb = scr_stream_dynopt_pick(o.final_fn, (void *)&scr_stream_dynopt_final_inv, final_fb, (void *)final_fb_inv, &finv);
|
|
1874
|
+
ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, destroy_fb, (void *)destroy_fb_inv, &dinv);
|
|
1875
|
+
scr_stream_init_writable(s, o.whwm, o.auto_destroy, o.emit_close,
|
|
1876
|
+
write, (ScrStreamChunkInv)winv, final_cb, (ScrStreamPlainInv)finv, destroy, (ScrStreamErrInv)dinv);
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
void scr_stream_init_duplex_dyn(ScrStream *s, ScrDyn *opts,
|
|
1880
|
+
ScrClosure *read_fb, ScrStreamReadInv read_fb_inv,
|
|
1881
|
+
ScrClosure *write_fb, ScrStreamChunkInv write_fb_inv,
|
|
1882
|
+
ScrClosure *final_fb, ScrStreamPlainInv final_fb_inv,
|
|
1883
|
+
ScrClosure *destroy_fb, ScrStreamErrInv destroy_fb_inv) {
|
|
1884
|
+
ScrStreamDynOpts o;
|
|
1885
|
+
if (!scr_stream_parse_dyn_opts(opts, 'd', &o)) {
|
|
1886
|
+
if (read_fb != NULL) scr_closure_release(read_fb);
|
|
1887
|
+
if (write_fb != NULL) scr_closure_release(write_fb);
|
|
1888
|
+
if (final_fb != NULL) scr_closure_release(final_fb);
|
|
1889
|
+
if (destroy_fb != NULL) scr_closure_release(destroy_fb);
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1892
|
+
void *rinv = NULL, *winv = NULL, *finv = NULL, *dinv = NULL;
|
|
1893
|
+
ScrClosure *read = scr_stream_dynopt_pick(o.read, (void *)&scr_stream_dynopt_read_inv, read_fb, (void *)read_fb_inv, &rinv);
|
|
1894
|
+
ScrClosure *write = scr_stream_dynopt_pick(o.write, (void *)&scr_stream_dynopt_write_inv, write_fb, (void *)write_fb_inv, &winv);
|
|
1895
|
+
ScrClosure *final_cb = scr_stream_dynopt_pick(o.final_fn, (void *)&scr_stream_dynopt_final_inv, final_fb, (void *)final_fb_inv, &finv);
|
|
1896
|
+
ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, destroy_fb, (void *)destroy_fb_inv, &dinv);
|
|
1897
|
+
scr_stream_init_duplex(s, o.rhwm, o.whwm, o.auto_destroy, o.emit_close,
|
|
1898
|
+
o.allow_half_open, o.readable_side, o.writable_side,
|
|
1899
|
+
read, (ScrStreamReadInv)rinv, write, (ScrStreamChunkInv)winv,
|
|
1900
|
+
final_cb, (ScrStreamPlainInv)finv, destroy, (ScrStreamErrInv)dinv);
|
|
1901
|
+
scr_stream_dynopt_apply_enc(s, o.enc);
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
static void scr_stream_init_transformish_dyn(ScrStream *s, ScrDyn *opts, char base,
|
|
1905
|
+
ScrClosure *transform_fb, ScrStreamChunkInv transform_fb_inv,
|
|
1906
|
+
ScrClosure *flush_fb, ScrStreamPlainInv flush_fb_inv,
|
|
1907
|
+
ScrClosure *destroy_fb, ScrStreamErrInv destroy_fb_inv) {
|
|
1908
|
+
ScrStreamDynOpts o;
|
|
1909
|
+
if (!scr_stream_parse_dyn_opts(opts, base, &o)) {
|
|
1910
|
+
if (transform_fb != NULL) scr_closure_release(transform_fb);
|
|
1911
|
+
if (flush_fb != NULL) scr_closure_release(flush_fb);
|
|
1912
|
+
if (destroy_fb != NULL) scr_closure_release(destroy_fb);
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
void *tinv = NULL, *linv = NULL, *dinv = NULL;
|
|
1916
|
+
ScrClosure *transform = scr_stream_dynopt_pick(o.transform, (void *)&scr_stream_dynopt_transform_inv, transform_fb, (void *)transform_fb_inv, &tinv);
|
|
1917
|
+
ScrClosure *flush = scr_stream_dynopt_pick(o.flush, (void *)&scr_stream_dynopt_flush_inv, flush_fb, (void *)flush_fb_inv, &linv);
|
|
1918
|
+
ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, destroy_fb, (void *)destroy_fb_inv, &dinv);
|
|
1919
|
+
if (base == 't') {
|
|
1920
|
+
scr_stream_init_transform(s, o.rhwm, o.whwm, o.auto_destroy, o.emit_close,
|
|
1921
|
+
o.allow_half_open, o.readable_side, o.writable_side,
|
|
1922
|
+
transform, (ScrStreamChunkInv)tinv, flush, (ScrStreamPlainInv)linv,
|
|
1923
|
+
destroy, (ScrStreamErrInv)dinv);
|
|
1924
|
+
} else {
|
|
1925
|
+
scr_stream_init_passthrough(s, o.rhwm, o.whwm, o.auto_destroy, o.emit_close,
|
|
1926
|
+
o.allow_half_open, o.readable_side, o.writable_side,
|
|
1927
|
+
transform, (ScrStreamChunkInv)tinv, flush, (ScrStreamPlainInv)linv,
|
|
1928
|
+
destroy, (ScrStreamErrInv)dinv);
|
|
1929
|
+
}
|
|
1930
|
+
scr_stream_dynopt_apply_enc(s, o.enc);
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
void scr_stream_init_transform_dyn(ScrStream *s, ScrDyn *opts,
|
|
1934
|
+
ScrClosure *transform_fb, ScrStreamChunkInv transform_fb_inv,
|
|
1935
|
+
ScrClosure *flush_fb, ScrStreamPlainInv flush_fb_inv,
|
|
1936
|
+
ScrClosure *destroy_fb, ScrStreamErrInv destroy_fb_inv) {
|
|
1937
|
+
scr_stream_init_transformish_dyn(s, opts, 't', transform_fb, transform_fb_inv,
|
|
1938
|
+
flush_fb, flush_fb_inv, destroy_fb, destroy_fb_inv);
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
void scr_stream_init_passthrough_dyn(ScrStream *s, ScrDyn *opts,
|
|
1942
|
+
ScrClosure *transform_fb, ScrStreamChunkInv transform_fb_inv,
|
|
1943
|
+
ScrClosure *flush_fb, ScrStreamPlainInv flush_fb_inv,
|
|
1944
|
+
ScrClosure *destroy_fb, ScrStreamErrInv destroy_fb_inv) {
|
|
1945
|
+
scr_stream_init_transformish_dyn(s, opts, 'p', transform_fb, transform_fb_inv,
|
|
1946
|
+
flush_fb, flush_fb_inv, destroy_fb, destroy_fb_inv);
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
/* ── finished() and pipeline() (lib/internal/streams/end-of-stream and
|
|
1950
|
+
* pipeline — the callback forms) ─────────────────────────────────────
|
|
1951
|
+
*
|
|
1952
|
+
* PROBED ORDERINGS (Node v24.15.0):
|
|
1953
|
+
* - finished(s, cb): cb fires right AFTER s's 'close' (willEmitClose —
|
|
1954
|
+
* the default autoDestroy+emitClose lifecycle), with `this` bound to
|
|
1955
|
+
* the stream, no arguments on success, the error on error, and
|
|
1956
|
+
* ERR_STREAM_PREMATURE_CLOSE ("Premature close") when the stream
|
|
1957
|
+
* closed before end/finish. finished returns a CLEANUP function that
|
|
1958
|
+
* unhooks the callback.
|
|
1959
|
+
* - pipeline(a, b, c, cb) on a mid-stream error: the erroring stream
|
|
1960
|
+
* destroys itself and emits 'error'+'close'; the pipeline then
|
|
1961
|
+
* destroys every OTHER stream with the SAME error (pipeline order),
|
|
1962
|
+
* each emitting 'error' then 'close'; cb(err) runs after the last
|
|
1963
|
+
* 'close'. Success: cb(null) after the destination's 'close'.
|
|
1964
|
+
* - both register 'error' handlers, so an otherwise-unhandled stream
|
|
1965
|
+
* error no longer crashes (the SCR_ST_ERROR suppression above). */
|
|
1966
|
+
|
|
1967
|
+
static void scr_stream_fin_add(ScrStreamState *st, ScrClosure *cb /*moves*/, ScrStreamErrInv inv) {
|
|
1968
|
+
if (st->fin.n == st->fin.cap) {
|
|
1969
|
+
st->fin.cap = st->fin.cap ? st->fin.cap * 2 : 2;
|
|
1970
|
+
st->fin.cb = realloc(st->fin.cb, st->fin.cap * sizeof *st->fin.cb);
|
|
1971
|
+
st->fin.inv = realloc(st->fin.inv, st->fin.cap * sizeof *st->fin.inv);
|
|
1972
|
+
if (!st->fin.cb || !st->fin.inv) scr_stream_oom();
|
|
1973
|
+
}
|
|
1974
|
+
st->fin.cb[st->fin.n] = cb;
|
|
1975
|
+
st->fin.inv[st->fin.n] = inv;
|
|
1976
|
+
st->fin.n++;
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
/* The stream's finish status: +1 error (the recorded one, or premature
|
|
1980
|
+
* close), or NULL for a clean end+finish. */
|
|
1981
|
+
static ScrError *scr_stream_finish_status(ScrStream *s) {
|
|
1982
|
+
ScrStreamState *st = s->st;
|
|
1983
|
+
if (st->errored) return scr_error_retain(st->errored);
|
|
1984
|
+
if ((st->has_r && !st->r.end_emitted) || (st->has_w && !st->w.finished)) {
|
|
1985
|
+
return scr_stream_mkerr("ERR_STREAM_PREMATURE_CLOSE", "Premature close");
|
|
1986
|
+
}
|
|
1987
|
+
return NULL;
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
static void scr_stream_notify_finished(ScrStream *s) {
|
|
1991
|
+
ScrStreamState *st = s->st;
|
|
1992
|
+
if (st->fin.n == 0) return;
|
|
1993
|
+
/* Take the list out first: a watcher may register another finished()
|
|
1994
|
+
* or run cleanup reentrantly. */
|
|
1995
|
+
ScrClosure **cbs = st->fin.cb;
|
|
1996
|
+
ScrStreamErrInv *invs = st->fin.inv;
|
|
1997
|
+
size_t n = st->fin.n;
|
|
1998
|
+
st->fin.cb = NULL;
|
|
1999
|
+
st->fin.inv = NULL;
|
|
2000
|
+
st->fin.n = 0;
|
|
2001
|
+
st->fin.cap = 0;
|
|
2002
|
+
ScrError *err = scr_stream_finish_status(s);
|
|
2003
|
+
for (size_t i = 0; i < n; i++) {
|
|
2004
|
+
if (!scr_exc_pending()) invs[i](cbs[i], s, err);
|
|
2005
|
+
scr_closure_release(cbs[i]);
|
|
2006
|
+
}
|
|
2007
|
+
if (err) scr_error_release(err);
|
|
2008
|
+
free(cbs);
|
|
2009
|
+
free(invs);
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
static void scr_stream_finished_cleanup_fn(ScrClosure *self) {
|
|
2013
|
+
ScrStream *s = scr_box_get_ref(self->caps[0]); /* +1 */
|
|
2014
|
+
ScrClosure *cb = scr_box_get_ref(self->caps[1]); /* +1 */
|
|
2015
|
+
ScrStreamState *st = s->st;
|
|
2016
|
+
for (size_t i = 0; i < st->fin.n; i++) {
|
|
2017
|
+
if (st->fin.cb[i] == cb) {
|
|
2018
|
+
scr_closure_release(st->fin.cb[i]);
|
|
2019
|
+
memmove(&st->fin.cb[i], &st->fin.cb[i + 1], (st->fin.n - i - 1) * sizeof *st->fin.cb);
|
|
2020
|
+
memmove(&st->fin.inv[i], &st->fin.inv[i + 1], (st->fin.n - i - 1) * sizeof *st->fin.inv);
|
|
2021
|
+
st->fin.n--;
|
|
2022
|
+
break;
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
scr_closure_release(cb);
|
|
2026
|
+
scr_stream_release(s);
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
ScrClosure *scr_stream_finished(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamErrInv inv) {
|
|
2030
|
+
ScrStreamState *st = s->st;
|
|
2031
|
+
scr_stream_fin_add(st, scr_closure_retain(cb), inv);
|
|
2032
|
+
if (st->close_emitted) {
|
|
2033
|
+
/* Already terminal: Node's eos still calls back asynchronously. */
|
|
2034
|
+
scr_st_tick(s, SCR_ST_FIN, NULL, NULL);
|
|
2035
|
+
}
|
|
2036
|
+
ScrClosure *cleanup = scr_closure_new((void *)&scr_stream_finished_cleanup_fn, 2);
|
|
2037
|
+
cleanup->caps[0] = scr_box_new_obj(&scr_stream_retain_v, &scr_stream_release_v, &scr_stream_trace);
|
|
2038
|
+
scr_box_set_ref(cleanup->caps[0], scr_stream_retain(s));
|
|
2039
|
+
cleanup->caps[1] = scr_box_new_obj(&scr_closure_retain_v, &scr_closure_release_v, &scr_closure_trace_v);
|
|
2040
|
+
scr_box_set_ref(cleanup->caps[1], cb); /* the move lands here */
|
|
2041
|
+
return cleanup;
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
/* The dyn-valued finished/pipeline callback (a mustCall wrapper): success
|
|
2045
|
+
* calls with NO arguments (Node's callback.call(stream)), an error boxes
|
|
2046
|
+
* through the boundary encoding. */
|
|
2047
|
+
void scr_stream_finished_dyn_inv(ScrClosure *cb, ScrStream *s, ScrError *err) {
|
|
2048
|
+
(void)s;
|
|
2049
|
+
ScrDyn *fn = scr_stream_dynopt_target(cb);
|
|
2050
|
+
ScrDyn *r;
|
|
2051
|
+
if (err != NULL) {
|
|
2052
|
+
ScrDyn *a = scr_dyn_from_error(err);
|
|
2053
|
+
r = scr_dyn_call(fn, &a, 1, "callback");
|
|
2054
|
+
scr_dyn_release(a);
|
|
2055
|
+
} else {
|
|
2056
|
+
r = scr_dyn_call(fn, NULL, 0, "callback");
|
|
2057
|
+
}
|
|
2058
|
+
scr_dyn_release(fn);
|
|
2059
|
+
if (r != NULL) scr_dyn_release(r);
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
typedef struct {
|
|
2063
|
+
size_t rc;
|
|
2064
|
+
size_t n, closed;
|
|
2065
|
+
ScrStream **streams; /* owned */
|
|
2066
|
+
ScrClosure *cb; /* owned; NULL once fired */
|
|
2067
|
+
ScrStreamErrInv inv;
|
|
2068
|
+
ScrError *err; /* first error, owned */
|
|
2069
|
+
} ScrStreamPipeCtx;
|
|
2070
|
+
|
|
2071
|
+
static void *scr_stream_pipectx_retain_v(void *p) {
|
|
2072
|
+
((ScrStreamPipeCtx *)p)->rc++;
|
|
2073
|
+
return p;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
static void scr_stream_pipectx_release_v(void *p) {
|
|
2077
|
+
ScrStreamPipeCtx *ctx = p;
|
|
2078
|
+
if (--ctx->rc > 0) return;
|
|
2079
|
+
for (size_t i = 0; i < ctx->n; i++) scr_stream_release(ctx->streams[i]);
|
|
2080
|
+
free(ctx->streams);
|
|
2081
|
+
if (ctx->cb) scr_closure_release(ctx->cb);
|
|
2082
|
+
if (ctx->err) scr_error_release(ctx->err);
|
|
2083
|
+
free(ctx);
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
static void scr_stream_pipeline_watch_inv(ScrClosure *cb, ScrStream *s, ScrError *err) {
|
|
2087
|
+
ScrStreamPipeCtx *ctx = scr_box_get_ref(cb->caps[0]); /* +1 */
|
|
2088
|
+
ctx->closed++;
|
|
2089
|
+
if (err != NULL && ctx->err == NULL) {
|
|
2090
|
+
ctx->err = scr_error_retain(err);
|
|
2091
|
+
/* Node's pipeline destroyer: every other stream goes down with the
|
|
2092
|
+
* SAME error, pipeline order (each emits 'error' then 'close'). */
|
|
2093
|
+
for (size_t i = 0; i < ctx->n; i++) {
|
|
2094
|
+
if (ctx->streams[i] != s) {
|
|
2095
|
+
ScrStream *chain = scr_stream_destroy(ctx->streams[i], ctx->err);
|
|
2096
|
+
scr_stream_release(chain);
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
if (ctx->closed == ctx->n && ctx->cb != NULL) {
|
|
2101
|
+
ScrClosure *ucb = ctx->cb;
|
|
2102
|
+
ctx->cb = NULL;
|
|
2103
|
+
ctx->inv(ucb, ctx->streams[ctx->n - 1], ctx->err);
|
|
2104
|
+
scr_closure_release(ucb);
|
|
2105
|
+
}
|
|
2106
|
+
scr_stream_pipectx_release_v(ctx);
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
/* The dyn-valued twins: the callback rides as a checked-dynamic value
|
|
2110
|
+
* (borrowed; the closure's cap box retains it). */
|
|
2111
|
+
ScrClosure *scr_stream_finished_dyn(ScrStream *s, ScrDyn *cb) {
|
|
2112
|
+
ScrClosure *c = scr_stream_dynopt_clo(cb, (void *)&scr_stream_finished_dyn_inv);
|
|
2113
|
+
ScrClosure *cleanup = scr_stream_finished(s, c, &scr_stream_finished_dyn_inv);
|
|
2114
|
+
return cleanup;
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
ScrStream *scr_stream_pipeline_dyn(double n, ScrStream **streams, ScrDyn *cb) {
|
|
2118
|
+
ScrClosure *c = scr_stream_dynopt_clo(cb, (void *)&scr_stream_finished_dyn_inv);
|
|
2119
|
+
return scr_stream_pipeline(n, streams, c, &scr_stream_finished_dyn_inv);
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
ScrStream *scr_stream_pipeline(double n_d, ScrStream **streams /*borrowed*/,
|
|
2123
|
+
ScrClosure *cb /*moves*/, ScrStreamErrInv inv) {
|
|
2124
|
+
size_t n = (size_t)n_d;
|
|
2125
|
+
ScrStreamPipeCtx *ctx = calloc(1, sizeof *ctx);
|
|
2126
|
+
if (!ctx) scr_stream_oom();
|
|
2127
|
+
ctx->rc = 1;
|
|
2128
|
+
ctx->n = n;
|
|
2129
|
+
ctx->streams = calloc(n, sizeof *ctx->streams);
|
|
2130
|
+
if (!ctx->streams) scr_stream_oom();
|
|
2131
|
+
for (size_t i = 0; i < n; i++) ctx->streams[i] = scr_stream_retain(streams[i]);
|
|
2132
|
+
ctx->cb = cb;
|
|
2133
|
+
ctx->inv = inv;
|
|
2134
|
+
/* Chain the pipes first (end: true — Node's pipeline forwards EOF). */
|
|
2135
|
+
for (size_t i = 0; i + 1 < n; i++) {
|
|
2136
|
+
ScrStream *d = scr_stream_pipe(streams[i], streams[i + 1], true);
|
|
2137
|
+
scr_stream_release(d);
|
|
2138
|
+
}
|
|
2139
|
+
/* One watcher per stream: the terminal statuses drive error propagation
|
|
2140
|
+
* and the final callback (after the LAST 'close'). */
|
|
2141
|
+
for (size_t i = 0; i < n; i++) {
|
|
2142
|
+
ScrClosure *w = scr_closure_new((void *)&scr_stream_pipeline_watch_inv, 1);
|
|
2143
|
+
w->caps[0] = scr_box_new_obj(&scr_stream_pipectx_retain_v, &scr_stream_pipectx_release_v, NULL);
|
|
2144
|
+
scr_box_set_ref(w->caps[0], scr_stream_pipectx_retain_v(ctx));
|
|
2145
|
+
scr_stream_fin_add(streams[i]->st, w, &scr_stream_pipeline_watch_inv);
|
|
2146
|
+
}
|
|
2147
|
+
scr_stream_pipectx_release_v(ctx); /* the boxes own it now */
|
|
2148
|
+
return scr_stream_retain(streams[n - 1]);
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
/* ── the readable surface ─────────────────────────────────────────────── */
|
|
2152
|
+
|
|
2153
|
+
bool scr_stream_push(ScrStream *s, ScrBytes *chunk) {
|
|
2154
|
+
return scr_stream_add_chunk(s, chunk, false);
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
bool scr_stream_push_str(ScrStream *s, ScrStr *str) {
|
|
2158
|
+
ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)str->len);
|
|
2159
|
+
memcpy(b->data, str->data, str->len);
|
|
2160
|
+
bool ret = scr_stream_add_chunk(s, b, false);
|
|
2161
|
+
scr_bytes_release(b);
|
|
2162
|
+
return ret;
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
bool scr_stream_push_null(ScrStream *s) {
|
|
2166
|
+
ScrStreamState *st = s->st;
|
|
2167
|
+
if (!st->has_r || st->destroyed) return false;
|
|
2168
|
+
st->r.reading = false;
|
|
2169
|
+
if (!st->r.ended) {
|
|
2170
|
+
/* onEofChunk flushes the decoder's held partial first (utf8's
|
|
2171
|
+
* replacement chars et al — Node's decoder.end() push). */
|
|
2172
|
+
if (st->r.encoded) {
|
|
2173
|
+
ScrStr *tail = scr_strdec_end(st->r.enc, st->r.dec_pending);
|
|
2174
|
+
st->r.dec_pending = 0;
|
|
2175
|
+
if (scr_str_utf16_len(tail) > 0) {
|
|
2176
|
+
scr_stream_rbuf_push(st, tail, false);
|
|
2177
|
+
if (st->r.need_readable) scr_stream_emit_readable_nt(s);
|
|
2178
|
+
} else {
|
|
2179
|
+
scr_str_release(tail);
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
st->r.ended = true;
|
|
2183
|
+
/* onEofChunk: inside a _read call (or before the first attempt) the
|
|
2184
|
+
* 'readable' pass defers to a tick; outside one it runs NOW — a
|
|
2185
|
+
* flowing stream delivers its buffered chunks synchronously here. */
|
|
2186
|
+
if (st->r.in_read_sync) {
|
|
2187
|
+
scr_stream_emit_readable_nt(s);
|
|
2188
|
+
if (st->r.length == 0) scr_stream_end_readable(s);
|
|
2189
|
+
} else {
|
|
2190
|
+
st->r.need_readable = false;
|
|
2191
|
+
st->r.emitted_readable = true;
|
|
2192
|
+
scr_stream_emit_readable_now(s);
|
|
2193
|
+
}
|
|
2194
|
+
if (!scr_exc_pending()) scr_stream_settle_next(s);
|
|
2195
|
+
}
|
|
2196
|
+
return false;
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
void scr_stream_unshift(ScrStream *s, ScrBytes *chunk) {
|
|
2200
|
+
ScrStreamState *st = s->st;
|
|
2201
|
+
if (st->r.head_off > 0) {
|
|
2202
|
+
/* materialize the part-read head so the front slot is whole */
|
|
2203
|
+
void *rest = scr_stream_rbuf_take(st, scr_stream_entry_len(st, st->r.buf[0]) - st->r.head_off);
|
|
2204
|
+
scr_stream_rbuf_push(st, rest, true);
|
|
2205
|
+
}
|
|
2206
|
+
scr_stream_add_chunk(s, chunk, true);
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
void scr_stream_unshift_str(ScrStream *s, ScrStr *str) {
|
|
2210
|
+
ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)str->len);
|
|
2211
|
+
memcpy(b->data, str->data, str->len);
|
|
2212
|
+
scr_stream_unshift(s, b);
|
|
2213
|
+
scr_bytes_release(b);
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
ScrBytes *scr_stream_read(ScrStream *s, double size) {
|
|
2217
|
+
/* a destroyed readable still DRAINS its buffer (Node: destroy leaves
|
|
2218
|
+
* buffered chunks readable; only _read refills are blocked) */
|
|
2219
|
+
if (!s->st->has_r) return NULL;
|
|
2220
|
+
if (s->st->r.encoded) {
|
|
2221
|
+
/* read() answers STRINGS once encoded — the static result type here
|
|
2222
|
+
* is `Buffer | null`, so the honest answer is a fence, not a
|
|
2223
|
+
* mistyped payload (SEMANTICS.md; 'data' listeners carry strings). */
|
|
2224
|
+
static const char msg[] =
|
|
2225
|
+
"read() on a stream with an encoding set is not supported yet "
|
|
2226
|
+
"(consume 'data' events, which deliver strings)";
|
|
2227
|
+
scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
|
|
2228
|
+
return NULL;
|
|
2229
|
+
}
|
|
2230
|
+
return scr_stream_read_n(s, size);
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
/* setEncoding(enc): flips the readable buffer into string mode — Node
|
|
2234
|
+
* re-decodes the buffered content into ONE string entry and re-counts
|
|
2235
|
+
* length in string units. Borrows enc (canonical, frontend-folded);
|
|
2236
|
+
* answers the receiver +1. */
|
|
2237
|
+
ScrStream *scr_stream_set_encoding(ScrStream *s, ScrStr *enc) {
|
|
2238
|
+
ScrStreamState *st = s->st;
|
|
2239
|
+
if (!st->has_r) return scr_stream_retain(s);
|
|
2240
|
+
if (st->r.encoded) {
|
|
2241
|
+
/* switching encodings mid-stream: fold the old decoder's pending
|
|
2242
|
+
* tail into the buffer first (rare; Node re-creates the decoder) */
|
|
2243
|
+
ScrStr *tail = scr_strdec_end(st->r.enc, st->r.dec_pending);
|
|
2244
|
+
if (scr_str_utf16_len(tail) > 0) scr_stream_rbuf_push(st, tail, false);
|
|
2245
|
+
else scr_str_release(tail);
|
|
2246
|
+
scr_str_release(st->r.enc);
|
|
2247
|
+
st->r.enc = scr_str_retain(enc);
|
|
2248
|
+
st->r.dec_pending = 0;
|
|
2249
|
+
return scr_stream_retain(s);
|
|
2250
|
+
}
|
|
2251
|
+
st->r.enc = scr_str_retain(enc);
|
|
2252
|
+
st->r.dec_pending = 0;
|
|
2253
|
+
/* decode the buffered bytes into one concatenated string entry */
|
|
2254
|
+
ScrStr *content = NULL;
|
|
2255
|
+
size_t skip = st->r.head_off; /* buf[0] may be part-read (byte offset) */
|
|
2256
|
+
for (size_t i = 0; i < st->r.n; i++) {
|
|
2257
|
+
ScrBytes *chunk = st->r.buf[i];
|
|
2258
|
+
ScrBytes *view = chunk;
|
|
2259
|
+
if (i == 0 && skip > 0) {
|
|
2260
|
+
view = scr_bytes_new(SCR_BYTES_U8, (double)(chunk->len - skip));
|
|
2261
|
+
memcpy(view->data, chunk->data + skip, chunk->len - skip);
|
|
2262
|
+
}
|
|
2263
|
+
ScrStr *piece = scr_strdec_write(st->r.enc, st->r.dec_pending, view);
|
|
2264
|
+
st->r.dec_pending = scr_strdec_next(st->r.enc, st->r.dec_pending, view);
|
|
2265
|
+
if (view != chunk) scr_bytes_release(view);
|
|
2266
|
+
scr_bytes_release(chunk);
|
|
2267
|
+
if (content == NULL) {
|
|
2268
|
+
content = piece;
|
|
2269
|
+
} else {
|
|
2270
|
+
ScrStr *joined = scr_str_concat(content, piece);
|
|
2271
|
+
scr_str_release(content);
|
|
2272
|
+
scr_str_release(piece);
|
|
2273
|
+
content = joined;
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
st->r.n = 0;
|
|
2277
|
+
st->r.head_off = 0;
|
|
2278
|
+
st->r.length = 0;
|
|
2279
|
+
st->r.encoded = true; /* AFTER the byte-entry walk above */
|
|
2280
|
+
if (content != NULL) {
|
|
2281
|
+
if (scr_str_utf16_len(content) > 0) scr_stream_rbuf_push(st, content, false);
|
|
2282
|
+
else scr_str_release(content);
|
|
2283
|
+
}
|
|
2284
|
+
return scr_stream_retain(s);
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
ScrStream *scr_stream_pause(ScrStream *s) {
|
|
2288
|
+
ScrStreamState *st = s->st;
|
|
2289
|
+
if (st->has_r && st->r.flowing != 0) {
|
|
2290
|
+
st->r.flowing = 0;
|
|
2291
|
+
scr_stream_emit0(s, "pause"); /* Node emits 'pause' synchronously */
|
|
2292
|
+
}
|
|
2293
|
+
return scr_stream_retain(s);
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
ScrStream *scr_stream_resume(ScrStream *s) {
|
|
2297
|
+
ScrStreamState *st = s->st;
|
|
2298
|
+
if (st->has_r && !st->destroyed && st->r.flowing != 1) {
|
|
2299
|
+
st->r.flowing = st->r.readable_listening ? 0 : 1;
|
|
2300
|
+
scr_stream_resume_kick(s);
|
|
2301
|
+
}
|
|
2302
|
+
return scr_stream_retain(s);
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
bool scr_stream_is_paused(ScrStream *s) { return s->st->r.flowing == 0; }
|
|
2306
|
+
|
|
2307
|
+
double scr_stream_flowing(ScrStream *s) {
|
|
2308
|
+
int f = s->st->r.flowing;
|
|
2309
|
+
return f < 0 ? -1 : f;
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
ScrStream *scr_stream_pipe(ScrStream *src, ScrStream *dst, bool end) {
|
|
2313
|
+
ScrStreamState *st = src->st;
|
|
2314
|
+
if (st->pipes.n == st->pipes.cap) {
|
|
2315
|
+
st->pipes.cap = st->pipes.cap ? st->pipes.cap * 2 : 2;
|
|
2316
|
+
st->pipes.dst = realloc(st->pipes.dst, st->pipes.cap * sizeof *st->pipes.dst);
|
|
2317
|
+
st->pipes.end = realloc(st->pipes.end, st->pipes.cap * sizeof *st->pipes.end);
|
|
2318
|
+
if (!st->pipes.dst || !st->pipes.end) scr_stream_oom();
|
|
2319
|
+
}
|
|
2320
|
+
st->pipes.dst[st->pipes.n] = scr_stream_retain(dst);
|
|
2321
|
+
st->pipes.end[st->pipes.n] = end;
|
|
2322
|
+
st->pipes.n++;
|
|
2323
|
+
scr_stream_emit_stream(dst, "pipe", src); /* Node emits 'pipe' synchronously */
|
|
2324
|
+
if (!scr_exc_pending() && st->r.flowing != 1 && st->await_drain == 0) {
|
|
2325
|
+
st->r.flowing = 1;
|
|
2326
|
+
scr_stream_resume_kick(src);
|
|
2327
|
+
}
|
|
2328
|
+
return scr_stream_retain(dst);
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
ScrStream *scr_stream_unpipe(ScrStream *src, ScrStream *dst) {
|
|
2332
|
+
ScrStreamState *st = src->st;
|
|
2333
|
+
for (size_t i = 0; i < st->pipes.n;) {
|
|
2334
|
+
if (dst == NULL || st->pipes.dst[i] == dst) {
|
|
2335
|
+
ScrStream *d = st->pipes.dst[i];
|
|
2336
|
+
memmove(st->pipes.dst + i, st->pipes.dst + i + 1, (st->pipes.n - i - 1) * sizeof *st->pipes.dst);
|
|
2337
|
+
memmove(st->pipes.end + i, st->pipes.end + i + 1, (st->pipes.n - i - 1) * sizeof *st->pipes.end);
|
|
2338
|
+
st->pipes.n--;
|
|
2339
|
+
st->r.flowing = 0; /* Node pauses the source on unpipe */
|
|
2340
|
+
scr_stream_emit_stream(d, "unpipe", src);
|
|
2341
|
+
scr_stream_release(d);
|
|
2342
|
+
if (scr_exc_pending()) break;
|
|
2343
|
+
if (dst != NULL) break;
|
|
2344
|
+
} else {
|
|
2345
|
+
i++;
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
return scr_stream_retain(src);
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
/* ── the writable surface ─────────────────────────────────────────────── */
|
|
2352
|
+
|
|
2353
|
+
static bool scr_stream_write_chunk(ScrStream *s, ScrBytes *chunk /*borrowed*/, ScrClosure *cb /*moves*/) {
|
|
2354
|
+
ScrStreamState *st = s->st;
|
|
2355
|
+
if (!st->has_w || st->destroyed || st->w.ending) {
|
|
2356
|
+
ScrError *e = st->has_w && !st->destroyed
|
|
2357
|
+
? scr_stream_mkerr("ERR_STREAM_WRITE_AFTER_END", "write after end")
|
|
2358
|
+
: scr_stream_mkerr("ERR_STREAM_DESTROYED", "Cannot call write after a stream was destroyed");
|
|
2359
|
+
if (cb) scr_st_tick(s, SCR_ST_WCB_ERR, NULL, cb);
|
|
2360
|
+
scr_stream_error_or_destroy(s, e);
|
|
2361
|
+
scr_error_release(e);
|
|
2362
|
+
return false;
|
|
2363
|
+
}
|
|
2364
|
+
size_t len = chunk->len;
|
|
2365
|
+
st->w.length += len;
|
|
2366
|
+
if (st->w.writing || st->w.corked > 0) {
|
|
2367
|
+
scr_stream_wq_push(st, scr_bytes_retain(chunk), cb);
|
|
2368
|
+
} else {
|
|
2369
|
+
scr_stream_do_write(s, scr_bytes_retain(chunk), cb);
|
|
2370
|
+
}
|
|
2371
|
+
/* Node computes the answer AFTER the (possibly synchronous) write:
|
|
2372
|
+
* a sync completion has already drained its bytes from length. */
|
|
2373
|
+
bool ret = st->w.length < st->w.hwm && !st->destroyed && !st->errored;
|
|
2374
|
+
if (!ret) st->w.need_drain = true;
|
|
2375
|
+
return ret;
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
bool scr_stream_write(ScrStream *s, ScrBytes *chunk, ScrClosure *cb) {
|
|
2379
|
+
return scr_stream_write_chunk(s, chunk, cb);
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
/* write(null) reached at runtime (a nullable-union chunk's null arm):
|
|
2383
|
+
* Node throws the ERR_STREAM_NULL_VALUES TypeError synchronously. */
|
|
2384
|
+
bool scr_stream_write_null(ScrStream *s) {
|
|
2385
|
+
(void)s;
|
|
2386
|
+
static const char msg[] = "May not write null values to stream";
|
|
2387
|
+
scr_throw_error_msg_code(SCR_ERR_TYPE, msg, sizeof msg - 1, "ERR_STREAM_NULL_VALUES");
|
|
2388
|
+
return false;
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
bool scr_stream_write_str(ScrStream *s, ScrStr *str, ScrClosure *cb) {
|
|
2392
|
+
ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)str->len);
|
|
2393
|
+
memcpy(b->data, str->data, str->len);
|
|
2394
|
+
bool ret = scr_stream_write_chunk(s, b, cb);
|
|
2395
|
+
scr_bytes_release(b);
|
|
2396
|
+
return ret;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
ScrStream *scr_stream_end(ScrStream *s, ScrBytes *chunk_b, ScrStr *chunk_s, ScrClosure *cb) {
|
|
2400
|
+
ScrStreamState *st = s->st;
|
|
2401
|
+
if (chunk_b) scr_stream_write(s, chunk_b, NULL);
|
|
2402
|
+
else if (chunk_s) scr_stream_write_str(s, chunk_s, NULL);
|
|
2403
|
+
if (!st->has_w || st->destroyed) {
|
|
2404
|
+
if (cb) scr_closure_release(cb);
|
|
2405
|
+
return scr_stream_retain(s);
|
|
2406
|
+
}
|
|
2407
|
+
if (cb) {
|
|
2408
|
+
if (st->w.finished) {
|
|
2409
|
+
scr_st_tick(s, SCR_ST_WCB_ERR, NULL, cb); /* already finished: plain call */
|
|
2410
|
+
} else {
|
|
2411
|
+
if (st->w.end_cbs_n == st->w.end_cbs_cap) {
|
|
2412
|
+
st->w.end_cbs_cap = st->w.end_cbs_cap ? st->w.end_cbs_cap * 2 : 2;
|
|
2413
|
+
st->w.end_cbs = realloc(st->w.end_cbs, st->w.end_cbs_cap * sizeof *st->w.end_cbs);
|
|
2414
|
+
if (!st->w.end_cbs) scr_stream_oom();
|
|
2415
|
+
}
|
|
2416
|
+
st->w.end_cbs[st->w.end_cbs_n++] = cb;
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
if (!st->w.ending) {
|
|
2420
|
+
st->w.ending = true;
|
|
2421
|
+
if (st->w.corked > 0) {
|
|
2422
|
+
st->w.corked = 1;
|
|
2423
|
+
scr_stream_uncork(s);
|
|
2424
|
+
}
|
|
2425
|
+
scr_stream_finish_maybe(s);
|
|
2426
|
+
}
|
|
2427
|
+
return scr_stream_retain(s);
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
void scr_stream_cork(ScrStream *s) { s->st->w.corked++; }
|
|
2431
|
+
|
|
2432
|
+
void scr_stream_uncork(ScrStream *s) {
|
|
2433
|
+
ScrStreamState *st = s->st;
|
|
2434
|
+
if (st->w.corked > 0 && --st->w.corked == 0) {
|
|
2435
|
+
scr_stream_clear_buffer(s);
|
|
2436
|
+
scr_stream_finish_maybe(s);
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
/* ── completion entries (called by the emitted done closures) ─────────── */
|
|
2441
|
+
|
|
2442
|
+
void scr_stream_write_done(ScrStream *s, ScrError *err /*moves*/) {
|
|
2443
|
+
ScrStreamState *st = s->st;
|
|
2444
|
+
if (!st->w.writing) {
|
|
2445
|
+
/* cb called twice (or spuriously): Node's ERR_MULTIPLE_CALLBACK */
|
|
2446
|
+
if (err) scr_error_release(err);
|
|
2447
|
+
ScrError *e = scr_stream_mkerr("ERR_MULTIPLE_CALLBACK", "Callback called multiple times");
|
|
2448
|
+
scr_stream_error_or_destroy(s, e);
|
|
2449
|
+
scr_error_release(e);
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
st->w.writing = false;
|
|
2453
|
+
st->w.length -= st->w.inflight_len;
|
|
2454
|
+
st->w.inflight_len = 0;
|
|
2455
|
+
ScrClosure *ucb = st->w.inflight_cb;
|
|
2456
|
+
st->w.inflight_cb = NULL;
|
|
2457
|
+
if (err) {
|
|
2458
|
+
if (ucb) scr_st_tick(s, SCR_ST_WCB_ERR, NULL, ucb);
|
|
2459
|
+
scr_stream_error_or_destroy(s, err);
|
|
2460
|
+
scr_error_release(err);
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
/* Node's onwrite: the buffered queue restarts FIRST (clearBuffer),
|
|
2464
|
+
* then afterWrite — deferred to a tick for a synchronous completion. */
|
|
2465
|
+
bool was_sync = st->w.wsync;
|
|
2466
|
+
scr_stream_clear_buffer(s);
|
|
2467
|
+
if (scr_exc_pending()) {
|
|
2468
|
+
if (ucb) scr_closure_release(ucb);
|
|
2469
|
+
return;
|
|
2470
|
+
}
|
|
2471
|
+
if (was_sync) {
|
|
2472
|
+
scr_st_tick(s, SCR_ST_AFTER_WRITE, NULL, ucb);
|
|
2473
|
+
} else {
|
|
2474
|
+
scr_stream_after_write(s, ucb);
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
void scr_stream_final_done(ScrStream *s, ScrError *err /*moves*/) {
|
|
2479
|
+
ScrStreamState *st = s->st;
|
|
2480
|
+
if (err) {
|
|
2481
|
+
scr_stream_error_or_destroy(s, err);
|
|
2482
|
+
scr_error_release(err);
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
st->w.prefinished = true;
|
|
2486
|
+
scr_stream_emit0(s, "prefinish");
|
|
2487
|
+
if (scr_exc_pending()) return;
|
|
2488
|
+
scr_stream_finish_maybe(s);
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
void scr_stream_destroy_done(ScrStream *s, ScrError *err /*moves*/) {
|
|
2492
|
+
ScrStreamState *st = s->st;
|
|
2493
|
+
/* Node's onDestroy: 'error' emits only with the error the USER'S
|
|
2494
|
+
* callback passed along — a `cb()` after destroy(err) SWALLOWS the
|
|
2495
|
+
* error (errored stays set, no emission; the default _destroy forwards,
|
|
2496
|
+
* which is the no-destroy-callback path in do_destroy). */
|
|
2497
|
+
if (err) {
|
|
2498
|
+
if (!st->errored) st->errored = scr_error_retain(err);
|
|
2499
|
+
if (!st->error_scheduled) {
|
|
2500
|
+
st->error_scheduled = true;
|
|
2501
|
+
scr_st_tick(s, SCR_ST_ERROR, err, NULL);
|
|
2502
|
+
} else {
|
|
2503
|
+
scr_error_release(err);
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
if (!st->close_scheduled) {
|
|
2507
|
+
st->close_scheduled = true;
|
|
2508
|
+
scr_st_tick(s, SCR_ST_CLOSE, NULL, NULL);
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
void scr_stream_transform_done(ScrStream *s, ScrError *err /*moves*/, ScrBytes *data /*moves*/,
|
|
2513
|
+
ScrStr *data_str /*moves*/) {
|
|
2514
|
+
if (!err) {
|
|
2515
|
+
if (data) {
|
|
2516
|
+
scr_stream_add_chunk(s, data, false);
|
|
2517
|
+
} else if (data_str) {
|
|
2518
|
+
ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)data_str->len);
|
|
2519
|
+
memcpy(b->data, data_str->data, data_str->len);
|
|
2520
|
+
scr_stream_add_chunk(s, b, false);
|
|
2521
|
+
scr_bytes_release(b);
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
if (data) scr_bytes_release(data);
|
|
2525
|
+
if (data_str) scr_str_release(data_str);
|
|
2526
|
+
if (scr_exc_pending()) {
|
|
2527
|
+
if (err) scr_error_release(err);
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
scr_stream_write_done(s, err);
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
void scr_stream_flush_done(ScrStream *s, ScrError *err /*moves*/, ScrBytes *data /*moves*/,
|
|
2534
|
+
ScrStr *data_str /*moves*/) {
|
|
2535
|
+
ScrStreamState *st = s->st;
|
|
2536
|
+
if (err) {
|
|
2537
|
+
if (data) scr_bytes_release(data);
|
|
2538
|
+
if (data_str) scr_str_release(data_str);
|
|
2539
|
+
scr_stream_error_or_destroy(s, err);
|
|
2540
|
+
scr_error_release(err);
|
|
2541
|
+
return;
|
|
2542
|
+
}
|
|
2543
|
+
if (data) {
|
|
2544
|
+
scr_stream_add_chunk(s, data, false);
|
|
2545
|
+
scr_bytes_release(data);
|
|
2546
|
+
} else if (data_str) {
|
|
2547
|
+
ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)data_str->len);
|
|
2548
|
+
memcpy(b->data, data_str->data, data_str->len);
|
|
2549
|
+
scr_stream_add_chunk(s, b, false);
|
|
2550
|
+
scr_bytes_release(b);
|
|
2551
|
+
scr_str_release(data_str);
|
|
2552
|
+
}
|
|
2553
|
+
if (scr_exc_pending()) return;
|
|
2554
|
+
scr_stream_push_null(s); /* EOF the readable side after flush */
|
|
2555
|
+
if (scr_exc_pending()) return;
|
|
2556
|
+
scr_stream_finish_maybe(s);
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
/* ── destroy ──────────────────────────────────────────────────────────── */
|
|
2560
|
+
|
|
2561
|
+
static void scr_stream_do_destroy(ScrStream *s, ScrError *err /*borrowed*/) {
|
|
2562
|
+
ScrStreamState *st = s->st;
|
|
2563
|
+
if (st->destroyed || st->destroy_calling) return;
|
|
2564
|
+
st->destroyed = true;
|
|
2565
|
+
if (err && !st->errored) st->errored = scr_error_retain(err);
|
|
2566
|
+
scr_stream_settle_next(s); /* a parked for-await rejects/finishes */
|
|
2567
|
+
if (st->destroy_cb) {
|
|
2568
|
+
st->destroy_calling = true;
|
|
2569
|
+
st->destroy_inv(st->destroy_cb, s, err);
|
|
2570
|
+
st->destroy_calling = false;
|
|
2571
|
+
if (scr_exc_pending()) return;
|
|
2572
|
+
/* the user's destroy must call its callback (destroy_done schedules
|
|
2573
|
+
* 'error'/'close'); a user destroy that never calls back leaves the
|
|
2574
|
+
* stream half-closed, exactly Node */
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
if (err) {
|
|
2578
|
+
if (!st->error_scheduled) {
|
|
2579
|
+
st->error_scheduled = true;
|
|
2580
|
+
scr_st_tick(s, SCR_ST_ERROR, scr_error_retain(err), NULL);
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
if (!st->close_scheduled) {
|
|
2584
|
+
st->close_scheduled = true;
|
|
2585
|
+
scr_st_tick(s, SCR_ST_CLOSE, NULL, NULL);
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
ScrStream *scr_stream_destroy(ScrStream *s, ScrError *err) {
|
|
2590
|
+
scr_stream_do_destroy(s, err);
|
|
2591
|
+
return scr_stream_retain(s);
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
ScrError *scr_stream_errored(ScrStream *s) {
|
|
2595
|
+
return s->st->errored ? scr_error_retain(s->st->errored) : NULL;
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
/* ── properties ───────────────────────────────────────────────────────── */
|
|
2599
|
+
|
|
2600
|
+
double scr_stream_prop(ScrStream *s, const char *name) {
|
|
2601
|
+
ScrStreamState *st = s->st;
|
|
2602
|
+
if (strcmp(name, "readable") == 0) {
|
|
2603
|
+
return st->has_r && !st->destroyed && !st->r.end_emitted && !st->errored;
|
|
2604
|
+
}
|
|
2605
|
+
if (strcmp(name, "readableEnded") == 0) return st->r.end_emitted;
|
|
2606
|
+
if (strcmp(name, "readableLength") == 0) return (double)st->r.length;
|
|
2607
|
+
if (strcmp(name, "readableHighWaterMark") == 0) return (double)st->r.hwm;
|
|
2608
|
+
if (strcmp(name, "readableObjectMode") == 0) return st->r.object_entries;
|
|
2609
|
+
if (strcmp(name, "writable") == 0) {
|
|
2610
|
+
return st->has_w && !st->destroyed && !st->w.ending && !st->errored;
|
|
2611
|
+
}
|
|
2612
|
+
if (strcmp(name, "writableEnded") == 0) return st->w.ending;
|
|
2613
|
+
if (strcmp(name, "writableFinished") == 0) return st->w.finished;
|
|
2614
|
+
if (strcmp(name, "writableNeedDrain") == 0) return st->w.need_drain;
|
|
2615
|
+
if (strcmp(name, "writableLength") == 0) return (double)st->w.length;
|
|
2616
|
+
if (strcmp(name, "writableHighWaterMark") == 0) return (double)st->w.hwm;
|
|
2617
|
+
if (strcmp(name, "writableCorked") == 0) return (double)st->w.corked;
|
|
2618
|
+
if (strcmp(name, "destroyed") == 0) return st->destroyed;
|
|
2619
|
+
if (strcmp(name, "closed") == 0) return st->close_emitted;
|
|
2620
|
+
if (strcmp(name, "allowHalfOpen") == 0) return st->allow_half_open;
|
|
2621
|
+
/* The _readableState/_writableState compat VIEW (read-only): each name
|
|
2622
|
+
* answers from the field the runtime actually keeps. Node's ending/
|
|
2623
|
+
* ended distinction inside end() collapses onto w.ending here. */
|
|
2624
|
+
if (strncmp(name, "rs:", 3) == 0) {
|
|
2625
|
+
const char *p = name + 3;
|
|
2626
|
+
if (strcmp(p, "ended") == 0) return st->r.ended;
|
|
2627
|
+
if (strcmp(p, "endEmitted") == 0) return st->r.end_emitted;
|
|
2628
|
+
if (strcmp(p, "destroyed") == 0) return st->destroyed;
|
|
2629
|
+
if (strcmp(p, "errorEmitted") == 0) return st->error_emitted;
|
|
2630
|
+
if (strcmp(p, "emittedReadable") == 0) return st->r.emitted_readable;
|
|
2631
|
+
if (strcmp(p, "needReadable") == 0) return st->r.need_readable;
|
|
2632
|
+
if (strcmp(p, "reading") == 0) return st->r.reading;
|
|
2633
|
+
if (strcmp(p, "readableListening") == 0) return st->r.readable_listening;
|
|
2634
|
+
if (strcmp(p, "resumeScheduled") == 0) return st->r.resume_scheduled;
|
|
2635
|
+
if (strcmp(p, "objectMode") == 0) return st->r.object_entries;
|
|
2636
|
+
if (strcmp(p, "constructed") == 0) return 1;
|
|
2637
|
+
if (strcmp(p, "closed") == 0) return st->close_emitted;
|
|
2638
|
+
if (strcmp(p, "length") == 0) return (double)st->r.length;
|
|
2639
|
+
if (strcmp(p, "highWaterMark") == 0) return (double)st->r.hwm;
|
|
2640
|
+
return 0;
|
|
2641
|
+
}
|
|
2642
|
+
if (strncmp(name, "ws:", 3) == 0) {
|
|
2643
|
+
const char *p = name + 3;
|
|
2644
|
+
if (strcmp(p, "ended") == 0 || strcmp(p, "ending") == 0) return st->w.ending;
|
|
2645
|
+
if (strcmp(p, "finished") == 0) return st->w.finished;
|
|
2646
|
+
if (strcmp(p, "prefinished") == 0) return st->w.prefinished;
|
|
2647
|
+
if (strcmp(p, "destroyed") == 0) return st->destroyed;
|
|
2648
|
+
if (strcmp(p, "errorEmitted") == 0) return st->error_emitted;
|
|
2649
|
+
if (strcmp(p, "needDrain") == 0) return st->w.need_drain;
|
|
2650
|
+
if (strcmp(p, "objectMode") == 0) return 0;
|
|
2651
|
+
if (strcmp(p, "constructed") == 0) return 1;
|
|
2652
|
+
if (strcmp(p, "closed") == 0) return st->close_emitted;
|
|
2653
|
+
if (strcmp(p, "length") == 0) return (double)st->w.length;
|
|
2654
|
+
if (strcmp(p, "highWaterMark") == 0) return (double)st->w.hwm;
|
|
2655
|
+
if (strcmp(p, "corked") == 0) return (double)st->w.corked;
|
|
2656
|
+
if (strcmp(p, "bufferedRequestCount") == 0) return (double)(st->w.n - st->w.head);
|
|
2657
|
+
return 0;
|
|
2658
|
+
}
|
|
2659
|
+
return 0;
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
/* emitReadable_ — the shared body of the 'readable' tick AND the
|
|
2663
|
+
* synchronous onEofChunk call (Node runs it directly when the eof push
|
|
2664
|
+
* arrives outside a _read call). */
|
|
2665
|
+
static void scr_stream_emit_readable_now(ScrStream *s) {
|
|
2666
|
+
ScrStreamState *st = s->st;
|
|
2667
|
+
st->r.emitted_readable = false;
|
|
2668
|
+
if (!st->destroyed && !st->errored && (st->r.length > 0 || st->r.ended)) {
|
|
2669
|
+
scr_stream_emit0(s, "readable");
|
|
2670
|
+
if (scr_exc_pending()) return;
|
|
2671
|
+
}
|
|
2672
|
+
st->r.need_readable = st->r.flowing != 1 && !st->r.ended &&
|
|
2673
|
+
st->r.length <= st->r.hwm;
|
|
2674
|
+
scr_stream_flow(s);
|
|
2675
|
+
if (!scr_exc_pending() && st->r.ended && st->r.length == 0) {
|
|
2676
|
+
scr_stream_end_readable(s);
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
/* ── tick dispatch ────────────────────────────────────────────────────── */
|
|
2681
|
+
|
|
2682
|
+
static void scr_stream_run_tick(ScrStreamTick *t) {
|
|
2683
|
+
ScrStream *s = t->s;
|
|
2684
|
+
ScrStreamState *st = s->st;
|
|
2685
|
+
switch (t->op) {
|
|
2686
|
+
case SCR_ST_RESUME: {
|
|
2687
|
+
st->r.resume_scheduled = false;
|
|
2688
|
+
if (!st->r.reading && st->r.flowing == 1) {
|
|
2689
|
+
/* read(0) kick: pull the user _read without consuming */
|
|
2690
|
+
st->r.need_readable = st->r.length == 0;
|
|
2691
|
+
if (!st->r.ended && !st->destroyed) scr_stream_call_read(s);
|
|
2692
|
+
if (scr_exc_pending()) break;
|
|
2693
|
+
}
|
|
2694
|
+
scr_stream_emit0(s, "resume");
|
|
2695
|
+
if (scr_exc_pending()) break;
|
|
2696
|
+
scr_stream_flow(s);
|
|
2697
|
+
if (scr_exc_pending()) break;
|
|
2698
|
+
if (st->r.flowing == 1 && !st->r.reading && !st->r.ended && !st->destroyed) {
|
|
2699
|
+
scr_stream_call_read(s);
|
|
2700
|
+
}
|
|
2701
|
+
break;
|
|
2702
|
+
}
|
|
2703
|
+
case SCR_ST_READABLE:
|
|
2704
|
+
scr_stream_emit_readable_now(s);
|
|
2705
|
+
break;
|
|
2706
|
+
case SCR_ST_END: {
|
|
2707
|
+
st->r.end_scheduled = false;
|
|
2708
|
+
if (st->destroyed || st->errored || st->r.end_emitted || st->r.length != 0 || !st->r.ended) break;
|
|
2709
|
+
st->r.end_emitted = true;
|
|
2710
|
+
scr_stream_emit0(s, "end");
|
|
2711
|
+
if (scr_exc_pending()) break;
|
|
2712
|
+
/* pipe end propagation */
|
|
2713
|
+
for (size_t i = 0; i < st->pipes.n; i++) {
|
|
2714
|
+
if (st->pipes.end[i]) {
|
|
2715
|
+
scr_stream_release(scr_stream_end(st->pipes.dst[i], NULL, NULL, NULL));
|
|
2716
|
+
if (scr_exc_pending()) break;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
if (scr_exc_pending()) break;
|
|
2720
|
+
if (st->has_w && !st->allow_half_open && !st->w.ending) {
|
|
2721
|
+
scr_st_tick(s, SCR_ST_END_W, NULL, NULL);
|
|
2722
|
+
break;
|
|
2723
|
+
}
|
|
2724
|
+
if (st->auto_destroy && (!st->has_w || st->w.finished)) {
|
|
2725
|
+
scr_stream_do_destroy(s, NULL);
|
|
2726
|
+
}
|
|
2727
|
+
break;
|
|
2728
|
+
}
|
|
2729
|
+
case SCR_ST_MAYBE_MORE: {
|
|
2730
|
+
st->r.maybe_more_scheduled = false;
|
|
2731
|
+
if (st->destroyed) break;
|
|
2732
|
+
/* loop while synchronous pushes make progress below the hwm */
|
|
2733
|
+
while (!st->r.reading && !st->r.ended && st->r.length < st->r.hwm &&
|
|
2734
|
+
(st->r.flowing == 1 || st->r.need_readable) && !scr_exc_pending()) {
|
|
2735
|
+
size_t before = st->r.length;
|
|
2736
|
+
scr_stream_call_read(s);
|
|
2737
|
+
if (scr_exc_pending() || st->r.length == before) break;
|
|
2738
|
+
}
|
|
2739
|
+
break;
|
|
2740
|
+
}
|
|
2741
|
+
case SCR_ST_AFTER_WRITE: {
|
|
2742
|
+
st->w.after_scheduled = false;
|
|
2743
|
+
ScrClosure *cb = t->cb;
|
|
2744
|
+
t->cb = NULL;
|
|
2745
|
+
if (!st->destroyed) {
|
|
2746
|
+
scr_stream_after_write(s, cb);
|
|
2747
|
+
} else if (cb) {
|
|
2748
|
+
((void (*)(ScrClosure *))cb->fn)(cb);
|
|
2749
|
+
scr_closure_release(cb);
|
|
2750
|
+
}
|
|
2751
|
+
break;
|
|
2752
|
+
}
|
|
2753
|
+
case SCR_ST_FINISH: {
|
|
2754
|
+
st->w.finish_scheduled = false;
|
|
2755
|
+
if (st->destroyed || st->w.finished) break;
|
|
2756
|
+
st->w.finished = true;
|
|
2757
|
+
/* Node runs the end(cb) callbacks (kOnFinished) BEFORE 'finish'. */
|
|
2758
|
+
while (st->w.end_cbs_n > 0) {
|
|
2759
|
+
ScrClosure *cb = st->w.end_cbs[0];
|
|
2760
|
+
memmove(st->w.end_cbs, st->w.end_cbs + 1, (st->w.end_cbs_n - 1) * sizeof *st->w.end_cbs);
|
|
2761
|
+
st->w.end_cbs_n--;
|
|
2762
|
+
((void (*)(ScrClosure *))cb->fn)(cb);
|
|
2763
|
+
scr_closure_release(cb);
|
|
2764
|
+
if (scr_exc_pending()) break;
|
|
2765
|
+
}
|
|
2766
|
+
if (scr_exc_pending()) break;
|
|
2767
|
+
scr_stream_emit0(s, "finish");
|
|
2768
|
+
if (scr_exc_pending()) break;
|
|
2769
|
+
if (st->auto_destroy && (!st->has_r || st->r.end_emitted || !st->r.readable_listening)) {
|
|
2770
|
+
/* Node: autoDestroy after finish once the readable half is done
|
|
2771
|
+
* (a half-open duplex with a live readable side stays up) */
|
|
2772
|
+
if (!st->has_r || st->r.end_emitted) scr_stream_do_destroy(s, NULL);
|
|
2773
|
+
}
|
|
2774
|
+
break;
|
|
2775
|
+
}
|
|
2776
|
+
case SCR_ST_ERROR: {
|
|
2777
|
+
st->error_scheduled = false;
|
|
2778
|
+
if (!st->error_emitted && t->err) {
|
|
2779
|
+
st->error_emitted = true;
|
|
2780
|
+
if (st->next_err_consumed && !scr_emitter_has((ScrEmitter *)s, "error")) {
|
|
2781
|
+
/* the for-await's rejection consumed it (Node's iterator
|
|
2782
|
+
* registers its own 'error' handler — no unhandled crash) */
|
|
2783
|
+
break;
|
|
2784
|
+
}
|
|
2785
|
+
if (st->fin.n > 0 && !scr_emitter_has((ScrEmitter *)s, "error")) {
|
|
2786
|
+
/* a finished()/pipeline watcher consumes it (Node's eos and
|
|
2787
|
+
* pipeline register their own 'error' handlers) — the watcher
|
|
2788
|
+
* reports it at the terminal point instead of crashing */
|
|
2789
|
+
break;
|
|
2790
|
+
}
|
|
2791
|
+
scr_stream_emit_error(s, t->err);
|
|
2792
|
+
}
|
|
2793
|
+
break;
|
|
2794
|
+
}
|
|
2795
|
+
case SCR_ST_CLOSE: {
|
|
2796
|
+
st->close_scheduled = false;
|
|
2797
|
+
if (!st->close_emitted) {
|
|
2798
|
+
st->close_emitted = true;
|
|
2799
|
+
if (st->emit_close) scr_stream_emit0(s, "close");
|
|
2800
|
+
/* eos ordering: the finished()/pipeline watchers fire right after
|
|
2801
|
+
* 'close' (Node's willEmitClose stance). */
|
|
2802
|
+
scr_stream_notify_finished(s);
|
|
2803
|
+
/* Fully closed: nothing calls the option callbacks again (Node's
|
|
2804
|
+
* contract), so drop them here — this breaks the reference cycle
|
|
2805
|
+
* a callback capturing its own stream forms, including the dyn-
|
|
2806
|
+
* options closures whose stream edge rides through a DOM function
|
|
2807
|
+
* value the cycle collector cannot traverse. */
|
|
2808
|
+
if (st->r.read_cb) { scr_closure_release(st->r.read_cb); st->r.read_cb = NULL; }
|
|
2809
|
+
if (st->w.write_cb) { scr_closure_release(st->w.write_cb); st->w.write_cb = NULL; }
|
|
2810
|
+
if (st->w.final_cb) { scr_closure_release(st->w.final_cb); st->w.final_cb = NULL; }
|
|
2811
|
+
if (st->transform_cb) { scr_closure_release(st->transform_cb); st->transform_cb = NULL; }
|
|
2812
|
+
if (st->flush_cb) { scr_closure_release(st->flush_cb); st->flush_cb = NULL; }
|
|
2813
|
+
if (st->destroy_cb) { scr_closure_release(st->destroy_cb); st->destroy_cb = NULL; }
|
|
2814
|
+
}
|
|
2815
|
+
break;
|
|
2816
|
+
}
|
|
2817
|
+
case SCR_ST_END_W: {
|
|
2818
|
+
scr_stream_release(scr_stream_end(s, NULL, NULL, NULL));
|
|
2819
|
+
break;
|
|
2820
|
+
}
|
|
2821
|
+
case SCR_ST_WCB_ERR: {
|
|
2822
|
+
ScrClosure *cb = t->cb;
|
|
2823
|
+
t->cb = NULL;
|
|
2824
|
+
if (cb) {
|
|
2825
|
+
((void (*)(ScrClosure *))cb->fn)(cb);
|
|
2826
|
+
scr_closure_release(cb);
|
|
2827
|
+
}
|
|
2828
|
+
break;
|
|
2829
|
+
}
|
|
2830
|
+
case SCR_ST_FIN: {
|
|
2831
|
+
scr_stream_notify_finished(s);
|
|
2832
|
+
break;
|
|
2833
|
+
}
|
|
2834
|
+
case SCR_ST_NEXT_EOF: {
|
|
2835
|
+
if (st->r.next_waiter && st->r.length == 0 && (st->r.ended || st->destroyed)) {
|
|
2836
|
+
ScrPromise *w = st->r.next_waiter;
|
|
2837
|
+
st->r.next_waiter = NULL;
|
|
2838
|
+
scr_stream_next_fulfill(st, w, NULL);
|
|
2839
|
+
} else if (st->r.next_waiter) {
|
|
2840
|
+
scr_stream_settle_next(s); /* content raced in: deliver it */
|
|
2841
|
+
}
|
|
2842
|
+
break;
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
static void scr_stream_dispatch(void) {
|
|
2848
|
+
while (scr_st_head != NULL && !scr_exc_pending() && !scr_loop_has_ready()) {
|
|
2849
|
+
ScrStreamTick *t = scr_st_head;
|
|
2850
|
+
scr_st_head = t->next;
|
|
2851
|
+
if (scr_st_head == NULL) scr_st_tail = NULL;
|
|
2852
|
+
scr_stream_run_tick(t);
|
|
2853
|
+
scr_stream_release(t->s);
|
|
2854
|
+
if (t->err) scr_error_release(t->err);
|
|
2855
|
+
if (t->cb) scr_closure_release(t->cb);
|
|
2856
|
+
free(t);
|
|
2857
|
+
}
|
|
2858
|
+
/* An uncaught throw ends the process (the loop returns to main): the
|
|
2859
|
+
* undelivered ticks are DROPPED — release their references here so the
|
|
2860
|
+
* RC audit stays clean (the children-teardown precedent). */
|
|
2861
|
+
if (scr_exc_pending()) {
|
|
2862
|
+
while (scr_st_head != NULL) {
|
|
2863
|
+
ScrStreamTick *t = scr_st_head;
|
|
2864
|
+
scr_st_head = t->next;
|
|
2865
|
+
scr_stream_release(t->s);
|
|
2866
|
+
if (t->err) scr_error_release(t->err);
|
|
2867
|
+
if (t->cb) scr_closure_release(t->cb);
|
|
2868
|
+
free(t);
|
|
2869
|
+
}
|
|
2870
|
+
scr_st_tail = NULL;
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
void scr_stream_install(void) {
|
|
2875
|
+
scr_emitter_on_hook = &scr_stream_on_listener;
|
|
2876
|
+
scr_loop_set_stream(&scr_stream_ticks_pending, &scr_stream_dispatch);
|
|
2877
|
+
}
|