shellsim 0.1.9__tar.gz → 0.1.11__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.9 → shellsim-0.1.11}/Cargo.lock +1 -1
  2. {shellsim-0.1.9 → shellsim-0.1.11}/Cargo.toml +1 -1
  3. {shellsim-0.1.9 → shellsim-0.1.11}/PKG-INFO +1 -1
  4. {shellsim-0.1.9 → shellsim-0.1.11}/python/native/Cargo.lock +2 -2
  5. {shellsim-0.1.9 → shellsim-0.1.11}/python/native/Cargo.toml +1 -1
  6. {shellsim-0.1.9 → shellsim-0.1.11}/src/exec.rs +8 -16
  7. {shellsim-0.1.9 → shellsim-0.1.11}/src/harness.rs +5 -1
  8. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/ast.rs +15 -0
  9. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/bytecode.rs +13 -0
  10. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/compiler.rs +135 -1
  11. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/heap.rs +8 -1
  12. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/lexer.rs +2 -0
  13. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/mod.rs +1 -0
  14. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/native.rs +23 -1
  15. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/parser.rs +40 -9
  16. shellsim-0.1.11/src/python/stdlib/asyncio.rs +232 -0
  17. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/frozen.rs +1 -0
  18. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/mod.rs +2 -0
  19. shellsim-0.1.11/src/python/stdlib/source/asyncio.py +555 -0
  20. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/token.rs +2 -0
  21. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/vm/calls.rs +2 -1
  22. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/vm/dispatch.rs +65 -0
  23. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/vm/host.rs +25 -2
  24. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/vm/iteration.rs +15 -1
  25. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/vm/namespace.rs +10 -0
  26. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/vm/native_runtime.rs +108 -0
  27. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/vm/objects.rs +26 -0
  28. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/vm.rs +10 -5
  29. {shellsim-0.1.9 → shellsim-0.1.11}/src/scheduler.rs +84 -31
  30. {shellsim-0.1.9 → shellsim-0.1.11}/LICENSE +0 -0
  31. {shellsim-0.1.9 → shellsim-0.1.11}/README.md +0 -0
  32. {shellsim-0.1.9 → shellsim-0.1.11}/pyproject.toml +0 -0
  33. {shellsim-0.1.9 → shellsim-0.1.11}/python/native/src/lib.rs +0 -0
  34. {shellsim-0.1.9 → shellsim-0.1.11}/python/shellsim/__init__.py +0 -0
  35. {shellsim-0.1.9 → shellsim-0.1.11}/python/shellsim/__main__.py +0 -0
  36. {shellsim-0.1.9 → shellsim-0.1.11}/python/shellsim/_api.py +0 -0
  37. {shellsim-0.1.9 → shellsim-0.1.11}/python/shellsim/_cli.py +0 -0
  38. {shellsim-0.1.9 → shellsim-0.1.11}/python/shellsim/py.typed +0 -0
  39. {shellsim-0.1.9 → shellsim-0.1.11}/python/shellsim/python.py +0 -0
  40. {shellsim-0.1.9 → shellsim-0.1.11}/rust-toolchain.toml +0 -0
  41. {shellsim-0.1.9 → shellsim-0.1.11}/src/bin/shellsim-python.rs +0 -0
  42. {shellsim-0.1.9 → shellsim-0.1.11}/src/clock.rs +0 -0
  43. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/archives.rs +0 -0
  44. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/awk/ast.rs +0 -0
  45. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/awk/eval.rs +0 -0
  46. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/awk/lexer.rs +0 -0
  47. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/awk/parser.rs +0 -0
  48. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/awk.rs +0 -0
  49. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/builtins.rs +0 -0
  50. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/dd.rs +0 -0
  51. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/echo.rs +0 -0
  52. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/find.rs +0 -0
  53. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/fs.rs +0 -0
  54. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/apply.rs +0 -0
  55. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/commit.rs +0 -0
  56. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/compare.rs +0 -0
  57. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/config.rs +0 -0
  58. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/conflict.rs +0 -0
  59. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/diff.rs +0 -0
  60. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/ignore.rs +0 -0
  61. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/import.rs +0 -0
  62. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/log.rs +0 -0
  63. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/merge.rs +0 -0
  64. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/mod.rs +0 -0
  65. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/plumbing.rs +0 -0
  66. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/rebase.rs +0 -0
  67. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/refs.rs +0 -0
  68. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/repo.rs +0 -0
  69. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/stash.rs +0 -0
  70. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/switch.rs +0 -0
  71. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/git/worktree.rs +0 -0
  72. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/hashing.rs +0 -0
  73. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/makecmd.rs +0 -0
  74. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/mod.rs +0 -0
  75. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/net.rs +0 -0
  76. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/options.rs +0 -0
  77. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/patch.rs +0 -0
  78. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/pkg.rs +0 -0
  79. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/printf.rs +0 -0
  80. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/proc.rs +0 -0
  81. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/regex_compat.rs +0 -0
  82. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/rgcmd.rs +0 -0
  83. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/sed.rs +0 -0
  84. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/sort.rs +0 -0
  85. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/system.rs +0 -0
  86. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/tarcmd.rs +0 -0
  87. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/text.rs +0 -0
  88. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/unavailable.rs +0 -0
  89. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/util.rs +0 -0
  90. {shellsim-0.1.9 → shellsim-0.1.11}/src/commands/zipcmd.rs +0 -0
  91. {shellsim-0.1.9 → shellsim-0.1.11}/src/descriptors.rs +0 -0
  92. {shellsim-0.1.9 → shellsim-0.1.11}/src/expand.rs +0 -0
  93. {shellsim-0.1.9 → shellsim-0.1.11}/src/harness_manager.rs +0 -0
  94. {shellsim-0.1.9 → shellsim-0.1.11}/src/hashes.rs +0 -0
  95. {shellsim-0.1.9 → shellsim-0.1.11}/src/host_ingest.rs +0 -0
  96. {shellsim-0.1.9 → shellsim-0.1.11}/src/interp.rs +0 -0
  97. {shellsim-0.1.9 → shellsim-0.1.11}/src/jqcmd.rs +0 -0
  98. {shellsim-0.1.9 → shellsim-0.1.11}/src/lib.rs +0 -0
  99. {shellsim-0.1.9 → shellsim-0.1.11}/src/main.rs +0 -0
  100. {shellsim-0.1.9 → shellsim-0.1.11}/src/mcp.rs +0 -0
  101. {shellsim-0.1.9 → shellsim-0.1.11}/src/net.rs +0 -0
  102. {shellsim-0.1.9 → shellsim-0.1.11}/src/netcmd.rs +0 -0
  103. {shellsim-0.1.9 → shellsim-0.1.11}/src/process.rs +0 -0
  104. {shellsim-0.1.9 → shellsim-0.1.11}/src/pseudo_fs.rs +0 -0
  105. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/filesystem.rs +0 -0
  106. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/http.rs +0 -0
  107. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/mapping.rs +0 -0
  108. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/number.rs +0 -0
  109. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/object_model.rs +0 -0
  110. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/process.rs +0 -0
  111. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/protocol.rs +0 -0
  112. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/slice.rs +0 -0
  113. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/source.rs +0 -0
  114. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/argparse.rs +0 -0
  115. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/base64.rs +0 -0
  116. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/bisect.rs +0 -0
  117. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/collections.rs +0 -0
  118. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/core.rs +0 -0
  119. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/dataclasses.rs +0 -0
  120. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/enum.rs +0 -0
  121. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/functools.rs +0 -0
  122. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/hashlib.rs +0 -0
  123. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/heapq.rs +0 -0
  124. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/http.rs +0 -0
  125. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/importlib.rs +0 -0
  126. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/itertools.rs +0 -0
  127. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/json.rs +0 -0
  128. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/math.rs +0 -0
  129. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/numpy.rs +0 -0
  130. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/os.rs +0 -0
  131. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/pytest.rs +0 -0
  132. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/re.rs +0 -0
  133. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/abc.py +0 -0
  134. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/base64.py +0 -0
  135. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/cmath.py +0 -0
  136. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/codecs.py +0 -0
  137. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/collections.py +0 -0
  138. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/complex.py +0 -0
  139. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/copy.py +0 -0
  140. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/csv.py +0 -0
  141. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/datetime.py +0 -0
  142. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/glob.py +0 -0
  143. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/hashlib.py +0 -0
  144. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/http.py +0 -0
  145. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/http_client.py +0 -0
  146. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/importlib.py +0 -0
  147. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/io.py +0 -0
  148. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/json.py +0 -0
  149. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/logging.py +0 -0
  150. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/numpy_random.py +0 -0
  151. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/os.py +0 -0
  152. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/pathlib.py +0 -0
  153. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/pytest.py +0 -0
  154. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/random.py +0 -0
  155. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/shutil.py +0 -0
  156. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/statistics.py +0 -0
  157. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/struct.py +0 -0
  158. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/subprocess.py +0 -0
  159. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/tempfile.py +0 -0
  160. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/textwrap.py +0 -0
  161. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/urllib.py +0 -0
  162. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/urllib_error.py +0 -0
  163. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/urllib_request.py +0 -0
  164. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/uuid.py +0 -0
  165. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/zipfile.py +0 -0
  166. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/source/zlib.py +0 -0
  167. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/string.rs +0 -0
  168. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/struct.rs +0 -0
  169. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/subprocess.rs +0 -0
  170. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/sys.rs +0 -0
  171. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/time.rs +0 -0
  172. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/typing.rs +0 -0
  173. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/unittest.rs +0 -0
  174. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/vfs.rs +0 -0
  175. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/stdlib/zlib.rs +0 -0
  176. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/string.rs +0 -0
  177. {shellsim-0.1.9 → shellsim-0.1.11}/src/python/vm/operations.rs +0 -0
  178. {shellsim-0.1.9 → shellsim-0.1.11}/src/resources.rs +0 -0
  179. {shellsim-0.1.9 → shellsim-0.1.11}/src/sandbox.rs +0 -0
  180. {shellsim-0.1.9 → shellsim-0.1.11}/src/scenario.rs +0 -0
  181. {shellsim-0.1.9 → shellsim-0.1.11}/src/shell.rs +0 -0
  182. {shellsim-0.1.9 → shellsim-0.1.11}/src/telemetry.rs +0 -0
  183. {shellsim-0.1.9 → shellsim-0.1.11}/src/vfs.rs +0 -0
