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.
Files changed (961) hide show
  1. package/__tests__/adapter-schema.test.js +2 -0
  2. package/__tests__/config.test.js +62 -1
  3. package/__tests__/help-json.test.js +2 -0
  4. package/__tests__/mcp-adapter.test.js +14 -4
  5. package/__tests__/mcp-local.test.js +159 -0
  6. package/__tests__/mcp-stdio-jsonrpc.test.js +105 -0
  7. package/__tests__/monty-plugin.test.js +121 -0
  8. package/__tests__/plugin-browser-use-uninstall.test.js +23 -0
  9. package/__tests__/plugin-browser-use.test.js +77 -0
  10. package/__tests__/plugins-command.test.js +92 -1
  11. package/__tests__/plugins-learn.test.js +62 -0
  12. package/__tests__/plugins-registry.test.js +3 -1
  13. package/__tests__/resend-plugin.test.js +122 -0
  14. package/__tests__/skills.test.js +4 -0
  15. package/cli/adapter-schema.js +3 -2
  16. package/cli/adapters/mcp.js +22 -3
  17. package/cli/adapters/process.js +34 -7
  18. package/cli/config.js +27 -1
  19. package/cli/help-json.js +2 -2
  20. package/cli/mcp-diagnostics.js +152 -0
  21. package/cli/mcp-discovery.js +221 -0
  22. package/cli/mcp-local.js +267 -25
  23. package/cli/mcp-stdio-jsonrpc.js +246 -0
  24. package/cli/plugin-install-guidance.js +25 -0
  25. package/cli/plugins-command.js +86 -3
  26. package/cli/plugins-learn.js +177 -0
  27. package/cli/plugins-manager.js +3 -0
  28. package/cli/plugins-registry.js +2 -1
  29. package/cli/skills-mcp.js +102 -0
  30. package/cli/skills.js +6 -40
  31. package/cli/supercli.js +7 -2
  32. package/docs/initial/mcp-local-mode.md +35 -0
  33. package/docs/mcp-cheatsheet.md +324 -0
  34. package/docs/plugins.md +7 -0
  35. package/package.json +1 -1
  36. package/plugins/browser-use/plugin.json +23 -0
  37. package/plugins/browser-use/scripts/post-install.js +146 -0
  38. package/plugins/browser-use/scripts/post-uninstall.js +28 -0
  39. package/plugins/browser-use/skills/quickstart/SKILL.md +47 -0
  40. package/plugins/monty/README.md +49 -0
  41. package/plugins/monty/plugin.json +69 -0
  42. package/plugins/monty/scripts/post-install.js +73 -0
  43. package/plugins/monty/scripts/post-uninstall.js +23 -0
  44. package/plugins/monty/scripts/run-python.js +140 -0
  45. package/plugins/monty/scripts/setup-monty.js +27 -0
  46. package/plugins/plugins.json +29 -0
  47. package/plugins/resend/plugin.json +371 -0
  48. package/plugins/resend/scripts/post-install.js +59 -0
  49. package/plugins/resend/scripts/post-uninstall.js +23 -0
  50. package/plugins/resend/scripts/setup-resend.js +27 -0
  51. package/plugins/resend/skills/quickstart/SKILL.md +80 -0
  52. package/ref-monty/.cargo/config.toml +3 -0
  53. package/ref-monty/.claude/settings.json +60 -0
  54. package/ref-monty/.claude/skills/fastmod/SKILL.md +22 -0
  55. package/ref-monty/.claude/skills/python-playground/SKILL.md +47 -0
  56. package/ref-monty/.codecov.yml +12 -0
  57. package/ref-monty/.github/actions/build-pgo-wheel/action.yml +72 -0
  58. package/ref-monty/.github/workflows/ci.yml +776 -0
  59. package/ref-monty/.github/workflows/codspeed.yml +45 -0
  60. package/ref-monty/.github/workflows/init-npm-packages.yml +82 -0
  61. package/ref-monty/.pre-commit-config.yaml +47 -0
  62. package/ref-monty/.python-version +1 -0
  63. package/ref-monty/.rustfmt.toml +4 -0
  64. package/ref-monty/.zed/settings.json +11 -0
  65. package/ref-monty/CLAUDE.md +535 -0
  66. package/ref-monty/Cargo.lock +3798 -0
  67. package/ref-monty/Cargo.toml +87 -0
  68. package/ref-monty/LICENSE +21 -0
  69. package/ref-monty/Makefile +216 -0
  70. package/ref-monty/README.md +430 -0
  71. package/ref-monty/RELEASING.md +47 -0
  72. package/ref-monty/crates/fuzz/Cargo.toml +30 -0
  73. package/ref-monty/crates/fuzz/fuzz_targets/string_input_panic.rs +37 -0
  74. package/ref-monty/crates/fuzz/fuzz_targets/tokens_input_panic.rs +552 -0
  75. package/ref-monty/crates/monty/Cargo.toml +68 -0
  76. package/ref-monty/crates/monty/benches/main.rs +247 -0
  77. package/ref-monty/crates/monty/build.rs +10 -0
  78. package/ref-monty/crates/monty/src/args.rs +733 -0
  79. package/ref-monty/crates/monty/src/asyncio.rs +179 -0
  80. package/ref-monty/crates/monty/src/builtins/abs.rs +55 -0
  81. package/ref-monty/crates/monty/src/builtins/all.rs +30 -0
  82. package/ref-monty/crates/monty/src/builtins/any.rs +30 -0
  83. package/ref-monty/crates/monty/src/builtins/bin.rs +59 -0
  84. package/ref-monty/crates/monty/src/builtins/chr.rs +46 -0
  85. package/ref-monty/crates/monty/src/builtins/divmod.rs +164 -0
  86. package/ref-monty/crates/monty/src/builtins/enumerate.rs +52 -0
  87. package/ref-monty/crates/monty/src/builtins/filter.rs +67 -0
  88. package/ref-monty/crates/monty/src/builtins/getattr.rs +65 -0
  89. package/ref-monty/crates/monty/src/builtins/hash.rs +28 -0
  90. package/ref-monty/crates/monty/src/builtins/hex.rs +58 -0
  91. package/ref-monty/crates/monty/src/builtins/id.rs +24 -0
  92. package/ref-monty/crates/monty/src/builtins/isinstance.rs +68 -0
  93. package/ref-monty/crates/monty/src/builtins/len.rs +25 -0
  94. package/ref-monty/crates/monty/src/builtins/map.rs +98 -0
  95. package/ref-monty/crates/monty/src/builtins/min_max.rs +113 -0
  96. package/ref-monty/crates/monty/src/builtins/mod.rs +246 -0
  97. package/ref-monty/crates/monty/src/builtins/next.rs +21 -0
  98. package/ref-monty/crates/monty/src/builtins/oct.rs +59 -0
  99. package/ref-monty/crates/monty/src/builtins/ord.rs +67 -0
  100. package/ref-monty/crates/monty/src/builtins/pow.rs +365 -0
  101. package/ref-monty/crates/monty/src/builtins/print.rs +141 -0
  102. package/ref-monty/crates/monty/src/builtins/repr.rs +16 -0
  103. package/ref-monty/crates/monty/src/builtins/reversed.rs +28 -0
  104. package/ref-monty/crates/monty/src/builtins/round.rs +174 -0
  105. package/ref-monty/crates/monty/src/builtins/sorted.rs +151 -0
  106. package/ref-monty/crates/monty/src/builtins/sum.rs +66 -0
  107. package/ref-monty/crates/monty/src/builtins/type_.rs +16 -0
  108. package/ref-monty/crates/monty/src/builtins/zip.rs +77 -0
  109. package/ref-monty/crates/monty/src/bytecode/builder.rs +699 -0
  110. package/ref-monty/crates/monty/src/bytecode/code.rs +310 -0
  111. package/ref-monty/crates/monty/src/bytecode/compiler.rs +3206 -0
  112. package/ref-monty/crates/monty/src/bytecode/mod.rs +24 -0
  113. package/ref-monty/crates/monty/src/bytecode/op.rs +617 -0
  114. package/ref-monty/crates/monty/src/bytecode/vm/async_exec.rs +1058 -0
  115. package/ref-monty/crates/monty/src/bytecode/vm/attr.rs +63 -0
  116. package/ref-monty/crates/monty/src/bytecode/vm/binary.rs +487 -0
  117. package/ref-monty/crates/monty/src/bytecode/vm/call.rs +767 -0
  118. package/ref-monty/crates/monty/src/bytecode/vm/collections.rs +741 -0
  119. package/ref-monty/crates/monty/src/bytecode/vm/compare.rs +147 -0
  120. package/ref-monty/crates/monty/src/bytecode/vm/exceptions.rs +297 -0
  121. package/ref-monty/crates/monty/src/bytecode/vm/format.rs +132 -0
  122. package/ref-monty/crates/monty/src/bytecode/vm/mod.rs +1958 -0
  123. package/ref-monty/crates/monty/src/bytecode/vm/scheduler.rs +620 -0
  124. package/ref-monty/crates/monty/src/exception_private.rs +1513 -0
  125. package/ref-monty/crates/monty/src/exception_public.rs +346 -0
  126. package/ref-monty/crates/monty/src/expressions.rs +694 -0
  127. package/ref-monty/crates/monty/src/fstring.rs +854 -0
  128. package/ref-monty/crates/monty/src/function.rs +119 -0
  129. package/ref-monty/crates/monty/src/heap.rs +1073 -0
  130. package/ref-monty/crates/monty/src/heap_data.rs +985 -0
  131. package/ref-monty/crates/monty/src/heap_traits.rs +312 -0
  132. package/ref-monty/crates/monty/src/intern.rs +837 -0
  133. package/ref-monty/crates/monty/src/io.rs +106 -0
  134. package/ref-monty/crates/monty/src/lib.rs +52 -0
  135. package/ref-monty/crates/monty/src/modules/asyncio.rs +144 -0
  136. package/ref-monty/crates/monty/src/modules/math.rs +1453 -0
  137. package/ref-monty/crates/monty/src/modules/mod.rs +120 -0
  138. package/ref-monty/crates/monty/src/modules/os.rs +116 -0
  139. package/ref-monty/crates/monty/src/modules/pathlib.rs +33 -0
  140. package/ref-monty/crates/monty/src/modules/re.rs +606 -0
  141. package/ref-monty/crates/monty/src/modules/sys.rs +60 -0
  142. package/ref-monty/crates/monty/src/modules/typing.rs +70 -0
  143. package/ref-monty/crates/monty/src/namespace.rs +21 -0
  144. package/ref-monty/crates/monty/src/object.rs +1040 -0
  145. package/ref-monty/crates/monty/src/os.rs +215 -0
  146. package/ref-monty/crates/monty/src/parse.rs +1730 -0
  147. package/ref-monty/crates/monty/src/prepare.rs +3015 -0
  148. package/ref-monty/crates/monty/src/repl.rs +1109 -0
  149. package/ref-monty/crates/monty/src/resource.rs +559 -0
  150. package/ref-monty/crates/monty/src/run.rs +457 -0
  151. package/ref-monty/crates/monty/src/run_progress.rs +821 -0
  152. package/ref-monty/crates/monty/src/signature.rs +651 -0
  153. package/ref-monty/crates/monty/src/sorting.rs +100 -0
  154. package/ref-monty/crates/monty/src/types/bytes.rs +2356 -0
  155. package/ref-monty/crates/monty/src/types/dataclass.rs +345 -0
  156. package/ref-monty/crates/monty/src/types/dict.rs +879 -0
  157. package/ref-monty/crates/monty/src/types/dict_view.rs +619 -0
  158. package/ref-monty/crates/monty/src/types/iter.rs +799 -0
  159. package/ref-monty/crates/monty/src/types/list.rs +929 -0
  160. package/ref-monty/crates/monty/src/types/long_int.rs +211 -0
  161. package/ref-monty/crates/monty/src/types/mod.rs +48 -0
  162. package/ref-monty/crates/monty/src/types/module.rs +146 -0
  163. package/ref-monty/crates/monty/src/types/namedtuple.rs +261 -0
  164. package/ref-monty/crates/monty/src/types/path.rs +596 -0
  165. package/ref-monty/crates/monty/src/types/property.rs +35 -0
  166. package/ref-monty/crates/monty/src/types/py_trait.rs +322 -0
  167. package/ref-monty/crates/monty/src/types/range.rs +285 -0
  168. package/ref-monty/crates/monty/src/types/re_match.rs +522 -0
  169. package/ref-monty/crates/monty/src/types/re_pattern.rs +726 -0
  170. package/ref-monty/crates/monty/src/types/set.rs +1373 -0
  171. package/ref-monty/crates/monty/src/types/slice.rs +257 -0
  172. package/ref-monty/crates/monty/src/types/str.rs +2051 -0
  173. package/ref-monty/crates/monty/src/types/tuple.rs +376 -0
  174. package/ref-monty/crates/monty/src/types/type.rs +407 -0
  175. package/ref-monty/crates/monty/src/value.rs +2558 -0
  176. package/ref-monty/crates/monty/test_cases/args__dict_get_no_args.py +3 -0
  177. package/ref-monty/crates/monty/test_cases/args__dict_get_too_many.py +3 -0
  178. package/ref-monty/crates/monty/test_cases/args__dict_items_with_args.py +3 -0
  179. package/ref-monty/crates/monty/test_cases/args__dict_keys_with_args.py +3 -0
  180. package/ref-monty/crates/monty/test_cases/args__dict_pop_no_args.py +3 -0
  181. package/ref-monty/crates/monty/test_cases/args__dict_pop_too_many.py +3 -0
  182. package/ref-monty/crates/monty/test_cases/args__dict_values_with_args.py +3 -0
  183. package/ref-monty/crates/monty/test_cases/args__id_too_many.py +2 -0
  184. package/ref-monty/crates/monty/test_cases/args__len_no_args.py +2 -0
  185. package/ref-monty/crates/monty/test_cases/args__len_too_many.py +2 -0
  186. package/ref-monty/crates/monty/test_cases/args__len_type_error_int.py +9 -0
  187. package/ref-monty/crates/monty/test_cases/args__len_type_error_none.py +9 -0
  188. package/ref-monty/crates/monty/test_cases/args__list_append_no_args.py +3 -0
  189. package/ref-monty/crates/monty/test_cases/args__list_append_too_many.py +3 -0
  190. package/ref-monty/crates/monty/test_cases/args__list_insert_too_few.py +3 -0
  191. package/ref-monty/crates/monty/test_cases/args__list_insert_too_many.py +3 -0
  192. package/ref-monty/crates/monty/test_cases/args__repr_no_args.py +2 -0
  193. package/ref-monty/crates/monty/test_cases/arith__div_zero_float.py +2 -0
  194. package/ref-monty/crates/monty/test_cases/arith__div_zero_int.py +2 -0
  195. package/ref-monty/crates/monty/test_cases/arith__floordiv_zero_float.py +2 -0
  196. package/ref-monty/crates/monty/test_cases/arith__floordiv_zero_int.py +2 -0
  197. package/ref-monty/crates/monty/test_cases/arith__pow_zero_neg.py +2 -0
  198. package/ref-monty/crates/monty/test_cases/arith__pow_zero_neg_builtin.py +9 -0
  199. package/ref-monty/crates/monty/test_cases/assert__expr_fail.py +2 -0
  200. package/ref-monty/crates/monty/test_cases/assert__fail.py +2 -0
  201. package/ref-monty/crates/monty/test_cases/assert__fail_msg.py +2 -0
  202. package/ref-monty/crates/monty/test_cases/assert__fn_fail.py +3 -0
  203. package/ref-monty/crates/monty/test_cases/assert__ops.py +11 -0
  204. package/ref-monty/crates/monty/test_cases/async__asyncio_run.py +47 -0
  205. package/ref-monty/crates/monty/test_cases/async__basic.py +10 -0
  206. package/ref-monty/crates/monty/test_cases/async__closure.py +14 -0
  207. package/ref-monty/crates/monty/test_cases/async__double_await_coroutine.py +16 -0
  208. package/ref-monty/crates/monty/test_cases/async__exception.py +10 -0
  209. package/ref-monty/crates/monty/test_cases/async__ext_call.py +73 -0
  210. package/ref-monty/crates/monty/test_cases/async__gather_all.py +85 -0
  211. package/ref-monty/crates/monty/test_cases/async__nested_await.py +15 -0
  212. package/ref-monty/crates/monty/test_cases/async__nested_gather_ext.py +37 -0
  213. package/ref-monty/crates/monty/test_cases/async__not_awaitable.py +10 -0
  214. package/ref-monty/crates/monty/test_cases/async__not_imported.py +14 -0
  215. package/ref-monty/crates/monty/test_cases/async__recursion_depth_isolation.py +27 -0
  216. package/ref-monty/crates/monty/test_cases/async__return_types.py +31 -0
  217. package/ref-monty/crates/monty/test_cases/async__sequential.py +16 -0
  218. package/ref-monty/crates/monty/test_cases/async__traceback.py +19 -0
  219. package/ref-monty/crates/monty/test_cases/async__with_args.py +14 -0
  220. package/ref-monty/crates/monty/test_cases/attr__get_int_error.py +9 -0
  221. package/ref-monty/crates/monty/test_cases/attr__get_list_error.py +9 -0
  222. package/ref-monty/crates/monty/test_cases/attr__set_frozen_nonfield.py +12 -0
  223. package/ref-monty/crates/monty/test_cases/attr__set_int_error.py +10 -0
  224. package/ref-monty/crates/monty/test_cases/attr__set_list_error.py +10 -0
  225. package/ref-monty/crates/monty/test_cases/bench__kitchen_sink.py +68 -0
  226. package/ref-monty/crates/monty/test_cases/bool__ops.py +20 -0
  227. package/ref-monty/crates/monty/test_cases/builtin__add_type_error.py +2 -0
  228. package/ref-monty/crates/monty/test_cases/builtin__filter.py +62 -0
  229. package/ref-monty/crates/monty/test_cases/builtin__filter_not_iterable.py +11 -0
  230. package/ref-monty/crates/monty/test_cases/builtin__getattr.py +84 -0
  231. package/ref-monty/crates/monty/test_cases/builtin__iter_funcs.py +42 -0
  232. package/ref-monty/crates/monty/test_cases/builtin__iter_next.py +66 -0
  233. package/ref-monty/crates/monty/test_cases/builtin__map.py +74 -0
  234. package/ref-monty/crates/monty/test_cases/builtin__map_not_iterable.py +11 -0
  235. package/ref-monty/crates/monty/test_cases/builtin__math_funcs.py +154 -0
  236. package/ref-monty/crates/monty/test_cases/builtin__more_iter_funcs.py +148 -0
  237. package/ref-monty/crates/monty/test_cases/builtin__next_stop_iteration.py +10 -0
  238. package/ref-monty/crates/monty/test_cases/builtin__print_invalid_kwarg.py +9 -0
  239. package/ref-monty/crates/monty/test_cases/builtin__print_kwargs.py +12 -0
  240. package/ref-monty/crates/monty/test_cases/builtin__repr.py +3 -0
  241. package/ref-monty/crates/monty/test_cases/builtin__string_funcs.py +73 -0
  242. package/ref-monty/crates/monty/test_cases/bytes__decode_invalid_utf8.py +18 -0
  243. package/ref-monty/crates/monty/test_cases/bytes__endswith_str_error.py +10 -0
  244. package/ref-monty/crates/monty/test_cases/bytes__getitem_index_error.py +10 -0
  245. package/ref-monty/crates/monty/test_cases/bytes__index_start_gt_end.py +10 -0
  246. package/ref-monty/crates/monty/test_cases/bytes__methods.py +394 -0
  247. package/ref-monty/crates/monty/test_cases/bytes__negative_count.py +9 -0
  248. package/ref-monty/crates/monty/test_cases/bytes__ops.py +90 -0
  249. package/ref-monty/crates/monty/test_cases/bytes__startswith_str_error.py +10 -0
  250. package/ref-monty/crates/monty/test_cases/call_object.py +3 -0
  251. package/ref-monty/crates/monty/test_cases/chain_comparison__all.py +79 -0
  252. package/ref-monty/crates/monty/test_cases/closure__param_shadows_outer.py +81 -0
  253. package/ref-monty/crates/monty/test_cases/closure__pep448.py +203 -0
  254. package/ref-monty/crates/monty/test_cases/closure__undefined_nonlocal.py +13 -0
  255. package/ref-monty/crates/monty/test_cases/compare__mixed_types.py +120 -0
  256. package/ref-monty/crates/monty/test_cases/comprehension__all.py +208 -0
  257. package/ref-monty/crates/monty/test_cases/comprehension__scope.py +7 -0
  258. package/ref-monty/crates/monty/test_cases/comprehension__unbound_local.py +14 -0
  259. package/ref-monty/crates/monty/test_cases/dataclass__basic.py +238 -0
  260. package/ref-monty/crates/monty/test_cases/dataclass__call_field_error.py +12 -0
  261. package/ref-monty/crates/monty/test_cases/dataclass__frozen_set_error.py +12 -0
  262. package/ref-monty/crates/monty/test_cases/dataclass__get_missing_attr_error.py +11 -0
  263. package/ref-monty/crates/monty/test_cases/dict__get_unhashable_key.py +3 -0
  264. package/ref-monty/crates/monty/test_cases/dict__literal_unhashable_key.py +2 -0
  265. package/ref-monty/crates/monty/test_cases/dict__method_pop_missing_error.py +3 -0
  266. package/ref-monty/crates/monty/test_cases/dict__methods.py +151 -0
  267. package/ref-monty/crates/monty/test_cases/dict__ops.py +133 -0
  268. package/ref-monty/crates/monty/test_cases/dict__pop_unhashable_key.py +4 -0
  269. package/ref-monty/crates/monty/test_cases/dict__popitem_empty.py +9 -0
  270. package/ref-monty/crates/monty/test_cases/dict__subscript_missing_key.py +3 -0
  271. package/ref-monty/crates/monty/test_cases/dict__unhashable_dict_key.py +2 -0
  272. package/ref-monty/crates/monty/test_cases/dict__unhashable_list_key.py +2 -0
  273. package/ref-monty/crates/monty/test_cases/dict__unpack_type_error.py +2 -0
  274. package/ref-monty/crates/monty/test_cases/dict__views.py +165 -0
  275. package/ref-monty/crates/monty/test_cases/edge__all.py +26 -0
  276. package/ref-monty/crates/monty/test_cases/edge__float_int_mod.py +2 -0
  277. package/ref-monty/crates/monty/test_cases/edge__int_float_mod.py +2 -0
  278. package/ref-monty/crates/monty/test_cases/exc__args.py +16 -0
  279. package/ref-monty/crates/monty/test_cases/exc__str.py +15 -0
  280. package/ref-monty/crates/monty/test_cases/execute_ok__all.py +54 -0
  281. package/ref-monty/crates/monty/test_cases/execute_raise__error_instance_str.py +2 -0
  282. package/ref-monty/crates/monty/test_cases/execute_raise__error_no_args.py +2 -0
  283. package/ref-monty/crates/monty/test_cases/execute_raise__error_string_arg.py +2 -0
  284. package/ref-monty/crates/monty/test_cases/execute_raise__error_string_arg_quotes.py +2 -0
  285. package/ref-monty/crates/monty/test_cases/execute_raise__error_type.py +2 -0
  286. package/ref-monty/crates/monty/test_cases/execute_raise__raise_instance_via_var.py +4 -0
  287. package/ref-monty/crates/monty/test_cases/execute_raise__raise_list.py +2 -0
  288. package/ref-monty/crates/monty/test_cases/execute_raise__raise_number.py +2 -0
  289. package/ref-monty/crates/monty/test_cases/execute_raise__raise_type_call_via_var.py +4 -0
  290. package/ref-monty/crates/monty/test_cases/execute_raise__raise_type_direct.py +3 -0
  291. package/ref-monty/crates/monty/test_cases/execute_raise__raise_type_via_var.py +4 -0
  292. package/ref-monty/crates/monty/test_cases/ext_call__arg_side_effect_bug.py +22 -0
  293. package/ref-monty/crates/monty/test_cases/ext_call__augmented.py +17 -0
  294. package/ref-monty/crates/monty/test_cases/ext_call__augmented_refcount_bug.py +7 -0
  295. package/ref-monty/crates/monty/test_cases/ext_call__bare_raise_after_resume.py +34 -0
  296. package/ref-monty/crates/monty/test_cases/ext_call__basic.py +99 -0
  297. package/ref-monty/crates/monty/test_cases/ext_call__boolean.py +37 -0
  298. package/ref-monty/crates/monty/test_cases/ext_call__boolean_side_effect_hang.py +17 -0
  299. package/ref-monty/crates/monty/test_cases/ext_call__closure_bug.py +16 -0
  300. package/ref-monty/crates/monty/test_cases/ext_call__comparison.py +26 -0
  301. package/ref-monty/crates/monty/test_cases/ext_call__deep_call_stack.py +18 -0
  302. package/ref-monty/crates/monty/test_cases/ext_call__elif.py +171 -0
  303. package/ref-monty/crates/monty/test_cases/ext_call__exc.py +4 -0
  304. package/ref-monty/crates/monty/test_cases/ext_call__exc_deep_stack.py +39 -0
  305. package/ref-monty/crates/monty/test_cases/ext_call__exc_in_function.py +17 -0
  306. package/ref-monty/crates/monty/test_cases/ext_call__exc_nested_functions.py +31 -0
  307. package/ref-monty/crates/monty/test_cases/ext_call__ext_exc.py +171 -0
  308. package/ref-monty/crates/monty/test_cases/ext_call__for.py +114 -0
  309. package/ref-monty/crates/monty/test_cases/ext_call__fstring.py +12 -0
  310. package/ref-monty/crates/monty/test_cases/ext_call__if.py +135 -0
  311. package/ref-monty/crates/monty/test_cases/ext_call__if_condition.py +37 -0
  312. package/ref-monty/crates/monty/test_cases/ext_call__in_closure.py +14 -0
  313. package/ref-monty/crates/monty/test_cases/ext_call__in_function.py +40 -0
  314. package/ref-monty/crates/monty/test_cases/ext_call__in_function_simple.py +7 -0
  315. package/ref-monty/crates/monty/test_cases/ext_call__literals.py +17 -0
  316. package/ref-monty/crates/monty/test_cases/ext_call__multi_in_func.py +32 -0
  317. package/ref-monty/crates/monty/test_cases/ext_call__name_lookup.py +69 -0
  318. package/ref-monty/crates/monty/test_cases/ext_call__name_lookup_undefined.py +4 -0
  319. package/ref-monty/crates/monty/test_cases/ext_call__nested_calls.py +14 -0
  320. package/ref-monty/crates/monty/test_cases/ext_call__recursion_bug.py +19 -0
  321. package/ref-monty/crates/monty/test_cases/ext_call__return.py +28 -0
  322. package/ref-monty/crates/monty/test_cases/ext_call__side_effects.py +25 -0
  323. package/ref-monty/crates/monty/test_cases/ext_call__subscript.py +7 -0
  324. package/ref-monty/crates/monty/test_cases/ext_call__ternary.py +28 -0
  325. package/ref-monty/crates/monty/test_cases/ext_call__try.py +280 -0
  326. package/ref-monty/crates/monty/test_cases/ext_call__try_simple.py +10 -0
  327. package/ref-monty/crates/monty/test_cases/ext_call__unary.py +13 -0
  328. package/ref-monty/crates/monty/test_cases/frozenset__ops.py +178 -0
  329. package/ref-monty/crates/monty/test_cases/fstring__all.py +236 -0
  330. package/ref-monty/crates/monty/test_cases/fstring__error_eq_align_on_str.py +3 -0
  331. package/ref-monty/crates/monty/test_cases/fstring__error_float_f_on_str.py +3 -0
  332. package/ref-monty/crates/monty/test_cases/fstring__error_int_d_on_float.py +3 -0
  333. package/ref-monty/crates/monty/test_cases/fstring__error_int_d_on_str.py +3 -0
  334. package/ref-monty/crates/monty/test_cases/fstring__error_invalid_spec.py +4 -0
  335. package/ref-monty/crates/monty/test_cases/fstring__error_invalid_spec_dynamic.py +4 -0
  336. package/ref-monty/crates/monty/test_cases/fstring__error_invalid_spec_str.py +4 -0
  337. package/ref-monty/crates/monty/test_cases/fstring__error_str_s_on_int.py +3 -0
  338. package/ref-monty/crates/monty/test_cases/function__call_duplicate_kwargs.py +6 -0
  339. package/ref-monty/crates/monty/test_cases/function__call_unpack.py +42 -0
  340. package/ref-monty/crates/monty/test_cases/function__defaults.py +117 -0
  341. package/ref-monty/crates/monty/test_cases/function__err_duplicate_arg.py +7 -0
  342. package/ref-monty/crates/monty/test_cases/function__err_duplicate_first_arg.py +7 -0
  343. package/ref-monty/crates/monty/test_cases/function__err_duplicate_kwarg_cleanup.py +9 -0
  344. package/ref-monty/crates/monty/test_cases/function__err_kwonly_as_positional.py +7 -0
  345. package/ref-monty/crates/monty/test_cases/function__err_missing_all_posonly.py +7 -0
  346. package/ref-monty/crates/monty/test_cases/function__err_missing_heap_cleanup.py +9 -0
  347. package/ref-monty/crates/monty/test_cases/function__err_missing_kwonly.py +7 -0
  348. package/ref-monty/crates/monty/test_cases/function__err_missing_posonly_with_kwarg.py +7 -0
  349. package/ref-monty/crates/monty/test_cases/function__err_missing_with_posonly.py +7 -0
  350. package/ref-monty/crates/monty/test_cases/function__err_posonly_as_kwarg.py +7 -0
  351. package/ref-monty/crates/monty/test_cases/function__err_posonly_first_as_kwarg.py +7 -0
  352. package/ref-monty/crates/monty/test_cases/function__err_too_many_posonly.py +7 -0
  353. package/ref-monty/crates/monty/test_cases/function__err_too_many_with_kwonly.py +7 -0
  354. package/ref-monty/crates/monty/test_cases/function__err_unexpected_kwarg.py +7 -0
  355. package/ref-monty/crates/monty/test_cases/function__err_unexpected_kwarg_cleanup.py +9 -0
  356. package/ref-monty/crates/monty/test_cases/function__err_unexpected_kwarg_quote.py +13 -0
  357. package/ref-monty/crates/monty/test_cases/function__err_unexpected_kwarg_simple.py +7 -0
  358. package/ref-monty/crates/monty/test_cases/function__err_unpack_duplicate_arg.py +6 -0
  359. package/ref-monty/crates/monty/test_cases/function__err_unpack_duplicate_heap.py +8 -0
  360. package/ref-monty/crates/monty/test_cases/function__err_unpack_int.py +6 -0
  361. package/ref-monty/crates/monty/test_cases/function__err_unpack_nonstring_key.py +6 -0
  362. package/ref-monty/crates/monty/test_cases/function__err_unpack_not_mapping.py +6 -0
  363. package/ref-monty/crates/monty/test_cases/function__kwargs_unpacking.py +173 -0
  364. package/ref-monty/crates/monty/test_cases/function__ops.py +294 -0
  365. package/ref-monty/crates/monty/test_cases/function__return_none.py +42 -0
  366. package/ref-monty/crates/monty/test_cases/function__signatures.py +47 -0
  367. package/ref-monty/crates/monty/test_cases/function__too_few_args_all.py +6 -0
  368. package/ref-monty/crates/monty/test_cases/function__too_few_args_one.py +6 -0
  369. package/ref-monty/crates/monty/test_cases/function__too_few_args_two.py +6 -0
  370. package/ref-monty/crates/monty/test_cases/function__too_many_args_one.py +6 -0
  371. package/ref-monty/crates/monty/test_cases/function__too_many_args_two.py +6 -0
  372. package/ref-monty/crates/monty/test_cases/function__too_many_args_zero.py +6 -0
  373. package/ref-monty/crates/monty/test_cases/global__error_assigned_before.py +7 -0
  374. package/ref-monty/crates/monty/test_cases/global__ops.py +163 -0
  375. package/ref-monty/crates/monty/test_cases/hash__dict_unhashable.py +2 -0
  376. package/ref-monty/crates/monty/test_cases/hash__list_unhashable.py +2 -0
  377. package/ref-monty/crates/monty/test_cases/hash__ops.py +153 -0
  378. package/ref-monty/crates/monty/test_cases/id__bytes_literals_distinct.py +3 -0
  379. package/ref-monty/crates/monty/test_cases/id__int_copy_distinct.py +5 -0
  380. package/ref-monty/crates/monty/test_cases/id__is_number_is_number.py +3 -0
  381. package/ref-monty/crates/monty/test_cases/id__non_overlapping_lifetimes_distinct_types.py +10 -0
  382. package/ref-monty/crates/monty/test_cases/id__non_overlapping_lifetimes_same_types.py +6 -0
  383. package/ref-monty/crates/monty/test_cases/id__ops.py +97 -0
  384. package/ref-monty/crates/monty/test_cases/id__str_literals_same.py +3 -0
  385. package/ref-monty/crates/monty/test_cases/if__elif_else.py +207 -0
  386. package/ref-monty/crates/monty/test_cases/if__raise_elif.py +11 -0
  387. package/ref-monty/crates/monty/test_cases/if__raise_else.py +13 -0
  388. package/ref-monty/crates/monty/test_cases/if__raise_if.py +9 -0
  389. package/ref-monty/crates/monty/test_cases/if__raise_in_elif_condition.py +18 -0
  390. package/ref-monty/crates/monty/test_cases/if__raise_in_if_condition.py +16 -0
  391. package/ref-monty/crates/monty/test_cases/if_else_expr__all.py +55 -0
  392. package/ref-monty/crates/monty/test_cases/import__error_cannot_import.py +9 -0
  393. package/ref-monty/crates/monty/test_cases/import__error_module_not_found.py +9 -0
  394. package/ref-monty/crates/monty/test_cases/import__local_scope.py +68 -0
  395. package/ref-monty/crates/monty/test_cases/import__os.py +25 -0
  396. package/ref-monty/crates/monty/test_cases/import__relative_error.py +9 -0
  397. package/ref-monty/crates/monty/test_cases/import__relative_no_module_error.py +9 -0
  398. package/ref-monty/crates/monty/test_cases/import__runtime_error_when_executed.py +14 -0
  399. package/ref-monty/crates/monty/test_cases/import__star_error.py +11 -0
  400. package/ref-monty/crates/monty/test_cases/import__sys.py +47 -0
  401. package/ref-monty/crates/monty/test_cases/import__sys_monty.py +28 -0
  402. package/ref-monty/crates/monty/test_cases/import__type_checking_guard.py +37 -0
  403. package/ref-monty/crates/monty/test_cases/import__typing.py +25 -0
  404. package/ref-monty/crates/monty/test_cases/import__typing_type_ignore.py +4 -0
  405. package/ref-monty/crates/monty/test_cases/int__bigint.py +467 -0
  406. package/ref-monty/crates/monty/test_cases/int__bigint_errors.py +260 -0
  407. package/ref-monty/crates/monty/test_cases/int__ops.py +219 -0
  408. package/ref-monty/crates/monty/test_cases/int__overflow_division.py +84 -0
  409. package/ref-monty/crates/monty/test_cases/is_variant__all.py +36 -0
  410. package/ref-monty/crates/monty/test_cases/isinstance__arg2_list_error.py +2 -0
  411. package/ref-monty/crates/monty/test_cases/isinstance__arg2_type_error.py +2 -0
  412. package/ref-monty/crates/monty/test_cases/iter__dict_mutation.py +4 -0
  413. package/ref-monty/crates/monty/test_cases/iter__for.py +243 -0
  414. package/ref-monty/crates/monty/test_cases/iter__for_loop_unpacking.py +66 -0
  415. package/ref-monty/crates/monty/test_cases/iter__generator_expr.py +20 -0
  416. package/ref-monty/crates/monty/test_cases/iter__generator_expr_type.py +7 -0
  417. package/ref-monty/crates/monty/test_cases/iter__not_iterable.py +3 -0
  418. package/ref-monty/crates/monty/test_cases/lambda__all.py +145 -0
  419. package/ref-monty/crates/monty/test_cases/list__extend_not_iterable.py +7 -0
  420. package/ref-monty/crates/monty/test_cases/list__getitem_out_of_bounds.py +3 -0
  421. package/ref-monty/crates/monty/test_cases/list__index_not_found.py +9 -0
  422. package/ref-monty/crates/monty/test_cases/list__index_start_gt_end.py +10 -0
  423. package/ref-monty/crates/monty/test_cases/list__ops.py +473 -0
  424. package/ref-monty/crates/monty/test_cases/list__pop_empty.py +9 -0
  425. package/ref-monty/crates/monty/test_cases/list__pop_out_of_range.py +9 -0
  426. package/ref-monty/crates/monty/test_cases/list__pop_type_error.py +9 -0
  427. package/ref-monty/crates/monty/test_cases/list__remove_not_found.py +9 -0
  428. package/ref-monty/crates/monty/test_cases/list__setitem_dict_index.py +13 -0
  429. package/ref-monty/crates/monty/test_cases/list__setitem_huge_int_index.py +13 -0
  430. package/ref-monty/crates/monty/test_cases/list__setitem_index_error.py +10 -0
  431. package/ref-monty/crates/monty/test_cases/list__setitem_type_error.py +10 -0
  432. package/ref-monty/crates/monty/test_cases/list__unpack_type_error.py +2 -0
  433. package/ref-monty/crates/monty/test_cases/longint__index_error.py +3 -0
  434. package/ref-monty/crates/monty/test_cases/longint__repeat_error.py +3 -0
  435. package/ref-monty/crates/monty/test_cases/loop__break_continue.py +113 -0
  436. package/ref-monty/crates/monty/test_cases/loop__break_finally.py +69 -0
  437. package/ref-monty/crates/monty/test_cases/loop__break_in_function_error.py +13 -0
  438. package/ref-monty/crates/monty/test_cases/loop__break_in_if_error.py +11 -0
  439. package/ref-monty/crates/monty/test_cases/loop__break_nested_except_clears.py +55 -0
  440. package/ref-monty/crates/monty/test_cases/loop__break_outside_error.py +9 -0
  441. package/ref-monty/crates/monty/test_cases/loop__continue_finally.py +81 -0
  442. package/ref-monty/crates/monty/test_cases/loop__continue_in_function_error.py +13 -0
  443. package/ref-monty/crates/monty/test_cases/loop__continue_in_if_error.py +11 -0
  444. package/ref-monty/crates/monty/test_cases/loop__continue_nested_except_clears.py +60 -0
  445. package/ref-monty/crates/monty/test_cases/loop__continue_outside_error.py +9 -0
  446. package/ref-monty/crates/monty/test_cases/math__acos_domain_error.py +11 -0
  447. package/ref-monty/crates/monty/test_cases/math__acosh_domain_error.py +11 -0
  448. package/ref-monty/crates/monty/test_cases/math__asin_domain_error.py +11 -0
  449. package/ref-monty/crates/monty/test_cases/math__atanh_domain_error.py +11 -0
  450. package/ref-monty/crates/monty/test_cases/math__cos_inf_error.py +11 -0
  451. package/ref-monty/crates/monty/test_cases/math__cosh_overflow_error.py +11 -0
  452. package/ref-monty/crates/monty/test_cases/math__exp_overflow_error.py +11 -0
  453. package/ref-monty/crates/monty/test_cases/math__factorial_float_error.py +11 -0
  454. package/ref-monty/crates/monty/test_cases/math__factorial_negative_error.py +11 -0
  455. package/ref-monty/crates/monty/test_cases/math__floor_inf_error.py +11 -0
  456. package/ref-monty/crates/monty/test_cases/math__floor_nan_error.py +11 -0
  457. package/ref-monty/crates/monty/test_cases/math__floor_str_error.py +11 -0
  458. package/ref-monty/crates/monty/test_cases/math__fmod_inf_error.py +11 -0
  459. package/ref-monty/crates/monty/test_cases/math__gamma_neg_int_error.py +11 -0
  460. package/ref-monty/crates/monty/test_cases/math__gcd_float_error.py +11 -0
  461. package/ref-monty/crates/monty/test_cases/math__isqrt_negative_error.py +11 -0
  462. package/ref-monty/crates/monty/test_cases/math__ldexp_overflow_error.py +11 -0
  463. package/ref-monty/crates/monty/test_cases/math__log1p_domain_error.py +11 -0
  464. package/ref-monty/crates/monty/test_cases/math__log_base1_error.py +11 -0
  465. package/ref-monty/crates/monty/test_cases/math__log_zero_error.py +11 -0
  466. package/ref-monty/crates/monty/test_cases/math__module.py +1432 -0
  467. package/ref-monty/crates/monty/test_cases/math__pow_domain_error.py +11 -0
  468. package/ref-monty/crates/monty/test_cases/math__sin_inf_error.py +11 -0
  469. package/ref-monty/crates/monty/test_cases/math__sqrt_negative_error.py +11 -0
  470. package/ref-monty/crates/monty/test_cases/math__tan_inf_error.py +11 -0
  471. package/ref-monty/crates/monty/test_cases/math__trunc_str_error.py +11 -0
  472. package/ref-monty/crates/monty/test_cases/method__args_kwargs_unpacking.py +259 -0
  473. package/ref-monty/crates/monty/test_cases/name_error__unbound_local_func.py +19 -0
  474. package/ref-monty/crates/monty/test_cases/name_error__unbound_local_module.py +12 -0
  475. package/ref-monty/crates/monty/test_cases/name_error__undefined_call_chained.py +9 -0
  476. package/ref-monty/crates/monty/test_cases/name_error__undefined_call_in_expr.py +9 -0
  477. package/ref-monty/crates/monty/test_cases/name_error__undefined_call_in_function.py +16 -0
  478. package/ref-monty/crates/monty/test_cases/name_error__undefined_call_with_args.py +9 -0
  479. package/ref-monty/crates/monty/test_cases/name_error__undefined_global.py +10 -0
  480. package/ref-monty/crates/monty/test_cases/namedtuple__missing_attr.py +11 -0
  481. package/ref-monty/crates/monty/test_cases/namedtuple__ops.py +34 -0
  482. package/ref-monty/crates/monty/test_cases/nonlocal__error_module_level.py +3 -0
  483. package/ref-monty/crates/monty/test_cases/nonlocal__ops.py +353 -0
  484. package/ref-monty/crates/monty/test_cases/os__environ.py +40 -0
  485. package/ref-monty/crates/monty/test_cases/os__getenv_key_list_error.py +5 -0
  486. package/ref-monty/crates/monty/test_cases/os__getenv_key_type_error.py +5 -0
  487. package/ref-monty/crates/monty/test_cases/parse_error__complex.py +3 -0
  488. package/ref-monty/crates/monty/test_cases/pathlib__import.py +11 -0
  489. package/ref-monty/crates/monty/test_cases/pathlib__os.py +136 -0
  490. package/ref-monty/crates/monty/test_cases/pathlib__os_read_error.py +12 -0
  491. package/ref-monty/crates/monty/test_cases/pathlib__pure.py +81 -0
  492. package/ref-monty/crates/monty/test_cases/pyobject__cycle_dict_self.py +5 -0
  493. package/ref-monty/crates/monty/test_cases/pyobject__cycle_list_dict.py +6 -0
  494. package/ref-monty/crates/monty/test_cases/pyobject__cycle_list_self.py +5 -0
  495. package/ref-monty/crates/monty/test_cases/pyobject__cycle_multiple_refs.py +6 -0
  496. package/ref-monty/crates/monty/test_cases/range__error_no_args.py +2 -0
  497. package/ref-monty/crates/monty/test_cases/range__error_step_zero.py +2 -0
  498. package/ref-monty/crates/monty/test_cases/range__error_too_many_args.py +2 -0
  499. package/ref-monty/crates/monty/test_cases/range__getitem_index_error.py +10 -0
  500. package/ref-monty/crates/monty/test_cases/range__ops.py +236 -0
  501. package/ref-monty/crates/monty/test_cases/re__basic.py +756 -0
  502. package/ref-monty/crates/monty/test_cases/re__grouping.py +241 -0
  503. package/ref-monty/crates/monty/test_cases/re__match.py +148 -0
  504. package/ref-monty/crates/monty/test_cases/recursion__deep_drop.py +26 -0
  505. package/ref-monty/crates/monty/test_cases/recursion__deep_eq.py +23 -0
  506. package/ref-monty/crates/monty/test_cases/recursion__deep_hash.py +46 -0
  507. package/ref-monty/crates/monty/test_cases/recursion__deep_repr.py +12 -0
  508. package/ref-monty/crates/monty/test_cases/recursion__function_depth.py +13 -0
  509. package/ref-monty/crates/monty/test_cases/refcount__cycle_mutual_reference.py +18 -0
  510. package/ref-monty/crates/monty/test_cases/refcount__cycle_self_reference.py +12 -0
  511. package/ref-monty/crates/monty/test_cases/refcount__dict_basic.py +5 -0
  512. package/ref-monty/crates/monty/test_cases/refcount__dict_get.py +5 -0
  513. package/ref-monty/crates/monty/test_cases/refcount__dict_keys_and.py +14 -0
  514. package/ref-monty/crates/monty/test_cases/refcount__dict_overwrite.py +6 -0
  515. package/ref-monty/crates/monty/test_cases/refcount__gather_cleanup.py +16 -0
  516. package/ref-monty/crates/monty/test_cases/refcount__gather_exception.py +18 -0
  517. package/ref-monty/crates/monty/test_cases/refcount__gather_nested_cancel.py +25 -0
  518. package/ref-monty/crates/monty/test_cases/refcount__immediate_skipped.py +4 -0
  519. package/ref-monty/crates/monty/test_cases/refcount__kwargs_unpacking.py +27 -0
  520. package/ref-monty/crates/monty/test_cases/refcount__list_append_multiple.py +6 -0
  521. package/ref-monty/crates/monty/test_cases/refcount__list_append_ref.py +5 -0
  522. package/ref-monty/crates/monty/test_cases/refcount__list_concat.py +5 -0
  523. package/ref-monty/crates/monty/test_cases/refcount__list_getitem.py +5 -0
  524. package/ref-monty/crates/monty/test_cases/refcount__list_iadd.py +5 -0
  525. package/ref-monty/crates/monty/test_cases/refcount__nested_list.py +4 -0
  526. package/ref-monty/crates/monty/test_cases/refcount__re_pattern_sub_error_paths.py +37 -0
  527. package/ref-monty/crates/monty/test_cases/refcount__re_search_match.py +34 -0
  528. package/ref-monty/crates/monty/test_cases/refcount__re_sub_error_paths.py +31 -0
  529. package/ref-monty/crates/monty/test_cases/refcount__shared_reference.py +4 -0
  530. package/ref-monty/crates/monty/test_cases/refcount__single_list.py +3 -0
  531. package/ref-monty/crates/monty/test_cases/repr__cycle_detection.py +24 -0
  532. package/ref-monty/crates/monty/test_cases/set__ops.py +191 -0
  533. package/ref-monty/crates/monty/test_cases/set__review_bugs.py +35 -0
  534. package/ref-monty/crates/monty/test_cases/set__unpack_type_error.py +2 -0
  535. package/ref-monty/crates/monty/test_cases/slice__invalid_indices.py +2 -0
  536. package/ref-monty/crates/monty/test_cases/slice__kwargs.py +9 -0
  537. package/ref-monty/crates/monty/test_cases/slice__no_args.py +9 -0
  538. package/ref-monty/crates/monty/test_cases/slice__ops.py +149 -0
  539. package/ref-monty/crates/monty/test_cases/slice__step_zero.py +9 -0
  540. package/ref-monty/crates/monty/test_cases/slice__step_zero_bytes.py +9 -0
  541. package/ref-monty/crates/monty/test_cases/slice__step_zero_range.py +9 -0
  542. package/ref-monty/crates/monty/test_cases/slice__step_zero_str.py +9 -0
  543. package/ref-monty/crates/monty/test_cases/slice__step_zero_tuple.py +9 -0
  544. package/ref-monty/crates/monty/test_cases/slice__too_many_args.py +9 -0
  545. package/ref-monty/crates/monty/test_cases/str__getitem_index_error.py +10 -0
  546. package/ref-monty/crates/monty/test_cases/str__index_not_found.py +9 -0
  547. package/ref-monty/crates/monty/test_cases/str__join_no_args.py +9 -0
  548. package/ref-monty/crates/monty/test_cases/str__join_non_string.py +9 -0
  549. package/ref-monty/crates/monty/test_cases/str__join_not_iterable.py +9 -0
  550. package/ref-monty/crates/monty/test_cases/str__join_too_many_args.py +9 -0
  551. package/ref-monty/crates/monty/test_cases/str__methods.py +327 -0
  552. package/ref-monty/crates/monty/test_cases/str__ops.py +162 -0
  553. package/ref-monty/crates/monty/test_cases/str__partition_empty.py +9 -0
  554. package/ref-monty/crates/monty/test_cases/str__rsplit_empty_sep.py +9 -0
  555. package/ref-monty/crates/monty/test_cases/str__split_empty_sep.py +9 -0
  556. package/ref-monty/crates/monty/test_cases/sys__types.py +7 -0
  557. package/ref-monty/crates/monty/test_cases/traceback__division_error.py +30 -0
  558. package/ref-monty/crates/monty/test_cases/traceback__index_error.py +17 -0
  559. package/ref-monty/crates/monty/test_cases/traceback__insert_as_int.py +10 -0
  560. package/ref-monty/crates/monty/test_cases/traceback__nested_call.py +29 -0
  561. package/ref-monty/crates/monty/test_cases/traceback__nonlocal_module_scope.py +10 -0
  562. package/ref-monty/crates/monty/test_cases/traceback__nonlocal_unbound.py +24 -0
  563. package/ref-monty/crates/monty/test_cases/traceback__range_as_int.py +9 -0
  564. package/ref-monty/crates/monty/test_cases/traceback__recursion_error.py +23 -0
  565. package/ref-monty/crates/monty/test_cases/traceback__set_mutation.py +11 -0
  566. package/ref-monty/crates/monty/test_cases/traceback__undefined_attr_call.py +16 -0
  567. package/ref-monty/crates/monty/test_cases/traceback__undefined_call.py +16 -0
  568. package/ref-monty/crates/monty/test_cases/traceback__undefined_raise.py +16 -0
  569. package/ref-monty/crates/monty/test_cases/try_except__all.py +472 -0
  570. package/ref-monty/crates/monty/test_cases/try_except__bare_raise_no_context.py +2 -0
  571. package/ref-monty/crates/monty/test_cases/try_except__invalid_type.py +5 -0
  572. package/ref-monty/crates/monty/test_cases/tuple__getitem_out_of_bounds.py +3 -0
  573. package/ref-monty/crates/monty/test_cases/tuple__index_not_found.py +9 -0
  574. package/ref-monty/crates/monty/test_cases/tuple__index_start_gt_end.py +10 -0
  575. package/ref-monty/crates/monty/test_cases/tuple__methods.py +19 -0
  576. package/ref-monty/crates/monty/test_cases/tuple__ops.py +133 -0
  577. package/ref-monty/crates/monty/test_cases/tuple__unpack_type_error.py +2 -0
  578. package/ref-monty/crates/monty/test_cases/type__builtin_attr_error.py +9 -0
  579. package/ref-monty/crates/monty/test_cases/type__bytes_negative.py +2 -0
  580. package/ref-monty/crates/monty/test_cases/type__cell_not_builtin.py +9 -0
  581. package/ref-monty/crates/monty/test_cases/type__exception_attr_error.py +11 -0
  582. package/ref-monty/crates/monty/test_cases/type__float_conversion_error.py +2 -0
  583. package/ref-monty/crates/monty/test_cases/type__float_repr_both_quotes.py +9 -0
  584. package/ref-monty/crates/monty/test_cases/type__float_repr_newline.py +9 -0
  585. package/ref-monty/crates/monty/test_cases/type__float_repr_single_quote.py +9 -0
  586. package/ref-monty/crates/monty/test_cases/type__int_conversion_error.py +2 -0
  587. package/ref-monty/crates/monty/test_cases/type__list_not_iterable.py +2 -0
  588. package/ref-monty/crates/monty/test_cases/type__non_builtin_name_error.py +9 -0
  589. package/ref-monty/crates/monty/test_cases/type__ops.py +200 -0
  590. package/ref-monty/crates/monty/test_cases/type__shadow_exc.py +3 -0
  591. package/ref-monty/crates/monty/test_cases/type__shadow_int.py +9 -0
  592. package/ref-monty/crates/monty/test_cases/type__shadow_len.py +3 -0
  593. package/ref-monty/crates/monty/test_cases/type__tuple_not_iterable.py +2 -0
  594. package/ref-monty/crates/monty/test_cases/type_error__int_add_list.py +2 -0
  595. package/ref-monty/crates/monty/test_cases/type_error__int_div_str.py +2 -0
  596. package/ref-monty/crates/monty/test_cases/type_error__int_floordiv_str.py +2 -0
  597. package/ref-monty/crates/monty/test_cases/type_error__int_iadd_str.py +3 -0
  598. package/ref-monty/crates/monty/test_cases/type_error__int_mod_str.py +2 -0
  599. package/ref-monty/crates/monty/test_cases/type_error__int_pow_str.py +2 -0
  600. package/ref-monty/crates/monty/test_cases/type_error__int_sub_str.py +2 -0
  601. package/ref-monty/crates/monty/test_cases/type_error__list_add_int.py +2 -0
  602. package/ref-monty/crates/monty/test_cases/type_error__list_add_str.py +2 -0
  603. package/ref-monty/crates/monty/test_cases/type_error__list_iadd_int.py +6 -0
  604. package/ref-monty/crates/monty/test_cases/type_error__str_add_int.py +2 -0
  605. package/ref-monty/crates/monty/test_cases/type_error__str_iadd_int.py +3 -0
  606. package/ref-monty/crates/monty/test_cases/type_error__unary_invert_str.py +3 -0
  607. package/ref-monty/crates/monty/test_cases/type_error__unary_minus_str.py +4 -0
  608. package/ref-monty/crates/monty/test_cases/type_error__unary_neg_str.py +3 -0
  609. package/ref-monty/crates/monty/test_cases/type_error__unary_plus_str.py +4 -0
  610. package/ref-monty/crates/monty/test_cases/typing__types.py +24 -0
  611. package/ref-monty/crates/monty/test_cases/unpack__nested.py +48 -0
  612. package/ref-monty/crates/monty/test_cases/unpack__non_sequence.py +9 -0
  613. package/ref-monty/crates/monty/test_cases/unpack__not_enough.py +9 -0
  614. package/ref-monty/crates/monty/test_cases/unpack__ops.py +153 -0
  615. package/ref-monty/crates/monty/test_cases/unpack__star_not_enough.py +9 -0
  616. package/ref-monty/crates/monty/test_cases/unpack__too_many.py +9 -0
  617. package/ref-monty/crates/monty/test_cases/version__cpython.py +4 -0
  618. package/ref-monty/crates/monty/test_cases/walrus__all.py +178 -0
  619. package/ref-monty/crates/monty/test_cases/while__all.py +206 -0
  620. package/ref-monty/crates/monty/tests/asyncio.rs +764 -0
  621. package/ref-monty/crates/monty/tests/binary_serde.rs +185 -0
  622. package/ref-monty/crates/monty/tests/bytecode_limits.rs +248 -0
  623. package/ref-monty/crates/monty/tests/datatest_runner.rs +2029 -0
  624. package/ref-monty/crates/monty/tests/inputs.rs +420 -0
  625. package/ref-monty/crates/monty/tests/json_serde.rs +250 -0
  626. package/ref-monty/crates/monty/tests/main.rs +71 -0
  627. package/ref-monty/crates/monty/tests/math_module.rs +114 -0
  628. package/ref-monty/crates/monty/tests/name_lookup.rs +482 -0
  629. package/ref-monty/crates/monty/tests/os_tests.rs +459 -0
  630. package/ref-monty/crates/monty/tests/parse_errors.rs +441 -0
  631. package/ref-monty/crates/monty/tests/print_writer.rs +238 -0
  632. package/ref-monty/crates/monty/tests/py_object.rs +121 -0
  633. package/ref-monty/crates/monty/tests/regex.rs +90 -0
  634. package/ref-monty/crates/monty/tests/repl.rs +344 -0
  635. package/ref-monty/crates/monty/tests/resource_limits.rs +1826 -0
  636. package/ref-monty/crates/monty/tests/try_from.rs +167 -0
  637. package/ref-monty/crates/monty-cli/Cargo.toml +25 -0
  638. package/ref-monty/crates/monty-cli/src/main.rs +541 -0
  639. package/ref-monty/crates/monty-js/.cargo/config.toml +2 -0
  640. package/ref-monty/crates/monty-js/.prettierignore +8 -0
  641. package/ref-monty/crates/monty-js/Cargo.toml +32 -0
  642. package/ref-monty/crates/monty-js/README.md +207 -0
  643. package/ref-monty/crates/monty-js/__test__/async.spec.ts +350 -0
  644. package/ref-monty/crates/monty-js/__test__/basic.spec.ts +114 -0
  645. package/ref-monty/crates/monty-js/__test__/exceptions.spec.ts +427 -0
  646. package/ref-monty/crates/monty-js/__test__/external.spec.ts +354 -0
  647. package/ref-monty/crates/monty-js/__test__/inputs.spec.ts +143 -0
  648. package/ref-monty/crates/monty-js/__test__/limits.spec.ts +162 -0
  649. package/ref-monty/crates/monty-js/__test__/package.json +3 -0
  650. package/ref-monty/crates/monty-js/__test__/print.spec.ts +229 -0
  651. package/ref-monty/crates/monty-js/__test__/repl.spec.ts +34 -0
  652. package/ref-monty/crates/monty-js/__test__/serialize.spec.ts +205 -0
  653. package/ref-monty/crates/monty-js/__test__/start.spec.ts +443 -0
  654. package/ref-monty/crates/monty-js/__test__/type_check.spec.ts +147 -0
  655. package/ref-monty/crates/monty-js/__test__/types.spec.ts +319 -0
  656. package/ref-monty/crates/monty-js/build.rs +61 -0
  657. package/ref-monty/crates/monty-js/index-header.d.ts +3 -0
  658. package/ref-monty/crates/monty-js/package-lock.json +4694 -0
  659. package/ref-monty/crates/monty-js/package.json +100 -0
  660. package/ref-monty/crates/monty-js/scripts/smoke-test.sh +69 -0
  661. package/ref-monty/crates/monty-js/smoke-test/package.json +17 -0
  662. package/ref-monty/crates/monty-js/smoke-test/test.ts +171 -0
  663. package/ref-monty/crates/monty-js/smoke-test/tsconfig.json +11 -0
  664. package/ref-monty/crates/monty-js/src/convert.rs +648 -0
  665. package/ref-monty/crates/monty-js/src/exceptions.rs +293 -0
  666. package/ref-monty/crates/monty-js/src/lib.rs +41 -0
  667. package/ref-monty/crates/monty-js/src/limits.rs +53 -0
  668. package/ref-monty/crates/monty-js/src/monty_cls.rs +1407 -0
  669. package/ref-monty/crates/monty-js/tsconfig.json +17 -0
  670. package/ref-monty/crates/monty-js/wrapper.ts +701 -0
  671. package/ref-monty/crates/monty-python/Cargo.toml +38 -0
  672. package/ref-monty/crates/monty-python/README.md +134 -0
  673. package/ref-monty/crates/monty-python/build.rs +4 -0
  674. package/ref-monty/crates/monty-python/example.py +40 -0
  675. package/ref-monty/crates/monty-python/exercise.py +46 -0
  676. package/ref-monty/crates/monty-python/pyproject.toml +57 -0
  677. package/ref-monty/crates/monty-python/python/pydantic_monty/__init__.py +281 -0
  678. package/ref-monty/crates/monty-python/python/pydantic_monty/_monty.pyi +677 -0
  679. package/ref-monty/crates/monty-python/python/pydantic_monty/os_access.py +933 -0
  680. package/ref-monty/crates/monty-python/python/pydantic_monty/py.typed +0 -0
  681. package/ref-monty/crates/monty-python/src/convert.rs +273 -0
  682. package/ref-monty/crates/monty-python/src/dataclass.rs +461 -0
  683. package/ref-monty/crates/monty-python/src/exceptions.rs +557 -0
  684. package/ref-monty/crates/monty-python/src/external.rs +165 -0
  685. package/ref-monty/crates/monty-python/src/lib.rs +77 -0
  686. package/ref-monty/crates/monty-python/src/limits.rs +142 -0
  687. package/ref-monty/crates/monty-python/src/monty_cls.rs +1650 -0
  688. package/ref-monty/crates/monty-python/src/repl.rs +470 -0
  689. package/ref-monty/crates/monty-python/src/serialization.rs +761 -0
  690. package/ref-monty/crates/monty-python/tests/test_async.py +1201 -0
  691. package/ref-monty/crates/monty-python/tests/test_basic.py +66 -0
  692. package/ref-monty/crates/monty-python/tests/test_dataclasses.py +971 -0
  693. package/ref-monty/crates/monty-python/tests/test_exceptions.py +361 -0
  694. package/ref-monty/crates/monty-python/tests/test_external.py +367 -0
  695. package/ref-monty/crates/monty-python/tests/test_inputs.py +126 -0
  696. package/ref-monty/crates/monty-python/tests/test_limits.py +257 -0
  697. package/ref-monty/crates/monty-python/tests/test_os_access.py +1286 -0
  698. package/ref-monty/crates/monty-python/tests/test_os_access_compat.py +731 -0
  699. package/ref-monty/crates/monty-python/tests/test_os_access_raw.py +483 -0
  700. package/ref-monty/crates/monty-python/tests/test_os_calls.py +819 -0
  701. package/ref-monty/crates/monty-python/tests/test_print.py +208 -0
  702. package/ref-monty/crates/monty-python/tests/test_re.py +170 -0
  703. package/ref-monty/crates/monty-python/tests/test_readme_examples.py +20 -0
  704. package/ref-monty/crates/monty-python/tests/test_repl.py +749 -0
  705. package/ref-monty/crates/monty-python/tests/test_serialize.py +284 -0
  706. package/ref-monty/crates/monty-python/tests/test_start.py +346 -0
  707. package/ref-monty/crates/monty-python/tests/test_threading.py +163 -0
  708. package/ref-monty/crates/monty-python/tests/test_type_check.py +344 -0
  709. package/ref-monty/crates/monty-python/tests/test_types.py +553 -0
  710. package/ref-monty/crates/monty-type-checking/Cargo.toml +32 -0
  711. package/ref-monty/crates/monty-type-checking/src/db.rs +116 -0
  712. package/ref-monty/crates/monty-type-checking/src/lib.rs +4 -0
  713. package/ref-monty/crates/monty-type-checking/src/type_check.rs +280 -0
  714. package/ref-monty/crates/monty-type-checking/tests/bad_types.py +109 -0
  715. package/ref-monty/crates/monty-type-checking/tests/bad_types_output.txt +21 -0
  716. package/ref-monty/crates/monty-type-checking/tests/good_types.py +475 -0
  717. package/ref-monty/crates/monty-type-checking/tests/main.rs +205 -0
  718. package/ref-monty/crates/monty-type-checking/tests/reveal_types.py +56 -0
  719. package/ref-monty/crates/monty-type-checking/tests/reveal_types_output.txt +41 -0
  720. package/ref-monty/crates/monty-typeshed/Cargo.toml +29 -0
  721. package/ref-monty/crates/monty-typeshed/README.md +11 -0
  722. package/ref-monty/crates/monty-typeshed/build.rs +101 -0
  723. package/ref-monty/crates/monty-typeshed/custom/README.md +1 -0
  724. package/ref-monty/crates/monty-typeshed/custom/asyncio.pyi +138 -0
  725. package/ref-monty/crates/monty-typeshed/custom/os.pyi +87 -0
  726. package/ref-monty/crates/monty-typeshed/custom/sys.pyi +33 -0
  727. package/ref-monty/crates/monty-typeshed/src/lib.rs +56 -0
  728. package/ref-monty/crates/monty-typeshed/update.py +321 -0
  729. package/ref-monty/crates/monty-typeshed/vendor/typeshed/source_commit.txt +1 -0
  730. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/VERSIONS +20 -0
  731. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/_collections_abc.pyi +105 -0
  732. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/_typeshed/__init__.pyi +394 -0
  733. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/asyncio.pyi +138 -0
  734. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/builtins.pyi +1434 -0
  735. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/collections/__init__.pyi +527 -0
  736. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/collections/abc.pyi +2 -0
  737. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/dataclasses.pyi +502 -0
  738. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/enum.pyi +376 -0
  739. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/math.pyi +149 -0
  740. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/os.pyi +87 -0
  741. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/pathlib/__init__.pyi +395 -0
  742. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/pathlib/types.pyi +8 -0
  743. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/re.pyi +337 -0
  744. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/sys.pyi +33 -0
  745. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/types.pyi +741 -0
  746. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/typing.pyi +1217 -0
  747. package/ref-monty/crates/monty-typeshed/vendor/typeshed/stdlib/typing_extensions.pyi +716 -0
  748. package/ref-monty/docs/usage-guide.md +117 -0
  749. package/ref-monty/examples/README.md +3 -0
  750. package/ref-monty/examples/expense_analysis/README.md +3 -0
  751. package/ref-monty/examples/expense_analysis/data.py +124 -0
  752. package/ref-monty/examples/expense_analysis/main.py +115 -0
  753. package/ref-monty/examples/sql_playground/README.md +20 -0
  754. package/ref-monty/examples/sql_playground/external_functions.py +129 -0
  755. package/ref-monty/examples/sql_playground/main.py +81 -0
  756. package/ref-monty/examples/sql_playground/sandbox_code.py +82 -0
  757. package/ref-monty/examples/sql_playground/type_stubs.pyi +14 -0
  758. package/ref-monty/examples/web_scraper/README.md +15 -0
  759. package/ref-monty/examples/web_scraper/browser.py +56 -0
  760. package/ref-monty/examples/web_scraper/example_code.py +59 -0
  761. package/ref-monty/examples/web_scraper/external_functions.py +324 -0
  762. package/ref-monty/examples/web_scraper/main.py +193 -0
  763. package/ref-monty/examples/web_scraper/sub_agent.py +79 -0
  764. package/ref-monty/monty-npm.md +235 -0
  765. package/ref-monty/pyproject.toml +162 -0
  766. package/ref-monty/scripts/check_imports.py +91 -0
  767. package/ref-monty/scripts/codecov_diff.py +412 -0
  768. package/ref-monty/scripts/complete_tests.py +146 -0
  769. package/ref-monty/scripts/flamegraph_to_text.py +208 -0
  770. package/ref-monty/scripts/iter_test_methods.py +540 -0
  771. package/ref-monty/scripts/run_traceback.py +180 -0
  772. package/ref-monty/scripts/startup_performance.py +130 -0
  773. package/ref-monty/uv.lock +1779 -0
  774. package/temp_resend_cli/repo/.github/scripts/pr-title-check.js +34 -0
  775. package/temp_resend_cli/repo/.github/workflows/ci.yml +67 -0
  776. package/temp_resend_cli/repo/.github/workflows/post-release.yml +51 -0
  777. package/temp_resend_cli/repo/.github/workflows/pr-title-check.yml +13 -0
  778. package/temp_resend_cli/repo/.github/workflows/release.yml +175 -0
  779. package/temp_resend_cli/repo/.github/workflows/test-install-unix.yml +34 -0
  780. package/temp_resend_cli/repo/.github/workflows/test-install-windows.yml +48 -0
  781. package/temp_resend_cli/repo/CHANGELOG.md +31 -0
  782. package/temp_resend_cli/repo/LICENSE +21 -0
  783. package/temp_resend_cli/repo/README.md +450 -0
  784. package/temp_resend_cli/repo/biome.json +36 -0
  785. package/temp_resend_cli/repo/install.ps1 +141 -0
  786. package/temp_resend_cli/repo/install.sh +301 -0
  787. package/temp_resend_cli/repo/package.json +61 -0
  788. package/temp_resend_cli/repo/pnpm-lock.yaml +2439 -0
  789. package/temp_resend_cli/repo/renovate.json +4 -0
  790. package/temp_resend_cli/repo/src/cli.ts +98 -0
  791. package/temp_resend_cli/repo/src/commands/api-keys/create.ts +114 -0
  792. package/temp_resend_cli/repo/src/commands/api-keys/delete.ts +47 -0
  793. package/temp_resend_cli/repo/src/commands/api-keys/index.ts +26 -0
  794. package/temp_resend_cli/repo/src/commands/api-keys/list.ts +35 -0
  795. package/temp_resend_cli/repo/src/commands/api-keys/utils.ts +8 -0
  796. package/temp_resend_cli/repo/src/commands/auth/index.ts +20 -0
  797. package/temp_resend_cli/repo/src/commands/auth/login.ts +234 -0
  798. package/temp_resend_cli/repo/src/commands/auth/logout.ts +105 -0
  799. package/temp_resend_cli/repo/src/commands/broadcasts/create.ts +196 -0
  800. package/temp_resend_cli/repo/src/commands/broadcasts/delete.ts +46 -0
  801. package/temp_resend_cli/repo/src/commands/broadcasts/get.ts +59 -0
  802. package/temp_resend_cli/repo/src/commands/broadcasts/index.ts +43 -0
  803. package/temp_resend_cli/repo/src/commands/broadcasts/list.ts +60 -0
  804. package/temp_resend_cli/repo/src/commands/broadcasts/send.ts +56 -0
  805. package/temp_resend_cli/repo/src/commands/broadcasts/update.ts +95 -0
  806. package/temp_resend_cli/repo/src/commands/broadcasts/utils.ts +35 -0
  807. package/temp_resend_cli/repo/src/commands/contact-properties/create.ts +118 -0
  808. package/temp_resend_cli/repo/src/commands/contact-properties/delete.ts +48 -0
  809. package/temp_resend_cli/repo/src/commands/contact-properties/get.ts +46 -0
  810. package/temp_resend_cli/repo/src/commands/contact-properties/index.ts +48 -0
  811. package/temp_resend_cli/repo/src/commands/contact-properties/list.ts +68 -0
  812. package/temp_resend_cli/repo/src/commands/contact-properties/update.ts +88 -0
  813. package/temp_resend_cli/repo/src/commands/contact-properties/utils.ts +17 -0
  814. package/temp_resend_cli/repo/src/commands/contacts/add-segment.ts +78 -0
  815. package/temp_resend_cli/repo/src/commands/contacts/create.ts +122 -0
  816. package/temp_resend_cli/repo/src/commands/contacts/delete.ts +49 -0
  817. package/temp_resend_cli/repo/src/commands/contacts/get.ts +53 -0
  818. package/temp_resend_cli/repo/src/commands/contacts/index.ts +58 -0
  819. package/temp_resend_cli/repo/src/commands/contacts/list.ts +57 -0
  820. package/temp_resend_cli/repo/src/commands/contacts/remove-segment.ts +48 -0
  821. package/temp_resend_cli/repo/src/commands/contacts/segments.ts +39 -0
  822. package/temp_resend_cli/repo/src/commands/contacts/topics.ts +45 -0
  823. package/temp_resend_cli/repo/src/commands/contacts/update-topics.ts +90 -0
  824. package/temp_resend_cli/repo/src/commands/contacts/update.ts +77 -0
  825. package/temp_resend_cli/repo/src/commands/contacts/utils.ts +119 -0
  826. package/temp_resend_cli/repo/src/commands/doctor.ts +216 -0
  827. package/temp_resend_cli/repo/src/commands/domains/create.ts +83 -0
  828. package/temp_resend_cli/repo/src/commands/domains/delete.ts +42 -0
  829. package/temp_resend_cli/repo/src/commands/domains/get.ts +47 -0
  830. package/temp_resend_cli/repo/src/commands/domains/index.ts +35 -0
  831. package/temp_resend_cli/repo/src/commands/domains/list.ts +53 -0
  832. package/temp_resend_cli/repo/src/commands/domains/update.ts +75 -0
  833. package/temp_resend_cli/repo/src/commands/domains/utils.ts +44 -0
  834. package/temp_resend_cli/repo/src/commands/domains/verify.ts +38 -0
  835. package/temp_resend_cli/repo/src/commands/emails/batch.ts +140 -0
  836. package/temp_resend_cli/repo/src/commands/emails/get.ts +44 -0
  837. package/temp_resend_cli/repo/src/commands/emails/index.ts +30 -0
  838. package/temp_resend_cli/repo/src/commands/emails/list.ts +84 -0
  839. package/temp_resend_cli/repo/src/commands/emails/receiving/attachment.ts +55 -0
  840. package/temp_resend_cli/repo/src/commands/emails/receiving/attachments.ts +68 -0
  841. package/temp_resend_cli/repo/src/commands/emails/receiving/get.ts +58 -0
  842. package/temp_resend_cli/repo/src/commands/emails/receiving/index.ts +28 -0
  843. package/temp_resend_cli/repo/src/commands/emails/receiving/list.ts +59 -0
  844. package/temp_resend_cli/repo/src/commands/emails/receiving/utils.ts +38 -0
  845. package/temp_resend_cli/repo/src/commands/emails/send.ts +189 -0
  846. package/temp_resend_cli/repo/src/commands/open.ts +27 -0
  847. package/temp_resend_cli/repo/src/commands/segments/create.ts +50 -0
  848. package/temp_resend_cli/repo/src/commands/segments/delete.ts +47 -0
  849. package/temp_resend_cli/repo/src/commands/segments/get.ts +38 -0
  850. package/temp_resend_cli/repo/src/commands/segments/index.ts +36 -0
  851. package/temp_resend_cli/repo/src/commands/segments/list.ts +58 -0
  852. package/temp_resend_cli/repo/src/commands/segments/utils.ts +7 -0
  853. package/temp_resend_cli/repo/src/commands/teams/index.ts +10 -0
  854. package/temp_resend_cli/repo/src/commands/teams/list.ts +35 -0
  855. package/temp_resend_cli/repo/src/commands/teams/remove.ts +86 -0
  856. package/temp_resend_cli/repo/src/commands/teams/switch.ts +76 -0
  857. package/temp_resend_cli/repo/src/commands/topics/create.ts +73 -0
  858. package/temp_resend_cli/repo/src/commands/topics/delete.ts +47 -0
  859. package/temp_resend_cli/repo/src/commands/topics/get.ts +42 -0
  860. package/temp_resend_cli/repo/src/commands/topics/index.ts +42 -0
  861. package/temp_resend_cli/repo/src/commands/topics/list.ts +34 -0
  862. package/temp_resend_cli/repo/src/commands/topics/update.ts +59 -0
  863. package/temp_resend_cli/repo/src/commands/topics/utils.ts +16 -0
  864. package/temp_resend_cli/repo/src/commands/webhooks/create.ts +128 -0
  865. package/temp_resend_cli/repo/src/commands/webhooks/delete.ts +49 -0
  866. package/temp_resend_cli/repo/src/commands/webhooks/get.ts +42 -0
  867. package/temp_resend_cli/repo/src/commands/webhooks/index.ts +42 -0
  868. package/temp_resend_cli/repo/src/commands/webhooks/list.ts +55 -0
  869. package/temp_resend_cli/repo/src/commands/webhooks/listen.ts +379 -0
  870. package/temp_resend_cli/repo/src/commands/webhooks/update.ts +83 -0
  871. package/temp_resend_cli/repo/src/commands/webhooks/utils.ts +36 -0
  872. package/temp_resend_cli/repo/src/commands/whoami.ts +71 -0
  873. package/temp_resend_cli/repo/src/lib/actions.ts +157 -0
  874. package/temp_resend_cli/repo/src/lib/client.ts +37 -0
  875. package/temp_resend_cli/repo/src/lib/config.ts +217 -0
  876. package/temp_resend_cli/repo/src/lib/files.ts +15 -0
  877. package/temp_resend_cli/repo/src/lib/help-text.ts +38 -0
  878. package/temp_resend_cli/repo/src/lib/output.ts +56 -0
  879. package/temp_resend_cli/repo/src/lib/pagination.ts +36 -0
  880. package/temp_resend_cli/repo/src/lib/prompts.ts +149 -0
  881. package/temp_resend_cli/repo/src/lib/spinner.ts +100 -0
  882. package/temp_resend_cli/repo/src/lib/table.ts +57 -0
  883. package/temp_resend_cli/repo/src/lib/tty.ts +28 -0
  884. package/temp_resend_cli/repo/src/lib/update-check.ts +169 -0
  885. package/temp_resend_cli/repo/src/lib/version.ts +4 -0
  886. package/temp_resend_cli/repo/tests/commands/api-keys/create.test.ts +196 -0
  887. package/temp_resend_cli/repo/tests/commands/api-keys/delete.test.ts +157 -0
  888. package/temp_resend_cli/repo/tests/commands/api-keys/list.test.ts +134 -0
  889. package/temp_resend_cli/repo/tests/commands/auth/login.test.ts +153 -0
  890. package/temp_resend_cli/repo/tests/commands/auth/logout.test.ts +153 -0
  891. package/temp_resend_cli/repo/tests/commands/broadcasts/create.test.ts +454 -0
  892. package/temp_resend_cli/repo/tests/commands/broadcasts/delete.test.ts +183 -0
  893. package/temp_resend_cli/repo/tests/commands/broadcasts/get.test.ts +147 -0
  894. package/temp_resend_cli/repo/tests/commands/broadcasts/list.test.ts +199 -0
  895. package/temp_resend_cli/repo/tests/commands/broadcasts/send.test.ts +162 -0
  896. package/temp_resend_cli/repo/tests/commands/broadcasts/update.test.ts +288 -0
  897. package/temp_resend_cli/repo/tests/commands/contact-properties/create.test.ts +251 -0
  898. package/temp_resend_cli/repo/tests/commands/contact-properties/delete.test.ts +184 -0
  899. package/temp_resend_cli/repo/tests/commands/contact-properties/get.test.ts +145 -0
  900. package/temp_resend_cli/repo/tests/commands/contact-properties/list.test.ts +181 -0
  901. package/temp_resend_cli/repo/tests/commands/contact-properties/update.test.ts +217 -0
  902. package/temp_resend_cli/repo/tests/commands/contacts/add-segment.test.ts +189 -0
  903. package/temp_resend_cli/repo/tests/commands/contacts/create.test.ts +271 -0
  904. package/temp_resend_cli/repo/tests/commands/contacts/delete.test.ts +193 -0
  905. package/temp_resend_cli/repo/tests/commands/contacts/get.test.ts +149 -0
  906. package/temp_resend_cli/repo/tests/commands/contacts/list.test.ts +176 -0
  907. package/temp_resend_cli/repo/tests/commands/contacts/remove-segment.test.ts +167 -0
  908. package/temp_resend_cli/repo/tests/commands/contacts/segments.test.ts +168 -0
  909. package/temp_resend_cli/repo/tests/commands/contacts/topics.test.ts +164 -0
  910. package/temp_resend_cli/repo/tests/commands/contacts/update-topics.test.ts +248 -0
  911. package/temp_resend_cli/repo/tests/commands/contacts/update.test.ts +206 -0
  912. package/temp_resend_cli/repo/tests/commands/doctor.test.ts +164 -0
  913. package/temp_resend_cli/repo/tests/commands/domains/create.test.ts +193 -0
  914. package/temp_resend_cli/repo/tests/commands/domains/delete.test.ts +157 -0
  915. package/temp_resend_cli/repo/tests/commands/domains/get.test.ts +138 -0
  916. package/temp_resend_cli/repo/tests/commands/domains/list.test.ts +165 -0
  917. package/temp_resend_cli/repo/tests/commands/domains/update.test.ts +224 -0
  918. package/temp_resend_cli/repo/tests/commands/domains/verify.test.ts +118 -0
  919. package/temp_resend_cli/repo/tests/commands/emails/batch.test.ts +324 -0
  920. package/temp_resend_cli/repo/tests/commands/emails/get.test.ts +132 -0
  921. package/temp_resend_cli/repo/tests/commands/emails/receiving/attachment.test.ts +141 -0
  922. package/temp_resend_cli/repo/tests/commands/emails/receiving/attachments.test.ts +169 -0
  923. package/temp_resend_cli/repo/tests/commands/emails/receiving/get.test.ts +141 -0
  924. package/temp_resend_cli/repo/tests/commands/emails/receiving/list.test.ts +182 -0
  925. package/temp_resend_cli/repo/tests/commands/emails/send.test.ts +312 -0
  926. package/temp_resend_cli/repo/tests/commands/segments/create.test.ts +164 -0
  927. package/temp_resend_cli/repo/tests/commands/segments/delete.test.ts +183 -0
  928. package/temp_resend_cli/repo/tests/commands/segments/get.test.ts +138 -0
  929. package/temp_resend_cli/repo/tests/commands/segments/list.test.ts +174 -0
  930. package/temp_resend_cli/repo/tests/commands/teams/list.test.ts +62 -0
  931. package/temp_resend_cli/repo/tests/commands/teams/remove.test.ts +110 -0
  932. package/temp_resend_cli/repo/tests/commands/teams/switch.test.ts +103 -0
  933. package/temp_resend_cli/repo/tests/commands/topics/create.test.ts +192 -0
  934. package/temp_resend_cli/repo/tests/commands/topics/delete.test.ts +157 -0
  935. package/temp_resend_cli/repo/tests/commands/topics/get.test.ts +126 -0
  936. package/temp_resend_cli/repo/tests/commands/topics/list.test.ts +125 -0
  937. package/temp_resend_cli/repo/tests/commands/topics/update.test.ts +178 -0
  938. package/temp_resend_cli/repo/tests/commands/webhooks/create.test.ts +225 -0
  939. package/temp_resend_cli/repo/tests/commands/webhooks/delete.test.ts +157 -0
  940. package/temp_resend_cli/repo/tests/commands/webhooks/get.test.ts +126 -0
  941. package/temp_resend_cli/repo/tests/commands/webhooks/list.test.ts +178 -0
  942. package/temp_resend_cli/repo/tests/commands/webhooks/update.test.ts +207 -0
  943. package/temp_resend_cli/repo/tests/commands/whoami.test.ts +98 -0
  944. package/temp_resend_cli/repo/tests/e2e/smoke.test.ts +93 -0
  945. package/temp_resend_cli/repo/tests/helpers.ts +86 -0
  946. package/temp_resend_cli/repo/tests/lib/client.test.ts +71 -0
  947. package/temp_resend_cli/repo/tests/lib/config.test.ts +451 -0
  948. package/temp_resend_cli/repo/tests/lib/files.test.ts +73 -0
  949. package/temp_resend_cli/repo/tests/lib/help-text.test.ts +97 -0
  950. package/temp_resend_cli/repo/tests/lib/output.test.ts +136 -0
  951. package/temp_resend_cli/repo/tests/lib/prompts.test.ts +185 -0
  952. package/temp_resend_cli/repo/tests/lib/spinner.test.ts +166 -0
  953. package/temp_resend_cli/repo/tests/lib/table.test.ts +63 -0
  954. package/temp_resend_cli/repo/tests/lib/tty.test.ts +89 -0
  955. package/temp_resend_cli/repo/tests/lib/update-check.test.ts +179 -0
  956. package/temp_resend_cli/repo/tsconfig.json +14 -0
  957. package/temp_resend_cli/repo/vitest.config.e2e.ts +8 -0
  958. package/temp_resend_cli/repo/vitest.config.ts +10 -0
  959. package/tests/test-mcp-browser-use-smoke.sh +28 -56
  960. package/tests/test-monty-smoke.sh +32 -0
  961. 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
+ }