1# -*- Mode: Python; py-indent-offset: 4 -*-
2import copy
3import sys
4
5def get_valid_scheme_definitions(defs):
6    return [x for x in defs if isinstance(x, tuple) and len(x) >= 2]
7
8def unescape(s):
9    s = s.replace('\r\n', '\\r\\n').replace('\t', '\\t')
10    return s.replace('\r', '\\r').replace('\n', '\\n')
11
12def make_docstring(lines):
13    return "(char *) " + '\n'.join(['"%s"' % unescape(s) for s in lines])
14
15# New Parameter class, wich emulates a tuple for compatibility reasons
16class Parameter(object):
17    def __init__(self, ptype, pname, pdflt, pnull, pdir=None):
18        self.ptype = ptype
19        self.pname = pname
20        self.pdflt = pdflt
21        self.pnull = pnull
22        self.pdir = pdir
23
24    def __len__(self): return 4
25    def __getitem__(self, i):
26        return (self.ptype, self.pname, self.pdflt, self.pnull)[i]
27
28    def merge(self, old):
29        if old.pdflt is not None:
30            self.pdflt = old.pdflt
31        if old.pnull is not None:
32            self.pnull = old.pnull
33
34# We currently subclass 'str' to make impact on the rest of codegen as
35# little as possible.  Later we can subclass 'object' instead, but
36# then we must find and adapt all places which expect return types to
37# be strings.
38class ReturnType(str):
39    def __new__(cls, *args, **kwds):
40        return str.__new__(cls, *args[:1])
41    def __init__(self, type_name, optional=False):
42        str.__init__(self)
43        self.optional = optional
44
45# Parameter for property based constructors
46class Property(object):
47    def __init__(self, pname, optional, argname):
48        self.pname = pname
49        self.optional = optional
50        self.argname = argname
51
52    def __len__(self): return 4
53    def __getitem__(self, i):
54        return ('', self.pname, self.optional, self.argname)[i]
55
56    def merge(self, old):
57        if old.optional is not None:
58            self.optional = old.optional
59        if old.argname is not None:
60            self.argname = old.argname
61
62
63class Definition(object):
64    docstring = "NULL"
65
66    def py_name(self):
67        return '%s.%s' % (self.module, self.name)
68
69    py_name = property(py_name)
70
71    def __init__(self, *args):
72        """Create a new defs object of this type.  The arguments are the
73        components of the definition"""
74        raise RuntimeError("this is an abstract class")
75
76    def merge(self, old):
77        """Merge in customisations from older version of definition"""
78        raise RuntimeError("this is an abstract class")
79
80    def write_defs(self, fp=sys.stdout):
81        """write out this definition in defs file format"""
82        raise RuntimeError("this is an abstract class")
83
84    def guess_return_value_ownership(self):
85        "return 1 if caller owns return value"
86        if getattr(self, 'is_constructor_of', False):
87            self.caller_owns_return = True
88        elif self.ret in ('char*', 'gchar*', 'string'):
89            self.caller_owns_return = True
90        else:
91            self.caller_owns_return = False
92
93
94class ObjectDef(Definition):
95    def __init__(self, name, *args):
96        self.name = name
97        self.module = None
98        self.parent = None
99        self.c_name = None
100        self.typecode = None
101        self.fields = []
102        self.implements = []
103        self.class_init_func = None
104        self.has_new_constructor_api = False
105        for arg in get_valid_scheme_definitions(args):
106            if arg[0] == 'in-module':
107                self.module = arg[1]
108            elif arg[0] == 'docstring':
109                self.docstring = make_docstring(arg[1:])
110            elif arg[0] == 'parent':
111                self.parent = arg[1]
112            elif arg[0] == 'c-name':
113                self.c_name = arg[1]
114            elif arg[0] == 'gtype-id':
115                self.typecode = arg[1]
116            elif arg[0] == 'fields':
117                for parg in arg[1:]:
118                    self.fields.append((parg[0], parg[1]))
119            elif arg[0] == 'implements':
120                self.implements.append(arg[1])
121    def merge(self, old):
122        # currently the .h parser doesn't try to work out what fields of
123        # an object structure should be public, so we just copy the list
124        # from the old version ...
125        self.fields = old.fields
126        self.implements = old.implements
127    def write_defs(self, fp=sys.stdout):
128        fp.write('(define-object ' + self.name + '\n')
129        if self.module:
130            fp.write('  (in-module "' + self.module + '")\n')
131        if self.parent != (None, None):
132            fp.write('  (parent "' + self.parent + '")\n')
133        for interface in self.implements:
134            fp.write('  (implements "' + interface + '")\n')
135        if self.c_name:
136            fp.write('  (c-name "' + self.c_name + '")\n')
137        if self.typecode:
138            fp.write('  (gtype-id "' + self.typecode + '")\n')
139        if self.fields:
140            fp.write('  (fields\n')
141            for (ftype, fname) in self.fields:
142                fp.write('    \'("' + ftype + '" "' + fname + '")\n')
143            fp.write('  )\n')
144        fp.write(')\n\n')
145
146class InterfaceDef(Definition):
147    def __init__(self, name, *args):
148        self.name = name
149        self.module = None
150        self.c_name = None
151        self.typecode = None
152        self.vtable = None
153        self.fields = []
154        self.interface_info = None
155        for arg in get_valid_scheme_definitions(args):
156            if arg[0] == 'in-module':
157                self.module = arg[1]
158            elif arg[0] == 'docstring':
159                self.docstring = make_docstring(arg[1:])
160            elif arg[0] == 'c-name':
161                self.c_name = arg[1]
162            elif arg[0] == 'gtype-id':
163                self.typecode = arg[1]
164            elif arg[0] == 'vtable':
165                self.vtable = arg[1]
166        if self.vtable is None:
167            self.vtable = self.c_name + "Iface"
168    def write_defs(self, fp=sys.stdout):
169        fp.write('(define-interface ' + self.name + '\n')
170        if self.module:
171            fp.write('  (in-module "' + self.module + '")\n')
172        if self.c_name:
173            fp.write('  (c-name "' + self.c_name + '")\n')
174        if self.typecode:
175            fp.write('  (gtype-id "' + self.typecode + '")\n')
176        fp.write(')\n\n')
177
178class EnumDef(Definition):
179    def __init__(self, name, *args):
180        self.deftype = 'enum'
181        self.name = name
182        self.in_module = None
183        self.c_name = None
184        self.typecode = None
185        self.values = []
186        for arg in get_valid_scheme_definitions(args):
187            if arg[0] == 'in-module':
188                self.in_module = arg[1]
189            elif arg[0] == 'c-name':
190                self.c_name = arg[1]
191            elif arg[0] == 'gtype-id':
192                self.typecode = arg[1]
193            elif arg[0] == 'values':
194                for varg in arg[1:]:
195                    self.values.append((varg[0], varg[1]))
196    def merge(self, old):
197        pass
198    def write_defs(self, fp=sys.stdout):
199        fp.write('(define-' + self.deftype + ' ' + self.name + '\n')
200        if self.in_module:
201            fp.write('  (in-module "' + self.in_module + '")\n')
202        fp.write('  (c-name "' + self.c_name + '")\n')
203        fp.write('  (gtype-id "' + self.typecode + '")\n')
204        if self.values:
205            fp.write('  (values\n')
206            for name, val in self.values:
207                fp.write('    \'("' + name + '" "' + val + '")\n')
208            fp.write('  )\n')
209        fp.write(')\n\n')
210
211class FlagsDef(EnumDef):
212    def __init__(self, *args):
213        EnumDef.__init__(*(self,) + args)
214        self.deftype = 'flags'
215
216class BoxedDef(Definition):
217    def __init__(self, name, *args):
218        self.name = name
219        self.module = None
220        self.c_name = None
221        self.typecode = None
222        self.copy = None
223        self.release = None
224        self.fields = []
225        for arg in get_valid_scheme_definitions(args):
226            if arg[0] == 'in-module':
227                self.module = arg[1]
228            elif arg[0] == 'c-name':
229                self.c_name = arg[1]
230            elif arg[0] == 'gtype-id':
231                self.typecode = arg[1]
232            elif arg[0] == 'copy-func':
233                self.copy = arg[1]
234            elif arg[0] == 'release-func':
235                self.release = arg[1]
236            elif arg[0] == 'fields':
237                for parg in arg[1:]:
238                    self.fields.append((parg[0], parg[1]))
239    def merge(self, old):
240        # currently the .h parser doesn't try to work out what fields of
241        # an object structure should be public, so we just copy the list
242        # from the old version ...
243        self.fields = old.fields
244    def write_defs(self, fp=sys.stdout):
245        fp.write('(define-boxed ' + self.name + '\n')
246        if self.module:
247            fp.write('  (in-module "' + self.module + '")\n')
248        if self.c_name:
249            fp.write('  (c-name "' + self.c_name + '")\n')
250        if self.typecode:
251            fp.write('  (gtype-id "' + self.typecode + '")\n')
252        if self.copy:
253            fp.write('  (copy-func "' + self.copy + '")\n')
254        if self.release:
255            fp.write('  (release-func "' + self.release + '")\n')
256        if self.fields:
257            fp.write('  (fields\n')
258            for (ftype, fname) in self.fields:
259                fp.write('    \'("' + ftype + '" "' + fname + '")\n')
260            fp.write('  )\n')
261        fp.write(')\n\n')
262
263class PointerDef(Definition):
264    def __init__(self, name, *args):
265        self.name = name
266        self.module = None
267        self.c_name = None
268        self.typecode = None
269        self.fields = []
270        for arg in get_valid_scheme_definitions(args):
271            if arg[0] == 'in-module':
272                self.module = arg[1]
273            elif arg[0] == 'c-name':
274                self.c_name = arg[1]
275            elif arg[0] == 'gtype-id':
276                self.typecode = arg[1]
277            elif arg[0] == 'fields':
278                for parg in arg[1:]:
279                    self.fields.append((parg[0], parg[1]))
280    def merge(self, old):
281        # currently the .h parser doesn't try to work out what fields of
282        # an object structure should be public, so we just copy the list
283        # from the old version ...
284        self.fields = old.fields
285    def write_defs(self, fp=sys.stdout):
286        fp.write('(define-pointer ' + self.name + '\n')
287        if self.module:
288            fp.write('  (in-module "' + self.module + '")\n')
289        if self.c_name:
290            fp.write('  (c-name "' + self.c_name + '")\n')
291        if self.typecode:
292            fp.write('  (gtype-id "' + self.typecode + '")\n')
293        if self.fields:
294            fp.write('  (fields\n')
295            for (ftype, fname) in self.fields:
296                fp.write('    \'("' + ftype + '" "' + fname + '")\n')
297            fp.write('  )\n')
298        fp.write(')\n\n')
299
300class MethodDefBase(Definition):
301    def __init__(self, name, *args):
302        dump = 0
303        self.name = name
304        self.ret = None
305        self.caller_owns_return = None
306        self.unblock_threads = None
307        self.c_name = None
308        self.typecode = None
309        self.of_object = None
310        self.params = [] # of form (type, name, default, nullok)
311        self.varargs = 0
312        self.deprecated = None
313        for arg in get_valid_scheme_definitions(args):
314            if arg[0] == 'of-object':
315                self.of_object = arg[1]
316            elif arg[0] == 'docstring':
317                self.docstring = make_docstring(arg[1:])
318            elif arg[0] == 'c-name':
319                self.c_name = arg[1]
320            elif arg[0] == 'gtype-id':
321                self.typecode = arg[1]
322            elif arg[0] == 'return-type':
323                type_name = arg[1]
324                optional = False
325                for prop in arg[2:]:
326                    if prop[0] == 'optional':
327                        optional = True
328                self.ret = ReturnType(type_name, optional)
329            elif arg[0] == 'caller-owns-return':
330                self.caller_owns_return = arg[1] in ('t', '#t')
331            elif arg[0] == 'unblock-threads':
332                self.unblock_threads = arg[1] in ('t', '#t')
333            elif arg[0] == 'parameters':
334                for parg in arg[1:]:
335                    ptype = parg[0]
336                    pname = parg[1]
337                    pdflt = None
338                    pnull = 0
339                    pdir = None
340                    for farg in parg[2:]:
341                        assert isinstance(farg, tuple)
342                        if farg[0] == 'default':
343                            pdflt = farg[1]
344                        elif farg[0] == 'null-ok':
345                            pnull = 1
346                        elif farg[0] == 'direction':
347                            pdir = farg[1]
348                    self.params.append(Parameter(ptype, pname, pdflt, pnull, pdir))
349            elif arg[0] == 'varargs':
350                self.varargs = arg[1] in ('t', '#t')
351            elif arg[0] == 'deprecated':
352                self.deprecated = arg[1]
353            else:
354                sys.stderr.write("Warning: %s argument unsupported.\n"
355                                 % (arg[0]))
356                dump = 1
357        if dump:
358            self.write_defs(sys.stderr)
359
360        if self.caller_owns_return is None and self.ret is not None:
361            self.guess_return_value_ownership()
362
363    def merge(self, old, parmerge):
364        self.caller_owns_return = old.caller_owns_return
365        self.varargs = old.varargs
366        # here we merge extra parameter flags accross to the new object.
367        if not parmerge:
368            self.params = copy.deepcopy(old.params)
369            return
370        for i in range(len(self.params)):
371            ptype, pname, pdflt, pnull = self.params[i]
372            for p2 in old.params:
373                if p2[1] == pname:
374                    self.params[i] = (ptype, pname, p2[2], p2[3])
375                    break
376    def _write_defs(self, fp=sys.stdout):
377        if self.of_object != (None, None):
378            fp.write('  (of-object "' + self.of_object + '")\n')
379        if self.c_name:
380            fp.write('  (c-name "' + self.c_name + '")\n')
381        if self.typecode:
382            fp.write('  (gtype-id "' + self.typecode + '")\n')
383        if self.caller_owns_return:
384            fp.write('  (caller-owns-return #t)\n')
385        if self.unblock_threads:
386            fp.write('  (unblock_threads #t)\n')
387        if self.ret:
388            fp.write('  (return-type "' + self.ret + '")\n')
389        if self.deprecated:
390            fp.write('  (deprecated "' + self.deprecated + '")\n')
391        if self.params:
392            fp.write('  (parameters\n')
393            for ptype, pname, pdflt, pnull in self.params:
394                fp.write('    \'("' + ptype + '" "' + pname +'"')
395                if pdflt: fp.write(' (default "' + pdflt + '")')
396                if pnull: fp.write(' (null-ok)')
397                fp.write(')\n')
398            fp.write('  )\n')
399        if self.varargs:
400            fp.write('  (varargs #t)\n')
401        fp.write(')\n\n')
402
403
404class MethodDef(MethodDefBase):
405    def __init__(self, name, *args):
406        MethodDefBase.__init__(self, name, *args)
407        for item in ('c_name', 'of_object'):
408            if self.__dict__[item] == None:
409                self.write_defs(sys.stderr)
410                raise RuntimeError("definition missing required %s" % (item,))
411
412    def write_defs(self, fp=sys.stdout):
413        fp.write('(define-method ' + self.name + '\n')
414        self._write_defs(fp)
415
416class VirtualDef(MethodDefBase):
417    def write_defs(self, fp=sys.stdout):
418        fp.write('(define-virtual ' + self.name + '\n')
419        self._write_defs(fp)
420
421class FunctionDef(Definition):
422    def __init__(self, name, *args):
423        dump = 0
424        self.name = name
425        self.in_module = None
426        self.is_constructor_of = None
427        self.ret = None
428        self.caller_owns_return = None
429        self.unblock_threads = None
430        self.c_name = None
431        self.typecode = None
432        self.params = [] # of form (type, name, default, nullok)
433        self.varargs = 0
434        self.deprecated = None
435        for arg in get_valid_scheme_definitions(args):
436            if arg[0] == 'in-module':
437                self.in_module = arg[1]
438            elif arg[0] == 'docstring':
439                self.docstring = make_docstring(arg[1:])
440            elif arg[0] == 'is-constructor-of':
441                self.is_constructor_of = arg[1]
442            elif arg[0] == 'c-name':
443                self.c_name = arg[1]
444            elif arg[0] == 'gtype-id':
445                self.typecode = arg[1]
446            elif arg[0] == 'return-type':
447                self.ret = arg[1]
448            elif arg[0] == 'caller-owns-return':
449                self.caller_owns_return = arg[1] in ('t', '#t')
450            elif arg[0] == 'unblock-threads':
451                self.unblock_threads = arg[1] in ('t', '#t')
452            elif arg[0] == 'parameters':
453                for parg in arg[1:]:
454                    ptype = parg[0]
455                    pname = parg[1]
456                    pdflt = None
457                    pnull = 0
458                    for farg in parg[2:]:
459                        if farg[0] == 'default':
460                            pdflt = farg[1]
461                        elif farg[0] == 'null-ok':
462                            pnull = 1
463                    self.params.append(Parameter(ptype, pname, pdflt, pnull))
464            elif arg[0] == 'properties':
465                if self.is_constructor_of is None:
466                    sys.stderr.write("Warning: (properties ...) "\
467                          "is only valid for constructors")
468                for prop in arg[1:]:
469                    pname = prop[0]
470                    optional = False
471                    argname = pname
472                    for farg in prop[1:]:
473                        if farg[0] == 'optional':
474                            optional = True
475                        elif farg[0] == 'argname':
476                            argname = farg[1]
477                    self.params.append(Property(pname, optional, argname))
478            elif arg[0] == 'varargs':
479                self.varargs = arg[1] in ('t', '#t')
480            elif arg[0] == 'deprecated':
481                self.deprecated = arg[1]
482            else:
483                sys.stderr.write("Warning: %s argument unsupported\n"
484                                 % (arg[0],))
485                dump = 1
486        if dump:
487            self.write_defs(sys.stderr)
488
489        if self.caller_owns_return is None and self.ret is not None:
490            self.guess_return_value_ownership()
491        for item in ('c_name',):
492            if self.__dict__[item] == None:
493                self.write_defs(sys.stderr)
494                raise RuntimeError("definition missing required %s" % (item,))
495
496    _method_write_defs = MethodDef.__dict__['write_defs']
497
498    def merge(self, old, parmerge):
499        self.caller_owns_return = old.caller_owns_return
500        self.varargs = old.varargs
501        if not parmerge:
502            self.params = copy.deepcopy(old.params)
503            return
504        # here we merge extra parameter flags accross to the new object.
505        def merge_param(param):
506            for old_param in old.params:
507                if old_param.pname == param.pname:
508                    if isinstance(old_param, Property):
509                        # h2def never scans Property's, therefore if
510                        # we have one it was manually written, so we
511                        # keep it.
512                        return copy.deepcopy(old_param)
513                    else:
514                        param.merge(old_param)
515                        return param
516            raise RuntimeError("could not find %s in old_parameters %r" % (
517                param.pname, [p.pname for p in old.params]))
518        try:
519            self.params = list(map(merge_param, self.params))
520        except RuntimeError:
521            # parameter names changed and we can't find a match; it's
522            # safer to keep the old parameter list untouched.
523            self.params = copy.deepcopy(old.params)
524
525        if not self.is_constructor_of:
526            try:
527                self.is_constructor_of = old.is_constructor_of
528            except AttributeError:
529                pass
530        if isinstance(old, MethodDef):
531            self.name = old.name
532            # transmogrify from function into method ...
533            self.write_defs = self._method_write_defs
534            self.of_object = old.of_object
535            del self.params[0]
536    def write_defs(self, fp=sys.stdout):
537        fp.write('(define-function ' + self.name + '\n')
538        if self.in_module:
539            fp.write('  (in-module "' + self.in_module + '")\n')
540        if self.is_constructor_of:
541            fp.write('  (is-constructor-of "' + self.is_constructor_of +'")\n')
542        if self.c_name:
543            fp.write('  (c-name "' + self.c_name + '")\n')
544        if self.typecode:
545            fp.write('  (gtype-id "' + self.typecode + '")\n')
546        if self.caller_owns_return:
547            fp.write('  (caller-owns-return #t)\n')
548        if self.unblock_threads:
549            fp.write('  (unblock-threads #t)\n')
550        if self.ret:
551            fp.write('  (return-type "' + self.ret + '")\n')
552        if self.deprecated:
553            fp.write('  (deprecated "' + self.deprecated + '")\n')
554        if self.params:
555            if isinstance(self.params[0], Parameter):
556                fp.write('  (parameters\n')
557                for ptype, pname, pdflt, pnull in self.params:
558                    fp.write('    \'("' + ptype + '" "' + pname +'"')
559                    if pdflt: fp.write(' (default "' + pdflt + '")')
560                    if pnull: fp.write(' (null-ok)')
561                    fp.write(')\n')
562                fp.write('  )\n')
563            elif isinstance(self.params[0], Property):
564                fp.write('  (properties\n')
565                for prop in self.params:
566                    fp.write('    \'("' + prop.pname +'"')
567                    if prop.optional: fp.write(' (optional)')
568                    fp.write(')\n')
569                fp.write('  )\n')
570            else:
571                assert False, "strange parameter list %r" % self.params[0]
572        if self.varargs:
573            fp.write('  (varargs #t)\n')
574
575        fp.write(')\n\n')
576