package govaluate import ( "errors" "fmt" ) type lexerState struct { isEOF bool isNullable bool kind TokenKind validNextKinds []TokenKind } // lexer states. // Constant for all purposes except compiler. var validLexerStates = []lexerState{ lexerState{ kind: UNKNOWN, isEOF: false, isNullable: true, validNextKinds: []TokenKind{ PREFIX, NUMERIC, BOOLEAN, VARIABLE, PATTERN, FUNCTION, STRING, TIME, CLAUSE, }, }, lexerState{ kind: CLAUSE, isEOF: false, isNullable: true, validNextKinds: []TokenKind{ PREFIX, NUMERIC, BOOLEAN, VARIABLE, PATTERN, FUNCTION, STRING, TIME, CLAUSE, CLAUSE_CLOSE, }, }, lexerState{ kind: CLAUSE_CLOSE, isEOF: true, isNullable: true, validNextKinds: []TokenKind{ COMPARATOR, MODIFIER, NUMERIC, BOOLEAN, VARIABLE, STRING, PATTERN, TIME, CLAUSE, CLAUSE_CLOSE, LOGICALOP, TERNARY, SEPARATOR, }, }, lexerState{ kind: NUMERIC, isEOF: true, isNullable: false, validNextKinds: []TokenKind{ MODIFIER, COMPARATOR, LOGICALOP, CLAUSE_CLOSE, TERNARY, SEPARATOR, }, }, lexerState{ kind: BOOLEAN, isEOF: true, isNullable: false, validNextKinds: []TokenKind{ MODIFIER, COMPARATOR, LOGICALOP, CLAUSE_CLOSE, TERNARY, SEPARATOR, }, }, lexerState{ kind: STRING, isEOF: true, isNullable: false, validNextKinds: []TokenKind{ MODIFIER, COMPARATOR, LOGICALOP, CLAUSE_CLOSE, TERNARY, SEPARATOR, }, }, lexerState{ kind: TIME, isEOF: true, isNullable: false, validNextKinds: []TokenKind{ MODIFIER, COMPARATOR, LOGICALOP, CLAUSE_CLOSE, SEPARATOR, }, }, lexerState{ kind: PATTERN, isEOF: true, isNullable: false, validNextKinds: []TokenKind{ MODIFIER, COMPARATOR, LOGICALOP, CLAUSE_CLOSE, SEPARATOR, }, }, lexerState{ kind: VARIABLE, isEOF: true, isNullable: false, validNextKinds: []TokenKind{ MODIFIER, COMPARATOR, LOGICALOP, CLAUSE_CLOSE, TERNARY, SEPARATOR, }, }, lexerState{ kind: MODIFIER, isEOF: false, isNullable: false, validNextKinds: []TokenKind{ PREFIX, NUMERIC, VARIABLE, FUNCTION, STRING, BOOLEAN, CLAUSE, CLAUSE_CLOSE, }, }, lexerState{ kind: COMPARATOR, isEOF: false, isNullable: false, validNextKinds: []TokenKind{ PREFIX, NUMERIC, BOOLEAN, VARIABLE, FUNCTION, STRING, TIME, CLAUSE, CLAUSE_CLOSE, PATTERN, }, }, lexerState{ kind: LOGICALOP, isEOF: false, isNullable: false, validNextKinds: []TokenKind{ PREFIX, NUMERIC, BOOLEAN, VARIABLE, FUNCTION, STRING, TIME, CLAUSE, CLAUSE_CLOSE, }, }, lexerState{ kind: PREFIX, isEOF: false, isNullable: false, validNextKinds: []TokenKind{ NUMERIC, BOOLEAN, VARIABLE, FUNCTION, CLAUSE, CLAUSE_CLOSE, }, }, lexerState{ kind: TERNARY, isEOF: false, isNullable: false, validNextKinds: []TokenKind{ PREFIX, NUMERIC, BOOLEAN, STRING, TIME, VARIABLE, FUNCTION, CLAUSE, SEPARATOR, }, }, lexerState{ kind: FUNCTION, isEOF: false, isNullable: false, validNextKinds: []TokenKind{ CLAUSE, }, }, lexerState{ kind: SEPARATOR, isEOF: false, isNullable: true, validNextKinds: []TokenKind{ PREFIX, NUMERIC, BOOLEAN, STRING, TIME, VARIABLE, FUNCTION, CLAUSE, }, }, } func (this lexerState) canTransitionTo(kind TokenKind) bool { for _, validKind := range this.validNextKinds { if validKind == kind { return true } } return false } func checkExpressionSyntax(tokens []ExpressionToken) error { var state lexerState var lastToken ExpressionToken var err error state = validLexerStates[0] for _, token := range tokens { if !state.canTransitionTo(token.Kind) { // call out a specific error for tokens looking like they want to be functions. if lastToken.Kind == VARIABLE && token.Kind == CLAUSE { return errors.New("Undefined function " + lastToken.Value.(string)) } firstStateName := fmt.Sprintf("%s [%v]", state.kind.String(), lastToken.Value) nextStateName := fmt.Sprintf("%s [%v]", token.Kind.String(), token.Value) return errors.New("Cannot transition token types from " + firstStateName + " to " + nextStateName) } state, err = getLexerStateForToken(token.Kind) if err != nil { return err } if !state.isNullable && token.Value == nil { errorMsg := fmt.Sprintf("Token kind '%v' cannot have a nil value", token.Kind.String()) return errors.New(errorMsg) } lastToken = token } if !state.isEOF { return errors.New("Unexpected end of expression") } return nil } func getLexerStateForToken(kind TokenKind) (lexerState, error) { for _, possibleState := range validLexerStates { if possibleState.kind == kind { return possibleState, nil } } errorMsg := fmt.Sprintf("No lexer state found for token kind '%v'\n", kind.String()) return validLexerStates[0], errors.New(errorMsg) }