Index: tools/telemetry/third_party/rope/rope/base/codeanalyze.py |
diff --git a/tools/telemetry/third_party/rope/rope/base/codeanalyze.py b/tools/telemetry/third_party/rope/rope/base/codeanalyze.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..0c1f055d5a57967f368128185ebd75709fde4c52 |
--- /dev/null |
+++ b/tools/telemetry/third_party/rope/rope/base/codeanalyze.py |
@@ -0,0 +1,362 @@ |
+import bisect |
+import re |
+import token |
+import tokenize |
+ |
+ |
+class ChangeCollector(object): |
+ |
+ def __init__(self, text): |
+ self.text = text |
+ self.changes = [] |
+ |
+ def add_change(self, start, end, new_text=None): |
+ if new_text is None: |
+ new_text = self.text[start:end] |
+ self.changes.append((start, end, new_text)) |
+ |
+ def get_changed(self): |
+ if not self.changes: |
+ return None |
+ |
+ self.changes.sort(key=lambda x: x[:2]) |
+ pieces = [] |
+ last_changed = 0 |
+ for change in self.changes: |
+ start, end, text = change |
+ pieces.append(self.text[last_changed:start] + text) |
+ last_changed = end |
+ if last_changed < len(self.text): |
+ pieces.append(self.text[last_changed:]) |
+ result = ''.join(pieces) |
+ if result != self.text: |
+ return result |
+ |
+ |
+class SourceLinesAdapter(object): |
+ """Adapts source to Lines interface |
+ |
+ Note: The creation of this class is expensive. |
+ """ |
+ |
+ def __init__(self, source_code): |
+ self.code = source_code |
+ self.starts = None |
+ self._initialize_line_starts() |
+ |
+ def _initialize_line_starts(self): |
+ self.starts = [] |
+ self.starts.append(0) |
+ try: |
+ i = 0 |
+ while True: |
+ i = self.code.index('\n', i) + 1 |
+ self.starts.append(i) |
+ except ValueError: |
+ pass |
+ self.starts.append(len(self.code) + 1) |
+ |
+ def get_line(self, lineno): |
+ return self.code[self.starts[lineno - 1]: |
+ self.starts[lineno] - 1] |
+ |
+ def length(self): |
+ return len(self.starts) - 1 |
+ |
+ def get_line_number(self, offset): |
+ return bisect.bisect(self.starts, offset) |
+ |
+ def get_line_start(self, lineno): |
+ return self.starts[lineno - 1] |
+ |
+ def get_line_end(self, lineno): |
+ return self.starts[lineno] - 1 |
+ |
+ |
+class ArrayLinesAdapter(object): |
+ |
+ def __init__(self, lines): |
+ self.lines = lines |
+ |
+ def get_line(self, line_number): |
+ return self.lines[line_number - 1] |
+ |
+ def length(self): |
+ return len(self.lines) |
+ |
+ |
+class LinesToReadline(object): |
+ |
+ def __init__(self, lines, start): |
+ self.lines = lines |
+ self.current = start |
+ |
+ def readline(self): |
+ if self.current <= self.lines.length(): |
+ self.current += 1 |
+ return self.lines.get_line(self.current - 1) + '\n' |
+ return '' |
+ |
+ def __call__(self): |
+ return self.readline() |
+ |
+ |
+class _CustomGenerator(object): |
+ |
+ def __init__(self, lines): |
+ self.lines = lines |
+ self.in_string = '' |
+ self.open_count = 0 |
+ self.continuation = False |
+ |
+ def __call__(self): |
+ size = self.lines.length() |
+ result = [] |
+ i = 1 |
+ while i <= size: |
+ while i <= size and not self.lines.get_line(i).strip(): |
+ i += 1 |
+ if i <= size: |
+ start = i |
+ while True: |
+ line = self.lines.get_line(i) |
+ self._analyze_line(line) |
+ if not (self.continuation or self.open_count or |
+ self.in_string) or i == size: |
+ break |
+ i += 1 |
+ result.append((start, i)) |
+ i += 1 |
+ return result |
+ |
+ _main_chars = re.compile(r'[\'|"|#|\\|\[|\]|\{|\}|\(|\)]') |
+ |
+ def _analyze_line(self, line): |
+ char = None |
+ for match in self._main_chars.finditer(line): |
+ char = match.group() |
+ i = match.start() |
+ if char in '\'"': |
+ if not self.in_string: |
+ self.in_string = char |
+ if char * 3 == line[i:i + 3]: |
+ self.in_string = char * 3 |
+ elif self.in_string == line[i:i + len(self.in_string)] and \ |
+ not (i > 0 and line[i - 1] == '\\' and |
+ not (i > 1 and line[i - 2] == '\\')): |
+ self.in_string = '' |
+ if self.in_string: |
+ continue |
+ if char == '#': |
+ break |
+ if char in '([{': |
+ self.open_count += 1 |
+ elif char in ')]}': |
+ self.open_count -= 1 |
+ if line and char != '#' and line.endswith('\\'): |
+ self.continuation = True |
+ else: |
+ self.continuation = False |
+ |
+ |
+def custom_generator(lines): |
+ return _CustomGenerator(lines)() |
+ |
+ |
+class LogicalLineFinder(object): |
+ |
+ def __init__(self, lines): |
+ self.lines = lines |
+ |
+ def logical_line_in(self, line_number): |
+ indents = count_line_indents(self.lines.get_line(line_number)) |
+ tries = 0 |
+ while True: |
+ block_start = get_block_start(self.lines, line_number, indents) |
+ try: |
+ return self._block_logical_line(block_start, line_number) |
+ except IndentationError as e: |
+ tries += 1 |
+ if tries == 5: |
+ raise e |
+ lineno = e.lineno + block_start - 1 |
+ indents = count_line_indents(self.lines.get_line(lineno)) |
+ |
+ def generate_starts(self, start_line=1, end_line=None): |
+ for start, end in self.generate_regions(start_line, end_line): |
+ yield start |
+ |
+ def generate_regions(self, start_line=1, end_line=None): |
+ # XXX: `block_start` should be at a better position! |
+ block_start = 1 |
+ readline = LinesToReadline(self.lines, block_start) |
+ try: |
+ for start, end in self._logical_lines(readline): |
+ real_start = start + block_start - 1 |
+ real_start = self._first_non_blank(real_start) |
+ if end_line is not None and real_start >= end_line: |
+ break |
+ real_end = end + block_start - 1 |
+ if real_start >= start_line: |
+ yield (real_start, real_end) |
+ except tokenize.TokenError: |
+ pass |
+ |
+ def _block_logical_line(self, block_start, line_number): |
+ readline = LinesToReadline(self.lines, block_start) |
+ shifted = line_number - block_start + 1 |
+ region = self._calculate_logical(readline, shifted) |
+ start = self._first_non_blank(region[0] + block_start - 1) |
+ if region[1] is None: |
+ end = self.lines.length() |
+ else: |
+ end = region[1] + block_start - 1 |
+ return start, end |
+ |
+ def _calculate_logical(self, readline, line_number): |
+ last_end = 1 |
+ try: |
+ for start, end in self._logical_lines(readline): |
+ if line_number <= end: |
+ return (start, end) |
+ last_end = end + 1 |
+ except tokenize.TokenError as e: |
+ current = e.args[1][0] |
+ return (last_end, max(last_end, current - 1)) |
+ return (last_end, None) |
+ |
+ def _logical_lines(self, readline): |
+ last_end = 1 |
+ for current_token in tokenize.generate_tokens(readline): |
+ current = current_token[2][0] |
+ if current_token[0] == token.NEWLINE: |
+ yield (last_end, current) |
+ last_end = current + 1 |
+ |
+ def _first_non_blank(self, line_number): |
+ current = line_number |
+ while current < self.lines.length(): |
+ line = self.lines.get_line(current).strip() |
+ if line and not line.startswith('#'): |
+ return current |
+ current += 1 |
+ return current |
+ |
+ |
+def tokenizer_generator(lines): |
+ return LogicalLineFinder(lines).generate_regions() |
+ |
+ |
+class CachingLogicalLineFinder(object): |
+ |
+ def __init__(self, lines, generate=custom_generator): |
+ self.lines = lines |
+ self._generate = generate |
+ |
+ _starts = None |
+ |
+ @property |
+ def starts(self): |
+ if self._starts is None: |
+ self._init_logicals() |
+ return self._starts |
+ |
+ _ends = None |
+ |
+ @property |
+ def ends(self): |
+ if self._ends is None: |
+ self._init_logicals() |
+ return self._ends |
+ |
+ def _init_logicals(self): |
+ """Should initialize _starts and _ends attributes""" |
+ size = self.lines.length() + 1 |
+ self._starts = [None] * size |
+ self._ends = [None] * size |
+ for start, end in self._generate(self.lines): |
+ self._starts[start] = True |
+ self._ends[end] = True |
+ |
+ def logical_line_in(self, line_number): |
+ start = line_number |
+ while start > 0 and not self.starts[start]: |
+ start -= 1 |
+ if start == 0: |
+ try: |
+ start = self.starts.index(True, line_number) |
+ except ValueError: |
+ return (line_number, line_number) |
+ return (start, self.ends.index(True, start)) |
+ |
+ def generate_starts(self, start_line=1, end_line=None): |
+ if end_line is None: |
+ end_line = self.lines.length() |
+ for index in range(start_line, end_line): |
+ if self.starts[index]: |
+ yield index |
+ |
+ |
+def get_block_start(lines, lineno, maximum_indents=80): |
+ """Approximate block start""" |
+ pattern = get_block_start_patterns() |
+ for i in range(lineno, 0, -1): |
+ match = pattern.search(lines.get_line(i)) |
+ if match is not None and \ |
+ count_line_indents(lines.get_line(i)) <= maximum_indents: |
+ striped = match.string.lstrip() |
+ # Maybe we're in a list comprehension or generator expression |
+ if i > 1 and striped.startswith('if') or striped.startswith('for'): |
+ bracs = 0 |
+ for j in range(i, min(i + 5, lines.length() + 1)): |
+ for c in lines.get_line(j): |
+ if c == '#': |
+ break |
+ if c in '[(': |
+ bracs += 1 |
+ if c in ')]': |
+ bracs -= 1 |
+ if bracs < 0: |
+ break |
+ if bracs < 0: |
+ break |
+ if bracs < 0: |
+ continue |
+ return i |
+ return 1 |
+ |
+ |
+_block_start_pattern = None |
+ |
+ |
+def get_block_start_patterns(): |
+ global _block_start_pattern |
+ if not _block_start_pattern: |
+ pattern = '^\\s*(((def|class|if|elif|except|for|while|with)\\s)|'\ |
+ '((try|else|finally|except)\\s*:))' |
+ _block_start_pattern = re.compile(pattern, re.M) |
+ return _block_start_pattern |
+ |
+ |
+def count_line_indents(line): |
+ indents = 0 |
+ for char in line: |
+ if char == ' ': |
+ indents += 1 |
+ elif char == '\t': |
+ indents += 8 |
+ else: |
+ return indents |
+ return 0 |
+ |
+ |
+def get_string_pattern(): |
+ start = r'(\b[uU]?[rR]?)?' |
+ longstr = r'%s"""(\\.|"(?!"")|\\\n|[^"\\])*"""' % start |
+ shortstr = r'%s"(\\.|\\\n|[^"\\])*"' % start |
+ return '|'.join([longstr, longstr.replace('"', "'"), |
+ shortstr, shortstr.replace('"', "'")]) |
+ |
+ |
+def get_comment_pattern(): |
+ return r'#[^\n]*' |