2017-08-08 22:42:32 +02:00
// Package expressions is an internal package that parses and evaluates the expression language.
2017-07-10 15:38:46 +02:00
//
// This is the language that is used inside Liquid object and tags; e.g. "a.b[c]" in {{ a.b[c] }}, and "pages = site.pages | reverse" in {% assign pages = site.pages | reverse %}.
2017-07-14 02:18:23 +02:00
package expressions
2017-06-26 14:23:50 +02:00
2017-06-27 23:29:50 +02:00
import (
2017-07-26 16:11:57 +02:00
"fmt"
"runtime/debug"
2017-07-28 00:11:37 +02:00
"github.com/osteele/liquid/values"
2017-06-27 23:29:50 +02:00
)
2017-06-27 22:11:32 +02:00
2017-07-09 16:36:32 +02:00
// TODO Expression and Closure are confusing names.
// An Expression is a compiled expression.
2017-06-28 17:24:24 +02:00
type Expression interface {
// Evaluate evaluates an expression in a context.
Evaluate ( ctx Context ) ( interface { } , error )
}
2017-07-09 16:36:32 +02:00
// A Closure is an expression within a lexical environment.
// A closure may refer to variables that are not defined in the
// environment. (Therefore it's not a technically a closure.)
2017-06-28 17:24:24 +02:00
type Closure interface {
2017-07-09 16:36:32 +02:00
// Bind creates a new closure with a new binding.
2017-06-28 17:24:24 +02:00
Bind ( name string , value interface { } ) Closure
Evaluate ( ) ( interface { } , error )
}
type closure struct {
expr Expression
context Context
}
func ( c closure ) Bind ( name string , value interface { } ) Closure {
2017-07-09 16:36:32 +02:00
ctx := c . context . Clone ( )
ctx . Set ( name , value )
return closure { c . expr , ctx }
2017-06-28 17:24:24 +02:00
}
func ( c closure ) Evaluate ( ) ( interface { } , error ) {
return c . expr . Evaluate ( c . context )
}
type expression struct {
2017-07-28 00:11:37 +02:00
evaluator func ( Context ) values . Value
2017-06-28 17:24:24 +02:00
}
2017-06-27 22:11:32 +02:00
func ( e expression ) Evaluate ( ctx Context ) ( out interface { } , err error ) {
defer func ( ) {
if r := recover ( ) ; r != nil {
switch e := r . ( type ) {
2017-07-28 00:11:37 +02:00
case values . TypeError :
2017-06-27 23:29:50 +02:00
err = e
2017-06-27 22:53:34 +02:00
case InterpreterError :
err = e
2017-06-29 13:54:31 +02:00
case UndefinedFilter :
2017-06-27 22:11:32 +02:00
err = e
2017-07-21 02:36:44 +02:00
case FilterError :
err = e
2017-07-26 16:11:57 +02:00
case error :
panic ( & rethrownError { e , debug . Stack ( ) } )
2017-06-27 22:11:32 +02:00
default :
panic ( r )
}
}
} ( )
2017-07-20 15:12:31 +02:00
return e . evaluator ( ctx ) . Interface ( ) , nil
2017-06-26 15:06:55 +02:00
}
2017-07-26 16:11:57 +02:00
// rethrownError is for use in a re-thrown error from panic recovery.
// When printed, it prints the original stacktrace.
// This works around a frequent problem, that it's difficult to debug an error inside a filter
// or ToLiquid implementation because Evaluate's recover replaces the stacktrace.
type rethrownError struct {
cause error
stack [ ] byte
}
func ( e * rethrownError ) Error ( ) string {
return fmt . Sprintf ( "%s\nOriginal stacktrace:\n%s\n" , e . cause , string ( e . stack ) )
}
func ( e * rethrownError ) Cause ( ) error {
return e . cause
}