OILS / opy / _regtest / src / asdl / py_meta.py View on Github | oilshell.org

309 lines, 159 significant
1#!/usr/bin/env python
2"""
3py_meta.py
4
5Parse an ASDL file, and generate Python classes using metaprogramming.
6All objects descends from Obj, which allows them to be dynamically type-checked
7and serialized. Objects hold type descriptors, which are defined in asdl.py.
8
9Usage:
10 from osh import ast_ as ast
11
12 n1 = ast.ArithVar()
13 n2 = ast.ArrayLiteralPart()
14
15API Notes:
16
17The Python AST module doesn't make any distinction between simple and compound
18sum types. (Simple types have no constructors with fields.)
19
20C++ has to make this distinction for reasons of representation. It's more
21efficient to hold an enum value than a pointer to a class with an enum value.
22In Python I guess that's not quite true.
23
24So in order to serialize the correct bytes for C++, our Python metaclass
25implementation has to differ from what's generated by asdl_c.py. More simply
26put: an op is Add() and not Add, an instance of a class, not an integer value.
27"""
28
29from asdl import asdl_ as asdl
30from asdl import const
31from asdl import format as fmt
32from core import util
33
34log = util.log
35
36
37def _CheckType(value, expected_desc):
38 """Is value of type expected_desc?
39
40 Args:
41 value: Obj or primitive type
42 expected_desc: instance of asdl.Product, asl.Sum, asdl.StrType,
43 asdl.IntType, ArrayType, MaybeType, etc.
44 """
45 if isinstance(expected_desc, asdl.Constructor):
46 # This doesn't make sense because the descriptors are derived from the
47 # declared types. You can declare a field as arith_expr_e but not
48 # ArithBinary.
49 raise AssertionError("Invalid Constructor descriptor")
50
51 if isinstance(expected_desc, asdl.MaybeType):
52 if value is None:
53 return True
54 return _CheckType(value, expected_desc.desc)
55
56 if isinstance(expected_desc, asdl.ArrayType):
57 if not isinstance(value, list):
58 return False
59 # Now check all entries
60 for item in value:
61 if not _CheckType(item, expected_desc.desc):
62 return False
63 return True
64
65 if isinstance(expected_desc, asdl.StrType):
66 return isinstance(value, str)
67
68 if isinstance(expected_desc, asdl.IntType):
69 return isinstance(value, int)
70
71 if isinstance(expected_desc, asdl.BoolType):
72 return isinstance(value, bool)
73
74 if isinstance(expected_desc, asdl.UserType):
75 return isinstance(value, expected_desc.typ)
76
77 try:
78 actual_desc = value.__class__.ASDL_TYPE
79 except AttributeError:
80 return False # it's not of the right type
81
82 if isinstance(expected_desc, asdl.Product):
83 return actual_desc is expected_desc
84
85 if isinstance(expected_desc, asdl.Sum):
86 if asdl.is_simple(expected_desc):
87 return actual_desc is expected_desc
88 else:
89 for cons in expected_desc.types: # It has to be one of the alternatives
90 #log("CHECKING desc %s against %s" % (desc, cons))
91 if actual_desc is cons:
92 return True
93 return False
94
95 raise AssertionError(
96 'Invalid descriptor %r: %r' % (expected_desc.__class__, expected_desc))
97
98
99class Obj(object):
100 # NOTE: We're using CAPS for these static fields, since they are constant at
101 # runtime after metaprogramming.
102 ASDL_TYPE = None # Used for type checking
103
104
105class SimpleObj(Obj):
106 """An enum value.
107
108 Other simple objects: int, str, maybe later a float.
109 """
110 def __init__(self, enum_id, name):
111 self.enum_id = enum_id
112 self.name = name
113
114 # TODO: Why is __hash__ needed? Otherwise native/fastlex_test.py fails.
115 # util.Enum required it too. I thought that instances would hash by
116 # identity?
117 #
118 # Example:
119 # class bool_arg_type_e(py_meta.SimpleObj):
120 # ASDL_TYPE = TYPE_LOOKUP.ByTypeName('bool_arg_type')
121 # bool_arg_type_e.Undefined = bool_arg_type_e(1, 'Undefined')
122
123 def __hash__(self):
124 # Could it be the integer self.enum_id?
125 return hash(self.__class__.__name__ + self.name)
126
127 def __repr__(self):
128 return '<%s %s %s>' % (self.__class__.__name__, self.name, self.enum_id)
129
130
131class CompoundObj(Obj):
132 # TODO: Remove tag?
133 # The tag is always set for constructor types, which are subclasses of sum
134 # types. Never set for product types.
135 tag = None
136
137 # NOTE: SimpleObj could share this.
138 def __repr__(self):
139 ast_f = fmt.TextOutput(util.Buffer()) # No color by default.
140 tree = fmt.MakeTree(self)
141 fmt.PrintTree(tree, ast_f)
142 s, _ = ast_f.GetRaw()
143 return s
144
145
146class DebugCompoundObj(CompoundObj):
147 """A CompoundObj that does dynamic type checks.
148
149 Used by MakeTypes().
150 """
151 # Always set for constructor types, which are subclasses of sum types. Never
152 # set for product types.
153 tag = None
154
155 def __init__(self, *args, **kwargs):
156 # The user must specify ALL required fields or NONE.
157 self._assigned = {f: False for f in self.ASDL_TYPE.GetFieldNames()}
158 self._SetDefaults()
159 if args or kwargs:
160 self._Init(args, kwargs)
161
162 def _SetDefaults(self):
163 for name, desc in self.ASDL_TYPE.GetFields():
164
165 if isinstance(desc, asdl.MaybeType):
166 child = desc.desc
167 if isinstance(child, asdl.IntType):
168 value = const.NO_INTEGER
169 elif isinstance(child, asdl.StrType):
170 value = ''
171 else:
172 value = None
173 self.__setattr__(name, value) # Maybe values can be None
174
175 elif isinstance(desc, asdl.ArrayType):
176 self.__setattr__(name, [])
177
178 def _Init(self, args, kwargs):
179 field_names = list(self.ASDL_TYPE.GetFieldNames())
180 for i, val in enumerate(args):
181 name = field_names[i]
182 self.__setattr__(name, val)
183
184 for name, val in kwargs.items():
185 if self._assigned[name]:
186 raise TypeError('Duplicate assignment of field %r' % name)
187 self.__setattr__(name, val)
188
189 # Disable type checking here
190 #return
191 for name in field_names:
192 if not self._assigned[name]:
193 # If anything was set, then required fields raise an error.
194 raise ValueError("Field %r is required and wasn't initialized" % name)
195
196 def CheckUnassigned(self):
197 """See if there are unassigned fields, for later encoding.
198
199 This is currently only used in unit tests.
200 """
201 unassigned = []
202 for name in self.ASDL_TYPE.GetFieldNames():
203 if not self._assigned[name]:
204 desc = self.ASDL_TYPE.LookupFieldType(name)
205 if not isinstance(desc, asdl.MaybeType):
206 unassigned.append(name)
207 if unassigned:
208 raise ValueError("Fields %r were't be assigned" % unassigned)
209
210 if 1: # Disable type checking here
211 def __setattr__(self, name, value):
212 if name == '_assigned':
213 self.__dict__[name] = value
214 return
215 try:
216 desc = self.ASDL_TYPE.LookupFieldType(name)
217 except KeyError:
218 raise AttributeError('Object of type %r has no attribute %r' %
219 (self.__class__.__name__, name))
220
221 if not _CheckType(value, desc):
222 raise AssertionError("Field %r should be of type %s, got %r (%s)" %
223 (name, desc, value, value.__class__))
224
225 self._assigned[name] = True # check this later when encoding
226 self.__dict__[name] = value
227
228
229def MakeTypes(module, root, type_lookup):
230 """
231 Args:
232 module: asdl.Module
233 root: an object/package to add types to
234 """
235 for defn in module.dfns:
236 typ = defn.value
237
238 #print('TYPE', defn.name, typ)
239 if isinstance(typ, asdl.Sum):
240 sum_type = typ
241 if asdl.is_simple(sum_type):
242 # An object without fields, which can be stored inline.
243
244 # Create a class called foo_e. Unlike the CompoundObj case, it doesn't
245 # have subtypes. Instead if has attributes foo_e.Bar, which Bar is an
246 # instance of foo_e.
247 #
248 # Problem: This means you have a dichotomy between:
249 # cflow_e.Break vs. cflow_e.Break()
250 # If you add a non-simple type like cflow_e.Return(5), the usage will
251 # change. I haven't run into this problem in practice yet.
252
253 class_name = defn.name + '_e'
254 class_attr = {'ASDL_TYPE': sum_type} # asdl.Sum
255 cls = type(class_name, (SimpleObj, ), class_attr)
256 setattr(root, class_name, cls)
257
258 # NOTE: Right now the ASDL_TYPE for for an enum value is the Sum type,
259 # not the Constructor type. We may want to change this if we need
260 # reflection.
261 for i, cons in enumerate(sum_type.types):
262 enum_id = i + 1
263 name = cons.name
264 val = cls(enum_id, cons.name) # Instantiate SimpleObj subtype
265
266 # Set a static attribute like op_id.Plus, op_id.Minus.
267 setattr(cls, name, val)
268 else:
269 tag_num = {}
270
271 # e.g. for arith_expr
272 # Should this be arith_expr_t? It is in C++.
273 base_class = type(defn.name, (DebugCompoundObj, ), {})
274 setattr(root, defn.name, base_class)
275
276 # Make a type and a enum tag for each alternative.
277 for i, cons in enumerate(sum_type.types):
278 tag = i + 1 # zero reserved?
279 tag_num[cons.name] = tag # for enum
280
281 class_attr = {
282 'ASDL_TYPE': cons, # asdl.Constructor
283 'tag': tag, # Does this API change?
284 }
285
286 cls = type(cons.name, (base_class, ), class_attr)
287 setattr(root, cons.name, cls)
288
289 # e.g. arith_expr_e.Const == 1
290 enum_name = defn.name + '_e'
291 tag_enum = type(enum_name, (), tag_num)
292 setattr(root, enum_name, tag_enum)
293
294 elif isinstance(typ, asdl.Product):
295 class_attr = {'ASDL_TYPE': typ}
296 cls = type(defn.name, (DebugCompoundObj, ), class_attr)
297 setattr(root, defn.name, cls)
298
299 else:
300 raise AssertionError(typ)
301
302
303def AssignTypes(src_module, dest_module):
304 """For generated code."""
305 for name in dir(src_module):
306 if not name.startswith('__'):
307 v = getattr(src_module, name)
308 setattr(dest_module, name, v)
309