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

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