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