rubyduino 0.1.3 → 0.2.1
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.
- checksums.yaml +4 -4
- data/.gitmodules +1 -0
- data/CHANGELOG.md +12 -0
- data/README.md +1 -2
- data/examples/hc_sr04.rb +39 -0
- data/lib/rubyduino/arduino_uno.rb +76 -0
- data/lib/rubyduino/sp_runtime.h +287 -0
- data/lib/rubyduino/spinel.rb +1 -1
- data/lib/rubyduino/spinel_arduino_codegen.rb +53 -13
- data/lib/rubyduino/version.rb +1 -1
- data/vendor/spinel/Makefile +463 -54
- data/vendor/spinel/README.md +248 -68
- data/vendor/spinel/benchmark/bm_jekyll_lite.rb +241 -0
- data/vendor/spinel/benchmark/bm_micro_lisp.rb +282 -0
- data/vendor/spinel/benchmark/bm_poly_cells.rb +75 -0
- data/vendor/spinel/benchmark/bm_range_each.rb +15 -0
- data/vendor/spinel/benchmark/bm_sinatra_mini.rb +126 -0
- data/vendor/spinel/compiler_helpers.rb +1506 -0
- data/vendor/spinel/docs/ANALYZE-IR.md +411 -0
- data/vendor/spinel/docs/AST.md +360 -0
- data/vendor/spinel/docs/CLASS-OBJECT.md +521 -0
- data/vendor/spinel/docs/FFI.md +9 -0
- data/vendor/spinel/docs/FLOAT-ROUNDING.md +47 -0
- data/vendor/spinel/docs/HASH-NULLABLE.md +379 -0
- data/vendor/spinel/docs/INCOMPATIBILITIES.md +102 -0
- data/vendor/spinel/docs/RBS-EXTRACT.md +128 -0
- data/vendor/spinel/docs/fast-bootstrap.md +74 -0
- data/vendor/spinel/docs/issue-646-plan.md +159 -0
- data/vendor/spinel/docs/promote-mode-plan.md +227 -0
- data/vendor/spinel/docs/setbyte-semantic-plan.md +203 -0
- data/vendor/spinel/examples/ffi/sqlite/README.md +7 -6
- data/vendor/spinel/examples/ffi/sqlite/sqlite3_lib.rb +7 -4
- data/vendor/spinel/experiments/rbs/box.rb +32 -0
- data/vendor/spinel/experiments/rbs/box.seed +18 -0
- data/vendor/spinel/experiments/rbs/harness.rb +117 -0
- data/vendor/spinel/lib/regexp/re_compile.c +265 -25
- data/vendor/spinel/lib/regexp/re_exec.c +80 -19
- data/vendor/spinel/lib/regexp/re_internal.h +22 -2
- data/vendor/spinel/lib/regexp/re_utf8.c +21 -2
- data/vendor/spinel/lib/sp_bigint.c +67 -3
- data/vendor/spinel/lib/sp_core.c +248 -0
- data/vendor/spinel/lib/sp_core.h +37 -0
- data/vendor/spinel/lib/sp_crypto.c +583 -0
- data/vendor/spinel/lib/sp_crypto.h +79 -0
- data/vendor/spinel/lib/sp_fiber.c +95 -0
- data/vendor/spinel/lib/sp_fiber.h +41 -0
- data/vendor/spinel/lib/sp_gc.c +89 -0
- data/vendor/spinel/lib/sp_gc.h +81 -0
- data/vendor/spinel/lib/sp_io.c +98 -0
- data/vendor/spinel/lib/sp_io.h +31 -0
- data/vendor/spinel/lib/sp_net.c +350 -0
- data/vendor/spinel/lib/sp_net.h +97 -0
- data/vendor/spinel/lib/sp_pack.c +431 -0
- data/vendor/spinel/lib/sp_runtime.h +3485 -341
- data/vendor/spinel/lib/sp_strscan.c +320 -0
- data/vendor/spinel/lib/sp_system.c +173 -0
- data/vendor/spinel/lib/sp_system.h +16 -0
- data/vendor/spinel/lib/sp_time.c +204 -0
- data/vendor/spinel/lib/sp_time.h +63 -0
- data/vendor/spinel/lib/sp_types.h +144 -0
- data/vendor/spinel/lib/stringio.c +31 -3
- data/vendor/spinel/lib/strscan.c +34 -7
- data/vendor/spinel/lib/strscan.rb +6 -53
- data/vendor/spinel/node_table_loader.rb +150 -0
- data/vendor/spinel/spinel +319 -21
- data/vendor/spinel/spinel_analyze.rb +37556 -0
- data/vendor/spinel/spinel_codegen.rb +47620 -33346
- data/vendor/spinel/spinel_parse +0 -0
- data/vendor/spinel/spinel_parse.c +801 -91
- data/vendor/spinel/spinel_rbs_extract.c +931 -0
- data/vendor/spinel/test/alias_attr_reader_source.rb +38 -0
- data/vendor/spinel/test/alias_attr_reader_source.rb.expected +7 -0
- data/vendor/spinel/test/alias_inherited_method.rb +20 -0
- data/vendor/spinel/test/alias_inherited_method.rb.expected +3 -0
- data/vendor/spinel/test/alias_method_dispatch.rb +14 -0
- data/vendor/spinel/test/analyze_fail/attributes_non_symbol.rb +3 -0
- data/vendor/spinel/test/analyze_fail/attributes_non_symbol.stderr.expected +1 -0
- data/vendor/spinel/test/analyze_fail/dynamic_define_method.rb +4 -0
- data/vendor/spinel/test/analyze_fail/dynamic_define_method.stderr.expected +1 -0
- data/vendor/spinel/test/analyze_fail/instance_exec_def_in_block.rb +14 -0
- data/vendor/spinel/test/analyze_fail/instance_exec_def_in_block.stderr.expected +1 -0
- data/vendor/spinel/test/analyze_fail/instance_exec_define_method.rb +15 -0
- data/vendor/spinel/test/analyze_fail/instance_exec_define_method.stderr.expected +1 -0
- data/vendor/spinel/test/analyze_fail/instance_exec_no_block.rb +13 -0
- data/vendor/spinel/test/analyze_fail/instance_exec_no_block.stderr.expected +1 -0
- data/vendor/spinel/test/analyze_fail/nonliteral_define_method_each.rb +7 -0
- data/vendor/spinel/test/analyze_fail/nonliteral_define_method_each.stderr.expected +1 -0
- data/vendor/spinel/test/argv_gc.rb +45 -0
- data/vendor/spinel/test/argv_gc.rb.args +1 -0
- data/vendor/spinel/test/argv_gc.rb.expected +6 -0
- data/vendor/spinel/test/array_at_out_of_bounds.rb +23 -0
- data/vendor/spinel/test/array_at_out_of_bounds.rb.expected +12 -0
- data/vendor/spinel/test/array_clear_expr.rb +26 -0
- data/vendor/spinel/test/array_clear_expr.rb.expected +10 -0
- data/vendor/spinel/test/array_combination_block.rb +11 -0
- data/vendor/spinel/test/array_compact_eq.rb +16 -0
- data/vendor/spinel/test/array_compact_eq.rb.expected +10 -0
- data/vendor/spinel/test/array_fill_poly.rb.expected +1 -0
- data/vendor/spinel/test/array_first_last_empty.rb +12 -0
- data/vendor/spinel/test/array_first_last_empty.rb.expected +8 -0
- data/vendor/spinel/test/array_flatten_typed_elements.rb +19 -0
- data/vendor/spinel/test/array_frozen_mutate_raises.rb +36 -0
- data/vendor/spinel/test/array_frozen_mutate_raises.rb.expected +20 -0
- data/vendor/spinel/test/array_frozen_slice_bang.rb +19 -0
- data/vendor/spinel/test/array_frozen_slice_bang.rb.expected +2 -0
- data/vendor/spinel/test/array_get_oob_negative.rb +22 -0
- data/vendor/spinel/test/array_get_oob_negative.rb.expected +6 -0
- data/vendor/spinel/test/array_grep.rb +12 -0
- data/vendor/spinel/test/array_grep.rb.expected +8 -0
- data/vendor/spinel/test/array_index_int_opt_narrow.rb +30 -0
- data/vendor/spinel/test/array_index_int_opt_narrow.rb.expected +7 -0
- data/vendor/spinel/test/array_map_bang.rb +28 -0
- data/vendor/spinel/test/array_map_bang.rb.expected +7 -0
- data/vendor/spinel/test/array_minmax_block.rb +14 -0
- data/vendor/spinel/test/array_negative_index_too_small.rb +22 -0
- data/vendor/spinel/test/array_negative_index_too_small.rb.expected +5 -0
- data/vendor/spinel/test/array_object_id.rb +12 -0
- data/vendor/spinel/test/array_object_id.rb.expected +6 -0
- data/vendor/spinel/test/array_pack_returns_string.expected +2 -0
- data/vendor/spinel/test/array_pack_returns_string.rb +4 -0
- data/vendor/spinel/test/array_pack_string_unpack.rb +12 -0
- data/vendor/spinel/test/array_pattern_match_basic.rb +27 -0
- data/vendor/spinel/test/array_predicate_no_block.rb +14 -0
- data/vendor/spinel/test/array_predicate_no_block.rb.expected +11 -0
- data/vendor/spinel/test/array_predicate_no_block_param.rb +14 -0
- data/vendor/spinel/test/array_predicate_no_block_param.rb.expected +10 -0
- data/vendor/spinel/test/array_product.rb +5 -0
- data/vendor/spinel/test/array_push_multi_arg.rb +32 -0
- data/vendor/spinel/test/array_push_multi_arg.rb.expected +10 -0
- data/vendor/spinel/test/array_select_bang.rb +34 -0
- data/vendor/spinel/test/array_select_bang.rb.expected +8 -0
- data/vendor/spinel/test/array_slice_before_after.rb +6 -0
- data/vendor/spinel/test/array_slice_when_chunk.rb +7 -0
- data/vendor/spinel/test/array_to_h_sort_bang_block.rb +26 -0
- data/vendor/spinel/test/array_to_h_sort_bang_block.rb.expected +10 -0
- data/vendor/spinel/test/array_transpose_poly_elements.rb +34 -0
- data/vendor/spinel/test/array_transpose_typed_elements.rb +19 -0
- data/vendor/spinel/test/array_unresolved_inspect_no_segv.rb +9 -0
- data/vendor/spinel/test/array_unresolved_inspect_no_segv.rb.expected +2 -0
- data/vendor/spinel/test/at_exit_lifo.rb +6 -0
- data/vendor/spinel/test/at_exit_lifo.rb.expected +4 -0
- data/vendor/spinel/test/back_ref.rb +15 -1
- data/vendor/spinel/test/back_ref.rb.expected +5 -0
- data/vendor/spinel/test/bang_op_ruby_truthy.expected +7 -0
- data/vendor/spinel/test/bang_op_ruby_truthy.rb +10 -0
- data/vendor/spinel/test/bare_arg_in_module_body.rb +29 -0
- data/vendor/spinel/test/bare_sibling_cls_method_call.rb +67 -0
- data/vendor/spinel/test/bare_sibling_cls_method_call.rb.expected +4 -0
- data/vendor/spinel/test/begin_rescue_as_expression.expected +5 -0
- data/vendor/spinel/test/begin_rescue_as_expression.rb +14 -0
- data/vendor/spinel/test/begin_rescue_else_clause.rb +63 -0
- data/vendor/spinel/test/begin_rescue_else_clause.rb.expected +7 -0
- data/vendor/spinel/test/bigint_propagation_chain.rb +33 -0
- data/vendor/spinel/test/bigint_propagation_chain.rb.expected +9 -0
- data/vendor/spinel/test/bigint_volatile_combo.rb +69 -0
- data/vendor/spinel/test/bigint_volatile_combo.rb.expected +18 -0
- data/vendor/spinel/test/block_given_block_param.rb +21 -3
- data/vendor/spinel/test/block_given_block_param.rb.expected +2 -0
- data/vendor/spinel/test/block_given_inline_call_site.expected +2 -0
- data/vendor/spinel/test/block_given_inline_call_site.rb +9 -0
- data/vendor/spinel/test/block_optional_unused.rb +18 -0
- data/vendor/spinel/test/block_optional_unused.rb.expected +4 -0
- data/vendor/spinel/test/block_param_call_block_given.rb +46 -0
- data/vendor/spinel/test/block_param_call_block_given.rb.expected +6 -0
- data/vendor/spinel/test/block_param_call_value_return.rb +32 -0
- data/vendor/spinel/test/block_param_call_value_return.rb.expected +5 -0
- data/vendor/spinel/test/bm_instance_eval.rb +9 -8
- data/vendor/spinel/test/bool_case_equality.rb +10 -0
- data/vendor/spinel/test/bool_logical_operators.rb +9 -0
- data/vendor/spinel/test/bool_logical_operators.rb.expected +8 -0
- data/vendor/spinel/test/builtin_array_method_capture.rb +38 -0
- data/vendor/spinel/test/builtin_array_method_capture.rb.expected +11 -0
- data/vendor/spinel/test/bundle_array_e.rb +250 -0
- data/vendor/spinel/test/bundle_array_e.rb.expected +57 -0
- data/vendor/spinel/test/bundle_array_f.rb +169 -0
- data/vendor/spinel/test/bundle_class_01.rb +43 -0
- data/vendor/spinel/test/bundle_class_01.rb.expected +4 -0
- data/vendor/spinel/test/bundle_class_03.rb +57 -0
- data/vendor/spinel/test/bundle_class_03.rb.expected +6 -0
- data/vendor/spinel/test/bundle_class_05.rb +187 -0
- data/vendor/spinel/test/bundle_class_05.rb.expected +14 -0
- data/vendor/spinel/test/bundle_class_06.rb +201 -0
- data/vendor/spinel/test/bundle_class_06.rb.expected +12 -0
- data/vendor/spinel/test/bundle_class_07.rb +72 -0
- data/vendor/spinel/test/bundle_class_07.rb.expected +6 -0
- data/vendor/spinel/test/bundle_class_08.rb +101 -0
- data/vendor/spinel/test/bundle_class_08.rb.expected +9 -0
- data/vendor/spinel/test/bundle_class_09.rb +171 -0
- data/vendor/spinel/test/bundle_class_09.rb.expected +9 -0
- data/vendor/spinel/test/bundle_class_10.rb +189 -0
- data/vendor/spinel/test/bundle_class_10.rb.expected +5 -0
- data/vendor/spinel/test/bundle_class_11.rb +191 -0
- data/vendor/spinel/test/bundle_class_11.rb.expected +8 -0
- data/vendor/spinel/test/bundle_class_12.rb +234 -0
- data/vendor/spinel/test/bundle_class_12.rb.expected +15 -0
- data/vendor/spinel/test/bundle_class_13.rb +61 -0
- data/vendor/spinel/test/bundle_class_13.rb.expected +2 -0
- data/vendor/spinel/test/bundle_class_14.rb +162 -0
- data/vendor/spinel/test/bundle_class_14.rb.expected +10 -0
- data/vendor/spinel/test/bundle_class_15.rb +184 -0
- data/vendor/spinel/test/bundle_class_15.rb.expected +8 -0
- data/vendor/spinel/test/bundle_class_18.rb +101 -0
- data/vendor/spinel/test/bundle_class_18.rb.expected +13 -0
- data/vendor/spinel/test/bundle_class_19.rb +67 -0
- data/vendor/spinel/test/bundle_class_19.rb.expected +8 -0
- data/vendor/spinel/test/bundle_class_20.rb +122 -0
- data/vendor/spinel/test/bundle_class_20.rb.expected +6 -0
- data/vendor/spinel/test/bundle_class_21.rb +208 -0
- data/vendor/spinel/test/bundle_class_21.rb.expected +27 -0
- data/vendor/spinel/test/bundle_class_22.rb +218 -0
- data/vendor/spinel/test/bundle_class_22.rb.expected +23 -0
- data/vendor/spinel/test/bundle_class_23.rb +187 -0
- data/vendor/spinel/test/bundle_class_23.rb.expected +19 -0
- data/vendor/spinel/test/bundle_class_a.rb +111 -0
- data/vendor/spinel/test/bundle_class_a.rb.expected +14 -0
- data/vendor/spinel/test/bundle_classd_04.rb +149 -0
- data/vendor/spinel/test/bundle_classd_04.rb.expected +15 -0
- data/vendor/spinel/test/bundle_classd_05.rb +65 -0
- data/vendor/spinel/test/bundle_classd_05.rb.expected +15 -0
- data/vendor/spinel/test/bundle_classd_08.rb +44 -0
- data/vendor/spinel/test/bundle_classd_08.rb.expected +4 -0
- data/vendor/spinel/test/bundle_classd_09.rb +221 -0
- data/vendor/spinel/test/bundle_classd_09.rb.expected +16 -0
- data/vendor/spinel/test/bundle_classd_10.rb +77 -0
- data/vendor/spinel/test/bundle_classd_10.rb.expected +10 -0
- data/vendor/spinel/test/bundle_classd_11.rb +89 -0
- data/vendor/spinel/test/bundle_classd_11.rb.expected +6 -0
- data/vendor/spinel/test/bundle_classd_12.rb +211 -0
- data/vendor/spinel/test/bundle_classd_12.rb.expected +19 -0
- data/vendor/spinel/test/bundle_classd_13.rb +246 -0
- data/vendor/spinel/test/bundle_classd_13.rb.expected +20 -0
- data/vendor/spinel/test/bundle_classd_14.rb +72 -0
- data/vendor/spinel/test/bundle_classd_14.rb.expected +10 -0
- data/vendor/spinel/test/bundle_classd_17.rb +206 -0
- data/vendor/spinel/test/bundle_classd_17.rb.expected +17 -0
- data/vendor/spinel/test/bundle_classd_18.rb +89 -0
- data/vendor/spinel/test/bundle_classd_18.rb.expected +3 -0
- data/vendor/spinel/test/bundle_classd_19.rb +134 -0
- data/vendor/spinel/test/bundle_classd_19.rb.expected +23 -0
- data/vendor/spinel/test/bundle_classd_26.rb +103 -0
- data/vendor/spinel/test/bundle_classd_26.rb.expected +7 -0
- data/vendor/spinel/test/bundle_classd_27.rb +241 -0
- data/vendor/spinel/test/bundle_classd_27.rb.expected +15 -0
- data/vendor/spinel/test/bundle_classd_28.rb +80 -0
- data/vendor/spinel/test/bundle_classd_28.rb.expected +10 -0
- data/vendor/spinel/test/bundle_classd_32.rb +63 -0
- data/vendor/spinel/test/bundle_classd_32.rb.expected +7 -0
- data/vendor/spinel/test/bundle_classd_33.rb +117 -0
- data/vendor/spinel/test/bundle_classd_33.rb.expected +17 -0
- data/vendor/spinel/test/bundle_classd_35.rb +90 -0
- data/vendor/spinel/test/bundle_classd_35.rb.expected +5 -0
- data/vendor/spinel/test/bundle_classd_36.rb +97 -0
- data/vendor/spinel/test/bundle_classd_36.rb.expected +5 -0
- data/vendor/spinel/test/bundle_classd_38.rb +50 -0
- data/vendor/spinel/test/bundle_classd_38.rb.expected +7 -0
- data/vendor/spinel/test/bundle_classd_39.rb +76 -0
- data/vendor/spinel/test/bundle_classd_39.rb.expected +15 -0
- data/vendor/spinel/test/bundle_classd_43.rb +84 -0
- data/vendor/spinel/test/bundle_classd_43.rb.expected +7 -0
- data/vendor/spinel/test/bundle_classd_45.rb +115 -0
- data/vendor/spinel/test/bundle_classd_45.rb.expected +7 -0
- data/vendor/spinel/test/bundle_classd_46.rb +72 -0
- data/vendor/spinel/test/bundle_classd_46.rb.expected +5 -0
- data/vendor/spinel/test/bundle_classd_48.rb +85 -0
- data/vendor/spinel/test/bundle_classd_49.rb +153 -0
- data/vendor/spinel/test/bundle_classd_50.rb +200 -0
- data/vendor/spinel/test/bundle_classd_50.rb.expected +10 -0
- data/vendor/spinel/test/bundle_classd_51.rb +191 -0
- data/vendor/spinel/test/bundle_classd_51.rb.expected +12 -0
- data/vendor/spinel/test/bundle_file_a.rb +119 -0
- data/vendor/spinel/test/bundle_file_a.rb.expected +23 -0
- data/vendor/spinel/test/bundle_hash_b1.rb +99 -0
- data/vendor/spinel/test/bundle_hash_b2.rb +107 -0
- data/vendor/spinel/test/bundle_int_a.rb +104 -0
- data/vendor/spinel/test/bundle_int_a.rb.expected +23 -0
- data/vendor/spinel/test/bundle_misc_c_08.rb +170 -0
- data/vendor/spinel/test/bundle_misc_c_08.rb.expected +33 -0
- data/vendor/spinel/test/bundle_misc_c_09.rb +72 -0
- data/vendor/spinel/test/bundle_misc_c_09.rb.expected +6 -0
- data/vendor/spinel/test/bundle_misc_c_10.rb +73 -0
- data/vendor/spinel/test/bundle_misc_c_10.rb.expected +8 -0
- data/vendor/spinel/test/bundle_misc_c_17.rb +124 -0
- data/vendor/spinel/test/bundle_misc_c_17.rb.expected +18 -0
- data/vendor/spinel/test/bundle_misc_c_23.rb +97 -0
- data/vendor/spinel/test/bundle_misc_c_23.rb.expected +10 -0
- data/vendor/spinel/test/bundle_misc_c_26.rb +107 -0
- data/vendor/spinel/test/bundle_misc_c_26.rb.expected +24 -0
- data/vendor/spinel/test/bundle_misc_c_30.rb +94 -0
- data/vendor/spinel/test/bundle_misc_c_31.rb +89 -0
- data/vendor/spinel/test/bundle_misc_c_31.rb.expected +14 -0
- data/vendor/spinel/test/bundle_misc_c_33.rb +163 -0
- data/vendor/spinel/test/bundle_misc_c_33.rb.expected +53 -0
- data/vendor/spinel/test/bundle_misc_c_34.rb +106 -0
- data/vendor/spinel/test/bundle_misc_c_34.rb.expected +11 -0
- data/vendor/spinel/test/bundle_misc_c_35.rb +143 -0
- data/vendor/spinel/test/bundle_misc_c_35.rb.expected +44 -0
- data/vendor/spinel/test/bundle_misc_c_36.rb +165 -0
- data/vendor/spinel/test/bundle_misc_c_36.rb.expected +59 -0
- data/vendor/spinel/test/bundle_misc_c_37.rb +119 -0
- data/vendor/spinel/test/bundle_misc_c_37.rb.expected +22 -0
- data/vendor/spinel/test/bundle_poly_b1.rb +44 -0
- data/vendor/spinel/test/bundle_poly_b1.rb.expected +7 -0
- data/vendor/spinel/test/bundle_string_c.rb +250 -0
- data/vendor/spinel/test/bundle_string_c.rb.expected +51 -0
- data/vendor/spinel/test/bundle_sym.rb +2 -4
- data/vendor/spinel/test/bundle_tiny_array.rb +160 -0
- data/vendor/spinel/test/bundle_tiny_array.rb.expected +53 -0
- data/vendor/spinel/test/bundle_tiny_misc.rb +187 -0
- data/vendor/spinel/test/bundle_tiny_misc.rb.expected +59 -0
- data/vendor/spinel/test/bundle_tiny_num.rb +87 -0
- data/vendor/spinel/test/bundle_tiny_num.rb.expected +27 -0
- data/vendor/spinel/test/bundle_tiny_string.rb +226 -0
- data/vendor/spinel/test/bundle_tiny_string.rb.expected +78 -0
- data/vendor/spinel/test/captured_block_call_value.rb +45 -0
- data/vendor/spinel/test/captured_block_call_value.rb.expected +5 -0
- data/vendor/spinel/test/case_fallthrough_returns_nil.expected +5 -0
- data/vendor/spinel/test/case_fallthrough_returns_nil.rb +8 -0
- data/vendor/spinel/test/case_in_no_match_error.rb +54 -0
- data/vendor/spinel/test/case_in_no_match_error.rb.expected +6 -0
- data/vendor/spinel/test/case_local_in_lambda.rb +57 -0
- data/vendor/spinel/test/case_local_in_lambda.rb.expected +5 -0
- data/vendor/spinel/test/case_when_class.rb +28 -66
- data/vendor/spinel/test/case_when_class.rb.expected +5 -5
- data/vendor/spinel/test/case_when_class_scalar_predicate.rb +53 -0
- data/vendor/spinel/test/case_when_class_scalar_predicate.rb.expected +3 -0
- data/vendor/spinel/test/case_when_poly_scrutinee_bound_to_local.rb +36 -0
- data/vendor/spinel/test/case_when_poly_scrutinee_bound_to_local.rb.expected +5 -0
- data/vendor/spinel/test/case_when_qualified_class.rb +47 -0
- data/vendor/spinel/test/case_when_qualified_class.rb.expected +2 -0
- data/vendor/spinel/test/case_when_regex.rb +23 -0
- data/vendor/spinel/test/case_when_regex.rb.expected +3 -0
- data/vendor/spinel/test/chained_aset_on_poly_hash_recv.rb +19 -0
- data/vendor/spinel/test/chained_aset_on_poly_hash_recv.rb.expected +1 -0
- data/vendor/spinel/test/clamp_string_and_range.rb +14 -0
- data/vendor/spinel/test/clamp_string_and_range.rb.expected +8 -0
- data/vendor/spinel/test/class_const_as_value.rb +58 -0
- data/vendor/spinel/test/class_const_as_value.rb.expected +4 -0
- data/vendor/spinel/test/class_hierarchy.rb +41 -0
- data/vendor/spinel/test/class_hierarchy.rb.expected +10 -0
- data/vendor/spinel/test/class_hierarchy_builtin.rb +43 -0
- data/vendor/spinel/test/class_hierarchy_builtin.rb.expected +14 -0
- data/vendor/spinel/test/class_hierarchy_module.rb +55 -0
- data/vendor/spinel/test/class_hierarchy_module.rb.expected +6 -0
- data/vendor/spinel/test/class_in_collection.rb +22 -0
- data/vendor/spinel/test/class_in_collection.rb.expected +10 -0
- data/vendor/spinel/test/class_ivar_in_class_method.rb +66 -0
- data/vendor/spinel/test/class_ivar_in_class_method.rb.expected +11 -0
- data/vendor/spinel/test/class_lt_builtin_parent.rb +13 -0
- data/vendor/spinel/test/class_name_in_cmeth.rb +30 -0
- data/vendor/spinel/test/class_name_in_cmeth.rb.expected +3 -0
- data/vendor/spinel/test/class_of_primitive.rb +14 -0
- data/vendor/spinel/test/class_of_primitive.rb.expected +9 -0
- data/vendor/spinel/test/class_phase2_name_eq.rb +61 -0
- data/vendor/spinel/test/class_phase2_name_eq.rb.expected +15 -0
- data/vendor/spinel/test/class_reopen_replaces_methods.rb +40 -0
- data/vendor/spinel/test/class_reopen_replaces_methods.rb.expected +3 -0
- data/vendor/spinel/test/class_singleton_def.rb +27 -0
- data/vendor/spinel/test/class_singleton_def.rb.expected +4 -0
- data/vendor/spinel/test/class_superclass.rb +15 -0
- data/vendor/spinel/test/class_superclass.rb.expected +3 -0
- data/vendor/spinel/test/class_value_builtin_and_nested.rb +36 -0
- data/vendor/spinel/test/cls_method_specialization_boxer.expected +2 -0
- data/vendor/spinel/test/cls_method_specialization_boxer.rb +47 -0
- data/vendor/spinel/test/cmeth_name_collision_with_kernel.rb +56 -0
- data/vendor/spinel/test/cmeth_name_collision_with_kernel.rb.expected +5 -0
- data/vendor/spinel/test/cmeth_return_nil_or_obj_via_intermediate.rb +89 -0
- data/vendor/spinel/test/cmeth_return_nil_or_obj_via_intermediate.rb.expected +6 -0
- data/vendor/spinel/test/comparable_between_user_type.rb +26 -0
- data/vendor/spinel/test/comparable_between_user_type.rb.expected +10 -0
- data/vendor/spinel/test/compile_time_attribute_singular.rb +12 -0
- data/vendor/spinel/test/compile_time_attribute_singular.rb.expected +2 -0
- data/vendor/spinel/test/compile_time_attribute_wrapped_record.rb +72 -0
- data/vendor/spinel/test/compile_time_attribute_wrapped_record.rb.expected +2 -0
- data/vendor/spinel/test/compile_time_attributes.rb +14 -0
- data/vendor/spinel/test/compile_time_attributes.rb.expected +3 -0
- data/vendor/spinel/test/compile_time_define_method_direct.rb +8 -0
- data/vendor/spinel/test/compile_time_define_method_direct.rb.expected +2 -0
- data/vendor/spinel/test/compile_time_define_method_int.rb +9 -0
- data/vendor/spinel/test/compile_time_define_method_int.rb.expected +2 -0
- data/vendor/spinel/test/compile_time_define_method_predicates.rb +18 -0
- data/vendor/spinel/test/compile_time_define_method_predicates.rb.expected +4 -0
- data/vendor/spinel/test/compile_time_define_method_strings.rb +9 -0
- data/vendor/spinel/test/compile_time_define_method_strings.rb.expected +2 -0
- data/vendor/spinel/test/complex_polar.rb +37 -0
- data/vendor/spinel/test/complex_polar.rb.expected +10 -0
- data/vendor/spinel/test/concat_chain_operand_gc_root.rb +16 -0
- data/vendor/spinel/test/concat_chain_operand_gc_root.rb.expected +4 -0
- data/vendor/spinel/test/concat_print_poly_unbox.rb +115 -0
- data/vendor/spinel/test/concat_print_poly_unbox.rb.expected +9 -0
- data/vendor/spinel/test/const_aliased_class_reopen_include.rb +33 -0
- data/vendor/spinel/test/const_aliased_class_reopen_include.rb.expected +2 -0
- data/vendor/spinel/test/const_compound_assign.rb +34 -0
- data/vendor/spinel/test/const_path_compound_assign.rb +15 -0
- data/vendor/spinel/test/const_path_deep_nesting.rb +30 -0
- data/vendor/spinel/test/const_path_deep_nesting.rb.expected +2 -0
- data/vendor/spinel/test/const_path_unresolved_require.rb +11 -0
- data/vendor/spinel/test/const_path_unresolved_require.rb.expected +2 -0
- data/vendor/spinel/test/const_self_ref_raises_name_error.rb +44 -0
- data/vendor/spinel/test/const_self_ref_raises_name_error.rb.expected +3 -0
- data/vendor/spinel/test/crashers_536.rb +32 -0
- data/vendor/spinel/test/crashers_536.rb.expected +4 -0
- data/vendor/spinel/test/cvar_method_first_assign.rb +6 -0
- data/vendor/spinel/test/data_define.rb +27 -0
- data/vendor/spinel/test/data_define.rb.expected +5 -0
- data/vendor/spinel/test/default_nil_param_with_str_return.rb +30 -0
- data/vendor/spinel/test/default_nil_param_with_str_return.rb.expected +4 -0
- data/vendor/spinel/test/defined_globals_methods.rb +15 -0
- data/vendor/spinel/test/defined_globals_methods.rb.expected +5 -0
- data/vendor/spinel/test/defined_op.rb +17 -0
- data/vendor/spinel/test/defined_op.rb.expected +8 -0
- data/vendor/spinel/test/destructure_typed_ptr_array_return.rb +30 -0
- data/vendor/spinel/test/destructure_typed_ptr_array_return.rb.expected +2 -0
- data/vendor/spinel/test/dir_exist.rb +2 -0
- data/vendor/spinel/test/dir_methods.rb +15 -0
- data/vendor/spinel/test/double_splat_kwargs.rb +26 -0
- data/vendor/spinel/test/double_splat_kwargs.rb.expected +6 -0
- data/vendor/spinel/test/dynamic_class_new.rb +44 -0
- data/vendor/spinel/test/dynamic_class_new.rb.expected +4 -0
- data/vendor/spinel/test/dynamic_is_a.rb +41 -0
- data/vendor/spinel/test/dynamic_is_a.rb.expected +5 -0
- data/vendor/spinel/test/each_cons_block_destructure.rb +10 -0
- data/vendor/spinel/test/each_cons_with_index_map_no_double_gets.rb +21 -0
- data/vendor/spinel/test/each_cons_with_index_map_no_double_gets.rb.expected +3 -0
- data/vendor/spinel/test/each_entry_reverse_each.rb +8 -0
- data/vendor/spinel/test/each_slice_map.rb +7 -0
- data/vendor/spinel/test/empty_array_local_arg_widen.rb +57 -0
- data/vendor/spinel/test/empty_array_local_arg_widen.rb.expected +2 -0
- data/vendor/spinel/test/empty_array_loop_intermediate_push.rb +103 -0
- data/vendor/spinel/test/empty_array_loop_intermediate_push.rb.expected +5 -0
- data/vendor/spinel/test/empty_array_push_container_elem.rb +35 -0
- data/vendor/spinel/test/empty_array_push_ptr.rb +26 -0
- data/vendor/spinel/test/empty_array_push_ptr.rb.expected +3 -0
- data/vendor/spinel/test/empty_hash_arg_param_widen.rb +44 -0
- data/vendor/spinel/test/empty_hash_arg_param_widen.rb.expected +6 -0
- data/vendor/spinel/test/empty_hash_int_int_no_widen.rb +46 -0
- data/vendor/spinel/test/empty_hash_int_int_no_widen.rb.expected +4 -0
- data/vendor/spinel/test/empty_hash_int_int_promote.rb +32 -0
- data/vendor/spinel/test/empty_hash_int_int_promote.rb.expected +8 -0
- data/vendor/spinel/test/empty_int_array_min_max.rb +15 -0
- data/vendor/spinel/test/empty_int_array_min_max.rb.expected +4 -0
- data/vendor/spinel/test/empty_method_returns_nil.rb +8 -0
- data/vendor/spinel/test/empty_method_returns_nil.rb.expected +3 -0
- data/vendor/spinel/test/empty_symbol_literal.rb +23 -0
- data/vendor/spinel/test/empty_symbol_literal.rb.expected +9 -0
- data/vendor/spinel/test/eq_string_vs_int.rb +20 -0
- data/vendor/spinel/test/eq_string_vs_int.rb.expected +9 -0
- data/vendor/spinel/test/exception_backtrace_empty.rb +12 -0
- data/vendor/spinel/test/exception_backtrace_empty.rb.expected +4 -0
- data/vendor/spinel/test/exception_class_new.rb +46 -0
- data/vendor/spinel/test/exception_class_new.rb.expected +10 -0
- data/vendor/spinel/test/exception_namespaced_default_message.rb +28 -0
- data/vendor/spinel/test/exception_namespaced_default_message.rb.expected +4 -0
- data/vendor/spinel/test/exception_object_methods.rb +3 -2
- data/vendor/spinel/test/exception_object_methods.rb.expected +1 -1
- data/vendor/spinel/test/exception_super_interpolated_message.rb +41 -0
- data/vendor/spinel/test/exception_super_interpolated_message.rb.expected +5 -0
- data/vendor/spinel/test/exception_user_subclass.rb +35 -0
- data/vendor/spinel/test/exception_user_subclass.rb.expected +10 -0
- data/vendor/spinel/test/extend_module.rb +30 -0
- data/vendor/spinel/test/extend_module.rb.expected +4 -0
- data/vendor/spinel/test/ffi_array_specs_decl.rb +25 -0
- data/vendor/spinel/test/ffi_int_arg_poly_value.rb +27 -0
- data/vendor/spinel/test/ffi_ptr_array.rb +35 -0
- data/vendor/spinel/test/ffi_ptr_int_literal.rb +31 -0
- data/vendor/spinel/test/ffi_str_arg_poly_value.rb +26 -0
- data/vendor/spinel/test/fiber_capture_no_leak_to_sibling.rb +27 -0
- data/vendor/spinel/test/fiber_capture_no_leak_to_sibling.rb.expected +1 -0
- data/vendor/spinel/test/fiber_gc_root_isolation.rb +76 -0
- data/vendor/spinel/test/file_dir_methods.rb +11 -0
- data/vendor/spinel/test/file_dir_methods.rb.expected +8 -0
- data/vendor/spinel/test/file_expand_path.rb +10 -0
- data/vendor/spinel/test/file_expand_path.rb.expected +8 -0
- data/vendor/spinel/test/file_mtime.rb +10 -0
- data/vendor/spinel/test/file_mtime.rb.expected +2 -0
- data/vendor/spinel/test/file_read_count.rb +19 -0
- data/vendor/spinel/test/file_read_count.rb.expected +5 -0
- data/vendor/spinel/test/file_readlines.rb +15 -0
- data/vendor/spinel/test/file_readlines.rb.expected +4 -0
- data/vendor/spinel/test/file_write_at_tail_position.rb +16 -0
- data/vendor/spinel/test/float_array_first_last_nil.rb +20 -0
- data/vendor/spinel/test/float_array_first_last_nil.rb.expected +8 -0
- data/vendor/spinel/test/float_divmod_zerodiv.rb +57 -0
- data/vendor/spinel/test/float_divmod_zerodiv.rb.expected +7 -0
- data/vendor/spinel/test/float_limit_constants.rb +14 -0
- data/vendor/spinel/test/float_round_ndigits_return_type.rb +24 -0
- data/vendor/spinel/test/float_round_ndigits_return_type.rb.expected +14 -0
- data/vendor/spinel/test/float_round_value_based_type.rb +17 -0
- data/vendor/spinel/test/float_round_value_based_type.rb.expected +14 -0
- data/vendor/spinel/test/float_round_zero.rb +18 -0
- data/vendor/spinel/test/float_round_zero.rb.expected +11 -0
- data/vendor/spinel/test/float_spaceship.rb +13 -0
- data/vendor/spinel/test/float_step_block.rb +16 -0
- data/vendor/spinel/test/float_step_block.rb.expected +3 -0
- data/vendor/spinel/test/float_step_to_a.rb +20 -0
- data/vendor/spinel/test/float_step_to_a.rb.expected +10 -0
- data/vendor/spinel/test/gc_grandparent_ivar_scan.rb +28 -0
- data/vendor/spinel/test/gc_grandparent_ivar_scan.rb.expected +2 -0
- data/vendor/spinel/test/gc_poly_local_int.rb +11 -0
- data/vendor/spinel/test/gc_poly_local_int.rb.expected +1 -0
- data/vendor/spinel/test/gc_poly_local_root.rb +19 -0
- data/vendor/spinel/test/gc_poly_local_root.rb.expected +1 -0
- data/vendor/spinel/test/gc_poly_local_setjmp.rb +16 -0
- data/vendor/spinel/test/gc_poly_local_setjmp.rb.expected +1 -0
- data/vendor/spinel/test/gc_root_call_args.rb +35 -0
- data/vendor/spinel/test/gc_root_call_args.rb.expected +3 -0
- data/vendor/spinel/test/gc_root_heap_param.rb +21 -0
- data/vendor/spinel/test/gc_root_heap_param.rb.expected +1 -0
- data/vendor/spinel/test/global_multi_assign.rb +28 -0
- data/vendor/spinel/test/global_var_punct.rb +9 -0
- data/vendor/spinel/test/gsub_sub_block.rb +14 -0
- data/vendor/spinel/test/gsub_sub_block.rb.expected +7 -0
- data/vendor/spinel/test/gsub_with_hash_replacement.rb +19 -0
- data/vendor/spinel/test/gsub_with_hash_replacement.rb.expected +3 -0
- data/vendor/spinel/test/hangs_548.rb +66 -0
- data/vendor/spinel/test/hangs_548.rb.expected +4 -0
- data/vendor/spinel/test/hash_block_methods_variants.rb +15 -0
- data/vendor/spinel/test/hash_block_methods_variants.rb.expected +12 -0
- data/vendor/spinel/test/hash_clear.rb +43 -0
- data/vendor/spinel/test/hash_clear.rb.expected +12 -0
- data/vendor/spinel/test/hash_compound_assign_more.rb +21 -0
- data/vendor/spinel/test/hash_delete_if_select_reject_bang.rb +19 -0
- data/vendor/spinel/test/hash_delete_if_select_reject_bang.rb.expected +4 -0
- data/vendor/spinel/test/hash_delete_returns_value.rb +21 -0
- data/vendor/spinel/test/hash_delete_returns_value.rb.expected +8 -0
- data/vendor/spinel/test/hash_dispatch_missing_cells.rb +24 -0
- data/vendor/spinel/test/hash_dispatch_missing_cells.rb.expected +8 -0
- data/vendor/spinel/test/hash_each_key_value.rb +85 -0
- data/vendor/spinel/test/hash_each_key_value.rb.expected +72 -0
- data/vendor/spinel/test/hash_each_with_object.rb +16 -0
- data/vendor/spinel/test/hash_each_with_object.rb.expected +2 -0
- data/vendor/spinel/test/hash_fetch_block.rb +31 -0
- data/vendor/spinel/test/hash_fetch_block.rb.expected +13 -0
- data/vendor/spinel/test/hash_fetch_block_key.rb +9 -0
- data/vendor/spinel/test/hash_fetch_block_key.rb.expected +4 -0
- data/vendor/spinel/test/hash_fetch_block_key_type.rb +37 -0
- data/vendor/spinel/test/hash_fetch_block_key_type.rb.expected +7 -0
- data/vendor/spinel/test/hash_fetch_keyerror.rb +23 -0
- data/vendor/spinel/test/hash_flatten_rassoc_compact.rb +33 -0
- data/vendor/spinel/test/hash_float_value_preserved.rb +27 -0
- data/vendor/spinel/test/hash_float_value_preserved.rb.expected +6 -0
- data/vendor/spinel/test/hash_index_assign_expression.rb +21 -0
- data/vendor/spinel/test/hash_inspect_poly.rb +13 -0
- data/vendor/spinel/test/hash_inspect_poly.rb.expected +6 -0
- data/vendor/spinel/test/hash_int_and_or_assign.rb +30 -0
- data/vendor/spinel/test/hash_int_and_or_assign.rb.expected +8 -0
- data/vendor/spinel/test/hash_invert.rb +16 -0
- data/vendor/spinel/test/hash_invert.rb.expected +4 -0
- data/vendor/spinel/test/hash_key_via_intermediate_local.rb +32 -0
- data/vendor/spinel/test/hash_map_block.rb +6 -0
- data/vendor/spinel/test/hash_merge_with_block.rb +10 -0
- data/vendor/spinel/test/hash_new_default_block_assign.rb +16 -0
- data/vendor/spinel/test/hash_param_each_kv_inference.rb +67 -0
- data/vendor/spinel/test/hash_param_each_kv_inference.rb.expected +3 -0
- data/vendor/spinel/test/hash_param_each_kv_weak_default.rb +75 -0
- data/vendor/spinel/test/hash_param_each_kv_weak_default.rb.expected +6 -0
- data/vendor/spinel/test/hash_param_widen_from_callee_slot.rb +43 -0
- data/vendor/spinel/test/hash_replace_variants.rb +17 -0
- data/vendor/spinel/test/hash_replace_variants.rb.expected +6 -0
- data/vendor/spinel/test/hash_temp_gc_root.rb +26 -0
- data/vendor/spinel/test/hash_transform_keys_cross.rb +12 -0
- data/vendor/spinel/test/hash_transform_keys_cross.rb.expected +5 -0
- data/vendor/spinel/test/hash_value_p.rb +19 -0
- data/vendor/spinel/test/hash_value_p.rb.expected +7 -0
- data/vendor/spinel/test/hash_value_split_widen.rb +22 -0
- data/vendor/spinel/test/hash_value_split_widen.rb.expected +2 -0
- data/vendor/spinel/test/hash_values_at_fetch_values.rb +23 -0
- data/vendor/spinel/test/hash_variant_call_boundary_sym.rb +27 -0
- data/vendor/spinel/test/hash_variant_call_boundary_sym.rb.expected +2 -0
- data/vendor/spinel/test/heterogeneous_tuple_return.rb +32 -0
- data/vendor/spinel/test/heterogeneous_tuple_return.rb.expected +7 -0
- data/vendor/spinel/test/i1006.rb +21 -0
- data/vendor/spinel/test/i1006.rb.expected +7 -0
- data/vendor/spinel/test/i1007.rb +25 -0
- data/vendor/spinel/test/i1007.rb.expected +4 -0
- data/vendor/spinel/test/i1009.rb +9 -0
- data/vendor/spinel/test/i1009.rb.expected +4 -0
- data/vendor/spinel/test/i1011.rb +12 -0
- data/vendor/spinel/test/i1011.rb.expected +2 -0
- data/vendor/spinel/test/i1015.rb +11 -0
- data/vendor/spinel/test/i1015.rb.expected +2 -0
- data/vendor/spinel/test/i1017.rb +37 -0
- data/vendor/spinel/test/i1017.rb.expected +1 -0
- data/vendor/spinel/test/i1020.rb +31 -0
- data/vendor/spinel/test/i1020.rb.expected +2 -0
- data/vendor/spinel/test/i1021.rb +12 -0
- data/vendor/spinel/test/i1021.rb.expected +9 -0
- data/vendor/spinel/test/i1027.rb +28 -0
- data/vendor/spinel/test/i1027.rb.expected +1 -0
- data/vendor/spinel/test/i1031.rb +64 -0
- data/vendor/spinel/test/i1031.rb.expected +4 -0
- data/vendor/spinel/test/i1035.rb +18 -0
- data/vendor/spinel/test/i1035.rb.expected +3 -0
- data/vendor/spinel/test/i1038.rb +7 -0
- data/vendor/spinel/test/i1038.rb.expected +4 -0
- data/vendor/spinel/test/i1041.rb +31 -0
- data/vendor/spinel/test/i1041.rb.expected +4 -0
- data/vendor/spinel/test/i709.rb +41 -0
- data/vendor/spinel/test/i709.rb.expected +3 -0
- data/vendor/spinel/test/i898.rb +23 -0
- data/vendor/spinel/test/i898.rb.expected +7 -0
- data/vendor/spinel/test/i912.rb +15 -0
- data/vendor/spinel/test/i912.rb.expected +8 -0
- data/vendor/spinel/test/i916.rb +14 -0
- data/vendor/spinel/test/i916.rb.expected +4 -0
- data/vendor/spinel/test/i918.rb +45 -0
- data/vendor/spinel/test/i918.rb.expected +13 -0
- data/vendor/spinel/test/i919.rb +12 -0
- data/vendor/spinel/test/i919.rb.expected +5 -0
- data/vendor/spinel/test/i920.rb +9 -0
- data/vendor/spinel/test/i920.rb.expected +4 -0
- data/vendor/spinel/test/i960.rb +13 -0
- data/vendor/spinel/test/i960.rb.expected +5 -0
- data/vendor/spinel/test/i972.rb +38 -0
- data/vendor/spinel/test/i972.rb.expected +11 -0
- data/vendor/spinel/test/i974.rb +23 -0
- data/vendor/spinel/test/i974.rb.expected +19 -0
- data/vendor/spinel/test/i994.rb +18 -0
- data/vendor/spinel/test/i994.rb.expected +9 -0
- data/vendor/spinel/test/i997.rb +20 -0
- data/vendor/spinel/test/i997.rb.expected +6 -0
- data/vendor/spinel/test/if_expr_multi_stmt_body.rb +78 -0
- data/vendor/spinel/test/if_expr_multi_stmt_body.rb.expected +13 -0
- data/vendor/spinel/test/implicit_alloc_site_cache_helper_specialization.rb +112 -0
- data/vendor/spinel/test/implicit_alloc_site_cache_helper_specialization.rb.expected +6 -0
- data/vendor/spinel/test/implicit_alloc_site_escape_layout.rb +30 -0
- data/vendor/spinel/test/implicit_alloc_site_escape_layout.rb.expected +2 -0
- data/vendor/spinel/test/implicit_alloc_site_escape_param.rb +27 -0
- data/vendor/spinel/test/implicit_alloc_site_escape_param.rb.expected +2 -0
- data/vendor/spinel/test/implicit_alloc_site_escape_scoped_locals.rb +44 -0
- data/vendor/spinel/test/implicit_alloc_site_escape_scoped_locals.rb.expected +2 -0
- data/vendor/spinel/test/implicit_alloc_site_holder_poly_array.rb +23 -0
- data/vendor/spinel/test/implicit_alloc_site_holder_poly_array.rb.expected +3 -0
- data/vendor/spinel/test/implicit_alloc_site_holder_shared_method.rb +23 -0
- data/vendor/spinel/test/implicit_alloc_site_holder_shared_method.rb.expected +3 -0
- data/vendor/spinel/test/implicit_alloc_site_holder_specialization.rb +19 -0
- data/vendor/spinel/test/implicit_alloc_site_holder_specialization.rb.expected +3 -0
- data/vendor/spinel/test/implicit_alloc_site_ivar_helper_return_specialization.rb +46 -0
- data/vendor/spinel/test/implicit_alloc_site_ivar_helper_return_specialization.rb.expected +3 -0
- data/vendor/spinel/test/implicit_alloc_site_store_specialization.rb +28 -0
- data/vendor/spinel/test/implicit_alloc_site_store_specialization.rb.expected +4 -0
- data/vendor/spinel/test/implicit_new_local_method_scope.rb +65 -0
- data/vendor/spinel/test/implicit_new_local_method_scope.rb.expected +2 -0
- data/vendor/spinel/test/include_chain_const_lookup.rb +48 -0
- data/vendor/spinel/test/include_chain_const_lookup.rb.expected +3 -0
- data/vendor/spinel/test/include_chain_module_nested_const.rb +62 -0
- data/vendor/spinel/test/include_chain_module_nested_const.rb.expected +2 -0
- data/vendor/spinel/test/include_constant_path.rb +98 -0
- data/vendor/spinel/test/include_constant_path.rb.expected +5 -0
- data/vendor/spinel/test/include_nested_module.rb +99 -0
- data/vendor/spinel/test/include_nested_module.rb.expected +3 -0
- data/vendor/spinel/test/include_skips_module_class_methods.rb +27 -0
- data/vendor/spinel/test/include_skips_module_class_methods.rb.expected +3 -0
- data/vendor/spinel/test/inherited_factory_poly_param_narrow_attr.rb +25 -0
- data/vendor/spinel/test/inherited_kwarg_nil_default_string_ivar.rb +61 -0
- data/vendor/spinel/test/inherited_kwarg_nil_default_string_ivar.rb.expected +2 -0
- data/vendor/spinel/test/inherited_method_kwarg_block_self.rb +67 -0
- data/vendor/spinel/test/inherited_method_kwarg_block_self.rb.expected +2 -0
- data/vendor/spinel/test/initialize_override_param_inference.rb +68 -0
- data/vendor/spinel/test/inject_symbol_op.rb +11 -0
- data/vendor/spinel/test/inject_symbol_op.rb.expected +7 -0
- data/vendor/spinel/test/inject_symbol_to_proc.rb +41 -0
- data/vendor/spinel/test/inject_symbol_to_proc.rb.expected +12 -0
- data/vendor/spinel/test/inlined_yield_block_param_type.rb +32 -0
- data/vendor/spinel/test/inlined_yield_block_param_type.rb.expected +2 -0
- data/vendor/spinel/test/inlined_yield_self_locals.rb +49 -0
- data/vendor/spinel/test/inlined_yield_self_locals.rb.expected +5 -0
- data/vendor/spinel/test/instance_eval_block_params.rb +93 -0
- data/vendor/spinel/test/instance_eval_block_params.rb.expected +10 -0
- data/vendor/spinel/test/instance_eval_trampoline.rb +17 -0
- data/vendor/spinel/test/instance_eval_trampoline.rb.expected +1 -0
- data/vendor/spinel/test/instance_exec_capture.rb +48 -0
- data/vendor/spinel/test/instance_exec_capture.rb.expected +7 -0
- data/vendor/spinel/test/instance_exec_direct.rb +72 -0
- data/vendor/spinel/test/instance_exec_direct.rb.expected +5 -0
- data/vendor/spinel/test/instance_exec_override.rb +69 -0
- data/vendor/spinel/test/instance_exec_override.rb.expected +4 -0
- data/vendor/spinel/test/instance_exec_parity.rb +44 -0
- data/vendor/spinel/test/instance_exec_parity.rb.expected +6 -0
- data/vendor/spinel/test/instance_exec_return.rb +54 -0
- data/vendor/spinel/test/instance_exec_return.rb.expected +7 -0
- data/vendor/spinel/test/instance_exec_trampoline.rb +101 -0
- data/vendor/spinel/test/instance_exec_trampoline.rb.expected +6 -0
- data/vendor/spinel/test/instance_exec_valuetype.rb +48 -0
- data/vendor/spinel/test/instance_exec_valuetype.rb.expected +7 -0
- data/vendor/spinel/test/instance_methods_false.rb +41 -0
- data/vendor/spinel/test/instance_methods_false.rb.expected +14 -0
- data/vendor/spinel/test/int_array_bracket_assign_expr.rb +30 -0
- data/vendor/spinel/test/int_array_bracket_assign_expr.rb.expected +8 -0
- data/vendor/spinel/test/int_array_tally.rb +14 -0
- data/vendor/spinel/test/int_array_tally.rb.expected +8 -0
- data/vendor/spinel/test/int_array_uniq_bang.rb +15 -0
- data/vendor/spinel/test/int_array_uniq_bang.rb.expected +3 -0
- data/vendor/spinel/test/int_ceil_floor_round.rb +11 -0
- data/vendor/spinel/test/int_ceil_floor_round.rb.expected +8 -0
- data/vendor/spinel/test/int_ceil_floor_round_truncate_ndigits.rb +17 -0
- data/vendor/spinel/test/int_ceil_floor_round_truncate_ndigits.rb.expected +12 -0
- data/vendor/spinel/test/int_hash_missing_nil_check.rb +27 -0
- data/vendor/spinel/test/int_hash_missing_nil_check.rb.expected +14 -0
- data/vendor/spinel/test/int_int_hash_literal.rb +13 -0
- data/vendor/spinel/test/int_int_hash_literal.rb.expected +7 -0
- data/vendor/spinel/test/int_int_hash_missing_key_nil.rb +10 -0
- data/vendor/spinel/test/int_int_hash_missing_key_nil.rb.expected +8 -0
- data/vendor/spinel/test/int_opt_p_inspect.rb +8 -0
- data/vendor/spinel/test/int_opt_p_inspect.rb.expected +5 -0
- data/vendor/spinel/test/int_overflow_promote.rb +41 -0
- data/vendor/spinel/test/int_overflow_raises.rb +34 -0
- data/vendor/spinel/test/int_overflow_raises.rb.expected +6 -0
- data/vendor/spinel/test/int_round_large_precision.rb +11 -0
- data/vendor/spinel/test/int_round_large_precision.rb.expected +8 -0
- data/vendor/spinel/test/integer_argument_error.rb +116 -0
- data/vendor/spinel/test/integer_argument_error.rb.expected +36 -0
- data/vendor/spinel/test/integer_extra_methods.rb +12 -0
- data/vendor/spinel/test/integer_extra_methods.rb.expected +8 -0
- data/vendor/spinel/test/integer_pow_negative_exponent.rb +17 -0
- data/vendor/spinel/test/integer_pow_negative_exponent.rb.expected +6 -0
- data/vendor/spinel/test/integer_times_as_expr.rb +9 -0
- data/vendor/spinel/test/integer_times_as_expr.rb.expected +3 -0
- data/vendor/spinel/test/interp_array_container.rb +41 -0
- data/vendor/spinel/test/interp_array_container.rb.expected +8 -0
- data/vendor/spinel/test/interpolation_multistmt.rb +15 -0
- data/vendor/spinel/test/interpolation_multistmt.rb.expected +3 -0
- data/vendor/spinel/test/interpolation_symbol_name.rb +23 -0
- data/vendor/spinel/test/interpolation_symbol_name.rb.expected +6 -0
- data/vendor/spinel/test/introspection_constant_path.rb +60 -0
- data/vendor/spinel/test/introspection_constant_path.rb.expected +13 -0
- data/vendor/spinel/test/io_pipe.rb +6 -0
- data/vendor/spinel/test/io_pipe.rb.expected +3 -0
- data/vendor/spinel/test/is_a_descendant_runtime.rb +59 -0
- data/vendor/spinel/test/is_a_descendant_runtime.rb.expected +11 -0
- data/vendor/spinel/test/is_a_included_module.rb +28 -0
- data/vendor/spinel/test/is_a_included_module.rb.expected +9 -0
- data/vendor/spinel/test/ivar_assign_and_expr.rb +22 -0
- data/vendor/spinel/test/ivar_assign_and_expr.rb.expected +3 -0
- data/vendor/spinel/test/ivar_lazy_init_push_via_inherited_getter.rb +67 -0
- data/vendor/spinel/test/ivar_lazy_init_push_via_inherited_getter.rb.expected +6 -0
- data/vendor/spinel/test/json_generate_basic.rb +16 -0
- data/vendor/spinel/test/json_generate_basic.rb.expected +12 -0
- data/vendor/spinel/test/json_generate_struct_record.rb +9 -0
- data/vendor/spinel/test/json_generate_struct_record.rb.expected +4 -0
- data/vendor/spinel/test/kernel_complex.rb +9 -0
- data/vendor/spinel/test/kernel_dir.rb +9 -0
- data/vendor/spinel/test/kernel_dir.rb.expected +2 -0
- data/vendor/spinel/test/kernel_float_strict.rb +16 -0
- data/vendor/spinel/test/kernel_float_strict.rb.expected +6 -0
- data/vendor/spinel/test/kernel_integer_with_base.rb +16 -0
- data/vendor/spinel/test/kernel_integer_with_base.rb.expected +6 -0
- data/vendor/spinel/test/kwarg_back_propagate_callee_type.rb +26 -0
- data/vendor/spinel/test/kwarg_back_propagate_callee_type.rb.expected +1 -0
- data/vendor/spinel/test/kwargs_double_splat.rb +15 -0
- data/vendor/spinel/test/kwargs_double_splat.rb.expected +2 -0
- data/vendor/spinel/test/kwargs_merge_default_hash.rb +51 -0
- data/vendor/spinel/test/lambda_arrow_string_return.rb +8 -0
- data/vendor/spinel/test/lambda_arrow_string_return.rb.expected +2 -0
- data/vendor/spinel/test/lambda_inference_chain.rb +35 -0
- data/vendor/spinel/test/lambda_inference_chain.rb.expected +5 -0
- data/vendor/spinel/test/local_empty_array_push_promotion_propagates.rb +61 -0
- data/vendor/spinel/test/local_empty_array_push_promotion_propagates.rb.expected +3 -0
- data/vendor/spinel/test/logical_pointer_guard.rb +20 -0
- data/vendor/spinel/test/logical_pointer_guard.rb.expected +2 -0
- data/vendor/spinel/test/logical_value_return.rb +15 -0
- data/vendor/spinel/test/lv_widen_from_callee_arg_slot.rb +56 -0
- data/vendor/spinel/test/lv_widen_from_callee_arg_slot.rb.expected +1 -0
- data/vendor/spinel/test/map_block_string_new_join.rb +33 -0
- data/vendor/spinel/test/map_block_string_new_join.rb.expected +3 -0
- data/vendor/spinel/test/map_obj_array_poly_field.rb +28 -0
- data/vendor/spinel/test/map_recv_emitted_once.rb +44 -0
- data/vendor/spinel/test/map_recv_emitted_once.rb.expected +7 -0
- data/vendor/spinel/test/map_to_heap_objects.rb +16 -0
- data/vendor/spinel/test/map_to_heap_objects.rb.expected +4 -0
- data/vendor/spinel/test/map_to_value_type_objects.rb +26 -0
- data/vendor/spinel/test/map_to_value_type_objects.rb.expected +7 -0
- data/vendor/spinel/test/masgn_classvar_target.rb +13 -0
- data/vendor/spinel/test/masgn_constpath_target.rb +11 -0
- data/vendor/spinel/test/method_level_rescue_return.rb +42 -0
- data/vendor/spinel/test/method_level_rescue_return.rb.expected +6 -0
- data/vendor/spinel/test/method_level_rescue_return_string.rb +39 -0
- data/vendor/spinel/test/method_level_rescue_return_string.rb.expected +6 -0
- data/vendor/spinel/test/method_return_mixed_int_string.rb +65 -0
- data/vendor/spinel/test/method_return_mixed_int_string.rb.expected +10 -0
- data/vendor/spinel/test/middle_splat_param.rb +14 -0
- data/vendor/spinel/test/middle_splat_param.rb.expected +4 -0
- data/vendor/spinel/test/missing_positional_arg_warns.rb +23 -0
- data/vendor/spinel/test/missing_positional_arg_warns.rb.expected +2 -0
- data/vendor/spinel/test/module_accessor_delete_chain.rb +32 -0
- data/vendor/spinel/test/module_accessor_delete_chain.rb.expected +1 -0
- data/vendor/spinel/test/module_accessor_nil_check.rb +40 -0
- data/vendor/spinel/test/module_accessor_nil_check.rb.expected +2 -0
- data/vendor/spinel/test/module_body_string_const_interp.rb +40 -0
- data/vendor/spinel/test/module_body_string_const_interp.rb.expected +5 -0
- data/vendor/spinel/test/module_class_body_side_effects.rb +32 -0
- data/vendor/spinel/test/module_class_body_side_effects.rb.expected +7 -0
- data/vendor/spinel/test/module_class_reflection.rb +27 -0
- data/vendor/spinel/test/module_class_reflection.rb.expected +14 -0
- data/vendor/spinel/test/module_const_defined.rb +13 -0
- data/vendor/spinel/test/module_const_defined.rb.expected +4 -0
- data/vendor/spinel/test/module_cvars.rb +32 -0
- data/vendor/spinel/test/module_cvars.rb.expected +4 -0
- data/vendor/spinel/test/module_def_self_ivar.rb +14 -0
- data/vendor/spinel/test/module_def_self_ivar.rb.expected +2 -0
- data/vendor/spinel/test/module_function_dead_method_dce.rb +29 -0
- data/vendor/spinel/test/module_function_dead_method_dce.rb.expected +2 -0
- data/vendor/spinel/test/module_function_namespace.rb.expected +3 -0
- data/vendor/spinel/test/module_function_str_method_param.rb +14 -0
- data/vendor/spinel/test/module_function_str_method_param.rb.expected +1 -0
- data/vendor/spinel/test/module_ivar_empty_hash_reset.rb +64 -0
- data/vendor/spinel/test/module_ivar_empty_hash_reset.rb.expected +7 -0
- data/vendor/spinel/test/module_ivar_nil_poly.rb +17 -0
- data/vendor/spinel/test/module_ivar_op_write.rb +53 -0
- data/vendor/spinel/test/module_ivar_op_write.rb.expected +3 -0
- data/vendor/spinel/test/module_reopen_after_include_ivar.rb +33 -0
- data/vendor/spinel/test/module_reopen_after_include_ivar.rb.expected +2 -0
- data/vendor/spinel/test/module_reopen_after_include_namespaced.rb +33 -0
- data/vendor/spinel/test/module_reopen_after_include_namespaced.rb.expected +1 -0
- data/vendor/spinel/test/module_reopen_cmeth_redefine.rb +26 -0
- data/vendor/spinel/test/module_reopen_cmeth_redefine.rb.expected +2 -0
- data/vendor/spinel/test/module_singleton_attr_accessor.rb +41 -0
- data/vendor/spinel/test/module_singleton_attr_accessor.rb.expected +4 -0
- data/vendor/spinel/test/module_singleton_new_user_method.rb +23 -0
- data/vendor/spinel/test/module_singleton_new_user_method.rb.expected +2 -0
- data/vendor/spinel/test/module_singleton_self_accessor.rb +31 -0
- data/vendor/spinel/test/module_singleton_self_accessor.rb.expected +4 -0
- data/vendor/spinel/test/more_truthies_nil_compare.rb +31 -0
- data/vendor/spinel/test/more_truthies_nil_compare.rb.expected +9 -0
- data/vendor/spinel/test/multi_arg_lambda.rb +31 -0
- data/vendor/spinel/test/multi_arg_lambda.rb.expected +6 -0
- data/vendor/spinel/test/multi_assign_ivar_type.rb +21 -0
- data/vendor/spinel/test/multi_assign_ivar_type.rb.expected +3 -0
- data/vendor/spinel/test/multi_write_call_rhs_as_expr.rb +31 -0
- data/vendor/spinel/test/multi_write_call_rhs_as_expr.rb.expected +10 -0
- data/vendor/spinel/test/multi_write_scalar_rhs.rb +12 -0
- data/vendor/spinel/test/multi_write_scalar_rhs.rb.expected +5 -0
- data/vendor/spinel/test/mutable_str_alias_uaf.rb +62 -0
- data/vendor/spinel/test/mutable_str_alias_uaf.rb.expected +4 -0
- data/vendor/spinel/test/mutable_str_force_encoding_return.rb +21 -0
- data/vendor/spinel/test/mutable_str_force_encoding_return.rb.expected +1 -0
- data/vendor/spinel/test/mutable_str_into_str_array.rb +46 -0
- data/vendor/spinel/test/mutable_str_into_str_array.rb.expected +7 -0
- data/vendor/spinel/test/mutex_synchronize_block.rb +29 -0
- data/vendor/spinel/test/mutex_synchronize_block.rb.expected +6 -0
- data/vendor/spinel/test/narrow_and_predicate.rb +23 -0
- data/vendor/spinel/test/narrow_and_predicate.rb.expected +1 -0
- data/vendor/spinel/test/negative_range_slice.rb +32 -0
- data/vendor/spinel/test/negative_range_slice.rb.expected +12 -0
- data/vendor/spinel/test/nested_array_eq.rb +22 -0
- data/vendor/spinel/test/nested_array_eq.rb.expected +12 -0
- data/vendor/spinel/test/nested_yield_with_block_param.rb +33 -0
- data/vendor/spinel/test/nil_default_param_widens_to_poly.rb +26 -0
- data/vendor/spinel/test/nil_default_param_widens_to_poly.rb.expected +4 -0
- data/vendor/spinel/test/nil_guard_narrows_int_or_nil.rb +40 -0
- data/vendor/spinel/test/nil_guard_narrows_int_or_nil.rb.expected +5 -0
- data/vendor/spinel/test/not_match_op.rb +8 -0
- data/vendor/spinel/test/not_match_op.rb.expected +4 -0
- data/vendor/spinel/test/nullable_hash_recv_bracket.expected +3 -0
- data/vendor/spinel/test/nullable_hash_recv_bracket.rb +33 -0
- data/vendor/spinel/test/nullable_obj_nil_compare.rb +92 -0
- data/vendor/spinel/test/nullable_obj_nil_compare.rb.expected +8 -0
- data/vendor/spinel/test/nullable_poly_hash_arg_unbox.rb +85 -0
- data/vendor/spinel/test/nullable_poly_hash_arg_unbox.rb.expected +4 -0
- data/vendor/spinel/test/numeric_coerce.rb +24 -0
- data/vendor/spinel/test/numeric_coerce.rb.expected +9 -0
- data/vendor/spinel/test/obj_class_dispatch.rb +69 -0
- data/vendor/spinel/test/obj_class_dispatch.rb.expected +4 -0
- data/vendor/spinel/test/object_id_basic.rb +19 -0
- data/vendor/spinel/test/object_id_basic.rb.expected +8 -0
- data/vendor/spinel/test/object_method_gaps_callee_clone_itself.rb +23 -0
- data/vendor/spinel/test/object_method_gaps_callee_clone_itself.rb.expected +7 -0
- data/vendor/spinel/test/op_assign_user_operator.rb.expected +2 -0
- data/vendor/spinel/test/op_method_bitwise.rb +30 -0
- data/vendor/spinel/test/op_method_bitwise.rb.expected +8 -0
- data/vendor/spinel/test/op_method_shift.rb +30 -0
- data/vendor/spinel/test/op_method_shift.rb.expected +3 -0
- data/vendor/spinel/test/open_class_array_builtin.rb +11 -0
- data/vendor/spinel/test/open_class_array_builtin.rb.expected +1 -0
- data/vendor/spinel/test/open_class_bool_builtin.rb +28 -0
- data/vendor/spinel/test/open_class_bool_builtin.rb.expected +6 -0
- data/vendor/spinel/test/open_class_default_arg.rb +47 -0
- data/vendor/spinel/test/open_class_default_arg.rb.expected +9 -0
- data/vendor/spinel/test/open_class_object.rb +26 -0
- data/vendor/spinel/test/open_class_object.rb.expected +5 -0
- data/vendor/spinel/test/pack_utf8.rb +12 -0
- data/vendor/spinel/test/pack_utf8.rb.expected +10 -0
- data/vendor/spinel/test/param_body_array_hash_inference.rb +58 -0
- data/vendor/spinel/test/param_body_array_hash_inference.rb.expected +3 -0
- data/vendor/spinel/test/param_body_hash_inference.rb +72 -0
- data/vendor/spinel/test/param_body_hash_inference.rb.expected +6 -0
- data/vendor/spinel/test/param_include_body_widen.rb +32 -0
- data/vendor/spinel/test/param_include_body_widen.rb.expected +5 -0
- data/vendor/spinel/test/param_lengthlike_body_widen.rb +53 -0
- data/vendor/spinel/test/param_lengthlike_body_widen.rb.expected +6 -0
- data/vendor/spinel/test/param_ptr_from_callee_slot.rb +42 -0
- data/vendor/spinel/test/param_ptr_from_callee_slot.rb.expected +6 -0
- data/vendor/spinel/test/parser_long_ast_record.rb +132 -0
- data/vendor/spinel/test/parser_long_ast_record.rb.expected +3 -0
- data/vendor/spinel/test/pattern_match_as_and_guard.rb +33 -0
- data/vendor/spinel/test/pattern_match_as_and_guard.rb.expected +4 -0
- data/vendor/spinel/test/pattern_match_splat_rest.rb +15 -0
- data/vendor/spinel/test/pattern_match_splat_rest.rb.expected +4 -0
- data/vendor/spinel/test/pattern_pin.rb +105 -0
- data/vendor/spinel/test/pattern_pin.rb.expected +11 -0
- data/vendor/spinel/test/poly_arg_int_keyed_hash.rb +42 -0
- data/vendor/spinel/test/poly_arg_int_keyed_hash.rb.expected +5 -0
- data/vendor/spinel/test/poly_arg_to_typed_method_param.rb +28 -0
- data/vendor/spinel/test/poly_arg_to_typed_method_param.rb.expected +4 -0
- data/vendor/spinel/test/poly_array_assoc.rb +9 -0
- data/vendor/spinel/test/poly_array_compact.rb +15 -0
- data/vendor/spinel/test/poly_array_compact.rb.expected +3 -0
- data/vendor/spinel/test/poly_array_compact_bang.rb +17 -0
- data/vendor/spinel/test/poly_array_compact_bang.rb.expected +3 -0
- data/vendor/spinel/test/poly_array_dup.rb +52 -0
- data/vendor/spinel/test/poly_array_dup.rb.expected +5 -0
- data/vendor/spinel/test/poly_array_flatten.rb +17 -0
- data/vendor/spinel/test/poly_array_flatten.rb.expected +4 -0
- data/vendor/spinel/test/poly_attr_reader_box_dispatch.expected +1 -0
- data/vendor/spinel/test/poly_attr_reader_box_dispatch.rb +35 -0
- data/vendor/spinel/test/poly_attr_writer.rb +95 -0
- data/vendor/spinel/test/poly_attr_writer.rb.expected +5 -0
- data/vendor/spinel/test/poly_class_dispatch.rb +11 -0
- data/vendor/spinel/test/poly_class_dispatch.rb.expected +7 -0
- data/vendor/spinel/test/poly_dispatch_inherited_attr_reader.rb +20 -0
- data/vendor/spinel/test/poly_dispatch_inherited_attr_reader.rb.expected +2 -0
- data/vendor/spinel/test/poly_dispatch_inherited_method.rb +19 -0
- data/vendor/spinel/test/poly_dispatch_inherited_method.rb.expected +2 -0
- data/vendor/spinel/test/poly_dispatch_multilevel_inheritance.rb +28 -0
- data/vendor/spinel/test/poly_dispatch_multilevel_inheritance.rb.expected +4 -0
- data/vendor/spinel/test/poly_dispatch_narrow_return_type.rb +53 -0
- data/vendor/spinel/test/poly_dispatch_narrow_return_type.rb.expected +3 -0
- data/vendor/spinel/test/poly_each_receiver_gc_root.rb +40 -0
- data/vendor/spinel/test/poly_each_receiver_gc_root.rb.expected +1 -0
- data/vendor/spinel/test/poly_gsub_str_arm.rb +24 -0
- data/vendor/spinel/test/poly_gsub_str_arm.rb.expected +1 -0
- data/vendor/spinel/test/poly_gsub_str_str_hash.rb +37 -0
- data/vendor/spinel/test/poly_gsub_str_str_hash.rb.expected +1 -0
- data/vendor/spinel/test/poly_idx_int_nullable.rb +14 -0
- data/vendor/spinel/test/poly_idx_int_nullable.rb.expected +1 -0
- data/vendor/spinel/test/poly_length_string_tag.rb +20 -0
- data/vendor/spinel/test/poly_length_string_tag.rb.expected +3 -0
- data/vendor/spinel/test/poly_local_is_a_string_narrow_in_non_tail_if.rb +39 -0
- data/vendor/spinel/test/poly_local_is_a_string_narrow_in_non_tail_if.rb.expected +1 -0
- data/vendor/spinel/test/poly_local_is_a_string_narrow_in_tail_if.rb +23 -0
- data/vendor/spinel/test/poly_local_is_a_string_narrow_in_tail_if.rb.expected +1 -0
- data/vendor/spinel/test/poly_local_is_a_string_narrow_in_y1_inline.rb +27 -0
- data/vendor/spinel/test/poly_local_is_a_string_narrow_in_y1_inline.rb.expected +1 -0
- data/vendor/spinel/test/poly_poly_hash_each_value.rb +34 -0
- data/vendor/spinel/test/poly_poly_hash_each_value.rb.expected +11 -0
- data/vendor/spinel/test/poly_poly_hash_fetch_dup.rb +26 -0
- data/vendor/spinel/test/poly_poly_hash_fetch_dup.rb.expected +2 -0
- data/vendor/spinel/test/poly_recv_aset_poly_key.rb +30 -0
- data/vendor/spinel/test/poly_recv_aset_poly_key.rb.expected +1 -0
- data/vendor/spinel/test/poly_recv_dispatch_narrow_module.rb +37 -0
- data/vendor/spinel/test/poly_recv_dispatch_narrow_module.rb.expected +3 -0
- data/vendor/spinel/test/poly_recv_fetch_arm_incompat.rb +37 -0
- data/vendor/spinel/test/poly_recv_fetch_arm_incompat.rb.expected +1 -0
- data/vendor/spinel/test/poly_recv_fetch_hash_arms.rb +31 -0
- data/vendor/spinel/test/poly_recv_fetch_hash_arms.rb.expected +2 -0
- data/vendor/spinel/test/poly_recv_hash_dup.rb +48 -0
- data/vendor/spinel/test/poly_recv_hash_each_dispatch.rb +40 -0
- data/vendor/spinel/test/poly_recv_hash_each_dispatch.rb.expected +5 -0
- data/vendor/spinel/test/poly_recv_map_join.rb +31 -0
- data/vendor/spinel/test/poly_recv_map_join.rb.expected +5 -0
- data/vendor/spinel/test/poly_recv_range_sym_hash_dispatch.rb +55 -0
- data/vendor/spinel/test/poly_recv_range_sym_hash_dispatch.rb.expected +9 -0
- data/vendor/spinel/test/poly_recv_two_arg_slice.rb +27 -0
- data/vendor/spinel/test/poly_value_into_str_str_hash.rb +44 -0
- data/vendor/spinel/test/poly_value_into_str_str_hash.rb.expected +2 -0
- data/vendor/spinel/test/prepend_super_basic.rb +18 -0
- data/vendor/spinel/test/prepend_super_basic.rb.expected +1 -0
- data/vendor/spinel/test/prepend_super_chain.rb +23 -0
- data/vendor/spinel/test/prepend_super_chain.rb.expected +1 -0
- data/vendor/spinel/test/prepend_super_forward.rb +17 -0
- data/vendor/spinel/test/prepend_super_forward.rb.expected +1 -0
- data/vendor/spinel/test/prepend_super_multi.rb +25 -0
- data/vendor/spinel/test/prepend_super_multi.rb.expected +1 -0
- data/vendor/spinel/test/prepend_super_subclass.rb +21 -0
- data/vendor/spinel/test/prepend_super_subclass.rb.expected +1 -0
- data/vendor/spinel/test/primitive_eq_user_class.rb +84 -0
- data/vendor/spinel/test/primitive_eq_user_class.rb.expected +49 -0
- data/vendor/spinel/test/proc_anon_dot_call.rb +50 -0
- data/vendor/spinel/test/proc_anon_dot_call.rb.expected +5 -0
- data/vendor/spinel/test/proc_arg_object.rb +17 -0
- data/vendor/spinel/test/proc_arg_object.rb.expected +1 -0
- data/vendor/spinel/test/proc_call_value_return.rb +20 -0
- data/vendor/spinel/test/proc_call_value_return.rb.expected +5 -0
- data/vendor/spinel/test/proc_introspection.rb +11 -0
- data/vendor/spinel/test/proc_introspection.rb.expected +6 -0
- data/vendor/spinel/test/process_pid.rb +5 -0
- data/vendor/spinel/test/process_pid.rb.expected +2 -0
- data/vendor/spinel/test/ptr_array_to_poly_array_at_call.rb +39 -0
- data/vendor/spinel/test/ptr_array_to_poly_array_at_call.rb.expected +2 -0
- data/vendor/spinel/test/public_send.rb +12 -0
- data/vendor/spinel/test/public_send.rb.expected +4 -0
- data/vendor/spinel/test/puts_class_value.rb +10 -0
- data/vendor/spinel/test/puts_class_value.rb.expected +2 -0
- data/vendor/spinel/test/puts_nil.rb +7 -0
- data/vendor/spinel/test/raise_reraise_in_def.rb +44 -0
- data/vendor/spinel/test/raise_unless_isa_narrow.rb +72 -0
- data/vendor/spinel/test/raise_unless_isa_narrow.rb.expected +3 -0
- data/vendor/spinel/test/range_and_array_range_args.rb +43 -0
- data/vendor/spinel/test/range_and_array_range_args.rb.expected +18 -0
- data/vendor/spinel/test/range_begin_end_polyarr_include_delete_bang.rb +21 -0
- data/vendor/spinel/test/range_bsearch.rb +11 -0
- data/vendor/spinel/test/range_bsearch.rb.expected +8 -0
- data/vendor/spinel/test/range_case_eq_type_mismatch.rb +10 -0
- data/vendor/spinel/test/range_case_eq_type_mismatch.rb.expected +7 -0
- data/vendor/spinel/test/range_each_fusion.rb +109 -0
- data/vendor/spinel/test/range_each_fusion.rb.expected +16 -0
- data/vendor/spinel/test/range_each_no_block.rb +5 -0
- data/vendor/spinel/test/range_equality.rb +9 -0
- data/vendor/spinel/test/range_exclude_end_size.rb +24 -0
- data/vendor/spinel/test/range_exclude_end_size.rb.expected +18 -0
- data/vendor/spinel/test/range_exclusive_nonliteral.rb +34 -0
- data/vendor/spinel/test/range_overlap_array_flatten_depth.rb +14 -0
- data/vendor/spinel/test/range_overlap_array_flatten_depth.rb.expected +8 -0
- data/vendor/spinel/test/range_step.rb +15 -0
- data/vendor/spinel/test/range_step.rb.expected +5 -0
- data/vendor/spinel/test/range_string_cover.rb +10 -0
- data/vendor/spinel/test/rational_complex_literals.rb +27 -0
- data/vendor/spinel/test/rational_complex_literals.rb.expected +12 -0
- data/vendor/spinel/test/rbs/README.md +45 -0
- data/vendor/spinel/test/rbs/arrays.rbs +10 -0
- data/vendor/spinel/test/rbs/arrays.seed.expected +7 -0
- data/vendor/spinel/test/rbs/dropped_members.rbs +13 -0
- data/vendor/spinel/test/rbs/dropped_members.seed.expected +2 -0
- data/vendor/spinel/test/rbs/dropped_sigs.rbs +14 -0
- data/vendor/spinel/test/rbs/dropped_sigs.seed.expected +2 -0
- data/vendor/spinel/test/rbs/dropped_types.rbs +14 -0
- data/vendor/spinel/test/rbs/dropped_types.seed.expected +2 -0
- data/vendor/spinel/test/rbs/hashes.rbs +11 -0
- data/vendor/spinel/test/rbs/hashes.seed.expected +7 -0
- data/vendor/spinel/test/rbs/ivars.rbs +8 -0
- data/vendor/spinel/test/rbs/ivars.seed.expected +5 -0
- data/vendor/spinel/test/rbs/members.rbs +7 -0
- data/vendor/spinel/test/rbs/members.seed.expected +5 -0
- data/vendor/spinel/test/rbs/nominal.rbs +7 -0
- data/vendor/spinel/test/rbs/nominal.seed.expected +3 -0
- data/vendor/spinel/test/rbs/nullable.rbs +8 -0
- data/vendor/spinel/test/rbs/nullable.seed.expected +5 -0
- data/vendor/spinel/test/rbs/overloads.rbs +13 -0
- data/vendor/spinel/test/rbs/overloads.seed.expected +3 -0
- data/vendor/spinel/test/rbs/scalars.rbs +12 -0
- data/vendor/spinel/test/rbs/scalars.seed.expected +9 -0
- data/vendor/spinel/test/rbs/union_poly.rbs +9 -0
- data/vendor/spinel/test/rbs/union_poly.seed.expected +4 -0
- data/vendor/spinel/test/rbs/unqualified.rbs +13 -0
- data/vendor/spinel/test/rbs/unqualified.seed.expected +4 -0
- data/vendor/spinel/test/recursive_block_forward_value.rb +19 -0
- data/vendor/spinel/test/recursive_block_forward_value.rb.expected +3 -0
- data/vendor/spinel/test/recursive_implicit_yield_value.rb +35 -0
- data/vendor/spinel/test/recursive_implicit_yield_value.rb.expected +7 -0
- data/vendor/spinel/test/regex.rb +7 -0
- data/vendor/spinel/test/regex.rb.expected +2 -0
- data/vendor/spinel/test/regex_backref_oob_nil.rb +13 -0
- data/vendor/spinel/test/regex_backref_oob_nil.rb.expected +5 -0
- data/vendor/spinel/test/regex_const_freeze.rb +17 -0
- data/vendor/spinel/test/regex_const_freeze.rb.expected +3 -0
- data/vendor/spinel/test/regex_hex_shorthand.rb +13 -0
- data/vendor/spinel/test/regex_hex_shorthand.rb.expected +12 -0
- data/vendor/spinel/test/regex_match_nonstring.rb +18 -0
- data/vendor/spinel/test/regex_match_nonstring.rb.expected +3 -0
- data/vendor/spinel/test/regex_namespaced_const.rb +17 -0
- data/vendor/spinel/test/regex_namespaced_const.rb.expected +4 -0
- data/vendor/spinel/test/regex_negated_shorthand_toplevel.rb +9 -0
- data/vendor/spinel/test/regex_negated_shorthand_toplevel.rb.expected +13 -0
- data/vendor/spinel/test/regex_sub_backref.rb +11 -0
- data/vendor/spinel/test/regex_unanchored_class_seq.rb +13 -0
- data/vendor/spinel/test/regex_unanchored_class_seq.rb.expected +6 -0
- data/vendor/spinel/test/regexp_capture_ivar_hash.rb +48 -0
- data/vendor/spinel/test/regexp_capture_ivar_hash.rb.expected +7 -0
- data/vendor/spinel/test/regexp_error_catchable.rb +14 -0
- data/vendor/spinel/test/regexp_error_catchable.rb.expected +3 -0
- data/vendor/spinel/test/regexp_gsub_zero_width.rb +27 -0
- data/vendor/spinel/test/regexp_gsub_zero_width.rb.expected +12 -0
- data/vendor/spinel/test/regexp_last_match_capture.rb +21 -0
- data/vendor/spinel/test/regexp_last_match_capture.rb.expected +7 -0
- data/vendor/spinel/test/regexp_line_anchors.rb +34 -0
- data/vendor/spinel/test/regexp_line_anchors.rb.expected +15 -0
- data/vendor/spinel/test/regexp_local_if_else_union.rb +36 -0
- data/vendor/spinel/test/regexp_local_if_else_union.rb.expected +5 -0
- data/vendor/spinel/test/regexp_match_data.rb +17 -0
- data/vendor/spinel/test/regexp_match_data.rb.expected +8 -0
- data/vendor/spinel/test/regexp_match_p_position.rb +7 -0
- data/vendor/spinel/test/regexp_match_p_position.rb.expected +4 -0
- data/vendor/spinel/test/regexp_new_literal.rb +72 -0
- data/vendor/spinel/test/regexp_new_literal.rb.expected +24 -0
- data/vendor/spinel/test/regexp_source_options.rb +10 -0
- data/vendor/spinel/test/regexp_source_options.rb.expected +5 -0
- data/vendor/spinel/test/regexp_sub_gsub_length.rb +17 -0
- data/vendor/spinel/test/regexp_sub_gsub_length.rb.expected +10 -0
- data/vendor/spinel/test/regexp_sub_gsub_realloc.rb +25 -0
- data/vendor/spinel/test/regexp_sub_gsub_realloc.rb.expected +6 -0
- data/vendor/spinel/test/regexp_threeq.rb +15 -0
- data/vendor/spinel/test/regexp_threeq.rb.expected +4 -0
- data/vendor/spinel/test/require_first_line.rb +7 -0
- data/vendor/spinel/test/require_first_line.rb.expected +1 -0
- data/vendor/spinel/test/require_modifier.rb +12 -0
- data/vendor/spinel/test/require_same_line.rb +5 -0
- data/vendor/spinel/test/require_same_line.rb.expected +2 -0
- data/vendor/spinel/test/require_unsupported_lib.rb +8 -0
- data/vendor/spinel/test/require_unsupported_lib.rb.expected +2 -0
- data/vendor/spinel/test/rescue_bind_in_fiber_body.rb +24 -0
- data/vendor/spinel/test/rescue_bind_in_fiber_body.rb.expected +6 -0
- data/vendor/spinel/test/rescue_bind_in_lambda_proc_body.rb +16 -0
- data/vendor/spinel/test/rescue_bind_in_lambda_proc_body.rb.expected +4 -0
- data/vendor/spinel/test/rescue_walks_class_hierarchy.rb +37 -0
- data/vendor/spinel/test/rescue_walks_class_hierarchy.rb.expected +7 -0
- data/vendor/spinel/test/rescue_widens_nil_local_to_exception.expected +1 -0
- data/vendor/spinel/test/rescue_widens_nil_local_to_exception.rb +11 -0
- data/vendor/spinel/test/respond_to_def_self_method.rb +14 -0
- data/vendor/spinel/test/respond_to_module_singleton_accessor.rb +10 -0
- data/vendor/spinel/test/respond_to_module_singleton_accessor.rb.expected +3 -0
- data/vendor/spinel/test/respond_to_primitive.rb +9 -0
- data/vendor/spinel/test/respond_to_primitive.rb.expected +5 -0
- data/vendor/spinel/test/return_unify_int_vs_pointer.rb +37 -0
- data/vendor/spinel/test/return_unify_int_vs_pointer.rb.expected +4 -0
- data/vendor/spinel/test/return_unify_isa_early_return.rb +29 -0
- data/vendor/spinel/test/return_unify_isa_early_return.rb.expected +2 -0
- data/vendor/spinel/test/rightward_assign_destructure.rb +27 -0
- data/vendor/spinel/test/rightward_assign_destructure.rb.expected +8 -0
- data/vendor/spinel/test/safe_navigation_returns_nil.expected +6 -0
- data/vendor/spinel/test/safe_navigation_returns_nil.rb +28 -0
- data/vendor/spinel/test/same_named_attr_no_widen.rb +24 -0
- data/vendor/spinel/test/same_named_attr_no_widen.rb.expected +1 -0
- data/vendor/spinel/test/scan_locals_def_boundary.rb +58 -0
- data/vendor/spinel/test/scan_locals_def_boundary.rb.expected +2 -0
- data/vendor/spinel/test/scan_walkers_def_boundary.rb +52 -0
- data/vendor/spinel/test/scan_walkers_def_boundary.rb.expected +4 -0
- data/vendor/spinel/test/self_class_divergent_returns.rb +61 -0
- data/vendor/spinel/test/self_class_divergent_returns.rb.expected +3 -0
- data/vendor/spinel/test/self_class_new_construction.rb +16 -0
- data/vendor/spinel/test/self_class_new_construction.rb.expected +2 -0
- data/vendor/spinel/test/self_operator_subclass_dispatch.rb +40 -0
- data/vendor/spinel/test/self_operator_subclass_dispatch.rb.expected +2 -0
- data/vendor/spinel/test/signal_trap_stub.rb +30 -0
- data/vendor/spinel/test/signal_trap_stub.rb.expected +3 -0
- data/vendor/spinel/test/sort_min_max_block_comparator.rb +18 -0
- data/vendor/spinel/test/sort_min_max_block_comparator.rb.expected +7 -0
- data/vendor/spinel/test/source_encoding.rb +19 -0
- data/vendor/spinel/test/source_encoding.rb.expected +9 -0
- data/vendor/spinel/test/sp_crypto_basic.rb +45 -0
- data/vendor/spinel/test/sp_crypto_basic.rb.expected +6 -0
- data/vendor/spinel/test/sp_crypto_sha1.rb +18 -0
- data/vendor/spinel/test/sp_crypto_sha1.rb.expected +3 -0
- data/vendor/spinel/test/sp_net_basic.rb +17 -0
- data/vendor/spinel/test/sp_net_basic.rb.expected +2 -0
- data/vendor/spinel/test/splat_heterogeneous_call.rb +14 -0
- data/vendor/spinel/test/splat_params_instance.rb +21 -0
- data/vendor/spinel/test/splat_params_instance.rb.expected +8 -0
- data/vendor/spinel/test/sprintf_long_interp.rb +23 -0
- data/vendor/spinel/test/sprintf_long_interp.rb.expected +3 -0
- data/vendor/spinel/test/stdlib_class_in_ivar.rb +85 -0
- data/vendor/spinel/test/stdlib_class_in_ivar.rb.expected +7 -0
- data/vendor/spinel/test/str_array_max_min_by.rb +6 -0
- data/vendor/spinel/test/str_array_sort_by.rb +11 -0
- data/vendor/spinel/test/str_array_sort_by.rb.expected +5 -0
- data/vendor/spinel/test/str_chomp_paragraph_mode.rb +16 -0
- data/vendor/spinel/test/str_chomp_paragraph_mode.rb.expected +9 -0
- data/vendor/spinel/test/str_count_multi_arg.rb +11 -0
- data/vendor/spinel/test/str_count_multi_arg.rb.expected +4 -0
- data/vendor/spinel/test/str_delete_count_negation.rb +16 -0
- data/vendor/spinel/test/str_delete_count_negation.rb.expected +6 -0
- data/vendor/spinel/test/str_force_encoding_return.rb +26 -0
- data/vendor/spinel/test/str_force_encoding_return.rb.expected +3 -0
- data/vendor/spinel/test/str_int_hash_missing_key_nil.rb +16 -0
- data/vendor/spinel/test/str_int_hash_missing_key_nil.rb.expected +10 -0
- data/vendor/spinel/test/str_match_position.rb +15 -0
- data/vendor/spinel/test/str_match_position.rb.expected +8 -0
- data/vendor/spinel/test/str_method_nil_arg_no_segv.rb +47 -0
- data/vendor/spinel/test/str_method_nil_arg_no_segv.rb.expected +7 -0
- data/vendor/spinel/test/str_oct_prefix.rb +11 -0
- data/vendor/spinel/test/str_oct_prefix.rb.expected +5 -0
- data/vendor/spinel/test/str_ord_empty.rb +11 -0
- data/vendor/spinel/test/str_ord_empty.rb.expected +3 -0
- data/vendor/spinel/test/str_poly_hash_dup_widen.rb +21 -0
- data/vendor/spinel/test/str_poly_hash_dup_widen.rb.expected +4 -0
- data/vendor/spinel/test/str_poly_hash_dup_widen_from_int.rb +17 -0
- data/vendor/spinel/test/str_poly_hash_dup_widen_from_int.rb.expected +3 -0
- data/vendor/spinel/test/str_rindex_position.rb +23 -0
- data/vendor/spinel/test/str_rindex_position.rb.expected +17 -0
- data/vendor/spinel/test/str_split_space_whitespace.rb +10 -0
- data/vendor/spinel/test/str_split_space_whitespace.rb.expected +4 -0
- data/vendor/spinel/test/str_split_trailing_empty.rb +12 -0
- data/vendor/spinel/test/str_split_trailing_empty.rb.expected +7 -0
- data/vendor/spinel/test/str_squeeze_arg.rb +18 -0
- data/vendor/spinel/test/str_squeeze_arg.rb.expected +7 -0
- data/vendor/spinel/test/str_str_hash_nullable_bracket_assign.rb +39 -0
- data/vendor/spinel/test/str_str_hash_nullable_bracket_assign.rb.expected +5 -0
- data/vendor/spinel/test/str_to_i_overflow.rb +32 -0
- data/vendor/spinel/test/str_to_i_overflow.rb.expected +6 -0
- data/vendor/spinel/test/str_tr_negation.rb +10 -0
- data/vendor/spinel/test/str_tr_negation.rb.expected +4 -0
- data/vendor/spinel/test/string_byteslice.rb +9 -0
- data/vendor/spinel/test/string_byteslice.rb.expected +6 -0
- data/vendor/spinel/test/string_casecmp.rb +9 -0
- data/vendor/spinel/test/string_casecmp.rb.expected +7 -0
- data/vendor/spinel/test/string_charset_range_notation.rb +9 -0
- data/vendor/spinel/test/string_charset_range_notation.rb.expected +5 -0
- data/vendor/spinel/test/string_chomp_bang_mutates.rb +20 -0
- data/vendor/spinel/test/string_chomp_bang_mutates.rb.expected +4 -0
- data/vendor/spinel/test/string_crypt.rb +22 -0
- data/vendor/spinel/test/string_crypt.rb.expected +7 -0
- data/vendor/spinel/test/string_delete_prefix_suffix_bang.rb +29 -0
- data/vendor/spinel/test/string_delete_prefix_suffix_bang.rb.expected +5 -0
- data/vendor/spinel/test/string_each_char_returns_self.rb +15 -0
- data/vendor/spinel/test/string_each_line_expr.rb +25 -0
- data/vendor/spinel/test/string_each_line_expr.rb.expected +19 -0
- data/vendor/spinel/test/string_each_line_keeps_newline.rb +15 -0
- data/vendor/spinel/test/string_encoding.rb +8 -0
- data/vendor/spinel/test/string_encoding.rb.expected +5 -0
- data/vendor/spinel/test/string_enum_block_returns_self.rb +27 -0
- data/vendor/spinel/test/string_enum_block_returns_self.rb.expected +11 -0
- data/vendor/spinel/test/string_format_polyarr.rb +6 -0
- data/vendor/spinel/test/string_frozen_mutation_raises.rb +27 -0
- data/vendor/spinel/test/string_frozen_mutation_raises.rb.expected +4 -0
- data/vendor/spinel/test/string_gsub_block.rb +8 -0
- data/vendor/spinel/test/string_index_nil_typeerror.rb +9 -0
- data/vendor/spinel/test/string_index_nil_typeerror.rb.expected +1 -0
- data/vendor/spinel/test/string_index_regex.rb +9 -0
- data/vendor/spinel/test/string_index_regex.rb.expected +6 -0
- data/vendor/spinel/test/string_lshift_int_codepoint.rb +9 -0
- data/vendor/spinel/test/string_lshift_int_codepoint.rb.expected +2 -0
- data/vendor/spinel/test/string_multiply_overflow.rb +14 -0
- data/vendor/spinel/test/string_multiply_overflow.rb.expected +4 -0
- data/vendor/spinel/test/string_nil_arg_typeerror.rb +38 -0
- data/vendor/spinel/test/string_nil_arg_typeerror.rb.expected +5 -0
- data/vendor/spinel/test/string_param_inference_from_body.rb +40 -0
- data/vendor/spinel/test/string_param_inference_from_body.rb.expected +3 -0
- data/vendor/spinel/test/string_partition_regex_and_puts.rb +8 -0
- data/vendor/spinel/test/string_partition_regex_and_puts.rb.expected +6 -0
- data/vendor/spinel/test/string_range_to_a.rb +10 -0
- data/vendor/spinel/test/string_range_to_a.rb.expected +4 -0
- data/vendor/spinel/test/string_replace_prepend_insert_clear.rb +34 -0
- data/vendor/spinel/test/string_rpartition_regex.rb +10 -0
- data/vendor/spinel/test/string_scanner_basic.rb +35 -0
- data/vendor/spinel/test/string_scanner_check_unscan.rb +24 -0
- data/vendor/spinel/test/string_scanner_getch_utf8.rb +26 -0
- data/vendor/spinel/test/string_scanner_nullable.rb +75 -0
- data/vendor/spinel/test/string_scanner_nullable.rb.expected +37 -0
- data/vendor/spinel/test/string_scanner_scan_to_local.rb +11 -0
- data/vendor/spinel/test/string_scanner_unscan_error.rb +54 -0
- data/vendor/spinel/test/string_scrub.rb +7 -0
- data/vendor/spinel/test/string_slice_range.rb +18 -0
- data/vendor/spinel/test/string_split_capture.rb +8 -0
- data/vendor/spinel/test/string_split_inline_arg_gc_root.rb +77 -0
- data/vendor/spinel/test/string_split_inline_arg_gc_root.rb.expected +1 -0
- data/vendor/spinel/test/string_squeeze_no_trailing_nul.rb +12 -0
- data/vendor/spinel/test/string_sub_hash_replacement.rb +8 -0
- data/vendor/spinel/test/string_to_i_base.rb +9 -0
- data/vendor/spinel/test/string_to_i_base.rb.expected +6 -0
- data/vendor/spinel/test/string_to_i_base_zero.rb +9 -0
- data/vendor/spinel/test/string_to_i_overflow_raises.rb +30 -0
- data/vendor/spinel/test/string_to_i_overflow_raises.rb.expected +6 -0
- data/vendor/spinel/test/string_undump.rb +9 -0
- data/vendor/spinel/test/string_upto.rb +20 -0
- data/vendor/spinel/test/string_with_embedded_nul.rb +46 -0
- data/vendor/spinel/test/string_with_embedded_nul.rb.expected +16 -0
- data/vendor/spinel/test/stringio_new_mode.rb +21 -0
- data/vendor/spinel/test/stringio_new_mode.rb.expected +5 -0
- data/vendor/spinel/test/stringio_new_nil.rb +14 -0
- data/vendor/spinel/test/stringio_new_nil.rb.expected +2 -0
- data/vendor/spinel/test/stringio_open.rb +30 -0
- data/vendor/spinel/test/stringio_open.rb.expected +10 -0
- data/vendor/spinel/test/stringio_putc_fsync.rb +24 -0
- data/vendor/spinel/test/stringio_putc_fsync.rb.expected +3 -0
- data/vendor/spinel/test/strip_nul.rb +13 -0
- data/vendor/spinel/test/strip_nul.rb.expected +8 -0
- data/vendor/spinel/test/struct_dig.rb +16 -0
- data/vendor/spinel/test/struct_int_member_unboxed.rb +14 -0
- data/vendor/spinel/test/struct_int_member_unboxed.rb.expected +6 -0
- data/vendor/spinel/test/struct_member_name_no_global_merge.rb +28 -0
- data/vendor/spinel/test/struct_member_name_no_global_merge.rb.expected +3 -0
- data/vendor/spinel/test/struct_to_a_values.rb +12 -0
- data/vendor/spinel/test/struct_to_a_values.rb.expected +6 -0
- data/vendor/spinel/test/struct_to_h.rb +8 -0
- data/vendor/spinel/test/struct_to_h.rb.expected +4 -0
- data/vendor/spinel/test/struct_to_h_block.rb +30 -0
- data/vendor/spinel/test/super_in_non_initialize.rb +44 -0
- data/vendor/spinel/test/super_in_non_initialize.rb.expected +3 -0
- data/vendor/spinel/test/super_with_args.rb +25 -0
- data/vendor/spinel/test/super_with_args.rb.expected +4 -0
- data/vendor/spinel/test/sym_hash_cross_variant_merge.rb +40 -0
- data/vendor/spinel/test/sym_hash_cross_variant_merge.rb.expected +3 -0
- data/vendor/spinel/test/sym_int_hash_merge.rb +52 -0
- data/vendor/spinel/test/sym_int_hash_merge.rb.expected +11 -0
- data/vendor/spinel/test/sym_int_hash_missing_key_nil.rb +15 -0
- data/vendor/spinel/test/sym_int_hash_missing_key_nil.rb.expected +11 -0
- data/vendor/spinel/test/sym_poly_hash_merge_sym_int.rb +18 -0
- data/vendor/spinel/test/sym_poly_hash_merge_sym_int.rb.expected +4 -0
- data/vendor/spinel/test/symbol_id2name_ruby_desc_minmax.rb +13 -0
- data/vendor/spinel/test/symbol_id2name_ruby_desc_minmax.rb.expected +7 -0
- data/vendor/spinel/test/symbol_to_proc_after_positional.rb +18 -0
- data/vendor/spinel/test/symbol_to_proc_after_positional.rb.expected +5 -0
- data/vendor/spinel/test/symbol_upcase_interpolation.rb +24 -0
- data/vendor/spinel/test/symbol_upcase_interpolation.rb.expected +5 -0
- data/vendor/spinel/test/system_argument_list.rb +15 -0
- data/vendor/spinel/test/system_argument_list.rb.expected +7 -0
- data/vendor/spinel/test/thread_new_value.rb +5 -0
- data/vendor/spinel/test/thread_new_value.rb.expected +2 -0
- data/vendor/spinel/test/time_localtime_gmtime.rb +19 -0
- data/vendor/spinel/test/time_mktime_local_utc.rb +24 -0
- data/vendor/spinel/test/time_mktime_local_utc.rb.expected +13 -0
- data/vendor/spinel/test/time_strftime_long_format.rb +21 -0
- data/vendor/spinel/test/time_strftime_long_format.rb.expected +5 -0
- data/vendor/spinel/test/time_subsec_utc.rb +19 -0
- data/vendor/spinel/test/time_subsec_utc.rb.expected +9 -0
- data/vendor/spinel/test/time_to_s.rb +5 -0
- data/vendor/spinel/test/toplevel_include_module_function.rb +101 -0
- data/vendor/spinel/test/toplevel_include_module_function.rb.expected +8 -0
- data/vendor/spinel/test/toplevel_kwrest_block_param.rb +19 -0
- data/vendor/spinel/test/toplevel_kwrest_block_param.rb.expected +2 -0
- data/vendor/spinel/test/tuple_inspect.rb +9 -0
- data/vendor/spinel/test/tuple_inspect.rb.expected +4 -0
- data/vendor/spinel/test/typed_array_empty_returns_nil.rb +19 -0
- data/vendor/spinel/test/typed_array_empty_returns_nil.rb.expected +8 -0
- data/vendor/spinel/test/typed_array_include_cross_type.rb +6 -0
- data/vendor/spinel/test/typed_hash_caller_no_body_widen.rb +46 -0
- data/vendor/spinel/test/typed_hash_caller_no_body_widen.rb.expected +1 -0
- data/vendor/spinel/test/typed_hash_inspect_and_each_value.rb +12 -0
- data/vendor/spinel/test/typed_hash_inspect_and_each_value.rb.expected +6 -0
- data/vendor/spinel/test/typed_hash_missing_returns_nil.rb +17 -0
- data/vendor/spinel/test/typed_hash_missing_returns_nil.rb.expected +4 -0
- data/vendor/spinel/test/unary_operator_methods.rb +19 -0
- data/vendor/spinel/test/unary_operator_methods.rb.expected +3 -0
- data/vendor/spinel/test/undef_method_raises.rb +34 -0
- data/vendor/spinel/test/undef_method_raises.rb.expected +3 -0
- data/vendor/spinel/test/uninit_const_raises_nameerror.rb +18 -0
- data/vendor/spinel/test/uninit_ivar_returns_nil.rb +23 -0
- data/vendor/spinel/test/uninit_ivar_returns_nil.rb.expected +4 -0
- data/vendor/spinel/test/unless_return_path_hash.rb +39 -0
- data/vendor/spinel/test/unless_return_path_hash.rb.expected +6 -0
- data/vendor/spinel/test/user_class_aset_dispatch.rb +75 -0
- data/vendor/spinel/test/user_class_aset_dispatch.rb.expected +3 -0
- data/vendor/spinel/test/value_type_tuple_gc_scan.rb +28 -0
- data/vendor/spinel/test/value_type_tuple_gc_scan.rb.expected +2 -0
- data/vendor/spinel/test/values_at.rb +19 -0
- data/vendor/spinel/test/values_at.rb.expected +4 -0
- data/vendor/spinel/test/volatile_strip_skips_coerced_arg.expected +1 -0
- data/vendor/spinel/test/volatile_strip_skips_coerced_arg.rb +30 -0
- data/vendor/spinel/test/warn_kernel.rb +13 -0
- data/vendor/spinel/test/warn_kernel.rb.expected +3 -0
- data/vendor/spinel/test/warn_unresolved_call_context.rb +20 -0
- data/vendor/spinel/test/when_splat_rational_complex.rb +30 -0
- data/vendor/spinel/test/when_splat_rational_complex.rb.expected +6 -0
- data/vendor/spinel/test/yield_call_assigned_return.rb +41 -0
- data/vendor/spinel/test/yield_call_assigned_return.rb.expected +6 -0
- data/vendor/spinel/test/yield_typed_hash_block_local.rb +41 -0
- data/vendor/spinel/test/yield_typed_hash_block_local.rb.expected +2 -0
- data/vendor/spinel/test/yield_value_return.rb +52 -0
- data/vendor/spinel/test/yield_value_return.rb.expected +8 -0
- data/vendor/spinel/test/yield_value_returns_from_method.expected +6 -0
- data/vendor/spinel/test/yield_value_returns_from_method.rb +27 -0
- data/vendor/spinel/tools/fast-bootstrap +74 -0
- metadata +1326 -279
- data/vendor/spinel/test/alias_method.rb +0 -28
- data/vendor/spinel/test/alias_method.rb.expected +0 -5
- data/vendor/spinel/test/and_node_poly_operand.rb +0 -24
- data/vendor/spinel/test/array_3d_nested.rb +0 -48
- data/vendor/spinel/test/array_clear_typed.rb +0 -40
- data/vendor/spinel/test/array_clear_typed.rb.expected +0 -7
- data/vendor/spinel/test/array_concat.rb +0 -18
- data/vendor/spinel/test/array_concat.rb.expected +0 -2
- data/vendor/spinel/test/array_each_with_object.rb +0 -17
- data/vendor/spinel/test/array_flat_map.rb +0 -16
- data/vendor/spinel/test/array_flat_map.rb.expected +0 -2
- data/vendor/spinel/test/array_keyed_hash_int_array_eql.rb +0 -29
- data/vendor/spinel/test/array_keyed_hash_int_array_eql.rb.expected +0 -6
- data/vendor/spinel/test/array_method_name_clash.rb +0 -15
- data/vendor/spinel/test/array_new_block_typed_container.rb +0 -37
- data/vendor/spinel/test/array_new_block_typed_container.rb.expected +0 -7
- data/vendor/spinel/test/array_new_empty_inner_deferred.rb +0 -47
- data/vendor/spinel/test/array_new_empty_inner_deferred.rb.expected +0 -8
- data/vendor/spinel/test/array_partition_gc.rb +0 -20
- data/vendor/spinel/test/array_partition_gc.rb.expected +0 -3
- data/vendor/spinel/test/array_plus.rb +0 -18
- data/vendor/spinel/test/array_plus.rb.expected +0 -2
- data/vendor/spinel/test/array_rotate_bang.rb +0 -54
- data/vendor/spinel/test/array_rotate_bang.rb.expected +0 -34
- data/vendor/spinel/test/array_shuffle_ptr.rb +0 -11
- data/vendor/spinel/test/attr_writer_poly_box.rb +0 -24
- data/vendor/spinel/test/attr_writer_poly_no_double_eval.rb +0 -29
- data/vendor/spinel/test/attr_writer_poly_no_double_eval.rb.expected +0 -3
- data/vendor/spinel/test/attr_writer_returns_rhs.rb +0 -23
- data/vendor/spinel/test/auto_unbox_keeps_int_slot.rb +0 -38
- data/vendor/spinel/test/auto_unbox_keeps_int_slot.rb.expected +0 -3
- data/vendor/spinel/test/auto_unbox_poly_to_int_local.rb +0 -36
- data/vendor/spinel/test/auto_unbox_poly_to_int_local.rb.expected +0 -2
- data/vendor/spinel/test/bare_return_in_cls_method.rb +0 -46
- data/vendor/spinel/test/bare_return_in_cls_method.rb.expected +0 -2
- data/vendor/spinel/test/bare_return_in_poly_method.rb +0 -26
- data/vendor/spinel/test/bare_return_in_poly_method.rb.expected +0 -2
- data/vendor/spinel/test/bound_method_array.rb +0 -22
- data/vendor/spinel/test/bound_method_array.rb.expected +0 -4
- data/vendor/spinel/test/bound_method_basic.rb +0 -27
- data/vendor/spinel/test/bound_method_basic.rb.expected +0 -2
- data/vendor/spinel/test/call_op_write.rb +0 -51
- data/vendor/spinel/test/call_op_write.rb.expected +0 -11
- data/vendor/spinel/test/chained_attr_setter.rb +0 -55
- data/vendor/spinel/test/chained_ivar_op_assign_emits_inner_write.rb +0 -28
- data/vendor/spinel/test/chained_ivar_op_assign_emits_inner_write.rb.expected +0 -3
- data/vendor/spinel/test/chained_ivar_write_split.rb +0 -26
- data/vendor/spinel/test/chained_ivar_write_split.rb.expected +0 -3
- data/vendor/spinel/test/chained_ivar_write_subclass.rb +0 -26
- data/vendor/spinel/test/chained_ivar_write_subclass.rb.expected +0 -2
- data/vendor/spinel/test/chained_or_assign_collection_array_nested.rb +0 -32
- data/vendor/spinel/test/chained_or_assign_collection_array_nested.rb.expected +0 -3
- data/vendor/spinel/test/chained_or_assign_collection_element.rb +0 -29
- data/vendor/spinel/test/chained_or_assign_collection_element.rb.expected +0 -2
- data/vendor/spinel/test/chained_or_assign_collection_poly_array.rb +0 -25
- data/vendor/spinel/test/chained_or_assign_collection_poly_array.rb.expected +0 -3
- data/vendor/spinel/test/chained_or_assign_collection_str_keys.rb +0 -24
- data/vendor/spinel/test/chained_or_assign_collection_str_keys.rb.expected +0 -3
- data/vendor/spinel/test/class_constant_sym_array.rb +0 -19
- data/vendor/spinel/test/class_var_write.rb +0 -19
- data/vendor/spinel/test/cls_ivar_type_parent_defer.rb +0 -32
- data/vendor/spinel/test/cls_method_object.rb +0 -31
- data/vendor/spinel/test/cls_method_object.rb.expected +0 -2
- data/vendor/spinel/test/comparable.rb +0 -21
- data/vendor/spinel/test/comparable.rb.expected +0 -5
- data/vendor/spinel/test/default_argv_narrows_from_string_call_site.rb +0 -32
- data/vendor/spinel/test/each_poly_recv_block_auto_splat.rb +0 -43
- data/vendor/spinel/test/each_poly_recv_block_auto_splat.rb.expected +0 -2
- data/vendor/spinel/test/each_with_object_seed_shadow.rb +0 -14
- data/vendor/spinel/test/each_with_object_seed_shadow.rb.expected +0 -4
- data/vendor/spinel/test/empty_hash_ivar_string_value.rb +0 -51
- data/vendor/spinel/test/empty_hash_ivar_string_value.rb.expected +0 -4
- data/vendor/spinel/test/ensure_raise_overrides_body_raise.rb +0 -19
- data/vendor/spinel/test/ensure_raise_overrides_body_raise.rb.expected +0 -1
- data/vendor/spinel/test/ensure_runs_on_raise.rb +0 -32
- data/vendor/spinel/test/ensure_runs_on_raise.rb.expected +0 -3
- data/vendor/spinel/test/ensure_runs_on_raise_nested.rb +0 -38
- data/vendor/spinel/test/ensure_runs_on_raise_nested.rb.expected +0 -4
- data/vendor/spinel/test/ensure_runs_on_return_from_rescue.rb +0 -26
- data/vendor/spinel/test/ensure_runs_on_return_from_rescue.rb.expected +0 -2
- data/vendor/spinel/test/ensure_runs_on_return_nested.rb +0 -37
- data/vendor/spinel/test/ensure_runs_on_return_nested.rb.expected +0 -3
- data/vendor/spinel/test/enumerable.rb +0 -19
- data/vendor/spinel/test/enumerable.rb.expected +0 -2
- data/vendor/spinel/test/exceptions.rb +0 -34
- data/vendor/spinel/test/exceptions.rb.expected +0 -4
- data/vendor/spinel/test/fiber_ivar_persists_across_yield.rb +0 -22
- data/vendor/spinel/test/fiber_ivar_persists_across_yield.rb.expected +0 -3
- data/vendor/spinel/test/fiber_yield_across_method_call.rb +0 -28
- data/vendor/spinel/test/fiber_yield_across_method_call.rb.expected +0 -3
- data/vendor/spinel/test/file_class_methods_int_recv.rb +0 -20
- data/vendor/spinel/test/file_class_methods_int_recv.rb.expected +0 -3
- data/vendor/spinel/test/float_array_sort.rb +0 -18
- data/vendor/spinel/test/float_array_sort.rb.expected +0 -8
- data/vendor/spinel/test/forward_call_class_method_inherited_init.rb +0 -36
- data/vendor/spinel/test/forward_call_class_method_inherited_init.rb.expected +0 -1
- data/vendor/spinel/test/forward_call_class_method_inherited_init_int_array.rb +0 -32
- data/vendor/spinel/test/forward_call_class_method_inherited_init_obj.rb +0 -41
- data/vendor/spinel/test/forward_call_class_method_inherited_init_obj.rb.expected +0 -1
- data/vendor/spinel/test/forward_call_class_method_int_array.rb +0 -26
- data/vendor/spinel/test/forward_call_class_method_int_array.rb.expected +0 -1
- data/vendor/spinel/test/forward_call_class_method_nested.rb +0 -35
- data/vendor/spinel/test/forward_call_class_method_obj.rb +0 -36
- data/vendor/spinel/test/forward_call_class_method_obj.rb.expected +0 -1
- data/vendor/spinel/test/forward_call_param_type_int_array.rb +0 -34
- data/vendor/spinel/test/forward_call_param_type_int_array.rb.expected +0 -1
- data/vendor/spinel/test/forward_call_param_type_obj.rb +0 -38
- data/vendor/spinel/test/forward_call_param_type_obj.rb.expected +0 -1
- data/vendor/spinel/test/gc_save_double_in_new.rb +0 -13
- data/vendor/spinel/test/gc_save_double_in_new.rb.expected +0 -1
- data/vendor/spinel/test/gc_scan_skip_inherited_ivar.rb +0 -54
- data/vendor/spinel/test/gc_scan_skip_inherited_ivar.rb.expected +0 -2
- data/vendor/spinel/test/heterogeneous_dispatch_int_return_narrow.rb +0 -46
- data/vendor/spinel/test/heterogeneous_dispatch_int_return_narrow.rb.expected +0 -2
- data/vendor/spinel/test/if_static_false_skips_dead_branch_compile.rb +0 -50
- data/vendor/spinel/test/if_static_false_skips_dead_branch_compile.rb.expected +0 -3
- data/vendor/spinel/test/implicit_self_param_poly.rb +0 -17
- data/vendor/spinel/test/implicit_self_param_poly.rb.expected +0 -1
- data/vendor/spinel/test/inherit.rb +0 -52
- data/vendor/spinel/test/inherit.rb.expected +0 -7
- data/vendor/spinel/test/inherited_method_param_widen.rb +0 -49
- data/vendor/spinel/test/init_locals.rb +0 -43
- data/vendor/spinel/test/init_locals.rb.expected +0 -3
- data/vendor/spinel/test/initialize_param_poly_call_sites.rb +0 -20
- data/vendor/spinel/test/initialize_param_poly_call_sites.rb.expected +0 -2
- data/vendor/spinel/test/initialize_param_through_call.rb +0 -15
- data/vendor/spinel/test/initialize_void_wrapper_gc_save.rb +0 -40
- data/vendor/spinel/test/int_array_replace_expr.rb +0 -23
- data/vendor/spinel/test/int_array_replace_expr.rb.expected +0 -7
- data/vendor/spinel/test/intarray_slice_assign_from_intarray_ptr_array_first.rb +0 -25
- data/vendor/spinel/test/intarray_slice_assign_from_intarray_ptr_array_first.rb.expected +0 -1
- data/vendor/spinel/test/integer_div.rb +0 -21
- data/vendor/spinel/test/integer_div.rb.expected +0 -10
- data/vendor/spinel/test/introspect.rb +0 -35
- data/vendor/spinel/test/introspect.rb.expected +0 -11
- data/vendor/spinel/test/issue176_empty_hash_default.rb +0 -34
- data/vendor/spinel/test/issue176_empty_hash_default.rb.expected +0 -3
- data/vendor/spinel/test/issue204_user_find_fetch.rb +0 -25
- data/vendor/spinel/test/issue204_user_find_fetch.rb.expected +0 -5
- data/vendor/spinel/test/issue207_factory_pattern.rb +0 -35
- data/vendor/spinel/test/issue207_factory_pattern.rb.expected +0 -1
- data/vendor/spinel/test/issue207_full_repro.rb +0 -52
- data/vendor/spinel/test/issue207_full_repro.rb.expected +0 -1
- data/vendor/spinel/test/issue208_inherited_class_method.rb +0 -24
- data/vendor/spinel/test/issue208_inherited_class_method.rb.expected +0 -4
- data/vendor/spinel/test/issue219_aref_chain.rb +0 -36
- data/vendor/spinel/test/issue219_aref_chain.rb.expected +0 -5
- data/vendor/spinel/test/issue235_chain_tail_widening.rb +0 -48
- data/vendor/spinel/test/issue235_chain_tail_widening.rb.expected +0 -5
- data/vendor/spinel/test/ivar_and_write_basic.rb +0 -34
- data/vendor/spinel/test/ivar_and_write_basic.rb.expected +0 -5
- data/vendor/spinel/test/ivar_c_keyword.rb +0 -14
- data/vendor/spinel/test/ivar_c_keyword.rb.expected +0 -1
- data/vendor/spinel/test/ivar_float_array.rb +0 -59
- data/vendor/spinel/test/ivar_float_array.rb.expected +0 -11
- data/vendor/spinel/test/ivar_op_assign_bitwise_and_shift.rb +0 -22
- data/vendor/spinel/test/ivar_op_assign_bitwise_and_shift.rb.expected +0 -6
- data/vendor/spinel/test/ivar_or_write_basic.rb +0 -57
- data/vendor/spinel/test/ivar_or_write_basic.rb.expected +0 -9
- data/vendor/spinel/test/ivar_writer_heterogeneity.rb +0 -34
- data/vendor/spinel/test/ivar_writer_heterogeneity.rb.expected +0 -2
- data/vendor/spinel/test/known_constants.rb +0 -25
- data/vendor/spinel/test/known_constants.rb.expected +0 -4
- data/vendor/spinel/test/kw_nil_default_string_call.rb +0 -6
- data/vendor/spinel/test/kw_nil_default_string_call.rb.expected +0 -2
- data/vendor/spinel/test/map_array_block_result.rb +0 -29
- data/vendor/spinel/test/map_array_block_result.rb.expected +0 -2
- data/vendor/spinel/test/map_block_returns_nested_array.rb +0 -53
- data/vendor/spinel/test/map_empty_block.rb +0 -26
- data/vendor/spinel/test/map_empty_block.rb.expected +0 -6
- data/vendor/spinel/test/map_range_recv_array_block.rb +0 -31
- data/vendor/spinel/test/method_param_unify_to_poly.rb +0 -40
- data/vendor/spinel/test/method_param_unify_to_poly.rb.expected +0 -2
- data/vendor/spinel/test/multi_arg_block_call.rb +0 -56
- data/vendor/spinel/test/multi_arg_block_call.rb.expected +0 -5
- data/vendor/spinel/test/multi_assign_ivar.rb +0 -33
- data/vendor/spinel/test/multi_assign_ivar.rb.expected +0 -1
- data/vendor/spinel/test/multi_write_const.rb +0 -36
- data/vendor/spinel/test/multi_write_const.rb.expected +0 -9
- data/vendor/spinel/test/multi_write_from_poly_recv.rb +0 -34
- data/vendor/spinel/test/multi_write_ivar.rb +0 -34
- data/vendor/spinel/test/multi_write_ivar.rb.expected +0 -3
- data/vendor/spinel/test/multi_write_ivar_widening.rb +0 -10
- data/vendor/spinel/test/multi_write_ivar_widening.rb.expected +0 -2
- data/vendor/spinel/test/multi_write_map_block.rb +0 -31
- data/vendor/spinel/test/multi_write_map_block.rb.expected +0 -7
- data/vendor/spinel/test/multi_write_setter.rb +0 -28
- data/vendor/spinel/test/multi_write_setter.rb.expected +0 -3
- data/vendor/spinel/test/multi_write_typed_array_dispatch.rb +0 -44
- data/vendor/spinel/test/multi_write_typed_array_dispatch.rb.expected +0 -3
- data/vendor/spinel/test/nested_class_in_class.rb +0 -38
- data/vendor/spinel/test/nested_class_in_class.rb.expected +0 -3
- data/vendor/spinel/test/no_attr_write_shortcut_complex.rb +0 -33
- data/vendor/spinel/test/no_attr_write_shortcut_complex.rb.expected +0 -3
- data/vendor/spinel/test/no_attr_write_shortcut_multi.rb +0 -38
- data/vendor/spinel/test/no_attr_write_shortcut_multi.rb.expected +0 -4
- data/vendor/spinel/test/no_keywords_param.rb +0 -15
- data/vendor/spinel/test/no_keywords_param.rb.expected +0 -2
- data/vendor/spinel/test/obj_array.rb +0 -26
- data/vendor/spinel/test/obj_array.rb.expected +0 -6
- data/vendor/spinel/test/obj_class_returns_string.rb +0 -24
- data/vendor/spinel/test/obj_class_returns_string.rb.expected +0 -2
- data/vendor/spinel/test/optional_string_param_method.rb +0 -11
- data/vendor/spinel/test/optional_string_param_method.rb.expected +0 -3
- data/vendor/spinel/test/poly_array_literal_callnode_typed_elem.rb +0 -37
- data/vendor/spinel/test/poly_array_literal_callnode_typed_elem.rb.expected +0 -9
- data/vendor/spinel/test/poly_array_slot_literal.rb +0 -41
- data/vendor/spinel/test/poly_array_slot_sized_default.rb +0 -33
- data/vendor/spinel/test/poly_box_int_array.rb +0 -13
- data/vendor/spinel/test/poly_box_int_array.rb.expected +0 -2
- data/vendor/spinel/test/poly_dispatch_args_ret.rb +0 -45
- data/vendor/spinel/test/poly_dispatch_args_ret.rb.expected +0 -4
- data/vendor/spinel/test/poly_dispatch_arity_padding.rb +0 -36
- data/vendor/spinel/test/poly_dispatch_arity_truncate.rb +0 -45
- data/vendor/spinel/test/poly_hash_literal_from_ivar.rb +0 -47
- data/vendor/spinel/test/poly_hash_literal_from_ivar.rb.expected +0 -5
- data/vendor/spinel/test/poly_int_arith_auto_unify.rb +0 -26
- data/vendor/spinel/test/poly_int_arith_auto_unify.rb.expected +0 -4
- data/vendor/spinel/test/poly_int_bit_index.rb +0 -50
- data/vendor/spinel/test/poly_int_bit_index.rb.expected +0 -14
- data/vendor/spinel/test/poly_or_returns_value.rb +0 -13
- data/vendor/spinel/test/poly_or_returns_value.rb.expected +0 -4
- data/vendor/spinel/test/poly_recv_bracket_assign.rb +0 -31
- data/vendor/spinel/test/poly_recv_bracket_assign.rb.expected +0 -1
- data/vendor/spinel/test/poly_recv_each.rb +0 -35
- data/vendor/spinel/test/poly_recv_each.rb.expected +0 -7
- data/vendor/spinel/test/poly_shl_array_push_dispatch.rb +0 -35
- data/vendor/spinel/test/poly_shl_array_push_dispatch.rb.expected +0 -5
- data/vendor/spinel/test/proc_hash_value.rb +0 -27
- data/vendor/spinel/test/regexp.rb +0 -34
- data/vendor/spinel/test/regexp.rb.expected +0 -12
- data/vendor/spinel/test/reopen_class.rb +0 -33
- data/vendor/spinel/test/reopen_class.rb.expected +0 -5
- data/vendor/spinel/test/runtime_widen_int_to_poly_array.rb +0 -27
- data/vendor/spinel/test/runtime_widen_int_to_poly_array.rb.expected +0 -3
- data/vendor/spinel/test/sample_user_method.rb +0 -20
- data/vendor/spinel/test/scan_ivars_inherited_slot.rb +0 -32
- data/vendor/spinel/test/scan_new_calls_class_scope.rb +0 -35
- data/vendor/spinel/test/send.rb +0 -15
- data/vendor/spinel/test/send.rb.expected +0 -2
- data/vendor/spinel/test/stdlib.rb +0 -28
- data/vendor/spinel/test/stdlib.rb.expected +0 -5
- data/vendor/spinel/test/strip_nullable_int_cast.rb +0 -24
- data/vendor/spinel/test/super_arg_cross_class_cast.rb +0 -39
- data/vendor/spinel/test/super_arg_cross_class_cast.rb.expected +0 -2
- data/vendor/spinel/test/super_forwarding.rb +0 -25
- data/vendor/spinel/test/super_forwarding.rb.expected +0 -3
- data/vendor/spinel/test/sym_case.rb +0 -15
- data/vendor/spinel/test/sym_case.rb.expected +0 -4
- data/vendor/spinel/test/symbol_ivar_reassign.rb +0 -25
- data/vendor/spinel/test/symbol_ivar_reassign.rb.expected +0 -2
- data/vendor/spinel/test/toplevel_ivar_array.rb +0 -19
- data/vendor/spinel/test/toplevel_ivar_array.rb.expected +0 -4
- data/vendor/spinel/test/unbox_poly_index_in_dispatch.rb +0 -20
- data/vendor/spinel/test/unbox_poly_index_in_dispatch.rb.expected +0 -1
- data/vendor/spinel/test/undef.rb +0 -33
- data/vendor/spinel/test/undef.rb.expected +0 -2
- data/vendor/spinel/test/user_method_named_like_mutator.rb +0 -30
- data/vendor/spinel/test/user_method_named_like_mutator.rb.expected +0 -2
- data/vendor/spinel/test/value_type_ctor_gc_save.rb +0 -46
- data/vendor/spinel/test/value_type_ctor_gc_save.rb.expected +0 -3
- data/vendor/spinel/test/widen_int_obj_to_poly.rb +0 -29
- /data/vendor/spinel/test/{multi_write_from_poly_recv.rb.expected → bare_arg_in_module_body.rb.expected} +0 -0
- /data/vendor/spinel/test/{default_argv_narrows_from_string_call_site.rb.expected → cvar_method_first_assign.rb.expected} +0 -0
- /data/vendor/spinel/test/{array_each_with_object.rb.expected → empty_array_push_container_elem.rb.expected} +0 -0
- /data/vendor/spinel/test/{class_var_write.rb.expected → ffi_array_specs_decl.rb.expected} +0 -0
- /data/vendor/spinel/test/{array_method_name_clash.rb.expected → ffi_int_arg_poly_value.rb.expected} +0 -0
- /data/vendor/spinel/test/{initialize_void_wrapper_gc_save.rb.expected → ffi_ptr_array.rb.expected} +0 -0
- /data/vendor/spinel/test/{inherited_method_param_widen.rb.expected → ffi_ptr_int_literal.rb.expected} +0 -0
- /data/vendor/spinel/test/{forward_call_class_method_nested.rb.expected → ffi_str_arg_poly_value.rb.expected} +0 -0
- /data/vendor/spinel/test/{proc_hash_value.rb.expected → file_write_at_tail_position.rb.expected} +0 -0
- /data/vendor/spinel/test/{array_shuffle_ptr.rb.expected → hash_key_via_intermediate_local.rb.expected} +0 -0
- /data/vendor/spinel/test/{forward_call_class_method_inherited_init_int_array.rb.expected → hash_param_widen_from_callee_slot.rb.expected} +0 -0
- /data/vendor/spinel/test/{strip_nullable_int_cast.rb.expected → hash_temp_gc_root.rb.expected} +0 -0
- /data/vendor/spinel/test/{scan_new_calls_class_scope.rb.expected → poly_recv_hash_dup.rb.expected} +0 -0
- /data/vendor/spinel/test/{class_constant_sym_array.rb.expected → respond_to_def_self_method.rb.expected} +0 -0
- /data/vendor/spinel/test/{initialize_param_through_call.rb.expected → warn_unresolved_call_context.rb.expected} +0 -0
|
@@ -2,17 +2,12 @@
|
|
|
2
2
|
#ifndef SP_RUNTIME_H
|
|
3
3
|
#define SP_RUNTIME_H
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
#define _XOPEN_SOURCE 600
|
|
12
|
-
#define _DARWIN_C_SOURCE
|
|
13
|
-
#pragma clang diagnostic push
|
|
14
|
-
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
15
|
-
#endif
|
|
5
|
+
/* Platform feature-test macros (_XOPEN_SOURCE for Darwin's ucontext, etc.)
|
|
6
|
+
live at the top of sp_types.h so every translation unit that includes it --
|
|
7
|
+
the generated TU here, and the standalone libspinel_rt.a units like
|
|
8
|
+
sp_fiber.c that reach <ucontext.h> via sp_fiber.h -> sp_gc.h -> sp_types.h --
|
|
9
|
+
define them before the first system header. Must precede <stdio.h>. */
|
|
10
|
+
#include "sp_types.h"
|
|
16
11
|
|
|
17
12
|
#include <stdio.h>
|
|
18
13
|
#include <stdlib.h>
|
|
@@ -24,8 +19,34 @@
|
|
|
24
19
|
#include <stdarg.h>
|
|
25
20
|
#include <time.h>
|
|
26
21
|
#include <setjmp.h>
|
|
22
|
+
#include <errno.h>
|
|
23
|
+
#include <sys/stat.h>
|
|
24
|
+
#include <dirent.h>
|
|
25
|
+
|
|
26
|
+
/* Opt-in native backtrace (spinel --debug). In a -g, non-inlined build the
|
|
27
|
+
sp_<method> symbols are present, so sp_raise_cls can snapshot the live C
|
|
28
|
+
stack at raise time and Exception#backtrace / caller format it into a
|
|
29
|
+
Ruby-style backtrace — no per-method shadow frames needed. Off unless the
|
|
30
|
+
generated main() sets sp_bt_enabled (debug builds), so non-debug behaviour
|
|
31
|
+
and cost are unchanged. execinfo is POSIX-ish; absent on Windows. */
|
|
32
|
+
#if !defined(_WIN32)
|
|
33
|
+
#include <execinfo.h>
|
|
34
|
+
#define SP_BT_AVAILABLE 1
|
|
35
|
+
#else
|
|
36
|
+
#define SP_BT_AVAILABLE 0
|
|
37
|
+
#endif
|
|
38
|
+
static int sp_bt_enabled = 0; /* set to 1 by debug-build main() */
|
|
39
|
+
static const char *sp_bt_srcfile = ""; /* toplevel .rb path, set by debug main() */
|
|
40
|
+
#if SP_BT_AVAILABLE
|
|
41
|
+
static void *sp_bt_buf[256]; /* frames captured at the last raise */
|
|
42
|
+
static int sp_bt_n = 0;
|
|
43
|
+
#endif
|
|
27
44
|
#ifdef _WIN32
|
|
28
45
|
#include <windows.h>
|
|
46
|
+
#include <process.h>
|
|
47
|
+
#include <direct.h>
|
|
48
|
+
#include <io.h> /* _pipe (POSIX pipe(2) is absent on MinGW) */
|
|
49
|
+
#include <fcntl.h> /* _O_BINARY */
|
|
29
50
|
/* POSIX compat shims for MinGW */
|
|
30
51
|
#define mmap(a,l,p,f,fd,off) VirtualAlloc(NULL,(l),MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE)
|
|
31
52
|
#define munmap(a,l) (VirtualFree((a),0,MEM_RELEASE)?0:-1)
|
|
@@ -39,8 +60,9 @@
|
|
|
39
60
|
#include <ucontext.h>
|
|
40
61
|
#include <unistd.h>
|
|
41
62
|
#include <sys/mman.h>
|
|
63
|
+
#include <sys/wait.h>
|
|
42
64
|
#endif
|
|
43
|
-
#if !defined(__APPLE__) && !defined(_WIN32)
|
|
65
|
+
#if !defined(__APPLE__) && !defined(_WIN32) && !defined(__FreeBSD__)
|
|
44
66
|
#include <malloc.h>
|
|
45
67
|
#else
|
|
46
68
|
/* Darwin's libc has no malloc_trim; make it a no-op so call sites stay portable. */
|
|
@@ -49,27 +71,30 @@
|
|
|
49
71
|
#ifndef MAP_ANONYMOUS
|
|
50
72
|
#define MAP_ANONYMOUS MAP_ANON
|
|
51
73
|
#endif
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
typedef double mrb_float;
|
|
55
|
-
typedef bool mrb_bool;
|
|
56
|
-
/* sp_sym is defined per-program in emit_sym_runtime, but poly helpers
|
|
57
|
-
below need to reference it by forward declaration. */
|
|
58
|
-
typedef mrb_int sp_sym;
|
|
59
|
-
static const char *sp_sym_to_s(sp_sym id);
|
|
60
|
-
#ifndef TRUE
|
|
61
|
-
#define TRUE true
|
|
74
|
+
#ifndef S_ISDIR
|
|
75
|
+
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
|
|
62
76
|
#endif
|
|
63
|
-
#ifndef
|
|
64
|
-
#define
|
|
77
|
+
#ifndef S_ISREG
|
|
78
|
+
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
|
|
65
79
|
#endif
|
|
66
80
|
|
|
81
|
+
/* Core value-type definitions (primitives, leaf structs, GC headers,
|
|
82
|
+
typed arrays, non-poly hashes) live in sp_types.h so libspinel_rt.a
|
|
83
|
+
sources can share them without the function bodies below. */
|
|
84
|
+
#include "sp_types.h"
|
|
85
|
+
/* sp_RbVal, the collector globals/entry points, and the hot inline mark
|
|
86
|
+
helpers; the collector body lives in libspinel_rt.a (lib/sp_gc.c). */
|
|
87
|
+
#include "sp_gc.h"
|
|
88
|
+
/* sp_Fiber + the Fiber API; the bodies live in libspinel_rt.a (lib/sp_fiber.c). */
|
|
89
|
+
#include "sp_fiber.h"
|
|
90
|
+
static const char *sp_sym_to_s(sp_sym id);
|
|
91
|
+
|
|
67
92
|
/* sp_raise_cls forward decl — defined later in this header (line ~1017).
|
|
68
93
|
Used by the integer-division helpers below to match CRuby semantics:
|
|
69
94
|
`a / 0`, `a % 0`, `a.divmod(0)`, `a.ceildiv(0)`, and `a.pow(e, 0)` all
|
|
70
95
|
raise ZeroDivisionError instead of triggering C undefined behaviour
|
|
71
96
|
(SIGFPE on x86) or silently returning 0. */
|
|
72
|
-
|
|
97
|
+
SP_NORETURN SP_COLD void sp_raise_cls(const char *cls, const char *msg);
|
|
73
98
|
|
|
74
99
|
static inline mrb_int sp_idiv(mrb_int a, mrb_int b) {
|
|
75
100
|
if (b == 0) sp_raise_cls("ZeroDivisionError", "divided by 0");
|
|
@@ -83,65 +108,318 @@ static inline mrb_int sp_imod(mrb_int a, mrb_int b) {
|
|
|
83
108
|
if ((r != 0) && ((r ^ b) < 0)) r += b;
|
|
84
109
|
return r;
|
|
85
110
|
}
|
|
111
|
+
/* Overflow-checked integer arithmetic (BIGINT.md option β: raise on
|
|
112
|
+
overflow, keep locals at native mrb_int width).
|
|
113
|
+
|
|
114
|
+
Build modes (preprocessor toggles):
|
|
115
|
+
default : overflow raises RangeError
|
|
116
|
+
SP_NO_OVERFLOW_CHECK : bare `+ - *`, silent wrap (UB on signed
|
|
117
|
+
overflow, but matches spinel's prior
|
|
118
|
+
behaviour where the user asks for
|
|
119
|
+
max speed and accepts the risk)
|
|
120
|
+
|
|
121
|
+
The actual overflow check is wrapped through
|
|
122
|
+
`sp_int_*_overflow_p` helpers so non-gcc/clang compilers fall back
|
|
123
|
+
to a portable unsigned-arithmetic implementation. The shape
|
|
124
|
+
mirrors mruby's mrb_int_*_overflow in include/mruby/numeric.h.
|
|
125
|
+
|
|
126
|
+
The outer sp_int_add/sub/mul/neg are GCC statement-expression
|
|
127
|
+
macros (`({ ... })`) rather than functions: an earlier inline-
|
|
128
|
+
function variant produced wrong optcarrot output (checksum
|
|
129
|
+
diverged 59662→4096) even with __attribute__((always_inline)),
|
|
130
|
+
apparently from a UB-assumption interaction between the
|
|
131
|
+
optimizer and the function-call-shaped expression that the
|
|
132
|
+
macro form bypasses by keeping every operand in its surrounding
|
|
133
|
+
expression context. The macros also let the compiler fold
|
|
134
|
+
constant-operand cases at every call site. */
|
|
135
|
+
#ifndef __has_builtin
|
|
136
|
+
# define __has_builtin(x) 0
|
|
137
|
+
#endif
|
|
138
|
+
#if (defined(__GNUC__) && __GNUC__ >= 5) || \
|
|
139
|
+
(__has_builtin(__builtin_add_overflow) && \
|
|
140
|
+
__has_builtin(__builtin_sub_overflow) && \
|
|
141
|
+
__has_builtin(__builtin_mul_overflow))
|
|
142
|
+
# define SP_HAVE_OVERFLOW_BUILTINS 1
|
|
143
|
+
#endif
|
|
144
|
+
|
|
145
|
+
#ifdef SP_HAVE_OVERFLOW_BUILTINS
|
|
146
|
+
static inline mrb_bool sp_int_add_overflow_p(mrb_int a, mrb_int b, mrb_int *r) {
|
|
147
|
+
return __builtin_add_overflow(a, b, r);
|
|
148
|
+
}
|
|
149
|
+
static inline mrb_bool sp_int_sub_overflow_p(mrb_int a, mrb_int b, mrb_int *r) {
|
|
150
|
+
return __builtin_sub_overflow(a, b, r);
|
|
151
|
+
}
|
|
152
|
+
static inline mrb_bool sp_int_mul_overflow_p(mrb_int a, mrb_int b, mrb_int *r) {
|
|
153
|
+
return __builtin_mul_overflow(a, b, r);
|
|
154
|
+
}
|
|
155
|
+
#else
|
|
156
|
+
/* Portable fallback for compilers lacking __builtin_*_overflow.
|
|
157
|
+
mrb_int is pointer-width (intptr_t), so compute in uintptr_t --
|
|
158
|
+
unsigned overflow is well-defined wrap-around in C -- and detect
|
|
159
|
+
signed overflow via the sign-bit XOR trick at the *correct* width
|
|
160
|
+
(the sign bit is mrb_int's top bit: 63 on 64-bit, 31 on 32-bit).
|
|
161
|
+
Bounds use INTPTR_MAX/MIN, not the int64 MRB_INT_* macros, so this
|
|
162
|
+
path is self-contained and width-correct. Mul checks bounds before
|
|
163
|
+
multiplying because a 2x-width intermediate isn't portable. */
|
|
164
|
+
#define SP_INT_OVF_SIGN ((uintptr_t)1 << (sizeof(mrb_int) * 8 - 1))
|
|
165
|
+
static inline mrb_bool sp_int_add_overflow_p(mrb_int a, mrb_int b, mrb_int *r) {
|
|
166
|
+
uintptr_t x = (uintptr_t)a, y = (uintptr_t)b, z = x + y;
|
|
167
|
+
*r = (mrb_int)z;
|
|
168
|
+
return !!(((x ^ z) & (y ^ z)) & SP_INT_OVF_SIGN);
|
|
169
|
+
}
|
|
170
|
+
static inline mrb_bool sp_int_sub_overflow_p(mrb_int a, mrb_int b, mrb_int *r) {
|
|
171
|
+
uintptr_t x = (uintptr_t)a, y = (uintptr_t)b, z = x - y;
|
|
172
|
+
*r = (mrb_int)z;
|
|
173
|
+
return !!(((x ^ z) & (~y ^ z)) & SP_INT_OVF_SIGN);
|
|
174
|
+
}
|
|
175
|
+
static inline mrb_bool sp_int_mul_overflow_p(mrb_int a, mrb_int b, mrb_int *r) {
|
|
176
|
+
if (a > 0 && b > 0 && a > INTPTR_MAX / b) { *r = a * b; return TRUE; }
|
|
177
|
+
if (a < 0 && b > 0 && a < INTPTR_MIN / b) { *r = a * b; return TRUE; }
|
|
178
|
+
if (a > 0 && b < 0 && b < INTPTR_MIN / a) { *r = a * b; return TRUE; }
|
|
179
|
+
if (a < 0 && b < 0 && (a <= INTPTR_MIN || b <= INTPTR_MIN || -a > INTPTR_MAX / -b)) {
|
|
180
|
+
*r = a * b; return TRUE;
|
|
181
|
+
}
|
|
182
|
+
*r = a * b;
|
|
183
|
+
return FALSE;
|
|
184
|
+
}
|
|
185
|
+
#undef SP_INT_OVF_SIGN
|
|
186
|
+
#endif
|
|
187
|
+
|
|
188
|
+
/* Three modes selected by `--int-overflow=raise|wrap|promote` on the
|
|
189
|
+
spinel wrapper, which passes -DSP_INT_OVERFLOW_MODE_{RAISE,WRAP,
|
|
190
|
+
PROMOTE}. WRAP skips the check entirely. PROMOTE still raises at
|
|
191
|
+
this layer -- the actual promotion semantics is implemented in the
|
|
192
|
+
analyzer by rewriting every int local as bigint, so the helpers
|
|
193
|
+
below are only reached on the few residual sites that stay int
|
|
194
|
+
even in promote mode (FFI argument coercion, etc.). */
|
|
195
|
+
#ifdef SP_INT_OVERFLOW_MODE_WRAP
|
|
196
|
+
# define sp_int_add(a, b) ((a) + (b))
|
|
197
|
+
# define sp_int_sub(a, b) ((a) - (b))
|
|
198
|
+
# define sp_int_mul(a, b) ((a) * (b))
|
|
199
|
+
# define sp_int_neg(a) (-(a))
|
|
200
|
+
#else
|
|
201
|
+
# define sp_int_add(a, b) ({ mrb_int _sp_a = (a), _sp_b = (b), _sp_r; \
|
|
202
|
+
if (sp_int_add_overflow_p(_sp_a, _sp_b, &_sp_r)) sp_raise_cls("RangeError", "integer overflow in +"); \
|
|
203
|
+
_sp_r; })
|
|
204
|
+
# define sp_int_sub(a, b) ({ mrb_int _sp_a = (a), _sp_b = (b), _sp_r; \
|
|
205
|
+
if (sp_int_sub_overflow_p(_sp_a, _sp_b, &_sp_r)) sp_raise_cls("RangeError", "integer overflow in -"); \
|
|
206
|
+
_sp_r; })
|
|
207
|
+
# define sp_int_mul(a, b) ({ mrb_int _sp_a = (a), _sp_b = (b), _sp_r; \
|
|
208
|
+
if (sp_int_mul_overflow_p(_sp_a, _sp_b, &_sp_r)) sp_raise_cls("RangeError", "integer overflow in *"); \
|
|
209
|
+
_sp_r; })
|
|
210
|
+
# define sp_int_neg(a) ({ mrb_int _sp_a = (a), _sp_r; \
|
|
211
|
+
if (sp_int_sub_overflow_p((mrb_int)0, _sp_a, &_sp_r)) sp_raise_cls("RangeError", "integer overflow in -@"); \
|
|
212
|
+
_sp_r; })
|
|
213
|
+
#endif
|
|
86
214
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
static mrb_int sp_powmod(mrb_int base,mrb_int exp,mrb_int mod){if(mod==0)sp_raise_cls("ZeroDivisionError","divided by 0");mrb_int r=1;mrb_int m=mod<0?-mod:mod;if(m==1){r=0;}else{base=base%m;if(base<0)base+=m;while(exp>0){if(exp%2==1)r=r*base%m;exp=exp/2;base=base*base%m;}}if(mod<0&&r>0)r-=m;return r;}
|
|
90
|
-
static mrb_int sp_ceildiv(mrb_int a,mrb_int b){if(b==0)sp_raise_cls("ZeroDivisionError","divided by 0");if(b==-1)return -a;mrb_int q=a/b;if(a%b!=0&&((a^b)>=0))q++;return q;}
|
|
91
|
-
static mrb_int sp_int_clamp(mrb_int v,mrb_int lo,mrb_int hi){return v<lo?lo:v>hi?hi:v;}
|
|
92
|
-
/* Integer square root via Newton's method — exact for the full mrb_int
|
|
93
|
-
range (no double-precision rounding loss for n > 2^53). CRuby raises
|
|
94
|
-
on negative input; we mirror Spinel's other arithmetic helpers and
|
|
95
|
-
return 0 to avoid an exception path. */
|
|
96
|
-
static mrb_int sp_int_sqrt(mrb_int n){if(n<0)return 0;if(n<2)return n;mrb_int x=n,y=(x+1)/2;while(y<x){x=y;y=(x+n/x)/2;}return x;}
|
|
215
|
+
/* sp_gcd / sp_lcm / sp_powmod / sp_ceildiv / sp_int_clamp / sp_int_sqrt
|
|
216
|
+
now live in libspinel_rt.a (lib/sp_core.c); declared via sp_core.h. */
|
|
97
217
|
static inline char *sp_str_alloc_raw(size_t total_with_null); /* fwd decl */
|
|
98
218
|
static const char*sp_int_chr(mrb_int n){char*s=sp_str_alloc_raw(2);s[0]=(char)n;s[1]=0;return s;}
|
|
99
|
-
|
|
100
|
-
|
|
219
|
+
/* sp_ipow10 / sp_int_round / sp_int_ceil / sp_int_floor /
|
|
220
|
+
sp_int_truncate / sp_str_oct now live in libspinel_rt.a
|
|
221
|
+
(lib/sp_core.c); declared via sp_core.h. */
|
|
222
|
+
/* Narrow a foreign 64-bit value (time_t / off_t, etc.) to a Ruby
|
|
223
|
+
Integer. On 64-bit mrb_int this is the identity. On 32-bit, a value
|
|
224
|
+
outside the mrb_int range can't be represented; rather than silently
|
|
225
|
+
truncating a clock/size value the program never computed (which
|
|
226
|
+
`int-overflow=wrap` is NOT meant to license -- wrap is about the
|
|
227
|
+
user's own arithmetic), raise RangeError. promote-mode bigint
|
|
228
|
+
promotion of these boundary values is a follow-up. */
|
|
229
|
+
static inline mrb_int sp_i64_to_int(int64_t v){
|
|
230
|
+
#if INTPTR_MAX == INT64_MAX
|
|
231
|
+
return (mrb_int)v;
|
|
232
|
+
#else
|
|
233
|
+
if(v < (int64_t)INTPTR_MIN || v > (int64_t)INTPTR_MAX)
|
|
234
|
+
sp_raise_cls("RangeError","value out of range for 32-bit Integer");
|
|
235
|
+
return (mrb_int)v;
|
|
236
|
+
#endif
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* Forward decls for helpers used across this header (and by the
|
|
240
|
+
string->number parsers that now live in libspinel_rt.a). */
|
|
241
|
+
SP_NORETURN SP_COLD void sp_raise_cls(const char *cls, const char *msg);
|
|
242
|
+
const char *sp_sprintf(const char *fmt, ...);
|
|
243
|
+
|
|
244
|
+
/* String -> number parsers now live in libspinel_rt.a (lib/sp_core.c). */
|
|
245
|
+
#include "sp_core.h"
|
|
246
|
+
/* system()/backtick ($?) support now lives in libspinel_rt.a (lib/sp_system.c). */
|
|
247
|
+
#include "sp_system.h"
|
|
248
|
+
|
|
249
|
+
static sp_Range sp_range_new(mrb_int f,mrb_int l,mrb_int e){sp_Range r;r.first=f;r.last=l;r.excl=e;return r;}
|
|
250
|
+
static mrb_bool sp_range_eq(sp_Range a,sp_Range b){return a.first==b.first&&a.last==b.last&&a.excl==b.excl;}
|
|
251
|
+
/* `Range#include?`/`#cover?` on the boxed (SP_TAG_OBJ cls_id
|
|
252
|
+
SP_BUILTIN_RANGE) Range value. The direct sp_Range typed path
|
|
253
|
+
inlines this same check via compile_range_method_expr; poly-recv
|
|
254
|
+
dispatch needs the wrapper so the cls_id arm in
|
|
255
|
+
emit_poly_builtin_dispatch can land on a single C expression. An
|
|
256
|
+
exclusive range stops one short of `last`, so the upper bound is
|
|
257
|
+
`last - excl` (excl is 0 or 1). */
|
|
258
|
+
static mrb_bool sp_range_include(sp_Range *r, mrb_int x){return r->first<=x && x<=r->last-r->excl;}
|
|
259
|
+
|
|
260
|
+
/* ---- Class object ----
|
|
261
|
+
Value-type Class reference: a single class id that indexes into
|
|
262
|
+
the per-program sp_class_names[] table emitted by codegen. Lets
|
|
263
|
+
`c = Foo` produce a runtime value (`(sp_Class){<id>}`) instead of
|
|
264
|
+
a bare C identifier, and `c.to_s` lower to a names-table lookup.
|
|
265
|
+
Other Class methods (`.name`, `.inspect`, `.==`, `.!=`,
|
|
266
|
+
`.superclass`, `.ancestors`, dynamic `is_a?(c)` against a
|
|
267
|
+
variable, etc.) are not yet supported. */
|
|
268
|
+
|
|
269
|
+
/* ---- Complex runtime ---- */
|
|
270
|
+
/* Value-type Cartesian Complex: 16 bytes, passed by value. Used by
|
|
271
|
+
optcarrot's nestopia palette generator; the palette is precomputed
|
|
272
|
+
in the default code path so this is exercised only with
|
|
273
|
+
`--nestopia-palette`. */
|
|
274
|
+
static inline sp_Complex sp_complex_polar(mrb_float m,mrb_float a){sp_Complex c;c.re=m*cos(a);c.im=m*sin(a);return c;}
|
|
275
|
+
static inline sp_Complex sp_complex_add(sp_Complex a,sp_Complex b){sp_Complex c;c.re=a.re+b.re;c.im=a.im+b.im;return c;}
|
|
276
|
+
static inline sp_Complex sp_complex_mul(sp_Complex a,sp_Complex b){sp_Complex c;c.re=a.re*b.re-a.im*b.im;c.im=a.re*b.im+a.im*b.re;return c;}
|
|
277
|
+
static inline sp_Complex sp_complex_conjugate(sp_Complex a){sp_Complex c;c.re=a.re;c.im=-a.im;return c;}
|
|
278
|
+
/* Inspect renders Complex per Ruby: `(re+imi)` or `(re-imi)` for
|
|
279
|
+
negative imaginary. Integer-valued components render without
|
|
280
|
+
decimals; fractional render via %g. Issue #840. */
|
|
281
|
+
static const char *sp_complex_inspect(sp_Complex c) {
|
|
282
|
+
char buf[128];
|
|
283
|
+
int n = 0;
|
|
284
|
+
/* Real part: keep integer-looking values short. */
|
|
285
|
+
if (c.re == (mrb_int)c.re) n += snprintf(buf + n, sizeof(buf) - n, "(%lld", (long long)c.re);
|
|
286
|
+
else n += snprintf(buf + n, sizeof(buf) - n, "(%g", c.re);
|
|
287
|
+
/* Imaginary sign + value. */
|
|
288
|
+
if (c.im < 0) {
|
|
289
|
+
if (c.im == (mrb_int)c.im) n += snprintf(buf + n, sizeof(buf) - n, "-%lldi)", -(long long)c.im);
|
|
290
|
+
else n += snprintf(buf + n, sizeof(buf) - n, "%gi)", c.im);
|
|
291
|
+
} else {
|
|
292
|
+
if (c.im == (mrb_int)c.im) n += snprintf(buf + n, sizeof(buf) - n, "+%lldi)", (long long)c.im);
|
|
293
|
+
else n += snprintf(buf + n, sizeof(buf) - n, "+%gi)", c.im);
|
|
294
|
+
}
|
|
295
|
+
if (n < 0) n = 0;
|
|
296
|
+
char *r = sp_str_alloc_raw(n + 1);
|
|
297
|
+
memcpy(r, buf, n);
|
|
298
|
+
r[n] = 0;
|
|
299
|
+
return r;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/* ---- Rational runtime ---- */
|
|
303
|
+
/* Value-type Rational: 16 bytes (two mrb_ints), passed by value.
|
|
304
|
+
Stored in reduced form -- the parser hands us the already-reduced
|
|
305
|
+
numerator/denominator from the literal; Integer#quo / arithmetic
|
|
306
|
+
normalizes via sp_rational_reduce. Issue #841. */
|
|
307
|
+
static inline mrb_int sp_rational_gcd_i(mrb_int a, mrb_int b) {
|
|
308
|
+
if (a < 0) a = -a;
|
|
309
|
+
if (b < 0) b = -b;
|
|
310
|
+
while (b) { mrb_int t = b; b = a % b; a = t; }
|
|
311
|
+
return a;
|
|
312
|
+
}
|
|
313
|
+
static inline sp_Rational sp_rational_new(mrb_int n, mrb_int d) {
|
|
314
|
+
sp_Rational r;
|
|
315
|
+
if (d == 0) { r.num = n; r.den = 0; return r; }
|
|
316
|
+
if (d < 0) { n = -n; d = -d; }
|
|
317
|
+
mrb_int g = sp_rational_gcd_i(n, d);
|
|
318
|
+
if (g <= 0) g = 1;
|
|
319
|
+
r.num = n / g;
|
|
320
|
+
r.den = d / g;
|
|
321
|
+
return r;
|
|
322
|
+
}
|
|
323
|
+
static const char *sp_rational_inspect(sp_Rational r) {
|
|
324
|
+
char buf[64];
|
|
325
|
+
int n = snprintf(buf, sizeof(buf), "(%lld/%lld)", (long long)r.num, (long long)r.den);
|
|
326
|
+
if (n < 0) n = 0;
|
|
327
|
+
char *o = sp_str_alloc_raw(n + 1);
|
|
328
|
+
memcpy(o, buf, n);
|
|
329
|
+
o[n] = 0;
|
|
330
|
+
return o;
|
|
331
|
+
}
|
|
101
332
|
|
|
102
333
|
/* ---- Time runtime ---- */
|
|
103
|
-
/* sp_Time
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
static
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
static sp_gc_hdr *sp_gc_heap = NULL; static size_t sp_gc_bytes = 0; static size_t sp_gc_threshold = 256*1024;
|
|
334
|
+
/* sp_Time and the libc-backed accessors / formatters live in
|
|
335
|
+
lib/sp_time.{c,h} (compiled into libspinel_rt.a). What stays here
|
|
336
|
+
are the GC-aware wrappers — sp_box_time copies the value onto the
|
|
337
|
+
GC heap, and the *_gc string forwarders allocate a small stack buf,
|
|
338
|
+
call the libspinel_rt format helper, then sp_str_dup_external the
|
|
339
|
+
result into the GC string heap. is_utc distinguishes UTC-coerced
|
|
340
|
+
times from local-zone times; the underlying epoch is the same. */
|
|
341
|
+
#include "sp_time.h"
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
/* `recycle`: optional sweep hook. If non-NULL, sp_gc_collect calls
|
|
345
|
+
recycle(h) on the unmarked object instead of finalize+free. The
|
|
346
|
+
hook is responsible for deciding whether to keep the storage
|
|
347
|
+
(pool push) or free it. Used by class-instance free-list pools. */
|
|
348
|
+
/* sp_gc_heap / sp_gc_bytes are defined in lib/sp_gc.c (extern via sp_gc.h). */
|
|
349
|
+
static size_t sp_gc_threshold = 256*1024;
|
|
350
|
+
|
|
351
|
+
/* ---- GC verify: opt-in mark-path validation (release-neutral) ------------
|
|
352
|
+
* SPINEL_GC_VERIFY=1 before the collector invokes an object's scan hook,
|
|
353
|
+
* check that the object is a currently-registered heap allocation. If a
|
|
354
|
+
* raw/aliased pointer (e.g. into a string or builder buffer) has been put
|
|
355
|
+
* on the mark path, abort with a diagnostic at that point instead of
|
|
356
|
+
* calling through a bogus scan-function pointer (which otherwise faults at
|
|
357
|
+
* a garbage address and is near-impossible to attribute).
|
|
358
|
+
* Default OFF; with it unset, behavior and codegen are unchanged. */
|
|
359
|
+
/* GC verify state + helpers live in lib/sp_gc.c. */
|
|
130
360
|
|
|
131
361
|
/* ---- String GC ---- */
|
|
132
|
-
typedef struct sp_str_hdr { struct sp_str_hdr *next; size_t size; } sp_str_hdr;
|
|
133
362
|
static sp_str_hdr *sp_str_heap = NULL;
|
|
134
363
|
#define SPL(s) (&("\xff" s)[1])
|
|
135
364
|
static const char sp_str_empty_data[] = "\xff";
|
|
136
365
|
#define sp_str_empty (sp_str_empty_data + 1)
|
|
137
366
|
|
|
367
|
+
/* RUBY_PLATFORM string -- host arch + OS. Detected at C compile time
|
|
368
|
+
so cross-builds report the target platform. Issue #890. */
|
|
369
|
+
#if defined(__x86_64__) || defined(_M_X64)
|
|
370
|
+
# define SP_RUBY_ARCH "x86_64"
|
|
371
|
+
#elif defined(__aarch64__) || defined(_M_ARM64)
|
|
372
|
+
# define SP_RUBY_ARCH "aarch64"
|
|
373
|
+
#elif defined(__i386__) || defined(_M_IX86)
|
|
374
|
+
# define SP_RUBY_ARCH "i686"
|
|
375
|
+
#elif defined(__arm__)
|
|
376
|
+
# define SP_RUBY_ARCH "arm"
|
|
377
|
+
#else
|
|
378
|
+
# define SP_RUBY_ARCH "unknown"
|
|
379
|
+
#endif
|
|
380
|
+
#if defined(__linux__)
|
|
381
|
+
# define SP_RUBY_OS "linux"
|
|
382
|
+
#elif defined(__APPLE__)
|
|
383
|
+
# define SP_RUBY_OS "darwin"
|
|
384
|
+
#elif defined(_WIN32) || defined(_WIN64)
|
|
385
|
+
# define SP_RUBY_OS "mingw32"
|
|
386
|
+
#elif defined(__FreeBSD__)
|
|
387
|
+
# define SP_RUBY_OS "freebsd"
|
|
388
|
+
#else
|
|
389
|
+
# define SP_RUBY_OS "unknown"
|
|
390
|
+
#endif
|
|
391
|
+
static const char sp_ruby_platform_data[] = "\xff" SP_RUBY_ARCH "-" SP_RUBY_OS;
|
|
392
|
+
static inline const char *sp_ruby_platform_str(void) { return sp_ruby_platform_data + 1; }
|
|
393
|
+
|
|
394
|
+
/* Process.ppid wrapper. MinGW's unistd.h doesn't expose getppid;
|
|
395
|
+
return 0 there. Issue #893. */
|
|
396
|
+
static inline mrb_int sp_process_ppid(void) {
|
|
397
|
+
#ifdef _WIN32
|
|
398
|
+
return 0;
|
|
399
|
+
#else
|
|
400
|
+
return (mrb_int)getppid();
|
|
401
|
+
#endif
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/* ---- Encoding runtime ----
|
|
405
|
+
Spinel currently assumes UTF-8 source and string data. Keep Encoding
|
|
406
|
+
as a tiny value type so `__ENCODING__` and `String#encoding` can
|
|
407
|
+
answer Ruby's Encoding-shaped protocol without carrying full
|
|
408
|
+
transcoding state. */
|
|
409
|
+
static inline sp_Encoding sp_encoding_utf8(void){return(sp_Encoding){"UTF-8"};}
|
|
410
|
+
static inline const char*sp_encoding_name(sp_Encoding e){return e.name?e.name:sp_str_empty;}
|
|
411
|
+
static inline const char*sp_encoding_inspect(sp_Encoding e){return sp_sprintf("#<Encoding:%s>",sp_encoding_name(e));}
|
|
412
|
+
static inline mrb_bool sp_encoding_eq(sp_Encoding a,sp_Encoding b){const char*an=sp_encoding_name(a);const char*bn=sp_encoding_name(b);return strcmp(an,bn)==0;}
|
|
413
|
+
|
|
138
414
|
static char *sp_str_alloc(size_t len) {
|
|
139
415
|
size_t total = sizeof(sp_str_hdr) + 1 + len + 1;
|
|
140
416
|
sp_str_hdr *h = (sp_str_hdr *)malloc(total);
|
|
417
|
+
if (!h) sp_oom_die();
|
|
141
418
|
h->next = sp_str_heap;
|
|
142
419
|
h->size = total;
|
|
420
|
+
h->len = len;
|
|
143
421
|
sp_str_heap = h;
|
|
144
|
-
/* Don't fold string-heap pressure into sp_gc_bytes
|
|
422
|
+
/* Don't fold string-heap pressure into sp_gc_bytes : the
|
|
145
423
|
threshold heuristic in sp_gc_alloc is keyed on heap survivors, and
|
|
146
424
|
the str-heap mark-sweep that runs alongside (sp_str_sweep,
|
|
147
425
|
called from sp_gc_collect) doesn't add surviving strings back into
|
|
@@ -166,6 +444,23 @@ static inline char *sp_str_alloc_raw(size_t total_with_null) {
|
|
|
166
444
|
return sp_str_alloc(total_with_null > 0 ? total_with_null - 1 : 0);
|
|
167
445
|
}
|
|
168
446
|
|
|
447
|
+
static inline size_t sp_str_byte_len(const char *s) {
|
|
448
|
+
if (!s) return 0;
|
|
449
|
+
unsigned char marker = ((const unsigned char *)s)[-1];
|
|
450
|
+
if (marker == 0xfe || marker == 0xfc) {
|
|
451
|
+
return (((const sp_str_hdr *)(s - 1)) - 1)->len;
|
|
452
|
+
}
|
|
453
|
+
return strlen(s);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
static inline void sp_str_set_len(char *s, size_t len) {
|
|
457
|
+
if (!s) return;
|
|
458
|
+
unsigned char marker = ((unsigned char *)s)[-1];
|
|
459
|
+
if (marker == 0xfe || marker == 0xfc) {
|
|
460
|
+
(((sp_str_hdr *)(s - 1)) - 1)->len = len;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
169
464
|
static const char *sp_str_dup_external(const char *s) {
|
|
170
465
|
if (!s) return NULL;
|
|
171
466
|
size_t n = strlen(s);
|
|
@@ -182,26 +477,167 @@ static inline int sp_utf8_char_len(unsigned char c){if(c<0x80)return 1;if(c<0xC0
|
|
|
182
477
|
static inline int sp_utf8_advance(const char*p){int cn=sp_utf8_char_len((unsigned char)*p);int i=1;while(i<cn&&((unsigned char)p[i]&0xC0)==0x80)i++;return i;}
|
|
183
478
|
static inline int sp_utf8_decode(const char*p,uint32_t*out){unsigned char c=(unsigned char)p[0];if(c<0x80){*out=c;return 1;}if(c<0xC0){*out=c;return 1;}unsigned char c1=(unsigned char)p[1];if((c1&0xC0)!=0x80){*out=c;return 1;}if(c<0xE0){*out=((uint32_t)(c&0x1F)<<6)|(c1&0x3F);return 2;}unsigned char c2=(unsigned char)p[2];if((c2&0xC0)!=0x80){*out=c;return 1;}if(c<0xF0){*out=((uint32_t)(c&0x0F)<<12)|((uint32_t)(c1&0x3F)<<6)|(c2&0x3F);return 3;}unsigned char c3=(unsigned char)p[3];if((c3&0xC0)!=0x80){*out=c;return 1;}*out=((uint32_t)(c&0x07)<<18)|((uint32_t)(c1&0x3F)<<12)|((uint32_t)(c2&0x3F)<<6)|(c3&0x3F);return 4;}
|
|
184
479
|
static inline int sp_utf8_encode(uint32_t cp,char*out){if(cp<0x80){out[0]=(char)cp;return 1;}if(cp<0x800){out[0]=(char)(0xC0|(cp>>6));out[1]=(char)(0x80|(cp&0x3F));return 2;}if(cp<0x10000){out[0]=(char)(0xE0|(cp>>12));out[1]=(char)(0x80|((cp>>6)&0x3F));out[2]=(char)(0x80|(cp&0x3F));return 3;}out[0]=(char)(0xF0|(cp>>18));out[1]=(char)(0x80|((cp>>12)&0x3F));out[2]=(char)(0x80|((cp>>6)&0x3F));out[3]=(char)(0x80|(cp&0x3F));return 4;}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
480
|
+
/* Issue #882: `"hello" << 33` should append the character with
|
|
481
|
+
that codepoint, not the decimal digits. UTF-8 encode (1..4 bytes)
|
|
482
|
+
and return a NUL-terminated string. */
|
|
483
|
+
static const char *sp_int_codepoint_to_str(mrb_int n) {
|
|
484
|
+
char *s = sp_str_alloc_raw(5);
|
|
485
|
+
if (n < 0 || n > 0x10FFFF) { s[0] = 0; return s; }
|
|
486
|
+
int len = sp_utf8_encode((uint32_t)n, s);
|
|
487
|
+
s[len] = 0;
|
|
488
|
+
return s;
|
|
489
|
+
}
|
|
490
|
+
/* Direct-mapped pointer-keyed cache for (byte_len, char_len). Populated lazily
|
|
491
|
+
by sp_str_length / sp_utf8_byte_offset; the same entries unlock both calls
|
|
492
|
+
so iterating a single string with `s.length` + `s[i]` walks UTF-8 once
|
|
493
|
+
instead of per access. ASCII strings (char_len == byte_len) take an O(1)
|
|
494
|
+
byte_offset fast path. Cleared by sp_str_sweep so freed-and-reused heap
|
|
495
|
+
addresses can't leak stale entries. Skipped for sp_String wrappers (marker
|
|
496
|
+
0xfd) whose buffers can move on realloc. */
|
|
497
|
+
#define SP_STR_LCACHE_BITS 5
|
|
498
|
+
#define SP_STR_LCACHE_SIZE (1u << SP_STR_LCACHE_BITS)
|
|
499
|
+
static struct sp_str_lcache_entry {
|
|
500
|
+
const char *s;
|
|
501
|
+
size_t byte_len;
|
|
502
|
+
mrb_int char_len;
|
|
503
|
+
} sp_str_lcache[SP_STR_LCACHE_SIZE];
|
|
504
|
+
|
|
505
|
+
static inline unsigned sp_str_lcache_hash(const char *s) {
|
|
506
|
+
uintptr_t k = (uintptr_t)s;
|
|
507
|
+
return (unsigned)((k ^ (k >> 4) ^ (k >> 12)) & (SP_STR_LCACHE_SIZE - 1));
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
static inline void sp_str_lcache_clear(void) {
|
|
511
|
+
for (unsigned i = 0; i < SP_STR_LCACHE_SIZE; i++) sp_str_lcache[i].s = NULL;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/* Count UTF-8 code points in s[0..bl). The 8-byte ASCII-detect prologue skips
|
|
515
|
+
bytes 8 at a time while the high bit stays clear, so pure-ASCII strings (the
|
|
516
|
+
common case in the JSON / CSV / template benchmarks) run vastly faster than
|
|
517
|
+
the per-byte advance loop they used to fall through. */
|
|
518
|
+
static mrb_int sp_str_count_chars(const char *s, size_t bl) {
|
|
519
|
+
const char *p = s, *end = s + bl;
|
|
520
|
+
mrb_int n = 0;
|
|
521
|
+
while (p + 8 <= end) {
|
|
522
|
+
uint64_t w;
|
|
523
|
+
memcpy(&w, p, sizeof(w));
|
|
524
|
+
if (w & 0x8080808080808080ULL) break;
|
|
525
|
+
p += 8; n += 8;
|
|
526
|
+
}
|
|
527
|
+
while (p < end) {
|
|
528
|
+
if ((unsigned char)*p < 0x80) { p++; n++; }
|
|
529
|
+
else { p += sp_utf8_advance(p); n++; }
|
|
530
|
+
}
|
|
531
|
+
return n;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/* True when `s` carries one of spinel's own string markers in the
|
|
535
|
+
preceding byte (0xfe / 0xfc heap, 0xff rodata literal). FFI returns
|
|
536
|
+
a bare `const char *` whose preceding byte is whatever C variable
|
|
537
|
+
sits before the buffer in memory — using the pointer as a cache
|
|
538
|
+
key without this gate aliased subsequent FFI calls into the prior
|
|
539
|
+
call's cached length (#611). 0xfd (sp_String wrapper) is excluded
|
|
540
|
+
too because its buffer can move on append. */
|
|
541
|
+
static inline int sp_str_cacheable(const char *s) {
|
|
542
|
+
unsigned char m = ((const unsigned char *)s)[-1];
|
|
543
|
+
return m == 0xfe || m == 0xfc || m == 0xff;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
static mrb_int sp_str_length(const char*s){
|
|
547
|
+
if (!s) return 0;
|
|
548
|
+
if (!sp_str_cacheable(s)) return sp_str_count_chars(s, sp_str_byte_len(s));
|
|
549
|
+
unsigned h = sp_str_lcache_hash(s);
|
|
550
|
+
if (sp_str_lcache[h].s == s) return sp_str_lcache[h].char_len;
|
|
551
|
+
size_t bl = sp_str_byte_len(s);
|
|
552
|
+
mrb_int n = sp_str_count_chars(s, bl);
|
|
553
|
+
sp_str_lcache[h].s = s;
|
|
554
|
+
sp_str_lcache[h].byte_len = bl;
|
|
555
|
+
sp_str_lcache[h].char_len = n;
|
|
556
|
+
return n;
|
|
557
|
+
}
|
|
558
|
+
static mrb_int sp_str_ord(const char*s){if(!s)sp_raise_cls("ArgumentError","empty string");unsigned char m=((const unsigned char*)s)[-1];size_t blen;if(m==0xfe||m==0xfc){blen=(((const sp_str_hdr*)(s-1))-1)->len;if(blen==0)sp_raise_cls("ArgumentError","empty string");}else{blen=strlen(s);if(blen==0)sp_raise_cls("ArgumentError","empty string");}uint32_t cp;sp_utf8_decode(s,&cp);return(mrb_int)cp;}
|
|
559
|
+
/* NULL-safe string equality. ENV[] returns NULL for unset vars
|
|
188
560
|
(the dispatch is `sp_str_dup_external(getenv(...))`, which propagates
|
|
189
561
|
NULL), so emitted strcmp(...) on the result of `ENV["X"] == "1"` would
|
|
190
562
|
dereference NULL on either side. nil-vs-string equality is false in
|
|
191
563
|
Ruby; nil == nil is true, so falling back to pointer equality on the
|
|
192
564
|
NULL path covers both. */
|
|
193
565
|
static inline int sp_str_eq(const char*a,const char*b){if(!a||!b)return a==b;return strcmp(a,b)==0;}
|
|
194
|
-
static size_t sp_utf8_byte_offset(const char*s,mrb_int char_idx){
|
|
195
|
-
|
|
196
|
-
|
|
566
|
+
static size_t sp_utf8_byte_offset(const char*s,mrb_int char_idx){
|
|
567
|
+
if (!s || char_idx <= 0) return 0;
|
|
568
|
+
if (sp_str_cacheable(s)) {
|
|
569
|
+
unsigned h = sp_str_lcache_hash(s);
|
|
570
|
+
if (sp_str_lcache[h].s == s
|
|
571
|
+
&& (size_t)sp_str_lcache[h].char_len == sp_str_lcache[h].byte_len) {
|
|
572
|
+
size_t off = (size_t)char_idx;
|
|
573
|
+
return off > sp_str_lcache[h].byte_len ? sp_str_lcache[h].byte_len : off;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
/* Walk char_idx code points or stop at byte_len, whichever comes first.
|
|
577
|
+
Bounding on byte_len (instead of "*p != 0") keeps the walk correct
|
|
578
|
+
past an embedded NUL byte -- a heap string with the 0xfe/0xfc marker
|
|
579
|
+
carries its real length in the sp_str_hdr, so a NUL byte inside the
|
|
580
|
+
payload no longer terminates the walk prematurely. For an external
|
|
581
|
+
0xff literal sp_str_byte_len falls back to strlen which legitimately
|
|
582
|
+
stops at NUL, matching the prior behaviour. */
|
|
583
|
+
size_t blen = sp_str_byte_len(s);
|
|
584
|
+
const char *p = s;
|
|
585
|
+
const char *end = s + blen;
|
|
586
|
+
while (char_idx > 0 && p < end) {
|
|
587
|
+
p += sp_utf8_advance(p);
|
|
588
|
+
char_idx--;
|
|
589
|
+
}
|
|
590
|
+
if (p > end) p = end;
|
|
591
|
+
return (size_t)(p - s);
|
|
592
|
+
}
|
|
593
|
+
/* Issue #762: check malloc/realloc returns. On OOM, return an empty
|
|
594
|
+
array rather than dereferencing NULL. */
|
|
595
|
+
static uint32_t*sp_utf8_decode_all(const char*s,size_t*out_n){size_t cap=8,n=0;uint32_t*cps=(uint32_t*)malloc(cap*sizeof(uint32_t));if(!cps){*out_n=0;return NULL;}const char*p=s;while(s&&*p){if(n>=cap){size_t nc=cap*2;uint32_t*nx=(uint32_t*)realloc(cps,nc*sizeof(uint32_t));if(!nx){free(cps);*out_n=0;return NULL;}cps=nx;cap=nc;}uint32_t cp;p+=sp_utf8_decode(p,&cp);cps[n++]=cp;}*out_n=n;return cps;}
|
|
197
596
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
597
|
+
/* Issue #858: expand `a-z` range notation in a String#delete /
|
|
598
|
+
String#tr / String#count character set. `^abc` negation is
|
|
599
|
+
NOT handled (separate v1 scope). Result is a malloc'd flat
|
|
600
|
+
codepoint array — caller frees. */
|
|
601
|
+
static uint32_t*sp_utf8_decode_charset(const char*s,size_t*out_n){
|
|
602
|
+
size_t cap=16,n=0;
|
|
603
|
+
uint32_t*cps=(uint32_t*)malloc(cap*sizeof(uint32_t));
|
|
604
|
+
if(!cps){*out_n=0;return NULL;}
|
|
605
|
+
const char*p=s;
|
|
606
|
+
uint32_t prev=0; int has_prev=0;
|
|
607
|
+
while(s&&*p){
|
|
608
|
+
uint32_t cp;
|
|
609
|
+
int len=sp_utf8_decode(p,&cp);
|
|
610
|
+
p+=len;
|
|
611
|
+
/* Detect range: prev '-' next (but leading or trailing '-'
|
|
612
|
+
is literal). When current char is '-' and there's a next
|
|
613
|
+
non-'-' char and we have a prev, expand. */
|
|
614
|
+
if(cp=='-' && has_prev && *p){
|
|
615
|
+
uint32_t hi;
|
|
616
|
+
int hi_len=sp_utf8_decode(p,&hi);
|
|
617
|
+
p+=hi_len;
|
|
618
|
+
if(hi>=prev){
|
|
619
|
+
/* Drop the prev we already wrote, re-emit the whole range. */
|
|
620
|
+
n--; /* undo prev */
|
|
621
|
+
for(uint32_t c=prev;c<=hi;c++){
|
|
622
|
+
if(n>=cap){cap*=2;cps=(uint32_t*)realloc(cps,cap*sizeof(uint32_t));}
|
|
623
|
+
cps[n++]=c;
|
|
624
|
+
}
|
|
625
|
+
has_prev=0;
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
/* Bad range (hi<prev): fall through, push '-' literally. */
|
|
629
|
+
cp='-';
|
|
630
|
+
}
|
|
631
|
+
if(n>=cap){cap*=2;cps=(uint32_t*)realloc(cps,cap*sizeof(uint32_t));}
|
|
632
|
+
cps[n++]=cp;
|
|
633
|
+
prev=cp; has_prev=1;
|
|
202
634
|
}
|
|
635
|
+
*out_n=n;
|
|
636
|
+
return cps;
|
|
203
637
|
}
|
|
638
|
+
static int sp_utf8_set_has(const uint32_t*cps,size_t n,uint32_t cp){for(size_t i=0;i<n;i++)if(cps[i]==cp)return 1;return 0;}
|
|
204
639
|
|
|
640
|
+
/* sp_mark_string is an inline helper in sp_gc.h. */
|
|
205
641
|
static void sp_str_sweep(void) {
|
|
206
642
|
sp_str_hdr **pp = &sp_str_heap;
|
|
207
643
|
while (*pp) {
|
|
@@ -217,10 +653,55 @@ static void sp_str_sweep(void) {
|
|
|
217
653
|
free(h);
|
|
218
654
|
}
|
|
219
655
|
}
|
|
656
|
+
sp_str_lcache_clear();
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/* GC-aware Time trampolines. The libspinel_rt format helpers write
|
|
660
|
+
into a local buffer; we sp_str_dup_external the result so the GC
|
|
661
|
+
tracks the lifetime. strftime returns 0 -- never overruns the buffer
|
|
662
|
+
-- when the formatted result would exceed it, which we surface as "".
|
|
663
|
+
The 4 KB buffer covers any realistic format (CRuby's built-ins are
|
|
664
|
+
~25 bytes; this leaves room for long literal text or wide fields). A
|
|
665
|
+
pathological field width (`"%1000000000F"`, which CRuby rejects with
|
|
666
|
+
ERANGE) does not fit and yields "" -- a graceful empty string rather
|
|
667
|
+
than a crash. */
|
|
668
|
+
static const char *sp_time_strftime(sp_Time t, const char *fmt) {
|
|
669
|
+
char buf[4096];
|
|
670
|
+
size_t n = sp_time_strftime_to(t, fmt, buf, sizeof(buf));
|
|
671
|
+
if (n == 0) return SPL("");
|
|
672
|
+
return sp_str_dup_external(buf);
|
|
673
|
+
}
|
|
674
|
+
static const char *sp_time_iso8601(sp_Time t) {
|
|
675
|
+
char buf[64];
|
|
676
|
+
size_t n = sp_time_iso8601_to(t, buf, sizeof(buf));
|
|
677
|
+
if (n == 0) return SPL("");
|
|
678
|
+
return sp_str_dup_external(buf);
|
|
679
|
+
}
|
|
680
|
+
static const char *sp_time_zone(sp_Time t) {
|
|
681
|
+
char buf[8];
|
|
682
|
+
sp_time_zone_to(t, buf, sizeof(buf));
|
|
683
|
+
return sp_str_dup_external(buf);
|
|
684
|
+
}
|
|
685
|
+
/* Scalar Time inspect. CRuby form: local "YYYY-MM-DD HH:MM:SS +0900",
|
|
686
|
+
UTC "YYYY-MM-DD HH:MM:SS UTC". The poly-box path keeps its own
|
|
687
|
+
sp_Time_inspect; this value-taking variant is for the scalar
|
|
688
|
+
p/puts/to_s codegen path. */
|
|
689
|
+
static const char *sp_time_inspect_v(sp_Time t) {
|
|
690
|
+
char buf[40];
|
|
691
|
+
sp_time_inspect_to(t, buf, sizeof(buf));
|
|
692
|
+
return sp_str_dup_external(buf);
|
|
220
693
|
}
|
|
221
694
|
|
|
222
|
-
|
|
223
|
-
|
|
695
|
+
/* SP_GC_STACK_MAX, sp_gc_roots, sp_gc_nroots come from sp_gc.h / lib/sp_gc.c. */
|
|
696
|
+
/* Cooperative-fiber GC root storage (issue #636).
|
|
697
|
+
sp_gc_roots[] holds the CURRENT fiber's active roots. When a fiber
|
|
698
|
+
yields, its roots get copied out to the fiber's saved_roots buffer
|
|
699
|
+
and the resuming fiber's saved_roots are copied back in — so the
|
|
700
|
+
per-fiber stacks never clobber each other through interleaved
|
|
701
|
+
pushes the way they did when a single global stack was shared by
|
|
702
|
+
every fiber. sp_gc_mark_all calls the hook below (installed once
|
|
703
|
+
the Fiber section's setup runs) to walk every live fiber's
|
|
704
|
+
saved_roots in addition to the current view. */
|
|
224
705
|
/* GC root tracking. SP_GC_ROOT registers a stack-resident root with a
|
|
225
706
|
cleanup-attribute sentinel so it's auto-popped when its declaring
|
|
226
707
|
scope ends — matches the variable's actual lifetime, including for
|
|
@@ -239,24 +720,116 @@ static inline void _sp_gc_root_pop(int *added) { if (*added) sp_gc_nroots--; }
|
|
|
239
720
|
#define _SP_GC_CONCAT(a,b) _SP_GC_CONCAT2(a,b)
|
|
240
721
|
#define SP_GC_SAVE() int __attribute__((cleanup(sp_gc_cleanup))) _gc_saved = sp_gc_nroots
|
|
241
722
|
#define SP_GC_ROOT(v) int __attribute__((cleanup(_sp_gc_root_pop))) _SP_GC_CONCAT(_sp_gcr_, __COUNTER__) = _sp_gc_root_push((void**)&(v))
|
|
723
|
+
/* Root a poly (sp_RbVal) local/global. The root stack stores void**
|
|
724
|
+
and marks *slot as a single GC pointer, but an sp_RbVal carries its
|
|
725
|
+
object pointer inside a union (offset != 0) and only when its tag is
|
|
726
|
+
STR/OBJ -- a blind *slot deref would read the tag word as a pointer.
|
|
727
|
+
So we tag the stored slot with the low bit (sp_RbVal addresses are
|
|
728
|
+
>=4-byte aligned, so the bit is free) and the mark walker, seeing the
|
|
729
|
+
tag, routes the entry through sp_mark_rbval instead. Same auto-pop
|
|
730
|
+
cleanup as SP_GC_ROOT, so SP_GC_SAVE/RESTORE and the fiber root
|
|
731
|
+
save/restore (which treat entries as opaque) need no changes. */
|
|
732
|
+
#define SP_GC_ROOT_RBVAL(v) int __attribute__((cleanup(_sp_gc_root_pop))) _SP_GC_CONCAT(_sp_gcr_, __COUNTER__) = _sp_gc_root_push((void**)((uintptr_t)&(v) | (uintptr_t)1))
|
|
242
733
|
#define SP_GC_RESTORE() sp_gc_nroots = _gc_saved
|
|
243
734
|
#define SP_GC_MARK_STACK_MAX (1024*64)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
static void sp_gc_mark_all(void){if(!sp_gc_mark_stack)sp_gc_mark_stack=(void**)malloc(sizeof(void*)*SP_GC_MARK_STACK_MAX);sp_gc_mark_top=0;for(int i=0;i<sp_gc_nroots;i++){void*obj=*sp_gc_roots[i];if(obj)sp_gc_mark(obj);}sp_re_mark_globals();while(sp_gc_mark_top>0){void*obj=sp_gc_mark_stack[--sp_gc_mark_top];sp_gc_hdr*h=(sp_gc_hdr*)((char*)obj-sizeof(sp_gc_hdr));if(h->scan)h->scan(obj);}}
|
|
735
|
+
/* sp_gc_mark / sp_gc_mark_all + the suspended-fiber hook live in
|
|
736
|
+
lib/sp_gc.c (declared in sp_gc.h). sp_re_mark_globals is defined below
|
|
737
|
+
(with the regex globals it marks) and carries external linkage so the
|
|
738
|
+
collector body can reach it. */
|
|
249
739
|
static void sp_gc_cleanup(int*p){sp_gc_nroots=*p;}
|
|
250
740
|
#define SP_GC_NBUCKETS 32
|
|
251
741
|
static sp_gc_hdr*sp_gc_buckets[SP_GC_NBUCKETS];
|
|
252
742
|
static inline int sp_gc_bucket(size_t sz){int b=(int)(sz/16);return b<SP_GC_NBUCKETS?b:SP_GC_NBUCKETS-1;}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
743
|
+
/* sp_gc_cycle / sp_gc_old_bytes are in lib/sp_gc.c (extern via sp_gc.h);
|
|
744
|
+
sp_gc_old_heap is collector-private to lib/sp_gc.c. */
|
|
745
|
+
|
|
746
|
+
/* GC verify support + sp_gc_collect live in lib/sp_gc.c. */
|
|
257
747
|
static size_t sp_gc_threshold_init=256*1024;
|
|
258
|
-
|
|
259
|
-
|
|
748
|
+
/* sp_oom_die + the SPINEL_MAX_HEAP_MB governor (sp_gc_enforce_mem_limit)
|
|
749
|
+
live in lib/sp_gc.c. */
|
|
750
|
+
void*sp_gc_alloc(size_t sz,void(*fin)(void*),void(*scn)(void*)){if(sp_gc_bytes>sp_gc_threshold){size_t before=sp_gc_bytes;sp_gc_collect();size_t freed=before-sp_gc_bytes;if(freed<before/4){sp_gc_threshold=before*2;}else if(sp_gc_bytes>0){sp_gc_threshold=sp_gc_bytes*4;if(sp_gc_threshold<sp_gc_threshold_init)sp_gc_threshold=sp_gc_threshold_init;}else{sp_gc_threshold=sp_gc_threshold_init;}sp_gc_enforce_mem_limit();}size_t need=sizeof(sp_gc_hdr)+sz;sp_gc_hdr*h=(sp_gc_hdr*)calloc(1,need);if(!h)sp_oom_die();h->finalize=fin;h->scan=scn;h->size=need;h->marked=0;h->next=sp_gc_heap;sp_gc_heap=h;sp_gc_bytes+=need;return(char*)h+sizeof(sp_gc_hdr);}
|
|
751
|
+
void*sp_gc_alloc_nogc(size_t sz,void(*fin)(void*),void(*scn)(void*)){size_t need=sizeof(sp_gc_hdr)+sz;sp_gc_hdr*h=(sp_gc_hdr*)calloc(1,need);if(!h)sp_oom_die();h->finalize=fin;h->scan=scn;h->size=need;h->marked=0;h->next=sp_gc_heap;sp_gc_heap=h;sp_gc_bytes+=need;return(char*)h+sizeof(sp_gc_hdr);}
|
|
752
|
+
/* GC-header frozen bit — used for containers whose mutators are NOT
|
|
753
|
+
on a hot path (hashes), so the extra cache line vs. a struct field
|
|
754
|
+
doesn't matter. Arrays co-locate `frozen` in the struct instead
|
|
755
|
+
(see sp_IntArray); strings use the 0xff marker / wrapper bit. */
|
|
756
|
+
static inline mrb_bool sp_gc_is_frozen(void *p) { if (!p) return FALSE; return ((sp_gc_hdr *)((char *)p - sizeof(sp_gc_hdr)))->frozen; }
|
|
757
|
+
static inline void *sp_gc_freeze(void *p) { if (p) ((sp_gc_hdr *)((char *)p - sizeof(sp_gc_hdr)))->frozen = 1; return p; }
|
|
758
|
+
static void __attribute__((noinline,cold)) sp_raise_frozen_hash(void){sp_raise_cls("FrozenError","can't modify frozen Hash");}
|
|
759
|
+
/* Pool-aware alloc. The recycle hook is stored in the gc_hdr; sweep
|
|
760
|
+
calls it on unmarked objects instead of finalize+free. The hook
|
|
761
|
+
decides whether to push the storage onto a per-class free-list or
|
|
762
|
+
actually free it. */
|
|
763
|
+
static void *sp_gc_alloc_pool(size_t sz, void(*scn)(void*), void(*recycle)(sp_gc_hdr*)) {
|
|
764
|
+
void *p = sp_gc_alloc(sz, NULL, scn);
|
|
765
|
+
sp_gc_hdr *h = (sp_gc_hdr *)((char *)p - sizeof(sp_gc_hdr));
|
|
766
|
+
h->recycle = recycle;
|
|
767
|
+
return p;
|
|
768
|
+
}
|
|
769
|
+
/* Re-link a previously-pooled slot back into sp_gc_heap so the next
|
|
770
|
+
GC cycle visits it. Called by class _new functions when reusing
|
|
771
|
+
a pooled instance. The storage was kept alive by the pool
|
|
772
|
+
free-list since the last sweep unlinked it from sp_gc_heap. */
|
|
773
|
+
static void sp_gc_pool_relink(sp_gc_hdr *h) {
|
|
774
|
+
h->marked = 0;
|
|
775
|
+
h->next = sp_gc_heap;
|
|
776
|
+
sp_gc_heap = h;
|
|
777
|
+
sp_gc_bytes += h->size;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/* Per-class free-list pool boilerplate. SP_POOL_DEFINE(CLS) goes at
|
|
781
|
+
file scope, near the class _new function. SP_POOL_NEW(CLS, scan)
|
|
782
|
+
replaces the body of an `sp_gc_alloc(sizeof(sp_CLS), NULL, scan)`
|
|
783
|
+
call, popping from the per-class free-list if non-empty.
|
|
784
|
+
Default cap can be overridden at runtime via SP_POOL_MAX envvar
|
|
785
|
+
(uniform across classes). SP_POOL_REPORT=1 dumps per-class stats
|
|
786
|
+
at exit. */
|
|
787
|
+
#define SP_POOL_DEFAULT_MAX 1048576L
|
|
788
|
+
#define SP_POOL_DEFINE(CLS) \
|
|
789
|
+
static sp_gc_hdr *sp_##CLS##_pool_head = NULL; \
|
|
790
|
+
static long sp_##CLS##_pool_count = 0; \
|
|
791
|
+
static long sp_##CLS##_pool_max = SP_POOL_DEFAULT_MAX; \
|
|
792
|
+
static long sp_##CLS##_pool_pushes = 0; \
|
|
793
|
+
static long sp_##CLS##_pool_pops = 0; \
|
|
794
|
+
static long sp_##CLS##_pool_freed = 0; \
|
|
795
|
+
static long sp_##CLS##_pool_hwm = 0; \
|
|
796
|
+
__attribute__((constructor)) static void sp_##CLS##_pool_init(void) { \
|
|
797
|
+
const char *m = getenv("SP_POOL_MAX"); \
|
|
798
|
+
if (m && *m) { long v = atol(m); if (v >= 0) sp_##CLS##_pool_max = v; } \
|
|
799
|
+
} \
|
|
800
|
+
static void sp_##CLS##_pool_recycle(sp_gc_hdr *h) { \
|
|
801
|
+
if (sp_##CLS##_pool_count >= sp_##CLS##_pool_max) { \
|
|
802
|
+
free(h); sp_##CLS##_pool_freed++; return; \
|
|
803
|
+
} \
|
|
804
|
+
h->next = sp_##CLS##_pool_head; \
|
|
805
|
+
sp_##CLS##_pool_head = h; \
|
|
806
|
+
sp_##CLS##_pool_count++; \
|
|
807
|
+
sp_##CLS##_pool_pushes++; \
|
|
808
|
+
if (sp_##CLS##_pool_count > sp_##CLS##_pool_hwm) sp_##CLS##_pool_hwm = sp_##CLS##_pool_count; \
|
|
809
|
+
} \
|
|
810
|
+
__attribute__((destructor)) static void sp_##CLS##_pool_report(void) { \
|
|
811
|
+
const char *e = getenv("SP_POOL_REPORT"); \
|
|
812
|
+
if (!e || !e[0] || e[0] == '0') return; \
|
|
813
|
+
fprintf(stderr, #CLS " pool: pops=%ld pushes=%ld over_cap_freed=%ld hwm=%ld retained=%ld cap=%ld\n", \
|
|
814
|
+
sp_##CLS##_pool_pops, sp_##CLS##_pool_pushes, sp_##CLS##_pool_freed, \
|
|
815
|
+
sp_##CLS##_pool_hwm, sp_##CLS##_pool_count, sp_##CLS##_pool_max); \
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
#define SP_POOL_NEW(CLS, SCAN) (__extension__ ({ \
|
|
819
|
+
sp_##CLS *_p; \
|
|
820
|
+
if (sp_##CLS##_pool_head) { \
|
|
821
|
+
sp_gc_hdr *_h = sp_##CLS##_pool_head; \
|
|
822
|
+
sp_##CLS##_pool_head = _h->next; \
|
|
823
|
+
sp_##CLS##_pool_count--; \
|
|
824
|
+
sp_##CLS##_pool_pops++; \
|
|
825
|
+
sp_gc_pool_relink(_h); \
|
|
826
|
+
_h->recycle = sp_##CLS##_pool_recycle; \
|
|
827
|
+
_p = (sp_##CLS *)((char *)_h + sizeof(sp_gc_hdr)); \
|
|
828
|
+
} else { \
|
|
829
|
+
_p = (sp_##CLS *)sp_gc_alloc_pool(sizeof(sp_##CLS), SCAN, sp_##CLS##_pool_recycle); \
|
|
830
|
+
} \
|
|
831
|
+
_p; \
|
|
832
|
+
}))
|
|
260
833
|
|
|
261
834
|
/* `Object.new` — a sentinel object whose only meaningful property is
|
|
262
835
|
identity. Each call returns a fresh GC-managed allocation, so two
|
|
@@ -264,10 +837,27 @@ void*sp_gc_alloc_nogc(size_t sz,void(*fin)(void*),void(*scn)(void*)){size_t need
|
|
|
264
837
|
typedef struct sp_Object_s { uint8_t _pad; } sp_Object;
|
|
265
838
|
static sp_Object *sp_Object_new(void){return(sp_Object*)sp_gc_alloc(sizeof(sp_Object),NULL,NULL);}
|
|
266
839
|
|
|
267
|
-
|
|
840
|
+
/* Cold out-of-line raise for frozen-container mutation; keeps the
|
|
841
|
+
hot mutators (push / []=) small enough to stay inlined. */
|
|
842
|
+
static void __attribute__((noinline,cold)) sp_raise_frozen_array(void){sp_raise_cls("FrozenError","can't modify frozen Array");}
|
|
843
|
+
|
|
844
|
+
/* `frozen` rides in the struct (not the GC header) so the hot push /
|
|
845
|
+
[]= paths read it from the same cache line as len/cap — no extra
|
|
846
|
+
cache miss vs. the GC-header bit. calloc in sp_gc_alloc zero-inits
|
|
847
|
+
it, so constructors need no change. Issue #918. */
|
|
268
848
|
static void sp_IntArray_fin(void*p){free(((sp_IntArray*)p)->data);}
|
|
269
|
-
static sp_IntArray*sp_IntArray_new(void){sp_IntArray*a=(sp_IntArray*)sp_gc_alloc(sizeof(sp_IntArray),sp_IntArray_fin,NULL);a->cap=16;a->data=(mrb_int*)malloc(sizeof(mrb_int)*a->cap);a->start=0;a->len=0;{sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));h->size+=sizeof(mrb_int)*a->cap;sp_gc_bytes+=sizeof(mrb_int)*a->cap;}return a;}
|
|
270
|
-
|
|
849
|
+
static sp_IntArray*sp_IntArray_new(void){sp_IntArray*a=(sp_IntArray*)sp_gc_alloc(sizeof(sp_IntArray),sp_IntArray_fin,NULL);a->cap=16;a->data=(mrb_int*)malloc(sizeof(mrb_int)*a->cap);if(!a->data)sp_oom_die();a->start=0;a->len=0;{sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));h->size+=sizeof(mrb_int)*a->cap;sp_gc_bytes+=sizeof(mrb_int)*a->cap;}return a;}
|
|
850
|
+
/* Issue #799: clamp e-s+1 against size_t overflow + an arbitrary
|
|
851
|
+
sanity cap (1 << 30 elements; ~8 GB at 8 bytes/elem). Without
|
|
852
|
+
the cap, `(1..MRB_INT_MAX).to_a` overflows the realloc size_t
|
|
853
|
+
to a tiny number, then writes past the allocation. */
|
|
854
|
+
static sp_IntArray*sp_IntArray_from_range(mrb_int s,mrb_int e){sp_IntArray*a=sp_IntArray_new();mrb_int n=e-s+1;if(n<0)n=0;if(n>(mrb_int)(1LL<<30))n=(mrb_int)(1LL<<30);if(n>a->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));sp_gc_bytes-=sizeof(mrb_int)*a->cap;h->size-=sizeof(mrb_int)*a->cap;a->cap=n;a->data=(mrb_int*)realloc(a->data,sizeof(mrb_int)*a->cap);h->size+=sizeof(mrb_int)*a->cap;sp_gc_bytes+=sizeof(mrb_int)*a->cap;}for(mrb_int i=0;i<n;i++)a->data[i]=s+i;a->len=n;return a;}
|
|
855
|
+
/* `(a..b).step(k)` -- emit a, a+k, a+2k, ... up to b. k must be > 0;
|
|
856
|
+
k <= 0 returns empty (matches CRuby's ArgumentError for k <= 0 in
|
|
857
|
+
spirit; we soft-fail instead). Forward-declared since
|
|
858
|
+
sp_IntArray_push is defined below. Issue #731. */
|
|
859
|
+
static void sp_IntArray_push(sp_IntArray*a,mrb_int v);
|
|
860
|
+
static sp_IntArray*sp_IntArray_from_range_step(mrb_int s,mrb_int e,mrb_int k){sp_IntArray*a=sp_IntArray_new();if(k<=0)return a;mrb_int v=s;while(v<=e){sp_IntArray_push(a,v);v+=k;}return a;}
|
|
271
861
|
static sp_IntArray*sp_IntArray_dup(sp_IntArray*a){sp_IntArray*b=sp_IntArray_new();if(a->len>b->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)b-sizeof(sp_gc_hdr));sp_gc_bytes-=sizeof(mrb_int)*b->cap;h->size-=sizeof(mrb_int)*b->cap;b->cap=a->len;b->data=(mrb_int*)realloc(b->data,sizeof(mrb_int)*b->cap);h->size+=sizeof(mrb_int)*b->cap;sp_gc_bytes+=sizeof(mrb_int)*b->cap;}memcpy(b->data,a->data+a->start,sizeof(mrb_int)*a->len);b->len=a->len;return b;}
|
|
272
862
|
/* a[start, len] / a[start..end] for IntArray. Negative start counts from
|
|
273
863
|
* the end. start past the array length yields an empty result; len is
|
|
@@ -275,201 +865,389 @@ static sp_IntArray*sp_IntArray_dup(sp_IntArray*a){sp_IntArray*b=sp_IntArray_new(
|
|
|
275
865
|
* out-of-bounds start; we return an empty IntArray since this typed
|
|
276
866
|
* collection has no nullable form. */
|
|
277
867
|
static sp_IntArray*sp_IntArray_slice(sp_IntArray*a,mrb_int start,mrb_int len){if(start<0)start+=a->len;if(start<0)start=0;sp_IntArray*b=sp_IntArray_new();if(start>=a->len||len<=0)return b;if(start+len>a->len)len=a->len-start;if(len>b->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)b-sizeof(sp_gc_hdr));sp_gc_bytes-=sizeof(mrb_int)*b->cap;h->size-=sizeof(mrb_int)*b->cap;b->cap=len;b->data=(mrb_int*)realloc(b->data,sizeof(mrb_int)*b->cap);h->size+=sizeof(mrb_int)*b->cap;sp_gc_bytes+=sizeof(mrb_int)*b->cap;}memcpy(b->data,a->data+a->start+start,sizeof(mrb_int)*len);b->len=len;return b;}
|
|
868
|
+
/* a[start..end] / a[start...end] with possibly negative endpoints.
|
|
869
|
+
Codegen used to compute (right - left + adj) for the length, which
|
|
870
|
+
silently produced a negative count for `a[1..-2]` and the runtime
|
|
871
|
+
then returned an empty array (issue #496). Normalize end against
|
|
872
|
+
a->len first; the bare _slice already handles negative start. */
|
|
873
|
+
static sp_IntArray*sp_IntArray_slice_range(sp_IntArray*a,mrb_int start,mrb_int end_,mrb_int excl){if(end_<0)end_+=a->len;if(start<0)start+=a->len;mrb_int n=end_-start+(excl?0:1);if(n<0||start<0)n=0;return sp_IntArray_slice(a,start,n);}
|
|
278
874
|
static void sp_IntArray_replace(sp_IntArray*dst,sp_IntArray*src){dst->len=0;dst->start=0;if(src->len>dst->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)dst-sizeof(sp_gc_hdr));sp_gc_bytes-=sizeof(mrb_int)*dst->cap;h->size-=sizeof(mrb_int)*dst->cap;void*nd=realloc(dst->data,sizeof(mrb_int)*src->len);if(!nd){perror("realloc");exit(1);}dst->data=(mrb_int*)nd;dst->cap=src->len;h->size+=sizeof(mrb_int)*dst->cap;sp_gc_bytes+=sizeof(mrb_int)*dst->cap;}memcpy(dst->data,src->data+src->start,sizeof(mrb_int)*src->len);dst->len=src->len;}
|
|
279
|
-
static void __attribute__((noinline)) sp_IntArray_push_grow(sp_IntArray*a){if(a->start>0){memmove(a->data,a->data+a->start,sizeof(mrb_int)*a->len);a->start=0;if(a->len<a->cap)return;}{sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));sp_gc_bytes-=sizeof(mrb_int)*a->cap;h->size-=sizeof(mrb_int)*a->cap;a->cap=a->cap*2+1;
|
|
280
|
-
static inline void sp_IntArray_push(sp_IntArray*a,mrb_int v){if(a->start+a->len>=a->cap)sp_IntArray_push_grow(a);a->data[a->start+a->len]=v;a->len++;}
|
|
281
|
-
|
|
282
|
-
|
|
875
|
+
static void __attribute__((noinline)) sp_IntArray_push_grow(sp_IntArray*a){if(a->start>0){memmove(a->data,a->data+a->start,sizeof(mrb_int)*a->len);a->start=0;if(a->len<a->cap)return;}{sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));sp_gc_bytes-=sizeof(mrb_int)*a->cap;h->size-=sizeof(mrb_int)*a->cap;a->cap=a->cap*2+1;void*nd=realloc(a->data,sizeof(mrb_int)*a->cap);if(!nd)sp_oom_die();a->data=(mrb_int*)nd;h->size+=sizeof(mrb_int)*a->cap;sp_gc_bytes+=sizeof(mrb_int)*a->cap;}}
|
|
876
|
+
static inline void sp_IntArray_push(sp_IntArray*a,mrb_int v){if(a->frozen){sp_raise_frozen_array();return;}if(a->start+a->len>=a->cap)sp_IntArray_push_grow(a);a->data[a->start+a->len]=v;a->len++;}
|
|
877
|
+
/* Issue #826: guard empty arrays. CRuby returns nil; spinel's int slot
|
|
878
|
+
collapses nil to 0. Without the guard, `--a->len` wraps to -1 and
|
|
879
|
+
reads past the buffer start. */
|
|
880
|
+
/* Issue #832: empty pop/shift return SP_INT_NIL (nullable int sentinel)
|
|
881
|
+
to match MRI's nil semantics; callers must treat as int? */
|
|
882
|
+
static inline mrb_int sp_IntArray_pop(sp_IntArray*a){if(!a||a->len<=0)return SP_INT_NIL;if(a->frozen){sp_raise_frozen_array();return SP_INT_NIL;}return a->data[a->start+--a->len];}
|
|
883
|
+
static inline mrb_int sp_IntArray_shift(sp_IntArray*a){if(!a||a->len<=0)return SP_INT_NIL;if(a->frozen){sp_raise_frozen_array();return SP_INT_NIL;}mrb_int v=a->data[a->start];a->start++;a->len--;return v;}
|
|
283
884
|
static inline mrb_int sp_IntArray_length(sp_IntArray*a){return a->len;}
|
|
284
885
|
static inline mrb_bool sp_IntArray_empty(sp_IntArray*a){return a->len==0;}
|
|
285
|
-
static inline mrb_int sp_IntArray_get(sp_IntArray*a,mrb_int i){if(i<0)i+=a->len;return a->data[a->start+i];}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
886
|
+
static inline mrb_int sp_IntArray_get(sp_IntArray*a,mrb_int i){if(!a)return SP_INT_NIL;if(i<0)i+=a->len;if(i<0||i>=a->len)return SP_INT_NIL;return a->data[a->start+i];}
|
|
887
|
+
/* Issue #769: a very-negative i (e.g. `a[-999] = 42` on a 3-elt
|
|
888
|
+
array) leaves i negative after the `i += a->len` adjustment.
|
|
889
|
+
CRuby raises IndexError; spinel no-ops as the safest fallback
|
|
890
|
+
(raising from a typed-array set would need setjmp plumbing
|
|
891
|
+
throughout the call chain). */
|
|
892
|
+
static void sp_IntArray_set_slow(sp_IntArray*a,mrb_int i,mrb_int v){if(i<0)return;while(a->start+i>=a->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));sp_gc_bytes-=sizeof(mrb_int)*a->cap;h->size-=sizeof(mrb_int)*a->cap;a->cap=a->cap*2+1;a->data=(mrb_int*)realloc(a->data,sizeof(mrb_int)*a->cap);h->size+=sizeof(mrb_int)*a->cap;sp_gc_bytes+=sizeof(mrb_int)*a->cap;}while(i>=a->len){a->data[a->start+a->len]=0;a->len++;}a->data[a->start+i]=v;}
|
|
893
|
+
/* Issue #839: an extreme negative index (still negative after
|
|
894
|
+
`i += len`) raises IndexError per MRI. */
|
|
895
|
+
static inline void sp_IntArray_set(sp_IntArray*a,mrb_int i,mrb_int v){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}mrb_int orig=i;if(i<0)i+=a->len;if(i<0)sp_raise_cls("IndexError",sp_sprintf("index %lld too small for array; minimum: %lld",(long long)orig,(long long)-a->len));if(i<a->len){a->data[a->start+i]=v;return;}sp_IntArray_set_slow(a,i,v);}
|
|
896
|
+
static void sp_IntArray_reverse_bang(sp_IntArray*a){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}for(mrb_int i=0,j=a->len-1;i<j;i++,j--){mrb_int t=a->data[a->start+i];a->data[a->start+i]=a->data[a->start+j];a->data[a->start+j]=t;}}
|
|
897
|
+
static void sp_IntArray_rotate_bang(sp_IntArray*a,mrb_int n){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}if(a->len<=0)return;n=((n%a->len)+a->len)%a->len;if(n==0)return;mrb_int*d=a->data+a->start;mrb_int lo=0,hi=n-1;while(lo<hi){mrb_int t=d[lo];d[lo]=d[hi];d[hi]=t;lo++;hi--;}lo=n;hi=a->len-1;while(lo<hi){mrb_int t=d[lo];d[lo]=d[hi];d[hi]=t;lo++;hi--;}lo=0;hi=a->len-1;while(lo<hi){mrb_int t=d[lo];d[lo]=d[hi];d[hi]=t;lo++;hi--;}}
|
|
290
898
|
static int _sp_int_cmp(const void*a,const void*b){mrb_int va=*(const mrb_int*)a,vb=*(const mrb_int*)b;return(va>vb)-(va<vb);}
|
|
291
899
|
static sp_IntArray*sp_IntArray_sort(sp_IntArray*a){sp_IntArray*b=sp_IntArray_dup(a);qsort(b->data+b->start,b->len,sizeof(mrb_int),_sp_int_cmp);return b;}
|
|
292
|
-
static void sp_IntArray_sort_bang(sp_IntArray*a){qsort(a->data+a->start,a->len,sizeof(mrb_int),_sp_int_cmp);}
|
|
293
|
-
static void sp_IntArray_shuffle_bang(sp_IntArray*a){for(mrb_int i=a->len-1;i>0;i--){mrb_int j=(mrb_int)(rand()%(i+1));mrb_int t=a->data[a->start+i];a->data[a->start+i]=a->data[a->start+j];a->data[a->start+j]=t;}}
|
|
900
|
+
static void sp_IntArray_sort_bang(sp_IntArray*a){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}qsort(a->data+a->start,a->len,sizeof(mrb_int),_sp_int_cmp);}
|
|
901
|
+
static void sp_IntArray_shuffle_bang(sp_IntArray*a){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}for(mrb_int i=a->len-1;i>0;i--){mrb_int j=(mrb_int)(rand()%(i+1));mrb_int t=a->data[a->start+i];a->data[a->start+i]=a->data[a->start+j];a->data[a->start+j]=t;}}
|
|
294
902
|
static sp_IntArray*sp_IntArray_shuffle(sp_IntArray*a){sp_IntArray*b=sp_IntArray_dup(a);sp_IntArray_shuffle_bang(b);return b;}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
903
|
+
/* Array#sample helpers. CRuby returns nil for `[].sample`; in
|
|
904
|
+
spinel's typed-array slots nil collapses to the C zero value
|
|
905
|
+
(0 / 0.0 / NULL / sp_box_nil for poly). Guards rand()%0, which
|
|
906
|
+
raises SIGFPE under -O0 and silently UBs under -O2+. Issue #536. */
|
|
907
|
+
static mrb_int sp_IntArray_sample(sp_IntArray*a){if(a->len<=0)return 0;return a->data[a->start+(mrb_int)(rand()%a->len)];}
|
|
908
|
+
/* Issue #745: guard the initial read on empty arrays. CRuby's [].min /
|
|
909
|
+
.max return nil; spinel's int-typed slot collapses nil to 0. Without
|
|
910
|
+
the guard, the first read is uninitialized memory. */
|
|
911
|
+
/* Issue #832: empty min/max return SP_INT_NIL (caller treats as int?). */
|
|
912
|
+
static mrb_int sp_IntArray_min(sp_IntArray*a){if(!a||a->len<=0)return SP_INT_NIL;mrb_int m=a->data[a->start];for(mrb_int i=1;i<a->len;i++)if(a->data[a->start+i]<m)m=a->data[a->start+i];return m;}
|
|
913
|
+
static mrb_int sp_IntArray_max(sp_IntArray*a){if(!a||a->len<=0)return SP_INT_NIL;mrb_int m=a->data[a->start];for(mrb_int i=1;i<a->len;i++)if(a->data[a->start+i]>m)m=a->data[a->start+i];return m;}
|
|
914
|
+
static mrb_int sp_IntArray_sum(sp_IntArray*a,mrb_int init){mrb_int s=init;for(mrb_int i=0;i<a->len;i++)s+=a->data[a->start+i];return s;}
|
|
298
915
|
static mrb_bool sp_IntArray_include(sp_IntArray*a,mrb_int v){for(mrb_int i=0;i<a->len;i++)if(a->data[a->start+i]==v)return TRUE;return FALSE;}
|
|
299
916
|
static mrb_int sp_IntArray_index(sp_IntArray*a,mrb_int v){for(mrb_int i=0;i<a->len;i++)if(a->data[a->start+i]==v)return i;return -1;}
|
|
300
917
|
static mrb_int sp_IntArray_rindex(sp_IntArray*a,mrb_int v){for(mrb_int i=a->len-1;i>=0;i--)if(a->data[a->start+i]==v)return i;return -1;}
|
|
301
|
-
static mrb_int sp_IntArray_delete_at(sp_IntArray*a,mrb_int i){if(i<0)i+=a->len;if(i<0||i>=a->len)return 0;mrb_int v=a->data[a->start+i];for(mrb_int j=i;j<a->len-1;j++)a->data[a->start+j]=a->data[a->start+j+1];a->len--;return v;}
|
|
302
|
-
static mrb_int sp_IntArray_delete(sp_IntArray*a,mrb_int v){mrb_int w=0;for(mrb_int i=0;i<a->len;i++){if(a->data[a->start+i]!=v){a->data[a->start+w]=a->data[a->start+i];w++;}}mrb_int d=a->len-w;a->len=w;return d>0?v:0;}
|
|
303
|
-
|
|
918
|
+
static mrb_int sp_IntArray_delete_at(sp_IntArray*a,mrb_int i){if(a&&a->frozen){sp_raise_frozen_array();return 0;}if(i<0)i+=a->len;if(i<0||i>=a->len)return 0;mrb_int v=a->data[a->start+i];for(mrb_int j=i;j<a->len-1;j++)a->data[a->start+j]=a->data[a->start+j+1];a->len--;return v;}
|
|
919
|
+
static mrb_int sp_IntArray_delete(sp_IntArray*a,mrb_int v){if(a&&a->frozen){sp_raise_frozen_array();return 0;}mrb_int w=0;for(mrb_int i=0;i<a->len;i++){if(a->data[a->start+i]!=v){a->data[a->start+w]=a->data[a->start+i];w++;}}mrb_int d=a->len-w;a->len=w;return d>0?v:0;}
|
|
920
|
+
/* Issue #788: clamp i so a very-negative index doesn't underflow past
|
|
921
|
+
a->start and write into the array's GC header. */
|
|
922
|
+
static void sp_IntArray_insert(sp_IntArray*a,mrb_int i,mrb_int v){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}if(i<0)i+=a->len+1;if(i<0)i=0;if(i>a->len)i=a->len;sp_IntArray_push(a,0);for(mrb_int j=a->len-1;j>i;j--)a->data[a->start+j]=a->data[a->start+j-1];a->data[a->start+i]=v;}
|
|
304
923
|
static sp_IntArray*sp_int_digits(mrb_int n,mrb_int base){sp_IntArray*a=sp_IntArray_new();if(base<2)base=10;if(n==0){sp_IntArray_push(a,0);return a;}if(n<0)n=-n;while(n>0){sp_IntArray_push(a,n%base);n/=base;}return a;}
|
|
305
924
|
static sp_IntArray*sp_IntArray_uniq(sp_IntArray*a){sp_IntArray*b=sp_IntArray_new();for(mrb_int i=0;i<a->len;i++){int found=0;for(mrb_int j=0;j<b->len;j++){if(b->data[b->start+j]==a->data[a->start+i]){found=1;break;}}if(!found)sp_IntArray_push(b,a->data[a->start+i]);}return b;}
|
|
306
925
|
static sp_IntArray*sp_IntArray_intersect(sp_IntArray*a,sp_IntArray*b){sp_IntArray*r=sp_IntArray_new();for(mrb_int i=0;i<a->len;i++){mrb_int v=a->data[a->start+i];if(sp_IntArray_include(b,v)&&!sp_IntArray_include(r,v))sp_IntArray_push(r,v);}return r;}
|
|
307
|
-
static sp_IntArray*sp_IntArray_union(sp_IntArray*a,sp_IntArray*b){sp_IntArray*r=sp_IntArray_new();for(mrb_int i=0;i<a->len;i++){mrb_int v=a->data[a->start+i];if(!sp_IntArray_include(r,v))sp_IntArray_push(r,v);}for(mrb_int i=0;i<b->len;i++){mrb_int v=b->data[b->start+i];if(!sp_IntArray_include(r,v))sp_IntArray_push(r,v);}return r;}
|
|
926
|
+
static sp_IntArray*sp_IntArray_union(sp_IntArray*a,sp_IntArray*b){sp_IntArray*r=sp_IntArray_new();for(mrb_int i=0;i<a->len;i++){mrb_int v=a->data[a->start+i];if(!sp_IntArray_include(r,v))sp_IntArray_push(r,v);}if(b){for(mrb_int i=0;i<b->len;i++){mrb_int v=b->data[b->start+i];if(!sp_IntArray_include(r,v))sp_IntArray_push(r,v);}}return r;}
|
|
308
927
|
/* Array#- / Array#difference: keep every LHS element NOT in RHS,
|
|
309
928
|
preserving the LHS's duplicates. CRuby's Array#- is not a set
|
|
310
929
|
subtraction — `[1,1,2,3] - [3]` is `[1,1,2]`, not `[1,2]`. */
|
|
311
930
|
static sp_IntArray*sp_IntArray_difference(sp_IntArray*a,sp_IntArray*b){sp_IntArray*r=sp_IntArray_new();for(mrb_int i=0;i<a->len;i++){mrb_int v=a->data[a->start+i];if(!sp_IntArray_include(b,v))sp_IntArray_push(r,v);}return r;}
|
|
312
|
-
static void sp_IntArray_unshift(sp_IntArray*a,mrb_int v){if(a->start>0){a->start--;a->data[a->start]=v;a->len++;}else{mrb_int e=a->len+1;if(e>a->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));sp_gc_bytes-=sizeof(mrb_int)*a->cap;h->size-=sizeof(mrb_int)*a->cap;a->cap=a->cap*2+1;a->data=(mrb_int*)realloc(a->data,sizeof(mrb_int)*a->cap);h->size+=sizeof(mrb_int)*a->cap;sp_gc_bytes+=sizeof(mrb_int)*a->cap;}memmove(a->data+1,a->data,sizeof(mrb_int)*a->len);a->data[0]=v;a->len++;}}
|
|
931
|
+
static void sp_IntArray_unshift(sp_IntArray*a,mrb_int v){if(a->frozen){sp_raise_frozen_array();return;}if(a->start>0){a->start--;a->data[a->start]=v;a->len++;}else{mrb_int e=a->len+1;if(e>a->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));sp_gc_bytes-=sizeof(mrb_int)*a->cap;h->size-=sizeof(mrb_int)*a->cap;a->cap=a->cap*2+1;a->data=(mrb_int*)realloc(a->data,sizeof(mrb_int)*a->cap);h->size+=sizeof(mrb_int)*a->cap;sp_gc_bytes+=sizeof(mrb_int)*a->cap;}memmove(a->data+1,a->data,sizeof(mrb_int)*a->len);a->data[0]=v;a->len++;}}
|
|
313
932
|
static const char*sp_IntArray_join(sp_IntArray*a,const char*sep){size_t sl=strlen(sep),cap=256;char*buf=(char*)malloc(cap);size_t len=0;for(mrb_int i=0;i<a->len;i++){if(i>0){if(len+sl>=cap){cap*=2;buf=(char*)realloc(buf,cap);}memcpy(buf+len,sep,sl);len+=sl;}char tmp[32];int n=snprintf(tmp,32,"%lld",(long long)a->data[a->start+i]);if(len+n>=cap){cap*=2;buf=(char*)realloc(buf,cap);}memcpy(buf+len,tmp,n);len+=n;}buf[len]=0;char*r=sp_str_alloc(len);memcpy(r,buf,len);free(buf);return r;}
|
|
314
|
-
static mrb_bool sp_IntArray_eq(sp_IntArray*a,sp_IntArray*b){if(a->len!=b->len)return FALSE;for(mrb_int i=0;i<a->len;i++)if(a->data[a->start+i]!=b->data[b->start+i])return FALSE;return TRUE;}
|
|
933
|
+
static mrb_bool sp_IntArray_eq(sp_IntArray*a,sp_IntArray*b){if(!a||!b)return a==b;if(a->len!=b->len)return FALSE;for(mrb_int i=0;i<a->len;i++)if(a->data[a->start+i]!=b->data[b->start+i])return FALSE;return TRUE;}
|
|
934
|
+
/* Array#<=> for IntArray. Lexicographic: per-element compare,
|
|
935
|
+
shorter array sorts before longer if all common elements match
|
|
936
|
+
(matches CRuby `[1,2] <=> [1,2,3] == -1`). Used by `.max` /
|
|
937
|
+
`.min` on `int_array_ptr_array` to dispatch the standard
|
|
938
|
+
sort/compare protocol over arrays of arrays. NULL recv compares
|
|
939
|
+
equal to NULL, lower than any non-NULL (matches the NULL-safe
|
|
940
|
+
pattern other compare helpers use). */
|
|
941
|
+
static mrb_int sp_IntArray_cmp(sp_IntArray*a,sp_IntArray*b){if(!a||!b)return a==b?0:(a?1:-1);mrb_int n=a->len<b->len?a->len:b->len;for(mrb_int i=0;i<n;i++){mrb_int av=a->data[a->start+i],bv=b->data[b->start+i];if(av<bv)return -1;if(av>bv)return 1;}if(a->len<b->len)return -1;if(a->len>b->len)return 1;return 0;}
|
|
315
942
|
|
|
316
|
-
typedef struct{mrb_float*data;mrb_int len;mrb_int cap;}sp_FloatArray;
|
|
317
943
|
static void sp_FloatArray_fin(void*p){sp_FloatArray*a=(sp_FloatArray*)p;sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));sp_gc_bytes-=sizeof(mrb_float)*a->cap;h->size-=sizeof(mrb_float)*a->cap;free(a->data);}
|
|
318
|
-
static sp_FloatArray*sp_FloatArray_new(void){sp_FloatArray*a=(sp_FloatArray*)sp_gc_alloc(sizeof(sp_FloatArray),sp_FloatArray_fin,NULL);a->cap=16;a->data=(mrb_float*)malloc(sizeof(mrb_float)*a->cap);a->len=0;{sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));h->size+=sizeof(mrb_float)*a->cap;sp_gc_bytes+=sizeof(mrb_float)*a->cap;}return a;}
|
|
319
|
-
static inline void sp_FloatArray_push(sp_FloatArray*a,mrb_float v){if(a->len>=a->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));sp_gc_bytes-=sizeof(mrb_float)*a->cap;h->size-=sizeof(mrb_float)*a->cap;a->cap=a->cap*2+1;a->data=(mrb_float*)realloc(a->data,sizeof(mrb_float)*a->cap);h->size+=sizeof(mrb_float)*a->cap;sp_gc_bytes+=sizeof(mrb_float)*a->cap;}a->data[a->len++]=v;}
|
|
944
|
+
static sp_FloatArray*sp_FloatArray_new(void){sp_FloatArray*a=(sp_FloatArray*)sp_gc_alloc(sizeof(sp_FloatArray),sp_FloatArray_fin,NULL);a->cap=16;a->data=(mrb_float*)malloc(sizeof(mrb_float)*a->cap);if(!a->data)sp_oom_die();a->len=0;{sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));h->size+=sizeof(mrb_float)*a->cap;sp_gc_bytes+=sizeof(mrb_float)*a->cap;}return a;}
|
|
945
|
+
static inline void sp_FloatArray_push(sp_FloatArray*a,mrb_float v){if(a->frozen){sp_raise_frozen_array();return;}if(a->len>=a->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));sp_gc_bytes-=sizeof(mrb_float)*a->cap;h->size-=sizeof(mrb_float)*a->cap;a->cap=a->cap*2+1;a->data=(mrb_float*)realloc(a->data,sizeof(mrb_float)*a->cap);h->size+=sizeof(mrb_float)*a->cap;sp_gc_bytes+=sizeof(mrb_float)*a->cap;}a->data[a->len++]=v;}
|
|
946
|
+
/* Float#step materialised as a FloatArray. Direction follows the
|
|
947
|
+
sign of k; k==0 yields an empty array to avoid an infinite loop. */
|
|
948
|
+
static sp_FloatArray*sp_FloatArray_from_step(mrb_float s,mrb_float e,mrb_float k){sp_FloatArray*a=sp_FloatArray_new();if(k==0.0)return a;mrb_float v=s;if(k>0){while(v<=e){sp_FloatArray_push(a,v);v+=k;}}else{while(v>=e){sp_FloatArray_push(a,v);v+=k;}}return a;}
|
|
320
949
|
static mrb_float sp_FloatArray_min(sp_FloatArray*a){if(a->len==0)return 0;mrb_float m=a->data[0];for(mrb_int i=1;i<a->len;i++)if(a->data[i]<m)m=a->data[i];return m;}
|
|
321
950
|
static mrb_float sp_FloatArray_max(sp_FloatArray*a){if(a->len==0)return 0;mrb_float m=a->data[0];for(mrb_int i=1;i<a->len;i++)if(a->data[i]>m)m=a->data[i];return m;}
|
|
322
|
-
static mrb_float sp_FloatArray_sum(sp_FloatArray*a){mrb_float s=
|
|
951
|
+
static mrb_float sp_FloatArray_sum(sp_FloatArray*a,mrb_float init){mrb_float s=init;for(mrb_int i=0;i<a->len;i++)s+=a->data[i];return s;}
|
|
323
952
|
static void sp_FloatArray_replace(sp_FloatArray*dst,sp_FloatArray*src){dst->len=0;if(src->len>dst->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)dst-sizeof(sp_gc_hdr));sp_gc_bytes-=sizeof(mrb_float)*dst->cap;h->size-=sizeof(mrb_float)*dst->cap;void*nd=realloc(dst->data,sizeof(mrb_float)*src->len);if(!nd){perror("realloc");exit(1);}dst->data=(mrb_float*)nd;dst->cap=src->len;h->size+=sizeof(mrb_float)*dst->cap;sp_gc_bytes+=sizeof(mrb_float)*dst->cap;}memcpy(dst->data,src->data,sizeof(mrb_float)*src->len);dst->len=src->len;}
|
|
324
|
-
static inline mrb_float sp_FloatArray_pop(sp_FloatArray*a){return a->data[--a->len];}
|
|
325
|
-
static inline mrb_float sp_FloatArray_shift(sp_FloatArray*a){if(a->len==0)return 0.0;mrb_float v=a->data[0];for(mrb_int i=0;i+1<a->len;i++)a->data[i]=a->data[i+1];a->len--;return v;}
|
|
953
|
+
static inline mrb_float sp_FloatArray_pop(sp_FloatArray*a){if(!a||a->len<=0)return 0.0;if(a->frozen){sp_raise_frozen_array();return 0.0;}return a->data[--a->len];}
|
|
954
|
+
static inline mrb_float sp_FloatArray_shift(sp_FloatArray*a){if(!a||a->len==0)return 0.0;if(a->frozen){sp_raise_frozen_array();return 0.0;}mrb_float v=a->data[0];for(mrb_int i=0;i+1<a->len;i++)a->data[i]=a->data[i+1];a->len--;return v;}
|
|
955
|
+
/* delete_at for float arrays -- the IntArray/StrArray peers existed but
|
|
956
|
+
FloatArray lacked it, so `[1.0].delete_at(0)` raised NoMethodError at runtime
|
|
957
|
+
(surfaced by the tep openai_server embeddings path). FloatArray is 0-based (no
|
|
958
|
+
`start` offset, unlike IntArray). Returns 0.0 on out-of-range (delete_at's nil
|
|
959
|
+
there). (join lives further down, where sp_float_to_s / sp_String are in scope.) */
|
|
960
|
+
static inline mrb_float sp_FloatArray_delete_at(sp_FloatArray*a,mrb_int i){if(!a)return 0.0;if(a->frozen){sp_raise_frozen_array();return 0.0;}if(i<0)i+=a->len;if(i<0||i>=a->len)return 0.0;mrb_float v=a->data[i];for(mrb_int j=i;j+1<a->len;j++)a->data[j]=a->data[j+1];a->len--;return v;}
|
|
326
961
|
static inline mrb_int sp_FloatArray_length(sp_FloatArray*a){return a->len;}
|
|
327
962
|
static inline mrb_bool sp_FloatArray_empty(sp_FloatArray*a){return a->len==0;}
|
|
328
|
-
static inline mrb_float sp_FloatArray_get(sp_FloatArray*a,mrb_int i){if(i<0)i+=a->len;return a->data[i];}
|
|
963
|
+
static inline mrb_float sp_FloatArray_get(sp_FloatArray*a,mrb_int i){if(!a)return sp_float_nil();if(i<0)i+=a->len;if(i<0||i>=a->len)return sp_float_nil();return a->data[i];}
|
|
964
|
+
/* first/last as float? : nil (sentinel) when empty, else the element.
|
|
965
|
+
`[i]` stays non-nullable (0.0 for OOB) -- only first/last produce nil,
|
|
966
|
+
matching CRuby where Array#first/#last on an empty array return nil. */
|
|
967
|
+
static inline mrb_float sp_FloatArray_first_opt(sp_FloatArray*a){return (!a||a->len<=0)?sp_float_nil():sp_FloatArray_get(a,0);}
|
|
968
|
+
static inline mrb_float sp_FloatArray_last_opt(sp_FloatArray*a){return (!a||a->len<=0)?sp_float_nil():sp_FloatArray_get(a,a->len-1);}
|
|
329
969
|
/* a[start, len] / a[start..end] for FloatArray. Same negative-start
|
|
330
970
|
* and length-clamping semantics as sp_IntArray_slice. */
|
|
331
971
|
static sp_FloatArray*sp_FloatArray_slice(sp_FloatArray*a,mrb_int start,mrb_int len){if(start<0)start+=a->len;if(start<0)start=0;sp_FloatArray*b=sp_FloatArray_new();if(start>=a->len||len<=0)return b;if(start+len>a->len)len=a->len-start;if(len>b->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)b-sizeof(sp_gc_hdr));sp_gc_bytes-=sizeof(mrb_float)*b->cap;h->size-=sizeof(mrb_float)*b->cap;b->cap=len;b->data=(mrb_float*)realloc(b->data,sizeof(mrb_float)*b->cap);h->size+=sizeof(mrb_float)*b->cap;sp_gc_bytes+=sizeof(mrb_float)*b->cap;}memcpy(b->data,a->data+start,sizeof(mrb_float)*len);b->len=len;return b;}
|
|
332
|
-
|
|
333
|
-
static
|
|
334
|
-
|
|
972
|
+
/* See sp_IntArray_slice_range -- same shape, issue #496. */
|
|
973
|
+
static sp_FloatArray*sp_FloatArray_slice_range(sp_FloatArray*a,mrb_int start,mrb_int end_,mrb_int excl){if(end_<0)end_+=a->len;if(start<0)start+=a->len;mrb_int n=end_-start+(excl?0:1);if(n<0||start<0)n=0;return sp_FloatArray_slice(a,start,n);}
|
|
974
|
+
/* Issue #769: no-op for negative index after adjustment. */
|
|
975
|
+
static inline void sp_FloatArray_set(sp_FloatArray*a,mrb_int i,mrb_float v){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}mrb_int orig=i;if(i<0)i+=a->len;if(i<0)sp_raise_cls("IndexError",sp_sprintf("index %lld too small for array; minimum: %lld",(long long)orig,(long long)-a->len));while(i>=a->cap){a->cap=a->cap*2+1;a->data=(mrb_float*)realloc(a->data,sizeof(mrb_float)*a->cap);}while(i>=a->len){a->data[a->len]=0.0;a->len++;}a->data[i]=v;}
|
|
976
|
+
static void sp_FloatArray_reverse_bang(sp_FloatArray*a){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}for(mrb_int i=0,j=a->len-1;i<j;i++,j--){mrb_float t=a->data[i];a->data[i]=a->data[j];a->data[j]=t;}}
|
|
977
|
+
static void sp_FloatArray_rotate_bang(sp_FloatArray*a,mrb_int n){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}if(a->len<=0)return;n=((n%a->len)+a->len)%a->len;if(n==0)return;mrb_float*d=a->data;mrb_int lo=0,hi=n-1;while(lo<hi){mrb_float t=d[lo];d[lo]=d[hi];d[hi]=t;lo++;hi--;}lo=n;hi=a->len-1;while(lo<hi){mrb_float t=d[lo];d[lo]=d[hi];d[hi]=t;lo++;hi--;}lo=0;hi=a->len-1;while(lo<hi){mrb_float t=d[lo];d[lo]=d[hi];d[hi]=t;lo++;hi--;}}
|
|
335
978
|
static int _sp_float_cmp(const void*a,const void*b){mrb_float va=*(const mrb_float*)a,vb=*(const mrb_float*)b;return(va>vb)-(va<vb);}
|
|
336
|
-
static void sp_FloatArray_sort_bang(sp_FloatArray*a){qsort(a->data,a->len,sizeof(mrb_float),_sp_float_cmp);}
|
|
337
|
-
static void sp_FloatArray_shuffle_bang(sp_FloatArray*a){for(mrb_int i=a->len-1;i>0;i--){mrb_int j=(mrb_int)(rand()%(i+1));mrb_float t=a->data[i];a->data[i]=a->data[j];a->data[j]=t;}}
|
|
979
|
+
static void sp_FloatArray_sort_bang(sp_FloatArray*a){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}qsort(a->data,a->len,sizeof(mrb_float),_sp_float_cmp);}
|
|
980
|
+
static void sp_FloatArray_shuffle_bang(sp_FloatArray*a){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}for(mrb_int i=a->len-1;i>0;i--){mrb_int j=(mrb_int)(rand()%(i+1));mrb_float t=a->data[i];a->data[i]=a->data[j];a->data[j]=t;}}
|
|
338
981
|
static sp_FloatArray*sp_FloatArray_shuffle(sp_FloatArray*a){sp_FloatArray*r=sp_FloatArray_new();sp_FloatArray_replace(r,a);sp_FloatArray_shuffle_bang(r);return r;}
|
|
982
|
+
static mrb_float sp_FloatArray_sample(sp_FloatArray*a){if(a->len<=0)return 0.0;return a->data[(mrb_int)(rand()%a->len)];}
|
|
339
983
|
/* IEEE 754 == on mrb_float: NaN never matches; +0.0 == -0.0 (diverges from Float#eql?). */
|
|
340
984
|
static mrb_bool sp_FloatArray_include(sp_FloatArray*a,mrb_float v){for(mrb_int i=0;i<a->len;i++)if(a->data[i]==v)return TRUE;return FALSE;}
|
|
341
985
|
static sp_FloatArray*sp_FloatArray_intersect(sp_FloatArray*a,sp_FloatArray*b){sp_FloatArray*r=sp_FloatArray_new();for(mrb_int i=0;i<a->len;i++){mrb_float v=a->data[i];if(sp_FloatArray_include(b,v)&&!sp_FloatArray_include(r,v))sp_FloatArray_push(r,v);}return r;}
|
|
342
|
-
static sp_FloatArray*sp_FloatArray_union(sp_FloatArray*a,sp_FloatArray*b){sp_FloatArray*r=sp_FloatArray_new();for(mrb_int i=0;i<a->len;i++){mrb_float v=a->data[i];if(!sp_FloatArray_include(r,v))sp_FloatArray_push(r,v);}for(mrb_int i=0;i<b->len;i++){mrb_float v=b->data[i];if(!sp_FloatArray_include(r,v))sp_FloatArray_push(r,v);}return r;}
|
|
986
|
+
static sp_FloatArray*sp_FloatArray_union(sp_FloatArray*a,sp_FloatArray*b){sp_FloatArray*r=sp_FloatArray_new();for(mrb_int i=0;i<a->len;i++){mrb_float v=a->data[i];if(!sp_FloatArray_include(r,v))sp_FloatArray_push(r,v);}if(b){for(mrb_int i=0;i<b->len;i++){mrb_float v=b->data[i];if(!sp_FloatArray_include(r,v))sp_FloatArray_push(r,v);}}return r;}
|
|
343
987
|
static sp_FloatArray*sp_FloatArray_difference(sp_FloatArray*a,sp_FloatArray*b){sp_FloatArray*r=sp_FloatArray_new();for(mrb_int i=0;i<a->len;i++){mrb_float v=a->data[i];if(!sp_FloatArray_include(b,v))sp_FloatArray_push(r,v);}return r;}
|
|
344
988
|
|
|
345
989
|
/* ---- PtrArray: array of void* pointers ---- */
|
|
346
|
-
typedef struct{void**data;mrb_int len;mrb_int cap;void(*scan_elem)(void*);}sp_PtrArray;
|
|
347
990
|
static void sp_PtrArray_fin(void*p){sp_PtrArray*a=(sp_PtrArray*)p;sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));sp_gc_bytes-=sizeof(void*)*a->cap;h->size-=sizeof(void*)*a->cap;free(a->data);}
|
|
348
991
|
static void sp_PtrArray_gc_scan(void*p){sp_PtrArray*a=(sp_PtrArray*)p;if(!a->scan_elem)return;for(mrb_int i=0;i<a->len;i++){if(a->data[i])a->scan_elem(a->data[i]);}}
|
|
349
|
-
static sp_PtrArray*sp_PtrArray_new_scan(void(*scan_elem)(void*)){sp_PtrArray*a=(sp_PtrArray*)sp_gc_alloc(sizeof(sp_PtrArray),sp_PtrArray_fin,scan_elem?sp_PtrArray_gc_scan:NULL);a->cap=16;a->data=(void**)malloc(sizeof(void*)*a->cap);a->len=0;a->scan_elem=scan_elem;{sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));h->size+=sizeof(void*)*a->cap;sp_gc_bytes+=sizeof(void*)*a->cap;}return a;}
|
|
992
|
+
static sp_PtrArray*sp_PtrArray_new_scan(void(*scan_elem)(void*)){sp_PtrArray*a=(sp_PtrArray*)sp_gc_alloc(sizeof(sp_PtrArray),sp_PtrArray_fin,scan_elem?sp_PtrArray_gc_scan:NULL);a->cap=16;a->data=(void**)malloc(sizeof(void*)*a->cap);if(!a->data)sp_oom_die();a->len=0;a->scan_elem=scan_elem;{sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));h->size+=sizeof(void*)*a->cap;sp_gc_bytes+=sizeof(void*)*a->cap;}return a;}
|
|
350
993
|
static sp_PtrArray*sp_PtrArray_new(void){return sp_PtrArray_new_scan(sp_gc_mark);}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
994
|
+
/* PtrArray for raw external pointers (FFI `:ptr` returns, dlopen
|
|
995
|
+
handles, ...). These don't carry sp_gc_hdr -- the default
|
|
996
|
+
sp_gc_mark element scan would read undefined bytes and likely
|
|
997
|
+
crash at collection time. Skip per-element scanning; the array
|
|
998
|
+
header itself is still GC-tracked. */
|
|
999
|
+
static sp_PtrArray*sp_PtrArray_new_noscan(void){return sp_PtrArray_new_scan(NULL);}
|
|
1000
|
+
static inline void sp_PtrArray_push(sp_PtrArray*a,void*v){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}if(a->len>=a->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));sp_gc_bytes-=sizeof(void*)*a->cap;h->size-=sizeof(void*)*a->cap;a->cap=a->cap*2+1;void*nd=realloc(a->data,sizeof(void*)*a->cap);if(!nd)sp_oom_die();a->data=(void**)nd;h->size+=sizeof(void*)*a->cap;sp_gc_bytes+=sizeof(void*)*a->cap;}a->data[a->len++]=v;}
|
|
1001
|
+
/* Array#pop on a `<X>_ptr_array`. Returns NULL when empty
|
|
1002
|
+
(matches CRuby's nil for typed-element arrays since the slot
|
|
1003
|
+
can't carry nil). Issue #520: previously the dispatch on
|
|
1004
|
+
`int_array_ptr_array.pop` warned "cannot resolve call to
|
|
1005
|
+
'pop'" and emitted 0, silently leaving the array intact. */
|
|
1006
|
+
static inline void *sp_PtrArray_pop(sp_PtrArray*a){if(!a||a->len==0)return NULL;return a->data[--a->len];}
|
|
1007
|
+
static inline void*sp_PtrArray_get(sp_PtrArray*a,mrb_int i){if(!a)return NULL;if(i<0)i+=a->len;if(i<0||i>=a->len)return NULL;return a->data[i];}
|
|
1008
|
+
/* Issue #770: bounds-check the final index. CRuby grows the array on
|
|
1009
|
+
out-of-range set; spinel's typed-array slots have a fixed capacity
|
|
1010
|
+
shape per element type, so the safer fallback is to no-op rather
|
|
1011
|
+
than write into adjacent memory. */
|
|
1012
|
+
static inline void sp_PtrArray_set(sp_PtrArray*a,mrb_int i,void*v){if(!a)return;if(i<0)i+=a->len;if(i<0||i>=a->len)return;a->data[i]=v;}
|
|
1013
|
+
static inline mrb_int sp_PtrArray_length(sp_PtrArray*a){if(!a)return 0;return a->len;}
|
|
1014
|
+
static inline mrb_bool sp_PtrArray_empty(sp_PtrArray*a){if(!a)return TRUE;return a->len==0;}
|
|
1015
|
+
/* `Array#delete_at(i)` -- remove and return the element at index i.
|
|
1016
|
+
Negative indices count from the end. Returns NULL when i is out
|
|
1017
|
+
of range (matches CRuby's nil for typed-element arrays). Mirrors
|
|
1018
|
+
sp_IntArray_delete_at / sp_StrArray_delete_at; without this peer
|
|
1019
|
+
user-class arrays can't shrink. */
|
|
1020
|
+
static void*sp_PtrArray_delete_at(sp_PtrArray*a,mrb_int i){if(i<0)i+=a->len;if(i<0||i>=a->len)return NULL;void*v=a->data[i];for(mrb_int j=i;j<a->len-1;j++)a->data[j]=a->data[j+1];a->len--;return v;}
|
|
356
1021
|
static void sp_PtrArray_reverse_bang(sp_PtrArray*a){for(mrb_int i=0,j=a->len-1;i<j;i++,j--){void*t=a->data[i];a->data[i]=a->data[j];a->data[j]=t;}}
|
|
357
|
-
static void sp_PtrArray_rotate_bang(sp_PtrArray*a,mrb_int n){if(a->len<=0)return;n=((n%a->len)+a->len)%a->len;if(n==0)return;void**
|
|
1022
|
+
static void sp_PtrArray_rotate_bang(sp_PtrArray*a,mrb_int n){if(a->len<=0)return;n=((n%a->len)+a->len)%a->len;if(n==0)return;void**d=a->data;mrb_int lo=0,hi=n-1;while(lo<hi){void*t=d[lo];d[lo]=d[hi];d[hi]=t;lo++;hi--;}lo=n;hi=a->len-1;while(lo<hi){void*t=d[lo];d[lo]=d[hi];d[hi]=t;lo++;hi--;}lo=0;hi=a->len-1;while(lo<hi){void*t=d[lo];d[lo]=d[hi];d[hi]=t;lo++;hi--;}}
|
|
358
1023
|
static sp_PtrArray*sp_PtrArray_dup(sp_PtrArray*a){sp_PtrArray*b=sp_PtrArray_new_scan(a->scan_elem);for(mrb_int i=0;i<a->len;i++)sp_PtrArray_push(b,a->data[i]);return b;}
|
|
359
1024
|
static sp_PtrArray*sp_PtrArray_slice(sp_PtrArray*a,mrb_int start,mrb_int len){if(start<0)start+=a->len;if(start<0)start=0;sp_PtrArray*b=sp_PtrArray_new_scan(a->scan_elem);if(start>=a->len||len<=0)return b;if(start+len>a->len)len=a->len-start;for(mrb_int i=0;i<len;i++)sp_PtrArray_push(b,a->data[start+i]);return b;}
|
|
360
1025
|
static void sp_PtrArray_shuffle_bang(sp_PtrArray*a){for(mrb_int i=a->len-1;i>0;i--){mrb_int j=(mrb_int)(rand()%(i+1));void*t=a->data[i];a->data[i]=a->data[j];a->data[j]=t;}}
|
|
361
1026
|
static sp_PtrArray*sp_PtrArray_shuffle(sp_PtrArray*a){sp_PtrArray*b=sp_PtrArray_dup(a);sp_PtrArray_shuffle_bang(b);return b;}
|
|
1027
|
+
static void *sp_PtrArray_sample(sp_PtrArray*a){if(a->len<=0)return NULL;return a->data[(mrb_int)(rand()%a->len)];}
|
|
362
1028
|
|
|
363
1029
|
/* Small-array optimization: keep the first SP_STRARR_INLINE elements
|
|
364
1030
|
* inside the struct so empty/short StrArrays skip the data malloc.
|
|
365
1031
|
* Common idiom "".split(",") and most class-metadata lists stay small.
|
|
366
1032
|
* data == inline_data is the discriminator for "still on inline storage". */
|
|
367
|
-
#define SP_STRARR_INLINE 4
|
|
368
|
-
typedef struct{const char**data;mrb_int len;mrb_int cap;const char*inline_data[SP_STRARR_INLINE];}sp_StrArray;
|
|
369
1033
|
static void sp_StrArray_fin(void*p){sp_StrArray*a=(sp_StrArray*)p;if(a->data!=a->inline_data){sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));sp_gc_bytes-=sizeof(const char*)*a->cap;h->size-=sizeof(const char*)*a->cap;free(a->data);}}
|
|
370
1034
|
static void sp_StrArray_scan(void*p){sp_StrArray*a=(sp_StrArray*)p;for(mrb_int i=0;i<a->len;i++)sp_mark_string(a->data[i]);}
|
|
371
1035
|
static sp_StrArray*sp_StrArray_new(void){sp_StrArray*a=(sp_StrArray*)sp_gc_alloc(sizeof(sp_StrArray),sp_StrArray_fin,sp_StrArray_scan);a->cap=SP_STRARR_INLINE;a->data=a->inline_data;a->len=0;return a;}
|
|
372
|
-
static inline void sp_StrArray_push(sp_StrArray*a,const char*v){if(a->len>=a->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));mrb_int nc=a->cap*2+1;if(a->data==a->inline_data){const char**nd=(const char**)malloc(sizeof(const char*)*nc);memcpy(nd,a->data,sizeof(const char*)*a->len);a->data=nd;}else{sp_gc_bytes-=sizeof(const char*)*a->cap;h->size-=sizeof(const char*)*a->cap;
|
|
1036
|
+
static inline void sp_StrArray_push(sp_StrArray*a,const char*v){if(a->frozen){sp_raise_frozen_array();return;}if(a->len>=a->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));mrb_int nc=a->cap*2+1;if(a->data==a->inline_data){const char**nd=(const char**)malloc(sizeof(const char*)*nc);if(!nd)sp_oom_die();memcpy(nd,a->data,sizeof(const char*)*a->len);a->data=nd;}else{sp_gc_bytes-=sizeof(const char*)*a->cap;h->size-=sizeof(const char*)*a->cap;void*nd=realloc(a->data,sizeof(const char*)*nc);if(!nd)sp_oom_die();a->data=(const char**)nd;}a->cap=nc;h->size+=sizeof(const char*)*a->cap;sp_gc_bytes+=sizeof(const char*)*a->cap;}a->data[a->len++]=v;}
|
|
373
1037
|
static void sp_StrArray_replace(sp_StrArray*dst,sp_StrArray*src){dst->len=0;if(src->len>dst->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)dst-sizeof(sp_gc_hdr));void*nd;if(dst->data==dst->inline_data){nd=malloc(sizeof(const char*)*src->len);if(!nd){perror("malloc");exit(1);}}else{sp_gc_bytes-=sizeof(const char*)*dst->cap;h->size-=sizeof(const char*)*dst->cap;nd=realloc(dst->data,sizeof(const char*)*src->len);if(!nd){perror("realloc");exit(1);}}dst->data=(const char**)nd;dst->cap=src->len;h->size+=sizeof(const char*)*dst->cap;sp_gc_bytes+=sizeof(const char*)*dst->cap;}memcpy(dst->data,src->data,sizeof(const char*)*src->len);dst->len=src->len;}
|
|
374
|
-
static const char*sp_StrArray_pop(sp_StrArray*a){return a->data[--a->len];}
|
|
1038
|
+
static const char*sp_StrArray_pop(sp_StrArray*a){if(!a||a->len<=0)return NULL;if(a->frozen){sp_raise_frozen_array();return NULL;}return a->data[--a->len];}
|
|
375
1039
|
static inline mrb_int sp_StrArray_length(sp_StrArray*a){return a->len;}
|
|
376
1040
|
static inline mrb_bool sp_StrArray_empty(sp_StrArray*a){return a->len==0;}
|
|
377
|
-
static inline const char*sp_StrArray_get(sp_StrArray*a,mrb_int i){if(i<0)i+=a->len;return a->data[i];}
|
|
1041
|
+
static inline const char*sp_StrArray_get(sp_StrArray*a,mrb_int i){if(!a)return NULL;if(i<0)i+=a->len;if(i<0||i>=a->len)return NULL;return a->data[i];}
|
|
378
1042
|
/* a[start, len] / a[start..end] for StrArray. Same negative-start and
|
|
379
1043
|
* length-clamping semantics as sp_IntArray_slice. Out-of-bounds start
|
|
380
1044
|
* returns an empty StrArray (we don't have a nullable form). */
|
|
381
1045
|
static sp_StrArray*sp_StrArray_slice(sp_StrArray*a,mrb_int start,mrb_int len){if(start<0)start+=a->len;if(start<0)start=0;sp_StrArray*b=sp_StrArray_new();if(start>=a->len||len<=0)return b;if(start+len>a->len)len=a->len-start;for(mrb_int i=0;i<len;i++)sp_StrArray_push(b,a->data[start+i]);return b;}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
1046
|
+
/* Forward decl — sp_str_succ is defined further down (it uses
|
|
1047
|
+
sp_utf8_decode); the StrArray_from_string_range loop below needs
|
|
1048
|
+
it visible early. */
|
|
1049
|
+
static const char *sp_str_succ(const char *s);
|
|
1050
|
+
/* String range to_a — single-char and multi-char ASCII ranges via
|
|
1051
|
+
sp_str_succ. The 4096-iteration cap stops a pathological prepend-
|
|
1052
|
+
style infinite loop before it eats memory. */
|
|
1053
|
+
static sp_StrArray *sp_StrArray_from_string_range(const char *s, const char *e, mrb_int excl) {
|
|
1054
|
+
sp_StrArray *a = sp_StrArray_new();
|
|
1055
|
+
if (!s || !e) return a;
|
|
1056
|
+
const char *cur = s;
|
|
1057
|
+
int iters = 0;
|
|
1058
|
+
while (iters < 4096) {
|
|
1059
|
+
int cmp = strcmp(cur, e);
|
|
1060
|
+
if (cmp > 0) break;
|
|
1061
|
+
if (cmp == 0 && excl) break;
|
|
1062
|
+
char *copy = sp_str_alloc(strlen(cur));
|
|
1063
|
+
strcpy(copy, cur);
|
|
1064
|
+
sp_StrArray_push(a, copy);
|
|
1065
|
+
if (cmp == 0) break;
|
|
1066
|
+
cur = sp_str_succ(cur);
|
|
1067
|
+
iters++;
|
|
1068
|
+
}
|
|
1069
|
+
return a;
|
|
1070
|
+
}
|
|
1071
|
+
/* See sp_IntArray_slice_range -- same shape, issue #496. */
|
|
1072
|
+
static sp_StrArray*sp_StrArray_slice_range(sp_StrArray*a,mrb_int start,mrb_int end_,mrb_int excl){if(end_<0)end_+=a->len;if(start<0)start+=a->len;mrb_int n=end_-start+(excl?0:1);if(n<0||start<0)n=0;return sp_StrArray_slice(a,start,n);}
|
|
1073
|
+
static inline void sp_StrArray_set(sp_StrArray*a,mrb_int i,const char*v){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}mrb_int orig=i;if(i<0)i+=a->len;if(i<0)sp_raise_cls("IndexError",sp_sprintf("index %lld too small for array; minimum: %lld",(long long)orig,(long long)-a->len));while(i>=a->len)sp_StrArray_push(a,sp_str_empty);a->data[i]=v;}
|
|
1074
|
+
static void sp_StrArray_reverse_bang(sp_StrArray*a){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}for(mrb_int i=0,j=a->len-1;i<j;i++,j--){const char*t=a->data[i];a->data[i]=a->data[j];a->data[j]=t;}}
|
|
1075
|
+
static void sp_StrArray_rotate_bang(sp_StrArray*a,mrb_int n){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}if(a->len<=0)return;n=((n%a->len)+a->len)%a->len;if(n==0)return;const char**d=a->data;mrb_int lo=0,hi=n-1;while(lo<hi){const char*t=d[lo];d[lo]=d[hi];d[hi]=t;lo++;hi--;}lo=n;hi=a->len-1;while(lo<hi){const char*t=d[lo];d[lo]=d[hi];d[hi]=t;lo++;hi--;}lo=0;hi=a->len-1;while(lo<hi){const char*t=d[lo];d[lo]=d[hi];d[hi]=t;lo++;hi--;}}
|
|
385
1076
|
static int _sp_str_cmp(const void*a,const void*b){return strcmp(*(const char*const*)a,*(const char*const*)b);}
|
|
386
|
-
static void sp_StrArray_sort_bang(sp_StrArray*a){qsort(a->data,a->len,sizeof(const char*),_sp_str_cmp);}
|
|
1077
|
+
static void sp_StrArray_sort_bang(sp_StrArray*a){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}qsort(a->data,a->len,sizeof(const char*),_sp_str_cmp);}
|
|
1078
|
+
/* Case-insensitive string compare. Portable across glibc / MinGW
|
|
1079
|
+
(avoids strcasecmp which lives in strings.h on POSIX and is named
|
|
1080
|
+
stricmp on Windows). Returns -1 / 0 / 1 like CRuby's String#casecmp. */
|
|
1081
|
+
static mrb_int sp_str_casecmp(const char*a,const char*b){if(!a||!b)return a==b?0:(a?1:-1);for(;;){int ca=tolower((unsigned char)*a),cb=tolower((unsigned char)*b);if(ca!=cb)return ca<cb?-1:1;if(!*a)return 0;a++;b++;}}
|
|
1082
|
+
/* String#valid_encoding? — walks the buffer and accepts pure ASCII
|
|
1083
|
+
or well-formed UTF-8 (RFC 3629 byte sequences with no overlong
|
|
1084
|
+
forms, no surrogate halves, code points <= U+10FFFF). */
|
|
1085
|
+
static mrb_bool sp_str_valid_encoding(const char*s){if(!s)return TRUE;const unsigned char*p=(const unsigned char*)s;while(*p){unsigned c=*p;if(c<0x80){p++;continue;}int extra;unsigned cp;unsigned min;if((c&0xE0)==0xC0){extra=1;cp=c&0x1F;min=0x80;}else if((c&0xF0)==0xE0){extra=2;cp=c&0x0F;min=0x800;}else if((c&0xF8)==0xF0){extra=3;cp=c&0x07;min=0x10000;}else return FALSE;p++;for(int i=0;i<extra;i++){if((*p&0xC0)!=0x80)return FALSE;cp=(cp<<6)|(*p&0x3F);p++;}if(cp<min)return FALSE;if(cp>=0xD800&&cp<=0xDFFF)return FALSE;if(cp>0x10FFFF)return FALSE;}return TRUE;}
|
|
387
1086
|
static const char*sp_StrArray_join(sp_StrArray*a,const char*sep){size_t sl=strlen(sep),cap=256;char*buf=(char*)malloc(cap);size_t len=0;for(mrb_int i=0;i<a->len;i++){if(i>0){if(len+sl>=cap){cap*=2;buf=(char*)realloc(buf,cap);}memcpy(buf+len,sep,sl);len+=sl;}size_t el=strlen(a->data[i]);if(len+el>=cap){cap=(len+el)*2+1;buf=(char*)realloc(buf,cap);}memcpy(buf+len,a->data[i],el);len+=el;}buf[len]=0;char*r=sp_str_alloc(len);memcpy(r,buf,len);free(buf);return r;}
|
|
388
1087
|
static mrb_bool sp_StrArray_include(sp_StrArray*a,const char*v){for(mrb_int i=0;i<a->len;i++)if(strcmp(a->data[i],v)==0)return TRUE;return FALSE;}
|
|
389
1088
|
static sp_StrArray*sp_StrArray_intersect(sp_StrArray*a,sp_StrArray*b){sp_StrArray*r=sp_StrArray_new();for(mrb_int i=0;i<a->len;i++){const char*v=a->data[i];if(sp_StrArray_include(b,v)&&!sp_StrArray_include(r,v))sp_StrArray_push(r,v);}return r;}
|
|
390
|
-
static sp_StrArray*sp_StrArray_union(sp_StrArray*a,sp_StrArray*b){sp_StrArray*r=sp_StrArray_new();for(mrb_int i=0;i<a->len;i++){const char*v=a->data[i];if(!sp_StrArray_include(r,v))sp_StrArray_push(r,v);}for(mrb_int i=0;i<b->len;i++){const char*v=b->data[i];if(!sp_StrArray_include(r,v))sp_StrArray_push(r,v);}return r;}
|
|
1089
|
+
static sp_StrArray*sp_StrArray_union(sp_StrArray*a,sp_StrArray*b){sp_StrArray*r=sp_StrArray_new();for(mrb_int i=0;i<a->len;i++){const char*v=a->data[i];if(!sp_StrArray_include(r,v))sp_StrArray_push(r,v);}if(b){for(mrb_int i=0;i<b->len;i++){const char*v=b->data[i];if(!sp_StrArray_include(r,v))sp_StrArray_push(r,v);}}return r;}
|
|
391
1090
|
static sp_StrArray*sp_StrArray_difference(sp_StrArray*a,sp_StrArray*b){sp_StrArray*r=sp_StrArray_new();for(mrb_int i=0;i<a->len;i++){const char*v=a->data[i];if(!sp_StrArray_include(b,v))sp_StrArray_push(r,v);}return r;}
|
|
392
1091
|
static mrb_int sp_StrArray_index(sp_StrArray*a,const char*v){for(mrb_int i=0;i<a->len;i++)if(strcmp(a->data[i],v)==0)return i;return -1;}
|
|
393
1092
|
static mrb_int sp_StrArray_rindex(sp_StrArray*a,const char*v){for(mrb_int i=a->len-1;i>=0;i--)if(strcmp(a->data[i],v)==0)return i;return -1;}
|
|
394
1093
|
static sp_StrArray*sp_StrArray_compact(sp_StrArray*a){sp_StrArray*r=sp_StrArray_new();for(mrb_int i=0;i<a->len;i++)if(a->data[i]!=NULL)sp_StrArray_push(r,a->data[i]);return r;}
|
|
395
|
-
static const char*sp_StrArray_delete_at(sp_StrArray*a,mrb_int i){if(i<0)i+=a->len;if(i<0||i>=a->len)return NULL;const char*v=a->data[i];for(mrb_int j=i;j<a->len-1;j++)a->data[j]=a->data[j+1];a->len--;return v;}
|
|
396
|
-
static const char*sp_StrArray_delete(sp_StrArray*a,const char*v){mrb_int w=0;const char*found=NULL;for(mrb_int i=0;i<a->len;i++){if(strcmp(a->data[i],v)!=0){a->data[w]=a->data[i];w++;}else{found=a->data[i];}}a->len=w;return found;}
|
|
397
|
-
static void sp_StrArray_insert(sp_StrArray*a,mrb_int i,const char*v){if(i<0)i+=a->len+1;sp_StrArray_push(a,
|
|
398
|
-
static void sp_StrArray_shuffle_bang(sp_StrArray*a){for(mrb_int i=a->len-1;i>0;i--){mrb_int j=(mrb_int)(rand()%(i+1));const char*t=a->data[i];a->data[i]=a->data[j];a->data[j]=t;}}
|
|
1094
|
+
static const char*sp_StrArray_delete_at(sp_StrArray*a,mrb_int i){if(!a)return NULL;if(a->frozen){sp_raise_frozen_array();return NULL;}if(i<0)i+=a->len;if(i<0||i>=a->len)return NULL;const char*v=a->data[i];for(mrb_int j=i;j<a->len-1;j++)a->data[j]=a->data[j+1];a->len--;return v;}
|
|
1095
|
+
static const char*sp_StrArray_delete(sp_StrArray*a,const char*v){if(!a)return NULL;if(a->frozen){sp_raise_frozen_array();return NULL;}mrb_int w=0;const char*found=NULL;for(mrb_int i=0;i<a->len;i++){if(strcmp(a->data[i],v)!=0){a->data[w]=a->data[i];w++;}else{found=a->data[i];}}a->len=w;return found;}
|
|
1096
|
+
static void sp_StrArray_insert(sp_StrArray*a,mrb_int i,const char*v){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}if(i<0)i+=a->len+1;sp_StrArray_push(a,sp_str_empty);for(mrb_int j=a->len-1;j>i;j--)a->data[j]=a->data[j-1];a->data[i]=v;}
|
|
1097
|
+
static void sp_StrArray_shuffle_bang(sp_StrArray*a){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}for(mrb_int i=a->len-1;i>0;i--){mrb_int j=(mrb_int)(rand()%(i+1));const char*t=a->data[i];a->data[i]=a->data[j];a->data[j]=t;}}
|
|
399
1098
|
static sp_StrArray*sp_StrArray_shuffle(sp_StrArray*a){sp_StrArray*r=sp_StrArray_new();sp_StrArray_replace(r,a);sp_StrArray_shuffle_bang(r);return r;}
|
|
1099
|
+
static const char *sp_StrArray_sample(sp_StrArray*a){if(a->len<=0)return sp_str_empty;return a->data[(mrb_int)(rand()%a->len)];}
|
|
400
1100
|
|
|
401
1101
|
static inline uint64_t sp_str_hash(const char*s){uint64_t h=14695981039346656037ULL;while(*s){h^=(unsigned char)*s++;h*=1099511628211ULL;}return h;}
|
|
402
|
-
typedef struct{const char**keys;mrb_int*vals;const char**order;mrb_int len;mrb_int cap;mrb_int mask;}sp_StrIntHash;
|
|
403
1102
|
static void sp_StrIntHash_fin(void*p){sp_StrIntHash*h=(sp_StrIntHash*)p;free(h->keys);free(h->vals);free(h->order);}
|
|
404
1103
|
static void sp_StrIntHash_scan(void*p){sp_StrIntHash*h=(sp_StrIntHash*)p;for(mrb_int i=0;i<h->cap;i++){if(h->keys[i])sp_mark_string(h->keys[i]);}}
|
|
405
|
-
|
|
1104
|
+
/* default_v is SP_INT_NIL for a hash with no explicit default ({} / {k=>v}),
|
|
1105
|
+
so a missing-key `[]` read surfaces Ruby nil (#801). Hash.new(N) sets it to
|
|
1106
|
+
N via _new_with_default. Proven-present internal reads use _get on present
|
|
1107
|
+
keys, so this only governs the miss path. */
|
|
1108
|
+
static sp_StrIntHash*sp_StrIntHash_new(void){sp_StrIntHash*h=(sp_StrIntHash*)sp_gc_alloc(sizeof(sp_StrIntHash),sp_StrIntHash_fin,sp_StrIntHash_scan);h->cap=16;h->mask=15;h->keys=(const char**)calloc(h->cap,sizeof(const char*));h->vals=(mrb_int*)calloc(h->cap,sizeof(mrb_int));h->order=(const char**)malloc(sizeof(const char*)*h->cap);h->len=0;h->default_v=SP_INT_NIL;return h;}
|
|
1109
|
+
static sp_StrIntHash*sp_StrIntHash_new_with_default(mrb_int d){sp_StrIntHash*h=sp_StrIntHash_new();h->default_v=d;return h;}
|
|
406
1110
|
static void sp_StrIntHash_grow(sp_StrIntHash*h){mrb_int oc=h->cap;const char**ok=h->keys;mrb_int*ov=h->vals;h->cap*=2;h->mask=h->cap-1;h->keys=(const char**)calloc(h->cap,sizeof(const char*));h->vals=(mrb_int*)calloc(h->cap,sizeof(mrb_int));h->order=(const char**)realloc(h->order,sizeof(const char*)*h->cap);mrb_int ol=h->len;h->len=0;for(mrb_int i=0;i<oc;i++){if(ok[i]){mrb_int idx=(mrb_int)(sp_str_hash(ok[i])&h->mask);while(h->keys[idx])idx=(idx+1)&h->mask;h->keys[idx]=ok[i];h->vals[idx]=ov[i];h->len++;}}free(ok);free(ov);}
|
|
407
|
-
static mrb_int sp_StrIntHash_get(sp_StrIntHash*h,const char*k){mrb_int idx=(mrb_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(strcmp(h->keys[idx],k)==0)return h->vals[idx];idx=(idx+1)&h->mask;}return
|
|
1111
|
+
static mrb_int sp_StrIntHash_get(sp_StrIntHash*h,const char*k){if(!h)return 0;mrb_int idx=(mrb_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(strcmp(h->keys[idx],k)==0)return h->vals[idx];idx=(idx+1)&h->mask;}return h->default_v;}
|
|
1112
|
+
/* Issue #801: maybe-missing public `[]` read. Returns default_v on a miss,
|
|
1113
|
+
which is SP_INT_NIL (Ruby nil at the value level) for a no-default hash and
|
|
1114
|
+
the explicit default for Hash.new(N). Proven-present reads keep using _get. */
|
|
1115
|
+
static mrb_int sp_StrIntHash_get_opt(sp_StrIntHash*h,const char*k){if(!h)return SP_INT_NIL;mrb_int idx=(mrb_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(strcmp(h->keys[idx],k)==0)return h->vals[idx];idx=(idx+1)&h->mask;}return h->default_v;}
|
|
408
1116
|
static void sp_StrIntHash_set(sp_StrIntHash*h,const char*k,mrb_int v){if(h->len*2>=h->cap)sp_StrIntHash_grow(h);mrb_int idx=(mrb_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(strcmp(h->keys[idx],k)==0){h->vals[idx]=v;return;}idx=(idx+1)&h->mask;}h->keys[idx]=k;h->vals[idx]=v;h->order[h->len]=k;h->len++;}
|
|
409
1117
|
static mrb_bool sp_StrIntHash_has_key(sp_StrIntHash*h,const char*k){mrb_int idx=(mrb_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(strcmp(h->keys[idx],k)==0)return TRUE;idx=(idx+1)&h->mask;}return FALSE;}
|
|
1118
|
+
/* Hash#value? -- scan values in insertion order. Issue #738. */
|
|
1119
|
+
static mrb_bool sp_StrIntHash_has_value(sp_StrIntHash*h,mrb_int v){if(!h)return FALSE;for(mrb_int i=0;i<h->len;i++)if(sp_StrIntHash_get(h,h->order[i])==v)return TRUE;return FALSE;}
|
|
410
1120
|
static mrb_int sp_StrIntHash_length(sp_StrIntHash*h){return h->len;}
|
|
411
1121
|
static void sp_StrIntHash_delete(sp_StrIntHash*h,const char*k){mrb_int idx=(mrb_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(strcmp(h->keys[idx],k)==0){h->keys[idx]=NULL;h->vals[idx]=0;h->len--;mrb_int j=(idx+1)&h->mask;while(h->keys[j]){mrb_int nj=(mrb_int)(sp_str_hash(h->keys[j])&h->mask);if((j>idx&&(nj<=idx||nj>j))||(j<idx&&nj<=idx&&nj>j)){h->keys[idx]=h->keys[j];h->vals[idx]=h->vals[j];h->keys[j]=NULL;h->vals[j]=0;idx=j;}j=(j+1)&h->mask;}{mrb_int oi=0;while(oi<=h->len){if(strcmp(h->order[oi],k)==0){while(oi<h->len){h->order[oi]=h->order[oi+1];oi++;}break;}oi++;}}return;}idx=(idx+1)&h->mask;}}
|
|
412
1122
|
static sp_StrArray*sp_StrIntHash_keys(sp_StrIntHash*h){sp_StrArray*a=sp_StrArray_new();for(mrb_int i=0;i<h->len;i++)sp_StrArray_push(a,h->order[i]);return a;}
|
|
413
1123
|
static sp_IntArray*sp_StrIntHash_values(sp_StrIntHash*h){sp_IntArray*a=sp_IntArray_new();for(mrb_int i=0;i<h->len;i++)sp_IntArray_push(a,sp_StrIntHash_get(h,h->order[i]));return a;}
|
|
414
1124
|
static sp_StrIntHash*sp_StrArray_tally(sp_StrArray*a){sp_StrIntHash*h=sp_StrIntHash_new();for(mrb_int i=0;i<a->len;i++){const char*k=a->data[i];mrb_int c=sp_StrIntHash_has_key(h,k)?sp_StrIntHash_get(h,k):0;sp_StrIntHash_set(h,k,c+1);}return h;}
|
|
415
|
-
static sp_StrIntHash*sp_StrIntHash_merge(sp_StrIntHash*a,sp_StrIntHash*b){sp_StrIntHash*r=sp_StrIntHash_new();for(mrb_int i=0;i<a->len;i++)sp_StrIntHash_set(r,a->order[i],sp_StrIntHash_get(a,a->order[i]));for(mrb_int i=0;i<b->len;i++)sp_StrIntHash_set(r,b->order[i],sp_StrIntHash_get(b,b->order[i]));return r;}
|
|
1125
|
+
static sp_StrIntHash*sp_StrIntHash_merge(sp_StrIntHash*a,sp_StrIntHash*b){sp_StrIntHash*r=sp_StrIntHash_new();r->default_v=a->default_v;for(mrb_int i=0;i<a->len;i++)sp_StrIntHash_set(r,a->order[i],sp_StrIntHash_get(a,a->order[i]));for(mrb_int i=0;i<b->len;i++)sp_StrIntHash_set(r,b->order[i],sp_StrIntHash_get(b,b->order[i]));return r;}
|
|
416
1126
|
static void sp_StrIntHash_update(sp_StrIntHash*a,sp_StrIntHash*b){for(mrb_int i=0;i<b->len;i++)sp_StrIntHash_set(a,b->order[i],sp_StrIntHash_get(b,b->order[i]));}
|
|
1127
|
+
static sp_StrIntHash*sp_StrIntHash_dup(sp_StrIntHash*h){sp_StrIntHash*r=sp_StrIntHash_new();r->default_v=h->default_v;for(mrb_int i=0;i<h->len;i++)sp_StrIntHash_set(r,h->order[i],sp_StrIntHash_get(h,h->order[i]));return r;}
|
|
1128
|
+
static mrb_bool sp_StrIntHash_eq(sp_StrIntHash*a,sp_StrIntHash*b){if(!a||!b)return a==b;if(a->len!=b->len)return FALSE;for(mrb_int i=0;i<a->len;i++){const char*k=a->order[i];if(!sp_StrIntHash_has_key(b,k))return FALSE;if(sp_StrIntHash_get(a,k)!=sp_StrIntHash_get(b,k))return FALSE;}return TRUE;}
|
|
1129
|
+
|
|
1130
|
+
/* GC.stat snapshot: String=>Integer hash over the collector globals.
|
|
1131
|
+
full_runs derives from sp_gc_cycle / SP_GC_FULL_INTERVAL (the major
|
|
1132
|
+
collection cadence). */
|
|
1133
|
+
static sp_StrIntHash*sp_gc_stat(void){sp_StrIntHash*h=sp_StrIntHash_new();sp_StrIntHash_set(h,"bytes",(mrb_int)sp_gc_bytes);sp_StrIntHash_set(h,"old_bytes",(mrb_int)sp_gc_old_bytes);sp_StrIntHash_set(h,"threshold",(mrb_int)sp_gc_threshold);sp_StrIntHash_set(h,"cycle",(mrb_int)sp_gc_cycle);sp_StrIntHash_set(h,"full_runs",(mrb_int)(sp_gc_cycle/SP_GC_FULL_INTERVAL));return h;}
|
|
417
1134
|
|
|
418
|
-
typedef struct{const char**keys;const char**vals;const char**order;mrb_int len;mrb_int cap;mrb_int mask;}sp_StrStrHash;
|
|
419
1135
|
static void sp_StrStrHash_fin(void*p){sp_StrStrHash*h=(sp_StrStrHash*)p;free(h->keys);free(h->vals);free(h->order);}
|
|
420
|
-
static void sp_StrStrHash_scan(void*p){sp_StrStrHash*h=(sp_StrStrHash*)p;for(mrb_int i=0;i<h->cap;i++){if(h->keys[i]){sp_mark_string(h->keys[i]);sp_mark_string(h->vals[i]);}}}
|
|
421
|
-
static sp_StrStrHash*sp_StrStrHash_new(void){sp_StrStrHash*h=(sp_StrStrHash*)sp_gc_alloc(sizeof(sp_StrStrHash),sp_StrStrHash_fin,sp_StrStrHash_scan);h->cap=16;h->mask=15;h->keys=(const char**)calloc(h->cap,sizeof(const char*));h->vals=(const char**)calloc(h->cap,sizeof(const char*));h->order=(const char**)malloc(sizeof(const char*)*h->cap);h->len=0;return h;}
|
|
1136
|
+
static void sp_StrStrHash_scan(void*p){sp_StrStrHash*h=(sp_StrStrHash*)p;for(mrb_int i=0;i<h->cap;i++){if(h->keys[i]){sp_mark_string(h->keys[i]);sp_mark_string(h->vals[i]);}}if(h->default_v)sp_mark_string(h->default_v);}
|
|
1137
|
+
static sp_StrStrHash*sp_StrStrHash_new(void){sp_StrStrHash*h=(sp_StrStrHash*)sp_gc_alloc(sizeof(sp_StrStrHash),sp_StrStrHash_fin,sp_StrStrHash_scan);h->cap=16;h->mask=15;h->keys=(const char**)calloc(h->cap,sizeof(const char*));h->vals=(const char**)calloc(h->cap,sizeof(const char*));h->order=(const char**)malloc(sizeof(const char*)*h->cap);h->len=0;h->default_v=NULL;return h;}
|
|
1138
|
+
static sp_StrStrHash*sp_StrStrHash_new_with_default(const char*d){sp_StrStrHash*h=sp_StrStrHash_new();h->default_v=d;return h;}
|
|
422
1139
|
static void sp_StrStrHash_grow(sp_StrStrHash*h){mrb_int oc=h->cap;const char**ok=h->keys;const char**ov=h->vals;h->cap*=2;h->mask=h->cap-1;h->keys=(const char**)calloc(h->cap,sizeof(const char*));h->vals=(const char**)calloc(h->cap,sizeof(const char*));h->order=(const char**)realloc(h->order,sizeof(const char*)*h->cap);h->len=0;for(mrb_int i=0;i<oc;i++){if(ok[i]){mrb_int idx=(mrb_int)(sp_str_hash(ok[i])&h->mask);while(h->keys[idx])idx=(idx+1)&h->mask;h->keys[idx]=ok[i];h->vals[idx]=ov[i];h->len++;}}free(ok);free(ov);}
|
|
423
|
-
static const char*sp_StrStrHash_get(sp_StrStrHash*h,const char*k){mrb_int idx=(mrb_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(strcmp(h->keys[idx],k)==0)return h->vals[idx];idx=(idx+1)&h->mask;}return
|
|
1140
|
+
static const char*sp_StrStrHash_get(sp_StrStrHash*h,const char*k){if(!h)return NULL;mrb_int idx=(mrb_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(strcmp(h->keys[idx],k)==0)return h->vals[idx];idx=(idx+1)&h->mask;}return h->default_v;}
|
|
424
1141
|
static void sp_StrStrHash_set(sp_StrStrHash*h,const char*k,const char*v){if(h->len*2>=h->cap)sp_StrStrHash_grow(h);mrb_int idx=(mrb_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(strcmp(h->keys[idx],k)==0){h->vals[idx]=v;return;}idx=(idx+1)&h->mask;}h->keys[idx]=k;h->vals[idx]=v;h->order[h->len]=k;h->len++;}
|
|
425
1142
|
static mrb_bool sp_StrStrHash_has_key(sp_StrStrHash*h,const char*k){mrb_int idx=(mrb_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(strcmp(h->keys[idx],k)==0)return TRUE;idx=(idx+1)&h->mask;}return FALSE;}
|
|
1143
|
+
static mrb_bool sp_StrStrHash_has_value(sp_StrStrHash*h,const char*v){if(!h||!v)return FALSE;for(mrb_int i=0;i<h->len;i++){const char*x=sp_StrStrHash_get(h,h->order[i]);if(x&&strcmp(x,v)==0)return TRUE;}return FALSE;}
|
|
426
1144
|
static mrb_int sp_StrStrHash_length(sp_StrStrHash*h){return h->len;}
|
|
427
1145
|
static void sp_StrStrHash_delete(sp_StrStrHash*h,const char*k){mrb_int idx=(mrb_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(strcmp(h->keys[idx],k)==0){h->keys[idx]=NULL;h->vals[idx]=NULL;h->len--;mrb_int j=(idx+1)&h->mask;while(h->keys[j]){mrb_int nj=(mrb_int)(sp_str_hash(h->keys[j])&h->mask);if((j>idx&&(nj<=idx||nj>j))||(j<idx&&nj<=idx&&nj>j)){h->keys[idx]=h->keys[j];h->vals[idx]=h->vals[j];h->keys[j]=NULL;h->vals[j]=NULL;idx=j;}j=(j+1)&h->mask;}{mrb_int oi=0;while(oi<=h->len){if(strcmp(h->order[oi],k)==0){while(oi<h->len){h->order[oi]=h->order[oi+1];oi++;}break;}oi++;}}return;}idx=(idx+1)&h->mask;}}
|
|
428
1146
|
static sp_StrArray*sp_StrStrHash_keys(sp_StrStrHash*h){sp_StrArray*a=sp_StrArray_new();for(mrb_int i=0;i<h->len;i++)sp_StrArray_push(a,h->order[i]);return a;}
|
|
429
1147
|
static sp_StrArray*sp_StrStrHash_values(sp_StrStrHash*h){sp_StrArray*a=sp_StrArray_new();for(mrb_int i=0;i<h->len;i++)sp_StrArray_push(a,sp_StrStrHash_get(h,h->order[i]));return a;}
|
|
430
1148
|
static sp_StrStrHash*sp_StrStrHash_invert(sp_StrStrHash*h){sp_StrStrHash*r=sp_StrStrHash_new();for(mrb_int i=0;i<h->len;i++){const char*k=h->order[i];sp_StrStrHash_set(r,sp_StrStrHash_get(h,k),k);}return r;}
|
|
431
1149
|
static void sp_StrStrHash_update(sp_StrStrHash*a,sp_StrStrHash*b){for(mrb_int i=0;i<b->len;i++)sp_StrStrHash_set(a,b->order[i],sp_StrStrHash_get(b,b->order[i]));}
|
|
1150
|
+
static sp_StrStrHash*sp_StrStrHash_dup(sp_StrStrHash*h){sp_StrStrHash*r=sp_StrStrHash_new();r->default_v=h->default_v;for(mrb_int i=0;i<h->len;i++)sp_StrStrHash_set(r,h->order[i],sp_StrStrHash_get(h,h->order[i]));return r;}
|
|
1151
|
+
static mrb_bool sp_StrStrHash_eq(sp_StrStrHash*a,sp_StrStrHash*b){if(!a||!b)return a==b;if(a->len!=b->len)return FALSE;for(mrb_int i=0;i<a->len;i++){const char*k=a->order[i];if(!sp_StrStrHash_has_key(b,k))return FALSE;if(!sp_str_eq(sp_StrStrHash_get(a,k),sp_StrStrHash_get(b,k)))return FALSE;}return TRUE;}
|
|
432
1152
|
|
|
433
|
-
typedef struct{mrb_int*keys;const char**vals;mrb_int*order;mrb_bool*used;mrb_int len;mrb_int cap;mrb_int mask;}sp_IntStrHash;
|
|
434
1153
|
static void sp_IntStrHash_fin(void*p){sp_IntStrHash*h=(sp_IntStrHash*)p;free(h->keys);free(h->vals);free(h->order);free(h->used);}
|
|
435
|
-
static void sp_IntStrHash_scan(void*p){sp_IntStrHash*h=(sp_IntStrHash*)p;for(mrb_int i=0;i<h->cap;i++)if(h->used[i])sp_mark_string(h->vals[i]);}
|
|
436
|
-
static sp_IntStrHash*sp_IntStrHash_new(void){sp_IntStrHash*h=(sp_IntStrHash*)sp_gc_alloc(sizeof(sp_IntStrHash),sp_IntStrHash_fin,sp_IntStrHash_scan);h->cap=16;h->mask=15;h->keys=(mrb_int*)calloc(h->cap,sizeof(mrb_int));h->vals=(const char**)calloc(h->cap,sizeof(const char*));h->order=(mrb_int*)malloc(sizeof(mrb_int)*h->cap);h->used=(mrb_bool*)calloc(h->cap,sizeof(mrb_bool));h->len=0;return h;}
|
|
1154
|
+
static void sp_IntStrHash_scan(void*p){sp_IntStrHash*h=(sp_IntStrHash*)p;for(mrb_int i=0;i<h->cap;i++)if(h->used[i])sp_mark_string(h->vals[i]);if(h->default_v)sp_mark_string(h->default_v);}
|
|
1155
|
+
static sp_IntStrHash*sp_IntStrHash_new(void){sp_IntStrHash*h=(sp_IntStrHash*)sp_gc_alloc(sizeof(sp_IntStrHash),sp_IntStrHash_fin,sp_IntStrHash_scan);h->cap=16;h->mask=15;h->keys=(mrb_int*)calloc(h->cap,sizeof(mrb_int));h->vals=(const char**)calloc(h->cap,sizeof(const char*));h->order=(mrb_int*)malloc(sizeof(mrb_int)*h->cap);h->used=(mrb_bool*)calloc(h->cap,sizeof(mrb_bool));h->len=0;h->default_v=NULL;return h;}
|
|
1156
|
+
static sp_IntStrHash*sp_IntStrHash_new_with_default(const char*d){sp_IntStrHash*h=sp_IntStrHash_new();h->default_v=d;return h;}
|
|
437
1157
|
static inline mrb_int _sp_istr_idx(mrb_int mask,mrb_int k){return(mrb_int)(((uint64_t)(unsigned long long)k*11400714819323198485ULL)&(uint64_t)mask);}
|
|
438
1158
|
static void sp_IntStrHash_grow(sp_IntStrHash*h){mrb_int oc=h->cap,ol=h->len;mrb_int*ok=h->keys;const char**ov=h->vals;mrb_bool*ou=h->used;mrb_int*oo=h->order;h->cap*=2;h->mask=h->cap-1;h->keys=(mrb_int*)calloc(h->cap,sizeof(mrb_int));h->vals=(const char**)calloc(h->cap,sizeof(const char*));h->order=(mrb_int*)malloc(sizeof(mrb_int)*h->cap);h->used=(mrb_bool*)calloc(h->cap,sizeof(mrb_bool));h->len=ol;for(mrb_int i=0;i<oc;i++){if(!ou[i])continue;mrb_int k=ok[i];const char*v=ov[i];mrb_int di=_sp_istr_idx(h->mask,k);while(h->used[di])di=(di+1)&h->mask;h->used[di]=TRUE;h->keys[di]=k;h->vals[di]=v;}for(mrb_int i=0;i<ol;i++)h->order[i]=oo[i];free(ok);free(ov);free(ou);free(oo);}
|
|
439
1159
|
static void sp_IntStrHash_set(sp_IntStrHash*h,mrb_int k,const char*v){if(h->len*2>=h->cap)sp_IntStrHash_grow(h);mrb_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k){h->vals[idx]=v;return;}idx=(idx+1)&h->mask;}h->used[idx]=TRUE;h->keys[idx]=k;h->vals[idx]=v;h->order[h->len++]=k;}
|
|
440
|
-
static const char*sp_IntStrHash_get(sp_IntStrHash*h,mrb_int k){mrb_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k)return h->vals[idx];idx=(idx+1)&h->mask;}return
|
|
1160
|
+
static const char*sp_IntStrHash_get(sp_IntStrHash*h,mrb_int k){if(!h)return NULL;mrb_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k)return h->vals[idx];idx=(idx+1)&h->mask;}return h->default_v;}
|
|
441
1161
|
static mrb_bool sp_IntStrHash_has_key(sp_IntStrHash*h,mrb_int k){mrb_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k)return TRUE;idx=(idx+1)&h->mask;}return FALSE;}
|
|
1162
|
+
static mrb_bool sp_IntStrHash_has_value(sp_IntStrHash*h,const char*v){if(!h||!v)return FALSE;for(mrb_int i=0;i<h->len;i++){const char*x=sp_IntStrHash_get(h,h->order[i]);if(x&&strcmp(x,v)==0)return TRUE;}return FALSE;}
|
|
442
1163
|
static mrb_int sp_IntStrHash_length(sp_IntStrHash*h){return h->len;}
|
|
443
1164
|
static sp_IntArray*sp_IntStrHash_keys(sp_IntStrHash*h){sp_IntArray*a=sp_IntArray_new();for(mrb_int i=0;i<h->len;i++)sp_IntArray_push(a,h->order[i]);return a;}
|
|
444
1165
|
static sp_StrArray*sp_IntStrHash_values(sp_IntStrHash*h){sp_StrArray*a=sp_StrArray_new();for(mrb_int i=0;i<h->len;i++)sp_StrArray_push(a,sp_IntStrHash_get(h,h->order[i]));return a;}
|
|
1166
|
+
static sp_IntStrHash*sp_IntStrHash_dup(sp_IntStrHash*h){sp_IntStrHash*r=sp_IntStrHash_new();r->default_v=h->default_v;for(mrb_int i=0;i<h->len;i++)sp_IntStrHash_set(r,h->order[i],sp_IntStrHash_get(h,h->order[i]));return r;}
|
|
1167
|
+
static mrb_bool sp_IntStrHash_eq(sp_IntStrHash*a,sp_IntStrHash*b){if(!a||!b)return a==b;if(a->len!=b->len)return FALSE;for(mrb_int i=0;i<a->len;i++){mrb_int k=a->order[i];if(!sp_IntStrHash_has_key(b,k))return FALSE;if(!sp_str_eq(sp_IntStrHash_get(a,k),sp_IntStrHash_get(b,k)))return FALSE;}return TRUE;}
|
|
1168
|
+
|
|
1169
|
+
/* Int → Int typed hash. Mirrors sp_IntStrHash's open-addressing
|
|
1170
|
+
layout (used[] bitmap so 0/-1 keys are distinguishable from
|
|
1171
|
+
empty), with int-valued slots. Used by Array#tally on int
|
|
1172
|
+
arrays — see #865. */
|
|
1173
|
+
static void sp_IntIntHash_fin(void*p){sp_IntIntHash*h=(sp_IntIntHash*)p;free(h->keys);free(h->vals);free(h->order);free(h->used);}
|
|
1174
|
+
/* default_v is SP_INT_NIL for a hash with no explicit default, so a
|
|
1175
|
+
missing-key `[]` read surfaces Ruby nil (#801). Hash.new(N) sets it via
|
|
1176
|
+
_new_with_default. */
|
|
1177
|
+
static sp_IntIntHash*sp_IntIntHash_new(void){sp_IntIntHash*h=(sp_IntIntHash*)sp_gc_alloc(sizeof(sp_IntIntHash),sp_IntIntHash_fin,NULL);h->cap=16;h->mask=15;h->keys=(mrb_int*)calloc(h->cap,sizeof(mrb_int));h->vals=(mrb_int*)calloc(h->cap,sizeof(mrb_int));h->order=(mrb_int*)malloc(sizeof(mrb_int)*h->cap);h->used=(mrb_bool*)calloc(h->cap,sizeof(mrb_bool));h->len=0;h->default_v=SP_INT_NIL;return h;}
|
|
1178
|
+
static sp_IntIntHash*sp_IntIntHash_new_with_default(mrb_int d){sp_IntIntHash*h=sp_IntIntHash_new();h->default_v=d;return h;}
|
|
1179
|
+
static void sp_IntIntHash_grow(sp_IntIntHash*h){mrb_int oc=h->cap,ol=h->len;mrb_int*ok=h->keys;mrb_int*ov=h->vals;mrb_bool*ou=h->used;mrb_int*oo=h->order;h->cap*=2;h->mask=h->cap-1;h->keys=(mrb_int*)calloc(h->cap,sizeof(mrb_int));h->vals=(mrb_int*)calloc(h->cap,sizeof(mrb_int));h->order=(mrb_int*)malloc(sizeof(mrb_int)*h->cap);h->used=(mrb_bool*)calloc(h->cap,sizeof(mrb_bool));h->len=ol;for(mrb_int i=0;i<oc;i++){if(!ou[i])continue;mrb_int k=ok[i];mrb_int v=ov[i];mrb_int di=_sp_istr_idx(h->mask,k);while(h->used[di])di=(di+1)&h->mask;h->used[di]=TRUE;h->keys[di]=k;h->vals[di]=v;}for(mrb_int i=0;i<ol;i++)h->order[i]=oo[i];free(ok);free(ov);free(ou);free(oo);}
|
|
1180
|
+
static void sp_IntIntHash_set(sp_IntIntHash*h,mrb_int k,mrb_int v){if(h->len*2>=h->cap)sp_IntIntHash_grow(h);mrb_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k){h->vals[idx]=v;return;}idx=(idx+1)&h->mask;}h->used[idx]=TRUE;h->keys[idx]=k;h->vals[idx]=v;h->order[h->len++]=k;}
|
|
1181
|
+
static mrb_int sp_IntIntHash_get(sp_IntIntHash*h,mrb_int k){if(!h)return 0;mrb_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k)return h->vals[idx];idx=(idx+1)&h->mask;}return h->default_v;}
|
|
1182
|
+
/* Issue #801: maybe-missing public `[]` read. Returns default_v on a miss
|
|
1183
|
+
(SP_INT_NIL for a no-default hash = Ruby nil; the explicit default for
|
|
1184
|
+
Hash.new(N)). Proven-present reads keep using _get. */
|
|
1185
|
+
static mrb_int sp_IntIntHash_get_opt(sp_IntIntHash*h,mrb_int k){if(!h)return SP_INT_NIL;mrb_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k)return h->vals[idx];idx=(idx+1)&h->mask;}return h->default_v;}
|
|
1186
|
+
static mrb_bool sp_IntIntHash_has_key(sp_IntIntHash*h,mrb_int k){mrb_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k)return TRUE;idx=(idx+1)&h->mask;}return FALSE;}
|
|
1187
|
+
static mrb_int sp_IntIntHash_length(sp_IntIntHash*h){return h?h->len:0;}
|
|
1188
|
+
static sp_IntIntHash*sp_IntIntHash_dup(sp_IntIntHash*h){sp_IntIntHash*r=sp_IntIntHash_new();r->default_v=h->default_v;for(mrb_int i=0;i<h->len;i++)sp_IntIntHash_set(r,h->order[i],sp_IntIntHash_get(h,h->order[i]));return r;}
|
|
1189
|
+
/* Array#tally on int_array. CRuby returns an Integer-keyed Hash
|
|
1190
|
+
mapping each distinct element to its occurrence count. */
|
|
1191
|
+
static sp_IntIntHash*sp_IntArray_tally_int(sp_IntArray*a){sp_IntIntHash*h=sp_IntIntHash_new();if(!a)return h;for(mrb_int i=0;i<a->len;i++){mrb_int k=a->data[a->start+i];mrb_int c=sp_IntIntHash_has_key(h,k)?sp_IntIntHash_get(h,k):0;sp_IntIntHash_set(h,k,c+1);}return h;}
|
|
445
1192
|
|
|
446
1193
|
/* Reuse an existing StrArray for split, avoiding GC alloc.
|
|
447
1194
|
Clears a->len and refills. Substring strings are still malloc'd. */
|
|
1195
|
+
static inline void sp_str_split_push(sp_StrArray*a,const char*p,size_t n){
|
|
1196
|
+
char*r=sp_str_alloc_raw(n+1);
|
|
1197
|
+
memcpy(r,p,n);
|
|
1198
|
+
r[n]=0;
|
|
1199
|
+
sp_StrArray_push(a,r);
|
|
1200
|
+
}
|
|
448
1201
|
static void sp_str_split_into(sp_StrArray*a,const char*s,const char*sep){
|
|
449
|
-
a
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
1202
|
+
SP_GC_ROOT(a);
|
|
1203
|
+
SP_GC_ROOT(s);
|
|
1204
|
+
SP_GC_ROOT(sep);
|
|
1205
|
+
a->len=0;
|
|
1206
|
+
if(*s==0)return;
|
|
1207
|
+
size_t sl=strlen(sep);
|
|
1208
|
+
if(sl==0){
|
|
1209
|
+
const char*p=s;
|
|
1210
|
+
while(*p){
|
|
1211
|
+
int cn=sp_utf8_advance(p);
|
|
1212
|
+
sp_str_split_push(a,p,(size_t)cn);
|
|
1213
|
+
p+=cn;
|
|
1214
|
+
}
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
const char*p=s;
|
|
1218
|
+
while(1){
|
|
1219
|
+
const char*f=strstr(p,sep);
|
|
1220
|
+
if(!f){
|
|
1221
|
+
sp_str_split_push(a,p,strlen(p));
|
|
1222
|
+
break;
|
|
1223
|
+
}
|
|
1224
|
+
size_t n=f-p;
|
|
1225
|
+
sp_str_split_push(a,p,n);
|
|
1226
|
+
p=f+sl;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
453
1229
|
/* Extract the n-th field (0-based) from s split by sep, without
|
|
454
1230
|
allocating a full StrArray. Returns a newly allocated string.
|
|
455
1231
|
If the field doesn't exist, returns "". */
|
|
456
1232
|
static const char*sp_str_field(const char*s,const char*sep,mrb_int n){
|
|
457
1233
|
size_t sl=strlen(sep);mrb_int cur=0;const char*p=s;
|
|
458
|
-
if(sl==0)return
|
|
459
|
-
while(cur<n){const char*f=strstr(p,sep);if(!f)return
|
|
1234
|
+
if(sl==0)return sp_str_empty;
|
|
1235
|
+
while(cur<n){const char*f=strstr(p,sep);if(!f)return sp_str_empty;p=f+sl;cur++;}
|
|
460
1236
|
const char*end=strstr(p,sep);size_t len=end?((size_t)(end-p)):strlen(p);
|
|
461
1237
|
char*r=sp_str_alloc_raw(len+1);memcpy(r,p,len);r[len]=0;return r;}
|
|
462
1238
|
/* Count fields in s split by sep (without allocating). */
|
|
463
1239
|
static mrb_int sp_str_field_count(const char*s,const char*sep){
|
|
464
1240
|
if(*s==0)return 0;size_t sl=strlen(sep);if(sl==0)return(mrb_int)strlen(s);
|
|
465
1241
|
mrb_int c=1;const char*p=s;while((p=strstr(p,sep))!=NULL){c++;p+=sl;}return c;}
|
|
466
|
-
static const char*sp_str_concat(const char*a,const char*b){size_t la=
|
|
467
|
-
|
|
468
|
-
static const char*
|
|
1242
|
+
static const char*sp_str_concat(const char*a,const char*b){if(!a)a=sp_str_empty;if(!b)b=sp_str_empty;size_t la=sp_str_byte_len(a),lb=sp_str_byte_len(b);char*r=sp_str_alloc(la+lb);memcpy(r,a,la);memcpy(r+la,b,lb);return r;}
|
|
1243
|
+
/* Issue #760: NULL src to memcpy is UB. Treat NULL as empty string. */
|
|
1244
|
+
static const char*sp_str_concat3(const char*a,const char*b,const char*c){if(!a)a="";if(!b)b="";if(!c)c="";size_t la=sp_str_byte_len(a),lb=sp_str_byte_len(b),lc=sp_str_byte_len(c);char*r=sp_str_alloc(la+lb+lc);memcpy(r,a,la);memcpy(r+la,b,lb);memcpy(r+la+lb,c,lc);return r;}
|
|
1245
|
+
static const char*sp_str_concat4(const char*a,const char*b,const char*c,const char*d){if(!a)a="";if(!b)b="";if(!c)c="";if(!d)d="";size_t la=sp_str_byte_len(a),lb=sp_str_byte_len(b),lc=sp_str_byte_len(c),ld=sp_str_byte_len(d);char*r=sp_str_alloc(la+lb+lc+ld);memcpy(r,a,la);memcpy(r+la,b,lb);memcpy(r+la+lb,c,lc);memcpy(r+la+lb+lc,d,ld);return r;}
|
|
469
1246
|
/* Concatenate N strings into a single GC-managed buffer. */
|
|
470
|
-
|
|
471
|
-
static const char*
|
|
472
|
-
static const char*
|
|
1247
|
+
/* Issue #760: NULL entries treated as empty strings. */
|
|
1248
|
+
static const char*sp_str_concat_arr(const char *const *parts,int n){size_t total=0;for(int i=0;i<n;i++)total+=sp_str_byte_len(parts[i]?parts[i]:"");char*r=sp_str_alloc(total);char*p=r;for(int i=0;i<n;i++){const char*s=parts[i]?parts[i]:"";size_t sl=sp_str_byte_len(s);memcpy(p,s,sl);p+=sl;}return r;}
|
|
1249
|
+
static const char*sp_int_to_s(mrb_int n){char*b=sp_str_alloc_raw(32);int len=snprintf(b,32,"%lld",(long long)n);if(len<0)len=0;sp_str_set_len(b,(size_t)len);return b;}
|
|
1250
|
+
static const char*sp_int_to_s_base(mrb_int n,mrb_int base){if(base<2||base>36)base=10;char*b=sp_str_alloc_raw(72);char tmp[72];int i=0;int neg=0;uint64_t u;if(n<0){neg=1;u=(uint64_t)(-(n+1))+1;}else{u=(uint64_t)n;}if(u==0){tmp[i++]='0';}else{while(u>0){mrb_int d=u%base;tmp[i++]=d<10?'0'+d:'a'+d-10;u/=base;}}int j=0;if(neg)b[j++]='-';while(i>0)b[j++]=tmp[--i];b[j]=0;sp_str_set_len(b,(size_t)j);return b;}
|
|
473
1251
|
/* Float#to_s (Ruby semantics): produce the shortest decimal that
|
|
474
1252
|
round-trips back to the same double, formatted per CRuby — fixed
|
|
475
1253
|
point when the decimal exponent is in [-4, 15], scientific
|
|
@@ -500,72 +1278,502 @@ static const char*sp_float_to_s(mrb_float f){
|
|
|
500
1278
|
if(e>=0)out[o++]='+';else{out[o++]='-';e=-e;}
|
|
501
1279
|
if(e<10){out[o++]='0';out[o++]=(char)('0'+e);}else o+=snprintf(out+o,16,"%d",e);
|
|
502
1280
|
}
|
|
503
|
-
out[o]=0;return out;
|
|
1281
|
+
out[o]=0;sp_str_set_len(out,(size_t)o);return out;
|
|
504
1282
|
}
|
|
505
1283
|
#define sp_float_inspect sp_float_to_s
|
|
506
1284
|
/* String#inspect: wrap in double quotes and escape \, ", \n, \t, \r,
|
|
507
1285
|
plus any non-printable byte as \xNN. Output is always ASCII-safe. */
|
|
508
|
-
static const char*sp_str_inspect(const char*s){if(!s){char*r=sp_str_alloc_raw(4);r[0]='n';r[1]='i';r[2]='l';r[3]=0;return r;}size_t sl=
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
static const char*
|
|
514
|
-
static const char*
|
|
515
|
-
static const char*
|
|
1286
|
+
static const char*sp_str_inspect(const char*s){if(!s){char*r=sp_str_alloc_raw(4);r[0]='n';r[1]='i';r[2]='l';r[3]=0;return r;}size_t sl=sp_str_byte_len(s);size_t cap=sl*4+3;char*r=sp_str_alloc_raw(cap);size_t o=0;r[o++]='"';for(size_t i=0;i<sl;i++){unsigned char c=(unsigned char)s[i];if(c=='\\'||c=='"'){r[o++]='\\';r[o++]=c;}else if(c=='\n'){r[o++]='\\';r[o++]='n';}else if(c=='\t'){r[o++]='\\';r[o++]='t';}else if(c=='\r'){r[o++]='\\';r[o++]='r';}else if(c<0x20||c==0x7f){snprintf(r+o,5,"\\x%02X",c);o+=4;}else{r[o++]=(char)c;}}r[o++]='"';r[o]=0;sp_str_set_len(r,o);return r;}
|
|
1287
|
+
/* Issue #791: loop to `i < l` and write the NUL terminator explicitly.
|
|
1288
|
+
The original `<= l` form worked because sp_str_alloc_raw(l+1) makes
|
|
1289
|
+
index l valid, but it's brittle if allocation changes. Issue #797
|
|
1290
|
+
adds the NULL guard. */
|
|
1291
|
+
static const char*sp_str_upcase(const char*s){if(!s)return sp_str_empty;size_t l=strlen(s);char*r=sp_str_alloc_raw(l+1);for(size_t i=0;i<l;i++)r[i]=toupper((unsigned char)s[i]);r[l]=0;return r;}
|
|
1292
|
+
static const char*sp_str_downcase(const char*s){if(!s)return sp_str_empty;size_t l=strlen(s);char*r=sp_str_alloc_raw(l+1);for(size_t i=0;i<l;i++)r[i]=tolower((unsigned char)s[i]);r[l]=0;return r;}
|
|
1293
|
+
static const char*sp_str_swapcase(const char*s){if(!s)return sp_str_empty;size_t l=strlen(s);char*r=sp_str_alloc_raw(l+1);for(size_t i=0;i<l;i++){unsigned char c=(unsigned char)s[i];if(isupper(c))r[i]=tolower(c);else if(islower(c))r[i]=toupper(c);else r[i]=s[i];}r[l]=0;return r;}
|
|
1294
|
+
/* String#undump: reverse of String#dump. The argument must be wrapped in
|
|
1295
|
+
double quotes; the escapes dump can emit (\n \t \r \f \v \a \b \e \s \0
|
|
1296
|
+
\" \\ \# \xHH \uHHHH \u{...}) are decoded back to bytes. The decoded
|
|
1297
|
+
string is never longer than the dumped form, so one buffer suffices. */
|
|
1298
|
+
static int _sp_hexval(unsigned char d){return (d<='9')?(d-'0'):(tolower(d)-'a'+10);}
|
|
1299
|
+
static const char*sp_str_undump(const char*s){
|
|
1300
|
+
if(!s)return sp_str_empty;
|
|
1301
|
+
size_t n=strlen(s);
|
|
1302
|
+
if(n<2||s[0]!='"'||s[n-1]!='"'){sp_raise_cls("RuntimeError","invalid dumped string");return sp_str_empty;}
|
|
1303
|
+
const char*p=s+1;const char*pe=s+n-1;
|
|
1304
|
+
char*out=sp_str_alloc_raw(n+1);size_t oi=0;
|
|
1305
|
+
while(p<pe){
|
|
1306
|
+
if(*p!='\\'){out[oi++]=*p++;continue;}
|
|
1307
|
+
p++;if(p>=pe)break;
|
|
1308
|
+
char c=*p++;
|
|
1309
|
+
if(c=='n')out[oi++]='\n';else if(c=='t')out[oi++]='\t';else if(c=='r')out[oi++]='\r';
|
|
1310
|
+
else if(c=='f')out[oi++]='\f';else if(c=='v')out[oi++]='\v';else if(c=='a')out[oi++]='\a';
|
|
1311
|
+
else if(c=='b')out[oi++]='\b';else if(c=='e')out[oi++]='\033';else if(c=='s')out[oi++]=' ';
|
|
1312
|
+
else if(c=='0')out[oi++]='\0';else if(c=='\\')out[oi++]='\\';else if(c=='"')out[oi++]='"';
|
|
1313
|
+
else if(c=='#')out[oi++]='#';
|
|
1314
|
+
else if(c=='x'){int v=0,k=0;while(k<2&&p<pe&&isxdigit((unsigned char)*p)){v=v*16+_sp_hexval((unsigned char)*p);p++;k++;}out[oi++]=(char)v;}
|
|
1315
|
+
else if(c=='u'){
|
|
1316
|
+
if(p<pe&&*p=='{'){p++;while(p<pe&&*p!='}'){while(p<pe&&*p==' ')p++;uint32_t cp=0;int k=0;while(k<8&&p<pe&&isxdigit((unsigned char)*p)){cp=cp*16+(uint32_t)_sp_hexval((unsigned char)*p);p++;k++;}char enc[4];int el=sp_utf8_encode(cp,enc);for(int j=0;j<el;j++)out[oi++]=enc[j];while(p<pe&&*p==' ')p++;}if(p<pe&&*p=='}')p++;}
|
|
1317
|
+
else{uint32_t cp=0;int k=0;while(k<4&&p<pe&&isxdigit((unsigned char)*p)){cp=cp*16+(uint32_t)_sp_hexval((unsigned char)*p);p++;k++;}char enc[4];int el=sp_utf8_encode(cp,enc);for(int j=0;j<el;j++)out[oi++]=enc[j];}
|
|
1318
|
+
}
|
|
1319
|
+
else out[oi++]=c;
|
|
1320
|
+
}
|
|
1321
|
+
out[oi]=0;return out;
|
|
1322
|
+
}
|
|
1323
|
+
/* Issue #797: NULL guards on receiver + needle for the chunk of
|
|
1324
|
+
string functions that read directly into a non-checked strlen. */
|
|
1325
|
+
static const char*sp_str_delete_prefix(const char*s,const char*p){if(!s)return sp_str_empty;if(!p)return s;size_t sl=strlen(s),pl=strlen(p);if(pl<=sl&&memcmp(s,p,pl)==0){char*r=sp_str_alloc_raw(sl-pl+1);memcpy(r,s+pl,sl-pl+1);return r;}char*r=sp_str_alloc_raw(sl+1);memcpy(r,s,sl+1);return r;}
|
|
1326
|
+
/* Issue #758: NULL guard + bound the start so a negative result from
|
|
1327
|
+
sp_str_index doesn't underflow the source pointer. */
|
|
1328
|
+
static const char*sp_str_substr(const char*s,mrb_int start,mrb_int len){if(!s||len<=0){char*r=sp_str_alloc_raw(1);r[0]=0;return r;}if(start<0)start=0;char*r=sp_str_alloc_raw(len+1);memcpy(r,s+start,len);r[len]=0;return r;}
|
|
1329
|
+
static const char*sp_str_delete_suffix(const char*s,const char*p){if(!s)return sp_str_empty;if(!p)return s;size_t sl=strlen(s),pl=strlen(p);if(pl<=sl&&memcmp(s+sl-pl,p,pl)==0){char*r=sp_str_alloc_raw(sl-pl+1);memcpy(r,s,sl-pl);r[sl-pl]=0;return r;}char*r=sp_str_alloc_raw(sl+1);memcpy(r,s,sl+1);return r;}
|
|
1330
|
+
static const char*sp_str_succ(const char*s){if(!s)return sp_str_empty;size_t l=strlen(s);if(l==0){char*r=sp_str_alloc_raw(1);r[0]=0;return r;}/* Find start of last codepoint */size_t lc=l-1;while(lc>0&&((unsigned char)s[lc]&0xC0)==0x80)lc--;if((unsigned char)s[lc]>=0x80){/* Multibyte tail: increment its codepoint */uint32_t cp;sp_utf8_decode(s+lc,&cp);cp++;char enc[4];int el=sp_utf8_encode(cp,enc);char*r=sp_str_alloc_raw(lc+el+1);memcpy(r,s,lc);memcpy(r+lc,enc,el);r[lc+el]=0;return r;}/* ASCII tail: existing carry logic */char*r=sp_str_alloc_raw(l+2);memcpy(r,s,l+1);mrb_int i=(mrb_int)l-1;while(i>=0){unsigned char c=(unsigned char)r[i];if(c>='0'&&c<'9'){r[i]=c+1;return r;}if(c=='9'){r[i]='0';i--;continue;}if(c>='a'&&c<'z'){r[i]=c+1;return r;}if(c=='z'){r[i]='a';i--;continue;}if(c>='A'&&c<'Z'){r[i]=c+1;return r;}if(c=='Z'){r[i]='A';i--;continue;}r[i]=c+1;return r;}memmove(r+1,r,l+1);if(r[1]=='0')r[0]='1';else if(r[1]=='a')r[0]='a';else if(r[1]=='A')r[0]='A';else r[0]=r[1];return r;}
|
|
516
1331
|
static const char*sp_gets(void){char buf[4096];if(!fgets(buf,sizeof(buf),stdin))return NULL;size_t l=strlen(buf);char*r=sp_str_alloc_raw(l+1);memcpy(r,buf,l+1);return r;}
|
|
517
1332
|
static sp_StrArray*sp_readlines(void){sp_StrArray*a=sp_StrArray_new();char buf[4096];while(fgets(buf,sizeof(buf),stdin)){size_t l=strlen(buf);char*r=sp_str_alloc_raw(l+1);memcpy(r,buf,l+1);sp_StrArray_push(a,r);}return a;}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
1333
|
+
/* strip / lstrip / rstrip. CRuby strips the set "\0\t\n\v\f\r " from the
|
|
1334
|
+
ends -- i.e. isspace() plus the NUL byte. Use sp_str_byte_len (not
|
|
1335
|
+
strlen) so a heap string carrying an embedded NUL (e.g. from pack /
|
|
1336
|
+
concat) is measured and stripped correctly; the result is a
|
|
1337
|
+
length-tracked heap string so any interior NUL survives. (A frozen
|
|
1338
|
+
literal with an embedded NUL is still truncated at the C level -- that
|
|
1339
|
+
needs length-tracked literals, out of scope.) */
|
|
1340
|
+
static const char*sp_str_strip(const char*s){if(!s)return sp_str_empty;size_t len=sp_str_byte_len(s);size_t a=0;while(a<len&&(isspace((unsigned char)s[a])||s[a]=='\0'))a++;size_t b=len;while(b>a&&(isspace((unsigned char)s[b-1])||s[b-1]=='\0'))b--;size_t n=b-a;char*r=sp_str_alloc(n);memcpy(r,s+a,n);r[n]=0;return r;}
|
|
1341
|
+
static const char*sp_str_chomp(const char*s){if(!s)return sp_str_empty;size_t l=strlen(s);if(l>=2&&s[l-2]=='\r'&&s[l-1]=='\n')l-=2;else if(l>0&&s[l-1]=='\n')l--;else if(l>0&&s[l-1]=='\r')l--;char*r=sp_str_alloc_raw(l+1);memcpy(r,s,l);r[l]=0;return r;}
|
|
1342
|
+
|
|
1343
|
+
/* Issue #881: `"hello!".chomp("!")` strips the explicit separator.
|
|
1344
|
+
Empty sep strips any trailing newlines (CRuby paragraph mode).
|
|
1345
|
+
NULL sep is caller's responsibility (codegen routes nil to a
|
|
1346
|
+
no-op before calling). */
|
|
1347
|
+
static const char *sp_str_chomp_sep(const char *s, const char *sep) {
|
|
1348
|
+
if (!s) return sp_str_empty;
|
|
1349
|
+
size_t l = strlen(s);
|
|
1350
|
+
if (!sep || !*sep) {
|
|
1351
|
+
/* Empty sep = paragraph mode: strip trailing \r\n pairs and
|
|
1352
|
+
standalone \n's, but NOT standalone \r's. A trailing \r that
|
|
1353
|
+
is not part of a \r\n pair stops the stripping. */
|
|
1354
|
+
while (l > 0) {
|
|
1355
|
+
if (l >= 2 && s[l-2] == '\r' && s[l-1] == '\n') { l -= 2; continue; }
|
|
1356
|
+
if (s[l-1] == '\n') { l--; continue; }
|
|
1357
|
+
break;
|
|
1358
|
+
}
|
|
1359
|
+
} else {
|
|
1360
|
+
size_t sl = strlen(sep);
|
|
1361
|
+
if (sl <= l && memcmp(s + l - sl, sep, sl) == 0) l -= sl;
|
|
1362
|
+
}
|
|
1363
|
+
char *r = sp_str_alloc_raw(l + 1);
|
|
1364
|
+
memcpy(r, s, l);
|
|
1365
|
+
r[l] = 0;
|
|
1366
|
+
return r;
|
|
1367
|
+
}
|
|
1368
|
+
static const char*sp_str_chop(const char*s){if(!s)return sp_str_empty;size_t l=strlen(s);if(l>0){if(l>=2&&s[l-2]=='\r'&&s[l-1]=='\n')l-=2;else l--;}char*r=sp_str_alloc_raw(l+1);memcpy(r,s,l);r[l]=0;return r;}
|
|
1369
|
+
static mrb_bool sp_str_include(const char*s,const char*sub){if(!sub)sp_raise_cls("TypeError","no implicit conversion of nil into String");if(!s)return FALSE;return strstr(s,sub)!=NULL;}
|
|
1370
|
+
static mrb_bool sp_str_start_with(const char*s,const char*p){if(!p)sp_raise_cls("TypeError","no implicit conversion of nil into String");if(!s)return FALSE;return strncmp(s,p,strlen(p))==0;}
|
|
1371
|
+
static mrb_bool sp_str_end_with(const char*s,const char*suf){if(!suf)sp_raise_cls("TypeError","no implicit conversion of nil into String");if(!s)return FALSE;size_t ls=strlen(s),lsuf=strlen(suf);if(lsuf>ls)return FALSE;return strcmp(s+ls-lsuf,suf)==0;}
|
|
1372
|
+
static sp_StrArray*sp_str_split(const char*s,const char*sep){
|
|
1373
|
+
SP_GC_ROOT(s);
|
|
1374
|
+
SP_GC_ROOT(sep);
|
|
1375
|
+
sp_StrArray*a=sp_StrArray_new();
|
|
1376
|
+
sp_str_split_into(a,s,sep);
|
|
1377
|
+
return a;
|
|
1378
|
+
}
|
|
1379
|
+
/* Same as sp_str_split but removes trailing empty strings
|
|
1380
|
+
(CRuby default limit behavior: split without limit drops
|
|
1381
|
+
trailing empties; split(sep, -1) keeps them). */
|
|
1382
|
+
static sp_StrArray*sp_str_split_drop_trailing(const char*s,const char*sep){sp_StrArray*a=sp_str_split(s,sep);while(a->len>0&&a->data[a->len-1][0]==0)a->len--;return a;}
|
|
1383
|
+
/* `s.split(sep, n)` with explicit limit. Positive n caps the result
|
|
1384
|
+
at n elements: the last element holds the unsplit remainder.
|
|
1385
|
+
n == 0 means "no limit" and drops trailing empty strings (same as
|
|
1386
|
+
the no-arg default); n < 0 means "no limit" but keeps trailing
|
|
1387
|
+
empties. Empty separator works the same as the no-limit path --
|
|
1388
|
+
splits into Unicode characters; the limit caps the array.
|
|
1389
|
+
Issue #619 puzzle 2. */
|
|
1390
|
+
static sp_StrArray*sp_str_split_limit(const char*s,const char*sep,mrb_int n){
|
|
1391
|
+
if(n==0)return sp_str_split_drop_trailing(s,sep);
|
|
1392
|
+
if(n<0)return sp_str_split(s,sep);
|
|
1393
|
+
SP_GC_ROOT(s);
|
|
1394
|
+
SP_GC_ROOT(sep);
|
|
1395
|
+
sp_StrArray*a=sp_StrArray_new();
|
|
1396
|
+
SP_GC_ROOT(a);
|
|
1397
|
+
if(*s==0)return a;
|
|
1398
|
+
size_t sl=strlen(sep);
|
|
1399
|
+
if(sl==0){
|
|
1400
|
+
const char*p=s;
|
|
1401
|
+
mrb_int k=0;
|
|
1402
|
+
while(*p&&k<n-1){
|
|
1403
|
+
int cn=sp_utf8_advance(p);
|
|
1404
|
+
sp_str_split_push(a,p,(size_t)cn);
|
|
1405
|
+
p+=cn;
|
|
1406
|
+
k++;
|
|
1407
|
+
}
|
|
1408
|
+
if(*p){
|
|
1409
|
+
sp_str_split_push(a,p,strlen(p));
|
|
1410
|
+
}
|
|
1411
|
+
return a;
|
|
1412
|
+
}
|
|
1413
|
+
const char*p=s;
|
|
1414
|
+
mrb_int k=0;
|
|
1415
|
+
while(k<n-1){
|
|
1416
|
+
const char*f=strstr(p,sep);
|
|
1417
|
+
if(!f)break;
|
|
1418
|
+
size_t m=f-p;
|
|
1419
|
+
sp_str_split_push(a,p,m);
|
|
1420
|
+
p=f+sl;
|
|
1421
|
+
k++;
|
|
1422
|
+
}
|
|
1423
|
+
sp_str_split_push(a,p,strlen(p));
|
|
1424
|
+
return a;
|
|
1425
|
+
}
|
|
1426
|
+
/* `s.split` / `s.split(nil)` -- whitespace mode: split on runs of
|
|
1427
|
+
ASCII whitespace, skip leading whitespace. Issue #507: the no-arg
|
|
1428
|
+
form previously emitted `sp_str_split(s, 0)` and segfaulted at
|
|
1429
|
+
strlen(NULL). */
|
|
1430
|
+
static sp_StrArray*sp_str_split_ws(const char*s){
|
|
1431
|
+
SP_GC_ROOT(s);
|
|
1432
|
+
sp_StrArray*a=sp_StrArray_new();
|
|
1433
|
+
SP_GC_ROOT(a);
|
|
1434
|
+
const char*p=s;
|
|
1435
|
+
while(*p==' '||*p=='\t'||*p=='\n'||*p=='\r'||*p=='\f'||*p=='\v')p++;
|
|
1436
|
+
while(*p){
|
|
1437
|
+
const char*start=p;
|
|
1438
|
+
while(*p&&!(*p==' '||*p=='\t'||*p=='\n'||*p=='\r'||*p=='\f'||*p=='\v'))p++;
|
|
1439
|
+
size_t n=p-start;
|
|
1440
|
+
sp_str_split_push(a,start,n);
|
|
1441
|
+
while(*p==' '||*p=='\t'||*p=='\n'||*p=='\r'||*p=='\f'||*p=='\v')p++;
|
|
1442
|
+
}
|
|
1443
|
+
return a;
|
|
1444
|
+
}
|
|
525
1445
|
/* String#lines: split on \n but PRESERVE the trailing newline on each
|
|
526
1446
|
line (CRuby semantics). The last line keeps its terminator if present;
|
|
527
1447
|
if absent, it just stops there. Empty string returns an empty array.
|
|
528
1448
|
`end` is computed once at entry so a string with no newlines avoids
|
|
529
1449
|
a redundant strlen call on the trailing piece. */
|
|
530
1450
|
static sp_StrArray*sp_str_lines(const char*s){sp_StrArray*a=sp_StrArray_new();if(*s==0)return a;const char*end=s+strlen(s);const char*p=s;while(p<end){const char*nl=strchr(p,'\n');size_t n=nl?(size_t)(nl-p+1):(size_t)(end-p);char*r=sp_str_alloc_raw(n+1);memcpy(r,p,n);r[n]=0;sp_StrArray_push(a,r);if(!nl)break;p=nl+1;}return a;}
|
|
531
|
-
static
|
|
1451
|
+
static sp_StrArray*sp_str_lines_chomp(const char*s){sp_StrArray*a=sp_StrArray_new();if(*s==0)return a;const char*end=s+strlen(s);const char*p=s;while(p<end){const char*nl=strchr(p,'\n');size_t n=nl?(size_t)(nl-p):(size_t)(end-p);if(nl&&nl>s&&nl[-1]=='\r')n--;char*r=sp_str_alloc_raw(n+1);memcpy(r,p,n);r[n]=0;sp_StrArray_push(a,r);if(!nl)break;p=nl+1;}return a;}
|
|
1452
|
+
/* Issue #827: gsub previously returned a raw malloc buffer. The GC's
|
|
1453
|
+
sp_mark_string writes byte[-1] = 0xfc, which on a raw malloc buffer
|
|
1454
|
+
clobbers malloc metadata. Build into a scratch buffer, then copy
|
|
1455
|
+
into a sp_str_alloc'd buffer that has the GC marker byte. */
|
|
1456
|
+
/* Issue #850: empty pattern inserts the replacement between every
|
|
1457
|
+
character (and at the start). CRuby walks codepoint-by-codepoint;
|
|
1458
|
+
spinel's gsub is byte-oriented so we mirror that with UTF-8 stride
|
|
1459
|
+
to keep multi-byte characters intact. */
|
|
1460
|
+
static const char*sp_str_gsub(const char*s,const char*pat,const char*rep){
|
|
1461
|
+
if(!s)return sp_str_empty;
|
|
1462
|
+
if(!pat||!rep)return s;
|
|
1463
|
+
size_t pl=strlen(pat),rl=strlen(rep),sl=strlen(s);
|
|
1464
|
+
if(pl==0){
|
|
1465
|
+
/* Empty pattern: insert rep between every codepoint + at start/end.
|
|
1466
|
+
Result size: (chars+1) * rl + sl. */
|
|
1467
|
+
size_t cap=sl+rl*(sl+1)+1;
|
|
1468
|
+
char*out=(char*)malloc(cap);
|
|
1469
|
+
size_t ol=0;
|
|
1470
|
+
memcpy(out+ol,rep,rl); ol+=rl;
|
|
1471
|
+
const char*p=s;
|
|
1472
|
+
while(*p){
|
|
1473
|
+
int n=sp_utf8_advance(p);
|
|
1474
|
+
memcpy(out+ol,p,n); ol+=n;
|
|
1475
|
+
memcpy(out+ol,rep,rl); ol+=rl;
|
|
1476
|
+
p+=n;
|
|
1477
|
+
}
|
|
1478
|
+
out[ol]=0;
|
|
1479
|
+
char*r=sp_str_alloc(ol); memcpy(r,out,ol+1); free(out); return r;
|
|
1480
|
+
}
|
|
1481
|
+
size_t cap=sl*2+1;
|
|
1482
|
+
char*out=(char*)malloc(cap);
|
|
1483
|
+
size_t ol=0;
|
|
1484
|
+
const char*p=s;
|
|
1485
|
+
while(*p){
|
|
1486
|
+
const char*f=strstr(p,pat);
|
|
1487
|
+
if(!f){size_t n=strlen(p);if(ol+n>=cap){cap=(ol+n)*2+1;out=(char*)realloc(out,cap);}memcpy(out+ol,p,n);ol+=n;break;}
|
|
1488
|
+
size_t n=f-p;
|
|
1489
|
+
if(ol+n+rl>=cap){cap=(ol+n+rl)*2+1;out=(char*)realloc(out,cap);}
|
|
1490
|
+
memcpy(out+ol,p,n);ol+=n;
|
|
1491
|
+
memcpy(out+ol,rep,rl);ol+=rl;
|
|
1492
|
+
p=f+pl;
|
|
1493
|
+
}
|
|
1494
|
+
out[ol]=0;char*r=sp_str_alloc(ol);memcpy(r,out,ol+1);free(out);return r;
|
|
1495
|
+
}
|
|
532
1496
|
/* Returns a *character* offset (codepoint index), not a byte offset. */
|
|
533
|
-
|
|
534
|
-
|
|
1497
|
+
/* Issue #759: NULL sub to strstr is UB. Issue #847: nil sub raises
|
|
1498
|
+
TypeError per MRI rather than silently returning -1 (would conflate
|
|
1499
|
+
"nil arg" with "not found"). Self NULL stays -1 -- shouldn't happen
|
|
1500
|
+
but keep defensive. */
|
|
1501
|
+
static mrb_int sp_str_index(const char*s,const char*sub){if(!s)return -1;if(!sub)sp_raise_cls("TypeError","no implicit conversion of nil into String");const char*f=strstr(s,sub);if(!f)return -1;mrb_int n=0;const char*p=s;while(p<f){p+=sp_utf8_advance(p);n++;}return n;}
|
|
1502
|
+
/* `s.index(sub, start)` -- search starts at codepoint index `start`.
|
|
1503
|
+
Negative start counts back from the end of the string. Returns the
|
|
1504
|
+
absolute codepoint offset of the match (not relative to start), or
|
|
1505
|
+
-1 when no match exists at or after start. Out-of-range starts
|
|
1506
|
+
(start > length) return -1, matching CRuby. The codepoint counter
|
|
1507
|
+
begins at `start` and walks from `s + boff`, so the "walk all
|
|
1508
|
+
matches" loop is O(N) total rather than O(N^2). */
|
|
1509
|
+
static mrb_int sp_str_index_from(const char*s,const char*sub,mrb_int start){mrb_int cl=sp_str_length(s);if(start<0)start+=cl;if(start<0)start=0;if(start>cl)return -1;size_t boff=sp_utf8_byte_offset(s,start);const char*f=strstr(s+boff,sub);if(!f)return -1;mrb_int n=start;const char*p=s+boff;while(p<f){p+=sp_utf8_advance(p);n++;}return n;}
|
|
1510
|
+
static mrb_int sp_str_rindex(const char*s,const char*sub){if(!sub)sp_raise_cls("TypeError","no implicit conversion of nil into String");size_t sl=strlen(sub);if(sl==0)return sp_str_length(s);const char*last=NULL;const char*p=s;while((p=strstr(p,sub))){last=p;p++;}if(!last)return -1;mrb_int n=0;const char*q=s;while(q<last){q+=sp_utf8_advance(q);n++;}return n;}
|
|
1511
|
+
/* `s.rindex(sub, pos)` — find the rightmost occurrence of sub at or
|
|
1512
|
+
before codepoint index pos. Walks all matches and keeps the last
|
|
1513
|
+
one whose codepoint index <= pos. */
|
|
1514
|
+
static mrb_int sp_str_rindex_from(const char*s,const char*sub,mrb_int pos){if(!s)return SP_INT_NIL;if(!sub)sp_raise_cls("TypeError","no implicit conversion of nil into String");mrb_int cl=sp_str_length(s);if(pos<0)pos=cl+pos;if(pos<0)return SP_INT_NIL;size_t sl=strlen(sub);if(sl==0){if(pos>=cl)return cl;return pos;}const char*p=s;mrb_int best=-1;const char*r=s;mrb_int cur_n=0;while((p=strstr(p,sub))!=NULL){while(r<p){r+=sp_utf8_advance(r);cur_n++;}if(cur_n>pos)break;best=cur_n;p++;}return best<0?SP_INT_NIL:best;}
|
|
1515
|
+
|
|
535
1516
|
static char sp_char_cache[256][3];
|
|
536
1517
|
static int sp_char_cache_init = 0;
|
|
537
1518
|
/* start/len are codepoint indices/counts. */
|
|
538
|
-
static const char*sp_str_sub_range(const char*s,mrb_int start,mrb_int len){mrb_int cl=sp_str_length(s);if(start<0)start+=cl;if(start<0)start=0;if(start>=cl||len<=0){return &("\xff" "")[1];}if(start+len>cl)len=cl-start;size_t boff=sp_utf8_byte_offset(s,start);size_t
|
|
1519
|
+
static const char*sp_str_sub_range(const char*s,mrb_int start,mrb_int len){mrb_int cl=sp_str_length(s);if(start<0)start+=cl;if(start<0)start=0;if(start>=cl||len<=0){return &("\xff" "")[1];}if(start+len>cl)len=cl-start;size_t boff=sp_utf8_byte_offset(s,start);size_t blen_total=sp_str_byte_len(s);size_t bp=boff;mrb_int rem=len;while(rem>0&&bp<blen_total){bp+=sp_utf8_advance(s+bp);rem--;}if(bp>blen_total)bp=blen_total;size_t bend=bp;size_t blen=bend-boff;if(len==1&&blen==1){unsigned char c=(unsigned char)s[boff];if(c!=0){if(!sp_char_cache_init){for(int i=0;i<256;i++){sp_char_cache[i][0]=(char)0xff;sp_char_cache[i][1]=(char)i;sp_char_cache[i][2]=0;}sp_char_cache_init=1;}return &sp_char_cache[c][1];}}char*r=sp_str_alloc_raw(blen+1);memcpy(r,s+boff,blen);r[blen]=0;return r;}
|
|
1520
|
+
/* Single-character form of `s[i]`. Returns NULL on out-of-bounds to
|
|
1521
|
+
match CRuby's `"hello"[20] -> nil`. The two-arg `s[i, len]` /
|
|
1522
|
+
range forms keep returning "" on OOB via sp_str_sub_range; only
|
|
1523
|
+
the bare single-int index aliases here. Issue #619 puzzle 3. */
|
|
1524
|
+
static const char*sp_str_char_at_or_nil(const char*s,mrb_int i){mrb_int cl=sp_str_length(s);if(i<0)i+=cl;if(i<0||i>=cl)return NULL;return sp_str_sub_range(s,i,1);}
|
|
1525
|
+
/* String#byteslice(start,len): byte-indexed (unlike the char-indexed
|
|
1526
|
+
sp_str_sub_range). Negative start counts from the byte length. Out of
|
|
1527
|
+
range yields the empty string, matching slice's "" rather than CRuby nil. */
|
|
1528
|
+
static const char*sp_str_byteslice(const char*s,mrb_int start,mrb_int len){mrb_int bl=(mrb_int)sp_str_byte_len(s);if(start<0)start+=bl;if(start<0||start>bl||len<0){return &("\xff" "")[1];}if(start+len>bl)len=bl-start;if(len<=0){return &("\xff" "")[1];}char*r=sp_str_alloc_raw(len+1);memcpy(r,s+start,len);r[len]=0;return r;}
|
|
1529
|
+
/* String#ascii_only?: 1 iff every byte is in the 7-bit ASCII range. */
|
|
1530
|
+
static int sp_str_ascii_only(const char*s){mrb_int bl=(mrb_int)sp_str_byte_len(s);for(mrb_int i=0;i<bl;i++){if((unsigned char)s[i]>=0x80)return 0;}return 1;}
|
|
539
1531
|
/* Char-indexed variant; the second arg used to be a hoisted byte length, now a
|
|
540
1532
|
hoisted codepoint count. We don't need it for correctness, but keeping the
|
|
541
1533
|
ABI lets callers pass it without a wrapper. */
|
|
542
|
-
static const char*sp_str_sub_range_len(const char*s,mrb_int cl,mrb_int start,mrb_int len){if(start<0)start+=cl;if(start<0)start=0;if(start>=cl||len<=0){return &("\xff" "")[1];}if(start+len>cl)len=cl-start;size_t boff=sp_utf8_byte_offset(s,start);size_t
|
|
543
|
-
|
|
1534
|
+
static const char*sp_str_sub_range_len(const char*s,mrb_int cl,mrb_int start,mrb_int len){if(start<0)start+=cl;if(start<0)start=0;if(start>=cl||len<=0){return &("\xff" "")[1];}if(start+len>cl)len=cl-start;size_t boff=sp_utf8_byte_offset(s,start);size_t blen_total=sp_str_byte_len(s);size_t bp=boff;mrb_int rem=len;while(rem>0&&bp<blen_total){bp+=sp_utf8_advance(s+bp);rem--;}if(bp>blen_total)bp=blen_total;size_t bend=bp;size_t blen=bend-boff;if(len==1&&blen==1){unsigned char c=(unsigned char)s[boff];if(c!=0){if(!sp_char_cache_init){for(int i=0;i<256;i++){sp_char_cache[i][0]=(char)0xff;sp_char_cache[i][1]=(char)i;sp_char_cache[i][2]=0;}sp_char_cache_init=1;}return &sp_char_cache[c][1];}}char*r=sp_str_alloc_raw(blen+1);memcpy(r,s+boff,blen);r[blen]=0;return r;}
|
|
1535
|
+
/* String s[start..end] / s[start...end] with possibly negative
|
|
1536
|
+
endpoints. Mirrors sp_IntArray_slice_range; issue #496. */
|
|
1537
|
+
static const char*sp_str_sub_range_r(const char*s,mrb_int start,mrb_int end_,mrb_int excl){mrb_int cl=sp_str_length(s);if(end_<0)end_+=cl;if(start<0)start+=cl;mrb_int n=end_-start+(excl?0:1);if(n<0||start<0)n=0;return sp_str_sub_range_len(s,cl,start,n);}
|
|
1538
|
+
static const char*sp_str_sub_range_len_r(const char*s,mrb_int cl,mrb_int start,mrb_int end_,mrb_int excl){if(end_<0)end_+=cl;if(start<0)start+=cl;mrb_int n=end_-start+(excl?0:1);if(n<0||start<0)n=0;return sp_str_sub_range_len(s,cl,start,n);}
|
|
1539
|
+
const char*sp_sprintf(const char*fmt,...){char _sp_tmp[4096];va_list ap;va_start(ap,fmt);int _sp_n=vsnprintf(_sp_tmp,sizeof(_sp_tmp),fmt,ap);va_end(ap);if(_sp_n<0)_sp_n=0;char*b=sp_str_alloc((size_t)_sp_n);if(_sp_n<(int)sizeof(_sp_tmp)){memcpy(b,_sp_tmp,(size_t)_sp_n);}else{/* result didn't fit the stack temp; re-render at full width (sp_str_alloc gives _sp_n bytes + NUL) so long string interpolations aren't truncated. re-arm the va_list rather than va_copy so the common fast path pays nothing */va_start(ap,fmt);vsnprintf(b,(size_t)_sp_n+1,fmt,ap);va_end(ap);}return b;}
|
|
544
1540
|
/* Use a temp pointer for realloc so the original buffer is not leaked
|
|
545
1541
|
on allocation failure. Match the perror+exit pattern used elsewhere
|
|
546
1542
|
(see sp_IntArray_replace) instead of returning a partial result. */
|
|
547
1543
|
static const char*sp_str_format_strarr(const char*fmt,sp_StrArray*a){size_t cap=strlen(fmt)+64;char*buf=(char*)malloc(cap);if(!buf){perror("malloc");exit(1);}size_t out=0;mrb_int idx=0;const char*p=fmt;while(*p){if(*p=='%'){if(p[1]=='s'){const char*s=(idx<a->len)?a->data[idx]:"";size_t sl=strlen(s);if(out+sl>=cap){size_t nc=(out+sl)*2+1;char*nb=(char*)realloc(buf,nc);if(!nb){free(buf);perror("realloc");exit(1);}buf=nb;cap=nc;}memcpy(buf+out,s,sl);out+=sl;idx++;p+=2;}else if(p[1]=='%'){if(out+1>=cap){size_t nc=cap*2;char*nb=(char*)realloc(buf,nc);if(!nb){free(buf);perror("realloc");exit(1);}buf=nb;cap=nc;}buf[out++]='%';p+=2;}else{if(out+1>=cap){size_t nc=cap*2;char*nb=(char*)realloc(buf,nc);if(!nb){free(buf);perror("realloc");exit(1);}buf=nb;cap=nc;}buf[out++]=*p++;}}else{if(out+1>=cap){size_t nc=cap*2;char*nb=(char*)realloc(buf,nc);if(!nb){free(buf);perror("realloc");exit(1);}buf=nb;cap=nc;}buf[out++]=*p++;}}buf[out]=0;char*r=sp_str_alloc(out);memcpy(r,buf,out);free(buf);return r;}
|
|
548
|
-
|
|
549
|
-
static const char*
|
|
550
|
-
static const char*
|
|
551
|
-
static
|
|
552
|
-
static
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
static const char*
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
1544
|
+
|
|
1545
|
+
static const char*sp_str_reverse(const char*s){if(!s)return sp_str_empty;size_t bl=strlen(s);char*r=sp_str_alloc_raw(bl+1);size_t end=bl;const char*p=s;while(*p){int cn=sp_utf8_advance(p);end-=cn;memcpy(r+end,p,cn);p+=cn;}r[bl]=0;return r;}
|
|
1546
|
+
static const char*sp_str_sub(const char*s,const char*pat,const char*rep){if(!s)return sp_str_empty;if(!pat||!rep)return s;const char*f=strstr(s,pat);if(!f)return s;size_t pl=strlen(pat),rl=strlen(rep),sl=strlen(s);char*r=sp_str_alloc_raw(sl-pl+rl+1);size_t n=f-s;memcpy(r,s,n);memcpy(r+n,rep,rl);memcpy(r+n+rl,f+pl,sl-n-pl+1);return r;}
|
|
1547
|
+
static const char*sp_str_capitalize(const char*s){if(!s)return sp_str_empty;size_t l=strlen(s);char*r=sp_str_alloc_raw(l+1);for(size_t i=0;i<=l;i++)r[i]=tolower((unsigned char)s[i]);if(l>0)r[0]=toupper((unsigned char)r[0]);return r;}
|
|
1548
|
+
static mrb_int sp_str_count(const char*s,const char*chars){if(!chars)sp_raise_cls("TypeError","no implicit conversion of nil into String");int negate=0;const char*csp=chars;if(*csp=='^'&&*(csp+1)){negate=1;csp++;}size_t setn;uint32_t*set=sp_utf8_decode_charset(csp,&setn);mrb_int c=0;const char*p=s;while(*p){uint32_t cp;p+=sp_utf8_decode(p,&cp);int in_set=sp_utf8_set_has(set,setn,cp);if(negate)in_set=!in_set;if(in_set)c++;}free(set);return c;}
|
|
1549
|
+
/* String#count with multiple args: intersection of charsets.
|
|
1550
|
+
Each additional arg further restricts which chars to count.
|
|
1551
|
+
sp_str_count_n(s, args[], n) computes the intersection. */
|
|
1552
|
+
static mrb_int sp_str_count_n(const char*s,const char**chars,mrb_int n){if(n<=0)return 0;size_t*setns=(size_t*)malloc(n*sizeof(size_t));uint32_t**sets=(uint32_t**)malloc(n*sizeof(uint32_t*));int*negs=(int*)malloc(n*sizeof(int));for(mrb_int i=0;i<n;i++){if(!chars[i])sp_raise_cls("TypeError","no implicit conversion of nil into String");const char*cs=chars[i];negs[i]=0;if(*cs=='^'&&*(cs+1)){negs[i]=1;cs++;}sets[i]=sp_utf8_decode_charset(cs,&setns[i]);}mrb_int c=0;const char*p=s;while(*p){uint32_t cp;p+=sp_utf8_decode(p,&cp);int all=1;for(mrb_int i=0;i<n;i++){int in_set=sp_utf8_set_has(sets[i],setns[i],cp);if(negs[i])in_set=!in_set;if(!in_set){all=0;break;}}if(all)c++;}for(mrb_int i=0;i<n;i++)free(sets[i]);free(sets);free(setns);free(negs);return c;}
|
|
1553
|
+
/* Issue #800: clamp l*n so a malicious input can't allocate a tiny
|
|
1554
|
+
buffer through size_t overflow. */
|
|
1555
|
+
/* Issue #836: bound the multiplier so a wildly oversized request
|
|
1556
|
+
raises ArgumentError rather than segfaulting when malloc returns
|
|
1557
|
+
NULL and memcpy walks it. 1 GiB cap covers realistic use. */
|
|
1558
|
+
static const char*sp_str_repeat(const char*s,mrb_int n){
|
|
1559
|
+
if(n<0) sp_raise_cls("ArgumentError","negative argument");
|
|
1560
|
+
if(!s||n<=0)return sp_str_empty;
|
|
1561
|
+
size_t l=strlen(s);
|
|
1562
|
+
if(l==0) return sp_str_empty;
|
|
1563
|
+
if((size_t)n>SIZE_MAX/l) sp_raise_cls("ArgumentError","string size too big");
|
|
1564
|
+
size_t total=(size_t)n*l;
|
|
1565
|
+
if(total>(size_t)(1u<<30)) sp_raise_cls("ArgumentError","string size too big");
|
|
1566
|
+
char*r=sp_str_alloc_raw(total+1);
|
|
1567
|
+
for(mrb_int i=0;i<n;i++)memcpy(r+l*i,s,l);
|
|
1568
|
+
r[total]=0;
|
|
1569
|
+
return r;
|
|
1570
|
+
}
|
|
1571
|
+
static sp_IntArray*sp_str_bytes(const char*s){sp_IntArray*a=sp_IntArray_new();if(!s)return a;size_t n=sp_str_byte_len(s);for(size_t i=0;i<n;i++)sp_IntArray_push(a,(mrb_int)(unsigned char)s[i]);return a;}
|
|
1572
|
+
/* Issue #903: String#codepoints -- one IntArray entry per UTF-8
|
|
1573
|
+
codepoint (not byte). Replacement-character behaviour mirrors
|
|
1574
|
+
sp_utf8_decode (returns the leading byte for malformed seqs). */
|
|
1575
|
+
static sp_IntArray*sp_str_codepoints(const char*s){sp_IntArray*a=sp_IntArray_new();if(!s)return a;const char*p=s;while(*p){uint32_t cp;int n=sp_utf8_decode(p,&cp);sp_IntArray_push(a,(mrb_int)cp);p+=n;}return a;}
|
|
1576
|
+
/* Issue #798: guard NULL inputs (CRuby treats nil/no-op gracefully). */
|
|
1577
|
+
static const char*sp_str_tr(const char*s,const char*from,const char*to){if(!s)return sp_str_empty;if(!from||!to)return s;int negate=0;const char*fp=from;if(*fp=='^'&&*(fp+1)){negate=1;fp++;}size_t fn,tn;uint32_t*fcps=sp_utf8_decode_charset(fp,&fn);uint32_t*tcps=sp_utf8_decode_charset(to,&tn);size_t bl=strlen(s);size_t cap=bl*4+1;char*buf=(char*)malloc(cap);size_t n=0;const char*p=s;while(*p){uint32_t cp;int cn=sp_utf8_decode(p,&cp);size_t mi=fn;for(size_t j=0;j<fn;j++)if(fcps[j]==cp){mi=j;break;}int in_set=(mi<fn);if(negate)in_set=!in_set;if(in_set&&tn>0){uint32_t rep=negate?tcps[tn-1]:(mi<tn?tcps[mi]:tcps[tn-1]);n+=sp_utf8_encode(rep,buf+n);}else if(in_set){}else{memcpy(buf+n,p,cn);n+=cn;}p+=cn;}buf[n]=0;char*r=sp_str_alloc(n);memcpy(r,buf,n+1);free(buf);free(fcps);free(tcps);return r;}
|
|
1578
|
+
/* Issue #902: String#tr_s -- translate AND squeeze consecutive
|
|
1579
|
+
identical results into one. Walks codepoint-by-codepoint and
|
|
1580
|
+
collapses adjacent duplicates only among the translated bytes
|
|
1581
|
+
(untranslated runs keep their original characters). */
|
|
1582
|
+
static const char*sp_str_tr_s(const char*s,const char*from,const char*to){
|
|
1583
|
+
if(!s)return sp_str_empty;
|
|
1584
|
+
if(!from||!to)return s;
|
|
1585
|
+
int negate=0;const char*fp=from;
|
|
1586
|
+
if(*fp=='^'&&*(fp+1)){negate=1;fp++;}
|
|
1587
|
+
size_t fn,tn;
|
|
1588
|
+
uint32_t*fcps=sp_utf8_decode_charset(fp,&fn);
|
|
1589
|
+
uint32_t*tcps=sp_utf8_decode_charset(to,&tn);
|
|
1590
|
+
size_t bl=strlen(s);
|
|
1591
|
+
size_t cap=bl*4+1;
|
|
1592
|
+
char*buf=(char*)malloc(cap);
|
|
1593
|
+
size_t n=0;
|
|
1594
|
+
const char*p=s;
|
|
1595
|
+
uint32_t last_emit=0; int has_last=0; int last_was_translated=0;
|
|
1596
|
+
while(*p){
|
|
1597
|
+
uint32_t cp; int cn=sp_utf8_decode(p,&cp);
|
|
1598
|
+
size_t mi=fn;
|
|
1599
|
+
for(size_t j=0;j<fn;j++)if(fcps[j]==cp){mi=j;break;}
|
|
1600
|
+
int in_set=(mi<fn);
|
|
1601
|
+
if(negate)in_set=!in_set;
|
|
1602
|
+
uint32_t emit_cp;
|
|
1603
|
+
int translated=0;
|
|
1604
|
+
if(in_set){
|
|
1605
|
+
if(tn>0){
|
|
1606
|
+
emit_cp=negate?tcps[tn-1]:(mi<tn?tcps[mi]:tcps[tn-1]);
|
|
1607
|
+
translated=1;
|
|
1608
|
+
} else {
|
|
1609
|
+
p+=cn; continue;
|
|
1610
|
+
}
|
|
1611
|
+
} else {
|
|
1612
|
+
emit_cp=cp;
|
|
1613
|
+
translated=0;
|
|
1614
|
+
}
|
|
1615
|
+
/* Squeeze only when both the previous and current emit were
|
|
1616
|
+
translated, AND the emitted codepoints match. */
|
|
1617
|
+
if(has_last && last_was_translated && translated && last_emit==emit_cp){
|
|
1618
|
+
/* skip */
|
|
1619
|
+
} else {
|
|
1620
|
+
n+=sp_utf8_encode(emit_cp,buf+n);
|
|
1621
|
+
last_emit=emit_cp;
|
|
1622
|
+
has_last=1;
|
|
1623
|
+
last_was_translated=translated;
|
|
1624
|
+
}
|
|
1625
|
+
p+=cn;
|
|
1626
|
+
}
|
|
1627
|
+
buf[n]=0;
|
|
1628
|
+
char*r=sp_str_alloc(n);
|
|
1629
|
+
memcpy(r,buf,n+1);
|
|
1630
|
+
free(buf); free(fcps); free(tcps);
|
|
1631
|
+
return r;
|
|
1632
|
+
}
|
|
1633
|
+
static const char*sp_str_delete(const char*s,const char*chars){if(!s)return sp_str_empty;if(!chars)return s;int negate=0;const char*csp=chars;if(*csp=='^'&&*(csp+1)){negate=1;csp++;}size_t setn;uint32_t*set=sp_utf8_decode_charset(csp,&setn);size_t bl=strlen(s);char*r=sp_str_alloc_raw(bl+1);size_t n=0;const char*p=s;while(*p){uint32_t cp;int cn=sp_utf8_decode(p,&cp);int in_set=sp_utf8_set_has(set,setn,cp);if(negate)in_set=!in_set;if(!in_set){memcpy(r+n,p,cn);n+=cn;}p+=cn;}r[n]=0;sp_str_set_len(r,n);free(set);return r;}
|
|
1634
|
+
/* Issue #921: shrink the heap-string header length to match the
|
|
1635
|
+
squeezed payload — the alloc gives bl+1 bytes, the squeezed
|
|
1636
|
+
write fills n<=bl, leaving the header's stored length stale.
|
|
1637
|
+
`bytes` / `length` consult the header (not strlen), so callers
|
|
1638
|
+
would see the alloc size and trailing NULs. */
|
|
1639
|
+
static const char*sp_str_squeeze(const char*s){if(!s)return sp_str_empty;size_t bl=strlen(s);char*r=sp_str_alloc_raw(bl+1);size_t n=0;uint32_t prev=0xFFFFFFFFu;const char*p=s;while(*p){uint32_t cp;int cn=sp_utf8_decode(p,&cp);if(cp!=prev){memcpy(r+n,p,cn);n+=cn;prev=cp;}p+=cn;}r[n]=0;sp_str_set_len(r,n);return r;}
|
|
1640
|
+
/* String#squeeze(chars) — only squeeze chars listed in the charset
|
|
1641
|
+
(same charset syntax as tr: a-z, ^x, etc.). Consecutive runs of
|
|
1642
|
+
non-listed chars pass through untouched. */
|
|
1643
|
+
static const char*sp_str_squeeze_chars(const char*s,const char*cs){if(!s)return sp_str_empty;if(!cs||!*cs)return sp_str_squeeze(s);int negate=0;const char*csp=cs;if(*csp=='^'&&*(csp+1)){negate=1;csp++;}size_t fn;uint32_t*fcps=sp_utf8_decode_charset(csp,&fn);size_t bl=strlen(s);char*r=sp_str_alloc_raw(bl+1);size_t n=0;uint32_t prev=0xFFFFFFFFu;const char*p=s;while(*p){uint32_t cp;int cn=sp_utf8_decode(p,&cp);int in_set=0;for(size_t j=0;j<fn;j++)if(fcps[j]==cp){in_set=1;break;}if(negate)in_set=!in_set;if(!in_set){memcpy(r+n,p,cn);n+=cn;prev=0xFFFFFFFFu;}else if(cp!=prev){memcpy(r+n,p,cn);n+=cn;prev=cp;}p+=cn;}r[n]=0;sp_str_set_len(r,n);free(fcps);return r;}
|
|
1644
|
+
/* Multi-arg delete/squeeze: delete (or squeeze runs of) characters that
|
|
1645
|
+
are in the INTERSECTION of all n charset args, mirroring
|
|
1646
|
+
sp_str_count_n. Each arg is a charset spec (^negation, a-z ranges). */
|
|
1647
|
+
static const char*sp_str_delete_n(const char*s,const char**chars,mrb_int n){if(!s)return sp_str_empty;if(n<=0)return s;size_t*setns=(size_t*)malloc(n*sizeof(size_t));uint32_t**sets=(uint32_t**)malloc(n*sizeof(uint32_t*));int*negs=(int*)malloc(n*sizeof(int));for(mrb_int i=0;i<n;i++){if(!chars[i])sp_raise_cls("TypeError","no implicit conversion of nil into String");const char*cs=chars[i];negs[i]=0;if(*cs=='^'&&*(cs+1)){negs[i]=1;cs++;}sets[i]=sp_utf8_decode_charset(cs,&setns[i]);}size_t bl=strlen(s);char*r=sp_str_alloc_raw(bl+1);size_t m=0;const char*p=s;while(*p){uint32_t cp;int cn=sp_utf8_decode(p,&cp);int all=1;for(mrb_int i=0;i<n;i++){int in_set=sp_utf8_set_has(sets[i],setns[i],cp);if(negs[i])in_set=!in_set;if(!in_set){all=0;break;}}if(!all){memcpy(r+m,p,cn);m+=cn;}p+=cn;}r[m]=0;sp_str_set_len(r,m);for(mrb_int i=0;i<n;i++)free(sets[i]);free(sets);free(setns);free(negs);return r;}
|
|
1648
|
+
static const char*sp_str_squeeze_n(const char*s,const char**chars,mrb_int n){if(!s)return sp_str_empty;if(n<=0)return sp_str_squeeze(s);size_t*setns=(size_t*)malloc(n*sizeof(size_t));uint32_t**sets=(uint32_t**)malloc(n*sizeof(uint32_t*));int*negs=(int*)malloc(n*sizeof(int));for(mrb_int i=0;i<n;i++){if(!chars[i])sp_raise_cls("TypeError","no implicit conversion of nil into String");const char*cs=chars[i];negs[i]=0;if(*cs=='^'&&*(cs+1)){negs[i]=1;cs++;}sets[i]=sp_utf8_decode_charset(cs,&setns[i]);}size_t bl=strlen(s);char*r=sp_str_alloc_raw(bl+1);size_t m=0;uint32_t prev=0xFFFFFFFFu;const char*p=s;while(*p){uint32_t cp;int cn=sp_utf8_decode(p,&cp);int all=1;for(mrb_int i=0;i<n;i++){int in_set=sp_utf8_set_has(sets[i],setns[i],cp);if(negs[i])in_set=!in_set;if(!in_set){all=0;break;}}if(!all){memcpy(r+m,p,cn);m+=cn;prev=0xFFFFFFFFu;}else if(cp!=prev){memcpy(r+m,p,cn);m+=cn;prev=cp;}p+=cn;}r[m]=0;sp_str_set_len(r,m);for(mrb_int i=0;i<n;i++)free(sets[i]);free(sets);free(setns);free(negs);return r;}
|
|
1649
|
+
|
|
1650
|
+
/* Forward decl from sp_crypto.h (libspinel_rt.a). Used by
|
|
1651
|
+
sp_str_crypt below to provide a deterministic crypt-like
|
|
1652
|
+
hash without dragging in libc crypt(3). */
|
|
1653
|
+
const char *sp_crypto_hmac_sha256_b64url(const char *key, const char *msg);
|
|
1654
|
+
|
|
1655
|
+
/* String#crypt — Spinel's crypt is NOT the libc DES crypt. It
|
|
1656
|
+
returns `salt[0..1] || hmac_sha256(salt, password)[0..10]` as
|
|
1657
|
+
a 13-char string (same length as DES crypt, deterministic,
|
|
1658
|
+
stronger primitive). CRuby's spec says String#crypt is impl-
|
|
1659
|
+
defined and "should not be used for security"; this matches
|
|
1660
|
+
that contract while keeping outputs reproducible across
|
|
1661
|
+
spinel builds. Short salts get padded with '.' so the result
|
|
1662
|
+
still has the canonical first-2-chars-are-salt shape. */
|
|
1663
|
+
static const char *sp_str_crypt(const char *s, const char *salt) {
|
|
1664
|
+
if (!salt) salt = "";
|
|
1665
|
+
char salt2[3];
|
|
1666
|
+
salt2[0] = salt[0] ? salt[0] : '.';
|
|
1667
|
+
salt2[1] = (salt[0] && salt[1]) ? salt[1] : '.';
|
|
1668
|
+
salt2[2] = 0;
|
|
1669
|
+
const char *digest = sp_crypto_hmac_sha256_b64url(salt2, s ? s : "");
|
|
1670
|
+
char *r = sp_str_alloc(13);
|
|
1671
|
+
r[0] = salt2[0];
|
|
1672
|
+
r[1] = salt2[1];
|
|
1673
|
+
for (int i = 0; i < 11; i++) {
|
|
1674
|
+
char c = digest[i];
|
|
1675
|
+
/* Map b64url's `-`/`_` to crypt-alphabet `.`/`/` so the
|
|
1676
|
+
output stays in `[./0-9A-Za-z]` like the historical
|
|
1677
|
+
crypt result. */
|
|
1678
|
+
if (c == '-') c = '.';
|
|
1679
|
+
else if (c == '_') c = '/';
|
|
1680
|
+
r[2 + i] = c;
|
|
1681
|
+
}
|
|
1682
|
+
r[13] = 0;
|
|
1683
|
+
sp_str_set_len(r, 13);
|
|
1684
|
+
return r;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
/* String#scrub — walk the bytes; for each valid UTF-8 lead +
|
|
1688
|
+
continuation sequence, copy through. For invalid bytes (lone
|
|
1689
|
+
continuation, truncated multi-byte, overlong, etc.), emit the
|
|
1690
|
+
replacement string and skip one byte. NULL replacement uses
|
|
1691
|
+
U+FFFD (3 UTF-8 bytes: EF BF BD), matching CRuby. */
|
|
1692
|
+
static const char *sp_str_scrub(const char *s, const char *repl) {
|
|
1693
|
+
if (!s) return sp_str_empty;
|
|
1694
|
+
static const char fffd[] = "\xEF\xBF\xBD";
|
|
1695
|
+
const char *r = repl ? repl : fffd;
|
|
1696
|
+
size_t rlen = strlen(r);
|
|
1697
|
+
size_t bl = sp_str_byte_len(s);
|
|
1698
|
+
size_t cap = bl + 64;
|
|
1699
|
+
/* malloc scratch (grown with realloc on invalid-byte runs); the final
|
|
1700
|
+
string is emitted at the exact length below. */
|
|
1701
|
+
char *out = (char *)malloc(cap);
|
|
1702
|
+
size_t olen = 0;
|
|
1703
|
+
size_t i = 0;
|
|
1704
|
+
while (i < bl) {
|
|
1705
|
+
unsigned char c = (unsigned char)s[i];
|
|
1706
|
+
int expected = sp_utf8_char_len(c);
|
|
1707
|
+
int valid = 1;
|
|
1708
|
+
if (expected == 1) {
|
|
1709
|
+
if (c >= 0x80) valid = 0;
|
|
1710
|
+
} else {
|
|
1711
|
+
if (i + (size_t)expected > bl) valid = 0;
|
|
1712
|
+
else {
|
|
1713
|
+
for (int k = 1; k < expected; k++) {
|
|
1714
|
+
if (((unsigned char)s[i + k] & 0xC0) != 0x80) { valid = 0; break; }
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
if (valid) {
|
|
1719
|
+
if (olen + (size_t)expected + 1 >= cap) { cap = (olen + expected) * 2 + 64; out = (char*)realloc(out, cap); }
|
|
1720
|
+
memcpy(out + olen, s + i, (size_t)expected);
|
|
1721
|
+
olen += (size_t)expected;
|
|
1722
|
+
i += (size_t)expected;
|
|
1723
|
+
} else {
|
|
1724
|
+
if (olen + rlen + 1 >= cap) { cap = (olen + rlen) * 2 + 64; out = (char*)realloc(out, cap); }
|
|
1725
|
+
memcpy(out + olen, r, rlen);
|
|
1726
|
+
olen += rlen;
|
|
1727
|
+
i += 1;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
char *res = sp_str_alloc(olen);
|
|
1731
|
+
memcpy(res, out, olen);
|
|
1732
|
+
free(out);
|
|
1733
|
+
return res;
|
|
1734
|
+
}
|
|
1735
|
+
static const char*sp_str_ljust(const char*s,mrb_int w){if(!s)return sp_str_empty;mrb_int cl=sp_str_length(s);if(cl>=w)return s;size_t bl=strlen(s);size_t pad=(size_t)(w-cl);char*r=sp_str_alloc_raw(bl+pad+1);memcpy(r,s,bl);memset(r+bl,' ',pad);r[bl+pad]=0;return r;}
|
|
1736
|
+
static const char*sp_str_rjust(const char*s,mrb_int w){if(!s)return sp_str_empty;mrb_int cl=sp_str_length(s);if(cl>=w)return s;size_t bl=strlen(s);size_t pad=(size_t)(w-cl);char*r=sp_str_alloc_raw(bl+pad+1);memset(r,' ',pad);memcpy(r+pad,s,bl);r[bl+pad]=0;return r;}
|
|
1737
|
+
static const char*sp_str_center(const char*s,mrb_int w){if(!s)return sp_str_empty;mrb_int cl=sp_str_length(s);if(cl>=w)return s;size_t bl=strlen(s);mrb_int pad=w-cl;mrb_int left=pad/2;mrb_int right=pad-left;char*r=sp_str_alloc_raw(bl+pad+1);memset(r,' ',left);memcpy(r+left,s,bl);memset(r+left+bl,' ',right);r[bl+pad]=0;return r;}
|
|
560
1738
|
static const char*sp_str_ljust2(const char*s,mrb_int w,const char*pad){mrb_int cl=sp_str_length(s);if(cl>=w)return s;size_t bl=strlen(s);size_t pn;uint32_t*pcps=sp_utf8_decode_all(pad,&pn);if(pn==0){free(pcps);char*r=sp_str_alloc_raw(bl+1);memcpy(r,s,bl+1);return r;}mrb_int need=w-cl;size_t padb=0;for(mrb_int i=0;i<need;i++){char tmp[4];padb+=sp_utf8_encode(pcps[i%pn],tmp);}char*r=sp_str_alloc_raw(bl+padb+1);memcpy(r,s,bl);size_t n=bl;for(mrb_int i=0;i<need;i++)n+=sp_utf8_encode(pcps[i%pn],r+n);r[n]=0;free(pcps);return r;}
|
|
561
1739
|
static const char*sp_str_rjust2(const char*s,mrb_int w,const char*pad){mrb_int cl=sp_str_length(s);if(cl>=w)return s;size_t bl=strlen(s);size_t pn;uint32_t*pcps=sp_utf8_decode_all(pad,&pn);if(pn==0){free(pcps);char*r=sp_str_alloc_raw(bl+1);memcpy(r,s,bl+1);return r;}mrb_int need=w-cl;size_t padb=0;for(mrb_int i=0;i<need;i++){char tmp[4];padb+=sp_utf8_encode(pcps[i%pn],tmp);}char*r=sp_str_alloc_raw(bl+padb+1);size_t n=0;for(mrb_int i=0;i<need;i++)n+=sp_utf8_encode(pcps[i%pn],r+n);memcpy(r+n,s,bl);r[n+bl]=0;free(pcps);return r;}
|
|
562
1740
|
static const char*sp_str_center2(const char*s,mrb_int w,const char*pad){mrb_int cl=sp_str_length(s);if(cl>=w)return s;size_t bl=strlen(s);size_t pn;uint32_t*pcps=sp_utf8_decode_all(pad,&pn);if(pn==0){free(pcps);char*r=sp_str_alloc_raw(bl+1);memcpy(r,s,bl+1);return r;}mrb_int pd=w-cl;mrb_int left=pd/2;mrb_int right=pd-left;size_t leftb=0,rightb=0;{char tmp[4];for(mrb_int i=0;i<left;i++)leftb+=sp_utf8_encode(pcps[i%pn],tmp);for(mrb_int i=0;i<right;i++)rightb+=sp_utf8_encode(pcps[i%pn],tmp);}char*r=sp_str_alloc_raw(leftb+bl+rightb+1);size_t n=0;for(mrb_int i=0;i<left;i++)n+=sp_utf8_encode(pcps[i%pn],r+n);memcpy(r+n,s,bl);n+=bl;for(mrb_int i=0;i<right;i++)n+=sp_utf8_encode(pcps[i%pn],r+n);r[n]=0;free(pcps);return r;}
|
|
563
|
-
static const char*sp_str_lstrip(const char*s){while(
|
|
564
|
-
static const char*sp_str_rstrip(const char*s){size_t
|
|
565
|
-
static const char*sp_str_dup(const char*s){char*r=sp_str_alloc_raw(
|
|
1741
|
+
static const char*sp_str_lstrip(const char*s){if(!s)return sp_str_empty;size_t len=sp_str_byte_len(s);size_t a=0;while(a<len&&(isspace((unsigned char)s[a])||s[a]=='\0'))a++;size_t n=len-a;char*r=sp_str_alloc(n);memcpy(r,s+a,n);r[n]=0;return r;}
|
|
1742
|
+
static const char*sp_str_rstrip(const char*s){if(!s)return sp_str_empty;size_t len=sp_str_byte_len(s);size_t b=len;while(b>0&&(isspace((unsigned char)s[b-1])||s[b-1]=='\0'))b--;char*r=sp_str_alloc(b);memcpy(r,s,b);r[b]=0;return r;}
|
|
1743
|
+
static const char*sp_str_dup(const char*s){if(!s)return NULL;size_t l=strlen(s);char*r=sp_str_alloc_raw(l+1);memcpy(r,s,l+1);return r;}
|
|
1744
|
+
|
|
1745
|
+
/* String#setbyte: mutate s[i] = v in place. Spinel adopts
|
|
1746
|
+
`# frozen_string_literal: true` semantics globally — all
|
|
1747
|
+
string literals are frozen, mutation requires a heap-allocated
|
|
1748
|
+
buffer (e.g. via .dup or string concatenation). The runtime
|
|
1749
|
+
marker byte at s[-1] tells us the provenance:
|
|
1750
|
+
0xfe / 0xfc -> sp_str_alloc heap (writable, GC unmarked / marked)
|
|
1751
|
+
0xfd -> sp_String wrapper buffer (writable)
|
|
1752
|
+
0xff -> rodata literal (frozen -> FrozenError)
|
|
1753
|
+
other -> FFI / unknown provenance, treated as frozen
|
|
1754
|
+
Returns the byte value (CRuby setbyte return). */
|
|
1755
|
+
static inline mrb_int sp_str_setbyte(const char *s, mrb_int i, mrb_int v) {
|
|
1756
|
+
if (!s) {
|
|
1757
|
+
sp_raise_cls("FrozenError", "can't modify frozen String");
|
|
1758
|
+
return v;
|
|
1759
|
+
}
|
|
1760
|
+
unsigned char m = ((const unsigned char *)s)[-1];
|
|
1761
|
+
if (m == 0xfe || m == 0xfc || m == 0xfd) {
|
|
1762
|
+
((char *)s)[i] = (char)v;
|
|
1763
|
+
return v;
|
|
1764
|
+
}
|
|
1765
|
+
sp_raise_cls("FrozenError", "can't modify frozen String");
|
|
1766
|
+
return v;
|
|
1767
|
+
}
|
|
566
1768
|
|
|
567
1769
|
typedef struct{char*data;int64_t len;int64_t cap;}sp_String;
|
|
568
|
-
|
|
1770
|
+
/* Per-mutable-string freeze flag rides in the GC header alongside
|
|
1771
|
+
`marked`. sp_String_freeze sets it; the in-place mutators below
|
|
1772
|
+
raise FrozenError when the bit is set. Literal `const char *`
|
|
1773
|
+
strings stay frozen via the 0xff marker byte (see sp_str_setbyte). */
|
|
1774
|
+
static inline mrb_bool sp_String_is_frozen(sp_String*s){if(!s)return TRUE;sp_gc_hdr*h=(sp_gc_hdr*)((char*)s-sizeof(sp_gc_hdr));return h->frozen;}
|
|
1775
|
+
static inline sp_String*sp_String_freeze(sp_String*s){if(s){sp_gc_hdr*h=(sp_gc_hdr*)((char*)s-sizeof(sp_gc_hdr));h->frozen=1;}return s;}
|
|
1776
|
+
static void sp_String_fin(void*p){free(((sp_String*)p)->data-1);}
|
|
569
1777
|
static sp_String*sp_String_new(const char*s){
|
|
570
1778
|
/* Copy s's payload into a raw-malloc'd buffer BEFORE sp_gc_alloc.
|
|
571
1779
|
If s is a heap string (sp_str_alloc) whose only liveness anchor
|
|
@@ -578,37 +1786,191 @@ static sp_String*sp_String_new(const char*s){
|
|
|
578
1786
|
any GC inside sp_gc_alloc. */
|
|
579
1787
|
int64_t len=(int64_t)strlen(s);
|
|
580
1788
|
int64_t cap=len*2+16;
|
|
581
|
-
char*
|
|
1789
|
+
char*raw=(char*)malloc(cap+2);
|
|
1790
|
+
raw[0]=(char)0xfd;
|
|
1791
|
+
char*data=raw+1;
|
|
582
1792
|
memcpy(data,s,len+1);
|
|
583
1793
|
sp_String*r=(sp_String*)sp_gc_alloc(sizeof(sp_String),sp_String_fin,NULL);
|
|
584
1794
|
r->len=len;r->cap=cap;r->data=data;
|
|
585
|
-
{sp_gc_hdr*h=(sp_gc_hdr*)((char*)r-sizeof(sp_gc_hdr));h->size+=r->cap+
|
|
1795
|
+
{sp_gc_hdr*h=(sp_gc_hdr*)((char*)r-sizeof(sp_gc_hdr));h->size+=r->cap+2;sp_gc_bytes+=r->cap+2;}
|
|
586
1796
|
return r;
|
|
587
1797
|
}
|
|
588
|
-
|
|
589
|
-
|
|
1798
|
+
/* Issue #757: realloc on growth used to overwrite s->data unconditionally,
|
|
1799
|
+
leaking the old buffer + null-dereferencing if realloc fails. Now we
|
|
1800
|
+
check the result and bail without mutating on failure. */
|
|
1801
|
+
static inline void sp_String_append(sp_String*s,const char*t){if(!s||!t)return;if(sp_String_is_frozen(s)){sp_raise_cls("FrozenError","can't modify frozen String");return;}int64_t tl=(int64_t)strlen(t);if(s->len+tl>=s->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)s-sizeof(sp_gc_hdr));int64_t new_cap=(s->len+tl)*2+16;char*raw=(char*)realloc(s->data-1,new_cap+2);if(!raw)return;sp_gc_bytes-=s->cap+2;h->size-=s->cap+2;s->cap=new_cap;raw[0]=(char)0xfd;s->data=raw+1;h->size+=s->cap+2;sp_gc_bytes+=s->cap+2;}memcpy(s->data+s->len,t,tl+1);s->len+=tl;}
|
|
1802
|
+
static inline void sp_String_prepend(sp_String*s,const char*t){if(!s||!t)return;if(sp_String_is_frozen(s)){sp_raise_cls("FrozenError","can't modify frozen String");return;}int64_t tl=(int64_t)strlen(t);if(s->len+tl>=s->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)s-sizeof(sp_gc_hdr));int64_t new_cap=(s->len+tl)*2+16;char*raw=(char*)realloc(s->data-1,new_cap+2);if(!raw)return;sp_gc_bytes-=s->cap+2;h->size-=s->cap+2;s->cap=new_cap;raw[0]=(char)0xfd;s->data=raw+1;h->size+=s->cap+2;sp_gc_bytes+=s->cap+2;}memmove(s->data+tl,s->data,s->len+1);memcpy(s->data,t,tl);s->len+=tl;}
|
|
1803
|
+
/* Issue #741: String#insert(idx, str) -- insert str at idx. Negative
|
|
1804
|
+
idx is relative to len+1 (insert before tail). */
|
|
1805
|
+
static inline void sp_String_insert(sp_String*s,int64_t idx,const char*t){if(!s||!t)return;if(sp_String_is_frozen(s)){sp_raise_cls("FrozenError","can't modify frozen String");return;}int64_t tl=(int64_t)strlen(t);if(tl==0)return;if(idx<0)idx+=s->len+1;if(idx<0)idx=0;if(idx>s->len)idx=s->len;if(s->len+tl>=s->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)s-sizeof(sp_gc_hdr));int64_t new_cap=(s->len+tl)*2+16;char*raw=(char*)realloc(s->data-1,new_cap+2);if(!raw)return;sp_gc_bytes-=s->cap+2;h->size-=s->cap+2;s->cap=new_cap;raw[0]=(char)0xfd;s->data=raw+1;h->size+=s->cap+2;sp_gc_bytes+=s->cap+2;}memmove(s->data+idx+tl,s->data+idx,s->len-idx+1);memcpy(s->data+idx,t,tl);s->len+=tl;}
|
|
1806
|
+
/* Issue #740/#741 sibling: String#replace(s) -- replace entire content. */
|
|
1807
|
+
static inline void sp_String_replace(sp_String*s,const char*t){if(!s||!t)return;if(sp_String_is_frozen(s)){sp_raise_cls("FrozenError","can't modify frozen String");return;}int64_t tl=(int64_t)strlen(t);if(tl>=s->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)s-sizeof(sp_gc_hdr));int64_t new_cap=tl*2+16;char*raw=(char*)realloc(s->data-1,new_cap+2);if(!raw)return;sp_gc_bytes-=s->cap+2;h->size-=s->cap+2;s->cap=new_cap;raw[0]=(char)0xfd;s->data=raw+1;h->size+=s->cap+2;sp_gc_bytes+=s->cap+2;}memcpy(s->data,t,tl);s->data[tl]='\0';s->len=tl;}
|
|
590
1808
|
static inline const char*sp_String_cstr(sp_String*s){return s->data;}
|
|
591
1809
|
static inline int64_t sp_String_length(sp_String*s){return s->len;}
|
|
592
1810
|
static sp_String*sp_String_dup(sp_String*s){return sp_String_new(s->data);}
|
|
1811
|
+
|
|
1812
|
+
/* `File.open(path, mode)` without a block returns an sp_File * — a
|
|
1813
|
+
GC-managed wrapper around `FILE *fp`. The finalizer fclose()s any
|
|
1814
|
+
still-open fp so a dropped file handle doesn't leak. `path` and
|
|
1815
|
+
`mode` are kept live for `f.path` / `f.mode` introspection; they
|
|
1816
|
+
come from the caller's already-live string slot so no extra mark
|
|
1817
|
+
is needed. */
|
|
1818
|
+
/* File / IO handle ops live in libspinel_rt.a (lib/sp_io.c); the sp_File
|
|
1819
|
+
struct and the allocation-free op prototypes come from sp_io.h. The
|
|
1820
|
+
string-returning readers below (gets / read / read_n / path) stay
|
|
1821
|
+
inline here because they allocate via the hot static sp_str_alloc,
|
|
1822
|
+
whose per-TU sp_str_heap can't be shared across translation units. */
|
|
1823
|
+
#include "sp_io.h"
|
|
1824
|
+
static inline const char *sp_File_gets(sp_File *f) {
|
|
1825
|
+
if (!f || !f->fp) return NULL;
|
|
1826
|
+
char buf[65536];
|
|
1827
|
+
if (!fgets(buf, (int)sizeof(buf), f->fp)) return NULL;
|
|
1828
|
+
size_t n = strlen(buf);
|
|
1829
|
+
char *r = sp_str_alloc(n);
|
|
1830
|
+
memcpy(r, buf, n);
|
|
1831
|
+
return r;
|
|
1832
|
+
}
|
|
1833
|
+
static inline const char *sp_File_read(sp_File *f) {
|
|
1834
|
+
if (!f || !f->fp) return sp_str_empty;
|
|
1835
|
+
long pos = ftell(f->fp);
|
|
1836
|
+
if (pos >= 0 && fseek(f->fp, 0, SEEK_END) == 0) {
|
|
1837
|
+
long end = ftell(f->fp);
|
|
1838
|
+
fseek(f->fp, pos, SEEK_SET);
|
|
1839
|
+
long n = (end > pos) ? (end - pos) : 0;
|
|
1840
|
+
if (n <= 0) return sp_str_empty;
|
|
1841
|
+
char *r = sp_str_alloc((size_t)n);
|
|
1842
|
+
size_t got = fread(r, 1, (size_t)n, f->fp);
|
|
1843
|
+
r[got] = 0;
|
|
1844
|
+
return r;
|
|
1845
|
+
}
|
|
1846
|
+
/* Non-seekable stream (IO.pipe end, socket): read to EOF in chunks. */
|
|
1847
|
+
size_t cap = 256, len = 0;
|
|
1848
|
+
char *buf = (char *)malloc(cap);
|
|
1849
|
+
if (!buf) return sp_str_empty;
|
|
1850
|
+
for (;;) {
|
|
1851
|
+
if (len + 4096 + 1 > cap) {
|
|
1852
|
+
cap = (len + 4096 + 1) * 2;
|
|
1853
|
+
char *nb = (char *)realloc(buf, cap);
|
|
1854
|
+
if (!nb) { free(buf); return sp_str_empty; }
|
|
1855
|
+
buf = nb;
|
|
1856
|
+
}
|
|
1857
|
+
size_t got = fread(buf + len, 1, 4096, f->fp);
|
|
1858
|
+
len += got;
|
|
1859
|
+
if (got < 4096) break;
|
|
1860
|
+
}
|
|
1861
|
+
char *r = sp_str_alloc(len);
|
|
1862
|
+
memcpy(r, buf, len);
|
|
1863
|
+
r[len] = 0;
|
|
1864
|
+
free(buf);
|
|
1865
|
+
return r;
|
|
1866
|
+
}
|
|
1867
|
+
/* IO#read(n): read up to n bytes from the current position. Returns NULL
|
|
1868
|
+
(nil) at EOF for a positive n, "" for n == 0, and the whole rest for a
|
|
1869
|
+
negative n (treated as the no-count read). A short read produces a
|
|
1870
|
+
string of the bytes actually read. */
|
|
1871
|
+
static inline const char *sp_File_read_n(sp_File *f, mrb_int n) {
|
|
1872
|
+
if (!f || !f->fp) return NULL;
|
|
1873
|
+
if (n < 0) return sp_File_read(f);
|
|
1874
|
+
if (n == 0) return sp_str_empty;
|
|
1875
|
+
char *r = sp_str_alloc((size_t)n);
|
|
1876
|
+
size_t got = fread(r, 1, (size_t)n, f->fp);
|
|
1877
|
+
if (got == 0) return NULL;
|
|
1878
|
+
if ((mrb_int)got == n) { r[got] = 0; return r; }
|
|
1879
|
+
char *s = sp_str_alloc(got);
|
|
1880
|
+
memcpy(s, r, got);
|
|
1881
|
+
s[got] = 0;
|
|
1882
|
+
return s;
|
|
1883
|
+
}
|
|
1884
|
+
static inline const char *sp_File_path(sp_File *f) { return f && f->path ? f->path : sp_str_empty; }
|
|
1885
|
+
|
|
593
1886
|
/* Array#inspect for each typed array: `[elem1, elem2, ...]` with each
|
|
594
1887
|
element rendered via its own primitive inspect. Matches CRuby's
|
|
595
1888
|
Array#inspect output byte-for-byte. Returns a GC-managed C string. */
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
1889
|
+
/* The inspect helpers NULL-guard their receiver: a NULL array can
|
|
1890
|
+
reach here when an unresolved call (e.g. an unsupported Array method)
|
|
1891
|
+
emits a 0 placeholder that flows into `.inspect`, and dereferencing
|
|
1892
|
+
a->len would segfault. Rendering "[]" stops the crash and degrades to
|
|
1893
|
+
the same shape as the empty-array case. */
|
|
1894
|
+
static const char*sp_IntArray_inspect(sp_IntArray*a){if(!a)return "[]";sp_String*s=sp_String_new("[");for(mrb_int i=0;i<a->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,sp_int_to_s(a->data[a->start+i]));}sp_String_append(s,"]");return s->data;}
|
|
1895
|
+
static const char*sp_FloatArray_inspect(sp_FloatArray*a){if(!a)return "[]";sp_String*s=sp_String_new("[");for(mrb_int i=0;i<a->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,sp_float_inspect(a->data[i]));}sp_String_append(s,"]");return s->data;}
|
|
1896
|
+
/* Array#join for float arrays -- each element via the Ruby-faithful
|
|
1897
|
+
sp_float_to_s ("1.0", not "1"). Mirrors sp_IntArray_join exactly: build in a
|
|
1898
|
+
malloc buffer, return an sp_str_alloc'd copy. (Not sp_String#data, whose owner
|
|
1899
|
+
isn't GC-rooted across the return.) sp_float_to_s's result is copied
|
|
1900
|
+
immediately, before the next call can reuse its buffer. */
|
|
1901
|
+
static const char*sp_FloatArray_join(sp_FloatArray*a,const char*sep){size_t sl=strlen(sep),cap=256;char*buf=(char*)malloc(cap);size_t len=0;if(a){for(mrb_int i=0;i<a->len;i++){if(i>0){if(len+sl>=cap){cap*=2;buf=(char*)realloc(buf,cap);}memcpy(buf+len,sep,sl);len+=sl;}const char*es=sp_float_to_s(a->data[i]);size_t el=strlen(es);if(len+el>=cap){while(len+el>=cap)cap*=2;buf=(char*)realloc(buf,cap);}memcpy(buf+len,es,el);len+=el;}}buf[len]=0;char*r=sp_str_alloc(len);memcpy(r,buf,len);free(buf);return r;}
|
|
1902
|
+
static mrb_bool sp_FloatArray_eq(sp_FloatArray*a,sp_FloatArray*b){if(!a||!b)return a==b;if(a->len!=b->len)return FALSE;for(mrb_int i=0;i<a->len;i++)if(a->data[i]!=b->data[i])return FALSE;return TRUE;}
|
|
1903
|
+
static const char*sp_StrArray_inspect(sp_StrArray*a){if(!a)return "[]";sp_String*s=sp_String_new("[");for(mrb_int i=0;i<a->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,sp_str_inspect(a->data[i]));}sp_String_append(s,"]");return s->data;}
|
|
1904
|
+
static mrb_bool sp_StrArray_eq(sp_StrArray*a,sp_StrArray*b){if(!a||!b)return a==b;if(a->len!=b->len)return FALSE;for(mrb_int i=0;i<a->len;i++)if(!sp_str_eq(a->data[i],b->data[i]))return FALSE;return TRUE;}
|
|
599
1905
|
/* Symbol arrays share the IntArray representation (sp_sym = mrb_int),
|
|
600
1906
|
but each element is rendered as ":name" via sp_sym_to_s. */
|
|
601
|
-
static const char*sp_SymArray_inspect(sp_IntArray*a){sp_String*s=sp_String_new("[");for(mrb_int i=0;i<a->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,":");sp_String_append(s,sp_sym_to_s((sp_sym)a->data[a->start+i]));}sp_String_append(s,"]");return s->data;}
|
|
1907
|
+
static inline const char*sp_SymArray_inspect(sp_IntArray*a){if(!a)return "[]";sp_String*s=sp_String_new("[");for(mrb_int i=0;i<a->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,":");sp_String_append(s,sp_sym_to_s((sp_sym)a->data[a->start+i]));}sp_String_append(s,"]");return s->data;}
|
|
602
1908
|
/* PtrArray elements are object pointers without a per-element class
|
|
603
1909
|
tag, so we render them as `#<Object>` rather than recursing. */
|
|
604
|
-
static const char*sp_PtrArray_inspect(sp_PtrArray*a){sp_String*s=sp_String_new("[");for(mrb_int i=0;i<a->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,"#<Object>");}sp_String_append(s,"]");return s->data;}
|
|
1910
|
+
static const char*sp_PtrArray_inspect(sp_PtrArray*a){if(!a)return "[]";sp_String*s=sp_String_new("[");for(mrb_int i=0;i<a->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,"#<Object>");}sp_String_append(s,"]");return s->data;}
|
|
1911
|
+
/* Issue #851: Hash#inspect for typed-hash variants beyond
|
|
1912
|
+
sym_int_hash. Renders Ruby's `{"k"=>v, ...}` (string keys),
|
|
1913
|
+
`{42=>"v", ...}` (int keys), or `{:k=>v, ...}` (sym keys but
|
|
1914
|
+
non-int value, since the bare `k: v` shorthand only applies
|
|
1915
|
+
when values are inspectable as one-liners — match CRuby). */
|
|
1916
|
+
static const char*sp_StrIntHash_inspect(sp_StrIntHash*h){sp_String*s=sp_String_new("{");if(h){for(mrb_int i=0;i<h->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,sp_str_inspect(h->order[i]));sp_String_append(s,"=>");sp_String_append(s,sp_int_to_s(sp_StrIntHash_get(h,h->order[i])));}}sp_String_append(s,"}");return s->data;}
|
|
1917
|
+
/* Hash#to_proc lookup fn — cap is the hash, args[0] the string key. */
|
|
1918
|
+
static mrb_int sp_StrIntHash_proc_fn(void *cap, mrb_int *args) { return sp_StrIntHash_get((sp_StrIntHash *)cap, (const char *)(uintptr_t)args[0]); }
|
|
1919
|
+
static const char*sp_StrStrHash_inspect(sp_StrStrHash*h){sp_String*s=sp_String_new("{");if(h){for(mrb_int i=0;i<h->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,sp_str_inspect(h->order[i]));sp_String_append(s,"=>");sp_String_append(s,sp_str_inspect(sp_StrStrHash_get(h,h->order[i])));}}sp_String_append(s,"}");return s->data;}
|
|
1920
|
+
static const char*sp_IntStrHash_inspect(sp_IntStrHash*h){sp_String*s=sp_String_new("{");if(h){for(mrb_int i=0;i<h->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,sp_int_to_s(h->order[i]));sp_String_append(s,"=>");sp_String_append(s,sp_str_inspect(sp_IntStrHash_get(h,h->order[i])));}}sp_String_append(s,"}");return s->data;}
|
|
1921
|
+
static const char*sp_IntIntHash_inspect(sp_IntIntHash*h){sp_String*s=sp_String_new("{");if(h){for(mrb_int i=0;i<h->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,sp_int_to_s(h->order[i]));sp_String_append(s,"=>");sp_String_append(s,sp_int_to_s(sp_IntIntHash_get(h,h->order[i])));}}sp_String_append(s,"}");return s->data;}
|
|
605
1922
|
/* Nested-array inspect: when codegen knows the ptr_array's element
|
|
606
1923
|
type is one of the four built-in T_array shapes, recurse into the
|
|
607
|
-
matching primitive inspect
|
|
1924
|
+
matching primitive inspect . */
|
|
608
1925
|
static const char*sp_IntArrayPtrArray_inspect(sp_PtrArray*a){sp_String*s=sp_String_new("[");for(mrb_int i=0;i<a->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,sp_IntArray_inspect((sp_IntArray*)a->data[i]));}sp_String_append(s,"]");return s->data;}
|
|
1926
|
+
/* Issue #742: Array#combination(k) on int_array -- emit all
|
|
1927
|
+
k-element ordered combinations as a PtrArray of IntArrays. */
|
|
1928
|
+
static void sp_int_combination_recur(sp_IntArray*src,mrb_int start,mrb_int k,sp_IntArray*acc,sp_PtrArray*out){if(k==0){sp_IntArray*cp=sp_IntArray_new();for(mrb_int i=0;i<acc->len;i++)sp_IntArray_push(cp,acc->data[acc->start+i]);sp_PtrArray_push(out,cp);return;}for(mrb_int i=start;i<=src->len-k;i++){sp_IntArray_push(acc,src->data[src->start+i]);sp_int_combination_recur(src,i+1,k-1,acc,out);acc->len--;}}
|
|
1929
|
+
static sp_PtrArray*sp_IntArray_combination(sp_IntArray*a,mrb_int k){sp_PtrArray*out=sp_PtrArray_new();if(!a||k<0||k>a->len)return out;sp_IntArray*acc=sp_IntArray_new();sp_int_combination_recur(a,0,k,acc,out);return out;}
|
|
1930
|
+
/* repeated_combination: like combination but an index may repeat, so the
|
|
1931
|
+
recursion restarts at `i` rather than `i+1`. */
|
|
1932
|
+
static void sp_int_repeated_combination_recur(sp_IntArray*src,mrb_int start,mrb_int k,sp_IntArray*acc,sp_PtrArray*out){if(k==0){sp_IntArray*cp=sp_IntArray_new();for(mrb_int i=0;i<acc->len;i++)sp_IntArray_push(cp,acc->data[acc->start+i]);sp_PtrArray_push(out,cp);return;}for(mrb_int i=start;i<src->len;i++){sp_IntArray_push(acc,src->data[src->start+i]);sp_int_repeated_combination_recur(src,i,k-1,acc,out);acc->len--;}}
|
|
1933
|
+
static sp_PtrArray*sp_IntArray_repeated_combination(sp_IntArray*a,mrb_int k){sp_PtrArray*out=sp_PtrArray_new();if(!a||k<0)return out;sp_IntArray*acc=sp_IntArray_new();sp_int_repeated_combination_recur(a,0,k,acc,out);return out;}
|
|
1934
|
+
|
|
1935
|
+
/* Cartesian product of two int arrays. Returns a PtrArray of
|
|
1936
|
+
2-element IntArrays. */
|
|
1937
|
+
static sp_PtrArray *sp_IntArray_product(sp_IntArray *a, sp_IntArray *b) {
|
|
1938
|
+
sp_PtrArray *out = sp_PtrArray_new();
|
|
1939
|
+
if (!a || !b) return out;
|
|
1940
|
+
for (mrb_int i = 0; i < a->len; i++) {
|
|
1941
|
+
for (mrb_int j = 0; j < b->len; j++) {
|
|
1942
|
+
sp_IntArray *pair = sp_IntArray_new();
|
|
1943
|
+
sp_IntArray_push(pair, a->data[a->start + i]);
|
|
1944
|
+
sp_IntArray_push(pair, b->data[b->start + j]);
|
|
1945
|
+
sp_PtrArray_push(out, pair);
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
return out;
|
|
1949
|
+
}
|
|
1950
|
+
/* Array#permutation(k) -- ordered k-permutations. */
|
|
1951
|
+
static void sp_int_permutation_recur(sp_IntArray*src,mrb_int k,sp_IntArray*used,sp_IntArray*acc,sp_PtrArray*out){if(k==0){sp_IntArray*cp=sp_IntArray_new();for(mrb_int i=0;i<acc->len;i++)sp_IntArray_push(cp,acc->data[acc->start+i]);sp_PtrArray_push(out,cp);return;}for(mrb_int i=0;i<src->len;i++){if(used->data[used->start+i])continue;used->data[used->start+i]=1;sp_IntArray_push(acc,src->data[src->start+i]);sp_int_permutation_recur(src,k-1,used,acc,out);acc->len--;used->data[used->start+i]=0;}}
|
|
1952
|
+
static sp_PtrArray*sp_IntArray_permutation(sp_IntArray*a,mrb_int k){sp_PtrArray*out=sp_PtrArray_new();if(!a||k<0||k>a->len)return out;sp_IntArray*used=sp_IntArray_new();for(mrb_int i=0;i<a->len;i++)sp_IntArray_push(used,0);sp_IntArray*acc=sp_IntArray_new();sp_int_permutation_recur(a,k,used,acc,out);return out;}
|
|
609
1953
|
static const char*sp_FloatArrayPtrArray_inspect(sp_PtrArray*a){sp_String*s=sp_String_new("[");for(mrb_int i=0;i<a->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,sp_FloatArray_inspect((sp_FloatArray*)a->data[i]));}sp_String_append(s,"]");return s->data;}
|
|
610
1954
|
static const char*sp_StrArrayPtrArray_inspect(sp_PtrArray*a){sp_String*s=sp_String_new("[");for(mrb_int i=0;i<a->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,sp_StrArray_inspect((sp_StrArray*)a->data[i]));}sp_String_append(s,"]");return s->data;}
|
|
1955
|
+
/* sp_PolyArrayPtrArray_inspect lives below sp_PolyArray_inspect's
|
|
1956
|
+
forward declaration (sp_PolyArray isn't defined until ~2542). */
|
|
611
1957
|
static const char*sp_SymArrayPtrArray_inspect(sp_PtrArray*a){sp_String*s=sp_String_new("[");for(mrb_int i=0;i<a->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,sp_SymArray_inspect((sp_IntArray*)a->data[i]));}sp_String_append(s,"]");return s->data;}
|
|
1958
|
+
/* issue #526: join for a sp_PtrArray of sp_String* (mutable_str_ptr_array).
|
|
1959
|
+
Sibling to sp_StrArray_join, but takes advantage of sp_String's known
|
|
1960
|
+
length: two-pass — sum the exact total, sp_str_alloc once, then memcpy
|
|
1961
|
+
each element by its s->len (preserves embedded NULs). Avoids the
|
|
1962
|
+
realloc-grow loop's leak-on-failure and long-separator overflow
|
|
1963
|
+
risks, and skips an intermediate malloc'd buffer. NULL entries
|
|
1964
|
+
contribute zero length. */
|
|
1965
|
+
static const char*sp_PtrArray_str_join(sp_PtrArray*a,const char*sep){mrb_int al=a->len;if(al==0)return sp_str_empty;size_t sl=strlen(sep),total=0;for(mrb_int i=0;i<al;i++){if(i>0)total+=sl;sp_String*s=(sp_String*)a->data[i];if(s)total+=(size_t)s->len;}char*r=sp_str_alloc(total);size_t cur=0;for(mrb_int i=0;i<al;i++){if(i>0){memcpy(r+cur,sep,sl);cur+=sl;}sp_String*s=(sp_String*)a->data[i];if(s&&s->len){memcpy(r+cur,s->data,(size_t)s->len);cur+=(size_t)s->len;}}return r;}
|
|
1966
|
+
|
|
1967
|
+
#ifdef __FreeBSD__
|
|
1968
|
+
|
|
1969
|
+
#define re_exec spinel_re_exec
|
|
1970
|
+
|
|
1971
|
+
#define MAP_NORESERVE 0
|
|
1972
|
+
|
|
1973
|
+
#endif
|
|
612
1974
|
|
|
613
1975
|
/* Regexp engine (link with libspre.a from lib/regexp/) */
|
|
614
1976
|
typedef struct mrb_regexp_pattern mrb_regexp_pattern;
|
|
@@ -637,18 +1999,71 @@ static const char *sp_re_match_str = NULL;
|
|
|
637
1999
|
static const char *sp_re_match_pre = NULL;
|
|
638
2000
|
static const char *sp_re_match_post = NULL;
|
|
639
2001
|
|
|
2002
|
+
/* ARGV runtime: argv[i] strings are dup'd via sp_str_dup_external on
|
|
2003
|
+
main() entry, which allocates from the str-heap with mark byte
|
|
2004
|
+
0xfe. Without explicit marking they get reaped on the first
|
|
2005
|
+
sp_str_sweep, leaving sp_argv.data[i] as a dangling pointer that
|
|
2006
|
+
later `ARGV[i]` reads dereference. The exact length boundary
|
|
2007
|
+
triggering the segfault depends on malloc's reuse pattern (so the
|
|
2008
|
+
bug surfaces non-deterministically by string length), but the
|
|
2009
|
+
underlying issue is unconditional. */
|
|
2010
|
+
typedef struct{const char**data;mrb_int len;}sp_Argv;
|
|
2011
|
+
static sp_Argv sp_argv;
|
|
2012
|
+
|
|
2013
|
+
/* Mark active in-flight exception messages. Most raises pass string
|
|
2014
|
+
literals (rodata, marker byte ≠ 0xfe → no-op for sp_mark_string),
|
|
2015
|
+
but raises that build messages dynamically via sp_sprintf (e.g.
|
|
2016
|
+
sp_str_to_i_strict's `"invalid value for Integer(): \"%s\""`)
|
|
2017
|
+
hand a heap-allocated string to sp_raise_cls. Without marking, a
|
|
2018
|
+
GC cycle between the raise and the rescue handler reading the
|
|
2019
|
+
message would sweep the message and leave sp_exc_msg[i] dangling.
|
|
2020
|
+
The helper itself is defined further down (after sp_exc_msg /
|
|
2021
|
+
sp_exc_top are declared); only the prototype lives here. */
|
|
2022
|
+
static void sp_mark_in_flight_exceptions(void);
|
|
2023
|
+
|
|
640
2024
|
/* Mark the regex globals as live during GC. Each holds a pointer to a
|
|
641
2025
|
string allocated via sp_str_alloc_raw on the str-heap; without this
|
|
642
2026
|
sp_str_sweep would reap them on the next collect, leaving dangling
|
|
643
2027
|
pointers in $1..$9, $&, $`, $'. sp_mark_string is null-safe and
|
|
644
2028
|
no-ops on non-heap strings (the empty-string default of
|
|
645
2029
|
sp_re_last_str), so it's safe to call unconditionally. */
|
|
2030
|
+
/* sp_fiber_root.storage needs marking here because sp_fiber_root is
|
|
2031
|
+
a static (not sp_gc_alloc'd), so its normal sp_Fiber_scan never
|
|
2032
|
+
runs via the heap walker. Without this, top-level `Fiber[:k] = v`
|
|
2033
|
+
writes get prematurely collected. The forward declaration is
|
|
2034
|
+
needed because sp_fiber_root is defined further down in the
|
|
2035
|
+
Fiber runtime block. */
|
|
2036
|
+
/* External linkage: lib/sp_gc.c's sp_gc_mark_all reaches this by name. */
|
|
646
2037
|
static void sp_re_mark_globals(void) {
|
|
647
2038
|
sp_mark_string(sp_re_last_str);
|
|
648
2039
|
for (int i = 0; i < 10; i++) sp_mark_string(sp_re_captures[i]);
|
|
649
2040
|
sp_mark_string(sp_re_match_str);
|
|
650
2041
|
sp_mark_string(sp_re_match_pre);
|
|
651
2042
|
sp_mark_string(sp_re_match_post);
|
|
2043
|
+
for (mrb_int i = 0; i < sp_argv.len; i++) sp_mark_string(sp_argv.data[i]);
|
|
2044
|
+
sp_mark_in_flight_exceptions();
|
|
2045
|
+
sp_mark_fiber_root_storage();
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
/* Hand the collector (lib/sp_gc.c) this TU's root-marking and string-heap
|
|
2049
|
+
sweep. Runs before main, so the hooks are set before the first
|
|
2050
|
+
allocation can trigger a collection. */
|
|
2051
|
+
__attribute__((constructor)) static void sp_gc_install_tu_hooks(void) {
|
|
2052
|
+
sp_gc_mark_globals_hook = sp_re_mark_globals;
|
|
2053
|
+
sp_gc_str_sweep_hook = sp_str_sweep;
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
/* `$+` / `$LAST_PAREN_MATCH` — contents of the highest-indexed group
|
|
2057
|
+
that participated in the match. Walks sp_re_captures[] from 9 down
|
|
2058
|
+
and returns the first non-NULL entry. NULL when no group matched
|
|
2059
|
+
(codegen ternary falls back to ""). Matches CRuby's behaviour:
|
|
2060
|
+
for /(a)(b)?/ matching "a", $+ is "a"; for /(a)(b)/ matching "ab",
|
|
2061
|
+
$+ is "b". */
|
|
2062
|
+
static const char *sp_re_last_paren_match(void) {
|
|
2063
|
+
for (int i = 9; i >= 1; i--) {
|
|
2064
|
+
if (sp_re_captures[i]) return sp_re_captures[i];
|
|
2065
|
+
}
|
|
2066
|
+
return NULL;
|
|
652
2067
|
}
|
|
653
2068
|
|
|
654
2069
|
static void sp_re_set_captures(const char *str, int *caps, int ncaps) {
|
|
@@ -684,12 +2099,50 @@ static void sp_re_set_captures(const char *str, int *caps, int ncaps) {
|
|
|
684
2099
|
}
|
|
685
2100
|
}
|
|
686
2101
|
|
|
2102
|
+
/* `=~` returns the match position (0-indexed) or -1 on miss.
|
|
2103
|
+
Codegen's regex truthy check (regex_match_call_node? arm in
|
|
2104
|
+
compile_cond_expr) compares against -1 so match-at-position-0
|
|
2105
|
+
is correctly truthy. Direct value use lines up with CRuby's
|
|
2106
|
+
`String#=~` int semantics: "abc" =~ /b/ -> 1, not 2. */
|
|
687
2107
|
static mrb_int sp_re_match(mrb_regexp_pattern *pat, const char *str) {
|
|
688
2108
|
int64_t slen = (int64_t)strlen(str);
|
|
689
2109
|
int ncaps = 32;
|
|
690
2110
|
int n = re_exec(pat, str, slen, 0, sp_re_caps, ncaps);
|
|
691
|
-
if (n > 0) { sp_re_set_captures(str, sp_re_caps, n/2); return sp_re_caps[0]
|
|
692
|
-
|
|
2111
|
+
if (n > 0) { sp_re_set_captures(str, sp_re_caps, n/2); return sp_re_caps[0]; }
|
|
2112
|
+
/* Issue #848: clear backrefs on no-match so a subsequent `$1`
|
|
2113
|
+
reads as nil rather than the previous match's group. */
|
|
2114
|
+
for (int i = 0; i < 10; i++) sp_re_captures[i] = NULL;
|
|
2115
|
+
sp_re_last_str = NULL;
|
|
2116
|
+
sp_re_match_str = NULL;
|
|
2117
|
+
sp_re_match_pre = NULL;
|
|
2118
|
+
sp_re_match_post = NULL;
|
|
2119
|
+
return -1;
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
/* `s.rindex(regex)` — last match start, in BYTE offset (matches
|
|
2123
|
+
the way sp_str_rindex reports indices for plain-string search;
|
|
2124
|
+
codepoint translation would require a UTF-8 walk and the
|
|
2125
|
+
handful of call sites that consume rindex don't need it).
|
|
2126
|
+
Walks forward through successive matches and remembers the
|
|
2127
|
+
latest start. Issue #504: previously the codegen routed
|
|
2128
|
+
`s.rindex(/re/)` to sp_str_rindex(s, 0) and SEGV'd at
|
|
2129
|
+
strlen(NULL). Returns -1 on no match. */
|
|
2130
|
+
static mrb_int sp_re_rindex(mrb_regexp_pattern *pat, const char *str) {
|
|
2131
|
+
int64_t slen = (int64_t)strlen(str);
|
|
2132
|
+
int caps[2];
|
|
2133
|
+
int64_t pos = 0;
|
|
2134
|
+
mrb_int last = -1;
|
|
2135
|
+
while (pos <= slen) {
|
|
2136
|
+
int n = re_exec(pat, str, slen, pos, caps, 2);
|
|
2137
|
+
if (n <= 0) break;
|
|
2138
|
+
last = caps[0];
|
|
2139
|
+
/* Advance past the match; for zero-width matches step by 1
|
|
2140
|
+
to avoid an infinite loop. */
|
|
2141
|
+
int64_t next = caps[1];
|
|
2142
|
+
if (next <= pos) next = pos + 1;
|
|
2143
|
+
pos = next;
|
|
2144
|
+
}
|
|
2145
|
+
return last;
|
|
693
2146
|
}
|
|
694
2147
|
|
|
695
2148
|
static mrb_bool sp_re_match_p(mrb_regexp_pattern *pat, const char *str) {
|
|
@@ -698,9 +2151,76 @@ static mrb_bool sp_re_match_p(mrb_regexp_pattern *pat, const char *str) {
|
|
|
698
2151
|
return re_exec(pat, str, slen, 0, caps, 2) > 0;
|
|
699
2152
|
}
|
|
700
2153
|
|
|
2154
|
+
/* Issue #869: Regexp#match?(str, pos) starts matching at byte
|
|
2155
|
+
offset `pos`. Negative pos counts from the end (CRuby compat).
|
|
2156
|
+
Out-of-range pos returns false. */
|
|
2157
|
+
static mrb_bool sp_re_match_p_at(mrb_regexp_pattern *pat, const char *str, mrb_int pos) {
|
|
2158
|
+
int64_t slen = (int64_t)strlen(str);
|
|
2159
|
+
if (pos < 0) pos += slen;
|
|
2160
|
+
if (pos < 0 || pos > slen) return FALSE;
|
|
2161
|
+
int caps[2];
|
|
2162
|
+
return re_exec(pat, str, slen, (mrb_int)pos, caps, 2) > 0;
|
|
2163
|
+
}
|
|
2164
|
+
/* String#match?(/re/, pos) — pos is a codepoint index (CRuby semantics),
|
|
2165
|
+
unlike Regexp#match?(str, pos) which uses byte offset. Convert the
|
|
2166
|
+
codepoint index to a byte offset before dispatching to re_exec. */
|
|
2167
|
+
static mrb_bool sp_str_re_match_p_at(mrb_regexp_pattern *pat, const char *str, mrb_int cpos) {
|
|
2168
|
+
mrb_int cl = sp_str_length(str);
|
|
2169
|
+
if (cpos < 0) cpos += cl;
|
|
2170
|
+
if (cpos < 0 || cpos > cl) return FALSE;
|
|
2171
|
+
size_t boff = sp_utf8_byte_offset(str, cpos);
|
|
2172
|
+
int64_t slen = (int64_t)strlen(str);
|
|
2173
|
+
int caps[2];
|
|
2174
|
+
return re_exec(pat, str, slen, (mrb_int)boff, caps, 2) > 0;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
/* Issue #855: expand `\1`..`\9` / `\&` / `\0` backreferences in
|
|
2178
|
+
the replacement string against the current caps[] array. `\\`
|
|
2179
|
+
is a literal backslash. Writes to *out_io at *olen_io, growing
|
|
2180
|
+
*out_io / *cap_io as needed. */
|
|
2181
|
+
static void sp_re_expand_rep(char **out_io, size_t *olen_io, size_t *cap_io,
|
|
2182
|
+
const char *rep, size_t rlen,
|
|
2183
|
+
const char *src, int *caps, int ncaps) {
|
|
2184
|
+
size_t olen = *olen_io;
|
|
2185
|
+
char *out = *out_io;
|
|
2186
|
+
size_t cap = *cap_io;
|
|
2187
|
+
size_t i = 0;
|
|
2188
|
+
while (i < rlen) {
|
|
2189
|
+
char c = rep[i];
|
|
2190
|
+
if (c == '\\' && i + 1 < rlen) {
|
|
2191
|
+
char d = rep[i+1];
|
|
2192
|
+
if ((d >= '0' && d <= '9') || d == '&') {
|
|
2193
|
+
int gi = (d == '&') ? 0 : (d - '0');
|
|
2194
|
+
if (gi*2 + 1 < ncaps && caps[gi*2] >= 0 && caps[gi*2+1] >= 0) {
|
|
2195
|
+
int g_len = caps[gi*2+1] - caps[gi*2];
|
|
2196
|
+
if (olen + g_len + 1 >= cap) { cap = (olen + g_len) * 2 + 64; out = (char*)realloc(out, cap); }
|
|
2197
|
+
memcpy(out+olen, src + caps[gi*2], g_len);
|
|
2198
|
+
olen += g_len;
|
|
2199
|
+
}
|
|
2200
|
+
i += 2;
|
|
2201
|
+
continue;
|
|
2202
|
+
} else if (d == '\\') {
|
|
2203
|
+
if (olen + 1 >= cap) { cap = cap * 2 + 64; out = (char*)realloc(out, cap); }
|
|
2204
|
+
out[olen++] = '\\';
|
|
2205
|
+
i += 2;
|
|
2206
|
+
continue;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
if (olen + 1 >= cap) { cap = cap * 2 + 64; out = (char*)realloc(out, cap); }
|
|
2210
|
+
out[olen++] = c;
|
|
2211
|
+
i++;
|
|
2212
|
+
}
|
|
2213
|
+
*out_io = out; *olen_io = olen; *cap_io = cap;
|
|
2214
|
+
}
|
|
2215
|
+
|
|
701
2216
|
static const char *sp_re_gsub(mrb_regexp_pattern *pat, const char *str, const char *rep) {
|
|
702
2217
|
int64_t slen = (int64_t)strlen(str); size_t rlen = strlen(rep);
|
|
703
|
-
size_t cap = slen * 2 +
|
|
2218
|
+
size_t cap = slen * 2 + rlen * 4 + 64;
|
|
2219
|
+
/* Build into a plain malloc scratch: the buffer is grown with realloc
|
|
2220
|
+
here and inside sp_re_expand_rep, which is only valid on a real
|
|
2221
|
+
malloc base (a sp_str body pointer is offset past its header). The
|
|
2222
|
+
final string is allocated at the exact length below. */
|
|
2223
|
+
char *out = (char *)malloc(cap); size_t olen = 0;
|
|
704
2224
|
int64_t pos = 0; int caps[64];
|
|
705
2225
|
while (pos <= slen) {
|
|
706
2226
|
int n = re_exec(pat, str, slen, pos, caps, 64);
|
|
@@ -708,13 +2228,127 @@ static const char *sp_re_gsub(mrb_regexp_pattern *pat, const char *str, const ch
|
|
|
708
2228
|
size_t before = caps[0] - pos;
|
|
709
2229
|
if (olen+before+rlen >= cap) { cap = (olen+before+rlen)*2+64; out = (char*)realloc(out, cap); }
|
|
710
2230
|
memcpy(out+olen, str+pos, before); olen += before;
|
|
711
|
-
|
|
712
|
-
|
|
2231
|
+
sp_re_expand_rep(&out, &olen, &cap, rep, rlen, str, caps, n);
|
|
2232
|
+
if (caps[0] == caps[1]) {
|
|
2233
|
+
/* Zero-width match (/^/, /$/, /\b/, an empty pattern): Ruby inserts
|
|
2234
|
+
the replacement before the char at this position, keeps that char,
|
|
2235
|
+
and advances past it. Copy the char and step by one so the scan
|
|
2236
|
+
makes progress without dropping it or spinning on the same spot. */
|
|
2237
|
+
if (caps[1] < slen) {
|
|
2238
|
+
if (olen+1 >= cap) { cap = olen*2+64; out = (char*)realloc(out, cap); }
|
|
2239
|
+
out[olen++] = str[caps[1]];
|
|
2240
|
+
}
|
|
2241
|
+
pos = caps[1] + 1;
|
|
2242
|
+
} else {
|
|
2243
|
+
pos = caps[1];
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
/* pos can land at slen+1 after a zero-width match at the end; guard the
|
|
2247
|
+
tail copy so `slen - pos` doesn't underflow size_t. */
|
|
2248
|
+
if (pos < slen) {
|
|
2249
|
+
size_t rest = slen - pos;
|
|
2250
|
+
if (olen+rest+1 >= cap) { cap = olen+rest+64; out = (char*)realloc(out, cap); }
|
|
2251
|
+
memcpy(out+olen, str+pos, rest); olen += rest;
|
|
2252
|
+
}
|
|
2253
|
+
/* Emit a string sized to exactly the bytes written (sp_str_alloc sets
|
|
2254
|
+
the length and null-terminates); release the scratch. */
|
|
2255
|
+
char *res = sp_str_alloc(olen);
|
|
2256
|
+
memcpy(res, out, olen);
|
|
2257
|
+
free(out);
|
|
2258
|
+
return res;
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
/* String#gsub(regex, hash) — per-match hash lookup form. CRuby's
|
|
2262
|
+
* semantics: each matched substring is looked up as a key in the
|
|
2263
|
+
* hash; the value (if present) is the replacement, otherwise the
|
|
2264
|
+
* matched substring is dropped (CRuby returns "", not the match).
|
|
2265
|
+
* Used by html_escape / json_escape idioms (gsub(/[&<>]/, ESCAPES)). */
|
|
2266
|
+
static const char *sp_re_gsub_str_str_hash(mrb_regexp_pattern *pat, const char *str, sp_StrStrHash *h) {
|
|
2267
|
+
int64_t slen = (int64_t)strlen(str);
|
|
2268
|
+
/* malloc scratch (realloc-safe); exact-sized string emitted below. */
|
|
2269
|
+
size_t cap = slen * 2 + 64; char *out = (char *)malloc(cap); size_t olen = 0;
|
|
2270
|
+
int64_t pos = 0; int caps[64];
|
|
2271
|
+
while (pos <= slen) {
|
|
2272
|
+
int n = re_exec(pat, str, slen, pos, caps, 64);
|
|
2273
|
+
if (n <= 0 || caps[0] < 0) break;
|
|
2274
|
+
size_t before = caps[0] - pos;
|
|
2275
|
+
int mlen = caps[1] - caps[0];
|
|
2276
|
+
char keybuf[64];
|
|
2277
|
+
char *key = mlen < (int)sizeof(keybuf) ? keybuf : (char *)malloc(mlen + 1);
|
|
2278
|
+
memcpy(key, str + caps[0], mlen);
|
|
2279
|
+
key[mlen] = 0;
|
|
2280
|
+
const char *rep = sp_StrStrHash_has_key(h, key) ? sp_StrStrHash_get(h, key) : "";
|
|
2281
|
+
size_t rlen = strlen(rep);
|
|
2282
|
+
if (olen + before + rlen >= cap) { cap = (olen + before + rlen) * 2 + 64; out = (char *)realloc(out, cap); }
|
|
2283
|
+
memcpy(out + olen, str + pos, before); olen += before;
|
|
2284
|
+
memcpy(out + olen, rep, rlen); olen += rlen;
|
|
2285
|
+
if (key != keybuf) free(key);
|
|
2286
|
+
if (caps[0] == caps[1]) {
|
|
2287
|
+
/* Zero-width match: keep the source char at this position and step
|
|
2288
|
+
past it (see sp_re_gsub for the rationale). */
|
|
2289
|
+
if (caps[1] < slen) {
|
|
2290
|
+
if (olen + 1 >= cap) { cap = olen * 2 + 64; out = (char *)realloc(out, cap); }
|
|
2291
|
+
out[olen++] = str[caps[1]];
|
|
2292
|
+
}
|
|
2293
|
+
pos = caps[1] + 1;
|
|
2294
|
+
} else {
|
|
2295
|
+
pos = caps[1];
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
if (pos < slen) {
|
|
2299
|
+
size_t rest = slen - pos;
|
|
2300
|
+
if (olen + rest >= cap) { cap = olen + rest + 1; out = (char *)realloc(out, cap); }
|
|
2301
|
+
memcpy(out + olen, str + pos, rest); olen += rest;
|
|
713
2302
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
2303
|
+
char *res = sp_str_alloc(olen);
|
|
2304
|
+
memcpy(res, out, olen);
|
|
2305
|
+
free(out);
|
|
2306
|
+
return res;
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
/* Issue #910: sub(regex, hash) — same lookup semantics as
|
|
2310
|
+
sp_re_gsub_str_str_hash but only the first match. */
|
|
2311
|
+
static const char *sp_re_sub_str_str_hash(mrb_regexp_pattern *pat, const char *str, sp_StrStrHash *h) {
|
|
2312
|
+
int64_t slen = (int64_t)strlen(str);
|
|
2313
|
+
int caps[64];
|
|
2314
|
+
int n = re_exec(pat, str, slen, 0, caps, 64);
|
|
2315
|
+
if (n <= 0 || caps[0] < 0) return str;
|
|
2316
|
+
int mlen = caps[1] - caps[0];
|
|
2317
|
+
char keybuf[64];
|
|
2318
|
+
char *key = mlen < (int)sizeof(keybuf) ? keybuf : (char *)malloc(mlen + 1);
|
|
2319
|
+
memcpy(key, str + caps[0], mlen);
|
|
2320
|
+
key[mlen] = 0;
|
|
2321
|
+
const char *rep = (h && sp_StrStrHash_has_key(h, key)) ? sp_StrStrHash_get(h, key) : "";
|
|
2322
|
+
size_t rlen = strlen(rep);
|
|
2323
|
+
size_t rest = slen - caps[1];
|
|
2324
|
+
size_t total = caps[0] + rlen + rest;
|
|
2325
|
+
char *out = sp_str_alloc_raw(total + 1);
|
|
2326
|
+
memcpy(out, str, caps[0]);
|
|
2327
|
+
memcpy(out + caps[0], rep, rlen);
|
|
2328
|
+
memcpy(out + caps[0] + rlen, str + caps[1], rest);
|
|
2329
|
+
out[total] = 0;
|
|
2330
|
+
if (key != keybuf) free(key);
|
|
2331
|
+
return out;
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
/* Issue #910: sub(string, hash) — literal-substring pattern
|
|
2335
|
+
with a hash replacement. Replaces only the first match. */
|
|
2336
|
+
static const char *sp_str_sub_str_str_hash(const char *str, const char *pat, sp_StrStrHash *h) {
|
|
2337
|
+
if (!str || !pat) return str;
|
|
2338
|
+
const char *found = strstr(str, pat);
|
|
2339
|
+
if (!found) return str;
|
|
2340
|
+
size_t before = (size_t)(found - str);
|
|
2341
|
+
size_t plen = strlen(pat);
|
|
2342
|
+
const char *rep = (h && sp_StrStrHash_has_key(h, pat)) ? sp_StrStrHash_get(h, pat) : "";
|
|
2343
|
+
size_t rlen = strlen(rep);
|
|
2344
|
+
size_t rest = strlen(str) - before - plen;
|
|
2345
|
+
size_t total = before + rlen + rest;
|
|
2346
|
+
char *out = sp_str_alloc_raw(total + 1);
|
|
2347
|
+
memcpy(out, str, before);
|
|
2348
|
+
memcpy(out + before, rep, rlen);
|
|
2349
|
+
memcpy(out + before + rlen, found + plen, rest);
|
|
2350
|
+
out[total] = 0;
|
|
2351
|
+
return out;
|
|
718
2352
|
}
|
|
719
2353
|
|
|
720
2354
|
static const char *sp_re_sub(mrb_regexp_pattern *pat, const char *str, const char *rep) {
|
|
@@ -722,12 +2356,21 @@ static const char *sp_re_sub(mrb_regexp_pattern *pat, const char *str, const cha
|
|
|
722
2356
|
int caps[64];
|
|
723
2357
|
int n = re_exec(pat, str, slen, 0, caps, 64);
|
|
724
2358
|
if (n <= 0 || caps[0] < 0) return str;
|
|
725
|
-
|
|
726
|
-
|
|
2359
|
+
/* Issue #855: expand `\1`..`\9` / `\&` from rep against caps. */
|
|
2360
|
+
size_t cap = caps[0] + rlen * 4 + (slen - caps[1]) + 64;
|
|
2361
|
+
/* malloc scratch: sp_re_expand_rep and the tail grow it with realloc,
|
|
2362
|
+
which needs a real malloc base. Exact-sized string emitted below. */
|
|
2363
|
+
char *out = (char *)malloc(cap);
|
|
727
2364
|
memcpy(out, str, caps[0]);
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
2365
|
+
size_t olen = caps[0];
|
|
2366
|
+
sp_re_expand_rep(&out, &olen, &cap, rep, rlen, str, caps, n);
|
|
2367
|
+
size_t rest = slen - caps[1];
|
|
2368
|
+
if (olen + rest + 1 >= cap) { cap = olen + rest + 64; out = (char*)realloc(out, cap); }
|
|
2369
|
+
memcpy(out+olen, str+caps[1], rest); olen += rest;
|
|
2370
|
+
char *res = sp_str_alloc(olen);
|
|
2371
|
+
memcpy(res, out, olen);
|
|
2372
|
+
free(out);
|
|
2373
|
+
return res;
|
|
731
2374
|
}
|
|
732
2375
|
|
|
733
2376
|
static sp_StrArray *sp_re_scan(mrb_regexp_pattern *pat, const char *str) {
|
|
@@ -755,6 +2398,20 @@ static sp_StrArray *sp_re_split(mrb_regexp_pattern *pat, const char *str) {
|
|
|
755
2398
|
}
|
|
756
2399
|
int len = caps[0] - pos; char *m = sp_str_alloc_raw(len+1);
|
|
757
2400
|
memcpy(m, str+pos, len); m[len] = 0; sp_StrArray_push(arr, m);
|
|
2401
|
+
/* Issue #856: when the splitter regex has capture groups,
|
|
2402
|
+
Ruby splices each captured substring into the result
|
|
2403
|
+
between the surrounding segments (caps[2..n-1] hold groups
|
|
2404
|
+
1..(n/2-1); group 0 is the whole match). */
|
|
2405
|
+
for (int gi = 1; gi * 2 + 1 < n; gi++) {
|
|
2406
|
+
if (caps[gi*2] >= 0 && caps[gi*2+1] >= 0) {
|
|
2407
|
+
int glen = caps[gi*2+1] - caps[gi*2];
|
|
2408
|
+
char *gm = sp_str_alloc_raw(glen+1);
|
|
2409
|
+
memcpy(gm, str + caps[gi*2], glen); gm[glen] = 0;
|
|
2410
|
+
sp_StrArray_push(arr, gm);
|
|
2411
|
+
} else {
|
|
2412
|
+
sp_StrArray_push(arr, sp_str_empty);
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
758
2415
|
pos = caps[1]; if (caps[0] == caps[1]) pos++;
|
|
759
2416
|
}
|
|
760
2417
|
return arr;
|
|
@@ -762,39 +2419,47 @@ static sp_StrArray *sp_re_split(mrb_regexp_pattern *pat, const char *str) {
|
|
|
762
2419
|
|
|
763
2420
|
/* NaN-boxed polymorphic value */
|
|
764
2421
|
typedef uint64_t sp_RbValue;
|
|
765
|
-
#define SP_TAG_INT
|
|
766
|
-
#define SP_TAG_STR
|
|
767
|
-
#define SP_TAG_FLT
|
|
2422
|
+
#define SP_TAG_INT 0
|
|
2423
|
+
#define SP_TAG_STR 1
|
|
2424
|
+
#define SP_TAG_FLT 2
|
|
768
2425
|
#define SP_TAG_BOOL 3
|
|
769
|
-
#define SP_TAG_NIL
|
|
770
|
-
#define SP_TAG_OBJ
|
|
771
|
-
#define SP_TAG_SYM
|
|
2426
|
+
#define SP_TAG_NIL 4
|
|
2427
|
+
#define SP_TAG_OBJ 5
|
|
2428
|
+
#define SP_TAG_SYM 6
|
|
2429
|
+
/* Class values boxed into a poly slot (e.g.
|
|
2430
|
+
as ancestors-array elements). cls_id of the sp_RbVal carries
|
|
2431
|
+
the boxed sp_Class's cls_id directly so unboxing is just a
|
|
2432
|
+
field read. */
|
|
2433
|
+
#define SP_TAG_CLASS 7
|
|
2434
|
+
/* Encoding values boxed into a poly slot. v.s carries the canonical
|
|
2435
|
+
encoding name (`"UTF-8"` in Spinel's current runtime model). */
|
|
2436
|
+
#define SP_TAG_ENCODING 8
|
|
772
2437
|
/* Negative cls_id values let SP_TAG_OBJ also carry built-in pointer
|
|
773
2438
|
types (IntArray, FloatArray, ...) — avoids minting a new SP_TAG_*
|
|
774
2439
|
per type. Non-negative cls_id stays an index into the user-class
|
|
775
2440
|
table as before. The element-type tag and the array cls_id are
|
|
776
2441
|
paired by `array_cls_id = -element_tag - 1`. */
|
|
777
2442
|
#define SP_BUILTIN_ARRAY_OF(tag) (-(tag) - 1)
|
|
778
|
-
#define SP_BUILTIN_INT_ARRAY
|
|
779
|
-
#define SP_BUILTIN_STR_ARRAY
|
|
780
|
-
#define SP_BUILTIN_FLT_ARRAY
|
|
781
|
-
#define SP_BUILTIN_PTR_ARRAY
|
|
782
|
-
#define SP_BUILTIN_SYM_ARRAY
|
|
783
|
-
#define SP_BUILTIN_PROC
|
|
784
|
-
#define SP_BUILTIN_RANGE
|
|
785
|
-
#define SP_BUILTIN_TIME
|
|
786
|
-
#define SP_BUILTIN_POLY_ARRAY
|
|
2443
|
+
#define SP_BUILTIN_INT_ARRAY SP_BUILTIN_ARRAY_OF(SP_TAG_INT) /* -1 */
|
|
2444
|
+
#define SP_BUILTIN_STR_ARRAY SP_BUILTIN_ARRAY_OF(SP_TAG_STR) /* -2 */
|
|
2445
|
+
#define SP_BUILTIN_FLT_ARRAY SP_BUILTIN_ARRAY_OF(SP_TAG_FLT) /* -3 */
|
|
2446
|
+
#define SP_BUILTIN_PTR_ARRAY SP_BUILTIN_ARRAY_OF(SP_TAG_OBJ) /* -6 */
|
|
2447
|
+
#define SP_BUILTIN_SYM_ARRAY SP_BUILTIN_ARRAY_OF(SP_TAG_SYM) /* -7 */
|
|
2448
|
+
#define SP_BUILTIN_PROC (-9) /* sp_Proc *, distinct from any tag-based id */
|
|
2449
|
+
#define SP_BUILTIN_RANGE (-10) /* sp_Range *, heap copy of stack-typed sp_Range when crossing into poly */
|
|
2450
|
+
#define SP_BUILTIN_TIME (-11) /* sp_Time *, heap copy of stack-typed sp_Time when crossing into poly */
|
|
2451
|
+
#define SP_BUILTIN_POLY_ARRAY (-12) /* sp_PolyArray *, array of sp_RbVal */
|
|
787
2452
|
/* Hash variant cls_ids — boxed into the cls_id of a poly slot so
|
|
788
2453
|
Hash#dig can recover the concrete hash type at runtime. */
|
|
789
|
-
#define SP_BUILTIN_STR_INT_HASH
|
|
790
|
-
#define SP_BUILTIN_STR_STR_HASH
|
|
791
|
-
#define SP_BUILTIN_INT_STR_HASH
|
|
792
|
-
#define SP_BUILTIN_SYM_INT_HASH
|
|
793
|
-
#define SP_BUILTIN_SYM_STR_HASH
|
|
2454
|
+
#define SP_BUILTIN_STR_INT_HASH (-13)
|
|
2455
|
+
#define SP_BUILTIN_STR_STR_HASH (-14)
|
|
2456
|
+
#define SP_BUILTIN_INT_STR_HASH (-15)
|
|
2457
|
+
#define SP_BUILTIN_SYM_INT_HASH (-16)
|
|
2458
|
+
#define SP_BUILTIN_SYM_STR_HASH (-17)
|
|
794
2459
|
#define SP_BUILTIN_STR_POLY_HASH (-18)
|
|
795
2460
|
#define SP_BUILTIN_SYM_POLY_HASH (-19)
|
|
796
2461
|
#define SP_BUILTIN_POLY_POLY_HASH (-20)
|
|
797
|
-
|
|
2462
|
+
/* sp_RbVal is defined in sp_gc.h (the mark helpers dispatch on its tag). */
|
|
798
2463
|
static sp_RbVal sp_box_int(mrb_int v) { sp_RbVal r; r.tag = SP_TAG_INT; r.cls_id = 0; r.v.i = v; return r; }
|
|
799
2464
|
static sp_RbVal sp_box_str(const char *v) { sp_RbVal r; r.tag = SP_TAG_STR; r.cls_id = 0; r.v.s = v; return r; }
|
|
800
2465
|
static sp_RbVal sp_box_float(mrb_float v) { sp_RbVal r; r.tag = SP_TAG_FLT; r.cls_id = 0; r.v.f = v; return r; }
|
|
@@ -802,7 +2467,98 @@ static sp_RbVal sp_box_bool(mrb_bool v) { sp_RbVal r; r.tag = SP_TAG_BOOL; r.cls
|
|
|
802
2467
|
static sp_RbVal sp_box_nil(void) { sp_RbVal r; r.tag = SP_TAG_NIL; r.cls_id = 0; r.v.i = 0; return r; }
|
|
803
2468
|
static sp_RbVal sp_box_obj(void *p, int cls_id) { sp_RbVal r; r.tag = SP_TAG_OBJ; r.cls_id = cls_id; r.v.p = p; return r; }
|
|
804
2469
|
static sp_RbVal sp_box_sym(sp_sym v) { sp_RbVal r; r.tag = SP_TAG_SYM; r.cls_id = 0; r.v.i = (mrb_int)v; return r; }
|
|
2470
|
+
/* box a sp_Class into a poly slot. */
|
|
2471
|
+
static sp_RbVal sp_box_class(sp_Class c) { sp_RbVal r; r.tag = SP_TAG_CLASS; r.cls_id = (int)c.cls_id; r.v.i = c.cls_id; return r; }
|
|
2472
|
+
static sp_RbVal sp_box_encoding(sp_Encoding e) { sp_RbVal r; r.tag = SP_TAG_ENCODING; r.cls_id = 0; r.v.s = sp_encoding_name(e); return r; }
|
|
2473
|
+
|
|
2474
|
+
/* every non-value-type sp_<C> starts
|
|
2475
|
+
with `mrb_int cls_id`. Read it back from a void* when
|
|
2476
|
+
the static type at the call site has lost the cls_id (e.g.
|
|
2477
|
+
sp_PtrArray_get returning void*). NULL-safe via the guard
|
|
2478
|
+
on p; expects p to actually point at a user-class struct
|
|
2479
|
+
when non-NULL. */
|
|
2480
|
+
static inline mrb_int sp_obj_cls_id_of(void *p) { return p ? *(mrb_int *)p : 0; }
|
|
805
2481
|
static sp_RbVal sp_box_nullable_str(const char *v) { return v ? sp_box_str(v) : sp_box_nil(); }
|
|
2482
|
+
/* String#index / #rindex return a boxed nil for not-found, boxed
|
|
2483
|
+
int for found. Issue #532: typed-int slot can't represent CRuby's
|
|
2484
|
+
nil-vs-real-index distinction in-band; widening the result type
|
|
2485
|
+
to sp_RbVal at the call site lets `pos.nil?` and `puts pos.inspect`
|
|
2486
|
+
work via the standard poly-tag dispatch. The -1 sentinel comes
|
|
2487
|
+
from the underlying sp_str_*_index helpers; we widen here at the
|
|
2488
|
+
boxing layer so existing call sites that want the raw int still
|
|
2489
|
+
work via `sp_str_index` directly. */
|
|
2490
|
+
static sp_RbVal sp_str_index_poly(const char *s, const char *sub) { mrb_int n = sp_str_index(s, sub); return n < 0 ? sp_box_nil() : sp_box_int(n); }
|
|
2491
|
+
static sp_RbVal sp_str_index_from_poly(const char *s, const char *sub, mrb_int start) { mrb_int n = sp_str_index_from(s, sub, start); return n < 0 ? sp_box_nil() : sp_box_int(n); }
|
|
2492
|
+
static sp_RbVal sp_str_rindex_poly(const char *s, const char *sub) { mrb_int n = sp_str_rindex(s, sub); return n < 0 ? sp_box_nil() : sp_box_int(n); }
|
|
2493
|
+
/* int? siblings of the String#index family. Same not-found
|
|
2494
|
+
semantics as the _poly variants, but the result is the int?
|
|
2495
|
+
sentinel (SP_INT_NIL) rather than a boxed sp_RbVal — keeps the
|
|
2496
|
+
`i = s.index(sub); return ... if i.nil?; i + 1` idiom on the
|
|
2497
|
+
direct integer arithmetic path. */
|
|
2498
|
+
static mrb_int sp_str_index_opt(const char *s, const char *sub) { mrb_int n = sp_str_index(s, sub); return n < 0 ? SP_INT_NIL : n; }
|
|
2499
|
+
static mrb_int sp_str_index_from_opt(const char *s, const char *sub, mrb_int start) { mrb_int n = sp_str_index_from(s, sub, start); return n < 0 ? SP_INT_NIL : n; }
|
|
2500
|
+
static mrb_int sp_str_rindex_opt(const char *s, const char *sub) { mrb_int n = sp_str_rindex(s, sub); return n < 0 ? SP_INT_NIL : n; }
|
|
2501
|
+
static sp_RbVal sp_re_rindex_poly(mrb_regexp_pattern *pat, const char *str) { mrb_int n = sp_re_rindex(pat, str); return n < 0 ? sp_box_nil() : sp_box_int(n); }
|
|
2502
|
+
/* `s.index(regex)` -- first match start (byte offset, as sp_re_match reports;
|
|
2503
|
+
matches the rindex regex variant, which also reports bytes). sp_re_match
|
|
2504
|
+
sets $~ as a side effect, which CRuby's String#index(regex) also does.
|
|
2505
|
+
Boxed Integer | nil. The plain-string path would lower the regex pattern to
|
|
2506
|
+
0 and feed a bogus arg to sp_str_index_opt. */
|
|
2507
|
+
static sp_RbVal sp_re_index_poly(mrb_regexp_pattern *pat, const char *str) { mrb_int n = sp_re_match(pat, str); return n < 0 ? sp_box_nil() : sp_box_int(n); }
|
|
2508
|
+
/* CRuby-compatible =~ wrapper: SP_TAG_INT(pos) on match, SP_TAG_NIL
|
|
2509
|
+
on miss. Codegen routes the `=~` operator here so
|
|
2510
|
+
`("abc" =~ /xyz/).nil?` answers true and `puts("abc" =~ /xyz/)`
|
|
2511
|
+
prints an empty line, matching CRuby. The raw `sp_re_match`
|
|
2512
|
+
(returning -1) stays available for internal callers needing the
|
|
2513
|
+
sentinel form. */
|
|
2514
|
+
static sp_RbVal sp_re_match_poly(mrb_regexp_pattern *pat, const char *str) { mrb_int n = sp_re_match(pat, str); return n < 0 ? sp_box_nil() : sp_box_int(n); }
|
|
2515
|
+
|
|
2516
|
+
/* Regexp.escape(s) / Regexp.quote(s) -- prefix every regex metachar
|
|
2517
|
+
and whitespace byte with a single backslash, returning a heap
|
|
2518
|
+
string that callers can feed into `Regexp.new(...)` to match the
|
|
2519
|
+
original bytes literally. Matches CRuby's rb_reg_quote for the
|
|
2520
|
+
ASCII range (the only range Spinel's regex engine indexes today,
|
|
2521
|
+
so multibyte passes through unchanged).
|
|
2522
|
+
|
|
2523
|
+
The metachars covered: \\ . ? * + ^ $ | ( ) [ ] { } # -
|
|
2524
|
+
The whitespace covered: ' ' \t \n \r \f \v
|
|
2525
|
+
Everything else copies byte-for-byte. */
|
|
2526
|
+
static const char *sp_re_escape(const char *src) {
|
|
2527
|
+
size_t i, in_len = strlen(src);
|
|
2528
|
+
size_t out_len = 0;
|
|
2529
|
+
for (i = 0; i < in_len; i++) {
|
|
2530
|
+
unsigned char c = (unsigned char)src[i];
|
|
2531
|
+
if (c == '\\' || c == '.' || c == '?' || c == '*' || c == '+' ||
|
|
2532
|
+
c == '^' || c == '$' || c == '|' || c == '(' || c == ')' ||
|
|
2533
|
+
c == '[' || c == ']' || c == '{' || c == '}' || c == '#' ||
|
|
2534
|
+
c == '-' || c == ' ' || c == '\t' || c == '\n' || c == '\r' ||
|
|
2535
|
+
c == '\f' || c == '\v') {
|
|
2536
|
+
out_len += 2;
|
|
2537
|
+
} else {
|
|
2538
|
+
out_len += 1;
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
if (out_len == in_len) {
|
|
2542
|
+
return src;
|
|
2543
|
+
}
|
|
2544
|
+
char *buf = sp_str_alloc(out_len);
|
|
2545
|
+
size_t j = 0;
|
|
2546
|
+
for (i = 0; i < in_len; i++) {
|
|
2547
|
+
unsigned char c = (unsigned char)src[i];
|
|
2548
|
+
if (c == '\\' || c == '.' || c == '?' || c == '*' || c == '+' ||
|
|
2549
|
+
c == '^' || c == '$' || c == '|' || c == '(' || c == ')' ||
|
|
2550
|
+
c == '[' || c == ']' || c == '{' || c == '}' || c == '#' ||
|
|
2551
|
+
c == '-' || c == ' ' || c == '\t' || c == '\n' || c == '\r' ||
|
|
2552
|
+
c == '\f' || c == '\v') {
|
|
2553
|
+
buf[j++] = '\\';
|
|
2554
|
+
buf[j++] = (char)c;
|
|
2555
|
+
} else {
|
|
2556
|
+
buf[j++] = (char)c;
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
buf[j] = 0;
|
|
2560
|
+
return buf;
|
|
2561
|
+
}
|
|
806
2562
|
static sp_RbVal sp_box_nullable_obj(void *p, int cls_id) { return p ? sp_box_obj(p, cls_id) : sp_box_nil(); }
|
|
807
2563
|
/* Built-in pointer boxes — share SP_TAG_OBJ with a reserved negative
|
|
808
2564
|
cls_id so the dispatch path is uniform. */
|
|
@@ -812,6 +2568,41 @@ static sp_RbVal sp_box_str_array(void *p) { return sp_box_obj(p, SP_BUILTIN_ST
|
|
|
812
2568
|
static sp_RbVal sp_box_sym_array(void *p) { return sp_box_obj(p, SP_BUILTIN_SYM_ARRAY); }
|
|
813
2569
|
static sp_RbVal sp_box_ptr_array(void *p) { return sp_box_obj(p, SP_BUILTIN_PTR_ARRAY); }
|
|
814
2570
|
static sp_RbVal sp_box_proc(void *p) { return sp_box_obj(p, SP_BUILTIN_PROC); }
|
|
2571
|
+
|
|
2572
|
+
/* CRuby-compatible Array#index / #rindex / #find_index: returns
|
|
2573
|
+
sp_RbVal (nil tag for not-found, int tag with the position when
|
|
2574
|
+
found). spinel's raw `_index` helpers return the -1 sentinel,
|
|
2575
|
+
which diverges from CRuby's nil. Codegen routes
|
|
2576
|
+
`arr.index(x)` / `arr.find_index(x)` / `arr.rindex(x)` through
|
|
2577
|
+
these `_poly` wrappers so `.nil?` / `== nil` / `inspect` etc.
|
|
2578
|
+
on the result behave the CRuby way. Sibling to
|
|
2579
|
+
`sp_str_index_poly` above; same widening rationale.
|
|
2580
|
+
Issue raised during #585 follow-up: spinel positions itself
|
|
2581
|
+
as a Ruby SUBSET, so documented Ruby APIs must match CRuby
|
|
2582
|
+
behavior. */
|
|
2583
|
+
static sp_RbVal sp_IntArray_index_poly(sp_IntArray *a, mrb_int v) { mrb_int n = sp_IntArray_index(a, v); return n < 0 ? sp_box_nil() : sp_box_int(n); }
|
|
2584
|
+
static sp_RbVal sp_IntArray_rindex_poly(sp_IntArray *a, mrb_int v) { mrb_int n = sp_IntArray_rindex(a, v); return n < 0 ? sp_box_nil() : sp_box_int(n); }
|
|
2585
|
+
static sp_RbVal sp_StrArray_index_poly(sp_StrArray *a, const char *v) { mrb_int n = sp_StrArray_index(a, v); return n < 0 ? sp_box_nil() : sp_box_int(n); }
|
|
2586
|
+
static sp_RbVal sp_StrArray_rindex_poly(sp_StrArray *a, const char *v) { mrb_int n = sp_StrArray_rindex(a, v); return n < 0 ? sp_box_nil() : sp_box_int(n); }
|
|
2587
|
+
/* int? siblings of the *_index_poly wrappers above. Same not-found
|
|
2588
|
+
semantics, but return the int? sentinel (SP_INT_NIL) instead of
|
|
2589
|
+
boxing into sp_RbVal. Used when the call site's static type
|
|
2590
|
+
tracking carries the result as int? rather than poly — eliminates
|
|
2591
|
+
the box/unbox round-trip for the common `i = arr.index(x);
|
|
2592
|
+
i.nil? ? ... : <use i as int>` idiom. */
|
|
2593
|
+
static mrb_int sp_IntArray_index_opt(sp_IntArray *a, mrb_int v) { mrb_int n = sp_IntArray_index(a, v); return n < 0 ? SP_INT_NIL : n; }
|
|
2594
|
+
static mrb_int sp_IntArray_rindex_opt(sp_IntArray *a, mrb_int v) { mrb_int n = sp_IntArray_rindex(a, v); return n < 0 ? SP_INT_NIL : n; }
|
|
2595
|
+
/* Inspect / to_s for an int? value. CRuby distinguishes the two on
|
|
2596
|
+
nil: `nil.to_s` is "" while `nil.inspect` is "nil". For a real
|
|
2597
|
+
integer they agree (Integer#to_s and #inspect are both the decimal
|
|
2598
|
+
form). Two wrappers keep call-site emit local. */
|
|
2599
|
+
static const char *sp_int_opt_inspect(mrb_int v) { return sp_int_is_nil(v) ? "nil" : sp_int_to_s(v); }
|
|
2600
|
+
static const char *sp_int_opt_to_s(mrb_int v) { return sp_int_is_nil(v) ? "" : sp_int_to_s(v); }
|
|
2601
|
+
/* float? (nullable float) counterparts: a non-nil value formats exactly
|
|
2602
|
+
like a plain Float (delegates to sp_float_to_s), nil renders "nil"
|
|
2603
|
+
(inspect) / "" (to_s). */
|
|
2604
|
+
static const char *sp_float_opt_inspect(mrb_float v) { return sp_float_is_nil(v) ? "nil" : sp_float_to_s(v); }
|
|
2605
|
+
static const char *sp_float_opt_to_s(mrb_float v) { return sp_float_is_nil(v) ? "" : sp_float_to_s(v); }
|
|
815
2606
|
/* sp_Range is a 16-byte value type that doesn't fit in sp_RbVal's union
|
|
816
2607
|
(max 8 bytes). When a Range crosses into a poly slot (heterogeneous
|
|
817
2608
|
hash / array / param / ivar), copy it onto the GC heap and box the
|
|
@@ -823,9 +2614,10 @@ static sp_RbVal sp_box_range(sp_Range v) {
|
|
|
823
2614
|
return sp_box_obj(p, SP_BUILTIN_RANGE);
|
|
824
2615
|
}
|
|
825
2616
|
static const char *sp_Range_inspect(sp_Range *r) {
|
|
826
|
-
/* "first..last" form. Buffer sized for two int64s
|
|
2617
|
+
/* "first..last" / "first...last" form. Buffer sized for two int64s
|
|
2618
|
+
plus the dots. */
|
|
827
2619
|
char *buf = sp_str_alloc_raw(48);
|
|
828
|
-
snprintf(buf, 48, "%lld..%lld", (long long)r->first, (long long)r->last);
|
|
2620
|
+
snprintf(buf, 48, r->excl ? "%lld...%lld" : "%lld..%lld", (long long)r->first, (long long)r->last);
|
|
829
2621
|
return buf;
|
|
830
2622
|
}
|
|
831
2623
|
/* Same heap-box rationale as sp_Range: sp_Time is 12+ bytes (tv_sec +
|
|
@@ -852,7 +2644,8 @@ static const char *sp_Time_inspect(sp_Time *t) {
|
|
|
852
2644
|
return buf;
|
|
853
2645
|
}
|
|
854
2646
|
static sp_RbVal sp_box_poly_array(void *p) { return sp_box_obj(p, SP_BUILTIN_POLY_ARRAY); }
|
|
855
|
-
static
|
|
2647
|
+
static const char *sp_class_to_s(sp_Class c); /* fwd decl: sp_poly_puts' SP_TAG_CLASS arm */
|
|
2648
|
+
static inline void sp_poly_puts(sp_RbVal v) {
|
|
856
2649
|
switch (v.tag) {
|
|
857
2650
|
case SP_TAG_INT: printf("%lld\n", (long long)v.v.i); break;
|
|
858
2651
|
case SP_TAG_STR: if (v.v.s) { fputs(v.v.s, stdout); if (!*v.v.s || v.v.s[strlen(v.v.s)-1] != '\n') putchar('\n'); } else putchar('\n'); break;
|
|
@@ -860,17 +2653,42 @@ static void sp_poly_puts(sp_RbVal v) {
|
|
|
860
2653
|
case SP_TAG_BOOL: puts(v.v.b ? "true" : "false"); break;
|
|
861
2654
|
case SP_TAG_NIL: putchar('\n'); break;
|
|
862
2655
|
case SP_TAG_SYM: { const char *_ss = sp_sym_to_s((sp_sym)v.v.i); fputs(_ss, stdout); putchar('\n'); break; }
|
|
2656
|
+
case SP_TAG_ENCODING: { const char *_es = v.v.s ? v.v.s : sp_str_empty; fputs(_es, stdout); putchar('\n'); break; }
|
|
2657
|
+
case SP_TAG_CLASS: { sp_Class _c = {v.v.i}; fputs(sp_class_to_s(_c), stdout); putchar('\n'); break; }
|
|
863
2658
|
case SP_TAG_OBJ: {
|
|
864
|
-
/*
|
|
865
|
-
(
|
|
866
|
-
|
|
867
|
-
since we don't have per-class inspect dispatch here. */
|
|
2659
|
+
/* MRI's `puts arr` iterates an Array, printing one element per
|
|
2660
|
+
line (using to_s on each); a non-Array OBJ falls back to
|
|
2661
|
+
inspect / class-name. */
|
|
868
2662
|
switch (v.cls_id) {
|
|
869
|
-
case SP_BUILTIN_INT_ARRAY:
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
2663
|
+
case SP_BUILTIN_INT_ARRAY: {
|
|
2664
|
+
sp_IntArray *_a = (sp_IntArray *)v.v.p;
|
|
2665
|
+
for (mrb_int _i = 0; _i < _a->len; _i++)
|
|
2666
|
+
printf("%lld\n", (long long)_a->data[_a->start + _i]);
|
|
2667
|
+
break;
|
|
2668
|
+
}
|
|
2669
|
+
case SP_BUILTIN_FLT_ARRAY: {
|
|
2670
|
+
sp_FloatArray *_a = (sp_FloatArray *)v.v.p;
|
|
2671
|
+
for (mrb_int _i = 0; _i < _a->len; _i++) {
|
|
2672
|
+
fputs(sp_float_to_s(_a->data[_i]), stdout); putchar('\n');
|
|
2673
|
+
}
|
|
2674
|
+
break;
|
|
2675
|
+
}
|
|
2676
|
+
case SP_BUILTIN_STR_ARRAY: {
|
|
2677
|
+
sp_StrArray *_a = (sp_StrArray *)v.v.p;
|
|
2678
|
+
for (mrb_int _i = 0; _i < _a->len; _i++) {
|
|
2679
|
+
const char *_s = _a->data[_i];
|
|
2680
|
+
if (_s) { fputs(_s, stdout); if (!*_s || _s[strlen(_s)-1] != '\n') putchar('\n'); } else putchar('\n');
|
|
2681
|
+
}
|
|
2682
|
+
break;
|
|
2683
|
+
}
|
|
2684
|
+
case SP_BUILTIN_SYM_ARRAY: {
|
|
2685
|
+
sp_IntArray *_a = (sp_IntArray *)v.v.p;
|
|
2686
|
+
for (mrb_int _i = 0; _i < _a->len; _i++) {
|
|
2687
|
+
const char *_s = sp_sym_to_s((sp_sym)_a->data[_a->start + _i]);
|
|
2688
|
+
fputs(_s, stdout); putchar('\n');
|
|
2689
|
+
}
|
|
2690
|
+
break;
|
|
2691
|
+
}
|
|
874
2692
|
case SP_BUILTIN_RANGE: puts(sp_Range_inspect((sp_Range *)v.v.p)); break;
|
|
875
2693
|
case SP_BUILTIN_TIME: puts(sp_Time_inspect((sp_Time *)v.v.p)); break;
|
|
876
2694
|
default: printf("#<Object:0x%p>\n", v.v.p); break;
|
|
@@ -882,7 +2700,14 @@ static void sp_poly_puts(sp_RbVal v) {
|
|
|
882
2700
|
}
|
|
883
2701
|
static mrb_bool sp_poly_nil_p(sp_RbVal v) { return v.tag == SP_TAG_NIL; }
|
|
884
2702
|
static mrb_bool sp_poly_truthy(sp_RbVal v) { return !(v.tag == SP_TAG_NIL || (v.tag == SP_TAG_BOOL && !v.v.b)); }
|
|
885
|
-
|
|
2703
|
+
/* forward-declare the program-emitted class
|
|
2704
|
+
name lookup so sp_poly_to_s's SP_TAG_CLASS arm resolves.
|
|
2705
|
+
The codegen emits a 1-line stub when no class const is used,
|
|
2706
|
+
or the real body when @needs_class_table fires. The forward
|
|
2707
|
+
decl always needs a definition somewhere because -Werror
|
|
2708
|
+
trips on "used but never defined" otherwise. */
|
|
2709
|
+
static const char *sp_class_to_s(sp_Class c);
|
|
2710
|
+
static inline const char *sp_poly_to_s(sp_RbVal v) {
|
|
886
2711
|
switch (v.tag) {
|
|
887
2712
|
case SP_TAG_INT: return sp_int_to_s(v.v.i);
|
|
888
2713
|
case SP_TAG_STR: return v.v.s ? v.v.s : sp_str_empty;
|
|
@@ -890,6 +2715,8 @@ static const char *sp_poly_to_s(sp_RbVal v) {
|
|
|
890
2715
|
case SP_TAG_BOOL: return v.v.b ? SPL("true") : SPL("false");
|
|
891
2716
|
case SP_TAG_NIL: return sp_str_empty;
|
|
892
2717
|
case SP_TAG_SYM: return sp_sym_to_s((sp_sym)v.v.i);
|
|
2718
|
+
case SP_TAG_CLASS: { sp_Class c = {v.v.i}; return sp_class_to_s(c); }
|
|
2719
|
+
case SP_TAG_ENCODING: return v.v.s ? v.v.s : sp_str_empty;
|
|
893
2720
|
case SP_TAG_OBJ:
|
|
894
2721
|
switch (v.cls_id) {
|
|
895
2722
|
case SP_BUILTIN_INT_ARRAY: return sp_IntArray_inspect((sp_IntArray *)v.v.p);
|
|
@@ -910,7 +2737,7 @@ static sp_RbVal sp_poly_mul(sp_RbVal a, sp_RbVal b) { if (a.tag == SP_TAG_INT &&
|
|
|
910
2737
|
static mrb_int sp_poly_to_i(sp_RbVal v) { if (v.tag == SP_TAG_INT || v.tag == SP_TAG_SYM) return v.v.i; if (v.tag == SP_TAG_STR) return (mrb_int)strtoll(v.v.s ? v.v.s : sp_str_empty, NULL, 10); if (v.tag == SP_TAG_FLT) return (mrb_int)v.v.f; if (v.tag == SP_TAG_BOOL) return v.v.b ? 1 : 0; return 0; }
|
|
911
2738
|
static mrb_float sp_poly_to_f(sp_RbVal v) { if (v.tag == SP_TAG_FLT) return v.v.f; if (v.tag == SP_TAG_INT || v.tag == SP_TAG_SYM) return (mrb_float)v.v.i; if (v.tag == SP_TAG_BOOL) return v.v.b ? 1.0 : 0.0; return 0.0; }
|
|
912
2739
|
static mrb_bool sp_poly_numeric_p(sp_RbVal v) { return v.tag == SP_TAG_INT || v.tag == SP_TAG_FLT; }
|
|
913
|
-
static mrb_bool sp_poly_eq(sp_RbVal a, sp_RbVal b) { if (sp_poly_numeric_p(a) && sp_poly_numeric_p(b)) return sp_poly_to_f(a) == sp_poly_to_f(b); if (a.tag != b.tag) return FALSE; switch (a.tag) { case SP_TAG_INT: return a.v.i == b.v.i; case SP_TAG_STR: return (a.v.s == NULL || b.v.s == NULL) ? (a.v.s == b.v.s) : (strcmp(a.v.s, b.v.s) == 0); case SP_TAG_FLT: return a.v.f == b.v.f; case SP_TAG_BOOL: return a.v.b == b.v.b; case SP_TAG_NIL: return TRUE; case SP_TAG_SYM: return a.v.i == b.v.i; case SP_TAG_OBJ: return a.cls_id == b.cls_id && a.v.p == b.v.p; default: return FALSE; } }
|
|
2740
|
+
static mrb_bool sp_poly_eq(sp_RbVal a, sp_RbVal b) { if (sp_poly_numeric_p(a) && sp_poly_numeric_p(b)) return sp_poly_to_f(a) == sp_poly_to_f(b); if (a.tag != b.tag) return FALSE; switch (a.tag) { case SP_TAG_INT: return a.v.i == b.v.i; case SP_TAG_STR: return (a.v.s == NULL || b.v.s == NULL) ? (a.v.s == b.v.s) : (strcmp(a.v.s, b.v.s) == 0); case SP_TAG_FLT: return a.v.f == b.v.f; case SP_TAG_BOOL: return a.v.b == b.v.b; case SP_TAG_NIL: return TRUE; case SP_TAG_SYM: return a.v.i == b.v.i; case SP_TAG_ENCODING: return (a.v.s == NULL || b.v.s == NULL) ? (a.v.s == b.v.s) : (strcmp(a.v.s, b.v.s) == 0); case SP_TAG_OBJ: return a.cls_id == b.cls_id && a.v.p == b.v.p; case SP_TAG_CLASS: return a.v.i == b.v.i; default: return FALSE; } }
|
|
914
2741
|
static mrb_int sp_poly_cmp(sp_RbVal a, sp_RbVal b, mrb_bool *comparable) { if (sp_poly_numeric_p(a) && sp_poly_numeric_p(b)) { mrb_float af = sp_poly_to_f(a), bf = sp_poly_to_f(b); *comparable = TRUE; return (af > bf) - (af < bf); } if (a.tag == SP_TAG_STR && b.tag == SP_TAG_STR) { if (a.v.s == NULL || b.v.s == NULL) { *comparable = (a.v.s == b.v.s); return 0; } *comparable = TRUE; return strcmp(a.v.s, b.v.s); } if (a.tag == SP_TAG_SYM && b.tag == SP_TAG_SYM) { *comparable = TRUE; return (a.v.i > b.v.i) - (a.v.i < b.v.i); } *comparable = FALSE; return 0; }
|
|
915
2742
|
static mrb_bool sp_poly_lt(sp_RbVal a, sp_RbVal b) { mrb_bool comparable; mrb_int cmp = sp_poly_cmp(a, b, &comparable); return comparable ? (cmp < 0) : FALSE; }
|
|
916
2743
|
static mrb_bool sp_poly_le(sp_RbVal a, sp_RbVal b) { mrb_bool comparable; mrb_int cmp = sp_poly_cmp(a, b, &comparable); return comparable ? (cmp <= 0) : FALSE; }
|
|
@@ -918,7 +2745,13 @@ static mrb_bool sp_poly_gt(sp_RbVal a, sp_RbVal b) { mrb_bool comparable; mrb_in
|
|
|
918
2745
|
static mrb_bool sp_poly_ge(sp_RbVal a, sp_RbVal b) { mrb_bool comparable; mrb_int cmp = sp_poly_cmp(a, b, &comparable); return comparable ? (cmp >= 0) : FALSE; }
|
|
919
2746
|
static sp_RbVal sp_poly_div(sp_RbVal a, sp_RbVal b) { if (a.tag == SP_TAG_FLT || b.tag == SP_TAG_FLT) return sp_box_float(sp_poly_to_f(a) / sp_poly_to_f(b)); return sp_box_int(sp_idiv(sp_poly_to_i(a), sp_poly_to_i(b))); }
|
|
920
2747
|
static sp_RbVal sp_poly_mod(sp_RbVal a, sp_RbVal b) { if (a.tag == SP_TAG_FLT || b.tag == SP_TAG_FLT) return sp_box_float(fmod(sp_poly_to_f(a), sp_poly_to_f(b))); return sp_box_int(sp_imod(sp_poly_to_i(a), sp_poly_to_i(b))); }
|
|
921
|
-
|
|
2748
|
+
/* Integer #** : Spinel has no Rational, so a negative integer exponent --
|
|
2749
|
+
which CRuby evaluates to a Rational like (1/2) -- raises RangeError rather
|
|
2750
|
+
than silently truncating toward 0. Float ** negative stays a float
|
|
2751
|
+
(CRuby-compatible: 2.0 ** -1 == 0.5). See docs/INCOMPATIBILITIES.md. */
|
|
2752
|
+
static mrb_int sp_int_pow(mrb_int base, mrb_int exp) __attribute__((unused));
|
|
2753
|
+
static mrb_int sp_int_pow(mrb_int base, mrb_int exp) { if (exp < 0) sp_raise_cls("RangeError", "negative exponent"); return (mrb_int)pow((double)base, (double)exp); }
|
|
2754
|
+
static sp_RbVal sp_poly_pow(sp_RbVal a, sp_RbVal b) { if (a.tag == SP_TAG_INT && b.tag == SP_TAG_INT && b.v.i < 0) sp_raise_cls("RangeError", "negative exponent"); double r = pow((double)sp_poly_to_f(a), (double)sp_poly_to_f(b)); if (a.tag == SP_TAG_INT && b.tag == SP_TAG_INT && b.v.i >= 0) return sp_box_int((mrb_int)r); return sp_box_float((mrb_float)r); }
|
|
922
2755
|
/* sp_poly_shl is defined after sp_PolyArray_push (below) so the
|
|
923
2756
|
push-dispatch path can call it directly. The Integer-bit-shift
|
|
924
2757
|
semantics fall through when the recv isn't an array. */
|
|
@@ -930,12 +2763,207 @@ static sp_RbVal sp_poly_bxor(sp_RbVal a, sp_RbVal b) { return sp_box_int(sp_poly
|
|
|
930
2763
|
static sp_RbVal sp_poly_neg(sp_RbVal a) { if (a.tag == SP_TAG_FLT) return sp_box_float(-a.v.f); return sp_box_int(-sp_poly_to_i(a)); }
|
|
931
2764
|
|
|
932
2765
|
/* PolyArray: array of sp_RbVal */
|
|
933
|
-
typedef struct { sp_RbVal *data; mrb_int len; mrb_int cap; } sp_PolyArray;
|
|
934
|
-
|
|
2766
|
+
typedef struct { sp_RbVal *data; mrb_int len; mrb_int cap; mrb_int frozen; } sp_PolyArray;
|
|
2767
|
+
/* sp_mark_rbval: inline helper in sp_gc.h. */
|
|
2768
|
+
/* Definition of the root-entry marker forward-declared near
|
|
2769
|
+
sp_gc_mark_all: a low-bit-tagged slot is an sp_RbVal* root. */
|
|
2770
|
+
/* sp_gc_mark_root_entry is an inline helper in sp_gc.h. */
|
|
935
2771
|
static void sp_PolyArray_scan(void *p) { sp_PolyArray *a = (sp_PolyArray *)p; for (mrb_int i = 0; i < a->len; i++) sp_mark_rbval(a->data[i]); }
|
|
936
2772
|
static void sp_PolyArray_fin(void *p) { sp_PolyArray *a = (sp_PolyArray *)p; sp_gc_hdr *h = (sp_gc_hdr *)((char *)a - sizeof(sp_gc_hdr)); sp_gc_bytes -= sizeof(sp_RbVal) * a->cap; h->size -= sizeof(sp_RbVal) * a->cap; free(a->data); }
|
|
937
|
-
static sp_PolyArray *sp_PolyArray_new(void) { sp_PolyArray *a = (sp_PolyArray *)sp_gc_alloc(sizeof(sp_PolyArray), sp_PolyArray_fin, sp_PolyArray_scan); a->cap = 16; a->data = (sp_RbVal *)malloc(sizeof(sp_RbVal) * a->cap); a->len = 0; { sp_gc_hdr *h = (sp_gc_hdr *)((char *)a - sizeof(sp_gc_hdr)); h->size += sizeof(sp_RbVal) * a->cap; sp_gc_bytes += sizeof(sp_RbVal) * a->cap; } return a; }
|
|
938
|
-
static void sp_PolyArray_push(sp_PolyArray *a, sp_RbVal v) { if (a->len >= a->cap) { sp_gc_hdr *h = (sp_gc_hdr *)((char *)a - sizeof(sp_gc_hdr)); sp_gc_bytes -= sizeof(sp_RbVal) * a->cap; h->size -= sizeof(sp_RbVal) * a->cap; a->cap = a->cap * 2 + 1;
|
|
2773
|
+
static sp_PolyArray *sp_PolyArray_new(void) { sp_PolyArray *a = (sp_PolyArray *)sp_gc_alloc(sizeof(sp_PolyArray), sp_PolyArray_fin, sp_PolyArray_scan); a->cap = 16; a->data = (sp_RbVal *)malloc(sizeof(sp_RbVal) * a->cap); if (!a->data) sp_oom_die(); a->len = 0; { sp_gc_hdr *h = (sp_gc_hdr *)((char *)a - sizeof(sp_gc_hdr)); h->size += sizeof(sp_RbVal) * a->cap; sp_gc_bytes += sizeof(sp_RbVal) * a->cap; } return a; }
|
|
2774
|
+
static void sp_PolyArray_push(sp_PolyArray *a, sp_RbVal v) { if (!a) return; if (a->frozen) { sp_raise_frozen_array(); return; } if (a->len >= a->cap) { sp_gc_hdr *h = (sp_gc_hdr *)((char *)a - sizeof(sp_gc_hdr)); sp_gc_bytes -= sizeof(sp_RbVal) * a->cap; h->size -= sizeof(sp_RbVal) * a->cap; a->cap = a->cap * 2 + 1; void *nd = realloc(a->data, sizeof(sp_RbVal) * a->cap); if (!nd) sp_oom_die(); a->data = (sp_RbVal *)nd; h->size += sizeof(sp_RbVal) * a->cap; sp_gc_bytes += sizeof(sp_RbVal) * a->cap; } a->data[a->len++] = v; }
|
|
2775
|
+
static sp_PolyArray *sp_re_scan_poly(mrb_regexp_pattern *pat, const char *str) {
|
|
2776
|
+
sp_PolyArray *arr = sp_PolyArray_new();
|
|
2777
|
+
SP_GC_ROOT(arr);
|
|
2778
|
+
int64_t slen = (int64_t)strlen(str);
|
|
2779
|
+
int64_t pos = 0;
|
|
2780
|
+
int ncaps = 64;
|
|
2781
|
+
int caps[64];
|
|
2782
|
+
while (pos <= slen) {
|
|
2783
|
+
int n = re_exec(pat, str, slen, pos, caps, ncaps);
|
|
2784
|
+
if (n <= 0 || caps[0] < 0) break;
|
|
2785
|
+
int pairs = (n > ncaps ? ncaps : n) / 2;
|
|
2786
|
+
if (pairs <= 1) {
|
|
2787
|
+
int len = caps[1] - caps[0];
|
|
2788
|
+
char *m = sp_str_alloc_raw(len + 1);
|
|
2789
|
+
memcpy(m, str + caps[0], len);
|
|
2790
|
+
m[len] = 0;
|
|
2791
|
+
sp_PolyArray_push(arr, sp_box_str(m));
|
|
2792
|
+
} else {
|
|
2793
|
+
sp_PolyArray *row = sp_PolyArray_new();
|
|
2794
|
+
for (int gi = 1; gi < pairs; gi++) {
|
|
2795
|
+
if (caps[gi * 2] >= 0 && caps[gi * 2 + 1] >= 0) {
|
|
2796
|
+
int glen = caps[gi * 2 + 1] - caps[gi * 2];
|
|
2797
|
+
char *gm = sp_str_alloc_raw(glen + 1);
|
|
2798
|
+
memcpy(gm, str + caps[gi * 2], glen);
|
|
2799
|
+
gm[glen] = 0;
|
|
2800
|
+
sp_PolyArray_push(row, sp_box_str(gm));
|
|
2801
|
+
} else {
|
|
2802
|
+
sp_PolyArray_push(row, sp_box_nil());
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
sp_PolyArray_push(arr, sp_box_poly_array(row));
|
|
2806
|
+
}
|
|
2807
|
+
pos = caps[1];
|
|
2808
|
+
if (caps[0] == caps[1]) pos++;
|
|
2809
|
+
}
|
|
2810
|
+
return arr;
|
|
2811
|
+
}
|
|
2812
|
+
static sp_PolyArray *sp_re_match_data(mrb_regexp_pattern *pat, const char *str) {
|
|
2813
|
+
int64_t slen = (int64_t)strlen(str);
|
|
2814
|
+
int ncaps = 64;
|
|
2815
|
+
int n = re_exec(pat, str, slen, 0, sp_re_caps, ncaps);
|
|
2816
|
+
if (n <= 0 || sp_re_caps[0] < 0) {
|
|
2817
|
+
for (int i = 0; i < 10; i++) sp_re_captures[i] = NULL;
|
|
2818
|
+
sp_re_last_str = NULL;
|
|
2819
|
+
sp_re_match_str = NULL;
|
|
2820
|
+
sp_re_match_pre = NULL;
|
|
2821
|
+
sp_re_match_post = NULL;
|
|
2822
|
+
return NULL;
|
|
2823
|
+
}
|
|
2824
|
+
int pairs = (n > ncaps ? ncaps : n) / 2;
|
|
2825
|
+
sp_re_set_captures(str, sp_re_caps, pairs);
|
|
2826
|
+
sp_PolyArray *arr = sp_PolyArray_new();
|
|
2827
|
+
for (int i = 0; i < pairs; i++) {
|
|
2828
|
+
int start = sp_re_caps[i * 2];
|
|
2829
|
+
int end = sp_re_caps[i * 2 + 1];
|
|
2830
|
+
if (start >= 0 && end >= start) {
|
|
2831
|
+
int len = end - start;
|
|
2832
|
+
char *buf = sp_str_alloc_raw(len + 1);
|
|
2833
|
+
memcpy(buf, str + start, len);
|
|
2834
|
+
buf[len] = 0;
|
|
2835
|
+
sp_PolyArray_push(arr, sp_box_str(buf));
|
|
2836
|
+
} else {
|
|
2837
|
+
sp_PolyArray_push(arr, sp_box_nil());
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
return arr;
|
|
2841
|
+
}
|
|
2842
|
+
/* MatchData — holds the source string and the per-group byte offsets
|
|
2843
|
+
the engine produced. `[]`/captures extract substrings on demand;
|
|
2844
|
+
offset/begin/end report CHARACTER offsets (CRuby semantics), so
|
|
2845
|
+
byte offsets are converted via sp_str_count_chars. Group i occupies
|
|
2846
|
+
caps[2i] (begin) and caps[2i+1] (end); -1 marks a non-participating
|
|
2847
|
+
group. Issue #974. */
|
|
2848
|
+
typedef struct { const char *source; int caps[64]; int ncap; } sp_MatchData;
|
|
2849
|
+
static void sp_MatchData_scan(void *p) { sp_MatchData *m = (sp_MatchData *)p; if (m->source) sp_mark_string(m->source); }
|
|
2850
|
+
static sp_MatchData *sp_re_matchdata(mrb_regexp_pattern *pat, const char *str) {
|
|
2851
|
+
int64_t slen = (int64_t)strlen(str);
|
|
2852
|
+
int caps[64];
|
|
2853
|
+
int n = re_exec(pat, str, slen, 0, caps, 64);
|
|
2854
|
+
if (n <= 0 || caps[0] < 0) {
|
|
2855
|
+
for (int i = 0; i < 10; i++) sp_re_captures[i] = NULL;
|
|
2856
|
+
sp_re_last_str = NULL; sp_re_match_str = NULL;
|
|
2857
|
+
sp_re_match_pre = NULL; sp_re_match_post = NULL;
|
|
2858
|
+
return NULL;
|
|
2859
|
+
}
|
|
2860
|
+
int pairs = (n > 64 ? 64 : n) / 2;
|
|
2861
|
+
sp_re_set_captures(str, caps, pairs);
|
|
2862
|
+
sp_MatchData *m = (sp_MatchData *)sp_gc_alloc(sizeof(sp_MatchData), NULL, sp_MatchData_scan);
|
|
2863
|
+
m->source = str;
|
|
2864
|
+
m->ncap = pairs;
|
|
2865
|
+
for (int i = 0; i < pairs * 2; i++) m->caps[i] = caps[i];
|
|
2866
|
+
return m;
|
|
2867
|
+
}
|
|
2868
|
+
/* String#match(/re/, pos) — pos is a codepoint index (CRuby semantics). */
|
|
2869
|
+
static sp_MatchData *sp_re_matchdata_at(mrb_regexp_pattern *pat, const char *str, mrb_int cpos) {
|
|
2870
|
+
mrb_int cl = sp_str_length(str);
|
|
2871
|
+
if (cpos < 0) cpos += cl;
|
|
2872
|
+
if (cpos < 0 || cpos > cl) return NULL;
|
|
2873
|
+
size_t boff = sp_utf8_byte_offset(str, cpos);
|
|
2874
|
+
int64_t slen = (int64_t)strlen(str);
|
|
2875
|
+
int caps[64];
|
|
2876
|
+
int n = re_exec(pat, str, slen, (mrb_int)boff, caps, 64);
|
|
2877
|
+
if (n <= 0 || caps[0] < 0) {
|
|
2878
|
+
for (int i = 0; i < 10; i++) sp_re_captures[i] = NULL;
|
|
2879
|
+
sp_re_last_str = NULL; sp_re_match_str = NULL;
|
|
2880
|
+
sp_re_match_pre = NULL; sp_re_match_post = NULL;
|
|
2881
|
+
return NULL;
|
|
2882
|
+
}
|
|
2883
|
+
int pairs = (n > 64 ? 64 : n) / 2;
|
|
2884
|
+
sp_re_set_captures(str, caps, pairs);
|
|
2885
|
+
sp_MatchData *m = (sp_MatchData *)sp_gc_alloc(sizeof(sp_MatchData), NULL, sp_MatchData_scan);
|
|
2886
|
+
m->source = str;
|
|
2887
|
+
m->ncap = pairs;
|
|
2888
|
+
for (int i = 0; i < pairs * 2; i++) m->caps[i] = caps[i];
|
|
2889
|
+
return m;
|
|
2890
|
+
}
|
|
2891
|
+
/* group i substring, or NULL for a non-participating / out-of-range group */
|
|
2892
|
+
static const char *sp_MatchData_aref(sp_MatchData *m, mrb_int i) {
|
|
2893
|
+
if (!m || i < 0 || i >= m->ncap) return NULL;
|
|
2894
|
+
int s = m->caps[i * 2], e = m->caps[i * 2 + 1];
|
|
2895
|
+
if (s < 0 || e < s) return NULL;
|
|
2896
|
+
int len = e - s;
|
|
2897
|
+
char *b = sp_str_alloc((size_t)len);
|
|
2898
|
+
memcpy(b, m->source + s, len);
|
|
2899
|
+
b[len] = 0;
|
|
2900
|
+
sp_str_set_len(b, (size_t)len);
|
|
2901
|
+
return b;
|
|
2902
|
+
}
|
|
2903
|
+
static mrb_int sp_MatchData_length(sp_MatchData *m) { return m ? m->ncap : 0; }
|
|
2904
|
+
/* char offset of a byte position within source */
|
|
2905
|
+
static mrb_int sp_md_char_off(sp_MatchData *m, int byteoff) {
|
|
2906
|
+
if (byteoff < 0) return SP_INT_NIL;
|
|
2907
|
+
return sp_str_count_chars(m->source, (size_t)byteoff);
|
|
2908
|
+
}
|
|
2909
|
+
static mrb_int sp_MatchData_begin(sp_MatchData *m, mrb_int i) {
|
|
2910
|
+
if (!m || i < 0 || i >= m->ncap) return SP_INT_NIL;
|
|
2911
|
+
return sp_md_char_off(m, m->caps[i * 2]);
|
|
2912
|
+
}
|
|
2913
|
+
static mrb_int sp_MatchData_end(sp_MatchData *m, mrb_int i) {
|
|
2914
|
+
if (!m || i < 0 || i >= m->ncap) return SP_INT_NIL;
|
|
2915
|
+
return sp_md_char_off(m, m->caps[i * 2 + 1]);
|
|
2916
|
+
}
|
|
2917
|
+
static sp_IntArray *sp_MatchData_offset(sp_MatchData *m, mrb_int i) {
|
|
2918
|
+
sp_IntArray *a = sp_IntArray_new();
|
|
2919
|
+
if (!m || i < 0 || i >= m->ncap) { sp_IntArray_push(a, SP_INT_NIL); sp_IntArray_push(a, SP_INT_NIL); return a; }
|
|
2920
|
+
sp_IntArray_push(a, sp_md_char_off(m, m->caps[i * 2]));
|
|
2921
|
+
sp_IntArray_push(a, sp_md_char_off(m, m->caps[i * 2 + 1]));
|
|
2922
|
+
return a;
|
|
2923
|
+
}
|
|
2924
|
+
/* whole-match string (group 0) — also MatchData#to_s */
|
|
2925
|
+
static const char *sp_MatchData_to_s(sp_MatchData *m) { const char *r = sp_MatchData_aref(m, 0); return r ? r : sp_str_empty; }
|
|
2926
|
+
/* captures: groups 1..n-1 as a poly array (nil for non-participating) */
|
|
2927
|
+
static sp_PolyArray *sp_MatchData_captures(sp_MatchData *m) {
|
|
2928
|
+
sp_PolyArray *r = sp_PolyArray_new();
|
|
2929
|
+
if (!m) return r;
|
|
2930
|
+
SP_GC_ROOT(r);
|
|
2931
|
+
for (mrb_int i = 1; i < m->ncap; i++) {
|
|
2932
|
+
const char *g = sp_MatchData_aref(m, i);
|
|
2933
|
+
sp_PolyArray_push(r, g ? sp_box_str(g) : sp_box_nil());
|
|
2934
|
+
}
|
|
2935
|
+
return r;
|
|
2936
|
+
}
|
|
2937
|
+
/* to_a: group 0 + captures */
|
|
2938
|
+
static sp_PolyArray *sp_MatchData_to_a(sp_MatchData *m) {
|
|
2939
|
+
sp_PolyArray *r = sp_PolyArray_new();
|
|
2940
|
+
if (!m) return r;
|
|
2941
|
+
SP_GC_ROOT(r);
|
|
2942
|
+
for (mrb_int i = 0; i < m->ncap; i++) {
|
|
2943
|
+
const char *g = sp_MatchData_aref(m, i);
|
|
2944
|
+
sp_PolyArray_push(r, g ? sp_box_str(g) : sp_box_nil());
|
|
2945
|
+
}
|
|
2946
|
+
return r;
|
|
2947
|
+
}
|
|
2948
|
+
static const char *sp_MatchData_pre_match(sp_MatchData *m) {
|
|
2949
|
+
if (!m) return sp_str_empty;
|
|
2950
|
+
int e = m->caps[0];
|
|
2951
|
+
if (e <= 0) return sp_str_empty;
|
|
2952
|
+
char *b = sp_str_alloc((size_t)e);
|
|
2953
|
+
memcpy(b, m->source, e); b[e] = 0; sp_str_set_len(b, (size_t)e);
|
|
2954
|
+
return b;
|
|
2955
|
+
}
|
|
2956
|
+
static const char *sp_MatchData_post_match(sp_MatchData *m) {
|
|
2957
|
+
if (!m) return sp_str_empty;
|
|
2958
|
+
int s = m->caps[1];
|
|
2959
|
+
size_t sl = strlen(m->source);
|
|
2960
|
+
if (s < 0 || (size_t)s >= sl) return sp_str_empty;
|
|
2961
|
+
size_t len = sl - (size_t)s;
|
|
2962
|
+
char *b = sp_str_alloc(len);
|
|
2963
|
+
memcpy(b, m->source + s, len); b[len] = 0; sp_str_set_len(b, len);
|
|
2964
|
+
return b;
|
|
2965
|
+
}
|
|
2966
|
+
|
|
939
2967
|
static sp_RbVal sp_poly_shl(sp_RbVal a, sp_RbVal b) {
|
|
940
2968
|
/* Dispatch by recv cls_id: an IntArray / PolyArray / etc. boxed
|
|
941
2969
|
into a poly slot still wants Array#<< (push), not Integer#<<
|
|
@@ -959,17 +2987,20 @@ static sp_RbVal sp_poly_shl(sp_RbVal a, sp_RbVal b) {
|
|
|
959
2987
|
return a;
|
|
960
2988
|
}
|
|
961
2989
|
if (a.cls_id == SP_BUILTIN_STR_ARRAY) {
|
|
962
|
-
sp_StrArray_push((sp_StrArray *)a.v.p, b.tag == SP_TAG_STR ? (const char *)b.v.p :
|
|
2990
|
+
sp_StrArray_push((sp_StrArray *)a.v.p, b.tag == SP_TAG_STR ? (const char *)b.v.p : sp_str_empty);
|
|
963
2991
|
return a;
|
|
964
2992
|
}
|
|
965
2993
|
}
|
|
966
2994
|
return sp_box_int(sp_poly_to_i(a) << sp_poly_to_i(b));
|
|
967
2995
|
}
|
|
968
|
-
static mrb_int sp_PolyArray_length(sp_PolyArray *a) { return a->len; }
|
|
969
|
-
static sp_RbVal sp_PolyArray_get(sp_PolyArray *a, mrb_int i) { if (i < 0) i += a->len; return a->data[i]; }
|
|
970
|
-
|
|
2996
|
+
static mrb_int sp_PolyArray_length(sp_PolyArray *a) { if (!a) return 0; return a->len; }
|
|
2997
|
+
static sp_RbVal sp_PolyArray_get(sp_PolyArray *a, mrb_int i) { if (!a) return sp_box_nil(); if (i < 0) i += a->len; if (i < 0 || i >= a->len) return sp_box_nil(); return a->data[i]; }
|
|
2998
|
+
/* Issues #770, #789: NULL + bounds guard. Out-of-range set no-ops. */
|
|
2999
|
+
static void sp_PolyArray_set(sp_PolyArray *a, mrb_int i, sp_RbVal v) { if (!a) return; if (a->frozen) { sp_raise_frozen_array(); return; } mrb_int orig=i; if (i < 0) i += a->len; if (i < 0) sp_raise_cls("IndexError", sp_sprintf("index %lld too small for array; minimum: %lld",(long long)orig,(long long)-a->len)); if (i >= a->len) return; a->data[i] = v; }
|
|
971
3000
|
static sp_PolyArray *sp_PolyArray_slice(sp_PolyArray *a, mrb_int start, mrb_int len) { if (start < 0) start += a->len; if (start < 0) start = 0; sp_PolyArray *b = sp_PolyArray_new(); if (start >= a->len || len <= 0) return b; if (start + len > a->len) len = a->len - start; for (mrb_int i = 0; i < len; i++) sp_PolyArray_push(b, a->data[start + i]); return b; }
|
|
972
3001
|
static sp_PolyArray *sp_PolyArray_slice_bang(sp_PolyArray *a, mrb_int from, mrb_int n) {
|
|
3002
|
+
if (!a) return sp_PolyArray_new();
|
|
3003
|
+
if (a->frozen) { sp_raise_frozen_array(); return sp_PolyArray_new(); }
|
|
973
3004
|
if (from < 0) from += a->len;
|
|
974
3005
|
if (from < 0) from = 0;
|
|
975
3006
|
if (from > a->len) from = a->len;
|
|
@@ -982,30 +3013,278 @@ static sp_PolyArray *sp_PolyArray_slice_bang(sp_PolyArray *a, mrb_int from, mrb_
|
|
|
982
3013
|
return r;
|
|
983
3014
|
}
|
|
984
3015
|
static sp_PolyArray *sp_PolyArray_dup(sp_PolyArray *a) { sp_PolyArray *b = sp_PolyArray_new(); for (mrb_int i = 0; i < a->len; i++) sp_PolyArray_push(b, a->data[i]); return b; }
|
|
985
|
-
|
|
986
|
-
static
|
|
3016
|
+
/* Array#compact for poly_array: keep elements whose tag is not SP_TAG_NIL. */
|
|
3017
|
+
static sp_PolyArray *sp_PolyArray_compact(sp_PolyArray *a) { sp_PolyArray *b = sp_PolyArray_new(); if (!a) return b; for (mrb_int i = 0; i < a->len; i++) { if (a->data[i].tag != SP_TAG_NIL) sp_PolyArray_push(b, a->data[i]); } return b; }
|
|
3018
|
+
/* Issue #738: Hash#to_a as poly_array of [key, value] poly_array pairs. */
|
|
3019
|
+
static sp_PolyArray*sp_StrIntHash_to_a(sp_StrIntHash*h){sp_PolyArray*r=sp_PolyArray_new();if(!h)return r;for(mrb_int i=0;i<h->len;i++){sp_PolyArray*p=sp_PolyArray_new();sp_PolyArray_push(p,sp_box_str(h->order[i]));sp_PolyArray_push(p,sp_box_int(sp_StrIntHash_get(h,h->order[i])));sp_PolyArray_push(r,sp_box_poly_array(p));}return r;}
|
|
3020
|
+
static sp_PolyArray*sp_StrStrHash_to_a(sp_StrStrHash*h){sp_PolyArray*r=sp_PolyArray_new();if(!h)return r;for(mrb_int i=0;i<h->len;i++){sp_PolyArray*p=sp_PolyArray_new();sp_PolyArray_push(p,sp_box_str(h->order[i]));sp_PolyArray_push(p,sp_box_str(sp_StrStrHash_get(h,h->order[i])));sp_PolyArray_push(r,sp_box_poly_array(p));}return r;}
|
|
3021
|
+
static sp_PolyArray*sp_IntStrHash_to_a(sp_IntStrHash*h){sp_PolyArray*r=sp_PolyArray_new();if(!h)return r;for(mrb_int i=0;i<h->len;i++){sp_PolyArray*p=sp_PolyArray_new();sp_PolyArray_push(p,sp_box_int(h->order[i]));sp_PolyArray_push(p,sp_box_str(sp_IntStrHash_get(h,h->order[i])));sp_PolyArray_push(r,sp_box_poly_array(p));}return r;}
|
|
3022
|
+
/* Array#flatten -- walk into nested array values recursively. Each
|
|
3023
|
+
array-tagged element (IntArray / StrArray / SymArray / FloatArray /
|
|
3024
|
+
PolyArray) is expanded inline; scalars are appended as-is. Issue
|
|
3025
|
+
#739. */
|
|
3026
|
+
static void sp_PolyArray_flatten_into(sp_PolyArray *dst, sp_RbVal v) {
|
|
3027
|
+
if (v.tag != SP_TAG_OBJ) { sp_PolyArray_push(dst, v); return; }
|
|
3028
|
+
if (v.cls_id == SP_BUILTIN_INT_ARRAY) { sp_IntArray *ia = (sp_IntArray *)v.v.p; for (mrb_int i = 0; i < ia->len; i++) sp_PolyArray_push(dst, sp_box_int(ia->data[ia->start + i])); return; }
|
|
3029
|
+
if (v.cls_id == SP_BUILTIN_STR_ARRAY) { sp_StrArray *sa = (sp_StrArray *)v.v.p; for (mrb_int i = 0; i < sa->len; i++) sp_PolyArray_push(dst, sp_box_str(sa->data[i])); return; }
|
|
3030
|
+
if (v.cls_id == SP_BUILTIN_POLY_ARRAY) { sp_PolyArray *pa = (sp_PolyArray *)v.v.p; for (mrb_int i = 0; i < pa->len; i++) sp_PolyArray_flatten_into(dst, pa->data[i]); return; }
|
|
3031
|
+
/* Other array variants fall through as opaque elements; rare for
|
|
3032
|
+
deep-flatten use cases. */
|
|
3033
|
+
sp_PolyArray_push(dst, v);
|
|
3034
|
+
}
|
|
3035
|
+
static sp_PolyArray *sp_PolyArray_flatten(sp_PolyArray *a) { sp_PolyArray *b = sp_PolyArray_new(); if (!a) return b; for (mrb_int i = 0; i < a->len; i++) sp_PolyArray_flatten_into(b, a->data[i]); return b; }
|
|
3036
|
+
|
|
3037
|
+
/* Box-into-poly converters used by the printf-with-array codegen
|
|
3038
|
+
(`"%fmt" % typed_array`). The format helper expects sp_RbVal
|
|
3039
|
+
slots so it can dispatch per-element. */
|
|
3040
|
+
static sp_PolyArray *sp_IntArray_to_poly(sp_IntArray *a) {
|
|
3041
|
+
sp_PolyArray *r = sp_PolyArray_new();
|
|
3042
|
+
if (!a) return r;
|
|
3043
|
+
for (mrb_int i = 0; i < a->len; i++) sp_PolyArray_push(r, sp_box_int(a->data[a->start + i]));
|
|
3044
|
+
return r;
|
|
3045
|
+
}
|
|
3046
|
+
static sp_PolyArray *sp_StrArray_to_poly_fmt(sp_StrArray *a) {
|
|
3047
|
+
sp_PolyArray *r = sp_PolyArray_new();
|
|
3048
|
+
if (!a) return r;
|
|
3049
|
+
for (mrb_int i = 0; i < a->len; i++) sp_PolyArray_push(r, sp_box_str(a->data[i]));
|
|
3050
|
+
return r;
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
/* String#% with a poly_array argument. Walks the format and for
|
|
3054
|
+
each spec ("%s", "%d", "%f", "%x", "%o", etc.) pulls the next
|
|
3055
|
+
array element. Width / flag chars between `%` and the conv
|
|
3056
|
+
letter (`-+0 #`, digits, `.`) are copied verbatim so printf
|
|
3057
|
+
does the substitution work. */
|
|
3058
|
+
static const char *sp_str_format_polyarr(const char *fmt, sp_PolyArray *a) {
|
|
3059
|
+
size_t cap = strlen(fmt) + 64;
|
|
3060
|
+
char *buf = (char *)malloc(cap);
|
|
3061
|
+
if (!buf) { perror("malloc"); exit(1); }
|
|
3062
|
+
size_t out = 0; mrb_int idx = 0; const char *p = fmt;
|
|
3063
|
+
while (*p) {
|
|
3064
|
+
if (*p != '%') {
|
|
3065
|
+
if (out + 1 >= cap) { cap = cap * 2; buf = (char *)realloc(buf, cap); }
|
|
3066
|
+
buf[out++] = *p++; continue;
|
|
3067
|
+
}
|
|
3068
|
+
if (p[1] == '%') {
|
|
3069
|
+
if (out + 1 >= cap) { cap = cap * 2; buf = (char *)realloc(buf, cap); }
|
|
3070
|
+
buf[out++] = '%'; p += 2; continue;
|
|
3071
|
+
}
|
|
3072
|
+
char spec[64]; size_t sl = 0; spec[sl++] = *p++;
|
|
3073
|
+
while (*p && sl < sizeof(spec) - 4) {
|
|
3074
|
+
char c = *p;
|
|
3075
|
+
if (c == '-' || c == '+' || c == ' ' || c == '#' || c == '0' || c == '.' || (c >= '0' && c <= '9')) { spec[sl++] = c; p++; }
|
|
3076
|
+
else break;
|
|
3077
|
+
}
|
|
3078
|
+
if (!*p) break;
|
|
3079
|
+
char conv = *p++; spec[sl++] = conv;
|
|
3080
|
+
char fmt_use[80];
|
|
3081
|
+
if (conv == 'd' || conv == 'i' || conv == 'x' || conv == 'X' || conv == 'o') {
|
|
3082
|
+
memcpy(fmt_use, spec, sl - 1);
|
|
3083
|
+
fmt_use[sl - 1] = 'l'; fmt_use[sl] = 'l'; fmt_use[sl + 1] = conv; fmt_use[sl + 2] = 0;
|
|
3084
|
+
} else {
|
|
3085
|
+
memcpy(fmt_use, spec, sl); fmt_use[sl] = 0;
|
|
3086
|
+
}
|
|
3087
|
+
char tmp[256]; int wn = 0;
|
|
3088
|
+
sp_RbVal v = (idx < a->len) ? a->data[idx] : sp_box_nil();
|
|
3089
|
+
idx++;
|
|
3090
|
+
if (conv == 'd' || conv == 'i' || conv == 'x' || conv == 'X' || conv == 'o') {
|
|
3091
|
+
long long lv = 0;
|
|
3092
|
+
if (v.tag == SP_TAG_INT) lv = (long long)v.v.i;
|
|
3093
|
+
else if (v.tag == SP_TAG_FLT) lv = (long long)v.v.f;
|
|
3094
|
+
else if (v.tag == SP_TAG_STR && v.v.s) lv = strtoll(v.v.s, NULL, 10);
|
|
3095
|
+
wn = snprintf(tmp, sizeof(tmp), fmt_use, lv);
|
|
3096
|
+
} else if (conv == 'f' || conv == 'e' || conv == 'E' || conv == 'g' || conv == 'G') {
|
|
3097
|
+
double dv = 0;
|
|
3098
|
+
if (v.tag == SP_TAG_FLT) dv = v.v.f;
|
|
3099
|
+
else if (v.tag == SP_TAG_INT) dv = (double)v.v.i;
|
|
3100
|
+
wn = snprintf(tmp, sizeof(tmp), fmt_use, dv);
|
|
3101
|
+
} else if (conv == 's') {
|
|
3102
|
+
const char *sv = "";
|
|
3103
|
+
char num_buf[32];
|
|
3104
|
+
if (v.tag == SP_TAG_STR) sv = v.v.s ? v.v.s : "";
|
|
3105
|
+
else if (v.tag == SP_TAG_INT) { snprintf(num_buf, sizeof(num_buf), "%lld", (long long)v.v.i); sv = num_buf; }
|
|
3106
|
+
else if (v.tag == SP_TAG_FLT) { snprintf(num_buf, sizeof(num_buf), "%g", v.v.f); sv = num_buf; }
|
|
3107
|
+
wn = snprintf(tmp, sizeof(tmp), fmt_use, sv);
|
|
3108
|
+
} else if (conv == 'c') {
|
|
3109
|
+
int cv = 0;
|
|
3110
|
+
if (v.tag == SP_TAG_INT) cv = (int)v.v.i;
|
|
3111
|
+
else if (v.tag == SP_TAG_STR && v.v.s && v.v.s[0]) cv = (unsigned char)v.v.s[0];
|
|
3112
|
+
wn = snprintf(tmp, sizeof(tmp), fmt_use, cv);
|
|
3113
|
+
} else {
|
|
3114
|
+
memcpy(tmp, spec, sl); tmp[sl] = 0; wn = (int)sl; idx--;
|
|
3115
|
+
}
|
|
3116
|
+
if (wn < 0) continue;
|
|
3117
|
+
if (out + (size_t)wn + 1 >= cap) { cap = (out + wn) * 2 + 64; buf = (char *)realloc(buf, cap); }
|
|
3118
|
+
memcpy(buf + out, tmp, wn); out += wn;
|
|
3119
|
+
}
|
|
3120
|
+
buf[out] = 0;
|
|
3121
|
+
char *r = sp_str_alloc(out); memcpy(r, buf, out); free(buf); return r;
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
/* Array#assoc — return the first sub-array (poly_array element)
|
|
3125
|
+
whose first element equals `key`. Returns NULL when no match
|
|
3126
|
+
so the caller's `.inspect` round-trips to "nil". */
|
|
3127
|
+
static sp_PolyArray *sp_PolyArray_assoc(sp_PolyArray *a, sp_RbVal key) {
|
|
3128
|
+
if (!a) return NULL;
|
|
3129
|
+
for (mrb_int i = 0; i < a->len; i++) {
|
|
3130
|
+
sp_RbVal el = a->data[i];
|
|
3131
|
+
if (el.tag != SP_TAG_OBJ) continue;
|
|
3132
|
+
if (el.cls_id != SP_BUILTIN_POLY_ARRAY) continue;
|
|
3133
|
+
sp_PolyArray *sub = (sp_PolyArray *)el.v.p;
|
|
3134
|
+
if (sub && sub->len >= 1 && sp_poly_eq(sub->data[0], key)) return sub;
|
|
3135
|
+
}
|
|
3136
|
+
return NULL;
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
/* Array#rassoc — same as assoc but matches against the second
|
|
3140
|
+
element of each sub-array. */
|
|
3141
|
+
static sp_PolyArray *sp_PolyArray_rassoc(sp_PolyArray *a, sp_RbVal val) {
|
|
3142
|
+
if (!a) return NULL;
|
|
3143
|
+
for (mrb_int i = 0; i < a->len; i++) {
|
|
3144
|
+
sp_RbVal el = a->data[i];
|
|
3145
|
+
if (el.tag != SP_TAG_OBJ) continue;
|
|
3146
|
+
if (el.cls_id != SP_BUILTIN_POLY_ARRAY) continue;
|
|
3147
|
+
sp_PolyArray *sub = (sp_PolyArray *)el.v.p;
|
|
3148
|
+
if (sub && sub->len >= 2 && sp_poly_eq(sub->data[1], val)) return sub;
|
|
3149
|
+
}
|
|
3150
|
+
return NULL;
|
|
3151
|
+
}
|
|
3152
|
+
/* Depth-bounded variant. depth==0 returns a shallow copy; each
|
|
3153
|
+
recursive step decrements depth, and a negative depth means
|
|
3154
|
+
"unlimited" (same as flatten without arg). Used by
|
|
3155
|
+
`Array#flatten(n)`. */
|
|
3156
|
+
static void sp_PolyArray_flatten_into_n(sp_PolyArray *dst, sp_RbVal v, mrb_int depth) {
|
|
3157
|
+
if (depth == 0 || v.tag != SP_TAG_OBJ) { sp_PolyArray_push(dst, v); return; }
|
|
3158
|
+
if (v.cls_id == SP_BUILTIN_INT_ARRAY) { sp_IntArray *ia = (sp_IntArray *)v.v.p; for (mrb_int i = 0; i < ia->len; i++) sp_PolyArray_push(dst, sp_box_int(ia->data[ia->start + i])); return; }
|
|
3159
|
+
if (v.cls_id == SP_BUILTIN_STR_ARRAY) { sp_StrArray *sa = (sp_StrArray *)v.v.p; for (mrb_int i = 0; i < sa->len; i++) sp_PolyArray_push(dst, sp_box_str(sa->data[i])); return; }
|
|
3160
|
+
if (v.cls_id == SP_BUILTIN_POLY_ARRAY) { sp_PolyArray *pa = (sp_PolyArray *)v.v.p; for (mrb_int i = 0; i < pa->len; i++) sp_PolyArray_flatten_into_n(dst, pa->data[i], depth - 1); return; }
|
|
3161
|
+
sp_PolyArray_push(dst, v);
|
|
3162
|
+
}
|
|
3163
|
+
static sp_PolyArray *sp_PolyArray_flatten_n(sp_PolyArray *a, mrb_int depth) {
|
|
3164
|
+
sp_PolyArray *b = sp_PolyArray_new();
|
|
3165
|
+
if (!a) return b;
|
|
3166
|
+
if (depth < 0) depth = INT64_MAX;
|
|
3167
|
+
for (mrb_int i = 0; i < a->len; i++) sp_PolyArray_flatten_into_n(b, a->data[i], depth);
|
|
3168
|
+
return b;
|
|
3169
|
+
}
|
|
3170
|
+
/* Transpose a poly-array of int-arrays (or int-arrays boxed in poly).
|
|
3171
|
+
Used by `Array#transpose` on a matrix whose rows are IntArrays. */
|
|
3172
|
+
/* sp_poly_array_transpose is defined below, after the sp_poly_length /
|
|
3173
|
+
sp_poly_int_get element accessors it relies on. */
|
|
3174
|
+
/* Runtime dispatch: .length/.size on a boxed array value (sp_RbVal). */
|
|
3175
|
+
static mrb_int sp_poly_length(sp_RbVal v) {
|
|
3176
|
+
if (v.tag != SP_TAG_OBJ) return 0;
|
|
3177
|
+
if (v.cls_id == SP_BUILTIN_INT_ARRAY) return sp_IntArray_length((sp_IntArray *)v.v.p);
|
|
3178
|
+
if (v.cls_id == SP_BUILTIN_STR_ARRAY) return ((sp_StrArray *)v.v.p)->len;
|
|
3179
|
+
if (v.cls_id == SP_BUILTIN_POLY_ARRAY) return ((sp_PolyArray *)v.v.p)->len;
|
|
3180
|
+
if (v.cls_id == SP_BUILTIN_FLT_ARRAY) return ((sp_FloatArray *)v.v.p)->len;
|
|
3181
|
+
return 0;
|
|
3182
|
+
}
|
|
3183
|
+
/* Runtime dispatch: [] on a boxed array value with integer index -> boxed element. */
|
|
3184
|
+
static sp_RbVal sp_poly_int_get(sp_RbVal v, mrb_int i) {
|
|
3185
|
+
if (v.tag != SP_TAG_OBJ) return sp_box_nil();
|
|
3186
|
+
if (v.cls_id == SP_BUILTIN_INT_ARRAY) {
|
|
3187
|
+
mrb_int val = sp_IntArray_get((sp_IntArray *)v.v.p, i);
|
|
3188
|
+
return val == SP_INT_NIL ? sp_box_nil() : sp_box_int(val);
|
|
3189
|
+
}
|
|
3190
|
+
if (v.cls_id == SP_BUILTIN_POLY_ARRAY) {
|
|
3191
|
+
sp_PolyArray *a = (sp_PolyArray *)v.v.p;
|
|
3192
|
+
if (i < 0 || i >= a->len) return sp_box_nil();
|
|
3193
|
+
return a->data[i];
|
|
3194
|
+
}
|
|
3195
|
+
if (v.cls_id == SP_BUILTIN_STR_ARRAY) {
|
|
3196
|
+
sp_StrArray *a = (sp_StrArray *)v.v.p;
|
|
3197
|
+
if (i < 0 || i >= a->len) return sp_box_nil();
|
|
3198
|
+
return sp_box_str(sp_StrArray_get(a, i));
|
|
3199
|
+
}
|
|
3200
|
+
if (v.cls_id == SP_BUILTIN_FLT_ARRAY) {
|
|
3201
|
+
sp_FloatArray *a = (sp_FloatArray *)v.v.p;
|
|
3202
|
+
if (i < 0 || i >= a->len) return sp_box_nil();
|
|
3203
|
+
return sp_box_float(sp_FloatArray_get(a, i));
|
|
3204
|
+
}
|
|
3205
|
+
return sp_box_nil();
|
|
3206
|
+
}
|
|
3207
|
+
/* Array#transpose on a poly_array (untyped nested array). Swaps rows and
|
|
3208
|
+
columns, preserving each element's runtime type via sp_poly_int_get
|
|
3209
|
+
(int / float / str / poly rows). Returns a poly_array of poly_array
|
|
3210
|
+
columns; the statically-typed nested-array paths are handled inline by
|
|
3211
|
+
codegen. Row length is taken from the first row (CRuby raises on a
|
|
3212
|
+
ragged matrix; here a short row reads nil past its end). */
|
|
3213
|
+
static sp_PolyArray *sp_poly_array_transpose(sp_PolyArray *rows) {
|
|
3214
|
+
SP_GC_SAVE();
|
|
3215
|
+
if (!rows || rows->len == 0) return sp_PolyArray_new();
|
|
3216
|
+
mrb_int ncols = sp_poly_length(rows->data[0]);
|
|
3217
|
+
sp_PolyArray *result = sp_PolyArray_new();
|
|
3218
|
+
SP_GC_ROOT(result);
|
|
3219
|
+
for (mrb_int j = 0; j < ncols; j++) {
|
|
3220
|
+
sp_PolyArray *col = sp_PolyArray_new();
|
|
3221
|
+
SP_GC_ROOT(col);
|
|
3222
|
+
for (mrb_int i = 0; i < rows->len; i++) {
|
|
3223
|
+
sp_PolyArray_push(col, sp_poly_int_get(rows->data[i], j));
|
|
3224
|
+
}
|
|
3225
|
+
sp_PolyArray_push(result, sp_box_poly_array(col));
|
|
3226
|
+
}
|
|
3227
|
+
return result;
|
|
3228
|
+
}
|
|
3229
|
+
/* Sum the integer-tagged elements of a poly_array. Used by
|
|
3230
|
+
`Array#sum` on a poly_array whose runtime tags are uniform int
|
|
3231
|
+
(e.g. the result of `arr.map { _1[:int_key] }`). Non-int tags
|
|
3232
|
+
contribute zero. */
|
|
3233
|
+
static mrb_int sp_PolyArray_sum_int(sp_PolyArray *a) { if (!a) return 0; mrb_int s = 0; for (mrb_int i = 0; i < a->len; i++) { if (a->data[i].tag == SP_TAG_INT) s += a->data[i].v.i; } return s; }
|
|
3234
|
+
static void sp_PolyArray_shuffle_bang(sp_PolyArray *a) { if (!a || a->frozen) { if (a && a->frozen) sp_raise_frozen_array(); return; } for (mrb_int i = a->len - 1; i > 0; i--) { mrb_int j = (mrb_int)(rand() % (i + 1)); sp_RbVal t = a->data[i]; a->data[i] = a->data[j]; a->data[j] = t; } }
|
|
3235
|
+
static void sp_PolyArray_rotate_bang(sp_PolyArray*a,mrb_int n){if(!a)return;if(a->frozen){sp_raise_frozen_array();return;}if(a->len<=0)return;n=((n%a->len)+a->len)%a->len;if(n==0)return;sp_RbVal*d=a->data;mrb_int lo=0,hi=n-1;while(lo<hi){sp_RbVal t=d[lo];d[lo]=d[hi];d[hi]=t;lo++;hi--;}lo=n;hi=a->len-1;while(lo<hi){sp_RbVal t=d[lo];d[lo]=d[hi];d[hi]=t;lo++;hi--;}lo=0;hi=a->len-1;while(lo<hi){sp_RbVal t=d[lo];d[lo]=d[hi];d[hi]=t;lo++;hi--;}}
|
|
987
3236
|
static sp_PolyArray *sp_PolyArray_shuffle(sp_PolyArray *a) { sp_PolyArray *b = sp_PolyArray_dup(a); sp_PolyArray_shuffle_bang(b); return b; }
|
|
3237
|
+
static sp_RbVal sp_PolyArray_sample(sp_PolyArray *a) { if (a->len <= 0) return sp_box_nil(); return a->data[(mrb_int)(rand()%a->len)]; }
|
|
3238
|
+
|
|
3239
|
+
/* Forward decl: sp_poly_inspect dispatches into sp_PolyArray_inspect
|
|
3240
|
+
for nested poly arrays (under promote, an `each_cons` chain's outer
|
|
3241
|
+
accumulator boxes each inner poly_array element), but the
|
|
3242
|
+
sp_PolyArray_inspect body lives a few lines below. */
|
|
3243
|
+
static const char *sp_PolyArray_inspect(sp_PolyArray *a);
|
|
3244
|
+
static const char*sp_PolyArrayPtrArray_inspect(sp_PtrArray*a){sp_String*s=sp_String_new("[");for(mrb_int i=0;i<a->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,sp_PolyArray_inspect((sp_PolyArray*)a->data[i]));}sp_String_append(s,"]");return s->data;}
|
|
988
3245
|
|
|
989
3246
|
/* Object#inspect for a tagged sp_RbVal. Dispatches on the runtime tag;
|
|
990
3247
|
each branch reuses the matching primitive inspect helper. Falls back
|
|
991
3248
|
to "#<Object>" for SP_TAG_OBJ because the runtime has no class-name
|
|
992
3249
|
table yet (follow-up PR). Returns a GC-managed C string. */
|
|
993
|
-
static const char *sp_poly_inspect(sp_RbVal v) {
|
|
3250
|
+
static inline const char *sp_poly_inspect(sp_RbVal v) {
|
|
994
3251
|
switch (v.tag) {
|
|
995
3252
|
case SP_TAG_INT: return sp_int_to_s(v.v.i);
|
|
996
3253
|
case SP_TAG_STR: return sp_str_inspect(v.v.s);
|
|
997
3254
|
case SP_TAG_FLT: return sp_float_to_s(v.v.f);
|
|
998
3255
|
case SP_TAG_BOOL: return v.v.b ? SPL("true") : SPL("false");
|
|
999
3256
|
case SP_TAG_NIL: return SPL("nil");
|
|
1000
|
-
case SP_TAG_SYM: return sp_str_concat(":", sp_sym_to_s((sp_sym)v.v.i));
|
|
1001
|
-
case
|
|
3257
|
+
case SP_TAG_SYM: return sp_str_concat(SPL(":"), sp_sym_to_s((sp_sym)v.v.i));
|
|
3258
|
+
case SP_TAG_ENCODING: return sp_sprintf("#<Encoding:%s>", v.v.s ? v.v.s : "");
|
|
3259
|
+
case SP_TAG_OBJ:
|
|
3260
|
+
/* Built-in container / value-type tags get their typed inspect
|
|
3261
|
+
helper. Matches the dispatch shape in sp_poly_to_s above and the
|
|
3262
|
+
`puts` poly arm earlier in this file; without it, a Range / Time
|
|
3263
|
+
/ typed Array stored as an sp_RbVal value (e.g. a sym_poly_hash
|
|
3264
|
+
that mixes `200..299` and `404`) reported "#<Object>" from
|
|
3265
|
+
`.inspect`, which was both wrong for CRuby parity and useless
|
|
3266
|
+
for debugging. */
|
|
3267
|
+
switch (v.cls_id) {
|
|
3268
|
+
case SP_BUILTIN_INT_ARRAY: return sp_IntArray_inspect((sp_IntArray *)v.v.p);
|
|
3269
|
+
case SP_BUILTIN_FLT_ARRAY: return sp_FloatArray_inspect((sp_FloatArray *)v.v.p);
|
|
3270
|
+
case SP_BUILTIN_STR_ARRAY: return sp_StrArray_inspect((sp_StrArray *)v.v.p);
|
|
3271
|
+
case SP_BUILTIN_SYM_ARRAY: return sp_SymArray_inspect((sp_IntArray *)v.v.p);
|
|
3272
|
+
case SP_BUILTIN_PTR_ARRAY: return sp_PtrArray_inspect((sp_PtrArray *)v.v.p);
|
|
3273
|
+
case SP_BUILTIN_POLY_ARRAY: return sp_PolyArray_inspect((sp_PolyArray *)v.v.p);
|
|
3274
|
+
case SP_BUILTIN_RANGE: return sp_Range_inspect((sp_Range *)v.v.p);
|
|
3275
|
+
case SP_BUILTIN_TIME: return sp_Time_inspect((sp_Time *)v.v.p);
|
|
3276
|
+
default: return SPL("#<Object>");
|
|
3277
|
+
}
|
|
1002
3278
|
default: return sp_str_empty;
|
|
1003
3279
|
}
|
|
1004
3280
|
}
|
|
1005
3281
|
/* Array#inspect for heterogeneous poly arrays. Each element dispatches
|
|
1006
3282
|
through sp_poly_inspect, so a mixed `[1, "x", :y]` renders
|
|
1007
|
-
`[1, "x", :y]` byte-for-byte identical to CRuby.
|
|
3283
|
+
`[1, "x", :y]` byte-for-byte identical to CRuby. NULL renders
|
|
3284
|
+
"nil" so callers that store a nil-returning slot (assoc/rassoc
|
|
3285
|
+
miss, etc.) round-trip cleanly through `.inspect`. */
|
|
1008
3286
|
static const char *sp_PolyArray_inspect(sp_PolyArray *a) {
|
|
3287
|
+
if (!a) { char *r = sp_str_alloc(3); r[0] = 'n'; r[1] = 'i'; r[2] = 'l'; r[3] = 0; sp_str_set_len(r, 3); return r; }
|
|
1009
3288
|
sp_String *s = sp_String_new("[");
|
|
1010
3289
|
for (mrb_int i = 0; i < a->len; i++) {
|
|
1011
3290
|
if (i > 0) sp_String_append(s, ", ");
|
|
@@ -1014,40 +3293,98 @@ static const char *sp_PolyArray_inspect(sp_PolyArray *a) {
|
|
|
1014
3293
|
sp_String_append(s, "]");
|
|
1015
3294
|
return s->data;
|
|
1016
3295
|
}
|
|
3296
|
+
/* Array#join for a mixed-element (poly) array: to_s each element via
|
|
3297
|
+
sp_poly_to_s and concatenate with sep. Mirrors sp_StrArray_join for
|
|
3298
|
+
the boxed-element case. */
|
|
3299
|
+
static const char *sp_PolyArray_join(sp_PolyArray *a, const char *sep) {
|
|
3300
|
+
if (!a) return sp_str_empty;
|
|
3301
|
+
sp_String *s = sp_String_new("");
|
|
3302
|
+
for (mrb_int i = 0; i < a->len; i++) {
|
|
3303
|
+
if (i > 0 && sep) sp_String_append(s, sep);
|
|
3304
|
+
sp_String_append(s, sp_poly_to_s(a->data[i]));
|
|
3305
|
+
}
|
|
3306
|
+
return s->data;
|
|
3307
|
+
}
|
|
3308
|
+
static mrb_bool sp_PolyArray_eq(sp_PolyArray *a, sp_PolyArray *b) {
|
|
3309
|
+
if (!a || !b) return a == b;
|
|
3310
|
+
if (a->len != b->len) return FALSE;
|
|
3311
|
+
for (mrb_int i = 0; i < a->len; i++) {
|
|
3312
|
+
if (!sp_poly_eq(a->data[i], b->data[i])) return FALSE;
|
|
3313
|
+
}
|
|
3314
|
+
return TRUE;
|
|
3315
|
+
}
|
|
3316
|
+
static mrb_bool sp_PolyArray_include(sp_PolyArray *a, sp_RbVal v) {
|
|
3317
|
+
if (!a) return FALSE;
|
|
3318
|
+
for (mrb_int i = 0; i < a->len; i++) {
|
|
3319
|
+
if (sp_poly_eq(a->data[i], v)) return TRUE;
|
|
3320
|
+
}
|
|
3321
|
+
return FALSE;
|
|
3322
|
+
}
|
|
1017
3323
|
|
|
1018
3324
|
/* Mark the embedded GC reference inside an sp_RbVal (string or obj).
|
|
1019
3325
|
Used as the scan hook for containers that store polymorphic values. */
|
|
1020
|
-
|
|
1021
|
-
if (v.tag == SP_TAG_STR) sp_mark_string(v.v.s);
|
|
1022
|
-
else if (v.tag == SP_TAG_OBJ) sp_gc_mark(v.v.p);
|
|
1023
|
-
}
|
|
3326
|
+
/* sp_mark_rbval is an inline helper in sp_gc.h. */
|
|
1024
3327
|
|
|
1025
3328
|
/* StrPolyHash: string keys, sp_RbVal values — for hashes with mixed value types. */
|
|
1026
|
-
|
|
3329
|
+
/* `dproc` holds a Hash.new{} default block, lowered to a dedicated C
|
|
3330
|
+
fn `sp_RbVal (*)(sp_StrPolyHash *self, const char *key)` with typed
|
|
3331
|
+
params (codegen emits it). Called by _get on a miss. Issue #912. */
|
|
3332
|
+
typedef struct sp_StrPolyHash sp_StrPolyHash;
|
|
3333
|
+
typedef sp_RbVal (*sp_strpoly_dproc_t)(sp_StrPolyHash *, const char *);
|
|
3334
|
+
struct sp_StrPolyHash{const char**keys;sp_RbVal*vals;const char**order;mrb_int len;mrb_int cap;mrb_int mask;sp_RbVal default_v;sp_strpoly_dproc_t dproc;};
|
|
1027
3335
|
static void sp_StrPolyHash_fin(void*p){sp_StrPolyHash*h=(sp_StrPolyHash*)p;free(h->keys);free(h->vals);free(h->order);}
|
|
1028
|
-
static void sp_StrPolyHash_scan(void*p){sp_StrPolyHash*h=(sp_StrPolyHash*)p;for(mrb_int i=0;i<h->cap;i++){if(h->keys[i]){sp_mark_string(h->keys[i]);sp_mark_rbval(h->vals[i]);}}}
|
|
1029
|
-
static sp_StrPolyHash*sp_StrPolyHash_new(void){sp_StrPolyHash*h=(sp_StrPolyHash*)sp_gc_alloc(sizeof(sp_StrPolyHash),sp_StrPolyHash_fin,sp_StrPolyHash_scan);h->cap=16;h->mask=15;h->keys=(const char**)calloc(h->cap,sizeof(const char*));h->vals=(sp_RbVal*)calloc(h->cap,sizeof(sp_RbVal));h->order=(const char**)malloc(sizeof(const char*)*h->cap);h->len=0;return h;}
|
|
3336
|
+
static void sp_StrPolyHash_scan(void*p){sp_StrPolyHash*h=(sp_StrPolyHash*)p;for(mrb_int i=0;i<h->cap;i++){if(h->keys[i]){sp_mark_string(h->keys[i]);sp_mark_rbval(h->vals[i]);}}sp_mark_rbval(h->default_v);}
|
|
3337
|
+
static sp_StrPolyHash*sp_StrPolyHash_new(void){sp_StrPolyHash*h=(sp_StrPolyHash*)sp_gc_alloc(sizeof(sp_StrPolyHash),sp_StrPolyHash_fin,sp_StrPolyHash_scan);h->cap=16;h->mask=15;h->keys=(const char**)calloc(h->cap,sizeof(const char*));h->vals=(sp_RbVal*)calloc(h->cap,sizeof(sp_RbVal));h->order=(const char**)malloc(sizeof(const char*)*h->cap);h->len=0;h->default_v=sp_box_nil();return h;}
|
|
3338
|
+
static sp_StrPolyHash*sp_StrPolyHash_new_with_default(sp_RbVal d){sp_StrPolyHash*h=sp_StrPolyHash_new();h->default_v=d;return h;}
|
|
3339
|
+
static sp_StrPolyHash*sp_StrPolyHash_new_dproc(sp_strpoly_dproc_t fn){sp_StrPolyHash*h=sp_StrPolyHash_new();h->dproc=fn;return h;}
|
|
1030
3340
|
static void sp_StrPolyHash_grow(sp_StrPolyHash*h){mrb_int oc=h->cap;const char**ok=h->keys;sp_RbVal*ov=h->vals;h->cap*=2;h->mask=h->cap-1;h->keys=(const char**)calloc(h->cap,sizeof(const char*));h->vals=(sp_RbVal*)calloc(h->cap,sizeof(sp_RbVal));h->order=(const char**)realloc(h->order,sizeof(const char*)*h->cap);h->len=0;for(mrb_int i=0;i<oc;i++){if(ok[i]){mrb_int idx=(mrb_int)(sp_str_hash(ok[i])&h->mask);while(h->keys[idx])idx=(idx+1)&h->mask;h->keys[idx]=ok[i];h->vals[idx]=ov[i];h->len++;}}free(ok);free(ov);}
|
|
1031
|
-
static sp_RbVal sp_StrPolyHash_get(sp_StrPolyHash*h,const char*k){mrb_int idx=(mrb_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(strcmp(h->keys[idx],k)==0)return h->vals[idx];idx=(idx+1)&h->mask;}return
|
|
3341
|
+
static sp_RbVal sp_StrPolyHash_get(sp_StrPolyHash*h,const char*k){if(!h)return sp_box_nil();mrb_int idx=(mrb_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(strcmp(h->keys[idx],k)==0)return h->vals[idx];idx=(idx+1)&h->mask;}if(h->dproc)return h->dproc(h,k);return h->default_v;}
|
|
1032
3342
|
static void sp_StrPolyHash_set(sp_StrPolyHash*h,const char*k,sp_RbVal v){if(h->len*2>=h->cap)sp_StrPolyHash_grow(h);mrb_int idx=(mrb_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(strcmp(h->keys[idx],k)==0){h->vals[idx]=v;return;}idx=(idx+1)&h->mask;}h->keys[idx]=k;h->vals[idx]=v;h->order[h->len]=k;h->len++;}
|
|
1033
|
-
static mrb_bool sp_StrPolyHash_has_key(sp_StrPolyHash*h,const char*k){mrb_int idx=(mrb_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(strcmp(h->keys[idx],k)==0)return TRUE;idx=(idx+1)&h->mask;}return FALSE;}
|
|
3343
|
+
static mrb_bool sp_StrPolyHash_has_key(sp_StrPolyHash*h,const char*k){if(!h)return FALSE;mrb_int idx=(mrb_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(strcmp(h->keys[idx],k)==0)return TRUE;idx=(idx+1)&h->mask;}return FALSE;}
|
|
1034
3344
|
static mrb_int sp_StrPolyHash_length(sp_StrPolyHash*h){return h->len;}
|
|
1035
|
-
static sp_StrArray*sp_StrPolyHash_keys(sp_StrPolyHash*h){sp_StrArray*a=sp_StrArray_new();for(mrb_int i=0;i<h->len;i++)sp_StrArray_push(a,h->order[i]);return a;}
|
|
3345
|
+
static sp_StrArray*sp_StrPolyHash_keys(sp_StrPolyHash*h){sp_StrArray*a=sp_StrArray_new();if(!h)return a;for(mrb_int i=0;i<h->len;i++)sp_StrArray_push(a,h->order[i]);return a;}
|
|
1036
3346
|
static sp_PolyArray*sp_StrPolyHash_values(sp_StrPolyHash*h){sp_PolyArray*a=sp_PolyArray_new();for(mrb_int i=0;i<h->len;i++)sp_PolyArray_push(a,sp_StrPolyHash_get(h,h->order[i]));return a;}
|
|
3347
|
+
/* Hash#merge for str_poly_hash. Same shape as the
|
|
3348
|
+
StrIntHash / SymPolyHash siblings -- copy recv's entries into a
|
|
3349
|
+
fresh hash, then overlay other's. */
|
|
3350
|
+
static sp_StrPolyHash*sp_StrPolyHash_merge(sp_StrPolyHash*a,sp_StrPolyHash*b){sp_StrPolyHash*r=sp_StrPolyHash_new();r->default_v=a->default_v;for(mrb_int i=0;i<a->len;i++)sp_StrPolyHash_set(r,a->order[i],sp_StrPolyHash_get(a,a->order[i]));for(mrb_int i=0;i<b->len;i++)sp_StrPolyHash_set(r,b->order[i],sp_StrPolyHash_get(b,b->order[i]));return r;}
|
|
3351
|
+
static sp_StrPolyHash*sp_StrPolyHash_dup(sp_StrPolyHash*h){sp_StrPolyHash*r=sp_StrPolyHash_new();r->default_v=h->default_v;for(mrb_int i=0;i<h->len;i++)sp_StrPolyHash_set(r,h->order[i],sp_StrPolyHash_get(h,h->order[i]));return r;}
|
|
3352
|
+
static mrb_bool sp_StrPolyHash_eq(sp_StrPolyHash*a,sp_StrPolyHash*b){if(!a||!b)return a==b;if(a->len!=b->len)return FALSE;for(mrb_int i=0;i<a->len;i++){const char*k=a->order[i];if(!sp_StrPolyHash_has_key(b,k))return FALSE;if(!sp_poly_eq(sp_StrPolyHash_get(a,k),sp_StrPolyHash_get(b,k)))return FALSE;}return TRUE;}
|
|
3353
|
+
/* Issue #851: inspect for str_poly_hash. */
|
|
3354
|
+
static const char*sp_StrPolyHash_inspect(sp_StrPolyHash*h){sp_String*s=sp_String_new("{");if(h){for(mrb_int i=0;i<h->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,sp_str_inspect(h->order[i]));sp_String_append(s,"=>");sp_String_append(s,sp_poly_inspect(sp_StrPolyHash_get(h,h->order[i])));}}sp_String_append(s,"}");return s->data;}
|
|
3355
|
+
/* Convert a narrower StrStrHash to a StrPolyHash. Needed when the
|
|
3356
|
+
analyzer widens an LV slot to sp_StrPolyHash* (e.g. later poly-value
|
|
3357
|
+
writes) but the initial RHS is a sibling narrower hash variant —
|
|
3358
|
+
raw pointer assignment would mix incompatible struct layouts
|
|
3359
|
+
(vals[] of const char** vs sp_RbVal*). See issue #614. */
|
|
3360
|
+
static sp_StrPolyHash*sp_StrPolyHash_from_str_str_hash(sp_StrStrHash*h){sp_StrPolyHash*r=sp_StrPolyHash_new();if(!h)return r;if(h->default_v)r->default_v=sp_box_str(h->default_v);for(mrb_int i=0;i<h->len;i++){const char*k=h->order[i];sp_StrPolyHash_set(r,k,sp_box_str(sp_StrStrHash_get(h,k)));}return r;}
|
|
3361
|
+
static sp_StrPolyHash*sp_StrPolyHash_from_str_int_hash(sp_StrIntHash*h){sp_StrPolyHash*r=sp_StrPolyHash_new();if(!h)return r;r->default_v=sp_box_int(h->default_v);for(mrb_int i=0;i<h->len;i++){const char*k=h->order[i];sp_StrPolyHash_set(r,k,sp_box_int(sp_StrIntHash_get(h,k)));}return r;}
|
|
1037
3362
|
|
|
1038
3363
|
/* SymPolyHash: symbol keys, sp_RbVal values — same shape as SymStrHash but with poly values. */
|
|
1039
|
-
|
|
3364
|
+
/* Named struct so lib/sp_fiber.c can forward-declare it for sp_Fiber's
|
|
3365
|
+
`storage` member without pulling in the full poly-hash machinery. */
|
|
3366
|
+
typedef struct sp_SymPolyHash{sp_sym*keys;sp_RbVal*vals;sp_sym*order;mrb_int len;mrb_int cap;mrb_int mask;sp_RbVal default_v;}sp_SymPolyHash;
|
|
1040
3367
|
static void sp_SymPolyHash_fin(void*p){sp_SymPolyHash*h=(sp_SymPolyHash*)p;free(h->keys);free(h->vals);free(h->order);}
|
|
1041
|
-
static void sp_SymPolyHash_scan(void*p){sp_SymPolyHash*h=(sp_SymPolyHash*)p;for(mrb_int i=0;i<h->cap;i++){if(h->keys[i]>=0)sp_mark_rbval(h->vals[i]);}}
|
|
1042
|
-
static sp_SymPolyHash*sp_SymPolyHash_new(void){sp_SymPolyHash*h=(sp_SymPolyHash*)sp_gc_alloc(sizeof(sp_SymPolyHash),sp_SymPolyHash_fin,sp_SymPolyHash_scan);h->cap=16;h->mask=15;h->keys=(sp_sym*)malloc(sizeof(sp_sym)*h->cap);for(mrb_int i=0;i<h->cap;i++)h->keys[i]=-1;h->vals=(sp_RbVal*)calloc(h->cap,sizeof(sp_RbVal));h->order=(sp_sym*)malloc(sizeof(sp_sym)*h->cap);h->len=0;return h;}
|
|
3368
|
+
static void sp_SymPolyHash_scan(void*p){sp_SymPolyHash*h=(sp_SymPolyHash*)p;for(mrb_int i=0;i<h->cap;i++){if(h->keys[i]>=0)sp_mark_rbval(h->vals[i]);}sp_mark_rbval(h->default_v);}
|
|
3369
|
+
static sp_SymPolyHash*sp_SymPolyHash_new(void){sp_SymPolyHash*h=(sp_SymPolyHash*)sp_gc_alloc(sizeof(sp_SymPolyHash),sp_SymPolyHash_fin,sp_SymPolyHash_scan);h->cap=16;h->mask=15;h->keys=(sp_sym*)malloc(sizeof(sp_sym)*h->cap);for(mrb_int i=0;i<h->cap;i++)h->keys[i]=-1;h->vals=(sp_RbVal*)calloc(h->cap,sizeof(sp_RbVal));h->order=(sp_sym*)malloc(sizeof(sp_sym)*h->cap);h->len=0;h->default_v=sp_box_nil();return h;}
|
|
3370
|
+
static sp_SymPolyHash*sp_SymPolyHash_new_with_default(sp_RbVal d){sp_SymPolyHash*h=sp_SymPolyHash_new();h->default_v=d;return h;}
|
|
1043
3371
|
static void sp_SymPolyHash_grow(sp_SymPolyHash*h){mrb_int oc=h->cap;sp_sym*ok=h->keys;sp_RbVal*ov=h->vals;h->cap*=2;h->mask=h->cap-1;h->keys=(sp_sym*)malloc(sizeof(sp_sym)*h->cap);for(mrb_int i=0;i<h->cap;i++)h->keys[i]=-1;h->vals=(sp_RbVal*)calloc(h->cap,sizeof(sp_RbVal));h->order=(sp_sym*)realloc(h->order,sizeof(sp_sym)*h->cap);h->len=0;for(mrb_int i=0;i<oc;i++){if(ok[i]>=0){mrb_int idx=(mrb_int)(((mrb_int)ok[i])&h->mask);while(h->keys[idx]>=0)idx=(idx+1)&h->mask;h->keys[idx]=ok[i];h->vals[idx]=ov[i];h->len++;}}free(ok);free(ov);}
|
|
1044
|
-
static sp_RbVal sp_SymPolyHash_get(sp_SymPolyHash*h,sp_sym k){mrb_int idx=(mrb_int)(((mrb_int)k)&h->mask);while(h->keys[idx]>=0){if(h->keys[idx]==k)return h->vals[idx];idx=(idx+1)&h->mask;}return
|
|
3372
|
+
static sp_RbVal sp_SymPolyHash_get(sp_SymPolyHash*h,sp_sym k){if(!h)return sp_box_nil();mrb_int idx=(mrb_int)(((mrb_int)k)&h->mask);while(h->keys[idx]>=0){if(h->keys[idx]==k)return h->vals[idx];idx=(idx+1)&h->mask;}return h->default_v;}
|
|
1045
3373
|
static void sp_SymPolyHash_set(sp_SymPolyHash*h,sp_sym k,sp_RbVal v){if(h->len*2>=h->cap)sp_SymPolyHash_grow(h);mrb_int idx=(mrb_int)(((mrb_int)k)&h->mask);while(h->keys[idx]>=0){if(h->keys[idx]==k){h->vals[idx]=v;return;}idx=(idx+1)&h->mask;}h->keys[idx]=k;h->vals[idx]=v;h->order[h->len]=k;h->len++;}
|
|
1046
3374
|
static mrb_bool sp_SymPolyHash_has_key(sp_SymPolyHash*h,sp_sym k){mrb_int idx=(mrb_int)(((mrb_int)k)&h->mask);while(h->keys[idx]>=0){if(h->keys[idx]==k)return TRUE;idx=(idx+1)&h->mask;}return FALSE;}
|
|
1047
3375
|
static mrb_int sp_SymPolyHash_length(sp_SymPolyHash*h){return h->len;}
|
|
1048
3376
|
static sp_IntArray*sp_SymPolyHash_keys(sp_SymPolyHash*h){sp_IntArray*a=sp_IntArray_new();for(mrb_int i=0;i<h->len;i++)sp_IntArray_push(a,(mrb_int)h->order[i]);return a;}
|
|
1049
3377
|
static sp_PolyArray*sp_SymPolyHash_values(sp_SymPolyHash*h){sp_PolyArray*a=sp_PolyArray_new();for(mrb_int i=0;i<h->len;i++)sp_PolyArray_push(a,sp_SymPolyHash_get(h,h->order[i]));return a;}
|
|
1050
|
-
static sp_SymPolyHash*sp_SymPolyHash_merge(sp_SymPolyHash*a,sp_SymPolyHash*b){sp_SymPolyHash*r=sp_SymPolyHash_new();for(mrb_int i=0;i<a->len;i++)sp_SymPolyHash_set(r,a->order[i],sp_SymPolyHash_get(a,a->order[i]));for(mrb_int i=0;i<b->len;i++)sp_SymPolyHash_set(r,b->order[i],sp_SymPolyHash_get(b,b->order[i]));return r;}
|
|
3378
|
+
static sp_SymPolyHash*sp_SymPolyHash_merge(sp_SymPolyHash*a,sp_SymPolyHash*b){sp_SymPolyHash*r=sp_SymPolyHash_new();r->default_v=a->default_v;for(mrb_int i=0;i<a->len;i++)sp_SymPolyHash_set(r,a->order[i],sp_SymPolyHash_get(a,a->order[i]));for(mrb_int i=0;i<b->len;i++)sp_SymPolyHash_set(r,b->order[i],sp_SymPolyHash_get(b,b->order[i]));return r;}
|
|
3379
|
+
/* Hash#delete for sym_poly_hash. Removes key and re-tombstones the
|
|
3380
|
+
slot, shifting probe-chain successors backward and dropping the
|
|
3381
|
+
key from the insertion-order list. Issue #510. */
|
|
3382
|
+
static void sp_SymPolyHash_delete(sp_SymPolyHash*h,sp_sym k){mrb_int idx=(mrb_int)(((mrb_int)k)&h->mask);while(h->keys[idx]>=0){if(h->keys[idx]==k){h->keys[idx]=-1;h->vals[idx]=sp_box_nil();h->len--;mrb_int j=(idx+1)&h->mask;while(h->keys[j]>=0){mrb_int nj=(mrb_int)(((mrb_int)h->keys[j])&h->mask);if((j>idx&&(nj<=idx||nj>j))||(j<idx&&nj<=idx&&nj>j)){h->keys[idx]=h->keys[j];h->vals[idx]=h->vals[j];h->keys[j]=-1;h->vals[j]=sp_box_nil();idx=j;}j=(j+1)&h->mask;}{mrb_int oi=0;while(oi<=h->len){if(h->order[oi]==k){while(oi<h->len){h->order[oi]=h->order[oi+1];oi++;}break;}oi++;}}return;}idx=(idx+1)&h->mask;}}
|
|
3383
|
+
static sp_SymPolyHash*sp_SymPolyHash_dup(sp_SymPolyHash*h){sp_SymPolyHash*r=sp_SymPolyHash_new();r->default_v=h->default_v;for(mrb_int i=0;i<h->len;i++)sp_SymPolyHash_set(r,h->order[i],sp_SymPolyHash_get(h,h->order[i]));return r;}
|
|
3384
|
+
static mrb_bool sp_SymPolyHash_eq(sp_SymPolyHash*a,sp_SymPolyHash*b){if(!a||!b)return a==b;if(a->len!=b->len)return FALSE;for(mrb_int i=0;i<a->len;i++){sp_sym k=a->order[i];if(!sp_SymPolyHash_has_key(b,k))return FALSE;if(!sp_poly_eq(sp_SymPolyHash_get(a,k),sp_SymPolyHash_get(b,k)))return FALSE;}return TRUE;}
|
|
3385
|
+
/* Hash#inspect for sym_poly_hash. CRuby 4.0 renders symbol keys
|
|
3386
|
+
in shorthand: `{a: 1, b: "x"}` rather than `{:a=>1, :b=>"x"}`. */
|
|
3387
|
+
static const char*sp_SymPolyHash_inspect(sp_SymPolyHash*h){if(!h){char*r=sp_str_alloc_raw(3);r[0]='{';r[1]='}';r[2]=0;sp_str_set_len(r,2);return r;}sp_String*s=sp_String_new("{");for(mrb_int i=0;i<h->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,sp_sym_to_s(h->order[i]));sp_String_append(s,": ");sp_String_append(s,sp_poly_inspect(sp_SymPolyHash_get(h,h->order[i])));}sp_String_append(s,"}");return s->data;}
|
|
1051
3388
|
|
|
1052
3389
|
/* PolyPolyHash: heterogeneous keys + values (both sp_RbVal). For
|
|
1053
3390
|
primitives the hash/eql is tag-based (value equality); for OBJ tag
|
|
@@ -1066,6 +3403,8 @@ static mrb_int sp_rbval_hash_key(sp_RbVal v) {
|
|
|
1066
3403
|
return (mrb_int)v.v.i;
|
|
1067
3404
|
case SP_TAG_STR:
|
|
1068
3405
|
return v.v.s ? (mrb_int)sp_str_hash(v.v.s) : 0;
|
|
3406
|
+
case SP_TAG_ENCODING:
|
|
3407
|
+
return v.v.s ? (mrb_int)sp_str_hash(v.v.s) : 0;
|
|
1069
3408
|
case SP_TAG_FLT: { uint64_t b; memcpy(&b, &v.v.f, sizeof(b)); return (mrb_int)b; }
|
|
1070
3409
|
case SP_TAG_OBJ:
|
|
1071
3410
|
if (sp_obj_hash_hook) return sp_obj_hash_hook(v.cls_id, v.v.p);
|
|
@@ -1082,6 +3421,10 @@ static mrb_bool sp_rbval_eql_key(sp_RbVal a, sp_RbVal b) {
|
|
|
1082
3421
|
if (a.v.s == b.v.s) return TRUE;
|
|
1083
3422
|
if (!a.v.s || !b.v.s) return FALSE;
|
|
1084
3423
|
return strcmp(a.v.s, b.v.s) == 0;
|
|
3424
|
+
case SP_TAG_ENCODING:
|
|
3425
|
+
if (a.v.s == b.v.s) return TRUE;
|
|
3426
|
+
if (!a.v.s || !b.v.s) return FALSE;
|
|
3427
|
+
return strcmp(a.v.s, b.v.s) == 0;
|
|
1085
3428
|
case SP_TAG_FLT:
|
|
1086
3429
|
return a.v.f == b.v.f;
|
|
1087
3430
|
case SP_TAG_OBJ:
|
|
@@ -1097,12 +3440,96 @@ static void sp_PolyPolyHash_fin(void*p){sp_PolyPolyHash*h=(sp_PolyPolyHash*)p;fr
|
|
|
1097
3440
|
static void sp_PolyPolyHash_scan(void*p){sp_PolyPolyHash*h=(sp_PolyPolyHash*)p;for(mrb_int i=0;i<h->cap;i++){if(h->occ[i]){sp_mark_rbval(h->keys[i]);sp_mark_rbval(h->vals[i]);}}}
|
|
1098
3441
|
static sp_PolyPolyHash*sp_PolyPolyHash_new(void){sp_PolyPolyHash*h=(sp_PolyPolyHash*)sp_gc_alloc(sizeof(sp_PolyPolyHash),sp_PolyPolyHash_fin,sp_PolyPolyHash_scan);h->cap=16;h->mask=15;h->keys=(sp_RbVal*)calloc(h->cap,sizeof(sp_RbVal));h->vals=(sp_RbVal*)calloc(h->cap,sizeof(sp_RbVal));h->order=(mrb_int*)malloc(sizeof(mrb_int)*h->cap);h->occ=(mrb_bool*)calloc(h->cap,sizeof(mrb_bool));h->len=0;return h;}
|
|
1099
3442
|
static void sp_PolyPolyHash_grow(sp_PolyPolyHash*h){mrb_int oc=h->cap;sp_RbVal*ok=h->keys;sp_RbVal*ov=h->vals;mrb_bool*oo=h->occ;mrb_int*oord=h->order;mrb_int olen=h->len;h->cap*=2;h->mask=h->cap-1;h->keys=(sp_RbVal*)calloc(h->cap,sizeof(sp_RbVal));h->vals=(sp_RbVal*)calloc(h->cap,sizeof(sp_RbVal));h->order=(mrb_int*)malloc(sizeof(mrb_int)*h->cap);h->occ=(mrb_bool*)calloc(h->cap,sizeof(mrb_bool));for(mrb_int i=0;i<olen;i++){mrb_int oi=oord[i];sp_RbVal k=ok[oi];mrb_int idx=(mrb_int)(sp_rbval_hash_key(k)&h->mask);while(h->occ[idx])idx=(idx+1)&h->mask;h->keys[idx]=k;h->vals[idx]=ov[oi];h->occ[idx]=TRUE;h->order[i]=idx;}free(ok);free(ov);free(oo);free(oord);}
|
|
1100
|
-
static sp_RbVal sp_PolyPolyHash_get(sp_PolyPolyHash*h,sp_RbVal k){mrb_int idx=(mrb_int)(sp_rbval_hash_key(k)&h->mask);while(h->occ[idx]){if(sp_rbval_eql_key(h->keys[idx],k))return h->vals[idx];idx=(idx+1)&h->mask;}return sp_box_nil();}
|
|
3443
|
+
static sp_RbVal sp_PolyPolyHash_get(sp_PolyPolyHash*h,sp_RbVal k){if(!h)return sp_box_nil();mrb_int idx=(mrb_int)(sp_rbval_hash_key(k)&h->mask);while(h->occ[idx]){if(sp_rbval_eql_key(h->keys[idx],k))return h->vals[idx];idx=(idx+1)&h->mask;}return sp_box_nil();}
|
|
1101
3444
|
static void sp_PolyPolyHash_set(sp_PolyPolyHash*h,sp_RbVal k,sp_RbVal v){if(h->len*2>=h->cap)sp_PolyPolyHash_grow(h);mrb_int idx=(mrb_int)(sp_rbval_hash_key(k)&h->mask);while(h->occ[idx]){if(sp_rbval_eql_key(h->keys[idx],k)){h->vals[idx]=v;return;}idx=(idx+1)&h->mask;}h->keys[idx]=k;h->vals[idx]=v;h->occ[idx]=TRUE;h->order[h->len]=idx;h->len++;}
|
|
1102
3445
|
static mrb_bool sp_PolyPolyHash_has_key(sp_PolyPolyHash*h,sp_RbVal k){mrb_int idx=(mrb_int)(sp_rbval_hash_key(k)&h->mask);while(h->occ[idx]){if(sp_rbval_eql_key(h->keys[idx],k))return TRUE;idx=(idx+1)&h->mask;}return FALSE;}
|
|
1103
3446
|
static mrb_int sp_PolyPolyHash_length(sp_PolyPolyHash*h){return h->len;}
|
|
1104
3447
|
static sp_PolyArray*sp_PolyPolyHash_keys(sp_PolyPolyHash*h){sp_PolyArray*a=sp_PolyArray_new();for(mrb_int i=0;i<h->len;i++)sp_PolyArray_push(a,h->keys[h->order[i]]);return a;}
|
|
1105
3448
|
static sp_PolyArray*sp_PolyPolyHash_values(sp_PolyPolyHash*h){sp_PolyArray*a=sp_PolyArray_new();for(mrb_int i=0;i<h->len;i++)sp_PolyArray_push(a,h->vals[h->order[i]]);return a;}
|
|
3449
|
+
static sp_PolyPolyHash*sp_PolyPolyHash_dup(sp_PolyPolyHash*h){sp_PolyPolyHash*r=sp_PolyPolyHash_new();for(mrb_int i=0;i<h->len;i++)sp_PolyPolyHash_set(r,h->keys[h->order[i]],h->vals[h->order[i]]);return r;}
|
|
3450
|
+
/* Issue #738: poly_poly_hash inspect using sp_poly_inspect on each
|
|
3451
|
+
k,v. Output mirrors Ruby's `{k=>v, ...}` for non-symbol keys and
|
|
3452
|
+
`{k: v, ...}` shorthand for symbol keys. */
|
|
3453
|
+
static const char *sp_poly_inspect(sp_RbVal v);
|
|
3454
|
+
static const char*sp_PolyPolyHash_inspect(sp_PolyPolyHash*h){sp_String*s=sp_String_new("{");if(!h){sp_String_append(s,"}");return s->data;}for(mrb_int i=0;i<h->len;i++){if(i>0)sp_String_append(s,", ");sp_RbVal k=h->keys[h->order[i]];if(k.tag==SP_TAG_SYM){sp_String_append(s,sp_sym_to_s((sp_sym)k.v.i));sp_String_append(s,": ");}else{sp_String_append(s,sp_poly_inspect(k));sp_String_append(s,"=>");}sp_String_append(s,sp_poly_inspect(h->vals[h->order[i]]));}sp_String_append(s,"}");return s->data;}
|
|
3455
|
+
/* Issue #738: Hash#invert -- swap keys and values. Returns a
|
|
3456
|
+
poly_poly_hash so any (key, value) pair shape is uniformly
|
|
3457
|
+
representable. str_str_hash_invert lives above (line ~1132)
|
|
3458
|
+
and stays as a same-type round-trip. */
|
|
3459
|
+
static sp_PolyPolyHash*sp_StrIntHash_invert_poly(sp_StrIntHash*h){sp_PolyPolyHash*r=sp_PolyPolyHash_new();if(!h)return r;for(mrb_int i=0;i<h->len;i++)sp_PolyPolyHash_set(r,sp_box_int(sp_StrIntHash_get(h,h->order[i])),sp_box_str(h->order[i]));return r;}
|
|
3460
|
+
static sp_PolyPolyHash*sp_IntStrHash_invert(sp_IntStrHash*h){sp_PolyPolyHash*r=sp_PolyPolyHash_new();if(!h)return r;for(mrb_int i=0;i<h->len;i++)sp_PolyPolyHash_set(r,sp_box_str(sp_IntStrHash_get(h,h->order[i])),sp_box_int(h->order[i]));return r;}
|
|
3461
|
+
static mrb_bool sp_PolyPolyHash_eq(sp_PolyPolyHash*a,sp_PolyPolyHash*b){if(!a||!b)return a==b;if(a->len!=b->len)return FALSE;for(mrb_int i=0;i<a->len;i++){sp_RbVal k=a->keys[a->order[i]];if(!sp_PolyPolyHash_has_key(b,k))return FALSE;if(!sp_poly_eq(sp_PolyPolyHash_get(a,k),sp_PolyPolyHash_get(b,k)))return FALSE;}return TRUE;}
|
|
3462
|
+
|
|
3463
|
+
/* JSON.generate support. sp_json_str quotes + escapes a string per
|
|
3464
|
+
JSON rules (control chars -> \uNNNN, plus the \" \\ \n \t \r \b \f
|
|
3465
|
+
shorthands; UTF-8 bytes >= 0x80 pass through unescaped, matching
|
|
3466
|
+
CRuby's default). sp_json_val recursively serializes any boxed
|
|
3467
|
+
value -- scalars, arrays, and the typed hash variants -- in CRuby's
|
|
3468
|
+
compact form (no spaces, `:` separator). Object keys are coerced to
|
|
3469
|
+
JSON strings (symbol -> name, integer -> decimal). Raw container
|
|
3470
|
+
element / value types are boxed and routed back through sp_json_val
|
|
3471
|
+
so nesting works. The sp_String accumulator is GC-rooted so a
|
|
3472
|
+
collection triggered by a nested allocation can't free it. */
|
|
3473
|
+
static const char *sp_json_str(const char *s) __attribute__((unused));
|
|
3474
|
+
static const char *sp_json_str(const char *s) {
|
|
3475
|
+
sp_String *o = sp_String_new("\"");
|
|
3476
|
+
SP_GC_ROOT(o);
|
|
3477
|
+
if (s) { const char *p = s; while (*p) { unsigned char c = (unsigned char)*p;
|
|
3478
|
+
if (c == '"') sp_String_append(o, "\\\"");
|
|
3479
|
+
else if (c == '\\') sp_String_append(o, "\\\\");
|
|
3480
|
+
else if (c == '\n') sp_String_append(o, "\\n");
|
|
3481
|
+
else if (c == '\t') sp_String_append(o, "\\t");
|
|
3482
|
+
else if (c == '\r') sp_String_append(o, "\\r");
|
|
3483
|
+
else if (c == '\b') sp_String_append(o, "\\b");
|
|
3484
|
+
else if (c == '\f') sp_String_append(o, "\\f");
|
|
3485
|
+
else if (c < 0x20) { char b[8]; snprintf(b, sizeof b, "\\u%04x", (unsigned)c); sp_String_append(o, b); }
|
|
3486
|
+
else { char ch[2]; ch[0] = (char)c; ch[1] = 0; sp_String_append(o, ch); }
|
|
3487
|
+
p++; } }
|
|
3488
|
+
sp_String_append(o, "\"");
|
|
3489
|
+
return o->data;
|
|
3490
|
+
}
|
|
3491
|
+
static const char *sp_json_val(sp_RbVal v) __attribute__((unused));
|
|
3492
|
+
static const char *sp_json_key(sp_RbVal k) {
|
|
3493
|
+
if (k.tag == SP_TAG_STR) return sp_json_str(k.v.s);
|
|
3494
|
+
if (k.tag == SP_TAG_SYM) return sp_json_str(sp_sym_to_s((sp_sym)k.v.i));
|
|
3495
|
+
if (k.tag == SP_TAG_INT) return sp_json_str(sp_int_to_s(k.v.i));
|
|
3496
|
+
if (k.tag == SP_TAG_BOOL) return sp_json_str(k.v.b ? "true" : "false");
|
|
3497
|
+
if (k.tag == SP_TAG_NIL) return sp_json_str("");
|
|
3498
|
+
return sp_json_str("");
|
|
3499
|
+
}
|
|
3500
|
+
static const char *sp_json_val(sp_RbVal v) {
|
|
3501
|
+
switch (v.tag) {
|
|
3502
|
+
case SP_TAG_INT: return sp_int_to_s(v.v.i);
|
|
3503
|
+
case SP_TAG_FLT: return sp_float_to_s(v.v.f);
|
|
3504
|
+
case SP_TAG_BOOL: return v.v.b ? SPL("true") : SPL("false");
|
|
3505
|
+
case SP_TAG_NIL: return SPL("null");
|
|
3506
|
+
case SP_TAG_STR: return sp_json_str(v.v.s);
|
|
3507
|
+
case SP_TAG_SYM: return sp_json_str(sp_sym_to_s((sp_sym)v.v.i));
|
|
3508
|
+
case SP_TAG_OBJ: {
|
|
3509
|
+
sp_String *o;
|
|
3510
|
+
switch (v.cls_id) {
|
|
3511
|
+
case SP_BUILTIN_INT_ARRAY: { sp_IntArray *a=(sp_IntArray*)v.v.p; o=sp_String_new("["); SP_GC_ROOT(o); if(a){for(mrb_int i=0;i<a->len;i++){if(i>0)sp_String_append(o,",");sp_String_append(o,sp_int_to_s(a->data[a->start+i]));}} sp_String_append(o,"]"); return o->data; }
|
|
3512
|
+
case SP_BUILTIN_FLT_ARRAY: { sp_FloatArray *a=(sp_FloatArray*)v.v.p; o=sp_String_new("["); SP_GC_ROOT(o); if(a){for(mrb_int i=0;i<a->len;i++){if(i>0)sp_String_append(o,",");sp_String_append(o,sp_float_to_s(a->data[i]));}} sp_String_append(o,"]"); return o->data; }
|
|
3513
|
+
case SP_BUILTIN_STR_ARRAY: { sp_StrArray *a=(sp_StrArray*)v.v.p; o=sp_String_new("["); SP_GC_ROOT(o); if(a){for(mrb_int i=0;i<a->len;i++){if(i>0)sp_String_append(o,",");sp_String_append(o,sp_json_str(a->data[i]));}} sp_String_append(o,"]"); return o->data; }
|
|
3514
|
+
case SP_BUILTIN_SYM_ARRAY: { sp_IntArray *a=(sp_IntArray*)v.v.p; o=sp_String_new("["); SP_GC_ROOT(o); if(a){for(mrb_int i=0;i<a->len;i++){if(i>0)sp_String_append(o,",");sp_String_append(o,sp_json_str(sp_sym_to_s((sp_sym)a->data[a->start+i])));}} sp_String_append(o,"]"); return o->data; }
|
|
3515
|
+
case SP_BUILTIN_POLY_ARRAY: { sp_PolyArray *a=(sp_PolyArray*)v.v.p; o=sp_String_new("["); SP_GC_ROOT(o); if(a){for(mrb_int i=0;i<a->len;i++){if(i>0)sp_String_append(o,",");sp_String_append(o,sp_json_val(a->data[i]));}} sp_String_append(o,"]"); return o->data; }
|
|
3516
|
+
case SP_BUILTIN_STR_INT_HASH: { sp_StrIntHash *h=(sp_StrIntHash*)v.v.p; o=sp_String_new("{"); SP_GC_ROOT(o); if(h){for(mrb_int i=0;i<h->len;i++){if(i>0)sp_String_append(o,",");sp_String_append(o,sp_json_str(h->order[i]));sp_String_append(o,":");sp_String_append(o,sp_int_to_s(sp_StrIntHash_get(h,h->order[i])));}} sp_String_append(o,"}"); return o->data; }
|
|
3517
|
+
case SP_BUILTIN_STR_STR_HASH: { sp_StrStrHash *h=(sp_StrStrHash*)v.v.p; o=sp_String_new("{"); SP_GC_ROOT(o); if(h){for(mrb_int i=0;i<h->len;i++){if(i>0)sp_String_append(o,",");sp_String_append(o,sp_json_str(h->order[i]));sp_String_append(o,":");sp_String_append(o,sp_json_str(sp_StrStrHash_get(h,h->order[i])));}} sp_String_append(o,"}"); return o->data; }
|
|
3518
|
+
case SP_BUILTIN_INT_STR_HASH: { sp_IntStrHash *h=(sp_IntStrHash*)v.v.p; o=sp_String_new("{"); SP_GC_ROOT(o); if(h){for(mrb_int i=0;i<h->len;i++){if(i>0)sp_String_append(o,",");sp_String_append(o,sp_json_str(sp_int_to_s(h->order[i])));sp_String_append(o,":");sp_String_append(o,sp_json_str(sp_IntStrHash_get(h,h->order[i])));}} sp_String_append(o,"}"); return o->data; }
|
|
3519
|
+
/* SYM_INT_HASH / SYM_STR_HASH structs are codegen-emitted (not
|
|
3520
|
+
in this static header), so their serializers are emitted by
|
|
3521
|
+
codegen as sp_SymIntHash_json / sp_SymStrHash_json and called
|
|
3522
|
+
directly from the JSON dispatch. A nested sym-int/str hash
|
|
3523
|
+
reaching here as a boxed poly value falls through to null. */
|
|
3524
|
+
case SP_BUILTIN_STR_POLY_HASH: { sp_StrPolyHash *h=(sp_StrPolyHash*)v.v.p; o=sp_String_new("{"); SP_GC_ROOT(o); if(h){for(mrb_int i=0;i<h->len;i++){if(i>0)sp_String_append(o,",");sp_String_append(o,sp_json_str(h->order[i]));sp_String_append(o,":");sp_String_append(o,sp_json_val(sp_StrPolyHash_get(h,h->order[i])));}} sp_String_append(o,"}"); return o->data; }
|
|
3525
|
+
case SP_BUILTIN_SYM_POLY_HASH: { sp_SymPolyHash *h=(sp_SymPolyHash*)v.v.p; o=sp_String_new("{"); SP_GC_ROOT(o); if(h){for(mrb_int i=0;i<h->len;i++){if(i>0)sp_String_append(o,",");sp_String_append(o,sp_json_str(sp_sym_to_s((sp_sym)h->order[i])));sp_String_append(o,":");sp_String_append(o,sp_json_val(sp_SymPolyHash_get(h,h->order[i])));}} sp_String_append(o,"}"); return o->data; }
|
|
3526
|
+
case SP_BUILTIN_POLY_POLY_HASH: { sp_PolyPolyHash *h=(sp_PolyPolyHash*)v.v.p; o=sp_String_new("{"); SP_GC_ROOT(o); if(h){for(mrb_int i=0;i<h->len;i++){if(i>0)sp_String_append(o,",");sp_String_append(o,sp_json_key(h->keys[h->order[i]]));sp_String_append(o,":");sp_String_append(o,sp_json_val(h->vals[h->order[i]]));}} sp_String_append(o,"}"); return o->data; }
|
|
3527
|
+
default: return SPL("null");
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
default: return SPL("null");
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
1106
3533
|
|
|
1107
3534
|
#include <setjmp.h>
|
|
1108
3535
|
#define SP_EXC_STACK_MAX 64
|
|
@@ -1110,10 +3537,218 @@ static jmp_buf sp_exc_stack[SP_EXC_STACK_MAX];
|
|
|
1110
3537
|
static const char *sp_exc_msg[SP_EXC_STACK_MAX];
|
|
1111
3538
|
static volatile int sp_exc_top = 0;
|
|
1112
3539
|
static const char *sp_exc_cls[SP_EXC_STACK_MAX];
|
|
1113
|
-
static volatile const char *sp_last_exc_cls =
|
|
1114
|
-
|
|
3540
|
+
static volatile const char *sp_last_exc_cls = sp_str_empty;
|
|
3541
|
+
/* ---- Native backtrace formatting (spinel --debug) ---------------------- */
|
|
3542
|
+
/* True for sp_<name> symbols that are runtime helpers, not user Ruby methods.
|
|
3543
|
+
A denylist of the lowercase runtime prefixes; user methods are sp_<rubyname>
|
|
3544
|
+
(top-level) or sp_<Class>_<method>, which don't match. Heuristic — a leaf
|
|
3545
|
+
runtime frame may occasionally slip through; refine with an emitted
|
|
3546
|
+
user-method allowlist later. */
|
|
3547
|
+
#if SP_BT_AVAILABLE
|
|
3548
|
+
static int sp_bt_is_runtime(const char *n) {
|
|
3549
|
+
static const char *pfx[] = {
|
|
3550
|
+
"int_", "str_", "float_", "sym_", "gc_", "bigint", "sprintf", "raise",
|
|
3551
|
+
"exc_", "range", "utf8", "oom", "bt_", "backtrace", "caller", "StrArray",
|
|
3552
|
+
"IntArray", "FloatArray", "PtrArray", "PolyArray", "Str", "Int", "Float",
|
|
3553
|
+
"Hash", "Range", "Complex", "Rational", "Sym", "alloc", "free", "to_s",
|
|
3554
|
+
"dup", "new", "pack", "unpack", "regex", "re_",
|
|
3555
|
+
/* arithmetic/runtime helpers that can raise and sit between the raise
|
|
3556
|
+
and the user frame (ZeroDivisionError via sp_idiv/sp_imod, etc.) */
|
|
3557
|
+
"idiv", "imod", "gcd", "fdiv", "ipow", "iclamp", "div_", "mod_", 0
|
|
3558
|
+
};
|
|
3559
|
+
for (int i = 0; pfx[i]; i++) {
|
|
3560
|
+
size_t l = strlen(pfx[i]);
|
|
3561
|
+
if (strncmp(n, pfx[i], l) == 0) return 1;
|
|
3562
|
+
}
|
|
3563
|
+
return 0;
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3566
|
+
/* Extract the symbol token from a backtrace_symbols line. Two formats:
|
|
3567
|
+
- glibc/Linux: "<module>(<symbol>+0x<off>) [0x<addr>]". The symbol is
|
|
3568
|
+
empty for unresolved frames (static or stripped fns) -> skip.
|
|
3569
|
+
- macOS: "<idx> <image> <addr> <symbol> + <off>".
|
|
3570
|
+
Returns NULL if it isn't a keepable user frame. Detect Linux by the '('
|
|
3571
|
+
that delimits the symbol (the macOS format has none). */
|
|
3572
|
+
static const char *sp_bt_symbol(const char *line) {
|
|
3573
|
+
char sym[256];
|
|
3574
|
+
const char *lp = strchr(line, '(');
|
|
3575
|
+
if (lp) { /* glibc/Linux paren form */
|
|
3576
|
+
const char *p = lp + 1;
|
|
3577
|
+
const char *end = p;
|
|
3578
|
+
while (*end && *end != '+' && *end != ')') end++;
|
|
3579
|
+
size_t len = (size_t)(end - p);
|
|
3580
|
+
if (len == 0 || len > 250) return 0; /* unresolved (static/stripped) */
|
|
3581
|
+
memcpy(sym, p, len); sym[len] = 0;
|
|
3582
|
+
} else { /* macOS: "<idx> <image> <addr> <symbol> + <off>" */
|
|
3583
|
+
/* The symbol is the token just before the " + <off>" delimiter. Parse
|
|
3584
|
+
backward from the last " + " rather than forward from "0x" — an image
|
|
3585
|
+
path containing "0x" (e.g. /path/0x_proj/bin) would otherwise misparse. */
|
|
3586
|
+
const char *plus = 0, *q = line;
|
|
3587
|
+
while ((q = strstr(q, " + ")) != 0) { plus = q; q += 3; }
|
|
3588
|
+
if (!plus) return 0;
|
|
3589
|
+
const char *end = plus;
|
|
3590
|
+
const char *p = end;
|
|
3591
|
+
while (p > line && p[-1] != ' ') p--; /* back up to the symbol's start */
|
|
3592
|
+
size_t len = (size_t)(end - p);
|
|
3593
|
+
if (len == 0 || len > 250) return 0;
|
|
3594
|
+
memcpy(sym, p, len); sym[len] = 0;
|
|
3595
|
+
}
|
|
3596
|
+
if (strcmp(sym, "main") == 0) return strdup("<main>");
|
|
3597
|
+
if (strncmp(sym, "sp_", 3) != 0) return 0; /* skip non-Spinel frames */
|
|
3598
|
+
const char *name = sym + 3;
|
|
3599
|
+
if (sp_bt_is_runtime(name)) return 0;
|
|
3600
|
+
/* De-mangle sp_<Class>_<method> back to Ruby. A Spinel symbol is a path of
|
|
3601
|
+
CamelCase class segments (each from a `::`, joined by `_`) followed by the
|
|
3602
|
+
method; the method is the first segment that starts lowercase. A literal
|
|
3603
|
+
`cls` segment marks a singleton method:
|
|
3604
|
+
sp_Helper_cls_boom -> Helper.boom
|
|
3605
|
+
sp_Tep_Url_parse_query -> Tep::Url#parse_query
|
|
3606
|
+
sp_Tep_AuthOAuth2_cls_find -> Tep::AuthOAuth2.find
|
|
3607
|
+
sp_toplevel -> toplevel (top-level method, no class)
|
|
3608
|
+
(Method names stay sanitized — e.g. enabled? is enabled_p; reversing that
|
|
3609
|
+
needs the emitted name table, a separate refinement.) */
|
|
3610
|
+
const char *mstart = 0; /* first lowercase-starting segment = the method */
|
|
3611
|
+
for (const char *p = name; *p; p++) {
|
|
3612
|
+
int seg_start = (p == name) || (p[-1] == '_');
|
|
3613
|
+
if (seg_start && *p >= 'a' && *p <= 'z') { mstart = p; break; }
|
|
3614
|
+
}
|
|
3615
|
+
if (!mstart) return strdup(name); /* all-uppercase: leave as-is */
|
|
3616
|
+
if (mstart == name) { /* no class path: top-level */
|
|
3617
|
+
if (strncmp(name, "cls_", 4) == 0) return strdup(name + 4); /* top-level singleton */
|
|
3618
|
+
return strdup(name);
|
|
3619
|
+
}
|
|
3620
|
+
char out[256]; size_t o = 0;
|
|
3621
|
+
const char *meth; char sep;
|
|
3622
|
+
if (strncmp(mstart, "cls_", 4) == 0) { meth = mstart + 4; sep = '.'; } /* singleton */
|
|
3623
|
+
else { meth = mstart; sep = '#'; } /* instance */
|
|
3624
|
+
for (const char *p = name; p < mstart - 1 && o + 2 < sizeof(out); p++) {
|
|
3625
|
+
if (*p == '_') { out[o++] = ':'; out[o++] = ':'; } /* class-path `_` was a `::` */
|
|
3626
|
+
else out[o++] = *p;
|
|
3627
|
+
}
|
|
3628
|
+
if (o + 1 < sizeof(out)) out[o++] = sep;
|
|
3629
|
+
size_t ml = strlen(meth);
|
|
3630
|
+
if (o + ml < sizeof(out)) { memcpy(out + o, meth, ml); o += ml; }
|
|
3631
|
+
out[o] = 0;
|
|
3632
|
+
return strdup(out);
|
|
3633
|
+
}
|
|
3634
|
+
|
|
3635
|
+
static sp_StrArray *sp_bt_format(void **buf, int n) {
|
|
3636
|
+
sp_StrArray *a = sp_StrArray_new();
|
|
3637
|
+
if (!sp_bt_enabled || n <= 0) return a;
|
|
3638
|
+
char **syms = backtrace_symbols(buf, n);
|
|
3639
|
+
if (!syms) return a;
|
|
3640
|
+
const char *src = (sp_bt_srcfile && sp_bt_srcfile[0]) ? sp_bt_srcfile : "(spinel)";
|
|
3641
|
+
for (int i = 0; i < n; i++) {
|
|
3642
|
+
char *name = (char *)sp_bt_symbol(syms[i]); /* always strdup'd; free after use */
|
|
3643
|
+
if (!name) continue;
|
|
3644
|
+
sp_StrArray_push(a, sp_sprintf("%s:in `%s'", src, name));
|
|
3645
|
+
free(name);
|
|
3646
|
+
}
|
|
3647
|
+
free(syms);
|
|
3648
|
+
return a;
|
|
3649
|
+
}
|
|
3650
|
+
#endif
|
|
3651
|
+
|
|
3652
|
+
/* Backtrace captured at the most recent raise (for Exception#backtrace). */
|
|
3653
|
+
static sp_StrArray *sp_backtrace_captured(void) {
|
|
3654
|
+
#if SP_BT_AVAILABLE
|
|
3655
|
+
return sp_bt_format(sp_bt_buf, sp_bt_n);
|
|
3656
|
+
#else
|
|
3657
|
+
return sp_StrArray_new();
|
|
3658
|
+
#endif
|
|
3659
|
+
}
|
|
3660
|
+
|
|
3661
|
+
/* The current stack, captured now (for Kernel#caller). */
|
|
3662
|
+
static sp_StrArray *sp_caller_now(void) {
|
|
3663
|
+
#if SP_BT_AVAILABLE
|
|
3664
|
+
if (!sp_bt_enabled) return sp_StrArray_new();
|
|
3665
|
+
void *buf[256];
|
|
3666
|
+
int n = backtrace(buf, 256);
|
|
3667
|
+
return sp_bt_format(buf, n);
|
|
3668
|
+
#else
|
|
3669
|
+
return sp_StrArray_new();
|
|
3670
|
+
#endif
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
SP_NORETURN SP_COLD void sp_raise_cls(const char *cls, const char *msg) {
|
|
3674
|
+
#if SP_BT_AVAILABLE
|
|
3675
|
+
if (sp_bt_enabled) sp_bt_n = backtrace(sp_bt_buf, 256);
|
|
3676
|
+
#endif
|
|
3677
|
+
if (sp_exc_top > 0) { sp_exc_msg[sp_exc_top-1] = msg; sp_exc_cls[sp_exc_top-1] = cls; sp_last_exc_cls = cls; longjmp(sp_exc_stack[sp_exc_top-1], 1); } fprintf(stderr, "unhandled exception: %s\n", msg); exit(1); }
|
|
1115
3678
|
static void sp_raise(const char *msg) { sp_raise_cls("RuntimeError", msg); }
|
|
1116
3679
|
|
|
3680
|
+
/* Issue #781: bridge between the regex compile-error path (which lives
|
|
3681
|
+
in the .a library and can't see the user program's static-inline
|
|
3682
|
+
sp_raise_cls) and the user's Ruby-level exception machinery. The
|
|
3683
|
+
library calls sp_re_set_error_handler at startup -- codegen emits
|
|
3684
|
+
the install call after the exception infrastructure is set up. */
|
|
3685
|
+
static void sp_re_default_error_handler(const char *msg) {
|
|
3686
|
+
sp_raise_cls("RegexpError", msg);
|
|
3687
|
+
}
|
|
3688
|
+
/* Issue #846: during sp_re_init (before main()'s setjmp scope is
|
|
3689
|
+
active), a bad literal `Regexp.new("[invalid")` pattern would
|
|
3690
|
+
route through sp_raise_cls -> "unhandled exception" + exit
|
|
3691
|
+
because sp_exc_top is 0. Install a startup handler that longjmps
|
|
3692
|
+
back to sp_re_init's wrapping setjmp; the codegen-emitted loop
|
|
3693
|
+
then stashes the error per slot for a deferred raise from the
|
|
3694
|
+
first use site (where the user's begin/rescue is active). The
|
|
3695
|
+
re_compile contract requires the error callback to not return
|
|
3696
|
+
(otherwise the library's fall-through fprintf+exit fires) so
|
|
3697
|
+
we longjmp out. */
|
|
3698
|
+
static const char *sp_re_startup_err = NULL;
|
|
3699
|
+
static jmp_buf sp_re_startup_jmp;
|
|
3700
|
+
static void sp_re_startup_error_handler(const char *msg) {
|
|
3701
|
+
if (msg) {
|
|
3702
|
+
size_t n = strlen(msg);
|
|
3703
|
+
char *buf = sp_str_alloc_raw(n + 1);
|
|
3704
|
+
memcpy(buf, msg, n);
|
|
3705
|
+
buf[n] = 0;
|
|
3706
|
+
sp_re_startup_err = buf;
|
|
3707
|
+
}
|
|
3708
|
+
longjmp(sp_re_startup_jmp, 1);
|
|
3709
|
+
}
|
|
3710
|
+
extern void sp_re_set_error_handler(void (*fn)(const char *msg));
|
|
3711
|
+
static void sp_mark_in_flight_exceptions(void) { for (int i = 0; i < sp_exc_top; i++) sp_mark_string(sp_exc_msg[i]); }
|
|
3712
|
+
|
|
3713
|
+
/* sp_Exception: first-class exception object. cls_name is a pointer
|
|
3714
|
+
to the per-class const string literal emitted by codegen
|
|
3715
|
+
(sp_class_names[] entry; not GC-managed). msg is GC-managed
|
|
3716
|
+
(sp_str_alloc'd). */
|
|
3717
|
+
typedef struct sp_Exception_s {
|
|
3718
|
+
const char *cls_name;
|
|
3719
|
+
const char *msg;
|
|
3720
|
+
} sp_Exception;
|
|
3721
|
+
static void sp_exc_gc_scan(void *p) {
|
|
3722
|
+
sp_Exception *e = (sp_Exception *)p;
|
|
3723
|
+
if (e->msg) sp_mark_string(e->msg);
|
|
3724
|
+
/* cls_name points into the .rodata sp_class_names[] table -- not
|
|
3725
|
+
a GC-managed string, no mark needed. */
|
|
3726
|
+
}
|
|
3727
|
+
static sp_Exception *sp_exc_new(const char *cls_name, const char *msg) {
|
|
3728
|
+
sp_Exception *e = (sp_Exception *)sp_gc_alloc(sizeof(sp_Exception), NULL, sp_exc_gc_scan);
|
|
3729
|
+
e->cls_name = cls_name ? cls_name : "RuntimeError";
|
|
3730
|
+
e->msg = msg ? msg : sp_str_empty;
|
|
3731
|
+
return e;
|
|
3732
|
+
}
|
|
3733
|
+
/* Accept `volatile` pointers: LV slots holding sp_Exception * are
|
|
3734
|
+
declared volatile when they live across setjmp, so callers may
|
|
3735
|
+
pass volatile-qualified pointers in. The pointee itself isn't
|
|
3736
|
+
volatile (cls_name/msg are stable post-construction), so we
|
|
3737
|
+
strip volatile internally for one access. */
|
|
3738
|
+
static const char *sp_exc_class_name(volatile sp_Exception *ve) {
|
|
3739
|
+
sp_Exception *e = (sp_Exception *)ve;
|
|
3740
|
+
return e ? e->cls_name : "RuntimeError";
|
|
3741
|
+
}
|
|
3742
|
+
static const char *sp_exc_message(volatile sp_Exception *ve) {
|
|
3743
|
+
sp_Exception *e = (sp_Exception *)ve;
|
|
3744
|
+
return e ? e->msg : sp_str_empty;
|
|
3745
|
+
}
|
|
3746
|
+
static void sp_raise_exc(volatile sp_Exception *ve) {
|
|
3747
|
+
sp_Exception *e = (sp_Exception *)ve;
|
|
3748
|
+
if (!e) sp_raise("(nil exception)");
|
|
3749
|
+
sp_raise_cls(e->cls_name, e->msg);
|
|
3750
|
+
}
|
|
3751
|
+
|
|
1117
3752
|
/* Cross-TU bridge for sp_bigint.c (compiled as a separate translation
|
|
1118
3753
|
unit; can't see static helpers in this header). Defined non-static
|
|
1119
3754
|
so sp_bigint.c's mrb_raise macro can dispatch into spinel's
|
|
@@ -1128,6 +3763,64 @@ static mrb_int sp_catch_val[SP_CATCH_STACK_MAX];
|
|
|
1128
3763
|
static volatile int sp_catch_top = 0;
|
|
1129
3764
|
static void sp_throw(const char *tag, mrb_int val) { int i = sp_catch_top - 1; while (i >= 0) { if (strcmp(sp_catch_tag[i], tag) == 0) { sp_catch_val[i] = val; sp_catch_top = i + 1; longjmp(sp_catch_stack[i], 1); } i--; } fprintf(stderr, "uncaught throw: %s\n", tag); exit(1); }
|
|
1130
3765
|
|
|
3766
|
+
/* Kernel#sleep with sub-second precision. Argument is seconds as a
|
|
3767
|
+
double so `sleep(0.5)` actually waits 500ms; the legacy `sleep((unsigned)0.5)`
|
|
3768
|
+
cast truncated to 0 and returned immediately. POSIX uses
|
|
3769
|
+
nanosleep(); Windows uses Sleep() (milliseconds). Negative or NaN
|
|
3770
|
+
inputs no-op. */
|
|
3771
|
+
static void sp_sleep(mrb_float s) {
|
|
3772
|
+
if (!(s > 0.0)) return;
|
|
3773
|
+
#ifdef _WIN32
|
|
3774
|
+
DWORD ms = (DWORD)(s * 1000.0);
|
|
3775
|
+
if (ms == 0) ms = 1;
|
|
3776
|
+
Sleep(ms);
|
|
3777
|
+
#else
|
|
3778
|
+
struct timespec req;
|
|
3779
|
+
req.tv_sec = (time_t)s;
|
|
3780
|
+
req.tv_nsec = (long)((s - (double)req.tv_sec) * 1e9);
|
|
3781
|
+
if (req.tv_nsec < 0) req.tv_nsec = 0;
|
|
3782
|
+
if (req.tv_nsec >= 1000000000L) req.tv_nsec = 999999999L;
|
|
3783
|
+
while (nanosleep(&req, &req) == -1 && errno == EINTR) {}
|
|
3784
|
+
#endif
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3787
|
+
#ifdef _WIN32
|
|
3788
|
+
static DWORD sp_file_attributes(const char *path) {
|
|
3789
|
+
if (!path) return INVALID_FILE_ATTRIBUTES;
|
|
3790
|
+
int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, -1, NULL, 0);
|
|
3791
|
+
if (len <= 0) return INVALID_FILE_ATTRIBUTES;
|
|
3792
|
+
wchar_t *wpath = (wchar_t *)malloc(sizeof(wchar_t) * (size_t)len);
|
|
3793
|
+
if (!wpath) return INVALID_FILE_ATTRIBUTES;
|
|
3794
|
+
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, -1, wpath, len) <= 0) {
|
|
3795
|
+
free(wpath);
|
|
3796
|
+
return INVALID_FILE_ATTRIBUTES;
|
|
3797
|
+
}
|
|
3798
|
+
DWORD attrs = GetFileAttributesW(wpath);
|
|
3799
|
+
free(wpath);
|
|
3800
|
+
return attrs;
|
|
3801
|
+
}
|
|
3802
|
+
|
|
3803
|
+
static mrb_bool sp_file_directory(const char *path) {
|
|
3804
|
+
DWORD attrs = sp_file_attributes(path);
|
|
3805
|
+
return attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY);
|
|
3806
|
+
}
|
|
3807
|
+
|
|
3808
|
+
static mrb_bool sp_file_file(const char *path) {
|
|
3809
|
+
DWORD attrs = sp_file_attributes(path);
|
|
3810
|
+
return attrs != INVALID_FILE_ATTRIBUTES && !(attrs & FILE_ATTRIBUTE_DIRECTORY);
|
|
3811
|
+
}
|
|
3812
|
+
#else
|
|
3813
|
+
static mrb_bool sp_file_directory(const char *path) {
|
|
3814
|
+
struct stat st;
|
|
3815
|
+
return path && stat(path, &st) == 0 && S_ISDIR(st.st_mode);
|
|
3816
|
+
}
|
|
3817
|
+
|
|
3818
|
+
static mrb_bool sp_file_file(const char *path) {
|
|
3819
|
+
struct stat st;
|
|
3820
|
+
return path && stat(path, &st) == 0 && S_ISREG(st.st_mode);
|
|
3821
|
+
}
|
|
3822
|
+
#endif
|
|
3823
|
+
|
|
1131
3824
|
/* Text mode ("r") matches CRuby's File.read: on Windows, CRLF is
|
|
1132
3825
|
normalized to LF on read, which cancels out fopen("w")'s
|
|
1133
3826
|
LF→CRLF on write. Without this, content from File.read passed to
|
|
@@ -1135,10 +3828,79 @@ static void sp_throw(const char *tag, mrb_int val) { int i = sp_catch_top - 1; w
|
|
|
1135
3828
|
and `\r\n` becomes `\r\r\n`. fread's actual byte count drives
|
|
1136
3829
|
null-termination because text mode shrinks the byte count below
|
|
1137
3830
|
ftell's raw-file size. */
|
|
1138
|
-
static const char *sp_file_read(const char *path) {
|
|
1139
|
-
|
|
3831
|
+
static const char *sp_file_read(const char *path) {
|
|
3832
|
+
if (sp_file_directory(path)) {
|
|
3833
|
+
sp_raise_cls("Errno::EISDIR", sp_sprintf("Is a directory @ io_fread - %s", path));
|
|
3834
|
+
}
|
|
3835
|
+
FILE *f = fopen(path, "r");
|
|
3836
|
+
if (!f) return &("\xff" "")[1];
|
|
3837
|
+
fseek(f, 0, SEEK_END);
|
|
3838
|
+
long sz = ftell(f);
|
|
3839
|
+
fseek(f, 0, SEEK_SET);
|
|
3840
|
+
char *buf = sp_str_alloc(sz);
|
|
3841
|
+
size_t n = 0;
|
|
3842
|
+
if (sz > 0) {
|
|
3843
|
+
n = fread(buf, 1, sz, f);
|
|
3844
|
+
}
|
|
3845
|
+
buf[n] = 0;
|
|
3846
|
+
fclose(f);
|
|
3847
|
+
return buf;
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
static void sp_file_write(const char *path, const char *data) {
|
|
3851
|
+
if (sp_file_directory(path)) {
|
|
3852
|
+
sp_raise_cls("Errno::EISDIR", sp_sprintf("Is a directory @ rb_sysopen - %s", path));
|
|
3853
|
+
}
|
|
3854
|
+
FILE *f = fopen(path, "wb");
|
|
3855
|
+
if (f) {
|
|
3856
|
+
fwrite(data, 1, sp_str_byte_len(data), f);
|
|
3857
|
+
fclose(f);
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
1140
3860
|
static mrb_bool sp_file_exist(const char *path) { FILE *f = fopen(path, "r"); if (f) { fclose(f); return TRUE; } return FALSE; }
|
|
1141
3861
|
static void sp_file_delete(const char *path) { remove(path); }
|
|
3862
|
+
static sp_Time sp_file_mtime(const char *path) {
|
|
3863
|
+
if (!path) {
|
|
3864
|
+
sp_raise_cls("TypeError", "no implicit conversion of nil into String");
|
|
3865
|
+
return (sp_Time){0, 0, 0};
|
|
3866
|
+
}
|
|
3867
|
+
#if defined(_WIN32)
|
|
3868
|
+
int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, -1, NULL, 0);
|
|
3869
|
+
if (len > 0) {
|
|
3870
|
+
wchar_t *wpath = (wchar_t *)malloc(sizeof(wchar_t) * (size_t)len);
|
|
3871
|
+
if (wpath) {
|
|
3872
|
+
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, -1, wpath, len) > 0) {
|
|
3873
|
+
WIN32_FILE_ATTRIBUTE_DATA attr_data;
|
|
3874
|
+
if (GetFileAttributesExW(wpath, GetFileExInfoStandard, &attr_data)) {
|
|
3875
|
+
free(wpath);
|
|
3876
|
+
ULARGE_INTEGER ull;
|
|
3877
|
+
ull.LowPart = attr_data.ftLastWriteTime.dwLowDateTime;
|
|
3878
|
+
ull.HighPart = attr_data.ftLastWriteTime.dwHighDateTime;
|
|
3879
|
+
int64_t total_ns = (ull.QuadPart - 116444736000000000ULL) * 100;
|
|
3880
|
+
return (sp_Time){total_ns / 1000000000LL, (int32_t)(total_ns % 1000000000LL), 0};
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
free(wpath);
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
sp_raise_cls("Errno::ENOENT", sp_sprintf("No such file or directory @ File.mtime - %s", path));
|
|
3887
|
+
return (sp_Time){0, 0, 0};
|
|
3888
|
+
#else
|
|
3889
|
+
struct stat st;
|
|
3890
|
+
if (stat(path, &st) == -1) {
|
|
3891
|
+
sp_raise_cls(errno == ENOENT ? "Errno::ENOENT" : "RuntimeError", sp_sprintf("%s @ File.mtime - %s", strerror(errno), path));
|
|
3892
|
+
return (sp_Time){0, 0, 0};
|
|
3893
|
+
}
|
|
3894
|
+
#if defined(__APPLE__)
|
|
3895
|
+
return (sp_Time){(int64_t)st.st_mtimespec.tv_sec, (int32_t)st.st_mtimespec.tv_nsec, 0};
|
|
3896
|
+
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
|
|
3897
|
+
return (sp_Time){(int64_t)st.st_mtimespec.tv_sec, (int32_t)st.st_mtimespec.tv_nsec, 0};
|
|
3898
|
+
#else
|
|
3899
|
+
/* Linux / others with st_mtim */
|
|
3900
|
+
return (sp_Time){(int64_t)st.st_mtim.tv_sec, (int32_t)st.st_mtim.tv_nsec, 0};
|
|
3901
|
+
#endif
|
|
3902
|
+
#endif
|
|
3903
|
+
}
|
|
1142
3904
|
static const char *sp_backtick(const char *cmd) { FILE *p = popen(cmd, "r"); if (!p) return sp_str_empty; char *buf = sp_str_alloc_raw(4096); size_t n = fread(buf, 1, 4095, p); buf[n] = 0; pclose(p); return buf; }
|
|
1143
3905
|
static const char *sp_file_basename(const char *path) {
|
|
1144
3906
|
const char *s = strrchr(path, '/');
|
|
@@ -1153,12 +3915,213 @@ static const char *sp_file_basename(const char *path) {
|
|
|
1153
3915
|
memcpy(buf, base, n + 1);
|
|
1154
3916
|
return buf;
|
|
1155
3917
|
}
|
|
3918
|
+
/* Issue #892: File.dirname / File.extname / Dir.pwd. */
|
|
3919
|
+
static const char *sp_file_dirname(const char *path) {
|
|
3920
|
+
const char *s = strrchr(path, '/');
|
|
3921
|
+
if (!s) { char *r = sp_str_alloc(1); r[0] = '.'; r[1] = 0; return r; }
|
|
3922
|
+
if (s == path) { char *r = sp_str_alloc(1); r[0] = '/'; r[1] = 0; return r; }
|
|
3923
|
+
size_t n = (size_t)(s - path);
|
|
3924
|
+
char *buf = sp_str_alloc(n);
|
|
3925
|
+
memcpy(buf, path, n); buf[n] = 0;
|
|
3926
|
+
return buf;
|
|
3927
|
+
}
|
|
3928
|
+
static const char *sp_file_extname(const char *path) {
|
|
3929
|
+
const char *base = strrchr(path, '/');
|
|
3930
|
+
base = base ? base + 1 : path;
|
|
3931
|
+
const char *dot = strrchr(base, '.');
|
|
3932
|
+
/* CRuby: leading-dot files (".bashrc") return "". Trailing-dot
|
|
3933
|
+
paths ("foo.") keep the dot since Ruby 2.7. */
|
|
3934
|
+
if (!dot || dot == base) return sp_str_empty;
|
|
3935
|
+
size_t n = strlen(dot);
|
|
3936
|
+
char *buf = sp_str_alloc(n);
|
|
3937
|
+
memcpy(buf, dot, n + 1);
|
|
3938
|
+
return buf;
|
|
3939
|
+
}
|
|
3940
|
+
static const char *sp_dir_pwd(void) {
|
|
3941
|
+
char tmp[4096];
|
|
3942
|
+
if (!getcwd(tmp, sizeof(tmp))) { return sp_str_empty; }
|
|
3943
|
+
size_t n = strlen(tmp);
|
|
3944
|
+
char *buf = sp_str_alloc(n);
|
|
3945
|
+
memcpy(buf, tmp, n + 1);
|
|
3946
|
+
return buf;
|
|
3947
|
+
}
|
|
3948
|
+
/* Dir singleton methods. mkdir/rmdir/chdir use the platform call (the
|
|
3949
|
+
Windows _-prefixed variants take a single path argument); each returns
|
|
3950
|
+
0 on success, matching CRuby's `Dir.mkdir` etc. */
|
|
3951
|
+
static mrb_int sp_dir_mkdir(const char *path) {
|
|
3952
|
+
#ifdef _WIN32
|
|
3953
|
+
return (mrb_int)_mkdir(path);
|
|
3954
|
+
#else
|
|
3955
|
+
return (mrb_int)mkdir(path, 0777);
|
|
3956
|
+
#endif
|
|
3957
|
+
}
|
|
3958
|
+
static mrb_int sp_dir_rmdir(const char *path) {
|
|
3959
|
+
#ifdef _WIN32
|
|
3960
|
+
return (mrb_int)_rmdir(path);
|
|
3961
|
+
#else
|
|
3962
|
+
return (mrb_int)rmdir(path);
|
|
3963
|
+
#endif
|
|
3964
|
+
}
|
|
3965
|
+
static mrb_int sp_dir_chdir(const char *path) {
|
|
3966
|
+
#ifdef _WIN32
|
|
3967
|
+
return (mrb_int)_chdir(path);
|
|
3968
|
+
#else
|
|
3969
|
+
return (mrb_int)chdir(path);
|
|
3970
|
+
#endif
|
|
3971
|
+
}
|
|
3972
|
+
static const char *sp_dir_home(void) {
|
|
3973
|
+
const char *h = getenv("HOME");
|
|
3974
|
+
#ifdef _WIN32
|
|
3975
|
+
if (!h || !*h) h = getenv("USERPROFILE");
|
|
3976
|
+
#endif
|
|
3977
|
+
if (!h) return sp_str_empty;
|
|
3978
|
+
return sp_str_dup_external(h);
|
|
3979
|
+
}
|
|
3980
|
+
/* Wildcard match for a single path component: `*` (any run, no `/`),
|
|
3981
|
+
`?` (one char). Recursive over `*`; adequate for the common
|
|
3982
|
+
single-directory glob patterns. */
|
|
3983
|
+
static int sp_fnmatch1(const char *pat, const char *str) {
|
|
3984
|
+
while (*pat) {
|
|
3985
|
+
if (*pat == '*') {
|
|
3986
|
+
pat++;
|
|
3987
|
+
if (!*pat) return 1;
|
|
3988
|
+
while (*str) { if (sp_fnmatch1(pat, str)) return 1; str++; }
|
|
3989
|
+
return sp_fnmatch1(pat, str);
|
|
3990
|
+
} else if (*pat == '?') {
|
|
3991
|
+
if (!*str) return 0;
|
|
3992
|
+
pat++; str++;
|
|
3993
|
+
} else {
|
|
3994
|
+
if (*pat != *str) return 0;
|
|
3995
|
+
pat++; str++;
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
return *str == 0;
|
|
3999
|
+
}
|
|
4000
|
+
/* Dir.glob(pattern): list directory entries matching the last component
|
|
4001
|
+
of `pattern` (an optional leading `dir/` selects the directory). Hidden
|
|
4002
|
+
entries match only when the pattern itself begins with `.`. Results are
|
|
4003
|
+
sorted, matching Ruby 3.0+ default glob ordering. */
|
|
4004
|
+
static sp_StrArray *sp_dir_glob(const char *pattern) {
|
|
4005
|
+
sp_StrArray *a = sp_StrArray_new();
|
|
4006
|
+
if (!pattern) return a;
|
|
4007
|
+
const char *slash = strrchr(pattern, '/');
|
|
4008
|
+
char dirbuf[1024];
|
|
4009
|
+
const char *dirpath;
|
|
4010
|
+
const char *base_pat;
|
|
4011
|
+
if (slash) {
|
|
4012
|
+
size_t dl = (size_t)(slash - pattern);
|
|
4013
|
+
if (dl >= sizeof(dirbuf)) return a;
|
|
4014
|
+
memcpy(dirbuf, pattern, dl);
|
|
4015
|
+
dirbuf[dl] = 0;
|
|
4016
|
+
dirpath = (dl == 0) ? "/" : dirbuf;
|
|
4017
|
+
base_pat = slash + 1;
|
|
4018
|
+
} else {
|
|
4019
|
+
dirpath = ".";
|
|
4020
|
+
base_pat = pattern;
|
|
4021
|
+
}
|
|
4022
|
+
DIR *d = opendir(dirpath);
|
|
4023
|
+
if (!d) return a;
|
|
4024
|
+
struct dirent *e;
|
|
4025
|
+
while ((e = readdir(d)) != NULL) {
|
|
4026
|
+
const char *name = e->d_name;
|
|
4027
|
+
if (name[0] == '.' && base_pat[0] != '.') continue;
|
|
4028
|
+
if (sp_fnmatch1(base_pat, name)) {
|
|
4029
|
+
char full[2048];
|
|
4030
|
+
if (slash) snprintf(full, sizeof(full), "%s/%s", dirbuf, name);
|
|
4031
|
+
else snprintf(full, sizeof(full), "%s", name);
|
|
4032
|
+
char *copy = sp_str_alloc(strlen(full));
|
|
4033
|
+
strcpy(copy, full);
|
|
4034
|
+
sp_StrArray_push(a, copy);
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
closedir(d);
|
|
4038
|
+
sp_StrArray_sort_bang(a);
|
|
4039
|
+
return a;
|
|
4040
|
+
}
|
|
4041
|
+
|
|
4042
|
+
/* File.expand_path(path[, base]) -- CRuby-compatible pure-string
|
|
4043
|
+
expansion (does NOT require the path to exist). A leading `~` / `~/`
|
|
4044
|
+
becomes $HOME; a relative path is resolved against `base` (itself
|
|
4045
|
+
expanded; NULL means the current working directory); and `.` / `..` /
|
|
4046
|
+
duplicate-slash segments are collapsed. `~user` is unsupported and is
|
|
4047
|
+
left as-is. */
|
|
4048
|
+
static const char *sp_file_expand_path(const char *path, const char *base) {
|
|
4049
|
+
char raw[8192];
|
|
4050
|
+
char cwd[4096];
|
|
4051
|
+
const char *home = getenv("HOME");
|
|
4052
|
+
if (!home) home = "";
|
|
4053
|
+
if (!path) path = "";
|
|
4054
|
+
|
|
4055
|
+
/* The `%.4000s` precision caps each component so the compiler can
|
|
4056
|
+
prove the combined output (<= 8001) fits in the 8192 buffer -- this
|
|
4057
|
+
silences -Wformat-truncation (an error under the test harness's
|
|
4058
|
+
-Werror). 4000 chars/component is well past PATH_MAX, so real paths
|
|
4059
|
+
never truncate. */
|
|
4060
|
+
if (path[0] == '~' && (path[1] == '\0' || path[1] == '/')) {
|
|
4061
|
+
snprintf(raw, sizeof(raw), "%.4000s%.4000s", home, path + 1);
|
|
4062
|
+
} else if (path[0] == '/') {
|
|
4063
|
+
snprintf(raw, sizeof(raw), "%.4000s", path);
|
|
4064
|
+
} else {
|
|
4065
|
+
char basebuf[8192];
|
|
4066
|
+
const char *b;
|
|
4067
|
+
if (base && base[0]) {
|
|
4068
|
+
if (base[0] == '~' && (base[1] == '\0' || base[1] == '/')) {
|
|
4069
|
+
snprintf(basebuf, sizeof(basebuf), "%.4000s%.4000s", home, base + 1);
|
|
4070
|
+
b = basebuf;
|
|
4071
|
+
} else if (base[0] == '/') {
|
|
4072
|
+
b = base;
|
|
4073
|
+
} else {
|
|
4074
|
+
if (!getcwd(cwd, sizeof(cwd))) cwd[0] = 0;
|
|
4075
|
+
snprintf(basebuf, sizeof(basebuf), "%.4000s/%.4000s", cwd, base);
|
|
4076
|
+
b = basebuf;
|
|
4077
|
+
}
|
|
4078
|
+
} else {
|
|
4079
|
+
if (!getcwd(cwd, sizeof(cwd))) cwd[0] = 0;
|
|
4080
|
+
b = cwd;
|
|
4081
|
+
}
|
|
4082
|
+
snprintf(raw, sizeof(raw), "%.4000s/%.4000s", b, path);
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
/* Normalize: walk segments, collapsing `.`/`..`/`//`. seg_start[k]
|
|
4086
|
+
records the output length to roll back to when a `..` pops the
|
|
4087
|
+
k-th kept segment. */
|
|
4088
|
+
size_t rawlen = strlen(raw);
|
|
4089
|
+
char *out = sp_str_alloc(rawlen + 1);
|
|
4090
|
+
size_t seg_start[1024];
|
|
4091
|
+
int nseg = 0;
|
|
4092
|
+
size_t olen = 0;
|
|
4093
|
+
out[olen++] = '/';
|
|
4094
|
+
const char *p = raw;
|
|
4095
|
+
while (*p) {
|
|
4096
|
+
if (*p == '/') { p++; continue; }
|
|
4097
|
+
const char *q = p;
|
|
4098
|
+
while (*q && *q != '/') q++;
|
|
4099
|
+
size_t slen = (size_t)(q - p);
|
|
4100
|
+
if (slen == 1 && p[0] == '.') {
|
|
4101
|
+
/* current dir -- skip */
|
|
4102
|
+
} else if (slen == 2 && p[0] == '.' && p[1] == '.') {
|
|
4103
|
+
if (nseg > 0) { nseg--; olen = seg_start[nseg]; }
|
|
4104
|
+
} else {
|
|
4105
|
+
size_t mark = olen;
|
|
4106
|
+
if (olen > 1) out[olen++] = '/';
|
|
4107
|
+
memcpy(out + olen, p, slen);
|
|
4108
|
+
olen += slen;
|
|
4109
|
+
if (nseg < 1024) seg_start[nseg++] = mark;
|
|
4110
|
+
}
|
|
4111
|
+
p = q;
|
|
4112
|
+
}
|
|
4113
|
+
out[olen] = 0;
|
|
4114
|
+
return out;
|
|
4115
|
+
}
|
|
1156
4116
|
|
|
1157
4117
|
/* Read a file's bytes into a fresh IntArray. Distinct from
|
|
1158
4118
|
`sp_str_bytes(sp_file_read(path))` because plain sp_str_bytes uses
|
|
1159
4119
|
null-termination and stops at the first 0x00 byte — wrong for
|
|
1160
4120
|
binary data (e.g. .nes ROM files). */
|
|
1161
4121
|
static sp_IntArray *sp_file_binread_bytes(const char *path) {
|
|
4122
|
+
if (sp_file_directory(path)) {
|
|
4123
|
+
sp_raise_cls("Errno::EISDIR", sp_sprintf("Is a directory @ io_fread - %s", path));
|
|
4124
|
+
}
|
|
1162
4125
|
FILE *f = fopen(path, "rb");
|
|
1163
4126
|
sp_IntArray *a = sp_IntArray_new();
|
|
1164
4127
|
if (!f) return a;
|
|
@@ -1182,6 +4145,8 @@ static sp_IntArray *sp_file_binread_bytes(const char *path) {
|
|
|
1182
4145
|
`start` field for an O(1) head peel (from == 0); the others
|
|
1183
4146
|
shift the tail down to fill the hole. */
|
|
1184
4147
|
static sp_IntArray *sp_IntArray_slice_bang(sp_IntArray *a, mrb_int from, mrb_int n) {
|
|
4148
|
+
if (!a) return sp_IntArray_new();
|
|
4149
|
+
if (a->frozen) { sp_raise_frozen_array(); return sp_IntArray_new(); }
|
|
1185
4150
|
if (from < 0) from += a->len;
|
|
1186
4151
|
if (from < 0) from = 0;
|
|
1187
4152
|
if (from > a->len) from = a->len;
|
|
@@ -1199,6 +4164,8 @@ static sp_IntArray *sp_IntArray_slice_bang(sp_IntArray *a, mrb_int from, mrb_int
|
|
|
1199
4164
|
return r;
|
|
1200
4165
|
}
|
|
1201
4166
|
static sp_FloatArray *sp_FloatArray_slice_bang(sp_FloatArray *a, mrb_int from, mrb_int n) {
|
|
4167
|
+
if (!a) return sp_FloatArray_new();
|
|
4168
|
+
if (a->frozen) { sp_raise_frozen_array(); return sp_FloatArray_new(); }
|
|
1202
4169
|
if (from < 0) from += a->len;
|
|
1203
4170
|
if (from < 0) from = 0;
|
|
1204
4171
|
if (from > a->len) from = a->len;
|
|
@@ -1211,6 +4178,8 @@ static sp_FloatArray *sp_FloatArray_slice_bang(sp_FloatArray *a, mrb_int from, m
|
|
|
1211
4178
|
return r;
|
|
1212
4179
|
}
|
|
1213
4180
|
static sp_StrArray *sp_StrArray_slice_bang(sp_StrArray *a, mrb_int from, mrb_int n) {
|
|
4181
|
+
if (!a) return sp_StrArray_new();
|
|
4182
|
+
if (a->frozen) { sp_raise_frozen_array(); return sp_StrArray_new(); }
|
|
1214
4183
|
if (from < 0) from += a->len;
|
|
1215
4184
|
if (from < 0) from = 0;
|
|
1216
4185
|
if (from > a->len) from = a->len;
|
|
@@ -1222,7 +4191,16 @@ static sp_StrArray *sp_StrArray_slice_bang(sp_StrArray *a, mrb_int from, mrb_int
|
|
|
1222
4191
|
a->len -= n;
|
|
1223
4192
|
return r;
|
|
1224
4193
|
}
|
|
4194
|
+
/* at_exit hooks: a static LIFO of registered procs. Initialized
|
|
4195
|
+
zero-len in BSS; main()'s tail walks it in reverse-registration
|
|
4196
|
+
order before returning. */
|
|
4197
|
+
#define SP_AT_EXIT_MAX 256
|
|
4198
|
+
struct sp_Proc;
|
|
4199
|
+
static struct sp_Proc *sp_at_exit_hooks[SP_AT_EXIT_MAX];
|
|
4200
|
+
static mrb_int sp_at_exit_count = 0;
|
|
1225
4201
|
static sp_PtrArray *sp_PtrArray_slice_bang(sp_PtrArray *a, mrb_int from, mrb_int n) {
|
|
4202
|
+
if (!a) return sp_PtrArray_new_scan(NULL);
|
|
4203
|
+
if (a->frozen) { sp_raise_frozen_array(); return sp_PtrArray_new_scan(a->scan_elem); }
|
|
1226
4204
|
if (from < 0) from += a->len;
|
|
1227
4205
|
if (from < 0) from = 0;
|
|
1228
4206
|
if (from > a->len) from = a->len;
|
|
@@ -1235,18 +4213,105 @@ static sp_PtrArray *sp_PtrArray_slice_bang(sp_PtrArray *a, mrb_int from, mrb_int
|
|
|
1235
4213
|
return r;
|
|
1236
4214
|
}
|
|
1237
4215
|
|
|
1238
|
-
typedef struct sp_Proc { void *fn; void *cap; void (*cap_scan)(void *); } sp_Proc;
|
|
4216
|
+
typedef struct sp_Proc { void *fn; void *cap; void (*cap_scan)(void *); mrb_int arity; mrb_bool lambda_p; mrb_int param_count; const sp_sym *param_kinds; const sp_sym *param_names; } sp_Proc;
|
|
1239
4217
|
static void sp_Proc_scan(void *p) { sp_Proc *pr = (sp_Proc *)p; if (pr->cap && pr->cap_scan) pr->cap_scan(pr->cap); }
|
|
1240
|
-
static sp_Proc *
|
|
1241
|
-
static
|
|
4218
|
+
static sp_Proc *sp_proc_new_meta(void *fn, void *cap, void (*cap_scan)(void *), mrb_int arity, mrb_bool lambda_p, mrb_int param_count, const sp_sym *param_kinds, const sp_sym *param_names) { sp_Proc *p = (sp_Proc *)sp_gc_alloc(sizeof(sp_Proc), NULL, sp_Proc_scan); p->fn = fn; p->cap = cap; p->cap_scan = cap_scan; p->arity = arity; p->lambda_p = lambda_p; p->param_count = param_count; p->param_kinds = param_kinds; p->param_names = param_names; return p; }
|
|
4219
|
+
static sp_Proc *sp_proc_new(void *fn, void *cap, void (*cap_scan)(void *)) { return sp_proc_new_meta(fn, cap, cap_scan, 0, FALSE, 0, NULL, NULL); }
|
|
4220
|
+
static mrb_int sp_proc_arity(sp_Proc *p) { return p ? p->arity : 0; }
|
|
4221
|
+
static mrb_bool sp_proc_lambda_p(sp_Proc *p) { return p ? p->lambda_p : FALSE; }
|
|
4222
|
+
static mrb_int sp_proc_call(sp_Proc *p, mrb_int *args) { if (!p || !p->fn) return 0; if (!args) { mrb_int noargs[16] = {0}; return ((mrb_int (*)(void *, mrb_int *))p->fn)(p->cap, noargs); } return ((mrb_int (*)(void *, mrb_int *))p->fn)(p->cap, args); }
|
|
4223
|
+
static sp_PolyArray *sp_proc_parameters(sp_Proc *p) { sp_PolyArray *r = sp_PolyArray_new(); if (!p || p->param_count <= 0 || !p->param_kinds) return r; SP_GC_ROOT(r); for (mrb_int i = 0; i < p->param_count; i++) { sp_PolyArray *pair = sp_PolyArray_new(); sp_PolyArray_push(pair, sp_box_sym(p->param_kinds[i])); if (p->param_names && p->param_names[i] >= 0) sp_PolyArray_push(pair, sp_box_sym(p->param_names[i])); sp_PolyArray_push(r, sp_box_poly_array(pair)); } return r; }
|
|
4224
|
+
|
|
4225
|
+
/* Proc#<< / Proc#>> composition. The composed proc captures the two
|
|
4226
|
+
operands and, on call, threads its single argument through inner
|
|
4227
|
+
then outer: `(f << g).call(x)` == f(g(x)). For `>>` the codegen
|
|
4228
|
+
swaps the operands so `(f >> g).call(x)` == g(f(x)). */
|
|
4229
|
+
typedef struct { sp_Proc *outer; sp_Proc *inner; } sp_ProcCompose;
|
|
4230
|
+
static void sp_proc_compose_scan(void *p) { sp_ProcCompose *c = (sp_ProcCompose *)p; if (c->outer) sp_gc_mark(c->outer); if (c->inner) sp_gc_mark(c->inner); }
|
|
4231
|
+
static mrb_int sp_proc_compose_fn(void *cap, mrb_int *args) {
|
|
4232
|
+
sp_ProcCompose *c = (sp_ProcCompose *)cap;
|
|
4233
|
+
mrb_int inner_args[16] = {0};
|
|
4234
|
+
if (args) inner_args[0] = args[0];
|
|
4235
|
+
mrb_int mid = sp_proc_call(c->inner, inner_args);
|
|
4236
|
+
mrb_int outer_args[16] = {0};
|
|
4237
|
+
outer_args[0] = mid;
|
|
4238
|
+
return sp_proc_call(c->outer, outer_args);
|
|
4239
|
+
}
|
|
4240
|
+
static sp_Proc *sp_proc_compose(sp_Proc *outer, sp_Proc *inner) {
|
|
4241
|
+
sp_ProcCompose *c = (sp_ProcCompose *)sp_gc_alloc(sizeof(sp_ProcCompose), NULL, sp_proc_compose_scan);
|
|
4242
|
+
c->outer = outer;
|
|
4243
|
+
c->inner = inner;
|
|
4244
|
+
return sp_proc_new_meta((void *)sp_proc_compose_fn, c, sp_proc_compose_scan, 1, TRUE, 1, NULL, NULL);
|
|
4245
|
+
}
|
|
4246
|
+
/* Hash#to_proc cap-scan: the proc's `cap` field IS the source hash
|
|
4247
|
+
(a single GC pointer), so marking it keeps the hash alive for the
|
|
4248
|
+
proc's lifetime. The per-variant lookup fn is emitted by codegen
|
|
4249
|
+
alongside the hash type it closes over. */
|
|
4250
|
+
static void sp_hashproc_cap_scan(void *p) { sp_gc_mark(p); }
|
|
4251
|
+
|
|
4252
|
+
/* Random — per-instance PRNG. CRuby uses MT19937; spinel uses a
|
|
4253
|
+
portable xorshift64 (rand_r is POSIX-only, absent on MinGW), so
|
|
4254
|
+
the *sequence* differs from MRI but each Random object keeps its
|
|
4255
|
+
own reproducible stream from its seed. Issue #898. */
|
|
4256
|
+
typedef struct { uint64_t state; } sp_Random;
|
|
4257
|
+
static uint64_t sp_random_next(sp_Random *r) {
|
|
4258
|
+
uint64_t x = r->state ? r->state : 0x9E3779B97F4A7C15ULL;
|
|
4259
|
+
x ^= x << 13; x ^= x >> 7; x ^= x << 17;
|
|
4260
|
+
r->state = x;
|
|
4261
|
+
return x;
|
|
4262
|
+
}
|
|
4263
|
+
static sp_Random *sp_Random_new(mrb_int seed) {
|
|
4264
|
+
sp_Random *r = (sp_Random *)sp_gc_alloc(sizeof(sp_Random), NULL, NULL);
|
|
4265
|
+
r->state = (uint64_t)seed ^ 0x9E3779B97F4A7C15ULL;
|
|
4266
|
+
return r;
|
|
4267
|
+
}
|
|
4268
|
+
static mrb_int sp_Random_rand_int(sp_Random *r, mrb_int n) {
|
|
4269
|
+
if (!r || n <= 0) return 0;
|
|
4270
|
+
return (mrb_int)(sp_random_next(r) % (uint64_t)n);
|
|
4271
|
+
}
|
|
4272
|
+
static mrb_float sp_Random_rand_float(sp_Random *r) {
|
|
4273
|
+
if (!r) return 0.0;
|
|
4274
|
+
return (mrb_float)(sp_random_next(r) >> 11) / (mrb_float)(1ULL << 53);
|
|
4275
|
+
}
|
|
4276
|
+
/* Class-method forms (`Random.rand` / `Random.bytes`) share one
|
|
4277
|
+
lazily-seeded default instance, mirroring CRuby's Random::DEFAULT. */
|
|
4278
|
+
static sp_Random sp_random_default = { 0 };
|
|
4279
|
+
static sp_Random *sp_random_default_get(void) {
|
|
4280
|
+
if (sp_random_default.state == 0) sp_random_default.state = (uint64_t)time(NULL) ^ 0x9E3779B97F4A7C15ULL;
|
|
4281
|
+
return &sp_random_default;
|
|
4282
|
+
}
|
|
4283
|
+
/* Random#bytes(n) — n random bytes as a String. Uses sp_str_set_len
|
|
4284
|
+
so embedded NULs are preserved and #length reports n. */
|
|
4285
|
+
static const char *sp_Random_bytes(sp_Random *r, mrb_int n) {
|
|
4286
|
+
if (n < 0) n = 0;
|
|
4287
|
+
char *b = sp_str_alloc((size_t)n);
|
|
4288
|
+
for (mrb_int i = 0; i < n; i++) b[i] = (char)(sp_random_next(r) & 0xff);
|
|
4289
|
+
b[n] = 0;
|
|
4290
|
+
sp_str_set_len(b, (size_t)n);
|
|
4291
|
+
return b;
|
|
4292
|
+
}
|
|
1242
4293
|
|
|
1243
4294
|
/* ---- StringIO runtime ---- */
|
|
1244
4295
|
typedef struct { char *buf; int64_t len; int64_t cap; int64_t pos; int64_t lineno; int closed; } sp_StringIO;
|
|
1245
4296
|
static void sio_grow(sp_StringIO *sio, int64_t need) { int64_t req = sio->pos + need; if (req <= sio->cap) return; int64_t nc = sio->cap ? sio->cap : 64; while (nc < req) nc *= 2; sio->buf = (char *)realloc(sio->buf, nc + 1); sio->cap = nc; }
|
|
1246
4297
|
static int64_t sio_write(sp_StringIO *sio, const char *d, int64_t dl) { sio_grow(sio, dl); if (sio->pos > sio->len) memset(sio->buf + sio->len, 0, sio->pos - sio->len); memcpy(sio->buf + sio->pos, d, dl); sio->pos += dl; if (sio->pos > sio->len) sio->len = sio->pos; sio->buf[sio->len] = '\0'; return dl; }
|
|
1247
4298
|
static sp_StringIO *sp_StringIO_new(void) { sp_StringIO *s = (sp_StringIO *)calloc(1, sizeof(sp_StringIO)); s->buf = (char *)calloc(1, 64); s->cap = 63; return s; }
|
|
1248
|
-
static sp_StringIO *sp_StringIO_new_s(const char *init) { sp_StringIO *s = (sp_StringIO *)calloc(1, sizeof(sp_StringIO)); int64_t l = (int64_t)strlen(init); int64_t c = l < 63 ? 63 : l; s->buf = (char*)malloc(c+1); memcpy(s->buf, init, l); s->buf[l]='\0'; s->len = l; s->cap = c; return s; }
|
|
1249
|
-
|
|
4299
|
+
static sp_StringIO *sp_StringIO_new_s(const char *init) { if (!init) sp_raise_cls("TypeError", "no implicit conversion of nil into String"); sp_StringIO *s = (sp_StringIO *)calloc(1, sizeof(sp_StringIO)); int64_t l = (int64_t)strlen(init); int64_t c = l < 63 ? 63 : l; s->buf = (char*)malloc(c+1); memcpy(s->buf, init, l); s->buf[l]='\0'; s->len = l; s->cap = c; return s; }
|
|
4300
|
+
/* StringIO.new(str, mode): the mode's first char selects the initial
|
|
4301
|
+
content/position. "w"/"w+" truncate to empty; "a"/"a+" keep the
|
|
4302
|
+
content and seek to the end (subsequent writes append); "r"/"r+" and
|
|
4303
|
+
anything else keep the content at position 0. Read-only enforcement
|
|
4304
|
+
("r" rejecting writes) is not modelled. */
|
|
4305
|
+
static sp_StringIO *sp_StringIO_new_sm(const char *init, const char *mode) {
|
|
4306
|
+
if (!init) sp_raise_cls("TypeError", "no implicit conversion of nil into String");
|
|
4307
|
+
if (!mode || !mode[0]) return sp_StringIO_new_s(init);
|
|
4308
|
+
char m0 = mode[0];
|
|
4309
|
+
if (m0 == 'w') return sp_StringIO_new();
|
|
4310
|
+
sp_StringIO *s = sp_StringIO_new_s(init);
|
|
4311
|
+
if (m0 == 'a') s->pos = s->len;
|
|
4312
|
+
return s;
|
|
4313
|
+
}
|
|
4314
|
+
static const char *sp_StringIO_string(sp_StringIO *s) { return s->buf ? s->buf : sp_str_empty; }
|
|
1250
4315
|
static int64_t sp_StringIO_pos(sp_StringIO *s) { return s->pos; }
|
|
1251
4316
|
static int64_t sp_StringIO_size(sp_StringIO *s) { return s->len; }
|
|
1252
4317
|
static int64_t sp_StringIO_write(sp_StringIO *s, const char *str) { return sio_write(s, str, (int64_t)strlen(str)); }
|
|
@@ -1286,39 +4351,47 @@ static sp_Val *sp_lam_int(mrb_int n) { sp_Val *v = (sp_Val *)sp_lam_alloc(sizeof
|
|
|
1286
4351
|
static sp_Val *sp_lam_bool(mrb_bool b) { sp_Val *v = (sp_Val *)sp_lam_alloc(sizeof(sp_Val)); v->tag = SP_BOOL2; v->u.bval = b; return v; }
|
|
1287
4352
|
static sp_Val sp_lam_nil_val = { .tag = SP_NIL2 };
|
|
1288
4353
|
static sp_Val *sp_lam_call(sp_Val *f, sp_Val *arg) { return f->u.proc.fn(f, arg); }
|
|
4354
|
+
/* Multi-arg lambda dispatch . The fn pointer is stored
|
|
4355
|
+
typed as `sp_fn_t` (1-arg) so re-cast at the call site to the
|
|
4356
|
+
right arity. The generated lambda body is declared with the
|
|
4357
|
+
matching arity so the actual ABI matches. */
|
|
4358
|
+
typedef sp_Val *(*sp_fn2_t)(sp_Val *self, sp_Val *a, sp_Val *b);
|
|
4359
|
+
typedef sp_Val *(*sp_fn3_t)(sp_Val *self, sp_Val *a, sp_Val *b, sp_Val *c);
|
|
4360
|
+
typedef sp_Val *(*sp_fn4_t)(sp_Val *self, sp_Val *a, sp_Val *b, sp_Val *c, sp_Val *d);
|
|
4361
|
+
static sp_Val *sp_lam_call2(sp_Val *f, sp_Val *a, sp_Val *b) { return ((sp_fn2_t)(uintptr_t)f->u.proc.fn)(f, a, b); }
|
|
4362
|
+
static sp_Val *sp_lam_call3(sp_Val *f, sp_Val *a, sp_Val *b, sp_Val *c) { return ((sp_fn3_t)(uintptr_t)f->u.proc.fn)(f, a, b, c); }
|
|
4363
|
+
static sp_Val *sp_lam_call4(sp_Val *f, sp_Val *a, sp_Val *b, sp_Val *c, sp_Val *d) { return ((sp_fn4_t)(uintptr_t)f->u.proc.fn)(f, a, b, c, d); }
|
|
4364
|
+
/* lambda#<< / #>> composition over the sp_Val * representation.
|
|
4365
|
+
captures[0] = outer, captures[1] = inner; `(f << g).(x)` == f(g(x)).
|
|
4366
|
+
The codegen swaps operands for `>>`. */
|
|
4367
|
+
static sp_Val *sp_lam_compose_fn(sp_Val *self, sp_Val *arg) { return sp_lam_call(self->captures[0], sp_lam_call(self->captures[1], arg)); }
|
|
4368
|
+
static sp_Val *sp_lam_compose(sp_Val *outer, sp_Val *inner) { sp_Val *v = sp_lam_proc(sp_lam_compose_fn, 2); v->captures[0] = outer; v->captures[1] = inner; return v; }
|
|
4369
|
+
/* Proc#curry over the sp_Val * representation. The curried value is a
|
|
4370
|
+
proc whose captures are [target, arity, arg0, arg1, ...]; applying
|
|
4371
|
+
one more arg either returns a fresh curried proc (still short of
|
|
4372
|
+
arity) or invokes the target with the full argument list. Each `[]`
|
|
4373
|
+
application supplies a single argument. */
|
|
4374
|
+
static sp_Val *sp_lam_curry_fn(sp_Val *self, sp_Val *arg) {
|
|
4375
|
+
sp_Val *target = self->captures[0];
|
|
4376
|
+
mrb_int arity = self->captures[1]->u.ival;
|
|
4377
|
+
int have = self->u.proc.ncaptures - 2;
|
|
4378
|
+
if (have + 1 < arity) {
|
|
4379
|
+
sp_Val *v = sp_lam_proc(sp_lam_curry_fn, self->u.proc.ncaptures + 1);
|
|
4380
|
+
v->captures[0] = target;
|
|
4381
|
+
v->captures[1] = self->captures[1];
|
|
4382
|
+
for (int i = 0; i < have; i++) v->captures[2 + i] = self->captures[2 + i];
|
|
4383
|
+
v->captures[2 + have] = arg;
|
|
4384
|
+
return v;
|
|
4385
|
+
}
|
|
4386
|
+
if (arity == 1) return sp_lam_call(target, arg);
|
|
4387
|
+
if (arity == 2) return sp_lam_call2(target, self->captures[2], arg);
|
|
4388
|
+
if (arity == 3) return sp_lam_call3(target, self->captures[2], self->captures[3], arg);
|
|
4389
|
+
if (arity == 4) return sp_lam_call4(target, self->captures[2], self->captures[3], self->captures[4], arg);
|
|
4390
|
+
return &sp_lam_nil_val;
|
|
4391
|
+
}
|
|
4392
|
+
static sp_Val *sp_lam_curry(sp_Val *f, mrb_int arity) { sp_Val *v = sp_lam_proc(sp_lam_curry_fn, 2); v->captures[0] = f; v->captures[1] = sp_lam_int(arity); return v; }
|
|
1289
4393
|
static mrb_int sp_lam_to_int(sp_Val *v) { return v->u.ival; }
|
|
1290
4394
|
|
|
1291
|
-
/* ---- Fiber runtime (ucontext) ---- */
|
|
1292
|
-
#ifdef _WIN32
|
|
1293
|
-
#define SP_FIBER_STACK_SIZE (64*1024)
|
|
1294
|
-
typedef struct sp_Fiber{LPVOID ctx;LPVOID caller_ctx;char*stack;int state;int transferred;sp_RbVal yielded_value;sp_RbVal resumed_value;void(*body)(struct sp_Fiber*);void*user_data;int saved_exc_top;int saved_catch_top;struct sp_Fiber*caller;}sp_Fiber;
|
|
1295
|
-
static sp_Fiber sp_fiber_root;
|
|
1296
|
-
static sp_Fiber*sp_fiber_current=&sp_fiber_root;
|
|
1297
|
-
static void sp_Fiber_fin(void*p){sp_Fiber*f=(sp_Fiber*)p;if(f->ctx)DeleteFiber(f->ctx);}
|
|
1298
|
-
static void sp_Fiber_scan(void*p){sp_Fiber*f=(sp_Fiber*)p;if(f->user_data)sp_gc_mark(f->user_data);}
|
|
1299
|
-
static sp_Fiber*sp_Fiber_new(void(*body)(sp_Fiber*)){sp_Fiber*f=(sp_Fiber*)sp_gc_alloc(sizeof(sp_Fiber),sp_Fiber_fin,sp_Fiber_scan);f->ctx=NULL;f->caller_ctx=NULL;f->stack=NULL;f->state=0;f->transferred=0;f->body=body;f->yielded_value=sp_box_nil();f->resumed_value=sp_box_nil();f->user_data=NULL;f->saved_exc_top=0;f->saved_catch_top=0;f->caller=NULL;return f;}
|
|
1300
|
-
static VOID CALLBACK sp_fiber_trampoline(LPVOID param){sp_Fiber*f=(sp_Fiber*)param;f->body(f);f->state=3;if(f->transferred){sp_fiber_current=&sp_fiber_root;SwitchToFiber(sp_fiber_root.ctx);}else{SwitchToFiber(f->caller->ctx);}}
|
|
1301
|
-
static void sp_fiber_ensure_root(void){if(!sp_fiber_root.ctx){sp_fiber_root.ctx=ConvertThreadToFiber(NULL);if(!sp_fiber_root.ctx)sp_fiber_root.ctx=GetCurrentFiber();}}
|
|
1302
|
-
static sp_RbVal sp_Fiber_resume(sp_Fiber*f,sp_RbVal val){if(f->state==3){sp_raise_cls("FiberError","attempt to resume a terminated fiber");}sp_fiber_ensure_root();f->resumed_value=val;sp_Fiber*prev=sp_fiber_current;f->caller=prev;sp_fiber_current=f;if(f->state==0){f->state=1;f->ctx=CreateFiber(SP_FIBER_STACK_SIZE,sp_fiber_trampoline,f);if(!f->ctx)sp_raise("failed to create fiber");}else{f->state=1;}SwitchToFiber(f->ctx);sp_fiber_current=prev;return f->yielded_value;}
|
|
1303
|
-
static sp_RbVal sp_Fiber_yield(sp_RbVal val){sp_Fiber*f=sp_fiber_current;f->yielded_value=val;f->state=2;SwitchToFiber(f->caller->ctx);return f->resumed_value;}
|
|
1304
|
-
static mrb_bool sp_Fiber_alive(sp_Fiber*f){return f->state!=3;}
|
|
1305
|
-
static sp_RbVal sp_Fiber_transfer(sp_Fiber*f,sp_RbVal val){sp_fiber_ensure_root();f->resumed_value=val;sp_Fiber*prev=sp_fiber_current;sp_fiber_current=f;if(f->state==0){f->state=1;f->transferred=1;f->caller=prev;f->ctx=CreateFiber(SP_FIBER_STACK_SIZE,sp_fiber_trampoline,f);if(!f->ctx)sp_raise("failed to create fiber");}else{f->state=1;}SwitchToFiber(f->ctx);sp_fiber_current=prev;return prev->resumed_value;}
|
|
1306
|
-
#else
|
|
1307
|
-
#include <ucontext.h>
|
|
1308
|
-
#include <sys/mman.h>
|
|
1309
|
-
#define SP_FIBER_STACK_SIZE (64*1024)
|
|
1310
|
-
typedef struct sp_Fiber{ucontext_t ctx;ucontext_t caller_ctx;char*stack;int state;int transferred;sp_RbVal yielded_value;sp_RbVal resumed_value;void(*body)(struct sp_Fiber*);void*user_data;int saved_exc_top;int saved_catch_top;}sp_Fiber;
|
|
1311
|
-
static sp_Fiber sp_fiber_root;
|
|
1312
|
-
static sp_Fiber*sp_fiber_current=&sp_fiber_root;
|
|
1313
|
-
static void sp_Fiber_fin(void*p){sp_Fiber*f=(sp_Fiber*)p;if(f->stack)munmap(f->stack,SP_FIBER_STACK_SIZE);}
|
|
1314
|
-
static void sp_Fiber_scan(void*p){sp_Fiber*f=(sp_Fiber*)p;if(f->user_data)sp_gc_mark(f->user_data);}
|
|
1315
|
-
static sp_Fiber*sp_Fiber_new(void(*body)(sp_Fiber*)){sp_Fiber*f=(sp_Fiber*)sp_gc_alloc(sizeof(sp_Fiber),sp_Fiber_fin,sp_Fiber_scan);f->stack=(char*)mmap(NULL,SP_FIBER_STACK_SIZE,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);f->state=0;f->transferred=0;f->body=body;f->yielded_value=sp_box_nil();f->resumed_value=sp_box_nil();f->user_data=NULL;f->saved_exc_top=0;f->saved_catch_top=0;return f;}
|
|
1316
|
-
static void sp_fiber_trampoline(void){sp_Fiber*f=sp_fiber_current;f->body(f);f->state=3;if(f->transferred){sp_fiber_current=&sp_fiber_root;setcontext(&sp_fiber_root.ctx);}else{swapcontext(&f->ctx,&f->caller_ctx);}}
|
|
1317
|
-
static sp_RbVal sp_Fiber_resume(sp_Fiber*f,sp_RbVal val){if(f->state==3){sp_raise_cls("FiberError","attempt to resume a terminated fiber");}f->resumed_value=val;sp_Fiber*prev=sp_fiber_current;sp_fiber_current=f;if(f->state==0){f->state=1;getcontext(&f->ctx);f->ctx.uc_stack.ss_sp=f->stack;f->ctx.uc_stack.ss_size=SP_FIBER_STACK_SIZE;f->ctx.uc_link=&f->caller_ctx;makecontext(&f->ctx,(void(*)(void))sp_fiber_trampoline,0);swapcontext(&f->caller_ctx,&f->ctx);}else{f->state=1;swapcontext(&f->caller_ctx,&f->ctx);}sp_fiber_current=prev;return f->yielded_value;}
|
|
1318
|
-
static sp_RbVal sp_Fiber_yield(sp_RbVal val){sp_Fiber*f=sp_fiber_current;f->yielded_value=val;f->state=2;swapcontext(&f->ctx,&f->caller_ctx);return f->resumed_value;}
|
|
1319
|
-
static mrb_bool sp_Fiber_alive(sp_Fiber*f){return f->state!=3;}
|
|
1320
|
-
static sp_RbVal sp_Fiber_transfer(sp_Fiber*f,sp_RbVal val){f->resumed_value=val;sp_Fiber*prev=sp_fiber_current;sp_fiber_current=f;if(f->state==0){f->state=1;f->transferred=1;getcontext(&f->ctx);f->ctx.uc_stack.ss_sp=f->stack;f->ctx.uc_stack.ss_size=SP_FIBER_STACK_SIZE;f->ctx.uc_link=&prev->ctx;makecontext(&f->ctx,(void(*)(void))sp_fiber_trampoline,0);swapcontext(&prev->ctx,&f->ctx);}else{f->state=1;swapcontext(&prev->ctx,&f->ctx);}sp_fiber_current=prev;return prev->resumed_value;}
|
|
1321
|
-
#endif
|
|
1322
4395
|
|
|
1323
4396
|
/* Bigint (linked from sp_bigint.o) */
|
|
1324
4397
|
typedef struct sp_Bigint sp_Bigint;
|
|
@@ -1335,9 +4408,23 @@ int64_t sp_bigint_to_int(sp_Bigint *b);
|
|
|
1335
4408
|
const char *sp_bigint_to_s(sp_Bigint *b);
|
|
1336
4409
|
void sp_bigint_free(sp_Bigint *b);
|
|
1337
4410
|
|
|
4411
|
+
/* Bitwise ops on bigint operands. Used by --int-overflow=promote
|
|
4412
|
+
mode where all int slots widen to sp_Bigint *. Implemented via
|
|
4413
|
+
int64 round-trip: bigint values produced by promotion are
|
|
4414
|
+
almost always derived from small ints (counters, masks, bit-
|
|
4415
|
+
width-sized values <= 64 bits), so the int64 path preserves
|
|
4416
|
+
the full Ruby-side semantics for any value that fits. Values
|
|
4417
|
+
exceeding int64 lose precision through these helpers -- those
|
|
4418
|
+
are extremely rare in practice (Ruby bitops on integers >
|
|
4419
|
+
2^63), and proper mpz_and / mpz_or / mpz_xor support can be
|
|
4420
|
+
added later in lib/sp_bigint.c if a real workload needs it. */
|
|
4421
|
+
sp_Bigint *sp_bigint_and(sp_Bigint *a, sp_Bigint *b);
|
|
4422
|
+
sp_Bigint *sp_bigint_or(sp_Bigint *a, sp_Bigint *b);
|
|
4423
|
+
sp_Bigint *sp_bigint_xor(sp_Bigint *a, sp_Bigint *b);
|
|
4424
|
+
sp_Bigint *sp_bigint_shl(sp_Bigint *a, int64_t n);
|
|
4425
|
+
sp_Bigint *sp_bigint_shr(sp_Bigint *a, int64_t n);
|
|
4426
|
+
sp_Bigint *sp_bigint_not(sp_Bigint *a);
|
|
1338
4427
|
|
|
1339
|
-
/* System/backtick support */
|
|
1340
|
-
static int sp_last_status = 0;
|
|
1341
4428
|
|
|
1342
4429
|
/* Bigint (linked from libspinel_rt.a) */
|
|
1343
4430
|
typedef struct sp_Bigint sp_Bigint;
|
|
@@ -1353,6 +4440,63 @@ int sp_bigint_cmp(sp_Bigint *a, sp_Bigint *b);
|
|
|
1353
4440
|
int64_t sp_bigint_to_int(sp_Bigint *b);
|
|
1354
4441
|
const char *sp_bigint_to_s(sp_Bigint *b);
|
|
1355
4442
|
void sp_bigint_free(sp_Bigint *b);
|
|
4443
|
+
sp_Bigint *sp_bigint_and(sp_Bigint *a, sp_Bigint *b);
|
|
4444
|
+
sp_Bigint *sp_bigint_or(sp_Bigint *a, sp_Bigint *b);
|
|
4445
|
+
sp_Bigint *sp_bigint_xor(sp_Bigint *a, sp_Bigint *b);
|
|
4446
|
+
sp_Bigint *sp_bigint_shl(sp_Bigint *a, int64_t n);
|
|
4447
|
+
sp_Bigint *sp_bigint_shr(sp_Bigint *a, int64_t n);
|
|
4448
|
+
sp_Bigint *sp_bigint_not(sp_Bigint *a);
|
|
4449
|
+
|
|
4450
|
+
/* ---- Pack / Unpack (linked from sp_pack.o) ----
|
|
4451
|
+
Implementation lives in libspinel_rt.a; the entry points
|
|
4452
|
+
below call into the static GC helpers in this header via the
|
|
4453
|
+
sp_ext_* shims defined further down. */
|
|
4454
|
+
const char *sp_IntArray_pack(sp_IntArray *arr, const char *fmt);
|
|
4455
|
+
const char *sp_PolyArray_pack(sp_PolyArray *arr, const char *fmt);
|
|
4456
|
+
sp_PolyArray *sp_str_unpack(const char *str, const char *fmt);
|
|
4457
|
+
|
|
4458
|
+
/* ---- StringScanner (linked from sp_strscan.o) ----
|
|
4459
|
+
GC-allocated; the source / matched fields are marked by
|
|
4460
|
+
sp_StringScanner_scan_gc through the shim layer. */
|
|
4461
|
+
typedef struct sp_StringScanner sp_StringScanner;
|
|
4462
|
+
sp_StringScanner *sp_StringScanner_new(const char *str);
|
|
4463
|
+
const char *sp_StringScanner_scan(sp_StringScanner *sc, mrb_regexp_pattern *pat);
|
|
4464
|
+
const char *sp_StringScanner_check(sp_StringScanner *sc, mrb_regexp_pattern *pat);
|
|
4465
|
+
const char *sp_StringScanner_scan_until(sp_StringScanner *sc, mrb_regexp_pattern *pat);
|
|
4466
|
+
const char *sp_StringScanner_aref(sp_StringScanner *sc, mrb_int n);
|
|
4467
|
+
const char *sp_StringScanner_matched(sp_StringScanner *sc);
|
|
4468
|
+
mrb_bool sp_StringScanner_matched_p(sp_StringScanner *sc);
|
|
4469
|
+
mrb_int sp_StringScanner_pos(sp_StringScanner *sc);
|
|
4470
|
+
mrb_int sp_StringScanner_pos_set(sp_StringScanner *sc, mrb_int p);
|
|
4471
|
+
mrb_bool sp_StringScanner_eos_p(sp_StringScanner *sc);
|
|
4472
|
+
const char *sp_StringScanner_getch(sp_StringScanner *sc);
|
|
4473
|
+
const char *sp_StringScanner_peek(sp_StringScanner *sc, mrb_int n);
|
|
4474
|
+
sp_StringScanner *sp_StringScanner_unscan(sp_StringScanner *sc);
|
|
4475
|
+
const char *sp_StringScanner_rest(sp_StringScanner *sc);
|
|
4476
|
+
mrb_int sp_StringScanner_rest_size(sp_StringScanner *sc);
|
|
4477
|
+
mrb_bool sp_StringScanner_rest_p(sp_StringScanner *sc);
|
|
4478
|
+
sp_StringScanner *sp_StringScanner_terminate(sp_StringScanner *sc);
|
|
4479
|
+
const char *sp_StringScanner_string(sp_StringScanner *sc);
|
|
4480
|
+
const char *sp_StringScanner_pre_match(sp_StringScanner *sc);
|
|
4481
|
+
const char *sp_StringScanner_post_match(sp_StringScanner *sc);
|
|
4482
|
+
sp_StringScanner *sp_StringScanner_reset(sp_StringScanner *sc);
|
|
4483
|
+
|
|
4484
|
+
/* External-TU shim wrappers. The runtime's GC-allocating helpers
|
|
4485
|
+
are `static inline`; separately-compiled .c files in
|
|
4486
|
+
libspinel_rt.a can't reach the per-TU state. Define plain
|
|
4487
|
+
external-linkage wrappers here so the main binary's single TU
|
|
4488
|
+
owns the GC state and lib code calls in via stable symbols.
|
|
4489
|
+
sp_runtime.h is included exactly once per binary (in the
|
|
4490
|
+
generated main file), so there's no multi-definition risk. */
|
|
4491
|
+
sp_PolyArray *sp_ext_poly_array_new(void) { return sp_PolyArray_new(); }
|
|
4492
|
+
void sp_ext_poly_array_push_int(sp_PolyArray *a, int64_t v) { sp_PolyArray_push(a, sp_box_int((mrb_int)v)); }
|
|
4493
|
+
void sp_ext_poly_array_push_str(sp_PolyArray *a, const char *s) { sp_PolyArray_push(a, sp_box_str(s)); }
|
|
4494
|
+
char *sp_ext_str_alloc(size_t n) { return sp_str_alloc(n); }
|
|
4495
|
+
void sp_ext_str_set_len(char *s, size_t n) { sp_str_set_len(s, n); }
|
|
4496
|
+
const char *sp_ext_str_empty(void) { return sp_str_empty; }
|
|
4497
|
+
size_t sp_ext_str_byte_len(const char *s) { return sp_str_byte_len(s); }
|
|
4498
|
+
void *sp_ext_gc_alloc(size_t sz, void (*fin)(void *), void (*scan)(void *)) { return sp_gc_alloc(sz, fin, scan); }
|
|
4499
|
+
void sp_ext_mark_string(const char *s) { sp_mark_string(s); }
|
|
1356
4500
|
|
|
1357
4501
|
#ifdef __APPLE__
|
|
1358
4502
|
#pragma clang diagnostic pop
|