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