api.py 37.2 KB
Newer Older
1
import string, sys, re, runpy
Paul Fultz II's avatar
Paul Fultz II committed
2
from functools import wraps
3
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
Paul Fultz II's avatar
Paul Fultz II committed
4

5
6
7
8
type_map: Dict[str, Callable[['Parameter'], None]] = {}
cpp_type_map: Dict[str, str] = {}
functions: List['Function'] = []
cpp_classes: List['CPPClass'] = []
Paul Fultz II's avatar
Paul Fultz II committed
9
10
11
12
error_type = ''
success_type = ''
try_wrap = ''

13
14
15
c_header_preamble: List[str] = []
c_api_body_preamble: List[str] = []
cpp_header_preamble: List[str] = []
Paul Fultz II's avatar
Paul Fultz II committed
16
17


18
def bad_param_error(msg: str):
Paul Fultz II's avatar
Paul Fultz II committed
19
20
21
22
23
24
25
26
    return 'throw std::runtime_error("{}")'.format(msg)


class Template(string.Template):
    idpattern = '[_a-zA-Z0-9@]+'


class Type:
27
    def __init__(self, name: str) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
28
29
        self.name = name.strip()

30
    def is_pointer(self) -> bool:
Paul Fultz II's avatar
Paul Fultz II committed
31
32
        return self.name.endswith('*')

33
    def is_reference(self) -> bool:
Paul Fultz II's avatar
Paul Fultz II committed
34
35
        return self.name.endswith('&')

36
    def is_const(self) -> bool:
Paul Fultz II's avatar
Paul Fultz II committed
37
38
        return self.name.startswith('const ')

39
40
41
    def is_variadic(self):
        return self.name.startswith('...')

42
    def add_pointer(self) -> 'Type':
Paul Fultz II's avatar
Paul Fultz II committed
43
44
45
46
47
        return Type(self.name + '*')

    def add_reference(self):
        return Type(self.name + '&')

48
    def add_const(self) -> 'Type':
Paul Fultz II's avatar
Paul Fultz II committed
49
50
        return Type('const ' + self.name)

51
    def inner_type(self) -> Optional['Type']:
Paul Fultz II's avatar
Paul Fultz II committed
52
53
54
55
56
57
58
        i = self.name.find('<')
        j = self.name.rfind('>')
        if i > 0 and j > 0:
            return Type(self.name[i + 1:j])
        else:
            return None

59
    def remove_generic(self) -> 'Type':
Paul Fultz II's avatar
Paul Fultz II committed
60
61
62
63
64
65
66
        i = self.name.find('<')
        j = self.name.rfind('>')
        if i > 0 and j > 0:
            return Type(self.name[0:i] + self.name[j + 1:])
        else:
            return self

67
    def remove_pointer(self) -> 'Type':
Paul Fultz II's avatar
Paul Fultz II committed
68
69
70
71
        if self.is_pointer():
            return Type(self.name[0:-1])
        return self

72
    def remove_reference(self) -> 'Type':
Paul Fultz II's avatar
Paul Fultz II committed
73
74
75
76
        if self.is_reference():
            return Type(self.name[0:-1])
        return self

77
    def remove_const(self) -> 'Type':
Paul Fultz II's avatar
Paul Fultz II committed
78
79
80
81
        if self.is_const():
            return Type(self.name[6:])
        return self

82
    def basic(self) -> 'Type':
Paul Fultz II's avatar
Paul Fultz II committed
83
84
        return self.remove_pointer().remove_const().remove_reference()

85
    def decay(self) -> 'Type':
Paul Fultz II's avatar
Paul Fultz II committed
86
87
88
89
90
91
        t = self.remove_reference()
        if t.is_pointer():
            return t
        else:
            return t.remove_const()

92
    def const_compatible(self, t: 'Type'):
Paul Fultz II's avatar
Paul Fultz II committed
93
94
95
96
        if t.is_const():
            return self.add_const()
        return self

97
    def str(self) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
98
99
100
101
102
103
104
        return self.name


header_function = Template('''
${error_type} ${name}(${params});
''')

105
106
107
108
function_pointer_typedef = Template('''
typedef ${error_type} (*${fname})(${params});
''')

Paul Fultz II's avatar
Paul Fultz II committed
109
110
111
c_api_impl = Template('''
extern "C" ${error_type} ${name}(${params})
{
112
    ${va_start}auto api_error_result = ${try_wrap}([&] {
Paul Fultz II's avatar
Paul Fultz II committed
113
114
        ${body};
    });
115
    ${va_end}return api_error_result;
Paul Fultz II's avatar
Paul Fultz II committed
116
117
118
119
120
}
''')


class CFunction:
121
    def __init__(self, name: str) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
122
        self.name = name
123
124
125
126
        self.params: List[str] = []
        self.body: List[str] = []
        self.va_start: List[str] = []
        self.va_end: List[str] = []
Paul Fultz II's avatar
Paul Fultz II committed
127

128
    def add_param(self, type: str, pname: str) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
129
130
        self.params.append('{} {}'.format(type, pname))

131
    def add_statement(self, stmt: str) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
132
133
        self.body.append(stmt)

134
    def add_vlist(self, name: str) -> None:
135
136
137
138
139
140
141
142
        last_param = self.params[-1].split()[-1]
        self.va_start = [
            'va_list {};'.format(name),
            'va_start({}, {});'.format(name, last_param)
        ]
        self.va_end = ['va_end({});'.format(name)]
        self.add_param('...', '')

