Skip to content

Commit

Permalink
Add len builtin
Browse files Browse the repository at this point in the history
  • Loading branch information
antonmedv committed Jul 30, 2018
1 parent 9f9aafe commit 2f79ed7
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 9 deletions.
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

0 comments on commit 2f79ed7

Please sign in to comment.