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

332 lines, 179 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, CompoundWord, source,
9 debug_frame, 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 i = index + length
82 else:
83 i = index
84
85 if 0 <= i and i < length:
86 return value.Frame(self.mem.var_stack[i])
87 else:
88 raise error.Structured(3, "Invalid frame %d" % index,
89 rd.LeftParenToken())
90
91
92class BindFrame(vm._Callable):
93
94 def __init__(self):
95 # type: () -> None
96 vm._Callable.__init__(self)
97
98 def Call(self, rd):
99 # type: (typed_args.Reader) -> value_t
100
101 # TODO: also take an ExprFrag -> Expr
102
103 frag = rd.PosCommandFrag()
104 frame = rd.PosFrame()
105 rd.Done()
106 return value.Null
107 # TODO: I guess you have to bind 2 frames?
108 #return Command(cmd_frag.Expr(frag), frame, None)
109
110
111class GetDebugStack(vm._Callable):
112
113 def __init__(self, mem):
114 # type: (state.Mem) -> None
115 vm._Callable.__init__(self)
116 self.mem = mem
117
118 def Call(self, rd):
119 # type: (typed_args.Reader) -> value_t
120 unused_self = rd.PosObj()
121 rd.Done()
122
123 debug_frames = [] # type: List[value_t]
124 for fr in self.mem.debug_stack:
125 # Don't show stack frames created when running the ERR trap - we
126 # want the main stuff
127 if fr.tag() == debug_frame_e.BeforeErrTrap:
128 break
129 if fr.tag() in (debug_frame_e.ProcLike, debug_frame_e.Source,
130 debug_frame_e.CompoundWord, debug_frame_e.Token):
131 debug_frames.append(value.DebugFrame(fr))
132
133 if 0:
134 for fr in debug_frames:
135 log('%s', fr.frame)
136 return value.List(debug_frames)
137
138
139def _FormatDebugFrame(buf, token):
140 # type: (mylib.Writer, Token) -> None
141 """
142 Based on _AddCallToken in core/state.py
143 Should probably move that into core/dev.py or something, and unify them
144
145 We also want the column number so we can print ^==
146 """
147 # note: absolute path can be lon,g, but Python prints it too
148 call_source = ui.GetLineSourceString(token.line)
149 line_num = token.line.line_num
150 call_line = token.line.content
151
152 func_str = ''
153 # This gives the wrong token? If we are calling p, it gives the definition
154 # of p. It doesn't give the func/proc that contains the call to p.
155
156 #if def_tok is not None:
157 # #log('DEF_TOK %s', def_tok)
158 # func_str = ' in %s' % lexer.TokenVal(def_tok)
159
160 # should be exactly 1 line
161 buf.write('%s:%d\n' % (call_source, line_num))
162
163 maybe_newline = '' if call_line.endswith('\n') else '\n'
164 buf.write(' %s%s' % (call_line, maybe_newline))
165
166 buf.write(' ') # prefix
167 ui.PrintCaretLine(call_line, token.col, token.length, buf)
168
169
170class DebugFrameToString(vm._Callable):
171
172 def __init__(self):
173 # type: () -> None
174 vm._Callable.__init__(self)
175
176 def Call(self, rd):
177 # type: (typed_args.Reader) -> value_t
178 frame = rd.PosDebugFrame()
179
180 rd.Done()
181
182 UP_frame = frame
183 buf = mylib.BufWriter()
184 with tagswitch(frame) as case:
185 if case(debug_frame_e.ProcLike):
186 frame = cast(debug_frame.ProcLike, UP_frame)
187 invoke_token = location.LeftTokenForCompoundWord(
188 frame.invoke_loc)
189 assert invoke_token is not None, frame.invoke_loc
190 _FormatDebugFrame(buf, invoke_token)
191
192 elif case(debug_frame_e.Source):
193 frame = cast(debug_frame.Source, UP_frame)
194 invoke_token = location.LeftTokenForCompoundWord(
195 frame.source_loc)
196 assert invoke_token is not None, frame.source_loc
197 _FormatDebugFrame(buf, invoke_token)
198
199 elif case(debug_frame_e.CompoundWord):
200 frame = cast(CompoundWord, UP_frame)
201 invoke_token = location.LeftTokenForCompoundWord(frame)
202 assert invoke_token is not None, frame
203 _FormatDebugFrame(buf, invoke_token)
204
205 elif case(debug_frame_e.Token):
206 frame = cast(Token, UP_frame)
207 _FormatDebugFrame(buf, frame)
208
209 # The location is unused; it is a sentinel
210 #elif case(debug_frame_e.BeforeErrTrap):
211 # frame = cast(debug_frame.BeforeErrTrap, UP_frame)
212 # _FormatDebugFrame(buf, frame.tok)
213
214 else:
215 raise AssertionError()
216
217 return value.Str(buf.getvalue())
218
219
220class Shvar_get(vm._Callable):
221 """Look up with dynamic scope."""
222
223 def __init__(self, mem):
224 # type: (state.Mem) -> None
225 vm._Callable.__init__(self)
226 self.mem = mem
227
228 def Call(self, rd):
229 # type: (typed_args.Reader) -> value_t
230 name = rd.PosStr()
231 rd.Done()
232 return state.DynamicGetVar(self.mem, name, scope_e.Dynamic)
233
234
235class GetVar(vm._Callable):
236 """Look up a variable, with normal scoping rules."""
237
238 def __init__(self, mem):
239 # type: (state.Mem) -> None
240 vm._Callable.__init__(self)
241 self.mem = mem
242
243 def Call(self, rd):
244 # type: (typed_args.Reader) -> value_t
245 name = rd.PosStr()
246 rd.Done()
247 return state.DynamicGetVar(self.mem, name, scope_e.LocalOrGlobal)
248
249
250class SetVar(vm._Callable):
251 """Set a variable in the local scope.
252
253 We could have a separae setGlobal() too.
254 """
255
256 def __init__(self, mem):
257 # type: (state.Mem) -> None
258 vm._Callable.__init__(self)
259 self.mem = mem
260
261 def Call(self, rd):
262 # type: (typed_args.Reader) -> value_t
263 var_name = rd.PosStr()
264 val = rd.PosValue()
265 set_global = rd.NamedBool('global', False)
266 rd.Done()
267 scope = scope_e.GlobalOnly if set_global else scope_e.LocalOnly
268 self.mem.SetNamed(location.LName(var_name), val, scope)
269 return value.Null
270
271
272class ParseCommand(vm._Callable):
273
274 def __init__(self, parse_ctx, mem, errfmt):
275 # type: (parse_lib.ParseContext, state.Mem, ui.ErrorFormatter) -> None
276 self.parse_ctx = parse_ctx
277 self.mem = mem
278 self.errfmt = errfmt
279
280 def Call(self, rd):
281 # type: (typed_args.Reader) -> value_t
282 code_str = rd.PosStr()
283 rd.Done()
284
285 line_reader = reader.StringLineReader(code_str, self.parse_ctx.arena)
286 c_parser = self.parse_ctx.MakeOshParser(line_reader)
287
288 # TODO: it would be nice to point to the location of the expression
289 # argument
290 src = source.Dynamic('parseCommand()', rd.LeftParenToken())
291 with alloc.ctx_SourceCode(self.parse_ctx.arena, src):
292 try:
293 cmd = main_loop.ParseWholeFile(c_parser)
294 except error.Parse as e:
295 # This prints the location
296 self.errfmt.PrettyPrintError(e)
297
298 # TODO: add inner location info to this structured error
299 raise error.Structured(3, "Syntax error in parseCommand()",
300 rd.LeftParenToken())
301
302 # TODO: It's a little weird that this captures?
303 # We should have scoping like 'eval $mystr'
304 # Or we should have
305 #
306 # var c = parseCommand('echo hi') # raw AST
307 # var block = Block(c) # attachs the current frame
308 #
309 # Yeah we might need this for value.Expr too, to control evaluation of
310 # names
311 #
312 # value.Expr vs. value.BoundExpr - it's bound to the frame it's defined
313 # in
314 # value.Command vs. value.Block - BoundCommand?
315
316 return value.Command(cmd_frag.Expr(cmd), self.mem.CurrentFrame(),
317 self.mem.GlobalFrame())
318
319
320class ParseExpr(vm._Callable):
321
322 def __init__(self, parse_ctx, errfmt):
323 # type: (parse_lib.ParseContext, ui.ErrorFormatter) -> None
324 self.parse_ctx = parse_ctx
325 self.errfmt = errfmt
326
327 def Call(self, rd):
328 # type: (typed_args.Reader) -> value_t
329 code_str = rd.PosStr()
330 rd.Done()
331
332 return value.Null