143
    def substitute(self, form: Template, **kwargs) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
144
145
146
147
        return form.substitute(error_type=error_type,
                               try_wrap=try_wrap,
                               name=self.name,
                               params=', '.join(self.params),
148
149
                               body=";\n        ".join(self.body),
                               va_start="\n    ".join(self.va_start),
150
151
                               va_end="\n    ".join(self.va_end),
                               **kwargs)
Paul Fultz II's avatar
Paul Fultz II committed
152

153
    def generate_header(self) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
154
155
        return self.substitute(header_function)

156
157
158
159
    def generate_function_pointer(self, name: Optional[str] = None) -> str:
        return self.substitute(function_pointer_typedef,
                               fname=name or self.name)

160
    def generate_body(self) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
161
162
163
164
        return self.substitute(c_api_impl)


class BadParam:
165
    def __init__(self, cond: str, msg: str) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
166
167
168
169
170
        self.cond = cond
        self.msg = msg


class Parameter:
171
172
173
174
    def __init__(self,
                 name: str,
                 type: str,
                 optional: bool = False,
175
176
177
                 returns: bool = False,
                 virtual: bool = False,
                 this: bool = False) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
178
179
180
        self.name = name
        self.type = Type(type)
        self.optional = optional
181
        self.cparams: List[Tuple[str, str]] = []
Paul Fultz II's avatar
Paul Fultz II committed
182
183
184
185
186
187
188
        self.size_cparam = -1
        self.size_name = ''
        self.read = '${name}'
        self.write = ['*${name} = ${result}']
        self.cpp_read = '${name}'
        self.cpp_write = '${name}'
        self.returns = returns
189
190
        self.virtual = virtual
        self.this = this
191
        self.bad_param_check: Optional[BadParam] = None
192
193
        self.virtual_read: Optional[List[str]] = None
        self.virtual_write: Optional[str] = None
Paul Fultz II's avatar
Paul Fultz II committed
194

195
    def get_name(self, prefix: Optional[str] = None) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
196
197
198
199
200
        if prefix:
            return prefix + self.name
        else:
            return self.name

201
    def get_cpp_type(self) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
202
203
204
205
206
207
208
209
210
        if self.type.str() in cpp_type_map:
            return cpp_type_map[self.type.basic().str()]
        elif self.type.basic().str() in cpp_type_map:
            return cpp_type_map[self.type.basic().str()]
        elif self.returns:
            return self.type.decay().str()
        else:
            return self.type.str()

211
212
213
214
    def substitute(self,
                   s: str,
                   prefix: Optional[str] = None,
                   result: Optional[str] = None) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
215
216
217
218
219
220
221
222
223
224
        ctype = None
        if len(self.cparams) > 0:
            ctype = Type(self.cparams[0][0]).basic().str()
        return Template(s).safe_substitute(name=self.get_name(prefix),
                                           type=self.type.str(),
                                           ctype=ctype or '',
                                           cpptype=self.get_cpp_type(),
                                           size=self.size_name,
                                           result=result or '')

225
226
    def add_param(self, t: Union[str, Type],
                  name: Optional[str] = None) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
227
228
229
230
        if not isinstance(t, str):
            t = t.str()
        self.cparams.append((t, name or self.name))

231
    def add_size_param(self, name: Optional[str] = None) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
232
233
234
235
236
237
238
        self.size_cparam = len(self.cparams)
        self.size_name = name or self.name + '_size'
        if self.returns:
            self.add_param('size_t *', self.size_name)
        else:
            self.add_param('size_t', self.size_name)

239
    def bad_param(self, cond: str, msg: str) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
240
241
242
243
244
245
246
247
248
249
        self.bad_param_check = BadParam(cond, msg)

    def remove_size_param(self, name):
        p = None
        if self.size_cparam >= 0:
            p = self.cparams[self.size_cparam]
            del self.cparams[self.size_cparam]
            self.size_name = name
        return p

250
    def update(self) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
        t = self.type.basic().str()
        g = self.type.remove_generic().basic().str()
        if t in type_map:
            type_map[t](self)
        elif g in type_map:
            type_map[g](self)
        else:
            if self.returns:
                self.add_param(self.type.remove_reference().add_pointer())
            else:
                self.add_param(self.type.remove_reference())
        if isinstance(self.write, str):
            raise ValueError("Error for {}: write cannot be a string".format(
                self.type.str()))

266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
    def virtual_arg(self, prefix: Optional[str] = None) -> List[str]:
        read = self.virtual_read
        if not read and len(self.write) >= len(self.cparams):
            read = [
                Template(w.partition('=')[2]).safe_substitute(result='${name}')
                for w in self.write
            ]
        if not read:
            raise ValueError("No virtual_read parameter provided for: " +
                             self.type.str())
        if isinstance(read, str):
            raise ValueError(
                "Error for {}: virtual_read cannot be a string".format(
                    self.type.str()))
        return [self.substitute(r, prefix=prefix) for r in read]

    def virtual_param(self, prefix: Optional[str] = None) -> str:
        return self.substitute('${type} ${name}', prefix=prefix)

    def virtual_output_args(self, prefix: Optional[str] = None) -> List[str]:
        return [
            '&{prefix}{n}'.format(prefix=prefix or '', n=n)
            for t, n in self.cparams
        ]

    def virtual_output_declarations(self,
                                    prefix: Optional[str] = None) -> List[str]:
        return [
            'std::remove_pointer_t<{type}> {prefix}{n};'.format(
                type=Type(t).str(), prefix=prefix or '', n=n)
            for t, n in self.cparams
        ]

    def virtual_output(self, prefix: Optional[str] = None) -> str:
        write = self.virtual_write
        if not write:
            if '*' in self.read or '->' in self.read:
                write = Template(self.read).safe_substitute(name='(&${name})')
            else:
                write = self.read
        return self.substitute(write, prefix=prefix)

