bpmodify: support numerical sort (#332)

bpmodify sorts touched list respecting numbers embedded in strings. For
example, "foo-2" comes before "foo-10".

Test: bpmodify_test.go
Change-Id: If2fe9bc871a463a47dd3c0b52982b34c9fde05f0

Co-authored-by: Jooyung Han <jooyung@google.com>
This commit is contained in:
Jooyung Han 2020-12-15 04:30:48 +09:00 committed by GitHub
parent cfa2e91dc4
commit 53e92a0de6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 110 additions and 17 deletions

View file

@ -206,6 +206,48 @@ var testCases = []struct {
"",
"dep3,dep4",
},
{
`
cc_foo {
name: "foo",
versions: ["1", "2"],
}
`,
`
cc_foo {
name: "foo",
versions: [
"1",
"2",
"10",
],
}
`,
"versions",
"10",
"",
},
{
`
cc_foo {
name: "foo",
deps: ["bar-v1-bar", "bar-v2-bar"],
}
`,
`
cc_foo {
name: "foo",
deps: [
"bar-v1-bar",
"bar-v2-bar",
"bar-v10-bar",
],
}
`,
"deps",
"bar-v10-bar",
"",
},
}
func simplifyModuleDefinition(def string) string {

View file

@ -15,10 +15,73 @@
package parser
import (
"fmt"
"sort"
"strconv"
"strings"
"text/scanner"
)
func numericStringLess(a, b string) bool {
byteIndex := 0
// Start with a byte comparison to find where the strings differ
for ; byteIndex < len(a) && byteIndex < len(b); byteIndex++ {
if a[byteIndex] != b[byteIndex] {
break
}
}
if byteIndex == len(a) && byteIndex != len(b) {
// Reached the end of a. a is a prefix of b.
return true
} else if byteIndex == len(b) {
// Reached the end of b. b is a prefix of a or b is equal to a.
return false
}
// Save the first differing bytes in case we have to fall back to a byte comparison.
aDifferingByte := a[byteIndex]
bDifferingByte := b[byteIndex]
// Save the differing suffixes of the strings. This may be invalid utf8 if the first
// rune was cut, but that's fine because are only looking for the bytes '0'-'9', which
// can only occur as single-byte runes in utf8.
aDifference := a[byteIndex:]
bDifference := b[byteIndex:]
isNumeric := func(r rune) bool { return r >= '0' && r <= '9' }
isNotNumeric := func(r rune) bool { return !isNumeric(r) }
// If the first runes are both numbers do a numeric comparison.
if isNumeric(rune(aDifferingByte)) && isNumeric(rune(bDifferingByte)) {
// Find the first non-number in each, using the full length if there isn't one.
endANumbers := strings.IndexFunc(aDifference, isNotNumeric)
endBNumbers := strings.IndexFunc(bDifference, isNotNumeric)
if endANumbers == -1 {
endANumbers = len(aDifference)
}
if endBNumbers == -1 {
endBNumbers = len(bDifference)
}
// Convert each to an int.
aNumber, err := strconv.Atoi(aDifference[:endANumbers])
if err != nil {
panic(fmt.Errorf("failed to convert %q from %q to number: %w",
aDifference[:endANumbers], a, err))
}
bNumber, err := strconv.Atoi(bDifference[:endBNumbers])
if err != nil {
panic(fmt.Errorf("failed to convert %q from %q to number: %w",
bDifference[:endBNumbers], b, err))
}
// Do a numeric comparison.
return aNumber < bNumber
}
// At least one is not a number, do a byte comparison.
return aDifferingByte < bDifferingByte
}
func SortLists(file *File) {
for _, def := range file.Defs {
if assignment, ok := def.(*Assignment); ok {
@ -97,7 +160,7 @@ func sortSubList(values []Expression, nextPos scanner.Position, file *File) {
if !isListOfPrimitives(values) {
return
}
l := make(elemList, len(values))
l := make([]elem, len(values))
for i, v := range values {
s, ok := v.(*String)
if !ok {
@ -110,7 +173,9 @@ func sortSubList(values []Expression, nextPos scanner.Position, file *File) {
l[i] = elem{s.Value, i, v.Pos(), n}
}
sort.Sort(l)
sort.SliceStable(l, func(i, j int) bool {
return numericStringLess(l[i].s, l[j].s)
})
copyValues := append([]Expression{}, values...)
copyComments := make([]*CommentGroup, len(file.Comments))
@ -150,7 +215,7 @@ func subListIsSorted(values []Expression) bool {
if !ok {
panic("list contains non-string element")
}
if prev > s.Value {
if prev != "" && numericStringLess(s.Value, prev) {
return false
}
prev = s.Value
@ -166,20 +231,6 @@ type elem struct {
nextPos scanner.Position
}
type elemList []elem
func (l elemList) Len() int {
return len(l)
}
func (l elemList) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
func (l elemList) Less(i, j int) bool {
return l[i].s < l[j].s
}
type commentsByOffset []*CommentGroup
func (l commentsByOffset) Len() int {