1#!/usr/bin/env python3
2#
3# © 2021 Qualcomm Innovation Center, Inc. All rights reserved.
4#
5# 2019 Cog Systems Pty Ltd.
6#
7# SPDX-License-Identifier: BSD-3-Clause
8
9import argparse
10import os
11import sys
12import logging
13import subprocess
14import inspect
15import pickle
16
17
18if __name__ == '__main__' and __package__ is None:
19    sys.path.append(os.path.dirname(os.path.dirname(__file__)))
20    from utils import genfile
21else:
22    from ..utils import genfile
23
24
25logger = logging.getLogger(__name__)
26
27
28def main():
29    logging.basicConfig(
30        level=logging.INFO,
31        format="%(message)s",
32    )
33    __loc__ = os.path.relpath(os.path.realpath(
34        os.path.dirname(os.path.join(os.getcwd(), os.path.dirname(__file__)))))
35
36    args = argparse.ArgumentParser()
37
38    mode_args = args.add_mutually_exclusive_group(required=True)
39    mode_args.add_argument('-t', '--template',
40                           type=argparse.FileType('r', encoding='utf-8'),
41                           help="Template file used to generate output")
42    mode_args.add_argument('--dump-tree', action='store_true',
43                           help="Print the parse tree and exit")
44    mode_args.add_argument('-P', '--dump-pickle',
45                           type=genfile.GenFileType('wb'),
46                           help="Dump the IR to a Python pickle")
47
48    args.add_argument('-m', '--module', default=None,
49                      help="Constrain output to a particular module")
50    args.add_argument('-I', '--extra-include', action='append', default=[],
51                      help="Extra headers to include")
52    args.add_argument('-d', "--deps", type=genfile.GenFileType('w'),
53                      help="Write implicit dependencies to Makefile",
54                      default=None)
55    args.add_argument('-o', '--output', type=genfile.GenFileType('w'),
56                      default=sys.stdout, help="Write output to file")
57    args.add_argument("-f", "--formatter",
58                      help="specify clang-format to format the code")
59    args.add_argument('-p', '--load-pickle', type=argparse.FileType('rb'),
60                      help="Load the IR from a Python pickle")
61    args.add_argument('input', metavar='INPUT', nargs='*',
62                      type=argparse.FileType('r', encoding='utf-8'),
63                      help="Event DSL files to process")
64    options = args.parse_args()
65
66    if options.input and options.load_pickle:
67        logger.error("Cannot specify both inputs and --load-pickle")
68        args.print_usage()
69        sys.exit(1)
70    elif options.input:
71        from lark import Lark, Visitor
72        from parser import TransformToIR
73
74        grammar_file = os.path.join(__loc__, 'grammars', 'events_dsl.lark')
75        parser = Lark.open(grammar_file, parser='lalr', start='start',
76                           propagate_positions=True)
77
78        modules = {}
79        events = {}
80        transformer = TransformToIR(modules, events)
81
82        for f in options.input:
83            tree = parser.parse(f.read())
84
85            class FilenameVisitor(Visitor):
86                def __init__(self, filename):
87                    self.filename = filename
88
89                def __default__(self, tree):
90                    tree.meta.filename = self.filename
91
92            FilenameVisitor(f.name).visit(tree)
93            if options.dump_tree:
94                print(tree.pretty(), file=options.output)
95            transformer.transform(tree)
96
97        if options.dump_tree:
98            return 0
99
100        errors = transformer.errors
101        for m in modules.values():
102            errors += m.resolve(events)
103
104        for m in modules.values():
105            errors += m.finalise()
106
107        if errors:
108            logger.error("Found %d errors, exiting...", errors)
109            sys.exit(1)
110    elif options.load_pickle:
111        modules = pickle.load(options.load_pickle)
112    else:
113        logger.error("Must specify inputs or --load-pickle")
114        args.print_usage()
115        sys.exit(1)
116
117    if options.dump_pickle:
118        pickle.dump(modules, options.dump_pickle, protocol=-1)
119    else:
120        from Cheetah.Template import Template
121
122        try:
123            module = modules[options.module]
124        except KeyError:
125            logger.error("Specified module '%s' is unknown", options.module)
126            sys.exit(1)
127
128        ns = [module, {'extra_includes': options.extra_include}]
129        template = Template(file=options.template, searchList=ns)
130
131        result = str(template)
132        if options.formatter:
133            ret = subprocess.run([options.formatter],
134                                 input=result.encode("utf-8"),
135                                 stdout=subprocess.PIPE)
136            result = ret.stdout.decode("utf-8")
137            if ret.returncode != 0:
138                logger.error("Error formatting output", result)
139                sys.exit(1)
140
141        options.output.write(result)
142
143    if options.deps is not None:
144        deps = set()
145        if options.input:
146            deps.add(grammar_file)
147        for m in sys.modules.values():
148            try:
149                f = inspect.getsourcefile(m)
150            except TypeError:
151                continue
152            if f is None:
153                continue
154            f = os.path.relpath(f)
155            if f.startswith('../'):
156                continue
157            deps.add(f)
158        if options.dump_pickle:
159            out_name = options.dump_pickle.name
160        else:
161            out_name = options.output.name
162        options.deps.write(out_name + ' : ')
163        options.deps.write(' '.join(sorted(deps)))
164        options.deps.write('\n')
165        options.deps.close()
166
167
168if __name__ == '__main__':
169    main()
170