308
    def cpp_param(self, prefix: Optional[str] = None) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
309
310
        return self.substitute('${cpptype} ${name}', prefix=prefix)

311
    def cpp_arg(self, prefix: Optional[str] = None) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
312
313
        return self.substitute(self.cpp_read, prefix=prefix)

314
    def cpp_output_args(self, prefix: Optional[str] = None) -> List[str]:
Paul Fultz II's avatar
Paul Fultz II committed
315
316
317
318
        return [
            '&{prefix}{n}'.format(prefix=prefix, n=n) for t, n in self.cparams
        ]

319
    def output_declarations(self, prefix: Optional[str] = None) -> List[str]:
Paul Fultz II's avatar
Paul Fultz II committed
320
321
322
323
324
325
326
327
328
329
330
        return [
            '{type} {prefix}{n};'.format(type=Type(t).remove_pointer().str(),
                                         prefix=prefix,
                                         n=n) for t, n in self.cparams
        ]

    def output_args(self, prefix=None):
        return [
            '&{prefix}{n};'.format(prefix=prefix, n=n) for t, n in self.cparams
        ]

331
    def cpp_output(self, prefix: Optional[str] = None) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
332
333
        return self.substitute(self.cpp_write, prefix=prefix)

334
    def input(self, prefix: Optional[str] = None) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
335
336
        return '(' + self.substitute(self.read, prefix=prefix) + ')'

337
    def outputs(self, result: Optional[str] = None) -> List[str]:
Paul Fultz II's avatar
Paul Fultz II committed
338
339
        return [self.substitute(w, result=result) for w in self.write]

340
    def add_to_cfunction(self, cfunction: CFunction) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
341
        for t, name in self.cparams:
342
343
344
345
            if t.startswith('...'):
                cfunction.add_vlist(name)
            else:
                cfunction.add_param(self.substitute(t), self.substitute(name))
Paul Fultz II's avatar
Paul Fultz II committed
346
347
348
349
350
351
352
353
        if self.bad_param_check:
            msg = 'Bad parameter {name}: {msg}'.format(
                name=self.name, msg=self.bad_param_check.msg)
            cfunction.add_statement('if ({cond}) {body}'.format(
                cond=self.substitute(self.bad_param_check.cond),
                body=bad_param_error(msg)))


354
def template_var(s: str) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
355
356
357
    return '${' + s + '}'


358
def to_template_vars(params: List[Union[Any, Parameter]]) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
359
360
361
362
363
    return ', '.join([template_var(p.name) for p in params])


class Function:
    def __init__(self,
364
365
366
367
368
369
370
                 name: str,
                 params: Optional[List[Parameter]] = None,
                 shared_size: bool = False,
                 returns: Optional[str] = None,
                 invoke: Optional[str] = None,
                 fname: Optional[str] = None,
                 return_name: Optional[str] = None,
371
                 virtual: bool = False,
372
                 **kwargs) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
373
374
375
        self.name = name
        self.params = params or []
        self.shared_size = False
376
        self.cfunction: Optional[CFunction] = None
Paul Fultz II's avatar
Paul Fultz II committed
377
378
379
380
381
        self.fname = fname
        self.invoke = invoke or '${__fname__}($@)'
        self.return_name = return_name or 'out'
        self.returns = Parameter(self.return_name, returns,
                                 returns=True) if returns else None
382
383
384
385
        for p in self.params:
            p.virtual = virtual
        if self.returns:
            self.returns.virtual = virtual
Paul Fultz II's avatar
Paul Fultz II committed
386

387
    def share_params(self) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
388
389
390
391
392
393
394
395
396
        if self.shared_size == True:
            size_param_name = 'size'
            size_type = Type('size_t')
            for param in self.params:
                p = param.remove_size_param(size_param_name)
                if p:
                    size_type = Type(p[0])
            self.params.append(Parameter(size_param_name, size_type.str()))

397
    def update(self) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
398
399
400
401
402
403
404
        self.share_params()
        for param in self.params:
            param.update()
        if self.returns:
            self.returns.update()
        self.create_cfunction()

405
    def inputs(self) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
406
407
        return ', '.join([p.input() for p in self.params])

408
409
410
    # TODO: Shoule we remove Optional?
    def input_map(self) -> Dict[str, Optional[str]]:
        m: Dict[str, Optional[str]] = {}
Paul Fultz II's avatar
Paul Fultz II committed
411
412
413
414
415
416
417
        for p in self.params:
            m[p.name] = p.input()
        m['return'] = self.return_name
        m['@'] = self.inputs()
        m['__fname__'] = self.fname
        return m

418
    def get_invoke(self) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
419
420
        return Template(self.invoke).safe_substitute(self.input_map())

421
422
423
    def write_to_tmp_var(self) -> bool:
        if not self.returns:
            return False
Paul Fultz II's avatar
Paul Fultz II committed
424
425
426
        return len(self.returns.write) > 1 or self.returns.write[0].count(
            '${result}') > 1

