| 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)
 |