forked from yidane/formula
/
expression.go
131 lines (105 loc) · 3.66 KB
/
expression.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package formula
import (
"bytes"
"errors"
"fmt"
"strings"
"github.com/antlr/antlr4/runtime/Go/antlr"
"github.com/yidane/formula/internal/cache"
"github.com/yidane/formula/internal/exp"
//register system functions
_ "github.com/yidane/formula/internal/fs"
"github.com/yidane/formula/internal/parser"
"github.com/yidane/formula/opt"
)
//Expression for build user's input
type Expression struct {
context *opt.FormulaContext
originalExpression string
parsedExpression *opt.LogicalExpression
}
//NewExpression create new Expression
func NewExpression(expression string, options ...opt.Option) *Expression {
return &Expression{
originalExpression: strings.TrimSpace(expression),
context: opt.NewFormulaContext(options...),
}
}
func (expression *Expression) compile() error {
//handle empty expression
if expression.originalExpression == "" {
expression.parsedExpression = exp.NewEmptyExpression()
return nil
}
//restore expression from cache
logicExpression := cache.Restore(expression.context.Option, expression.originalExpression)
if logicExpression != nil {
expression.parsedExpression = logicExpression
return nil
}
//compile expression
lexer := parser.NewFormulaLexer(antlr.NewInputStream(expression.originalExpression))
formulaParser := parser.NewFormulaParser(antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel))
//handle compile error
errListener := newFormulaErrorListener()
formulaParser.AddErrorListener(errListener)
calcContext := formulaParser.Calc()
if errListener.HasError() {
return errListener.Error()
}
expression.parsedExpression = calcContext.GetRetValue()
return nil
}
//OriginalString return user's input text
func (expression *Expression) OriginalString() string {
return expression.originalExpression
}
//AddParameter add user's parameter which is required in the expression
func (expression *Expression) AddParameter(name string, value interface{}) error {
name = strings.TrimSpace(name)
if name == "" {
return errors.New("argument name can not be empty")
}
if _, ok := expression.context.Parameters[name]; ok {
return fmt.Errorf("argument %s dureplate", name)
}
expression.context.Parameters[name] = value
return nil
}
//ResetParameters clear all parameter
func (expression *Expression) ResetParameters() {
expression.context.Parameters = make(map[string]interface{})
}
//Evaluate return result of expression
func (expression *Expression) Evaluate() (*opt.Argument, error) {
err := expression.compile()
if err != nil {
return nil, err
}
result, err := (*expression.parsedExpression).Evaluate(expression.context)
if err != nil {
return nil, err
}
return result, nil
}
type formulaErrorListener struct {
buf bytes.Buffer
}
func newFormulaErrorListener() *formulaErrorListener {
return new(formulaErrorListener)
}
func (l *formulaErrorListener) HasError() bool {
return l.buf.Len() > 0
}
func (l *formulaErrorListener) Error() error {
return errors.New(l.buf.String())
}
func (l *formulaErrorListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, line, column int, msg string, e antlr.RecognitionException) {
l.buf.WriteString(msg)
}
func (*formulaErrorListener) ReportAmbiguity(recognizer antlr.Parser, dfa *antlr.DFA, startIndex, stopIndex int, exact bool, ambigAlts *antlr.BitSet, configs antlr.ATNConfigSet) {
}
func (*formulaErrorListener) ReportAttemptingFullContext(recognizer antlr.Parser, dfa *antlr.DFA, startIndex, stopIndex int, conflictingAlts *antlr.BitSet, configs antlr.ATNConfigSet) {
}
func (*formulaErrorListener) ReportContextSensitivity(recognizer antlr.Parser, dfa *antlr.DFA, startIndex, stopIndex, prediction int, configs antlr.ATNConfigSet) {
}