@@ -330,7 +330,7 @@ dependencies = [
330
330
 
331
331
  [[package]]
332
332
  name = "shellsim"
333
- version = "0.1.9"
333
+ version = "0.1.11"
334
334
  dependencies = [
335
335
  "base64",
336
336
  "crc32fast",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "shellsim"
3
- version = "0.1.9"
3
+ version = "0.1.11"
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.9
3
+ Version: 0.1.11
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.9"
409
+ version = "0.1.11"
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.9"
430
+ version = "0.1.11"
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.9"
5
+ version = "0.1.11"
6
6
  edition = "2021"
7
7
  description = "Python bindings for shellsim"
8
8
  license = "Apache-2.0"
@@ -405,7 +405,7 @@ enum ExpansionLocation {
405
405
  Redirect(usize),
406
406
  }
407
407
 
408
- #[derive(Clone, Copy, Debug, PartialEq, Eq)]
408
+ #[derive(Clone, Debug, PartialEq, Eq)]
409
409
  pub(crate) enum ShellPoll {
410
410
  Pending,
411
411
  Blocked(crate::scheduler::WaitReason),
@@ -2507,21 +2507,13 @@ fn handle_ready_events(interp: &mut Interp) -> Result<(), String> {
2507
2507
  continue;
2508
2508
  };
2509
2509
  let deadline = event.id.deadline_ns();
2510
- let timed_wait = matches!(
2511
- interp.scheduler.state(pid),
2512
- Some(crate::scheduler::TaskState::Blocked(
2513
- crate::scheduler::WaitReason::Timer(value)
2514
- | crate::scheduler::WaitReason::ChildDeadline(_, value)
2515
- | crate::scheduler::WaitReason::ChildActivityDeadline(_, value)
2516
- ))
2517
- | Some(crate::scheduler::TaskState::Stopped(
2518
- crate::scheduler::StoppedTask::Blocked(
2519
- crate::scheduler::WaitReason::Timer(value)
2520
- | crate::scheduler::WaitReason::ChildDeadline(_, value)
2521
- | crate::scheduler::WaitReason::ChildActivityDeadline(_, value)
2522
- )
2523
- )) if value == deadline
2524
- );
2510
+ let timed_wait = match interp.scheduler.state(pid) {
2511
+ Some(crate::scheduler::TaskState::Blocked(reason))
2512
+ | Some(crate::scheduler::TaskState::Stopped(
2513
+ crate::scheduler::StoppedTask::Blocked(reason),
2514
+ )) => reason.accepts_timer(deadline),
2515
+ _ => false,
2516
+ };
2525
2517
  if timed_wait {
2526
2518
  interp
2527
2519
  .scheduler
@@ -304,7 +304,7 @@ pub enum ProcessViewStatus {
304
304
  }
305
305
 
306
306
  /// Serializable scheduler wait reason without exposing implementation strings.
307
- #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
307
+ #[derive(Clone, Debug, PartialEq, Eq, Serialize)]
308
308
  #[serde(tag = "kind", rename_all = "snake_case")]
309
309
  pub enum WaitReasonView {
310
310
  Timer { deadline_ns: u64 },
@@ -315,6 +315,7 @@ pub enum WaitReasonView {
315
315
  ChildActivity { pid: u32 },
316
316
  ChildDeadline { pid: u32, deadline_ns: u64 },
317
317
  ChildActivityDeadline { pid: u32, deadline_ns: u64 },
318
+ Any { reasons: Vec<WaitReasonView> },
318
319
  }
319
320
 
320
321
  /// One stable path-level difference from the last checkpoint.
@@ -1259,6 +1260,9 @@ fn wait_reason_view(reason: WaitReason) -> WaitReasonView {
1259
1260
  WaitReason::ChildActivityDeadline(pid, deadline_ns) => {
1260
1261
  WaitReasonView::ChildActivityDeadline { pid, deadline_ns }
1261
1262
  }
1263
+ WaitReason::Any(reasons) => WaitReasonView::Any {
1264
+ reasons: reasons.into_iter().map(wait_reason_view).collect(),
1265
+ },
1262
1266
  }
1263
1267
  }
1264
1268
 
@@ -50,10 +50,17 @@ pub enum StatementKind {
50
50
  body: Vec<Statement>,
51
51
  otherwise: Vec<Statement>,
52
52
  },
53
+ AsyncFor {
54
+ target: AssignmentTarget,
55
+ iterable: Expression,
56
+ body: Vec<Statement>,
57
+ otherwise: Vec<Statement>,
58
+ },
53
59
  Function {
54
60
  name: String,
55
61
  parameters: Vec<Parameter>,
56
62
  body: Vec<Statement>,
63
+ is_async: bool,
57
64
  },
58
65
  Class {
59
66
  name: String,
@@ -91,6 +98,11 @@ pub enum StatementKind {
91
98
  target: Option<AssignmentTarget>,
92
99
  body: Vec<Statement>,
93
100
  },
101
+ AsyncWith {
102
+ context: Expression,
103
+ target: Option<AssignmentTarget>,
104
+ body: Vec<Statement>,
105
+ },
94
106
  }
95
107
 
96
108
  #[derive(Clone, Debug, PartialEq)]
@@ -122,11 +134,13 @@ impl StatementKind {
122
134
  Self::If { .. }
123
135
  | Self::While { .. }
124
136
  | Self::For { .. }
137
+ | Self::AsyncFor { .. }
125
138
  | Self::Function { .. }
126
139
  | Self::Class { .. }
127
140
  | Self::Decorated { .. }
128
141
  | Self::Try { .. }
129
142
  | Self::With { .. }
143
+ | Self::AsyncWith { .. }
130
144
  )
131
145
  }
132
146
  }
@@ -180,6 +194,7 @@ pub enum ExpressionKind {
180
194
  },
181
195
  Yield(Option<Box<Expression>>),
182
196
  YieldFrom(Box<Expression>),
197
+ Await(Box<Expression>),
183
198
  Attribute {
184
199
  value: Box<Expression>,
185
200
  name: String,
@@ -65,6 +65,7 @@ pub struct Code {
65
65
  #[derive(Clone, Debug, PartialEq, Eq)]
66
66
  pub struct CallSignature {
67
67
  pub is_generator: bool,
68
+ pub is_coroutine: bool,
68
69
  pub positional_count: usize,
69
70
  pub variadic_slot: Option<usize>,
70
71
  pub default_slots: Box<[usize]>,
@@ -223,9 +224,12 @@ pub enum Opcode {
223
224
  Reraise,
224
225
  Raise(bool),
225
226
  Yield,
227
+ AwaitResult,
226
228
  WithEnter,
227
229
  WithExit,
228
230
  WithExitException,
231
+ AsyncWithExitException,
232
+ AsyncWithFinishException,
229
233
  PopExpression,
230
234
  Halt,
231
235
  }
@@ -321,9 +325,13 @@ pub enum Operation {
321
325
  Raise(bool),
322
326
  /// Suspend a generator frame and return the value on top of the stack.
323
327
  Yield,
328
+ /// Unwrap the scheduler outcome sent into a suspended coroutine.
329
+ AwaitResult,
324
330
  WithEnter,
325
331
  WithExit,
326
332
  WithExitException,
333
+ AsyncWithExitException,
334
+ AsyncWithFinishException,
327
335
  PopExpression,
328
336
  Halt,
329
337
  }
@@ -489,9 +497,12 @@ impl CodeBuilder {
489
497
  Operation::Reraise => Opcode::Reraise,
490
498
  Operation::Raise(cause) => Opcode::Raise(cause),
491
499
  Operation::Yield => Opcode::Yield,
500
+ Operation::AwaitResult => Opcode::AwaitResult,
492
501
  Operation::WithEnter => Opcode::WithEnter,
493
502
  Operation::WithExit => Opcode::WithExit,
494
503
  Operation::WithExitException => Opcode::WithExitException,
504
+ Operation::AsyncWithExitException => Opcode::AsyncWithExitException,
505
+ Operation::AsyncWithFinishException => Opcode::AsyncWithFinishException,
495
506
  Operation::PopExpression => Opcode::PopExpression,
496
507
  Operation::Halt => Opcode::Halt,
497
508
  }
@@ -503,6 +514,7 @@ impl CodeBuilder {
503
514
  spans: Vec<Span>,
504
515
  parameters: Vec<Parameter>,
505
516
  local_names: Vec<String>,
517
+ is_coroutine: bool,
506
518
  ) -> CodeRef {
507
519
  let positional_count = parameters
508
520
  .iter()
@@ -512,6 +524,7 @@ impl CodeBuilder {
512
524
  is_generator: instructions
513
525
  .iter()
514
526
  .any(|instruction| matches!(instruction.opcode, Opcode::Yield)),
527
+ is_coroutine,
515
528
  positional_count,
516
529
  variadic_slot: parameters.iter().position(|parameter| parameter.variadic),
517
530
  default_slots: parameters
@@ -15,6 +15,7 @@ pub fn compile(program: Program) -> CodeRef {
15
15
  finalizers: Vec::new(),
16
16
  protected_regions: Vec::new(),
17
17
  in_function: false,
18
+ is_coroutine: false,
18
19
  globals: HashSet::new(),
19
20
  nonlocals: HashSet::new(),
20
21
  named_expression: NamedExpressionContext::local(),
@@ -38,6 +39,7 @@ struct Compiler {
38
39
  /// at compile time rather than losing that state in a suspended generator.
39
40
  protected_regions: Vec<&'static str>,
40
41
  in_function: bool,
42
+ is_coroutine: bool,
41
43
  globals: HashSet<String>,
42
44
  nonlocals: HashSet<String>,
43
45
  named_expression: NamedExpressionContext,
@@ -155,7 +157,7 @@ impl Compiler {
155
157
  });
156
158
  spans.push(instruction.span);
157
159
  }
158
- builder.finish(bytecode, spans, parameters, local_names)
160
+ builder.finish(bytecode, spans, parameters, local_names, self.is_coroutine)
159
161
  }
160
162
 
161
163
  fn statements(&mut self, statements: Vec<Statement>) {
@@ -320,10 +322,70 @@ impl Compiler {
320
322
  let finished = self.instructions.len();
321
323
  self.finish_loop(finished);
322
324
  }
325
+ StatementKind::AsyncFor {
326
+ target,
327
+ iterable,
328
+ body,
329
+ otherwise,
330
+ } => {
331
+ let iterator_name =
332
+ format!("$__shellsim_async_iterator_{}", self.instructions.len());
333
+ self.expression(iterable);
334
+ self.emit(Operation::LoadAttribute("__aiter__".into()), span);
335
+ self.emit(
336
+ Operation::Call {
337
+ positional: 0,
338
+ keywords: Vec::new(),
339
+ starred: Vec::new(),
340
+ },
341
+ span,
342
+ );
343
+ self.emit(Operation::StoreName(iterator_name.clone()), span);
344
+ let next = self.instructions.len();
345
+ let begin = self.emit(Operation::TryBegin(usize::MAX), span);
346
+ self.emit(Operation::LoadName(iterator_name), span);
347
+ self.emit(Operation::LoadAttribute("__anext__".into()), span);
348
+ self.emit(
349
+ Operation::Call {
350
+ positional: 0,
351
+ keywords: Vec::new(),
352
+ starred: Vec::new(),
353
+ },
354
+ span,
355
+ );
356
+ self.emit(Operation::Yield, span);
357
+ self.emit(Operation::AwaitResult, span);
358
+ self.emit(Operation::TryEnd, span);
359
+ self.store_target(target, span);
360
+ self.loops.push(LoopContext {
361
+ continue_target: next,
362
+ iterator_on_stack: false,
363
+ finalizer_depth: self.finalizers.len(),
364
+ breaks: Vec::new(),
365
+ });
366
+ self.statements(body);
367
+ self.emit(Operation::Jump(next), span);
368
+ let handler = self.instructions.len();
369
+ self.patch_try_begin(begin, handler);
370
+ self.emit(Operation::LoadName("StopAsyncIteration".into()), span);
371
+ self.emit(Operation::MatchException { typed: true }, span);
372
+ let rethrow = self.emit(Operation::PopJumpIfFalse(usize::MAX), span);
373
+ self.emit(Operation::PopTop, span);
374
+ self.emit(Operation::ClearException, span);
375
+ self.statements(otherwise);
376
+ let done = self.emit(Operation::Jump(usize::MAX), span);
377
+ self.patch_jump(rethrow, self.instructions.len());
378
+ self.emit(Operation::PopTop, span);
379
+ self.emit(Operation::Reraise, span);
380
+ let finished = self.instructions.len();
381
+ self.patch_jump(done, finished);
382
+ self.finish_loop(finished);
383
+ }
323
384
  StatementKind::Function {
324
385
  name,
325
386
  parameters,
326
387
  body,
388
+ is_async,
327
389
  } => {
328
390
  let defaults = parameters
329
391
  .iter()
@@ -338,6 +400,7 @@ impl Compiler {
338
400
  finalizers: Vec::new(),
339
401
  protected_regions: Vec::new(),
340
402
  in_function: true,
403
+ is_coroutine: is_async,
341
404
  globals: HashSet::new(),
342
405
  nonlocals: HashSet::new(),
343
406
  named_expression: NamedExpressionContext::local(),
@@ -391,6 +454,7 @@ impl Compiler {
391
454
  finalizers: Vec::new(),
392
455
  protected_regions: Vec::new(),
393
456
  in_function: false,
457
+ is_coroutine: false,
394
458
  globals: HashSet::new(),
395
459
  nonlocals: HashSet::new(),
396
460
  named_expression: NamedExpressionContext::local(),
@@ -660,6 +724,60 @@ impl Compiler {
660
724
  self.emit(Operation::WithExitException, span);
661
725
  self.patch_jump(done, self.instructions.len());
662
726
  }
727
+ StatementKind::AsyncWith {
728
+ context,
729
+ target,
730
+ body,
731
+ } => {
732
+ let context_name = format!("$__shellsim_async_context_{}", self.instructions.len());
733
+ self.expression(context);
734
+ self.emit(Operation::StoreName(context_name.clone()), span);
735
+ self.emit(Operation::LoadName(context_name.clone()), span);
736
+ self.emit(Operation::LoadAttribute("__aenter__".into()), span);
737
+ self.emit(
738
+ Operation::Call {
739
+ positional: 0,
740
+ keywords: Vec::new(),
741
+ starred: Vec::new(),
742
+ },
743
+ span,
744
+ );
745
+ self.emit(Operation::Yield, span);
746
+ self.emit(Operation::AwaitResult, span);
747
+ if let Some(target) = target {
748
+ self.store_target(target, span);
749
+ } else {
750
+ self.emit(Operation::PopTop, span);
751
+ }
752
+ let begin = self.emit(Operation::TryBegin(usize::MAX), span);
753
+ self.statements(body);
754
+ self.emit(Operation::TryEnd, span);
755
+ self.emit(Operation::LoadName(context_name.clone()), span);
756
+ self.emit(Operation::LoadAttribute("__aexit__".into()), span);
757
+ for _ in 0..3 {
758
+ self.emit(Operation::LoadConstant(Constant::None), span);
759
+ }
760
+ self.emit(
761
+ Operation::Call {
762
+ positional: 3,
763
+ keywords: Vec::new(),
764
+ starred: vec![false; 3],
765
+ },
766
+ span,
767
+ );
768
+ self.emit(Operation::Yield, span);
769
+ self.emit(Operation::AwaitResult, span);
770
+ self.emit(Operation::PopTop, span);
771
+ let done = self.emit(Operation::Jump(usize::MAX), span);
772
+ let handler = self.instructions.len();
773
+ self.patch_try_begin(begin, handler);
774
+ self.emit(Operation::LoadName(context_name), span);
775
+ self.emit(Operation::AsyncWithExitException, span);
776
+ self.emit(Operation::Yield, span);
777
+ self.emit(Operation::AwaitResult, span);
778
+ self.emit(Operation::AsyncWithFinishException, span);
779
+ self.patch_jump(done, self.instructions.len());
780
+ }
663
781
  }
664
782
  }
665
783
 
@@ -971,6 +1089,18 @@ impl Compiler {
971
1089
  self.emit(Operation::LoadConstant(Constant::None), span);
972
1090
  }
973
1091
  }
1092
+ ExpressionKind::Await(value) => {
1093
+ if !self.is_coroutine {
1094
+ self.emit(
1095
+ Operation::RuntimeError("'await' outside async function".into()),
1096
+ span,
1097
+ );
1098
+ } else {
1099
+ self.expression(*value);
1100
+ self.emit(Operation::Yield, span);
1101
+ self.emit(Operation::AwaitResult, span);
1102
+ }
1103
+ }
974
1104
  ExpressionKind::Attribute { value, name } => {
975
1105
  self.expression(*value);
976
1106
  self.emit(Operation::LoadAttribute(name), span);
@@ -1042,6 +1172,7 @@ impl Compiler {
1042
1172
  finalizers: Vec::new(),
1043
1173
  protected_regions: Vec::new(),
1044
1174
  in_function: true,
1175
+ is_coroutine: false,
1045
1176
  globals: HashSet::new(),
1046
1177
  nonlocals: HashSet::new(),
1047
1178
  named_expression: NamedExpressionContext::local(),
@@ -1190,6 +1321,7 @@ impl Compiler {
1190
1321
  finalizers: Vec::new(),
1191
1322
  protected_regions: Vec::new(),
1192
1323
  in_function: true,
1324
+ is_coroutine: false,
1193
1325
  globals: HashSet::new(),
1194
1326
  nonlocals: HashSet::new(),
1195
1327
  named_expression,
@@ -1239,6 +1371,7 @@ impl Compiler {
1239
1371
  finalizers: Vec::new(),
1240
1372
  protected_regions: Vec::new(),
1241
1373
  in_function: true,
1374
+ is_coroutine: false,
1242
1375
  globals: HashSet::new(),
1243
1376
  nonlocals: HashSet::new(),
1244
1377
  named_expression,
@@ -1280,6 +1413,7 @@ impl Compiler {
1280
1413
  finalizers: Vec::new(),
1281
1414
  protected_regions: Vec::new(),
1282
1415
  in_function: true,
1416
+ is_coroutine: false,
1283
1417
  globals: HashSet::new(),
1284
1418
  nonlocals: HashSet::new(),
1285
1419
  named_expression,
@@ -212,6 +212,7 @@ pub enum Object {
212
212
  stack: Vec<Value>,
213
213
  exhausted: bool,
214
214
  running: bool,
215
+ return_value: Value,
215
216
  },
216
217
  Module {
217
218
  name: String,
@@ -1430,9 +1431,15 @@ fn trace_object(
1430
1431
  trace_value(*callable, object_work);
1431
1432
  trace_value(*sentinel, object_work);
1432
1433
  }
1433
- Object::Generator { scope, stack, .. } => {
1434
+ Object::Generator {
1435
+ scope,
1436
+ stack,
1437
+ return_value,
1438
+ ..
1439
+ } => {
1434
1440
  scope_work.push(*scope);
1435
1441
  trace_values(stack.iter().copied(), object_work);
1442
+ trace_value(*return_value, object_work);
1436
1443
  }
1437
1444
  Object::Module { scope, .. } => scope_work.push(*scope),
1438
1445
  Object::Array { storage, .. } => object_work.push(*storage),
@@ -280,6 +280,8 @@ impl<'a> Lexer<'a> {
280
280
  "raise" => TokenKind::Raise,
281
281
  "with" => TokenKind::With,
282
282
  "yield" => TokenKind::Yield,
283
+ "async" => TokenKind::Async,
284
+ "await" => TokenKind::Await,
283
285
  name => TokenKind::Name((*name).to_string()),
284
286
  }
285
287
  }
@@ -1302,6 +1302,7 @@ fn collect_pytest_functions(source: &str) -> Result<PytestCollection, String> {
1302
1302
  name,
1303
1303
  parameters,
1304
1304
  body,
1305
+ ..
1305
1306
  } = function
1306
1307
  else {
1307
1308
  unreachable!()
@@ -81,7 +81,7 @@ impl fmt::Debug for ValueKindDef {
81
81
  }
82
82
 
83
83
  /// Stable error categories produced by native Python operations.
84
- #[derive(Clone, Copy, Debug, PartialEq, Eq)]
84
+ #[derive(Clone, Debug, PartialEq, Eq)]
85
85
  pub(super) enum PyErrorKind {
86
86
  Type,
87
87
  Value,
@@ -298,6 +298,12 @@ pub(super) struct PyProcessOutput {
298
298
  pub inherited_stderr: Vec<u8>,
299
299
  }
300
300
 
301
+ /// Result of probing a modeled process operation without driving the scheduler.
302
+ pub(super) enum PyProcessPoll<T> {
303
+ Ready(T),
304
+ Blocked(crate::scheduler::WaitReason),
305
+ }
306
+
301
307
  /// Fully-owned request for a child that outlives the native launch call.
302
308
  pub(super) struct PyProcessStartRequest {
303
309
  pub argv: Vec<String>,
@@ -339,8 +345,19 @@ pub(super) trait PyProcessRunner {
339
345
  fd: i32,
340
346
  amount: Option<usize>,
341
347
  ) -> PyResult<Vec<u8>>;
348
+ fn try_read_pipe(
349
+ &mut self,
350
+ handle: PyProcessHandle,
351
+ fd: i32,
352
+ amount: Option<usize>,
353
+ ) -> PyResult<PyProcessPoll<Vec<u8>>>;
342
354
  /// Write bytes to a captured child stdin, cooperatively scheduling while the pipe is full.
343
355
  fn write_pipe(&mut self, handle: PyProcessHandle, input: Vec<u8>) -> PyResult<usize>;
356
+ fn try_write_pipe(
357
+ &mut self,
358
+ handle: PyProcessHandle,
359
+ input: Vec<u8>,
360
+ ) -> PyResult<PyProcessPoll<usize>>;
344
361
  /// Close one parent-side captured stream endpoint.
345
362
  fn close_pipe(&mut self, handle: PyProcessHandle, fd: i32) -> PyResult<()>;
346
363
  fn send_signal(
@@ -452,6 +469,9 @@ pub(super) trait PyRuntime {
452
469
  generator: PyIterator,
453
470
  value: PyValue,
454
471
  ) -> PyResult<Option<PyValue>>;
472
+ fn generator_return_value(&self, generator: PyIterator) -> PyResult<PyValue>;
473
+ /// Resume a coroutine and return 0 for yield, 1 for return, or 2 for an exception.
474
+ fn coroutine_step(&mut self, coroutine: PyIterator, value: PyValue) -> PyResult<(u8, PyValue)>;
455
475
  fn generator_close(&mut self, generator: PyIterator) -> PyResult<()>;
456
476
  fn generator_throw(&mut self, generator: PyIterator, exception: PyValue) -> PyResult;
457
477
  fn new_iterator(&mut self, values: Vec<PyValue>) -> PyResult<PyValue>;
@@ -554,6 +574,8 @@ pub(super) trait PyRuntime {
554
574
  fn exception_type_name(&self, value: &PyValue) -> Option<&'static str>;
555
575
  /// Return one interpreter-owned exception class from the runtime's closed type table.
556
576
  fn exception_type(&self, name: &'static str) -> PyValue;
577
+ /// Suspend the enclosing logical process until any modeled resource becomes ready.
578
+ fn wait_on(&mut self, reasons: Vec<crate::scheduler::WaitReason>) -> PyResult<()>;
557
579
  fn clock(&mut self) -> &mut dyn PyClock;
558
580
  fn environment(&self) -> &dyn PyEnvironment;
559
581
  fn filesystem(&mut self) -> &mut dyn PyFilesystem;
@@ -90,6 +90,12 @@ impl Parser {
90
90
  )?;
91
91
  self.separators();
92
92
  }
93
+ let is_async = self.take(|kind| matches!(kind, TokenKind::Async)).is_some();
94
+ if is_async
95
+ && !self.at(|kind| matches!(kind, TokenKind::Def | TokenKind::For | TokenKind::With))
96
+ {
97
+ return Err(self.error("expected 'def', 'for', or 'with' after 'async'"));
98
+ }
93
99
  let kind = if self.take(|kind| matches!(kind, TokenKind::If)).is_some() {
94
100
  self.if_statement(start)?
95
101
  } else if self.take(|kind| matches!(kind, TokenKind::Try)).is_some() {
@@ -145,10 +151,18 @@ impl Parser {
145
151
  None
146
152
  };
147
153
  let body = self.suite()?;
148
- StatementKind::With {
149
- context,
150
- target,
151
- body,
154
+ if is_async {
155
+ StatementKind::AsyncWith {
156
+ context,
157
+ target,
158
+ body,
159
+ }
160
+ } else {
161
+ StatementKind::With {
162
+ context,
163
+ target,
164
+ body,
165
+ }
152
166
  }
153
167
  } else if self.take(|kind| matches!(kind, TokenKind::While)).is_some() {
154
168
  let test = self.expression()?;
@@ -176,11 +190,20 @@ impl Parser {
176
190
  } else {
177
191
  Vec::new()
178
192
  };
179
- StatementKind::For {
180
- target,
181
- iterable,
182
- body,
183
- otherwise,
193
+ if is_async {
194
+ StatementKind::AsyncFor {
195
+ target,
196
+ iterable,
197
+ body,
198
+ otherwise,
199
+ }
200
+ } else {
201
+ StatementKind::For {
202
+ target,
203
+ iterable,
204
+ body,
205
+ otherwise,
206
+ }
184
207
  }
185
208
  } else if self.take(|kind| matches!(kind, TokenKind::Def)).is_some() {
186
209
  let name = self.name("expected a function name after 'def'")?;
@@ -257,6 +280,7 @@ impl Parser {
257
280
  name,
258
281
  parameters,
259
282
  body: self.suite()?,
283
+ is_async,
260
284
  }
261
285
  } else if self.take(|kind| matches!(kind, TokenKind::Class)).is_some() {
262
286
  let name = self.name("expected a class name after 'class'")?;
@@ -1180,6 +1204,13 @@ impl Parser {
1180
1204
 
1181
1205
  fn unary(&mut self) -> Result<Expression, ParseError> {
1182
1206
  let start = self.peek().span;
1207
+ if self.take(|kind| matches!(kind, TokenKind::Await)).is_some() {
1208
+ let value = self.postfix()?;
1209
+ return Ok(Expression {
1210
+ span: start.through(value.span),
1211
+ kind: ExpressionKind::Await(Box::new(value)),
1212
+ });
1213
+ }
1183
1214
  let operator = if self.take(|kind| matches!(kind, TokenKind::Plus)).is_some() {
1184
1215
  Some(UnaryOperator::Positive)
1185
1216
  } else if self.take(|kind| matches!(kind, TokenKind::Minus)).is_some() {