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

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