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

338 lines, 170 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, value_t
9from core import error
10from core import pyos
11from mycpp.mylib import log
12
13from typing import List, 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: value.IO
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 RunCommandSub(self, cs_part):
202 # type: (CommandSub) -> str
203 return ''
204
205 def RunProcessSub(self, cs_part):
206 # type: (CommandSub) -> str
207 return ''
208
209 def PushRedirects(self, redirects, err_out):
210 # type: (List[RedirValue], List[error.IOError_OSError]) -> None
211 pass
212
213 def PopRedirects(self, num_redirects, err_out):
214 # type: (int, List[error.IOError_OSError]) -> None
215 pass
216
217 def PushProcessSub(self):
218 # type: () -> None
219 pass
220
221 def PopProcessSub(self, compound_st):
222 # type: (StatusArray) -> None
223 pass
224
225
226#
227# Abstract base classes
228#
229
230
231class _AssignBuiltin(object):
232 """Interface for assignment builtins."""
233
234 def __init__(self):
235 # type: () -> None
236 """Empty constructor for mycpp."""
237 pass
238
239 def Run(self, cmd_val):
240 # type: (cmd_value.Assign) -> int
241 raise NotImplementedError()
242
243
244class _Builtin(object):
245 """All builtins except 'command' obey this interface.
246
247 Assignment builtins use cmd_value.Assign; others use cmd_value.Argv.
248 """
249
250 def __init__(self):
251 # type: () -> None
252 """Empty constructor for mycpp."""
253 pass
254
255 def Run(self, cmd_val):
256 # type: (cmd_value.Argv) -> int
257 raise NotImplementedError()
258
259
260class _Callable(object):
261 """Interface for functions in the runtime."""
262
263 def __init__(self):
264 # type: () -> None
265 """Empty constructor for mycpp."""
266 pass
267
268 def Call(self, args):
269 # type: (typed_args.Reader) -> value_t
270 raise NotImplementedError()
271
272
273class ctx_Redirect(object):
274 """For closing files.
275
276 This is asymmetric because if PushRedirects fails, then we don't execute
277 the command at all.
278
279 Example:
280 { seq 3 > foo.txt; echo 4; } > bar.txt
281 """
282
283 def __init__(self, shell_ex, num_redirects, err_out):
284 # type: (_Executor, int, List[error.IOError_OSError]) -> None
285 self.shell_ex = shell_ex
286 self.num_redirects = num_redirects
287 self.err_out = err_out
288
289 def __enter__(self):
290 # type: () -> None
291 pass
292
293 def __exit__(self, type, value, traceback):
294 # type: (Any, Any, Any) -> None
295 self.shell_ex.PopRedirects(self.num_redirects, self.err_out)
296
297
298class ctx_ProcessSub(object):
299 """For waiting on processes started during word evaluation.
300
301 Example:
302 diff <(seq 3) <(seq 4) > >(tac)
303 """
304
305 def __init__(self, shell_ex, process_sub_status):
306 # type: (_Executor, StatusArray) -> None
307 shell_ex.PushProcessSub()
308 self.shell_ex = shell_ex
309 self.process_sub_status = process_sub_status
310
311 def __enter__(self):
312 # type: () -> None
313 pass
314
315 def __exit__(self, type, value, traceback):
316 # type: (Any, Any, Any) -> None
317
318 # Wait and return array to set _process_sub_status
319 self.shell_ex.PopProcessSub(self.process_sub_status)
320
321
322class ctx_FlushStdout(object):
323
324 def __init__(self, err_out):
325 # type: (List[error.IOError_OSError]) -> None
326 self.err_out = err_out
327
328 def __enter__(self):
329 # type: () -> None
330 pass
331
332 def __exit__(self, type, value, traceback):
333 # type: (Any, Any, Any) -> None
334
335 # Can't raise exception in destructor! So we append it to out param.
336 err = pyos.FlushStdout()
337 if err is not None:
338 self.err_out.append(err)