compile.py 22.1 KB
Newer Older
eckhart's avatar
mend  
eckhart committed
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
# compile.py - Syntax driven compilation support for DHParser
#
# Copyright 2016  by Eckhart Arnold (arnold@badw.de)
#                 Bavarian Academy of Sciences an Humanities (badw.de)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.  See the License for the specific language governing
# permissions and limitations under the License.

"""
Module ``compile`` contains a skeleton class for syntax
driven compilation support. Class ``Compiler`` can serve as base
class for a compiler. Compiler objects
are callable an receive the Abstract syntax tree (AST)
as argument and yield whatever output the compiler produces. In
most Digital Humanities applications this will be
XML-code. However, it can also be anything else, like binary
code or, as in the case of DHParser's EBNF-compiler, Python
source code.

Function ``compile_source`` invokes all stages of the compilation
eckhart's avatar
eckhart committed
30
process, i.e. pre-processing, parsing, CST to AST-transformation
eckhart's avatar
mend  
eckhart committed
31
32
33
34
35
36
and compilation.

See module ``ebnf`` for a sample of the implementation of a
compiler object.
"""

eckhart's avatar
eckhart committed
37
import copy
eckhart's avatar
eckhart committed
38
import functools
di68kap's avatar
di68kap committed
39
import os
40
import traceback
eckhart's avatar
eckhart committed
41
from typing import Any, Optional, Tuple, List, Set, Union, Callable, cast
eckhart's avatar
mend  
eckhart committed
42

eckhart's avatar
eckhart committed
43
from DHParser.configuration import get_config_value
di68kap's avatar
di68kap committed
44
from DHParser.preprocess import PreprocessorFunc
di68kap's avatar
di68kap committed
45
from DHParser.syntaxtree import Node, RootNode, EMPTY_PTYPE, TreeContext
eckhart's avatar
mend  
eckhart committed
46
47
from DHParser.transform import TransformationFunc
from DHParser.parse import Grammar
Eckhart Arnold's avatar
Eckhart Arnold committed
48
from DHParser.preprocess import gen_neutral_srcmap_func
49
50
from DHParser.error import is_error, is_fatal, Error, FATAL, \
    TREE_PROCESSING_CRASH, COMPILER_CRASH, AST_TRANSFORM_CRASH, has_errors
51
from DHParser.log import log_parsing_history, log_ST, is_logging
Eckhart Arnold's avatar
Eckhart Arnold committed
52
from DHParser.toolkit import load_if_file, is_filename
eckhart's avatar
mend  
eckhart committed
53
54


55
56
__all__ = ('CompilerError',
           'Compiler',
57
58
59
           'GrammarCallable',
           'CompilerCallable',
           'ResultTuple',
60
61
           'compile_source',
           'visitor_name',
62
           'attr_visitor_name',
63
64
           'TreeProcessor',
           'process_tree')
eckhart's avatar
eckhart committed
65
66
67


class CompilerError(Exception):
68
69
    """
    Exception raised when an error of the compiler itself is detected.
eckhart's avatar
eckhart committed
70
71
    Compiler errors are not to be confused with errors in the source
    code to be compiled, which do not raise Exceptions but are merely
72
73
    reported as an error.
    """
eckhart's avatar
eckhart committed
74
75
76
    pass


eckhart's avatar
eckhart committed
77
78
def visitor_name(node_name: str) -> str:
    """
79
    Returns the visitor_method name for `node_name`, e.g.::
eckhart's avatar
eckhart committed
80

81
82
    >>> visitor_name('expression')
    'on_expression'
eckhart's avatar
eckhart committed
83
84
85
86
87
    """
    # assert re.match(r'\w+$', node_name)
    return 'on_' + node_name


88
89
90
91
92
93
94
95
96
97
98
99
def attr_visitor_name(attr_name: str) -> str:
    """
    Returns the visitor_method name for `attr_name`, e.g.::

    >>> attr_visitor_name('class')
    'attr_class'
    """
    # assert re.match(r'\w+$', node_name)
    return 'attr_' + attr_name



100
101
102
ROOTNODE_PLACEHOLDER = RootNode()


