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

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