shellsim 0.1.5__tar.gz → 0.1.7__tar.gz

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 (177) hide show
  1. {shellsim-0.1.5 → shellsim-0.1.7}/Cargo.lock +1 -1
  2. {shellsim-0.1.5 → shellsim-0.1.7}/Cargo.toml +1 -1
  3. {shellsim-0.1.5 → shellsim-0.1.7}/PKG-INFO +1 -1
  4. {shellsim-0.1.5 → shellsim-0.1.7}/python/native/Cargo.lock +2 -2
  5. {shellsim-0.1.5 → shellsim-0.1.7}/python/native/Cargo.toml +1 -1
  6. {shellsim-0.1.5 → shellsim-0.1.7}/python/shellsim/_cli.py +4 -1
  7. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/heap.rs +41 -0
  8. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/object_model.rs +1 -0
  9. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/core.rs +290 -1
  10. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/frozen.rs +2 -0
  11. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/math.rs +150 -0
  12. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/mod.rs +1 -0
  13. shellsim-0.1.7/src/python/stdlib/source/cmath.py +65 -0
  14. shellsim-0.1.7/src/python/stdlib/source/complex.py +112 -0
  15. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm/calls.rs +93 -4
  16. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm/namespace.rs +12 -0
  17. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm.rs +2 -0
  18. {shellsim-0.1.5 → shellsim-0.1.7}/src/vfs.rs +163 -41
  19. {shellsim-0.1.5 → shellsim-0.1.7}/LICENSE +0 -0
  20. {shellsim-0.1.5 → shellsim-0.1.7}/README.md +0 -0
  21. {shellsim-0.1.5 → shellsim-0.1.7}/pyproject.toml +0 -0
  22. {shellsim-0.1.5 → shellsim-0.1.7}/python/native/src/lib.rs +0 -0
  23. {shellsim-0.1.5 → shellsim-0.1.7}/python/shellsim/__init__.py +0 -0
  24. {shellsim-0.1.5 → shellsim-0.1.7}/python/shellsim/__main__.py +0 -0
  25. {shellsim-0.1.5 → shellsim-0.1.7}/python/shellsim/_api.py +0 -0
  26. {shellsim-0.1.5 → shellsim-0.1.7}/python/shellsim/py.typed +0 -0
  27. {shellsim-0.1.5 → shellsim-0.1.7}/python/shellsim/python.py +0 -0
  28. {shellsim-0.1.5 → shellsim-0.1.7}/rust-toolchain.toml +0 -0
  29. {shellsim-0.1.5 → shellsim-0.1.7}/src/bin/shellsim-python.rs +0 -0
  30. {shellsim-0.1.5 → shellsim-0.1.7}/src/clock.rs +0 -0
  31. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/archives.rs +0 -0
  32. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/awk/ast.rs +0 -0
  33. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/awk/eval.rs +0 -0
  34. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/awk/lexer.rs +0 -0
  35. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/awk/parser.rs +0 -0
  36. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/awk.rs +0 -0
  37. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/builtins.rs +0 -0
  38. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/dd.rs +0 -0
  39. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/echo.rs +0 -0
  40. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/find.rs +0 -0
  41. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/fs.rs +0 -0
  42. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/apply.rs +0 -0
  43. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/commit.rs +0 -0
  44. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/compare.rs +0 -0
  45. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/config.rs +0 -0
  46. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/conflict.rs +0 -0
  47. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/diff.rs +0 -0
  48. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/ignore.rs +0 -0
  49. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/import.rs +0 -0
  50. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/log.rs +0 -0
  51. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/merge.rs +0 -0
  52. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/mod.rs +0 -0
  53. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/plumbing.rs +0 -0
  54. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/rebase.rs +0 -0
  55. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/refs.rs +0 -0
  56. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/repo.rs +0 -0
  57. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/stash.rs +0 -0
  58. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/switch.rs +0 -0
  59. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/git/worktree.rs +0 -0
  60. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/hashing.rs +0 -0
  61. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/makecmd.rs +0 -0
  62. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/mod.rs +0 -0
  63. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/net.rs +0 -0
  64. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/options.rs +0 -0
  65. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/patch.rs +0 -0
  66. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/pkg.rs +0 -0
  67. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/printf.rs +0 -0
  68. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/proc.rs +0 -0
  69. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/regex_compat.rs +0 -0
  70. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/rgcmd.rs +0 -0
  71. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/sed.rs +0 -0
  72. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/sort.rs +0 -0
  73. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/system.rs +0 -0
  74. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/tarcmd.rs +0 -0
  75. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/text.rs +0 -0
  76. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/unavailable.rs +0 -0
  77. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/util.rs +0 -0
  78. {shellsim-0.1.5 → shellsim-0.1.7}/src/commands/zipcmd.rs +0 -0
  79. {shellsim-0.1.5 → shellsim-0.1.7}/src/descriptors.rs +0 -0
  80. {shellsim-0.1.5 → shellsim-0.1.7}/src/exec.rs +0 -0
  81. {shellsim-0.1.5 → shellsim-0.1.7}/src/expand.rs +0 -0
  82. {shellsim-0.1.5 → shellsim-0.1.7}/src/harness.rs +0 -0
  83. {shellsim-0.1.5 → shellsim-0.1.7}/src/harness_manager.rs +0 -0
  84. {shellsim-0.1.5 → shellsim-0.1.7}/src/hashes.rs +0 -0
  85. {shellsim-0.1.5 → shellsim-0.1.7}/src/host_ingest.rs +0 -0
  86. {shellsim-0.1.5 → shellsim-0.1.7}/src/interp.rs +0 -0
  87. {shellsim-0.1.5 → shellsim-0.1.7}/src/jqcmd.rs +0 -0
  88. {shellsim-0.1.5 → shellsim-0.1.7}/src/lib.rs +0 -0
  89. {shellsim-0.1.5 → shellsim-0.1.7}/src/main.rs +0 -0
  90. {shellsim-0.1.5 → shellsim-0.1.7}/src/mcp.rs +0 -0
  91. {shellsim-0.1.5 → shellsim-0.1.7}/src/net.rs +0 -0
  92. {shellsim-0.1.5 → shellsim-0.1.7}/src/netcmd.rs +0 -0
  93. {shellsim-0.1.5 → shellsim-0.1.7}/src/process.rs +0 -0
  94. {shellsim-0.1.5 → shellsim-0.1.7}/src/pseudo_fs.rs +0 -0
  95. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/ast.rs +0 -0
  96. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/bytecode.rs +0 -0
  97. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/compiler.rs +0 -0
  98. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/filesystem.rs +0 -0
  99. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/http.rs +0 -0
  100. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/lexer.rs +0 -0
  101. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/mapping.rs +0 -0
  102. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/mod.rs +0 -0
  103. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/native.rs +0 -0
  104. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/number.rs +0 -0
  105. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/parser.rs +0 -0
  106. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/process.rs +0 -0
  107. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/protocol.rs +0 -0
  108. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/slice.rs +0 -0
  109. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/source.rs +0 -0
  110. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/argparse.rs +0 -0
  111. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/base64.rs +0 -0
  112. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/bisect.rs +0 -0
  113. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/collections.rs +0 -0
  114. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/dataclasses.rs +0 -0
  115. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/enum.rs +0 -0
  116. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/functools.rs +0 -0
  117. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/hashlib.rs +0 -0
  118. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/heapq.rs +0 -0
  119. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/http.rs +0 -0
  120. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/importlib.rs +0 -0
  121. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/itertools.rs +0 -0
  122. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/json.rs +0 -0
  123. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/numpy.rs +0 -0
  124. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/os.rs +0 -0
  125. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/pytest.rs +0 -0
  126. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/re.rs +0 -0
  127. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/abc.py +0 -0
  128. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/base64.py +0 -0
  129. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/codecs.py +0 -0
  130. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/collections.py +0 -0
  131. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/csv.py +0 -0
  132. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/datetime.py +0 -0
  133. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/glob.py +0 -0
  134. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/hashlib.py +0 -0
  135. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/http.py +0 -0
  136. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/http_client.py +0 -0
  137. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/importlib.py +0 -0
  138. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/io.py +0 -0
  139. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/json.py +0 -0
  140. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/logging.py +0 -0
  141. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/numpy_random.py +0 -0
  142. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/os.py +0 -0
  143. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/pathlib.py +0 -0
  144. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/random.py +0 -0
  145. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/statistics.py +0 -0
  146. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/struct.py +0 -0
  147. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/subprocess.py +0 -0
  148. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/tempfile.py +0 -0
  149. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/urllib.py +0 -0
  150. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/urllib_error.py +0 -0
  151. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/urllib_request.py +0 -0
  152. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/uuid.py +0 -0
  153. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/zipfile.py +0 -0
  154. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/source/zlib.py +0 -0
  155. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/string.rs +0 -0
  156. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/struct.rs +0 -0
  157. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/subprocess.rs +0 -0
  158. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/sys.rs +0 -0
  159. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/time.rs +0 -0
  160. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/typing.rs +0 -0
  161. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/unittest.rs +0 -0
  162. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/vfs.rs +0 -0
  163. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/stdlib/zlib.rs +0 -0
  164. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/string.rs +0 -0
  165. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/token.rs +0 -0
  166. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm/dispatch.rs +0 -0
  167. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm/host.rs +0 -0
  168. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm/iteration.rs +0 -0
  169. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm/native_runtime.rs +0 -0
  170. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm/objects.rs +0 -0
  171. {shellsim-0.1.5 → shellsim-0.1.7}/src/python/vm/operations.rs +0 -0
  172. {shellsim-0.1.5 → shellsim-0.1.7}/src/resources.rs +0 -0
  173. {shellsim-0.1.5 → shellsim-0.1.7}/src/sandbox.rs +0 -0
  174. {shellsim-0.1.5 → shellsim-0.1.7}/src/scenario.rs +0 -0
  175. {shellsim-0.1.5 → shellsim-0.1.7}/src/scheduler.rs +0 -0
  176. {shellsim-0.1.5 → shellsim-0.1.7}/src/shell.rs +0 -0
  177. {shellsim-0.1.5 → shellsim-0.1.7}/src/telemetry.rs +0 -0
