1
0
mirror of https://github.com/danog/liquid.git synced 2024-11-30 08:28:58 +01:00
liquid/evaluator/value.go

275 lines
6.4 KiB
Go
Raw Normal View History

package evaluator
import (
2017-07-20 15:52:05 +02:00
"fmt"
"reflect"
"strings"
)
// A Value is a Liquid runtime value.
type Value interface {
// Value retrieval
Interface() interface{}
Int() int
// Comparison
Equal(Value) bool
Less(Value) bool
Contains(Value) bool
IndexValue(Value) Value
PropertyValue(Value) Value
// Predicate
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())
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}}
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 }
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()
}
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
}
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())
}
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() {
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"
)
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:
if rv.Len() > 0 {
return ValueOf(rv.Index(0).Interface())
}
2017-07-20 15:37:56 +02:00
case lastKey:
if rv.Len() > 0 {
return ValueOf(rv.Index(rv.Len() - 1).Interface())
}
2017-07-20 15:37:56 +02:00
case sizeKey:
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)
switch {
2017-07-20 15:37:56 +02:00
case ev.IsValid():
return ValueOf(ev.Interface())
case index.Interface() == sizeKey:
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 {
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())
}