427
428
429
430
431
432
433
    def get_cfunction(self) -> CFunction:
        if self.cfunction:
            return self.cfunction
        raise Exception(
            "self.cfunction is None: self.update() needs to be called.")

    def create_cfunction(self) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
434
435
436
437
438
439
440
        self.cfunction = CFunction(self.name)
        # Add the return as a parameter
        if self.returns:
            self.returns.add_to_cfunction(self.cfunction)
        # Add the input parameters
        for param in self.params:
            param.add_to_cfunction(self.cfunction)
441
        f: Optional[str] = self.get_invoke()
Paul Fultz II's avatar
Paul Fultz II committed
442
443
444
445
        # Write the assignments
        assigns = []
        if self.returns:
            result = f
446
            if self.write_to_tmp_var() and f:
Paul Fultz II's avatar
Paul Fultz II committed
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
                f = 'auto&& api_result = ' + f
                result = 'api_result'
            else:
                f = None
            assigns = self.returns.outputs(result)
        if f:
            self.cfunction.add_statement(f)
        for assign in assigns:
            self.cfunction.add_statement(assign)


cpp_class_template = Template('''

struct ${name} : handle_base<${ctype}, decltype(&${destroy}), ${destroy}>
{
    ${name}(${ctype} p, bool own = true)
    : m_handle(nullptr)
    {
        this->set_handle(p, own);
    }
    ${constructors}

    ${methods}
};
''')

cpp_class_method_template = Template('''
    ${return_type} ${name}(${params}) const
    {
        ${outputs}
        this->call_handle(${args});
        return ${result};
    }
''')

cpp_class_void_method_template = Template('''
    void ${name}(${params}) const
    {
        this->call_handle(${args});
    }
''')

cpp_class_constructor_template = Template('''
    ${name}(${params})
    : m_handle(nullptr)
    {
        m_handle = this->make_handle(${args});
    }
''')


class CPPMember:
499
500
501
502
503
    def __init__(self,
                 name: str,
                 function: Function,
                 prefix: str,
                 method: bool = True) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
504
505
506
507
508
        self.name = name
        self.function = function
        self.prefix = prefix
        self.method = method

509
    def get_function_params(self) -> List[Union[Any, Parameter]]:
Paul Fultz II's avatar
Paul Fultz II committed
510
511
512
513
514
        if self.method:
            return self.function.params[1:]
        else:
            return self.function.params

515
    def get_args(self) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
516
517
518
        output_args = []
        if self.function.returns:
            output_args = self.function.returns.cpp_output_args(self.prefix)
519
520
        if not self.function.cfunction:
            raise Exception('self.function.update() must be called')
Paul Fultz II's avatar
Paul Fultz II committed
521
522
523
524
        return ', '.join(
            ['&{}'.format(self.function.cfunction.name)] + output_args +
            [p.cpp_arg(self.prefix) for p in self.get_function_params()])

525
    def get_params(self) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
526
527
528
        return ', '.join(
            [p.cpp_param(self.prefix) for p in self.get_function_params()])

529
    def get_return_declarations(self) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
530
531
532
533
534
535
536
537
538
539
540
        if self.function.returns:
            return '\n        '.join([
                d
                for d in self.function.returns.output_declarations(self.prefix)
            ])
        else:
            return ''

    def get_result(self):
        return self.function.returns.input(self.prefix)

541
542
543
    def generate_method(self) -> str:
        if not self.function.cfunction:
            raise Exception('self.function.update() must be called')
Paul Fultz II's avatar
Paul Fultz II committed
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
        if self.function.returns:
            return_type = self.function.returns.get_cpp_type()
            return cpp_class_method_template.safe_substitute(
                return_type=return_type,
                name=self.name,
                cfunction=self.function.cfunction.name,
                result=self.function.returns.cpp_output(self.prefix),
                params=self.get_params(),
                outputs=self.get_return_declarations(),
                args=self.get_args(),
                success=success_type)
        else:
            return cpp_class_void_method_template.safe_substitute(
                name=self.name,
                cfunction=self.function.cfunction.name,
                params=self.get_params(),
                args=self.get_args(),
                success=success_type)

563
564
565
    def generate_constructor(self, name: str) -> str:
        if not self.function.cfunction:
            raise Exception('self.function.update() must be called')
Paul Fultz II's avatar
Paul Fultz II committed
566
567
568
569
570
571
572
573
574
        return cpp_class_constructor_template.safe_substitute(
            name=name,
            cfunction=self.function.cfunction.name,
            params=self.get_params(),
            args=self.get_args(),
            success=success_type)


class CPPClass:
575
    def __init__(self, name: str, ctype: str) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
576
577
        self.name = name
        self.ctype = ctype
578
579
        self.constructors: List[CPPMember] = []
        self.methods: List[CPPMember] = []
Paul Fultz II's avatar
Paul Fultz II committed
580
581
        self.prefix = 'p'

582
    def add_method(self, name: str, f: Function) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
583
584
        self.methods.append(CPPMember(name, f, self.prefix, method=True))

585
    def add_constructor(self, name: str, f: Function) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
586
587
        self.constructors.append(CPPMember(name, f, self.prefix, method=True))

588
    def generate_methods(self) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
589
590
        return '\n    '.join([m.generate_method() for m in self.methods])

591
    def generate_constructors(self) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
