OILS / builtin / method_io.py View on Github | oils.pub

318 lines, 183 significant
1"""Methods on Obj that is the io type"""
2from __future__ import print_function
3
4from _devbuild.gen.value_asdl import value, value_e, value_t
5from _devbuild.gen.syntax_asdl import loc_t
6
7from core import error
8from core import num
9from core import state
10from core import vm
11from frontend import typed_args
12from mycpp.mylib import log, NewDict
13from osh import prompt
14
15from typing import Dict, List, Optional, cast, TYPE_CHECKING
16if TYPE_CHECKING:
17 from _devbuild.gen.runtime_asdl import Cell
18 from osh import cmd_eval
19 from ysh import expr_eval
20
21_ = log
22
23EVAL_NULL = 1
24EVAL_DICT = 2
25
26
27def _CheckPosArgs(pos_args_raw, blame_loc):
28 # type: (Optional[List[value_t]], loc_t) -> Optional[List[str]]
29
30 if pos_args_raw is None:
31 return None
32
33 pos_args = [] # type: List[str]
34 for arg in pos_args_raw:
35 if arg.tag() != value_e.Str:
36 raise error.TypeErr(arg, "Expected pos_args to be a List of Strs",
37 blame_loc)
38
39 pos_args.append(cast(value.Str, arg).s)
40 return pos_args
41
42
43class EvalExpr(vm._Callable):
44 """io->evalExpr(ex) evaluates an expression
45
46 Notes compared with io->eval(cmd):
47 - there is no to_dict=true variant - doesn't make sense
48 - Does it need in_captured_frame=true?
49 - That is for "inline procs" like cd, but doesn't seem to be necessary
50 for expressions. Unless we had mutations in expressions.
51 """
52
53 def __init__(
54 self,
55 expr_ev, # type: expr_eval.ExprEvaluator
56 pure_ex, # type: Optional[vm._Executor]
57 cmd_ev, # type: Optional[cmd_eval.CommandEvaluator]
58 ):
59 # type: (...) -> None
60 self.expr_ev = expr_ev
61 self.pure_ex = pure_ex
62 self.cmd_ev = cmd_ev
63 self.mem = expr_ev.mem
64
65 def Call(self, rd):
66 # type: (typed_args.Reader) -> value_t
67 if self.pure_ex is None:
68 unused_self = rd.PosObj()
69 lazy = rd.PosExpr()
70
71 dollar0 = rd.NamedStr("dollar0", None)
72 pos_args_raw = rd.NamedList("pos_args", None)
73 vars_ = rd.NamedDict("vars", None)
74 rd.Done()
75
76 blame_tok = rd.LeftParenToken()
77 pos_args = _CheckPosArgs(pos_args_raw, blame_tok)
78
79 # Note: ctx_Eval is on the outside, while ctx_EnclosedFrame is used in
80 # EvalExprClosure
81 with state.ctx_TokenDebugFrame(self.mem, blame_tok):
82 with state.ctx_EnclosedFrame(self.mem, lazy.captured_frame,
83 lazy.module_frame, None):
84 with state.ctx_Eval(self.mem, dollar0, pos_args, vars_):
85 with vm.ctx_MaybePure(self.pure_ex, self.cmd_ev):
86 result = self.expr_ev.EvalExpr(lazy.e, blame_tok)
87
88 return result
89
90
91if 0:
92
93 def _PrintFrame(prefix, frame):
94 # type: (str, Dict[str, Cell]) -> None
95 print('%s %s' % (prefix, ' '.join(frame.keys())))
96
97 rear = frame.get('__E__')
98 if rear:
99 rear_val = rear.val
100 if rear_val.tag() == value_e.Frame:
101 r = cast(value.Frame, rear_val)
102 _PrintFrame('--> ' + prefix, r.frame)
103
104
105class EvalInFrame(vm._Callable):
106 """
107 DEPRECATED, replaced by eval(b, in_captured_frame=true)
108 """
109
110 def __init__(self, mem, cmd_ev):
111 # type: (state.Mem, cmd_eval.CommandEvaluator) -> None
112 self.mem = mem
113 self.cmd_ev = cmd_ev
114
115 def Call(self, rd):
116 # type: (typed_args.Reader) -> value_t
117 unused = rd.PosValue()
118
119 cmd = rd.PosCommand()
120 in_frame = rd.PosFrame()
121 rd.Done()
122
123 frag = typed_args.GetCommandFrag(cmd)
124
125 # Note that 'cd' uses cmd_ev.EvalCommandFrag(), because a builtin does
126 # NOT push a new stack frame. But a proc does. So we need
127 # evalInFrame().
128
129 with state.ctx_TokenDebugFrame(self.mem, rd.LeftParenToken()):
130 # Evaluate with the given frame at the TOP of the stack
131 with state.ctx_EvalInFrame(self.mem, in_frame):
132 unused_status = self.cmd_ev.EvalCommandFrag(frag)
133
134 return value.Null
135
136
137class Eval(vm._Callable):
138 """
139 These are similar:
140
141 var cmd = ^(echo hi)
142 call io->eval(cmd)
143
144 Also give the top namespace
145
146 call io->evalToDict(cmd)
147
148 The CALLER must handle errors.
149 """
150
151 def __init__(self, mem, cmd_ev, pure_ex, which):
152 # type: (state.Mem, cmd_eval.CommandEvaluator, Optional[vm._Executor], int) -> None
153 self.mem = mem
154 self.cmd_ev = cmd_ev
155 self.pure_ex = pure_ex
156 self.which = which
157
158 def Call(self, rd):
159 # type: (typed_args.Reader) -> value_t
160 if self.pure_ex is None:
161 unused = rd.PosValue()
162 bound = rd.PosCommand()
163
164 dollar0 = rd.NamedStr("dollar0", None)
165 pos_args_raw = rd.NamedList("pos_args", None)
166 vars_ = rd.NamedDict("vars", None)
167 in_captured_frame = rd.NamedBool("in_captured_frame", False)
168 to_dict = rd.NamedBool("to_dict", False)
169 rd.Done()
170
171 frag = typed_args.GetCommandFrag(bound)
172
173 pos_args = _CheckPosArgs(pos_args_raw, rd.LeftParenToken())
174
175 with state.ctx_TokenDebugFrame(self.mem, rd.LeftParenToken()):
176 if self.which == EVAL_NULL:
177 if to_dict:
178 bindings = NewDict() # type: Optional[Dict[str, value_t]]
179 else:
180 bindings = None
181 elif self.which == EVAL_DICT: # TODO: remove evalToDict()
182 bindings = NewDict()
183 else:
184 raise AssertionError()
185
186 # _PrintFrame('[captured]', captured_frame)
187 with state.ctx_EnclosedFrame(self.mem,
188 bound.captured_frame,
189 bound.module_frame,
190 bindings,
191 inside=in_captured_frame):
192 # _PrintFrame('[new]', self.cmd_ev.mem.var_stack[-1])
193 with state.ctx_Eval(self.mem, dollar0, pos_args, vars_):
194 with vm.ctx_MaybePure(self.pure_ex, self.cmd_ev):
195 unused_status = self.cmd_ev.EvalCommandFrag(frag)
196
197 if bindings is not None:
198 return value.Dict(bindings)
199 else:
200 return value.Null
201
202
203class CaptureStdout(vm._Callable):
204
205 def __init__(self, mem, shell_ex):
206 # type: (state.Mem, vm._Executor) -> None
207 self.mem = mem
208 self.shell_ex = shell_ex
209
210 def Call(self, rd):
211 # type: (typed_args.Reader) -> value_t
212
213 unused = rd.PosValue()
214 cmd = rd.PosCommand()
215 rd.Done() # no more args
216
217 frag = typed_args.GetCommandFrag(cmd)
218 with state.ctx_EnclosedFrame(self.mem, cmd.captured_frame,
219 cmd.module_frame, None):
220 status, stdout_str = self.shell_ex.CaptureStdout(frag)
221 if status != 0:
222 # Note that $() raises error.ErrExit with the status.
223 # But I think that results in a more confusing error message, so we
224 # "wrap" the errors.
225 properties = {
226 'status': num.ToBig(status)
227 } # type: Dict[str, value_t]
228 raise error.Structured(
229 4, 'captureStdout(): command failed with status %d' % status,
230 rd.LeftParenToken(), properties)
231
232 return value.Str(stdout_str)
233
234
235class CaptureAll(vm._Callable):
236 def __init__(self, mem, shell_ex):
237 # type: (state.Mem, vm._Executor) -> None
238 self.mem = mem
239 self.shell_ex = shell_ex
240
241 def Call(self, rd):
242 # type: (typed_args.Reader) -> value_t
243 unused = rd.PosValue()
244 cmd = rd.PosCommand()
245 rd.Done() # no more args
246 frag = typed_args.GetCommandFrag(cmd)
247 with state.ctx_EnclosedFrame(self.mem, cmd.captured_frame,
248 cmd.module_frame, None):
249 status, stdout_str, stderr_str = self.shell_ex.Capture3(frag)
250
251 out = NewDict() # type: Dict[str, value_t]
252 out['stdout'] = value.Str(stdout_str)
253 out['stderr'] = value.Str(stderr_str)
254 out['status'] = num.ToBig(status)
255
256 return value.Dict(out)
257
258class PromptVal(vm._Callable):
259 """
260 _io->promptVal('$') is like \$
261 It expands to $ or # when root
262 """
263
264 def __init__(self, prompt_ev):
265 # type: (prompt.Evaluator) -> None
266 self.prompt_ev = prompt_ev
267
268 def Call(self, rd):
269 # type: (typed_args.Reader) -> value_t
270
271 # "self" param is guaranteed to succeed
272 unused = rd.PosValue()
273 what = rd.PosStr()
274 rd.Done() # no more args
275
276 # Bug fix: protect against crash later in PromptVal()
277 if len(what) != 1:
278 raise error.Expr(
279 'promptVal() expected a single char, got %r' % what,
280 rd.LeftParenToken())
281
282 return value.Str(self.prompt_ev.PromptVal(what))
283
284
285# TODO: Implement these
286
287
288class Time(vm._Callable):
289
290 def __init__(self):
291 # type: () -> None
292 pass
293
294 def Call(self, rd):
295 # type: (typed_args.Reader) -> value_t
296 return value.Null
297
298
299class Strftime(vm._Callable):
300
301 def __init__(self):
302 # type: () -> None
303 pass
304
305 def Call(self, rd):
306 # type: (typed_args.Reader) -> value_t
307 return value.Null
308
309
310class Glob(vm._Callable):
311
312 def __init__(self):
313 # type: () -> None
314 pass
315
316 def Call(self, rd):
317 # type: (typed_args.Reader) -> value_t
318 return value.Null