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

227 lines, 115 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 = rd.PosInt()
75 rd.Done()
76
77 # TODO: 0 is global, -1 is current, -2 is parent
78 return value.Frame(self.mem.CurrentFrame())
79
80
81class BindFrame(vm._Callable):
82
83 def __init__(self):
84 # type: () -> None
85 vm._Callable.__init__(self)
86
87 def Call(self, rd):
88 # type: (typed_args.Reader) -> value_t
89
90 # TODO: also take an ExprFrag -> Expr
91
92 frag = rd.PosCommandFrag()
93 frame = rd.PosFrame()
94 rd.Done()
95 return value.Null
96 # TODO: I guess you have to bind 2 frames?
97 #return Command(cmd_frag.Expr(frag), frame, None)
98
99
100class Shvar_get(vm._Callable):
101 """Look up with dynamic scope."""
102
103 def __init__(self, mem):
104 # type: (state.Mem) -> None
105 vm._Callable.__init__(self)
106 self.mem = mem
107
108 def Call(self, rd):
109 # type: (typed_args.Reader) -> value_t
110 name = rd.PosStr()
111 rd.Done()
112 return state.DynamicGetVar(self.mem, name, scope_e.Dynamic)
113
114
115class GetVar(vm._Callable):
116 """Look up a variable, with normal scoping rules."""
117
118 def __init__(self, mem):
119 # type: (state.Mem) -> None
120 vm._Callable.__init__(self)
121 self.mem = mem
122
123 def Call(self, rd):
124 # type: (typed_args.Reader) -> value_t
125 name = rd.PosStr()
126 rd.Done()
127 return state.DynamicGetVar(self.mem, name, scope_e.LocalOrGlobal)
128
129
130class SetVar(vm._Callable):
131 """Set a variable in the local scope.
132
133 We could have a separae setGlobal() too.
134 """
135
136 def __init__(self, mem):
137 # type: (state.Mem) -> None
138 vm._Callable.__init__(self)
139 self.mem = mem
140
141 def Call(self, rd):
142 # type: (typed_args.Reader) -> value_t
143 var_name = rd.PosStr()
144 val = rd.PosValue()
145 rd.Done()
146 self.mem.SetNamed(location.LName(var_name), val, scope_e.LocalOnly)
147 return value.Null
148
149
150class ParseCommand(vm._Callable):
151
152 def __init__(self, parse_ctx, mem, errfmt):
153 # type: (parse_lib.ParseContext, state.Mem, ui.ErrorFormatter) -> None
154 self.parse_ctx = parse_ctx
155 self.mem = mem
156 self.errfmt = errfmt
157
158 def Call(self, rd):
159 # type: (typed_args.Reader) -> value_t
160 code_str = rd.PosStr()
161 rd.Done()
162
163 line_reader = reader.StringLineReader(code_str, self.parse_ctx.arena)
164 c_parser = self.parse_ctx.MakeOshParser(line_reader)
165
166 # TODO: it would be nice to point to the location of the expression
167 # argument
168 src = source.Dynamic('parseCommand()', rd.LeftParenToken())
169 with alloc.ctx_SourceCode(self.parse_ctx.arena, src):
170 try:
171 cmd = main_loop.ParseWholeFile(c_parser)
172 except error.Parse as e:
173 # This prints the location
174 self.errfmt.PrettyPrintError(e)
175
176 # TODO: add inner location info to this structured error
177 raise error.Structured(3, "Syntax error in parseCommand()",
178 rd.LeftParenToken())
179
180 # TODO: It's a little weird that this captures?
181 # We should have scoping like 'eval $mystr'
182 # Or we should have
183 #
184 # var c = parseCommand('echo hi') # raw AST
185 # var block = Block(c) # attachs the current frame
186 #
187 # Yeah we might need this for value.Expr too, to control evaluation of
188 # names
189 #
190 # value.Expr vs. value.BoundExpr - it's bound to the frame it's defined
191 # in
192 # value.Command vs. value.Block - BoundCommand?
193
194 return value.Command(cmd_frag.Expr(cmd), self.mem.CurrentFrame(),
195 self.mem.GlobalFrame())
196
197
198class ParseExpr(vm._Callable):
199
200 def __init__(self, parse_ctx, errfmt):
201 # type: (parse_lib.ParseContext, ui.ErrorFormatter) -> None
202 self.parse_ctx = parse_ctx
203 self.errfmt = errfmt
204
205 def Call(self, rd):
206 # type: (typed_args.Reader) -> value_t
207 code_str = rd.PosStr()
208 rd.Done()
209
210 return value.Null
211
212
213class EvalExpr(vm._Callable):
214
215 def __init__(self, expr_ev):
216 # type: (expr_eval.ExprEvaluator) -> None
217 self.expr_ev = expr_ev
218
219 def Call(self, rd):
220 # type: (typed_args.Reader) -> value_t
221 unused_self = rd.PosObj()
222 lazy = rd.PosExpr()
223 rd.Done()
224
225 result = self.expr_ev.EvalExprClosure(lazy, rd.LeftParenToken())
226
227 return result