Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,27 @@ func (n methodNode) eval(env interface{}) (interface{}, error) {
return call(n.property.value, method, n.arguments, env)
}

func (n builtinNode) eval(env interface{}) (interface{}, error) {
if len(n.arguments) == 0 {
return nil, fmt.Errorf("missing argument to %v", n.name)
}
if len(n.arguments) > 1 {
return nil, fmt.Errorf("too many arguments to %v: %v", n.name, n)
}

a, err := Run(n.arguments[0], env)
if err != nil {
return nil, err
}

switch n.name {
case "len":
return count(n.arguments[0], a)
}

return nil, fmt.Errorf("unknown %q builtin", n.name)
}

func (n functionNode) eval(env interface{}) (interface{}, error) {
fn, err := extract(env, n.name)
if err != nil {
Expand Down
25 changes: 25 additions & 0 deletions eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,16 @@ var evalTests = []evalTest{
map[string]interface{}{"foo": []rune{'a', 'b', 'c'}},
'c',
},
{
"len(foo) == 3",
map[string]interface{}{"foo": []rune{'a', 'b', 'c'}},
true,
},
{
`len(foo) == 6`,
map[string]string{"foo": "foobar"},
true,
},
{
"[1, 2, 3][2/2]",
nil,
Expand Down Expand Up @@ -359,6 +369,21 @@ var evalErrorTests = []evalErrorTest{
nil,
"operator in not defined on string",
},
{
"len(1)",
nil,
"invalid argument 1 (type float64) for len",
},
{
"len(foo, bar)",
map[string]interface{}{"foo": nil, "bar": nil},
"too many arguments to len: len(foo, bar)",
},
{
"len()",
nil,
"missing argument to len",
},
}

func TestEval(t *testing.T) {
Expand Down
7 changes: 6 additions & 1 deletion node.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package expr

// Node items of abstract syntax tree.
// Node represents items of abstract syntax tree.
type Node interface{}

type nilNode struct{}
Expand Down Expand Up @@ -47,6 +47,11 @@ type methodNode struct {
arguments []Node
}

type builtinNode struct {
name string
arguments []Node
}

type functionNode struct {
name string
arguments []Node
Expand Down
28 changes: 20 additions & 8 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ var binaryOperators = map[string]info{
"**": {200, right},
}

var builtins = map[string]bool{
"len": true,
}

type parser struct {
input string
tokens []token
Expand Down Expand Up @@ -284,16 +288,24 @@ func (p *parser) parsePrimaryExpression() (Node, error) {
return nilNode{}, nil
default:
if p.current.is(punctuation, "(") {
if p.options.funcs != nil {
if _, ok := p.options.funcs[token.value]; !ok {
return nil, p.errorf("unknown func %v", token.value)
if _, ok := builtins[token.value]; ok {
arguments, err := p.parseArguments()
if err != nil {
return nil, err
}
node = builtinNode{name: token.value, arguments: arguments}
} else {
if p.options.funcs != nil {
if _, ok := p.options.funcs[token.value]; !ok {
return nil, p.errorf("unknown func %v", token.value)
}
}
arguments, err := p.parseArguments()
if err != nil {
return nil, err
}
node = functionNode{name: token.value, arguments: arguments}
}
arguments, err := p.parseArguments()
if err != nil {
return nil, err
}
node = functionNode{name: token.value, arguments: arguments}
} else {
if p.options.names != nil {
if _, ok := p.options.names[token.value]; !ok {
Expand Down
4 changes: 4 additions & 0 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ var parseTests = []parseTest{
"{foo:1}.bar",
propertyNode{mapNode{[]pairNode{{identifierNode{"foo"}, numberNode{1}}}}, identifierNode{"bar"}},
},
{
"len(foo)",
builtinNode{"len", []Node{nameNode{"foo"}}},
},
}

type parseErrorTest struct {
Expand Down
11 changes: 11 additions & 0 deletions print.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ func (n methodNode) String() string {
return s + ")"
}

func (n builtinNode) String() string {
s := fmt.Sprintf("%v(", n.name)
for i, a := range n.arguments {
if i != 0 {
s += ", "
}
s += fmt.Sprintf("%v", a)
}
return s + ")"
}

func (n functionNode) String() string {
s := fmt.Sprintf("%v(", n.name)
for i, a := range n.arguments {
Expand Down
15 changes: 15 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,21 @@ func contains(needle interface{}, array interface{}) (bool, error) {
return false, nil
}

func count(node Node, array interface{}) (float64, error) {
if array != nil {
value := reflect.ValueOf(array)
switch reflect.TypeOf(array).Kind() {
case reflect.Array, reflect.Slice:
return float64(value.Len()), nil
case reflect.String:
return float64(value.Len()), nil
}
return 0, fmt.Errorf("invalid argument %v (type %T) for len", node, array)
}

return 0, nil
}

func call(name string, fn interface{}, arguments []Node, env interface{}) (interface{}, error) {
in := make([]reflect.Value, 0)
for _, arg := range arguments {
Expand Down