Update the generator

Outstanding changes to the generator to:
  Support hashing
  Support versions
  Clean up 'C' header generation
  Update of 'C' header generation tests
  Fix line wrapping
  Pass error counts through to File objects


BUG= http://code.google.com/p/chromium/issues/detail?id=84272
TEST= python idl_c_header.py & gcl try
Generate the new headers, add them to a CL, and try

TBR= [email protected]
Review URL: http://codereview.chromium.org/7408002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@92930 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/ppapi/generators/idl_diff.py b/ppapi/generators/idl_diff.py
index d005a205..9d1abbbe 100644
--- a/ppapi/generators/idl_diff.py
+++ b/ppapi/generators/idl_diff.py
@@ -10,7 +10,7 @@
 import sys
 
 from idl_option import GetOption, Option, ParseOptions
-
+from idl_outfile import IDLOutFile
 #
 # IDLDiff
 #
@@ -23,16 +23,18 @@
 
 Option('gen', 'IDL generated files', default='hdir')
 Option('src', 'Original ".h" files', default='../c')
-
+Option('halt', 'Stop if a difference is found')
+Option('diff', 'Directory holding acceptable diffs', default='diff')
+Option('ok', 'Write out the diff file.')
 # Change
 #
 # A Change object contains the previous lines, new news and change type.
 #
 class Change(object):
-  def __init__(self, change):
-    self.mode = str(change['mode'])
-    self.was = list(change['was'])
-    self.now = list(change['now'])
+  def __init__(self, mode, was, now):
+    self.mode = mode
+    self.was = was
+    self.now = now
 
   def Dump(self):
     if not self.was:
@@ -43,9 +45,9 @@
       print 'Modifying %s' % self.mode
 
     for line in self.was:
-      print 'src: %s' % line
+      print 'src: >>%s<<' % line
     for line in self.now:
-      print 'gen: %s' % line
+      print 'gen: >>%s<<' % line
     print
 
 #
@@ -55,9 +57,20 @@
 # such as non-matching years.
 #
 def IsCopyright(change):
-  if len(change['now']) != 1 or len(change['was']) != 1: return False
-  if 'Copyright (c)' not in change['now'][0]: return False
-  if 'Copyright (c)' not in change['was'][0]: return False
+  if len(change.now) != 1 or len(change.was) != 1: return False
+  if 'Copyright (c)' not in change.now[0]: return False
+  if 'Copyright (c)' not in change.was[0]: return False
+  return True
+
+#
+# IsBlankComment
+#
+# Return True if this change only removes a blank line from a comment
+#
+def IsBlankComment(change):
+  if change.now: return False
+  if len(change.was) != 1: return False
+  if change.was[0].strip() != '*': return False
   return True
 
 #
@@ -66,14 +79,46 @@
 # Return True if this change only adds or removes blank lines
 #
 def IsBlank(change):
-  for line in change['now']:
+  for line in change.now:
     if line: return False
-  for line in change['was']:
+  for line in change.was:
     if line: return False
   return True
 
 
 #
+# IsCppComment
+#
+# Return True if this change only going from C++ to C style
+#
+def IsToCppComment(change):
+  if not len(change.now) or len(change.now) != len(change.was):
+    return False
+  for index in range(len(change.now)):
+    was = change.was[index].strip()
+    if was[:2] != '//':
+      return False
+    was = was[2:].strip()
+    now = change.now[index].strip()
+    if now[:2] != '/*':
+      return False
+    now = now[2:-2].strip()
+    if now != was:
+      return False
+  return True
+
+
+  return True
+
+def IsMergeComment(change):
+  if len(change.was) != 1: return False
+  if change.was[0].strip() != '*': return False
+  for line in change.now:
+    stripped = line.strip()
+    if stripped != '*' and stripped[:2] != '/*' and stripped[-2:] != '*/':
+      return False
+  return True
+#
 # IsSpacing
 #
 # Return True if this change is only different in the way 'words' are spaced
@@ -85,17 +130,17 @@
 #   ENUM_XYY_Y = 2,
 #
 def IsSpacing(change):
-  if len(change['now']) != len(change['was']): return False
-  for i in range(len(change['now'])):
+  if len(change.now) != len(change.was): return False
+  for i in range(len(change.now)):
     # Also ignore right side comments
-    line = change['was'][i]
+    line = change.was[i]
     offs = line.find('//')
     if offs == -1:
       offs = line.find('/*')
     if offs >-1:
       line = line[:offs-1]
 
-    words1 = change['now'][i].split()
+    words1 = change.now[i].split()
     words2 = line.split()
     if words1 != words2: return False
   return True
@@ -106,8 +151,9 @@
 # Return True if change has extra includes
 #
 def IsInclude(change):
-  if len(change['was']): return False
-  for line in change['now']:
+  for line in change.was:
+    if line.strip().find('struct'): return False
+  for line in change.now:
     if line and '#include' not in line: return False
   return True
 
@@ -117,8 +163,8 @@
 # Return True if the change is only missing C++ comments
 #
 def IsCppComment(change):
-  if len(change['now']): return False
-  for line in change['was']:
+  if len(change.now): return False
+  for line in change.was:
     line = line.strip()
     if line[:2] != '//': return False
   return True
@@ -128,7 +174,10 @@
 # Return True if none of the changes does not patch an above "bogus" change.
 #
 def ValidChange(change):
+  if IsToCppComment(change): return False
   if IsCopyright(change): return False
+  if IsBlankComment(change): return False
+  if IsMergeComment(change): return False
   if IsBlank(change): return False
   if IsSpacing(change): return False
   if IsInclude(change): return False
