1#!/usr/bin/env python3 -B 2# This Source Code Form is subject to the terms of the Mozilla Public 3# License, v. 2.0. If a copy of the MPL was not distributed with this file, 4# You can obtain one at http://mozilla.org/MPL/2.0/. 5 6import re 7 8quoted_pat = re.compile(r"([^A-Za-z0-9]|^)'([^']+)'") 9js_pat = re.compile(r"([^A-Za-z0-9]|^)(JS[A-Z0-9_\*]+)") 10 11 12def codify(text): 13 text = re.sub(quoted_pat, "\\1<code>\\2</code>", text) 14 text = re.sub(js_pat, "\\1<code>\\2</code>", text) 15 16 return text 17 18 19space_star_space_pat = re.compile("^\s*\* ?", re.M) 20 21 22def get_comment_body(comment): 23 return re.sub(space_star_space_pat, "", comment).split("\n") 24 25 26quote_pat = re.compile('"([^"]+)"') 27str_pat = re.compile("js_([^_]+)_str") 28 29 30def parse_name(s): 31 m = quote_pat.search(s) 32 if m: 33 return m.group(1) 34 m = str_pat.search(s) 35 if m: 36 return m.group(1) 37 return s 38 39 40csv_pat = re.compile(", *") 41 42 43def parse_csv(s): 44 a = csv_pat.split(s) 45 if len(a) == 1 and a[0] == "": 46 return [] 47 return a 48 49 50def get_stack_count(stack): 51 if stack == "": 52 return 0 53 if "..." in stack: 54 return -1 55 return len(stack.split(",")) 56 57 58def parse_index(comment): 59 index = [] 60 current_types = None 61 category_name = "" 62 category_pat = re.compile("\[([^\]]+)\]") 63 for line in get_comment_body(comment): 64 m = category_pat.search(line) 65 if m: 66 category_name = m.group(1) 67 if category_name == "Index": 68 continue 69 current_types = [] 70 index.append((category_name, current_types)) 71 else: 72 type_name = line.strip() 73 if type_name and current_types is not None: 74 current_types.append((type_name, [])) 75 76 return index 77 78 79# Holds the information stored in the comment with the following format: 80# /* 81# * {desc} 82# * Category: {category_name} 83# * Type: {type_name} 84# * Operands: {operands} 85# * Stack: {stack_uses} => {stack_defs} 86# */ 87 88 89class CommentInfo: 90 def __init__(self): 91 self.desc = "" 92 self.category_name = "" 93 self.type_name = "" 94 self.operands = "" 95 self.stack_uses = "" 96 self.stack_defs = "" 97 98 99# Holds the information stored in the macro with the following format: 100# MACRO({op}, {op_snake}, {token}, {length}, {nuses}, {ndefs}, {format}) 101# and the information from CommentInfo. 102 103 104class OpcodeInfo: 105 def __init__(self, value, comment_info): 106 self.op = "" 107 self.op_snake = "" 108 self.value = value 109 self.token = "" 110 self.length = "" 111 self.nuses = "" 112 self.ndefs = "" 113 self.format_ = "" 114 115 self.operands_array = [] 116 self.stack_uses_array = [] 117 self.stack_defs_array = [] 118 119 self.desc = comment_info.desc 120 self.category_name = comment_info.category_name 121 self.type_name = comment_info.type_name 122 self.operands = comment_info.operands 123 self.operands_array = comment_info.operands_array 124 self.stack_uses = comment_info.stack_uses 125 self.stack_uses_array = comment_info.stack_uses_array 126 self.stack_defs = comment_info.stack_defs 127 self.stack_defs_array = comment_info.stack_defs_array 128 129 # List of OpcodeInfo that corresponds to macros after this. 130 # /* 131 # * comment 132 # */ 133 # MACRO(JSOP_SUB, ...) 134 # MACRO(JSOP_MUL, ...) 135 # MACRO(JSOP_DIV, ...) 136 self.group = [] 137 138 self.sort_key = "" 139 140 141def find_by_name(list, name): 142 for (n, body) in list: 143 if n == name: 144 return body 145 146 return None 147 148 149def add_to_index(index, opcode): 150 types = find_by_name(index, opcode.category_name) 151 if types is None: 152 raise Exception( 153 "Category is not listed in index: " 154 "{name}".format(name=opcode.category_name) 155 ) 156 opcodes = find_by_name(types, opcode.type_name) 157 if opcodes is None: 158 if opcode.type_name: 159 raise Exception( 160 "Type is not listed in {category}: " 161 "{name}".format(category=opcode.category_name, name=opcode.type_name) 162 ) 163 types.append((opcode.type_name, [opcode])) 164 return 165 166 opcodes.append(opcode) 167 168 169tag_pat = re.compile("^\s*[A-Za-z]+:\s*|\s*$") 170 171 172def get_tag_value(line): 173 return re.sub(tag_pat, "", line) 174 175 176RUST_OR_CPP_KEYWORDS = { 177 "and", 178 "case", 179 "default", 180 "double", 181 "false", 182 "goto", 183 "in", 184 "new", 185 "not", 186 "or", 187 "return", 188 "throw", 189 "true", 190 "try", 191 "typeof", 192 "void", 193} 194 195 196def get_opcodes(dir): 197 iter_pat = re.compile( 198 r"/\*(.*?)\*/" # either a documentation comment... 199 r"|" 200 r"MACRO\(" # or a MACRO(...) call 201 r"(?P<op>[^,]+),\s*" 202 r"(?P<op_snake>[^,]+),\s*" 203 r"(?P<token>[^,]+,)\s*" 204 r"(?P<length>[0-9\-]+),\s*" 205 r"(?P<nuses>[0-9\-]+),\s*" 206 r"(?P<ndefs>[0-9\-]+),\s*" 207 r"(?P<format>[^\)]+)" 208 r"\)", 209 re.S, 210 ) 211 stack_pat = re.compile(r"^(?P<uses>.*?)" r"\s*=>\s*" r"(?P<defs>.*?)$") 212 213 opcodes = dict() 214 index = [] 215 216 with open("{dir}/js/src/vm/Opcodes.h".format(dir=dir), "r", encoding="utf-8") as f: 217 data = f.read() 218 219 comment_info = None 220 opcode = None 221 222 # The first opcode after the comment. 223 group_head = None 224 next_opcode_value = 0 225 226 for m in re.finditer(iter_pat, data): 227 comment = m.group(1) 228 op = m.group("op") 229 230 if comment: 231 if "[Index]" in comment: 232 index = parse_index(comment) 233 continue 234 235 if "Operands:" not in comment: 236 continue 237 238 group_head = None 239 240 comment_info = CommentInfo() 241 242 state = "desc" 243 stack = "" 244 desc = "" 245 246 for line in get_comment_body(comment): 247 if line.startswith(" Category:"): 248 state = "category" 249 comment_info.category_name = get_tag_value(line) 250 elif line.startswith(" Type:"): 251 state = "type" 252 comment_info.type_name = get_tag_value(line) 253 elif line.startswith(" Operands:"): 254 state = "operands" 255 comment_info.operands = get_tag_value(line) 256 elif line.startswith(" Stack:"): 257 state = "stack" 258 stack = get_tag_value(line) 259 elif state == "desc": 260 desc += line + "\n" 261 elif line.startswith(" "): 262 if line.isspace(): 263 pass 264 elif state == "operands": 265 comment_info.operands += " " + line.strip() 266 elif state == "stack": 267 stack += " " + line.strip() 268 else: 269 raise ValueError( 270 "unrecognized line in comment: {!r}\n\nfull comment was:\n{}".format( 271 line, comment 272 ) 273 ) 274 275 comment_info.desc = desc 276 277 comment_info.operands_array = parse_csv(comment_info.operands) 278 comment_info.stack_uses_array = parse_csv(comment_info.stack_uses) 279 comment_info.stack_defs_array = parse_csv(comment_info.stack_defs) 280 281 m2 = stack_pat.search(stack) 282 if m2: 283 comment_info.stack_uses = m2.group("uses") 284 comment_info.stack_defs = m2.group("defs") 285 else: 286 assert op is not None 287 opcode = OpcodeInfo(next_opcode_value, comment_info) 288 next_opcode_value += 1 289 290 opcode.op = op 291 opcode.op_snake = m.group("op_snake") 292 opcode.token = parse_name(m.group("token")) 293 opcode.length = m.group("length") 294 opcode.nuses = m.group("nuses") 295 opcode.ndefs = m.group("ndefs") 296 opcode.format_ = m.group("format").split("|") 297 298 expected_snake = re.sub(r"(?<!^)(?=[A-Z])", "_", opcode.op).lower() 299 if expected_snake in RUST_OR_CPP_KEYWORDS: 300 expected_snake += "_" 301 if opcode.op_snake != expected_snake: 302 raise ValueError( 303 "Unexpected snake-case name for {}: expected {!r}, got {!r}".format( 304 opcode.op_camel, expected_snake, opcode.op_snake 305 ) 306 ) 307 308 if not group_head: 309 group_head = opcode 310 311 opcode.sort_key = opcode.op 312 if opcode.category_name == "": 313 raise Exception( 314 "Category is not specified for " "{op}".format(op=opcode.op) 315 ) 316 add_to_index(index, opcode) 317 else: 318 if group_head.length != opcode.length: 319 raise Exception( 320 "length should be same for opcodes of the" 321 " same group: " 322 "{value1}({op1}) != " 323 "{value2}({op2})".format( 324 op1=group_head.op, 325 value1=group_head.length, 326 op2=opcode.op, 327 value2=opcode.length, 328 ) 329 ) 330 if group_head.nuses != opcode.nuses: 331 raise Exception( 332 "nuses should be same for opcodes of the" 333 " same group: " 334 "{value1}({op1}) != " 335 "{value2}({op2})".format( 336 op1=group_head.op, 337 value1=group_head.nuses, 338 op2=opcode.op, 339 value2=opcode.nuses, 340 ) 341 ) 342 if group_head.ndefs != opcode.ndefs: 343 raise Exception( 344 "ndefs should be same for opcodes of the" 345 " same group: " 346 "{value1}({op1}) != " 347 "{value2}({op2})".format( 348 op1=group_head.op, 349 value1=group_head.ndefs, 350 op2=opcode.op, 351 value2=opcode.ndefs, 352 ) 353 ) 354 355 group_head.group.append(opcode) 356 357 if opcode.op < group_head.op: 358 group_head.sort_key = opcode.op 359 360 opcodes[op] = opcode 361 362 # Verify stack notation. 363 nuses = int(opcode.nuses) 364 ndefs = int(opcode.ndefs) 365 366 stack_nuses = get_stack_count(opcode.stack_uses) 367 stack_ndefs = get_stack_count(opcode.stack_defs) 368 369 if nuses != -1 and stack_nuses != -1 and nuses != stack_nuses: 370 raise Exception( 371 "nuses should match stack notation: {op}: " 372 "{nuses} != {stack_nuses} " 373 "(stack_nuses)".format(op=op, nuses=nuses, stack_nuses=stack_nuses) 374 ) 375 if ndefs != -1 and stack_ndefs != -1 and ndefs != stack_ndefs: 376 raise Exception( 377 "ndefs should match stack notation: {op}: " 378 "{ndefs} != {stack_ndefs} " 379 "(stack_ndefs)".format(op=op, ndefs=ndefs, stack_ndefs=stack_ndefs) 380 ) 381 382 return index, opcodes 383