eckhart's avatar
mend  
eckhart committed
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
class Compiler:
    """
    Class Compiler is the abstract base class for compilers. Compiler
    objects are callable and take the root node of the abstract
    syntax tree (AST) as argument and return the compiled code in a
    format chosen by the compiler itself.

    Subclasses implementing a compiler must define `on_XXX()`-methods
    for each node name that can occur in the AST where 'XXX' is the
    node's name(for unnamed nodes it is the node's ptype without the
    leading colon ':').

    These compiler methods take the node on which they are run as
    argument. Other than in the AST transformation, which runs depth-first,
    compiler methods are called forward moving starting with the root
    node, and they are responsible for compiling the child nodes
    themselves. This should be done by invoking the `compile(node)`-
    method which will pick the right `on_XXX`-method. It is not
    recommended to call the `on_XXX`-methods directly.

123
    :ivar source: The source text of the AST to be compiled. This needs to be
di68kap's avatar
di68kap committed
124
125
                assigned by the user of the Compiler object - as is done
                by function `compile_source()`
126
    :ivar context:  A list of parent nodes that ends with the currently
eckhart's avatar
mend  
eckhart committed
127
                compiled node.
128
129
    :ivar tree: The root of the abstract syntax tree.
    :ivar finalizers:  A stack of tuples (function, parameters) that will be
130
                called in reverse order after compilation.
eckhart's avatar
eckhart committed
131

132
133
134
135
136
137
    :ivar has_attribute_visitors:  A flag indicating that the class has
                attribute-visitor-methods which are named 'attr_ATTRIBUTENAME'
                and will be called if the currently processed node has one
                or more attributes for which such visitors exist.

    :ivar _dirty_flag:  A flag indicating that the compiler has already been
eckhart's avatar
mend  
eckhart committed
138
139
                called at least once and that therefore all compilation
                variables must be reset when it is called again.
140
    :ivar _debug: A flag indicating that debugging is turned on. The value
141
                for this flag is read before each call of the configuration
142
                (see debugging section in DHParser.configuration).
143
                If debugging is turned on the compiler class raises en
144
145
146
                error if there is an attempt to be compile one and the same
                node a second time..
    :ivar _debug_already_compiled: A set of nodes that have already been compiled.
eckhart's avatar
mend  
eckhart committed
147
148
    """

eckhart's avatar
eckhart committed
149
    def __init__(self):
150
151
        self.has_attribute_visitors = any(field[0:5] == 'attr_' and callable(getattr(self, field))
                                          for field in dir(self))
152
        self.reset()
eckhart's avatar
mend  
eckhart committed
153

154
    def reset(self):
di68kap's avatar
di68kap committed
155
        self.source = ''  # type: str
156
        self.tree = ROOTNODE_PLACEHOLDER   # type: RootNode
di68kap's avatar
di68kap committed
157
        self.context = []  # type: TreeContext
158
        self._None_check = True  # type: bool
eckhart's avatar
mend  
eckhart committed
159
        self._dirty_flag = False
eckhart's avatar
eckhart committed
160
161
        self._debug = get_config_value('debug_compiler')  # type: bool
        self._debug_already_compiled = set()              # type: Set[Node]
Eckhart Arnold's avatar
Eckhart Arnold committed
162
        self.finalizers = []  # type: List[Tuple[Callable, Tuple]]
163
164
165
166
167
168
169
170
171
172
173
174
175
176

    def prepare(self) -> None:
        """
        A preparation method that will be called after everything else has
        been initialized and immediately before compilation starts. This method
        can be overwritten in order to implement preparation tasks.
        """
        pass

    def finalize(self) -> None:
        """
        A finalization method that is called after compilation has finished and
        after all tasks from the finalizers stack have been executed
        """
di68kap's avatar
di68kap committed
177
        pass
eckhart's avatar
mend  
eckhart committed
178

179
    def __call__(self, root: RootNode) -> Any:
eckhart's avatar
mend  
eckhart committed
180
181
182
183
184
185
186
187
        """
        Compiles the abstract syntax tree with the root node `node` and
        returns the compiled code. It is up to subclasses implementing
        the compiler to determine the format of the returned data.
        (This very much depends on the kind and purpose of the
        implemented compiler.)
        """
        if self._dirty_flag:
188
            self.reset()
eckhart's avatar
mend  
eckhart committed
189
        self._dirty_flag = True
eckhart's avatar
eckhart committed
190
        self.tree = root
191
        # self.source = source  # type: str
192
        self.prepare()