592
593
594
        return '\n    '.join(
            [m.generate_constructor(self.name) for m in self.constructors])

595
596
    def substitute(self, s: Union[string.Template, str], **kwargs) -> str:
        t = string.Template(s) if isinstance(s, str) else s
Paul Fultz II's avatar
Paul Fultz II committed
597
598
599
600
601
602
        destroy = self.ctype + '_destroy'
        return t.safe_substitute(name=self.name,
                                 ctype=self.ctype,
                                 destroy=destroy,
                                 **kwargs)

603
    def generate(self) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
604
605
606
607
608
609
        return self.substitute(
            cpp_class_template,
            constructors=self.substitute(self.generate_constructors()),
            methods=self.substitute(self.generate_methods()))


610
611
def params(virtual: Optional[Dict[str, str]] = None,
           **kwargs) -> List[Parameter]:
Paul Fultz II's avatar
Paul Fultz II committed
612
    result = []
613
614
615
    v: Dict[str, str] = virtual or {}
    for name in v:
        result.append(Parameter(name, v[name]))
Paul Fultz II's avatar
Paul Fultz II committed
616
617
618
619
620
    for name in kwargs:
        result.append(Parameter(name, kwargs[name]))
    return result


621
622
623
gparams = params


624
def add_function(name: str, *args, **kwargs) -> Function:
Paul Fultz II's avatar
Paul Fultz II committed
625
626
627
628
629
    f = Function(name, *args, **kwargs)
    functions.append(f)
    return f


630
def once(f: Callable) -> Any:
Paul Fultz II's avatar
Paul Fultz II committed
631
632
633
634
635
636
    @wraps(f)
    def decorated(*args, **kwargs):
        if not decorated.has_run:
            decorated.has_run = True
            return f(*args, **kwargs)

637
638
639
    d: Any = decorated
    d.has_run = False
    return d
Paul Fultz II's avatar
Paul Fultz II committed
640
641
642


@once
643
def process_functions() -> None:
Paul Fultz II's avatar
Paul Fultz II committed
644
645
646
647
    for f in functions:
        f.update()


648
def generate_lines(p: List[str]) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
649
650
651
    return '\n'.join(p)


652
def generate_c_header() -> str:
Paul Fultz II's avatar
Paul Fultz II committed
653
    process_functions()
654
655
656
    return generate_lines(
        c_header_preamble +
        [f.get_cfunction().generate_header() for f in functions])
Paul Fultz II's avatar
Paul Fultz II committed
657
658


659
def generate_c_api_body() -> str:
Paul Fultz II's avatar
Paul Fultz II committed
660
    process_functions()
661
662
663
    return generate_lines(
        c_api_body_preamble +
        [f.get_cfunction().generate_body() for f in functions])
Paul Fultz II's avatar
Paul Fultz II committed
664
665


666
def generate_cpp_header() -> str:
Paul Fultz II's avatar
Paul Fultz II committed
667
668
669
670
671
    process_functions()
    return generate_lines(cpp_header_preamble +
                          [c.generate() for c in cpp_classes])


672
def cwrap(name: str) -> Callable:
Paul Fultz II's avatar
Paul Fultz II committed
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
    def with_cwrap(f):
        type_map[name] = f

        @wraps(f)
        def decorated(*args, **kwargs):
            return f(*args, **kwargs)

        return decorated

    return with_cwrap


handle_typedef = Template('''
typedef struct ${ctype} * ${ctype}_t;
typedef const struct ${ctype} * const_${ctype}_t;
''')

handle_definition = Template('''
extern "C" struct ${ctype};
struct ${ctype} {
    template<class... Ts>
    ${ctype}(Ts&&... xs)
695
    : object(std::forward<Ts>(xs)...) // NOLINT(readability-redundant-member-init)
Paul Fultz II's avatar
Paul Fultz II committed
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
    {}
    ${cpptype} object;
};
''')

handle_preamble = '''
template<class T, class U, class Target=std::remove_pointer_t<T>>
Target* object_cast(U* x)
{
    return reinterpret_cast<Target*>(x);
}
template<class T, class U, class Target=std::remove_pointer_t<T>>
const Target* object_cast(const U* x)
{
    return reinterpret_cast<const Target*>(x);
}

template<class T, class... Ts, class Target=std::remove_pointer_t<T>>
Target* allocate(Ts&&... xs)
{
    return new Target(std::forward<Ts>(xs)...); // NOLINT
}

template<class T>
void destroy(T* x)
{
    delete x; // NOLINT
}
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
// TODO: Move to interface preamble
template <class C, class D>
struct manage_generic_ptr
{
    manage_generic_ptr() = default;
    
    manage_generic_ptr(std::nullptr_t)
    {
    }

    manage_generic_ptr(void* pdata, C pcopier, D pdeleter)
        : data(nullptr), copier(pcopier), deleter(pdeleter)
    {
        copier(&data, pdata);
    }

    manage_generic_ptr(const manage_generic_ptr& rhs)
        : data(nullptr), copier(rhs.copier), deleter(rhs.deleter)
    {
        if(copier)
            copier(&data, rhs.data);
    }

    manage_generic_ptr(manage_generic_ptr&& other) noexcept
        : data(other.data), copier(other.copier), deleter(other.deleter)
    {
        other.data    = nullptr;
        other.copier  = nullptr;
        other.deleter = nullptr;
    }

    manage_generic_ptr& operator=(manage_generic_ptr rhs)
    {
        std::swap(data, rhs.data);
        std::swap(copier, rhs.copier);
        std::swap(deleter, rhs.deleter);
        return *this;
    }

    ~manage_generic_ptr()
    {
        if(data != nullptr)
            deleter(data);
    }

    void* data = nullptr;
    C copier   = nullptr;
    D deleter  = nullptr;
};
Paul Fultz II's avatar
Paul Fultz II committed
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
'''

