OILS / core / vm.py View on Github | oilshell.org

342 lines, 172 significant
1"""vm.py: Library for executing shell."""
2from __future__ import print_function
3
4from _devbuild.gen.id_kind_asdl import Id
5from _devbuild.gen.runtime_asdl import (CommandStatus, StatusArray, flow_e,
6 flow_t)
7from _devbuild.gen.syntax_asdl import Token
8from _devbuild.gen.value_asdl import value_t, Obj
9from core import error
10from core import pyos
11from mycpp.mylib import log
12
13from typing import List, Tuple, Any, TYPE_CHECKING
14if TYPE_CHECKING:
15 from _devbuild.gen.runtime_asdl import cmd_value, RedirValue
16 from _devbuild.gen.syntax_asdl import (command, command_t, CommandSub)
17 from frontend import typed_args
18 from osh import sh_expr_eval
19 from osh.sh_expr_eval import ArithEvaluator
20 from osh.sh_expr_eval import BoolEvaluator
21 from ysh.expr_eval import ExprEvaluator
22 from osh.word_eval import NormalWordEvaluator
23 from osh.cmd_eval import CommandEvaluator
24 from osh import prompt
25 from core import dev
26 from core import state
27
28_ = log
29
30
31class ControlFlow(Exception):
32 """Internal exception for control flow.
33
34 Used by CommandEvaluator and 'source' builtin
35
36 break and continue are caught by loops, return is caught by functions.
37 """
38 pass
39
40
41class IntControlFlow(Exception):
42
43 def __init__(self, token, arg):
44 # type: (Token, int) -> None
45 """
46 Args:
47 token: the keyword token
48 arg: exit code to 'return', or number of levels to break/continue
49 """
50 self.token = token
51 self.arg = arg
52
53 def IsReturn(self):
54 # type: () -> bool
55 return self.token.id == Id.ControlFlow_Return
56
57 def IsBreak(self):
58 # type: () -> bool
59 return self.token.id == Id.ControlFlow_Break
60
61 def IsContinue(self):
62 # type: () -> bool
63 return self.token.id == Id.ControlFlow_Continue
64
65 def StatusCode(self):
66 # type: () -> int
67 assert self.IsReturn()
68 # All shells except dash do this truncation.
69 # turn 257 into 1, and -1 into 255.
70 return self.arg & 0xff
71
72 def HandleLoop(self):
73 # type: () -> flow_t
74 """Mutates this exception and returns what the caller should do."""
75
76 if self.IsBreak():
77 self.arg -= 1
78 if self.arg == 0:
79 return flow_e.Break # caller should break out of loop
80
81 elif self.IsContinue():
82 self.arg -= 1
83 if self.arg == 0:
84 return flow_e.Nothing # do nothing to continue
85
86 # return / break 2 / continue 2 need to pop up more
87 return flow_e.Raise
88
89 def __repr__(self):
90 # type: () -> str
91 return '<IntControlFlow %s %s>' % (self.token, self.arg)
92
93
94class ValueControlFlow(Exception):
95
96 def __init__(self, token, value):
97 # type: (Token, value_t) -> None
98 """
99 Args:
100 token: the keyword token
101 value: value_t to 'return' from a function
102 """
103 self.token = token
104 self.value = value
105
106 def __repr__(self):
107 # type: () -> str
108 return '<ValueControlFlow %s %s>' % (self.token, self.value)
109
110
111def InitUnsafeArith(mem, word_ev, unsafe_arith):
112 # type: (state.Mem, NormalWordEvaluator, sh_expr_eval.UnsafeArith) -> None
113 """Wire up circular dependencies for UnsafeArith."""
114 mem.unsafe_arith = unsafe_arith # for 'declare -n' nameref expansion of a[i]
115 word_ev.unsafe_arith = unsafe_arith # for ${!ref} expansion of a[i]
116
117
118def InitCircularDeps(
119 arith_ev, # type: ArithEvaluator
120 bool_ev, # type: BoolEvaluator
121 expr_ev, # type: ExprEvaluator
122 word_ev, # type: NormalWordEvaluator
123 cmd_ev, # type: CommandEvaluator
124 shell_ex, # type: _Executor
125 prompt_ev, # type: prompt.Evaluator
126 global_io, # type: Obj
127 tracer, # type: dev.Tracer
128):
129 # type: (...) -> None
130 """Wire up mutually recursive evaluators and runtime objects."""
131 arith_ev.word_ev = word_ev
132 bool_ev.word_ev = word_ev
133
134 if expr_ev: # for pure OSH
135 expr_ev.shell_ex = shell_ex
136 expr_ev.cmd_ev = cmd_ev
137 expr_ev.word_ev = word_ev
138
139 word_ev.arith_ev = arith_ev
140 word_ev.expr_ev = expr_ev
141 word_ev.prompt_ev = prompt_ev
142 word_ev.shell_ex = shell_ex
143
144 cmd_ev.shell_ex = shell_ex
145 cmd_ev.arith_ev = arith_ev
146 cmd_ev.bool_ev = bool_ev
147 cmd_ev.expr_ev = expr_ev
148 cmd_ev.word_ev = word_ev
149 cmd_ev.tracer = tracer
150
151 shell_ex.cmd_ev = cmd_ev
152
153 prompt_ev.word_ev = word_ev
154 prompt_ev.expr_ev = expr_ev
155 prompt_ev.global_io = global_io
156
157 tracer.word_ev = word_ev
158
159 arith_ev.CheckCircularDeps()
160 bool_ev.CheckCircularDeps()
161 if expr_ev:
162 expr_ev.CheckCircularDeps()
163 word_ev.CheckCircularDeps()
164 cmd_ev.CheckCircularDeps()
165 shell_ex.CheckCircularDeps()
166 prompt_ev.CheckCircularDeps()
167 tracer.CheckCircularDeps()
168
169
170class _Executor(object):
171
172 def __init__(self):
173 # type: () -> None
174 self.cmd_ev = None # type: CommandEvaluator
175
176 def CheckCircularDeps(self):
177 # type: () -> None
178 pass
179
180 def RunBuiltin(self, builtin_id, cmd_val):
181 # type: (int, cmd_value.Argv) -> int
182 """The 'builtin' builtin in osh/builtin_meta.py needs this."""
183 return 0
184
185 def RunSimpleCommand(self, cmd_val, cmd_st, run_flags):
186 # type: (cmd_value.Argv, CommandStatus, int) -> int
187 return 0
188
189 def RunBackgroundJob(self, node):
190 # type: (command_t) -> int
191 return 0
192
193 def RunPipeline(self, node, status_out):
194 # type: (command.Pipeline, CommandStatus) -> None
195 pass
196
197 def RunSubshell(self, node):
198 # type: (command_t) -> int
199 return 0
200
201 def CaptureStdout(self, node):
202 # type: (command_t) -> Tuple[int, str]
203 return 0, ''
204
205 def RunCommandSub(self, cs_part):
206 # type: (CommandSub) -> str
207 return ''
208
209 def RunProcessSub(self, cs_part):
210 # type: (CommandSub) -> str
211 return ''
212
213 def PushRedirects(self, redirects, err_out):
214 # type: (List[RedirValue], List[error.IOError_OSError]) -> None
215 pass
216
217 def PopRedirects(self, num_redirects, err_out):
218 # type: (int, List[error.IOError_OSError]) -> None
219 pass
220
221 def PushProcessSub(self):
222 # type: () -> None
223 pass
224
225 def PopProcessSub(self, compound_st):
226 # type: (StatusArray) -> None
227 pass
228
229
230#
231# Abstract base classes
232#
233
234
235class _AssignBuiltin(object):
236 """Interface for assignment builtins."""
237
238 def __init__(self):
239 # type: () -> None
240 """Empty constructor for mycpp."""
241 pass
242
243 def Run(self, cmd_val):
244 # type: (cmd_value.Assign) -> int
245 raise NotImplementedError()
246
247
248class _Builtin(object):
249 """All builtins except 'command' obey this interface.
250
251 Assignment builtins use cmd_value.Assign; others use cmd_value.Argv.
252 """
253
254 def __init__(self):
255 # type: () -> None
256 """Empty constructor for mycpp."""
257 pass
258
259 def Run(self, cmd_val):
260 # type: (cmd_value.Argv) -> int
261 raise NotImplementedError()
262
263
264class _Callable(object):
265 """Interface for functions in the runtime."""
266
267 def __init__(self):
268 # type: () -> None
269 """Empty constructor for mycpp."""
270 pass
271
272 def Call(self, args):
273 # type: (typed_args.Reader) -> value_t
274 raise NotImplementedError()
275
276
277class ctx_Redirect(object):
278 """For closing files.
279
280 This is asymmetric because if PushRedirects fails, then we don't execute
281 the command at all.
282
283 Example:
284 { seq 3 > foo.txt; echo 4; } > bar.txt
285 """
286
287 def __init__(self, shell_ex, num_redirects, err_out):
288 # type: (_Executor, int, List[error.IOError_OSError]) -> None
289 self.shell_ex = shell_ex
290 self.num_redirects = num_redirects
291 self.err_out = err_out
292
293 def __enter__(self):
294 # type: () -> None
295 pass
296
297 def __exit__(self, type, value, traceback):
298 # type: (Any, Any, Any) -> None
299 self.shell_ex.PopRedirects(self.num_redirects, self.err_out)
300
301
302class ctx_ProcessSub(object):
303 """For waiting on processes started during word evaluation.
304
305 Example:
306 diff <(seq 3) <(seq 4) > >(tac)
307 """
308
309 def __init__(self, shell_ex, process_sub_status):
310 # type: (_Executor, StatusArray) -> None
311 shell_ex.PushProcessSub()
312 self.shell_ex = shell_ex
313 self.process_sub_status = process_sub_status
314
315 def __enter__(self):
316 # type: () -> None
317 pass
318
319 def __exit__(self, type, value, traceback):
320 # type: (Any, Any, Any) -> None
321
322 # Wait and return array to set _process_sub_status
323 self.shell_ex.PopProcessSub(self.process_sub_status)
324
325
326class ctx_FlushStdout(object):
327
328 def __init__(self, err_out):
329 # type: (List[error.IOError_OSError]) -> None
330 self.err_out = err_out
331
332 def __enter__(self):
333 # type: () -> None
334 pass
335
336 def __exit__(self, type, value, traceback):
337 # type: (Any, Any, Any) -> None
338
339 # Can't raise exception in destructor! So we append it to out param.
340 err = pyos.FlushStdout()
341 if err is not None:
342 self.err_out.append(err)