OILS / builtin / func_reflect.py View on Github | oilshell.org

233 lines, 122 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
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
22from ysh import expr_eval
23
24from typing import TYPE_CHECKING
25if TYPE_CHECKING:
26 from frontend import parse_lib
27 from display import ui
28
29_ = log
30
31
32class Id(vm._Callable):
33 """Return an integer object ID, like Python's id().
34
35 Long shot: pointer tagging, boxless value_t, and small string optimization
36 could mean that value.Str is no longer heap-allocated, and thus doesn't
37 have a GC ID?
38
39 What about value.{Bool,Int,Float}?
40
41 I guess only mutable objects can have IDs then
42 """
43
44 def __init__(self):
45 # type: () -> None
46 vm._Callable.__init__(self)
47
48 def Call(self, rd):
49 # type: (typed_args.Reader) -> value_t
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 Shvar_get(vm._Callable):
107 """Look up with dynamic scope."""
108
109 def __init__(self, mem):
110 # type: (state.Mem) -> None
111 vm._Callable.__init__(self)
112 self.mem = mem
113
114 def Call(self, rd):
115 # type: (typed_args.Reader) -> value_t
116 name = rd.PosStr()
117 rd.Done()
118 return state.DynamicGetVar(self.mem, name, scope_e.Dynamic)
119
120
121class GetVar(vm._Callable):
122 """Look up a variable, with normal scoping rules."""
123
124 def __init__(self, mem):
125 # type: (state.Mem) -> None
126 vm._Callable.__init__(self)
127 self.mem = mem
128
129 def Call(self, rd):
130 # type: (typed_args.Reader) -> value_t
131 name = rd.PosStr()
132 rd.Done()
133 return state.DynamicGetVar(self.mem, name, scope_e.LocalOrGlobal)
134
135
136class SetVar(vm._Callable):
137 """Set a variable in the local scope.
138
139 We could have a separae setGlobal() too.
140 """
141
142 def __init__(self, mem):
143 # type: (state.Mem) -> None
144 vm._Callable.__init__(self)
145 self.mem = mem
146
147 def Call(self, rd):
148 # type: (typed_args.Reader) -> value_t
149 var_name = rd.PosStr()
150 val = rd.PosValue()
151 rd.Done()
152 self.mem.SetNamed(location.LName(var_name), val, scope_e.LocalOnly)
153 return value.Null
154
155
156class ParseCommand(vm._Callable):
157
158 def __init__(self, parse_ctx, mem, errfmt):
159 # type: (parse_lib.ParseContext, state.Mem, ui.ErrorFormatter) -> None
160 self.parse_ctx = parse_ctx
161 self.mem = mem
162 self.errfmt = errfmt
163
164 def Call(self, rd):
165 # type: (typed_args.Reader) -> value_t
166 code_str = rd.PosStr()
167 rd.Done()
168
169 line_reader = reader.StringLineReader(code_str, self.parse_ctx.arena)
170 c_parser = self.parse_ctx.MakeOshParser(line_reader)
171
172 # TODO: it would be nice to point to the location of the expression
173 # argument
174 src = source.Dynamic('parseCommand()', rd.LeftParenToken())
175 with alloc.ctx_SourceCode(self.parse_ctx.arena, src):
176 try:
177 cmd = main_loop.ParseWholeFile(c_parser)
178 except error.Parse as e:
179 # This prints the location
180 self.errfmt.PrettyPrintError(e)
181
182 # TODO: add inner location info to this structured error
183 raise error.Structured(3, "Syntax error in parseCommand()",
184 rd.LeftParenToken())
185
186 # TODO: It's a little weird that this captures?
187 # We should have scoping like 'eval $mystr'
188 # Or we should have
189 #
190 # var c = parseCommand('echo hi') # raw AST
191 # var block = Block(c) # attachs the current frame
192 #
193 # Yeah we might need this for value.Expr too, to control evaluation of
194 # names
195 #
196 # value.Expr vs. value.BoundExpr - it's bound to the frame it's defined
197 # in
198 # value.Command vs. value.Block - BoundCommand?
199
200 return value.Command(cmd_frag.Expr(cmd), self.mem.CurrentFrame(),
201 self.mem.GlobalFrame())
202
203
204class ParseExpr(vm._Callable):
205
206 def __init__(self, parse_ctx, errfmt):
207 # type: (parse_lib.ParseContext, ui.ErrorFormatter) -> None
208 self.parse_ctx = parse_ctx
209 self.errfmt = errfmt
210
211 def Call(self, rd):
212 # type: (typed_args.Reader) -> value_t
213 code_str = rd.PosStr()
214 rd.Done()
215
216 return value.Null
217
218
219class EvalExpr(vm._Callable):
220
221 def __init__(self, expr_ev):
222 # type: (expr_eval.ExprEvaluator) -> None
223 self.expr_ev = expr_ev
224
225 def Call(self, rd):
226 # type: (typed_args.Reader) -> value_t
227 unused_self = rd.PosObj()
228 lazy = rd.PosExpr()
229 rd.Done()
230
231 result = self.expr_ev.EvalExprClosure(lazy, rd.LeftParenToken())
232
233 return result