File: compile.py

package info (click to toggle)
gringo 5.8.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 32,128 kB
  • sloc: cpp: 210,867; ansic: 37,507; python: 11,271; yacc: 825; javascript: 627; sh: 368; xml: 364; makefile: 102
file content (376 lines) | stat: -rwxr-xr-x 14,261 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
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
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
#!/usr/bin/env python
"""
Helper to generate constructor function for clingo's python API.

Attention: Because this code exists just for code generation and won't run in
production, the functions in this module have been written with the least
amount of effort possible. Always check the generated code!
"""

import argparse
import os.path
import re
import sys
from itertools import chain

from cffi import FFI

try:
    from _clingo import ffi as _ffi
    from _clingo import lib as _lib

    imported = True

    _an = _lib.g_clingo_ast_attribute_names
    _cs = _lib.g_clingo_ast_constructors
except ImportError:
    imported = False


def to_camel_case(s):
    components = s.split("_")
    return "".join(x.title() for x in components)


def argument_type_str(idx):
    if idx == _lib.clingo_ast_attribute_type_number:
        return "int"
    if idx == _lib.clingo_ast_attribute_type_string:
        return "str"
    if idx == _lib.clingo_ast_attribute_type_symbol:
        return "Symbol"
    if idx == _lib.clingo_ast_attribute_type_location:
        return "Location"
    if idx == _lib.clingo_ast_attribute_type_ast:
        return "AST"
    if idx == _lib.clingo_ast_attribute_type_optional_ast:
        return "Optional[AST]"
    if idx == _lib.clingo_ast_attribute_type_ast_array:
        return "Sequence[AST]"
    assert idx == _lib.clingo_ast_attribute_type_string_array
    return "Sequence[str]"


def generate_arguments(constructor):
    args = []
    for i in range(constructor.size):
        argument = constructor.arguments[i]
        argument_type = argument_type_str(argument.type)
        name = _ffi.string(_an.names[argument.attribute]).decode()
        args.append((name, argument_type))
    return args


def generate_parameter(name, idx):
    if idx == _lib.clingo_ast_attribute_type_number:
        return [f"_ffi.cast('int', {name})"]
    if idx == _lib.clingo_ast_attribute_type_string:
        return [f"_ffi.new('char const[]', {name}.encode())"]
    if idx == _lib.clingo_ast_attribute_type_symbol:
        return [f"_ffi.cast('clingo_symbol_t', {name}._rep)"]
    if idx == _lib.clingo_ast_attribute_type_location:
        return [f"c_{name}[0]"]
    if idx == _lib.clingo_ast_attribute_type_ast:
        return [f"{name}._rep"]
    if idx == _lib.clingo_ast_attribute_type_optional_ast:
        return [f"_ffi.NULL if {name} is None else {name}._rep"]
    if idx == _lib.clingo_ast_attribute_type_ast_array:
        return [
            f"_ffi.new('clingo_ast_t*[]', [ x._rep for x in {name} ])",
            f"_ffi.cast('size_t', len({name}))",
        ]
    assert idx == _lib.clingo_ast_attribute_type_string_array
    return [f"_ffi.new('char*[]', c_{name})", f"_ffi.cast('size_t', len({name}))"]


def generate_aux(name, idx):
    if idx == _lib.clingo_ast_attribute_type_string_array:
        return [f"c_{name} = [ _ffi.new('char[]', x.encode()) for x in {name} ]"]
    if idx == _lib.clingo_ast_attribute_type_location:
        return [f"c_{name} = _c_location({name})"]
    return []


def generate_parameters(constructor):
    args, aux = [], []
    for i in range(constructor.size):
        argument = constructor.arguments[i]
        name = _ffi.string(_an.names[argument.attribute]).decode()
        args.extend(generate_parameter(name, argument.type))
        aux.extend(generate_aux(name, argument.type))
    return args, aux


def generate_sig(name, arguments):
    comma = False
    ret = f"def {name}("
    m = len(ret)
    n = m
    for a, t in arguments:
        if comma:
            ret += ", "
            n += 2
        else:
            comma = True
        arg = f"{a}: {t}"
        if n + len(arg) <= 111:
            n += len(arg)
        else:
            ret = ret[:-1] + "\n" + m * " "
            n = m + len(arg)
        ret += arg
    ret += ")"
    arg = " -> AST:"
    assert n + len(arg) <= 120
    ret += arg
    return ret


