shellsim 0.1.7__tar.gz → 0.1.9__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 (183) hide show
  1. {shellsim-0.1.7 → shellsim-0.1.9}/Cargo.lock +1 -1
  2. {shellsim-0.1.7 → shellsim-0.1.9}/Cargo.toml +1 -1
  3. {shellsim-0.1.7 → shellsim-0.1.9}/PKG-INFO +5 -3
  4. {shellsim-0.1.7 → shellsim-0.1.9}/README.md +4 -2
  5. {shellsim-0.1.7 → shellsim-0.1.9}/python/native/Cargo.lock +2 -2
  6. {shellsim-0.1.7 → shellsim-0.1.9}/python/native/Cargo.toml +1 -1
  7. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/builtins.rs +120 -1
  8. {shellsim-0.1.7 → shellsim-0.1.9}/src/interp.rs +21 -0
  9. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/ast.rs +2 -3
  10. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/compiler.rs +88 -9
  11. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/mod.rs +443 -28
  12. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/native.rs +7 -0
  13. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/object_model.rs +4 -0
  14. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/parser.rs +7 -0
  15. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/bisect.rs +109 -11
  16. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/core.rs +344 -2
  17. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/frozen.rs +4 -0
  18. shellsim-0.1.9/src/python/stdlib/itertools.rs +344 -0
  19. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/math.rs +169 -0
  20. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/mod.rs +1 -1
  21. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/pytest.rs +4 -4
  22. shellsim-0.1.9/src/python/stdlib/source/copy.py +29 -0
  23. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/pathlib.py +30 -0
  24. shellsim-0.1.9/src/python/stdlib/source/pytest.py +36 -0
  25. shellsim-0.1.9/src/python/stdlib/source/shutil.py +57 -0
  26. shellsim-0.1.9/src/python/stdlib/source/textwrap.py +73 -0
  27. shellsim-0.1.9/src/python/stdlib/typing.rs +136 -0
  28. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/vm/calls.rs +73 -1
  29. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/vm/iteration.rs +12 -3
  30. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/vm/namespace.rs +6 -0
  31. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/vm/native_runtime.rs +62 -1
  32. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/vm.rs +6 -0
  33. shellsim-0.1.7/src/python/stdlib/itertools.rs +0 -118
  34. shellsim-0.1.7/src/python/stdlib/typing.rs +0 -45
  35. {shellsim-0.1.7 → shellsim-0.1.9}/LICENSE +0 -0
  36. {shellsim-0.1.7 → shellsim-0.1.9}/pyproject.toml +0 -0
  37. {shellsim-0.1.7 → shellsim-0.1.9}/python/native/src/lib.rs +0 -0
  38. {shellsim-0.1.7 → shellsim-0.1.9}/python/shellsim/__init__.py +0 -0
  39. {shellsim-0.1.7 → shellsim-0.1.9}/python/shellsim/__main__.py +0 -0
  40. {shellsim-0.1.7 → shellsim-0.1.9}/python/shellsim/_api.py +0 -0
  41. {shellsim-0.1.7 → shellsim-0.1.9}/python/shellsim/_cli.py +0 -0
  42. {shellsim-0.1.7 → shellsim-0.1.9}/python/shellsim/py.typed +0 -0
  43. {shellsim-0.1.7 → shellsim-0.1.9}/python/shellsim/python.py +0 -0
  44. {shellsim-0.1.7 → shellsim-0.1.9}/rust-toolchain.toml +0 -0
  45. {shellsim-0.1.7 → shellsim-0.1.9}/src/bin/shellsim-python.rs +0 -0
  46. {shellsim-0.1.7 → shellsim-0.1.9}/src/clock.rs +0 -0
  47. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/archives.rs +0 -0
  48. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/awk/ast.rs +0 -0
  49. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/awk/eval.rs +0 -0
  50. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/awk/lexer.rs +0 -0
  51. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/awk/parser.rs +0 -0
  52. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/awk.rs +0 -0
  53. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/dd.rs +0 -0
  54. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/echo.rs +0 -0
  55. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/find.rs +0 -0
  56. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/fs.rs +0 -0
  57. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/apply.rs +0 -0
  58. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/commit.rs +0 -0
  59. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/compare.rs +0 -0
  60. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/config.rs +0 -0
  61. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/conflict.rs +0 -0
  62. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/diff.rs +0 -0
  63. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/ignore.rs +0 -0
  64. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/import.rs +0 -0
  65. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/log.rs +0 -0
  66. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/merge.rs +0 -0
  67. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/mod.rs +0 -0
  68. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/plumbing.rs +0 -0
  69. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/rebase.rs +0 -0
  70. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/refs.rs +0 -0
  71. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/repo.rs +0 -0
  72. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/stash.rs +0 -0
  73. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/switch.rs +0 -0
  74. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/git/worktree.rs +0 -0
  75. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/hashing.rs +0 -0
  76. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/makecmd.rs +0 -0
  77. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/mod.rs +0 -0
  78. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/net.rs +0 -0
  79. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/options.rs +0 -0
  80. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/patch.rs +0 -0
  81. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/pkg.rs +0 -0
  82. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/printf.rs +0 -0
  83. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/proc.rs +0 -0
  84. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/regex_compat.rs +0 -0
  85. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/rgcmd.rs +0 -0
  86. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/sed.rs +0 -0
  87. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/sort.rs +0 -0
  88. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/system.rs +0 -0
  89. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/tarcmd.rs +0 -0
  90. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/text.rs +0 -0
  91. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/unavailable.rs +0 -0
  92. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/util.rs +0 -0
  93. {shellsim-0.1.7 → shellsim-0.1.9}/src/commands/zipcmd.rs +0 -0
  94. {shellsim-0.1.7 → shellsim-0.1.9}/src/descriptors.rs +0 -0
  95. {shellsim-0.1.7 → shellsim-0.1.9}/src/exec.rs +0 -0
  96. {shellsim-0.1.7 → shellsim-0.1.9}/src/expand.rs +0 -0
  97. {shellsim-0.1.7 → shellsim-0.1.9}/src/harness.rs +0 -0
  98. {shellsim-0.1.7 → shellsim-0.1.9}/src/harness_manager.rs +0 -0
  99. {shellsim-0.1.7 → shellsim-0.1.9}/src/hashes.rs +0 -0
  100. {shellsim-0.1.7 → shellsim-0.1.9}/src/host_ingest.rs +0 -0
  101. {shellsim-0.1.7 → shellsim-0.1.9}/src/jqcmd.rs +0 -0
  102. {shellsim-0.1.7 → shellsim-0.1.9}/src/lib.rs +0 -0
  103. {shellsim-0.1.7 → shellsim-0.1.9}/src/main.rs +0 -0
  104. {shellsim-0.1.7 → shellsim-0.1.9}/src/mcp.rs +0 -0
  105. {shellsim-0.1.7 → shellsim-0.1.9}/src/net.rs +0 -0
  106. {shellsim-0.1.7 → shellsim-0.1.9}/src/netcmd.rs +0 -0
  107. {shellsim-0.1.7 → shellsim-0.1.9}/src/process.rs +0 -0
  108. {shellsim-0.1.7 → shellsim-0.1.9}/src/pseudo_fs.rs +0 -0
  109. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/bytecode.rs +0 -0
  110. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/filesystem.rs +0 -0
  111. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/heap.rs +0 -0
  112. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/http.rs +0 -0
  113. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/lexer.rs +0 -0
  114. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/mapping.rs +0 -0
  115. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/number.rs +0 -0
  116. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/process.rs +0 -0
  117. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/protocol.rs +0 -0
  118. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/slice.rs +0 -0
  119. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/source.rs +0 -0
  120. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/argparse.rs +0 -0
  121. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/base64.rs +0 -0
  122. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/collections.rs +0 -0
  123. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/dataclasses.rs +0 -0
  124. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/enum.rs +0 -0
  125. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/functools.rs +0 -0
  126. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/hashlib.rs +0 -0
  127. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/heapq.rs +0 -0
  128. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/http.rs +0 -0
  129. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/importlib.rs +0 -0
  130. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/json.rs +0 -0
  131. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/numpy.rs +0 -0
  132. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/os.rs +0 -0
  133. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/re.rs +0 -0
  134. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/abc.py +0 -0
  135. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/base64.py +0 -0
  136. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/cmath.py +0 -0
  137. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/codecs.py +0 -0
  138. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/collections.py +0 -0
  139. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/complex.py +0 -0
  140. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/csv.py +0 -0
  141. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/datetime.py +0 -0
  142. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/glob.py +0 -0
  143. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/hashlib.py +0 -0
  144. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/http.py +0 -0
  145. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/http_client.py +0 -0
  146. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/importlib.py +0 -0
  147. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/io.py +0 -0
  148. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/json.py +0 -0
  149. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/logging.py +0 -0
  150. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/numpy_random.py +0 -0
  151. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/os.py +0 -0
  152. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/random.py +0 -0
  153. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/statistics.py +0 -0
  154. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/struct.py +0 -0
  155. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/subprocess.py +0 -0
  156. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/tempfile.py +0 -0
  157. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/urllib.py +0 -0
  158. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/urllib_error.py +0 -0
  159. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/urllib_request.py +0 -0
  160. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/uuid.py +0 -0
  161. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/zipfile.py +0 -0
  162. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/source/zlib.py +0 -0
  163. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/string.rs +0 -0
  164. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/struct.rs +0 -0
  165. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/subprocess.rs +0 -0
  166. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/sys.rs +0 -0
  167. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/time.rs +0 -0
  168. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/unittest.rs +0 -0
  169. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/vfs.rs +0 -0
  170. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/stdlib/zlib.rs +0 -0
  171. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/string.rs +0 -0
  172. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/token.rs +0 -0
  173. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/vm/dispatch.rs +0 -0
  174. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/vm/host.rs +0 -0
  175. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/vm/objects.rs +0 -0
  176. {shellsim-0.1.7 → shellsim-0.1.9}/src/python/vm/operations.rs +0 -0
  177. {shellsim-0.1.7 → shellsim-0.1.9}/src/resources.rs +0 -0
  178. {shellsim-0.1.7 → shellsim-0.1.9}/src/sandbox.rs +0 -0
  179. {shellsim-0.1.7 → shellsim-0.1.9}/src/scenario.rs +0 -0
  180. {shellsim-0.1.7 → shellsim-0.1.9}/src/scheduler.rs +0 -0
  181. {shellsim-0.1.7 → shellsim-0.1.9}/src/shell.rs +0 -0
  182. {shellsim-0.1.7 → shellsim-0.1.9}/src/telemetry.rs +0 -0
  183. {shellsim-0.1.7 → shellsim-0.1.9}/src/vfs.rs +0 -0
