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

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