cpp_handle_preamble = '''
template<class T, class Deleter, Deleter deleter>
struct handle_base
{

    template<class F, class... Ts>
    void make_handle(F f, Ts&&... xs)
    {
        T* result = nullptr;
        auto e = F(&result, std::forward<Ts>(xs)...);
        if (e != ${success})
            throw std::runtime_error("Failed to call function");
        set_handle(result);
    }

    template<class F, class... Ts>
    void call_handle(F f, Ts&&... xs)
    {
        auto e = F(this->get_handle_ptr(), std::forward<Ts>(xs)...);
        if (e != ${success})
            throw std::runtime_error("Failed to call function");
    }

    const std::shared_ptr<T>& get_handle() const
    {
        return m_handle;
    }

    T* get_handle_ptr() const
    {
        assert(m_handle != nullptr);
        return get_handle().get();
    }

    void set_handle(T* ptr, bool own = true)
    {
        if (own)
            m_handle = std::shared_ptr<T>{ptr, deleter};
        else
            m_handle = std::shared_ptr<T>{ptr, [](T*) {}};
    }

protected:
    std::shared_ptr<T> m_handle;
};

'''


@once
825
def add_handle_preamble() -> None:
Paul Fultz II's avatar
Paul Fultz II committed
826
827
828
829
830
    c_api_body_preamble.append(handle_preamble)
    cpp_header_preamble.append(
        string.Template(cpp_handle_preamble).substitute(success=success_type))


831
832
833
834
def add_handle(name: str,
               ctype: str,
               cpptype: str,
               destroy: Optional[str] = None,
835
836
               ref=False,
               skip_def=False) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
837
    opaque_type = ctype + '_t'
838
    const_opaque_type = 'const_' + opaque_type
Paul Fultz II's avatar
Paul Fultz II committed
839

840
    def handle_wrap(p: Parameter):
Paul Fultz II's avatar
Paul Fultz II committed
841
842
843
        t = Type(opaque_type)
        if p.type.is_const():
            t = Type('const_' + opaque_type)
844
845
846
847
        # p.read = 'object_cast<${ctype}>(&(${name}))'
        if p.virtual:
            p.add_param(t)
        elif p.returns:
Paul Fultz II's avatar
Paul Fultz II committed
848
849
850
851
            p.add_param(t.add_pointer())
        else:
            p.add_param(t)
            p.bad_param('${name} == nullptr', 'Null pointer')
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
        if p.type.is_reference():
            p.virtual_read = ['object_cast<${ctype}>(&(${name}))']
            p.cpp_write = '${cpptype}(${name}, false)'
            p.write = ['*${name} = object_cast<${ctype}>(&(${result}))']
        elif p.type.is_pointer():
            p.virtual_read = ['object_cast<${ctype}>(${result})']
            p.cpp_write = '${cpptype}(${name}, false)'
            p.write = ['*${name} = object_cast<${ctype}>(${result})']
        else:
            p.virtual_read = ['object_cast<${ctype}>(&(${name}))']
            p.cpp_write = '${cpptype}(${name})'
            p.write = ['*${name} = allocate<${ctype}>(${result})']
        if skip_def:
            p.read = '*${name}'
        else:
Paul Fultz II's avatar
Paul Fultz II committed
867
            p.read = '${name}->object'
868
        p.cpp_read = '${name}.get_handle_ptr()'
Paul Fultz II's avatar
Paul Fultz II committed
869
870

    type_map[cpptype] = handle_wrap
871
872
873
874
    if not ref:
        add_function(destroy or ctype + '_' + 'destroy',
                     params({name: opaque_type}),
                     fname='destroy')
875
876
877
        add_function(ctype + '_' + 'assign_to',
                     params(output=opaque_type, input=const_opaque_type),
                     invoke='*output = *input')
Paul Fultz II's avatar
Paul Fultz II committed
878
879
    add_handle_preamble()
    c_header_preamble.append(handle_typedef.substitute(locals()))
880
881
    if not skip_def:
        c_api_body_preamble.append(handle_definition.substitute(locals()))
Paul Fultz II's avatar
Paul Fultz II committed
882
883
884


@cwrap('std::vector')
885
886
887
888
889
890
def vector_c_wrap(p: Parameter) -> None:
    inner = p.type.inner_type()
    # Not a generic type
    if not inner:
        return
    t = inner.add_pointer()
891
892
893
    if p.type.is_reference():
        if p.type.is_const():
            t = t.add_const()
Paul Fultz II's avatar
Paul Fultz II committed
894
895
896
897
898
899
900
901
902
903
904
905
    if p.returns:
        if p.type.is_reference():
            p.add_param(t.add_pointer())
            p.add_size_param()
            p.bad_param('${name} == nullptr or ${size} == nullptr',
                        'Null pointer')
        else:
            p.add_param(t)
            p.bad_param('${name} == nullptr', 'Null pointer')
    else:
        p.add_param(t)
        p.add_size_param()
906
        p.bad_param('${name} == nullptr and ${size} != 0', 'Null pointer')
