1 | """vm.py: Library for executing shell."""
|
2 | from __future__ import print_function
|
3 |
|
4 | from _devbuild.gen.id_kind_asdl import Id
|
5 | from _devbuild.gen.runtime_asdl import (CommandStatus, StatusArray, flow_e,
|
6 | flow_t)
|
7 | from _devbuild.gen.syntax_asdl import Token
|
8 | from _devbuild.gen.value_asdl import value, value_t
|
9 | from core import error
|
10 | from core import pyos
|
11 | from mycpp.mylib import log
|
12 |
|
13 | from typing import List, Any, TYPE_CHECKING
|
14 | if 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 |
|
31 | class 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 |
|
41 | class 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 |
|
94 | class 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 |
|
111 | def 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 |
|
118 | def 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 |
|
170 | class _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 |
|
231 | class _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 |
|
244 | class _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 |
|
260 | class _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 |
|
273 | class 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 |
|
298 | class 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 |
|
322 | class 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)
|