├── .github └── workflows │ └── go.yml ├── .gitignore ├── .golangci.yml ├── LICENSE.txt ├── README.md ├── engine.go ├── engine_test.go ├── examples └── discount.go ├── go.mod ├── go.sum ├── internal └── parser │ ├── evaluate.go │ ├── gen_lin.go │ ├── gen_win.go │ ├── lexer.go │ ├── parser.go │ ├── parser.go.y │ ├── parser_test.go │ └── parserutils.go ├── rule.go └── rule_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | go: 16 | - '1.19' 17 | - '1.18' 18 | - '1.20' 19 | 20 | steps: 21 | - uses: actions/setup-go@v3 22 | with: 23 | go-version: ${{ matrix.go }} 24 | - uses: actions/checkout@v3 25 | - run: go test -v -coverprofile=coverage.txt -race -covermode=atomic ./... 26 | 27 | # - name: golangci-lint 28 | # uses: golangci/golangci-lint-action@v3 29 | # with: 30 | # # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. 31 | # version: v1.50.1 32 | 33 | - name: Upload coverage reports to Codecov 34 | uses: codecov/codecov-action@v3 35 | 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /internal/parser/y.output 2 | /.idea 3 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # This code is licensed under the terms of the MIT license. 2 | 3 | ## Golden config for golangci-lint v1.51.1 4 | # 5 | # This is the best config for golangci-lint based on my experience and opinion. 6 | # It is very strict, but not extremely strict. 7 | # Feel free to adopt and change it for your needs. 8 | 9 | run: 10 | # Timeout for analysis, e.g. 30s, 5m. 11 | # Default: 1m 12 | timeout: 3m 13 | 14 | 15 | # This file contains only configs which differ from defaults. 16 | # All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml 17 | linters-settings: 18 | cyclop: 19 | # The maximal code complexity to report. 20 | # Default: 10 21 | max-complexity: 30 22 | # The maximal average package complexity. 23 | # If it's higher than 0.0 (float) the check is enabled 24 | # Default: 0.0 25 | package-average: 10.0 26 | 27 | errcheck: 28 | # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. 29 | # Such cases aren't reported by default. 30 | # Default: false 31 | check-type-assertions: true 32 | 33 | exhaustive: 34 | # Program elements to check for exhaustiveness. 35 | # Default: [ switch ] 36 | check: 37 | - switch 38 | - map 39 | 40 | exhaustruct: 41 | # List of regular expressions to exclude struct packages and names from check. 42 | # Default: [] 43 | exclude: 44 | # std libs 45 | - "^net/http.Client$" 46 | - "^net/http.Cookie$" 47 | - "^net/http.Request$" 48 | - "^net/http.Response$" 49 | - "^net/http.Server$" 50 | - "^net/http.Transport$" 51 | - "^net/url.URL$" 52 | - "^os/exec.Cmd$" 53 | - "^reflect.StructField$" 54 | # public libs 55 | - "^github.com/Shopify/sarama.Config$" 56 | - "^github.com/Shopify/sarama.ProducerMessage$" 57 | - "^github.com/mitchellh/mapstructure.DecoderConfig$" 58 | - "^github.com/prometheus/client_golang/.+Opts$" 59 | - "^github.com/spf13/cobra.Command$" 60 | - "^github.com/spf13/cobra.CompletionOptions$" 61 | - "^golang.org/x/tools/go/analysis.Analyzer$" 62 | - "^google.golang.org/protobuf/.+Options$" 63 | - "^gopkg.in/yaml.v3.Node$" 64 | 65 | funlen: 66 | # Checks the number of lines in a function. 67 | # If lower than 0, disable the check. 68 | # Default: 60 69 | lines: 100 70 | # Checks the number of statements in a function. 71 | # If lower than 0, disable the check. 72 | # Default: 40 73 | statements: 50 74 | 75 | gocognit: 76 | # Minimal code complexity to report. 77 | # Default: 30 (but we recommend 10-20) 78 | min-complexity: 20 79 | 80 | gocritic: 81 | # Settings passed to gocritic. 82 | # The settings key is the name of a supported gocritic checker. 83 | # The list of supported checkers can be find in https://go-critic.github.io/overview. 84 | settings: 85 | captLocal: 86 | # Whether to restrict checker to params only. 87 | # Default: true 88 | paramsOnly: false 89 | underef: 90 | # Whether to skip (*x).method() calls where x is a pointer receiver. 91 | # Default: true 92 | skipRecvDeref: false 93 | 94 | gomnd: 95 | # List of function patterns to exclude from analysis. 96 | # Values always ignored: `time.Date`, 97 | # `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`, 98 | # `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`. 99 | # Default: [] 100 | ignored-functions: 101 | - os.Chmod 102 | - os.Mkdir 103 | - os.MkdirAll 104 | - os.OpenFile 105 | - os.WriteFile 106 | - prometheus.ExponentialBuckets 107 | - prometheus.ExponentialBucketsRange 108 | - prometheus.LinearBuckets 109 | 110 | gomodguard: 111 | blocked: 112 | # List of blocked modules. 113 | # Default: [] 114 | modules: 115 | - github.com/golang/protobuf: 116 | recommendations: 117 | - google.golang.org/protobuf 118 | reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules" 119 | - github.com/satori/go.uuid: 120 | recommendations: 121 | - github.com/google/uuid 122 | reason: "satori's package is not maintained" 123 | - github.com/gofrs/uuid: 124 | recommendations: 125 | - github.com/google/uuid 126 | reason: "gofrs' package is not go module" 127 | 128 | govet: 129 | # Enable all analyzers. 130 | # Default: false 131 | enable-all: true 132 | # Disable analyzers by name. 133 | # Run `go tool vet help` to see all analyzers. 134 | # Default: [] 135 | disable: 136 | - fieldalignment # too strict 137 | # Settings per analyzer. 138 | settings: 139 | shadow: 140 | # Whether to be strict about shadowing; can be noisy. 141 | # Default: false 142 | strict: true 143 | 144 | nakedret: 145 | # Make an issue if func has more lines of code than this setting, and it has naked returns. 146 | # Default: 30 147 | max-func-lines: 0 148 | 149 | nolintlint: 150 | # Exclude following linters from requiring an explanation. 151 | # Default: [] 152 | allow-no-explanation: [ funlen, gocognit, lll ] 153 | # Enable to require an explanation of nonzero length after each nolint directive. 154 | # Default: false 155 | require-explanation: true 156 | # Enable to require nolint directives to mention the specific linter being suppressed. 157 | # Default: false 158 | require-specific: true 159 | 160 | rowserrcheck: 161 | # database/sql is always checked 162 | # Default: [] 163 | packages: 164 | - github.com/jmoiron/sqlx 165 | 166 | tenv: 167 | # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. 168 | # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. 169 | # Default: false 170 | all: true 171 | 172 | 173 | linters: 174 | disable-all: true 175 | enable: 176 | ## enabled by default 177 | - errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases 178 | - gosimple # specializes in simplifying a code 179 | - govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string 180 | - ineffassign # detects when assignments to existing variables are not used 181 | - staticcheck # is a go vet on steroids, applying a ton of static analysis checks 182 | - typecheck # like the front-end of a Go compiler, parses and type-checks Go code 183 | - unused # checks for unused constants, variables, functions and types 184 | ## disabled by default 185 | - asasalint # checks for pass []any as any in variadic func(...any) 186 | - asciicheck # checks that your code does not contain non-ASCII identifiers 187 | - bidichk # checks for dangerous unicode character sequences 188 | - cyclop # checks function and package cyclomatic complexity 189 | - dupl # tool for code clone detection 190 | - durationcheck # checks for two durations multiplied together 191 | - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error 192 | - errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13 193 | - exhaustive # checks exhaustiveness of enum switch statements 194 | - exportloopref # checks for pointers to enclosing loop variables 195 | - forbidigo # forbids identifiers 196 | - funlen # tool for detection of long functions 197 | - gochecknoglobals # checks that no global variables exist 198 | - gochecknoinits # checks that no init functions are present in Go code 199 | - gocognit # computes and checks the cognitive complexity of functions 200 | - goconst # finds repeated strings that could be replaced by a constant 201 | - gocritic # provides diagnostics that check for bugs, performance and style issues 202 | - gocyclo # computes and checks the cyclomatic complexity of functions 203 | - godot # checks if comments end in a period 204 | - goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt 205 | - gomnd # detects magic numbers 206 | - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod 207 | - gomodguard # allow and block lists linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations 208 | - goprintffuncname # checks that printf-like functions are named with f at the end 209 | - gosec # inspects source code for security problems 210 | - lll # reports long lines 211 | - makezero # finds slice declarations with non-zero initial length 212 | - nakedret # finds naked returns in functions greater than a specified function length 213 | - nestif # reports deeply nested if statements 214 | - nilerr # finds the code that returns nil even if it checks that the error is not nil 215 | - nilnil # checks that there is no simultaneous return of nil error and an invalid value 216 | - noctx # finds sending http request without context.Context 217 | - nolintlint # reports ill-formed or insufficient nolint directives 218 | - nonamedreturns # reports all named returns 219 | - nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL 220 | - predeclared # finds code that shadows one of Go's predeclared identifiers 221 | - reassign # checks that package variables are not reassigned 222 | - revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint 223 | - stylecheck # is a replacement for golint 224 | - tenv # detects using os.Setenv instead of t.Setenv since Go1.17 225 | - testableexamples # checks if examples are testable (have an expected output) 226 | - testpackage # makes you use a separate _test package 227 | - tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes 228 | - unconvert # removes unnecessary type conversions 229 | - unparam # reports unused function parameters 230 | - usestdlibvars # detects the possibility to use variables/constants from the Go standard library 231 | - wastedassign # finds wasted assignment statements 232 | 233 | ## you may want to enable 234 | #- decorder # checks declaration order and count of types, constants, variables and functions 235 | #- exhaustruct # [highly recommend to enable] checks if all structure fields are initialized 236 | #- gci # controls golang package import order and makes it always deterministic 237 | #- ginkgolinter # [if you use ginkgo/gomega] enforces standards of using ginkgo and gomega 238 | #- godox # detects FIXME, TODO and other comment keywords 239 | #- goheader # checks is file header matches to pattern 240 | #- interfacebloat # checks the number of methods inside an interface 241 | #- ireturn # accept interfaces, return concrete types 242 | #- prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated 243 | #- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope 244 | #- wrapcheck # checks that errors returned from external packages are wrapped 245 | 246 | ## disabled 247 | #- containedctx # detects struct contained context.Context field 248 | #- contextcheck # [too many false positives] checks the function whether use a non-inherited context 249 | #- depguard # [replaced by gomodguard] checks if package imports are in a list of acceptable packages 250 | #- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) 251 | #- dupword # [useless without config] checks for duplicate words in the source code 252 | #- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted 253 | #- forcetypeassert # [replaced by errcheck] finds forced type assertions 254 | #- goerr113 # [too strict] checks the errors handling expressions 255 | #- gofmt # [replaced by goimports] checks whether code was gofmt-ed 256 | #- gofumpt # [replaced by goimports, gofumports is not available yet] checks whether code was gofumpt-ed 257 | #- grouper # analyzes expression groups 258 | #- importas # enforces consistent import aliases 259 | #- maintidx # measures the maintainability index of each function 260 | #- misspell # [useless] finds commonly misspelled English words in comments 261 | #- nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity 262 | #- paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test 263 | #- tagliatelle # checks the struct tags 264 | #- thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers 265 | #- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines 266 | 267 | ## deprecated 268 | #- deadcode # [deprecated, replaced by unused] finds unused code 269 | #- exhaustivestruct # [deprecated, replaced by exhaustruct] checks if all struct's fields are initialized 270 | #- golint # [deprecated, replaced by revive] golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes 271 | #- ifshort # [deprecated] checks that your code uses short syntax for if-statements whenever possible 272 | #- interfacer # [deprecated] suggests narrower interface types 273 | #- maligned # [deprecated, replaced by govet fieldalignment] detects Go structs that would take less memory if their fields were sorted 274 | #- nosnakecase # [deprecated, replaced by revive var-naming] detects snake case of variable naming and function name 275 | #- scopelint # [deprecated, replaced by exportloopref] checks for unpinned variables in go programs 276 | #- structcheck # [deprecated, replaced by unused] finds unused struct fields 277 | #- varcheck # [deprecated, replaced by unused] finds unused global variables and constants 278 | 279 | 280 | issues: 281 | # Maximum count of issues with the same text. 282 | # Set to 0 to disable. 283 | # Default: 3 284 | max-same-issues: 50 285 | 286 | exclude-rules: 287 | - source: "^//\\s*go:generate\\s" 288 | linters: [ lll ] 289 | - source: "(noinspection|TODO)" 290 | linters: [ godot ] 291 | - source: "//noinspection" 292 | linters: [ gocritic ] 293 | - source: "^\\s+if _, ok := err\\.\\([^.]+\\.InternalError\\); ok {" 294 | linters: [ errorlint ] 295 | - path: "_test\\.go" 296 | linters: 297 | - bodyclose 298 | - dupl 299 | - funlen 300 | - goconst 301 | - gosec 302 | - noctx 303 | - wrapcheck -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 spikewong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GoRule 2 | ===== 3 | [![codecov](https://codecov.io/gh/SpikeWong/gorule/branch/master/graph/badge.svg?token=0GT7UT61NS)](https://codecov.io/gh/SpikeWong/gorule) 4 | ![workflow status](https://github.com/SpikeWong/gorule/actions/workflows/go.yml/badge.svg) 5 | Gorule is a rules engine written based on [goyacc](https://pkg.go.dev/golang.org/x/tools/cmd/goyacc). The rule engine is mainly used to replace complex and frequently changed hard-coded conditional judgement scenarios. The rules defining the trigger conditions are decoupled from the program code and can be stored in files, database records etc. and loaded at runtime for parsing. The program provides input to the rules engine, which eventually matches and returns a collection of successful rules. 6 | 7 | 8 | This project is licensed under the terms of the MIT license. 9 | 10 | # Usage 11 | ```shell 12 | go get -u github.com/spikewong/gorule 13 | ``` 14 | 15 | Minimal example: 16 | ```go 17 | engine := gorule.NewEngine( 18 | gorule.WithLogger(log.New(os.Stdout, "", log.LstdFlags)), 19 | gorule.WithConfig(&gorule.Config{SkipBadRuleDuringMatch: false}), 20 | ) 21 | rule := gorule.NewRule( 22 | "example rule name", "isAgeMatched(age) && undergraduate", 23 | func(i interface{}) (interface{}, error) { 24 | return "teenager", nil 25 | }) 26 | 27 | err := engine.AddRule(rule) 28 | if err != nil { 29 | fmt.Printf("add rule error: %v", err) 30 | } 31 | 32 | isAgeMatched := func(args ...interface{}) (interface{}, error) { 33 | age := args[0].(int) 34 | 35 | return age > 10 && age < 18, nil 36 | } 37 | 38 | 39 | rules, err := engine.Match( 40 | map[string]interface{}{ "age": 12, "undergraduate": true }, 41 | map[string]parser.ExpressionFunction{ 42 | "isAgeMatched": isAgeMatched, 43 | }) 44 | if err != nil { 45 | fmt.Printf("engine match error: %v", err) 46 | } 47 | 48 | for _, v := range rules { 49 | fmt.Println(v.Execute(nil)) // return "teenager", nil 50 | } 51 | ``` 52 | you can find another example under examples directory 53 | 54 | # Supported rule expressions 55 | 56 | ## Types 57 | 58 | This library fully supports the following types: `nil`, `bool`, `int`, `float64`, `string`, `[]interface{}` (=arrays) and `map[string]interface{}` (=objects). 59 | 60 | Within expressions, `int` and `float64` both have the type `number` and are completely transparent.\ 61 | If necessary, numerical values will be automatically converted between `int` and `float64`, as long as no precision is lost. 62 | 63 | Arrays and Objects are untyped. They can store any other value ("mixed arrays"). 64 | 65 | Structs are note supported to keep the functionality clear and manageable. 66 | They would introduce too many edge cases and loose ends and are therefore out-of-scope. 67 | 68 | ## Variables 69 | 70 | It is possible to directly access custom-defined variables. 71 | Variables are read-only and cannot be modified from within expressions. 72 | 73 | Examples: 74 | 75 | ``` 76 | var 77 | var.field 78 | var[0] 79 | var["field"] 80 | var[anotherVar] 81 | 82 | var["fie" + "ld"].field[42 - var2][0] 83 | ``` 84 | 85 | ## Functions 86 | It is possible to call custom-defined functions from within expressions. 87 | 88 | Examples: 89 | 90 | ``` 91 | rand() 92 | floor(42) 93 | min(4, 3, 12, max(1, 3, 3)) 94 | len("te" + "xt") 95 | ``` 96 | 97 | ## Literals 98 | 99 | Any literal can be defined within expressions. 100 | String literals can be put in double-quotes `"` or back-ticks \`. 101 | Hex-literals start with the prefix `0x`. 102 | 103 | Examples: 104 | 105 | ``` 106 | nil 107 | true 108 | false 109 | 3 110 | 3.2 111 | "Hello, 世界!\n" 112 | "te\"xt" 113 | `te"xt` 114 | [0, 1, 2] 115 | [] 116 | [0, ["text", false], 4.2] 117 | {} 118 | {"a": 1, "b": {c: 3}} 119 | {"key" + 42: "value"} 120 | {"k" + "e" + "y": "value"} 121 | 122 | 0xA // 10 123 | 0x0A // 10 124 | 0xFF // 255 125 | 0xFFFFFFFF // 32bit appl.: -1 64bit appl.: 4294967295 126 | 0xFFFFFFFFFFFFFFFF // 64bit appl.: -1 32bit appl.: error 127 | ``` 128 | 129 | It is possible to access elements of array and object literals: 130 | 131 | Examples: 132 | 133 | ``` 134 | [1, 2, 3][1] // 2 135 | [1, [2, 3, 42][1][2] // 42 136 | 137 | {"a": 1}.a // 1 138 | {"a": {"b": 42}}.a.b // 42 139 | {"a": {"b": 42}}["a"]["b"] // 42 140 | ``` 141 | 142 | ## Precedence 143 | 144 | Operator precedence strictly follows [C/C++ rules](http://en.cppreference.com/w/cpp/language/operator_precedence). 145 | 146 | Parenthesis `()` is used to control precedence. 147 | 148 | Examples: 149 | 150 | ``` 151 | 1 + 2 * 3 // 7 152 | (1 + 2) * 3 // 9 153 | ``` 154 | 155 | ## Operators 156 | 157 | ### Arithmetic 158 | 159 | #### Arithmetic `+` `-` `*` `/` 160 | 161 | If both sides are integers, the resulting value is also an integer. 162 | Otherwise, the result will be a floating point number. 163 | 164 | Examples: 165 | 166 | ``` 167 | 3 + 4 // 7 168 | 2 + 2 * 3 // 8 169 | 2 * 3 + 2.5 // 8.5 170 | 12 - 7 - 5 // 0 171 | 24 / 10 // 2 172 | 24.0 / 10 // 2.4 173 | ``` 174 | 175 | #### Modulo `%` 176 | 177 | If both sides are integers, the resulting value is also an integer. 178 | Otherwise, the result will be a floating point number. 179 | 180 | Examples: 181 | 182 | ``` 183 | 4 % 3 // 1 184 | 144 % 85 // -55 185 | 5.5 % 2 // 1.5 186 | 10 % 3.5 // 3.0 187 | ``` 188 | 189 | #### Negation `-` (unary minus) 190 | 191 | Negates the number on the right. 192 | 193 | Examples: 194 | 195 | ``` 196 | -4 // -4 197 | 5 + -4 // 1 198 | -5 - -4 // -1 199 | 1 + --1 // syntax error 200 | -(4+3) // -7 201 | -varName 202 | ``` 203 | 204 | 205 | ### Concatenation 206 | 207 | #### String concatenation `+` 208 | 209 | If either the left or right side of the `+` operator is a `string`, a string concatenation is performed. 210 | Supports strings, numbers, booleans and nil. 211 | 212 | Examples: 213 | 214 | ``` 215 | "text" + 42 // "text42" 216 | "text" + 4.2 // "text4.2" 217 | 42 + "text" // "42text" 218 | "text" + nil // "textnil" 219 | "text" + true // "texttrue" 220 | ``` 221 | 222 | #### Array concatenation `+` 223 | 224 | If both sides of the `+` operator are arrays, they are concatenated 225 | 226 | Examples: 227 | 228 | ``` 229 | [0, 1] + [2, 3] // [0, 1, 2, 3] 230 | [0] + [1] + [[2]] + [] // [0, 1, [2]] 231 | ``` 232 | 233 | #### Object concatenation `+` 234 | 235 | If both sides of the `+` operator are objects, their fields are combined into a new object. 236 | If both objects contain the same keys, the value of the right object will override those of the left. 237 | 238 | Examples: 239 | 240 | ``` 241 | {"a": 1} + {"b": 2} + {"c": 3} // {"a": 1, "b": 2, "c": 3} 242 | {"a": 1, "b": 2} + {"b": 3, "c": 4} // {"a": 1, "b": 3, "c": 4} 243 | {"b": 3, "c": 4} + {"a": 1, "b": 2} // {"a": 1, "b": 2, "c": 4} 244 | ``` 245 | 246 | ### Logic 247 | 248 | #### Equals `==`, NotEquals `!=` 249 | 250 | Performs a deep-compare between the two operands. 251 | When comparing `int` and `float64`, 252 | the integer will be cast to a floating point number. 253 | 254 | #### Comparisons `<`, `>`, `<=`, `>=` 255 | 256 | Compares two numbers. If one side of the operator is an integer and the other is a floating point number, 257 | the integer number will be cast. This might lead to unexpected results for very big numbers which are rounded 258 | during that process. 259 | 260 | Examples: 261 | 262 | ``` 263 | 3 <-4 // false 264 | 45 > 3.4 // false 265 | -4 <= -1 // true 266 | 3.5 >= 3.5 // true 267 | ``` 268 | 269 | #### And `&&`, Or `||` 270 | 271 | Examples: 272 | 273 | ``` 274 | true && true // true 275 | false || false // false 276 | true || false && false // true 277 | false && false || true // true 278 | ``` 279 | 280 | 281 | #### Not `!` 282 | 283 | Inverts the boolean on the right. 284 | 285 | Examples: 286 | 287 | ``` 288 | !true // false 289 | !false // true 290 | !!true // true 291 | !varName 292 | ``` 293 | 294 | 295 | ### Ternary `? :` 296 | 297 | If the expression resolves to `true`, the operator resolves to the left operand. \ 298 | If the expression resolves to `false`, the operator resolves to the right operand. 299 | 300 | Examples: 301 | 302 | ``` 303 | true ? 1 : 2 // 1 304 | false ? 1 : 2 // 2 305 | 306 | 2 < 5 ? "a" : 1.5 // "a" 307 | 9 > 12 ? "a" : [42] // [42] 308 | 309 | false ? (true ? 1:2) : (true ? 3:4) // 3 310 | ``` 311 | 312 | 313 | Note that all operands are resolved (no short-circuiting). 314 | In the following example, both functions are called (the return value of `func2` is simply ignored): 315 | 316 | ``` 317 | true ? func1() : func2() 318 | ``` 319 | 320 | ### Bit Manipulation 321 | 322 | #### Logical Or `|`, Logical And `&`, Logical XOr `^` 323 | 324 | If one side of the operator is a floating point number, the number is cast to an integer if possible. 325 | If decimal places would be lost during that process, it is considered a type error. 326 | The resulting number is always an integer. 327 | 328 | Examples: 329 | 330 | ``` 331 | 8 | 2 // 10 332 | 9 | 5 // 13 333 | 8 | 2.0 // 10 334 | 8 | 2.1 // type error 335 | 336 | 13 & 10 // 8 337 | 10 & 15.0 & 2 // 2 338 | 339 | 13 ^ 10 // 7 340 | 10 ^ 15 ^ 1 // 4 341 | ``` 342 | 343 | #### Bitwise Not `~` 344 | 345 | If performed on a floating point number, the number is cast to an integer if possible. 346 | If decimal places would be lost during that process, it is considered a type error. 347 | The resulting number is always an integer. 348 | 349 | The results can differ between 32bit and 64bit architectures. 350 | 351 | Examples: 352 | 353 | ``` 354 | ~-1 // 0 355 | (~0xA55A) & 0xFFFF // 0x5AA5 356 | (~0x5AA5) & 0xFFFF // 0xA55A 357 | 358 | ~0xFFFFFFFF // 64bit appl.: 0xFFFFFFFF 00000000; 32bit appl.: 0x00 359 | ~0xFFFFFFFF FFFFFFFF // 64bit appl.: 0x00; 32bit: error 360 | ``` 361 | 362 | #### Bit-Shift `<<`, `>>` 363 | 364 | If one side of the operator is a floating point number, the number is cast to an integer if possible. 365 | If decimal places would be lost during that process, it is considered a type error. 366 | The resulting number is always an integer. 367 | 368 | When shifting to the right, sign-extension is performed. 369 | The results can differ between 32bit and 64bit architectures. 370 | 371 | Examples: 372 | 373 | ``` 374 | 1 << 0 // 1 375 | 1 << 1 // 2 376 | 1 << 2 // 4 377 | 8 << -1 // 4 378 | 8 >> -1 // 16 379 | 380 | 1 << 31 // 0x00000000 80000000 64bit appl.: 2147483648; 32bit appl.: -2147483648 381 | 1 << 32 // 0x00000001 00000000 32bit appl.: 0 (overflow) 382 | 383 | 1 << 63 // 0x80000000 00000000 32bit appl.: 0 (overflow); 64bit appl.: -9223372036854775808 384 | 1 << 64 // 0x00000000 00000000 0 (overflow) 385 | 386 | 0x80000000 00000000 >> 63 // 0xFFFFFFFF FFFFFFFF 64bit: -1 (sign extension); 32bit: error (cannot parse number literal) 387 | 0x80000000 >> 31 // 64bit: 0x00000000 0000001; 32bit: 0xFFFFFFFF (-1, sign extension) 388 | ``` 389 | 390 | ### More 391 | 392 | #### Array contains `in` 393 | 394 | Returns true or false whether the array contains a specific element. 395 | 396 | Examples: 397 | 398 | ``` 399 | "txt" in [nil, "hello", "txt", 42] // true 400 | true in [nil, "hello", "txt", 42] // false 401 | nil in [nil, "hello", "txt", 42] // true 402 | 42.0 in [nil, "hello", "txt", 42] // true 403 | 2 in [1, [2, 3], 4] // false 404 | [2, 3] in [1, [2, 3], 4] // true 405 | [2, 3, 4] in [1, [2, 3], 4] // false 406 | ``` 407 | 408 | #### Substrings `[a:b]` 409 | 410 | Slices a string and returns the given substring. 411 | Strings are indexed byte-wise. Multi-byte characters need to be treated carefully. 412 | 413 | The start-index indicates the first byte to be present in the substring.\ 414 | The end-index indicates the last byte NOT to be present in the substring.\ 415 | Hence, valid indices are in the range `[0, len(str)]`. 416 | 417 | Examples: 418 | 419 | ``` 420 | "abcdefg"[:] // "abcdefg" 421 | "abcdefg"[1:] // "bcdefg" 422 | "abcdefg"[:6] // "abcdef" 423 | "abcdefg"[2:5] // "cde" 424 | "abcdefg"[3:4] // "d" 425 | 426 | // The characters 世 and 界 both require 3 bytes: 427 | "Hello, 世界"[7:13] // "世界" 428 | "Hello, 世界"[7:10] // "世" 429 | "Hello, 世界"[10:13] // "界" 430 | ``` 431 | 432 | 433 | #### Array Slicing `[a:b]` 434 | 435 | Slices an array and returns the given subarray. 436 | 437 | The start-index indicates the first element to be present in the subarray.\ 438 | The end-index indicates the last element NOT to be present in the subarray.\ 439 | Hence, valid indices are in the range `[0, len(arr)]`. 440 | 441 | Examples: 442 | 443 | ``` 444 | // Assuming `arr := [0, 1, 2, 3, 4, 5, 6]`: 445 | arr[:] // [0, 1, 2, 3, 4, 5, 6] 446 | arr[1:] // [1, 2, 3, 4, 5, 6] 447 | arr[:6] // [0, 1, 2, 3, 4, 5] 448 | arr[2:5] // [2, 3, 4] 449 | arr[3:4] // [3] 450 | ``` 451 | 452 | -------------------------------------------------------------------------------- /engine.go: -------------------------------------------------------------------------------- 1 | package gorule 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "os" 8 | "sync" 9 | 10 | "github.com/davecgh/go-spew/spew" 11 | 12 | "github.com/spikewong/gorule/internal/parser" 13 | ) 14 | 15 | var ( 16 | ErrRuleExists = errors.New("rule name already exists") 17 | ErrNonBooleanResult = errors.New("encountered non boolean result during eval rule") 18 | ) 19 | 20 | type Config struct { 21 | SkipBadRuleDuringMatch bool 22 | } 23 | 24 | type Engine struct { 25 | mu sync.Mutex 26 | 27 | rules map[string]*Rule 28 | config *Config 29 | logger *log.Logger 30 | } 31 | 32 | type Option func(*Engine) 33 | 34 | // NewEngine initializes engine with options 35 | // by default SkipBadRuleDuringMatch is false, which means will return error if error 36 | // or non-boolean value encountered during Match. 37 | func NewEngine(opts ...Option) *Engine { 38 | engine := &Engine{ 39 | rules: make(map[string]*Rule), 40 | config: &Config{SkipBadRuleDuringMatch: false}, 41 | logger: log.New(os.Stdout, "", log.LstdFlags), 42 | } 43 | 44 | for _, opt := range opts { 45 | opt(engine) 46 | } 47 | 48 | return engine 49 | } 50 | 51 | // WithConfig sets config for engine. 52 | func WithConfig(config *Config) Option { 53 | return func(e *Engine) { 54 | e.config = config 55 | } 56 | } 57 | 58 | // WithLogger sets logger for engine. 59 | func WithLogger(logger *log.Logger) Option { 60 | return func(e *Engine) { 61 | e.logger = logger 62 | } 63 | } 64 | 65 | // AddRule adds rule into engine, return error if rule name exists. 66 | func (e *Engine) AddRule(rule *Rule) error { 67 | e.mu.Lock() 68 | defer e.mu.Unlock() 69 | 70 | for name, _ := range e.rules { 71 | if name == rule.Name() { 72 | return fmt.Errorf("%w: %s", ErrRuleExists, name) 73 | } 74 | } 75 | 76 | e.rules[rule.Name()] = rule 77 | 78 | return nil 79 | } 80 | 81 | // Match iterates through all the rules of the engine and will return the matching rules. 82 | func (e *Engine) Match(vars map[string]interface{}, functions map[string]parser.ExpressionFunction) ([]Rule, error) { 83 | matchedRules := make([]Rule, 0) 84 | 85 | for _, r := range e.rules { 86 | res, err := parser.Evaluate(r.condition, vars, functions) 87 | matched, ok := res.(bool) 88 | if !e.config.SkipBadRuleDuringMatch { 89 | if err != nil { 90 | e.logger.Printf("Error: rule %s returned unexpected error during match: %v", r.Name(), err) 91 | return nil, fmt.Errorf("unexpected error occured during match: %w", err) 92 | } else if !ok { 93 | e.logger.Printf("Error: rule %s returned non-boolean value with vars %v", r.Name(), spew.Sdump(vars)) 94 | return nil, fmt.Errorf("%s: %w", r.Name(), ErrNonBooleanResult) 95 | } 96 | } 97 | 98 | if matched { 99 | matchedRules = append(matchedRules, *r) 100 | } 101 | } 102 | 103 | return matchedRules, nil 104 | } 105 | -------------------------------------------------------------------------------- /engine_test.go: -------------------------------------------------------------------------------- 1 | package gorule 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "log" 7 | "os" 8 | "reflect" 9 | "regexp" 10 | "sync" 11 | "testing" 12 | 13 | "github.com/spikewong/gorule/internal/parser" 14 | ) 15 | 16 | func TestNewEngine(t *testing.T) { 17 | type args struct { 18 | opts []Option 19 | } 20 | tests := []struct { 21 | name string 22 | args args 23 | want *Engine 24 | }{ 25 | { 26 | name: "initialize engine", 27 | args: args{opts: []Option{}}, 28 | want: &Engine{ 29 | mu: sync.Mutex{}, 30 | rules: make(map[string]*Rule, 0), 31 | config: &Config{SkipBadRuleDuringMatch: false}, 32 | logger: log.New(os.Stdout, "", log.LstdFlags), 33 | }, 34 | }, 35 | { 36 | name: "initialize engine with opts", 37 | args: args{opts: []Option{ 38 | WithConfig(&Config{SkipBadRuleDuringMatch: true}), 39 | WithLogger(log.New(io.Discard, "", log.LstdFlags)), 40 | }}, 41 | want: &Engine{ 42 | mu: sync.Mutex{}, 43 | rules: make(map[string]*Rule, 0), 44 | config: &Config{SkipBadRuleDuringMatch: true}, 45 | logger: log.New(io.Discard, "", log.LstdFlags), 46 | }, 47 | }, 48 | } 49 | for _, tt := range tests { 50 | t.Run(tt.name, func(t *testing.T) { 51 | if got := NewEngine(tt.args.opts...); !reflect.DeepEqual(got, tt.want) { 52 | t.Errorf("NewEngine() = %v, want %v", got, tt.want) 53 | } 54 | }) 55 | } 56 | } 57 | 58 | func TestEngine_AddRule(t *testing.T) { 59 | type fields struct { 60 | mu sync.Mutex 61 | rules map[string]*Rule 62 | config *Config 63 | logger *log.Logger 64 | } 65 | type args struct { 66 | rule *Rule 67 | } 68 | 69 | rule := NewRule("test_rule@v1", "x + y > 2", func(i interface{}) (interface{}, error) { 70 | return "matched", nil 71 | }) 72 | 73 | tests := []struct { 74 | name string 75 | fields fields 76 | args args 77 | wantErr bool 78 | }{ 79 | { 80 | name: "happy path", 81 | fields: fields{ 82 | mu: sync.Mutex{}, 83 | rules: make(map[string]*Rule), 84 | config: &Config{SkipBadRuleDuringMatch: true}, 85 | logger: log.New(io.Discard, "", log.LstdFlags), 86 | }, 87 | args: args{ 88 | rule: rule, 89 | }, 90 | wantErr: false, 91 | }, 92 | { 93 | name: "error: rule name exists", 94 | fields: fields{ 95 | mu: sync.Mutex{}, 96 | rules: map[string]*Rule{rule.Name(): rule}, 97 | config: &Config{SkipBadRuleDuringMatch: true}, 98 | logger: log.New(io.Discard, "", log.LstdFlags), 99 | }, 100 | args: args{rule: rule}, 101 | wantErr: true, 102 | }, 103 | } 104 | for _, tt := range tests { 105 | t.Run(tt.name, func(t *testing.T) { 106 | e := &Engine{ 107 | mu: tt.fields.mu, 108 | rules: tt.fields.rules, 109 | config: tt.fields.config, 110 | logger: tt.fields.logger, 111 | } 112 | if err := e.AddRule(tt.args.rule); (err != nil) != tt.wantErr { 113 | t.Errorf("AddRule() error = %v, wantErr %v", err, tt.wantErr) 114 | } 115 | }) 116 | } 117 | } 118 | 119 | func TestEngine_MatchWithoutFunctions(t *testing.T) { 120 | type fields struct { 121 | mu sync.Mutex 122 | rules map[string]*Rule 123 | config *Config 124 | logger *log.Logger 125 | } 126 | type args struct { 127 | vars map[string]interface{} 128 | functions map[string]parser.ExpressionFunction 129 | } 130 | 131 | badGradeRule := NewRule( 132 | "if grade less than 40, then inform the parents", 133 | "grade < 40", 134 | func(i interface{}) (interface{}, error) { 135 | return "bad", nil 136 | }) 137 | passedGradeRule := NewRule( 138 | "If grade gt 60, then passed", 139 | "grade > 60", 140 | func(i interface{}) (interface{}, error) { 141 | return "passed", nil 142 | }) 143 | gradeRules := map[string]*Rule{ 144 | badGradeRule.Name(): badGradeRule, 145 | passedGradeRule.Name(): passedGradeRule, 146 | } 147 | 148 | tests := []struct { 149 | name string 150 | fields fields 151 | args args 152 | want []Rule 153 | wantErr bool 154 | }{ 155 | { 156 | name: "happy path, matched one rule", 157 | fields: fields{ 158 | mu: sync.Mutex{}, 159 | rules: gradeRules, 160 | config: &Config{SkipBadRuleDuringMatch: false}, 161 | logger: log.New(os.Stdout, "", log.LstdFlags), 162 | }, 163 | args: args{vars: map[string]interface{}{"grade": 30}}, 164 | wantErr: false, 165 | want: []Rule{*badGradeRule}, 166 | }, 167 | { 168 | name: "happy path, not matched any rule", 169 | fields: fields{ 170 | mu: sync.Mutex{}, 171 | rules: gradeRules, 172 | config: &Config{SkipBadRuleDuringMatch: false}, 173 | logger: log.New(os.Stdout, "", log.LstdFlags), 174 | }, 175 | args: args{vars: map[string]interface{}{"grade": 50}}, 176 | wantErr: false, 177 | want: []Rule{}, 178 | }, 179 | { 180 | name: "error: missing vars", 181 | fields: fields{ 182 | mu: sync.Mutex{}, 183 | rules: gradeRules, 184 | config: &Config{SkipBadRuleDuringMatch: false}, 185 | logger: log.New(os.Stdout, "", log.LstdFlags), 186 | }, 187 | args: args{vars: map[string]interface{}{}}, 188 | wantErr: true, 189 | }, 190 | } 191 | for _, tt := range tests { 192 | t.Run(tt.name, func(t *testing.T) { 193 | e := &Engine{ 194 | mu: tt.fields.mu, 195 | rules: tt.fields.rules, 196 | config: tt.fields.config, 197 | logger: tt.fields.logger, 198 | } 199 | got, err := e.Match(tt.args.vars, tt.args.functions) 200 | if (err != nil) != tt.wantErr { 201 | t.Errorf("Match() error = %v, wantErr %v", err, tt.wantErr) 202 | return 203 | } 204 | if len(got) != len(tt.want) { 205 | t.Errorf("the length of got and want are not euqal, got: %d, want: %d", len(got), len(tt.want)) 206 | return 207 | } 208 | for i, v := range got { 209 | if v.Name() != tt.want[i].Name() { 210 | t.Errorf("matched rule not equal, got: %s, want: %s", v.Name(), tt.want[i].Name()) 211 | return 212 | } 213 | } 214 | }) 215 | } 216 | } 217 | 218 | func TestEngine_MatchWithFunctions(t *testing.T) { 219 | type fields struct { 220 | mu sync.Mutex 221 | rules map[string]*Rule 222 | config *Config 223 | logger *log.Logger 224 | } 225 | type args struct { 226 | vars map[string]interface{} 227 | functions map[string]parser.ExpressionFunction 228 | } 229 | 230 | regexRule := NewRule( 231 | "match string based on regex", 232 | `matches(text, regex)`, 233 | func(i interface{}) (interface{}, error) { 234 | return "matched", nil 235 | }, 236 | ) 237 | goodFunctions := map[string]parser.ExpressionFunction{ 238 | "matches": func(args ...interface{}) (interface{}, error) { 239 | return regexp.MustCompile(args[1].(string)).MatchString(args[0].(string)), nil 240 | }, 241 | } 242 | badFunctions := map[string]parser.ExpressionFunction{ 243 | "matches": func(args ...interface{}) (interface{}, error) { 244 | return nil, errors.New("bad function") 245 | }, 246 | } 247 | 248 | tests := []struct { 249 | name string 250 | fields fields 251 | args args 252 | want []Rule 253 | wantErr bool 254 | }{ 255 | { 256 | name: "good function", 257 | fields: fields{ 258 | mu: sync.Mutex{}, 259 | rules: map[string]*Rule{regexRule.Name(): regexRule}, 260 | config: &Config{SkipBadRuleDuringMatch: false}, 261 | logger: log.New(os.Stdout, "", log.LstdFlags), 262 | }, 263 | args: args{ 264 | vars: map[string]interface{}{ 265 | "text": "hello world", 266 | "regex": "hello.*", 267 | }, 268 | functions: goodFunctions, 269 | }, 270 | want: []Rule{*regexRule}, 271 | wantErr: false, 272 | }, 273 | { 274 | name: "bad function", 275 | fields: fields{ 276 | mu: sync.Mutex{}, 277 | rules: map[string]*Rule{regexRule.Name(): regexRule}, 278 | config: &Config{SkipBadRuleDuringMatch: false}, 279 | logger: log.New(os.Stdout, "", log.LstdFlags), 280 | }, 281 | args: args{ 282 | vars: map[string]interface{}{ 283 | "text": "hello world", 284 | "regex": "hello.*", 285 | }, 286 | functions: badFunctions, 287 | }, 288 | want: nil, 289 | wantErr: true, 290 | }, 291 | } 292 | for _, tt := range tests { 293 | t.Run(tt.name, func(t *testing.T) { 294 | e := &Engine{ 295 | mu: tt.fields.mu, 296 | rules: tt.fields.rules, 297 | config: tt.fields.config, 298 | logger: tt.fields.logger, 299 | } 300 | got, err := e.Match(tt.args.vars, tt.args.functions) 301 | if (err != nil) != tt.wantErr { 302 | t.Errorf("Match() error = %v, wantErr %v", err, tt.wantErr) 303 | return 304 | } 305 | if len(got) != len(tt.want) { 306 | t.Errorf("the length of got and want are not euqal, got: %d, want: %d", len(got), len(tt.want)) 307 | return 308 | } 309 | for i, v := range got { 310 | if v.Name() != tt.want[i].Name() { 311 | t.Errorf("matched rule not equal, got: %s, want: %s", v.Name(), tt.want[i].Name()) 312 | return 313 | } 314 | } 315 | }) 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /examples/discount.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/spikewong/gorule" 6 | "github.com/spikewong/gorule/internal/parser" 7 | "log" 8 | "os" 9 | ) 10 | 11 | type User struct { 12 | balance float64 13 | vipLevel int 14 | inBlackList bool 15 | } 16 | 17 | func main() { 18 | // A simple rule engine used to calculate discount 19 | var ( 20 | blacklistDiscount, vipDiscount int 21 | 22 | userInBlackList = &User{ 23 | inBlackList: true, 24 | } 25 | vip = &User{ 26 | vipLevel: 10, 27 | balance: 100, 28 | inBlackList: false, 29 | } 30 | 31 | blackListRule = gorule.NewRule( 32 | "black list rule", 33 | "inBlacklist", 34 | func(i interface{}) (interface{}, error) { 35 | return 0, nil 36 | }, 37 | ) 38 | vipRule = gorule.NewRule( 39 | "high level vip rule", 40 | "isPremiumVip(vipLevel) && !inBlacklist", 41 | func(i interface{}) (interface{}, error) { 42 | return 30, nil 43 | }, 44 | ) 45 | ) 46 | 47 | discountEngine := gorule.NewEngine( 48 | gorule.WithLogger(log.New(os.Stdout, "discount", log.LstdFlags)), 49 | gorule.WithConfig(&gorule.Config{SkipBadRuleDuringMatch: false}), 50 | ) 51 | for _, v := range []*gorule.Rule{vipRule, blackListRule} { 52 | if err := discountEngine.AddRule(v); err != nil { 53 | fmt.Printf("add rule failed: %v", err) 54 | } 55 | } 56 | 57 | isPremiumVip := func(args ...interface{}) (interface{}, error) { 58 | vipLevel := args[0].(int) 59 | 60 | return vipLevel > 5, nil 61 | } 62 | 63 | matchedRules, err := discountEngine.Match( 64 | map[string]interface{}{ 65 | "inBlacklist": userInBlackList.inBlackList, 66 | "balance": userInBlackList.balance, 67 | "vipLevel": userInBlackList.vipLevel, 68 | }, 69 | map[string]parser.ExpressionFunction{"isPremiumVip": isPremiumVip}, 70 | ) 71 | if err != nil { 72 | fmt.Printf("encountered error when cal blacklist discount: %v \n", err) 73 | } 74 | for _, v := range matchedRules { 75 | d, _ := v.Execute(nil) 76 | discount, _ := d.(int) 77 | blacklistDiscount += discount 78 | } 79 | 80 | matchedRules, err = discountEngine.Match( 81 | map[string]interface{}{ 82 | "inBlacklist": vip.inBlackList, 83 | "balance": vip.balance, 84 | "vipLevel": vip.vipLevel, 85 | }, 86 | map[string]parser.ExpressionFunction{"isPremiumVip": isPremiumVip}, 87 | ) 88 | if err != nil { 89 | fmt.Printf("encountered error when cal vip discount: %v \n", err) 90 | } 91 | for _, v := range matchedRules { 92 | d, _ := v.Execute(nil) 93 | discount, _ := d.(int) 94 | vipDiscount += discount 95 | } 96 | 97 | fmt.Printf("blacklist discount: %v, vip discount: %v", blacklistDiscount, vipDiscount) 98 | } 99 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/spikewong/gorule 2 | 3 | go 1.18 4 | 5 | require github.com/stretchr/testify v1.8.1 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 8 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 9 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 10 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 11 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 12 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 13 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 14 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 15 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 16 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 17 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 18 | -------------------------------------------------------------------------------- /internal/parser/evaluate.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "runtime" 5 | ) 6 | 7 | func Evaluate( 8 | str string, 9 | variables map[string]interface{}, 10 | functions map[string]ExpressionFunction, 11 | ) (result interface{}, err error) { 12 | defer func() { 13 | if r := recover(); r != nil { 14 | if _, ok := r.(runtime.Error); ok { 15 | panic(r) 16 | } 17 | err = r.(error) 18 | } 19 | }() 20 | 21 | lexer := NewLexer(str, variables, functions) 22 | yyNewParser().Parse(lexer) 23 | return lexer.Result(), nil 24 | } 25 | -------------------------------------------------------------------------------- /internal/parser/gen_lin.go: -------------------------------------------------------------------------------- 1 | //go:build linux || darwin 2 | // +build linux darwin 3 | 4 | package parser 5 | 6 | //go:generate echo "Generating parser 'using golang.org/x/tools/cmd/goyacc...'" 7 | //go:generate goyacc -o parser.go parser.go.y 8 | -------------------------------------------------------------------------------- /internal/parser/gen_win.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package parser 5 | 6 | //go:generate cmd /C echo "Generating parser 'using golang.org/x/tools/cmd/goyacc...'" 7 | //go:generate goyacc.exe -o parser.go parser.go.y 8 | -------------------------------------------------------------------------------- /internal/parser/lexer.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "go/scanner" 7 | "go/token" 8 | "strconv" 9 | "strings" 10 | "unsafe" 11 | ) 12 | 13 | const BitSizeOfInt = int(unsafe.Sizeof(0)) * 8 14 | 15 | type Token struct { 16 | literal string 17 | value interface{} 18 | } 19 | 20 | type Lexer struct { 21 | scanner scanner.Scanner 22 | result interface{} 23 | 24 | nextTokenType int 25 | nextTokenInfo Token 26 | 27 | variables map[string]interface{} 28 | functions map[string]ExpressionFunction 29 | } 30 | 31 | func NewLexer(src string, variables map[string]interface{}, functions map[string]ExpressionFunction) *Lexer { 32 | if variables == nil { 33 | variables = map[string]interface{}{} 34 | } 35 | if functions == nil { 36 | functions = map[string]ExpressionFunction{} 37 | } 38 | 39 | lexer := &Lexer{ 40 | variables: variables, 41 | functions: functions, 42 | } 43 | 44 | fset := token.NewFileSet() 45 | file := fset.AddFile("", fset.Base(), len(src)) 46 | 47 | lexer.scanner.Init(file, []byte(src), nil, 0) 48 | return lexer 49 | } 50 | 51 | func (l *Lexer) scan() (token.Pos, token.Token, string) { 52 | for { 53 | pos, tok, lit := l.scanner.Scan() 54 | if tok == token.SEMICOLON && lit == "\n" { 55 | // go/scanner automatically inserted this token --> ignore it 56 | continue 57 | } 58 | if tok.IsKeyword() { 59 | // go knows about keywords, we don't. So we treat them as simple identifiers 60 | tok = token.IDENT 61 | } 62 | return pos, tok, lit 63 | } 64 | } 65 | func (l *Lexer) Lex(lval *yySymType) int { 66 | var tokenType int 67 | var err error 68 | 69 | if l.nextTokenType > 0 { 70 | // The last scan-operation returned multiple tokens, so we return the remaining one 71 | tokenType = l.nextTokenType 72 | l.nextTokenType = 0 73 | lval.token = l.nextTokenInfo 74 | return tokenType 75 | } 76 | 77 | pos, tok, lit := l.scan() 78 | 79 | tokenInfo := Token{ 80 | value: nil, 81 | literal: lit, 82 | } 83 | 84 | switch tok { 85 | 86 | case token.EOF: 87 | tokenType = 0 88 | 89 | // Literals 90 | 91 | case token.INT: 92 | tokenType = LITERAL_NUMBER 93 | hex := strings.TrimPrefix(lit, "0x") 94 | if len(hex) < len(lit) { 95 | var hexVal uint64 96 | hexVal, err = strconv.ParseUint(hex, 16, BitSizeOfInt) 97 | tokenInfo.value = int(hexVal) 98 | } else { 99 | tokenInfo.value, err = strconv.Atoi(lit) 100 | } 101 | if err != nil { 102 | l.Perrorf(pos, "parse error: cannot parse integer") 103 | } 104 | case token.FLOAT: 105 | tokenType = LITERAL_NUMBER 106 | tokenInfo.value, err = strconv.ParseFloat(lit, 64) 107 | if err != nil { 108 | l.Perrorf(pos, "parse error: cannot parse float") 109 | } 110 | 111 | case token.STRING: 112 | tokenType = LITERAL_STRING 113 | tokenInfo.value, err = strconv.Unquote(lit) 114 | if err != nil { 115 | l.Perrorf(pos, "parse error: cannot unquote string literal") 116 | } 117 | 118 | // Arithmetic 119 | 120 | case token.ADD, token.SUB, token.MUL, token.QUO, token.REM: 121 | tokenType = int(tok.String()[0]) 122 | 123 | // Logic 124 | 125 | case token.NOT: 126 | tokenType = int(tok.String()[0]) 127 | 128 | case token.LAND: 129 | tokenType = AND 130 | case token.LOR: 131 | tokenType = OR 132 | 133 | case token.EQL: 134 | tokenType = EQL 135 | case token.NEQ: 136 | tokenType = NEQ 137 | 138 | case token.LSS: 139 | tokenType = LSS 140 | case token.GTR: 141 | tokenType = GTR 142 | case token.LEQ: 143 | tokenType = LEQ 144 | case token.GEQ: 145 | tokenType = GEQ 146 | 147 | case token.ARROW: 148 | // This token is known by go, but not within our expressions. 149 | // Instead, we treat it as two tokens (less and unary-minus). 150 | tokenType = LSS 151 | tokenInfo.literal = "<" 152 | // Remember the minus-operator and omit it the next time: 153 | l.nextTokenType = int('-') 154 | l.nextTokenInfo = Token{ 155 | value: nil, 156 | literal: "-", 157 | } 158 | 159 | // Bit manipulations 160 | 161 | case token.AND, token.OR, token.XOR: 162 | tokenType = int(tok.String()[0]) 163 | 164 | case token.SHL: 165 | tokenType = SHL 166 | case token.SHR: 167 | tokenType = SHR 168 | 169 | case token.IDENT: 170 | if lit == "nil" { 171 | tokenType = LITERAL_NIL 172 | } else if lit == "true" { 173 | tokenType = LITERAL_BOOL 174 | tokenInfo.value = true 175 | } else if lit == "false" { 176 | tokenType = LITERAL_BOOL 177 | tokenInfo.value = false 178 | } else if lit == "in" || lit == "IN" { 179 | tokenType = IN 180 | } else { 181 | tokenType = IDENT 182 | } 183 | 184 | case token.PERIOD: 185 | tokenType = int('.') 186 | 187 | case token.COMMA: 188 | tokenType = int(',') 189 | 190 | case token.COLON: 191 | tokenType = int(':') 192 | 193 | case token.LBRACK, token.RBRACK, 194 | token.LBRACE, token.RBRACE, 195 | token.LPAREN, token.RPAREN: 196 | tokenType = int(tok.String()[0]) 197 | 198 | case token.TILDE: 199 | tokenType = BIT_NOT 200 | 201 | case token.ILLEGAL: 202 | if lit == "?" { 203 | tokenType = int('?') 204 | break 205 | } 206 | if lit == ":" { 207 | tokenType = int(':') 208 | break 209 | } 210 | if lit == "~" { // for backwards compatibility with old go versions where token.TILDE did not exist yet 211 | tokenType = BIT_NOT 212 | break 213 | } 214 | fallthrough 215 | 216 | default: 217 | l.Perrorf(pos, "unknown token %q (%q)", tok.String(), lit) 218 | } 219 | 220 | lval.token = tokenInfo 221 | return tokenType 222 | } 223 | 224 | func (l *Lexer) Error(e string) { 225 | panic(errors.New(e)) 226 | } 227 | 228 | func (l *Lexer) Perrorf(pos token.Pos, format string, a ...interface{}) { 229 | if pos.IsValid() { 230 | format = format + " at position " + strconv.Itoa(int(pos)) 231 | } 232 | panic(fmt.Errorf(format, a...)) 233 | } 234 | 235 | func (l *Lexer) Result() interface{} { 236 | return l.result 237 | } 238 | -------------------------------------------------------------------------------- /internal/parser/parser.go: -------------------------------------------------------------------------------- 1 | // Code generated by goyacc -o parser.go parser.go.y. DO NOT EDIT. 2 | 3 | //line parser.go.y:2 4 | package parser 5 | 6 | import __yyfmt__ "fmt" 7 | 8 | //line parser.go.y:2 9 | 10 | //line parser.go.y:6 11 | type yySymType struct { 12 | yys int 13 | token Token 14 | expr interface{} 15 | exprList []interface{} 16 | exprMap map[string]interface{} 17 | } 18 | 19 | const LITERAL_NIL = 57346 20 | const LITERAL_BOOL = 57347 21 | const LITERAL_NUMBER = 57348 22 | const LITERAL_STRING = 57349 23 | const IDENT = 57350 24 | const AND = 57351 25 | const OR = 57352 26 | const EQL = 57353 27 | const NEQ = 57354 28 | const LSS = 57355 29 | const GTR = 57356 30 | const LEQ = 57357 31 | const GEQ = 57358 32 | const SHL = 57359 33 | const SHR = 57360 34 | const BIT_NOT = 57361 35 | const IN = 57362 36 | 37 | var yyToknames = [...]string{ 38 | "$end", 39 | "error", 40 | "$unk", 41 | "LITERAL_NIL", 42 | "LITERAL_BOOL", 43 | "LITERAL_NUMBER", 44 | "LITERAL_STRING", 45 | "IDENT", 46 | "AND", 47 | "OR", 48 | "EQL", 49 | "NEQ", 50 | "LSS", 51 | "GTR", 52 | "LEQ", 53 | "GEQ", 54 | "SHL", 55 | "SHR", 56 | "BIT_NOT", 57 | "IN", 58 | "'?'", 59 | "':'", 60 | "'|'", 61 | "'^'", 62 | "'&'", 63 | "'+'", 64 | "'-'", 65 | "'*'", 66 | "'/'", 67 | "'%'", 68 | "'!'", 69 | "'.'", 70 | "'['", 71 | "']'", 72 | "'('", 73 | "')'", 74 | "'{'", 75 | "'}'", 76 | "','", 77 | } 78 | 79 | var yyStatenames = [...]string{} 80 | 81 | const yyEofCode = 1 82 | const yyErrCode = 2 83 | const yyInitialStackSize = 16 84 | 85 | //line parser.go.y:145 86 | 87 | //line yacctab:1 88 | var yyExca = [...]int8{ 89 | -1, 1, 90 | 1, -1, 91 | -2, 0, 92 | } 93 | 94 | const yyPrivate = 57344 95 | 96 | const yyLast = 561 97 | 98 | var yyAct = [...]int8{ 99 | 45, 2, 88, 80, 81, 79, 78, 71, 42, 41, 100 | 47, 79, 44, 38, 39, 7, 48, 49, 50, 51, 101 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 102 | 62, 63, 64, 65, 66, 67, 68, 69, 70, 6, 103 | 72, 74, 31, 32, 25, 26, 27, 28, 29, 30, 104 | 36, 37, 5, 40, 19, 77, 33, 35, 34, 20, 105 | 21, 22, 23, 24, 40, 38, 39, 4, 3, 75, 106 | 1, 0, 0, 0, 86, 0, 38, 39, 0, 0, 107 | 89, 0, 90, 91, 92, 0, 93, 31, 32, 25, 108 | 26, 27, 28, 29, 30, 36, 37, 98, 40, 19, 109 | 85, 33, 35, 34, 20, 21, 22, 23, 24, 0, 110 | 38, 39, 84, 31, 32, 25, 26, 27, 28, 29, 111 | 30, 36, 37, 0, 40, 19, 0, 33, 35, 34, 112 | 20, 21, 22, 23, 24, 0, 38, 39, 97, 31, 113 | 32, 25, 26, 27, 28, 29, 30, 36, 37, 0, 114 | 40, 19, 0, 33, 35, 34, 20, 21, 22, 23, 115 | 24, 0, 38, 39, 95, 31, 32, 25, 26, 27, 116 | 28, 29, 30, 36, 37, 0, 40, 19, 96, 33, 117 | 35, 34, 20, 21, 22, 23, 24, 0, 38, 39, 118 | 31, 32, 25, 26, 27, 28, 29, 30, 36, 37, 119 | 0, 40, 19, 83, 33, 35, 34, 20, 21, 22, 120 | 23, 24, 0, 38, 39, 31, 32, 25, 26, 27, 121 | 28, 29, 30, 36, 37, 0, 40, 19, 82, 33, 122 | 35, 34, 20, 21, 22, 23, 24, 0, 38, 39, 123 | 31, 32, 25, 26, 27, 28, 29, 30, 36, 37, 124 | 0, 40, 19, 0, 33, 35, 34, 20, 21, 22, 125 | 23, 24, 0, 38, 39, 31, 0, 25, 26, 27, 126 | 28, 29, 30, 36, 37, 0, 40, 0, 0, 33, 127 | 35, 34, 20, 21, 22, 23, 24, 0, 38, 39, 128 | 25, 26, 27, 28, 29, 30, 36, 37, 0, 40, 129 | 0, 0, 33, 35, 34, 20, 21, 22, 23, 24, 130 | 0, 38, 39, 25, 26, 27, 28, 29, 30, 36, 131 | 37, 0, 40, 0, 0, 0, 35, 34, 20, 21, 132 | 22, 23, 24, 0, 38, 39, 25, 26, 27, 28, 133 | 29, 30, 36, 37, 0, 40, 0, 0, 0, 0, 134 | 34, 20, 21, 22, 23, 24, 0, 38, 39, 10, 135 | 11, 12, 13, 9, 0, 0, 0, 40, 10, 11, 136 | 12, 13, 9, 0, 18, 22, 23, 24, 0, 38, 137 | 39, 0, 16, 18, 0, 0, 17, 0, 14, 0, 138 | 8, 16, 15, 46, 0, 17, 0, 14, 94, 8, 139 | 0, 15, 10, 11, 12, 13, 9, 27, 28, 29, 140 | 30, 36, 37, 0, 40, 0, 0, 18, 0, 0, 141 | 20, 21, 22, 23, 24, 16, 38, 39, 0, 17, 142 | 0, 14, 87, 8, 0, 15, 25, 26, 27, 28, 143 | 29, 30, 36, 37, 0, 40, 10, 11, 12, 13, 144 | 9, 20, 21, 22, 23, 24, 0, 38, 39, 0, 145 | 0, 18, 0, 0, 0, 0, 0, 0, 0, 16, 146 | 0, 0, 0, 17, 0, 14, 0, 8, 76, 15, 147 | 10, 11, 12, 13, 9, 10, 11, 12, 13, 9, 148 | 0, 0, 0, 0, 0, 18, 0, 0, 73, 0, 149 | 18, 0, 0, 16, 0, 0, 0, 17, 16, 14, 150 | 0, 8, 17, 15, 14, 43, 8, 0, 15, 10, 151 | 11, 12, 13, 9, 0, 0, 0, 0, 36, 37, 152 | 0, 40, 0, 0, 18, 0, 0, 20, 21, 22, 153 | 23, 24, 16, 38, 39, 0, 17, 40, 14, 0, 154 | 8, 0, 15, 20, 21, 22, 23, 24, 0, 38, 155 | 39, 156 | } 157 | 158 | var yyPact = [...]int16{ 159 | 515, -1000, 231, -1000, -1000, -1000, -1000, -1000, 515, -27, 160 | -1000, -1000, -1000, -1000, 481, 355, 515, 515, 515, 515, 161 | 515, 515, 515, 515, 515, 515, 515, 515, 515, 515, 162 | 515, 515, 515, 515, 515, 515, 515, 515, -1, 476, 163 | 515, 33, 442, -1000, -28, 231, -1000, -35, 206, 44, 164 | 44, 44, 181, 347, 347, 44, 44, 44, 394, 394, 165 | 511, 511, 511, 511, 279, 256, 302, 425, 325, 527, 166 | 527, -1000, 78, 398, -19, -1000, -1000, -34, -1000, 515, 167 | -1000, 515, 515, 515, -1000, 364, 130, -1000, -1000, 231, 168 | 156, 231, 231, 104, -1000, -1000, 515, -1000, 231, 169 | } 170 | 171 | var yyPgo = [...]int8{ 172 | 0, 70, 0, 68, 67, 52, 39, 15, 12, 10, 173 | } 174 | 175 | var yyR1 = [...]int8{ 176 | 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 177 | 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 178 | 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 179 | 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 180 | 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 181 | 9, 9, 182 | } 183 | 184 | var yyR2 = [...]int8{ 185 | 0, 1, 1, 1, 1, 1, 1, 5, 3, 3, 186 | 4, 1, 1, 1, 1, 2, 3, 2, 3, 2, 187 | 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 188 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 189 | 1, 3, 4, 3, 6, 5, 5, 4, 1, 3, 190 | 3, 5, 191 | } 192 | 193 | var yyChk = [...]int16{ 194 | -1000, -1, -2, -3, -4, -5, -6, -7, 35, 8, 195 | 4, 5, 6, 7, 33, 37, 27, 31, 19, 21, 196 | 26, 27, 28, 29, 30, 11, 12, 13, 14, 15, 197 | 16, 9, 10, 23, 25, 24, 17, 18, 32, 33, 198 | 20, -2, 35, 34, -8, -2, 38, -9, -2, -2, 199 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 200 | -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 201 | -2, 8, -2, 22, -2, 36, 36, -8, 34, 39, 202 | 38, 39, 22, 22, 34, 22, -2, 34, 36, -2, 203 | -2, -2, -2, -2, 34, 34, 22, 34, -2, 204 | } 205 | 206 | var yyDef = [...]int8{ 207 | 0, -2, 1, 2, 3, 4, 5, 6, 0, 40, 208 | 11, 12, 13, 14, 0, 0, 0, 0, 0, 0, 209 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 210 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 211 | 0, 0, 0, 15, 0, 48, 17, 0, 0, 19, 212 | 25, 39, 0, 20, 21, 22, 23, 24, 26, 27, 213 | 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 214 | 38, 41, 0, 0, 43, 8, 9, 0, 16, 0, 215 | 18, 0, 0, 0, 42, 0, 0, 47, 10, 49, 216 | 0, 50, 7, 0, 46, 45, 0, 44, 51, 217 | } 218 | 219 | var yyTok1 = [...]int8{ 220 | 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 221 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 222 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 223 | 3, 3, 3, 31, 3, 3, 3, 30, 25, 3, 224 | 35, 36, 28, 26, 39, 27, 32, 29, 3, 3, 225 | 3, 3, 3, 3, 3, 3, 3, 3, 22, 3, 226 | 3, 3, 3, 21, 3, 3, 3, 3, 3, 3, 227 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 228 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 229 | 3, 33, 3, 34, 24, 3, 3, 3, 3, 3, 230 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 231 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 232 | 3, 3, 3, 37, 23, 38, 233 | } 234 | 235 | var yyTok2 = [...]int8{ 236 | 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 237 | 12, 13, 14, 15, 16, 17, 18, 19, 20, 238 | } 239 | 240 | var yyTok3 = [...]int8{ 241 | 0, 242 | } 243 | 244 | var yyErrorMessages = [...]struct { 245 | state int 246 | token int 247 | msg string 248 | }{} 249 | 250 | //line yaccpar:1 251 | 252 | /* parser for yacc output */ 253 | 254 | var ( 255 | yyDebug = 0 256 | yyErrorVerbose = false 257 | ) 258 | 259 | type yyLexer interface { 260 | Lex(lval *yySymType) int 261 | Error(s string) 262 | } 263 | 264 | type yyParser interface { 265 | Parse(yyLexer) int 266 | Lookahead() int 267 | } 268 | 269 | type yyParserImpl struct { 270 | lval yySymType 271 | stack [yyInitialStackSize]yySymType 272 | char int 273 | } 274 | 275 | func (p *yyParserImpl) Lookahead() int { 276 | return p.char 277 | } 278 | 279 | func yyNewParser() yyParser { 280 | return &yyParserImpl{} 281 | } 282 | 283 | const yyFlag = -1000 284 | 285 | func yyTokname(c int) string { 286 | if c >= 1 && c-1 < len(yyToknames) { 287 | if yyToknames[c-1] != "" { 288 | return yyToknames[c-1] 289 | } 290 | } 291 | return __yyfmt__.Sprintf("tok-%v", c) 292 | } 293 | 294 | func yyStatname(s int) string { 295 | if s >= 0 && s < len(yyStatenames) { 296 | if yyStatenames[s] != "" { 297 | return yyStatenames[s] 298 | } 299 | } 300 | return __yyfmt__.Sprintf("state-%v", s) 301 | } 302 | 303 | func yyErrorMessage(state, lookAhead int) string { 304 | const TOKSTART = 4 305 | 306 | if !yyErrorVerbose { 307 | return "syntax error" 308 | } 309 | 310 | for _, e := range yyErrorMessages { 311 | if e.state == state && e.token == lookAhead { 312 | return "syntax error: " + e.msg 313 | } 314 | } 315 | 316 | res := "syntax error: unexpected " + yyTokname(lookAhead) 317 | 318 | // To match Bison, suggest at most four expected tokens. 319 | expected := make([]int, 0, 4) 320 | 321 | // Look for shiftable tokens. 322 | base := int(yyPact[state]) 323 | for tok := TOKSTART; tok-1 < len(yyToknames); tok++ { 324 | if n := base + tok; n >= 0 && n < yyLast && int(yyChk[int(yyAct[n])]) == tok { 325 | if len(expected) == cap(expected) { 326 | return res 327 | } 328 | expected = append(expected, tok) 329 | } 330 | } 331 | 332 | if yyDef[state] == -2 { 333 | i := 0 334 | for yyExca[i] != -1 || int(yyExca[i+1]) != state { 335 | i += 2 336 | } 337 | 338 | // Look for tokens that we accept or reduce. 339 | for i += 2; yyExca[i] >= 0; i += 2 { 340 | tok := int(yyExca[i]) 341 | if tok < TOKSTART || yyExca[i+1] == 0 { 342 | continue 343 | } 344 | if len(expected) == cap(expected) { 345 | return res 346 | } 347 | expected = append(expected, tok) 348 | } 349 | 350 | // If the default action is to accept or reduce, give up. 351 | if yyExca[i+1] != 0 { 352 | return res 353 | } 354 | } 355 | 356 | for i, tok := range expected { 357 | if i == 0 { 358 | res += ", expecting " 359 | } else { 360 | res += " or " 361 | } 362 | res += yyTokname(tok) 363 | } 364 | return res 365 | } 366 | 367 | func yylex1(lex yyLexer, lval *yySymType) (char, token int) { 368 | token = 0 369 | char = lex.Lex(lval) 370 | if char <= 0 { 371 | token = int(yyTok1[0]) 372 | goto out 373 | } 374 | if char < len(yyTok1) { 375 | token = int(yyTok1[char]) 376 | goto out 377 | } 378 | if char >= yyPrivate { 379 | if char < yyPrivate+len(yyTok2) { 380 | token = int(yyTok2[char-yyPrivate]) 381 | goto out 382 | } 383 | } 384 | for i := 0; i < len(yyTok3); i += 2 { 385 | token = int(yyTok3[i+0]) 386 | if token == char { 387 | token = int(yyTok3[i+1]) 388 | goto out 389 | } 390 | } 391 | 392 | out: 393 | if token == 0 { 394 | token = int(yyTok2[1]) /* unknown char */ 395 | } 396 | if yyDebug >= 3 { 397 | __yyfmt__.Printf("lex %s(%d)\n", yyTokname(token), uint(char)) 398 | } 399 | return char, token 400 | } 401 | 402 | func yyParse(yylex yyLexer) int { 403 | return yyNewParser().Parse(yylex) 404 | } 405 | 406 | func (yyrcvr *yyParserImpl) Parse(yylex yyLexer) int { 407 | var yyn int 408 | var yyVAL yySymType 409 | var yyDollar []yySymType 410 | _ = yyDollar // silence set and not used 411 | yyS := yyrcvr.stack[:] 412 | 413 | Nerrs := 0 /* number of errors */ 414 | Errflag := 0 /* error recovery flag */ 415 | yystate := 0 416 | yyrcvr.char = -1 417 | yytoken := -1 // yyrcvr.char translated into internal numbering 418 | defer func() { 419 | // Make sure we report no lookahead when not parsing. 420 | yystate = -1 421 | yyrcvr.char = -1 422 | yytoken = -1 423 | }() 424 | yyp := -1 425 | goto yystack 426 | 427 | ret0: 428 | return 0 429 | 430 | ret1: 431 | return 1 432 | 433 | yystack: 434 | /* put a state and value onto the stack */ 435 | if yyDebug >= 4 { 436 | __yyfmt__.Printf("char %v in %v\n", yyTokname(yytoken), yyStatname(yystate)) 437 | } 438 | 439 | yyp++ 440 | if yyp >= len(yyS) { 441 | nyys := make([]yySymType, len(yyS)*2) 442 | copy(nyys, yyS) 443 | yyS = nyys 444 | } 445 | yyS[yyp] = yyVAL 446 | yyS[yyp].yys = yystate 447 | 448 | yynewstate: 449 | yyn = int(yyPact[yystate]) 450 | if yyn <= yyFlag { 451 | goto yydefault /* simple state */ 452 | } 453 | if yyrcvr.char < 0 { 454 | yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) 455 | } 456 | yyn += yytoken 457 | if yyn < 0 || yyn >= yyLast { 458 | goto yydefault 459 | } 460 | yyn = int(yyAct[yyn]) 461 | if int(yyChk[yyn]) == yytoken { /* valid shift */ 462 | yyrcvr.char = -1 463 | yytoken = -1 464 | yyVAL = yyrcvr.lval 465 | yystate = yyn 466 | if Errflag > 0 { 467 | Errflag-- 468 | } 469 | goto yystack 470 | } 471 | 472 | yydefault: 473 | /* default state action */ 474 | yyn = int(yyDef[yystate]) 475 | if yyn == -2 { 476 | if yyrcvr.char < 0 { 477 | yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) 478 | } 479 | 480 | /* look through exception table */ 481 | xi := 0 482 | for { 483 | if yyExca[xi+0] == -1 && int(yyExca[xi+1]) == yystate { 484 | break 485 | } 486 | xi += 2 487 | } 488 | for xi += 2; ; xi += 2 { 489 | yyn = int(yyExca[xi+0]) 490 | if yyn < 0 || yyn == yytoken { 491 | break 492 | } 493 | } 494 | yyn = int(yyExca[xi+1]) 495 | if yyn < 0 { 496 | goto ret0 497 | } 498 | } 499 | if yyn == 0 { 500 | /* error ... attempt to resume parsing */ 501 | switch Errflag { 502 | case 0: /* brand new error */ 503 | yylex.Error(yyErrorMessage(yystate, yytoken)) 504 | Nerrs++ 505 | if yyDebug >= 1 { 506 | __yyfmt__.Printf("%s", yyStatname(yystate)) 507 | __yyfmt__.Printf(" saw %s\n", yyTokname(yytoken)) 508 | } 509 | fallthrough 510 | 511 | case 1, 2: /* incompletely recovered error ... try again */ 512 | Errflag = 3 513 | 514 | /* find a state where "error" is a legal shift action */ 515 | for yyp >= 0 { 516 | yyn = int(yyPact[yyS[yyp].yys]) + yyErrCode 517 | if yyn >= 0 && yyn < yyLast { 518 | yystate = int(yyAct[yyn]) /* simulate a shift of "error" */ 519 | if int(yyChk[yystate]) == yyErrCode { 520 | goto yystack 521 | } 522 | } 523 | 524 | /* the current p has no shift on "error", pop stack */ 525 | if yyDebug >= 2 { 526 | __yyfmt__.Printf("error recovery pops state %d\n", yyS[yyp].yys) 527 | } 528 | yyp-- 529 | } 530 | /* there is no state on the stack with an error shift ... abort */ 531 | goto ret1 532 | 533 | case 3: /* no shift yet; clobber input char */ 534 | if yyDebug >= 2 { 535 | __yyfmt__.Printf("error recovery discards %s\n", yyTokname(yytoken)) 536 | } 537 | if yytoken == yyEofCode { 538 | goto ret1 539 | } 540 | yyrcvr.char = -1 541 | yytoken = -1 542 | goto yynewstate /* try again in the same state */ 543 | } 544 | } 545 | 546 | /* reduction by production yyn */ 547 | if yyDebug >= 2 { 548 | __yyfmt__.Printf("reduce %v in:\n\t%v\n", yyn, yyStatname(yystate)) 549 | } 550 | 551 | yynt := yyn 552 | yypt := yyp 553 | _ = yypt // guard against "declared and not used" 554 | 555 | yyp -= int(yyR2[yyn]) 556 | // yyp is now the index of $0. Perform the default action. Iff the 557 | // reduced production is ε, $1 is possibly out of range. 558 | if yyp+1 >= len(yyS) { 559 | nyys := make([]yySymType, len(yyS)*2) 560 | copy(nyys, yyS) 561 | yyS = nyys 562 | } 563 | yyVAL = yyS[yyp+1] 564 | 565 | /* consult goto table to find next state */ 566 | yyn = int(yyR1[yyn]) 567 | yyg := int(yyPgo[yyn]) 568 | yyj := yyg + yyS[yyp].yys + 1 569 | 570 | if yyj >= yyLast { 571 | yystate = int(yyAct[yyg]) 572 | } else { 573 | yystate = int(yyAct[yyj]) 574 | if int(yyChk[yystate]) != -yyn { 575 | yystate = int(yyAct[yyg]) 576 | } 577 | } 578 | // dummy call; replaced with literal code 579 | switch yynt { 580 | 581 | case 1: 582 | yyDollar = yyS[yypt-1 : yypt+1] 583 | //line parser.go.y:65 584 | { 585 | yyVAL.expr = yyDollar[1].expr 586 | yylex.(*Lexer).result = yyVAL.expr 587 | } 588 | case 7: 589 | yyDollar = yyS[yypt-5 : yypt+1] 590 | //line parser.go.y:77 591 | { 592 | if asBool(yyDollar[1].expr) { 593 | yyVAL.expr = yyDollar[3].expr 594 | } else { 595 | yyVAL.expr = yyDollar[5].expr 596 | } 597 | } 598 | case 8: 599 | yyDollar = yyS[yypt-3 : yypt+1] 600 | //line parser.go.y:78 601 | { 602 | yyVAL.expr = yyDollar[2].expr 603 | } 604 | case 9: 605 | yyDollar = yyS[yypt-3 : yypt+1] 606 | //line parser.go.y:79 607 | { 608 | yyVAL.expr = callFunction(yylex.(*Lexer).functions, yyDollar[1].token.literal, []interface{}{}) 609 | } 610 | case 10: 611 | yyDollar = yyS[yypt-4 : yypt+1] 612 | //line parser.go.y:80 613 | { 614 | yyVAL.expr = callFunction(yylex.(*Lexer).functions, yyDollar[1].token.literal, yyDollar[3].exprList) 615 | } 616 | case 11: 617 | yyDollar = yyS[yypt-1 : yypt+1] 618 | //line parser.go.y:84 619 | { 620 | yyVAL.expr = nil 621 | } 622 | case 12: 623 | yyDollar = yyS[yypt-1 : yypt+1] 624 | //line parser.go.y:85 625 | { 626 | yyVAL.expr = yyDollar[1].token.value 627 | } 628 | case 13: 629 | yyDollar = yyS[yypt-1 : yypt+1] 630 | //line parser.go.y:86 631 | { 632 | yyVAL.expr = yyDollar[1].token.value 633 | } 634 | case 14: 635 | yyDollar = yyS[yypt-1 : yypt+1] 636 | //line parser.go.y:87 637 | { 638 | yyVAL.expr = yyDollar[1].token.value 639 | } 640 | case 15: 641 | yyDollar = yyS[yypt-2 : yypt+1] 642 | //line parser.go.y:88 643 | { 644 | yyVAL.expr = []interface{}{} 645 | } 646 | case 16: 647 | yyDollar = yyS[yypt-3 : yypt+1] 648 | //line parser.go.y:89 649 | { 650 | yyVAL.expr = yyDollar[2].exprList 651 | } 652 | case 17: 653 | yyDollar = yyS[yypt-2 : yypt+1] 654 | //line parser.go.y:90 655 | { 656 | yyVAL.expr = map[string]interface{}{} 657 | } 658 | case 18: 659 | yyDollar = yyS[yypt-3 : yypt+1] 660 | //line parser.go.y:91 661 | { 662 | yyVAL.expr = yyDollar[2].exprMap 663 | } 664 | case 19: 665 | yyDollar = yyS[yypt-2 : yypt+1] 666 | //line parser.go.y:95 667 | { 668 | yyVAL.expr = unaryMinus(yyDollar[2].expr) 669 | } 670 | case 20: 671 | yyDollar = yyS[yypt-3 : yypt+1] 672 | //line parser.go.y:96 673 | { 674 | yyVAL.expr = add(yyDollar[1].expr, yyDollar[3].expr) 675 | } 676 | case 21: 677 | yyDollar = yyS[yypt-3 : yypt+1] 678 | //line parser.go.y:97 679 | { 680 | yyVAL.expr = sub(yyDollar[1].expr, yyDollar[3].expr) 681 | } 682 | case 22: 683 | yyDollar = yyS[yypt-3 : yypt+1] 684 | //line parser.go.y:98 685 | { 686 | yyVAL.expr = mul(yyDollar[1].expr, yyDollar[3].expr) 687 | } 688 | case 23: 689 | yyDollar = yyS[yypt-3 : yypt+1] 690 | //line parser.go.y:99 691 | { 692 | yyVAL.expr = div(yyDollar[1].expr, yyDollar[3].expr) 693 | } 694 | case 24: 695 | yyDollar = yyS[yypt-3 : yypt+1] 696 | //line parser.go.y:100 697 | { 698 | yyVAL.expr = mod(yyDollar[1].expr, yyDollar[3].expr) 699 | } 700 | case 25: 701 | yyDollar = yyS[yypt-2 : yypt+1] 702 | //line parser.go.y:104 703 | { 704 | yyVAL.expr = !asBool(yyDollar[2].expr) 705 | } 706 | case 26: 707 | yyDollar = yyS[yypt-3 : yypt+1] 708 | //line parser.go.y:105 709 | { 710 | yyVAL.expr = deepEqual(yyDollar[1].expr, yyDollar[3].expr) 711 | } 712 | case 27: 713 | yyDollar = yyS[yypt-3 : yypt+1] 714 | //line parser.go.y:106 715 | { 716 | yyVAL.expr = !deepEqual(yyDollar[1].expr, yyDollar[3].expr) 717 | } 718 | case 28: 719 | yyDollar = yyS[yypt-3 : yypt+1] 720 | //line parser.go.y:107 721 | { 722 | yyVAL.expr = compare(yyDollar[1].expr, yyDollar[3].expr, "<") 723 | } 724 | case 29: 725 | yyDollar = yyS[yypt-3 : yypt+1] 726 | //line parser.go.y:108 727 | { 728 | yyVAL.expr = compare(yyDollar[1].expr, yyDollar[3].expr, ">") 729 | } 730 | case 30: 731 | yyDollar = yyS[yypt-3 : yypt+1] 732 | //line parser.go.y:109 733 | { 734 | yyVAL.expr = compare(yyDollar[1].expr, yyDollar[3].expr, "<=") 735 | } 736 | case 31: 737 | yyDollar = yyS[yypt-3 : yypt+1] 738 | //line parser.go.y:110 739 | { 740 | yyVAL.expr = compare(yyDollar[1].expr, yyDollar[3].expr, ">=") 741 | } 742 | case 32: 743 | yyDollar = yyS[yypt-3 : yypt+1] 744 | //line parser.go.y:111 745 | { 746 | left := asBool(yyDollar[1].expr) 747 | right := asBool(yyDollar[3].expr) 748 | yyVAL.expr = left && right 749 | } 750 | case 33: 751 | yyDollar = yyS[yypt-3 : yypt+1] 752 | //line parser.go.y:112 753 | { 754 | left := asBool(yyDollar[1].expr) 755 | right := asBool(yyDollar[3].expr) 756 | yyVAL.expr = left || right 757 | } 758 | case 34: 759 | yyDollar = yyS[yypt-3 : yypt+1] 760 | //line parser.go.y:116 761 | { 762 | yyVAL.expr = asInteger(yyDollar[1].expr) | asInteger(yyDollar[3].expr) 763 | } 764 | case 35: 765 | yyDollar = yyS[yypt-3 : yypt+1] 766 | //line parser.go.y:117 767 | { 768 | yyVAL.expr = asInteger(yyDollar[1].expr) & asInteger(yyDollar[3].expr) 769 | } 770 | case 36: 771 | yyDollar = yyS[yypt-3 : yypt+1] 772 | //line parser.go.y:118 773 | { 774 | yyVAL.expr = asInteger(yyDollar[1].expr) ^ asInteger(yyDollar[3].expr) 775 | } 776 | case 37: 777 | yyDollar = yyS[yypt-3 : yypt+1] 778 | //line parser.go.y:119 779 | { 780 | l := asInteger(yyDollar[1].expr) 781 | r := asInteger(yyDollar[3].expr) 782 | if r >= 0 { 783 | yyVAL.expr = l << uint(r) 784 | } else { 785 | yyVAL.expr = l >> uint(-r) 786 | } 787 | } 788 | case 38: 789 | yyDollar = yyS[yypt-3 : yypt+1] 790 | //line parser.go.y:120 791 | { 792 | l := asInteger(yyDollar[1].expr) 793 | r := asInteger(yyDollar[3].expr) 794 | if r >= 0 { 795 | yyVAL.expr = l >> uint(r) 796 | } else { 797 | yyVAL.expr = l << uint(-r) 798 | } 799 | } 800 | case 39: 801 | yyDollar = yyS[yypt-2 : yypt+1] 802 | //line parser.go.y:121 803 | { 804 | yyVAL.expr = ^asInteger(yyDollar[2].expr) 805 | } 806 | case 40: 807 | yyDollar = yyS[yypt-1 : yypt+1] 808 | //line parser.go.y:125 809 | { 810 | yyVAL.expr = accessVar(yylex.(*Lexer).variables, yyDollar[1].token.literal) 811 | } 812 | case 41: 813 | yyDollar = yyS[yypt-3 : yypt+1] 814 | //line parser.go.y:126 815 | { 816 | yyVAL.expr = accessField(yyDollar[1].expr, yyDollar[3].token.literal) 817 | } 818 | case 42: 819 | yyDollar = yyS[yypt-4 : yypt+1] 820 | //line parser.go.y:127 821 | { 822 | yyVAL.expr = accessField(yyDollar[1].expr, yyDollar[3].expr) 823 | } 824 | case 43: 825 | yyDollar = yyS[yypt-3 : yypt+1] 826 | //line parser.go.y:128 827 | { 828 | yyVAL.expr = arrayContains(yyDollar[3].expr, yyDollar[1].expr) 829 | } 830 | case 44: 831 | yyDollar = yyS[yypt-6 : yypt+1] 832 | //line parser.go.y:129 833 | { 834 | yyVAL.expr = slice(yyDollar[1].expr, yyDollar[3].expr, yyDollar[5].expr) 835 | } 836 | case 45: 837 | yyDollar = yyS[yypt-5 : yypt+1] 838 | //line parser.go.y:130 839 | { 840 | yyVAL.expr = slice(yyDollar[1].expr, nil, yyDollar[4].expr) 841 | } 842 | case 46: 843 | yyDollar = yyS[yypt-5 : yypt+1] 844 | //line parser.go.y:131 845 | { 846 | yyVAL.expr = slice(yyDollar[1].expr, yyDollar[3].expr, nil) 847 | } 848 | case 47: 849 | yyDollar = yyS[yypt-4 : yypt+1] 850 | //line parser.go.y:132 851 | { 852 | yyVAL.expr = slice(yyDollar[1].expr, nil, nil) 853 | } 854 | case 48: 855 | yyDollar = yyS[yypt-1 : yypt+1] 856 | //line parser.go.y:136 857 | { 858 | yyVAL.exprList = []interface{}{yyDollar[1].expr} 859 | } 860 | case 49: 861 | yyDollar = yyS[yypt-3 : yypt+1] 862 | //line parser.go.y:137 863 | { 864 | yyVAL.exprList = append(yyDollar[1].exprList, yyDollar[3].expr) 865 | } 866 | case 50: 867 | yyDollar = yyS[yypt-3 : yypt+1] 868 | //line parser.go.y:141 869 | { 870 | yyVAL.exprMap = make(map[string]interface{}) 871 | yyVAL.exprMap[asObjectKey(yyDollar[1].expr)] = yyDollar[3].expr 872 | } 873 | case 51: 874 | yyDollar = yyS[yypt-5 : yypt+1] 875 | //line parser.go.y:142 876 | { 877 | yyVAL.exprMap = addObjectMember(yyDollar[1].exprMap, yyDollar[3].expr, yyDollar[5].expr) 878 | } 879 | } 880 | goto yystack /* stack new state and value */ 881 | } 882 | -------------------------------------------------------------------------------- /internal/parser/parser.go.y: -------------------------------------------------------------------------------- 1 | %{ 2 | package internal 3 | 4 | %} 5 | 6 | %union { 7 | token Token 8 | expr interface{} 9 | exprList []interface{} 10 | exprMap map[string]interface{} 11 | } 12 | 13 | 14 | %start program 15 | 16 | %type program 17 | %type expr 18 | %type literal 19 | %type math 20 | %type logic 21 | %type bitManipulation 22 | %type varAccess 23 | %type exprList 24 | %type exprMap 25 | 26 | %token LITERAL_NIL // nil 27 | %token LITERAL_BOOL // true false 28 | %token LITERAL_NUMBER // 42 4.2 4e2 4.2e2 29 | %token LITERAL_STRING // "text" 'text' 30 | %token IDENT 31 | %token AND // && 32 | %token OR // || 33 | %token EQL // == 34 | %token NEQ // != 35 | %token LSS // < 36 | %token GTR // > 37 | %token LEQ // <= 38 | %token GEQ // >= 39 | %token SHL // << 40 | %token SHR // >> 41 | %token BIT_NOT // ~ 42 | %token IN // in 43 | 44 | /* Operator precedence is taken from C/C++: http://en.cppreference.com/w/c/language/operator_precedence */ 45 | 46 | %right '?' ':' 47 | %left OR 48 | %left AND 49 | %left '|' 50 | %left '^' 51 | %left '&' 52 | %left EQL NEQ 53 | %left LSS LEQ GTR GEQ 54 | %left SHL SHR 55 | %left '+' '-' 56 | %left '*' '/' '%' 57 | %right '!' BIT_NOT 58 | %left IN 59 | %left '.' '[' ']' 60 | 61 | %% 62 | 63 | program 64 | : expr 65 | { 66 | $$ = $1 67 | yylex.(*Lexer).result = $$ 68 | } 69 | ; 70 | 71 | expr 72 | : literal 73 | | math 74 | | logic 75 | | bitManipulation 76 | | varAccess 77 | | expr '?' expr ':' expr { if asBool($1) { $$ = $3 } else { $$ = $5 } } 78 | | '(' expr ')' { $$ = $2 } 79 | | IDENT '(' ')' { $$ = callFunction(yylex.(*Lexer).functions, $1.literal, []interface{}{}) } 80 | | IDENT '(' exprList ')' { $$ = callFunction(yylex.(*Lexer).functions, $1.literal, $3) } 81 | ; 82 | 83 | literal 84 | : LITERAL_NIL { $$ = nil } 85 | | LITERAL_BOOL { $$ = $1.value } 86 | | LITERAL_NUMBER { $$ = $1.value } 87 | | LITERAL_STRING { $$ = $1.value } 88 | | '[' ']' { $$ = []interface{}{} } 89 | | '[' exprList ']' { $$ = $2 } 90 | | '{' '}' { $$ = map[string]interface{}{} } 91 | | '{' exprMap '}' { $$ = $2 } 92 | ; 93 | 94 | math 95 | : '-' expr %prec '!' { $$ = unaryMinus($2) } /* unary minus has higher precedence */ 96 | | expr '+' expr { $$ = add($1, $3) } 97 | | expr '-' expr { $$ = sub($1, $3) } 98 | | expr '*' expr { $$ = mul($1, $3) } 99 | | expr '/' expr { $$ = div($1, $3) } 100 | | expr '%' expr { $$ = mod($1, $3) } 101 | ; 102 | 103 | logic 104 | : '!' expr { $$ = !asBool($2) } 105 | | expr EQL expr { $$ = deepEqual($1, $3) } 106 | | expr NEQ expr { $$ = !deepEqual($1, $3) } 107 | | expr LSS expr { $$ = compare($1, $3, "<") } 108 | | expr GTR expr { $$ = compare($1, $3, ">") } 109 | | expr LEQ expr { $$ = compare($1, $3, "<=") } 110 | | expr GEQ expr { $$ = compare($1, $3, ">=") } 111 | | expr AND expr { left := asBool($1); right := asBool($3); $$ = left && right } 112 | | expr OR expr { left := asBool($1); right := asBool($3); $$ = left || right } 113 | ; 114 | 115 | bitManipulation 116 | : expr '|' expr { $$ = asInteger($1) | asInteger($3) } 117 | | expr '&' expr { $$ = asInteger($1) & asInteger($3) } 118 | | expr '^' expr { $$ = asInteger($1) ^ asInteger($3) } 119 | | expr SHL expr { l := asInteger($1); r := asInteger($3); if r >= 0 { $$ = l << uint(r) } else {$$ = l >> uint(-r)} } 120 | | expr SHR expr { l := asInteger($1); r := asInteger($3); if r >= 0 { $$ = l >> uint(r) } else {$$ = l << uint(-r)} } 121 | | BIT_NOT expr { $$ = ^asInteger($2) } 122 | ; 123 | 124 | varAccess 125 | : IDENT { $$ = accessVar(yylex.(*Lexer).variables, $1.literal) } 126 | | expr '.' IDENT { $$ = accessField($1, $3.literal) } 127 | | expr '[' expr ']' { $$ = accessField($1, $3) } 128 | | expr IN expr { $$ = arrayContains($3, $1) } 129 | | expr '[' expr ':' expr ']' { $$ = slice($1, $3, $5) } 130 | | expr '[' ':' expr ']' { $$ = slice($1, nil, $4) } 131 | | expr '[' expr ':' ']' { $$ = slice($1, $3, nil) } 132 | | expr '[' ':' ']' { $$ = slice($1, nil, nil) } 133 | ; 134 | 135 | exprList 136 | : expr { $$ = []interface{}{$1} } 137 | | exprList ',' expr { $$ = append($1, $3) } 138 | ; 139 | 140 | exprMap 141 | : expr ':' expr { $$ = make(map[string]interface{}); $$[asObjectKey($1)] = $3 } 142 | | exprMap ',' expr ':' expr { $$ = addObjectMember($1, $3, $5) } 143 | ; 144 | 145 | %% -------------------------------------------------------------------------------- /internal/parser/parser_test.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strconv" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func Test_Literals_Simple(t *testing.T) { 13 | assertEvaluation(t, nil, nil, "nil") 14 | 15 | assertEvaluation(t, nil, true, "true") 16 | assertEvaluation(t, nil, false, "false") 17 | 18 | assertEvaluation(t, nil, 42, "42") 19 | 20 | assertEvaluation(t, nil, 4.2, "4.2") 21 | assertEvaluation(t, nil, 42.0, "42.0") 22 | assertEvaluation(t, nil, 42.0, "4.2e1") 23 | assertEvaluation(t, nil, 400.0, "4e2") 24 | 25 | assertEvaluation(t, nil, "text", `"text"`) 26 | assertEvaluation(t, nil, "", `""`) 27 | assertEvaluation(t, nil, `te"xt`, `"te\"xt"`) 28 | assertEvaluation(t, nil, `text\`, `"text\\"`) 29 | 30 | assertEvaluation(t, nil, "text", "`text`") 31 | assertEvaluation(t, nil, "", "``") 32 | assertEvaluation(t, nil, `text\`, "`text\\`") 33 | 34 | assertEvaluation(t, nil, "Hello, 世界", `"Hello, 世界"`) 35 | assertEvaluation(t, nil, "\t\t\n\xFF\u0100.+=!", `"\t \n\xFF\u0100.+=!"`) 36 | } 37 | 38 | func Test_Literals_Hex(t *testing.T) { 39 | assertEvaluation(t, nil, 0, "0x0") 40 | assertEvaluation(t, nil, 1, "0x01") 41 | assertEvaluation(t, nil, 10, "0x0A") 42 | assertEvaluation(t, nil, 255, "0xFF") 43 | assertEvaluation(t, nil, 42330, "0xA55A") 44 | assertEvaluation(t, nil, 23205, "0x5AA5") 45 | assertEvaluation(t, nil, 65535, "0xFFFF") // 16bit 46 | 47 | result, err := Evaluate("0x7FFFFFFF", nil, nil) // 32bit, leading zero 48 | if assert.NoError(t, err) { 49 | assert.Equal(t, int64(2147483647), int64(result.(int))) 50 | } 51 | 52 | if BitSizeOfInt == 32 { 53 | result, err = Evaluate("0x80000000", nil, nil) // 32bit, leading one (highest negative) 54 | if assert.NoError(t, err) { 55 | assert.Equal(t, int32(-2147483648), int32(result.(int))) 56 | } 57 | 58 | result, err = Evaluate("0xFFFFFFFF", nil, nil) // 32bit, leading one (lowest negative) 59 | if assert.NoError(t, err) { 60 | assert.Equal(t, int32(-1), int32(result.(int))) 61 | } 62 | } 63 | 64 | if BitSizeOfInt >= 64 { 65 | result, err = Evaluate("0xFFFFFFFF", nil, nil) // 32bit 66 | if assert.NoError(t, err) { 67 | assert.Equal(t, int64(4294967295), int64(result.(int))) 68 | } 69 | 70 | result, err = Evaluate("0x7FFFFFFFFFFFFFFF", nil, nil) // 64bit, leading zero (highest positive) 71 | if assert.NoError(t, err) { 72 | assert.Equal(t, int64(9223372036854775807), int64(result.(int))) 73 | } 74 | 75 | result, err = Evaluate("0x8000000000000000", nil, nil) // 64bit, leading one (highest negative) 76 | if assert.NoError(t, err) { 77 | assert.Equal(t, int64(-9223372036854775808), int64(result.(int))) 78 | } 79 | 80 | result, err = Evaluate("0xFFFFFFFFFFFFFFFF", nil, nil) // 64bit, leading one (lowest negative) 81 | if assert.NoError(t, err) { 82 | assert.Equal(t, int64(-1), int64(result.(int))) 83 | } 84 | } 85 | } 86 | 87 | func Test_Literals_Arrays(t *testing.T) { 88 | assertEvaluation(t, nil, []interface{}{}, `[]`) 89 | assertEvaluation(t, nil, []interface{}{1, 2, 3}, `[1, 2, 3]`) 90 | assertEvaluation(t, nil, []interface{}{true, false, 42, 4.2, "text"}, `[true, false, 42, 4.2, "text"]`) 91 | assertEvaluation(t, nil, []interface{}{[]interface{}{1, 2}, []interface{}{3}, []interface{}{}}, `[ [1,2], [3], [] ]`) 92 | 93 | assertEvaluation(t, nil, []interface{}{map[string]interface{}{}}, `[{}]`) 94 | assertEvaluation(t, nil, []interface{}{map[string]interface{}{"a": 1}}, `[{"a": 1}]`) 95 | 96 | } 97 | 98 | func Test_Literals_Objects(t *testing.T) { 99 | assertEvaluation(t, nil, map[string]interface{}{}, `{}`) 100 | assertEvaluation(t, nil, map[string]interface{}{"a": true}, `{"a": true}`) 101 | 102 | expected := map[string]interface{}{ 103 | "a": false, "b": true, "c": 42, "d": 4.2, "e": "text", 104 | } 105 | assertEvaluation(t, nil, expected, `{"a": false, "b": true, "c": 42, "d": 4.2, "e": "text"}`) 106 | 107 | expected = map[string]interface{}{ 108 | "a": []interface{}{34.0}, 109 | "b": map[string]interface{}{"A": 45, "B": 1.2}, 110 | } 111 | assertEvaluation(t, nil, expected, `{"a": [34.0], "b": {"A": 45, "B": 1.2}}`) 112 | } 113 | 114 | func Test_Literals_Objects_DynamicKeys(t *testing.T) { 115 | vars := map[string]interface{}{ 116 | "str": "text", 117 | } 118 | assertEvaluation(t, vars, map[string]interface{}{"ab": true}, `{"a" + "b": true}`) 119 | assertEvaluation(t, vars, map[string]interface{}{"key42": true}, `{"key" + 42: true}`) 120 | assertEvaluation(t, vars, map[string]interface{}{"text": true}, `{str: true}`) 121 | } 122 | 123 | func Test_LiteralsOutOfRange(t *testing.T) { 124 | if BitSizeOfInt == 32 { 125 | assertEvalError(t, nil, "parse error: cannot parse integer at position 1", "0x100000000") // 33bit 126 | } else { 127 | assertEvalError(t, nil, "parse error: cannot parse integer at position 1", "0x10000000000000000") // 65bit 128 | } 129 | 130 | assertEvalError(t, nil, "parse error: cannot parse integer at position 1", "9999999999999999999999999999") 131 | assertEvalError(t, nil, "parse error: cannot parse float at position 1", "9.9e999") 132 | } 133 | 134 | func Test_Literals_Objects_DuplicateKey(t *testing.T) { 135 | assertEvalError(t, nil, "syntax error: duplicate object key \"a\"", `{"a": 0, "a": 0}`) 136 | } 137 | 138 | func Test_Literals_Objects_InvalidKeyType(t *testing.T) { 139 | assertEvalError(t, nil, "type error: object key must be string, but was nil", `{nil: 0}`) 140 | assertEvalError(t, nil, "type error: object key must be string, but was number", `{0: 0}`) 141 | assertEvalError(t, nil, "type error: object key must be string, but was number", `{"a": 0, 1: 0}`) 142 | } 143 | 144 | func Test_MissingOperator(t *testing.T) { 145 | assertEvalError(t, nil, "syntax error: unexpected LITERAL_BOOL", "true false") 146 | assertEvalError(t, nil, "syntax error: unexpected '!'", "true!") 147 | assertEvalError(t, nil, "syntax error: unexpected LITERAL_NUMBER", "42 42") 148 | assertEvalError(t, nil, "syntax error: unexpected LITERAL_NIL", "nil nil") 149 | assertEvalError(t, nil, "syntax error: unexpected IDENT", "42 var") 150 | assertEvalError(t, nil, "syntax error: unexpected IDENT", `42text`) 151 | assertEvalError(t, nil, "syntax error: unexpected LITERAL_STRING", `"text" "text"`) 152 | } 153 | 154 | func Test_UnsupportedTokens(t *testing.T) { 155 | assertEvalError(t, nil, "unknown token \"ILLEGAL\" (\"§\") at position 3", "0 § 0") 156 | assertEvalError(t, nil, "unknown token \"...\" (\"\") at position 3", "0 ... 0") 157 | assertEvalError(t, nil, "unknown token \"+=\" (\"\") at position 3", "0 += 0") 158 | } 159 | 160 | func Test_InvalidLiterals(t *testing.T) { 161 | assertEvalError(t, nil, "var error: variable \"bool\" does not exist", "bool") 162 | assertEvalError(t, nil, "var error: variable \"null\" does not exist", "null") 163 | assertEvalError(t, nil, "syntax error: unexpected LITERAL_NUMBER", `4.2.0`) 164 | 165 | assertEvalError(t, nil, "unknown token \"CHAR\" (\"'t'\") at position 1", `'t'`) 166 | assertEvalError(t, nil, "unknown token \"CHAR\" (\"'text'\") at position 1", `'text'`) 167 | assertEvalError(t, nil, "parse error: cannot unquote string literal at position 1", `"`) 168 | assertEvalError(t, nil, "parse error: cannot unquote string literal at position 1", `"text`) 169 | assertEvalError(t, nil, "parse error: cannot unquote string literal at position 5", `text"`) 170 | 171 | assertEvalError(t, nil, "syntax error: unexpected $end", `[`) 172 | assertEvalError(t, nil, "syntax error: unexpected ']'", `]`) 173 | assertEvalError(t, nil, "syntax error: unexpected ']'", `[1, ]`) 174 | assertEvalError(t, nil, "syntax error: unexpected ','", `[, 1]`) 175 | 176 | assertEvalError(t, nil, "syntax error: unexpected $end", `{`) 177 | assertEvalError(t, nil, "syntax error: unexpected '}'", `}`) 178 | assertEvalError(t, nil, "syntax error: unexpected '}'", `{"a":}`) 179 | assertEvalError(t, nil, "syntax error: unexpected '}'", `{"a"}`) 180 | assertEvalError(t, nil, "syntax error: unexpected ':'", `{:1}`) 181 | } 182 | 183 | func Test_Bool_Not(t *testing.T) { 184 | vars := getTestVars() 185 | assertEvaluation(t, vars, false, "!true") 186 | assertEvaluation(t, vars, true, "!false") 187 | 188 | assertEvaluation(t, vars, true, "!!true") 189 | assertEvaluation(t, vars, false, "!!false") 190 | 191 | // via variables: 192 | assertEvaluation(t, vars, false, "!tr") 193 | assertEvaluation(t, vars, true, "!fl") 194 | 195 | assertEvaluation(t, vars, true, "(!(!(true)))") 196 | assertEvaluation(t, vars, false, "(!(!(false)))") 197 | } 198 | 199 | func Test_Bool_Not_NotApplicable(t *testing.T) { 200 | assertEvalError(t, nil, "type error: required bool, but was number", "!0") 201 | assertEvalError(t, nil, "type error: required bool, but was number", "!1") 202 | 203 | assertEvalError(t, nil, "type error: required bool, but was string", `!"text"`) 204 | assertEvalError(t, nil, "type error: required bool, but was number", "!1.0") 205 | assertEvalError(t, nil, "type error: required bool, but was array", "![]") 206 | assertEvalError(t, nil, "type error: required bool, but was array", "![false]") 207 | } 208 | 209 | func Test_String_Concat(t *testing.T) { 210 | // string + string 211 | assertEvaluation(t, nil, "text", `"te" + "xt"`) 212 | assertEvaluation(t, nil, "00", `"0" + "0"`) 213 | assertEvaluation(t, nil, "text", `"t" + "e" + "x" + "t"`) 214 | assertEvaluation(t, nil, "", `"" + ""`) 215 | 216 | // string + number 217 | assertEvaluation(t, nil, "text42", `"text" + 42`) 218 | assertEvaluation(t, nil, "text4.2", `"text" + 4.2`) 219 | assertEvaluation(t, nil, "42text", `42 + "text"`) 220 | assertEvaluation(t, nil, "4.2text", `4.2 + "text"`) 221 | 222 | // string + bool 223 | assertEvaluation(t, nil, "texttrue", `"text" + true`) 224 | assertEvaluation(t, nil, "textfalse", `"text" + false`) 225 | assertEvaluation(t, nil, "truetext", `true + "text"`) 226 | assertEvaluation(t, nil, "falsetext", `false + "text"`) 227 | 228 | // string + nil 229 | assertEvaluation(t, nil, "textnil", `"text" + nil`) 230 | assertEvaluation(t, nil, "niltext", `nil + "text"`) 231 | 232 | assertEvaluation(t, nil, "truetext42false", `true + "text" + 42 + false`) 233 | } 234 | 235 | func Test_Arithmetic_Add(t *testing.T) { 236 | // int + int 237 | assertEvaluation(t, nil, 42, "21 + 21") 238 | assertEvaluation(t, nil, 4, "0 + 4") 239 | // float + float 240 | assertEvaluation(t, nil, 4.2, "2.1 + 2.1") 241 | assertEvaluation(t, nil, 0.4, "0.0 + 0.4") 242 | // int + float 243 | assertEvaluation(t, nil, 23.1, "21 + 2.1") 244 | assertEvaluation(t, nil, 0.4, "0 + 0.4") 245 | // float + int 246 | assertEvaluation(t, nil, 23.1, "2.1 + 21") 247 | assertEvaluation(t, nil, 0.4, "0.4 + 0") 248 | 249 | assertEvaluation(t, nil, 63, "21 + 21 + 21") 250 | assertEvaluation(t, nil, 6.4, "2.1 + 2.1 + 2.2") 251 | } 252 | 253 | func Test_Add_WithUnaryMinus(t *testing.T) { 254 | assertEvaluation(t, nil, 21, "42 + -21") 255 | assertEvaluation(t, nil, 2.1, "4.2 + -2.1") 256 | 257 | assertEvaluation(t, nil, -1, "-4+3") 258 | assertEvaluation(t, nil, -1, "(-4)+3") 259 | assertEvaluation(t, nil, -7, "-(4+3)") 260 | } 261 | 262 | func Test_Array_Concat(t *testing.T) { 263 | vars := map[string]interface{}{ 264 | "arr": []interface{}{true, 42}, 265 | } 266 | 267 | assertEvaluation(t, vars, []interface{}{}, `[] + []`) 268 | assertEvaluation(t, vars, []interface{}{0, 1, 2, 3}, `[0, 1] + [2, 3]`) 269 | 270 | assertEvaluation(t, vars, []interface{}{true, 42, true, 42, true, 42}, `[] + arr + [] + arr + arr`) 271 | assert.Len(t, vars["arr"], 2) 272 | 273 | assertEvaluation(t, vars, []interface{}{true, 42, 0, 1, true, 42}, `arr + [0, 1] + arr`) 274 | assert.Len(t, vars["arr"], 2) 275 | } 276 | 277 | func Test_Object_Concat(t *testing.T) { 278 | vars := map[string]interface{}{ 279 | "obj1": map[string]interface{}{"a": 1, "b": 2}, 280 | "obj2": map[string]interface{}{"b": 3, "c": 4}, 281 | } 282 | 283 | assertEvaluation(t, vars, map[string]interface{}{}, `{} + {}`) 284 | assertEvaluation(t, vars, map[string]interface{}{"a": 1, "b": 3, "c": 4}, `{"a": 1, "b": 2} + {"b": 3, "c": 4}`) 285 | 286 | assertEvaluation(t, vars, map[string]interface{}{"a": 1, "b": 3, "c": 4}, `obj1 + obj2`) 287 | assert.Equal(t, map[string]interface{}{"a": 1, "b": 2}, vars["obj1"]) 288 | 289 | assertEvaluation(t, vars, map[string]interface{}{"a": 1, "b": 2, "c": 4}, `obj2 + obj1`) 290 | assertEvaluation(t, vars, map[string]interface{}{"a": 1, "b": 3, "c": 4}, `{} + obj1 + {} + obj2 + obj2`) 291 | assertEvaluation(t, vars, map[string]interface{}{"a": 1, "b": 3, "c": 4, "d": 42}, `obj1 + {"d": 42} + obj2`) 292 | 293 | assert.Equal(t, map[string]interface{}{"a": 1, "b": 2}, vars["obj1"]) 294 | assert.Equal(t, map[string]interface{}{"b": 3, "c": 4}, vars["obj2"]) 295 | } 296 | 297 | func Test_Add_IncompatibleTypes(t *testing.T) { 298 | vars := getTestVars() 299 | assertEvalError(t, vars, "type error: cannot add or concatenate type number and nil", `0 + nil`) 300 | assertEvalError(t, vars, "type error: cannot add or concatenate type bool and bool", `false + false`) 301 | assertEvalError(t, vars, "type error: cannot add or concatenate type bool and bool", `false + true`) 302 | assertEvalError(t, vars, "type error: cannot add or concatenate type bool and number", `false + 42`) 303 | assertEvalError(t, vars, "type error: cannot add or concatenate type bool and array", `false + arr`) 304 | assertEvalError(t, vars, "type error: cannot add or concatenate type bool and object", `false + obj`) 305 | 306 | assertEvalError(t, vars, "type error: cannot add or concatenate type number and bool", `42 + false`) 307 | assertEvalError(t, vars, "type error: cannot add or concatenate type bool and bool", `true + false`) 308 | assertEvalError(t, vars, "type error: cannot add or concatenate type number and bool", `42 + false`) 309 | assertEvalError(t, vars, "type error: cannot add or concatenate type array and bool", `arr + false`) 310 | assertEvalError(t, vars, "type error: cannot add or concatenate type object and bool", `obj + false`) 311 | 312 | assertEvalError(t, vars, "type error: cannot add or concatenate type array and object", `arr + obj`) 313 | assertEvalError(t, vars, "type error: cannot add or concatenate type object and array", `obj + arr`) 314 | assertEvalError(t, vars, "type error: cannot add or concatenate type nil and array", `nil + arr`) 315 | } 316 | 317 | func Test_UnaryMinus(t *testing.T) { 318 | vars := getTestVars() 319 | assertEvaluation(t, vars, -42, "-42") 320 | assertEvaluation(t, vars, -4.2, "-4.2") 321 | assertEvaluation(t, vars, -42.0, "-42.0") 322 | assertEvaluation(t, vars, -42.0, "-4.2e1") 323 | assertEvaluation(t, vars, -400.0, "-4e2") 324 | 325 | assertEvaluation(t, vars, -42, "-int") 326 | assertEvaluation(t, vars, -4.2, "-float") 327 | 328 | assertEvaluation(t, vars, -42, "(-(42))") 329 | assertEvaluation(t, vars, -4.2, "(-(4.2))") 330 | } 331 | 332 | func Test_UnaryMinus_IncompatibleTypes(t *testing.T) { 333 | vars := getTestVars() 334 | assertEvalError(t, vars, "type error: unary minus requires number, but was nil", "-nil") 335 | assertEvalError(t, vars, "type error: unary minus requires number, but was bool", "-true") 336 | assertEvalError(t, vars, "type error: unary minus requires number, but was bool", "-false") 337 | assertEvalError(t, vars, "type error: unary minus requires number, but was string", `-"0"`) 338 | 339 | assertEvalError(t, vars, "type error: unary minus requires number, but was array", `-arr`) 340 | assertEvalError(t, vars, "type error: unary minus requires number, but was object", `-obj`) 341 | } 342 | 343 | func Test_Arithmetic_Subtract(t *testing.T) { 344 | // int - int 345 | assertEvaluation(t, nil, 21, "42 - 21") 346 | assertEvaluation(t, nil, -4, "0 - 4") 347 | // float - float 348 | assertEvaluation(t, nil, 2.1, "4.2 - 2.1") 349 | assertEvaluation(t, nil, -0.4, "0.0 - 0.4") 350 | // int - float 351 | assertEvaluation(t, nil, 18.9, "21 - 2.1") 352 | assertEvaluation(t, nil, -0.4, "0 - 0.4") 353 | // float - int 354 | assertEvaluation(t, nil, -18.9, "2.1 - 21") 355 | assertEvaluation(t, nil, 0.4, "0.4 - 0") 356 | 357 | assertEvaluation(t, nil, 22, "42 - 12 - 8") 358 | assertEvaluation(t, nil, 2.2, "4.2 - 1.2 - 0.8") 359 | } 360 | 361 | func Test_Subtract_WithUnaryMinus(t *testing.T) { 362 | assertEvaluation(t, nil, 42, "21 - -21") 363 | assertEvaluation(t, nil, 4.2, "2.1 - -2.1") 364 | } 365 | 366 | func Test_Arithmetic_Multiply(t *testing.T) { 367 | // int * int 368 | assertEvaluation(t, nil, 8, "4 * 2") 369 | assertEvaluation(t, nil, 0, "0 * 4") 370 | assertEvaluation(t, nil, -8, "-2 * 4") 371 | assertEvaluation(t, nil, 8, "-2 * -4") 372 | // float * float 373 | assertEvaluation(t, nil, 10.5, "4.2 * 2.5") 374 | assertEvaluation(t, nil, 0.0, "0.0 * 2.4") 375 | assertEvaluation(t, nil, -0.8, "-2.0 * 0.4") 376 | assertEvaluation(t, nil, 0.8, "-2.0 * -0.4") 377 | // int * float 378 | assertEvaluation(t, nil, 50.0, "20 * 2.5") 379 | assertEvaluation(t, nil, -5.0, "10 * -0.5") 380 | // float * int 381 | assertEvaluation(t, nil, 50.0, "2.5 * 20") 382 | assertEvaluation(t, nil, 6.0, "0.5 * 12") 383 | 384 | assertEvaluation(t, nil, 24, "2 * 3 * 4") 385 | assertEvaluation(t, nil, 9.0, "1.2 * 2.5 * 3") 386 | } 387 | 388 | func Test_Arithmetic_Divide(t *testing.T) { 389 | // int / int 390 | assertEvaluation(t, nil, 1, "4 / 3") 391 | assertEvaluation(t, nil, 3, "12 / 4") 392 | assertEvaluation(t, nil, -2, "-4 / 2") 393 | assertEvaluation(t, nil, 2, "-4 / -2") 394 | // float / float 395 | assertEvaluation(t, nil, 2.75, "5.5 / 2.0") 396 | assertEvaluation(t, nil, 3.0, "12.0 / 4.0") 397 | assertEvaluation(t, nil, -2/4.5, "-2.0 / 4.5") 398 | assertEvaluation(t, nil, 2/4.5, "-2.0 / -4.5") 399 | // int / float 400 | assertEvaluation(t, nil, 2/4.5, "2 / 4.5") 401 | // float / int 402 | assertEvaluation(t, nil, 2.75, "5.5 / 2") 403 | 404 | assertEvaluation(t, nil, 2, "144 / 12 / 6") 405 | assertEvaluation(t, nil, 1.2/2.5/3, "1.2 / 2.5 / 3") 406 | } 407 | 408 | func Test_Arithmetic_Modulo(t *testing.T) { 409 | // int % int 410 | assertEvaluation(t, nil, 1, "4 % 3") 411 | assertEvaluation(t, nil, 0, "12 % -4") 412 | assertEvaluation(t, nil, -55, "-140 % 85") 413 | assertEvaluation(t, nil, -1, "-7 % -2") 414 | assertEvaluation(t, nil, 8, "8 % 13") 415 | // float % float 416 | assertEvaluation(t, nil, 1.5, "5.5 % 2.0") 417 | assertEvaluation(t, nil, 0.0, "12.0 % 4.0") 418 | assertEvaluation(t, nil, -2.0, "-2.0 % 4.5") 419 | assertEvaluation(t, nil, -4.0, "-12.5 % -4.25") 420 | // int % float 421 | assertEvaluation(t, nil, 1.0, "10 % 4.5") 422 | // float % int 423 | assertEvaluation(t, nil, 1.5, "5.5 % 2") 424 | 425 | assertEvaluation(t, nil, 4, "154 % 12 % 6") 426 | assertEvaluation(t, nil, 0.5, "1.5 % 2.5 % 1") 427 | } 428 | 429 | func Test_Arithmetic_InvalidTypes(t *testing.T) { 430 | vars := getTestVars() 431 | allTypes := []string{"nil", "true", "false", "42", "4.2", `"text"`, `"0"`, "[0]", "[]", "arr", `{"a":0}`, "{}", "obj"} 432 | typeOfAllTypes := []string{"nil", "bool", "bool", "number", "number", "string", "string", "array", "array", "array", "object", "object", "object"} 433 | 434 | for idx1, t1 := range allTypes { 435 | for idx2, t2 := range allTypes { 436 | typ1 := typeOfAllTypes[idx1] 437 | typ2 := typeOfAllTypes[idx2] 438 | 439 | if typ1 == "number" && typ2 == "number" { 440 | continue 441 | } 442 | 443 | // + --> tested separately 444 | // - 445 | expectedErr := fmt.Sprintf("type error: cannot subtract type %s and %s", typ1, typ2) 446 | assertEvalError(t, vars, expectedErr, t1+"-"+t2) 447 | // * 448 | expectedErr = fmt.Sprintf("type error: cannot multiply type %s and %s", typ1, typ2) 449 | assertEvalError(t, vars, expectedErr, t1+"*"+t2) 450 | // / 451 | expectedErr = fmt.Sprintf("type error: cannot divide type %s and %s", typ1, typ2) 452 | assertEvalError(t, vars, expectedErr, t1+"/"+t2) 453 | // % 454 | expectedErr = fmt.Sprintf("type error: cannot perform modulo on type %s and %s", typ1, typ2) 455 | assertEvalError(t, vars, expectedErr, t1+"%"+t2) 456 | } 457 | 458 | } 459 | } 460 | 461 | func Test_Arithmetic_Order(t *testing.T) { 462 | assertEvaluation(t, nil, 8, "2 + 2 * 3") 463 | assertEvaluation(t, nil, 8, "2 * 3 + 2") 464 | 465 | assertEvaluation(t, nil, 6, "4 + 8 / 4") 466 | assertEvaluation(t, nil, 6, "8 / 4 + 4") 467 | } 468 | 469 | func Test_Arithmetic_Parenthesis(t *testing.T) { 470 | assertEvaluation(t, nil, 8, "2 + (2 * 3)") 471 | assertEvaluation(t, nil, 12, "(2 + 2) * 3") 472 | assertEvaluation(t, nil, 8, "(2 * 3) + 2") 473 | assertEvaluation(t, nil, 10, "2 * (3 + 2)") 474 | 475 | assertEvaluation(t, nil, 6, "4 + (8 / 4)") 476 | assertEvaluation(t, nil, 3, "(4 + 8) / 4") 477 | assertEvaluation(t, nil, 6, "(8 / 4) + 4") 478 | assertEvaluation(t, nil, 1, "8 / (4 + 4)") 479 | } 480 | 481 | func Test_Literals_Parenthesis(t *testing.T) { 482 | assertEvaluation(t, nil, true, "(true)") 483 | assertEvaluation(t, nil, false, "(false)") 484 | 485 | assertEvaluation(t, nil, 42, "(42)") 486 | assertEvaluation(t, nil, 4.2, "(4.2)") 487 | 488 | assertEvaluation(t, nil, "text", `("text")`) 489 | } 490 | 491 | func Test_And(t *testing.T) { 492 | assertEvaluation(t, nil, false, "false && false") 493 | assertEvaluation(t, nil, false, "false && true") 494 | assertEvaluation(t, nil, false, "true && false") 495 | assertEvaluation(t, nil, true, "true && true") 496 | 497 | assertEvaluation(t, nil, false, "true && false && true") 498 | } 499 | 500 | func Test_Or(t *testing.T) { 501 | assertEvaluation(t, nil, false, "false || false") 502 | assertEvaluation(t, nil, true, "false || true") 503 | assertEvaluation(t, nil, true, "true || false") 504 | assertEvaluation(t, nil, true, "true || true") 505 | 506 | assertEvaluation(t, nil, true, "true || false || true") 507 | } 508 | 509 | func Test_AndOr_Order(t *testing.T) { 510 | // AND has precedes over OR 511 | assertEvaluation(t, nil, true, "true || false && false") 512 | assertEvaluation(t, nil, true, "false && false || true") 513 | } 514 | 515 | func Test_AndOr_InvalidTypes(t *testing.T) { 516 | vars := getTestVars() 517 | allTypes := []string{"nil", "true", "false", "42", "4.2", `"text"`, `"0"`, "[0]", "[]", "arr", `{"a":0}`, "{}", "obj"} 518 | typeOfAllTypes := []string{"nil", "bool", "bool", "number", "number", "string", "string", "array", "array", "array", "object", "object", "object"} 519 | 520 | for idx1, t1 := range allTypes { 521 | for idx2, t2 := range allTypes { 522 | typ1 := typeOfAllTypes[idx1] 523 | typ2 := typeOfAllTypes[idx2] 524 | 525 | if typ1 == "bool" && typ2 == "bool" { 526 | continue 527 | } 528 | 529 | nonBoolType := typ1 530 | if typ1 == "bool" { 531 | nonBoolType = typ2 532 | } 533 | 534 | // and 535 | expectedErr := fmt.Sprintf("type error: required bool, but was %s", nonBoolType) 536 | assertEvalError(t, vars, expectedErr, t1+"&&"+t2) 537 | // or 538 | expectedErr = fmt.Sprintf("type error: required bool, but was %s", nonBoolType) 539 | assertEvalError(t, vars, expectedErr, t1+"||"+t2) 540 | 541 | result, err := Evaluate(t1+"||"+t2, vars, nil) 542 | assert.Errorf(t, err, "%v || %v\n", t1, t2) 543 | assert.Nil(t, result) 544 | } 545 | 546 | } 547 | } 548 | 549 | func assertEquality(t *testing.T, variables map[string]interface{}, equal bool, v1, v2 string) { 550 | assertEvaluation(t, variables, equal, v1+"=="+v2) 551 | assertEvaluation(t, variables, !equal, v1+"!="+v2) 552 | } 553 | 554 | func Test_Equality_Simple(t *testing.T) { 555 | assertEquality(t, nil, true, "nil", "nil") 556 | assertEquality(t, nil, false, "nil", "false") 557 | assertEquality(t, nil, false, "false", "nil") 558 | 559 | assertEquality(t, nil, true, "false", "false") 560 | assertEquality(t, nil, true, "true", "true") 561 | assertEquality(t, nil, false, "false", "true") 562 | 563 | assertEquality(t, nil, true, "42", "42") 564 | assertEquality(t, nil, false, "42", "41") 565 | assertEquality(t, nil, false, "1", "-1") 566 | 567 | assertEquality(t, nil, true, "4.2", "4.2") 568 | assertEquality(t, nil, false, "4.2", "4.1") 569 | 570 | assertEquality(t, nil, true, "42", "42.0") 571 | assertEquality(t, nil, true, "42.0", "42") 572 | 573 | assertEquality(t, nil, false, "42", "42.1") 574 | assertEquality(t, nil, false, "42.1", "42") 575 | 576 | assertEquality(t, nil, true, `""`, `""`) 577 | assertEquality(t, nil, true, `"text"`, ` "text"`) 578 | assertEquality(t, nil, true, `"text"`, ` "te" + "xt"`) 579 | 580 | assertEquality(t, nil, false, `"text"`, ` "Text"`) 581 | assertEquality(t, nil, false, `"0"`, ` 0`) 582 | assertEquality(t, nil, false, `""`, ` 0`) 583 | } 584 | 585 | func Test_Equality_Arrays(t *testing.T) { 586 | vars := map[string]interface{}{ 587 | "null": nil, 588 | "emptyArr": []interface{}{}, 589 | 590 | "arr1a": []interface{}{nil, false, true, 42, 4.2, "text", []interface{}{34.0}, map[string]interface{}{"A": 45, "B": 1.2}}, 591 | "arr1b": []interface{}{nil, false, true, 42.0, 4.2, "text", []interface{}{34}, map[string]interface{}{"B": 1.2, "A": 45}}, 592 | 593 | "arr2": []interface{}{[]interface{}{34.0}, map[string]interface{}{"A": 45, "B": 1.2}, false, true, 42, 4.2, "text"}, 594 | "arr3": []interface{}{false, true, 42, 4.2, "text"}, 595 | "arr4": []interface{}{false, true, 42, 4.2, ""}, 596 | } 597 | 598 | assertEquality(t, vars, true, `emptyArr`, `emptyArr`) 599 | assertEquality(t, vars, true, `[]`, `emptyArr`) 600 | assertEquality(t, vars, true, `emptyArr`, `[]`) 601 | assertEquality(t, vars, true, `arr1a`, `arr1a`) 602 | assertEquality(t, vars, true, `arr1b`, `arr1b`) 603 | assertEquality(t, vars, true, `arr2`, `arr2`) 604 | assertEquality(t, vars, true, `arr3`, `arr3`) 605 | assertEquality(t, vars, true, `arr3`, `[false, true, 42, 4.2, "text"]`) 606 | assertEquality(t, vars, true, `arr4`, `arr4`) 607 | assertEquality(t, vars, true, `arr4`, `[false, true, 42, 4.2, ""]`) 608 | 609 | assertEquality(t, vars, true, `arr1a`, `arr1b`) 610 | assertEquality(t, vars, true, `arr1b`, `arr1b`) 611 | 612 | assertEquality(t, vars, false, `arr1a`, `arr2`) 613 | assertEquality(t, vars, false, `arr1a`, `arr3`) 614 | assertEquality(t, vars, false, `arr2`, `arr3`) 615 | assertEquality(t, vars, false, `arr3`, `arr4`) 616 | 617 | assertEquality(t, vars, false, `emptyArr`, `null`) 618 | assertEquality(t, vars, false, `emptyArr`, `0`) 619 | assertEquality(t, vars, false, `emptyArr`, `arr1a`) 620 | assertEquality(t, vars, false, `emptyArr`, `""`) 621 | } 622 | 623 | func Test_Equal_Objects(t *testing.T) { 624 | vars := map[string]interface{}{ 625 | "null": nil, 626 | "emptyObj": map[string]interface{}{}, 627 | 628 | "obj1a": map[string]interface{}{"n": nil, "a": false, "b": true, "c": 42, "d": 4.2, "e": "text", "f": []interface{}{34.0}, "g": map[string]interface{}{"A": 45, "B": 1.2}}, 629 | "obj1b": map[string]interface{}{"n": nil, "b": true, "a": false, "c": 42.0, "d": 4.2, "e": "text", "f": []interface{}{34}, "g": map[string]interface{}{"A": 45, "B": 1.2}}, 630 | 631 | "obj2": map[string]interface{}{"a": false, "b": true, "c": 42, "d": 4.2, "e": "text"}, 632 | "obj3": map[string]interface{}{"a": false, "b": true, "c": 42, "d": 4.2, "e": ""}, 633 | } 634 | 635 | assertEquality(t, vars, true, "emptyObj", "emptyObj") 636 | assertEquality(t, vars, true, "obj1a", "obj1a") 637 | assertEquality(t, vars, true, "obj1b", "obj1b") 638 | assertEquality(t, vars, true, "obj2", "obj2") 639 | assertEquality(t, vars, true, "obj3", "obj3") 640 | 641 | assertEquality(t, vars, true, "obj1a", "obj1b") 642 | assertEquality(t, vars, true, "obj1b", "obj1b") 643 | 644 | assertEquality(t, vars, false, "obj1a", "obj2") 645 | assertEquality(t, vars, false, "obj1a", "obj3") 646 | assertEquality(t, vars, false, "obj2", "obj3") 647 | 648 | assertEquality(t, vars, false, "emptyObj", "null") 649 | assertEquality(t, vars, false, "emptyObj", "0") 650 | assertEquality(t, vars, false, "emptyObj", "obj1a") 651 | assertEquality(t, vars, false, `emptyObj`, `""`) 652 | } 653 | 654 | func assertComparison(t *testing.T, variables map[string]interface{}, v1, v2 interface{}) { 655 | int1, ok := v1.(int) 656 | if ok { 657 | int2, ok := v2.(int) 658 | if ok { 659 | assertEvaluation(t, variables, int1 < int2, fmt.Sprintf("%d<%d", int1, int2)) 660 | assertEvaluation(t, variables, int1 <= int2, fmt.Sprintf("%d<=%d", int1, int2)) 661 | assertEvaluation(t, variables, int1 > int2, fmt.Sprintf("%d>%d", int1, int2)) 662 | assertEvaluation(t, variables, int1 >= int2, fmt.Sprintf("%d>=%d", int1, int2)) 663 | } else { 664 | float2 := v2.(float64) 665 | assertEvaluation(t, variables, float64(int1) < float2, fmt.Sprintf("%d<%f", int1, float2)) 666 | assertEvaluation(t, variables, float64(int1) <= float2, fmt.Sprintf("%d<=%f", int1, float2)) 667 | assertEvaluation(t, variables, float64(int1) > float2, fmt.Sprintf("%d>%f", int1, float2)) 668 | assertEvaluation(t, variables, float64(int1) >= float2, fmt.Sprintf("%d>=%f", int1, float2)) 669 | } 670 | return 671 | } 672 | 673 | float1 := v1.(float64) 674 | int2, ok := v2.(int) 675 | if ok { 676 | assertEvaluation(t, variables, float1 < float64(int2), fmt.Sprintf("%f<%d", float1, int2)) 677 | assertEvaluation(t, variables, float1 <= float64(int2), fmt.Sprintf("%f<=%d", float1, int2)) 678 | assertEvaluation(t, variables, float1 > float64(int2), fmt.Sprintf("%f>%d", float1, int2)) 679 | assertEvaluation(t, variables, float1 >= float64(int2), fmt.Sprintf("%f>=%d", float1, int2)) 680 | } else { 681 | float2 := v2.(float64) 682 | assertEvaluation(t, variables, float1 < float2, fmt.Sprintf("%f<%f", float1, float2)) 683 | assertEvaluation(t, variables, float1 <= float2, fmt.Sprintf("%f<=%f", float1, float2)) 684 | assertEvaluation(t, variables, float1 > float2, fmt.Sprintf("%f>%f", float1, float2)) 685 | assertEvaluation(t, variables, float1 >= float2, fmt.Sprintf("%f>=%f", float1, float2)) 686 | } 687 | return 688 | } 689 | 690 | func Test_Compare(t *testing.T) { 691 | // int, int 692 | assertComparison(t, nil, 3, 4) 693 | assertComparison(t, nil, -4, 2) 694 | assertComparison(t, nil, 4, 3) 695 | assertComparison(t, nil, 2, -4) 696 | assertComparison(t, nil, 2, 2) 697 | 698 | // float, float 699 | assertComparison(t, nil, 3.5, 3.51) 700 | assertComparison(t, nil, -4.9, 2.0) 701 | assertComparison(t, nil, 3.51, 3.5) 702 | assertComparison(t, nil, 2.1, -4.0) 703 | assertComparison(t, nil, 2.0, 2.0) 704 | 705 | // int, float 706 | assertComparison(t, nil, 3, 3.1) 707 | assertComparison(t, nil, -4, 2.0) 708 | assertComparison(t, nil, 4, 3.5) 709 | assertComparison(t, nil, 2, -4.0) 710 | assertComparison(t, nil, 2, 2.0) 711 | 712 | // float, int 713 | assertComparison(t, nil, 3.5, 4) 714 | assertComparison(t, nil, -4.9, 2) 715 | assertComparison(t, nil, 3.51, 3) 716 | assertComparison(t, nil, 2.1, -4) 717 | assertComparison(t, nil, 2.0, 2) 718 | } 719 | 720 | func Test_CompareHugeIntegers(t *testing.T) { 721 | // these integers can't be represented accurately as floats: 722 | i := 999999999999999998 723 | j := 999999999999999999 724 | assert.True(t, i < j) 725 | assert.False(t, float64(i) < float64(j)) 726 | 727 | // ... we should be able to deal with them: 728 | assertEvaluation(t, nil, true, fmt.Sprintf("%d < %d", i, j)) 729 | assertEvaluation(t, nil, false, fmt.Sprintf("%d.0 < %d.0", i, j)) 730 | 731 | assertEvaluation(t, nil, true, fmt.Sprintf("%d <= %d", i, j)) 732 | assertEvaluation(t, nil, true, fmt.Sprintf("%d.0 <= %d.0", i, j)) 733 | 734 | assertEvaluation(t, nil, true, fmt.Sprintf("%d > %d", j, i)) 735 | assertEvaluation(t, nil, false, fmt.Sprintf("%d.0 > %d.0", j, i)) 736 | 737 | assertEvaluation(t, nil, true, fmt.Sprintf("%d >= %d", j, i)) 738 | assertEvaluation(t, nil, true, fmt.Sprintf("%d.0 >= %d.0", j, i)) 739 | } 740 | 741 | func Test_Compare_InvalidTypes(t *testing.T) { 742 | vars := getTestVars() 743 | allTypes := []string{"nil", "true", "false", "42", "4.2", `"text"`, `"0"`, "[0]", "[]", "arr", `{"a":0}`, "{}", "obj"} 744 | typeOfAllTypes := []string{"nil", "bool", "bool", "number", "number", "string", "string", "array", "array", "array", "object", "object", "object"} 745 | 746 | for idx1, t1 := range allTypes { 747 | for idx2, t2 := range allTypes { 748 | typ1 := typeOfAllTypes[idx1] 749 | typ2 := typeOfAllTypes[idx2] 750 | 751 | if typ1 == "number" && typ2 == "number" { 752 | continue 753 | } 754 | 755 | // < 756 | expectedErr := fmt.Sprintf("type error: cannot compare type %s and %s", typ1, typ2) 757 | assertEvalError(t, vars, expectedErr, t1+"<"+t2) 758 | // <= 759 | expectedErr = fmt.Sprintf("type error: cannot compare type %s and %s", typ1, typ2) 760 | assertEvalError(t, vars, expectedErr, t1+"<="+t2) 761 | // > 762 | expectedErr = fmt.Sprintf("type error: cannot compare type %s and %s", typ1, typ2) 763 | assertEvalError(t, vars, expectedErr, t1+">"+t2) 764 | // >= 765 | expectedErr = fmt.Sprintf("type error: cannot compare type %s and %s", typ1, typ2) 766 | assertEvalError(t, vars, expectedErr, t1+">="+t2) 767 | } 768 | 769 | } 770 | } 771 | 772 | func Test_BitManipulation_Or(t *testing.T) { 773 | assertEvaluation(t, nil, 0, "0|0") 774 | assertEvaluation(t, nil, 10, "8|2") 775 | assertEvaluation(t, nil, 11, "8|2|1") 776 | assertEvaluation(t, nil, 15, "8|4|2|1") 777 | assertEvaluation(t, nil, 13, "9|5") 778 | 779 | assertEvaluation(t, nil, 10, "8|2.0") 780 | assertEvaluation(t, nil, 10, "8.0|2") 781 | assertEvaluation(t, nil, 10, "8.0|2.0") 782 | 783 | assertEvalError(t, nil, "type error: cannot cast floating point number to integer without losing precision", "8|2.1") 784 | assertEvalError(t, nil, "type error: cannot cast floating point number to integer without losing precision", "8.1|2") 785 | assertEvalError(t, nil, "type error: cannot cast floating point number to integer without losing precision", "8.1|2.1") 786 | } 787 | 788 | func Test_BitManipulation_And(t *testing.T) { 789 | assertEvaluation(t, nil, 0, "8&2") 790 | assertEvaluation(t, nil, 8, "13&10") 791 | assertEvaluation(t, nil, 2, "10&15&2") 792 | 793 | assertEvaluation(t, nil, 2, "15&2.0") 794 | assertEvaluation(t, nil, 2, "15.0&2") 795 | assertEvaluation(t, nil, 2, "15.0&2.0") 796 | 797 | assertEvalError(t, nil, "type error: cannot cast floating point number to integer without losing precision", "15&2.1") 798 | assertEvalError(t, nil, "type error: cannot cast floating point number to integer without losing precision", "15.1&2") 799 | assertEvalError(t, nil, "type error: cannot cast floating point number to integer without losing precision", "15.1&2.1") 800 | } 801 | 802 | func Test_BitManipulation_XOr(t *testing.T) { 803 | assertEvaluation(t, nil, 10, "8^2") 804 | assertEvaluation(t, nil, 7, "13^10") 805 | assertEvaluation(t, nil, 0, "15^15") 806 | assertEvaluation(t, nil, 4, "10^15^1") 807 | 808 | assertEvaluation(t, nil, 7, "13^10.0") 809 | assertEvaluation(t, nil, 7, "13.0^10") 810 | assertEvaluation(t, nil, 7, "13.0^10.0") 811 | 812 | assertEvalError(t, nil, "type error: cannot cast floating point number to integer without losing precision", "13^10.1") 813 | assertEvalError(t, nil, "type error: cannot cast floating point number to integer without losing precision", "13.1^10") 814 | assertEvalError(t, nil, "type error: cannot cast floating point number to integer without losing precision", "13.1^10.1") 815 | } 816 | 817 | func Test_BitManipulation_Not(t *testing.T) { 818 | assertEvaluation(t, nil, 0, "~-1") 819 | assertEvaluation(t, nil, 0, "~-1.0") 820 | assertEvaluation(t, nil, 0x5AA5, "(~0xA55A) & 0xFFFF") 821 | assertEvaluation(t, nil, 0xA55A, "(~0x5AA5) & 0xFFFF") 822 | 823 | if BitSizeOfInt == 32 { 824 | assertEvaluation(t, nil, -1, "~0") 825 | assertEvaluation(t, nil, 0, "~0xFFFFFFFF") 826 | } else if BitSizeOfInt == 64 { 827 | assertEvaluation(t, nil, -1, "~0") 828 | assertEvaluation(t, nil, 0, "~0xFFFFFFFFFFFFFFFF") 829 | } 830 | } 831 | 832 | func Test_BitManipulation_Not_InvalidTypes(t *testing.T) { 833 | assertEvalError(t, nil, "type error: required number of type integer, but was nil", "~nil") 834 | assertEvalError(t, nil, "type error: required number of type integer, but was bool", "~true") 835 | assertEvalError(t, nil, "type error: required number of type integer, but was bool", "~false") 836 | assertEvalError(t, nil, "type error: cannot cast floating point number to integer without losing precision", "~4.2") 837 | assertEvalError(t, nil, "type error: required number of type integer, but was string", `~"text"`) 838 | assertEvalError(t, nil, "type error: required number of type integer, but was array", "~[]") 839 | assertEvalError(t, nil, "type error: required number of type integer, but was object", "~{}") 840 | } 841 | 842 | func Test_BitManipulation_Shift(t *testing.T) { 843 | assertEvaluation(t, nil, 1, "0x01 << 0") 844 | assertEvaluation(t, nil, 2, "0x01 << 1") 845 | assertEvaluation(t, nil, 4, "0x01 << 2") 846 | assertEvaluation(t, nil, 24, "0x03 << 3") 847 | 848 | if BitSizeOfInt == 32 { 849 | assertEvaluation(t, nil, -2147483648, "0x01 << 31") // 32bit, leading one (highest negative) 850 | assertEvaluation(t, nil, 0, "0x01 << 32") // 32bit, truncated 851 | } else if BitSizeOfInt == 64 { 852 | assertEvaluation(t, nil, -9223372036854775808, "0x01 << 63") // 64bit, leading one (highest negative) 853 | assertEvaluation(t, nil, 0, "0x01 << 64") // 64bit, truncated 854 | } 855 | 856 | if BitSizeOfInt == 32 { 857 | assertEvaluation(t, nil, 1, "0x40000000 >> 30") 858 | assertEvaluation(t, nil, 2, "0x40000000 >> 28") 859 | assertEvaluation(t, nil, 4, "0x40000000 >> 28") 860 | assertEvaluation(t, nil, 12, "0x60000000 >> 27") 861 | 862 | assertEvaluation(t, nil, 0, "0x40000000 >> 31") // underflow 863 | assertEvaluation(t, nil, -1, "0x80000000 >> 31") // sign extension 864 | } else if BitSizeOfInt == 64 { 865 | assertEvaluation(t, nil, 1, "0x4000000000000000 >> 62") 866 | assertEvaluation(t, nil, 2, "0x4000000000000000 >> 61") 867 | assertEvaluation(t, nil, 4, "0x4000000000000000 >> 60") 868 | assertEvaluation(t, nil, 12, "0x6000000000000000 >> 59") 869 | 870 | assertEvaluation(t, nil, 0, "0x4000000000000000 >> 63") // underflow 871 | assertEvaluation(t, nil, -1, "0x8000000000000000 >> 63") // sign extension 872 | } 873 | } 874 | 875 | func Test_BitManipulation_NegativeShift(t *testing.T) { 876 | assertEvaluation(t, nil, 0, "0x01 << -1") // underflow 877 | 878 | assertEvaluation(t, nil, 2, "0x01 >> -1") 879 | assertEvaluation(t, nil, 4, "0x01 >> -2") 880 | assertEvaluation(t, nil, 24, "0x03 >> -3") 881 | 882 | if BitSizeOfInt == 32 { 883 | assertEvaluation(t, nil, -2147483648, "0x01 >> -31") // 32bit, leading one (highest negative) 884 | assertEvaluation(t, nil, 0, "0x01 >> -32") // 32bit, truncated 885 | } else if BitSizeOfInt == 64 { 886 | assertEvaluation(t, nil, -9223372036854775808, "0x01 >> -63") // 64bit, leading one (highest negative) 887 | assertEvaluation(t, nil, 0, "0x01 >> -64") // 64bit, truncated 888 | } 889 | 890 | if BitSizeOfInt == 32 { 891 | assertEvaluation(t, nil, 1, "0x40000000 << -30") 892 | assertEvaluation(t, nil, 2, "0x40000000 << -28") 893 | assertEvaluation(t, nil, 4, "0x40000000 << -28") 894 | assertEvaluation(t, nil, 12, "0x60000000 << -27") 895 | 896 | assertEvaluation(t, nil, 0, "0x40000000 << -31") // underflow 897 | assertEvaluation(t, nil, -1, "0x80000000 << -31") // sign extension 898 | } else if BitSizeOfInt == 64 { 899 | assertEvaluation(t, nil, 1, "0x4000000000000000 << -62") 900 | assertEvaluation(t, nil, 2, "0x4000000000000000 << -61") 901 | assertEvaluation(t, nil, 4, "0x4000000000000000 << -60") 902 | assertEvaluation(t, nil, 12, "0x6000000000000000 << -59") 903 | 904 | assertEvaluation(t, nil, 0, "0x4000000000000000 << -63") // underflow 905 | assertEvaluation(t, nil, -1, "0x8000000000000000 << -63") // sign extension 906 | } 907 | } 908 | 909 | func Test_BitManipulation_InvalidTypes(t *testing.T) { 910 | vars := getTestVars() 911 | allTypes := []string{"nil", "true", "false", "42", "4.0", `"text"`, `"0"`, "[0]", "[]", "arr", `{"a":0}`, "{}", "obj"} 912 | typeOfAllTypes := []string{"nil", "bool", "bool", "number", "number", "string", "string", "array", "array", "array", "object", "object", "object"} 913 | 914 | for idx1, t1 := range allTypes { 915 | for idx2, t2 := range allTypes { 916 | typ1 := typeOfAllTypes[idx1] 917 | typ2 := typeOfAllTypes[idx2] 918 | 919 | if typ1 == "number" && typ2 == "number" { 920 | continue 921 | } 922 | 923 | nonIntType := typ1 924 | if typ1 == "number" { 925 | nonIntType = typ2 926 | } 927 | // & 928 | expectedErr := fmt.Sprintf("type error: required number of type integer, but was %s", nonIntType) 929 | assertEvalError(t, vars, expectedErr, t1+"&"+t2) 930 | // | 931 | expectedErr = fmt.Sprintf("type error: required number of type integer, but was %s", nonIntType) 932 | assertEvalError(t, vars, expectedErr, t1+"|"+t2) 933 | // ^ 934 | expectedErr = fmt.Sprintf("type error: required number of type integer, but was %s", nonIntType) 935 | assertEvalError(t, vars, expectedErr, t1+"^"+t2) 936 | // << 937 | expectedErr = fmt.Sprintf("type error: required number of type integer, but was %s", nonIntType) 938 | assertEvalError(t, vars, expectedErr, t1+"<<"+t2) 939 | // >> 940 | expectedErr = fmt.Sprintf("type error: required number of type integer, but was %s", nonIntType) 941 | assertEvalError(t, vars, expectedErr, t1+">>"+t2) 942 | } 943 | 944 | } 945 | } 946 | 947 | func Test_BitManipulation_CannotCastFloat(t *testing.T) { 948 | expectedErr := "type error: cannot cast floating point number to integer without losing precision" 949 | 950 | // & 951 | assertEvalError(t, nil, expectedErr, "0 & 4.2") 952 | assertEvalError(t, nil, expectedErr, "4.2 & 0") 953 | assertEvalError(t, nil, expectedErr, "4.2 & 4.2") 954 | // | 955 | assertEvalError(t, nil, expectedErr, "0 | 4.2") 956 | assertEvalError(t, nil, expectedErr, "4.2 | 0") 957 | assertEvalError(t, nil, expectedErr, "4.2 | 4.2") 958 | // ^ 959 | assertEvalError(t, nil, expectedErr, "0 ^ 4.2") 960 | assertEvalError(t, nil, expectedErr, "4.2 ^ 0") 961 | assertEvalError(t, nil, expectedErr, "4.2 ^ 4.2") 962 | // << 963 | assertEvalError(t, nil, expectedErr, "0 << 4.2") 964 | assertEvalError(t, nil, expectedErr, "4.2 << 0") 965 | assertEvalError(t, nil, expectedErr, "4.2 << 4.2") 966 | // >> 967 | assertEvalError(t, nil, expectedErr, "0 >> 4.2") 968 | assertEvalError(t, nil, expectedErr, "4.2 >> 0") 969 | assertEvalError(t, nil, expectedErr, "4.2 >> 4.2") 970 | } 971 | 972 | func Test_VariableAccess_Simple(t *testing.T) { 973 | vars := getTestVars() 974 | for key, val := range vars { 975 | assertEvaluation(t, vars, val, key) 976 | assertEvaluation(t, vars, val, "("+key+")") 977 | } 978 | } 979 | 980 | func Test_VariableAccess_DoesNotExist(t *testing.T) { 981 | assertEvalError(t, nil, "var error: variable \"var\" does not exist", "var") 982 | assertEvalError(t, nil, "var error: variable \"varName\" does not exist", "varName") 983 | 984 | assertEvalError(t, nil, "var error: variable \"var\" does not exist", "var.field") 985 | assertEvalError(t, nil, "var error: variable \"var\" does not exist", "var[0]") 986 | assertEvalError(t, nil, "var error: variable \"var\" does not exist", "var[fieldName]") 987 | } 988 | 989 | func Test_VariableAccess_Arithmetic(t *testing.T) { 990 | vars := getTestVars() 991 | assertEvaluation(t, vars, 84, "int + int") 992 | assertEvaluation(t, vars, 8.4, "float + float") 993 | assertEvaluation(t, vars, 88.2, "int + float + int") 994 | } 995 | 996 | func Test_VariableAccess_DotSyntax(t *testing.T) { 997 | vars := getTestVars() 998 | 999 | // access object fields 1000 | for key, val := range vars["obj"].(map[string]interface{}) { 1001 | assertEvaluation(t, vars, val, "obj."+key) 1002 | } 1003 | 1004 | assertEvaluation(t, vars, 4.2, `{"a": 4.2}.a`) 1005 | assertEvaluation(t, vars, 4.2, `({"a": 4.2}).a`) 1006 | assertEvaluation(t, vars, 4.2, `{"a": 4.2}["a"]`) 1007 | assertEvaluation(t, vars, 4.2, `({"a": 4.2})["a"]`) 1008 | 1009 | assertEvaluation(t, vars, 42, `{"a": {"b": 42}}.a.b`) 1010 | assertEvaluation(t, vars, 42, `{"a": {"b": 42}}["a"]["b"]`) 1011 | } 1012 | 1013 | func Test_VariableAccess_DotSyntax_DoesNotExist(t *testing.T) { 1014 | vars := getTestVars() 1015 | assertEvalError(t, vars, "var error: object has no member \"key\"", "obj.key") 1016 | assertEvalError(t, vars, "var error: object has no member \"key\"", "obj.key.field") 1017 | assertEvalError(t, vars, "var error: object has no member \"key\"", "obj.key[0]") 1018 | assertEvalError(t, vars, "var error: object has no member \"key\"", "obj.key[fieldName]") 1019 | } 1020 | 1021 | func Test_VariableAccess_DotSyntax_InvalidType(t *testing.T) { 1022 | vars := getTestVars() 1023 | assertEvalError(t, vars, "syntax error: unexpected LITERAL_NUMBER", "obj.0") 1024 | 1025 | assertEvalError(t, vars, "syntax error: array index must be number, but was string", "arr.key") 1026 | assertEvalError(t, vars, "syntax error: cannot access fields on type string", `"txt".key`) 1027 | assertEvalError(t, vars, "syntax error: cannot access fields on type nil", `nil.key`) 1028 | assertEvalError(t, vars, "syntax error: cannot access fields on type number", `4.2.key`) 1029 | } 1030 | 1031 | func Test_VariableAccess_DotSyntax_InvalidSyntax(t *testing.T) { 1032 | vars := getTestVars() 1033 | assertEvalError(t, vars, "syntax error: unexpected '[', expecting IDENT", "obj.[b]") 1034 | } 1035 | 1036 | func Test_VariableAccess_ArraySyntax(t *testing.T) { 1037 | vars := getTestVars() 1038 | 1039 | // access object fields 1040 | for key, val := range vars["obj"].(map[string]interface{}) { 1041 | assertEvaluation(t, vars, val, `obj["`+key+`"]`) 1042 | assertEvaluation(t, vars, val, `obj[("`+key+`")]`) 1043 | } 1044 | 1045 | // access array elements 1046 | for idx, val := range vars["arr"].([]interface{}) { 1047 | strIdx := strconv.Itoa(idx) 1048 | // with int: 1049 | assertEvaluation(t, vars, val, `arr[`+strIdx+`]`) 1050 | assertEvaluation(t, vars, val, `arr[(`+strIdx+`)]`) 1051 | // with float: 1052 | assertEvaluation(t, vars, val, `arr[`+strIdx+`.0]`) 1053 | assertEvaluation(t, vars, val, `arr[(`+strIdx+`.0)]`) 1054 | } 1055 | 1056 | // access array literals 1057 | assertEvaluation(t, vars, false, `[false, 42, "text"][0]`) 1058 | assertEvaluation(t, vars, 42, `[false, 42, "text"][1]`) 1059 | assertEvaluation(t, vars, "text", `[false, 42, "text"][2]`) 1060 | assertEvaluation(t, vars, 0.0, `([0.0])[0]`) 1061 | 1062 | assertEvaluation(t, vars, 42, `[0, [1, 2, 42]][1][2]`) 1063 | } 1064 | 1065 | func Test_VariableAccess_ArraySyntax_DoesNotExist(t *testing.T) { 1066 | vars := getTestVars() 1067 | assertEvalError(t, vars, "var error: object has no member \"key\"", `obj["key"]`) 1068 | assertEvalError(t, vars, "var error: object has no member \"key\"", `obj["key"].field`) 1069 | assertEvalError(t, vars, "var error: object has no member \"key\"", `obj["key"][0]`) 1070 | assertEvalError(t, vars, "var error: object has no member \"key\"", `obj["key"][fieldName]`) 1071 | 1072 | assertEvalError(t, vars, "var error: array index 5 is out of range [0, 4]", `arr[5]`) 1073 | assertEvalError(t, vars, "var error: array index 6 is out of range [0, 4]", `arr[6]`) 1074 | assertEvalError(t, vars, "var error: array index 0 is out of range [0, 0]", `[][0]`) 1075 | assertEvalError(t, vars, "var error: array index 41 is out of range [0, 1]", `[1][41]`) 1076 | } 1077 | 1078 | func Test_VariableAccess_ArraySyntax_InvalidType(t *testing.T) { 1079 | vars := getTestVars() 1080 | assertEvalError(t, vars, "syntax error: object key must be string, but was bool", `obj[true]`) 1081 | assertEvalError(t, vars, "syntax error: object key must be string, but was number", `obj[0]`) 1082 | assertEvalError(t, vars, "syntax error: object key must be string, but was array", `obj[arr]`) 1083 | assertEvalError(t, vars, "syntax error: object key must be string, but was object", `obj[obj]`) 1084 | 1085 | assertEvalError(t, vars, "syntax error: array index must be number, but was bool", `arr[true]`) 1086 | assertEvalError(t, vars, "syntax error: array index must be number, but was string", `arr["0"]`) 1087 | assertEvalError(t, vars, "syntax error: array index must be number, but was string", `["0"]["0"]`) 1088 | assertEvalError(t, vars, "syntax error: array index must be number, but was array", `arr[arr]`) 1089 | assertEvalError(t, vars, "syntax error: array index must be number, but was object", `arr[obj]`) 1090 | 1091 | assertEvalError(t, vars, "syntax error: cannot access fields on type string", `"txt"[0]`) 1092 | assertEvalError(t, vars, "syntax error: cannot access fields on type nil", `nil[0]`) 1093 | assertEvalError(t, vars, "syntax error: cannot access fields on type number", `4.2[0]`) 1094 | } 1095 | 1096 | func Test_VariableAccess_ArraySyntax_FloatHasDecimals(t *testing.T) { 1097 | vars := getTestVars() 1098 | assertEvalError(t, vars, "eval error: array index must be whole number, but was 0.100000", `arr[0.1]`) 1099 | assertEvalError(t, vars, "eval error: array index must be whole number, but was 0.500000", `arr[0.5]`) 1100 | assertEvalError(t, vars, "eval error: array index must be whole number, but was 0.900000", `arr[0.9]`) 1101 | assertEvalError(t, vars, "eval error: array index must be whole number, but was 2.000100", `arr[2.0001]`) 1102 | } 1103 | 1104 | func Test_VariableAccess_Nested(t *testing.T) { 1105 | vars := map[string]interface{}{ 1106 | "arr": []interface{}{ 1107 | 10, "a", 1108 | []interface{}{ 1109 | 11, "b", 1110 | }, 1111 | map[string]interface{}{ 1112 | "a": 13, 1113 | "b": "c", 1114 | }, 1115 | }, 1116 | "obj": map[string]interface{}{ 1117 | "a": 20, 1118 | "b": "a", 1119 | "c": []interface{}{ 1120 | 22, 23, 1121 | }, 1122 | "d": map[string]interface{}{ 1123 | "a": 24, 1124 | "b": "b", 1125 | }, 1126 | }, 1127 | } 1128 | 1129 | // array: 1130 | assertEvaluation(t, vars, 10, `arr[0]`) 1131 | assertEvaluation(t, vars, "a", `arr[1]`) 1132 | assertEvaluation(t, vars, 11, `arr[2][0]`) 1133 | assertEvaluation(t, vars, "b", `arr[2][1]`) 1134 | assertEvaluation(t, vars, 13, `arr[3].a`) 1135 | assertEvaluation(t, vars, 13, `arr[3]["a"]`) 1136 | assertEvaluation(t, vars, "c", `arr[3].b`) 1137 | assertEvaluation(t, vars, "c", `arr[3]["b"]`) 1138 | // object: 1139 | assertEvaluation(t, vars, 20, `obj.a`) 1140 | assertEvaluation(t, vars, 20, `obj["a"]`) 1141 | assertEvaluation(t, vars, "a", `obj.b`) 1142 | assertEvaluation(t, vars, "a", `obj["b"]`) 1143 | assertEvaluation(t, vars, 22, `obj.c[0]`) 1144 | assertEvaluation(t, vars, 23, `obj["c"][1]`) 1145 | assertEvaluation(t, vars, 24, `obj.d.a`) 1146 | assertEvaluation(t, vars, 24, `obj.d["a"]`) 1147 | assertEvaluation(t, vars, "b", `obj["d"].b`) 1148 | assertEvaluation(t, vars, "b", `obj["d"]["b"]`) 1149 | } 1150 | 1151 | func Test_VariableAccess_DynamicAccess(t *testing.T) { 1152 | vars := map[string]interface{}{ 1153 | "num0": 0, 1154 | "num1": 1, 1155 | "letA": "a", 1156 | "letB": "b", 1157 | 1158 | "arr": []interface{}{ 1159 | 0, 4, "a", "abc", 42, 1160 | }, 1161 | 1162 | "obj": map[string]interface{}{ 1163 | "a": 0, 1164 | "b": 4, 1165 | "c": "a", 1166 | "d": "abc", 1167 | "abc": 43, 1168 | }, 1169 | } 1170 | 1171 | assertEvaluation(t, vars, 0, `arr[num0]`) 1172 | assertEvaluation(t, vars, 4, `arr[num1]`) 1173 | assertEvaluation(t, vars, "a", `arr[num1 + 1]`) 1174 | assertEvaluation(t, vars, "abc", `arr[num1 + 1 + num1]`) 1175 | 1176 | assertEvaluation(t, vars, 0, `obj[letA]`) 1177 | assertEvaluation(t, vars, 4, `obj[letB]`) 1178 | assertEvaluation(t, vars, 43, `obj[letA + letB + "c"]`) 1179 | 1180 | assertEvaluation(t, vars, 0, `arr[ obj.a ]`) 1181 | assertEvaluation(t, vars, 42, `arr[ obj["b"] ]`) 1182 | assertEvaluation(t, vars, 42, `arr[ obj[letB] ]`) 1183 | assertEvaluation(t, vars, 0, `arr[ obj[arr[2]] ]`) 1184 | assertEvaluation(t, vars, 0, `arr[ arr[obj.a] ]`) 1185 | 1186 | assertEvaluation(t, vars, 0, `obj[ arr[2] ]`) 1187 | assertEvaluation(t, vars, 43, `obj[ arr[num1 + num1 + 1] ]`) 1188 | assertEvaluation(t, vars, 43, `obj[ arr[obj.a + 3] ]`) 1189 | assertEvaluation(t, vars, 43, `obj[ arr[obj["a"] + 3] ]`) 1190 | } 1191 | 1192 | func Test_In(t *testing.T) { 1193 | obj := map[string]interface{}{ 1194 | "a": 3, 1195 | "b": 4.0, 1196 | "c": 5.5, 1197 | } 1198 | arr := []interface{}{ 1199 | nil, true, false, 42, 4.2, 8.0, "", "abc", []interface{}{}, []interface{}{0, 1.0, 2.4}, obj, 1200 | } 1201 | vars := map[string]interface{}{ 1202 | "num": 42, 1203 | "empty": []interface{}{}, 1204 | "obj": obj, 1205 | 1206 | "arr": arr, 1207 | } 1208 | 1209 | assertEvaluation(t, vars, false, `nil in []`) 1210 | assertEvaluation(t, vars, false, `false in []`) 1211 | assertEvaluation(t, vars, false, `true in [false]`) 1212 | 1213 | assertEvaluation(t, vars, true, `1 in [1]`) 1214 | assertEvaluation(t, vars, true, `1 in [1.0]`) 1215 | assertEvaluation(t, vars, true, `1.0 in [1]`) 1216 | assertEvaluation(t, vars, true, `1.0 in [1.0]`) 1217 | assertEvaluation(t, vars, true, `1 IN [1]`) 1218 | 1219 | assertEvaluation(t, vars, true, `false in [false, true]`) 1220 | assertEvaluation(t, vars, true, `true in [false, true]`) 1221 | 1222 | assertEvaluation(t, vars, true, `nil in arr`) 1223 | assertEvaluation(t, vars, true, `true in arr`) 1224 | assertEvaluation(t, vars, true, `false in arr`) 1225 | assertEvaluation(t, vars, true, `42 in arr`) 1226 | assertEvaluation(t, vars, true, `42.0 in arr`) 1227 | assertEvaluation(t, vars, true, `4.2 in arr`) 1228 | assertEvaluation(t, vars, true, `8.0 in arr`) 1229 | assertEvaluation(t, vars, true, `8 in arr`) 1230 | assertEvaluation(t, vars, true, `"" in arr`) 1231 | assertEvaluation(t, vars, true, `"abc" in arr`) 1232 | assertEvaluation(t, vars, true, `[] in arr`) 1233 | assertEvaluation(t, vars, true, `[0, 1.0, 2.4] in arr`) 1234 | assertEvaluation(t, vars, true, `[0.0, 1, 2.4] in arr`) 1235 | assertEvaluation(t, vars, true, `{"a":3, "b": 4.0, "c": 5.5} in arr`) 1236 | assertEvaluation(t, vars, true, `{"a":3.0, "c": 5.5, "b": 4} in arr`) 1237 | 1238 | assertEvaluation(t, vars, false, `8.01 in arr`) 1239 | assertEvaluation(t, vars, false, `[nil] in arr`) 1240 | assertEvaluation(t, vars, false, `[0, 1.0] in arr`) 1241 | assertEvaluation(t, vars, false, `1.0 in arr`) 1242 | 1243 | assertEvaluation(t, vars, false, `{"a":3, "b": 4.0} in arr`) 1244 | assertEvaluation(t, vars, false, `{} in arr`) 1245 | 1246 | assertEvaluation(t, vars, true, `empty in arr`) 1247 | assertEvaluation(t, vars, true, `num in arr`) 1248 | assertEvaluation(t, vars, true, `obj in arr`) 1249 | assertEvaluation(t, vars, false, `arr in arr`) 1250 | } 1251 | 1252 | func Test_In_InvalidTypes(t *testing.T) { 1253 | assertEvalError(t, nil, "syntax error: in-operator requires array, but was nil", "0 in nil") 1254 | assertEvalError(t, nil, "syntax error: in-operator requires array, but was bool", "0 in true") 1255 | assertEvalError(t, nil, "syntax error: in-operator requires array, but was bool", "0 in false") 1256 | assertEvalError(t, nil, "syntax error: in-operator requires array, but was number", "0 in 42") 1257 | assertEvalError(t, nil, "syntax error: in-operator requires array, but was number", "0 in 4.2") 1258 | assertEvalError(t, nil, "syntax error: in-operator requires array, but was string", `0 in "text"`) 1259 | assertEvalError(t, nil, "syntax error: in-operator requires array, but was object", "0 in {}") 1260 | } 1261 | 1262 | func Test_String_Slice(t *testing.T) { 1263 | assertEvaluation(t, nil, "abcdefg", `"abcdefg"[:]`) 1264 | 1265 | assertEvaluation(t, nil, "abcdefg", `"abcdefg"[0:]`) 1266 | assertEvaluation(t, nil, "bcdefg", `"abcdefg"[1:]`) 1267 | assertEvaluation(t, nil, "fg", `"abcdefg"[5:]`) 1268 | assertEvaluation(t, nil, "g", `"abcdefg"[6:]`) 1269 | assertEvaluation(t, nil, "", `"abcdefg"[7:]`) 1270 | 1271 | assertEvaluation(t, nil, "", `"abcdefg"[:0]`) 1272 | assertEvaluation(t, nil, "a", `"abcdefg"[:1]`) 1273 | assertEvaluation(t, nil, "abcde", `"abcdefg"[:5]`) 1274 | assertEvaluation(t, nil, "abcdef", `"abcdefg"[:6]`) 1275 | assertEvaluation(t, nil, "abcdefg", `"abcdefg"[:7]`) 1276 | 1277 | assertEvaluation(t, nil, "cde", `"abcdefg"[2:5]`) 1278 | assertEvaluation(t, nil, "d", `"abcdefg"[3:4]`) 1279 | } 1280 | 1281 | func Test_String_Slice_Unicode(t *testing.T) { 1282 | // The characters 世 and 界 both require 3 bytes 1283 | assertEvaluation(t, nil, "Hello, ", `"Hello, 世界"[:7]`) 1284 | assertEvaluation(t, nil, "世界", `"Hello, 世界"[7:13]`) 1285 | assertEvaluation(t, nil, "世", `"Hello, 世界"[7:10]`) 1286 | assertEvaluation(t, nil, "界", `"Hello, 世界"[10:13]`) 1287 | } 1288 | 1289 | func Test_String_Slice_OutOfRange(t *testing.T) { 1290 | assertEvalError(t, nil, "range error: start-index -1 is negative", `"abcd"[-1:]`) 1291 | assertEvalError(t, nil, "range error: start-index -42 is negative", `"abcd"[-42:]`) 1292 | 1293 | assertEvalError(t, nil, "range error: end-index -1 is out of range [0, 4]", `"abcd"[:-1]`) 1294 | assertEvalError(t, nil, "range error: end-index 5 is out of range [0, 4]", `"abcd"[:5]`) 1295 | assertEvalError(t, nil, "range error: end-index 42 is out of range [0, 4]", `"abcd"[:42]`) 1296 | 1297 | assertEvalError(t, nil, "range error: start-index 2 is greater than end-index 1", `"abcd"[2:1]`) 1298 | } 1299 | 1300 | func Test_Array_Slice(t *testing.T) { 1301 | arr := []interface{}{0, 1, 2, 3, 4, 5, 6} 1302 | vars := map[string]interface{}{"arr": arr} 1303 | 1304 | assertEvaluation(t, vars, []interface{}{}, `[][:]`) 1305 | assertEvaluation(t, vars, []interface{}{1}, `[1][:]`) 1306 | 1307 | assertEvaluation(t, vars, arr, `arr[:]`) 1308 | 1309 | assertEvaluation(t, vars, arr[0:], `arr[0:]`) 1310 | assertEvaluation(t, vars, arr[1:], `arr[1:]`) 1311 | assertEvaluation(t, vars, arr[5:], `arr[5:]`) 1312 | assertEvaluation(t, vars, arr[6:], `arr[6:]`) 1313 | assertEvaluation(t, vars, arr[7:], `arr[7:]`) 1314 | 1315 | assertEvaluation(t, vars, arr[:0], `arr[:0]`) 1316 | assertEvaluation(t, vars, arr[:1], `arr[:1]`) 1317 | assertEvaluation(t, vars, arr[:5], `arr[:5]`) 1318 | assertEvaluation(t, vars, arr[:6], `arr[:6]`) 1319 | assertEvaluation(t, vars, arr[:7], `arr[:7]`) 1320 | 1321 | assertEvaluation(t, vars, arr[2:5], `arr[2:5]`) 1322 | assertEvaluation(t, vars, arr[3:4], `arr[3:4]`) 1323 | } 1324 | 1325 | func Test_Array_Slice_OutOfRange(t *testing.T) { 1326 | assertEvalError(t, nil, "range error: start-index -1 is negative", `[0,1,2,3][-1:]`) 1327 | assertEvalError(t, nil, "range error: start-index -42 is negative", `[0,1,2,3][-42:]`) 1328 | 1329 | assertEvalError(t, nil, "range error: end-index -1 is out of range [0, 4]", `[0,1,2,3][:-1]`) 1330 | assertEvalError(t, nil, "range error: end-index 5 is out of range [0, 4]", `[0,1,2,3][:5]`) 1331 | assertEvalError(t, nil, "range error: end-index 42 is out of range [0, 4]", `[0,1,2,3][:42]`) 1332 | 1333 | assertEvalError(t, nil, "range error: start-index 2 is greater than end-index 1", `[0,1,2,3][2:1]`) 1334 | } 1335 | 1336 | func Test_Slicing_InvalidTypes(t *testing.T) { 1337 | vars := getTestVars() 1338 | allTypes := []string{"nil", "true", "false", "42", "4.2", `{"a":0}`, "{}", "obj"} 1339 | typeOfAllTypes := []string{"nil", "bool", "bool", "number", "number", "object", "object", "object"} 1340 | 1341 | for idx, e := range allTypes { 1342 | typ := typeOfAllTypes[idx] 1343 | 1344 | expectedErr := fmt.Sprintf("syntax error: slicing requires an array or string, but was %s", typ) 1345 | assertEvalError(t, vars, expectedErr, e+"[:]") 1346 | } 1347 | } 1348 | 1349 | func Test_FunctionCall_Simple(t *testing.T) { 1350 | var shouldReturn interface{} 1351 | var expectedArg interface{} 1352 | 1353 | functions := map[string]ExpressionFunction{ 1354 | "func1": func(args ...interface{}) (interface{}, error) { 1355 | return shouldReturn, nil 1356 | }, 1357 | "func2": func(args ...interface{}) (interface{}, error) { 1358 | assert.Equal(t, expectedArg, args[0]) 1359 | return args[0], nil 1360 | }, 1361 | "func3": func(args ...interface{}) (interface{}, error) { 1362 | return []interface{}{len(args), args}, nil 1363 | }, 1364 | } 1365 | 1366 | tests := map[string]interface{}{`nil`: nil, `true`: true, `false`: false, `42`: 42, `4.2`: 4.2, `"text"`: "text", `"0"`: "0"} 1367 | 1368 | for expr, expected := range tests { 1369 | shouldReturn = expected 1370 | assertEvaluationFuncs(t, nil, functions, expected, `func1()`) 1371 | expectedArg = expected 1372 | assertEvaluationFuncs(t, nil, functions, expected, `func2(`+expr+`)`) 1373 | } 1374 | 1375 | expectedReturn := []interface{}{6, []interface{}{true, false, 42, 4.2, "text", "0"}} 1376 | assertEvaluationFuncs(t, nil, functions, expectedReturn, `func3(true, false, 42, 4.2, "text", "0")`) 1377 | } 1378 | 1379 | func Test_FunctionCall_Nested(t *testing.T) { 1380 | functions := map[string]ExpressionFunction{ 1381 | "func": func(args ...interface{}) (interface{}, error) { 1382 | var allArgs = make([]interface{}, 0) 1383 | 1384 | for _, arg := range args { 1385 | multi, ok := arg.([]interface{}) 1386 | if ok { 1387 | allArgs = append(allArgs, multi...) 1388 | } else { 1389 | allArgs = append(allArgs, arg) 1390 | } 1391 | } 1392 | return allArgs, nil 1393 | }, 1394 | } 1395 | 1396 | assertEvaluationFuncs(t, nil, functions, []interface{}{1, 2, 3, 4}, `func(1, 2, 3, 4)`) 1397 | assertEvaluationFuncs(t, nil, functions, []interface{}{1, 2, 3, 4}, `func([1, 2], [3], 4)`) 1398 | assertEvaluationFuncs(t, nil, functions, []interface{}{1, 2, 3, 4}, `func(func(1, 2, 3, 4))`) 1399 | assertEvaluationFuncs(t, nil, functions, []interface{}{1, 2, 3, 4}, `func(func(1, 2), func(3, 4))`) 1400 | assertEvaluationFuncs(t, nil, functions, []interface{}{1, 2, 3, 4}, `func(func(1, func(2), func()), func(), func(3, 4))`) 1401 | } 1402 | 1403 | func Test_FunctionCall_Variables(t *testing.T) { 1404 | vars := getTestVars() 1405 | 1406 | functions := map[string]ExpressionFunction{ 1407 | "func": func(args ...interface{}) (interface{}, error) { 1408 | assert.Len(t, args, 2) 1409 | varName := args[0].(string) 1410 | varValue := args[1] 1411 | assert.Equal(t, vars[varName], varValue) 1412 | return varValue, nil 1413 | }, 1414 | } 1415 | for name, val := range vars { 1416 | assertEvaluationFuncs(t, vars, functions, val, `func("`+name+`", `+name+` )`) 1417 | } 1418 | 1419 | // function with same name as variable: 1420 | vars["func"] = "foo" 1421 | assertEvaluationFuncs(t, vars, functions, "foo", `func("func", func)`) 1422 | } 1423 | 1424 | func Test_FunctionCall_Errors(t *testing.T) { 1425 | // panic(error) should be indistinguishable from returning an error 1426 | functions := map[string]ExpressionFunction{ 1427 | "func1": func(args ...interface{}) (interface{}, error) { 1428 | return nil, errors.New("simulated error") 1429 | }, 1430 | "func2": func(args ...interface{}) (interface{}, error) { 1431 | panic(errors.New("simulated error")) 1432 | }, 1433 | "func3": func(args ...interface{}) (interface{}, error) { 1434 | panic("error string") 1435 | }, 1436 | "func4": func(args ...interface{}) (interface{}, error) { 1437 | panic(42) 1438 | }, 1439 | "func5": func(args ...interface{}) (interface{}, error) { 1440 | var s []int 1441 | return s[1], nil // runtime panic 1442 | }, 1443 | } 1444 | 1445 | assertEvalErrorFuncs(t, nil, functions, "function error: \"func1\" - simulated error", "func1()") 1446 | assertEvalErrorFuncs(t, nil, functions, "function error: \"func2\" - simulated error", "func2()") 1447 | assertEvalErrorFuncs(t, nil, functions, "function error: \"func3\" - panic: error string", "func3()") 1448 | assertEvalErrorFuncs(t, nil, functions, "function error: \"func4\" - panic: 42", "func4()") 1449 | 1450 | assert.Panics(t, func() { 1451 | _, _ = Evaluate("func5()", nil, functions) 1452 | }) 1453 | } 1454 | 1455 | func Test_InvalidFunctionCalls(t *testing.T) { 1456 | vars := map[string]interface{}{"func": nil} 1457 | functions := map[string]ExpressionFunction{ 1458 | "func": func(args ...interface{}) (interface{}, error) { 1459 | return nil, nil 1460 | }, 1461 | } 1462 | 1463 | assertEvalErrorFuncs(t, vars, functions, "syntax error: no such function \"noFunc\"", `noFunc()`) 1464 | assertEvalErrorFuncs(t, vars, functions, "syntax error: unexpected $end", `func(`) 1465 | assertEvalErrorFuncs(t, vars, functions, "syntax error: unexpected ')'", `func)`) 1466 | assertEvalErrorFuncs(t, vars, functions, "syntax error: unexpected ','", `func((1, 2))`) 1467 | } 1468 | 1469 | func Test_Ternary_Simple(t *testing.T) { 1470 | assertEvaluation(t, nil, 1, `true ? 1 : 2`) 1471 | assertEvaluation(t, nil, 2, `false ? 1 : 2`) 1472 | 1473 | assertEvaluation(t, nil, 2, `!true ? 1 : 2`) 1474 | assertEvaluation(t, nil, 1, `!false ? 1 : 2`) 1475 | 1476 | assertEvaluation(t, nil, 1, `4==4 ? 1 : 2`) 1477 | assertEvaluation(t, nil, 2, `1==4 ? 1 : 2`) 1478 | 1479 | assertEvaluation(t, nil, 2, `!(4==4) ? 1 : 2`) 1480 | assertEvaluation(t, nil, 1, `!(1==4) ? 1 : 2`) 1481 | 1482 | assertEvaluation(t, nil, 1, `((4==4) ? (1) : (2))`) 1483 | assertEvaluation(t, nil, 2, `((1==4) ? (1) : (2))`) 1484 | } 1485 | 1486 | func Test_Ternary_TypeMix(t *testing.T) { 1487 | assertEvaluation(t, nil, "a", `true ? "a" : 1.5`) 1488 | assertEvaluation(t, nil, 1.5, `false ? "a" : 1.5`) 1489 | 1490 | assertEvaluation(t, nil, []interface{}{42}, `true ? [42] : {"a":"b"}`) 1491 | assertEvaluation(t, nil, map[string]interface{}{"a": "b"}, `false ? [42] : {"a":"b"}`) 1492 | 1493 | assertEvaluation(t, nil, map[string]interface{}{"a": "b"}, `true ? {"a":"b"} : [42]`) 1494 | assertEvaluation(t, nil, []interface{}{42}, `false ? {"a":"b"} : [42]`) 1495 | 1496 | assertEvaluation(t, nil, 1, `"a" == "a" ? 1 : 2`) 1497 | assertEvaluation(t, nil, 2, `"a" == "b" ? 1 : 2`) 1498 | 1499 | assertEvaluation(t, nil, 1, `2 IN [1,2,3] ? 1 : 2`) 1500 | assertEvaluation(t, nil, 2, `2 IN [4,5,6] ? 1 : 2`) 1501 | } 1502 | 1503 | func Test_Ternary_Nesting(t *testing.T) { 1504 | assertEvaluation(t, nil, 1, "true ? (true ? 1 : 2) : (true ? 3:4)") 1505 | assertEvaluation(t, nil, 2, "true ? (false ? 1 : 2) : (true ? 3:4)") 1506 | assertEvaluation(t, nil, 3, "false ? (true ? 1 : 2) : (true ? 3:4)") 1507 | assertEvaluation(t, nil, 4, "false ? (true ? 1 : 2) : (false ? 3:4)") 1508 | } 1509 | 1510 | func Test_Ternary_RightAssociativity(t *testing.T) { 1511 | assertEvaluation(t, nil, 2, "false ? 1 : true ? 2 : false ? 3 : 4") // In case of left-associativity, this would not compile (1 is not a boolean) 1512 | } 1513 | 1514 | func Test_Ternary_NoShortCircuit(t *testing.T) { 1515 | var func1Calls, func2Calls int 1516 | 1517 | functions := map[string]ExpressionFunction{ 1518 | "func1": func(args ...interface{}) (interface{}, error) { 1519 | func1Calls++ 1520 | return 1, nil 1521 | }, 1522 | "func2": func(args ...interface{}) (interface{}, error) { 1523 | func2Calls++ 1524 | return 2, nil 1525 | }, 1526 | } 1527 | 1528 | assertEvaluationFuncs(t, nil, functions, 1, `true ? func1() : func2()`) 1529 | assert.Equal(t, 1, func1Calls) 1530 | assert.Equal(t, 1, func2Calls) 1531 | 1532 | assertEvaluationFuncs(t, nil, functions, 2, `false ? func1() : func2()`) 1533 | assert.Equal(t, 2, func1Calls) 1534 | assert.Equal(t, 2, func2Calls) 1535 | } 1536 | 1537 | func Test_Ternary_InvalidSyntax(t *testing.T) { 1538 | assertEvalError(t, nil, `syntax error: unexpected $end`, `true ? 1 :`) 1539 | assertEvalError(t, nil, `syntax error: unexpected ':'`, `true ? : 2`) 1540 | assertEvalError(t, nil, `syntax error: unexpected '?'`, `? 1 : 2`) 1541 | 1542 | assertEvalError(t, nil, `syntax error: unexpected $end`, `true ?`) 1543 | assertEvalError(t, nil, `syntax error: unexpected ':'`, `true : 1 ? 2`) 1544 | } 1545 | 1546 | func Test_Ternary_InvalidTypes(t *testing.T) { 1547 | assertEvalError(t, nil, `type error: required bool, but was string`, `"text" ? "a" : 1.5`) 1548 | assertEvalError(t, nil, `type error: required bool, but was number`, `0 ? "a" : 1.5`) 1549 | assertEvalError(t, nil, `type error: required bool, but was nil`, `nil ? "a" : 1.5`) 1550 | } 1551 | 1552 | func assertEvaluation(t *testing.T, variables map[string]interface{}, expected interface{}, str string) { 1553 | t.Helper() 1554 | result, err := Evaluate(str, variables, nil) 1555 | if assert.NoError(t, err) { 1556 | assert.Equal(t, expected, result) 1557 | } 1558 | } 1559 | 1560 | func assertEvaluationFuncs(t *testing.T, variables map[string]interface{}, functions map[string]ExpressionFunction, expected interface{}, str string) { 1561 | t.Helper() 1562 | result, err := Evaluate(str, variables, functions) 1563 | if assert.NoError(t, err) { 1564 | assert.Equal(t, expected, result) 1565 | } 1566 | } 1567 | 1568 | func assertEvalError(t *testing.T, variables map[string]interface{}, expectedErr string, str string) { 1569 | t.Helper() 1570 | assertEvalErrorFuncs(t, variables, nil, expectedErr, str) 1571 | } 1572 | 1573 | func assertEvalErrorFuncs(t *testing.T, variables map[string]interface{}, functions map[string]ExpressionFunction, expectedErr string, str string) { 1574 | t.Helper() 1575 | result, err := Evaluate(str, variables, functions) 1576 | if assert.Error(t, err) { 1577 | assert.Equal(t, expectedErr, err.Error()) 1578 | } 1579 | assert.Nil(t, result) 1580 | } 1581 | 1582 | func getTestVars() map[string]interface{} { 1583 | return map[string]interface{}{ 1584 | "nl": nil, 1585 | "tr": true, 1586 | "fl": false, 1587 | "int": 42, 1588 | "float": 4.2, 1589 | "str": "text", 1590 | "arr": []interface{}{true, 21, 2.1, "txt"}, 1591 | "obj": map[string]interface{}{ 1592 | "b": false, 1593 | "i": 51, 1594 | "f": 5.1, 1595 | "s": "tx", 1596 | }, 1597 | } 1598 | } 1599 | -------------------------------------------------------------------------------- /internal/parser/parserutils.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "reflect" 7 | "runtime" 8 | "strconv" 9 | ) 10 | 11 | func init() { 12 | yyDebug = 0 // can be increased for debugging the generated parser 13 | yyErrorVerbose = true // make sure to get better errors than just "syntax error" 14 | } 15 | 16 | // ExpressionFunction can be called from within expressions. 17 | // The returned object needs to have one of the following types: `nil`, `bool`, `int`, `float64`, `string`, `[]interface{}` or `map[string]interface{}`. 18 | type ExpressionFunction = func(args ...interface{}) (interface{}, error) 19 | 20 | func typeOf(val interface{}) string { 21 | if val == nil { 22 | return "nil" 23 | } 24 | 25 | kind := reflect.TypeOf(val).Kind() 26 | 27 | switch kind { 28 | case reflect.Bool: 29 | return "bool" 30 | case reflect.Int, reflect.Float64: 31 | return "number" 32 | case reflect.String: 33 | return "string" 34 | } 35 | 36 | if _, ok := val.([]interface{}); ok { 37 | return "array" 38 | } 39 | 40 | if _, ok := val.(map[string]interface{}); ok { 41 | return "object" 42 | } 43 | 44 | return "" 45 | } 46 | 47 | func asBool(val interface{}) bool { 48 | b, ok := val.(bool) 49 | if !ok { 50 | panic(fmt.Errorf("type error: required bool, but was %s", typeOf(val))) 51 | } 52 | return b 53 | } 54 | 55 | func asInteger(val interface{}) int { 56 | i, ok := val.(int) 57 | if ok { 58 | return i 59 | } 60 | f, ok := val.(float64) 61 | if !ok { 62 | panic(fmt.Errorf("type error: required number of type integer, but was %s", typeOf(val))) 63 | } 64 | 65 | i = int(f) 66 | if float64(i) != f { 67 | panic(fmt.Errorf("type error: cannot cast floating point number to integer without losing precision")) 68 | } 69 | return i 70 | } 71 | 72 | func add(val1 interface{}, val2 interface{}) interface{} { 73 | str1, str1OK := val1.(string) 74 | str2, str2OK := val2.(string) 75 | 76 | if str1OK && str2OK { // string + string = string 77 | return str1 + str2 78 | } 79 | 80 | int1, int1OK := val1.(int) 81 | int2, int2OK := val2.(int) 82 | 83 | if int1OK && int2OK { // int + int = int 84 | return int1 + int2 85 | } 86 | 87 | float1, float1OK := val1.(float64) 88 | float2, float2OK := val2.(float64) 89 | 90 | if int1OK { 91 | float1 = float64(int1) 92 | float1OK = true 93 | } 94 | if int2OK { 95 | float2 = float64(int2) 96 | float2OK = true 97 | } 98 | 99 | if float1OK && float2OK { 100 | return float1 + float2 101 | } 102 | if str1OK && float2OK { 103 | return str1 + strconv.FormatFloat(float2, 'f', -1, 64) 104 | } 105 | if float1OK && str2OK { 106 | return strconv.FormatFloat(float1, 'f', -1, 64) + str2 107 | } 108 | 109 | if str1OK && val2 == nil { 110 | return str1 + "nil" 111 | } 112 | if val1 == nil && str2OK { 113 | return "nil" + str2 114 | } 115 | 116 | bool1, bool1OK := val1.(bool) 117 | bool2, bool2OK := val2.(bool) 118 | 119 | if str1OK && bool2OK { 120 | return str1 + strconv.FormatBool(bool2) 121 | } 122 | if bool1OK && str2OK { 123 | return strconv.FormatBool(bool1) + str2 124 | } 125 | 126 | arr1, arr1OK := val1.([]interface{}) 127 | arr2, arr2OK := val2.([]interface{}) 128 | 129 | if arr1OK && arr2OK { 130 | return append(arr1, arr2...) 131 | } 132 | 133 | obj1, obj1OK := val1.(map[string]interface{}) 134 | obj2, obj2OK := val2.(map[string]interface{}) 135 | 136 | if obj1OK && obj2OK { 137 | sum := make(map[string]interface{}) 138 | for k, v := range obj1 { 139 | sum[k] = v 140 | } 141 | for k, v := range obj2 { 142 | sum[k] = v 143 | } 144 | return sum 145 | } 146 | 147 | panic(fmt.Errorf("type error: cannot add or concatenate type %s and %s", typeOf(val1), typeOf(val2))) 148 | } 149 | 150 | func sub(val1 interface{}, val2 interface{}) interface{} { 151 | int1, int1OK := val1.(int) 152 | int2, int2OK := val2.(int) 153 | 154 | if int1OK && int2OK { 155 | return int1 - int2 156 | } 157 | 158 | float1, float1OK := val1.(float64) 159 | float2, float2OK := val2.(float64) 160 | 161 | if int1OK { 162 | float1 = float64(int1) 163 | float1OK = true 164 | } 165 | if int2OK { 166 | float2 = float64(int2) 167 | float2OK = true 168 | } 169 | 170 | if float1OK && float2OK { 171 | return float1 - float2 172 | } 173 | panic(fmt.Errorf("type error: cannot subtract type %s and %s", typeOf(val1), typeOf(val2))) 174 | } 175 | 176 | func mul(val1 interface{}, val2 interface{}) interface{} { 177 | int1, int1OK := val1.(int) 178 | int2, int2OK := val2.(int) 179 | 180 | if int1OK && int2OK { 181 | return int1 * int2 182 | } 183 | 184 | float1, float1OK := val1.(float64) 185 | float2, float2OK := val2.(float64) 186 | 187 | if int1OK { 188 | float1 = float64(int1) 189 | float1OK = true 190 | } 191 | if int2OK { 192 | float2 = float64(int2) 193 | float2OK = true 194 | } 195 | 196 | if float1OK && float2OK { 197 | return float1 * float2 198 | } 199 | panic(fmt.Errorf("type error: cannot multiply type %s and %s", typeOf(val1), typeOf(val2))) 200 | } 201 | 202 | func div(val1 interface{}, val2 interface{}) interface{} { 203 | int1, int1OK := val1.(int) 204 | int2, int2OK := val2.(int) 205 | 206 | if int1OK && int2OK { 207 | return int1 / int2 208 | } 209 | 210 | float1, float1OK := val1.(float64) 211 | float2, float2OK := val2.(float64) 212 | 213 | if int1OK { 214 | float1 = float64(int1) 215 | float1OK = true 216 | } 217 | if int2OK { 218 | float2 = float64(int2) 219 | float2OK = true 220 | } 221 | 222 | if float1OK && float2OK { 223 | return float1 / float2 224 | } 225 | panic(fmt.Errorf("type error: cannot divide type %s and %s", typeOf(val1), typeOf(val2))) 226 | } 227 | 228 | func mod(val1 interface{}, val2 interface{}) interface{} { 229 | int1, int1OK := val1.(int) 230 | int2, int2OK := val2.(int) 231 | 232 | if int1OK && int2OK { 233 | return int1 % int2 234 | } 235 | 236 | float1, float1OK := val1.(float64) 237 | float2, float2OK := val2.(float64) 238 | 239 | if int1OK { 240 | float1 = float64(int1) 241 | float1OK = true 242 | } 243 | if int2OK { 244 | float2 = float64(int2) 245 | float2OK = true 246 | } 247 | 248 | if float1OK && float2OK { 249 | return math.Mod(float1, float2) 250 | } 251 | panic(fmt.Errorf("type error: cannot perform modulo on type %s and %s", typeOf(val1), typeOf(val2))) 252 | } 253 | 254 | func unaryMinus(val interface{}) interface{} { 255 | intVal, ok := val.(int) 256 | if ok { 257 | return -intVal 258 | } 259 | floatVal, ok := val.(float64) 260 | if ok { 261 | return -floatVal 262 | } 263 | panic(fmt.Errorf("type error: unary minus requires number, but was %s", typeOf(val))) 264 | } 265 | 266 | func deepEqual(val1 interface{}, val2 interface{}) bool { 267 | switch typ1 := val1.(type) { 268 | 269 | case []interface{}: 270 | typ2, ok := val2.([]interface{}) 271 | if !ok || len(typ1) != len(typ2) { 272 | return false 273 | } 274 | for idx := range typ1 { 275 | if !deepEqual(typ1[idx], typ2[idx]) { 276 | return false 277 | } 278 | } 279 | return true 280 | 281 | case map[string]interface{}: 282 | typ2, ok := val2.(map[string]interface{}) 283 | if !ok || len(typ1) != len(typ2) { 284 | return false 285 | } 286 | for idx := range typ1 { 287 | if !deepEqual(typ1[idx], typ2[idx]) { 288 | return false 289 | } 290 | } 291 | return true 292 | 293 | case int: 294 | int2, ok := val2.(int) 295 | if ok { 296 | return typ1 == int2 297 | } 298 | float2, ok := val2.(float64) 299 | if ok { 300 | return float64(typ1) == float2 301 | } 302 | return false 303 | 304 | case float64: 305 | float2, ok := val2.(float64) 306 | if ok { 307 | return typ1 == float2 308 | } 309 | int2, ok := val2.(int) 310 | if ok { 311 | return typ1 == float64(int2) 312 | } 313 | return false 314 | } 315 | return val1 == val2 316 | } 317 | 318 | func compare(val1 interface{}, val2 interface{}, operation string) bool { 319 | int1, int1OK := val1.(int) 320 | int2, int2OK := val2.(int) 321 | 322 | if int1OK && int2OK { 323 | return compareInt(int1, int2, operation) 324 | } 325 | 326 | float1, float1OK := val1.(float64) 327 | float2, float2OK := val2.(float64) 328 | 329 | if int1OK { 330 | float1 = float64(int1) 331 | float1OK = true 332 | } 333 | if int2OK { 334 | float2 = float64(int2) 335 | float2OK = true 336 | } 337 | 338 | if float1OK && float2OK { 339 | return compareFloat(float1, float2, operation) 340 | } 341 | panic(fmt.Errorf("type error: cannot compare type %s and %s", typeOf(val1), typeOf(val2))) 342 | } 343 | 344 | func compareInt(val1 int, val2 int, operation string) bool { 345 | switch operation { 346 | case "<": 347 | return val1 < val2 348 | case "<=": 349 | return val1 <= val2 350 | case ">": 351 | return val1 > val2 352 | case ">=": 353 | return val1 >= val2 354 | } 355 | panic(fmt.Errorf("syntax error: unsupported operation %q", operation)) 356 | } 357 | 358 | func compareFloat(val1 float64, val2 float64, operation string) bool { 359 | switch operation { 360 | case "<": 361 | return val1 < val2 362 | case "<=": 363 | return val1 <= val2 364 | case ">": 365 | return val1 > val2 366 | case ">=": 367 | return val1 >= val2 368 | } 369 | panic(fmt.Errorf("syntax error: unsupported operation %q", operation)) 370 | } 371 | 372 | func asObjectKey(key interface{}) string { 373 | s, ok := key.(string) 374 | if !ok { 375 | panic(fmt.Errorf("type error: object key must be string, but was %s", typeOf(key))) 376 | } 377 | return s 378 | } 379 | 380 | func addObjectMember(obj map[string]interface{}, key, val interface{}) map[string]interface{} { 381 | s := asObjectKey(key) 382 | _, ok := obj[s] 383 | if ok { 384 | panic(fmt.Errorf("syntax error: duplicate object key %q", s)) 385 | } 386 | obj[s] = val 387 | return obj 388 | } 389 | 390 | func accessVar(variables map[string]interface{}, varName string) interface{} { 391 | val, ok := variables[varName] 392 | if !ok { 393 | panic(fmt.Errorf("var error: variable %q does not exist", varName)) 394 | } 395 | return val 396 | } 397 | 398 | func accessField(s interface{}, field interface{}) interface{} { 399 | obj, ok := s.(map[string]interface{}) 400 | if ok { 401 | key, ok := field.(string) 402 | if !ok { 403 | panic(fmt.Errorf("syntax error: object key must be string, but was %s", typeOf(field))) 404 | } 405 | val, ok := obj[key] 406 | if !ok { 407 | panic(fmt.Errorf("var error: object has no member %q", field)) 408 | } 409 | return val 410 | } 411 | 412 | arrVar, ok := s.([]interface{}) 413 | if ok { 414 | intIdx, ok := field.(int) 415 | if !ok { 416 | floatIdx, ok := field.(float64) 417 | if !ok { 418 | panic(fmt.Errorf("syntax error: array index must be number, but was %s", typeOf(field))) 419 | } 420 | intIdx = int(floatIdx) 421 | if float64(intIdx) != floatIdx { 422 | panic(fmt.Errorf("eval error: array index must be whole number, but was %f", floatIdx)) 423 | } 424 | } 425 | 426 | if intIdx < 0 || intIdx >= len(arrVar) { 427 | panic(fmt.Errorf("var error: array index %d is out of range [%d, %d]", intIdx, 0, len(arrVar))) 428 | } 429 | return arrVar[intIdx] 430 | } 431 | 432 | panic(fmt.Errorf("syntax error: cannot access fields on type %s", typeOf(s))) 433 | } 434 | 435 | func slice(v interface{}, from, to interface{}) interface{} { 436 | str, isStr := v.(string) 437 | arr, isArr := v.([]interface{}) 438 | 439 | if !isStr && !isArr { 440 | panic(fmt.Errorf("syntax error: slicing requires an array or string, but was %s", typeOf(v))) 441 | } 442 | 443 | var fromInt, toInt int 444 | if from == nil { 445 | fromInt = 0 446 | } else { 447 | fromInt = asInteger(from) 448 | } 449 | 450 | if to == nil && isStr { 451 | toInt = len(str) 452 | } else if to == nil && isArr { 453 | toInt = len(arr) 454 | } else { 455 | toInt = asInteger(to) 456 | } 457 | 458 | if fromInt < 0 { 459 | panic(fmt.Errorf("range error: start-index %d is negative", fromInt)) 460 | } 461 | 462 | if isStr { 463 | if toInt < 0 || toInt > len(str) { 464 | panic(fmt.Errorf("range error: end-index %d is out of range [0, %d]", toInt, len(str))) 465 | } 466 | if fromInt > toInt { 467 | panic(fmt.Errorf("range error: start-index %d is greater than end-index %d", fromInt, toInt)) 468 | } 469 | return str[fromInt:toInt] 470 | } 471 | 472 | if toInt < 0 || toInt > len(arr) { 473 | panic(fmt.Errorf("range error: end-index %d is out of range [0, %d]", toInt, len(arr))) 474 | } 475 | if fromInt > toInt { 476 | panic(fmt.Errorf("range error: start-index %d is greater than end-index %d", fromInt, toInt)) 477 | } 478 | return arr[fromInt:toInt] 479 | } 480 | 481 | func arrayContains(arr interface{}, val interface{}) bool { 482 | a, ok := arr.([]interface{}) 483 | if !ok { 484 | panic(fmt.Errorf("syntax error: in-operator requires array, but was %s", typeOf(arr))) 485 | } 486 | 487 | for _, v := range a { 488 | if deepEqual(v, val) { 489 | return true 490 | } 491 | } 492 | return false 493 | } 494 | 495 | func callFunction(functions map[string]ExpressionFunction, name string, args []interface{}) interface{} { 496 | f, ok := functions[name] 497 | if !ok { 498 | panic(fmt.Errorf("syntax error: no such function %q", name)) 499 | } 500 | 501 | res, err := callAndRecover(f, args) 502 | if err != nil { 503 | panic(fmt.Errorf("function error: %q - %w", name, err)) 504 | } 505 | return res 506 | } 507 | 508 | func callAndRecover(f ExpressionFunction, args []interface{}) (_ interface{}, retErr error) { 509 | defer func() { 510 | r := recover() 511 | if r == nil { 512 | return 513 | } 514 | 515 | _, ok := r.(runtime.Error) 516 | if ok { 517 | panic(r) 518 | } 519 | 520 | if err, ok := r.(error); ok { 521 | retErr = err 522 | } else { 523 | retErr = fmt.Errorf("panic: %v", r) 524 | } 525 | }() 526 | return f(args...) 527 | } 528 | -------------------------------------------------------------------------------- /rule.go: -------------------------------------------------------------------------------- 1 | package gorule 2 | 3 | type Rule struct { 4 | name string 5 | condition string 6 | action func(interface{}) (interface{}, error) 7 | } 8 | 9 | // NewRule creates rule with trigger condition and action function to be 10 | // executed when the condition is met. 11 | func NewRule(name, condition string, action func(interface{}) (interface{}, error)) *Rule { 12 | return &Rule{name: name, condition: condition, action: action} 13 | } 14 | 15 | // Name returns the name of rule. 16 | func (r *Rule) Name() string { 17 | return r.name 18 | } 19 | 20 | // Execute will execute action function with input. 21 | func (r *Rule) Execute(input interface{}) (interface{}, error) { 22 | return r.action(input) 23 | } 24 | -------------------------------------------------------------------------------- /rule_test.go: -------------------------------------------------------------------------------- 1 | package gorule 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestRule_Execute(t *testing.T) { 10 | type fields struct { 11 | name string 12 | condition string 13 | action func(interface{}) (interface{}, error) 14 | } 15 | type args struct { 16 | input interface{} 17 | } 18 | tests := []struct { 19 | name string 20 | fields fields 21 | args args 22 | want interface{} 23 | wantErr bool 24 | }{ 25 | { 26 | name: "happy path", 27 | fields: fields{ 28 | name: "test_rule", 29 | condition: "1 = 1", 30 | action: func(i interface{}) (interface{}, error) { 31 | return nil, errors.New("failed") 32 | }, 33 | }, 34 | args: args{input: "input"}, 35 | want: nil, 36 | wantErr: true, 37 | }, 38 | } 39 | for _, tt := range tests { 40 | t.Run(tt.name, func(t *testing.T) { 41 | r := &Rule{ 42 | name: tt.fields.name, 43 | condition: tt.fields.condition, 44 | action: tt.fields.action, 45 | } 46 | got, err := r.Execute(tt.args.input) 47 | if (err != nil) != tt.wantErr { 48 | t.Errorf("Execute() error = %v, wantErr %v", err, tt.wantErr) 49 | return 50 | } 51 | if !reflect.DeepEqual(got, tt.want) { 52 | t.Errorf("Execute() got = %v, want %v", got, tt.want) 53 | } 54 | }) 55 | } 56 | } 57 | --------------------------------------------------------------------------------