907
908
909
910
911
912
913
914
915
916

    p.read = '${type}(${name}, ${name}+${size})'
    p.cpp_write = '${type}(${name}, ${name}+${size})'
    p.virtual_read = ['${name}.data()', '${name}.size()']
    if p.type.is_reference():
        p.write = [
            '*${name} = ${result}.data()', '*${size} = ${result}.size()'
        ]
    else:
        p.write = ['std::copy(${result}.begin(), ${result}.end(), ${name})']
Paul Fultz II's avatar
Paul Fultz II committed
917
918


919
@cwrap('std::string')
920
def string_c_wrap(p: Parameter) -> None:
921
922
923
924
925
926
927
928
929
930
931
932
    t = Type('char*')
    if p.returns:
        if p.type.is_reference():
            p.add_param(t.add_pointer())
            p.bad_param('${name} == nullptr', 'Null pointer')
        else:
            p.add_param(t)
            p.add_param('size_t', p.name + '_size')
            p.bad_param('${name} == nullptr', 'Null pointer')
    else:
        p.add_param(t)
        p.bad_param('${name} == nullptr', 'Null pointer')
933
934
935
936
937
938
939
940
941
942
943

    p.read = '${type}(${name})'
    p.cpp_write = '${type}(${name})'
    p.virtual_read = ['${name}.c_str()']
    if p.type.is_reference():
        p.write = ['*${name} = ${result}.c_str()']
    else:
        p.write = [
            'auto* it = std::copy_n(${result}.begin(), std::min(${result}.size(), ${name}_size - 1), ${name});'
            '*it = \'\\0\''
        ]
944
945


Paul Fultz II's avatar
Paul Fultz II committed
946
class Handle:
947
    def __init__(self, name: str, ctype: str, cpptype: str, **kwargs) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
948
949
950
        self.name = name
        self.ctype = ctype
        self.cpptype = cpptype
951
        self.opaque_type = self.ctype + '_t'
Paul Fultz II's avatar
Paul Fultz II committed
952
        self.cpp_class = CPPClass(name, ctype)
953
        add_handle(name, ctype, cpptype, **kwargs)
Paul Fultz II's avatar
Paul Fultz II committed
954
955
        cpp_type_map[cpptype] = name

956
    def cname(self, name: str) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
957
958
        return self.ctype + '_' + name

959
    def substitute(self, s: str, **kwargs) -> str:
Paul Fultz II's avatar
Paul Fultz II committed
960
961
962
        return Template(s).safe_substitute(name=self.name,
                                           ctype=self.ctype,
                                           cpptype=self.cpptype,
963
                                           opaque_type=self.opaque_type,
Paul Fultz II's avatar
Paul Fultz II committed
964
965
                                           **kwargs)

966
967
968
969
970
971
    def constructor(self,
                    name: str,
                    params: Optional[List[Parameter]] = None,
                    fname: Optional[str] = None,
                    invoke: Optional[str] = None,
                    **kwargs) -> 'Handle':
Paul Fultz II's avatar
Paul Fultz II committed
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
        create = self.substitute('allocate<${cpptype}>($@)')
        if fname:
            create = self.substitute('allocate<${cpptype}>(${fname}($@))',
                                     fname=fname)

        f = add_function(self.cname(name),
                         params=params,
                         invoke=invoke or create,
                         returns=self.cpptype + '*',
                         return_name=self.name,
                         **kwargs)
        self.cpp_class.add_constructor(name, f)
        return self

    def method(self,
987
988
989
990
991
992
993
               name: str,
               params: Optional[List[Parameter]] = None,
               fname: Optional[str] = None,
               invoke: Optional[str] = None,
               cpp_name: Optional[str] = None,
               const: Optional[bool] = None,
               **kwargs) -> 'Handle':
Paul Fultz II's avatar
Paul Fultz II committed
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
        cpptype = self.cpptype
        if const:
            cpptype = Type(cpptype).add_const().str()
        p = Parameter(self.name, cpptype)
        args = to_template_vars(params or [])
        f = add_function(self.cname(name),
                         params=[p] + (params or []),
                         invoke=invoke
                         or self.substitute('${var}.${fname}(${args})',
                                            var=template_var(self.name),
                                            fname=fname or name,
                                            args=args),
                         **kwargs)
        self.cpp_class.add_method(cpp_name or name, f)
        return self

    def function(self, name, params=None, **kwargs):
        add_function(self.cname(name), params=params, **kwargs)
        return self

1014
    def add_cpp_class(self) -> None:
Paul Fultz II's avatar
Paul Fultz II committed
1015
1016
1017
        cpp_classes.append(self.cpp_class)


1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
interface_handle_definition = Template('''
extern "C" struct ${ctype};
struct ${ctype} {
    template<class... Ts>
    ${ctype}(void* p, ${copier} c, ${deleter} d, Ts&&... xs)
    : object_ptr(p, c, d), xobject(std::forward<Ts>(xs)...)
    {}
    manage_generic_ptr<${copier}, ${deleter}> object_ptr = nullptr;
    ${cpptype} xobject;
    ${functions}
};
''')

c_api_virtual_impl = Template('''
${return_type} ${name}(${params}) const
{
    ${output_decls}
    if (${fname} == nullptr)
        throw std::runtime_error("${name} function is missing.");
    auto api_error_result = ${fname}(${args});
    if (api_error_result != ${success})
        throw std::runtime_error("Error in ${name}.");
    return ${output};
}
''')