193
        result = self.compile(root)
194
195
196
        while self.finalizers:
            task, parameters = self.finalizers.pop()
            task(*parameters)
di68kap's avatar
di68kap committed
197
        self.finalize()
eckhart's avatar
mend  
eckhart committed
198
199
        return result

200
    def fallback_compiler(self, node: Node, block_attribute_visitors: bool=False) -> Any:
eckhart's avatar
mend  
eckhart committed
201
202
203
        """This is a generic compiler function which will be called on
        all those node types for which no compiler method `on_XXX` has
        been defined."""
204
        replacements = {}  # type: Dict[Node, Node]
205
206
207
        if node.children:
            for child in node.children:
                nd = self.compile(child)
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
                if id(nd) != id(child):
                    replacements[id(child)] = nd
                if nd is not None and not isinstance(nd, Node):
                    tn = node.tag_name
                    raise TypeError(
                        'Fallback compiler for Node `%s` received a value of type '
                        '`%s` from child `%s` instead of the required return type `Node`. '
                        'Override `DHParser.compile.Compiler.fallback_compiler()` or add '
                        'method `on_%s(self, node)` in class `%s` to avoid this error!'
                        % (tn, str(type(nd)), child.tag_name, tn, self.__class__.__name__))
            if replacements:
                # replace Nodes the identity of which has been changed during transformation
                # and drop any returned None-results
                result = []
                for child in node.children:
                    nd = replacements.get(id(child), child)
                    if nd is not None and nd.tag_name != EMPTY_PTYPE:
                        result.append(nd)
                node.result = tuple(result)
227
        if self.has_attribute_visitors and not block_attribute_visitors and node.has_attr():
di68kap's avatar
di68kap committed
228
            for attribute, value in node.attr.items():
229
230
231
232
233
                try:
                    attribute_visitor = self.__getattribute__(attr_visitor_name(attribute))
                    node = attribute_visitor(node, value) or node
                except AttributeError:
                    pass
eckhart's avatar
mend  
eckhart committed
234
235
236
237
238
239
240
241
        return node

    def compile(self, node: Node) -> Any:
        """
        Calls the compilation method for the given node and returns the
        result of the compilation.

        The method's name is derived from either the node's parser
242
        name or, if the parser is disposable, the node's parser's class
eckhart's avatar
mend  
eckhart committed
243
244
245
246
247
248
        name by adding the prefix ``on_``.

        Note that ``compile`` does not call any compilation functions
        for the parsers of the sub nodes by itself. Rather, this should
        be done within the compilation methods.
        """
249
250
251
252
        if self._debug:
            assert node not in self._debug_already_compiled
            self._debug_already_compiled.add(node)

253
        elem = node.tag_name
254
        if elem[:1] == ':':
255
            elem = elem[1:] + '__'
eckhart's avatar
eckhart committed
256
257
        try:
            compiler = self.__getattribute__(visitor_name(elem))
di68kap's avatar
di68kap committed
258
            # print(self.__class__.__name__, elem, str(node)[:80])
eckhart's avatar
eckhart committed
259
260
261
262
263
        except AttributeError:
            compiler = self.fallback_compiler
        self.context.append(node)
        result = compiler(node)
        self.context.pop()
264
        if result is None and self._None_check:
265
266
267
268
269
270
271
            raise CompilerError(
                ('Method on_%s returned `None` instead of a valid compilation '
                 'result! It is recommended to use `syntaxtree.EMPTY_NODE as a '
                 'void value. This Error can be turn off by adding '
                 '`self._None_check = False` to the reset()-Method of your'
                 'compiler class, in case on_%s actually SHOULD be allowed to '
                 'return None.') % (elem, elem))
eckhart's avatar
eckhart committed
272
        return result
eckhart's avatar
mend  
eckhart committed
273
274


275
276
277
278
279
280
281
282
def logfile_basename(filename_or_text, function_or_class_or_instance) -> str:
    """Generates a reasonable logfile-name (without extension) based on
    the given information.
    """
    if is_filename(filename_or_text):
        return os.path.basename(os.path.splitext(filename_or_text)[0])
    else:
        try:
di68kap's avatar
di68kap committed
283
            name = function_or_class_or_instance.__qualname__
284
285
286
287
288
289
        except AttributeError:
            name = function_or_class_or_instance.__class__.__name__
        i = name.find('.')
        return name[:i] + '_out' if i >= 0 else name


