just-bash 0.1.5__py3-none-any.whl

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 (193) hide show
  1. just_bash/__init__.py +55 -0
  2. just_bash/ast/__init__.py +213 -0
  3. just_bash/ast/factory.py +320 -0
  4. just_bash/ast/types.py +953 -0
  5. just_bash/bash.py +220 -0
  6. just_bash/commands/__init__.py +23 -0
  7. just_bash/commands/argv/__init__.py +5 -0
  8. just_bash/commands/argv/argv.py +21 -0
  9. just_bash/commands/awk/__init__.py +5 -0
  10. just_bash/commands/awk/awk.py +1168 -0
  11. just_bash/commands/base64/__init__.py +5 -0
  12. just_bash/commands/base64/base64.py +138 -0
  13. just_bash/commands/basename/__init__.py +5 -0
  14. just_bash/commands/basename/basename.py +72 -0
  15. just_bash/commands/bash/__init__.py +5 -0
  16. just_bash/commands/bash/bash.py +188 -0
  17. just_bash/commands/cat/__init__.py +5 -0
  18. just_bash/commands/cat/cat.py +173 -0
  19. just_bash/commands/checksum/__init__.py +5 -0
  20. just_bash/commands/checksum/checksum.py +179 -0
  21. just_bash/commands/chmod/__init__.py +5 -0
  22. just_bash/commands/chmod/chmod.py +216 -0
  23. just_bash/commands/column/__init__.py +5 -0
  24. just_bash/commands/column/column.py +180 -0
  25. just_bash/commands/comm/__init__.py +5 -0
  26. just_bash/commands/comm/comm.py +150 -0
  27. just_bash/commands/compression/__init__.py +5 -0
  28. just_bash/commands/compression/compression.py +298 -0
  29. just_bash/commands/cp/__init__.py +5 -0
  30. just_bash/commands/cp/cp.py +149 -0
  31. just_bash/commands/curl/__init__.py +5 -0
  32. just_bash/commands/curl/curl.py +801 -0
  33. just_bash/commands/cut/__init__.py +5 -0
  34. just_bash/commands/cut/cut.py +327 -0
  35. just_bash/commands/date/__init__.py +5 -0
  36. just_bash/commands/date/date.py +258 -0
  37. just_bash/commands/diff/__init__.py +5 -0
  38. just_bash/commands/diff/diff.py +118 -0
  39. just_bash/commands/dirname/__init__.py +5 -0
  40. just_bash/commands/dirname/dirname.py +56 -0
  41. just_bash/commands/du/__init__.py +5 -0
  42. just_bash/commands/du/du.py +150 -0
  43. just_bash/commands/echo/__init__.py +5 -0
  44. just_bash/commands/echo/echo.py +125 -0
  45. just_bash/commands/env/__init__.py +5 -0
  46. just_bash/commands/env/env.py +163 -0
  47. just_bash/commands/expand/__init__.py +5 -0
  48. just_bash/commands/expand/expand.py +299 -0
  49. just_bash/commands/expr/__init__.py +5 -0
  50. just_bash/commands/expr/expr.py +273 -0
  51. just_bash/commands/file/__init__.py +5 -0
  52. just_bash/commands/file/file.py +274 -0
  53. just_bash/commands/find/__init__.py +5 -0
  54. just_bash/commands/find/find.py +623 -0
  55. just_bash/commands/fold/__init__.py +5 -0
  56. just_bash/commands/fold/fold.py +160 -0
  57. just_bash/commands/grep/__init__.py +5 -0
  58. just_bash/commands/grep/grep.py +418 -0
  59. just_bash/commands/head/__init__.py +5 -0
  60. just_bash/commands/head/head.py +167 -0
  61. just_bash/commands/help/__init__.py +5 -0
  62. just_bash/commands/help/help.py +67 -0
  63. just_bash/commands/hostname/__init__.py +5 -0
  64. just_bash/commands/hostname/hostname.py +21 -0
  65. just_bash/commands/html_to_markdown/__init__.py +5 -0
  66. just_bash/commands/html_to_markdown/html_to_markdown.py +191 -0
  67. just_bash/commands/join/__init__.py +5 -0
  68. just_bash/commands/join/join.py +252 -0
  69. just_bash/commands/jq/__init__.py +5 -0
  70. just_bash/commands/jq/jq.py +280 -0
  71. just_bash/commands/ln/__init__.py +5 -0
  72. just_bash/commands/ln/ln.py +127 -0
  73. just_bash/commands/ls/__init__.py +5 -0
  74. just_bash/commands/ls/ls.py +280 -0
  75. just_bash/commands/mkdir/__init__.py +5 -0
  76. just_bash/commands/mkdir/mkdir.py +92 -0
  77. just_bash/commands/mv/__init__.py +5 -0
  78. just_bash/commands/mv/mv.py +142 -0
  79. just_bash/commands/nl/__init__.py +5 -0
  80. just_bash/commands/nl/nl.py +180 -0
  81. just_bash/commands/od/__init__.py +5 -0
  82. just_bash/commands/od/od.py +157 -0
  83. just_bash/commands/paste/__init__.py +5 -0
  84. just_bash/commands/paste/paste.py +100 -0
  85. just_bash/commands/printf/__init__.py +5 -0
  86. just_bash/commands/printf/printf.py +157 -0
  87. just_bash/commands/pwd/__init__.py +5 -0
  88. just_bash/commands/pwd/pwd.py +23 -0
  89. just_bash/commands/read/__init__.py +5 -0
  90. just_bash/commands/read/read.py +185 -0
  91. just_bash/commands/readlink/__init__.py +5 -0
  92. just_bash/commands/readlink/readlink.py +86 -0
  93. just_bash/commands/registry.py +844 -0
  94. just_bash/commands/rev/__init__.py +5 -0
  95. just_bash/commands/rev/rev.py +74 -0
  96. just_bash/commands/rg/__init__.py +5 -0
  97. just_bash/commands/rg/rg.py +1048 -0
  98. just_bash/commands/rm/__init__.py +5 -0
  99. just_bash/commands/rm/rm.py +106 -0
  100. just_bash/commands/search_engine/__init__.py +13 -0
  101. just_bash/commands/search_engine/matcher.py +170 -0
  102. just_bash/commands/search_engine/regex.py +159 -0
  103. just_bash/commands/sed/__init__.py +5 -0
  104. just_bash/commands/sed/sed.py +863 -0
  105. just_bash/commands/seq/__init__.py +5 -0
  106. just_bash/commands/seq/seq.py +190 -0
  107. just_bash/commands/shell/__init__.py +5 -0
  108. just_bash/commands/shell/shell.py +206 -0
  109. just_bash/commands/sleep/__init__.py +5 -0
  110. just_bash/commands/sleep/sleep.py +62 -0
  111. just_bash/commands/sort/__init__.py +5 -0
  112. just_bash/commands/sort/sort.py +411 -0
  113. just_bash/commands/split/__init__.py +5 -0
  114. just_bash/commands/split/split.py +237 -0
  115. just_bash/commands/sqlite3/__init__.py +5 -0
  116. just_bash/commands/sqlite3/sqlite3_cmd.py +505 -0
  117. just_bash/commands/stat/__init__.py +5 -0
  118. just_bash/commands/stat/stat.py +150 -0
  119. just_bash/commands/strings/__init__.py +5 -0
  120. just_bash/commands/strings/strings.py +150 -0
  121. just_bash/commands/tac/__init__.py +5 -0
  122. just_bash/commands/tac/tac.py +158 -0
  123. just_bash/commands/tail/__init__.py +5 -0
  124. just_bash/commands/tail/tail.py +180 -0
  125. just_bash/commands/tar/__init__.py +5 -0
  126. just_bash/commands/tar/tar.py +1067 -0
  127. just_bash/commands/tee/__init__.py +5 -0
  128. just_bash/commands/tee/tee.py +63 -0
  129. just_bash/commands/timeout/__init__.py +5 -0
  130. just_bash/commands/timeout/timeout.py +188 -0
  131. just_bash/commands/touch/__init__.py +5 -0
  132. just_bash/commands/touch/touch.py +91 -0
  133. just_bash/commands/tr/__init__.py +5 -0
  134. just_bash/commands/tr/tr.py +297 -0
  135. just_bash/commands/tree/__init__.py +5 -0
  136. just_bash/commands/tree/tree.py +139 -0
  137. just_bash/commands/true/__init__.py +5 -0
  138. just_bash/commands/true/true.py +32 -0
  139. just_bash/commands/uniq/__init__.py +5 -0
  140. just_bash/commands/uniq/uniq.py +323 -0
  141. just_bash/commands/wc/__init__.py +5 -0
  142. just_bash/commands/wc/wc.py +169 -0
  143. just_bash/commands/which/__init__.py +5 -0
  144. just_bash/commands/which/which.py +52 -0
  145. just_bash/commands/xan/__init__.py +5 -0
  146. just_bash/commands/xan/xan.py +1663 -0
  147. just_bash/commands/xargs/__init__.py +5 -0
  148. just_bash/commands/xargs/xargs.py +136 -0
  149. just_bash/commands/yq/__init__.py +5 -0
  150. just_bash/commands/yq/yq.py +848 -0
  151. just_bash/fs/__init__.py +29 -0
  152. just_bash/fs/in_memory_fs.py +621 -0
  153. just_bash/fs/mountable_fs.py +504 -0
  154. just_bash/fs/overlay_fs.py +894 -0
  155. just_bash/fs/read_write_fs.py +455 -0
  156. just_bash/interpreter/__init__.py +37 -0
  157. just_bash/interpreter/builtins/__init__.py +92 -0
  158. just_bash/interpreter/builtins/alias.py +154 -0
  159. just_bash/interpreter/builtins/cd.py +76 -0
  160. just_bash/interpreter/builtins/control.py +127 -0
  161. just_bash/interpreter/builtins/declare.py +336 -0
  162. just_bash/interpreter/builtins/export.py +56 -0
  163. just_bash/interpreter/builtins/let.py +44 -0
  164. just_bash/interpreter/builtins/local.py +57 -0
  165. just_bash/interpreter/builtins/mapfile.py +152 -0
  166. just_bash/interpreter/builtins/misc.py +378 -0
  167. just_bash/interpreter/builtins/readonly.py +80 -0
  168. just_bash/interpreter/builtins/set.py +234 -0
  169. just_bash/interpreter/builtins/shopt.py +201 -0
  170. just_bash/interpreter/builtins/source.py +136 -0
  171. just_bash/interpreter/builtins/test.py +290 -0
  172. just_bash/interpreter/builtins/unset.py +53 -0
  173. just_bash/interpreter/conditionals.py +387 -0
  174. just_bash/interpreter/control_flow.py +381 -0
  175. just_bash/interpreter/errors.py +116 -0
  176. just_bash/interpreter/expansion.py +1156 -0
  177. just_bash/interpreter/interpreter.py +813 -0
  178. just_bash/interpreter/types.py +134 -0
  179. just_bash/network/__init__.py +1 -0
  180. just_bash/parser/__init__.py +39 -0
  181. just_bash/parser/lexer.py +948 -0
  182. just_bash/parser/parser.py +2162 -0
  183. just_bash/py.typed +0 -0
  184. just_bash/query_engine/__init__.py +83 -0
  185. just_bash/query_engine/builtins/__init__.py +1283 -0
  186. just_bash/query_engine/evaluator.py +578 -0
  187. just_bash/query_engine/parser.py +525 -0
  188. just_bash/query_engine/tokenizer.py +329 -0
  189. just_bash/query_engine/types.py +373 -0
  190. just_bash/types.py +180 -0
  191. just_bash-0.1.5.dist-info/METADATA +410 -0
  192. just_bash-0.1.5.dist-info/RECORD +193 -0
  193. just_bash-0.1.5.dist-info/WHEEL +4 -0
