xref: /aosp_15_r20/external/emboss/compiler/front_end/emboss_front_end.py (revision 99e0aae7469b87d12f0ad23e61142c2d74c1ef70)
1# Copyright 2019 Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Emboss front end.
16
17This is an internal tool, normally called by the "embossc" driver, rather than
18directly by a user.  It parses a .emb file and its dependencies, and prints an
19intermediate representation of the parse trees and symbol tables to stdout, or
20prints various bits of debug info, depending on which flags are passed.
21"""
22
23from __future__ import print_function
24
25import argparse
26import os
27from os import path
28import sys
29
30from compiler.front_end import glue
31from compiler.front_end import module_ir
32from compiler.util import error
33from compiler.util import ir_data_utils
34
35
36def _parse_command_line(argv):
37  """Parses the given command-line arguments."""
38  parser = argparse.ArgumentParser(description="Emboss compiler front end.",
39                                   prog=argv[0])
40  parser.add_argument("input_file",
41                      type=str,
42                      nargs=1,
43                      help=".emb file to compile.")
44  parser.add_argument("--debug-show-tokenization",
45                      action="store_true",
46                      help="Show the tokenization of the main input file.")
47  parser.add_argument("--debug-show-parse-tree",
48                      action="store_true",
49                      help="Show the parse tree of the main input file.")
50  parser.add_argument("--debug-show-module-ir",
51                      action="store_true",
52                      help="Show the module-level IR of the main input file "
53                           "before symbol resolution.")
54  parser.add_argument("--debug-show-full-ir",
55                      action="store_true",
56                      help="Show the final IR of the main input file.")
57  parser.add_argument("--debug-show-used-productions",
58                      action="store_true",
59                      help="Show all of the grammar productions used in "
60                           "parsing the main input file.")
61  parser.add_argument("--debug-show-unused-productions",
62                      action="store_true",
63                      help="Show all of the grammar productions not used in "
64                           "parsing the main input file.")
65  parser.add_argument("--output-ir-to-stdout",
66                      action="store_true",
67                      help="Dump serialized IR to stdout.")
68  parser.add_argument("--output-file",
69                      type=str,
70                      help="Write serialized IR to file.")
71  parser.add_argument("--no-debug-show-header-lines",
72                      dest="debug_show_header_lines",
73                      action="store_false",
74                      help="Print header lines before output if true.")
75  parser.add_argument("--color-output",
76                      default="if_tty",
77                      choices=["always", "never", "if_tty", "auto"],
78                      help="Print error messages using color.  'auto' is a "
79                           "synonym for 'if_tty'.")
80  parser.add_argument("--import-dir", "-I",
81                      dest="import_dirs",
82                      action="append",
83                      default=["."],
84                      help="A directory to use when searching for imported "
85                           "embs.  If no import_dirs are specified, the "
86                           "current directory will be used.")
87  return parser.parse_args(argv[1:])
88
89
90def _show_errors(errors, ir, color_output):
91  """Prints errors with source code snippets."""
92  source_codes = {}
93  if ir:
94    for module in ir.module:
95      source_codes[module.source_file_name] = module.source_text
96  use_color = (color_output == "always" or
97               (color_output in ("auto", "if_tty") and
98                os.isatty(sys.stderr.fileno())))
99  print(error.format_errors(errors, source_codes, use_color), file=sys.stderr)
100
101
102def _find_in_dirs_and_read(import_dirs):
103  """Returns a function which will search import_dirs for a file."""
104
105  def _find_and_read(file_name):
106    """Searches import_dirs for file_name and returns the contents."""
107    errors = []
108    # *All* source files, including the one specified on the command line, will
109    # be searched for in the import_dirs.  This may be surprising, especially if
110    # the current directory is *not* an import_dir.
111    # TODO(bolms): Determine if this is really the desired behavior.
112    for import_dir in import_dirs:
113      full_name = path.join(import_dir, file_name)
114      try:
115        with open(full_name) as f:
116          # As written, this follows the typical compiler convention of checking
117          # the include/import directories in the order specified by the user,
118          # and always reading the first matching file, even if other files
119          # might match in later directories.  This lets files shadow other
120          # files, which can be useful in some cases (to override things), but
121          # can also cause accidental shadowing, which can be tricky to fix.
122          #
123          # TODO(bolms): Check if any other files with the same name are in the
124          # import path, and give a warning or error?
125          return f.read(), None
126      except IOError as e:
127        errors.append(str(e))
128    return None, errors + ["import path " + ":".join(import_dirs)]
129
130  return _find_and_read
131
132def parse_and_log_errors(input_file, import_dirs, color_output):
133  """Fully parses an .emb and logs any errors.
134
135  Arguments:
136    input_file: The path of the module source file.
137    import_dirs: Directories to search for imported dependencies.
138    color_output: Used when logging errors: "always", "never", "if_tty", "auto"
139
140  Returns:
141    (ir, debug_info, errors)
142  """
143  ir, debug_info, errors = glue.parse_emboss_file(
144      input_file, _find_in_dirs_and_read(import_dirs))
145  if errors:
146    _show_errors(errors, ir, color_output)
147
148  return (ir, debug_info, errors)
149
150def main(flags):
151  ir, debug_info, errors = parse_and_log_errors(
152    flags.input_file[0], flags.import_dirs, flags.color_output)
153  if errors:
154    return 1
155  main_module_debug_info = debug_info.modules[flags.input_file[0]]
156  if flags.debug_show_tokenization:
157    if flags.debug_show_header_lines:
158      print("Tokenization:")
159    print(main_module_debug_info.format_tokenization())
160  if flags.debug_show_parse_tree:
161    if flags.debug_show_header_lines:
162      print("Parse Tree:")
163    print(main_module_debug_info.format_parse_tree())
164  if flags.debug_show_module_ir:
165    if flags.debug_show_header_lines:
166      print("Module IR:")
167    print(main_module_debug_info.format_module_ir())
168  if flags.debug_show_full_ir:
169    if flags.debug_show_header_lines:
170      print("Full IR:")
171    print(str(ir))
172  if flags.debug_show_used_productions:
173    if flags.debug_show_header_lines:
174      print("Used Productions:")
175    print(glue.format_production_set(main_module_debug_info.used_productions))
176  if flags.debug_show_unused_productions:
177    if flags.debug_show_header_lines:
178      print("Unused Productions:")
179    print(glue.format_production_set(
180        set(module_ir.PRODUCTIONS) - main_module_debug_info.used_productions))
181  if flags.output_ir_to_stdout:
182    print(ir_data_utils.IrDataSerializer(ir).to_json())
183  if flags.output_file:
184    with open(flags.output_file, "w") as f:
185      f.write(ir_data_utils.IrDataSerializer(ir).to_json())
186  return 0
187
188
189if __name__ == "__main__":
190  sys.exit(main(_parse_command_line(sys.argv)))
191