diff --git a/g/os/gview/internal/fmtsort/sort.go b/g/os/gview/internal/fmtsort/sort.go deleted file mode 100644 index c959cbee1..000000000 --- a/g/os/gview/internal/fmtsort/sort.go +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package fmtsort provides a general stable ordering mechanism -// for maps, on behalf of the fmt and text/template packages. -// It is not guaranteed to be efficient and works only for types -// that are valid map keys. -package fmtsort - -import ( - "reflect" - "sort" -) - -// Note: Throughout this package we avoid calling reflect.Value.Interface as -// it is not always legal to do so and it's easier to avoid the issue than to face it. - -// SortedMap represents a map's keys and values. The keys and values are -// aligned in index order: Value[i] is the value in the map corresponding to Key[i]. -type SortedMap struct { - Key []reflect.Value - Value []reflect.Value -} - -func (o *SortedMap) Len() int { return len(o.Key) } -func (o *SortedMap) Less(i, j int) bool { return compare(o.Key[i], o.Key[j]) < 0 } -func (o *SortedMap) Swap(i, j int) { - o.Key[i], o.Key[j] = o.Key[j], o.Key[i] - o.Value[i], o.Value[j] = o.Value[j], o.Value[i] -} - -// Sort accepts a map and returns a SortedMap that has the same keys and -// values but in a stable sorted order according to the keys, modulo issues -// raised by unorderable key values such as NaNs. -// -// The ordering rules are more general than with Go's < operator: -// -// - when applicable, nil compares low -// - ints, floats, and strings order by < -// - NaN compares less than non-NaN floats -// - bool compares false before true -// - complex compares real, then imag -// - pointers compare by machine address -// - channel values compare by machine address -// - structs compare each field in turn -// - arrays compare each element in turn. -// Otherwise identical arrays compare by length. -// - interface values compare first by reflect.Type describing the concrete type -// and then by concrete value as described in the previous rules. -// -func Sort(mapValue reflect.Value) *SortedMap { - if mapValue.Type().Kind() != reflect.Map { - return nil - } - key := make([]reflect.Value, mapValue.Len()) - value := make([]reflect.Value, len(key)) - iter := mapValue.MapRange() - for i := 0; iter.Next(); i++ { - key[i] = iter.Key() - value[i] = iter.Value() - } - sorted := &SortedMap{ - Key: key, - Value: value, - } - sort.Stable(sorted) - return sorted -} - -// compare compares two values of the same type. It returns -1, 0, 1 -// according to whether a > b (1), a == b (0), or a < b (-1). -// If the types differ, it returns -1. -// See the comment on Sort for the comparison rules. -func compare(aVal, bVal reflect.Value) int { - aType, bType := aVal.Type(), bVal.Type() - if aType != bType { - return -1 // No good answer possible, but don't return 0: they're not equal. - } - switch aVal.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - a, b := aVal.Int(), bVal.Int() - switch { - case a < b: - return -1 - case a > b: - return 1 - default: - return 0 - } - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - a, b := aVal.Uint(), bVal.Uint() - switch { - case a < b: - return -1 - case a > b: - return 1 - default: - return 0 - } - case reflect.String: - a, b := aVal.String(), bVal.String() - switch { - case a < b: - return -1 - case a > b: - return 1 - default: - return 0 - } - case reflect.Float32, reflect.Float64: - return floatCompare(aVal.Float(), bVal.Float()) - case reflect.Complex64, reflect.Complex128: - a, b := aVal.Complex(), bVal.Complex() - if c := floatCompare(real(a), real(b)); c != 0 { - return c - } - return floatCompare(imag(a), imag(b)) - case reflect.Bool: - a, b := aVal.Bool(), bVal.Bool() - switch { - case a == b: - return 0 - case a: - return 1 - default: - return -1 - } - case reflect.Ptr: - a, b := aVal.Pointer(), bVal.Pointer() - switch { - case a < b: - return -1 - case a > b: - return 1 - default: - return 0 - } - case reflect.Chan: - if c, ok := nilCompare(aVal, bVal); ok { - return c - } - ap, bp := aVal.Pointer(), bVal.Pointer() - switch { - case ap < bp: - return -1 - case ap > bp: - return 1 - default: - return 0 - } - case reflect.Struct: - for i := 0; i < aVal.NumField(); i++ { - if c := compare(aVal.Field(i), bVal.Field(i)); c != 0 { - return c - } - } - return 0 - case reflect.Array: - for i := 0; i < aVal.Len(); i++ { - if c := compare(aVal.Index(i), bVal.Index(i)); c != 0 { - return c - } - } - return 0 - case reflect.Interface: - if c, ok := nilCompare(aVal, bVal); ok { - return c - } - c := compare(reflect.ValueOf(aType), reflect.ValueOf(bType)) - if c != 0 { - return c - } - return compare(aVal.Elem(), bVal.Elem()) - default: - // Certain types cannot appear as keys (maps, funcs, slices), but be explicit. - panic("bad type in compare: " + aType.String()) - } -} - -// nilCompare checks whether either value is nil. If not, the boolean is false. -// If either value is nil, the boolean is true and the integer is the comparison -// value. The comparison is defined to be 0 if both are nil, otherwise the one -// nil value compares low. Both arguments must represent a chan, func, -// interface, map, pointer, or slice. -func nilCompare(aVal, bVal reflect.Value) (int, bool) { - if aVal.IsNil() { - if bVal.IsNil() { - return 0, true - } - return -1, true - } - if bVal.IsNil() { - return 1, true - } - return 0, false -} - -// floatCompare compares two floating-point values. NaNs compare low. -func floatCompare(a, b float64) int { - switch { - case isNaN(a): - return -1 // No good answer if b is a NaN so don't bother checking. - case isNaN(b): - return 1 - case a < b: - return -1 - case a > b: - return 1 - } - return 0 -} - -func isNaN(a float64) bool { - return a != a -} diff --git a/g/os/gview/internal/text/template/doc.go b/g/os/gview/internal/text/template/doc.go index 0179dec5c..4b243067b 100644 --- a/g/os/gview/internal/text/template/doc.go +++ b/g/os/gview/internal/text/template/doc.go @@ -142,9 +142,7 @@ An argument is a simple value, denoted by one of the following. - A boolean, string, character, integer, floating-point, imaginary or complex constant in Go syntax. These behave like Go's untyped - constants. Note that, as in Go, whether a large integer constant - overflows when assigned or passed to a function can depend on whether - the host machine's ints are 32 or 64 bits. + constants. - The keyword nil, representing an untyped Go nil. - The character '.' (period): . diff --git a/g/os/gview/internal/text/template/exec.go b/g/os/gview/internal/text/template/exec.go index 1920e2088..fe1488fbd 100644 --- a/g/os/gview/internal/text/template/exec.go +++ b/g/os/gview/internal/text/template/exec.go @@ -7,12 +7,12 @@ package template import ( "bytes" "fmt" - "github.com/gogf/gf/g/os/gview/internal/fmtsort" "io" "reflect" "runtime" + "sort" "strings" - "text/template/parse" + "github.com/gogf/gf/g/os/gview/internal/text/template/parse" ) // maxExecDepth specifies the maximum stack depth of templates within @@ -102,7 +102,7 @@ func (s *state) at(node parse.Node) { // doublePercent returns the string with %'s replaced by %%, if necessary, // so it can be used safely inside a Printf format string. func doublePercent(str string) string { - return strings.ReplaceAll(str, "%", "%%") + return strings.Replace(str, "%", "%%", -1) } // TODO: It would be nice if ExecError was more broken down, but @@ -362,9 +362,8 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { if val.Len() == 0 { break } - om := fmtsort.Sort(val) - for i, key := range om.Key { - oneIteration(key, om.Value[i]) + for _, key := range sortKeys(val.MapKeys()) { + oneIteration(key, val.MapIndex(key)) } return case reflect.Chan: @@ -693,13 +692,13 @@ func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, a } argv[i] = s.validateType(final, t) } - v, err := safeCall(fun, argv) - // If we have an error that is not nil, stop execution and return that - // error to the caller. - if err != nil { + result := fun.Call(argv) + // If we have an error that is not nil, stop execution and return that error to the caller. + if len(result) == 2 && !result[1].IsNil() { s.at(node) - s.errorf("error calling %s: %v", name, err) + s.errorf("error calling %s: %s", name, result[1].Interface().(error)) } + v := result[0] if v.Type() == reflectValueType { v = v.Interface().(reflect.Value) } @@ -959,3 +958,29 @@ func printableValue(v reflect.Value) (interface{}, bool) { } return v.Interface(), true } + +// sortKeys sorts (if it can) the slice of reflect.Values, which is a slice of map keys. +func sortKeys(v []reflect.Value) []reflect.Value { + if len(v) <= 1 { + return v + } + switch v[0].Kind() { + case reflect.Float32, reflect.Float64: + sort.Slice(v, func(i, j int) bool { + return v[i].Float() < v[j].Float() + }) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + sort.Slice(v, func(i, j int) bool { + return v[i].Int() < v[j].Int() + }) + case reflect.String: + sort.Slice(v, func(i, j int) bool { + return v[i].String() < v[j].String() + }) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + sort.Slice(v, func(i, j int) bool { + return v[i].Uint() < v[j].Uint() + }) + } + return v +} diff --git a/g/os/gview/internal/text/template/funcs.go b/g/os/gview/internal/text/template/funcs.go index 72d3f6669..abddfa114 100644 --- a/g/os/gview/internal/text/template/funcs.go +++ b/g/os/gview/internal/text/template/funcs.go @@ -65,7 +65,7 @@ func createValueFuncs(funcMap FuncMap) map[string]reflect.Value { func addValueFuncs(out map[string]reflect.Value, in FuncMap) { for name, fn := range in { if !goodName(name) { - panic(fmt.Errorf("function name %q is not a valid identifier", name)) + panic(fmt.Errorf("function name %s is not a valid identifier", name)) } v := reflect.ValueOf(fn) if v.Kind() != reflect.Func { @@ -275,26 +275,11 @@ func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) { return reflect.Value{}, fmt.Errorf("arg %d: %s", i, err) } } - return safeCall(v, argv) -} - -// safeCall runs fun.Call(args), and returns the resulting value and error, if -// any. If the call panics, the panic value is returned as an error. -func safeCall(fun reflect.Value, args []reflect.Value) (val reflect.Value, err error) { - defer func() { - if r := recover(); r != nil { - if e, ok := r.(error); ok { - err = e - } else { - err = fmt.Errorf("%v", r) - } - } - }() - ret := fun.Call(args) - if len(ret) == 2 && !ret[1].IsNil() { - return ret[0], ret[1].Interface().(error) + result := v.Call(argv) + if len(result) == 2 && !result[1].IsNil() { + return result[0], result[1].Interface().(error) } - return ret[0], nil + return result[0], nil } // Boolean logic. diff --git a/g/os/gview/internal/text/template/parse/lex.go b/g/os/gview/internal/text/template/parse/lex.go index 94a676c57..fc259f351 100644 --- a/g/os/gview/internal/text/template/parse/lex.go +++ b/g/os/gview/internal/text/template/parse/lex.go @@ -117,7 +117,6 @@ type lexer struct { items chan item // channel of scanned items parenDepth int // nesting depth of ( ) exprs line int // 1+number of newlines seen - startLine int // start line of this item } // next returns the next rune in the input. @@ -153,16 +152,19 @@ func (l *lexer) backup() { // emit passes an item back to the client. func (l *lexer) emit(t itemType) { - l.items <- item{t, l.start, l.input[l.start:l.pos], l.startLine} + l.items <- item{t, l.start, l.input[l.start:l.pos], l.line} + // Some items contain text internally. If so, count their newlines. + switch t { + case itemText, itemRawString, itemLeftDelim, itemRightDelim: + l.line += strings.Count(l.input[l.start:l.pos], "\n") + } l.start = l.pos - l.startLine = l.line } // ignore skips over the pending input before this point. func (l *lexer) ignore() { l.line += strings.Count(l.input[l.start:l.pos], "\n") l.start = l.pos - l.startLine = l.line } // accept consumes the next rune if it's from the valid set. @@ -184,7 +186,7 @@ func (l *lexer) acceptRun(valid string) { // errorf returns an error token and terminates the scan by passing // back a nil pointer that will be the next state, terminating l.nextItem. func (l *lexer) errorf(format string, args ...interface{}) stateFn { - l.items <- item{itemError, l.start, fmt.Sprintf(format, args...), l.startLine} + l.items <- item{itemError, l.start, fmt.Sprintf(format, args...), l.line} return nil } @@ -216,7 +218,6 @@ func lex(name, input, left, right string) *lexer { rightDelim: right, items: make(chan item), line: 1, - startLine: 1, } go l.run() return l @@ -251,17 +252,16 @@ func lexText(l *lexer) stateFn { } l.pos -= trimLength if l.pos > l.start { - l.line += strings.Count(l.input[l.start:l.pos], "\n") l.emit(itemText) } l.pos += trimLength l.ignore() return lexLeftDelim + } else { + l.pos = Pos(len(l.input)) } - l.pos = Pos(len(l.input)) // Correctly reached EOF. if l.pos > l.start { - l.line += strings.Count(l.input[l.start:l.pos], "\n") l.emit(itemText) } l.emit(itemEOF) @@ -609,10 +609,14 @@ Loop: // lexRawQuote scans a raw quoted string. func lexRawQuote(l *lexer) stateFn { + startLine := l.line Loop: for { switch l.next() { case eof: + // Restore line number to location of opening quote. + // We will error out so it's ok just to overwrite the field. + l.line = startLine return l.errorf("unterminated raw quoted string") case '`': break Loop diff --git a/g/os/gview/internal/text/template/parse/parse.go b/g/os/gview/internal/text/template/parse/parse.go index 7c35b0ff3..cb9b44e9d 100644 --- a/g/os/gview/internal/text/template/parse/parse.go +++ b/g/os/gview/internal/text/template/parse/parse.go @@ -148,6 +148,9 @@ func (t *Tree) ErrorContext(n Node) (location, context string) { } lineNum := 1 + strings.Count(text, "\n") context = n.String() + if len(context) > 20 { + context = fmt.Sprintf("%.20s...", context) + } return fmt.Sprintf("%s:%d:%d", tree.ParseName, lineNum, byteNum), context } @@ -380,44 +383,46 @@ func (t *Tree) action() (n Node) { // Pipeline: // declarations? command ('|' command)* func (t *Tree) pipeline(context string) (pipe *PipeNode) { + decl := false + var vars []*VariableNode token := t.peekNonSpace() - pipe = t.newPipeline(token.pos, token.line, nil) + pos := token.pos // Are there declarations or assignments? -decls: - if v := t.peekNonSpace(); v.typ == itemVariable { - t.next() - // Since space is a token, we need 3-token look-ahead here in the worst case: - // in "$x foo" we need to read "foo" (as opposed to ":=") to know that $x is an - // argument variable rather than a declaration. So remember the token - // adjacent to the variable so we can push it back if necessary. - tokenAfterVariable := t.peek() - next := t.peekNonSpace() - switch { - case next.typ == itemAssign, next.typ == itemDeclare: - pipe.IsAssign = next.typ == itemAssign - t.nextNonSpace() - pipe.Decl = append(pipe.Decl, t.newVariable(v.pos, v.val)) - t.vars = append(t.vars, v.val) - case next.typ == itemChar && next.val == ",": - t.nextNonSpace() - pipe.Decl = append(pipe.Decl, t.newVariable(v.pos, v.val)) - t.vars = append(t.vars, v.val) - if context == "range" && len(pipe.Decl) < 2 { - switch t.peekNonSpace().typ { - case itemVariable, itemRightDelim, itemRightParen: - // second initialized variable in a range pipeline - goto decls - default: - t.errorf("range can only initialize variables") + for { + if v := t.peekNonSpace(); v.typ == itemVariable { + t.next() + // Since space is a token, we need 3-token look-ahead here in the worst case: + // in "$x foo" we need to read "foo" (as opposed to ":=") to know that $x is an + // argument variable rather than a declaration. So remember the token + // adjacent to the variable so we can push it back if necessary. + tokenAfterVariable := t.peek() + next := t.peekNonSpace() + switch { + case next.typ == itemAssign, next.typ == itemDeclare, + next.typ == itemChar && next.val == ",": + t.nextNonSpace() + variable := t.newVariable(v.pos, v.val) + vars = append(vars, variable) + t.vars = append(t.vars, v.val) + if next.typ == itemDeclare { + decl = true } + if next.typ == itemChar && next.val == "," { + if context == "range" && len(vars) < 2 { + continue + } + t.errorf("too many declarations in %s", context) + } + case tokenAfterVariable.typ == itemSpace: + t.backup3(v, tokenAfterVariable) + default: + t.backup2(v) } - t.errorf("too many declarations in %s", context) - case tokenAfterVariable.typ == itemSpace: - t.backup3(v, tokenAfterVariable) - default: - t.backup2(v) } + break } + pipe = t.newPipeline(pos, token.line, vars) + pipe.IsAssign = !decl for { switch token := t.nextNonSpace(); token.typ { case itemRightDelim, itemRightParen: diff --git a/g/os/gview/internal/text/template/template.go b/g/os/gview/internal/text/template/template.go index 41cdd5682..16d95e8e6 100644 --- a/g/os/gview/internal/text/template/template.go +++ b/g/os/gview/internal/text/template/template.go @@ -7,7 +7,7 @@ package template import ( "reflect" "sync" - "text/template/parse" + "github.com/gogf/gf/g/os/gview/internal/text/template/parse" ) // common holds the information shared by related templates. diff --git a/g/os/gview/internal/text/text.go b/g/os/gview/internal/text/text.go index 2102ce4a3..022b3a513 100644 --- a/g/os/gview/internal/text/text.go +++ b/g/os/gview/internal/text/text.go @@ -1,3 +1,3 @@ -// from golang-1.12 text/template +// from golang-1.11.2 // 1. remove "" when template variable does not exist; package text