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_type", 47 "pipe_shader_cap", 48 "pipe_shader_ir", 49 "pipe_cap", 50 "pipe_capf", 51 "pipe_compute_cap", 52 "pipe_resource_param", 53 "pipe_fd_type", 54] 55 56 57### 58### Utility functions 59### 60## Fatal error handler 61def pkk_fatal(smsg): 62 print("ERROR: "+ smsg) 63 sys.exit(1) 64 65 66## Handler for SIGINT signals 67def pkk_signal_handler(signal, frame): 68 print("\nQuitting due to SIGINT / Ctrl+C!") 69 sys.exit(1) 70 71 72## Argument parser subclass 73class PKKArgumentParser(argparse.ArgumentParser): 74 def print_help(self): 75 print("enums2names - Parse and convert enums to translator code\n" 76 "(C) Copyright 2021 Matti 'ccr' Hämäläinen <ccr@tnsp.org>\n") 77 super().print_help() 78 79 def error(self, msg): 80 self.print_help() 81 print(f"\nERROR: {msg}", file=sys.stderr) 82 sys.exit(2) 83 84 85def pkk_get_argparser(): 86 optparser = PKKArgumentParser( 87 usage="%(prog)s [options] <infile|->\n" 88 "example: %(prog)s ../../include/pipe/p_defines.h -C tr_util.c -H tr_util.h" 89 ) 90 91 optparser.add_argument("in_file", 92 type=str, 93 metavar="infile", 94 help="path to input header file p_defines.h (or '-' for stdin)") 95 96 optparser.add_argument("-C", 97 type=str, 98 metavar="outfile", 99 dest="out_source", 100 help="output C source file") 101 102 optparser.add_argument("-H", 103 type=str, 104 metavar="outfile", 105 dest="out_header", 106 help="output C header file") 107 108 optparser.add_argument("-I", 109 type=str, 110 metavar="include", 111 dest="include_file", 112 help="include file / path used for C source output") 113 114 return optparser 115 116 117class PKKHeaderParser: 118 119 def __init__(self, nfilename): 120 self.filename = nfilename 121 self.enums = {} 122 self.state = 0 123 self.nline = 0 124 self.mdata = [] 125 self.start = 0 126 self.name = None 127 128 def error(self, msg): 129 pkk_fatal(f"{self.filename}:{self.nline} : {msg}") 130 131 def parse_line(self, sline): 132 # A kingdom for Py3.8 := operator ... 133 smatch = re.match(r'^enum\s+([A-Za-z0-9_]+)\s+.*;', sline) 134 if smatch: 135 pass 136 else: 137 smatch = re.match(r'^enum\s+([A-Za-z0-9_]+)', sline) 138 if smatch: 139 stmp = smatch.group(1) 140 141 if self.state != 0: 142 self.error(f"enum '{stmp}' starting inside another enum '{self.name}'") 143 144 self.name = stmp 145 self.state = 1 146 self.start = self.nline 147 self.mdata = [] 148 else: 149 smatch = re.match(r'^}(\s*|\s*[A-Z][A-Z_]+\s*);', sline) 150 if smatch: 151 if self.state == 1: 152 if self.name in self.enums: 153 self.error("duplicate enum definition '{}', lines {} - {} vs {} - {}".format( 154 self.name, self.enums[self.name]["start"], self.enums[self.name]["end"], 155 self.start, self.nline)) 156 157 self.enums[self.name] = { 158 "data": self.mdata, 159 "start": self.start, 160 "end": self.nline 161 } 162 163 self.state = 0 164 165 elif self.state == 1: 166 smatch = re.match(r'([A-Za-z0-9_]+)\s*=\s*(.+)\s*,?', sline) 167 if smatch: 168 self.mdata.append(smatch.group(1)) 169 else: 170 smatch = re.match(r'([A-Za-z0-9_]+)\s*,?', sline) 171 if smatch: 172 self.mdata.append(smatch.group(1)) 173 174 def parse_file(self, fh): 175 self.nline = 0 176 for line in fh: 177 self.nline += 1 178 self.parse_line(line.strip()) 179 180 return self.enums 181 182 183def pkk_output_header(fh): 184 prototypes = [f"const char *\n" 185 f"tr_util_{name}_name(enum {name} value);\n" for name in lst_enum_include] 186 187 print(textwrap.dedent("""\ 188 /* 189 * File generated with {program}, please do not edit manually. 190 */ 191 #ifndef {include_header_guard} 192 #define {include_header_guard} 193 194 195 #include "pipe/p_defines.h" 196 197 198 #ifdef __cplusplus 199 extern "C" {{ 200 #endif 201 202 {prototypes} 203 204 #ifdef __cplusplus 205 }} 206 #endif 207 208 #endif /* {include_header_guard} */\ 209 """).format( 210 program=pkk_progname, 211 include_header_guard=re.sub(r'[^A-Z]', '_', os.path.basename(pkk_cfg.out_header).upper()), 212 prototypes="".join(prototypes) 213 ), file=fh) 214 215 216def pkk_output_source(fh): 217 if pkk_cfg.include_file == None: 218 pkk_fatal("Output C source enabled, but include file is not set (-I option).") 219 220 print(textwrap.dedent("""\ 221 /* 222 * File generated with {program}, please do not edit manually. 223 */ 224 #include "{include_file}" 225 """).format( 226 program=pkk_progname, 227 include_file=pkk_cfg.include_file, 228 ), file=fh) 229 230 for name in lst_enum_include: 231 cases = [f" case {eid}: return \"{eid}\";\n" 232 for eid in enums[name]["data"]] 233 234 print(textwrap.dedent("""\ 235 236 const char * 237 tr_util_{name}_name(enum {name} value) 238 {{ 239 switch (value) {{ 240 {cases} 241 default: return "{ucname}_UNKNOWN"; 242 }} 243 }} 244 """).format( 245 name=name, 246 ucname=name.upper(), 247 cases="".join(cases) 248 ), file=fh) 249 250### 251### Main program starts 252### 253if __name__ == "__main__": 254 signal.signal(signal.SIGINT, pkk_signal_handler) 255 256 ### Parse arguments 257 pkk_progname = sys.argv[0] 258 optparser = pkk_get_argparser() 259 pkk_cfg = optparser.parse_args() 260 261 ### Parse input 262 hdrparser = PKKHeaderParser(pkk_cfg.in_file) 263 264 try: 265 if pkk_cfg.in_file != "-": 266 with open(pkk_cfg.in_file, "r", encoding="UTF-8") as fh: 267 enums = hdrparser.parse_file(fh) 268 else: 269 enums = hdrparser.parse_file(sys.stdin) 270 271 except OSError as e: 272 pkk_fatal(str(e)) 273 274 ### Check if any of the required enums are missing 275 errors = False 276 for name in lst_enum_include: 277 if name not in enums: 278 print(f"ERROR: Missing enum '{name}'!") 279 errors = True 280 281 if errors: 282 pkk_fatal(f"Errors in input. Edit this script ({pkk_progname}) to add/remove included enums.") 283 284 ### Perform output 285 if pkk_cfg.out_header: 286 with open(pkk_cfg.out_header, "w", encoding="UTF-8") as fh: 287 pkk_output_header(fh) 288 289 if pkk_cfg.out_source: 290 with open(pkk_cfg.out_source, "w", encoding="UTF-8") as fh: 291 pkk_output_source(fh) 292