It's very common to evaluate an expression dynamicly, so that's why we are here.
We use s-epxression syntax to parse and evaluate.
In computing, s-expressions, sexprs or sexps (for "symbolic expression") are a notation for nested list (tree-structured) data, invented for and popularized by the programming language Lisp, which uses them for source code as well as data.
For example, for expression in common way:
(
(gender = "female")
and
((age % 2) != 0)
)
it's coresponding format in s-expression is:
(and
(= gender "female")
(!=
(% age 2)
0
)
)
-
number
For convenience, we treat float64, int64 and so on as type of number. For example, float100.0
is equal to int100
, but not euqal to string"100"
-
string
character string quoted with`
,'
, or"
are treated as type ofstring
. You can convert typestring
to any other defined type you like by type convert functions which are mentioned later -
function or variable
character string without quotes are regarded as type offunction
orvariable
which depends on whether this function exists. For example in expression(age birthdate)
, bothage
andbirthdate
is unquoted.age
is type of function because we have registered a function namedage
, whilebirthdate
is type of variable for not found. The program will come to errors if there is neither parameter nor function namedbirthdate
when evaluating
You can evaluate directly:
params := evaluator.MapParams{
"gender": "female",
}
res, err := evaluator.EvalBool(`(in gender ("female" "male"))`, params)
if err != nil {
log.Fatal(err)
}
fmt.Println(res)
# true
or you can reuse the Expression
to evaluate multiple times:
params := evaluator.MapParams{
"gender": "female",
}
exp, err := evaluator.New(`(in gender ("female" "male"))`)
if err != nil {
log.Fatal(err)
}
res, err := exp.EvalBool(params)
if err != nil {
log.Fatal(err)
}
fmt.Println(res)
# true
-
(in gender ("male", "female"))
-
(between now (td_time "2017-01-02 12:00:00") (td_time "2017-12-02 12:00:00"))
-
(ne (mod (age birthdate) 7) 5)
-
or multiple-line for clarity
(and (ne os "ios") (eq gender "male") (beteen version (t_version "2.7.1") (t_version "2.9.1")) )
operand | function | example | description |
---|---|---|---|
- | in |
(in 1 (1 2)) |
also suport array like (in (1) ((1))) |
- | between |
(between age 18 20) |
|
- | overlap |
(overlap region (3142 1860)) |
|
& |
and |
(and (eq gender "femal") (between age 18 20)) |
|
` | ` | or |
|
! |
not |
||
= |
eq |
equal | |
!= |
ne |
not equal | |
> |
gt |
greater than | |
< |
lt |
less than | |
>= |
ge |
greater than or equal to | |
<= |
le |
less than or equal to | |
% |
mod |
||
+ |
- | plus | |
- |
- | minus | |
* |
- | multiply | |
/ |
- | divide | |
- | t_version |
convert type to version | |
- | t_time |
(t_time "2006-01-02 15:04" "2017-09-09 12:00") |
convert type to time, first param must be the layout for the time |
- | td_time |
(td_time "2017:09:09 12:00:00) |
convert type to time of default layout format 2006-01-02 15:04:05 |
_ | td_date |
(in (td_date now) (td_date ("2017-01-02" "2017-02-01")) ) |
convert type to time of default layout format 2006-01-02 |
p.s. either operand or function can be used in expression
Yes, you can write your own function by following thses steps:
- implement your function
- regist to functions
- enjoy it
here is an example:
package main
import (
"errors"
"log"
"time"
"github.com/nullne/evaluator"
"github.com/nullne/evaluator/function"
)
// define your own function and don't forget to register
func age(params ...interface{}) (interface{}, error) {
if len(params) != 1 {
return nil, errors.New("only one params accepted")
}
birth, ok := params[0].(string)
if !ok {
return nil, errors.New("birth format need to be string")
}
r, err := time.Parse("2006-01-02", birth)
if err != nil {
return nil, err
}
now := time.Now()
a := r.Year() - now.Year()
if r.Month() < now.Month() {
a--
} else if r.Month() == now.Month() {
if r.Day() < now.Day() {
a--
}
}
return a, nil
}
func main() {
if err := function.Regist("age", age); err != nil {
log.Print(err)
}
exp := `(not (between (age birthdate) 18 20))`
vvf := evaluator.MapParams{
"birthdate": "1980-02-01",
}
e, err := evaluator.New(exp)
if err != nil {
log.Print(err)
}
r, err := e.Eval(vvf)
if err != nil {
log.Print(err)
}
log.Printf("expression: `%s`, wanna: %+v, got: %+v\r", exp, true, r)
}
Params
interface, which has a method namedGet
to get all params neededMapParams
a simple implementedParams
inmap
BenchmarkEqualString-8 3000000 473 ns/op
BenchmarkInString-8 2000000 916 ns/op
BenchmarkBetweenInt-8 3000000 467 ns/op
BenchmarkBetweenTime-8 1000000 2089 ns/op
BenchmarkOverlapInt-8 500000 2966 ns/op
BenchmarkTypeTime-8 2000000 638 ns/op
BenchmarkTypeVersion-8 3000000 539 ns/op
p.s. on MacBook Pro (Retina, 15-inch, Mid 2015), Memory: 16 GB 1600 MHz DDR3, Processor: 2.2 GHz Intel Core i7