@@ -330,7 +330,7 @@ dependencies = [
330
330
 
331
331
  [[package]]
332
332
  name = "shellsim"
333
- version = "0.1.5"
333
+ version = "0.1.7"
334
334
  dependencies = [
335
335
  "base64",
336
336
  "crc32fast",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "shellsim"
3
- version = "0.1.5"
3
+ version = "0.1.7"
4
4
  edition = "2021"
5
5
  description = "In-process BusyBox-like environment for RL rollouts and agent tests"
6
6
  license = "Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shellsim
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: License :: OSI Approved :: Apache Software License
6
6
  Classifier: Operating System :: MacOS :: MacOS X
@@ -406,7 +406,7 @@ dependencies = [
406
406
 
407
407
  [[package]]
408
408
  name = "shellsim"
409
- version = "0.1.5"
409
+ version = "0.1.7"
410
410
  dependencies = [
411
411
  "base64",
412
412
  "crc32fast",
@@ -427,7 +427,7 @@ dependencies = [
427
427
 
428
428
  [[package]]
429
429
  name = "shellsim-python-bindings"
430
- version = "0.1.5"
430
+ version = "0.1.7"
431
431
  dependencies = [
432
432
  "pyo3",
433
433
  "serde",
@@ -2,7 +2,7 @@
2
2
 
3
3
  [package]
4
4
  name = "shellsim-python-bindings"
5
- version = "0.1.5"
5
+ version = "0.1.7"
6
6
  edition = "2021"
7
7
  description = "Python bindings for shellsim"
8
8
  license = "Apache-2.0"
@@ -148,7 +148,10 @@ def _interactive(environment: Environment, stdin: Any, stdout: Any, stderr: Any)
148
148
  def main(arguments: Optional[Sequence[str]] = None) -> int:
149
149
  """Run the package CLI and return the simulated or usage exit status."""
150
150
 
151
- options = _parser().parse_args(sys.argv[1:] if arguments is None else arguments)
151
+ parsed_arguments = list(sys.argv[1:] if arguments is None else arguments)
152
+ if parsed_arguments[-1:] == ["--"]:
153
+ parsed_arguments.pop()
154
+ options = _parser().parse_args(parsed_arguments)
152
155
  environment, preparation_status = _prepare_environment(options, sys.stdout, sys.stderr)
153
156
  if environment is None:
154
157
  return preparation_status
@@ -48,6 +48,10 @@ impl SymbolId {
48
48
  pub(super) const fn index(self) -> usize {
49
49
  self.0 as usize
50
50
  }
51
+
52
+ pub(super) fn from_index(index: usize) -> Option<Self> {
53
+ u32::try_from(index).ok().map(Self)
54
+ }
51
55
  }
52
56
 
53
57
  /// Runtime-local identity for one append-only instance storage layout.
@@ -386,6 +390,43 @@ impl Heap {
386
390
  self.symbol_ids.get(name).copied()
387
391
  }
388
392
 
393
+ /// Resolve a symbol identity back to its interpreter-owned name.
394
+ pub fn symbol_name(&self, symbol: SymbolId) -> Option<&str> {
395
+ self.symbol_names.get(symbol.index()).map(AsRef::as_ref)
396
+ }
397
+
398
+ /// Snapshot the names stored directly on an instance in either attribute representation.
399
+ pub fn instance_attribute_names(&self, id: ObjectId) -> Result<Vec<String>, String> {
400
+ let object = self
401
+ .objects
402
+ .get(id.0)
403
+ .and_then(Option::as_ref)
404
+ .ok_or("invalid object reference")?;
405
+ let Object::Instance { attributes, .. } = &object.payload else {
406
+ return Err("object does not have instance attributes".into());
407
+ };
408
+ match attributes {
409
+ InstanceAttributes::Shaped { shape, values } => (0..values.len())
410
+ .map(|slot| {
411
+ let symbol = self
412
+ .shape_attribute_at(*shape, slot)
413
+ .ok_or("invalid instance shape slot")?;
414
+ self.symbol_name(symbol)
415
+ .map(str::to_string)
416
+ .ok_or_else(|| "invalid instance attribute symbol".into())
417
+ })
418
+ .collect(),
419
+ InstanceAttributes::Dictionary(values) => values
420
+ .keys()
421
+ .map(|symbol| {
422
+ self.symbol_name(*symbol)
423
+ .map(str::to_string)
424
+ .ok_or_else(|| "invalid instance attribute symbol".into())
425
+ })
426
+ .collect(),
427
+ }
428
+ }
429
+
389
430
  /// Intern one identifier, charging its process-lifetime storage before mutation.
390
431
  pub fn intern_symbol(
391
432
  &mut self,
@@ -883,6 +883,7 @@ fn install_builtin_slots(types: &mut [PyType]) {
883
883
  slots.add = Some(intrinsic(super::stdlib::core::slot_string_add));
884
884
  slots.multiply = Some(intrinsic(super::stdlib::core::slot_string_multiply));
885
885
  slots.reflected_multiply = Some(intrinsic(super::stdlib::core::slot_string_multiply));
886
+ slots.remainder = Some(intrinsic(super::stdlib::core::slot_string_remainder));
886
887
 
887
888
  let slots = &mut types[BuiltinType::Bytes as usize].slots;
888
889
  slots.length = Some(unary(super::stdlib::core::slot_bytes_length));
@@ -13,7 +13,7 @@ use super::super::native::PyValue as Value;
13
13
  use super::super::native::{
14
14
  CallArgs, FunctionDef, MethodDef, NativeTypeDef, OwnedPyString, PyByteArray, PyBytes,
15
15
  PyCallable, PyDict, PyError, PyKind, PyList, PyProperty, PyResult, PyRuntime, PySequence,
16
- PySet, PyValue, PyValueCast,
16
+ PySet, PyTuple, PyValue, PyValueCast,
17
17
  };
18
18
  use super::super::number::PyNumber;
19
19
  use super::super::slice::SlicePlan;
@@ -56,6 +56,8 @@ pub(crate) static STRING_TYPE: NativeTypeDef = NativeTypeDef {
56
56
  method("str", "join", string_join),
57
57
  method("str", "replace", string_replace),
58
58
  method("str", "format", string_format),
59
+ method("str", "ljust", string_ljust),
60
+ method("str", "rjust", string_rjust),
59
61
  method("str", "encode", string_encode),
60
62
  method("str", "lower", string_lower),
61
63
  method("str", "upper", string_upper),
@@ -737,6 +739,293 @@ fn string_replace(runtime: &mut dyn PyRuntime, receiver: PyValue, args: CallArgs
737
739
  runtime.new_string(result)
738
740
  }
739
741
 
742
+ fn string_ljust(runtime: &mut dyn PyRuntime, receiver: PyValue, args: CallArgs) -> PyResult {
743
+ string_justify(runtime, receiver, args, false)
744
+ }
745
+
746
+ fn string_rjust(runtime: &mut dyn PyRuntime, receiver: PyValue, args: CallArgs) -> PyResult {
747
+ string_justify(runtime, receiver, args, true)
748
+ }
749
+
750
+ fn string_justify(
751
+ runtime: &mut dyn PyRuntime,
752
+ receiver: PyValue,
753
+ args: CallArgs,
754
+ right: bool,
755
+ ) -> PyResult {
756
+ let name = if right { "str.rjust" } else { "str.ljust" };
757
+ args.expect_positional(name, 1, 2)?;
758
+ args.reject_keywords(name)?;
759
+ let OwnedPyString(value) = receiver.cast(runtime)?;
760
+ let width = runtime
761
+ .int_value(&args.positional()[0])
762
+ .ok_or_else(|| PyError::type_error("width must be an integer"))?;
763
+ let fill = if let Some(fill) = args.positional().get(1) {
764
+ let OwnedPyString(fill) = (*fill).cast(runtime)?;
765
+ if fill.chars().count() != 1 {
766
+ return Err(PyError::type_error(
767
+ "the fill character must be exactly one character long",
768
+ ));
769
+ }
770
+ fill
771
+ } else {
772
+ " ".to_string()
773
+ };
774
+ let padding = usize::try_from(width)
775
+ .ok()
776
+ .unwrap_or_default()
777
+ .saturating_sub(value.chars().count());
778
+ let added = fill
779
+ .len()
780
+ .checked_mul(padding)
781
+ .ok_or_else(|| PyError::resource_error("justified string is too large"))?;
782
+ let capacity = value
783
+ .len()
784
+ .checked_add(added)
785
+ .ok_or_else(|| PyError::resource_error("justified string is too large"))?;
786
+ runtime.reserve_memory(capacity)?;
787
+ runtime.charge_cpu(u64::try_from(capacity).unwrap_or(u64::MAX))?;
788
+ let padding = fill.repeat(padding);
789
+ runtime.new_string(if right {
790
+ format!("{padding}{value}")
791
+ } else {
792
+ format!("{value}{padding}")
793
+ })
794
+ }
795
+
796
+ /// Implement the established string `%` protocol without routing numeric remainder through it.
797
+ pub(crate) fn slot_string_remainder(
798
+ runtime: &mut dyn PyRuntime,
799
+ left: PyValue,
800
+ right: PyValue,
801
+ ) -> PyResult<Option<PyValue>> {
802
+ let OwnedPyString(template) = left.cast(runtime)?;
803
+ runtime.reserve_memory(template.len())?;
804
+ let arguments = if runtime.kind(&right)? == PyKind::Tuple {
805
+ right.cast::<PyTuple>(runtime)?.items(runtime)?
806
+ } else {
807
+ vec![right]
808
+ };
809
+ let mut argument = 0usize;
810
+ let mut used_mapping = false;
811
+ let mut output = String::new();
812
+ let characters = template.chars().collect::<Vec<_>>();
813
+ let mut index = 0usize;
814
+ while index < characters.len() {
815
+ runtime.charge_cpu(1)?;
816
+ if characters[index] != '%' {
817
+ output.push(characters[index]);
818
+ index += 1;
819
+ continue;
820
+ }
821
+ index += 1;
822
+ if characters.get(index) == Some(&'%') {
823
+ output.push('%');
824
+ index += 1;
825
+ continue;
826
+ }
827
+ let mapping_key = if characters.get(index) == Some(&'(') {
828
+ index += 1;
829
+ let start = index;
830
+ while characters
831
+ .get(index)
832
+ .is_some_and(|character| *character != ')')
833
+ {
834
+ index += 1;
835
+ }
836
+ if characters.get(index) != Some(&')') {
837
+ return Err(PyError::value_error("incomplete format key"));
838
+ }
839
+ let key = characters[start..index].iter().collect::<String>();
840
+ index += 1;
841
+ Some(key)
842
+ } else {
843
+ None
844
+ };
845
+ let mut left_align = false;
846
+ let mut plus = false;
847
+ let mut space = false;
848
+ let mut alternate = false;
849
+ let mut zero = false;
850
+ while let Some(flag) = characters.get(index) {
851
+ match flag {
852
+ '-' => left_align = true,
853
+ '+' => plus = true,
854
+ ' ' => space = true,
855
+ '#' => alternate = true,
856
+ '0' => zero = true,
857
+ _ => break,
858
+ }
859
+ index += 1;
860
+ }
861
+ let width = parse_format_digits(&characters, &mut index)?;
862
+ let precision = if characters.get(index) == Some(&'.') {
863
+ index += 1;
864
+ Some(parse_format_digits(&characters, &mut index)?.unwrap_or(0))
865
+ } else {
866
+ None
867
+ };
868
+ let requested = width.unwrap_or_default().max(precision.unwrap_or_default());
869
+ runtime.reserve_memory(requested)?;
870
+ runtime.charge_cpu(u64::try_from(requested).unwrap_or(u64::MAX))?;
871
+ let conversion = *characters
872
+ .get(index)
873
+ .ok_or_else(|| PyError::value_error("incomplete format"))?;
874
+ index += 1;
875
+ let value = if let Some(key) = mapping_key {
876
+ used_mapping = true;
877
+ let mapping = right.cast::<PyDict>(runtime)?;
878
+ let key_value = runtime.new_string(key.clone())?;
879
+ runtime
880
+ .dict_get(mapping, &key_value)?
881
+ .ok_or_else(|| PyError::exception("KeyError", key))?
882
+ } else {
883
+ let value = arguments
884
+ .get(argument)
885
+ .copied()
886
+ .ok_or_else(|| PyError::type_error("not enough arguments for format string"))?;
887
+ argument += 1;
888
+ value
889
+ };
890
+ let mut rendered = match conversion {
891
+ 's' => runtime.display(&value)?,
892
+ 'r' | 'a' => runtime.repr(&value)?,
893
+ 'd' | 'i' | 'u' => runtime
894
+ .integer_text(&value)?
895
+ .ok_or_else(|| PyError::type_error("%d format: a real number is required"))?,
896
+ 'x' | 'X' | 'o' => {
897
+ let decimal = runtime
898
+ .integer_text(&value)?
899
+ .ok_or_else(|| PyError::type_error("integer format requires an integer"))?;
900
+ let integer = decimal
901
+ .parse::<BigInt>()
902
+ .map_err(|_| PyError::runtime_error("invalid internal integer"))?;
903
+ let digits = match conversion {
904
+ 'x' => format!("{integer:x}"),
905
+ 'X' => format!("{integer:X}"),
906
+ 'o' => format!("{integer:o}"),
907
+ _ => unreachable!(),
908
+ };
909
+ if alternate && integer != BigInt::zero() {
910
+ match conversion {
911
+ 'x' => format!("0x{digits}"),
912
+ 'X' => format!("0X{digits}"),
913
+ 'o' => format!("0o{digits}"),
914
+ _ => unreachable!(),
915
+ }
916
+ } else {
917
+ digits
918
+ }
919
+ }
920
+ 'f' | 'F' | 'e' | 'E' | 'g' | 'G' => {
921
+ let number = value.cast::<PyNumber>(runtime)?.into_f64()?;
922
+ let precision = precision.unwrap_or(6);
923
+ match conversion {
924
+ 'f' | 'F' => format!("{number:.precision$}"),
925
+ 'e' => format!("{number:.precision$e}"),
926
+ 'E' => format!("{number:.precision$E}"),
927
+ 'g' | 'G' => {
928
+ let mut text = format!("{number:.precision$}");
929
+ if conversion == 'G' {
930
+ text.make_ascii_uppercase();
931
+ }
932
+ text
933
+ }
934
+ _ => unreachable!(),
935
+ }
936
+ }
937
+ 'c' => {
938
+ if let Some(integer) = runtime.int_value(&value) {
939
+ u32::try_from(integer)
940
+ .ok()
941
+ .and_then(char::from_u32)
942
+ .ok_or_else(|| PyError::overflow_error("%c arg not in range"))?
943
+ .to_string()
944
+ } else {
945
+ let OwnedPyString(text) = value.cast(runtime)?;
946
+ if text.chars().count() != 1 {
947
+ return Err(PyError::type_error("%c requires int or char"));
948
+ }
949
+ text
950
+ }
951
+ }
952
+ other => {
953
+ return Err(PyError::value_error(format!(
954
+ "unsupported format character {other:?}"
955
+ )))
956
+ }
957
+ };
958
+ if matches!(conversion, 's' | 'r' | 'a') {
959
+ if let Some(precision) = precision {
960
+ rendered = rendered.chars().take(precision).collect();
961
+ }
962
+ } else if matches!(conversion, 'd' | 'i' | 'u') {
963
+ rendered = pad_integer_precision(rendered, precision);
964
+ }
965
+ if matches!(
966
+ conversion,
967
+ 'd' | 'i' | 'u' | 'f' | 'F' | 'e' | 'E' | 'g' | 'G'
968
+ ) && !rendered.starts_with('-')
969
+ {
970
+ if plus {
971
+ rendered.insert(0, '+');
972
+ } else if space {
973
+ rendered.insert(0, ' ');
974
+ }
975
+ }
976
+ if let Some(width) = width {
977
+ let padding = width.saturating_sub(rendered.chars().count());
978
+ if padding > 0 {
979
+ let fill = if zero && !left_align { '0' } else { ' ' };
980
+ if left_align {
981
+ rendered.extend(std::iter::repeat_n(fill, padding));
982
+ } else if fill == '0' && matches!(rendered.chars().next(), Some('+' | '-' | ' ')) {
983
+ let sign = rendered.remove(0);
984
+ rendered = format!("{sign}{}{rendered}", "0".repeat(padding));
985
+ } else {
986
+ rendered = format!("{}{rendered}", fill.to_string().repeat(padding));
987
+ }
988
+ }
989
+ }
990
+ runtime.reserve_memory(rendered.len())?;
991
+ output.push_str(&rendered);
992
+ }
993
+ if !used_mapping && argument < arguments.len() {
994
+ return Err(PyError::type_error(
995
+ "not all arguments converted during string formatting",
996
+ ));
997
+ }
998
+ runtime.reserve_memory(output.len())?;
999
+ Ok(Some(runtime.new_string(output)?))
1000
+ }
1001
+
1002
+ fn parse_format_digits(characters: &[char], index: &mut usize) -> PyResult<Option<usize>> {
1003
+ let start = *index;
1004
+ let mut value = 0usize;
1005
+ while let Some(character) = characters.get(*index).and_then(|value| value.to_digit(10)) {
1006
+ value = value
1007
+ .checked_mul(10)
1008
+ .and_then(|value| value.checked_add(character as usize))
1009
+ .ok_or_else(|| PyError::resource_error("format width is too large"))?;
1010
+ *index += 1;
1011
+ }
1012
+ Ok((*index != start).then_some(value))
1013
+ }
1014
+
1015
+ fn pad_integer_precision(mut value: String, precision: Option<usize>) -> String {
1016
+ let Some(precision) = precision else {
1017
+ return value;
1018
+ };
1019
+ let sign = value.starts_with('-').then(|| value.remove(0));
1020
+ if value.len() < precision {
1021
+ value = format!("{}{value}", "0".repeat(precision - value.len()));
1022
+ }
1023
+ if let Some(sign) = sign {
1024
+ value.insert(0, sign);
1025
+ }
1026
+ value
1027
+ }
1028
+
740
1029
  fn string_format(runtime: &mut dyn PyRuntime, receiver: PyValue, args: CallArgs) -> PyResult {
741
1030
  let OwnedPyString(template) = receiver.cast(runtime)?;
742
1031
  let mut result = String::new();
@@ -10,6 +10,8 @@ pub(super) fn module_source(name: &str) -> Option<&'static str> {
10
10
  "abc" => Some(include_str!("source/abc.py")),
11
11
  "base64" => Some(include_str!("source/base64.py")),
12
12
  "codecs" => Some(include_str!("source/codecs.py")),
13
+ "_complex" => Some(include_str!("source/complex.py")),
14
+ "cmath" => Some(include_str!("source/cmath.py")),
13
15
  "csv" => Some(include_str!("source/csv.py")),
14
16
  "datetime" => Some(include_str!("source/datetime.py")),
15
17
  "collections" => Some(include_str!("source/collections.py")),
@@ -17,6 +17,26 @@ use super::super::number::PyNumber;
17
17
  pub(super) static MODULE: ModuleDef = ModuleDef {
18
18
  name: "math",
19
19
  functions: &[
20
+ FunctionDef {
21
+ module: "math",
22
+ name: "acos",
23
+ call: native_acos,
24
+ },
25
+ FunctionDef {
26
+ module: "math",
27
+ name: "asin",
28
+ call: native_asin,
29
+ },
30
+ FunctionDef {
31
+ module: "math",
32
+ name: "atan",
33
+ call: native_atan,
34
+ },
35
+ FunctionDef {
36
+ module: "math",
37
+ name: "atan2",
38
+ call: native_atan2,
39
+ },
20
40
  FunctionDef {
21
41
  module: "math",
22
42
  name: "ceil",
@@ -27,11 +47,21 @@ pub(super) static MODULE: ModuleDef = ModuleDef {
27
47
  name: "cos",
28
48
  call: native_cos,
29
49
  },
50
+ FunctionDef {
51
+ module: "math",
52
+ name: "degrees",
53
+ call: native_degrees,
54
+ },
30
55
  FunctionDef {
31
56
  module: "math",
32
57
  name: "exp",
33
58
  call: native_exp,
34
59
  },
60
+ FunctionDef {
61
+ module: "math",
62
+ name: "hypot",
63
+ call: native_hypot,
64
+ },
35
65
  FunctionDef {
36
66
  module: "math",
37
67
  name: "isinf",
@@ -47,6 +77,21 @@ pub(super) static MODULE: ModuleDef = ModuleDef {
47
77
  name: "log",
48
78
  call: native_log,
49
79
  },
80
+ FunctionDef {
81
+ module: "math",
82
+ name: "log10",
83
+ call: native_log10,
84
+ },
85
+ FunctionDef {
86
+ module: "math",
87
+ name: "pow",
88
+ call: native_pow,
89
+ },
90
+ FunctionDef {
91
+ module: "math",
92
+ name: "radians",
93
+ call: native_radians,
94
+ },
50
95
  FunctionDef {
51
96
  module: "math",
52
97
  name: "sin",
@@ -87,6 +132,22 @@ pub(super) static MODULE: ModuleDef = ModuleDef {
87
132
  ],
88
133
  };
89
134
 
135
+ fn native_acos(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
136
+ native_call(runtime, args, "acos")
137
+ }
138
+
139
+ fn native_asin(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
140
+ native_call(runtime, args, "asin")
141
+ }
142
+
143
+ fn native_atan(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
144
+ native_call(runtime, args, "atan")
145
+ }
146
+
147
+ fn native_atan2(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
148
+ native_call(runtime, args, "atan2")
149
+ }
150
+
90
151
  fn native_ceil(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
91
152
  native_call(runtime, args, "ceil")
92
153
  }
@@ -95,10 +156,18 @@ fn native_cos(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
95
156
  native_call(runtime, args, "cos")
96
157
  }
97
158
 
159
+ fn native_degrees(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
160
+ native_call(runtime, args, "degrees")
161
+ }
162
+
98
163
  fn native_exp(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
99
164
  native_call(runtime, args, "exp")
100
165
  }
101
166
 
167
+ fn native_hypot(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
168
+ native_call(runtime, args, "hypot")
169
+ }
170
+
102
171
  fn native_isinf(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
103
172
  native_call(runtime, args, "isinf")
104
173
  }
@@ -111,6 +180,18 @@ fn native_log(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
111
180
  native_call(runtime, args, "log")
112
181
  }
113
182
 
183
+ fn native_log10(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
184
+ native_call(runtime, args, "log10")
185
+ }
186
+
187
+ fn native_pow(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
188
+ native_call(runtime, args, "pow")
189
+ }
190
+
191
+ fn native_radians(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
192
+ native_call(runtime, args, "radians")
193
+ }
194
+
114
195
  fn native_sin(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
115
196
  native_call(runtime, args, "sin")
116
197
  }
@@ -220,9 +301,19 @@ pub fn constant(name: &str) -> Option<MathValue> {
220
301
  /// Dispatch one of the observed/reviewer-requested `math` functions.
221
302
  pub fn call(name: &str, args: &[f64]) -> MathResult {
222
303
  match name {
304
+ "acos" => unary("acos", args, acos),
305
+ "asin" => unary("asin", args, asin),
306
+ "atan" => unary("atan", args, |value| Ok(MathValue::Float(value.atan()))),
307
+ "atan2" => binary("atan2", args, |y, x| Ok(MathValue::Float(y.atan2(x)))),
223
308
  "ceil" => unary("ceil", args, ceil),
224
309
  "cos" => unary("cos", args, cos),
310
+ "degrees" => unary("degrees", args, |value| {
311
+ Ok(MathValue::Float(value.to_degrees()))
312
+ }),
225
313
  "exp" => unary("exp", args, exp),
314
+ "hypot" => Ok(MathValue::Float(
315
+ args.iter().copied().fold(0.0_f64, f64::hypot),
316
+ )),
226
317
  "isinf" => unary("isinf", args, |value| Ok(MathValue::Bool(isinf(value)))),
227
318
  "isnan" => unary("isnan", args, |value| Ok(MathValue::Bool(isnan(value)))),
228
319
  "log" => match args {
@@ -234,6 +325,11 @@ pub fn call(name: &str, args: &[f64]) -> MathResult {
234
325
  actual: args.len(),
235
326
  }),
236
327
  },
328
+ "log10" => unary("log10", args, log10),
329
+ "pow" => binary("pow", args, pow),
330
+ "radians" => unary("radians", args, |value| {
331
+ Ok(MathValue::Float(value.to_radians()))
332
+ }),
237
333
  "sin" => unary("sin", args, sin),
238
334
  "sqrt" => unary("sqrt", args, sqrt),
239
335
  "tan" => unary("tan", args, tan),
@@ -241,6 +337,21 @@ pub fn call(name: &str, args: &[f64]) -> MathResult {
241
337
  }
242
338
  }
243
339
 
340
+ fn binary(
341
+ function: &'static str,
342
+ args: &[f64],
343
+ operation: impl FnOnce(f64, f64) -> MathResult,
344
+ ) -> MathResult {
345
+ let [left, right] = args else {
346
+ return Err(MathError::Arity {
347
+ function,
348
+ expected: "exactly 2",
349
+ actual: args.len(),
350
+ });
351
+ };
352
+ operation(*left, *right)
353
+ }
354
+
244
355
  fn unary(
245
356
  function: &'static str,
246
357
  args: &[f64],
@@ -288,6 +399,45 @@ pub fn exp(value: f64) -> MathResult {
288
399
  Ok(MathValue::Float(result))
289
400
  }
290
401
 
402
+ /// Return the inverse sine in radians.
403
+ pub fn asin(value: f64) -> MathResult {
404
+ if !(-1.0..=1.0).contains(&value) {
405
+ return Err(MathError::ValueError("math domain error"));
406
+ }
407
+ Ok(MathValue::Float(value.asin()))
408
+ }
409
+
410
+ /// Return the inverse cosine in radians.
411
+ pub fn acos(value: f64) -> MathResult {
412
+ if !(-1.0..=1.0).contains(&value) {
413
+ return Err(MathError::ValueError("math domain error"));
414
+ }
415
+ Ok(MathValue::Float(value.acos()))
416
+ }
417
+
418
+ /// Return the base-ten logarithm of a positive value.
419
+ pub fn log10(value: f64) -> MathResult {
420
+ if value <= 0.0 {
421
+ return Err(MathError::ValueError("math domain error"));
422
+ }
423
+ Ok(MathValue::Float(value.log10()))
424
+ }
425
+
426
+ /// Return `left**right` with Python's real-number domain and overflow errors.
427
+ pub fn pow(left: f64, right: f64) -> MathResult {
428
+ if left == 0.0 && right < 0.0 {
429
+ return Err(MathError::ValueError("math domain error"));
430
+ }
431
+ let value = left.powf(right);
432
+ if value.is_nan() && !(left.is_nan() || right.is_nan()) {
433
+ return Err(MathError::ValueError("math domain error"));
434
+ }
435
+ if value.is_infinite() && left.is_finite() && right.is_finite() {
436
+ return Err(MathError::OverflowError("math range error"));
437
+ }
438
+ Ok(MathValue::Float(value))
439
+ }
440
+
291
441
  /// Return whether `value` is positive or negative infinity.
292
442
  pub const fn isinf(value: f64) -> bool {
293
443
  value.is_infinite()