def generate_virtual_impl(f: Function, fname: str) -> str:
    success = success_type
    name = f.name
    return_type = 'void'
    output_decls = ''
    output = ''
    largs = []
    lparams = []
    if f.returns:
        return_type = f.returns.type.str()
        output_decls = '\n'.join(f.returns.virtual_output_declarations())
        largs += f.returns.virtual_output_args()
        output = f.returns.virtual_output()
    largs += [arg for p in f.params for arg in p.virtual_arg()]
    lparams += [p.virtual_param() for p in f.params if not p.this]
    args = ', '.join(largs)
    params = ', '.join(lparams)
    return c_api_virtual_impl.substitute(locals())


class Interface(Handle):
    def __init__(self, name: str, ctype: str, cpptype: str) -> None:
        super().__init__(name, ctype, cpptype, skip_def=True)
        self.ifunctions: List[Function] = []
        self.members: List[str] = []

    def mname(self, name: str) -> str:
        return name + "_f"

    def constructor(  # type: ignore
            self,
            name: str,
            params: Optional[List[Parameter]] = None,
            **kwargs) -> 'Interface':
        create = self.substitute('allocate<${opaque_type}>($@)')

        initial_params = gparams(obj='void*',
                                 c=self.cname('copy'),
                                 d=self.cname('delete'))

        add_function(self.cname(name),
                     params=initial_params + (params or []),
                     invoke=create,
                     returns=self.opaque_type,
                     return_name=self.name,
                     **kwargs)
        return self

    def method(self, *args, **kwargs) -> 'Interface':
        super().method(*args, **kwargs)
        return self

    def virtual(self,
                name: str,
                params: Optional[List[Parameter]] = None,
                const: Optional[bool] = None,
                **kwargs) -> 'Interface':

        # Add this parameter to the function
        this = Parameter('obj', 'void*', this=True)
        this.virtual_read = ['object_ptr.data']
        f = Function(name,
                     params=[this] + (params or []),
                     virtual=True,
                     **kwargs)
        self.ifunctions.append(f)

        add_function(self.cname('set_' + name),
                     params=gparams(obj=self.opaque_type,
                                    input=self.cname(name)),
                     invoke='${{obj}}->{name} = ${{input}}'.format(
                         name=self.mname(name)))
        return self

    def generate_function(self, f: Function):
        cname = self.cname(f.name)
        mname = self.mname(f.name)
        function = generate_virtual_impl(f, fname=mname)
        return f"{cname} {mname} = nullptr;{function}"

    def generate(self):
        required_functions = [
            Function('copy',
                     params=gparams(out='void**', input='void*'),
                     virtual=True),
            Function('delete', params=gparams(input='void*'), virtual=True)
        ]
        for f in self.ifunctions + required_functions:
            f.update()
        c_header_preamble.extend([
            f.get_cfunction().generate_function_pointer(self.cname(f.name))
            for f in self.ifunctions + required_functions
        ])
        function_list = [self.generate_function(f) for f in self.ifunctions]
        ctype = self.ctype
        cpptype = self.cpptype
        copier = self.cname('copy')
        deleter = self.cname('delete')
        functions = '\n'.join(function_list)

        c_api_body_preamble.append(
            interface_handle_definition.substitute(locals()))


1149
1150
1151
1152
def handle(ctype: str,
           cpptype: str,
           name: Optional[str] = None,
           ref: Optional[bool] = None) -> Callable:
Paul Fultz II's avatar
Paul Fultz II committed
1153
1154
    def with_handle(f):
        n = name or f.__name__
1155
        h = Handle(n, ctype, cpptype, ref=ref)
Paul Fultz II's avatar
Paul Fultz II committed
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
        f(h)
        h.add_cpp_class()

        @wraps(f)
        def decorated(*args, **kwargs):
            return f(*args, **kwargs)

        return decorated

    return with_handle


1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
def interface(ctype: str, cpptype: str,
              name: Optional[str] = None) -> Callable:
    def with_interface(f):
        n = name or f.__name__
        h = Interface(n, ctype, cpptype)
        f(h)
        h.generate()

        @wraps(f)
        def decorated(*args, **kwargs):
            return f(*args, **kwargs)

        return decorated

    return with_interface


Paul Fultz II's avatar
Paul Fultz II committed
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
def template_eval(template, **kwargs):
    start = '<%'
    end = '%>'
    escaped = (re.escape(start), re.escape(end))
    mark = re.compile('%s(.*?)%s' % escaped, re.DOTALL)
    for key in kwargs:
        exec('%s = %s' % (key, kwargs[key]))
    for item in mark.findall(template):
        e = eval(item.strip())
        template = template.replace(start + item + end, str(e))
    return template


1198
1199
1200
1201
def run(args: List[str]) -> None:
    runpy.run_path(args[0])
    if len(args) > 1:
        f = open(args[1]).read()
Paul Fultz II's avatar
Paul Fultz II committed
1202
1203
1204
1205
1206
        r = template_eval(f)
        sys.stdout.write(r)
    else:
        sys.stdout.write(generate_c_header())
        sys.stdout.write(generate_c_api_body())
1207
        # sys.stdout.write(generate_cpp_header())
Paul Fultz II's avatar
Paul Fultz II committed
1208
1209
1210
1211


if __name__ == "__main__":
    sys.modules['api'] = sys.modules['__main__']
1212
    run(sys.argv[1:])