Skip to content

Commit

Permalink
Completely working expression functions and separators.
Browse files Browse the repository at this point in the history
  • Loading branch information
Knetic committed Aug 11, 2016
1 parent a6558f5 commit db7388b
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 10 deletions.
8 changes: 8 additions & 0 deletions OperatorToken.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const (

// as distinct from the TokenKind.
FUNCTION_OPERATOR
SEPARATOR_OPERATOR
)

type OperatorPrecedence int
Expand All @@ -58,6 +59,7 @@ const (
COMPARATOR_PRECEDENCE
TERNARY_PRECEDENCE
LOGICAL_PRECEDENCE
SEPARATOR_PRECEDENCE
)

func findOperatorPrecedenceForSymbol(symbol OperatorSymbol) OperatorPrecedence {
Expand Down Expand Up @@ -119,6 +121,8 @@ func findOperatorPrecedenceForSymbol(symbol OperatorSymbol) OperatorPrecedence {
return TERNARY_PRECEDENCE
case FUNCTION_OPERATOR:
return FUNCTION_PRECEDENCE
case SEPARATOR_OPERATOR:
return SEPARATOR_PRECEDENCE
}

return VALUE_PRECEDENCE
Expand Down Expand Up @@ -198,6 +202,10 @@ var MODIFIER_SYMBOLS = map[string]OperatorSymbol{
"<<": BITWISE_LSHIFT,
}

var SEPARATOR_SYMBOLS = map[string]OperatorSymbol{
",": SEPARATOR_OPERATOR,
}

var ADDITIVE_MODIFIERS = []OperatorSymbol{
PLUS, MINUS,
}
Expand Down
27 changes: 26 additions & 1 deletion evaluationFailure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"strings"
"testing"
"errors"
)

type DebugStruct struct {
Expand All @@ -19,6 +20,7 @@ type DebugStruct struct {
type EvaluationFailureTest struct {
Name string
Input string
Functions map[string]ExpressionFunction
Parameters map[string]interface{}
Expected string
}
Expand Down Expand Up @@ -407,6 +409,25 @@ func TestRegexParameterCompilation(test *testing.T) {
runEvaluationFailureTests(evaluationTests, test)
}

func TestFunctionExecution(test *testing.T) {

evaluationTests := []EvaluationFailureTest{
EvaluationFailureTest{

Name: "Function error bubbling",
Input: "error()",
Functions: map[string]ExpressionFunction{
"error": func(arguments ...interface{}) (interface{}, error) {
return nil, errors.New("Huge problems")
},
},
Expected: "Huge problems",
},
}

runEvaluationFailureTests(evaluationTests, test)
}

func runEvaluationFailureTests(evaluationTests []EvaluationFailureTest, test *testing.T) {

var expression *EvaluableExpression
Expand All @@ -416,7 +437,11 @@ func runEvaluationFailureTests(evaluationTests []EvaluationFailureTest, test *te

for _, testCase := range evaluationTests {

expression, err = NewEvaluableExpression(testCase.Input)
if(len(testCase.Functions) > 0) {
expression, err = NewEvaluableExpressionWithFunctions(testCase.Input, testCase.Functions)
} else {
expression, err = NewEvaluableExpression(testCase.Input)
}

if err != nil {

Expand Down
10 changes: 4 additions & 6 deletions evaluationStage.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,11 @@ func separatorStage(left interface{}, right interface{}, parameters Parameters)

var ret []interface{}

if(left != nil) {
ret = []interface{} {left}
}

switch right.(type) {
switch left.(type) {
case []interface{}:
ret = append(ret, left)
ret = append(left.([]interface{}), right)
default:
ret = []interface{} {left, right}
}

return ret, nil
Expand Down
58 changes: 57 additions & 1 deletion evaluation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,13 +467,42 @@ func TestNoParameterEvaluation(test *testing.T) {
Input: "passthrough(1)",
Functions: map[string]ExpressionFunction {
"passthrough": func(arguments ...interface{}) (interface{}, error) {
fmt.Printf("Arg zero: %v\n", arguments)
return arguments[0], nil
},
},

Expected: 1.0,
},

EvaluationTest{

Name: "Function with arguments",
Input: "passthrough(1, 2)",
Functions: map[string]ExpressionFunction {
"passthrough": func(arguments ...interface{}) (interface{}, error) {
return arguments[0].(float64) + arguments[1].(float64), nil
},
},

Expected: 3.0,
},
EvaluationTest{

Name: "Nested function with precedence",
Input: "sum(1, sum(2, 3), 2 + 2, true ? 4 : 5)",
Functions: map[string]ExpressionFunction {
"sum": func(arguments ...interface{}) (interface{}, error) {

sum := 0.0
for _, v := range arguments {
sum += v.(float64)
}
return sum, nil
},
},

Expected: 14.0,
},
}

runEvaluationTests(evaluationTests, test)
Expand Down Expand Up @@ -894,6 +923,33 @@ func TestParameterizedEvaluation(test *testing.T) {
},
Expected: 1.0,
},
EvaluationTest{

Name: "Mixed function and parameters",
Input: "sum(1.2, amount) + name",
Functions: map[string]ExpressionFunction {
"sum": func(arguments ...interface{}) (interface{}, error) {

sum := 0.0
for _, v := range arguments {
sum += v.(float64)
}
return sum, nil
},
},
Parameters: []EvaluationParameter{
EvaluationParameter{
Name: "amount",
Value: .8,
},
EvaluationParameter{
Name: "name",
Value: "awesome",
},
},

Expected: "2awesome",
},
}

runEvaluationTests(evaluationTests, test)
Expand Down
1 change: 1 addition & 0 deletions parsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ func readToken(stream *lexerStream, state lexerState, functions map[string]Expre
// comma, separator
if character == ',' {

tokenValue = ","
kind = SEPARATOR
break
}
Expand Down
9 changes: 7 additions & 2 deletions stagePlanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var stageSymbolMap = map[OperatorSymbol]evaluationOperator{
TERNARY_TRUE: ternaryIfStage,
TERNARY_FALSE: ternaryElseStage,
COALESCE: ternaryElseStage,
SEPARATOR_OPERATOR: separatorStage,
}

/*
Expand Down Expand Up @@ -157,8 +158,10 @@ func init() {
next: planLogical,
})
planSeparator = makePrecedentFromPlanner(&precedencePlanner{
validSymbols: SEPARATOR_SYMBOLS,
validKinds: []TokenKind {SEPARATOR},
next: planTernary,
typeErrorFormat: "separator",
next: planTernary,
})
}

Expand Down Expand Up @@ -433,8 +436,10 @@ func planValue(stream *tokenStream) (*evaluationStage, error) {
// advance past the CLAUSE_CLOSE token. We know that it's a CLAUSE_CLOSE, because at parse-time we check for unbalanced parens.
stream.next()
return ret, nil

case CLAUSE_CLOSE:

// when functions do not have anything within the parens, the CLAUSE_CLOSE is not consumed. This consumes it.
return planTokens(stream)

case VARIABLE:
Expand Down

0 comments on commit db7388b

Please sign in to comment.