eckhart's avatar
eckhart committed
290
GrammarCallable = Union[Grammar, Callable[[str], RootNode], functools.partial]
eckhart's avatar
eckhart committed
291
CompilerCallable = Union[Compiler, Callable[[Node], Any], functools.partial]
292
ResultTuple = Tuple[Optional[Any], List[Error], Optional[Node]]
eckhart's avatar
eckhart committed
293
294


295
296
297
def filter_stacktrace(stacktrace: List[str]) -> List[str]:
    """Removes those frames from a formatted stacktrace that are located
    within the DHParser-code."""
298
    n = 0
299
300
301
302
303
304
305
306
    for n, frame in enumerate(stacktrace):
        i = frame.find('"')
        k = frame.find('"', i + 1)
        if frame.find("DHParser", i, k) < 0:
            break
    return stacktrace[n:]


eckhart's avatar
mend  
eckhart committed
307
308
def compile_source(source: str,
                   preprocessor: Optional[PreprocessorFunc],  # str -> str
eckhart's avatar
eckhart committed
309
                   parser: GrammarCallable,  # str -> Node (concrete syntax tree (CST))
310
                   transformer: TransformationFunc,  # Node (CST) -> Node (abstract ST (AST))
eckhart's avatar
eckhart committed
311
                   compiler: CompilerCallable,  # Node (AST), Source -> Any
312
                   # out_source_data: list = NOPE,  # Tuple[str, SourceMapFunc]
di68kap's avatar
di68kap committed
313
314
                   *, preserve_AST: bool = False) \
        -> Tuple[Optional[Any], List[Error], Optional[Node]]:
eckhart's avatar
eckhart committed
315
316
    """Compiles a source in four stages:

eckhart's avatar
eckhart committed
317
    1. Pre-Processing (if needed)
eckhart's avatar
mend  
eckhart committed
318
319
320
321
    2. Parsing
    3. AST-transformation
    4. Compiling.

322
323
324
    The later stages AST-transformation, compilation will only be invoked if
    no fatal errors occurred in any of the earlier stages of the processing
    pipeline.
eckhart's avatar
mend  
eckhart committed
325

326
    :param source: The input text for compilation or a the name of a
eckhart's avatar
mend  
eckhart committed
327
            file containing the input text.
328
    :param preprocessor:  text -> text. A preprocessor function
eckhart's avatar
mend  
eckhart committed
329
            or None, if no preprocessor is needed.
330
331
    :param parser:  A parsing function or grammar class
    :param transformer:  A transformation function that takes
eckhart's avatar
mend  
eckhart committed
332
333
            the root-node of the concrete syntax tree as an argument and
            transforms it (in place) into an abstract syntax tree.
334
    :param compiler: A compiler function or compiler class
eckhart's avatar
mend  
eckhart committed
335
            instance
336
    :param preserve_AST: Preserves the AST-tree.
eckhart's avatar
mend  
eckhart committed
337

338
    :returns: The result of the compilation as a 3-tuple
eckhart's avatar
mend  
eckhart committed
339
        (result, errors, abstract syntax tree). In detail:
340

eckhart's avatar
mend  
eckhart committed
341
342
        1. The result as returned by the compiler or ``None`` in case of failure
        2. A list of error or warning messages
eckhart's avatar
eckhart committed
343
        3. The root-node of the abstract syntax tree if `preserve_ast` is True
di68kap's avatar
di68kap committed
344
           or `None` otherwise.
eckhart's avatar
mend  
eckhart committed
345
    """
eckhart's avatar
eckhart committed
346
347
    ast = None  # type: Optional[Node]
    original_text = load_if_file(source)  # type: str
348
    source_name = source if is_filename(source) else 'source'
di68kap's avatar
di68kap committed
349
    compiler.source = original_text
350
    log_file_name = logfile_basename(source, compiler) if is_logging() else ''  # type: str
351
352
353
354
355
    if not hasattr(parser, 'free_char_parsefunc__') or parser.history_tracking__:
        # log only for custom parser/transformer/compilers
        log_syntax_trees = get_config_value('log_syntax_trees')
    else:
        log_syntax_trees = set()
356
357
358

    # preprocessing

359
    errors = []
eckhart's avatar
mend  
eckhart committed
360
    if preprocessor is None:
