1#!/usr/local/bin/python
2#
3#
4# Licensed to the Apache Software Foundation (ASF) under one
5# or more contributor license agreements.  See the NOTICE file
6# distributed with this work for additional information
7# regarding copyright ownership.  The ASF licenses this file
8# to you under the Apache License, Version 2.0 (the
9# "License"); you may not use this file except in compliance
10# with the License.  You may obtain a copy of the License at
11#
12#   http://www.apache.org/licenses/LICENSE-2.0
13#
14# Unless required by applicable law or agreed to in writing,
15# software distributed under the License is distributed on an
16# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17# KIND, either express or implied.  See the License for the
18# specific language governing permissions and limitations
19# under the License.
20#
21#
22#
23# Find places in our code that are part of control statements
24# i.e. "for", "if" and "while".  That output is then easily
25# searched for various interesting / complex pattern.
26#
27#
28# USAGE: find-control-statements.py FILE1 FILE2 ...
29#
30
31import sys
32
33header_shown = False
34last_line_num = None
35
36def print_line(fname, line_num, line):
37  """ Print LINE of number LINE_NUM in file FNAME.
38  Show FNAME only once per file and LINE_NUM only for
39  non-consecutive lines.
40  """
41  global header_shown
42  global last_line_num
43
44  if not header_shown:
45    print('')
46    print(fname)
47    header_shown = True
48
49  if last_line_num and (last_line_num + 1 == line_num):
50    print("      %s" % line),
51  else:
52    print('%5d:%s' % (line_num, line)),
53
54  last_line_num = line_num
55
56def is_control(line, index, word):
57  """ Return whether LINE[INDEX] is actual the start position of
58  control statement WORD.  It must be followed by an opening
59  parantheses and only whitespace in between WORD and the '('.
60  """
61  if index > 0:
62    if not (line[index-1] in [' ', '\t', ';']):
63      return False
64
65  index = index + len(word)
66  parantheses_index = line.find('(', index)
67  if parantheses_index == -1:
68    return False
69
70  while index < parantheses_index:
71    if not (line[index] in [' ', '\t',]):
72      return False
73
74    index += 1
75
76  return True
77
78def find_specific_control(line, control):
79  """ Return the first offset of the control statement CONTROL
80  in LINE, or -1 if it can't be found.
81  """
82  current = 0
83
84  while current != -1:
85    index = line.find(control, current)
86    if index == -1:
87      break
88
89    if is_control(line, index, control):
90      return index
91
92    current = index + len(control);
93
94  return -1
95
96def find_control(line):
97  """ Return the offset of the first control in LINE or -1
98  if there is none.
99  """
100  current = 0
101
102  for_index = find_specific_control(line, "for")
103  if_index = find_specific_control(line, "if")
104  while_index = find_specific_control(line, "while")
105
106  first = len(line)
107  if for_index >= 0 and first > for_index:
108    first = for_index
109  if if_index >= 0 and first > if_index:
110    first = if_index
111  if while_index >= 0 and first > while_index:
112    first = while_index
113
114  if first == len(line):
115    return -1
116  return first
117
118def parantheses_delta(line):
119  """ Return the number of opening minus the number of closing
120  parantheses in LINE.  Don't count those inside strings or chars.
121  """
122  escaped = False
123  in_squote = False
124  in_dquote = False
125
126  delta = 0
127
128  for c in line:
129    if escaped:
130      escaped = False
131
132    elif in_dquote:
133      if c == '\\':
134        escaped = True
135      elif c == '"':
136        in_dquote = False
137
138    elif in_squote:
139      if c == '\\':
140        escaped = True
141      elif c == "'":
142        in_squote = False
143
144    elif c == '(':
145      delta += 1
146    elif c == ')':
147      delta -= 1
148    elif c == '"':
149      in_dquote = True
150    elif c == "'":
151      in_squote -= True
152
153  return delta
154
155def scan_file(fname):
156  lines = open(fname).readlines()
157
158  line_num = 1
159  parantheses_level = 0
160
161  for line in lines:
162
163    if parantheses_level > 0:
164      index = 0
165    else:
166      index = find_control(line)
167
168    if index >= 0:
169      print_line(fname, line_num, line)
170      parantheses_level += parantheses_delta(line[index:])
171
172    line_num += 1
173
174if __name__ == '__main__':
175  for fname in sys.argv[1:]:
176    header_shown = False
177    last_line_num = None
178    scan_file(fname)
179