Add comment conversion tool.

This is a utility to switch comments from /* C-style */ to // C++-style.
It's purely aesthetic, but it matches how most of Google C++ looks.
Running it over libssl, the script seems to get all but one or two cases
right.

We may also wish to convert the C code for consistency while we're here.
We've accidentally put both styles of comments all over the place, so
our toolchains can tolerate // in C.

Bug: 132
Change-Id: If2f4d58c0a4ad8f9a2113705435bff90e0dabcc3
Reviewed-on: https://boringssl-review.googlesource.com/18064
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/util/convert_comments.go b/util/convert_comments.go
new file mode 100644
index 0000000..f5171c3
--- /dev/null
+++ b/util/convert_comments.go
@@ -0,0 +1,216 @@
+// Copyright (c) 2017, Google Inc.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+package main
+
+import (
+	"bytes"
+	"io/ioutil"
+	"os"
+	"strings"
+)
+
+// convert_comments.go converts C-style block comments to C++-style line
+// comments. A block comment is converted if all of the following are true:
+//
+//   * The comment begins after the first blank line, to leave the license
+//     blocks alone.
+//
+//   * There are no characters between the '*/' and the end of the line.
+//
+//   * Either one of the following are true:
+//
+//     - The comment fits on one line.
+//
+//     - Each line the comment spans begins with N spaces, followed by '/*' for
+//       the initial line or ' *' for subsequent lines, where N is the same for
+//       each line.
+//
+// This tool is a heuristic. While it gets almost all cases correct, the final
+// output should still be looked over and fixed up as needed.
+
+// allSpaces returns true if |s| consists entirely of spaces.
+func allSpaces(s string) bool {
+	return strings.IndexFunc(s, func(r rune) bool { return r != ' ' }) == -1
+}
+
+// isContinuation returns true if |s| is a continuation line for a multi-line
+// comment indented to the specified column.
+func isContinuation(s string, column int) bool {
+	if len(s) < column+2 {
+		return false
+	}
+	if !allSpaces(s[:column]) {
+		return false
+	}
+	return s[column:column+2] == " *"
+}
+
+// indexFrom behaves like strings.Index but only reports matches starting at
+// |idx|.
+func indexFrom(s, sep string, idx int) int {
+	ret := strings.Index(s[idx:], sep)
+	if ret < 0 {
+		return -1
+	}
+	return idx + ret
+}
+
+// writeLine writes |line| to |out|, followed by a newline.
+func writeLine(out *bytes.Buffer, line string) {
+	out.WriteString(line)
+	out.WriteByte('\n')
+}
+
+func convertComments(in []byte) []byte {
+	lines := strings.Split(string(in), "\n")
+	var out bytes.Buffer
+
+	// Account for the trailing newline.
+	if len(lines) > 0 && len(lines[len(lines)-1]) == 0 {
+		lines = lines[:len(lines)-1]
+	}
+
+	// Find the license block separator.
+	for len(lines) > 0 {
+		line := lines[0]
+		lines = lines[1:]
+		writeLine(&out, line)
+		if len(line) == 0 {
+			break
+		}
+	}
+
+	// inComment is true if we are in the middle of a comment.
+	var inComment bool
+	// comment is the currently buffered multi-line comment to convert. If
+	// |inComment| is true and it is nil, the current multi-line comment is
+	// not convertable and we copy lines to |out| as-is.
+	var comment []string
+	// column is the column offset of |comment|.
+	var column int
+	for len(lines) > 0 {
+		line := lines[0]
+		lines = lines[1:]
+
+		var idx int
+		if inComment {
+			// Stop buffering if this comment isn't eligible.
+			if comment != nil && !isContinuation(line, column) {
+				for _, l := range comment {
+					writeLine(&out, l)
+				}
+				comment = nil
+			}
+
+			// Look for the end of the current comment.
+			idx = strings.Index(line, "*/")
+			if idx < 0 {
+				if comment != nil {
+					comment = append(comment, line)
+				} else {
+					writeLine(&out, line)
+				}
+				continue
+			}
+
+			inComment = false
+			if comment != nil {
+				if idx == len(line)-2 {
+					// This is a convertable multi-line comment.
+					if idx >= column+2 {
+						// |idx| may be equal to
+						// |column| + 1, if the line is
+						// a '*/' on its own. In that
+						// case, we discard the line.
+						comment = append(comment, line[:idx])
+					}
+					for _, l := range comment {
+						out.WriteString(l[:column])
+						out.WriteString("//")
+						writeLine(&out, strings.TrimRight(l[column+2:], " "))
+					}
+					comment = nil
+					continue
+				}
+
+				// Flush the buffered comment unmodified.
+				for _, l := range comment {
+					writeLine(&out, l)
+				}
+				comment = nil
+			}
+			idx += 2
+		}
+
+		// Parse starting from |idx|, looking for either a convertable
+		// line comment or a multi-line comment.
+		for {
+			idx = indexFrom(line, "/*", idx)
+			if idx < 0 {
+				writeLine(&out, line)
+				break
+			}
+
+			endIdx := indexFrom(line, "*/", idx)
+			if endIdx < 0 {
+				inComment = true
+				if allSpaces(line[:idx]) {
+					// The comment is, so far, eligible for conversion.
+					column = idx
+					comment = []string{line}
+				}
+				break
+			}
+
+			if endIdx != len(line)-2 {
+				// Continue parsing for more comments in this line.
+				idx = endIdx + 2
+				continue
+			}
+
+			out.WriteString(line[:idx])
+
+			// Google C++ style prefers two spaces before a
+			// comment if it is on the same line as code,
+			// but clang-format has been placing one space
+			// for block comments. Fix this.
+			if !allSpaces(line[:idx]) {
+				if line[idx-1] != ' ' {
+					out.WriteString("  ")
+				} else if line[idx-2] != ' ' {
+					out.WriteString(" ")
+				}
+			}
+
+			out.WriteString("//")
+			writeLine(&out, strings.TrimRight(line[idx+2:endIdx], " "))
+			break
+		}
+	}
+
+	return out.Bytes()
+}
+
+func main() {
+	for _, arg := range os.Args[1:] {
+		in, err := ioutil.ReadFile(arg)
+		if err != nil {
+			panic(err)
+		}
+		if err := ioutil.WriteFile(arg, convertComments(in), 0666); err != nil {
+			panic(err)
+		}
+	}
+}