1#!/usr/bin/env python
2# Copyright 2014 the V8 project authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import js2c
7import os
8import re
9import sys
10
11FILENAME = "src/runtime.cc"
12FUNCTION = re.compile("^RUNTIME_FUNCTION\(Runtime_(\w+)")
13FUNCTIONEND = "}\n"
14MACRO = re.compile(r"^#define ([^ ]+)\(([^)]*)\) *([^\\]*)\\?\n$")
15FIRST_WORD = re.compile("^\s*(.*?)[\s({\[]")
16
17# Expand these macros, they define further runtime functions.
18EXPAND_MACROS = [
19  "BUFFER_VIEW_GETTER",
20  "DATA_VIEW_GETTER",
21  "DATA_VIEW_SETTER",
22  "ELEMENTS_KIND_CHECK_RUNTIME_FUNCTION",
23  "FIXED_TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION",
24  "RUNTIME_UNARY_MATH",
25  "TYPED_ARRAYS_CHECK_RUNTIME_FUNCTION",
26]
27
28
29class Function(object):
30  def __init__(self, match):
31    self.name = match.group(1)
32
33
34class Macro(object):
35  def __init__(self, match):
36    self.name = match.group(1)
37    self.args = [s.strip() for s in match.group(2).split(",")]
38    self.lines = []
39    self.indentation = 0
40    self.AddLine(match.group(3))
41
42  def AddLine(self, line):
43    if not line: return
44    if not self.lines:
45      # This is the first line, detect indentation.
46      self.indentation = len(line) - len(line.lstrip())
47    line = line.rstrip("\\\n ")
48    if not line: return
49    assert len(line[:self.indentation].strip()) == 0, \
50        ("expected whitespace: '%s', full line: '%s'" %
51         (line[:self.indentation], line))
52    line = line[self.indentation:]
53    if not line: return
54    self.lines.append(line + "\n")
55
56  def Finalize(self):
57    for arg in self.args:
58      pattern = re.compile(r"(##|\b)%s(##|\b)" % arg)
59      for i in range(len(self.lines)):
60        self.lines[i] = re.sub(pattern, "%%(%s)s" % arg, self.lines[i])
61
62  def FillIn(self, arg_values):
63    filler = {}
64    assert len(arg_values) == len(self.args)
65    for i in range(len(self.args)):
66      filler[self.args[i]] = arg_values[i]
67    result = []
68    for line in self.lines:
69      result.append(line % filler)
70    return result
71
72
73def ReadFileAndExpandMacros(filename):
74  found_macros = {}
75  expanded_lines = []
76  with open(filename, "r") as f:
77    found_macro = None
78    for line in f:
79      if found_macro is not None:
80        found_macro.AddLine(line)
81        if not line.endswith("\\\n"):
82          found_macro.Finalize()
83          found_macro = None
84        continue
85
86      match = MACRO.match(line)
87      if match:
88        found_macro = Macro(match)
89        if found_macro.name in EXPAND_MACROS:
90          found_macros[found_macro.name] = found_macro
91        else:
92          found_macro = None
93        continue
94
95      match = FIRST_WORD.match(line)
96      if match:
97        first_word = match.group(1)
98        if first_word in found_macros:
99          MACRO_CALL = re.compile("%s\(([^)]*)\)" % first_word)
100          match = MACRO_CALL.match(line)
101          assert match
102          args = [s.strip() for s in match.group(1).split(",")]
103          expanded_lines += found_macros[first_word].FillIn(args)
104          continue
105
106      expanded_lines.append(line)
107  return expanded_lines
108
109
110# Detects runtime functions by parsing FILENAME.
111def FindRuntimeFunctions():
112  functions = []
113  expanded_lines = ReadFileAndExpandMacros(FILENAME)
114  function = None
115  partial_line = ""
116  for line in expanded_lines:
117    # Multi-line definition support, ignoring macros.
118    if line.startswith("RUNTIME_FUNCTION") and not line.endswith("{\n"):
119      if line.endswith("\\\n"): continue
120      partial_line = line.rstrip()
121      continue
122    if partial_line:
123      partial_line += " " + line.strip()
124      if partial_line.endswith("{"):
125        line = partial_line
126        partial_line = ""
127      else:
128        continue
129
130    match = FUNCTION.match(line)
131    if match:
132      function = Function(match)
133      continue
134    if function is None: continue
135
136    if line == FUNCTIONEND:
137      if function is not None:
138        functions.append(function)
139        function = None
140  return functions
141
142
143class Builtin(object):
144  def __init__(self, match):
145    self.name = match.group(1)
146
147
148def FindJSNatives():
149  PATH = "src"
150  fileslist = []
151  for (root, dirs, files) in os.walk(PATH):
152    for f in files:
153      if f.endswith(".js"):
154        fileslist.append(os.path.join(root, f))
155  natives = []
156  regexp = re.compile("^function (\w+)\s*\((.*?)\) {")
157  matches = 0
158  for filename in fileslist:
159    with open(filename, "r") as f:
160      file_contents = f.read()
161    file_contents = js2c.ExpandInlineMacros(file_contents)
162    lines = file_contents.split("\n")
163    partial_line = ""
164    for line in lines:
165      if line.startswith("function") and not '{' in line:
166        partial_line += line.rstrip()
167        continue
168      if partial_line:
169        partial_line += " " + line.strip()
170        if '{' in line:
171          line = partial_line
172          partial_line = ""
173        else:
174          continue
175      match = regexp.match(line)
176      if match:
177        natives.append(Builtin(match))
178  return natives
179
180
181def Main():
182  functions = FindRuntimeFunctions()
183  natives = FindJSNatives()
184  errors = 0
185  runtime_map = {}
186  for f in functions:
187    runtime_map[f.name] = 1
188  for b in natives:
189    if b.name in runtime_map:
190      print("JS_Native/Runtime_Function name clash: %s" % b.name)
191      errors += 1
192
193  if errors > 0:
194    return 1
195  print("Runtime/Natives name clashes: checked %d/%d functions, all good." %
196        (len(functions), len(natives)))
197  return 0
198
199
200if __name__ == "__main__":
201  sys.exit(Main())
202