@@ -0,0 +1,381 @@
1
+ """Control Flow Execution.
2
+
3
+ Handles control flow constructs:
4
+ - if/elif/else
5
+ - for loops
6
+ - C-style for loops
7
+ - while loops
8
+ - until loops
9
+ - case statements
10
+ - break/continue
11
+ """
12
+
13
+ from typing import TYPE_CHECKING
14
+
15
+ from ..ast.types import (
16
+ IfNode,
17
+ ForNode,
18
+ CStyleForNode,
19
+ WhileNode,
20
+ UntilNode,
21
+ CaseNode,
22
+ )
23
+ from ..types import ExecResult
24
+ from .errors import BreakError, ContinueError, ExecutionLimitError
25
+ from .expansion import expand_word_async, expand_word_with_glob, evaluate_arithmetic
26
+
27
+ if TYPE_CHECKING:
28
+ from .types import InterpreterContext
29
+
30
+
31
+ def _result(stdout: str, stderr: str, exit_code: int) -> ExecResult:
32
+ """Create an ExecResult."""
33
+ return ExecResult(stdout=stdout, stderr=stderr, exit_code=exit_code)
34
+
35
+
36
+ def _failure(stderr: str) -> ExecResult:
37
+ """Create a failed result."""
38
+ return ExecResult(stdout="", stderr=stderr, exit_code=1)
39
+
40
+
41
+ async def execute_if(ctx: "InterpreterContext", node: IfNode) -> ExecResult:
42
+ """Execute an if statement."""
43
+ stdout = ""
44
+ stderr = ""
45
+
46
+ for clause in node.clauses:
47
+ # Execute condition
48
+ cond_result = await execute_condition(ctx, clause.condition)
49
+ stdout += cond_result.stdout
50
+ stderr += cond_result.stderr
51
+
52
+ if cond_result.exit_code == 0:
53
+ # Condition is true - execute body
54
+ return await execute_statements(ctx, clause.body, stdout, stderr)
55
+
56
+ # No condition matched - check for else
57
+ if node.else_body:
58
+ return await execute_statements(ctx, node.else_body, stdout, stderr)
59
+
60
+ return _result(stdout, stderr, 0)
61
+
62
+
63
+ async def execute_for(ctx: "InterpreterContext", node: ForNode) -> ExecResult:
64
+ """Execute a for loop."""
65
+ stdout = ""
66
+ stderr = ""
67
+ exit_code = 0
68
+ iterations = 0
69
+
70
+ # Validate variable name
71
+ import re
72
+ if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', node.variable):
73
+ return _failure(f"bash: `{node.variable}': not a valid identifier\n")
74
+
75
+ # Get words to iterate over
76
+ words: list[str] = []
77
+ if node.words is None:
78
+ # Iterate over positional parameters
79
+ params = ctx.state.env.get("@", "").split()
80
+ words = [p for p in params if p]
81
+ elif len(node.words) == 0:
82
+ words = []
83
+ else:
84
+ for word in node.words:
85
+ expanded = await expand_word_with_glob(ctx, word)
86
+ words.extend(expanded["values"])
87
+
88
+ ctx.state.loop_depth += 1
89
+ try:
90
+ for value in words:
91
+ iterations += 1
92
+ if iterations > ctx.limits.max_loop_iterations:
93
+ raise ExecutionLimitError(
94
+ f"for loop: too many iterations ({ctx.limits.max_loop_iterations})",
95
+ "iterations",
96
+ )
97
+
98
+ ctx.state.env[node.variable] = value
99
+
100
+ try:
101
+ for stmt in node.body:
102
+ result = await ctx.execute_statement(stmt)
103
+ stdout += result.stdout
104
+ stderr += result.stderr
105
+ exit_code = result.exit_code
106
+ except BreakError as e:
107
+ stdout += e.stdout
108
+ stderr += e.stderr
109
+ if e.levels > 1 and ctx.state.loop_depth > 1:
110
+ e.levels -= 1
111
+ e.stdout = stdout
112
+ e.stderr = stderr
113
+ raise
114
+ break
115
+ except ContinueError as e:
116
+ stdout += e.stdout
117
+ stderr += e.stderr
118
+ if e.levels > 1 and ctx.state.loop_depth > 1:
119
+ e.levels -= 1
120
+ e.stdout = stdout
121
+ e.stderr = stderr
122
+ raise
123
+ continue
124
+ finally:
125
+ ctx.state.loop_depth -= 1
126
+
127
+ return _result(stdout, stderr, exit_code)
128
+
129
+
130
+ async def execute_c_style_for(ctx: "InterpreterContext", node: CStyleForNode) -> ExecResult:
131
+ """Execute a C-style for loop: for ((init; cond; update))."""
132
+ stdout = ""
133
+ stderr = ""
134
+ exit_code = 0
135
+ iterations = 0
136
+
137
+ # Execute init
138
+ if node.init:
139
+ await evaluate_arithmetic(ctx, node.init.expression)
140
+
141
+ ctx.state.loop_depth += 1
142
+ try:
143
+ while True:
144
+ iterations += 1
145
+ if iterations > ctx.limits.max_loop_iterations:
146
+ raise ExecutionLimitError(
147
+ f"for loop: too many iterations ({ctx.limits.max_loop_iterations})",
148
+ "iterations",
149
+ )
150
+
151
+ # Check condition
152
+ if node.condition:
153
+ cond_result = await evaluate_arithmetic(ctx, node.condition.expression)
154
+ if cond_result == 0:
155
+ break
156
+
157
+ try:
158
+ for stmt in node.body:
159
+ result = await ctx.execute_statement(stmt)
160
+ stdout += result.stdout
161
+ stderr += result.stderr
162
+ exit_code = result.exit_code
163
+ except BreakError as e:
164
+ stdout += e.stdout
165
+ stderr += e.stderr
166
+ if e.levels > 1 and ctx.state.loop_depth > 1:
167
+ e.levels -= 1
168
+ raise
169
+ break
170
+ except ContinueError as e:
171
+ stdout += e.stdout
172
+ stderr += e.stderr
173
+ if e.levels > 1 and ctx.state.loop_depth > 1:
174
+ e.levels -= 1
175
+ raise
176
+ # Still run update on continue
177
+ if node.update:
178
+ await evaluate_arithmetic(ctx, node.update.expression)
179
+ continue
180
+
181
+ # Execute update
182
+ if node.update:
183
+ await evaluate_arithmetic(ctx, node.update.expression)
184
+ finally:
185
+ ctx.state.loop_depth -= 1
186
+
187
+ return _result(stdout, stderr, exit_code)
188
+
189
+
190
+ async def execute_while(ctx: "InterpreterContext", node: WhileNode, stdin: str = "") -> ExecResult:
191
+ """Execute a while loop."""
192
+ stdout = ""
193
+ stderr = ""
194
+ exit_code = 0
195
+ iterations = 0
196
+
197
+ ctx.state.loop_depth += 1
198
+ try:
199
+ while True:
200
+ iterations += 1
201
+ if iterations > ctx.limits.max_loop_iterations:
202
+ raise ExecutionLimitError(
203
+ f"while loop: too many iterations ({ctx.limits.max_loop_iterations})",
204
+ "iterations",
205
+ )
206
+
207
+ # Execute condition
208
+ saved_in_condition = ctx.state.in_condition
209
+ ctx.state.in_condition = True
210
+ try:
211
+ cond_result = await execute_condition(ctx, node.condition)
212
+ stdout += cond_result.stdout
213
+ stderr += cond_result.stderr
214
+
215
+ if cond_result.exit_code != 0:
216
+ break
217
+ finally:
218
+ ctx.state.in_condition = saved_in_condition
219
+
220
+ try:
221
+ for stmt in node.body:
222
+ result = await ctx.execute_statement(stmt)
223
+ stdout += result.stdout
224
+ stderr += result.stderr
225
+ exit_code = result.exit_code
226
+ except BreakError as e:
227
+ stdout += e.stdout
228
+ stderr += e.stderr
229
+ if e.levels > 1 and ctx.state.loop_depth > 1:
230
+ e.levels -= 1
231
+ raise
232
+ break
233
+ except ContinueError as e:
234
+ stdout += e.stdout
235
+ stderr += e.stderr
236
+ if e.levels > 1 and ctx.state.loop_depth > 1:
237
+ e.levels -= 1
238
+ raise
239
+ continue
240
+ finally:
241
+ ctx.state.loop_depth -= 1
242
+
243
+ return _result(stdout, stderr, exit_code)
244
+
245
+
246
+ async def execute_until(ctx: "InterpreterContext", node: UntilNode) -> ExecResult:
247
+ """Execute an until loop."""
248
+ stdout = ""
249
+ stderr = ""
250
+ exit_code = 0
251
+ iterations = 0
252
+
253
+ ctx.state.loop_depth += 1
254
+ try:
255
+ while True:
256
+ iterations += 1
257
+ if iterations > ctx.limits.max_loop_iterations:
258
+ raise ExecutionLimitError(
259
+ f"until loop: too many iterations ({ctx.limits.max_loop_iterations})",
260
+ "iterations",
261
+ )
262
+
263
+ # Execute condition (until exits when condition is TRUE)
264
+ saved_in_condition = ctx.state.in_condition
265
+ ctx.state.in_condition = True
266
+ try:
267
+ cond_result = await execute_condition(ctx, node.condition)
268
+ stdout += cond_result.stdout
269
+ stderr += cond_result.stderr
270
+
271
+ if cond_result.exit_code == 0:
272
+ break # Condition became true, exit loop
273
+ finally:
274
+ ctx.state.in_condition = saved_in_condition
275
+
276
+ try:
277
+ for stmt in node.body:
278
+ result = await ctx.execute_statement(stmt)
279
+ stdout += result.stdout
280
+ stderr += result.stderr
281
+ exit_code = result.exit_code
282
+ except BreakError as e:
283
+ stdout += e.stdout
284
+ stderr += e.stderr
285
+ if e.levels > 1 and ctx.state.loop_depth > 1:
286
+ e.levels -= 1
287
+ raise
288
+ break
289
+ except ContinueError as e:
290
+ stdout += e.stdout
291
+ stderr += e.stderr
292
+ if e.levels > 1 and ctx.state.loop_depth > 1:
293
+ e.levels -= 1
294
+ raise
295
+ continue
296
+ finally:
297
+ ctx.state.loop_depth -= 1
298
+
299
+ return _result(stdout, stderr, exit_code)
300
+
301
+
302
+ async def execute_case(ctx: "InterpreterContext", node: CaseNode) -> ExecResult:
303
+ """Execute a case statement."""
304
+ import fnmatch
305
+
306
+ stdout = ""
307
+ stderr = ""
308
+ exit_code = 0
309
+
310
+ # Expand the word to match against
311
+ word_value = await expand_word_async(ctx, node.word)
312
+
313
+ for case_item in node.items:
314
+ # Check each pattern
315
+ matched = False
316
+ for pattern in case_item.patterns:
317
+ pattern_value = await expand_word_async(ctx, pattern)
318
+ if fnmatch.fnmatch(word_value, pattern_value):
319
+ matched = True
320
+ break
321
+
322
+ if matched:
323
+ # Execute the body for this case
324
+ for stmt in case_item.body:
325
+ result = await ctx.execute_statement(stmt)
326
+ stdout += result.stdout
327
+ stderr += result.stderr
328
+ exit_code = result.exit_code
329
+
330
+ # Check terminator
331
+ if case_item.terminator == ";;":
332
+ # Normal termination - exit case
333
+ break
334
+ elif case_item.terminator == ";&":
335
+ # Fall through to next case body (without pattern check)
336
+ continue
337
+ elif case_item.terminator == ";;&":
338
+ # Continue checking patterns
339
+ continue
340
+ else:
341
+ break
342
+
343
+ return _result(stdout, stderr, exit_code)
344
+
345
+
346
+ async def execute_condition(ctx: "InterpreterContext", condition: list) -> ExecResult:
347
+ """Execute a condition (list of statements) and return the result."""
348
+ stdout = ""
349
+ stderr = ""
350
+ exit_code = 0
351
+
352
+ saved_in_condition = ctx.state.in_condition
353
+ ctx.state.in_condition = True
354
+ try:
355
+ for stmt in condition:
356
+ result = await ctx.execute_statement(stmt)
357
+ stdout += result.stdout
358
+ stderr += result.stderr
359
+ exit_code = result.exit_code
360
+ finally:
361
+ ctx.state.in_condition = saved_in_condition
362
+
363
+ return _result(stdout, stderr, exit_code)
364
+
365
+
366
+ async def execute_statements(
367
+ ctx: "InterpreterContext",
368
+ statements: list,
369
+ stdout: str = "",
370
+ stderr: str = "",
371
+ ) -> ExecResult:
372
+ """Execute a list of statements."""
373
+ exit_code = 0
374
+
375
+ for stmt in statements:
376
+ result = await ctx.execute_statement(stmt)
377
+ stdout += result.stdout
378
+ stderr += result.stderr
379
+ exit_code = result.exit_code
380
+
381
+ return _result(stdout, stderr, exit_code)
@@ -0,0 +1,116 @@
1
+ """Interpreter errors for just-bash."""
2
+
3
+
4
+ class InterpreterError(Exception):
5
+ """Base class for interpreter errors."""
6
+
7
+ def __init__(self, message: str = ""):
8
+ super().__init__(message)
9
+ self.stdout = ""
10
+ self.stderr = ""
11
+
12
+ def prepend_output(self, stdout: str, stderr: str) -> None:
13
+ """Prepend accumulated output to error output."""
14
+ self.stdout = stdout + self.stdout
15
+ self.stderr = stderr + self.stderr
16
+
17
+
18
+ class ExitError(InterpreterError):
19
+ """Raised when 'exit' builtin is called."""
20
+
21
+ def __init__(self, exit_code: int, stdout: str = "", stderr: str = ""):
22
+ super().__init__(f"exit {exit_code}")
23
+ self.exit_code = exit_code
24
+ self.stdout = stdout
25
+ self.stderr = stderr
26
+
27
+
28
+ class ReturnError(InterpreterError):
29
+ """Raised when 'return' builtin is called."""
30
+
31
+ def __init__(self, exit_code: int, stdout: str = "", stderr: str = ""):
32
+ super().__init__(f"return {exit_code}")
33
+ self.exit_code = exit_code
34
+ self.stdout = stdout
35
+ self.stderr = stderr
36
+
37
+
38
+ class BreakError(InterpreterError):
39
+ """Raised when 'break' builtin is called."""
40
+
41
+ def __init__(self, levels: int = 1, stdout: str = "", stderr: str = ""):
42
+ super().__init__(f"break {levels}")
43
+ self.levels = levels
44
+ self.stdout = stdout
45
+ self.stderr = stderr
46
+
47
+
48
+ class ContinueError(InterpreterError):
49
+ """Raised when 'continue' builtin is called."""
50
+
51
+ def __init__(self, levels: int = 1, stdout: str = "", stderr: str = ""):
52
+ super().__init__(f"continue {levels}")
53
+ self.levels = levels
54
+ self.stdout = stdout
55
+ self.stderr = stderr
56
+
57
+
58
+ class ErrexitError(InterpreterError):
59
+ """Raised when errexit (set -e) terminates execution."""
60
+
61
+ def __init__(self, exit_code: int, stdout: str = "", stderr: str = ""):
62
+ super().__init__(f"errexit with code {exit_code}")
63
+ self.exit_code = exit_code
64
+ self.stdout = stdout
65
+ self.stderr = stderr
66
+
67
+
68
+ class NounsetError(InterpreterError):
69
+ """Raised when nounset (set -u) encounters unset variable."""
70
+
71
+ def __init__(self, variable: str, stdout: str = "", stderr: str = ""):
72
+ super().__init__(f"unbound variable: {variable}")
73
+ self.variable = variable
74
+ self.stdout = stdout
75
+ self.stderr = stderr
76
+
77
+
78
+ class BadSubstitutionError(InterpreterError):
79
+ """Raised for bad parameter substitution."""
80
+
81
+ def __init__(self, message: str, stdout: str = "", stderr: str = ""):
82
+ super().__init__(message)
83
+ self.stdout = stdout
84
+ self.stderr = stderr
85
+
86
+
87
+ class ArithmeticError(InterpreterError):
88
+ """Raised for arithmetic evaluation errors."""
89
+
90
+ def __init__(self, message: str, stdout: str = "", stderr: str = ""):
91
+ super().__init__(message)
92
+ self.stdout = stdout
93
+ self.stderr = stderr
94
+
95
+
96
+ class ExecutionLimitError(InterpreterError):
97
+ """Raised when execution limits are exceeded."""
98
+
99
+ def __init__(self, message: str, limit_type: str):
100
+ super().__init__(message)
101
+ self.limit_type = limit_type
102
+
103
+
104
+ class SubshellExitError(InterpreterError):
105
+ """Raised when a subshell exits."""
106
+
107
+ def __init__(self, exit_code: int, stdout: str = "", stderr: str = ""):
108
+ super().__init__(f"subshell exit {exit_code}")
109
+ self.exit_code = exit_code
110
+ self.stdout = stdout
111
+ self.stderr = stderr
112
+
113
+
114
+ def is_scope_exit_error(error: Exception) -> bool:
115
+ """Check if an error should exit the current scope."""
116
+ return isinstance(error, (ExitError, ReturnError, BreakError, ContinueError))