Skip to content

Commit 2f79ed7

Browse files
committed
Add len builtin
1 parent 9f9aafe commit 2f79ed7

File tree

7 files changed

+102
-9
lines changed

7 files changed

+102
-9
lines changed

eval.go

+21
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,27 @@ func (n methodNode) eval(env interface{}) (interface{}, error) {
282282
return call(n.property.value, method, n.arguments, env)
283283
}
284284

285+
func (n builtinNode) eval(env interface{}) (interface{}, error) {
286+
if len(n.arguments) == 0 {
287+
return nil, fmt.Errorf("missing argument to %v", n.name)
288+
}
289+
if len(n.arguments) > 1 {
290+
return nil, fmt.Errorf("too many arguments to %v: %v", n.name, n)
291+
}
292+
293+
a, err := Run(n.arguments[0], env)
294+
if err != nil {
295+
return nil, err
296+
}
297+
298+
switch n.name {
299+
case "len":
300+
return count(n.arguments[0], a)
301+
}
302+
303+
return nil, fmt.Errorf("unknown %q builtin", n.name)
304+
}
305+
285306
func (n functionNode) eval(env interface{}) (interface{}, error) {
286307
fn, err := extract(env, n.name)
287308
if err != nil {

eval_test.go

+25
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,16 @@ var evalTests = []evalTest{
149149
map[string]interface{}{"foo": []rune{'a', 'b', 'c'}},
150150
'c',
151151
},
152+
{
153+
"len(foo) == 3",
154+
map[string]interface{}{"foo": []rune{'a', 'b', 'c'}},
155+
true,
156+
},
157+
{
158+
`len(foo) == 6`,
159+
map[string]string{"foo": "foobar"},
160+
true,
161+
},
152162
{
153163
"[1, 2, 3][2/2]",
154164
nil,
@@ -359,6 +369,21 @@ var evalErrorTests = []evalErrorTest{
359369
nil,
360370
"operator in not defined on string",
361371
},
372+
{
373+
"len(1)",
374+
nil,
375+
"invalid argument 1 (type float64) for len",
376+
},
377+
{
378+
"len(foo, bar)",
379+
map[string]interface{}{"foo": nil, "bar": nil},
380+
"too many arguments to len: len(foo, bar)",
381+
},
382+
{
383+
"len()",
384+
nil,
385+
"missing argument to len",
386+
},
362387
}
363388

364389
func TestEval(t *testing.T) {

node.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package expr
22

3-
// Node items of abstract syntax tree.
3+
// Node represents items of abstract syntax tree.
44
type Node interface{}
55

66
type nilNode struct{}
@@ -47,6 +47,11 @@ type methodNode struct {
4747
arguments []Node
4848
}
4949

50+
type builtinNode struct {
51+
name string
52+
arguments []Node
53+
}
54+
5055
type functionNode struct {
5156
name string
5257
arguments []Node

parser.go

+20-8
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ var binaryOperators = map[string]info{
5252
"**": {200, right},
5353
}
5454

55+
var builtins = map[string]bool{
56+
"len": true,
57+
}
58+
5559
type parser struct {
5660
input string
5761
tokens []token
@@ -284,16 +288,24 @@ func (p *parser) parsePrimaryExpression() (Node, error) {
284288
return nilNode{}, nil
285289
default:
286290
if p.current.is(punctuation, "(") {
287-
if p.options.funcs != nil {
288-
if _, ok := p.options.funcs[token.value]; !ok {
289-
return nil, p.errorf("unknown func %v", token.value)
291+
if _, ok := builtins[token.value]; ok {
292+
arguments, err := p.parseArguments()
293+
if err != nil {
294+
return nil, err
290295
}
296+
node = builtinNode{name: token.value, arguments: arguments}
297+
} else {
298+
if p.options.funcs != nil {
299+
if _, ok := p.options.funcs[token.value]; !ok {
300+
return nil, p.errorf("unknown func %v", token.value)
301+
}
302+
}
303+
arguments, err := p.parseArguments()
304+
if err != nil {
305+
return nil, err
306+
}
307+
node = functionNode{name: token.value, arguments: arguments}
291308
}
292-
arguments, err := p.parseArguments()
293-
if err != nil {
294-
return nil, err
295-
}
296-
node = functionNode{name: token.value, arguments: arguments}
297309
} else {
298310
if p.options.names != nil {
299311
if _, ok := p.options.names[token.value]; !ok {

parser_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ var parseTests = []parseTest{
121121
"{foo:1}.bar",
122122
propertyNode{mapNode{[]pairNode{{identifierNode{"foo"}, numberNode{1}}}}, identifierNode{"bar"}},
123123
},
124+
{
125+
"len(foo)",
126+
builtinNode{"len", []Node{nameNode{"foo"}}},
127+
},
124128
}
125129

126130
type parseErrorTest struct {

print.go

+11
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,17 @@ func (n methodNode) String() string {
6363
return s + ")"
6464
}
6565

66+
func (n builtinNode) String() string {
67+
s := fmt.Sprintf("%v(", n.name)
68+
for i, a := range n.arguments {
69+
if i != 0 {
70+
s += ", "
71+
}
72+
s += fmt.Sprintf("%v", a)
73+
}
74+
return s + ")"
75+
}
76+
6677
func (n functionNode) String() string {
6778
s := fmt.Sprintf("%v(", n.name)
6879
for i, a := range n.arguments {

utils.go

+15
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,21 @@ func contains(needle interface{}, array interface{}) (bool, error) {
120120
return false, nil
121121
}
122122

123+
func count(node Node, array interface{}) (float64, error) {
124+
if array != nil {
125+
value := reflect.ValueOf(array)
126+
switch reflect.TypeOf(array).Kind() {
127+
case reflect.Array, reflect.Slice:
128+
return float64(value.Len()), nil
129+
case reflect.String:
130+
return float64(value.Len()), nil
131+
}
132+
return 0, fmt.Errorf("invalid argument %v (type %T) for len", node, array)
133+
}
134+
135+
return 0, nil
136+
}
137+
123138
func call(name string, fn interface{}, arguments []Node, env interface{}) (interface{}, error) {
124139
in := make([]reflect.Value, 0)
125140
for _, arg := range arguments {

0 commit comments

Comments
 (0)