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

307 lines, 168 significant
1#!/usr/bin/env python2
2"""
3func_reflect.py - Functions for reflecting on Oils code - OSH or YSH.
4"""
5from __future__ import print_function
6
7from _devbuild.gen.runtime_asdl import scope_e
8from _devbuild.gen.syntax_asdl import (Token, source, debug_frame,
9 debug_frame_e)
10from _devbuild.gen.value_asdl import (value, value_e, value_t, cmd_frag)
11
12from core import alloc
13from core import error
14from core import main_loop
15from core import state
16from core import vm
17from data_lang import j8
18from display import ui
19from frontend import location
20from frontend import reader
21from frontend import typed_args
22from mycpp import mops
23from mycpp import mylib
24from mycpp.mylib import log, tagswitch
25
26from typing import List, cast, TYPE_CHECKING
27if TYPE_CHECKING:
28 from frontend import parse_lib
29
30_ = log
31
32
33class Id(vm._Callable):
34 """Return an integer object ID, like Python's id().
35
36 Long shot: pointer tagging, boxless value_t, and small string optimization
37 could mean that value.Str is no longer heap-allocated, and thus doesn't
38 have a GC ID?
39
40 What about value.{Bool,Int,Float}?
41
42 I guess only mutable objects can have IDs then
43 """
44
45 def __init__(self):
46 # type: () -> None
47 vm._Callable.__init__(self)
48
49 def Call(self, rd):
50 # type: (typed_args.Reader) -> value_t
51 unused_vm = rd.PosValue() # vm.id()
52 val = rd.PosValue()
53 rd.Done()
54
55 # Select mutable values for now
56 with tagswitch(val) as case:
57 if case(value_e.List, value_e.Dict, value_e.Obj):
58 id_ = j8.HeapValueId(val)
59 return value.Int(mops.IntWiden(id_))
60 else:
61 raise error.TypeErr(val, 'id() expected List, Dict, or Obj',
62 rd.BlamePos())
63 raise AssertionError()
64
65
66class GetFrame(vm._Callable):
67
68 def __init__(self, mem):
69 # type: (state.Mem) -> None
70 vm._Callable.__init__(self)
71 self.mem = mem
72
73 def Call(self, rd):
74 # type: (typed_args.Reader) -> value_t
75 unused_self = rd.PosObj()
76 index = mops.BigTruncate(rd.PosInt())
77 rd.Done()
78
79 length = len(self.mem.var_stack)
80 if index < 0:
81 index += length
82 if 0 <= index and index < length:
83 return value.Frame(self.mem.var_stack[index])
84 else:
85 raise error.Structured(3, "Invalid frame %d" % index,
86 rd.LeftParenToken())
87
88
89class BindFrame(vm._Callable):
90
91 def __init__(self):
92 # type: () -> None
93 vm._Callable.__init__(self)
94
95 def Call(self, rd):
96 # type: (typed_args.Reader) -> value_t
97
98 # TODO: also take an ExprFrag -> Expr
99
100 frag = rd.PosCommandFrag()
101 frame = rd.PosFrame()
102 rd.Done()
103 return value.Null
104 # TODO: I guess you have to bind 2 frames?
105 #return Command(cmd_frag.Expr(frag), frame, None)
106
107
108class GetDebugStack(vm._Callable):
109
110 def __init__(self, mem):
111 # type: (state.Mem) -> None
112 vm._Callable.__init__(self)
113 self.mem = mem
114
115 def Call(self, rd):
116 # type: (typed_args.Reader) -> value_t
117 unused_self = rd.PosObj()
118 rd.Done()
119
120 debug_frames = [
121 value.DebugFrame(fr) for fr in self.mem.debug_stack
122 if fr.tag() in (debug_frame_e.ProcLike, debug_frame_e.Func,
123 debug_frame_e.Source, debug_frame_e.Use,
124 debug_frame_e.EvalBuiltin)
125 ] # type: List[value_t]
126 return value.List(debug_frames)
127
128
129def _FormatDebugFrame(buf, token):
130 # type: (mylib.Writer, Token) -> None
131 """
132 Based on _AddCallToken in core/state.py
133 Should probably move that into core/dev.py or something, and unify them
134
135 We also want the column number so we can print ^==
136 """
137 # note: absolute path can be lon,g, but Python prints it too
138 call_source = ui.GetLineSourceString(token.line)
139 line_num = token.line.line_num
140 call_line = token.line.content
141
142 func_str = ''
143 # This gives the wrong token? If we are calling p, it gives the definition
144 # of p. It doesn't give the func/proc that contains the call to p.
145
146 #if def_tok is not None:
147 # #log('DEF_TOK %s', def_tok)
148 # func_str = ' in %s' % lexer.TokenVal(def_tok)
149
150 # should be exactly 1 line
151 buf.write('%s:%d\n' % (call_source, line_num))
152
153 maybe_newline = '' if call_line.endswith('\n') else '\n'
154 buf.write(' %s%s' % (call_line, maybe_newline))
155
156 buf.write(' ') # prefix
157 ui.PrintCaretLine(call_line, token.col, token.length, buf)
158
159
160class DebugFrameToString(vm._Callable):
161
162 def __init__(self):
163 # type: () -> None
164 vm._Callable.__init__(self)
165
166 def Call(self, rd):
167 # type: (typed_args.Reader) -> value_t
168 frame = rd.PosDebugFrame()
169
170 rd.Done()
171
172 UP_frame = frame
173 buf = mylib.BufWriter()
174 with tagswitch(frame) as case:
175 if case(debug_frame_e.ProcLike):
176 frame = cast(debug_frame.ProcLike, UP_frame)
177 _FormatDebugFrame(buf, frame.call_tok)
178 elif case(debug_frame_e.Func):
179 frame = cast(debug_frame.Func, UP_frame)
180 _FormatDebugFrame(buf, frame.call_tok)
181 elif case(debug_frame_e.Source):
182 frame = cast(debug_frame.Source, UP_frame)
183 _FormatDebugFrame(buf, frame.call_tok)
184 elif case(debug_frame_e.Use):
185 frame = cast(debug_frame.Use, UP_frame)
186 _FormatDebugFrame(buf, frame.invoke_tok)
187 elif case(debug_frame_e.EvalBuiltin):
188 frame = cast(debug_frame.EvalBuiltin, UP_frame)
189 _FormatDebugFrame(buf, frame.invoke_tok)
190 else:
191 raise AssertionError()
192 return value.Str(buf.getvalue())
193
194
195class Shvar_get(vm._Callable):
196 """Look up with dynamic scope."""
197
198 def __init__(self, mem):
199 # type: (state.Mem) -> None
200 vm._Callable.__init__(self)
201 self.mem = mem
202
203 def Call(self, rd):
204 # type: (typed_args.Reader) -> value_t
205 name = rd.PosStr()
206 rd.Done()
207 return state.DynamicGetVar(self.mem, name, scope_e.Dynamic)
208
209
210class GetVar(vm._Callable):
211 """Look up a variable, with normal scoping rules."""
212
213 def __init__(self, mem):
214 # type: (state.Mem) -> None
215 vm._Callable.__init__(self)
216 self.mem = mem
217
218 def Call(self, rd):
219 # type: (typed_args.Reader) -> value_t
220 name = rd.PosStr()
221 rd.Done()
222 return state.DynamicGetVar(self.mem, name, scope_e.LocalOrGlobal)
223
224
225class SetVar(vm._Callable):
226 """Set a variable in the local scope.
227
228 We could have a separae setGlobal() too.
229 """
230
231 def __init__(self, mem):
232 # type: (state.Mem) -> None
233 vm._Callable.__init__(self)
234 self.mem = mem
235
236 def Call(self, rd):
237 # type: (typed_args.Reader) -> value_t
238 var_name = rd.PosStr()
239 val = rd.PosValue()
240 set_global = rd.NamedBool('global', False)
241 rd.Done()
242 scope = scope_e.GlobalOnly if set_global else scope_e.LocalOnly
243 self.mem.SetNamed(location.LName(var_name), val, scope)
244 return value.Null
245
246
247class ParseCommand(vm._Callable):
248
249 def __init__(self, parse_ctx, mem, errfmt):
250 # type: (parse_lib.ParseContext, state.Mem, ui.ErrorFormatter) -> None
251 self.parse_ctx = parse_ctx
252 self.mem = mem
253 self.errfmt = errfmt
254
255 def Call(self, rd):
256 # type: (typed_args.Reader) -> value_t
257 code_str = rd.PosStr()
258 rd.Done()
259
260 line_reader = reader.StringLineReader(code_str, self.parse_ctx.arena)
261 c_parser = self.parse_ctx.MakeOshParser(line_reader)
262
263 # TODO: it would be nice to point to the location of the expression
264 # argument
265 src = source.Dynamic('parseCommand()', rd.LeftParenToken())
266 with alloc.ctx_SourceCode(self.parse_ctx.arena, src):
267 try:
268 cmd = main_loop.ParseWholeFile(c_parser)
269 except error.Parse as e:
270 # This prints the location
271 self.errfmt.PrettyPrintError(e)
272
273 # TODO: add inner location info to this structured error
274 raise error.Structured(3, "Syntax error in parseCommand()",
275 rd.LeftParenToken())
276
277 # TODO: It's a little weird that this captures?
278 # We should have scoping like 'eval $mystr'
279 # Or we should have
280 #
281 # var c = parseCommand('echo hi') # raw AST
282 # var block = Block(c) # attachs the current frame
283 #
284 # Yeah we might need this for value.Expr too, to control evaluation of
285 # names
286 #
287 # value.Expr vs. value.BoundExpr - it's bound to the frame it's defined
288 # in
289 # value.Command vs. value.Block - BoundCommand?
290
291 return value.Command(cmd_frag.Expr(cmd), self.mem.CurrentFrame(),
292 self.mem.GlobalFrame())
293
294
295class ParseExpr(vm._Callable):
296
297 def __init__(self, parse_ctx, errfmt):
298 # type: (parse_lib.ParseContext, ui.ErrorFormatter) -> None
299 self.parse_ctx = parse_ctx
300 self.errfmt = errfmt
301
302 def Call(self, rd):
303 # type: (typed_args.Reader) -> value_t
304 code_str = rd.PosStr()
305 rd.Done()
306
307 return value.Null