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

255 lines, 136 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
45 def __init__(self, expr_ev):
46 # type: (expr_eval.ExprEvaluator) -> None
47 self.expr_ev = expr_ev
48 self.mem = expr_ev.mem
49
50 def Call(self, rd):
51 # type: (typed_args.Reader) -> value_t
52 unused_self = rd.PosObj()
53 lazy = rd.PosExpr()
54
55 dollar0 = rd.NamedStr("dollar0", None)
56 pos_args_raw = rd.NamedList("pos_args", None)
57 vars_ = rd.NamedDict("vars", None)
58 rd.Done()
59
60 pos_args = _CheckPosArgs(pos_args_raw, rd.LeftParenToken())
61
62 # Note: ctx_Eval is on the outside, while ctx_EnclosedFrame is used in
63 # EvalExprClosure
64 with state.ctx_EnclosedFrame(self.mem, lazy.captured_frame,
65 lazy.module_frame, None):
66 with state.ctx_Eval(self.mem, dollar0, pos_args, vars_):
67 result = self.expr_ev.EvalExpr(lazy.e, rd.LeftParenToken())
68
69 return result
70
71
72def _PrintFrame(prefix, frame):
73 # type: (str, Dict[str, Cell]) -> None
74 print('%s %s' % (prefix, ' '.join(frame.keys())))
75
76 rear = frame.get('__E__')
77 if rear:
78 rear_val = rear.val
79 if rear_val.tag() == value_e.Frame:
80 r = cast(value.Frame, rear_val)
81 _PrintFrame('--> ' + prefix, r.frame)
82
83
84class EvalInFrame(vm._Callable):
85 """
86 For making "inline procs"
87 """
88
89 def __init__(self, mem, cmd_ev):
90 # type: (state.Mem, cmd_eval.CommandEvaluator) -> None
91 self.mem = mem
92 self.cmd_ev = cmd_ev
93
94 def Call(self, rd):
95 # type: (typed_args.Reader) -> value_t
96 frag = rd.PosCommandFrag()
97 bound = rd.PosFrame()
98
99 # TODO: EvalCommandFrag()
100
101 return value.Null
102
103
104class Eval(vm._Callable):
105 """
106 These are similar:
107
108 var cmd = ^(echo hi)
109 call io->eval(cmd)
110
111 Also give the top namespace
112
113 call io->evalToDict(cmd)
114
115 The CALLER must handle errors.
116 """
117
118 def __init__(self, mem, cmd_ev, which):
119 # type: (state.Mem, cmd_eval.CommandEvaluator, int) -> None
120 self.mem = mem
121 self.cmd_ev = cmd_ev
122 self.which = which
123
124 def Call(self, rd):
125 # type: (typed_args.Reader) -> value_t
126 unused = rd.PosValue()
127 bound = rd.PosCommand()
128
129 cmd = typed_args.GetCommandFrag(bound)
130
131 dollar0 = rd.NamedStr("dollar0", None)
132 pos_args_raw = rd.NamedList("pos_args", None)
133 vars_ = rd.NamedDict("vars", None)
134 rd.Done()
135
136 pos_args = _CheckPosArgs(pos_args_raw, rd.LeftParenToken())
137
138 # TODO: Add debug_frame here, with ctx_Eval or ctx_EvalDebugFrame
139 if self.which == EVAL_NULL:
140 # _PrintFrame('[captured]', captured_frame)
141 with state.ctx_EnclosedFrame(self.mem, bound.captured_frame,
142 bound.module_frame, None):
143 # _PrintFrame('[new]', self.cmd_ev.mem.var_stack[-1])
144 with state.ctx_Eval(self.mem, dollar0, pos_args, vars_):
145 unused_status = self.cmd_ev.EvalCommandFrag(cmd)
146 return value.Null
147
148 elif self.which == EVAL_DICT:
149 # TODO: dollar0, pos_args, vars_ not supported
150 #
151 # Does ctx_EnclosedFrame has different scoping rules? For "vars"?
152
153 bindings = NewDict() # type: Dict[str, value_t]
154 with state.ctx_EnclosedFrame(self.mem, bound.captured_frame,
155 bound.module_frame, bindings):
156 unused_status = self.cmd_ev.EvalCommandFrag(cmd)
157 return value.Dict(bindings)
158
159 else:
160 raise AssertionError()
161
162
163class CaptureStdout(vm._Callable):
164
165 def __init__(self, mem, shell_ex):
166 # type: (state.Mem, vm._Executor) -> None
167 self.mem = mem
168 self.shell_ex = shell_ex
169
170 def Call(self, rd):
171 # type: (typed_args.Reader) -> value_t
172
173 unused = rd.PosValue()
174 cmd = rd.PosCommand()
175 rd.Done() # no more args
176
177 frag = typed_args.GetCommandFrag(cmd)
178 with state.ctx_EnclosedFrame(self.mem, cmd.captured_frame,
179 cmd.module_frame, None):
180 status, stdout_str = self.shell_ex.CaptureStdout(frag)
181 if status != 0:
182 # Note that $() raises error.ErrExit with the status.
183 # But I think that results in a more confusing error message, so we
184 # "wrap" the errors.
185 properties = {
186 'status': num.ToBig(status)
187 } # type: Dict[str, value_t]
188 raise error.Structured(
189 4, 'captureStdout(): command failed with status %d' % status,
190 rd.LeftParenToken(), properties)
191
192 return value.Str(stdout_str)
193
194
195class PromptVal(vm._Callable):
196 """
197 _io->promptVal('$') is like \$
198 It expands to $ or # when root
199 """
200
201 def __init__(self, prompt_ev):
202 # type: (prompt.Evaluator) -> None
203 self.prompt_ev = prompt_ev
204
205 def Call(self, rd):
206 # type: (typed_args.Reader) -> value_t
207
208 # "self" param is guaranteed to succeed
209 unused = rd.PosValue()
210 what = rd.PosStr()
211 rd.Done() # no more args
212
213 # Bug fix: protect against crash later in PromptVal()
214 if len(what) != 1:
215 raise error.Expr(
216 'promptVal() expected a single char, got %r' % what,
217 rd.LeftParenToken())
218
219 return value.Str(self.prompt_ev.PromptVal(what))
220
221
222# TODO: Implement these
223
224
225class Time(vm._Callable):
226
227 def __init__(self):
228 # type: () -> None
229 pass
230
231 def Call(self, rd):
232 # type: (typed_args.Reader) -> value_t
233 return value.Null
234
235
236class Strftime(vm._Callable):
237
238 def __init__(self):
239 # type: () -> None
240 pass
241
242 def Call(self, rd):
243 # type: (typed_args.Reader) -> value_t
244 return value.Null
245
246
247class Glob(vm._Callable):
248
249 def __init__(self):
250 # type: () -> None
251 pass
252
253 def Call(self, rd):
254 # type: (typed_args.Reader) -> value_t
255 return value.Null