Skip to content

Commit

Permalink
Function calls (#429)
Browse files Browse the repository at this point in the history
This is mostly the framework for calling functions.  It includes the
implementation of one function: Math.sqrt().  Additional functions to
be added in subsequent PRs.
  • Loading branch information
aswan authored Mar 18, 2020
1 parent 2d8a04c commit b53f254
Show file tree
Hide file tree
Showing 10 changed files with 1,427 additions and 852 deletions.
7 changes: 7 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,14 @@ type BinaryExpression struct {
RHS Expression `json:"rhs"`
}

type FunctionCall struct {
Node
Function string `json:"function"`
Args []Expression `json:"args"`
}

func (*BinaryExpression) exprNode() {}
func (*FunctionCall) exprNode() {}
func (*Literal) exprNode() {}
func (*FieldRead) exprNode() {}

Expand Down
34 changes: 34 additions & 0 deletions expr/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type ExpressionEvaluator func(*zng.Record) (zng.Value, error)
var ErrNoSuchField = errors.New("field is not present")
var ErrIncompatibleTypes = errors.New("incompatible types")
var ErrIndexOutOfBounds = errors.New("array index out of bounds")
var ErrNoSuchFunction = errors.New("no such function")

type NativeValue struct {
typ zng.Type
Expand Down Expand Up @@ -275,6 +276,9 @@ func compileNative(node ast.Expression) (NativeEvaluator, error) {
return nil, fmt.Errorf("invalid binary operator %s", n.Operator)
}

case *ast.FunctionCall:
return compileFunctionCall(*n)

default:
return nil, fmt.Errorf("invalid expression type %T", node)
}
Expand Down Expand Up @@ -912,3 +916,33 @@ func compileFieldReference(lhsFunc, rhsFunc NativeEvaluator, operator string) (N
return toNativeValue(zng.Value{rType.Columns[idx].Type, zv})
}, nil
}

func compileFunctionCall(node ast.FunctionCall) (NativeEvaluator, error) {
fn := allFns[node.Function]
if fn == nil {
return nil, fmt.Errorf("%s: %w", node.Function, ErrNoSuchFunction)
}

nargs := len(node.Args)
exprs := make([]NativeEvaluator, nargs)
for i, expr := range node.Args {
eval, err := compileNative(expr)
if err != nil {
return nil, err
}
exprs[i] = eval
}

return func(r *zng.Record) (NativeValue, error) {
args := make([]NativeValue, 0, nargs)
for _, a := range exprs {
val, err := a(r)
if err != nil {
return NativeValue{}, err
}
args = append(args, val)
}

return fn(args)
}, nil
}
2 changes: 1 addition & 1 deletion expr/expr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func testError(t *testing.T, e string, record *zng.Record, expectErr error, desc
t.Run(description, func(t *testing.T) {
_, err := evaluate(e, record)
assert.Errorf(t, err, "got error when %s", description)
assert.Equalf(t, expectErr, err, "got correct error when %s", description)
assert.True(t, errors.Is(err, expectErr), "got correct error when %s", description)
})
}

Expand Down
46 changes: 46 additions & 0 deletions expr/functions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package expr

import (
"errors"
"fmt"
"math"

"github.com/brimsec/zq/zng"
)

type Function func([]NativeValue) (NativeValue, error)

var ErrWrongArgc = errors.New("wrong number of arguments")
var ErrBadArgument = errors.New("bad argument")

var allFns = map[string]Function{
"Math.sqrt": mathSqrt,
}

func mathSqrt(args []NativeValue) (NativeValue, error) {
if len(args) < 1 || len(args) > 1 {
return NativeValue{}, fmt.Errorf("Math.sqrt: %w", ErrWrongArgc)
}

var x float64
switch args[0].typ.ID() {
case zng.IdFloat64:
x = args[0].value.(float64)
case zng.IdInt16, zng.IdInt32, zng.IdInt64:
x = float64(args[0].value.(int64))
case zng.IdByte, zng.IdUint16, zng.IdUint32, zng.IdUint64:
x = float64(args[0].value.(uint64))
default:
return NativeValue{}, fmt.Errorf("Math.sqrt: %w", ErrBadArgument)
}

r := math.Sqrt(x)
if math.IsNaN(r) {
// For now we can't represent non-numeric values in a float64,
// we will revisit this but it has implications for file
// formats, zql, etc.
return NativeValue{}, fmt.Errorf("Math.sqrt: %w", ErrBadArgument)
}

return NativeValue{zng.TypeFloat64, r}, nil
}
27 changes: 27 additions & 0 deletions expr/functions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package expr_test

import (
"testing"

"github.com/brimsec/zq/expr"
"github.com/stretchr/testify/require"
)

func TestBadFunction(t *testing.T) {
testError(t, "notafunction()", nil, expr.ErrNoSuchFunction, "calling nonexistent function")
}

func TestSqrt(t *testing.T) {
record, err := parseOneRecord(`
#0:record[f:float64,i:int32]
0:[6.25;9;]`)
require.NoError(t, err)

testSuccessful(t, "Math.sqrt(4.0)", record, zfloat64(2.0))
testSuccessful(t, "Math.sqrt(f)", record, zfloat64(2.5))
testSuccessful(t, "Math.sqrt(i)", record, zfloat64(3.0))

testError(t, "Math.sqrt()", record, expr.ErrWrongArgc, "sqrt with no args")
testError(t, "Math.sqrt(1, 2)", record, expr.ErrWrongArgc, "sqrt with too many args")
testError(t, "Math.sqrt(-1)", record, expr.ErrBadArgument, "sqrt of negative")
}
13 changes: 13 additions & 0 deletions zql/parser-support.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,19 @@ func makeBinaryExprChain(firstIn, restIn interface{}) ast.Expression {
return result
}

func makeFunctionCall(fn, argsIn interface{}) ast.Expression {
argArray := argsIn.([]interface{})
args := make([]ast.Expression, len(argArray))
for i, a := range argArray {
args[i] = a.(ast.Expression)
}
return &ast.FunctionCall{
ast.Node{"FunctionCall"},
fn.(string),
args,
}
}

func joinChars(in interface{}) string {
str := bytes.Buffer{}
for _, i := range in.([]interface{}) {
Expand Down
4 changes: 4 additions & 0 deletions zql/parser-support.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ function makeBinaryExprChain(first, rest) {
return ret
}

function makeFunctionCall(fn, args) {
return { op: "FunctionCall", function: fn, args };
}

function joinChars(chars) {
return chars.join("");
}
Expand Down
Loading

0 comments on commit b53f254

Please sign in to comment.