@@ -330,7 +330,7 @@ dependencies = [
330
330
 
331
331
  [[package]]
332
332
  name = "shellsim"
333
- version = "0.1.7"
333
+ version = "0.1.9"
334
334
  dependencies = [
335
335
  "base64",
336
336
  "crc32fast",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "shellsim"
3
- version = "0.1.7"
3
+ version = "0.1.9"
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.7
3
+ Version: 0.1.9
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: License :: OSI Approved :: Apache Software License
6
6
  Classifier: Operating System :: MacOS :: MacOS X
@@ -160,8 +160,10 @@ printf '%s\n' \
160
160
 
161
161
  CPU is deterministic fuel, memory is modeled working set, disk is current virtual-filesystem
162
162
  usage, and output bounds materialized stdout and stderr. The defaults are 10,000,000 CPU units,
163
- 64 MiB memory, 64 MiB disk, and 4 MiB output. Costs are stable and intentionally approximate.
164
- Exhaustion is observable and never falls back to an ambient host implementation.
163
+ 64 MiB memory, 64 MiB disk, and 4 MiB output. Costs use simple, stable approximations intended to
164
+ be correct within an order of magnitude. They bound runaway work and make runs comparable; they
165
+ do not model allocator layouts or instruction timing exactly. Exhaustion is observable and never
166
+ falls back to an ambient host implementation.
165
167
 
166
168
  For internals and contribution workflow, see [implementation](docs/implementation.md),
167
169
  [Python](docs/python.md), and [CONTRIBUTING.md](CONTRIBUTING.md).
@@ -137,8 +137,10 @@ printf '%s\n' \
137
137
 
138
138
  CPU is deterministic fuel, memory is modeled working set, disk is current virtual-filesystem
139
139
  usage, and output bounds materialized stdout and stderr. The defaults are 10,000,000 CPU units,
140
- 64 MiB memory, 64 MiB disk, and 4 MiB output. Costs are stable and intentionally approximate.
141
- Exhaustion is observable and never falls back to an ambient host implementation.
140
+ 64 MiB memory, 64 MiB disk, and 4 MiB output. Costs use simple, stable approximations intended to
141
+ be correct within an order of magnitude. They bound runaway work and make runs comparable; they
142
+ do not model allocator layouts or instruction timing exactly. Exhaustion is observable and never
143
+ falls back to an ambient host implementation.
142
144
 
143
145
  For internals and contribution workflow, see [implementation](docs/implementation.md),
144
146
  [Python](docs/python.md), and [CONTRIBUTING.md](CONTRIBUTING.md).
@@ -406,7 +406,7 @@ dependencies = [
406
406
 
407
407
  [[package]]
408
408
  name = "shellsim"
409
- version = "0.1.7"
409
+ version = "0.1.9"
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.7"
430
+ version = "0.1.9"
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.7"
5
+ version = "0.1.9"
6
6
  edition = "2021"
7
7
  description = "Python bindings for shellsim"
8
8
  license = "Apache-2.0"
@@ -50,7 +50,7 @@ pub fn register(m: &mut HashMap<&'static str, CommandSpec>) {
50
50
  reg_resumable(m, &["command"], Trust::Real, cmd_command, start_command);
51
51
  reg(m, &["alias"], Trust::Partial, cmd_alias);
52
52
  reg(m, &["unalias"], Trust::Real, cmd_unalias);
53
- reg_unsupported(m, &["getopts"]);
53
+ reg(m, &["getopts"], Trust::Real, cmd_getopts);
54
54
  reg(m, &["let"], Trust::Real, cmd_let);
55
55
  reg(m, &["mapfile", "readarray"], Trust::Real, cmd_mapfile);
56
56
  reg(m, &["pushd"], Trust::Real, cmd_pushd);
@@ -1259,6 +1259,125 @@ fn cmd_shift(interp: &mut CommandContext<'_>, args: &[String], _io: &mut Io) ->
1259
1259
  0
1260
1260
  }
1261
1261
 
1262
+ fn cmd_getopts(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
1263
+ if args.len() < 2 {
1264
+ ewln(io.err, "getopts: usage: getopts optstring name [arg ...]");
1265
+ return 2;
1266
+ }
1267
+ let optstring = &args[0];
1268
+ let name = &args[1];
1269
+ let operands = if args.len() > 2 {
1270
+ args[2..].to_vec()
1271
+ } else {
1272
+ interp.positional.clone()
1273
+ };
1274
+ let visible_optind = interp
1275
+ .get_var("OPTIND")
1276
+ .and_then(|value| value.parse::<usize>().ok())
1277
+ .filter(|value| *value > 0)
1278
+ .unwrap_or(1);
1279
+ if visible_optind != interp.getopts.optind {
1280
+ interp.getopts.optind = visible_optind;
1281
+ interp.getopts.offset = 1;
1282
+ }
1283
+
1284
+ let argument_index = interp.getopts.optind - 1;
1285
+ let Some(argument) = operands.get(argument_index) else {
1286
+ set_getopts_optind(interp);
1287
+ return 1;
1288
+ };
1289
+ if argument == "--" {
1290
+ interp.getopts.optind += 1;
1291
+ interp.getopts.offset = 1;
1292
+ set_getopts_optind(interp);
1293
+ return 1;
1294
+ }
1295
+ let option_characters = argument.chars().collect::<Vec<_>>();
1296
+ if !argument.starts_with('-')
1297
+ || argument == "-"
1298
+ || interp.getopts.offset >= option_characters.len()
1299
+ {
1300
+ set_getopts_optind(interp);
1301
+ return 1;
1302
+ }
1303
+
1304
+ let option = option_characters[interp.getopts.offset];
1305
+ let silent = optstring.starts_with(':');
1306
+ let specification = optstring
1307
+ .trim_start_matches(':')
1308
+ .chars()
1309
+ .collect::<Vec<_>>();
1310
+ let Some(specification_index) = specification
1311
+ .iter()
1312
+ .position(|candidate| *candidate == option)
1313
+ else {
1314
+ advance_getopts(interp, option_characters.len());
1315
+ if silent {
1316
+ interp.set_var("OPTARG", option.to_string());
1317
+ } else {
1318
+ interp.vars.remove("OPTARG");
1319
+ }
1320
+ interp.set_var(name, "?");
1321
+ set_getopts_optind(interp);
1322
+ if !silent {
1323
+ ewln(io.err, &format!("getopts: illegal option -- {option}"));
1324
+ }
1325
+ return 0;
1326
+ };
1327
+ let requires_argument = specification.get(specification_index + 1) == Some(&':');
1328
+ if !requires_argument {
1329
+ advance_getopts(interp, option_characters.len());
1330
+ interp.vars.remove("OPTARG");
1331
+ interp.set_var(name, option.to_string());
1332
+ set_getopts_optind(interp);
1333
+ return 0;
1334
+ }
1335
+
1336
+ let inline = option_characters[interp.getopts.offset + 1..]
1337
+ .iter()
1338
+ .collect::<String>();
1339
+ let option_argument = if !inline.is_empty() {
1340
+ interp.getopts.optind += 1;
1341
+ inline
1342
+ } else if let Some(value) = operands.get(argument_index + 1) {
1343
+ interp.getopts.optind += 2;
1344
+ value.clone()
1345
+ } else {
1346
+ interp.getopts.optind += 1;
1347
+ interp.getopts.offset = 1;
1348
+ set_getopts_optind(interp);
1349
+ interp.set_var(name, if silent { ":" } else { "?" });
1350
+ if silent {
1351
+ interp.set_var("OPTARG", option.to_string());
1352
+ } else {
1353
+ interp.vars.remove("OPTARG");
1354
+ ewln(
1355
+ io.err,
1356
+ &format!("getopts: option requires an argument -- {option}"),
1357
+ );
1358
+ }
1359
+ return 0;
1360
+ };
1361
+ interp.getopts.offset = 1;
1362
+ interp.set_var("OPTARG", option_argument);
1363
+ interp.set_var(name, option.to_string());
1364
+ set_getopts_optind(interp);
1365
+ 0
1366
+ }
1367
+
1368
+ fn advance_getopts(interp: &mut CommandContext<'_>, argument_length: usize) {
1369
+ interp.getopts.offset += 1;
1370
+ if interp.getopts.offset >= argument_length {
1371
+ interp.getopts.optind += 1;
1372
+ interp.getopts.offset = 1;
1373
+ }
1374
+ }
1375
+
1376
+ fn set_getopts_optind(interp: &mut CommandContext<'_>) {
1377
+ let value = interp.getopts.optind.to_string();
1378
+ interp.vars.insert("OPTIND".to_string(), value);
1379
+ }
1380
+
1262
1381
  fn cmd_test(interp: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32 {
1263
1382
  eval_test_cmd(interp, "test", args, io)
1264
1383
  }
@@ -24,6 +24,13 @@ pub enum ArrayVal {
24
24
  Assoc(BTreeMap<String, String>),
25
25
  }
26
26
 
27
+ /// Cursor retained between calls to the `getopts` shell builtin.
28
+ #[derive(Clone, Debug)]
29
+ pub(crate) struct GetoptsState {
30
+ pub(crate) optind: usize,
31
+ pub(crate) offset: usize,
32
+ }
33
+
27
34
  /// A simulated background job started with `&`.
28
35
  ///
29
36
  /// Its process continuation is owned separately by [`ProcessStates`]. This shell-facing record
@@ -144,6 +151,7 @@ pub struct ProcessState {
144
151
  random_state: Cell<u32>,
145
152
  /// positional parameters `$1 $2 ... $@`
146
153
  pub positional: Vec<String>,
154
+ pub(crate) getopts: GetoptsState,
147
155
  /// `set -e` / `set -u` / `set -x`
148
156
  pub opt_errexit: bool,
149
157
  pub opt_nounset: bool,
@@ -316,6 +324,7 @@ impl ProcessState {
316
324
  last_status: self.last_status,
317
325
  random_state: Cell::new(self.random_state.get()),
318
326
  positional: self.positional.clone(),
327
+ getopts: self.getopts.clone(),
319
328
  opt_errexit: self.opt_errexit,
320
329
  opt_nounset: self.opt_nounset,
321
330
  opt_xtrace: self.opt_xtrace,
@@ -550,6 +559,10 @@ impl Environment {
550
559
  last_status: 0,
551
560
  random_state: Cell::new(1),
552
561
  positional: Vec::new(),
562
+ getopts: GetoptsState {
563
+ optind: 1,
564
+ offset: 1,
565
+ },
553
566
  opt_errexit: false,
554
567
  opt_nounset: false,
555
568
  opt_xtrace: false,
@@ -1457,6 +1470,14 @@ impl Environment {
1457
1470
  if name == "PWD" {
1458
1471
  self.cwd = val.clone();
1459
1472
  }
1473
+ if name == "OPTIND" {
1474
+ self.getopts.optind = val
1475
+ .parse::<usize>()
1476
+ .ok()
1477
+ .filter(|value| *value > 0)
1478
+ .unwrap_or(1);
1479
+ self.getopts.offset = 1;
1480
+ }
1460
1481
  // A plain scalar assignment to an array name in bash sets element 0; we instead treat it
1461
1482
  // as a fresh scalar (drop the array) — the common case in our scripts and lower-risk.
1462
1483
  self.arrays.remove(name);
@@ -173,14 +173,13 @@ pub enum ExpressionKind {
173
173
  value: Box<Expression>,
174
174
  clauses: Vec<ComprehensionClause>,
175
175
  },
176
- /// The VM deliberately materializes this as a list for now. This gives
177
- /// generator expressions an ordinary iterable and isolated comprehension
178
- /// scope while keeping the bounded-resource contract simple and explicit.
176
+ /// A lazy comprehension evaluated in its own generator frame.
179
177
  GeneratorExpression {
180
178
  element: Box<Expression>,
181
179
  clauses: Vec<ComprehensionClause>,
182
180
  },
183
181
  Yield(Option<Box<Expression>>),
182
+ YieldFrom(Box<Expression>),
184
183
  Attribute {
185
184
  value: Box<Expression>,
186
185
  name: String,
@@ -936,10 +936,7 @@ impl Compiler {
936
936
  self.emit_dict_comprehension(*key, *value, clauses, span);
937
937
  }
938
938
  ExpressionKind::GeneratorExpression { element, clauses } => {
939
- // The first implementation intentionally materializes generator expressions.
940
- // It is still a separate function scope (like CPython's generator frame), and
941
- // all iteration remains metered by the ordinary bytecode loop.
942
- self.emit_comprehension(ComprehensionKind::List, *element, clauses, span);
939
+ self.emit_generator_expression(*element, clauses, span);
943
940
  }
944
941
  ExpressionKind::Yield(value) => {
945
942
  if !self.in_function {
@@ -947,11 +944,6 @@ impl Compiler {
947
944
  Operation::RuntimeError("'yield' outside function".into()),
948
945
  span,
949
946
  );
950
- } else if let Some(region) = self.protected_regions.last() {
951
- self.emit(
952
- Operation::RuntimeError(format!("yield is not supported inside a {region} cleanup region; suspending here would lose exception or context-manager state")),
953
- span,
954
- );
955
947
  } else {
956
948
  if let Some(value) = value {
957
949
  self.expression(*value);
@@ -961,6 +953,24 @@ impl Compiler {
961
953
  self.emit(Operation::Yield, span);
962
954
  }
963
955
  }
956
+ ExpressionKind::YieldFrom(value) => {
957
+ if !self.in_function {
958
+ self.emit(
959
+ Operation::RuntimeError("'yield from' outside function".into()),
960
+ span,
961
+ );
962
+ } else {
963
+ self.expression(*value);
964
+ self.emit(Operation::GetIterator, span);
965
+ let next = self.emit(Operation::ForIterator(usize::MAX), span);
966
+ self.emit(Operation::Yield, span);
967
+ self.emit(Operation::PopTop, span);
968
+ self.emit(Operation::Jump(next), span);
969
+ let exhausted = self.instructions.len();
970
+ self.patch_jump(next, exhausted);
971
+ self.emit(Operation::LoadConstant(Constant::None), span);
972
+ }
973
+ }
964
974
  ExpressionKind::Attribute { value, name } => {
965
975
  self.expression(*value);
966
976
  self.emit(Operation::LoadAttribute(name), span);
@@ -1216,6 +1226,46 @@ impl Compiler {
1216
1226
  );
1217
1227
  }
1218
1228
 
1229
+ fn emit_generator_expression(
1230
+ &mut self,
1231
+ element: Expression,
1232
+ clauses: Vec<ComprehensionClause>,
1233
+ span: Span,
1234
+ ) {
1235
+ let named_expression = self.comprehension_named_expression_context();
1236
+ let mut nested = Compiler {
1237
+ instructions: Vec::new(),
1238
+ loops: Vec::new(),
1239
+ finalizers: Vec::new(),
1240
+ protected_regions: Vec::new(),
1241
+ in_function: true,
1242
+ globals: HashSet::new(),
1243
+ nonlocals: HashSet::new(),
1244
+ named_expression,
1245
+ is_class_scope: false,
1246
+ structural_depth: self.structural_depth,
1247
+ };
1248
+ nested.emit_generator_comprehension_body(&clauses, 0, &element, span);
1249
+ nested.emit(Operation::LoadConstant(Constant::None), span);
1250
+ nested.emit(Operation::Return, span);
1251
+ self.emit(
1252
+ Operation::MakeFunction {
1253
+ name: "<genexpr>".into(),
1254
+ code: nested.finish(Vec::new()),
1255
+ defaults: 0,
1256
+ },
1257
+ span,
1258
+ );
1259
+ self.emit(
1260
+ Operation::Call {
1261
+ positional: 0,
1262
+ keywords: Vec::new(),
1263
+ starred: Vec::new(),
1264
+ },
1265
+ span,
1266
+ );
1267
+ }
1268
+
1219
1269
  fn emit_dict_comprehension(
1220
1270
  &mut self,
1221
1271
  key: Expression,
@@ -1307,6 +1357,35 @@ impl Compiler {
1307
1357
  self.patch_jump(next, exhausted);
1308
1358
  }
1309
1359
 
1360
+ fn emit_generator_comprehension_body(
1361
+ &mut self,
1362
+ clauses: &[ComprehensionClause],
1363
+ index: usize,
1364
+ element: &Expression,
1365
+ span: Span,
1366
+ ) {
1367
+ let clause = &clauses[index];
1368
+ self.expression(clause.iterable.clone());
1369
+ self.emit(Operation::GetIterator, span);
1370
+ let next = self.emit(Operation::ForIterator(usize::MAX), span);
1371
+ self.store_target(clause.target.clone(), span);
1372
+ for condition in &clause.conditions {
1373
+ self.expression(condition.clone());
1374
+ let skip = self.emit(Operation::PopJumpIfFalse(usize::MAX), span);
1375
+ self.patch_jump(skip, next);
1376
+ }
1377
+ if index + 1 < clauses.len() {
1378
+ self.emit_generator_comprehension_body(clauses, index + 1, element, span);
1379
+ } else {
1380
+ self.expression(element.clone());
1381
+ self.emit(Operation::Yield, span);
1382
+ self.emit(Operation::PopTop, span);
1383
+ }
1384
+ self.emit(Operation::Jump(next), span);
1385
+ let exhausted = self.instructions.len();
1386
+ self.patch_jump(next, exhausted);
1387
+ }
1388
+
1310
1389
  fn emit_dict_comprehension_body(
1311
1390
  &mut self,
1312
1391
  clauses: &[ComprehensionClause],