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

262 lines, 145 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 = [
119 value.DebugFrame(fr) for fr in self.mem.debug_stack
120 if fr.tag() in (debug_frame_e.Call, debug_frame_e.Source)
121 ] # type: List[value_t]
122 return value.List(debug_frames)
123
124
125class FormatDebugFrame(vm._Callable):
126
127 def __init__(self):
128 # type: () -> None
129 vm._Callable.__init__(self)
130
131 def Call(self, rd):
132 # type: (typed_args.Reader) -> value_t
133 frame = rd.PosDebugFrame()
134 rd.Done()
135
136 UP_frame = frame
137 result = ''
138 with tagswitch(frame) as case:
139 if case(debug_frame_e.Call):
140 frame = cast(debug_frame.Call, UP_frame)
141 result = 'call'
142 elif case(debug_frame_e.Source):
143 frame = cast(debug_frame.Source, UP_frame)
144 result = 'source'
145 else:
146 raise AssertionError()
147 return value.Str(result)
148
149
150class Shvar_get(vm._Callable):
151 """Look up with dynamic scope."""
152
153 def __init__(self, mem):
154 # type: (state.Mem) -> None
155 vm._Callable.__init__(self)
156 self.mem = mem
157
158 def Call(self, rd):
159 # type: (typed_args.Reader) -> value_t
160 name = rd.PosStr()
161 rd.Done()
162 return state.DynamicGetVar(self.mem, name, scope_e.Dynamic)
163
164
165class GetVar(vm._Callable):
166 """Look up a variable, with normal scoping rules."""
167
168 def __init__(self, mem):
169 # type: (state.Mem) -> None
170 vm._Callable.__init__(self)
171 self.mem = mem
172
173 def Call(self, rd):
174 # type: (typed_args.Reader) -> value_t
175 name = rd.PosStr()
176 rd.Done()
177 return state.DynamicGetVar(self.mem, name, scope_e.LocalOrGlobal)
178
179
180class SetVar(vm._Callable):
181 """Set a variable in the local scope.
182
183 We could have a separae setGlobal() too.
184 """
185
186 def __init__(self, mem):
187 # type: (state.Mem) -> None
188 vm._Callable.__init__(self)
189 self.mem = mem
190
191 def Call(self, rd):
192 # type: (typed_args.Reader) -> value_t
193 var_name = rd.PosStr()
194 val = rd.PosValue()
195 set_global = rd.NamedBool('global', False)
196 rd.Done()
197 scope = scope_e.GlobalOnly if set_global else scope_e.LocalOnly
198 self.mem.SetNamed(location.LName(var_name), val, scope)
199 return value.Null
200
201
202class ParseCommand(vm._Callable):
203
204 def __init__(self, parse_ctx, mem, errfmt):
205 # type: (parse_lib.ParseContext, state.Mem, ui.ErrorFormatter) -> None
206 self.parse_ctx = parse_ctx
207 self.mem = mem
208 self.errfmt = errfmt
209
210 def Call(self, rd):
211 # type: (typed_args.Reader) -> value_t
212 code_str = rd.PosStr()
213 rd.Done()
214
215 line_reader = reader.StringLineReader(code_str, self.parse_ctx.arena)
216 c_parser = self.parse_ctx.MakeOshParser(line_reader)
217
218 # TODO: it would be nice to point to the location of the expression
219 # argument
220 src = source.Dynamic('parseCommand()', rd.LeftParenToken())
221 with alloc.ctx_SourceCode(self.parse_ctx.arena, src):
222 try:
223 cmd = main_loop.ParseWholeFile(c_parser)
224 except error.Parse as e:
225 # This prints the location
226 self.errfmt.PrettyPrintError(e)
227
228 # TODO: add inner location info to this structured error
229 raise error.Structured(3, "Syntax error in parseCommand()",
230 rd.LeftParenToken())
231
232 # TODO: It's a little weird that this captures?
233 # We should have scoping like 'eval $mystr'
234 # Or we should have
235 #
236 # var c = parseCommand('echo hi') # raw AST
237 # var block = Block(c) # attachs the current frame
238 #
239 # Yeah we might need this for value.Expr too, to control evaluation of
240 # names
241 #
242 # value.Expr vs. value.BoundExpr - it's bound to the frame it's defined
243 # in
244 # value.Command vs. value.Block - BoundCommand?
245
246 return value.Command(cmd_frag.Expr(cmd), self.mem.CurrentFrame(),
247 self.mem.GlobalFrame())
248
249
250class ParseExpr(vm._Callable):
251
252 def __init__(self, parse_ctx, errfmt):
253 # type: (parse_lib.ParseContext, ui.ErrorFormatter) -> None
254 self.parse_ctx = parse_ctx
255 self.errfmt = errfmt
256
257 def Call(self, rd):
258 # type: (typed_args.Reader) -> value_t
259 code_str = rd.PosStr()
260 rd.Done()
261
262 return value.Null