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

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