You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
291 lines
7.4 KiB
291 lines
7.4 KiB
// Copyright 2014 Google Inc. All rights reserved.
|
|
//
|
|
// 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
|
|
//
|
|
// http://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.
|
|
|
|
package parser
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"text/scanner"
|
|
)
|
|
|
|
// numericStringLess compares two strings, returning a lexicographical comparison unless the first
|
|
// difference occurs in a sequence of 1 or more numeric characters, in which case it returns the
|
|
// numerical comparison of the two numbers.
|
|
func numericStringLess(a, b string) bool {
|
|
isNumeric := func(r rune) bool { return r >= '0' && r <= '9' }
|
|
isNotNumeric := func(r rune) bool { return !isNumeric(r) }
|
|
|
|
minLength := len(a)
|
|
if len(b) < minLength {
|
|
minLength = len(b)
|
|
}
|
|
|
|
byteIndex := 0
|
|
numberStartIndex := -1
|
|
|
|
var aByte, bByte byte
|
|
|
|
// Start with a byte comparison to find where the strings differ.
|
|
for ; byteIndex < minLength; byteIndex++ {
|
|
aByte, bByte = a[byteIndex], b[byteIndex]
|
|
if aByte != bByte {
|
|
break
|
|
}
|
|
byteIsNumeric := isNumeric(rune(aByte))
|
|
if numberStartIndex != -1 && !byteIsNumeric {
|
|
numberStartIndex = -1
|
|
} else if numberStartIndex == -1 && byteIsNumeric {
|
|
numberStartIndex = byteIndex
|
|
}
|
|
}
|
|
|
|
// Handle the case where we reached the end of one or both strings without finding a difference.
|
|
if byteIndex == minLength {
|
|
if len(a) < len(b) {
|
|
// Reached the end of a. a is a prefix of b.
|
|
return true
|
|
} else {
|
|
// Reached the end of b. b is a prefix of a or b is equal to a.
|
|
return false
|
|
}
|
|
}
|
|
|
|
aByteNumeric := isNumeric(rune(aByte))
|
|
bByteNumeric := isNumeric(rune(bByte))
|
|
|
|
if (aByteNumeric || bByteNumeric) && !(aByteNumeric && bByteNumeric) && numberStartIndex != -1 {
|
|
// Only one of aByte and bByte is a number, but the previous byte was a number. That means
|
|
// one is a longer number with the same prefix, which must be numerically larger. If bByte
|
|
// is a number then the number in b is numerically larger than the number in a.
|
|
return bByteNumeric
|
|
}
|
|
|
|
// If the bytes are both numbers do a numeric comparison.
|
|
if aByteNumeric && bByteNumeric {
|
|
// Extract the numbers from each string, starting from the first number after the last
|
|
// non-number. This won't be invalid utf8 because we are only looking for the bytes
|
|
//'0'-'9', which can only occur as single-byte runes in utf8.
|
|
if numberStartIndex == -1 {
|
|
numberStartIndex = byteIndex
|
|
}
|
|
aNumberString := a[numberStartIndex:]
|
|
bNumberString := b[numberStartIndex:]
|
|
|
|
// Find the first non-number in each, using the full length if there isn't one.
|
|
endANumbers := strings.IndexFunc(aNumberString, isNotNumeric)
|
|
endBNumbers := strings.IndexFunc(bNumberString, isNotNumeric)
|
|
if endANumbers == -1 {
|
|
endANumbers = len(aNumberString)
|
|
}
|
|
if endBNumbers == -1 {
|
|
endBNumbers = len(bNumberString)
|
|
}
|
|
|
|
// Convert each to an int.
|
|
aNumber, err := strconv.Atoi(aNumberString[:endANumbers])
|
|
if err != nil {
|
|
panic(fmt.Errorf("failed to convert %q from %q to number: %w",
|
|
aNumberString[:endANumbers], a, err))
|
|
}
|
|
bNumber, err := strconv.Atoi(bNumberString[:endBNumbers])
|
|
if err != nil {
|
|
panic(fmt.Errorf("failed to convert %q from %q to number: %w",
|
|
bNumberString[:endBNumbers], b, err))
|
|
}
|
|
// Do a numeric comparison.
|
|
return aNumber < bNumber
|
|
}
|
|
|
|
// At least one is not a number, do a byte comparison.
|
|
return aByte < bByte
|
|
}
|
|
|
|
func SortLists(file *File) {
|
|
for _, def := range file.Defs {
|
|
if assignment, ok := def.(*Assignment); ok {
|
|
sortListsInValue(assignment.Value, file)
|
|
} else if module, ok := def.(*Module); ok {
|
|
for _, prop := range module.Properties {
|
|
sortListsInValue(prop.Value, file)
|
|
}
|
|
}
|
|
}
|
|
sort.Sort(commentsByOffset(file.Comments))
|
|
}
|
|
|
|
func SortList(file *File, list *List) {
|
|
if !isListOfPrimitives(list.Values) {
|
|
return
|
|
}
|
|
for i := 0; i < len(list.Values); i++ {
|
|
// Find a set of values on contiguous lines
|
|
line := list.Values[i].Pos().Line
|
|
var j int
|
|
for j = i + 1; j < len(list.Values); j++ {
|
|
if list.Values[j].Pos().Line > line+1 {
|
|
break
|
|
}
|
|
line = list.Values[j].Pos().Line
|
|
}
|
|
|
|
nextPos := list.End()
|
|
if j < len(list.Values) {
|
|
nextPos = list.Values[j].Pos()
|
|
}
|
|
sortSubList(list.Values[i:j], nextPos, file)
|
|
i = j - 1
|
|
}
|
|
}
|
|
|
|
func ListIsSorted(list *List) bool {
|
|
for i := 0; i < len(list.Values); i++ {
|
|
// Find a set of values on contiguous lines
|
|
line := list.Values[i].Pos().Line
|
|
var j int
|
|
for j = i + 1; j < len(list.Values); j++ {
|
|
if list.Values[j].Pos().Line > line+1 {
|
|
break
|
|
}
|
|
line = list.Values[j].Pos().Line
|
|
}
|
|
|
|
if !subListIsSorted(list.Values[i:j]) {
|
|
return false
|
|
}
|
|
i = j - 1
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func sortListsInValue(value Expression, file *File) {
|
|
switch v := value.(type) {
|
|
case *Variable:
|
|
// Nothing
|
|
case *Operator:
|
|
sortListsInValue(v.Args[0], file)
|
|
sortListsInValue(v.Args[1], file)
|
|
case *Map:
|
|
for _, p := range v.Properties {
|
|
sortListsInValue(p.Value, file)
|
|
}
|
|
case *List:
|
|
SortList(file, v)
|
|
}
|
|
}
|
|
|
|
func sortSubList(values []Expression, nextPos scanner.Position, file *File) {
|
|
if !isListOfPrimitives(values) {
|
|
return
|
|
}
|
|
l := make([]elem, len(values))
|
|
for i, v := range values {
|
|
s, ok := v.(*String)
|
|
if !ok {
|
|
panic("list contains non-string element")
|
|
}
|
|
n := nextPos
|
|
if i < len(values)-1 {
|
|
n = values[i+1].Pos()
|
|
}
|
|
l[i] = elem{s.Value, i, v.Pos(), n}
|
|
}
|
|
|
|
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))
|
|
for i := range file.Comments {
|
|
cg := *file.Comments[i]
|
|
cg.Comments = make([]*Comment, len(cg.Comments))
|
|
for j := range file.Comments[i].Comments {
|
|
c := *file.Comments[i].Comments[j]
|
|
cg.Comments[j] = &c
|
|
}
|
|
copyComments[i] = &cg
|
|
}
|
|
|
|
curPos := values[0].Pos()
|
|
for i, e := range l {
|
|
values[i] = copyValues[e.i]
|
|
values[i].(*String).LiteralPos = curPos
|
|
for j, c := range copyComments {
|
|
if c.Pos().Offset > e.pos.Offset && c.Pos().Offset < e.nextPos.Offset {
|
|
file.Comments[j].Comments[0].Slash.Line = curPos.Line
|
|
file.Comments[j].Comments[0].Slash.Offset += values[i].Pos().Offset - e.pos.Offset
|
|
}
|
|
}
|
|
|
|
curPos.Offset += e.nextPos.Offset - e.pos.Offset
|
|
curPos.Line++
|
|
}
|
|
}
|
|
|
|
func subListIsSorted(values []Expression) bool {
|
|
if !isListOfPrimitives(values) {
|
|
return true
|
|
}
|
|
prev := ""
|
|
for _, v := range values {
|
|
s, ok := v.(*String)
|
|
if !ok {
|
|
panic("list contains non-string element")
|
|
}
|
|
if prev != "" && numericStringLess(s.Value, prev) {
|
|
return false
|
|
}
|
|
prev = s.Value
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
type elem struct {
|
|
s string
|
|
i int
|
|
pos scanner.Position
|
|
nextPos scanner.Position
|
|
}
|
|
|
|
type commentsByOffset []*CommentGroup
|
|
|
|
func (l commentsByOffset) Len() int {
|
|
return len(l)
|
|
}
|
|
|
|
func (l commentsByOffset) Less(i, j int) bool {
|
|
return l[i].Pos().Offset < l[j].Pos().Offset
|
|
}
|
|
|
|
func (l commentsByOffset) Swap(i, j int) {
|
|
l[i], l[j] = l[j], l[i]
|
|
}
|
|
|
|
func isListOfPrimitives(values []Expression) bool {
|
|
if len(values) == 0 {
|
|
return true
|
|
}
|
|
switch values[0].Type() {
|
|
case BoolType, StringType, Int64Type:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|