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

266 lines, 149 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 (source, debug_frame, debug_frame_e)
9from _devbuild.gen.value_asdl import (value, value_e, value_t, cmd_frag)
10
11from core import alloc
12from core import error
13from core import main_loop
14from core import state
15from core import vm
16from data_lang import j8
17from frontend import location
18from frontend import reader
19from frontend import typed_args
20from mycpp import mops
21from mycpp.mylib import log, tagswitch
22
23from typing import List, cast, TYPE_CHECKING
24if TYPE_CHECKING:
25 from frontend import parse_lib
26 from display import ui
27
28_ = log
29
30
31class Id(vm._Callable):
32 """Return an integer object ID, like Python's id().
33
34 Long shot: pointer tagging, boxless value_t, and small string optimization
35 could mean that value.Str is no longer heap-allocated, and thus doesn't
36 have a GC ID?
37
38 What about value.{Bool,Int,Float}?
39
40 I guess only mutable objects can have IDs then
41 """
42
43 def __init__(self):
44 # type: () -> None
45 vm._Callable.__init__(self)
46
47 def Call(self, rd):
48 # type: (typed_args.Reader) -> value_t
49 unused_vm = rd.PosValue() # vm.id()
50 val = rd.PosValue()
51 rd.Done()
52
53 # Select mutable values for now
54 with tagswitch(val) as case:
55 if case(value_e.List, value_e.Dict, value_e.Obj):
56 id_ = j8.HeapValueId(val)
57 return value.Int(mops.IntWiden(id_))
58 else:
59 raise error.TypeErr(val, 'id() expected List, Dict, or Obj',
60 rd.BlamePos())
61 raise AssertionError()
62
63
64class GetFrame(vm._Callable):
65
66 def __init__(self, mem):
67 # type: (state.Mem) -> None
68 vm._Callable.__init__(self)
69 self.mem = mem
70
71 def Call(self, rd):
72 # type: (typed_args.Reader) -> value_t
73 unused_self = rd.PosObj()
74 index = mops.BigTruncate(rd.PosInt())
75 rd.Done()
76
77 length = len(self.mem.var_stack)
78 if index < 0:
79 index += length
80 if 0 <= index and index < length:
81 return value.Frame(self.mem.var_stack[index])
82 else:
83 raise error.Structured(3, "Invalid frame %d" % index,
84 rd.LeftParenToken())
85
86
87class BindFrame(vm._Callable):
88
89 def __init__(self):
90 # type: () -> None
91 vm._Callable.__init__(self)
92
93 def Call(self, rd):
94 # type: (typed_args.Reader) -> value_t
95
96 # TODO: also take an ExprFrag -> Expr
97
98 frag = rd.PosCommandFrag()
99 frame = rd.PosFrame()
100 rd.Done()
101 return value.Null
102 # TODO: I guess you have to bind 2 frames?
103 #return Command(cmd_frag.Expr(frag), frame, None)
104
105
106class GetDebugStack(vm._Callable):
107
108 def __init__(self, mem):
109 # type: (state.Mem) -> None
110 vm._Callable.__init__(self)
111 self.mem = mem
112
113 def Call(self, rd):
114 # type: (typed_args.Reader) -> value_t
115 unused_self = rd.PosObj()
116 rd.Done()
117
118 debug_frames = [value.DebugFrame(fr)
119 for fr in self.mem.debug_stack] # type: List[value_t]
120 return value.List(debug_frames)
121
122
123class FormatDebugFrame(vm._Callable):
124
125 def __init__(self):
126 # type: () -> None
127 vm._Callable.__init__(self)
128
129 def Call(self, rd):
130 # type: (typed_args.Reader) -> value_t
131 frame = rd.PosDebugFrame()
132 rd.Done()
133
134 UP_frame = frame
135 result = ''
136 with tagswitch(frame) as case:
137 if case(debug_frame_e.MainEntry):
138 frame = cast(debug_frame.MainEntry, UP_frame)
139 result = 'main entry %s' % frame.description
140 elif case(debug_frame_e.MainFile):
141 frame = cast(debug_frame.MainFile, UP_frame)
142 result = 'main file %s' % frame.main_filename
143 elif case(debug_frame_e.Call):
144 frame = cast(debug_frame.Call, UP_frame)
145 result = 'call'
146 elif case(debug_frame_e.Source):
147 frame = cast(debug_frame.Source, UP_frame)
148 result = 'source'
149 else:
150 raise AssertionError()
151 return value.Str(result)
152
153
154class Shvar_get(vm._Callable):
155 """Look up with dynamic scope."""
156
157 def __init__(self, mem):
158 # type: (state.Mem) -> None
159 vm._Callable.__init__(self)
160 self.mem = mem
161
162 def Call(self, rd):
163 # type: (typed_args.Reader) -> value_t
164 name = rd.PosStr()
165 rd.Done()
166 return state.DynamicGetVar(self.mem, name, scope_e.Dynamic)
167
168
169class GetVar(vm._Callable):
170 """Look up a variable, with normal scoping rules."""
171
172 def __init__(self, mem):
173 # type: (state.Mem) -> None
174 vm._Callable.__init__(self)
175 self.mem = mem
176
177 def Call(self, rd):
178 # type: (typed_args.Reader) -> value_t
179 name = rd.PosStr()
180 rd.Done()
181 return state.DynamicGetVar(self.mem, name, scope_e.LocalOrGlobal)
182
183
184class SetVar(vm._Callable):
185 """Set a variable in the local scope.
186
187 We could have a separae setGlobal() too.
188 """
189
190 def __init__(self, mem):
191 # type: (state.Mem) -> None
192 vm._Callable.__init__(self)
193 self.mem = mem
194
195 def Call(self, rd):
196 # type: (typed_args.Reader) -> value_t
197 var_name = rd.PosStr()
198 val = rd.PosValue()
199 set_global = rd.NamedBool('global', False)
200 rd.Done()
201 scope = scope_e.GlobalOnly if set_global else scope_e.LocalOnly
202 self.mem.SetNamed(location.LName(var_name), val, scope)
203 return value.Null
204
205
206class ParseCommand(vm._Callable):
207
208 def __init__(self, parse_ctx, mem, errfmt):
209 # type: (parse_lib.ParseContext, state.Mem, ui.ErrorFormatter) -> None
210 self.parse_ctx = parse_ctx
211 self.mem = mem
212 self.errfmt = errfmt
213
214 def Call(self, rd):
215 # type: (typed_args.Reader) -> value_t
216 code_str = rd.PosStr()
217 rd.Done()
218
219 line_reader = reader.StringLineReader(code_str, self.parse_ctx.arena)
220 c_parser = self.parse_ctx.MakeOshParser(line_reader)
221
222 # TODO: it would be nice to point to the location of the expression
223 # argument
224 src = source.Dynamic('parseCommand()', rd.LeftParenToken())
225 with alloc.ctx_SourceCode(self.parse_ctx.arena, src):
226 try:
227 cmd = main_loop.ParseWholeFile(c_parser)
228 except error.Parse as e:
229 # This prints the location
230 self.errfmt.PrettyPrintError(e)
231
232 # TODO: add inner location info to this structured error
233 raise error.Structured(3, "Syntax error in parseCommand()",
234 rd.LeftParenToken())
235
236 # TODO: It's a little weird that this captures?
237 # We should have scoping like 'eval $mystr'
238 # Or we should have
239 #
240 # var c = parseCommand('echo hi') # raw AST
241 # var block = Block(c) # attachs the current frame
242 #
243 # Yeah we might need this for value.Expr too, to control evaluation of
244 # names
245 #
246 # value.Expr vs. value.BoundExpr - it's bound to the frame it's defined
247 # in
248 # value.Command vs. value.Block - BoundCommand?
249
250 return value.Command(cmd_frag.Expr(cmd), self.mem.CurrentFrame(),
251 self.mem.GlobalFrame())
252
253
254class ParseExpr(vm._Callable):
255
256 def __init__(self, parse_ctx, errfmt):
257 # type: (parse_lib.ParseContext, ui.ErrorFormatter) -> None
258 self.parse_ctx = parse_ctx
259 self.errfmt = errfmt
260
261 def Call(self, rd):
262 # type: (typed_args.Reader) -> value_t
263 code_str = rd.PosStr()
264 rd.Done()
265
266 return value.Null