eckhart's avatar
eckhart committed
361
        source_text = original_text  # type: str
Eckhart Arnold's avatar
Eckhart Arnold committed
362
363
        source_mapping = gen_neutral_srcmap_func(source_text, source_name)
            # lambda i: SourceLocation(source_name, 0, i)    # type: SourceMapFunc
eckhart's avatar
mend  
eckhart committed
364
    else:
365
366
        _, source_text, source_mapping, errors = preprocessor(original_text, source_name)

367
368
    if has_errors(errors, FATAL):
        return None, errors, None
369
370
371

    # parsing

372
    syntax_tree = parser(source_text, source_mapping=source_mapping)  # type: RootNode
373
    for e in errors:  syntax_tree.add_error(None, e)
374
375
    syntax_tree.source = original_text
    syntax_tree.source_mapping = source_mapping
eckhart's avatar
eckhart committed
376
    if 'cst' in log_syntax_trees:
eckhart's avatar
mend  
eckhart committed
377
        log_ST(syntax_tree, log_file_name + '.cst')
378
    if parser.history_tracking__:
eckhart's avatar
mend  
eckhart committed
379
380
        log_parsing_history(parser, log_file_name)

Eckhart Arnold's avatar
Eckhart Arnold committed
381
382
383
    # assert is_error(syntax_tree.error_flag) or str(syntax_tree) == strip_tokens(source_text), \
    #     str(syntax_tree) # Ony valid if neither tokens or whitespace are dropped early

eckhart's avatar
mend  
eckhart committed
384
    result = None
385
386
387
388
389
    if not is_fatal(syntax_tree.error_flag):

        # AST-transformation

        if is_error(syntax_tree.error_flag):
390
            # catch Python exception, because if an error has occurred
391
392
393
394
            # earlier, the syntax tree might not look like expected,
            # which could (fatally) break AST transformations.
            try:
                transformer(syntax_tree)
395
            except Exception as e:
396
397
                syntax_tree.new_error(syntax_tree,
                                      "AST-Transformation failed due to earlier parser errors. "
398
                                      "Crash Message: %s: %s" % (e.__class__.__name__, str(e)),
399
                                      AST_TRANSFORM_CRASH)
400
401
402
        else:
            transformer(syntax_tree)

eckhart's avatar
eckhart committed
403
        if 'ast' in log_syntax_trees:
eckhart's avatar
mend  
eckhart committed
404
            log_ST(syntax_tree, log_file_name + '.ast')
405
406

        if not is_fatal(syntax_tree.error_flag):
407
            if preserve_AST:
eckhart's avatar
eckhart committed
408
                ast = copy.deepcopy(syntax_tree)
409
410
411
412
413
414
415

            # Compilation

            if is_error(syntax_tree.error_flag):
                # assume Python crashes are merely a consequence of earlier
                # errors, so let's catch them
                try:
416
                    result = compiler(syntax_tree)
417
                except Exception as e:
418
                    # raise e
eckhart's avatar
eckhart committed
419
420
421
                    node = syntax_tree  # type: Node
                    if isinstance(compiler, Compiler) and compiler.context:
                        node = compiler.context[-1]
422
423
                    st = traceback.format_list(traceback.extract_tb(e.__traceback__))
                    trace = ''.join(filter_stacktrace(st))
424
425
                    syntax_tree.new_error(
                        node, "Compilation failed, most likely, due to errors earlier "
426
427
                              "in the processing pipeline. Crash Message: %s: %s\n%s"
                              % (e.__class__.__name__, str(e), trace),
428
                        COMPILER_CRASH)
429
430
431
            else:
                # assume Python crashes are programming mistakes, so let
                # the exceptions through
432
                result = compiler(syntax_tree)
eckhart's avatar
mend  
eckhart committed
433

eckhart's avatar
eckhart committed
434
    messages = syntax_tree.errors_sorted  # type: List[Error]
435
436
    # Obsolete, because RootNode adjusts error locations whenever an error is added:
    # adjust_error_locations(messages, original_text, source_mapping)
437
    return result, messages, ast
di68kap's avatar
di68kap committed
438
439


