superacli 1.1.4 → 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/adapter-schema.test.js +2 -0
- package/__tests__/config.test.js +62 -1
- package/__tests__/help-json.test.js +2 -0
- package/__tests__/mcp-adapter.test.js +14 -4
- package/__tests__/mcp-local.test.js +159 -0
- package/__tests__/mcp-stdio-jsonrpc.test.js +105 -0
- package/__tests__/monty-plugin.test.js +121 -0
- package/__tests__/plugin-browser-use-uninstall.test.js +23 -0
- package/__tests__/plugin-browser-use.test.js +77 -0
- package/__tests__/plugins-command.test.js +92 -1
- package/__tests__/plugins-learn.test.js +62 -0
- package/__tests__/plugins-registry.test.js +3 -1
- package/__tests__/resend-plugin.test.js +122 -0
- package/__tests__/skills.test.js +4 -0
- package/cli/adapter-schema.js +3 -2
- package/cli/adapters/mcp.js +22 -3
- package/cli/adapters/process.js +34 -7
- package/cli/config.js +27 -1
- package/cli/help-json.js +2 -2
- package/cli/mcp-diagnostics.js +152 -0
- package/cli/mcp-discovery.js +221 -0
- package/cli/mcp-local.js +267 -25
- package/cli/mcp-stdio-jsonrpc.js +246 -0
- package/cli/plugin-install-guidance.js +25 -0
- package/cli/plugins-command.js +86 -3
- package/cli/plugins-learn.js +177 -0
- package/cli/plugins-manager.js +3 -0
- package/cli/plugins-registry.js +2 -1
- package/cli/skills-mcp.js +102 -0
- package/cli/skills.js +6 -40
- package/cli/supercli.js +7 -2
- package/docs/initial/mcp-local-mode.md +35 -0
- package/docs/mcp-cheatsheet.md +324 -0
- package/docs/plugins.md +7 -0
- package/package.json +1 -1
- package/plugins/browser-use/plugin.json +23 -0
- package/plugins/browser-use/scripts/post-install.js +146 -0
- package/plugins/browser-use/scripts/post-uninstall.js +28 -0
- package/plugins/browser-use/skills/quickstart/SKILL.md +47 -0
- package/plugins/monty/README.md +49 -0
- package/plugins/monty/plugin.json +69 -0
- package/plugins/monty/scripts/post-install.js +73 -0
- package/plugins/monty/scripts/post-uninstall.js +23 -0
- package/plugins/monty/scripts/run-python.js +140 -0
- package/plugins/monty/scripts/setup-monty.js +27 -0
- package/plugins/plugins.json +29 -0
- package/plugins/resend/plugin.json +371 -0
- package/plugins/resend/scripts/post-install.js +59 -0
- package/plugins/resend/scripts/post-uninstall.js +23 -0
- package/plugins/resend/scripts/setup-resend.js +27 -0
- package/plugins/resend/skills/quickstart/SKILL.md +80 -0
- package/ref-monty/.cargo/config.toml +3 -0
- package/ref-monty/.claude/settings.json +60 -0
- package/ref-monty/.claude/skills/fastmod/SKILL.md +22 -0
- package/ref-monty/.claude/skills/python-playground/SKILL.md +47 -0
- package/ref-monty/.codecov.yml +12 -0
- package/ref-monty/.github/actions/build-pgo-wheel/action.yml +72 -0
- package/ref-monty/.github/workflows/ci.yml +776 -0
- package/ref-monty/.github/workflows/codspeed.yml +45 -0
- package/ref-monty/.github/workflows/init-npm-packages.yml +82 -0
- package/ref-monty/.pre-commit-config.yaml +47 -0
- package/ref-monty/.python-version +1 -0
- package/ref-monty/.rustfmt.toml +4 -0
- package/ref-monty/.zed/settings.json +11 -0
- package/ref-monty/CLAUDE.md +535 -0
- package/ref-monty/Cargo.lock +3798 -0
- package/ref-monty/Cargo.toml +87 -0
- package/ref-monty/LICENSE +21 -0
- package/ref-monty/Makefile +216 -0
- package/ref-monty/README.md +430 -0
- package/ref-monty/RELEASING.md +47 -0
- package/ref-monty/crates/fuzz/Cargo.toml +30 -0
- package/ref-monty/crates/fuzz/fuzz_targets/string_input_panic.rs +37 -0
- package/ref-monty/crates/fuzz/fuzz_targets/tokens_input_panic.rs +552 -0
- package/ref-monty/crates/monty/Cargo.toml +68 -0
- package/ref-monty/crates/monty/benches/main.rs +247 -0
- package/ref-monty/crates/monty/build.rs +10 -0
- package/ref-monty/crates/monty/src/args.rs +733 -0
- package/ref-monty/crates/monty/src/asyncio.rs +179 -0
- package/ref-monty/crates/monty/src/builtins/abs.rs +55 -0
- package/ref-monty/crates/monty/src/builtins/all.rs +30 -0
- package/ref-monty/crates/monty/src/builtins/any.rs +30 -0
- package/ref-monty/crates/monty/src/builtins/bin.rs +59 -0
- package/ref-monty/crates/monty/src/builtins/chr.rs +46 -0
- package/ref-monty/crates/monty/src/builtins/divmod.rs +164 -0
- package/ref-monty/crates/monty/src/builtins/enumerate.rs +52 -0
- package/ref-monty/crates/monty/src/builtins/filter.rs +67 -0
- package/ref-monty/crates/monty/src/builtins/getattr.rs +65 -0
- package/ref-monty/crates/monty/src/builtins/hash.rs +28 -0
- package/ref-monty/crates/monty/src/builtins/hex.rs +58 -0
- package/ref-monty/crates/monty/src/builtins/id.rs +24 -0
- package/ref-monty/crates/monty/src/builtins/isinstance.rs +68 -0
- package/ref-monty/crates/monty/src/builtins/len.rs +25 -0
- package/ref-monty/crates/monty/src/builtins/map.rs +98 -0
- package/ref-monty/crates/monty/src/builtins/min_max.rs +113 -0
- package/ref-monty/crates/monty/src/builtins/mod.rs +246 -0
- package/ref-monty/crates/monty/src/builtins/next.rs +21 -0
- package/ref-monty/crates/monty/src/builtins/oct.rs +59 -0
- package/ref-monty/crates/monty/src/builtins/ord.rs +67 -0
- package/ref-monty/crates/monty/src/builtins/pow.rs +365 -0
- package/ref-monty/crates/monty/src/builtins/print.rs +141 -0
- package/ref-monty/crates/monty/src/builtins/repr.rs +16 -0
- package/ref-monty/crates/monty/src/builtins/reversed.rs +28 -0
- package/ref-monty/crates/monty/src/builtins/round.rs +174 -0
- package/ref-monty/crates/monty/src/builtins/sorted.rs +151 -0
- package/ref-monty/crates/monty/src/builtins/sum.rs +66 -0
- package/ref-monty/crates/monty/src/builtins/type_.rs +16 -0
- package/ref-monty/crates/monty/src/builtins/zip.rs +77 -0
- package/ref-monty/crates/monty/src/bytecode/builder.rs +699 -0
- package/ref-monty/crates/monty/src/bytecode/code.rs +310 -0
- package/ref-monty/crates/monty/src/bytecode/compiler.rs +3206 -0
- package/ref-monty/crates/monty/src/bytecode/mod.rs +24 -0
- package/ref-monty/crates/monty/src/bytecode/op.rs +617 -0
- package/ref-monty/crates/monty/src/bytecode/vm/async_exec.rs +1058 -0
- package/ref-monty/crates/monty/src/bytecode/vm/attr.rs +63 -0
- package/ref-monty/crates/monty/src/bytecode/vm/binary.rs +487 -0
- package/ref-monty/crates/monty/src/bytecode/vm/call.rs +767 -0
- package/ref-monty/crates/monty/src/bytecode/vm/collections.rs +741 -0
- package/ref-monty/crates/monty/src/bytecode/vm/compare.rs +147 -0
- package/ref-monty/crates/monty/src/bytecode/vm/exceptions.rs +297 -0
- package/ref-monty/crates/monty/src/bytecode/vm/format.rs +132 -0
- package/ref-monty/crates/monty/src/bytecode/vm/mod.rs +1958 -0
- package/ref-monty/crates/monty/src/bytecode/vm/scheduler.rs +620 -0
- package/ref-monty/crates/monty/src/exception_private.rs +1513 -0
- package/ref-monty/crates/monty/src/exception_public.rs +346 -0
- package/ref-monty/crates/monty/src/expressions.rs +694 -0
- package/ref-monty/crates/monty/src/fstring.rs +854 -0
- package/ref-monty/crates/monty/src/function.rs +119 -0
- package/ref-monty/crates/monty/src/heap.rs +1073 -0
- package/ref-monty/crates/monty/src/heap_data.rs +985 -0
- package/ref-monty/crates/monty/src/heap_traits.rs +312 -0
- package/ref-monty/crates/monty/src/intern.rs +837 -0
- package/ref-monty/crates/monty/src/io.rs +106 -0
- package/ref-monty/crates/monty/src/lib.rs +52 -0
- package/ref-monty/crates/monty/src/modules/asyncio.rs +144 -0
- package/ref-monty/crates/monty/src/modules/math.rs +1453 -0
- package/ref-monty/crates/monty/src/modules/mod.rs +120 -0
- package/ref-monty/crates/monty/src/modules/os.rs +116 -0
- package/ref-monty/crates/monty/src/modules/pathlib.rs +33 -0
- package/ref-monty/crates/monty/src/modules/re.rs +606 -0
- package/ref-monty/crates/monty/src/modules/sys.rs +60 -0
- package/ref-monty/crates/monty/src/modules/typing.rs +70 -0
- package/ref-monty/crates/monty/src/namespace.rs +21 -0
- package/ref-monty/crates/monty/src/object.rs +1040 -0
- package/ref-monty/crates/monty/src/os.rs +215 -0
- package/ref-monty/crates/monty/src/parse.rs +1730 -0
- package/ref-monty/crates/monty/src/prepare.rs +3015 -0
- package/ref-monty/crates/monty/src/repl.rs +1109 -0
- package/ref-monty/crates/monty/src/resource.rs +559 -0
- package/ref-monty/crates/monty/src/run.rs +457 -0
- package/ref-monty/crates/monty/src/run_progress.rs +821 -0
- package/ref-monty/crates/monty/src/signature.rs +651 -0
- package/ref-monty/crates/monty/src/sorting.rs +100 -0
- package/ref-monty/crates/monty/src/types/bytes.rs +2356 -0
- package/ref-monty/crates/monty/src/types/dataclass.rs +345 -0
- package/ref-monty/crates/monty/src/types/dict.rs +879 -0
- package/ref-monty/crates/monty/src/types/dict_view.rs +619 -0
- package/ref-monty/crates/monty/src/types/iter.rs +799 -0
- package/ref-monty/crates/monty/src/types/list.rs +929 -0
- package/ref-monty/crates/monty/src/types/long_int.rs +211 -0
- package/ref-monty/crates/monty/src/types/mod.rs +48 -0
- package/ref-monty/crates/monty/src/types/module.rs +146 -0
- package/ref-monty/crates/monty/src/types/namedtuple.rs +261 -0
- package/ref-monty/crates/monty/src/types/path.rs +596 -0
- package/ref-monty/crates/monty/src/types/property.rs +35 -0
- package/ref-monty/crates/monty/src/types/py_trait.rs +322 -0
- package/ref-monty/crates/monty/src/types/range.rs +285 -0
- package/ref-monty/crates/monty/src/types/re_match.rs +522 -0
- package/ref-monty/crates/monty/src/types/re_pattern.rs +726 -0
- package/ref-monty/crates/monty/src/types/set.rs +1373 -0
- package/ref-monty/crates/monty/src/types/slice.rs +257 -0
- package/ref-monty/crates/monty/src/types/str.rs +2051 -0
- package/ref-monty/crates/monty/src/types/tuple.rs +376 -0
- package/ref-monty/crates/monty/src/types/type.rs +407 -0
- package/ref-monty/crates/monty/src/value.rs +2558 -0
- package/ref-monty/crates/monty/test_cases/args__dict_get_no_args.py +3 -0
- package/ref-monty/crates/monty/test_cases/args__dict_get_too_many.py +3 -0
- package/ref-monty/crates/monty/test_cases/args__dict_items_with_args.py +3 -0
- package/ref-monty/crates/monty/test_cases/args__dict_keys_with_args.py +3 -0
- package/ref-monty/crates/monty/test_cases/args__dict_pop_no_args.py +3 -0
- package/ref-monty/crates/monty/test_cases/args__dict_pop_too_many.py +3 -0
- package/ref-monty/crates/monty/test_cases/args__dict_values_with_args.py +3 -0
- package/ref-monty/crates/monty/test_cases/args__id_too_many.py +2 -0
- package/ref-monty/crates/monty/test_cases/args__len_no_args.py +2 -0
- package/ref-monty/crates/monty/test_cases/args__len_too_many.py +2 -0
- package/ref-monty/crates/monty/test_cases/args__len_type_error_int.py +9 -0
- package/ref-monty/crates/monty/test_cases/args__len_type_error_none.py +9 -0
- package/ref-monty/crates/monty/test_cases/args__list_append_no_args.py +3 -0
- package/ref-monty/crates/monty/test_cases/args__list_append_too_many.py +3 -0
- package/ref-monty/crates/monty/test_cases/args__list_insert_too_few.py +3 -0
- package/ref-monty/crates/monty/test_cases/args__list_insert_too_many.py +3 -0
- package/ref-monty/crates/monty/test_cases/args__repr_no_args.py +2 -0
- package/ref-monty/crates/monty/test_cases/arith__div_zero_float.py +2 -0
- package/ref-monty/crates/monty/test_cases/arith__div_zero_int.py +2 -0
- package/ref-monty/crates/monty/test_cases/arith__floordiv_zero_float.py +2 -0
- package/ref-monty/crates/monty/test_cases/arith__floordiv_zero_int.py +2 -0
- package/ref-monty/crates/monty/test_cases/arith__pow_zero_neg.py +2 -0
- package/ref-monty/crates/monty/test_cases/arith__pow_zero_neg_builtin.py +9 -0
- package/ref-monty/crates/monty/test_cases/assert__expr_fail.py +2 -0
- package/ref-monty/crates/monty/test_cases/assert__fail.py +2 -0
- package/ref-monty/crates/monty/test_cases/assert__fail_msg.py +2 -0
- package/ref-monty/crates/monty/test_cases/assert__fn_fail.py +3 -0
- package/ref-monty/crates/monty/test_cases/assert__ops.py +11 -0
- package/ref-monty/crates/monty/test_cases/async__asyncio_run.py +47 -0
- package/ref-monty/crates/monty/test_cases/async__basic.py +10 -0
- package/ref-monty/crates/monty/test_cases/async__closure.py +14 -0
- package/ref-monty/crates/monty/test_cases/async__double_await_coroutine.py +16 -0
- package/ref-monty/crates/monty/test_cases/async__exception.py +10 -0
- package/ref-monty/crates/monty/test_cases/async__ext_call.py +73 -0
- package/ref-monty/crates/monty/test_cases/async__gather_all.py +85 -0
- package/ref-monty/crates/monty/test_cases/async__nested_await.py +15 -0
- package/ref-monty/crates/monty/test_cases/async__nested_gather_ext.py +37 -0
- package/ref-monty/crates/monty/test_cases/async__not_awaitable.py +10 -0
- package/ref-monty/crates/monty/test_cases/async__not_imported.py +14 -0
- package/ref-monty/crates/monty/test_cases/async__recursion_depth_isolation.py +27 -0
- package/ref-monty/crates/monty/test_cases/async__return_types.py +31 -0
- package/ref-monty/crates/monty/test_cases/async__sequential.py +16 -0
- package/ref-monty/crates/monty/test_cases/async__traceback.py +19 -0
- package/ref-monty/crates/monty/test_cases/async__with_args.py +14 -0
- package/ref-monty/crates/monty/test_cases/attr__get_int_error.py +9 -0
- package/ref-monty/crates/monty/test_cases/attr__get_list_error.py +9 -0
- package/ref-monty/crates/monty/test_cases/attr__set_frozen_nonfield.py +12 -0
- package/ref-monty/crates/monty/test_cases/attr__set_int_error.py +10 -0
- package/ref-monty/crates/monty/test_cases/attr__set_list_error.py +10 -0
- package/ref-monty/crates/monty/test_cases/bench__kitchen_sink.py +68 -0
- package/ref-monty/crates/monty/test_cases/bool__ops.py +20 -0
- package/ref-monty/crates/monty/test_cases/builtin__add_type_error.py +2 -0
- package/ref-monty/crates/monty/test_cases/builtin__filter.py +62 -0
- package/ref-monty/crates/monty/test_cases/builtin__filter_not_iterable.py +11 -0
- package/ref-monty/crates/monty/test_cases/builtin__getattr.py +84 -0
- package/ref-monty/crates/monty/test_cases/builtin__iter_funcs.py +42 -0
- package/ref-monty/crates/monty/test_cases/builtin__iter_next.py +66 -0
- package/ref-monty/crates/monty/test_cases/builtin__map.py +74 -0
- package/ref-monty/crates/monty/test_cases/builtin__map_not_iterable.py +11 -0
- package/ref-monty/crates/monty/test_cases/builtin__math_funcs.py +154 -0
- package/ref-monty/crates/monty/test_cases/builtin__more_iter_funcs.py +148 -0
- package/ref-monty/crates/monty/test_cases/builtin__next_stop_iteration.py +10 -0
- package/ref-monty/crates/monty/test_cases/builtin__print_invalid_kwarg.py +9 -0
- package/ref-monty/crates/monty/test_cases/builtin__print_kwargs.py +12 -0
- package/ref-monty/crates/monty/test_cases/builtin__repr.py +3 -0
- package/ref-monty/crates/monty/test_cases/builtin__string_funcs.py +73 -0
- package/ref-monty/crates/monty/test_cases/bytes__decode_invalid_utf8.py +18 -0
- package/ref-monty/crates/monty/test_cases/bytes__endswith_str_error.py +10 -0
- package/ref-monty/crates/monty/test_cases/bytes__getitem_index_error.py +10 -0
- package/ref-monty/crates/monty/test_cases/bytes__index_start_gt_end.py +10 -0
- package/ref-monty/crates/monty/test_cases/bytes__methods.py +394 -0
- package/ref-monty/crates/monty/test_cases/bytes__negative_count.py +9 -0
- package/ref-monty/crates/monty/test_cases/bytes__ops.py +90 -0
- package/ref-monty/crates/monty/test_cases/bytes__startswith_str_error.py +10 -0
- package/ref-monty/crates/monty/test_cases/call_object.py +3 -0
- package/ref-monty/crates/monty/test_cases/chain_comparison__all.py +79 -0
- package/ref-monty/crates/monty/test_cases/closure__param_shadows_outer.py +81 -0
- package/ref-monty/crates/monty/test_cases/closure__pep448.py +203 -0
- package/ref-monty/crates/monty/test_cases/closure__undefined_nonlocal.py +13 -0
- package/ref-monty/crates/monty/test_cases/compare__mixed_types.py +120 -0
- package/ref-monty/crates/monty/test_cases/comprehension__all.py +208 -0
- package/ref-monty/crates/monty/test_cases/comprehension__scope.py +7 -0
- package/ref-monty/crates/monty/test_cases/comprehension__unbound_local.py +14 -0
- package/ref-monty/crates/monty/test_cases/dataclass__basic.py +238 -0
- package/ref-monty/crates/monty/test_cases/dataclass__call_field_error.py +12 -0
- package/ref-monty/crates/monty/test_cases/dataclass__frozen_set_error.py +12 -0
- package/ref-monty/crates/monty/test_cases/dataclass__get_missing_attr_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/dict__get_unhashable_key.py +3 -0
- package/ref-monty/crates/monty/test_cases/dict__literal_unhashable_key.py +2 -0
- package/ref-monty/crates/monty/test_cases/dict__method_pop_missing_error.py +3 -0
- package/ref-monty/crates/monty/test_cases/dict__methods.py +151 -0
- package/ref-monty/crates/monty/test_cases/dict__ops.py +133 -0
- package/ref-monty/crates/monty/test_cases/dict__pop_unhashable_key.py +4 -0
- package/ref-monty/crates/monty/test_cases/dict__popitem_empty.py +9 -0
- package/ref-monty/crates/monty/test_cases/dict__subscript_missing_key.py +3 -0
- package/ref-monty/crates/monty/test_cases/dict__unhashable_dict_key.py +2 -0
- package/ref-monty/crates/monty/test_cases/dict__unhashable_list_key.py +2 -0
- package/ref-monty/crates/monty/test_cases/dict__unpack_type_error.py +2 -0
- package/ref-monty/crates/monty/test_cases/dict__views.py +165 -0
- package/ref-monty/crates/monty/test_cases/edge__all.py +26 -0
- package/ref-monty/crates/monty/test_cases/edge__float_int_mod.py +2 -0
- package/ref-monty/crates/monty/test_cases/edge__int_float_mod.py +2 -0
- package/ref-monty/crates/monty/test_cases/exc__args.py +16 -0
- package/ref-monty/crates/monty/test_cases/exc__str.py +15 -0
- package/ref-monty/crates/monty/test_cases/execute_ok__all.py +54 -0
- package/ref-monty/crates/monty/test_cases/execute_raise__error_instance_str.py +2 -0
- package/ref-monty/crates/monty/test_cases/execute_raise__error_no_args.py +2 -0
- package/ref-monty/crates/monty/test_cases/execute_raise__error_string_arg.py +2 -0
- package/ref-monty/crates/monty/test_cases/execute_raise__error_string_arg_quotes.py +2 -0
- package/ref-monty/crates/monty/test_cases/execute_raise__error_type.py +2 -0
- package/ref-monty/crates/monty/test_cases/execute_raise__raise_instance_via_var.py +4 -0
- package/ref-monty/crates/monty/test_cases/execute_raise__raise_list.py +2 -0
- package/ref-monty/crates/monty/test_cases/execute_raise__raise_number.py +2 -0
- package/ref-monty/crates/monty/test_cases/execute_raise__raise_type_call_via_var.py +4 -0
- package/ref-monty/crates/monty/test_cases/execute_raise__raise_type_direct.py +3 -0
- package/ref-monty/crates/monty/test_cases/execute_raise__raise_type_via_var.py +4 -0
- package/ref-monty/crates/monty/test_cases/ext_call__arg_side_effect_bug.py +22 -0
- package/ref-monty/crates/monty/test_cases/ext_call__augmented.py +17 -0
- package/ref-monty/crates/monty/test_cases/ext_call__augmented_refcount_bug.py +7 -0
- package/ref-monty/crates/monty/test_cases/ext_call__bare_raise_after_resume.py +34 -0
- package/ref-monty/crates/monty/test_cases/ext_call__basic.py +99 -0
- package/ref-monty/crates/monty/test_cases/ext_call__boolean.py +37 -0
- package/ref-monty/crates/monty/test_cases/ext_call__boolean_side_effect_hang.py +17 -0
- package/ref-monty/crates/monty/test_cases/ext_call__closure_bug.py +16 -0
- package/ref-monty/crates/monty/test_cases/ext_call__comparison.py +26 -0
- package/ref-monty/crates/monty/test_cases/ext_call__deep_call_stack.py +18 -0
- package/ref-monty/crates/monty/test_cases/ext_call__elif.py +171 -0
- package/ref-monty/crates/monty/test_cases/ext_call__exc.py +4 -0
- package/ref-monty/crates/monty/test_cases/ext_call__exc_deep_stack.py +39 -0
- package/ref-monty/crates/monty/test_cases/ext_call__exc_in_function.py +17 -0
- package/ref-monty/crates/monty/test_cases/ext_call__exc_nested_functions.py +31 -0
- package/ref-monty/crates/monty/test_cases/ext_call__ext_exc.py +171 -0
- package/ref-monty/crates/monty/test_cases/ext_call__for.py +114 -0
- package/ref-monty/crates/monty/test_cases/ext_call__fstring.py +12 -0
- package/ref-monty/crates/monty/test_cases/ext_call__if.py +135 -0
- package/ref-monty/crates/monty/test_cases/ext_call__if_condition.py +37 -0
- package/ref-monty/crates/monty/test_cases/ext_call__in_closure.py +14 -0
- package/ref-monty/crates/monty/test_cases/ext_call__in_function.py +40 -0
- package/ref-monty/crates/monty/test_cases/ext_call__in_function_simple.py +7 -0
- package/ref-monty/crates/monty/test_cases/ext_call__literals.py +17 -0
- package/ref-monty/crates/monty/test_cases/ext_call__multi_in_func.py +32 -0
- package/ref-monty/crates/monty/test_cases/ext_call__name_lookup.py +69 -0
- package/ref-monty/crates/monty/test_cases/ext_call__name_lookup_undefined.py +4 -0
- package/ref-monty/crates/monty/test_cases/ext_call__nested_calls.py +14 -0
- package/ref-monty/crates/monty/test_cases/ext_call__recursion_bug.py +19 -0
- package/ref-monty/crates/monty/test_cases/ext_call__return.py +28 -0
- package/ref-monty/crates/monty/test_cases/ext_call__side_effects.py +25 -0
- package/ref-monty/crates/monty/test_cases/ext_call__subscript.py +7 -0
- package/ref-monty/crates/monty/test_cases/ext_call__ternary.py +28 -0
- package/ref-monty/crates/monty/test_cases/ext_call__try.py +280 -0
- package/ref-monty/crates/monty/test_cases/ext_call__try_simple.py +10 -0
- package/ref-monty/crates/monty/test_cases/ext_call__unary.py +13 -0
- package/ref-monty/crates/monty/test_cases/frozenset__ops.py +178 -0
- package/ref-monty/crates/monty/test_cases/fstring__all.py +236 -0
- package/ref-monty/crates/monty/test_cases/fstring__error_eq_align_on_str.py +3 -0
- package/ref-monty/crates/monty/test_cases/fstring__error_float_f_on_str.py +3 -0
- package/ref-monty/crates/monty/test_cases/fstring__error_int_d_on_float.py +3 -0
- package/ref-monty/crates/monty/test_cases/fstring__error_int_d_on_str.py +3 -0
- package/ref-monty/crates/monty/test_cases/fstring__error_invalid_spec.py +4 -0
- package/ref-monty/crates/monty/test_cases/fstring__error_invalid_spec_dynamic.py +4 -0
- package/ref-monty/crates/monty/test_cases/fstring__error_invalid_spec_str.py +4 -0
- package/ref-monty/crates/monty/test_cases/fstring__error_str_s_on_int.py +3 -0
- package/ref-monty/crates/monty/test_cases/function__call_duplicate_kwargs.py +6 -0
- package/ref-monty/crates/monty/test_cases/function__call_unpack.py +42 -0
- package/ref-monty/crates/monty/test_cases/function__defaults.py +117 -0
- package/ref-monty/crates/monty/test_cases/function__err_duplicate_arg.py +7 -0
- package/ref-monty/crates/monty/test_cases/function__err_duplicate_first_arg.py +7 -0
- package/ref-monty/crates/monty/test_cases/function__err_duplicate_kwarg_cleanup.py +9 -0
- package/ref-monty/crates/monty/test_cases/function__err_kwonly_as_positional.py +7 -0
- package/ref-monty/crates/monty/test_cases/function__err_missing_all_posonly.py +7 -0
- package/ref-monty/crates/monty/test_cases/function__err_missing_heap_cleanup.py +9 -0
- package/ref-monty/crates/monty/test_cases/function__err_missing_kwonly.py +7 -0
- package/ref-monty/crates/monty/test_cases/function__err_missing_posonly_with_kwarg.py +7 -0
- package/ref-monty/crates/monty/test_cases/function__err_missing_with_posonly.py +7 -0
- package/ref-monty/crates/monty/test_cases/function__err_posonly_as_kwarg.py +7 -0
- package/ref-monty/crates/monty/test_cases/function__err_posonly_first_as_kwarg.py +7 -0
- package/ref-monty/crates/monty/test_cases/function__err_too_many_posonly.py +7 -0
- package/ref-monty/crates/monty/test_cases/function__err_too_many_with_kwonly.py +7 -0
- package/ref-monty/crates/monty/test_cases/function__err_unexpected_kwarg.py +7 -0
- package/ref-monty/crates/monty/test_cases/function__err_unexpected_kwarg_cleanup.py +9 -0
- package/ref-monty/crates/monty/test_cases/function__err_unexpected_kwarg_quote.py +13 -0
- package/ref-monty/crates/monty/test_cases/function__err_unexpected_kwarg_simple.py +7 -0
- package/ref-monty/crates/monty/test_cases/function__err_unpack_duplicate_arg.py +6 -0
- package/ref-monty/crates/monty/test_cases/function__err_unpack_duplicate_heap.py +8 -0
- package/ref-monty/crates/monty/test_cases/function__err_unpack_int.py +6 -0
- package/ref-monty/crates/monty/test_cases/function__err_unpack_nonstring_key.py +6 -0
- package/ref-monty/crates/monty/test_cases/function__err_unpack_not_mapping.py +6 -0
- package/ref-monty/crates/monty/test_cases/function__kwargs_unpacking.py +173 -0
- package/ref-monty/crates/monty/test_cases/function__ops.py +294 -0
- package/ref-monty/crates/monty/test_cases/function__return_none.py +42 -0
- package/ref-monty/crates/monty/test_cases/function__signatures.py +47 -0
- package/ref-monty/crates/monty/test_cases/function__too_few_args_all.py +6 -0
- package/ref-monty/crates/monty/test_cases/function__too_few_args_one.py +6 -0
- package/ref-monty/crates/monty/test_cases/function__too_few_args_two.py +6 -0
- package/ref-monty/crates/monty/test_cases/function__too_many_args_one.py +6 -0
- package/ref-monty/crates/monty/test_cases/function__too_many_args_two.py +6 -0
- package/ref-monty/crates/monty/test_cases/function__too_many_args_zero.py +6 -0
- package/ref-monty/crates/monty/test_cases/global__error_assigned_before.py +7 -0
- package/ref-monty/crates/monty/test_cases/global__ops.py +163 -0
- package/ref-monty/crates/monty/test_cases/hash__dict_unhashable.py +2 -0
- package/ref-monty/crates/monty/test_cases/hash__list_unhashable.py +2 -0
- package/ref-monty/crates/monty/test_cases/hash__ops.py +153 -0
- package/ref-monty/crates/monty/test_cases/id__bytes_literals_distinct.py +3 -0
- package/ref-monty/crates/monty/test_cases/id__int_copy_distinct.py +5 -0
- package/ref-monty/crates/monty/test_cases/id__is_number_is_number.py +3 -0
- package/ref-monty/crates/monty/test_cases/id__non_overlapping_lifetimes_distinct_types.py +10 -0
- package/ref-monty/crates/monty/test_cases/id__non_overlapping_lifetimes_same_types.py +6 -0
- package/ref-monty/crates/monty/test_cases/id__ops.py +97 -0
- package/ref-monty/crates/monty/test_cases/id__str_literals_same.py +3 -0
- package/ref-monty/crates/monty/test_cases/if__elif_else.py +207 -0
- package/ref-monty/crates/monty/test_cases/if__raise_elif.py +11 -0
- package/ref-monty/crates/monty/test_cases/if__raise_else.py +13 -0
- package/ref-monty/crates/monty/test_cases/if__raise_if.py +9 -0
- package/ref-monty/crates/monty/test_cases/if__raise_in_elif_condition.py +18 -0
- package/ref-monty/crates/monty/test_cases/if__raise_in_if_condition.py +16 -0
- package/ref-monty/crates/monty/test_cases/if_else_expr__all.py +55 -0
- package/ref-monty/crates/monty/test_cases/import__error_cannot_import.py +9 -0
- package/ref-monty/crates/monty/test_cases/import__error_module_not_found.py +9 -0
- package/ref-monty/crates/monty/test_cases/import__local_scope.py +68 -0
- package/ref-monty/crates/monty/test_cases/import__os.py +25 -0
- package/ref-monty/crates/monty/test_cases/import__relative_error.py +9 -0
- package/ref-monty/crates/monty/test_cases/import__relative_no_module_error.py +9 -0
- package/ref-monty/crates/monty/test_cases/import__runtime_error_when_executed.py +14 -0
- package/ref-monty/crates/monty/test_cases/import__star_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/import__sys.py +47 -0
- package/ref-monty/crates/monty/test_cases/import__sys_monty.py +28 -0
- package/ref-monty/crates/monty/test_cases/import__type_checking_guard.py +37 -0
- package/ref-monty/crates/monty/test_cases/import__typing.py +25 -0
- package/ref-monty/crates/monty/test_cases/import__typing_type_ignore.py +4 -0
- package/ref-monty/crates/monty/test_cases/int__bigint.py +467 -0
- package/ref-monty/crates/monty/test_cases/int__bigint_errors.py +260 -0
- package/ref-monty/crates/monty/test_cases/int__ops.py +219 -0
- package/ref-monty/crates/monty/test_cases/int__overflow_division.py +84 -0
- package/ref-monty/crates/monty/test_cases/is_variant__all.py +36 -0
- package/ref-monty/crates/monty/test_cases/isinstance__arg2_list_error.py +2 -0
- package/ref-monty/crates/monty/test_cases/isinstance__arg2_type_error.py +2 -0
- package/ref-monty/crates/monty/test_cases/iter__dict_mutation.py +4 -0
- package/ref-monty/crates/monty/test_cases/iter__for.py +243 -0
- package/ref-monty/crates/monty/test_cases/iter__for_loop_unpacking.py +66 -0
- package/ref-monty/crates/monty/test_cases/iter__generator_expr.py +20 -0
- package/ref-monty/crates/monty/test_cases/iter__generator_expr_type.py +7 -0
- package/ref-monty/crates/monty/test_cases/iter__not_iterable.py +3 -0
- package/ref-monty/crates/monty/test_cases/lambda__all.py +145 -0
- package/ref-monty/crates/monty/test_cases/list__extend_not_iterable.py +7 -0
- package/ref-monty/crates/monty/test_cases/list__getitem_out_of_bounds.py +3 -0
- package/ref-monty/crates/monty/test_cases/list__index_not_found.py +9 -0
- package/ref-monty/crates/monty/test_cases/list__index_start_gt_end.py +10 -0
- package/ref-monty/crates/monty/test_cases/list__ops.py +473 -0
- package/ref-monty/crates/monty/test_cases/list__pop_empty.py +9 -0
- package/ref-monty/crates/monty/test_cases/list__pop_out_of_range.py +9 -0
- package/ref-monty/crates/monty/test_cases/list__pop_type_error.py +9 -0
- package/ref-monty/crates/monty/test_cases/list__remove_not_found.py +9 -0
- package/ref-monty/crates/monty/test_cases/list__setitem_dict_index.py +13 -0
- package/ref-monty/crates/monty/test_cases/list__setitem_huge_int_index.py +13 -0
- package/ref-monty/crates/monty/test_cases/list__setitem_index_error.py +10 -0
- package/ref-monty/crates/monty/test_cases/list__setitem_type_error.py +10 -0
- package/ref-monty/crates/monty/test_cases/list__unpack_type_error.py +2 -0
- package/ref-monty/crates/monty/test_cases/longint__index_error.py +3 -0
- package/ref-monty/crates/monty/test_cases/longint__repeat_error.py +3 -0
- package/ref-monty/crates/monty/test_cases/loop__break_continue.py +113 -0
- package/ref-monty/crates/monty/test_cases/loop__break_finally.py +69 -0
- package/ref-monty/crates/monty/test_cases/loop__break_in_function_error.py +13 -0
- package/ref-monty/crates/monty/test_cases/loop__break_in_if_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/loop__break_nested_except_clears.py +55 -0
- package/ref-monty/crates/monty/test_cases/loop__break_outside_error.py +9 -0
- package/ref-monty/crates/monty/test_cases/loop__continue_finally.py +81 -0
- package/ref-monty/crates/monty/test_cases/loop__continue_in_function_error.py +13 -0
- package/ref-monty/crates/monty/test_cases/loop__continue_in_if_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/loop__continue_nested_except_clears.py +60 -0
- package/ref-monty/crates/monty/test_cases/loop__continue_outside_error.py +9 -0
- package/ref-monty/crates/monty/test_cases/math__acos_domain_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__acosh_domain_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__asin_domain_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__atanh_domain_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__cos_inf_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__cosh_overflow_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__exp_overflow_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__factorial_float_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__factorial_negative_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__floor_inf_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__floor_nan_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__floor_str_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__fmod_inf_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__gamma_neg_int_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__gcd_float_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__isqrt_negative_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__ldexp_overflow_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__log1p_domain_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__log_base1_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__log_zero_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__module.py +1432 -0
- package/ref-monty/crates/monty/test_cases/math__pow_domain_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__sin_inf_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__sqrt_negative_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__tan_inf_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/math__trunc_str_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/method__args_kwargs_unpacking.py +259 -0
- package/ref-monty/crates/monty/test_cases/name_error__unbound_local_func.py +19 -0
- package/ref-monty/crates/monty/test_cases/name_error__unbound_local_module.py +12 -0
- package/ref-monty/crates/monty/test_cases/name_error__undefined_call_chained.py +9 -0
- package/ref-monty/crates/monty/test_cases/name_error__undefined_call_in_expr.py +9 -0
- package/ref-monty/crates/monty/test_cases/name_error__undefined_call_in_function.py +16 -0
- package/ref-monty/crates/monty/test_cases/name_error__undefined_call_with_args.py +9 -0
- package/ref-monty/crates/monty/test_cases/name_error__undefined_global.py +10 -0
- package/ref-monty/crates/monty/test_cases/namedtuple__missing_attr.py +11 -0
- package/ref-monty/crates/monty/test_cases/namedtuple__ops.py +34 -0
- package/ref-monty/crates/monty/test_cases/nonlocal__error_module_level.py +3 -0
- package/ref-monty/crates/monty/test_cases/nonlocal__ops.py +353 -0
- package/ref-monty/crates/monty/test_cases/os__environ.py +40 -0
- package/ref-monty/crates/monty/test_cases/os__getenv_key_list_error.py +5 -0
- package/ref-monty/crates/monty/test_cases/os__getenv_key_type_error.py +5 -0
- package/ref-monty/crates/monty/test_cases/parse_error__complex.py +3 -0
- package/ref-monty/crates/monty/test_cases/pathlib__import.py +11 -0
- package/ref-monty/crates/monty/test_cases/pathlib__os.py +136 -0
- package/ref-monty/crates/monty/test_cases/pathlib__os_read_error.py +12 -0
- package/ref-monty/crates/monty/test_cases/pathlib__pure.py +81 -0
- package/ref-monty/crates/monty/test_cases/pyobject__cycle_dict_self.py +5 -0
- package/ref-monty/crates/monty/test_cases/pyobject__cycle_list_dict.py +6 -0
- package/ref-monty/crates/monty/test_cases/pyobject__cycle_list_self.py +5 -0
- package/ref-monty/crates/monty/test_cases/pyobject__cycle_multiple_refs.py +6 -0
- package/ref-monty/crates/monty/test_cases/range__error_no_args.py +2 -0
- package/ref-monty/crates/monty/test_cases/range__error_step_zero.py +2 -0
- package/ref-monty/crates/monty/test_cases/range__error_too_many_args.py +2 -0
- package/ref-monty/crates/monty/test_cases/range__getitem_index_error.py +10 -0
- package/ref-monty/crates/monty/test_cases/range__ops.py +236 -0
- package/ref-monty/crates/monty/test_cases/re__basic.py +756 -0
- package/ref-monty/crates/monty/test_cases/re__grouping.py +241 -0
- package/ref-monty/crates/monty/test_cases/re__match.py +148 -0
- package/ref-monty/crates/monty/test_cases/recursion__deep_drop.py +26 -0
- package/ref-monty/crates/monty/test_cases/recursion__deep_eq.py +23 -0
- package/ref-monty/crates/monty/test_cases/recursion__deep_hash.py +46 -0
- package/ref-monty/crates/monty/test_cases/recursion__deep_repr.py +12 -0
- package/ref-monty/crates/monty/test_cases/recursion__function_depth.py +13 -0
- package/ref-monty/crates/monty/test_cases/refcount__cycle_mutual_reference.py +18 -0
- package/ref-monty/crates/monty/test_cases/refcount__cycle_self_reference.py +12 -0
- package/ref-monty/crates/monty/test_cases/refcount__dict_basic.py +5 -0
- package/ref-monty/crates/monty/test_cases/refcount__dict_get.py +5 -0
- package/ref-monty/crates/monty/test_cases/refcount__dict_keys_and.py +14 -0
- package/ref-monty/crates/monty/test_cases/refcount__dict_overwrite.py +6 -0
- package/ref-monty/crates/monty/test_cases/refcount__gather_cleanup.py +16 -0
- package/ref-monty/crates/monty/test_cases/refcount__gather_exception.py +18 -0
- package/ref-monty/crates/monty/test_cases/refcount__gather_nested_cancel.py +25 -0
- package/ref-monty/crates/monty/test_cases/refcount__immediate_skipped.py +4 -0
- package/ref-monty/crates/monty/test_cases/refcount__kwargs_unpacking.py +27 -0
- package/ref-monty/crates/monty/test_cases/refcount__list_append_multiple.py +6 -0
- package/ref-monty/crates/monty/test_cases/refcount__list_append_ref.py +5 -0
- package/ref-monty/crates/monty/test_cases/refcount__list_concat.py +5 -0
- package/ref-monty/crates/monty/test_cases/refcount__list_getitem.py +5 -0
- package/ref-monty/crates/monty/test_cases/refcount__list_iadd.py +5 -0
- package/ref-monty/crates/monty/test_cases/refcount__nested_list.py +4 -0
- package/ref-monty/crates/monty/test_cases/refcount__re_pattern_sub_error_paths.py +37 -0
- package/ref-monty/crates/monty/test_cases/refcount__re_search_match.py +34 -0
- package/ref-monty/crates/monty/test_cases/refcount__re_sub_error_paths.py +31 -0
- package/ref-monty/crates/monty/test_cases/refcount__shared_reference.py +4 -0
- package/ref-monty/crates/monty/test_cases/refcount__single_list.py +3 -0
- package/ref-monty/crates/monty/test_cases/repr__cycle_detection.py +24 -0
- package/ref-monty/crates/monty/test_cases/set__ops.py +191 -0
- package/ref-monty/crates/monty/test_cases/set__review_bugs.py +35 -0
- package/ref-monty/crates/monty/test_cases/set__unpack_type_error.py +2 -0
- package/ref-monty/crates/monty/test_cases/slice__invalid_indices.py +2 -0
- package/ref-monty/crates/monty/test_cases/slice__kwargs.py +9 -0
- package/ref-monty/crates/monty/test_cases/slice__no_args.py +9 -0
- package/ref-monty/crates/monty/test_cases/slice__ops.py +149 -0
- package/ref-monty/crates/monty/test_cases/slice__step_zero.py +9 -0
- package/ref-monty/crates/monty/test_cases/slice__step_zero_bytes.py +9 -0
- package/ref-monty/crates/monty/test_cases/slice__step_zero_range.py +9 -0
- package/ref-monty/crates/monty/test_cases/slice__step_zero_str.py +9 -0
- package/ref-monty/crates/monty/test_cases/slice__step_zero_tuple.py +9 -0
- package/ref-monty/crates/monty/test_cases/slice__too_many_args.py +9 -0
- package/ref-monty/crates/monty/test_cases/str__getitem_index_error.py +10 -0
- package/ref-monty/crates/monty/test_cases/str__index_not_found.py +9 -0
- package/ref-monty/crates/monty/test_cases/str__join_no_args.py +9 -0
- package/ref-monty/crates/monty/test_cases/str__join_non_string.py +9 -0
- package/ref-monty/crates/monty/test_cases/str__join_not_iterable.py +9 -0
- package/ref-monty/crates/monty/test_cases/str__join_too_many_args.py +9 -0
- package/ref-monty/crates/monty/test_cases/str__methods.py +327 -0
- package/ref-monty/crates/monty/test_cases/str__ops.py +162 -0
- package/ref-monty/crates/monty/test_cases/str__partition_empty.py +9 -0
- package/ref-monty/crates/monty/test_cases/str__rsplit_empty_sep.py +9 -0
- package/ref-monty/crates/monty/test_cases/str__split_empty_sep.py +9 -0
- package/ref-monty/crates/monty/test_cases/sys__types.py +7 -0
- package/ref-monty/crates/monty/test_cases/traceback__division_error.py +30 -0
- package/ref-monty/crates/monty/test_cases/traceback__index_error.py +17 -0
- package/ref-monty/crates/monty/test_cases/traceback__insert_as_int.py +10 -0
- package/ref-monty/crates/monty/test_cases/traceback__nested_call.py +29 -0
- package/ref-monty/crates/monty/test_cases/traceback__nonlocal_module_scope.py +10 -0
- package/ref-monty/crates/monty/test_cases/traceback__nonlocal_unbound.py +24 -0
- package/ref-monty/crates/monty/test_cases/traceback__range_as_int.py +9 -0
- package/ref-monty/crates/monty/test_cases/traceback__recursion_error.py +23 -0
- package/ref-monty/crates/monty/test_cases/traceback__set_mutation.py +11 -0
- package/ref-monty/crates/monty/test_cases/traceback__undefined_attr_call.py +16 -0
- package/ref-monty/crates/monty/test_cases/traceback__undefined_call.py +16 -0
- package/ref-monty/crates/monty/test_cases/traceback__undefined_raise.py +16 -0
- package/ref-monty/crates/monty/test_cases/try_except__all.py +472 -0
- package/ref-monty/crates/monty/test_cases/try_except__bare_raise_no_context.py +2 -0
- package/ref-monty/crates/monty/test_cases/try_except__invalid_type.py +5 -0
- package/ref-monty/crates/monty/test_cases/tuple__getitem_out_of_bounds.py +3 -0
- package/ref-monty/crates/monty/test_cases/tuple__index_not_found.py +9 -0
- package/ref-monty/crates/monty/test_cases/tuple__index_start_gt_end.py +10 -0
- package/ref-monty/crates/monty/test_cases/tuple__methods.py +19 -0
- package/ref-monty/crates/monty/test_cases/tuple__ops.py +133 -0
- package/ref-monty/crates/monty/test_cases/tuple__unpack_type_error.py +2 -0
- package/ref-monty/crates/monty/test_cases/type__builtin_attr_error.py +9 -0
- package/ref-monty/crates/monty/test_cases/type__bytes_negative.py +2 -0
- package/ref-monty/crates/monty/test_cases/type__cell_not_builtin.py +9 -0
- package/ref-monty/crates/monty/test_cases/type__exception_attr_error.py +11 -0
- package/ref-monty/crates/monty/test_cases/type__float_conversion_error.py +2 -0
- package/ref-monty/crates/monty/test_cases/type__float_repr_both_quotes.py +9 -0
- package/ref-monty/crates/monty/test_cases/type__float_repr_newline.py +9 -0
- package/ref-monty/crates/monty/test_cases/type__float_repr_single_quote.py +9 -0
- package/ref-monty/crates/monty/test_cases/type__int_conversion_error.py +2 -0
- package/ref-monty/crates/monty/test_cases/type__list_not_iterable.py +2 -0
- package/ref-monty/crates/monty/test_cases/type__non_builtin_name_error.py +9 -0
- package/ref-monty/crates/monty/test_cases/type__ops.py +200 -0
- package/ref-monty/crates/monty/test_cases/type__shadow_exc.py +3 -0
- package/ref-monty/crates/monty/test_cases/type__shadow_int.py +9 -0
- package/ref-monty/crates/monty/test_cases/type__shadow_len.py +3 -0
- package/ref-monty/crates/monty/test_cases/type__tuple_not_iterable.py +2 -0
- package/ref-monty/crates/monty/test_cases/type_error__int_add_list.py +2 -0
- package/ref-monty/crates/monty/test_cases/type_error__int_div_str.py +2 -0
- package/ref-monty/crates/monty/test_cases/type_error__int_floordiv_str.py +2 -0
- package/ref-monty/crates/monty/test_cases/type_error__int_iadd_str.py +3 -0
- package/ref-monty/crates/monty/test_cases/type_error__int_mod_str.py +2 -0
- package/ref-monty/crates/monty/test_cases/type_error__int_pow_str.py +2 -0
- package/ref-monty/crates/monty/test_cases/type_error__int_sub_str.py +2 -0
- package/ref-monty/crates/monty/test_cases/type_error__list_add_int.py +2 -0
- package/ref-monty/crates/monty/test_cases/type_error__list_add_str.py +2 -0
- package/ref-monty/crates/monty/test_cases/type_error__list_iadd_int.py +6 -0
- package/ref-monty/crates/monty/test_cases/type_error__str_add_int.py +2 -0
- package/ref-monty/crates/monty/test_cases/type_error__str_iadd_int.py +3 -0
- package/ref-monty/crates/monty/test_cases/type_error__unary_invert_str.py +3 -0
- package/ref-monty/crates/monty/test_cases/type_error__unary_minus_str.py +4 -0
- package/ref-monty/crates/monty/test_cases/type_error__unary_neg_str.py +3 -0
- package/ref-monty/crates/monty/test_cases/type_error__unary_plus_str.py +4 -0
- package/ref-monty/crates/monty/test_cases/typing__types.py +24 -0
- package/ref-monty/crates/monty/test_cases/unpack__nested.py +48 -0
- package/ref-monty/crates/monty/test_cases/unpack__non_sequence.py +9 -0
- package/ref-monty/crates/monty/test_cases/unpack__not_enough.py +9 -0
- package/ref-monty/crates/monty/test_cases/unpack__ops.py +153 -0
- package/ref-monty/crates/monty/test_cases/unpack__star_not_enough.py +9 -0
- package/ref-monty/crates/monty/test_cases/unpack__too_many.py +9 -0
- package/ref-monty/crates/monty/test_cases/version__cpython.py +4 -0
- package/ref-monty/crates/monty/test_cases/walrus__all.py +178 -0
- package/ref-monty/crates/monty/test_cases/while__all.py +206 -0
- package/ref-monty/crates/monty/tests/asyncio.rs +764 -0
- package/ref-monty/crates/monty/tests/binary_serde.rs +185 -0
- package/ref-monty/crates/monty/tests/bytecode_limits.rs +248 -0
- package/ref-monty/crates/monty/tests/datatest_runner.rs +2029 -0
- package/ref-monty/crates/monty/tests/inputs.rs +420 -0
- package/ref-monty/crates/monty/tests/json_serde.rs +250 -0
- package/ref-monty/crates/monty/tests/main.rs +71 -0
- package/ref-monty/crates/monty/tests/math_module.rs +114 -0
- package/ref-monty/crates/monty/tests/name_lookup.rs +482 -0
- package/ref-monty/crates/monty/tests/os_tests.rs +459 -0
- package/ref-monty/crates/monty/tests/parse_errors.rs +441 -0
- package/ref-monty/crates/monty/tests/print_writer.rs +238 -0
- package/ref-monty/crates/monty/tests/py_object.rs +121 -0
- package/ref-monty/crates/monty/tests/regex.rs +90 -0
- package/ref-monty/crates/monty/tests/repl.rs +344 -0
- package/ref-monty/crates/monty/tests/resource_limits.rs +1826 -0
- package/ref-monty/crates/monty/tests/try_from.rs +167 -0
- package/ref-monty/crates/monty-cli/Cargo.toml +25 -0
- package/ref-monty/crates/monty-cli/src/main.rs +541 -0
- package/ref-monty/crates/monty-js/.cargo/config.toml +2 -0
- package/ref-monty/crates/monty-js/.prettierignore +8 -0
- package/ref-monty/crates/monty-js/Cargo.toml +32 -0
- package/ref-monty/crates/monty-js/README.md +207 -0
- package/ref-monty/crates/monty-js/__test__/async.spec.ts +350 -0
- package/ref-monty/crates/monty-js/__test__/basic.spec.ts +114 -0
- package/ref-monty/crates/monty-js/__test__/exceptions.spec.ts +427 -0
- package/ref-monty/crates/monty-js/__test__/external.spec.ts +354 -0
- package/ref-monty/crates/monty-js/__test__/inputs.spec.ts +143 -0
- package/ref-monty/crates/monty-js/__test__/limits.spec.ts +162 -0
- package/ref-monty/crates/monty-js/__test__/package.json +3 -0
- package/ref-monty/crates/monty-js/__test__/print.spec.ts +229 -0
- package/ref-monty/crates/monty-js/__test__/repl.spec.ts +34 -0
- package/ref-monty/crates/monty-js/__test__/serialize.spec.ts +205 -0
- package/ref-monty/crates/monty-js/__test__/start.spec.ts +443 -0
- package/ref-monty/crates/monty-js/__test__/type_check.spec.ts +147 -0
- package/ref-monty/crates/monty-js/__test__/types.spec.ts +319 -0
- package/ref-monty/crates/monty-js/build.rs +61 -0
- package/ref-monty/crates/monty-js/index-header.d.ts +3 -0
- package/ref-monty/crates/monty-js/package-lock.json +4694 -0
- package/ref-monty/crates/monty-js/package.json +100 -0
- package/ref-monty/crates/monty-js/scripts/smoke-test.sh +69 -0
- package/ref-monty/crates/monty-js/smoke-test/package.json +17 -0
- package/ref-monty/crates/monty-js/smoke-test/test.ts +171 -0
- package/ref-monty/crates/monty-js/smoke-test/tsconfig.json +11 -0
- package/ref-monty/crates/monty-js/src/convert.rs +648 -0
- package/ref-monty/crates/monty-js/src/exceptions.rs +293 -0
- package/ref-monty/crates/monty-js/src/lib.rs +41 -0
- package/ref-monty/crates/monty-js/src/limits.rs +53 -0
- package/ref-monty/crates/monty-js/src/monty_cls.rs +1407 -0
- package/ref-monty/crates/monty-js/tsconfig.json +17 -0
- package/ref-monty/crates/monty-js/wrapper.ts +701 -0
- package/ref-monty/crates/monty-python/Cargo.toml +38 -0
- package/ref-monty/crates/monty-python/README.md +134 -0
- package/ref-monty/crates/monty-python/build.rs +4 -0
- package/ref-monty/crates/monty-python/example.py +40 -0
- package/ref-monty/crates/monty-python/exercise.py +46 -0
- package/ref-monty/crates/monty-python/pyproject.toml +57 -0
- package/ref-monty/crates/monty-python/python/pydantic_monty/__init__.py +281 -0
- package/ref-monty/crates/monty-python/python/pydantic_monty/_monty.pyi +677 -0
- package/ref-monty/crates/monty-python/python/pydantic_monty/os_access.py +933 -0
- package/ref-monty/crates/monty-python/python/pydantic_monty/py.typed +0 -0
- package/ref-monty/crates/monty-python/src/convert.rs +273 -0
- package/ref-monty/crates/monty-python/src/dataclass.rs +461 -0
- package/ref-monty/crates/monty-python/src/exceptions.rs +557 -0
- package/ref-monty/crates/monty-python/src/external.rs +165 -0
- package/ref-monty/crates/monty-python/src/lib.rs +77 -0
- package/ref-monty/crates/monty-python/src/limits.rs +142 -0
- package/ref-monty/crates/monty-python/src/monty_cls.rs +1650 -0
- package/ref-monty/crates/monty-python/src/repl.rs +470 -0
- package/ref-monty/crates/monty-python/src/serialization.rs +761 -0
- package/ref-monty/crates/monty-python/tests/test_async.py +1201 -0
- package/ref-monty/crates/monty-python/tests/test_basic.py +66 -0
- package/ref-monty/crates/monty-python/tests/test_dataclasses.py +971 -0
- package/ref-monty/crates/monty-python/tests/test_exceptions.py +361 -0
- package/ref-monty/crates/monty-python/tests/test_external.py +367 -0
- package/ref-monty/crates/monty-python/tests/test_inputs.py +126 -0
- package/ref-monty/crates/monty-python/tests/test_limits.py +257 -0
- package/ref-monty/crates/monty-python/tests/test_os_access.py +1286 -0
- package/ref-monty/crates/monty-python/tests/test_os_access_compat.py +731 -0
- package/ref-monty/crates/monty-python/tests/test_os_access_raw.py +483 -0
- package/ref-monty/crates/monty-python/tests/test_os_calls.py +819 -0
- package/ref-monty/crates/monty-python/tests/test_print.py +208 -0
- package/ref-monty/crates/monty-python/tests/test_re.py +170 -0
- package/ref-monty/crates/monty-python/tests/test_readme_examples.py +20 -0
- package/ref-monty/crates/monty-python/tests/test_repl.py +749 -0
- package/ref-monty/crates/monty-python/tests/test_serialize.py +284 -0
- package/ref-monty/crates/monty-python/tests/test_start.py +346 -0
- package/ref-monty/crates/monty-python/tests/test_threading.py +163 -0
- package/ref-monty/crates/monty-python/tests/test_type_check.py +344 -0
- package/ref-monty/crates/monty-python/tests/test_types.py +553 -0
- package/ref-monty/crates/monty-type-checking/Cargo.toml +32 -0
- package/ref-monty/crates/monty-type-checking/src/db.rs +116 -0
- package/ref-monty/crates/monty-type-checking/src/lib.rs +4 -0
- package/ref-monty/crates/monty-type-checking/src/type_check.rs +280 -0
- package/ref-monty/crates/monty-type-checking/tests/bad_types.py +109 -0
- package/ref-monty/crates/monty-type-checking/tests/bad_types_output.txt +21 -0
- package/ref-monty/crates/monty-type-checking/tests/good_types.py +475 -0
- package/ref-monty/crates/monty-type-checking/tests/main.rs +205 -0
- package/ref-monty/crates/monty-type-checking/tests/reveal_types.py +56 -0
- package/ref-monty/crates/monty-type-checking/tests/reveal_types_output.txt +41 -0
- package/ref-monty/crates/monty-typeshed/Cargo.toml +29 -0
- package/ref-monty/crates/monty-typeshed/README.md +11 -0
- package/ref-monty/crates/monty-typeshed/build.rs +101 -0
- package/ref-monty/crates/monty-typeshed/custom/README.md +1 -0
- package/ref-monty/crates/monty-typeshed/custom/asyncio.pyi +138 -0
- package/ref-monty/crates/monty-typeshed/custom/os.pyi +87 -0
- package/ref-monty/crates/monty-typeshed/custom/sys.pyi +33 -0
- package/ref-monty/crates/monty-typeshed/src/lib.rs +56 -0
- package/ref-monty/crates/monty-typeshed/update.py +321 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/source_commit.txt +1 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/VERSIONS +20 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/_collections_abc.pyi +105 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/_typeshed/__init__.pyi +394 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/asyncio.pyi +138 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/builtins.pyi +1434 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/collections/__init__.pyi +527 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/collections/abc.pyi +2 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/dataclasses.pyi +502 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/enum.pyi +376 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/math.pyi +149 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/os.pyi +87 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/pathlib/__init__.pyi +395 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/pathlib/types.pyi +8 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/re.pyi +337 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/sys.pyi +33 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/types.pyi +741 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/typing.pyi +1217 -0
- package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/typing_extensions.pyi +716 -0
- package/ref-monty/docs/usage-guide.md +117 -0
- package/ref-monty/examples/README.md +3 -0
- package/ref-monty/examples/expense_analysis/README.md +3 -0
- package/ref-monty/examples/expense_analysis/data.py +124 -0
- package/ref-monty/examples/expense_analysis/main.py +115 -0
- package/ref-monty/examples/sql_playground/README.md +20 -0
- package/ref-monty/examples/sql_playground/external_functions.py +129 -0
- package/ref-monty/examples/sql_playground/main.py +81 -0
- package/ref-monty/examples/sql_playground/sandbox_code.py +82 -0
- package/ref-monty/examples/sql_playground/type_stubs.pyi +14 -0
- package/ref-monty/examples/web_scraper/README.md +15 -0
- package/ref-monty/examples/web_scraper/browser.py +56 -0
- package/ref-monty/examples/web_scraper/example_code.py +59 -0
- package/ref-monty/examples/web_scraper/external_functions.py +324 -0
- package/ref-monty/examples/web_scraper/main.py +193 -0
- package/ref-monty/examples/web_scraper/sub_agent.py +79 -0
- package/ref-monty/monty-npm.md +235 -0
- package/ref-monty/pyproject.toml +162 -0
- package/ref-monty/scripts/check_imports.py +91 -0
- package/ref-monty/scripts/codecov_diff.py +412 -0
- package/ref-monty/scripts/complete_tests.py +146 -0
- package/ref-monty/scripts/flamegraph_to_text.py +208 -0
- package/ref-monty/scripts/iter_test_methods.py +540 -0
- package/ref-monty/scripts/run_traceback.py +180 -0
- package/ref-monty/scripts/startup_performance.py +130 -0
- package/ref-monty/uv.lock +1779 -0
- package/temp_resend_cli/repo/.github/scripts/pr-title-check.js +34 -0
- package/temp_resend_cli/repo/.github/workflows/ci.yml +67 -0
- package/temp_resend_cli/repo/.github/workflows/post-release.yml +51 -0
- package/temp_resend_cli/repo/.github/workflows/pr-title-check.yml +13 -0
- package/temp_resend_cli/repo/.github/workflows/release.yml +175 -0
- package/temp_resend_cli/repo/.github/workflows/test-install-unix.yml +34 -0
- package/temp_resend_cli/repo/.github/workflows/test-install-windows.yml +48 -0
- package/temp_resend_cli/repo/CHANGELOG.md +31 -0
- package/temp_resend_cli/repo/LICENSE +21 -0
- package/temp_resend_cli/repo/README.md +450 -0
- package/temp_resend_cli/repo/biome.json +36 -0
- package/temp_resend_cli/repo/install.ps1 +141 -0
- package/temp_resend_cli/repo/install.sh +301 -0
- package/temp_resend_cli/repo/package.json +61 -0
- package/temp_resend_cli/repo/pnpm-lock.yaml +2439 -0
- package/temp_resend_cli/repo/renovate.json +4 -0
- package/temp_resend_cli/repo/src/cli.ts +98 -0
- package/temp_resend_cli/repo/src/commands/api-keys/create.ts +114 -0
- package/temp_resend_cli/repo/src/commands/api-keys/delete.ts +47 -0
- package/temp_resend_cli/repo/src/commands/api-keys/index.ts +26 -0
- package/temp_resend_cli/repo/src/commands/api-keys/list.ts +35 -0
- package/temp_resend_cli/repo/src/commands/api-keys/utils.ts +8 -0
- package/temp_resend_cli/repo/src/commands/auth/index.ts +20 -0
- package/temp_resend_cli/repo/src/commands/auth/login.ts +234 -0
- package/temp_resend_cli/repo/src/commands/auth/logout.ts +105 -0
- package/temp_resend_cli/repo/src/commands/broadcasts/create.ts +196 -0
- package/temp_resend_cli/repo/src/commands/broadcasts/delete.ts +46 -0
- package/temp_resend_cli/repo/src/commands/broadcasts/get.ts +59 -0
- package/temp_resend_cli/repo/src/commands/broadcasts/index.ts +43 -0
- package/temp_resend_cli/repo/src/commands/broadcasts/list.ts +60 -0
- package/temp_resend_cli/repo/src/commands/broadcasts/send.ts +56 -0
- package/temp_resend_cli/repo/src/commands/broadcasts/update.ts +95 -0
- package/temp_resend_cli/repo/src/commands/broadcasts/utils.ts +35 -0
- package/temp_resend_cli/repo/src/commands/contact-properties/create.ts +118 -0
- package/temp_resend_cli/repo/src/commands/contact-properties/delete.ts +48 -0
- package/temp_resend_cli/repo/src/commands/contact-properties/get.ts +46 -0
- package/temp_resend_cli/repo/src/commands/contact-properties/index.ts +48 -0
- package/temp_resend_cli/repo/src/commands/contact-properties/list.ts +68 -0
- package/temp_resend_cli/repo/src/commands/contact-properties/update.ts +88 -0
- package/temp_resend_cli/repo/src/commands/contact-properties/utils.ts +17 -0
- package/temp_resend_cli/repo/src/commands/contacts/add-segment.ts +78 -0
- package/temp_resend_cli/repo/src/commands/contacts/create.ts +122 -0
- package/temp_resend_cli/repo/src/commands/contacts/delete.ts +49 -0
- package/temp_resend_cli/repo/src/commands/contacts/get.ts +53 -0
- package/temp_resend_cli/repo/src/commands/contacts/index.ts +58 -0
- package/temp_resend_cli/repo/src/commands/contacts/list.ts +57 -0
- package/temp_resend_cli/repo/src/commands/contacts/remove-segment.ts +48 -0
- package/temp_resend_cli/repo/src/commands/contacts/segments.ts +39 -0
- package/temp_resend_cli/repo/src/commands/contacts/topics.ts +45 -0
- package/temp_resend_cli/repo/src/commands/contacts/update-topics.ts +90 -0
- package/temp_resend_cli/repo/src/commands/contacts/update.ts +77 -0
- package/temp_resend_cli/repo/src/commands/contacts/utils.ts +119 -0
- package/temp_resend_cli/repo/src/commands/doctor.ts +216 -0
- package/temp_resend_cli/repo/src/commands/domains/create.ts +83 -0
- package/temp_resend_cli/repo/src/commands/domains/delete.ts +42 -0
- package/temp_resend_cli/repo/src/commands/domains/get.ts +47 -0
- package/temp_resend_cli/repo/src/commands/domains/index.ts +35 -0
- package/temp_resend_cli/repo/src/commands/domains/list.ts +53 -0
- package/temp_resend_cli/repo/src/commands/domains/update.ts +75 -0
- package/temp_resend_cli/repo/src/commands/domains/utils.ts +44 -0
- package/temp_resend_cli/repo/src/commands/domains/verify.ts +38 -0
- package/temp_resend_cli/repo/src/commands/emails/batch.ts +140 -0
- package/temp_resend_cli/repo/src/commands/emails/get.ts +44 -0
- package/temp_resend_cli/repo/src/commands/emails/index.ts +30 -0
- package/temp_resend_cli/repo/src/commands/emails/list.ts +84 -0
- package/temp_resend_cli/repo/src/commands/emails/receiving/attachment.ts +55 -0
- package/temp_resend_cli/repo/src/commands/emails/receiving/attachments.ts +68 -0
- package/temp_resend_cli/repo/src/commands/emails/receiving/get.ts +58 -0
- package/temp_resend_cli/repo/src/commands/emails/receiving/index.ts +28 -0
- package/temp_resend_cli/repo/src/commands/emails/receiving/list.ts +59 -0
- package/temp_resend_cli/repo/src/commands/emails/receiving/utils.ts +38 -0
- package/temp_resend_cli/repo/src/commands/emails/send.ts +189 -0
- package/temp_resend_cli/repo/src/commands/open.ts +27 -0
- package/temp_resend_cli/repo/src/commands/segments/create.ts +50 -0
- package/temp_resend_cli/repo/src/commands/segments/delete.ts +47 -0
- package/temp_resend_cli/repo/src/commands/segments/get.ts +38 -0
- package/temp_resend_cli/repo/src/commands/segments/index.ts +36 -0
- package/temp_resend_cli/repo/src/commands/segments/list.ts +58 -0
- package/temp_resend_cli/repo/src/commands/segments/utils.ts +7 -0
- package/temp_resend_cli/repo/src/commands/teams/index.ts +10 -0
- package/temp_resend_cli/repo/src/commands/teams/list.ts +35 -0
- package/temp_resend_cli/repo/src/commands/teams/remove.ts +86 -0
- package/temp_resend_cli/repo/src/commands/teams/switch.ts +76 -0
- package/temp_resend_cli/repo/src/commands/topics/create.ts +73 -0
- package/temp_resend_cli/repo/src/commands/topics/delete.ts +47 -0
- package/temp_resend_cli/repo/src/commands/topics/get.ts +42 -0
- package/temp_resend_cli/repo/src/commands/topics/index.ts +42 -0
- package/temp_resend_cli/repo/src/commands/topics/list.ts +34 -0
- package/temp_resend_cli/repo/src/commands/topics/update.ts +59 -0
- package/temp_resend_cli/repo/src/commands/topics/utils.ts +16 -0
- package/temp_resend_cli/repo/src/commands/webhooks/create.ts +128 -0
- package/temp_resend_cli/repo/src/commands/webhooks/delete.ts +49 -0
- package/temp_resend_cli/repo/src/commands/webhooks/get.ts +42 -0
- package/temp_resend_cli/repo/src/commands/webhooks/index.ts +42 -0
- package/temp_resend_cli/repo/src/commands/webhooks/list.ts +55 -0
- package/temp_resend_cli/repo/src/commands/webhooks/listen.ts +379 -0
- package/temp_resend_cli/repo/src/commands/webhooks/update.ts +83 -0
- package/temp_resend_cli/repo/src/commands/webhooks/utils.ts +36 -0
- package/temp_resend_cli/repo/src/commands/whoami.ts +71 -0
- package/temp_resend_cli/repo/src/lib/actions.ts +157 -0
- package/temp_resend_cli/repo/src/lib/client.ts +37 -0
- package/temp_resend_cli/repo/src/lib/config.ts +217 -0
- package/temp_resend_cli/repo/src/lib/files.ts +15 -0
- package/temp_resend_cli/repo/src/lib/help-text.ts +38 -0
- package/temp_resend_cli/repo/src/lib/output.ts +56 -0
- package/temp_resend_cli/repo/src/lib/pagination.ts +36 -0
- package/temp_resend_cli/repo/src/lib/prompts.ts +149 -0
- package/temp_resend_cli/repo/src/lib/spinner.ts +100 -0
- package/temp_resend_cli/repo/src/lib/table.ts +57 -0
- package/temp_resend_cli/repo/src/lib/tty.ts +28 -0
- package/temp_resend_cli/repo/src/lib/update-check.ts +169 -0
- package/temp_resend_cli/repo/src/lib/version.ts +4 -0
- package/temp_resend_cli/repo/tests/commands/api-keys/create.test.ts +196 -0
- package/temp_resend_cli/repo/tests/commands/api-keys/delete.test.ts +157 -0
- package/temp_resend_cli/repo/tests/commands/api-keys/list.test.ts +134 -0
- package/temp_resend_cli/repo/tests/commands/auth/login.test.ts +153 -0
- package/temp_resend_cli/repo/tests/commands/auth/logout.test.ts +153 -0
- package/temp_resend_cli/repo/tests/commands/broadcasts/create.test.ts +454 -0
- package/temp_resend_cli/repo/tests/commands/broadcasts/delete.test.ts +183 -0
- package/temp_resend_cli/repo/tests/commands/broadcasts/get.test.ts +147 -0
- package/temp_resend_cli/repo/tests/commands/broadcasts/list.test.ts +199 -0
- package/temp_resend_cli/repo/tests/commands/broadcasts/send.test.ts +162 -0
- package/temp_resend_cli/repo/tests/commands/broadcasts/update.test.ts +288 -0
- package/temp_resend_cli/repo/tests/commands/contact-properties/create.test.ts +251 -0
- package/temp_resend_cli/repo/tests/commands/contact-properties/delete.test.ts +184 -0
- package/temp_resend_cli/repo/tests/commands/contact-properties/get.test.ts +145 -0
- package/temp_resend_cli/repo/tests/commands/contact-properties/list.test.ts +181 -0
- package/temp_resend_cli/repo/tests/commands/contact-properties/update.test.ts +217 -0
- package/temp_resend_cli/repo/tests/commands/contacts/add-segment.test.ts +189 -0
- package/temp_resend_cli/repo/tests/commands/contacts/create.test.ts +271 -0
- package/temp_resend_cli/repo/tests/commands/contacts/delete.test.ts +193 -0
- package/temp_resend_cli/repo/tests/commands/contacts/get.test.ts +149 -0
- package/temp_resend_cli/repo/tests/commands/contacts/list.test.ts +176 -0
- package/temp_resend_cli/repo/tests/commands/contacts/remove-segment.test.ts +167 -0
- package/temp_resend_cli/repo/tests/commands/contacts/segments.test.ts +168 -0
- package/temp_resend_cli/repo/tests/commands/contacts/topics.test.ts +164 -0
- package/temp_resend_cli/repo/tests/commands/contacts/update-topics.test.ts +248 -0
- package/temp_resend_cli/repo/tests/commands/contacts/update.test.ts +206 -0
- package/temp_resend_cli/repo/tests/commands/doctor.test.ts +164 -0
- package/temp_resend_cli/repo/tests/commands/domains/create.test.ts +193 -0
- package/temp_resend_cli/repo/tests/commands/domains/delete.test.ts +157 -0
- package/temp_resend_cli/repo/tests/commands/domains/get.test.ts +138 -0
- package/temp_resend_cli/repo/tests/commands/domains/list.test.ts +165 -0
- package/temp_resend_cli/repo/tests/commands/domains/update.test.ts +224 -0
- package/temp_resend_cli/repo/tests/commands/domains/verify.test.ts +118 -0
- package/temp_resend_cli/repo/tests/commands/emails/batch.test.ts +324 -0
- package/temp_resend_cli/repo/tests/commands/emails/get.test.ts +132 -0
- package/temp_resend_cli/repo/tests/commands/emails/receiving/attachment.test.ts +141 -0
- package/temp_resend_cli/repo/tests/commands/emails/receiving/attachments.test.ts +169 -0
- package/temp_resend_cli/repo/tests/commands/emails/receiving/get.test.ts +141 -0
- package/temp_resend_cli/repo/tests/commands/emails/receiving/list.test.ts +182 -0
- package/temp_resend_cli/repo/tests/commands/emails/send.test.ts +312 -0
- package/temp_resend_cli/repo/tests/commands/segments/create.test.ts +164 -0
- package/temp_resend_cli/repo/tests/commands/segments/delete.test.ts +183 -0
- package/temp_resend_cli/repo/tests/commands/segments/get.test.ts +138 -0
- package/temp_resend_cli/repo/tests/commands/segments/list.test.ts +174 -0
- package/temp_resend_cli/repo/tests/commands/teams/list.test.ts +62 -0
- package/temp_resend_cli/repo/tests/commands/teams/remove.test.ts +110 -0
- package/temp_resend_cli/repo/tests/commands/teams/switch.test.ts +103 -0
- package/temp_resend_cli/repo/tests/commands/topics/create.test.ts +192 -0
- package/temp_resend_cli/repo/tests/commands/topics/delete.test.ts +157 -0
- package/temp_resend_cli/repo/tests/commands/topics/get.test.ts +126 -0
- package/temp_resend_cli/repo/tests/commands/topics/list.test.ts +125 -0
- package/temp_resend_cli/repo/tests/commands/topics/update.test.ts +178 -0
- package/temp_resend_cli/repo/tests/commands/webhooks/create.test.ts +225 -0
- package/temp_resend_cli/repo/tests/commands/webhooks/delete.test.ts +157 -0
- package/temp_resend_cli/repo/tests/commands/webhooks/get.test.ts +126 -0
- package/temp_resend_cli/repo/tests/commands/webhooks/list.test.ts +178 -0
- package/temp_resend_cli/repo/tests/commands/webhooks/update.test.ts +207 -0
- package/temp_resend_cli/repo/tests/commands/whoami.test.ts +98 -0
- package/temp_resend_cli/repo/tests/e2e/smoke.test.ts +93 -0
- package/temp_resend_cli/repo/tests/helpers.ts +86 -0
- package/temp_resend_cli/repo/tests/lib/client.test.ts +71 -0
- package/temp_resend_cli/repo/tests/lib/config.test.ts +451 -0
- package/temp_resend_cli/repo/tests/lib/files.test.ts +73 -0
- package/temp_resend_cli/repo/tests/lib/help-text.test.ts +97 -0
- package/temp_resend_cli/repo/tests/lib/output.test.ts +136 -0
- package/temp_resend_cli/repo/tests/lib/prompts.test.ts +185 -0
- package/temp_resend_cli/repo/tests/lib/spinner.test.ts +166 -0
- package/temp_resend_cli/repo/tests/lib/table.test.ts +63 -0
- package/temp_resend_cli/repo/tests/lib/tty.test.ts +89 -0
- package/temp_resend_cli/repo/tests/lib/update-check.test.ts +179 -0
- package/temp_resend_cli/repo/tsconfig.json +14 -0
- package/temp_resend_cli/repo/vitest.config.e2e.ts +8 -0
- package/temp_resend_cli/repo/vitest.config.ts +10 -0
- package/tests/test-mcp-browser-use-smoke.sh +28 -56
- package/tests/test-monty-smoke.sh +32 -0
- package/tests/test-resend-smoke.sh +36 -0
|
@@ -0,0 +1,2356 @@
|
|
|
1
|
+
/// Python bytes type, wrapping a `Vec<u8>`.
|
|
2
|
+
///
|
|
3
|
+
/// This type provides Python bytes semantics with operations on ASCII bytes only.
|
|
4
|
+
/// Unlike str methods which operate on Unicode codepoints, bytes methods only
|
|
5
|
+
/// recognize ASCII characters (0-127) for case transformations and predicates.
|
|
6
|
+
///
|
|
7
|
+
/// # Implemented Methods
|
|
8
|
+
///
|
|
9
|
+
/// ## Encoding/Decoding
|
|
10
|
+
/// - `decode([encoding[, errors]])` - Decode to string (UTF-8 only)
|
|
11
|
+
/// - `hex([sep[, bytes_per_sep]])` - Return hex string representation
|
|
12
|
+
/// - `fromhex(string)` - Create bytes from hex string (classmethod)
|
|
13
|
+
///
|
|
14
|
+
/// ## Simple Transformations
|
|
15
|
+
/// - `lower()` - Convert ASCII uppercase to lowercase
|
|
16
|
+
/// - `upper()` - Convert ASCII lowercase to uppercase
|
|
17
|
+
/// - `capitalize()` - First byte uppercase, rest lowercase
|
|
18
|
+
/// - `title()` - Titlecase ASCII letters
|
|
19
|
+
/// - `swapcase()` - Swap ASCII case
|
|
20
|
+
///
|
|
21
|
+
/// ## Predicates
|
|
22
|
+
/// - `isalpha()` - All bytes are ASCII letters
|
|
23
|
+
/// - `isdigit()` - All bytes are ASCII digits
|
|
24
|
+
/// - `isalnum()` - All bytes are ASCII alphanumeric
|
|
25
|
+
/// - `isspace()` - All bytes are ASCII whitespace
|
|
26
|
+
/// - `islower()` - Has cased bytes, all lowercase
|
|
27
|
+
/// - `isupper()` - Has cased bytes, all uppercase
|
|
28
|
+
/// - `isascii()` - All bytes are ASCII (0-127)
|
|
29
|
+
/// - `istitle()` - Titlecased
|
|
30
|
+
///
|
|
31
|
+
/// ## Search Methods
|
|
32
|
+
/// - `count(sub[, start[, end]])` - Count non-overlapping occurrences
|
|
33
|
+
/// - `find(sub[, start[, end]])` - Find first occurrence (-1 if not found)
|
|
34
|
+
/// - `rfind(sub[, start[, end]])` - Find last occurrence (-1 if not found)
|
|
35
|
+
/// - `index(sub[, start[, end]])` - Find first occurrence (raises ValueError)
|
|
36
|
+
/// - `rindex(sub[, start[, end]])` - Find last occurrence (raises ValueError)
|
|
37
|
+
/// - `startswith(prefix[, start[, end]])` - Check if starts with prefix
|
|
38
|
+
/// - `endswith(suffix[, start[, end]])` - Check if ends with suffix
|
|
39
|
+
///
|
|
40
|
+
/// ## Strip/Trim Methods
|
|
41
|
+
/// - `strip([chars])` - Remove leading/trailing bytes
|
|
42
|
+
/// - `lstrip([chars])` - Remove leading bytes
|
|
43
|
+
/// - `rstrip([chars])` - Remove trailing bytes
|
|
44
|
+
/// - `removeprefix(prefix)` - Remove prefix if present
|
|
45
|
+
/// - `removesuffix(suffix)` - Remove suffix if present
|
|
46
|
+
///
|
|
47
|
+
/// ## Split Methods
|
|
48
|
+
/// - `split([sep[, maxsplit]])` - Split on separator
|
|
49
|
+
/// - `rsplit([sep[, maxsplit]])` - Split from right
|
|
50
|
+
/// - `splitlines([keepends])` - Split on line boundaries
|
|
51
|
+
/// - `partition(sep)` - Split into 3 parts at first sep
|
|
52
|
+
/// - `rpartition(sep)` - Split into 3 parts at last sep
|
|
53
|
+
///
|
|
54
|
+
/// ## Replace/Padding Methods
|
|
55
|
+
/// - `replace(old, new[, count])` - Replace occurrences
|
|
56
|
+
/// - `center(width[, fillbyte])` - Center with fill byte
|
|
57
|
+
/// - `ljust(width[, fillbyte])` - Left justify with fill byte
|
|
58
|
+
/// - `rjust(width[, fillbyte])` - Right justify with fill byte
|
|
59
|
+
/// - `zfill(width)` - Pad with zeros
|
|
60
|
+
///
|
|
61
|
+
/// ## Other Methods
|
|
62
|
+
/// - `join(iterable)` - Join bytes sequences
|
|
63
|
+
///
|
|
64
|
+
/// # Unimplemented Methods
|
|
65
|
+
/// - `expandtabs(tabsize=8)` - Tab expansion
|
|
66
|
+
/// - `translate(table[, delete])` - Character translation
|
|
67
|
+
/// - `maketrans(frm, to)` - Create translation table (staticmethod)
|
|
68
|
+
use std::cmp::Ordering;
|
|
69
|
+
use std::fmt::Write;
|
|
70
|
+
|
|
71
|
+
use ahash::AHashSet;
|
|
72
|
+
use smallvec::smallvec;
|
|
73
|
+
|
|
74
|
+
use super::{MontyIter, PyTrait, Type, str::Str};
|
|
75
|
+
use crate::{
|
|
76
|
+
args::ArgValues,
|
|
77
|
+
bytecode::{CallResult, VM},
|
|
78
|
+
defer_drop, defer_drop_mut,
|
|
79
|
+
exception_private::{ExcType, RunResult, SimpleException},
|
|
80
|
+
heap::{DropWithHeap, Heap, HeapData, HeapGuard, HeapId},
|
|
81
|
+
intern::{Interns, StaticStrings, StringId},
|
|
82
|
+
resource::{ResourceError, ResourceTracker, check_repeat_size, check_replace_size},
|
|
83
|
+
types::List,
|
|
84
|
+
value::{EitherStr, Value},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// =============================================================================
|
|
88
|
+
// ASCII byte helper functions
|
|
89
|
+
// =============================================================================
|
|
90
|
+
|
|
91
|
+
/// Returns true if the byte is Python ASCII whitespace.
|
|
92
|
+
///
|
|
93
|
+
/// Python considers these bytes as whitespace: space, tab, newline, carriage return,
|
|
94
|
+
/// vertical tab (0x0b), and form feed (0x0c). Note: Rust's `is_ascii_whitespace()`
|
|
95
|
+
/// does not include vertical tab (0x0b).
|
|
96
|
+
#[inline]
|
|
97
|
+
fn is_py_whitespace(b: u8) -> bool {
|
|
98
|
+
matches!(b, b' ' | b'\t' | b'\n' | b'\r' | 0x0b | 0x0c)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Gets the byte at a given index, handling negative indices.
|
|
102
|
+
///
|
|
103
|
+
/// Returns `None` if the index is out of bounds.
|
|
104
|
+
/// Negative indices count from the end: -1 is the last byte.
|
|
105
|
+
pub fn get_byte_at_index(bytes: &[u8], index: i64) -> Option<u8> {
|
|
106
|
+
let len = i64::try_from(bytes.len()).ok()?;
|
|
107
|
+
let normalized = if index < 0 { index + len } else { index };
|
|
108
|
+
|
|
109
|
+
if normalized < 0 || normalized >= len {
|
|
110
|
+
return None;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let idx = usize::try_from(normalized).ok()?;
|
|
114
|
+
Some(bytes[idx])
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// Extracts a slice of a byte array.
|
|
118
|
+
///
|
|
119
|
+
/// Handles both positive and negative step values. For negative step,
|
|
120
|
+
/// iterates backward from start down to (but not including) stop.
|
|
121
|
+
/// The `stop` parameter uses a sentinel value of `len + 1` for negative
|
|
122
|
+
/// step to indicate "go to the beginning".
|
|
123
|
+
///
|
|
124
|
+
/// Note: step must be non-zero (callers should validate this via `slice.indices()`).
|
|
125
|
+
pub(crate) fn get_bytes_slice(bytes: &[u8], start: usize, stop: usize, step: i64) -> Vec<u8> {
|
|
126
|
+
let mut result = Vec::new();
|
|
127
|
+
|
|
128
|
+
// try_from succeeds for non-negative step; step==0 rejected upstream by slice.indices()
|
|
129
|
+
if let Ok(step_usize) = usize::try_from(step) {
|
|
130
|
+
// Positive step: iterate forward
|
|
131
|
+
let mut i = start;
|
|
132
|
+
while i < stop && i < bytes.len() {
|
|
133
|
+
result.push(bytes[i]);
|
|
134
|
+
i += step_usize;
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
// Negative step: iterate backward
|
|
138
|
+
// start is the highest index, stop is the sentinel
|
|
139
|
+
// stop > bytes.len() means "go to the beginning"
|
|
140
|
+
let step_abs = usize::try_from(-step).expect("step is negative so -step is positive");
|
|
141
|
+
let step_abs_i64 = i64::try_from(step_abs).expect("step magnitude fits in i64");
|
|
142
|
+
let mut i = i64::try_from(start).expect("start index fits in i64");
|
|
143
|
+
let stop_i64 = if stop > bytes.len() {
|
|
144
|
+
-1
|
|
145
|
+
} else {
|
|
146
|
+
i64::try_from(stop).expect("stop bounded by bytes.len() fits in i64")
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
while let Ok(i_usize) = usize::try_from(i) {
|
|
150
|
+
if i_usize >= bytes.len() || i <= stop_i64 {
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
result.push(bytes[i_usize]);
|
|
154
|
+
i -= step_abs_i64;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
result
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/// Python bytes value stored on the heap.
|
|
162
|
+
///
|
|
163
|
+
/// Wraps a `Vec<u8>` and provides Python-compatible operations.
|
|
164
|
+
/// See the module-level documentation for implemented and unimplemented methods.
|
|
165
|
+
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
|
|
166
|
+
pub(crate) struct Bytes(Vec<u8>);
|
|
167
|
+
|
|
168
|
+
impl Bytes {
|
|
169
|
+
/// Creates a new Bytes from a byte vector.
|
|
170
|
+
#[must_use]
|
|
171
|
+
pub fn new(bytes: Vec<u8>) -> Self {
|
|
172
|
+
Self(bytes)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// Returns a reference to the inner byte slice.
|
|
176
|
+
#[must_use]
|
|
177
|
+
pub fn as_slice(&self) -> &[u8] {
|
|
178
|
+
&self.0
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// Creates bytes from the `bytes()` constructor call.
|
|
182
|
+
///
|
|
183
|
+
/// - `bytes()` with no args returns empty bytes
|
|
184
|
+
/// - `bytes(int)` returns bytes of that length filled with zeros
|
|
185
|
+
/// - `bytes(string)` encodes the string as UTF-8 (simplified, no encoding param)
|
|
186
|
+
/// - `bytes(bytes)` returns a copy of the bytes
|
|
187
|
+
///
|
|
188
|
+
/// Note: Full Python semantics for bytes() are more complex (encoding, errors params).
|
|
189
|
+
pub fn init(vm: &mut VM<'_, '_, impl ResourceTracker>, args: ArgValues) -> RunResult<Value> {
|
|
190
|
+
let heap = &mut *vm.heap;
|
|
191
|
+
let interns = vm.interns;
|
|
192
|
+
let value = args.get_zero_one_arg("bytes", heap)?;
|
|
193
|
+
defer_drop!(value, heap);
|
|
194
|
+
let new_data = match value {
|
|
195
|
+
None => Vec::new(),
|
|
196
|
+
Some(Value::Int(n)) => {
|
|
197
|
+
if *n < 0 {
|
|
198
|
+
return Err(ExcType::value_error_negative_bytes_count());
|
|
199
|
+
}
|
|
200
|
+
let size = usize::try_from(*n).expect("bytes count validated non-negative");
|
|
201
|
+
vec![0u8; size]
|
|
202
|
+
}
|
|
203
|
+
Some(Value::InternString(string_id)) => {
|
|
204
|
+
let s = interns.get_str(*string_id);
|
|
205
|
+
s.as_bytes().to_vec()
|
|
206
|
+
}
|
|
207
|
+
Some(Value::InternBytes(bytes_id)) => {
|
|
208
|
+
let b = interns.get_bytes(*bytes_id);
|
|
209
|
+
b.to_vec()
|
|
210
|
+
}
|
|
211
|
+
Some(v @ Value::Ref(id)) => match heap.get(*id) {
|
|
212
|
+
HeapData::Str(s) => s.as_str().as_bytes().to_vec(),
|
|
213
|
+
HeapData::Bytes(b) => b.as_slice().to_vec(),
|
|
214
|
+
_ => return Err(ExcType::type_error_bytes_init(v.py_type(heap))),
|
|
215
|
+
},
|
|
216
|
+
Some(v) => return Err(ExcType::type_error_bytes_init(v.py_type(heap))),
|
|
217
|
+
};
|
|
218
|
+
let heap_id = heap.allocate(HeapData::Bytes(Self::new(new_data)))?;
|
|
219
|
+
Ok(Value::Ref(heap_id))
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
impl From<Vec<u8>> for Bytes {
|
|
224
|
+
fn from(bytes: Vec<u8>) -> Self {
|
|
225
|
+
Self(bytes)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
impl From<&[u8]> for Bytes {
|
|
230
|
+
fn from(bytes: &[u8]) -> Self {
|
|
231
|
+
Self(bytes.to_vec())
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
impl From<Bytes> for Vec<u8> {
|
|
236
|
+
fn from(bytes: Bytes) -> Self {
|
|
237
|
+
bytes.0
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
impl std::ops::Deref for Bytes {
|
|
242
|
+
type Target = Vec<u8>;
|
|
243
|
+
|
|
244
|
+
fn deref(&self) -> &Self::Target {
|
|
245
|
+
&self.0
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
impl PyTrait for Bytes {
|
|
250
|
+
fn py_type(&self, _heap: &Heap<impl ResourceTracker>) -> Type {
|
|
251
|
+
Type::Bytes
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
fn py_estimate_size(&self) -> usize {
|
|
255
|
+
std::mem::size_of::<Self>() + self.0.len()
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
fn py_len(&self, _vm: &VM<'_, '_, impl ResourceTracker>) -> Option<usize> {
|
|
259
|
+
Some(self.0.len())
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
fn py_getitem(&self, key: &Value, vm: &mut VM<'_, '_, impl ResourceTracker>) -> RunResult<Value> {
|
|
263
|
+
let heap = &mut *vm.heap;
|
|
264
|
+
// Check for slice first (Value::Ref pointing to HeapData::Slice)
|
|
265
|
+
if let Value::Ref(id) = key
|
|
266
|
+
&& let HeapData::Slice(slice) = heap.get(*id)
|
|
267
|
+
{
|
|
268
|
+
let (start, stop, step) = slice
|
|
269
|
+
.indices(self.0.len())
|
|
270
|
+
.map_err(|()| ExcType::value_error_slice_step_zero())?;
|
|
271
|
+
|
|
272
|
+
let sliced_bytes = get_bytes_slice(&self.0, start, stop, step);
|
|
273
|
+
let heap_id = heap.allocate(HeapData::Bytes(Self::new(sliced_bytes)))?;
|
|
274
|
+
return Ok(Value::Ref(heap_id));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Extract integer index, accepting Int, Bool (True=1, False=0), and LongInt
|
|
278
|
+
let index = key.as_index(heap, Type::Bytes)?;
|
|
279
|
+
|
|
280
|
+
// Use helper for byte indexing
|
|
281
|
+
let byte = get_byte_at_index(&self.0, index).ok_or_else(ExcType::bytes_index_error)?;
|
|
282
|
+
Ok(Value::Int(i64::from(byte)))
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
fn py_eq(&self, other: &Self, _vm: &mut VM<'_, '_, impl ResourceTracker>) -> Result<bool, ResourceError> {
|
|
286
|
+
Ok(self.0 == other.0)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/// Bytes don't contain nested heap references.
|
|
290
|
+
fn py_dec_ref_ids(&mut self, _stack: &mut Vec<HeapId>) {
|
|
291
|
+
// No-op: bytes don't hold Value references
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
fn py_cmp(
|
|
295
|
+
&self,
|
|
296
|
+
other: &Self,
|
|
297
|
+
_vm: &mut VM<'_, '_, impl ResourceTracker>,
|
|
298
|
+
) -> Result<Option<Ordering>, ResourceError> {
|
|
299
|
+
Ok(Some(self.0.cmp(&other.0)))
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
fn py_bool(&self, _vm: &VM<'_, '_, impl ResourceTracker>) -> bool {
|
|
303
|
+
!self.0.is_empty()
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
fn py_repr_fmt(
|
|
307
|
+
&self,
|
|
308
|
+
f: &mut impl Write,
|
|
309
|
+
_vm: &VM<'_, '_, impl ResourceTracker>,
|
|
310
|
+
_heap_ids: &mut AHashSet<HeapId>,
|
|
311
|
+
) -> std::fmt::Result {
|
|
312
|
+
bytes_repr_fmt(&self.0, f)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
fn py_call_attr(
|
|
316
|
+
&mut self,
|
|
317
|
+
_self_id: HeapId,
|
|
318
|
+
vm: &mut VM<'_, '_, impl ResourceTracker>,
|
|
319
|
+
attr: &EitherStr,
|
|
320
|
+
args: ArgValues,
|
|
321
|
+
) -> RunResult<CallResult> {
|
|
322
|
+
let Some(method) = attr.static_string() else {
|
|
323
|
+
args.drop_with_heap(vm.heap);
|
|
324
|
+
return Err(ExcType::attribute_error(Type::Bytes, attr.as_str(vm.interns)));
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
call_bytes_method_impl(self.as_slice(), method, args, vm).map(CallResult::Value)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/// Calls a bytes method on a byte slice by method name.
|
|
332
|
+
///
|
|
333
|
+
/// This is the entry point for bytes method calls from the VM on interned bytes.
|
|
334
|
+
/// Converts the `StringId` to `StaticStrings` and delegates to `call_bytes_method_impl`.
|
|
335
|
+
pub fn call_bytes_method(
|
|
336
|
+
bytes: &[u8],
|
|
337
|
+
method_id: StringId,
|
|
338
|
+
args: ArgValues,
|
|
339
|
+
vm: &mut VM<'_, '_, impl ResourceTracker>,
|
|
340
|
+
) -> RunResult<Value> {
|
|
341
|
+
let Some(method) = StaticStrings::from_string_id(method_id) else {
|
|
342
|
+
args.drop_with_heap(vm.heap);
|
|
343
|
+
return Err(ExcType::attribute_error(Type::Bytes, vm.interns.get_str(method_id)));
|
|
344
|
+
};
|
|
345
|
+
call_bytes_method_impl(bytes, method, args, vm)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/// Calls a bytes method on a byte slice.
|
|
349
|
+
///
|
|
350
|
+
/// This is the unified implementation for bytes method calls, used by both
|
|
351
|
+
/// heap-allocated `Bytes` (via `py_call_attr`) and interned bytes literals
|
|
352
|
+
/// (`Value::InternBytes`).
|
|
353
|
+
fn call_bytes_method_impl(
|
|
354
|
+
bytes: &[u8],
|
|
355
|
+
method: StaticStrings,
|
|
356
|
+
args: ArgValues,
|
|
357
|
+
vm: &mut VM<'_, '_, impl ResourceTracker>,
|
|
358
|
+
) -> RunResult<Value> {
|
|
359
|
+
let heap = &mut *vm.heap;
|
|
360
|
+
let interns = vm.interns;
|
|
361
|
+
match method {
|
|
362
|
+
// Decode method
|
|
363
|
+
StaticStrings::Decode => bytes_decode(bytes, args, heap, interns),
|
|
364
|
+
// Simple transformations (no arguments)
|
|
365
|
+
StaticStrings::Lower => {
|
|
366
|
+
args.check_zero_args("bytes.lower", heap)?;
|
|
367
|
+
bytes_lower(bytes, heap)
|
|
368
|
+
}
|
|
369
|
+
StaticStrings::Upper => {
|
|
370
|
+
args.check_zero_args("bytes.upper", heap)?;
|
|
371
|
+
bytes_upper(bytes, heap)
|
|
372
|
+
}
|
|
373
|
+
StaticStrings::Capitalize => {
|
|
374
|
+
args.check_zero_args("bytes.capitalize", heap)?;
|
|
375
|
+
bytes_capitalize(bytes, heap)
|
|
376
|
+
}
|
|
377
|
+
StaticStrings::Title => {
|
|
378
|
+
args.check_zero_args("bytes.title", heap)?;
|
|
379
|
+
bytes_title(bytes, heap)
|
|
380
|
+
}
|
|
381
|
+
StaticStrings::Swapcase => {
|
|
382
|
+
args.check_zero_args("bytes.swapcase", heap)?;
|
|
383
|
+
bytes_swapcase(bytes, heap)
|
|
384
|
+
}
|
|
385
|
+
// Predicate methods (no arguments, return bool)
|
|
386
|
+
StaticStrings::Isalpha => {
|
|
387
|
+
args.check_zero_args("bytes.isalpha", heap)?;
|
|
388
|
+
Ok(Value::Bool(bytes_isalpha(bytes)))
|
|
389
|
+
}
|
|
390
|
+
StaticStrings::Isdigit => {
|
|
391
|
+
args.check_zero_args("bytes.isdigit", heap)?;
|
|
392
|
+
Ok(Value::Bool(bytes_isdigit(bytes)))
|
|
393
|
+
}
|
|
394
|
+
StaticStrings::Isalnum => {
|
|
395
|
+
args.check_zero_args("bytes.isalnum", heap)?;
|
|
396
|
+
Ok(Value::Bool(bytes_isalnum(bytes)))
|
|
397
|
+
}
|
|
398
|
+
StaticStrings::Isspace => {
|
|
399
|
+
args.check_zero_args("bytes.isspace", heap)?;
|
|
400
|
+
Ok(Value::Bool(bytes_isspace(bytes)))
|
|
401
|
+
}
|
|
402
|
+
StaticStrings::Islower => {
|
|
403
|
+
args.check_zero_args("bytes.islower", heap)?;
|
|
404
|
+
Ok(Value::Bool(bytes_islower(bytes)))
|
|
405
|
+
}
|
|
406
|
+
StaticStrings::Isupper => {
|
|
407
|
+
args.check_zero_args("bytes.isupper", heap)?;
|
|
408
|
+
Ok(Value::Bool(bytes_isupper(bytes)))
|
|
409
|
+
}
|
|
410
|
+
StaticStrings::Isascii => {
|
|
411
|
+
args.check_zero_args("bytes.isascii", heap)?;
|
|
412
|
+
Ok(Value::Bool(bytes.iter().all(|&b| b <= 127)))
|
|
413
|
+
}
|
|
414
|
+
StaticStrings::Istitle => {
|
|
415
|
+
args.check_zero_args("bytes.istitle", heap)?;
|
|
416
|
+
Ok(Value::Bool(bytes_istitle(bytes)))
|
|
417
|
+
}
|
|
418
|
+
// Search methods
|
|
419
|
+
StaticStrings::Count => bytes_count(bytes, args, heap, interns),
|
|
420
|
+
StaticStrings::Find => bytes_find(bytes, args, heap, interns),
|
|
421
|
+
StaticStrings::Rfind => bytes_rfind(bytes, args, heap, interns),
|
|
422
|
+
StaticStrings::Index => bytes_index(bytes, args, heap, interns),
|
|
423
|
+
StaticStrings::Rindex => bytes_rindex(bytes, args, heap, interns),
|
|
424
|
+
StaticStrings::Startswith => bytes_startswith(bytes, args, heap, interns),
|
|
425
|
+
StaticStrings::Endswith => bytes_endswith(bytes, args, heap, interns),
|
|
426
|
+
// Strip/trim methods
|
|
427
|
+
StaticStrings::Strip => bytes_strip(bytes, args, heap, interns),
|
|
428
|
+
StaticStrings::Lstrip => bytes_lstrip(bytes, args, heap, interns),
|
|
429
|
+
StaticStrings::Rstrip => bytes_rstrip(bytes, args, heap, interns),
|
|
430
|
+
StaticStrings::Removeprefix => bytes_removeprefix(bytes, args, heap, interns),
|
|
431
|
+
StaticStrings::Removesuffix => bytes_removesuffix(bytes, args, heap, interns),
|
|
432
|
+
// Split methods
|
|
433
|
+
StaticStrings::Split => bytes_split(bytes, args, heap, interns),
|
|
434
|
+
StaticStrings::Rsplit => bytes_rsplit(bytes, args, heap, interns),
|
|
435
|
+
StaticStrings::Splitlines => bytes_splitlines(bytes, args, vm),
|
|
436
|
+
StaticStrings::Partition => bytes_partition(bytes, args, heap, interns),
|
|
437
|
+
StaticStrings::Rpartition => bytes_rpartition(bytes, args, heap, interns),
|
|
438
|
+
// Replace/padding methods
|
|
439
|
+
StaticStrings::Replace => bytes_replace(bytes, args, heap, interns),
|
|
440
|
+
StaticStrings::Center => bytes_center(bytes, args, heap, interns),
|
|
441
|
+
StaticStrings::Ljust => bytes_ljust(bytes, args, heap, interns),
|
|
442
|
+
StaticStrings::Rjust => bytes_rjust(bytes, args, heap, interns),
|
|
443
|
+
StaticStrings::Zfill => bytes_zfill(bytes, args, heap),
|
|
444
|
+
// Join method
|
|
445
|
+
StaticStrings::Join => {
|
|
446
|
+
let iterable = args.get_one_arg("bytes.join", vm.heap)?;
|
|
447
|
+
bytes_join(bytes, iterable, vm)
|
|
448
|
+
}
|
|
449
|
+
// Hex method
|
|
450
|
+
StaticStrings::Hex => bytes_hex(bytes, args, heap, interns),
|
|
451
|
+
// fromhex is a classmethod but also accessible on instances
|
|
452
|
+
StaticStrings::Fromhex => bytes_fromhex(args, heap, interns),
|
|
453
|
+
_ => {
|
|
454
|
+
args.drop_with_heap(heap);
|
|
455
|
+
Err(ExcType::attribute_error(Type::Bytes, method.into()))
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/// Writes a CPython-compatible repr string for bytes to a formatter.
|
|
461
|
+
///
|
|
462
|
+
/// Format: `b'...'` or `b"..."` depending on content.
|
|
463
|
+
/// - Uses single quotes by default
|
|
464
|
+
/// - Switches to double quotes if bytes contain `'` but not `"`
|
|
465
|
+
/// - Escapes: `\\`, `\t`, `\n`, `\r`, `\xNN` for non-printable bytes
|
|
466
|
+
pub fn bytes_repr_fmt(bytes: &[u8], f: &mut impl Write) -> std::fmt::Result {
|
|
467
|
+
// Determine quote character: use double quotes if single quote present but not double
|
|
468
|
+
let has_single = bytes.contains(&b'\'');
|
|
469
|
+
let has_double = bytes.contains(&b'"');
|
|
470
|
+
let quote = if has_single && !has_double { '"' } else { '\'' };
|
|
471
|
+
|
|
472
|
+
f.write_char('b')?;
|
|
473
|
+
f.write_char(quote)?;
|
|
474
|
+
|
|
475
|
+
for &byte in bytes {
|
|
476
|
+
match byte {
|
|
477
|
+
b'\\' => f.write_str("\\\\")?,
|
|
478
|
+
b'\t' => f.write_str("\\t")?,
|
|
479
|
+
b'\n' => f.write_str("\\n")?,
|
|
480
|
+
b'\r' => f.write_str("\\r")?,
|
|
481
|
+
b'\'' if quote == '\'' => f.write_str("\\'")?,
|
|
482
|
+
b'"' if quote == '"' => f.write_str("\\\"")?,
|
|
483
|
+
// Printable ASCII (32-126)
|
|
484
|
+
0x20..=0x7e => f.write_char(byte as char)?,
|
|
485
|
+
// Non-printable: use \xNN format
|
|
486
|
+
_ => write!(f, "\\x{byte:02x}")?,
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
f.write_char(quote)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/// Returns a CPython-compatible repr string for bytes.
|
|
494
|
+
///
|
|
495
|
+
/// Convenience wrapper around `bytes_repr_fmt` that returns an owned String.
|
|
496
|
+
#[must_use]
|
|
497
|
+
pub fn bytes_repr(bytes: &[u8]) -> String {
|
|
498
|
+
let mut result = String::new();
|
|
499
|
+
// Writing to String never fails
|
|
500
|
+
bytes_repr_fmt(bytes, &mut result).unwrap();
|
|
501
|
+
result
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/// Implements Python's `bytes.decode([encoding[, errors]])` method.
|
|
505
|
+
///
|
|
506
|
+
/// Converts bytes to a string. Currently only supports UTF-8 encoding.
|
|
507
|
+
fn bytes_decode(
|
|
508
|
+
bytes: &[u8],
|
|
509
|
+
args: ArgValues,
|
|
510
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
511
|
+
interns: &Interns,
|
|
512
|
+
) -> RunResult<Value> {
|
|
513
|
+
let (encoding, errors) = args.get_zero_one_two_args("bytes.decode", heap)?;
|
|
514
|
+
defer_drop!(encoding, heap);
|
|
515
|
+
defer_drop!(errors, heap); // NB we don't use errors argument yet
|
|
516
|
+
|
|
517
|
+
// Check encoding (default UTF-8)
|
|
518
|
+
let encoding = if let Some(enc) = encoding {
|
|
519
|
+
get_encoding_str(enc, heap, interns)?.to_ascii_lowercase()
|
|
520
|
+
} else {
|
|
521
|
+
"utf-8".to_owned()
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
// Only support UTF-8 family
|
|
525
|
+
if !matches!(encoding.as_str(), "utf-8" | "utf8" | "utf_8") {
|
|
526
|
+
return Err(ExcType::lookup_error_unknown_encoding(&encoding));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Decode as UTF-8
|
|
530
|
+
match std::str::from_utf8(bytes) {
|
|
531
|
+
Ok(s) => {
|
|
532
|
+
let heap_id = heap.allocate(HeapData::Str(Str::from(s.to_owned())))?;
|
|
533
|
+
Ok(Value::Ref(heap_id))
|
|
534
|
+
}
|
|
535
|
+
Err(_) => Err(ExcType::unicode_decode_error_invalid_utf8()),
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/// Helper function to extract encoding string from a value.
|
|
540
|
+
fn get_encoding_str<'a>(
|
|
541
|
+
encoding: &Value,
|
|
542
|
+
heap: &'a Heap<impl ResourceTracker>,
|
|
543
|
+
interns: &'a Interns,
|
|
544
|
+
) -> RunResult<&'a str> {
|
|
545
|
+
match encoding {
|
|
546
|
+
Value::InternString(id) => Ok(interns.get_str(*id)),
|
|
547
|
+
Value::Ref(id) => match heap.get(*id) {
|
|
548
|
+
HeapData::Str(s) => Ok(s.as_str()),
|
|
549
|
+
_ => Err(ExcType::type_error(
|
|
550
|
+
"decode() argument 'encoding' must be str, not bytes",
|
|
551
|
+
)),
|
|
552
|
+
},
|
|
553
|
+
// FIXME: should use proper encoding.py_type() here
|
|
554
|
+
_ => Err(ExcType::type_error("decode() argument 'encoding' must be str, not int")),
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/// Implements Python's `bytes.count(sub[, start[, end]])` method.
|
|
559
|
+
///
|
|
560
|
+
/// Returns the number of non-overlapping occurrences of the subsequence.
|
|
561
|
+
fn bytes_count(
|
|
562
|
+
bytes: &[u8],
|
|
563
|
+
args: ArgValues,
|
|
564
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
565
|
+
interns: &Interns,
|
|
566
|
+
) -> RunResult<Value> {
|
|
567
|
+
let (sub, start, end) = parse_bytes_sub_args("bytes.count", bytes.len(), args, heap, interns)?;
|
|
568
|
+
|
|
569
|
+
let slice = &bytes[start..end];
|
|
570
|
+
let count = if sub.is_empty() {
|
|
571
|
+
// Empty subsequence: count positions between each byte plus 1
|
|
572
|
+
slice.len() + 1
|
|
573
|
+
} else {
|
|
574
|
+
count_non_overlapping(slice, &sub)
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
let count_i64 = i64::try_from(count).expect("count exceeds i64::MAX");
|
|
578
|
+
Ok(Value::Int(count_i64))
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/// Counts non-overlapping occurrences of needle in haystack.
|
|
582
|
+
fn count_non_overlapping(haystack: &[u8], needle: &[u8]) -> usize {
|
|
583
|
+
let mut count = 0;
|
|
584
|
+
let mut pos = 0;
|
|
585
|
+
while pos + needle.len() <= haystack.len() {
|
|
586
|
+
if &haystack[pos..pos + needle.len()] == needle {
|
|
587
|
+
count += 1;
|
|
588
|
+
pos += needle.len();
|
|
589
|
+
} else {
|
|
590
|
+
pos += 1;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
count
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/// Implements Python's `bytes.find(sub[, start[, end]])` method.
|
|
597
|
+
///
|
|
598
|
+
/// Returns the lowest index where the subsequence is found, or -1 if not found.
|
|
599
|
+
fn bytes_find(
|
|
600
|
+
bytes: &[u8],
|
|
601
|
+
args: ArgValues,
|
|
602
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
603
|
+
interns: &Interns,
|
|
604
|
+
) -> RunResult<Value> {
|
|
605
|
+
let (sub, start, end) = parse_bytes_sub_args("bytes.find", bytes.len(), args, heap, interns)?;
|
|
606
|
+
|
|
607
|
+
let slice = &bytes[start..end];
|
|
608
|
+
let result = if sub.is_empty() {
|
|
609
|
+
// Empty subsequence: always found at start position
|
|
610
|
+
Some(0)
|
|
611
|
+
} else {
|
|
612
|
+
find_subsequence(slice, &sub)
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
let idx = match result {
|
|
616
|
+
Some(i) => i64::try_from(start + i).expect("index exceeds i64::MAX"),
|
|
617
|
+
None => -1,
|
|
618
|
+
};
|
|
619
|
+
Ok(Value::Int(idx))
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/// Finds the first occurrence of needle in haystack.
|
|
623
|
+
fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
|
|
624
|
+
haystack.windows(needle.len()).position(|window| window == needle)
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/// Implements Python's `bytes.index(sub[, start[, end]])` method.
|
|
628
|
+
///
|
|
629
|
+
/// Like find(), but raises ValueError if the subsequence is not found.
|
|
630
|
+
fn bytes_index(
|
|
631
|
+
bytes: &[u8],
|
|
632
|
+
args: ArgValues,
|
|
633
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
634
|
+
interns: &Interns,
|
|
635
|
+
) -> RunResult<Value> {
|
|
636
|
+
let (sub, start, end) = parse_bytes_sub_args("bytes.index", bytes.len(), args, heap, interns)?;
|
|
637
|
+
|
|
638
|
+
let slice = &bytes[start..end];
|
|
639
|
+
let result = if sub.is_empty() {
|
|
640
|
+
// Empty subsequence: always found at start position
|
|
641
|
+
Some(0)
|
|
642
|
+
} else {
|
|
643
|
+
find_subsequence(slice, &sub)
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
match result {
|
|
647
|
+
Some(i) => {
|
|
648
|
+
let idx = i64::try_from(start + i).expect("index exceeds i64::MAX");
|
|
649
|
+
Ok(Value::Int(idx))
|
|
650
|
+
}
|
|
651
|
+
None => Err(ExcType::value_error_subsequence_not_found()),
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/// Implements Python's `bytes.startswith(prefix[, start[, end]])` method.
|
|
656
|
+
///
|
|
657
|
+
/// Returns True if bytes starts with the specified prefix.
|
|
658
|
+
/// Accepts bytes or a tuple of bytes as prefix. If a tuple is given, returns True
|
|
659
|
+
/// if any of the prefixes match.
|
|
660
|
+
fn bytes_startswith(
|
|
661
|
+
bytes: &[u8],
|
|
662
|
+
args: ArgValues,
|
|
663
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
664
|
+
interns: &Interns,
|
|
665
|
+
) -> RunResult<Value> {
|
|
666
|
+
let (prefix_arg, start, end) =
|
|
667
|
+
parse_bytes_prefix_suffix_args("bytes.startswith", bytes.len(), args, heap, interns)?;
|
|
668
|
+
|
|
669
|
+
let slice = &bytes[start..end];
|
|
670
|
+
let result = match prefix_arg {
|
|
671
|
+
PrefixSuffixArg::Single(prefix_bytes) => slice.starts_with(&prefix_bytes),
|
|
672
|
+
PrefixSuffixArg::Multiple(prefixes) => prefixes.iter().any(|p| slice.starts_with(p)),
|
|
673
|
+
};
|
|
674
|
+
Ok(Value::Bool(result))
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/// Implements Python's `bytes.endswith(suffix[, start[, end]])` method.
|
|
678
|
+
///
|
|
679
|
+
/// Returns True if bytes ends with the specified suffix.
|
|
680
|
+
/// Accepts bytes or a tuple of bytes as suffix. If a tuple is given, returns True
|
|
681
|
+
/// if any of the suffixes match.
|
|
682
|
+
fn bytes_endswith(
|
|
683
|
+
bytes: &[u8],
|
|
684
|
+
args: ArgValues,
|
|
685
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
686
|
+
interns: &Interns,
|
|
687
|
+
) -> RunResult<Value> {
|
|
688
|
+
let (suffix_arg, start, end) = parse_bytes_prefix_suffix_args("bytes.endswith", bytes.len(), args, heap, interns)?;
|
|
689
|
+
|
|
690
|
+
let slice = &bytes[start..end];
|
|
691
|
+
let result = match suffix_arg {
|
|
692
|
+
PrefixSuffixArg::Single(suffix_bytes) => slice.ends_with(&suffix_bytes),
|
|
693
|
+
PrefixSuffixArg::Multiple(suffixes) => suffixes.iter().any(|s| slice.ends_with(s)),
|
|
694
|
+
};
|
|
695
|
+
Ok(Value::Bool(result))
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/// Argument type for prefix/suffix matching methods.
|
|
699
|
+
///
|
|
700
|
+
/// Represents either a single bytes value or a tuple of bytes values
|
|
701
|
+
/// for matching in startswith/endswith.
|
|
702
|
+
enum PrefixSuffixArg {
|
|
703
|
+
/// A single bytes value to match
|
|
704
|
+
Single(Vec<u8>),
|
|
705
|
+
/// Multiple bytes values to match (from a tuple)
|
|
706
|
+
Multiple(Vec<Vec<u8>>),
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/// Parses arguments for bytes.startswith/endswith methods.
|
|
710
|
+
///
|
|
711
|
+
/// Returns (prefix/suffix_arg, start, end) where start and end are normalized indices.
|
|
712
|
+
/// The prefix/suffix_arg can be a single bytes value or a tuple of bytes values.
|
|
713
|
+
/// Guarantees `start <= end` to prevent slice panics.
|
|
714
|
+
fn parse_bytes_prefix_suffix_args(
|
|
715
|
+
method: &str,
|
|
716
|
+
len: usize,
|
|
717
|
+
args: ArgValues,
|
|
718
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
719
|
+
interns: &Interns,
|
|
720
|
+
) -> RunResult<(PrefixSuffixArg, usize, usize)> {
|
|
721
|
+
let pos = args.into_pos_only(method, heap)?;
|
|
722
|
+
defer_drop!(pos, heap);
|
|
723
|
+
|
|
724
|
+
let (prefix, start, end) = match pos.as_slice() {
|
|
725
|
+
[prefix_value] => {
|
|
726
|
+
let prefix = extract_bytes_for_prefix_suffix(prefix_value, method, heap, interns)?;
|
|
727
|
+
(prefix, 0, len)
|
|
728
|
+
}
|
|
729
|
+
[prefix_value, start_value] => {
|
|
730
|
+
let prefix = extract_bytes_for_prefix_suffix(prefix_value, method, heap, interns)?;
|
|
731
|
+
let start = normalize_bytes_index(start_value.as_int(heap)?, len);
|
|
732
|
+
(prefix, start, len)
|
|
733
|
+
}
|
|
734
|
+
[prefix_value, start_value, end_value] => {
|
|
735
|
+
let prefix = extract_bytes_for_prefix_suffix(prefix_value, method, heap, interns)?;
|
|
736
|
+
let start = normalize_bytes_index(start_value.as_int(heap)?, len);
|
|
737
|
+
let end = normalize_bytes_index(end_value.as_int(heap)?, len);
|
|
738
|
+
(prefix, start, end)
|
|
739
|
+
}
|
|
740
|
+
[] => return Err(ExcType::type_error_at_least(method, 1, 0)),
|
|
741
|
+
_ => return Err(ExcType::type_error_at_most(method, 3, pos.len())),
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
// Ensure start <= end to prevent slice panics
|
|
745
|
+
Ok((prefix, start, end.max(start)))
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/// Extracts bytes (or tuple of bytes) for startswith/endswith methods.
|
|
749
|
+
///
|
|
750
|
+
/// Returns `PrefixSuffixArg::Single` for a single bytes value, or
|
|
751
|
+
/// `PrefixSuffixArg::Multiple` for a tuple of bytes values.
|
|
752
|
+
fn extract_bytes_for_prefix_suffix(
|
|
753
|
+
value: &Value,
|
|
754
|
+
method: &str,
|
|
755
|
+
heap: &Heap<impl ResourceTracker>,
|
|
756
|
+
interns: &Interns,
|
|
757
|
+
) -> RunResult<PrefixSuffixArg> {
|
|
758
|
+
// Extract the method name (e.g., "startswith" from "bytes.startswith")
|
|
759
|
+
let method_name = method.strip_prefix("bytes.").unwrap_or(method);
|
|
760
|
+
|
|
761
|
+
match value {
|
|
762
|
+
Value::InternBytes(id) => Ok(PrefixSuffixArg::Single(interns.get_bytes(*id).to_vec())),
|
|
763
|
+
Value::InternString(_) => Err(ExcType::type_error(format!(
|
|
764
|
+
"{method_name} first arg must be bytes or a tuple of bytes, not str"
|
|
765
|
+
))),
|
|
766
|
+
Value::Ref(id) => match heap.get(*id) {
|
|
767
|
+
HeapData::Bytes(b) => Ok(PrefixSuffixArg::Single(b.as_slice().to_vec())),
|
|
768
|
+
HeapData::Str(_) => Err(ExcType::type_error(format!(
|
|
769
|
+
"{method_name} first arg must be bytes or a tuple of bytes, not str"
|
|
770
|
+
))),
|
|
771
|
+
HeapData::Tuple(tuple) => {
|
|
772
|
+
// Extract each element as bytes
|
|
773
|
+
let items = tuple.as_slice();
|
|
774
|
+
let mut prefixes = Vec::with_capacity(items.len());
|
|
775
|
+
for (i, item) in items.iter().enumerate() {
|
|
776
|
+
if let Ok(b) = extract_single_bytes_for_prefix_suffix(item, heap, interns) {
|
|
777
|
+
prefixes.push(b);
|
|
778
|
+
} else {
|
|
779
|
+
let item_type = item.py_type(heap);
|
|
780
|
+
return Err(ExcType::type_error(format!(
|
|
781
|
+
"{method_name} first arg must be bytes or a tuple of bytes, \
|
|
782
|
+
not tuple containing {item_type} at index {i}"
|
|
783
|
+
)));
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
Ok(PrefixSuffixArg::Multiple(prefixes))
|
|
787
|
+
}
|
|
788
|
+
_ => Err(ExcType::type_error(format!(
|
|
789
|
+
"{method_name} first arg must be bytes or a tuple of bytes, not {}",
|
|
790
|
+
value.py_type(heap)
|
|
791
|
+
))),
|
|
792
|
+
},
|
|
793
|
+
_ => Err(ExcType::type_error(format!(
|
|
794
|
+
"{method_name} first arg must be bytes or a tuple of bytes, not {}",
|
|
795
|
+
value.py_type(heap)
|
|
796
|
+
))),
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/// Extracts a single bytes value for tuple element in startswith/endswith.
|
|
801
|
+
fn extract_single_bytes_for_prefix_suffix(
|
|
802
|
+
value: &Value,
|
|
803
|
+
heap: &Heap<impl ResourceTracker>,
|
|
804
|
+
interns: &Interns,
|
|
805
|
+
) -> RunResult<Vec<u8>> {
|
|
806
|
+
match value {
|
|
807
|
+
Value::InternBytes(id) => Ok(interns.get_bytes(*id).to_vec()),
|
|
808
|
+
Value::InternString(_) => Err(ExcType::type_error("expected bytes, not str")),
|
|
809
|
+
Value::Ref(id) => match heap.get(*id) {
|
|
810
|
+
HeapData::Bytes(b) => Ok(b.as_slice().to_vec()),
|
|
811
|
+
_ => Err(ExcType::type_error("expected bytes")),
|
|
812
|
+
},
|
|
813
|
+
_ => Err(ExcType::type_error("expected bytes")),
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/// Extracts bytes from a Value (bytes only, NOT str - matches CPython behavior).
|
|
818
|
+
///
|
|
819
|
+
/// CPython raises `TypeError: a bytes-like object is required, not 'str'` when
|
|
820
|
+
/// a str is passed to bytes methods like find, count, index, startswith, endswith.
|
|
821
|
+
fn extract_bytes_only<'a>(
|
|
822
|
+
value: &Value,
|
|
823
|
+
heap: &'a Heap<impl ResourceTracker>,
|
|
824
|
+
interns: &'a Interns,
|
|
825
|
+
) -> RunResult<&'a [u8]> {
|
|
826
|
+
match value {
|
|
827
|
+
Value::InternBytes(id) => Ok(interns.get_bytes(*id)),
|
|
828
|
+
Value::InternString(_) => Err(ExcType::type_error("a bytes-like object is required, not 'str'")),
|
|
829
|
+
Value::Ref(id) => match heap.get(*id) {
|
|
830
|
+
HeapData::Bytes(b) => Ok(b.as_slice()),
|
|
831
|
+
HeapData::Str(_) => Err(ExcType::type_error("a bytes-like object is required, not 'str'")),
|
|
832
|
+
_ => Err(ExcType::type_error("a bytes-like object is required")),
|
|
833
|
+
},
|
|
834
|
+
_ => Err(ExcType::type_error("a bytes-like object is required")),
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/// Parses arguments for bytes.find/count/index methods.
|
|
839
|
+
///
|
|
840
|
+
/// Returns (sub_bytes, start, end) where start and end are normalized indices.
|
|
841
|
+
/// Guarantees `start <= end` to prevent slice panics.
|
|
842
|
+
fn parse_bytes_sub_args(
|
|
843
|
+
method: &str,
|
|
844
|
+
len: usize,
|
|
845
|
+
args: ArgValues,
|
|
846
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
847
|
+
interns: &Interns,
|
|
848
|
+
) -> RunResult<(Vec<u8>, usize, usize)> {
|
|
849
|
+
let pos = args.into_pos_only(method, heap)?;
|
|
850
|
+
defer_drop!(pos, heap);
|
|
851
|
+
|
|
852
|
+
let (sub, start, end) = match pos.as_slice() {
|
|
853
|
+
[sub_value] => {
|
|
854
|
+
let sub = extract_bytes_only(sub_value, heap, interns)?;
|
|
855
|
+
(sub, 0, len)
|
|
856
|
+
}
|
|
857
|
+
[sub_value, start_value] => {
|
|
858
|
+
let sub = extract_bytes_only(sub_value, heap, interns)?;
|
|
859
|
+
let start = normalize_bytes_index(start_value.as_int(heap)?, len);
|
|
860
|
+
(sub, start, len)
|
|
861
|
+
}
|
|
862
|
+
[sub_value, start_value, end_value] => {
|
|
863
|
+
let sub = extract_bytes_only(sub_value, heap, interns)?;
|
|
864
|
+
let start = normalize_bytes_index(start_value.as_int(heap)?, len);
|
|
865
|
+
let end = normalize_bytes_index(end_value.as_int(heap)?, len);
|
|
866
|
+
(sub, start, end)
|
|
867
|
+
}
|
|
868
|
+
[] => return Err(ExcType::type_error_at_least(method, 1, 0)),
|
|
869
|
+
_ => return Err(ExcType::type_error_at_most(method, 3, pos.len())),
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
// Ensure start <= end to prevent slice panics (Python treats start > end as empty slice)
|
|
873
|
+
Ok((sub.to_owned(), start, end.max(start)))
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/// Normalizes a Python-style bytes index to a valid index in range [0, len].
|
|
877
|
+
fn normalize_bytes_index(index: i64, len: usize) -> usize {
|
|
878
|
+
if index < 0 {
|
|
879
|
+
let abs_index = usize::try_from(-index).unwrap_or(usize::MAX);
|
|
880
|
+
len.saturating_sub(abs_index)
|
|
881
|
+
} else {
|
|
882
|
+
usize::try_from(index).unwrap_or(len).min(len)
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// =============================================================================
|
|
887
|
+
// Simple transformations (no arguments)
|
|
888
|
+
// =============================================================================
|
|
889
|
+
|
|
890
|
+
/// Implements Python's `bytes.lower()` method.
|
|
891
|
+
///
|
|
892
|
+
/// Returns a copy of the bytes with all ASCII uppercase characters converted to lowercase.
|
|
893
|
+
fn bytes_lower(bytes: &[u8], heap: &mut Heap<impl ResourceTracker>) -> RunResult<Value> {
|
|
894
|
+
let result: Vec<u8> = bytes.iter().map(|&b| b.to_ascii_lowercase()).collect();
|
|
895
|
+
allocate_bytes(result, heap)
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/// Implements Python's `bytes.upper()` method.
|
|
899
|
+
///
|
|
900
|
+
/// Returns a copy of the bytes with all ASCII lowercase characters converted to uppercase.
|
|
901
|
+
fn bytes_upper(bytes: &[u8], heap: &mut Heap<impl ResourceTracker>) -> RunResult<Value> {
|
|
902
|
+
let result: Vec<u8> = bytes.iter().map(|&b| b.to_ascii_uppercase()).collect();
|
|
903
|
+
allocate_bytes(result, heap)
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/// Implements Python's `bytes.capitalize()` method.
|
|
907
|
+
///
|
|
908
|
+
/// Returns a copy of the bytes with the first byte capitalized (if ASCII) and
|
|
909
|
+
/// the rest lowercased.
|
|
910
|
+
fn bytes_capitalize(bytes: &[u8], heap: &mut Heap<impl ResourceTracker>) -> RunResult<Value> {
|
|
911
|
+
let mut result = Vec::with_capacity(bytes.len());
|
|
912
|
+
if let Some((&first, rest)) = bytes.split_first() {
|
|
913
|
+
result.push(first.to_ascii_uppercase());
|
|
914
|
+
for &b in rest {
|
|
915
|
+
result.push(b.to_ascii_lowercase());
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
allocate_bytes(result, heap)
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/// Implements Python's `bytes.title()` method.
|
|
922
|
+
///
|
|
923
|
+
/// Returns a titlecased version of the bytes where words start with an uppercase
|
|
924
|
+
/// ASCII character and the remaining characters are lowercase.
|
|
925
|
+
fn bytes_title(bytes: &[u8], heap: &mut Heap<impl ResourceTracker>) -> RunResult<Value> {
|
|
926
|
+
let mut result = Vec::with_capacity(bytes.len());
|
|
927
|
+
let mut prev_is_cased = false;
|
|
928
|
+
|
|
929
|
+
for &b in bytes {
|
|
930
|
+
if prev_is_cased {
|
|
931
|
+
result.push(b.to_ascii_lowercase());
|
|
932
|
+
} else {
|
|
933
|
+
result.push(b.to_ascii_uppercase());
|
|
934
|
+
}
|
|
935
|
+
prev_is_cased = b.is_ascii_alphabetic();
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
allocate_bytes(result, heap)
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/// Implements Python's `bytes.swapcase()` method.
|
|
942
|
+
///
|
|
943
|
+
/// Returns a copy of the bytes with ASCII uppercase characters converted to
|
|
944
|
+
/// lowercase and vice versa.
|
|
945
|
+
fn bytes_swapcase(bytes: &[u8], heap: &mut Heap<impl ResourceTracker>) -> RunResult<Value> {
|
|
946
|
+
let result: Vec<u8> = bytes
|
|
947
|
+
.iter()
|
|
948
|
+
.map(|&b| {
|
|
949
|
+
if b.is_ascii_uppercase() {
|
|
950
|
+
b.to_ascii_lowercase()
|
|
951
|
+
} else if b.is_ascii_lowercase() {
|
|
952
|
+
b.to_ascii_uppercase()
|
|
953
|
+
} else {
|
|
954
|
+
b
|
|
955
|
+
}
|
|
956
|
+
})
|
|
957
|
+
.collect();
|
|
958
|
+
allocate_bytes(result, heap)
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// =============================================================================
|
|
962
|
+
// Predicate methods (no arguments, return bool)
|
|
963
|
+
// =============================================================================
|
|
964
|
+
|
|
965
|
+
/// Implements Python's `bytes.isalpha()` method.
|
|
966
|
+
///
|
|
967
|
+
/// Returns True if all bytes in the bytes are ASCII letters and there is at least one byte.
|
|
968
|
+
fn bytes_isalpha(bytes: &[u8]) -> bool {
|
|
969
|
+
!bytes.is_empty() && bytes.iter().all(|&b| b.is_ascii_alphabetic())
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/// Implements Python's `bytes.isdigit()` method.
|
|
973
|
+
///
|
|
974
|
+
/// Returns True if all bytes in the bytes are ASCII digits and there is at least one byte.
|
|
975
|
+
fn bytes_isdigit(bytes: &[u8]) -> bool {
|
|
976
|
+
!bytes.is_empty() && bytes.iter().all(|&b| b.is_ascii_digit())
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/// Implements Python's `bytes.isalnum()` method.
|
|
980
|
+
///
|
|
981
|
+
/// Returns True if all bytes in the bytes are ASCII alphanumeric and there is at least one byte.
|
|
982
|
+
fn bytes_isalnum(bytes: &[u8]) -> bool {
|
|
983
|
+
!bytes.is_empty() && bytes.iter().all(|&b| b.is_ascii_alphanumeric())
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/// Implements Python's `bytes.isspace()` method.
|
|
987
|
+
///
|
|
988
|
+
/// Returns True if all bytes in the bytes are ASCII whitespace and there is at least one byte.
|
|
989
|
+
fn bytes_isspace(bytes: &[u8]) -> bool {
|
|
990
|
+
!bytes.is_empty() && bytes.iter().all(|&b| is_py_whitespace(b))
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/// Implements Python's `bytes.islower()` method.
|
|
994
|
+
///
|
|
995
|
+
/// Returns True if all cased bytes are lowercase and there is at least one cased byte.
|
|
996
|
+
fn bytes_islower(bytes: &[u8]) -> bool {
|
|
997
|
+
let mut has_cased = false;
|
|
998
|
+
for &b in bytes {
|
|
999
|
+
if b.is_ascii_uppercase() {
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
if b.is_ascii_lowercase() {
|
|
1003
|
+
has_cased = true;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
has_cased
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
/// Implements Python's `bytes.isupper()` method.
|
|
1010
|
+
///
|
|
1011
|
+
/// Returns True if all cased bytes are uppercase and there is at least one cased byte.
|
|
1012
|
+
fn bytes_isupper(bytes: &[u8]) -> bool {
|
|
1013
|
+
let mut has_cased = false;
|
|
1014
|
+
for &b in bytes {
|
|
1015
|
+
if b.is_ascii_lowercase() {
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
if b.is_ascii_uppercase() {
|
|
1019
|
+
has_cased = true;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
has_cased
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
/// Implements Python's `bytes.istitle()` method.
|
|
1026
|
+
///
|
|
1027
|
+
/// Returns True if the bytes are titlecased: uppercase characters follow
|
|
1028
|
+
/// uncased characters and lowercase characters follow cased characters.
|
|
1029
|
+
fn bytes_istitle(bytes: &[u8]) -> bool {
|
|
1030
|
+
if bytes.is_empty() {
|
|
1031
|
+
return false;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
let mut prev_cased = false;
|
|
1035
|
+
let mut has_cased = false;
|
|
1036
|
+
|
|
1037
|
+
for &b in bytes {
|
|
1038
|
+
if b.is_ascii_uppercase() {
|
|
1039
|
+
if prev_cased {
|
|
1040
|
+
return false;
|
|
1041
|
+
}
|
|
1042
|
+
prev_cased = true;
|
|
1043
|
+
has_cased = true;
|
|
1044
|
+
} else if b.is_ascii_lowercase() {
|
|
1045
|
+
if !prev_cased {
|
|
1046
|
+
return false;
|
|
1047
|
+
}
|
|
1048
|
+
prev_cased = true;
|
|
1049
|
+
has_cased = true;
|
|
1050
|
+
} else {
|
|
1051
|
+
prev_cased = false;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
has_cased
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// =============================================================================
|
|
1059
|
+
// Search methods
|
|
1060
|
+
// =============================================================================
|
|
1061
|
+
|
|
1062
|
+
/// Implements Python's `bytes.rfind(sub[, start[, end]])` method.
|
|
1063
|
+
///
|
|
1064
|
+
/// Returns the highest index where the subsequence is found, or -1 if not found.
|
|
1065
|
+
fn bytes_rfind(
|
|
1066
|
+
bytes: &[u8],
|
|
1067
|
+
args: ArgValues,
|
|
1068
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1069
|
+
interns: &Interns,
|
|
1070
|
+
) -> RunResult<Value> {
|
|
1071
|
+
let (sub, start, end) = parse_bytes_sub_args("bytes.rfind", bytes.len(), args, heap, interns)?;
|
|
1072
|
+
|
|
1073
|
+
let slice = &bytes[start..end];
|
|
1074
|
+
let result = if sub.is_empty() {
|
|
1075
|
+
// Empty subsequence: always found at end position
|
|
1076
|
+
Some(slice.len())
|
|
1077
|
+
} else {
|
|
1078
|
+
rfind_subsequence(slice, &sub)
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
let idx = match result {
|
|
1082
|
+
Some(i) => i64::try_from(start + i).expect("index exceeds i64::MAX"),
|
|
1083
|
+
None => -1,
|
|
1084
|
+
};
|
|
1085
|
+
Ok(Value::Int(idx))
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/// Finds the last occurrence of needle in haystack.
|
|
1089
|
+
fn rfind_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
|
|
1090
|
+
if needle.len() > haystack.len() {
|
|
1091
|
+
return None;
|
|
1092
|
+
}
|
|
1093
|
+
haystack.windows(needle.len()).rposition(|window| window == needle)
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/// Implements Python's `bytes.rindex(sub[, start[, end]])` method.
|
|
1097
|
+
///
|
|
1098
|
+
/// Like rfind(), but raises ValueError if the subsequence is not found.
|
|
1099
|
+
fn bytes_rindex(
|
|
1100
|
+
bytes: &[u8],
|
|
1101
|
+
args: ArgValues,
|
|
1102
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1103
|
+
interns: &Interns,
|
|
1104
|
+
) -> RunResult<Value> {
|
|
1105
|
+
let (sub, start, end) = parse_bytes_sub_args("bytes.rindex", bytes.len(), args, heap, interns)?;
|
|
1106
|
+
|
|
1107
|
+
let slice = &bytes[start..end];
|
|
1108
|
+
let result = if sub.is_empty() {
|
|
1109
|
+
Some(slice.len())
|
|
1110
|
+
} else {
|
|
1111
|
+
rfind_subsequence(slice, &sub)
|
|
1112
|
+
};
|
|
1113
|
+
|
|
1114
|
+
match result {
|
|
1115
|
+
Some(i) => {
|
|
1116
|
+
let idx = i64::try_from(start + i).expect("index exceeds i64::MAX");
|
|
1117
|
+
Ok(Value::Int(idx))
|
|
1118
|
+
}
|
|
1119
|
+
None => Err(ExcType::value_error_subsequence_not_found()),
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// =============================================================================
|
|
1124
|
+
// Strip/trim methods
|
|
1125
|
+
// =============================================================================
|
|
1126
|
+
|
|
1127
|
+
/// Implements Python's `bytes.strip([chars])` method.
|
|
1128
|
+
///
|
|
1129
|
+
/// Returns a copy of the bytes with leading and trailing bytes removed.
|
|
1130
|
+
/// If chars is not specified, ASCII whitespace bytes are removed.
|
|
1131
|
+
fn bytes_strip(
|
|
1132
|
+
bytes: &[u8],
|
|
1133
|
+
args: ArgValues,
|
|
1134
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1135
|
+
interns: &Interns,
|
|
1136
|
+
) -> RunResult<Value> {
|
|
1137
|
+
let value = args.get_zero_one_arg("bytes.strip", heap)?;
|
|
1138
|
+
defer_drop!(value, heap);
|
|
1139
|
+
let result = match value {
|
|
1140
|
+
None | Some(Value::None) => bytes_strip_whitespace_both(bytes),
|
|
1141
|
+
Some(v) => bytes_strip_both(bytes, extract_bytes_only(v, heap, interns)?),
|
|
1142
|
+
};
|
|
1143
|
+
allocate_bytes(result.to_vec(), heap)
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
/// Implements Python's `bytes.lstrip([chars])` method.
|
|
1147
|
+
///
|
|
1148
|
+
/// Returns a copy of the bytes with leading bytes removed.
|
|
1149
|
+
fn bytes_lstrip(
|
|
1150
|
+
bytes: &[u8],
|
|
1151
|
+
args: ArgValues,
|
|
1152
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1153
|
+
interns: &Interns,
|
|
1154
|
+
) -> RunResult<Value> {
|
|
1155
|
+
let value = args.get_zero_one_arg("bytes.lstrip", heap)?;
|
|
1156
|
+
defer_drop!(value, heap);
|
|
1157
|
+
let result = match value {
|
|
1158
|
+
None | Some(Value::None) => bytes_strip_whitespace_start(bytes),
|
|
1159
|
+
Some(v) => bytes_strip_start(bytes, extract_bytes_only(v, heap, interns)?),
|
|
1160
|
+
};
|
|
1161
|
+
allocate_bytes(result.to_vec(), heap)
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/// Implements Python's `bytes.rstrip([chars])` method.
|
|
1165
|
+
///
|
|
1166
|
+
/// Returns a copy of the bytes with trailing bytes removed.
|
|
1167
|
+
fn bytes_rstrip(
|
|
1168
|
+
bytes: &[u8],
|
|
1169
|
+
args: ArgValues,
|
|
1170
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1171
|
+
interns: &Interns,
|
|
1172
|
+
) -> RunResult<Value> {
|
|
1173
|
+
let value = args.get_zero_one_arg("bytes.rstrip", heap)?;
|
|
1174
|
+
defer_drop!(value, heap);
|
|
1175
|
+
let result = match value {
|
|
1176
|
+
None | Some(Value::None) => bytes_strip_whitespace_end(bytes),
|
|
1177
|
+
Some(v) => bytes_strip_end(bytes, extract_bytes_only(v, heap, interns)?),
|
|
1178
|
+
};
|
|
1179
|
+
allocate_bytes(result.to_vec(), heap)
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
/// Strips bytes in `chars` from both ends of the byte slice.
|
|
1183
|
+
fn bytes_strip_both<'a>(bytes: &'a [u8], chars: &[u8]) -> &'a [u8] {
|
|
1184
|
+
let start = bytes.iter().position(|b| !chars.contains(b)).unwrap_or(bytes.len());
|
|
1185
|
+
let end = bytes
|
|
1186
|
+
.iter()
|
|
1187
|
+
.rposition(|b| !chars.contains(b))
|
|
1188
|
+
.map_or(start, |pos| pos + 1);
|
|
1189
|
+
&bytes[start..end]
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/// Strips bytes in `chars` from the start of the byte slice.
|
|
1193
|
+
fn bytes_strip_start<'a>(bytes: &'a [u8], chars: &[u8]) -> &'a [u8] {
|
|
1194
|
+
let start = bytes.iter().position(|b| !chars.contains(b)).unwrap_or(bytes.len());
|
|
1195
|
+
&bytes[start..]
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
/// Strips bytes in `chars` from the end of the byte slice.
|
|
1199
|
+
fn bytes_strip_end<'a>(bytes: &'a [u8], chars: &[u8]) -> &'a [u8] {
|
|
1200
|
+
let end = bytes.iter().rposition(|b| !chars.contains(b)).map_or(0, |pos| pos + 1);
|
|
1201
|
+
&bytes[..end]
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
/// Strips ASCII whitespace from both ends of the byte slice.
|
|
1205
|
+
fn bytes_strip_whitespace_both(bytes: &[u8]) -> &[u8] {
|
|
1206
|
+
let start = bytes.iter().position(|b| !is_py_whitespace(*b)).unwrap_or(bytes.len());
|
|
1207
|
+
let end = bytes
|
|
1208
|
+
.iter()
|
|
1209
|
+
.rposition(|b| !is_py_whitespace(*b))
|
|
1210
|
+
.map_or(start, |pos| pos + 1);
|
|
1211
|
+
&bytes[start..end]
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/// Strips ASCII whitespace from the start of the byte slice.
|
|
1215
|
+
fn bytes_strip_whitespace_start(bytes: &[u8]) -> &[u8] {
|
|
1216
|
+
let start = bytes.iter().position(|b| !is_py_whitespace(*b)).unwrap_or(bytes.len());
|
|
1217
|
+
&bytes[start..]
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/// Strips ASCII whitespace from the end of the byte slice.
|
|
1221
|
+
fn bytes_strip_whitespace_end(bytes: &[u8]) -> &[u8] {
|
|
1222
|
+
let end = bytes
|
|
1223
|
+
.iter()
|
|
1224
|
+
.rposition(|b| !is_py_whitespace(*b))
|
|
1225
|
+
.map_or(0, |pos| pos + 1);
|
|
1226
|
+
&bytes[..end]
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
/// Implements Python's `bytes.removeprefix(prefix)` method.
|
|
1230
|
+
///
|
|
1231
|
+
/// If the bytes start with the prefix, return bytes[len(prefix):].
|
|
1232
|
+
/// Otherwise, return a copy of the original bytes.
|
|
1233
|
+
fn bytes_removeprefix(
|
|
1234
|
+
bytes: &[u8],
|
|
1235
|
+
args: ArgValues,
|
|
1236
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1237
|
+
interns: &Interns,
|
|
1238
|
+
) -> RunResult<Value> {
|
|
1239
|
+
let prefix_value = args.get_one_arg("bytes.removeprefix", heap)?;
|
|
1240
|
+
defer_drop!(prefix_value, heap);
|
|
1241
|
+
let prefix = extract_bytes_only(prefix_value, heap, interns)?;
|
|
1242
|
+
|
|
1243
|
+
let result = if bytes.starts_with(prefix) {
|
|
1244
|
+
bytes[prefix.len()..].to_vec()
|
|
1245
|
+
} else {
|
|
1246
|
+
bytes.to_vec()
|
|
1247
|
+
};
|
|
1248
|
+
allocate_bytes(result, heap)
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
/// Implements Python's `bytes.removesuffix(suffix)` method.
|
|
1252
|
+
///
|
|
1253
|
+
/// If the bytes end with the suffix, return bytes[:-len(suffix)].
|
|
1254
|
+
/// Otherwise, return a copy of the original bytes.
|
|
1255
|
+
fn bytes_removesuffix(
|
|
1256
|
+
bytes: &[u8],
|
|
1257
|
+
args: ArgValues,
|
|
1258
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1259
|
+
interns: &Interns,
|
|
1260
|
+
) -> RunResult<Value> {
|
|
1261
|
+
let suffix_value = args.get_one_arg("bytes.removesuffix", heap)?;
|
|
1262
|
+
defer_drop!(suffix_value, heap);
|
|
1263
|
+
let suffix = extract_bytes_only(suffix_value, heap, interns)?;
|
|
1264
|
+
|
|
1265
|
+
let result = if bytes.ends_with(suffix) && !suffix.is_empty() {
|
|
1266
|
+
bytes[..bytes.len() - suffix.len()].to_vec()
|
|
1267
|
+
} else {
|
|
1268
|
+
bytes.to_vec()
|
|
1269
|
+
};
|
|
1270
|
+
allocate_bytes(result, heap)
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// =============================================================================
|
|
1274
|
+
// Split methods
|
|
1275
|
+
// =============================================================================
|
|
1276
|
+
|
|
1277
|
+
/// Implements Python's `bytes.split([sep[, maxsplit]])` method.
|
|
1278
|
+
///
|
|
1279
|
+
/// Returns a list of the bytes split by the separator.
|
|
1280
|
+
fn bytes_split(
|
|
1281
|
+
bytes: &[u8],
|
|
1282
|
+
args: ArgValues,
|
|
1283
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1284
|
+
interns: &Interns,
|
|
1285
|
+
) -> RunResult<Value> {
|
|
1286
|
+
let (sep, maxsplit) = parse_bytes_split_args("bytes.split", args, heap, interns)?;
|
|
1287
|
+
|
|
1288
|
+
let parts: Vec<&[u8]> = match &sep {
|
|
1289
|
+
Some(sep) => {
|
|
1290
|
+
if sep.is_empty() {
|
|
1291
|
+
return Err(ExcType::value_error_empty_separator());
|
|
1292
|
+
}
|
|
1293
|
+
if maxsplit < 0 {
|
|
1294
|
+
bytes_split_by_seq(bytes, sep)
|
|
1295
|
+
} else {
|
|
1296
|
+
let max = usize::try_from(maxsplit).unwrap_or(usize::MAX);
|
|
1297
|
+
bytes_splitn_by_seq(bytes, sep, max + 1)
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
None => {
|
|
1301
|
+
if maxsplit < 0 {
|
|
1302
|
+
bytes_split_whitespace(bytes)
|
|
1303
|
+
} else {
|
|
1304
|
+
let max = usize::try_from(maxsplit).unwrap_or(usize::MAX);
|
|
1305
|
+
bytes_splitn_whitespace(bytes, max)
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
let mut list_items = Vec::with_capacity(parts.len());
|
|
1311
|
+
for part in parts {
|
|
1312
|
+
heap.check_time()?;
|
|
1313
|
+
list_items.push(allocate_bytes(part.to_vec(), heap)?);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
let list = List::new(list_items);
|
|
1317
|
+
let heap_id = heap.allocate(HeapData::List(list))?;
|
|
1318
|
+
Ok(Value::Ref(heap_id))
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
/// Implements Python's `bytes.rsplit([sep[, maxsplit]])` method.
|
|
1322
|
+
///
|
|
1323
|
+
/// Returns a list of the bytes split by the separator, splitting from the right.
|
|
1324
|
+
fn bytes_rsplit(
|
|
1325
|
+
bytes: &[u8],
|
|
1326
|
+
args: ArgValues,
|
|
1327
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1328
|
+
interns: &Interns,
|
|
1329
|
+
) -> RunResult<Value> {
|
|
1330
|
+
let (sep, maxsplit) = parse_bytes_split_args("bytes.rsplit", args, heap, interns)?;
|
|
1331
|
+
|
|
1332
|
+
let parts: Vec<&[u8]> = match &sep {
|
|
1333
|
+
Some(sep) => {
|
|
1334
|
+
if sep.is_empty() {
|
|
1335
|
+
return Err(ExcType::value_error_empty_separator());
|
|
1336
|
+
}
|
|
1337
|
+
if maxsplit < 0 {
|
|
1338
|
+
bytes_split_by_seq(bytes, sep)
|
|
1339
|
+
} else {
|
|
1340
|
+
let max = usize::try_from(maxsplit).unwrap_or(usize::MAX);
|
|
1341
|
+
bytes_rsplitn_by_seq(bytes, sep, max + 1)
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
None => {
|
|
1345
|
+
if maxsplit < 0 {
|
|
1346
|
+
bytes_split_whitespace(bytes)
|
|
1347
|
+
} else {
|
|
1348
|
+
let max = usize::try_from(maxsplit).unwrap_or(usize::MAX);
|
|
1349
|
+
bytes_rsplitn_whitespace(bytes, max)
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
|
|
1354
|
+
let mut list_items = Vec::with_capacity(parts.len());
|
|
1355
|
+
for part in parts {
|
|
1356
|
+
heap.check_time()?;
|
|
1357
|
+
list_items.push(allocate_bytes(part.to_vec(), heap)?);
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
let list = List::new(list_items);
|
|
1361
|
+
let heap_id = heap.allocate(HeapData::List(list))?;
|
|
1362
|
+
Ok(Value::Ref(heap_id))
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/// Parses arguments for bytes split methods.
|
|
1366
|
+
fn parse_bytes_split_args(
|
|
1367
|
+
method: &str,
|
|
1368
|
+
args: ArgValues,
|
|
1369
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1370
|
+
interns: &Interns,
|
|
1371
|
+
) -> RunResult<(Option<Vec<u8>>, i64)> {
|
|
1372
|
+
let (pos_iter, kwargs) = args.into_parts();
|
|
1373
|
+
defer_drop_mut!(pos_iter, heap);
|
|
1374
|
+
let kwargs_iter = kwargs.into_iter();
|
|
1375
|
+
defer_drop_mut!(kwargs_iter, heap);
|
|
1376
|
+
|
|
1377
|
+
let sep_value = pos_iter.next();
|
|
1378
|
+
defer_drop_mut!(sep_value, heap);
|
|
1379
|
+
let maxsplit_value = pos_iter.next();
|
|
1380
|
+
defer_drop_mut!(maxsplit_value, heap);
|
|
1381
|
+
|
|
1382
|
+
// Check no extra positional arguments
|
|
1383
|
+
if pos_iter.len() != 0 {
|
|
1384
|
+
return Err(ExcType::type_error_at_most(method, 2, 3));
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// Process keyword arguments
|
|
1388
|
+
for (key, value) in kwargs_iter {
|
|
1389
|
+
defer_drop!(key, heap);
|
|
1390
|
+
let mut value_guard = HeapGuard::new(value, heap);
|
|
1391
|
+
|
|
1392
|
+
let Some(keyword_name) = key.as_either_str(value_guard.heap()) else {
|
|
1393
|
+
return Err(ExcType::type_error("keywords must be strings"));
|
|
1394
|
+
};
|
|
1395
|
+
|
|
1396
|
+
let key_str = keyword_name.as_str(interns);
|
|
1397
|
+
match key_str {
|
|
1398
|
+
"sep" => {
|
|
1399
|
+
if let Some(previous_value) = sep_value.replace(value_guard.into_inner()) {
|
|
1400
|
+
previous_value.drop_with_heap(heap);
|
|
1401
|
+
return Err(ExcType::type_error(format!(
|
|
1402
|
+
"{method}() got multiple values for argument 'sep'"
|
|
1403
|
+
)));
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
"maxsplit" => {
|
|
1407
|
+
if let Some(previous_value) = maxsplit_value.replace(value_guard.into_inner()) {
|
|
1408
|
+
previous_value.drop_with_heap(heap);
|
|
1409
|
+
return Err(ExcType::type_error(format!(
|
|
1410
|
+
"{method}() got multiple values for argument 'maxsplit'"
|
|
1411
|
+
)));
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
_ => {
|
|
1415
|
+
return Err(ExcType::type_error(format!(
|
|
1416
|
+
"'{key_str}' is an invalid keyword argument for {method}()"
|
|
1417
|
+
)));
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// Extract sep (default None)
|
|
1423
|
+
let sep = if let Some(v) = sep_value {
|
|
1424
|
+
if matches!(v, Value::None) {
|
|
1425
|
+
None
|
|
1426
|
+
} else {
|
|
1427
|
+
Some(extract_bytes_only(v, heap, interns)?.to_owned())
|
|
1428
|
+
}
|
|
1429
|
+
} else {
|
|
1430
|
+
None
|
|
1431
|
+
};
|
|
1432
|
+
|
|
1433
|
+
// Extract maxsplit (default -1)
|
|
1434
|
+
let maxsplit = if let Some(v) = maxsplit_value {
|
|
1435
|
+
v.as_int(heap)?
|
|
1436
|
+
} else {
|
|
1437
|
+
-1
|
|
1438
|
+
};
|
|
1439
|
+
|
|
1440
|
+
Ok((sep, maxsplit))
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
/// Splits bytes by a separator sequence.
|
|
1444
|
+
fn bytes_split_by_seq<'a>(bytes: &'a [u8], sep: &[u8]) -> Vec<&'a [u8]> {
|
|
1445
|
+
let mut parts = Vec::new();
|
|
1446
|
+
let mut start = 0;
|
|
1447
|
+
|
|
1448
|
+
while let Some(pos) = find_subsequence(&bytes[start..], sep) {
|
|
1449
|
+
parts.push(&bytes[start..start + pos]);
|
|
1450
|
+
start = start + pos + sep.len();
|
|
1451
|
+
}
|
|
1452
|
+
parts.push(&bytes[start..]);
|
|
1453
|
+
|
|
1454
|
+
parts
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
/// Splits bytes by a separator sequence, returning at most n parts.
|
|
1458
|
+
fn bytes_splitn_by_seq<'a>(bytes: &'a [u8], sep: &[u8], n: usize) -> Vec<&'a [u8]> {
|
|
1459
|
+
let mut parts = Vec::new();
|
|
1460
|
+
let mut start = 0;
|
|
1461
|
+
let mut count = 0;
|
|
1462
|
+
|
|
1463
|
+
while count + 1 < n {
|
|
1464
|
+
if let Some(pos) = find_subsequence(&bytes[start..], sep) {
|
|
1465
|
+
parts.push(&bytes[start..start + pos]);
|
|
1466
|
+
start = start + pos + sep.len();
|
|
1467
|
+
count += 1;
|
|
1468
|
+
} else {
|
|
1469
|
+
break;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
parts.push(&bytes[start..]);
|
|
1473
|
+
|
|
1474
|
+
parts
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
/// Splits bytes by a separator sequence from the right, returning at most n parts.
|
|
1478
|
+
fn bytes_rsplitn_by_seq<'a>(bytes: &'a [u8], sep: &[u8], n: usize) -> Vec<&'a [u8]> {
|
|
1479
|
+
let mut parts = Vec::new();
|
|
1480
|
+
let mut end = bytes.len();
|
|
1481
|
+
let mut count = 0;
|
|
1482
|
+
|
|
1483
|
+
while count + 1 < n {
|
|
1484
|
+
if let Some(pos) = rfind_subsequence(&bytes[..end], sep) {
|
|
1485
|
+
parts.push(&bytes[pos + sep.len()..end]);
|
|
1486
|
+
end = pos;
|
|
1487
|
+
count += 1;
|
|
1488
|
+
} else {
|
|
1489
|
+
break;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
parts.push(&bytes[..end]);
|
|
1493
|
+
parts.reverse();
|
|
1494
|
+
|
|
1495
|
+
parts
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
/// Splits bytes by ASCII whitespace, filtering empty parts.
|
|
1499
|
+
fn bytes_split_whitespace(bytes: &[u8]) -> Vec<&[u8]> {
|
|
1500
|
+
let mut parts = Vec::new();
|
|
1501
|
+
let mut start = None;
|
|
1502
|
+
|
|
1503
|
+
for (i, &b) in bytes.iter().enumerate() {
|
|
1504
|
+
if is_py_whitespace(b) {
|
|
1505
|
+
if let Some(s) = start {
|
|
1506
|
+
parts.push(&bytes[s..i]);
|
|
1507
|
+
start = None;
|
|
1508
|
+
}
|
|
1509
|
+
} else if start.is_none() {
|
|
1510
|
+
start = Some(i);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
if let Some(s) = start {
|
|
1515
|
+
parts.push(&bytes[s..]);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
parts
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
/// Splits bytes by ASCII whitespace, returning at most maxsplit+1 parts.
|
|
1522
|
+
fn bytes_splitn_whitespace(bytes: &[u8], maxsplit: usize) -> Vec<&[u8]> {
|
|
1523
|
+
let mut parts = Vec::new();
|
|
1524
|
+
let mut start = None;
|
|
1525
|
+
let mut count = 0;
|
|
1526
|
+
|
|
1527
|
+
let trimmed = bytes_strip_whitespace_start(bytes);
|
|
1528
|
+
let offset = bytes.len() - trimmed.len();
|
|
1529
|
+
|
|
1530
|
+
for (i, &b) in trimmed.iter().enumerate() {
|
|
1531
|
+
if is_py_whitespace(b) {
|
|
1532
|
+
if let Some(s) = start
|
|
1533
|
+
&& count < maxsplit
|
|
1534
|
+
{
|
|
1535
|
+
parts.push(&bytes[offset + s..offset + i]);
|
|
1536
|
+
count += 1;
|
|
1537
|
+
start = None;
|
|
1538
|
+
}
|
|
1539
|
+
} else if start.is_none() {
|
|
1540
|
+
start = Some(i);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
if let Some(s) = start {
|
|
1545
|
+
parts.push(&bytes[offset + s..]);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
parts
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/// Splits bytes by ASCII whitespace from the right, returning at most maxsplit+1 parts.
|
|
1552
|
+
fn bytes_rsplitn_whitespace(bytes: &[u8], maxsplit: usize) -> Vec<&[u8]> {
|
|
1553
|
+
let mut parts = Vec::new();
|
|
1554
|
+
let mut end = None;
|
|
1555
|
+
let mut count = 0;
|
|
1556
|
+
|
|
1557
|
+
let trimmed = bytes_strip_whitespace_end(bytes);
|
|
1558
|
+
|
|
1559
|
+
for i in (0..trimmed.len()).rev() {
|
|
1560
|
+
let b = trimmed[i];
|
|
1561
|
+
if is_py_whitespace(b) {
|
|
1562
|
+
if let Some(e) = end
|
|
1563
|
+
&& count < maxsplit
|
|
1564
|
+
{
|
|
1565
|
+
parts.push(&trimmed[i + 1..e]);
|
|
1566
|
+
count += 1;
|
|
1567
|
+
end = None;
|
|
1568
|
+
}
|
|
1569
|
+
} else if end.is_none() {
|
|
1570
|
+
end = Some(i + 1);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
if let Some(e) = end {
|
|
1575
|
+
parts.push(&trimmed[..e]);
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
parts.reverse();
|
|
1579
|
+
parts
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
/// Implements Python's `bytes.splitlines([keepends])` method.
|
|
1583
|
+
///
|
|
1584
|
+
/// Returns a list of the lines in the bytes, breaking at line boundaries.
|
|
1585
|
+
fn bytes_splitlines(bytes: &[u8], args: ArgValues, vm: &mut VM<'_, '_, impl ResourceTracker>) -> RunResult<Value> {
|
|
1586
|
+
let keepends = parse_bytes_splitlines_args(args, vm)?;
|
|
1587
|
+
|
|
1588
|
+
let mut lines = Vec::new();
|
|
1589
|
+
let mut start = 0;
|
|
1590
|
+
let len = bytes.len();
|
|
1591
|
+
|
|
1592
|
+
while start < len {
|
|
1593
|
+
vm.heap.check_time()?;
|
|
1594
|
+
|
|
1595
|
+
let mut end = start;
|
|
1596
|
+
let mut line_end = start;
|
|
1597
|
+
|
|
1598
|
+
while end < len {
|
|
1599
|
+
match bytes[end] {
|
|
1600
|
+
b'\n' => {
|
|
1601
|
+
line_end = end;
|
|
1602
|
+
end += 1;
|
|
1603
|
+
break;
|
|
1604
|
+
}
|
|
1605
|
+
b'\r' => {
|
|
1606
|
+
line_end = end;
|
|
1607
|
+
end += 1;
|
|
1608
|
+
if end < len && bytes[end] == b'\n' {
|
|
1609
|
+
end += 1;
|
|
1610
|
+
}
|
|
1611
|
+
break;
|
|
1612
|
+
}
|
|
1613
|
+
_ => {
|
|
1614
|
+
end += 1;
|
|
1615
|
+
line_end = end;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
let line = if keepends {
|
|
1621
|
+
&bytes[start..end]
|
|
1622
|
+
} else {
|
|
1623
|
+
&bytes[start..line_end]
|
|
1624
|
+
};
|
|
1625
|
+
lines.push(allocate_bytes(line.to_vec(), vm.heap)?);
|
|
1626
|
+
start = end;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
let list = List::new(lines);
|
|
1630
|
+
let heap_id = vm.heap.allocate(HeapData::List(list))?;
|
|
1631
|
+
Ok(Value::Ref(heap_id))
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
/// Parses arguments for bytes.splitlines method.
|
|
1635
|
+
fn parse_bytes_splitlines_args(args: ArgValues, vm: &mut VM<'_, '_, impl ResourceTracker>) -> RunResult<bool> {
|
|
1636
|
+
let (pos_iter, kwargs) = args.into_parts();
|
|
1637
|
+
defer_drop_mut!(pos_iter, vm);
|
|
1638
|
+
let kwargs = kwargs.into_iter();
|
|
1639
|
+
defer_drop_mut!(kwargs, vm);
|
|
1640
|
+
|
|
1641
|
+
let keepends_value = pos_iter.next();
|
|
1642
|
+
defer_drop_mut!(keepends_value, vm);
|
|
1643
|
+
|
|
1644
|
+
// Check no extra positional arguments
|
|
1645
|
+
if pos_iter.len() != 0 {
|
|
1646
|
+
return Err(ExcType::type_error_at_most("bytes.splitlines", 1, 2));
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// Process kwargs
|
|
1650
|
+
for (key, value) in kwargs {
|
|
1651
|
+
defer_drop!(key, vm);
|
|
1652
|
+
let mut value_guard = HeapGuard::new(value, vm);
|
|
1653
|
+
|
|
1654
|
+
let Some(keyword_name) = key.as_either_str(value_guard.heap().heap) else {
|
|
1655
|
+
return Err(ExcType::type_error("keywords must be strings"));
|
|
1656
|
+
};
|
|
1657
|
+
|
|
1658
|
+
let key_str = keyword_name.as_str(value_guard.heap().interns);
|
|
1659
|
+
if key_str == "keepends" {
|
|
1660
|
+
if let Some(previous_value) = keepends_value.replace(value_guard.into_inner()) {
|
|
1661
|
+
previous_value.drop_with_heap(vm);
|
|
1662
|
+
return Err(ExcType::type_error(
|
|
1663
|
+
"bytes.splitlines() got multiple values for argument 'keepends'",
|
|
1664
|
+
));
|
|
1665
|
+
}
|
|
1666
|
+
} else {
|
|
1667
|
+
return Err(ExcType::type_error(format!(
|
|
1668
|
+
"'{key_str}' is an invalid keyword argument for bytes.splitlines()"
|
|
1669
|
+
)));
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
// Extract keepends (default false)
|
|
1674
|
+
let keepends = if let Some(v) = keepends_value {
|
|
1675
|
+
v.py_bool(vm)
|
|
1676
|
+
} else {
|
|
1677
|
+
false
|
|
1678
|
+
};
|
|
1679
|
+
|
|
1680
|
+
Ok(keepends)
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
/// Implements Python's `bytes.partition(sep)` method.
|
|
1684
|
+
///
|
|
1685
|
+
/// Splits the bytes at the first occurrence of sep, and returns a 3-tuple.
|
|
1686
|
+
fn bytes_partition(
|
|
1687
|
+
bytes: &[u8],
|
|
1688
|
+
args: ArgValues,
|
|
1689
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1690
|
+
interns: &Interns,
|
|
1691
|
+
) -> RunResult<Value> {
|
|
1692
|
+
let sep_value = args.get_one_arg("bytes.partition", heap)?;
|
|
1693
|
+
defer_drop!(sep_value, heap);
|
|
1694
|
+
let sep = extract_bytes_only(sep_value, heap, interns)?;
|
|
1695
|
+
|
|
1696
|
+
if sep.is_empty() {
|
|
1697
|
+
return Err(ExcType::value_error_empty_separator());
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
let (before, sep_found, after) = match find_subsequence(bytes, sep) {
|
|
1701
|
+
Some(pos) => (bytes[..pos].to_vec(), sep.to_vec(), bytes[pos + sep.len()..].to_vec()),
|
|
1702
|
+
None => (bytes.to_vec(), Vec::new(), Vec::new()),
|
|
1703
|
+
};
|
|
1704
|
+
|
|
1705
|
+
let before_val = allocate_bytes(before, heap)?;
|
|
1706
|
+
let sep_val = allocate_bytes(sep_found, heap)?;
|
|
1707
|
+
let after_val = allocate_bytes(after, heap)?;
|
|
1708
|
+
|
|
1709
|
+
Ok(crate::types::allocate_tuple(
|
|
1710
|
+
smallvec![before_val, sep_val, after_val],
|
|
1711
|
+
heap,
|
|
1712
|
+
)?)
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
/// Implements Python's `bytes.rpartition(sep)` method.
|
|
1716
|
+
///
|
|
1717
|
+
/// Splits the bytes at the last occurrence of sep, and returns a 3-tuple.
|
|
1718
|
+
fn bytes_rpartition(
|
|
1719
|
+
bytes: &[u8],
|
|
1720
|
+
args: ArgValues,
|
|
1721
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1722
|
+
interns: &Interns,
|
|
1723
|
+
) -> RunResult<Value> {
|
|
1724
|
+
let sep_value = args.get_one_arg("bytes.rpartition", heap)?;
|
|
1725
|
+
defer_drop!(sep_value, heap);
|
|
1726
|
+
let sep = extract_bytes_only(sep_value, heap, interns)?;
|
|
1727
|
+
|
|
1728
|
+
if sep.is_empty() {
|
|
1729
|
+
return Err(ExcType::value_error_empty_separator());
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
let (before, sep_found, after) = match rfind_subsequence(bytes, sep) {
|
|
1733
|
+
Some(pos) => (bytes[..pos].to_vec(), sep.to_vec(), bytes[pos + sep.len()..].to_vec()),
|
|
1734
|
+
None => (Vec::new(), Vec::new(), bytes.to_vec()),
|
|
1735
|
+
};
|
|
1736
|
+
|
|
1737
|
+
let before_val = allocate_bytes(before, heap)?;
|
|
1738
|
+
let sep_val = allocate_bytes(sep_found, heap)?;
|
|
1739
|
+
let after_val = allocate_bytes(after, heap)?;
|
|
1740
|
+
|
|
1741
|
+
Ok(crate::types::allocate_tuple(
|
|
1742
|
+
smallvec![before_val, sep_val, after_val],
|
|
1743
|
+
heap,
|
|
1744
|
+
)?)
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
// =============================================================================
|
|
1748
|
+
// Replace/padding methods
|
|
1749
|
+
// =============================================================================
|
|
1750
|
+
|
|
1751
|
+
/// Implements Python's `bytes.replace(old, new[, count])` method.
|
|
1752
|
+
///
|
|
1753
|
+
/// Returns a copy with all occurrences of old replaced by new.
|
|
1754
|
+
fn bytes_replace(
|
|
1755
|
+
bytes: &[u8],
|
|
1756
|
+
args: ArgValues,
|
|
1757
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1758
|
+
interns: &Interns,
|
|
1759
|
+
) -> RunResult<Value> {
|
|
1760
|
+
let (old, new, count) = parse_bytes_replace_args("bytes.replace", args, heap, interns)?;
|
|
1761
|
+
|
|
1762
|
+
check_replace_size(bytes.len(), old.len(), new.len(), count, heap.tracker())?;
|
|
1763
|
+
|
|
1764
|
+
let result = if count < 0 {
|
|
1765
|
+
bytes_replace_all(bytes, &old, &new, heap)?
|
|
1766
|
+
} else {
|
|
1767
|
+
let n = usize::try_from(count).unwrap_or(usize::MAX);
|
|
1768
|
+
bytes_replace_n(bytes, &old, &new, n, heap)?
|
|
1769
|
+
};
|
|
1770
|
+
|
|
1771
|
+
allocate_bytes(result, heap)
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
/// Parses arguments for bytes.replace method.
|
|
1775
|
+
fn parse_bytes_replace_args(
|
|
1776
|
+
method: &str,
|
|
1777
|
+
args: ArgValues,
|
|
1778
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1779
|
+
interns: &Interns,
|
|
1780
|
+
) -> RunResult<(Vec<u8>, Vec<u8>, i64)> {
|
|
1781
|
+
let (pos_iter, kwargs) = args.into_parts();
|
|
1782
|
+
defer_drop_mut!(pos_iter, heap);
|
|
1783
|
+
let kwargs_iter = kwargs.into_iter();
|
|
1784
|
+
defer_drop_mut!(kwargs_iter, heap);
|
|
1785
|
+
|
|
1786
|
+
let Some(old_value) = pos_iter.next() else {
|
|
1787
|
+
return Err(ExcType::type_error_at_least(method, 2, 0));
|
|
1788
|
+
};
|
|
1789
|
+
defer_drop!(old_value, heap);
|
|
1790
|
+
|
|
1791
|
+
let Some(new_value) = pos_iter.next() else {
|
|
1792
|
+
return Err(ExcType::type_error_at_least(method, 2, 1));
|
|
1793
|
+
};
|
|
1794
|
+
defer_drop!(new_value, heap);
|
|
1795
|
+
|
|
1796
|
+
let count_value = pos_iter.next();
|
|
1797
|
+
defer_drop_mut!(count_value, heap);
|
|
1798
|
+
|
|
1799
|
+
// Check no extra positional arguments
|
|
1800
|
+
if pos_iter.len() != 0 {
|
|
1801
|
+
return Err(ExcType::type_error_at_most(method, 3, pos_iter.len() + 3));
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
// Process keyword arguments
|
|
1805
|
+
for (key, value) in kwargs_iter {
|
|
1806
|
+
defer_drop!(key, heap);
|
|
1807
|
+
let mut value_guard = HeapGuard::new(value, heap);
|
|
1808
|
+
|
|
1809
|
+
let Some(keyword_name) = key.as_either_str(value_guard.heap()) else {
|
|
1810
|
+
return Err(ExcType::type_error("keywords must be strings"));
|
|
1811
|
+
};
|
|
1812
|
+
|
|
1813
|
+
let key_str = keyword_name.as_str(interns);
|
|
1814
|
+
match key_str {
|
|
1815
|
+
"count" => {
|
|
1816
|
+
if let Some(previous_value) = count_value.replace(value_guard.into_inner()) {
|
|
1817
|
+
previous_value.drop_with_heap(heap);
|
|
1818
|
+
return Err(ExcType::type_error(format!(
|
|
1819
|
+
"{method}() got multiple values for argument 'count'"
|
|
1820
|
+
)));
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
_ => {
|
|
1824
|
+
return Err(ExcType::type_error(format!(
|
|
1825
|
+
"'{key_str}' is an invalid keyword argument for {method}()"
|
|
1826
|
+
)));
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
// Extract old bytes
|
|
1832
|
+
let old = extract_bytes_only(old_value, heap, interns)?.to_owned();
|
|
1833
|
+
|
|
1834
|
+
// Extract new bytes
|
|
1835
|
+
let new = extract_bytes_only(new_value, heap, interns)?.to_owned();
|
|
1836
|
+
|
|
1837
|
+
// Extract count (default -1)
|
|
1838
|
+
let count = if let Some(v) = count_value { v.as_int(heap)? } else { -1 };
|
|
1839
|
+
|
|
1840
|
+
Ok((old, new, count))
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
/// Replaces all occurrences of `old` with `new` in bytes.
|
|
1844
|
+
///
|
|
1845
|
+
/// Checks the time limit periodically to enforce `max_duration` during
|
|
1846
|
+
/// potentially long replacement operations on large byte sequences.
|
|
1847
|
+
fn bytes_replace_all(
|
|
1848
|
+
bytes: &[u8],
|
|
1849
|
+
old: &[u8],
|
|
1850
|
+
new: &[u8],
|
|
1851
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1852
|
+
) -> Result<Vec<u8>, ResourceError> {
|
|
1853
|
+
if old.is_empty() {
|
|
1854
|
+
// Empty pattern: insert new before each byte and at the end
|
|
1855
|
+
let mut result = Vec::with_capacity(bytes.len() + new.len() * (bytes.len() + 1));
|
|
1856
|
+
for &b in bytes {
|
|
1857
|
+
heap.check_time()?;
|
|
1858
|
+
result.extend_from_slice(new);
|
|
1859
|
+
result.push(b);
|
|
1860
|
+
}
|
|
1861
|
+
result.extend_from_slice(new);
|
|
1862
|
+
Ok(result)
|
|
1863
|
+
} else {
|
|
1864
|
+
let mut result = Vec::new();
|
|
1865
|
+
let mut start = 0;
|
|
1866
|
+
while let Some(pos) = find_subsequence(&bytes[start..], old) {
|
|
1867
|
+
heap.check_time()?;
|
|
1868
|
+
result.extend_from_slice(&bytes[start..start + pos]);
|
|
1869
|
+
result.extend_from_slice(new);
|
|
1870
|
+
start = start + pos + old.len();
|
|
1871
|
+
}
|
|
1872
|
+
result.extend_from_slice(&bytes[start..]);
|
|
1873
|
+
Ok(result)
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
/// Replaces at most n occurrences of `old` with `new` in bytes.
|
|
1878
|
+
///
|
|
1879
|
+
/// Checks the time limit periodically to enforce `max_duration` during
|
|
1880
|
+
/// potentially long replacement operations on large byte sequences.
|
|
1881
|
+
fn bytes_replace_n(
|
|
1882
|
+
bytes: &[u8],
|
|
1883
|
+
old: &[u8],
|
|
1884
|
+
new: &[u8],
|
|
1885
|
+
n: usize,
|
|
1886
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1887
|
+
) -> Result<Vec<u8>, ResourceError> {
|
|
1888
|
+
if old.is_empty() {
|
|
1889
|
+
// Empty pattern: insert new before each byte (up to n times)
|
|
1890
|
+
let mut result = Vec::new();
|
|
1891
|
+
let mut count = 0;
|
|
1892
|
+
for &b in bytes {
|
|
1893
|
+
heap.check_time()?;
|
|
1894
|
+
if count < n {
|
|
1895
|
+
result.extend_from_slice(new);
|
|
1896
|
+
count += 1;
|
|
1897
|
+
}
|
|
1898
|
+
result.push(b);
|
|
1899
|
+
}
|
|
1900
|
+
if count < n {
|
|
1901
|
+
result.extend_from_slice(new);
|
|
1902
|
+
}
|
|
1903
|
+
Ok(result)
|
|
1904
|
+
} else {
|
|
1905
|
+
let mut result = Vec::new();
|
|
1906
|
+
let mut start = 0;
|
|
1907
|
+
let mut count = 0;
|
|
1908
|
+
while count < n {
|
|
1909
|
+
heap.check_time()?;
|
|
1910
|
+
if let Some(pos) = find_subsequence(&bytes[start..], old) {
|
|
1911
|
+
result.extend_from_slice(&bytes[start..start + pos]);
|
|
1912
|
+
result.extend_from_slice(new);
|
|
1913
|
+
start = start + pos + old.len();
|
|
1914
|
+
count += 1;
|
|
1915
|
+
} else {
|
|
1916
|
+
break;
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
result.extend_from_slice(&bytes[start..]);
|
|
1920
|
+
Ok(result)
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
/// Implements Python's `bytes.center(width[, fillbyte])` method.
|
|
1925
|
+
///
|
|
1926
|
+
/// Returns centered in a bytes of length width.
|
|
1927
|
+
fn bytes_center(
|
|
1928
|
+
bytes: &[u8],
|
|
1929
|
+
args: ArgValues,
|
|
1930
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1931
|
+
interns: &Interns,
|
|
1932
|
+
) -> RunResult<Value> {
|
|
1933
|
+
let (width, fillbyte) = parse_bytes_justify_args("bytes.center", args, heap, interns)?;
|
|
1934
|
+
let len = bytes.len();
|
|
1935
|
+
|
|
1936
|
+
let result = if width <= len {
|
|
1937
|
+
bytes.to_vec()
|
|
1938
|
+
} else {
|
|
1939
|
+
check_repeat_size(width, 1, heap.tracker())?;
|
|
1940
|
+
let total_pad = width - len;
|
|
1941
|
+
let left_pad = total_pad / 2;
|
|
1942
|
+
let right_pad = total_pad - left_pad;
|
|
1943
|
+
let mut result = Vec::with_capacity(width);
|
|
1944
|
+
for _ in 0..left_pad {
|
|
1945
|
+
result.push(fillbyte);
|
|
1946
|
+
}
|
|
1947
|
+
result.extend_from_slice(bytes);
|
|
1948
|
+
for _ in 0..right_pad {
|
|
1949
|
+
result.push(fillbyte);
|
|
1950
|
+
}
|
|
1951
|
+
result
|
|
1952
|
+
};
|
|
1953
|
+
|
|
1954
|
+
allocate_bytes(result, heap)
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
/// Implements Python's `bytes.ljust(width[, fillbyte])` method.
|
|
1958
|
+
///
|
|
1959
|
+
/// Returns left-justified in a bytes of length width.
|
|
1960
|
+
fn bytes_ljust(
|
|
1961
|
+
bytes: &[u8],
|
|
1962
|
+
args: ArgValues,
|
|
1963
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1964
|
+
interns: &Interns,
|
|
1965
|
+
) -> RunResult<Value> {
|
|
1966
|
+
let (width, fillbyte) = parse_bytes_justify_args("bytes.ljust", args, heap, interns)?;
|
|
1967
|
+
let len = bytes.len();
|
|
1968
|
+
|
|
1969
|
+
let result = if width <= len {
|
|
1970
|
+
bytes.to_vec()
|
|
1971
|
+
} else {
|
|
1972
|
+
check_repeat_size(width, 1, heap.tracker())?;
|
|
1973
|
+
let pad = width - len;
|
|
1974
|
+
let mut result = Vec::with_capacity(width);
|
|
1975
|
+
result.extend_from_slice(bytes);
|
|
1976
|
+
for _ in 0..pad {
|
|
1977
|
+
result.push(fillbyte);
|
|
1978
|
+
}
|
|
1979
|
+
result
|
|
1980
|
+
};
|
|
1981
|
+
|
|
1982
|
+
allocate_bytes(result, heap)
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
/// Implements Python's `bytes.rjust(width[, fillbyte])` method.
|
|
1986
|
+
///
|
|
1987
|
+
/// Returns right-justified in a bytes of length width.
|
|
1988
|
+
fn bytes_rjust(
|
|
1989
|
+
bytes: &[u8],
|
|
1990
|
+
args: ArgValues,
|
|
1991
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
1992
|
+
interns: &Interns,
|
|
1993
|
+
) -> RunResult<Value> {
|
|
1994
|
+
let (width, fillbyte) = parse_bytes_justify_args("bytes.rjust", args, heap, interns)?;
|
|
1995
|
+
let len = bytes.len();
|
|
1996
|
+
|
|
1997
|
+
let result = if width <= len {
|
|
1998
|
+
bytes.to_vec()
|
|
1999
|
+
} else {
|
|
2000
|
+
check_repeat_size(width, 1, heap.tracker())?;
|
|
2001
|
+
let pad = width - len;
|
|
2002
|
+
let mut result = Vec::with_capacity(width);
|
|
2003
|
+
for _ in 0..pad {
|
|
2004
|
+
result.push(fillbyte);
|
|
2005
|
+
}
|
|
2006
|
+
result.extend_from_slice(bytes);
|
|
2007
|
+
result
|
|
2008
|
+
};
|
|
2009
|
+
|
|
2010
|
+
allocate_bytes(result, heap)
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
/// Parses arguments for bytes justify methods (center, ljust, rjust).
|
|
2014
|
+
fn parse_bytes_justify_args(
|
|
2015
|
+
method: &str,
|
|
2016
|
+
args: ArgValues,
|
|
2017
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
2018
|
+
interns: &Interns,
|
|
2019
|
+
) -> RunResult<(usize, u8)> {
|
|
2020
|
+
let pos = args.into_pos_only(method, heap)?;
|
|
2021
|
+
defer_drop!(pos, heap);
|
|
2022
|
+
|
|
2023
|
+
let extract_width = |v: &Value| -> RunResult<usize> {
|
|
2024
|
+
let w = v.as_int(heap)?;
|
|
2025
|
+
Ok(if w < 0 {
|
|
2026
|
+
0
|
|
2027
|
+
} else {
|
|
2028
|
+
usize::try_from(w).unwrap_or(usize::MAX)
|
|
2029
|
+
})
|
|
2030
|
+
};
|
|
2031
|
+
|
|
2032
|
+
let extract_fill = |v: &Value| -> RunResult<u8> {
|
|
2033
|
+
let fill_bytes = extract_bytes_only(v, heap, interns)?;
|
|
2034
|
+
if fill_bytes.len() != 1 {
|
|
2035
|
+
return Err(ExcType::type_error(format!(
|
|
2036
|
+
"{method}() argument 2 must be a byte string of length 1, not bytes of length {}",
|
|
2037
|
+
fill_bytes.len()
|
|
2038
|
+
)));
|
|
2039
|
+
}
|
|
2040
|
+
Ok(fill_bytes[0])
|
|
2041
|
+
};
|
|
2042
|
+
|
|
2043
|
+
match pos.as_slice() {
|
|
2044
|
+
[width_value] => Ok((extract_width(width_value)?, b' ')),
|
|
2045
|
+
[width_value, fillbyte_value] => Ok((extract_width(width_value)?, extract_fill(fillbyte_value)?)),
|
|
2046
|
+
[] => Err(ExcType::type_error_at_least(method, 1, 0)),
|
|
2047
|
+
_ => Err(ExcType::type_error_at_most(method, 2, pos.len())),
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
/// Implements Python's `bytes.zfill(width)` method.
|
|
2052
|
+
///
|
|
2053
|
+
/// Returns a copy of the bytes left filled with ASCII '0' digits.
|
|
2054
|
+
fn bytes_zfill(bytes: &[u8], args: ArgValues, heap: &mut Heap<impl ResourceTracker>) -> RunResult<Value> {
|
|
2055
|
+
let width_value = args.get_one_arg("bytes.zfill", heap)?;
|
|
2056
|
+
defer_drop!(width_value, heap);
|
|
2057
|
+
let width_i64 = width_value.as_int(heap)?;
|
|
2058
|
+
|
|
2059
|
+
let width = if width_i64 < 0 {
|
|
2060
|
+
0
|
|
2061
|
+
} else {
|
|
2062
|
+
usize::try_from(width_i64).unwrap_or(usize::MAX)
|
|
2063
|
+
};
|
|
2064
|
+
let len = bytes.len();
|
|
2065
|
+
|
|
2066
|
+
let result = if width <= len {
|
|
2067
|
+
bytes.to_vec()
|
|
2068
|
+
} else {
|
|
2069
|
+
check_repeat_size(width, 1, heap.tracker())?;
|
|
2070
|
+
let pad = width - len;
|
|
2071
|
+
let mut result = Vec::with_capacity(width);
|
|
2072
|
+
|
|
2073
|
+
// Handle sign prefix
|
|
2074
|
+
if !bytes.is_empty() && (bytes[0] == b'+' || bytes[0] == b'-') {
|
|
2075
|
+
result.push(bytes[0]);
|
|
2076
|
+
result.resize(pad + 1, b'0');
|
|
2077
|
+
result.extend_from_slice(&bytes[1..]);
|
|
2078
|
+
} else {
|
|
2079
|
+
result.resize(pad, b'0');
|
|
2080
|
+
result.extend_from_slice(bytes);
|
|
2081
|
+
}
|
|
2082
|
+
result
|
|
2083
|
+
};
|
|
2084
|
+
|
|
2085
|
+
allocate_bytes(result, heap)
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
// =============================================================================
|
|
2089
|
+
// Join method
|
|
2090
|
+
// =============================================================================
|
|
2091
|
+
|
|
2092
|
+
/// Implements Python's `bytes.join(iterable)` method.
|
|
2093
|
+
///
|
|
2094
|
+
/// Joins elements of the iterable with the separator bytes.
|
|
2095
|
+
fn bytes_join(separator: &[u8], iterable: Value, vm: &mut VM<'_, '_, impl ResourceTracker>) -> RunResult<Value> {
|
|
2096
|
+
let Ok(iter) = MontyIter::new(iterable, vm) else {
|
|
2097
|
+
return Err(ExcType::type_error_join_not_iterable());
|
|
2098
|
+
};
|
|
2099
|
+
defer_drop_mut!(iter, vm);
|
|
2100
|
+
|
|
2101
|
+
let mut result = Vec::new();
|
|
2102
|
+
let mut index = 0usize;
|
|
2103
|
+
|
|
2104
|
+
while let Some(item) = iter.for_next(vm)? {
|
|
2105
|
+
defer_drop!(item, vm);
|
|
2106
|
+
|
|
2107
|
+
if index > 0 {
|
|
2108
|
+
result.extend_from_slice(separator);
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
// Check item is bytes and extract its content
|
|
2112
|
+
match item {
|
|
2113
|
+
Value::InternBytes(id) => {
|
|
2114
|
+
result.extend_from_slice(vm.interns.get_bytes(*id));
|
|
2115
|
+
}
|
|
2116
|
+
Value::Ref(heap_id) => {
|
|
2117
|
+
if let HeapData::Bytes(b) = vm.heap.get(*heap_id) {
|
|
2118
|
+
result.extend_from_slice(b.as_slice());
|
|
2119
|
+
} else {
|
|
2120
|
+
let t = item.py_type(vm.heap);
|
|
2121
|
+
return Err(ExcType::type_error(format!(
|
|
2122
|
+
"sequence item {index}: expected a bytes-like object, {t} found"
|
|
2123
|
+
)));
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
_ => {
|
|
2127
|
+
let t = item.py_type(vm.heap);
|
|
2128
|
+
return Err(ExcType::type_error(format!(
|
|
2129
|
+
"sequence item {index}: expected a bytes-like object, {t} found"
|
|
2130
|
+
)));
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
index += 1;
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
allocate_bytes(result, vm.heap)
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
// =============================================================================
|
|
2140
|
+
// Hex method
|
|
2141
|
+
// =============================================================================
|
|
2142
|
+
|
|
2143
|
+
/// Implements Python's `bytes.hex([sep[, bytes_per_sep]])` method.
|
|
2144
|
+
///
|
|
2145
|
+
/// Returns a string containing the hexadecimal representation of the bytes.
|
|
2146
|
+
fn bytes_hex(
|
|
2147
|
+
bytes: &[u8],
|
|
2148
|
+
args: ArgValues,
|
|
2149
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
2150
|
+
interns: &Interns,
|
|
2151
|
+
) -> RunResult<Value> {
|
|
2152
|
+
let (sep, bytes_per_sep) = parse_bytes_hex_args(args, heap, interns)?;
|
|
2153
|
+
|
|
2154
|
+
let hex_chars: Vec<char> = bytes
|
|
2155
|
+
.iter()
|
|
2156
|
+
.flat_map(|b| {
|
|
2157
|
+
let hi = (b >> 4) & 0xf;
|
|
2158
|
+
let lo = b & 0xf;
|
|
2159
|
+
let hi_char = if hi < 10 {
|
|
2160
|
+
(b'0' + hi) as char
|
|
2161
|
+
} else {
|
|
2162
|
+
(b'a' + hi - 10) as char
|
|
2163
|
+
};
|
|
2164
|
+
let lo_char = if lo < 10 {
|
|
2165
|
+
(b'0' + lo) as char
|
|
2166
|
+
} else {
|
|
2167
|
+
(b'a' + lo - 10) as char
|
|
2168
|
+
};
|
|
2169
|
+
[hi_char, lo_char]
|
|
2170
|
+
})
|
|
2171
|
+
.collect();
|
|
2172
|
+
|
|
2173
|
+
let result = if let Some(sep) = sep {
|
|
2174
|
+
if bytes_per_sep == 0 || bytes.is_empty() {
|
|
2175
|
+
hex_chars.iter().collect()
|
|
2176
|
+
} else {
|
|
2177
|
+
// Insert separator every `bytes_per_sep` bytes (2*bytes_per_sep hex chars)
|
|
2178
|
+
let chars_per_group = usize::try_from(bytes_per_sep.unsigned_abs()).unwrap_or(usize::MAX) * 2;
|
|
2179
|
+
let mut result = String::new();
|
|
2180
|
+
|
|
2181
|
+
if bytes_per_sep > 0 {
|
|
2182
|
+
// Positive: count from right, so partial group is at the START
|
|
2183
|
+
let total_len = hex_chars.len();
|
|
2184
|
+
let first_chunk_len = total_len % chars_per_group;
|
|
2185
|
+
let first_chunk_len = if first_chunk_len == 0 {
|
|
2186
|
+
chars_per_group
|
|
2187
|
+
} else {
|
|
2188
|
+
first_chunk_len
|
|
2189
|
+
};
|
|
2190
|
+
|
|
2191
|
+
result.extend(&hex_chars[..first_chunk_len]);
|
|
2192
|
+
for chunk in hex_chars[first_chunk_len..].chunks(chars_per_group) {
|
|
2193
|
+
result.push(sep);
|
|
2194
|
+
result.extend(chunk);
|
|
2195
|
+
}
|
|
2196
|
+
} else {
|
|
2197
|
+
// Negative: count from left, so partial group is at the END
|
|
2198
|
+
for (i, chunk) in hex_chars.chunks(chars_per_group).enumerate() {
|
|
2199
|
+
if i > 0 {
|
|
2200
|
+
result.push(sep);
|
|
2201
|
+
}
|
|
2202
|
+
result.extend(chunk);
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
result
|
|
2206
|
+
}
|
|
2207
|
+
} else {
|
|
2208
|
+
hex_chars.iter().collect()
|
|
2209
|
+
};
|
|
2210
|
+
|
|
2211
|
+
crate::types::str::allocate_string(result, heap)
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
/// Parses arguments for bytes.hex method.
|
|
2215
|
+
fn parse_bytes_hex_args(
|
|
2216
|
+
args: ArgValues,
|
|
2217
|
+
heap: &mut Heap<impl ResourceTracker>,
|
|
2218
|
+
interns: &Interns,
|
|
2219
|
+
) -> RunResult<(Option<char>, i64)> {
|
|
2220
|
+
let pos = args.into_pos_only("bytes.hex", heap)?;
|
|
2221
|
+
defer_drop!(pos, heap);
|
|
2222
|
+
|
|
2223
|
+
let (sep_value, bps_value) = match pos.as_slice() {
|
|
2224
|
+
[] => return Ok((None, 1)),
|
|
2225
|
+
[sep_value] => (sep_value, None),
|
|
2226
|
+
[sep_value, bps_value] => (sep_value, Some(bps_value)),
|
|
2227
|
+
other => return Err(ExcType::type_error_at_most("bytes.hex", 2, other.len())),
|
|
2228
|
+
};
|
|
2229
|
+
|
|
2230
|
+
let sep_bytes = match sep_value {
|
|
2231
|
+
Value::InternString(id) => interns.get_str(*id).as_bytes(),
|
|
2232
|
+
Value::InternBytes(id) => interns.get_bytes(*id),
|
|
2233
|
+
Value::Ref(heap_id) => match heap.get(*heap_id) {
|
|
2234
|
+
HeapData::Str(s) => s.as_bytes(),
|
|
2235
|
+
HeapData::Bytes(b) => b.as_slice(),
|
|
2236
|
+
_ => return Err(ExcType::type_error("sep must be str or bytes")),
|
|
2237
|
+
},
|
|
2238
|
+
_ => return Err(ExcType::type_error("sep must be str or bytes")),
|
|
2239
|
+
};
|
|
2240
|
+
|
|
2241
|
+
let sep = match sep_bytes {
|
|
2242
|
+
[b] if b.is_ascii() => *b as char,
|
|
2243
|
+
_ => return Err(SimpleException::new_msg(ExcType::ValueError, "sep must be a single ASCII character").into()),
|
|
2244
|
+
};
|
|
2245
|
+
|
|
2246
|
+
let bytes_per_sep = if let Some(bps_value) = bps_value {
|
|
2247
|
+
bps_value.as_int(heap)?
|
|
2248
|
+
} else {
|
|
2249
|
+
1
|
|
2250
|
+
};
|
|
2251
|
+
|
|
2252
|
+
Ok((Some(sep), bytes_per_sep))
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
// =============================================================================
|
|
2256
|
+
// fromhex classmethod
|
|
2257
|
+
// =============================================================================
|
|
2258
|
+
|
|
2259
|
+
/// Implements Python's `bytes.fromhex(string)` classmethod.
|
|
2260
|
+
///
|
|
2261
|
+
/// Creates bytes from a hexadecimal string. Whitespace is allowed between byte pairs,
|
|
2262
|
+
/// but not between the two digits of a byte.
|
|
2263
|
+
pub fn bytes_fromhex(args: ArgValues, heap: &mut Heap<impl ResourceTracker>, interns: &Interns) -> RunResult<Value> {
|
|
2264
|
+
let hex_value = args.get_one_arg("bytes.fromhex", heap)?;
|
|
2265
|
+
defer_drop!(hex_value, heap);
|
|
2266
|
+
|
|
2267
|
+
let hex_str = match hex_value {
|
|
2268
|
+
Value::InternString(id) => interns.get_str(*id),
|
|
2269
|
+
Value::Ref(heap_id) => {
|
|
2270
|
+
if let HeapData::Str(s) = heap.get(*heap_id) {
|
|
2271
|
+
s.as_str()
|
|
2272
|
+
} else {
|
|
2273
|
+
return Err(ExcType::type_error("fromhex() argument must be str, not bytes"));
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
_ => {
|
|
2277
|
+
let t = hex_value.py_type(heap);
|
|
2278
|
+
return Err(ExcType::type_error(format!("fromhex() argument must be str, not {t}")));
|
|
2279
|
+
}
|
|
2280
|
+
};
|
|
2281
|
+
|
|
2282
|
+
// CPython allows whitespace BETWEEN byte pairs, but NOT within a pair.
|
|
2283
|
+
// - "de ad" is valid (whitespace between pairs)
|
|
2284
|
+
// - "d e" or "0 1" are NOT valid (whitespace within a pair)
|
|
2285
|
+
// - " 01 " is valid (whitespace before/after)
|
|
2286
|
+
//
|
|
2287
|
+
// Error messages:
|
|
2288
|
+
// - Invalid char (including whitespace in wrong place): "non-hexadecimal number found ... at position X"
|
|
2289
|
+
// - Odd number of valid hex digits: "must contain an even number of hexadecimal digits"
|
|
2290
|
+
|
|
2291
|
+
let mut result = Vec::new();
|
|
2292
|
+
let mut chars = hex_str.chars().enumerate().peekable();
|
|
2293
|
+
|
|
2294
|
+
loop {
|
|
2295
|
+
// Skip whitespace BETWEEN byte pairs (before the high nibble)
|
|
2296
|
+
while chars.peek().is_some_and(|(_, c)| c.is_whitespace()) {
|
|
2297
|
+
chars.next();
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
// Get high nibble
|
|
2301
|
+
let Some((hi_pos, hi_char)) = chars.next() else {
|
|
2302
|
+
break; // End of string - we're done
|
|
2303
|
+
};
|
|
2304
|
+
|
|
2305
|
+
let Some(hi_val) = hex_char_to_value(hi_char) else {
|
|
2306
|
+
return Err(SimpleException::new_msg(
|
|
2307
|
+
ExcType::ValueError,
|
|
2308
|
+
format!("non-hexadecimal number found in fromhex() arg at position {hi_pos}"),
|
|
2309
|
+
)
|
|
2310
|
+
.into());
|
|
2311
|
+
};
|
|
2312
|
+
|
|
2313
|
+
// Get low nibble - must be IMMEDIATELY after high nibble (no whitespace)
|
|
2314
|
+
let Some((lo_pos, lo_char)) = chars.next() else {
|
|
2315
|
+
// End of string after high nibble = odd number of hex digits
|
|
2316
|
+
return Err(SimpleException::new_msg(
|
|
2317
|
+
ExcType::ValueError,
|
|
2318
|
+
"fromhex() arg must contain an even number of hexadecimal digits",
|
|
2319
|
+
)
|
|
2320
|
+
.into());
|
|
2321
|
+
};
|
|
2322
|
+
|
|
2323
|
+
let Some(lo_val) = hex_char_to_value(lo_char) else {
|
|
2324
|
+
// Invalid character (including whitespace) in low nibble position
|
|
2325
|
+
return Err(SimpleException::new_msg(
|
|
2326
|
+
ExcType::ValueError,
|
|
2327
|
+
format!("non-hexadecimal number found in fromhex() arg at position {lo_pos}"),
|
|
2328
|
+
)
|
|
2329
|
+
.into());
|
|
2330
|
+
};
|
|
2331
|
+
|
|
2332
|
+
result.push((hi_val << 4) | lo_val);
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
allocate_bytes(result, heap)
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
/// Converts a hex character to its numeric value.
|
|
2339
|
+
fn hex_char_to_value(c: char) -> Option<u8> {
|
|
2340
|
+
match c {
|
|
2341
|
+
'0'..='9' => Some(c as u8 - b'0'),
|
|
2342
|
+
'a'..='f' => Some(c as u8 - b'a' + 10),
|
|
2343
|
+
'A'..='F' => Some(c as u8 - b'A' + 10),
|
|
2344
|
+
_ => None,
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
// =============================================================================
|
|
2349
|
+
// Helper function for bytes allocation
|
|
2350
|
+
// =============================================================================
|
|
2351
|
+
|
|
2352
|
+
/// Allocates bytes on the heap.
|
|
2353
|
+
fn allocate_bytes(bytes: Vec<u8>, heap: &mut Heap<impl ResourceTracker>) -> RunResult<Value> {
|
|
2354
|
+
let heap_id = heap.allocate(HeapData::Bytes(Bytes::new(bytes)))?;
|
|
2355
|
+
Ok(Value::Ref(heap_id))
|
|
2356
|
+
}
|