2017-07-20 15:12:31 +02:00
|
|
|
package evaluator
|
|
|
|
|
|
|
|
import (
|
2017-07-20 15:52:05 +02:00
|
|
|
"fmt"
|
2017-07-20 15:12:31 +02:00
|
|
|
"reflect"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// A Value is a Liquid runtime value.
|
|
|
|
type Value interface {
|
2017-07-21 22:02:30 +02:00
|
|
|
// Value retrieval
|
|
|
|
Interface() interface{}
|
|
|
|
Int() int
|
|
|
|
|
|
|
|
// Comparison
|
2017-07-20 15:12:31 +02:00
|
|
|
Equal(Value) bool
|
|
|
|
Less(Value) bool
|
2017-07-21 22:02:30 +02:00
|
|
|
|
2017-07-20 15:12:31 +02:00
|
|
|
Contains(Value) bool
|
2017-07-21 22:02:30 +02:00
|
|
|
IndexValue(Value) Value
|
2017-07-20 15:12:31 +02:00
|
|
|
PropertyValue(Value) Value
|
2017-07-21 22:02:30 +02:00
|
|
|
|
|
|
|
// Predicate
|
2017-07-20 15:12:31 +02:00
|
|
|
Test() bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// ValueOf returns a Value that wraps its argument.
|
|
|
|
// If the argument is already a Value, it returns this.
|
|
|
|
func ValueOf(value interface{}) Value {
|
|
|
|
switch value {
|
|
|
|
case nil:
|
|
|
|
return nilValue
|
|
|
|
case true:
|
|
|
|
return trueValue
|
|
|
|
case false:
|
|
|
|
return falseValue
|
|
|
|
}
|
|
|
|
if v, ok := value.(Value); ok {
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
rk := reflect.TypeOf(value).Kind()
|
|
|
|
if rk <= reflect.Float64 {
|
|
|
|
return wrapperValue{value}
|
|
|
|
}
|
|
|
|
switch rk {
|
2017-07-20 15:42:36 +02:00
|
|
|
case reflect.Ptr:
|
|
|
|
rv := reflect.ValueOf(value)
|
2017-07-20 16:40:46 +02:00
|
|
|
if rv.Type().Elem().Kind() == reflect.Struct {
|
|
|
|
return structValue{wrapperValue{value}}
|
|
|
|
}
|
2017-07-20 15:42:36 +02:00
|
|
|
return ValueOf(rv.Elem().Interface())
|
2017-07-20 15:12:31 +02:00
|
|
|
case reflect.String:
|
|
|
|
return stringValue{wrapperValue{value}}
|
|
|
|
case reflect.Array, reflect.Slice:
|
|
|
|
return arrayValue{wrapperValue{value}}
|
|
|
|
case reflect.Map:
|
|
|
|
return mapValue{wrapperValue{value}}
|
2017-07-20 16:29:05 +02:00
|
|
|
case reflect.Struct:
|
|
|
|
return structValue{wrapperValue{value}}
|
2017-07-20 15:12:31 +02:00
|
|
|
default:
|
|
|
|
return wrapperValue{value}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// embed this in a struct to give it default implementations of the Value interface
|
|
|
|
type valueEmbed struct{}
|
|
|
|
|
|
|
|
func (v valueEmbed) Equal(Value) bool { return false }
|
|
|
|
func (v valueEmbed) Less(Value) bool { return false }
|
|
|
|
func (v valueEmbed) IndexValue(Value) Value { return nilValue }
|
|
|
|
func (v valueEmbed) Contains(Value) bool { return false }
|
|
|
|
func (v valueEmbed) Int() int { panic(conversionError("", v, reflect.TypeOf(1))) }
|
|
|
|
func (v valueEmbed) Interface() interface{} { return nil }
|
|
|
|
func (v valueEmbed) PropertyValue(Value) Value { return nilValue }
|
|
|
|
func (v valueEmbed) Test() bool { return true }
|
|
|
|
|
|
|
|
type wrapperValue struct{ basis interface{} }
|
|
|
|
|
|
|
|
func (v wrapperValue) Equal(other Value) bool { return Equal(v.basis, other.Interface()) }
|
|
|
|
func (v wrapperValue) Less(other Value) bool { return Less(v.basis, other.Interface()) }
|
|
|
|
func (v wrapperValue) IndexValue(Value) Value { return nilValue }
|
|
|
|
func (v wrapperValue) Contains(Value) bool { return false }
|
|
|
|
func (v wrapperValue) Interface() interface{} { return v.basis }
|
|
|
|
func (v wrapperValue) PropertyValue(Value) Value { return nilValue }
|
|
|
|
func (v wrapperValue) Test() bool { return v.basis != nil && v.basis != false }
|
|
|
|
|
|
|
|
func (v wrapperValue) Int() int {
|
|
|
|
if n, ok := v.basis.(int); ok {
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
panic(conversionError("", v.basis, reflect.TypeOf(1)))
|
|
|
|
}
|
|
|
|
|
|
|
|
var nilValue = wrapperValue{nil}
|
|
|
|
var falseValue = wrapperValue{false}
|
|
|
|
var trueValue = wrapperValue{true}
|
|
|
|
|
|
|
|
type arrayValue struct{ wrapperValue }
|
|
|
|
type mapValue struct{ wrapperValue }
|
|
|
|
type stringValue struct{ wrapperValue }
|
2017-07-20 16:29:05 +02:00
|
|
|
type structValue struct{ wrapperValue }
|
2017-07-20 15:12:31 +02:00
|
|
|
|
|
|
|
func (v arrayValue) Contains(elem Value) bool {
|
|
|
|
rv := reflect.ValueOf(v.basis)
|
|
|
|
e := elem.Interface()
|
|
|
|
for i, len := 0, rv.Len(); i < len; i++ {
|
|
|
|
if Equal(rv.Index(i).Interface(), e) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-07-20 15:52:05 +02:00
|
|
|
func (v mapValue) Contains(index Value) bool {
|
|
|
|
rv := reflect.ValueOf(v.basis)
|
|
|
|
iv := reflect.ValueOf(index.Interface())
|
|
|
|
if rv.Type().Key() == iv.Type() {
|
|
|
|
return rv.MapIndex(iv).IsValid()
|
2017-07-20 15:12:31 +02:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-07-20 15:52:05 +02:00
|
|
|
func (v stringValue) Contains(substr Value) bool {
|
|
|
|
s, ok := substr.Interface().(string)
|
|
|
|
if !ok {
|
|
|
|
s = fmt.Sprint(substr.Interface())
|
|
|
|
}
|
|
|
|
return strings.Contains(v.basis.(string), s)
|
|
|
|
}
|
|
|
|
|
2017-07-20 16:29:05 +02:00
|
|
|
func (v structValue) Contains(elem Value) bool {
|
|
|
|
name, ok := elem.Interface().(string)
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
rt := reflect.TypeOf(v.basis)
|
2017-07-20 16:40:46 +02:00
|
|
|
if rt.Kind() == reflect.Ptr {
|
|
|
|
rt = rt.Elem()
|
|
|
|
}
|
2017-07-20 16:29:05 +02:00
|
|
|
if _, found := rt.FieldByName(name); found {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if _, found := rt.MethodByName(name); found {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-07-20 15:12:31 +02:00
|
|
|
func (v arrayValue) IndexValue(index Value) Value {
|
2017-07-20 15:37:56 +02:00
|
|
|
rv := reflect.ValueOf(v.basis)
|
2017-07-21 22:23:08 +02:00
|
|
|
var n int
|
|
|
|
switch ix := index.Interface().(type) {
|
|
|
|
case int:
|
|
|
|
n = ix
|
|
|
|
case float32:
|
|
|
|
// this how Ruby arrays, and therefore Liquid arrays, work
|
|
|
|
n = int(ix)
|
|
|
|
case float64:
|
|
|
|
n = int(ix)
|
|
|
|
default:
|
|
|
|
return nilValue
|
|
|
|
}
|
|
|
|
if n < 0 {
|
|
|
|
n += rv.Len()
|
|
|
|
}
|
|
|
|
if 0 <= n && n < rv.Len() {
|
|
|
|
return ValueOf(rv.Index(n).Interface())
|
2017-07-20 15:12:31 +02:00
|
|
|
}
|
|
|
|
return nilValue
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v mapValue) IndexValue(index Value) Value {
|
|
|
|
rv := reflect.ValueOf(v.basis)
|
|
|
|
iv := reflect.ValueOf(index.Interface())
|
2017-07-20 15:37:56 +02:00
|
|
|
if rv.Type().Key() == iv.Type() {
|
2017-07-20 15:12:31 +02:00
|
|
|
ev := rv.MapIndex(iv)
|
|
|
|
if ev.IsValid() {
|
|
|
|
return ValueOf(ev.Interface())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nilValue
|
|
|
|
}
|
|
|
|
|
2017-07-20 16:40:46 +02:00
|
|
|
func (v structValue) IndexValue(index Value) Value {
|
|
|
|
return v.PropertyValue(index)
|
|
|
|
}
|
|
|
|
|
2017-07-20 15:37:56 +02:00
|
|
|
const (
|
|
|
|
firstKey = "first"
|
|
|
|
lastKey = "last"
|
|
|
|
sizeKey = "size"
|
|
|
|
)
|
|
|
|
|
2017-07-20 15:12:31 +02:00
|
|
|
func (v arrayValue) PropertyValue(index Value) Value {
|
|
|
|
rv := reflect.ValueOf(v.basis)
|
|
|
|
switch index.Interface() {
|
2017-07-20 15:37:56 +02:00
|
|
|
case firstKey:
|
2017-07-20 15:12:31 +02:00
|
|
|
if rv.Len() > 0 {
|
|
|
|
return ValueOf(rv.Index(0).Interface())
|
|
|
|
}
|
2017-07-20 15:37:56 +02:00
|
|
|
case lastKey:
|
2017-07-20 15:12:31 +02:00
|
|
|
if rv.Len() > 0 {
|
|
|
|
return ValueOf(rv.Index(rv.Len() - 1).Interface())
|
|
|
|
}
|
2017-07-20 15:37:56 +02:00
|
|
|
case sizeKey:
|
2017-07-20 15:12:31 +02:00
|
|
|
return ValueOf(rv.Len())
|
|
|
|
}
|
|
|
|
return nilValue
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v mapValue) PropertyValue(index Value) Value {
|
|
|
|
rv := reflect.ValueOf(v.Interface())
|
2017-07-20 15:37:56 +02:00
|
|
|
iv := reflect.ValueOf(index.Interface())
|
|
|
|
ev := rv.MapIndex(iv)
|
2017-07-20 15:12:31 +02:00
|
|
|
switch {
|
2017-07-20 15:37:56 +02:00
|
|
|
case ev.IsValid():
|
|
|
|
return ValueOf(ev.Interface())
|
|
|
|
case index.Interface() == sizeKey:
|
2017-07-20 15:12:31 +02:00
|
|
|
return ValueOf(rv.Len())
|
|
|
|
default:
|
|
|
|
return nilValue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v stringValue) PropertyValue(index Value) Value {
|
2017-07-20 15:37:56 +02:00
|
|
|
if index.Interface() == sizeKey {
|
2017-07-20 15:12:31 +02:00
|
|
|
return ValueOf(len(v.basis.(string)))
|
|
|
|
}
|
|
|
|
return nilValue
|
|
|
|
}
|
2017-07-20 16:29:05 +02:00
|
|
|
|
|
|
|
func (v structValue) PropertyValue(index Value) Value {
|
|
|
|
name, ok := index.Interface().(string)
|
|
|
|
if !ok {
|
|
|
|
return nilValue
|
|
|
|
}
|
|
|
|
rv := reflect.ValueOf(v.basis)
|
|
|
|
rt := reflect.TypeOf(v.basis)
|
2017-07-20 16:40:46 +02:00
|
|
|
if _, found := rt.MethodByName(name); found {
|
|
|
|
m := rv.MethodByName(name)
|
|
|
|
return v.invoke(m)
|
|
|
|
}
|
|
|
|
if rt.Kind() == reflect.Ptr {
|
|
|
|
rt = rt.Elem()
|
|
|
|
rv = rv.Elem()
|
|
|
|
}
|
2017-07-20 16:29:05 +02:00
|
|
|
if _, found := rt.FieldByName(name); found {
|
|
|
|
fv := rv.FieldByName(name)
|
|
|
|
if fv.Kind() == reflect.Func {
|
|
|
|
return v.invoke(fv)
|
|
|
|
}
|
|
|
|
return ValueOf(fv.Interface())
|
|
|
|
}
|
|
|
|
if _, found := rt.MethodByName(name); found {
|
|
|
|
m := rv.MethodByName(name)
|
|
|
|
return v.invoke(m)
|
|
|
|
}
|
|
|
|
return nilValue
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v structValue) invoke(fv reflect.Value) Value {
|
|
|
|
if fv.IsNil() {
|
|
|
|
return nilValue
|
|
|
|
}
|
|
|
|
mt := fv.Type()
|
|
|
|
if mt.NumIn() > 0 || mt.NumOut() > 2 {
|
|
|
|
return nilValue
|
|
|
|
}
|
|
|
|
results := fv.Call([]reflect.Value{})
|
|
|
|
if len(results) > 1 && !results[1].IsNil() {
|
|
|
|
panic(results[1].Interface())
|
|
|
|
}
|
|
|
|
return ValueOf(results[0].Interface())
|
|
|
|
}
|