|  | // Copyright 2017 The BoringSSL Authors | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //     https://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | //go:build ignore | 
|  |  | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "fmt" | 
|  | "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 | 
|  | } | 
|  |  | 
|  | // A lineGroup is a contiguous group of lines with an eligible comment at the | 
|  | // same column. Any trailing '*/'s will already be removed. | 
|  | type lineGroup struct { | 
|  | // column is the column where the eligible comment begins. line[column] | 
|  | // and line[column+1] will both be replaced with '/'. It is -1 if this | 
|  | // group is not to be converted. | 
|  | column int | 
|  | lines  []string | 
|  | } | 
|  |  | 
|  | func addLine(groups *[]lineGroup, line string, column int) { | 
|  | if len(*groups) == 0 || (*groups)[len(*groups)-1].column != column { | 
|  | *groups = append(*groups, lineGroup{column, nil}) | 
|  | } | 
|  | (*groups)[len(*groups)-1].lines = append((*groups)[len(*groups)-1].lines, line) | 
|  | } | 
|  |  | 
|  | // writeLine writes |line| to |out|, followed by a newline. | 
|  | func writeLine(out *bytes.Buffer, line string) { | 
|  | out.WriteString(line) | 
|  | out.WriteByte('\n') | 
|  | } | 
|  |  | 
|  | func convertComments(path string, in []byte) []byte { | 
|  | lines := strings.Split(string(in), "\n") | 
|  |  | 
|  | // Account for the trailing newline. | 
|  | if len(lines) > 0 && len(lines[len(lines)-1]) == 0 { | 
|  | lines = lines[:len(lines)-1] | 
|  | } | 
|  |  | 
|  | // First pass: identify all comments to be converted. Group them into | 
|  | // lineGroups with the same column. | 
|  | var groups []lineGroup | 
|  |  | 
|  | // Find the license block separator. | 
|  | for len(lines) > 0 { | 
|  | line := lines[0] | 
|  | lines = lines[1:] | 
|  | addLine(&groups, line, -1) | 
|  | 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 convertible 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 { | 
|  | addLine(&groups, l, -1) | 
|  | } | 
|  | 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 { | 
|  | addLine(&groups, line, -1) | 
|  | } | 
|  | continue | 
|  | } | 
|  |  | 
|  | inComment = false | 
|  | if comment != nil { | 
|  | if idx == len(line)-2 { | 
|  | // This is a convertible 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 { | 
|  | addLine(&groups, l, column) | 
|  | } | 
|  | comment = nil | 
|  | continue | 
|  | } | 
|  |  | 
|  | // Flush the buffered comment unmodified. | 
|  | for _, l := range comment { | 
|  | addLine(&groups, l, -1) | 
|  | } | 
|  | comment = nil | 
|  | } | 
|  | idx += 2 | 
|  | } | 
|  |  | 
|  | // Parse starting from |idx|, looking for either a convertible | 
|  | // line comment or a multi-line comment. | 
|  | for { | 
|  | idx = indexFrom(line, "/*", idx) | 
|  | if idx < 0 { | 
|  | addLine(&groups, line, -1) | 
|  | break | 
|  | } | 
|  |  | 
|  | endIdx := indexFrom(line, "*/", idx) | 
|  | if endIdx < 0 { | 
|  | // The comment is, so far, eligible for conversion. | 
|  | inComment = true | 
|  | column = idx | 
|  | comment = []string{line} | 
|  | break | 
|  | } | 
|  |  | 
|  | if endIdx != len(line)-2 { | 
|  | // Continue parsing for more comments in this line. | 
|  | idx = endIdx + 2 | 
|  | continue | 
|  | } | 
|  |  | 
|  | addLine(&groups, line[:endIdx], idx) | 
|  | break | 
|  | } | 
|  | } | 
|  |  | 
|  | // Second pass: convert the lineGroups, adjusting spacing as needed. | 
|  | var out bytes.Buffer | 
|  | var lineNo int | 
|  | for _, group := range groups { | 
|  | if group.column < 0 { | 
|  | for _, line := range group.lines { | 
|  | writeLine(&out, line) | 
|  | } | 
|  | } else { | 
|  | // 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. All | 
|  | // comments within a group should be adjusted by the | 
|  | // same amount. | 
|  | var adjust string | 
|  | for _, line := range group.lines { | 
|  | if !allSpaces(line[:group.column]) && line[group.column-1] != '(' { | 
|  | if line[group.column-1] != ' ' { | 
|  | if len(adjust) < 2 { | 
|  | adjust = "  " | 
|  | } | 
|  | } else if line[group.column-2] != ' ' { | 
|  | if len(adjust) < 1 { | 
|  | adjust = " " | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | for i, line := range group.lines { | 
|  | // The OpenSSL style writes multiline block comments with a | 
|  | // blank line at the top and bottom, like so: | 
|  | // | 
|  | //   /* | 
|  | //    * Some multi-line | 
|  | //    * comment | 
|  | //    */ | 
|  | // | 
|  | // The trailing lines are already removed above, when buffering. | 
|  | // Remove the leading lines here. (The leading lines cannot be | 
|  | // removed when buffering because we may discover the comment is | 
|  | // not convertible in later lines.) | 
|  | // | 
|  | // Note the leading line cannot be easily removed if there is | 
|  | // code before it, such as the following. Skip those cases. | 
|  | // | 
|  | //   foo(); /* | 
|  | //           * Some multi-line | 
|  | //           * comment | 
|  | //           */ | 
|  | if i == 0 && allSpaces(line[:group.column]) && len(line) == group.column+2 { | 
|  | continue | 
|  | } | 
|  | newLine := fmt.Sprintf("%s%s//%s", line[:group.column], adjust, strings.TrimRight(line[group.column+2:], " ")) | 
|  | if len(newLine) > 80 { | 
|  | fmt.Fprintf(os.Stderr, "%s:%d: Line is now longer than 80 characters\n", path, lineNo+i+1) | 
|  | } | 
|  | writeLine(&out, newLine) | 
|  | } | 
|  |  | 
|  | } | 
|  | lineNo += len(group.lines) | 
|  | } | 
|  | return out.Bytes() | 
|  | } | 
|  |  | 
|  | func main() { | 
|  | for _, arg := range os.Args[1:] { | 
|  | in, err := os.ReadFile(arg) | 
|  | if err != nil { | 
|  | panic(err) | 
|  | } | 
|  | if err := os.WriteFile(arg, convertComments(arg, in), 0666); err != nil { | 
|  | panic(err) | 
|  | } | 
|  | } | 
|  | } |