Skip to content

Commit

Permalink
Parsing for accessors
Browse files Browse the repository at this point in the history
  • Loading branch information
Knetic committed Jun 19, 2017
1 parent ed428fc commit f9296ab
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 6 deletions.
3 changes: 3 additions & 0 deletions OperatorSymbol.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const (
COALESCE

FUNCTIONAL
ACCESS
SEPARATE
)

Expand Down Expand Up @@ -129,6 +130,8 @@ func findOperatorPrecedenceForSymbol(symbol OperatorSymbol) operatorPrecedence {
fallthrough
case TERNARY_FALSE:
return ternaryPrecedence
case ACCESS:
fallthrough
case FUNCTIONAL:
return functionalPrecedence
case SEPARATE:
Expand Down
3 changes: 3 additions & 0 deletions TokenKind.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const (
VARIABLE
FUNCTION
SEPARATOR
ACCESSOR

COMPARATOR
LOGICALOP
Expand Down Expand Up @@ -66,6 +67,8 @@ func (kind TokenKind) String() string {
return "CLAUSE_CLOSE"
case TERNARY:
return "TERNARY"
case ACCESSOR:
return "ACCESSOR"
}

return "UNKNOWN"
Expand Down
60 changes: 59 additions & 1 deletion evaluationStage.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (this *evaluationStage) isShortCircuitable() bool {
fallthrough
case OR:
fallthrough
case TERNARY_TRUE:
case TERNARY_TRUE:
fallthrough
case TERNARY_FALSE:
fallthrough
Expand Down Expand Up @@ -243,7 +243,65 @@ func makeFunctionStage(function ExpressionFunction) evaluationOperator {
default:
return function(right)
}
}
}

func makeAccessorStage(pair []string) evaluationOperator {

return func(left interface{}, right interface{}, parameters Parameters) (interface{}, error) {

var params []reflect.Value

value, err := parameters.Get(pair[0])
if err != nil {
return nil, err
}

coreValue := reflect.ValueOf(value)
if coreValue.Kind() != reflect.Struct {
return nil, errors.New("Unable to access '"+pair[1]+"', '"+pair[0]+"' is not a struct");
}

field := coreValue.FieldByName(pair[1])
if field != (reflect.Value{}) {
return field.Interface(), nil
}

method := coreValue.MethodByName(pair[1])
if method == (reflect.Value{}) {
return nil, errors.New("No method or field '"+pair[1]+"' present on parameter '"+pair[0]+"'")
}

switch right.(type) {
case []interface{}:

givenParams := right.([]interface{})
params = make([]reflect.Value, len(givenParams))
for i, _ := range givenParams {
params[i] = reflect.ValueOf(givenParams[i])
}

default:
params = []reflect.Value {reflect.ValueOf(right.(interface{}))}
}

returned := method.Call(params)
if len(returned) == 0 {
return nil, errors.New("Method call '"+pair[0]+"."+pair[1]+"' did not return any values.")
}

if len(returned) == 1 {
return returned[0], nil
}

if len(returned) == 2 {
err, validType := returned[1].Interface().(error)
if validType {
return returned[0].Interface(), err
}
}

return nil, errors.New("Method call '"+pair[0]+"."+pair[1]+"' did not return either one value, or a value and an error. Cannot interpret meaning.")
}
}

Expand Down
23 changes: 23 additions & 0 deletions lexerState.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var validLexerStates = []lexerState{
VARIABLE,
PATTERN,
FUNCTION,
ACCESSOR,
STRING,
TIME,
CLAUSE,
Expand All @@ -47,6 +48,7 @@ var validLexerStates = []lexerState{
VARIABLE,
PATTERN,
FUNCTION,
ACCESSOR,
STRING,
TIME,
CLAUSE,
Expand Down Expand Up @@ -176,6 +178,7 @@ var validLexerStates = []lexerState{
NUMERIC,
VARIABLE,
FUNCTION,
ACCESSOR,
STRING,
BOOLEAN,
CLAUSE,
Expand All @@ -194,6 +197,7 @@ var validLexerStates = []lexerState{
BOOLEAN,
VARIABLE,
FUNCTION,
ACCESSOR,
STRING,
TIME,
CLAUSE,
Expand All @@ -213,6 +217,7 @@ var validLexerStates = []lexerState{
BOOLEAN,
VARIABLE,
FUNCTION,
ACCESSOR,
STRING,
TIME,
CLAUSE,
Expand All @@ -230,6 +235,7 @@ var validLexerStates = []lexerState{
BOOLEAN,
VARIABLE,
FUNCTION,
ACCESSOR,
CLAUSE,
CLAUSE_CLOSE,
},
Expand All @@ -249,6 +255,7 @@ var validLexerStates = []lexerState{
TIME,
VARIABLE,
FUNCTION,
ACCESSOR,
CLAUSE,
SEPARATOR,
},
Expand All @@ -262,6 +269,21 @@ var validLexerStates = []lexerState{
CLAUSE,
},
},
lexerState{

kind: ACCESSOR,
isEOF: true,
isNullable: false,
validNextKinds: []TokenKind{
CLAUSE,
MODIFIER,
COMPARATOR,
LOGICALOP,
CLAUSE_CLOSE,
TERNARY,
SEPARATOR,
},
},
lexerState{

kind: SEPARATOR,
Expand All @@ -276,6 +298,7 @@ var validLexerStates = []lexerState{
TIME,
VARIABLE,
FUNCTION,
ACCESSOR,
CLAUSE,
},
},
Expand Down
11 changes: 10 additions & 1 deletion parsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strconv"
"time"
"unicode"
"strings"
)

func parseTokens(expression string, functions map[string]ExpressionFunction) ([]ExpressionToken, error) {
Expand Down Expand Up @@ -153,6 +154,13 @@ func readToken(stream *lexerStream, state lexerState, functions map[string]Expre
kind = FUNCTION
tokenValue = function
}

// accessor?
accessorIndex := strings.Index(tokenString, ".")
if accessorIndex > 0 {
kind = ACCESSOR
tokenValue = strings.Split(tokenString, ".")
}
break
}

Expand Down Expand Up @@ -392,7 +400,8 @@ func isVariableName(character rune) bool {

return unicode.IsLetter(character) ||
unicode.IsDigit(character) ||
character == '_'
character == '_' ||
character == '.'
}

func isNotClosingBracket(character rune) bool {
Expand Down
64 changes: 62 additions & 2 deletions parsing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,32 @@ func TestConstantParsing(test *testing.T) {
},
},
},
TokenParsingTest{
Name: "Accessor variable",
Input: "foo.Var",
Expected: []ExpressionToken{
ExpressionToken{
Kind: ACCESSOR,
Value: []string {"foo", "Var"},
},
},
},
TokenParsingTest{
Name: "Accessor function",
Input: "foo.Operation()",
Expected: []ExpressionToken{
ExpressionToken{
Kind: ACCESSOR,
Value: []string {"foo", "Operation"},
},
ExpressionToken{
Kind: CLAUSE,
},
ExpressionToken{
Kind: CLAUSE_CLOSE,
},
},
},
}

tokenParsingTests = combineWhitespaceExpressions(tokenParsingTests)
Expand Down Expand Up @@ -1487,6 +1513,7 @@ func stripUnquotedWhitespace(expression string) string {

func runTokenParsingTest(tokenParsingTests []TokenParsingTest, test *testing.T) {

var parsingTest TokenParsingTest
var expression *EvaluableExpression
var actualTokens []ExpressionToken
var actualToken ExpressionToken
Expand All @@ -1495,9 +1522,15 @@ func runTokenParsingTest(tokenParsingTests []TokenParsingTest, test *testing.T)
var err error

fmt.Printf("Running %d parsing test cases...\n", len(tokenParsingTests))
// defer func() {
// if r := recover(); r != nil {
// test.Logf("Panic in test '%s': %v", parsingTest.Name, r)
// test.Fail()
// }
// }()

// Run the test cases.
for _, parsingTest := range tokenParsingTests {
for _, parsingTest = range tokenParsingTests {

if parsingTest.Functions != nil {
expression, err = NewEvaluableExpressionWithFunctions(parsingTest.Input, parsingTest.Functions)
Expand Down Expand Up @@ -1540,9 +1573,36 @@ func runTokenParsingTest(tokenParsingTests []TokenParsingTest, test *testing.T)
continue
}

if expectedToken.Value == nil || reflect.TypeOf(expectedToken.Value).Kind() == reflect.Func {
if expectedToken.Value == nil {
continue
}

reflectedKind := reflect.TypeOf(expectedToken.Value).Kind()
if reflectedKind == reflect.Func {
continue
}

// gotta be an accessor
if reflectedKind == reflect.Slice {

if actualToken.Value == nil {
test.Logf("Test '%s' failed:", parsingTest.Name)
test.Logf("Expected token value '%v' does not match nil", expectedToken.Value)
test.Fail()
}

for z, actual := range actualToken.Value.([]string) {

if actual != expectedToken.Value.([]string)[z] {

test.Logf("Test '%s' failed:", parsingTest.Name)
test.Logf("Expected token value '%v' does not match '%v'", expectedToken.Value, actualToken.Value)
test.Fail()
}
}
continue
}

if actualToken.Value != expectedToken.Value {

test.Logf("Test '%s' failed:", parsingTest.Name)
Expand Down
39 changes: 37 additions & 2 deletions stagePlanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,10 +302,10 @@ func planFunction(stream *tokenStream) (*evaluationStage, error) {

if token.Kind != FUNCTION {
stream.rewind()
return planValue(stream)
return planAccessor(stream)
}

rightStage, err = planValue(stream)
rightStage, err = planAccessor(stream)
if err != nil {
return nil, err
}
Expand All @@ -319,6 +319,37 @@ func planFunction(stream *tokenStream) (*evaluationStage, error) {
}, nil
}

func planAccessor(stream *tokenStream) (*evaluationStage, error) {

var token ExpressionToken
var rightStage *evaluationStage
var err error

if !stream.hasNext() {
return nil, nil
}

token = stream.next()

if token.Kind != ACCESSOR {
stream.rewind()
return planValue(stream)
}

rightStage, err = planValue(stream)
if err != nil {
return nil, err
}

return &evaluationStage{

symbol: ACCESS,
rightStage: rightStage,
operator: makeAccessorStage(token.Value.([]string)),
typeErrorFormat: "Unable to access parameter field or method '%v': %v",
}, nil
}

/*
A truly special precedence function, this handles all the "lowest-case" errata of the process, including literals, parmeters,
clauses, and prefixes.
Expand All @@ -331,6 +362,10 @@ func planValue(stream *tokenStream) (*evaluationStage, error) {
var operator evaluationOperator
var err error

if !stream.hasNext() {
return nil, nil
}

token = stream.next()

switch token.Kind {
Expand Down

0 comments on commit f9296ab

Please sign in to comment.