@@ -136,55 +185,170 @@
   return True
 
 
+#
+# Swapped
+#
+# Check if the combination of last + next change signals they are both
+# invalid such as swap of line around an invalid block.
+#
+def Swapped(last, next):
+  if not last.now and not next.was and len(last.was) == len(next.now):
+    cnt = len(last.was)
+    for i in range(cnt):
+      match = True
+      for j in range(cnt):
+        if last.was[j] != next.now[(i + j) % cnt]:
+          match = False
+          break;
+      if match: return True
+  if not last.was and not next.now and len(last.now) == len(next.was):
+    cnt = len(last.now)
+    for i in range(cnt):
+      match = True
+      for j in range(cnt):
+        if last.now[i] != next.was[(i + j) % cnt]:
+          match = False
+          break;
+      if match: return True
+  return False
 
+
+def FilterLinesIn(output):
+  was = []
+  now = []
+  filter = []
+  for index in range(len(output)):
+    filter.append(False)
+    line = output[index]
+    if len(line) < 2: continue
+    if line[0] == '<':
+      if line[2:].strip() == '': continue
+      was.append((index, line[2:]))
+    elif line[0] == '>':
+      if line[2:].strip() == '': continue
+      now.append((index, line[2:]))
+  for windex, wline in was:
+    for nindex, nline in now:
+      if filter[nindex]: continue
+      if filter[windex]: continue
+      if wline == nline:
+        filter[nindex] = True
+        filter[windex] = True
+        if GetOption('verbose'):
+          print "Found %d, %d >>%s<<" % (windex + 1, nindex + 1, wline)
+  out = []
+  for index in range(len(output)):
+    if not filter[index]:
+      out.append(output[index])
+
+  return out
 #
 # GetChanges
 #
 # Parse the output into discrete change blocks.
 #
 def GetChanges(output):
+  # Split on lines, adding an END marker to simply add logic
   lines = output.split('\n')
+  lines = FilterLinesIn(lines)
+  lines.append('END')
+
   changes = []
-  change = { 'mode' : None,
-             'was': [],
-             'now': []
-            }
+  was = []
+  now = []
+  mode = ''
+  last = None
+
   for line in lines:
+#    print "LINE=%s" % line
     if not line: continue
+
     elif line[0] == '<':
-      change['was'].append(line[2:])
+      if line[2:].strip() == '': continue
+      # Ignore prototypes
+      if len(line) > 10:
+        words = line[2:].split()
+        if len(words) == 2 and words[1][-1] == ';':
+          if words[0] == 'struct' or words[0] == 'union':
+            continue
+      was.append(line[2:])
     elif line[0] == '>':
-      change['now'].append(line[2:])
+      if line[2:].strip() == '': continue
+      if line[2:10] == '#include': continue
+      now.append(line[2:])
     elif line[0] == '-':
       continue
     else:
-      if ValidChange(change): changes.append(Change(change))
-      change['mode'] = line
-      change['now'] = []
-      change['was'] = []
-  if ValidChange(change): changes.append(Change(change))
-  return changes
+      change = Change(line, was, now)
+      was = []
+      now = []
+      if ValidChange(change):
+          changes.append(change)
+      if line == 'END':
+        break
+
+  return FilterChanges(changes)
+
+def FilterChanges(changes):
+  if len(changes) < 2: return changes
+  out = []
+  filter = [False for change in changes]
+  for cur in range(len(changes)):
+    for cmp in range(cur+1, len(changes)):
+      if filter[cmp]:
+        continue
+      if Swapped(changes[cur], changes[cmp]):
+        filter[cur] = True
+        filter[cmp] = True
+  for cur in range(len(changes)):
+    if filter[cur]: continue
+    out.append(changes[cur])
+  return out
 
 def Main(args):
   filenames = ParseOptions(args)
   if not filenames:
     gendir = os.path.join(GetOption('gen'), '*.h')
-    filenames = glob.glob(gendir)
+    filenames = sorted(glob.glob(gendir))
+    srcdir = os.path.join(GetOption('src'), '*.h')
+    srcs = sorted(glob.glob(srcdir))
+    for name in srcs:
+      name = os.path.split(name)[1]
+      name = os.path.join(GetOption('gen'), name)
+      if name not in filenames:
+        print 'Missing: %s' % name
 
   for filename in filenames:
     gen = filename
     filename = filename[len(GetOption('gen')) + 1:]
     src = os.path.join(GetOption('src'), filename)
+    diff = os.path.join(GetOption('diff'), filename)
     p = subprocess.Popen(['diff', src, gen], stdout=subprocess.PIPE)
     output, errors = p.communicate()
 
-    changes = GetChanges(output)
+    try:
+      input = open(diff, 'rt').read()
+    except:
+      input = ''
+
+    if input != output:
+      changes = GetChanges(output)
+    else:
+      changes = []
+
     if changes:
       print "\n\nDelta between:\n  src=%s\n  gen=%s\n" % (src, gen)
-      for change in GetChanges(output):
+      for change in changes:
         change.Dump()
+      print 'Done with %s\n\n' % src
+      if GetOption('ok'):
+        open(diff, 'wt').write(output)
+      if GetOption('halt'):
+        return 1
     else:
-      print "\nSAME:\n  src=%s\n  gen=%s\n" % (src, gen)
+      print "\nSAME:\n  src=%s\n  gen=%s" % (src, gen)
+      if input: print '  ** Matched expected diff. **'
+      print '\n'
 
 if __name__ == '__main__':
   sys.exit(Main(sys.argv[1:]))