def generate_python():
    for i in range(_cs.size):
        constructor = _cs.constructors[i]
        c_name = _ffi.string(constructor.name).decode()
        name = to_camel_case(c_name)
        sys.stdout.write(f"\n{generate_sig(name, generate_arguments(constructor))}\n")
        sys.stdout.write("    '''\n")
        sys.stdout.write(f"    Construct an AST node of type `ASTType.{name}`.\n")
        sys.stdout.write("    '''\n")
        parameters, aux = generate_parameters(constructor)
        sys.stdout.write(f"    p_ast = _ffi.new('clingo_ast_t**')\n")
        for x in aux:
            sys.stdout.write(f"    {x}\n")
        sys.stdout.write(f"    _handle_error(_lib.clingo_ast_build(\n")
        sys.stdout.write(f"        _lib.clingo_ast_type_{c_name}, p_ast")
        for param in parameters:
            sys.stdout.write(f",\n        {param}")
        sys.stdout.write(f"))\n")

        sys.stdout.write(f"    return AST(p_ast[0])\n")


def generate_c(action):
    clingo_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))

    ffi = FFI()

    cnt = []
    with open(f"{clingo_dir}/libclingo/clingo.h") as f:
        for line in f:
            if not re.match(r' *(#|//|extern *"C" *{|}$|$)', line):
                cnt.append(line.replace("CLINGO_VISIBILITY_DEFAULT ", "").strip())

    # callbacks
    cnt.append(
        'extern "Python" bool pyclingo_solve_event_callback(clingo_solve_event_type_t type, void *event, void *data, bool *goon);'
    )
    cnt.append(
        'extern "Python" void pyclingo_logger_callback(clingo_warning_t code, char const *message, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_ground_callback(clingo_location_t const *location, char const *name, clingo_symbol_t const *arguments, size_t arguments_size, void *data, clingo_symbol_callback_t symbol_callback, void *symbol_callback_data);'
    )
    # propagator callbacks
    cnt.append(
        'extern "Python" bool pyclingo_propagator_init(clingo_propagate_init_t *init, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_propagator_propagate(clingo_propagate_control_t *control, clingo_literal_t const *changes, size_t size, void *data);'
    )
    cnt.append(
        'extern "Python" void pyclingo_propagator_undo(clingo_propagate_control_t const *control, clingo_literal_t const *changes, size_t size, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_propagator_check(clingo_propagate_control_t *control, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_propagator_decide(clingo_id_t thread_id, clingo_assignment_t const *assignment, clingo_literal_t fallback, void *data, clingo_literal_t *decision);'
    )
    # observer callbacks
    cnt.append(
        'extern "Python" bool pyclingo_observer_init_program(bool incremental, void *data);'
    )
    cnt.append('extern "Python" bool pyclingo_observer_begin_step(void *data);')
    cnt.append('extern "Python" bool pyclingo_observer_end_step(void *data);')
    cnt.append(
        'extern "Python" bool pyclingo_observer_rule(bool choice, clingo_atom_t const *head, size_t head_size, clingo_literal_t const *body, size_t body_size, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_observer_weight_rule(bool choice, clingo_atom_t const *head, size_t head_size, clingo_weight_t lower_bound, clingo_weighted_literal_t const *body, size_t body_size, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_observer_minimize(clingo_weight_t priority, clingo_weighted_literal_t const* literals, size_t size, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_observer_project(clingo_atom_t const *atoms, size_t size, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_observer_output_atom(clingo_symbol_t symbol, clingo_atom_t atom, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_observer_output_term(clingo_symbol_t symbol, clingo_literal_t const *condition, size_t size, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_observer_external(clingo_atom_t atom, clingo_external_type_t type, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_observer_assume(clingo_literal_t const *literals, size_t size, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_observer_heuristic(clingo_atom_t atom, clingo_heuristic_type_t type, int bias, unsigned priority, clingo_literal_t const *condition, size_t size, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_observer_acyc_edge(int node_u, int node_v, clingo_literal_t const *condition, size_t size, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_observer_theory_term_number(clingo_id_t term_id, int number, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_observer_theory_term_string(clingo_id_t term_id, char const *name, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_observer_theory_term_compound(clingo_id_t term_id, int name_id_or_type, clingo_id_t const *arguments, size_t size, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_observer_theory_element(clingo_id_t element_id, clingo_id_t const *terms, size_t terms_size, clingo_literal_t const *condition, size_t condition_size, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_observer_theory_atom(clingo_id_t atom_id_or_zero, clingo_id_t term_id, clingo_id_t const *elements, size_t size, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_observer_theory_atom_with_guard(clingo_id_t atom_id_or_zero, clingo_id_t term_id, clingo_id_t const *elements, size_t size, clingo_id_t operator_id, clingo_id_t right_hand_side_id, void *data);'
    )
    # application callbacks
    cnt.append(
        'extern "Python" char const *pyclingo_application_program_name(void *data);'
    )
    cnt.append('extern "Python" char const *pyclingo_application_version(void *data);')
    cnt.append(
        'extern "Python" unsigned pyclingo_application_message_limit(void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_application_main(clingo_control_t *control, char const *const * files, size_t size, void *data);'
    )
    cnt.append(
        'extern "Python" void pyclingo_application_logger(clingo_warning_t code, char const *message, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_application_print_model(clingo_model_t const *model, clingo_default_model_printer_t printer, void *printer_data, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_application_register_options(clingo_options_t *options, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_application_validate_options(void *data);'
    )
    # application options callbacks
    cnt.append(
        'extern "Python" bool pyclingo_application_options_parse(char const *value, void *data);'
    )
    # ast callbacks
    cnt.append(
        'extern "Python" bool pyclingo_ast_callback(clingo_ast_t const *, void *);'
    )
    # script callbacks
    cnt.append(
        'extern "Python" bool pyclingo_script_execute(clingo_location_t *loc, char const *code, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_script_call(clingo_location_t *loc, char const *name, void *arguments, size_t size, void *symbol_callback, void *symbol_callback_data, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_script_callable(char const * name, bool *ret, void *data);'
    )
    cnt.append(
        'extern "Python" bool pyclingo_script_main(clingo_control_t *ctl, void *data);'
    )

    code = ""

    if action == "embed":
        ffi.embedding_api(
            """\
bool pyclingo_execute(void *loc, char const *code, void *data);
bool pyclingo_call(void *loc, char const *name, void *arguments, size_t size, void *symbol_callback, void *symbol_callback_data, void *data);
bool pyclingo_callable(char const * name, bool *ret, void *data);
bool pyclingo_main(void *ctl, void *data);
"""
        )

        ffi.embedding_init_code(
            f"""\
import os
import sys
import clingo.script

sys.path.insert(0, os.getcwd())
"""
        )

        code = """\
#ifdef CFFI_DLLEXPORT
#undef CFFI_DLLEXPORT
#define CFFI_DLLEXPORT
#endif
#ifdef PYPY_VERSION
void pyclingo_finalize() { }
#else
void pyclingo_finalize() {
    if (Py_IsInitialized()) {
        PyGILState_Ensure();
        Py_Finalize();
    }
}
#endif
"""
    else:
        cnt.append(
            'extern "Python" bool pyclingo_execute(void *loc, char const *code, void *data);'
        )
        cnt.append(
            'extern "Python" bool pyclingo_call(void *loc, char const *name, void *arguments, size_t size, void *symbol_callback, void *symbol_callback_data, void *data);'
        )
        cnt.append(
            'extern "Python" bool pyclingo_callable(char const * name, bool *ret, void *data);'
        )
        cnt.append('extern "Python" bool pyclingo_main(void *ctl, void *data);')

    if action != "header":
        ffi.set_source(
            "_clingo",
            f"""\
#include <clingo.h>
{code}
""",
        )
        ffi.cdef("\n".join(cnt))
        ffi.emit_c_code("_clingo.c")
    else:
        with open("_clingo.cdef", "w") as f:
            f.write("".join(f"{line}\n" for line in cnt))


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="generate code for clingo python binding"
    )
    subparsers = parser.add_subparsers(
        dest="command", required=True, help="type of code to generate"
    )

    parser_c = subparsers.add_parser("c", help="Generate C code.")
    parser_c.add_argument(
        "-e", "--embed", action="store_true", help="add support for embedding"
    )

    parser_h = subparsers.add_parser("h", help="Generate C header for cffi.")

    if imported:
        parser_py = subparsers.add_parser("python", help="generate Python code")

    args = parser.parse_args()
    if args.command == "h":
        generate_c("header")
    if args.command == "c":
        generate_c("embed" if args.embed else "source")
    if args.command == "python":
        generate_python()