1
0
mirror of https://github.com/danog/liquid.git synced 2025-01-22 17:41:11 +01:00

Move generics to own package

This commit is contained in:
Oliver Steele 2017-06-27 10:28:39 -04:00
parent 612f456745
commit f52d00f836
7 changed files with 298 additions and 268 deletions

View File

@ -3,9 +3,12 @@ package expressions
import (
"fmt"
"reflect"
"github.com/osteele/liquid/generics"
)
func init() {
// This allows adding and removing references to fmt in the rules below,
// without having to edit the import statement to avoid erorrs each time.
_ = fmt.Sprint("")
}
@ -79,25 +82,24 @@ expr1:
rel:
expr1
| expr EQ expr {
a, b := $1, $3
fa, fb := $1, $3
$$ = func(ctx Context) interface{} {
aref, bref := reflect.ValueOf(a(ctx)), reflect.ValueOf(b(ctx))
return GenericCompare(aref, bref) == 0
a, b := fa(ctx), fb(ctx)
return generics.Equal(a, b)
}
}
| expr '<' expr {
a, b := $1, $3
fa, fb := $1, $3
$$ = func(ctx Context) interface{} {
aref, bref := reflect.ValueOf(a(ctx)), reflect.ValueOf(b(ctx))
return GenericCompare(aref, bref) < 0
a, b := fa(ctx), fb(ctx)
return generics.Less(a, b)
}
}
| expr '>' expr {
a, b := $1, $3
fa, fb := $1, $3
$$ = func(ctx Context) interface{} {
aref, bref := reflect.ValueOf(a(ctx)), reflect.ValueOf(b(ctx))
return GenericCompare(aref, bref) > 0
a, b := fa(ctx), fb(ctx)
return generics.Less(b, a)
}
}
;

View File

