1from __future__ import print_function
2
3import sys
4
5PASS_BY_VALUE = 1
6PASS_BY_REFERENCE = 2
7
8DIR_NONE = 0
9DIR_IN   = 1
10DIR_OUT  = 2
11DIR_BOTH = 3
12
13_default_namespace = ""
14def namespace(s):
15    if type(s) != str:
16        raise TypeError("Require a string for the default namespace.")
17    global _default_namespace
18    _default_namespace = s
19
20def _get_default_namespace():
21    global _default_namespace
22    return _default_namespace
23
24_default_hidden = False
25def hidden(b):
26    global _default_hidden
27    _default_hidden = b
28
29def _get_default_hidden():
30    global _default_hidden
31    return _default_hidden
32
33class Type(object):
34    def __init__(self, typename, **kwargs):
35        self.namespace = kwargs.setdefault('namespace',
36                _get_default_namespace())
37        self._hidden = kwargs.setdefault('hidden', _get_default_hidden())
38        self.dir = kwargs.setdefault('dir', DIR_BOTH)
39        if self.dir not in [DIR_NONE, DIR_IN, DIR_OUT, DIR_BOTH]:
40            raise ValueError
41
42        self.passby = kwargs.setdefault('passby', PASS_BY_VALUE)
43        if self.passby not in [PASS_BY_VALUE, PASS_BY_REFERENCE]:
44            raise ValueError
45
46        self.private = kwargs.setdefault('private', False)
47
48        if typename is None: # Anonymous type
49            self.typename = None
50            self.rawname = None
51        elif self.namespace is None: # e.g. system provided types
52            self.typename = typename
53            self.rawname = typename
54        else:
55            self.typename = self.namespace + typename
56            self.rawname = typename
57
58        if self.typename is not None:
59            self.dispose_fn = kwargs.setdefault('dispose_fn', self.typename + "_dispose")
60        else:
61            self.dispose_fn = kwargs.setdefault('dispose_fn', None)
62
63        self.autogenerate_dispose_fn = kwargs.setdefault('autogenerate_dispose_fn', True)
64
65        if self.typename is not None:
66            self.copy_fn = kwargs.setdefault('copy_fn', self.typename + "_copy")
67        else:
68            self.copy_fn = kwargs.setdefault('copy_fn', None)
69
70        self.autogenerate_copy_fn = kwargs.setdefault('autogenerate_copy_fn', True)
71
72        self.init_fn = kwargs.setdefault('init_fn', None)
73        self.init_val = kwargs.setdefault('init_val', None)
74        self.autogenerate_init_fn = kwargs.setdefault('autogenerate_init_fn', False)
75
76        self.check_default_fn = kwargs.setdefault('check_default_fn', None)
77        self.copy_deprecated_fn = kwargs.setdefault('copy_deprecated_fn',
78                                                    None)
79
80        if self.typename is not None and not self.private:
81            self.json_gen_fn = kwargs.setdefault('json_gen_fn', self.typename + "_gen_json")
82            self.json_parse_type = kwargs.setdefault('json_parse_type', "JSON_ANY")
83            if self.namespace is not None:
84                self.json_parse_fn = kwargs.setdefault('json_parse_fn',
85                                                       self.namespace + "_" + self.rawname  + "_parse_json")
86            else:
87                self.json_parse_fn = kwargs.setdefault('json_parse_fn',
88                                                       self.typename + "_parse_json")
89        else:
90            self.json_gen_fn = kwargs.setdefault('json_gen_fn', None)
91            self.json_parse_type = kwargs.setdefault('json_parse_type', None)
92            self.json_parse_fn = kwargs.setdefault('json_parse_fn', None)
93
94        self.autogenerate_json = kwargs.setdefault('autogenerate_json', True)
95
96    def marshal_in(self):
97        return self.dir in [DIR_IN, DIR_BOTH]
98    def marshal_out(self):
99        return self.dir in [DIR_OUT, DIR_BOTH]
100
101    def hidden(self):
102        if self._hidden:
103            return "_hidden "
104        else:
105            return ""
106
107    def make_arg(self, n, passby=None):
108        if passby is None: passby = self.passby
109
110        if passby == PASS_BY_REFERENCE:
111            return "%s *%s" % (self.typename, n)
112        else:
113            return "%s %s" % (self.typename, n)
114
115    def pass_arg(self, n, isref=None, passby=None):
116        if passby is None: passby = self.passby
117        if isref is None: isref = self.passby == PASS_BY_REFERENCE
118
119        if passby == PASS_BY_REFERENCE:
120            if isref:
121                return "%s" % (n)
122            else:
123                return "&%s" % (n)
124        else:
125            if isref:
126                return "*%s" % (n)
127            else:
128                return "%s" % (n)
129
130class Builtin(Type):
131    """Builtin type"""
132    def __init__(self, typename, **kwargs):
133        kwargs.setdefault('dispose_fn', None)
134        kwargs.setdefault('autogenerate_dispose_fn', False)
135        kwargs.setdefault('autogenerate_json', False)
136        Type.__init__(self, typename, **kwargs)
137
138class Number(Builtin):
139    def __init__(self, ctype, **kwargs):
140        kwargs.setdefault('namespace', None)
141        kwargs.setdefault('dispose_fn', None)
142        kwargs.setdefault('copy_fn', None)
143        kwargs.setdefault('signed', False)
144        kwargs.setdefault('json_gen_fn', "yajl_gen_integer")
145        kwargs.setdefault('json_parse_type', "JSON_INTEGER")
146        # json_parse_fn might be overriden on specific type
147        kwargs.setdefault('json_parse_fn', "libxl__int_parse_json")
148        self.signed = kwargs['signed']
149        Builtin.__init__(self, ctype, **kwargs)
150
151class UInt(Number):
152    def __init__(self, w, **kwargs):
153        kwargs.setdefault('namespace', None)
154        kwargs.setdefault('dispose_fn', None)
155        kwargs.setdefault('json_parse_fn', "libxl__uint%d_parse_json" % w)
156        kwargs.setdefault('copy_fn', None)
157        Number.__init__(self, "uint%d_t" % w, **kwargs)
158
159        self.width = w
160
161class EnumerationValue(object):
162    def __init__(self, enum, value, name, **kwargs):
163        self.enum = enum
164
165        self.valuename = str.upper(name)
166        self.rawname = str.upper(enum.rawname) + "_" + self.valuename
167        self.name = str.upper(enum.value_namespace) + self.rawname
168        self.value = value
169
170class Enumeration(Type):
171    def __init__(self, typename, values, **kwargs):
172        kwargs.setdefault('dispose_fn', None)
173        kwargs.setdefault('copy_fn', None)
174        kwargs.setdefault('json_parse_type', "JSON_STRING")
175        Type.__init__(self, typename, **kwargs)
176
177        self.value_namespace = kwargs.setdefault('value_namespace',
178            self.namespace)
179
180        self.values = []
181        for v in values:
182            # (value, name)
183            (num,name) = v
184            self.values.append(EnumerationValue(self, num, name,
185                                                typename=self.rawname))
186    def lookup(self, name):
187        for v in self.values:
188            if v.valuename == str.upper(name):
189                return v
190        return ValueError
191
192class Field(object):
193    """An element of an Aggregate type"""
194    def __init__(self, type, name, **kwargs):
195        self.type = type
196        self.name = name
197        self.const = kwargs.setdefault('const', False)
198        self.enumname = kwargs.setdefault('enumname', None)
199        self.init_val = kwargs.setdefault('init_val', None)
200        self.deprecated_by = kwargs.setdefault('deprecated_by', None)
201
202class Aggregate(Type):
203    """A type containing a collection of other types"""
204    def __init__(self, kind, typename, fields, **kwargs):
205        kwargs.setdefault('json_parse_type', "JSON_MAP")
206        Type.__init__(self, typename, **kwargs)
207
208        if self.typename is not None:
209            self.init_fn = kwargs.setdefault('init_fn', self.typename + "_init")
210        else:
211            self.init_fn = kwargs.setdefault('init_fn', None)
212
213        self.autogenerate_init_fn = kwargs.setdefault('autogenerate_init_fn', True)
214
215        self.kind = kind
216
217        self.fields = []
218        for f in fields:
219            # (name, type[, {kw args}])
220            if len(f) == 2:
221                n,t = f
222                kw = {}
223            elif len(f) == 3:
224                n,t,kw = f
225            else:
226                raise ValueError
227            if n is None:
228                raise ValueError
229            self.fields.append(Field(t,n,**kw))
230
231    # Returns a tuple (stem, field-expr)
232    #
233    # field-expr is a C expression for a field "f" within the struct
234    # "v".
235    #
236    # stem is the stem common to both "f" and any other sibbling field
237    # within the "v".
238    def member(self, v, f, isref):
239        if isref:
240            deref = v + "->"
241        else:
242            deref = v + "."
243
244        if f.name is None: # Anonymous
245            return (deref, deref)
246        else:
247            return (deref, deref + f.name)
248
249class Struct(Aggregate):
250    def __init__(self, name, fields, **kwargs):
251        kwargs.setdefault('passby', PASS_BY_REFERENCE)
252        Aggregate.__init__(self, "struct", name, fields, **kwargs)
253
254    def has_fields(self):
255        return len(self.fields) != 0
256
257class Union(Aggregate):
258    def __init__(self, name, fields, **kwargs):
259        # Generally speaking some intelligence is required to free a
260        # union therefore any specific instance of this class will
261        # need to provide an explicit destructor function.
262        kwargs.setdefault('passby', PASS_BY_REFERENCE)
263        kwargs.setdefault('dispose_fn', None)
264        Aggregate.__init__(self, "union", name, fields, **kwargs)
265
266class KeyedUnion(Aggregate):
267    """A union which is keyed of another variable in the parent structure"""
268    def __init__(self, name, keyvar_type, keyvar_name, fields, **kwargs):
269        Aggregate.__init__(self, "union", name, [], **kwargs)
270
271        if not isinstance(keyvar_type, Enumeration):
272            raise ValueError
273
274        kv_kwargs = dict([(x.lstrip('keyvar_'),y) for (x,y) in kwargs.items() if x.startswith('keyvar_')])
275
276        self.keyvar = Field(keyvar_type, keyvar_name, **kv_kwargs)
277
278        for f in fields:
279            # (name, enum, type)
280            e, ty = f
281            ev = keyvar_type.lookup(e)
282            en = ev.name
283            self.fields.append(Field(ty, e, enumname=en))
284
285#
286# Standard Types
287#
288
289void = Builtin("void *", namespace = None)
290bool = Builtin("bool", namespace = None,
291               copy_fn=None,
292               json_gen_fn = "yajl_gen_bool",
293               json_parse_type = "JSON_BOOL",
294               json_parse_fn = "libxl__bool_parse_json",
295               autogenerate_json = False)
296
297size_t = Number("size_t", namespace = None)
298
299integer = Number("int", namespace = None, signed = True)
300
301uint8 = UInt(8)
302uint16 = UInt(16)
303uint32 = UInt(32)
304uint64 = UInt(64, json_gen_fn = "libxl__uint64_gen_json")
305
306string = Builtin("char *", namespace = None, copy_fn = "libxl_string_copy", dispose_fn = "free",
307                 json_gen_fn = "libxl__string_gen_json",
308                 json_parse_type = "JSON_STRING | JSON_NULL",
309                 json_parse_fn = "libxl__string_parse_json",
310                 autogenerate_json = False,
311                 check_default_fn="libxl__string_is_default")
312
313class Array(Type):
314    """An array of the same type"""
315    def __init__(self, elem_type, lenvar_name, **kwargs):
316        kwargs.setdefault('dispose_fn', 'free')
317        kwargs.setdefault('json_parse_type', 'JSON_ARRAY')
318        Type.__init__(self, namespace=elem_type.namespace, typename=elem_type.rawname + " *", **kwargs)
319
320        lv_kwargs = dict([(x.lstrip('lenvar_'),y) for (x,y) in kwargs.items() if x.startswith('lenvar_')])
321
322        self.lenvar = Field(integer, lenvar_name, **lv_kwargs)
323        self.elem_type = elem_type
324
325class OrderedDict(dict):
326    """A dictionary which remembers insertion order.
327
328       push to back on duplicate insertion"""
329
330    def __init__(self):
331        dict.__init__(self)
332        self.__ordered = []
333
334    def __setitem__(self, key, value):
335        try:
336            self.__ordered.remove(key)
337        except ValueError:
338            pass
339
340        self.__ordered.append(key)
341        dict.__setitem__(self, key, value)
342
343    def ordered_keys(self):
344        return self.__ordered
345    def ordered_values(self):
346        return [self[x] for x in self.__ordered]
347    def ordered_items(self):
348        return [(x,self[x]) for x in self.__ordered]
349
350def parse(f):
351    print("Parsing %s" % f, file=sys.stderr)
352
353    globs = {}
354    locs = OrderedDict()
355
356    for n,t in globals().items():
357        if isinstance(t, Type):
358            globs[n] = t
359        elif isinstance(t,type(object)) and issubclass(t, Type):
360            globs[n] = t
361        elif n in ['PASS_BY_REFERENCE', 'PASS_BY_VALUE',
362                   'DIR_NONE', 'DIR_IN', 'DIR_OUT', 'DIR_BOTH',
363                   'namespace', 'hidden']:
364            globs[n] = t
365
366    try:
367        exec(compile(open(f).read(), f, 'exec'), globs, locs)
368    except SyntaxError as e:
369        raise SyntaxError("Errors were found at line %d while processing %s:\n\t%s"
370                          % (e.lineno, f, e.text))
371
372    types = [t for t in locs.ordered_values() if isinstance(t,Type)]
373
374    builtins = [t for t in types if isinstance(t,Builtin)]
375    types = [t for t in types if not isinstance(t,Builtin)]
376
377    return (builtins,types)
378