1#!/usr/bin/env python3 2# coding=utf-8 3########################################################################## 4# 5# enums2names - Parse and convert enums to translator code 6# (C) Copyright 2021 Matti 'ccr' Hämäläinen <ccr@tnsp.org> 7# 8# Permission is hereby granted, free of charge, to any person obtaining a 9# copy of this software and associated documentation files (the 10# "Software"), to deal in the Software without restriction, including 11# without limitation the rights to use, copy, modify, merge, publish, 12# distribute, sub license, and/or sell copies of the Software, and to 13# permit persons to whom the Software is furnished to do so, subject to 14# the following conditions: 15# 16# The above copyright notice and this permission notice (including the 17# next paragraph) shall be included in all copies or substantial portions 18# of the Software. 19# 20# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 21# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. 23# IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR 24# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 25# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 26# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27# 28########################################################################## 29 30import sys 31import os.path 32import re 33import signal 34import argparse 35import textwrap 36 37assert sys.version_info >= (3, 6) 38 39 40# 41# List of enums we wish to include in output. 42# NOTE: This needs to be updated if such enums are added. 43# 44lst_enum_include = [ 45 "pipe_texture_target", 46 "pipe_shader_cap", 47 "pipe_shader_ir", 48 "pipe_map_flags", 49 "pipe_compute_cap", 50 "pipe_video_cap", 51 "pipe_video_profile", 52 "pipe_video_entrypoint", 53 "pipe_video_vpp_orientation", 54 "pipe_video_vpp_blend_mode", 55 "pipe_resource_param", 56 "pipe_fd_type", 57 "pipe_blendfactor", 58 "pipe_blend_func", 59 "pipe_logicop", 60] 61 62 63### 64### Utility functions 65### 66## Fatal error handler 67def pkk_fatal(smsg): 68 print("ERROR: "+ smsg) 69 sys.exit(1) 70 71 72## Handler for SIGINT signals 73def pkk_signal_handler(signal, frame): 74 print("\nQuitting due to SIGINT / Ctrl+C!") 75 sys.exit(1) 76 77 78## Argument parser subclass 79class PKKArgumentParser(argparse.ArgumentParser): 80 def print_help(self): 81 print("enums2names - Parse and convert enums to translator code\n" 82 "(C) Copyright 2021 Matti 'ccr' Hämäläinen <ccr@tnsp.org>\n") 83 super().print_help() 84 85 def error(self, msg): 86 self.print_help() 87 print(f"\nERROR: {msg}", file=sys.stderr) 88 sys.exit(2) 89 90 91def pkk_get_argparser(): 92 optparser = PKKArgumentParser( 93 usage="%(prog)s [options] <infile|->\n" 94 "example: %(prog)s ../../include/pipe/p_defines.h -C tr_util.c -H tr_util.h" 95 ) 96 97 optparser.add_argument("in_files", 98 type=str, 99 metavar="infiles", 100 nargs="+", 101 help="path to input header files (or '-' for stdin)") 102 103 optparser.add_argument("-C", 104 type=str, 105 metavar="outfile", 106 dest="out_source", 107 help="output C source file") 108 109 optparser.add_argument("-H", 110 type=str, 111 metavar="outfile", 112 dest="out_header", 113 help="output C header file") 114 115 optparser.add_argument("-I", 116 type=str, 117 metavar="include", 118 dest="include_file", 119 help="include file / path used for C source output") 120 121 return optparser 122 123 124class PKKHeaderParser: 125 126 def __init__(self, nfilename): 127 self.filename = nfilename 128 self.enums = {} 129 self.state = 0 130 self.nline = 0 131 self.mdata = [] 132 self.start = 0 133 self.name = None 134 self.in_multiline_comment = False 135 136 def error(self, msg): 137 pkk_fatal(f"{self.filename}:{self.nline} : {msg}") 138 139 def parse_line(self, sline: str): 140 start = sline.find('/*') 141 end = sline.find('*/') 142 if not self.in_multiline_comment and start >= 0: 143 if end >= 0: 144 assert end > start 145 sline = sline[:start] + sline[end + 2:] 146 else: 147 sline = sline[:start] 148 self.in_multiline_comment = True 149 elif self.in_multiline_comment and end >= 0: 150 self.in_multiline_comment = False 151 sline = sline[end + 2:] 152 elif self.in_multiline_comment: 153 return 154 # A kingdom for Py3.8 := operator ... 155 smatch = re.match(r'^enum\s+([A-Za-z0-9_]+)\s+.*;', sline) 156 if smatch: 157 pass 158 else: 159 smatch = re.match(r'^enum\s+([A-Za-z0-9_]+)', sline) 160 if smatch: 161 stmp = smatch.group(1) 162 163 if self.state != 0: 164 self.error(f"enum '{stmp}' starting inside another enum '{self.name}'") 165 166 self.name = stmp 167 self.state = 1 168 self.start = self.nline 169 self.mdata = [] 170 else: 171 smatch = re.match(r'^}(\s*|\s*[A-Z][A-Z_]+\s*);', sline) 172 if smatch: 173 if self.state == 1: 174 if self.name in self.enums: 175 self.error("duplicate enum definition '{}', lines {} - {} vs {} - {}".format( 176 self.name, self.enums[self.name]["start"], self.enums[self.name]["end"], 177 self.start, self.nline)) 178 179 self.enums[self.name] = { 180 "data": self.mdata, 181 "start": self.start, 182 "end": self.nline 183 } 184 185 self.state = 0 186 187 elif self.state == 1: 188 smatch = re.match(r'([A-Za-z0-9_]+)\s*=\s*(.+)\s*,?', sline) 189 if smatch: 190 self.mdata.append(smatch.group(1)) 191 else: 192 smatch = re.match(r'([A-Za-z0-9_]+)\s*,?', sline) 193 if smatch: 194 self.mdata.append(smatch.group(1)) 195 196 def parse_file(self, fh): 197 self.nline = 0 198 for line in fh: 199 self.nline += 1 200 self.parse_line(line.strip()) 201 202 return self.enums 203 204 205def pkk_output_header(fh): 206 prototypes = [f"const char *\n" 207 f"tr_util_{name}_name(enum {name} value);\n" for name in lst_enum_include] 208 209 print(textwrap.dedent("""\ 210 /* 211 * File generated with {program}, please do not edit manually. 212 */ 213 #ifndef {include_header_guard} 214 #define {include_header_guard} 215 216 217 #include "pipe/p_defines.h" 218 #include "pipe/p_video_enums.h" 219 220 221 #ifdef __cplusplus 222 extern "C" {{ 223 #endif 224 225 {prototypes} 226 227 #ifdef __cplusplus 228 }} 229 #endif 230 231 #endif /* {include_header_guard} */\ 232 """).format( 233 program=pkk_progname, 234 include_header_guard=re.sub(r'[^A-Z]', '_', os.path.basename(pkk_cfg.out_header).upper()), 235 prototypes="".join(prototypes) 236 ), file=fh) 237 238 239def pkk_output_source(fh): 240 if pkk_cfg.include_file == None: 241 pkk_fatal("Output C source enabled, but include file is not set (-I option).") 242 243 print(textwrap.dedent("""\ 244 /* 245 * File generated with {program}, please do not edit manually. 246 */ 247 #include "{include_file}" 248 """).format( 249 program=pkk_progname, 250 include_file=pkk_cfg.include_file, 251 ), file=fh) 252 253 for name in lst_enum_include: 254 cases = [f" case {eid}: return \"{eid}\";\n" 255 for eid in enums[name]["data"]] 256 257 print(textwrap.dedent("""\ 258 259 const char * 260 tr_util_{name}_name(enum {name} value) 261 {{ 262 switch (value) {{ 263 {cases} 264 default: return "{ucname}_UNKNOWN"; 265 }} 266 }} 267 """).format( 268 name=name, 269 ucname=name.upper(), 270 cases="".join(cases) 271 ), file=fh) 272 273### 274### Main program starts 275### 276if __name__ == "__main__": 277 signal.signal(signal.SIGINT, pkk_signal_handler) 278 279 ### Parse arguments 280 pkk_progname = sys.argv[0] 281 optparser = pkk_get_argparser() 282 pkk_cfg = optparser.parse_args() 283 284 ### Parse input files 285 enums = {} 286 for file in pkk_cfg.in_files: 287 hdrparser = PKKHeaderParser(file) 288 289 try: 290 if file != "-": 291 with open(file, "r", encoding="UTF-8") as fh: 292 enums.update(hdrparser.parse_file(fh)) 293 else: 294 enums.update(hdrparser.parse_file(sys.stdin)) 295 break 296 except OSError as e: 297 pkk_fatal(str(e)) 298 299 ### Check if any of the required enums are missing 300 errors = False 301 for name in lst_enum_include: 302 if name not in enums: 303 print(f"ERROR: Missing enum '{name}'!") 304 errors = True 305 306 if errors: 307 pkk_fatal(f"Errors in input. Edit this script ({pkk_progname}) to add/remove included enums.") 308 309 ### Perform output 310 if pkk_cfg.out_header: 311 with open(pkk_cfg.out_header, "w", encoding="UTF-8") as fh: 312 pkk_output_header(fh) 313 314 if pkk_cfg.out_source: 315 with open(pkk_cfg.out_source, "w", encoding="UTF-8") as fh: 316 pkk_output_source(fh) 317