440
class TreeProcessor(Compiler):
441
    """A special kind of Compiler class that takes a tree as input (just like
442
443
444
445
446
447
448
449
450
451
452
453
454
455
    `Compiler`) but always yields a tree as result.

    The intended use case for TreeProcessor are digital-humanities-applications
    where domain specific languages often describe data structures that, again,
    most of the times are tree structures that can be serialized as XML or HTML.
    Typically, these tree structures pass through several processing stages in
    sequence that - as long as no fatal errors occur on the way - end with
    HTML-preview or a preprint-XML.

    The tree-processors can most suitably be invoked with the `process-tree()`-
    functions which makes sure that a tree-processor is only invoked if no
    fatal errors have occurred in any of the earlier stages.
    """
    def __call__(self, root: RootNode) -> RootNode:
di68kap's avatar
di68kap committed
456
        assert isinstance(root, RootNode)
457
        result = super().__call__(root)
458
        assert isinstance(result, RootNode), str(result)
459
460
461
        return cast(RootNode, result)


462
def process_tree(tp: TreeProcessor, tree: RootNode) -> Tuple[RootNode, List[Error]]:
463
    """Process a tree with the tree-processor `tp` only if no fatal error
464
    has occurred so far. Catch any Python-exceptions in case
465
    any normal errors have occurred earlier in the processing pipeline.
466
    Don't catch Python-exceptions if no errors have occurred earlier.
467
468
469
470
471
472
473
474

    This behaviour is based on the assumption that given any non-fatal
    errors have occurred earlier, the tree passed through the pipeline
    might not be in a state that is expected by the later stages, thus if
    an exception occurs it is not really to be considered a programming
    error. Processing stages should be written with possible errors
    occurring in earlier stages in mind, though. However, because it could
    be difficult to provide for all possible kinds of badly structured
475
    trees resulting from errors, exceptions occurring when processing
476
    potentially faulty trees will be dealt with gracefully.
477
478
479

    Although process_tree returns the root-node of the processed tree,
    tree processing should generally be assumed to change the tree
480
    in place. If the input tree shall be preserved, it is necessary to
481
    make a deep copy of the input tree, before calling process_tree.
482
483
484
485
486
487
488
489
490
491
    """
    assert isinstance(tp, TreeProcessor)
    if not is_fatal(tree.error_flag):
        if is_error(tree.error_flag):
            # assume Python crashes are merely a consequence of earlier
            # errors, so let's catch them
            try:
                tree = tp(tree)
            except Exception as e:
                node = tp.context[-1] if tp.context else tree
492
493
                st = traceback.format_list(traceback.extract_tb(e.__traceback__))
                trace = ''.join(filter_stacktrace(st))
494
495
                tree.new_error(
                    node, "Tree-processing failed, most likely, due to errors earlier in "
496
497
                          "in the processing pipeline. Crash Message: %s: %s\n%s"
                          % (e.__class__.__name__, str(e), trace),
498
                    TREE_PROCESSING_CRASH)
499
500
501
502
503
        else:
            # assume Python crashes are programming mistakes, so let
            # the exceptions through
            tree = tp(tree)
        assert isinstance(tree, RootNode)
di68kap's avatar
di68kap committed
504
505
506

    messages = tree.errors_sorted  # type: List[Error]
    new_msgs = [msg for msg in messages if msg.line < 0]
507
508
    # Obsolete, because RootNode adjusts error locations whenever an error is added:
    # adjust_error_locations(new_msgs, tree.source, tree.source_mapping)
di68kap's avatar
di68kap committed
509
    return tree, messages
510
511


512
513
514
515
516
517
518
519
520
521
522
523
# def compiler_factory(compiler_class: Compiler) -> CompilerCallable
#
#     def get_compiler() -> CompilerCallable:
#         """Returns a thread/process-exclusive Compiler-singleton."""
#         THREAD_LOCALS = access_thread_locals()
#         try:
#             compiler = THREAD_LOCALS.{NAME}_{ID:08d}_compiler_singleton
#         except AttributeError:
#             THREAD_LOCALS.{NAME}_{ID:08d}_compiler_singleton = {NAME}Compiler()
#             compiler = THREAD_LOCALS.{NAME}_{ID:08d}_compiler_singleton
#         return compiler

eckhart's avatar
eckhart committed
524
525
# TODO: Verify compiler against grammar,
#       i.e. make sure that for all on_X()-methods, `X` is the name of a parser
526
# TODO: AST validation against an ASDL-Specification