@ -7,6 +7,7 @@ import (
"strings"
"github.com/osteele/liquid/errors"
"github.com/osteele/liquid/generics"
)
type InterpreterError string
@ -33,9 +34,9 @@ func sortFilter(in []interface{}, key interface{}) []interface{} {
out[i] = v
}
if key == nil {
genericSort(out)
generics.Sort(out)
} else {
sortByProperty(out, key.(string))
generics.SortByProperty(out, key.(string))
}
return out
}
@ -87,7 +88,7 @@ func makeFilter(f valueFn, name string, param valueFn) valueFn {
defer func() {
if r := recover(); r != nil {
switch e := r.(type) {
case genericError:
case generics.GenericError:
panic(InterpreterError(e.Error()))
default:
// fmt.Println(string(debug.Stack()))
@ -99,7 +100,7 @@ func makeFilter(f valueFn, name string, param valueFn) valueFn {
if param != nil {
args = append(args, param(ctx))
}
out, err := genericApply(fr, args)
out, err := generics.Apply(fr, args)
if err != nil {
panic(err)
}

View File

@ -1,233 +0,0 @@
package expressions
import (
"fmt"
"reflect"
"sort"
)
type genericError string
func (e genericError) Error() string { return string(e) }
func genericErrorf(format string, a ...interface{}) error {
return genericError(fmt.Sprintf(format, a...))
}
type genericSortable []interface{}
func genericSort(data []interface{}) {
sort.Sort(genericSortable(data))
}
// Len is part of sort.Interface.
func (s genericSortable) Len() int {
return len(s)
}
// Swap is part of sort.Interface.
func (s genericSortable) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Less is part of sort.Interface.
func (s genericSortable) Less(i, j int) bool {
return genericSameTypeCompare(s[i], s[j]) < 0
}
func sortByProperty(data []interface{}, key string) {
sort.Sort(sortableByProperty{data, key})
}
type sortableByProperty struct {
data []interface{}
key string
}
// Len is part of sort.Interface.
func (s sortableByProperty) Len() int {
return len(s.data)
}
// Swap is part of sort.Interface.
func (s sortableByProperty) Swap(i, j int) {
data := s.data
data[i], data[j] = data[j], data[i]
}
// Less is part of sort.Interface.
func (s sortableByProperty) Less(i, j int) bool {
// index returns the value at the s.key, if in is a map that contains this key
index := func(in interface{}) interface{} {
rt := reflect.ValueOf(in)
if rt.Kind() == reflect.Map && rt.Type().Key().Kind() == reflect.String {
return rt.MapIndex(reflect.ValueOf(s.key)).Interface()
}
return nil
}
a, b := index(s.data[i]), index(s.data[j])
// TODO implement nil-first vs. nil last
switch {
case a == nil:
return true
case b == nil:
return false
default:
// TODO relax same type requirement
return genericSameTypeCompare(a, b) < 0
}
}
// genericApply applies a function to arguments, converting them as necessary.
// The conversion follows Liquid semantics, which are more aggressive than
// Go conversion. The function should return one or two values; the second value,
// if present, should be an error.
func genericApply(fn reflect.Value, args []interface{}) (interface{}, error) {
in := convertArguments(fn, args)
outs := fn.Call(in)
if len(outs) > 1 && outs[1].Interface() != nil {
switch e := outs[1].Interface().(type) {
case error:
return nil, e
default:
panic(e)
}
}
return outs[0].Interface(), nil
}
// Convert val to the type. This is a more aggressive conversion, that will
// recursively create new map and slice values as necessary. It doesn't
// handle circular references.
func convertType(val interface{}, t reflect.Type) reflect.Value {
r := reflect.ValueOf(val)
if r.Type().ConvertibleTo(t) {
return r.Convert(t)
}
if reflect.PtrTo(r.Type()) == t {
return reflect.ValueOf(&val)
}
switch t.Kind() {
case reflect.Slice:
if r.Kind() != reflect.Array && r.Kind() != reflect.Slice {
break
}
x := reflect.MakeSlice(t, 0, r.Len())
for i := 0; i < r.Len(); i++ {
c := convertType(r.Index(i).Interface(), t.Elem())
x = reflect.Append(x, c)
}
return x
}
panic(genericErrorf("convertType: can't convert %#v<%s> to %v", val, r.Type(), t))
}
// Convert args to match the input types of function fn.
func convertArguments(fn reflect.Value, in []interface{}) []reflect.Value {
rt := fn.Type()
out := make([]reflect.Value, rt.NumIn())
for i, arg := range in {
if i < rt.NumIn() {
out[i] = convertType(arg, rt.In(i))
}
}
for i := len(in); i < rt.NumIn(); i++ {
out[i] = reflect.Zero(rt.In(i))
}
return out
}
func genericSameTypeCompare(av, bv interface{}) int {
a, b := reflect.ValueOf(av), reflect.ValueOf(bv)
if a.Kind() != b.Kind() {
panic(fmt.Errorf("genericSameTypeCompare called on different types: %v and %v", a, b))
}
if a == b {
return 0
}
switch a.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if a.Int() < b.Int() {
return -1
}
case reflect.Float32, reflect.Float64:
if a.Float() < b.Float() {
return -1
}
case reflect.String:
if a.String() < b.String() {
return -1
}
default:
panic(genericErrorf("unimplemented generic same-type comparison for %v and %v", a, b))
}
return 1
}
func GenericCompare(a, b reflect.Value) int {
if a.Interface() == b.Interface() {
return 0
}
ak, bk := a.Kind(), b.Kind()
// _ = ak.Convert
switch a.Kind() {
case reflect.Bool:
if b.Kind() == reflect.Bool {
switch {
case a.Bool() && b.Bool():
return 0
case a.Bool():
return 1
case b.Bool():
return -1
default:
return 0
}
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if hasIntKind(b) {
if a.Int() < b.Int() {
return -1
}
if a.Int() > b.Int() {
return 1
}
return 0
}
if hasFloatKind(b) {
return GenericCompare(reflect.ValueOf(float64(a.Int())), b)
}
case reflect.Float32, reflect.Float64:
if hasIntKind(b) {
b = reflect.ValueOf(float64(b.Int()))
}
if hasFloatKind(b) {
if a.Float() < b.Float() {
return -1
}
if a.Float() > b.Float() {
return 1
}
return 0
}
}
panic(genericErrorf("unimplemented: comparison of %v<%s> with %v<%s>", a, ak, b, bk))
}
func hasIntKind(n reflect.Value) bool {
switch n.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return true
default:
return false
}
}
func hasFloatKind(n reflect.Value) bool {
switch n.Kind() {
case reflect.Float32, reflect.Float64:
return true
default:
return false
}
}

View File

@ -6,14 +6,17 @@ import __yyfmt__ "fmt"
//line expressions.y:2
import (
"fmt"
"github.com/osteele/liquid/generics"
"reflect"
)
func init() {
// This allows adding and removing references to fmt in the rules below,
// without having to edit the import statement to avoid erorrs each time.
_ = fmt.Sprint("")
}
//line expressions.y:13
//line expressions.y:16
type yySymType struct {
yys int
name string
@ -466,13 +469,13 @@ yydefault:
case 1:
yyDollar = yyS[yypt-2 : yypt+1]
//line expressions.y:27
//line expressions.y:30
{
yylex.(*lexer).val = yyDollar[1].f
}
case 2:
yyDollar = yyS[yypt-5 : yypt+1]
//line expressions.y:28
//line expressions.y:31
{
name, expr := yyDollar[2].name, yyDollar[4].f
yylex.(*lexer).val = func(ctx Context) interface{} {
@ -482,21 +485,21 @@ yydefault:
}
case 3:
yyDollar = yyS[yypt-1 : yypt+1]
//line expressions.y:37
//line expressions.y:40
{
val := yyDollar[1].val
yyVAL.f = func(_ Context) interface{} { return val }
}
case 4:
yyDollar = yyS[yypt-1 : yypt+1]
//line expressions.y:38
//line expressions.y:41
{
name := yyDollar[1].name
yyVAL.f = func(ctx Context) interface{} { return ctx.Get(name) }
}
case 5:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:39
//line expressions.y:42
{
e, attr := yyDollar[1].f, yyDollar[3].name
yyVAL.f = func(ctx Context) interface{} {
@ -514,7 +517,7 @@ yydefault:
}
case 6:
yyDollar = yyS[yypt-4 : yypt+1]
//line expressions.y:54
//line expressions.y:57
{
e, i := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
@ -535,44 +538,44 @@ yydefault:
}
case 8:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:75
//line expressions.y:78
{
yyVAL.f = makeFilter(yyDollar[1].f, yyDollar[3].name, nil)
}
case 9:
yyDollar = yyS[yypt-4 : yypt+1]
//line expressions.y:76
//line expressions.y:79
{
yyVAL.f = makeFilter(yyDollar[1].f, yyDollar[3].name, yyDollar[4].f)
}
case 11:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:81
//line expressions.y:84
{
a, b := yyDollar[1].f, yyDollar[3].f
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
aref, bref := reflect.ValueOf(a(ctx)), reflect.ValueOf(b(ctx))
return GenericCompare(aref, bref) == 0
a, b := fa(ctx), fb(ctx)
return a == b
}
}
case 12:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:88
//line expressions.y:91
{
a, b := yyDollar[1].f, yyDollar[3].f
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
aref, bref := reflect.ValueOf(a(ctx)), reflect.ValueOf(b(ctx))
return GenericCompare(aref, bref) < 0
a, b := fa(ctx), fb(ctx)
return generics.Less(a, b)
}
}
case 13:
yyDollar = yyS[yypt-3 : yypt+1]
//line expressions.y:95
//line expressions.y:98
{
a, b := yyDollar[1].f, yyDollar[3].f
fa, fb := yyDollar[1].f, yyDollar[3].f
yyVAL.f = func(ctx Context) interface{} {
aref, bref := reflect.ValueOf(a(ctx)), reflect.ValueOf(b(ctx))
return GenericCompare(aref, bref) > 0
a, b := fa(ctx), fb(ctx)
return generics.Less(b, a)
}
}
}

111
generics/compare.go Normal file
View File

@ -0,0 +1,111 @@
package generics
import (
"fmt"
"reflect"
)
// Equal returns a bool indicating whether a == b after conversion.
func Equal(a, b interface{}) bool {
return genericCompare(reflect.ValueOf(a), reflect.ValueOf(b)) == 0
}
// Less returns a bool indicating whether a < b.
func Less(a, b interface{}) bool {
return genericCompare(reflect.ValueOf(a), reflect.ValueOf(b)) < 0
}
func genericSameTypeCompare(av, bv interface{}) int {
a, b := reflect.ValueOf(av), reflect.ValueOf(bv)
if a.Kind() != b.Kind() {
panic(fmt.Errorf("genericSameTypeCompare called on different types: %v and %v", a, b))
}
if a == b {
return 0
}
switch a.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if a.Int() < b.Int() {
return -1
}
case reflect.Float32, reflect.Float64:
if a.Float() < b.Float() {
return -1
}
case reflect.String:
if a.String() < b.String() {
return -1
}
default:
panic(genericErrorf("unimplemented generic same-type comparison for %v and %v", a, b))
}
return 1
}
func genericCompare(a, b reflect.Value) int {
if a.Interface() == b.Interface() {
return 0
}
ak, bk := a.Kind(), b.Kind()
// _ = ak.Convert
switch a.Kind() {
case reflect.Bool:
if b.Kind() == reflect.Bool {
switch {
case a.Bool() && b.Bool():
return 0
case a.Bool():
return 1
case b.Bool():
return -1
default:
return 0
}
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if hasIntKind(b) {
if a.Int() < b.Int() {
return -1
}
if a.Int() > b.Int() {
return 1
}
return 0
}
if hasFloatKind(b) {
return genericCompare(reflect.ValueOf(float64(a.Int())), b)
}
case reflect.Float32, reflect.Float64:
if hasIntKind(b) {
b = reflect.ValueOf(float64(b.Int()))
}
if hasFloatKind(b) {
if a.Float() < b.Float() {
return -1
}
if a.Float() > b.Float() {
return 1
}
return 0
}
}
panic(genericErrorf("unimplemented: comparison of %v<%s> with %v<%s>", a, ak, b, bk))
}
func hasIntKind(n reflect.Value) bool {
switch n.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return true
default:
return false
}
}
func hasFloatKind(n reflect.Value) bool {
switch n.Kind() {
case reflect.Float32, reflect.Float64:
return true
default:
return false
}
}

74
generics/generics.go Normal file
View File

@ -0,0 +1,74 @@
package generics
import (
"fmt"
"reflect"
)
// GenericError is an error regarding generic conversion.
type GenericError string
func (e GenericError) Error() string { return string(e) }
func genericErrorf(format string, a ...interface{}) error {
return GenericError(fmt.Sprintf(format, a...))
}
// Apply applies a function to arguments, converting them as necessary.
// The conversion follows Liquid semantics, which are more aggressive than
// Go conversion. The function should return one or two values; the second value,
// if present, should be an error.
func Apply(fn reflect.Value, args []interface{}) (interface{}, error) {
in := convertArguments(fn, args)
outs := fn.Call(in)
if len(outs) > 1 && outs[1].Interface() != nil {
switch e := outs[1].Interface().(type) {
case error:
return nil, e
default:
panic(e)
}
}
return outs[0].Interface(), nil
}
// Convert val to the type. This is a more aggressive conversion, that will
// recursively create new map and slice values as necessary. It doesn't
// handle circular references.
func convertType(val interface{}, t reflect.Type) reflect.Value {
r := reflect.ValueOf(val)
if r.Type().ConvertibleTo(t) {
return r.Convert(t)
}
if reflect.PtrTo(r.Type()) == t {
return reflect.ValueOf(&val)
}
switch t.Kind() {
case reflect.Slice:
if r.Kind() != reflect.Array && r.Kind() != reflect.Slice {
break
}
x := reflect.MakeSlice(t, 0, r.Len())
for i := 0; i < r.Len(); i++ {
c := convertType(r.Index(i).Interface(), t.Elem())
x = reflect.Append(x, c)
}
return x
}
panic(genericErrorf("convertType: can't convert %#v<%s> to %v", val, r.Type(), t))
}
// Convert args to match the input types of function fn.
func convertArguments(fn reflect.Value, in []interface{}) []reflect.Value {
rt := fn.Type()
out := make([]reflect.Value, rt.NumIn())
for i, arg := range in {
if i < rt.NumIn() {
out[i] = convertType(arg, rt.In(i))
}
}
for i := len(in); i < rt.NumIn(); i++ {
out[i] = reflect.Zero(rt.In(i))
}
return out
}

72
generics/sort.go Normal file
View File

@ -0,0 +1,72 @@
package generics
import (
"reflect"
"sort"
)
type genericSortable []interface{}
// Sort any []interface{} value.
func Sort(data []interface{}) {
sort.Sort(genericSortable(data))
}
// Len is part of sort.Interface.
func (s genericSortable) Len() int {
return len(s)
}
// Swap is part of sort.Interface.
func (s genericSortable) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Less is part of sort.Interface.
func (s genericSortable) Less(i, j int) bool {
return genericSameTypeCompare(s[i], s[j]) < 0
}
// SortByProperty sorts maps on their key indices.
func SortByProperty(data []interface{}, key string) {
sort.Sort(sortableByProperty{data, key})
}
type sortableByProperty struct {
data []interface{}
key string
}
// Len is part of sort.Interface.
func (s sortableByProperty) Len() int {
return len(s.data)
}
// Swap is part of sort.Interface.
func (s sortableByProperty) Swap(i, j int) {
data := s.data
data[i], data[j] = data[j], data[i]
}
// Less is part of sort.Interface.
func (s sortableByProperty) Less(i, j int) bool {
// index returns the value at the s.key, if in is a map that contains this key
index := func(in interface{}) interface{} {
rt := reflect.ValueOf(in)
if rt.Kind() == reflect.Map && rt.Type().Key().Kind() == reflect.String {
return rt.MapIndex(reflect.ValueOf(s.key)).Interface()
}
return nil
}
a, b := index(s.data[i]), index(s.data[j])
// TODO implement nil-first vs. nil last
switch {
case a == nil:
return true
case b == nil:
return false
default:
// TODO relax same type requirement
return genericSameTypeCompare(a, b) < 0
}
}