Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ jobs:
runs-on: ubuntu-latest
name: Build & Test
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v5
- name: Setup go
uses: actions/setup-go@v1
uses: actions/setup-go@v6
with:
go-version: "1.18"
go-version-file: go.mod
- run: go test -coverprofile=coverage.txt -covermode=atomic ./...
- uses: codecov/codecov-action@v1
- uses: codecov/codecov-action@v5
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Try it out on the [Go Playground](https://play.golang.org/p/Z0UcEBgfxu_r)! You c
import "github.com/danielgtaylor/mexpr"

// Convenience for lexing/parsing/running in one step:
result, err := mexpr.Eval("a > b", map[string]interface{}{
result, err := mexpr.Eval("a > b", map[string]any{
"a": 2,
"b": 1,
})
Comment thread
danielgtaylor marked this conversation as resolved.
Expand All @@ -42,18 +42,18 @@ result, err := mexpr.Eval("a > b", map[string]interface{}{
// omitted for brevity.
l := mexpr.NewLexer("a > b")
p := mexpr.NewParser(l)
ast, err := mexpr.Parse()
typeExamples = map[string]interface{}{
ast, err := p.Parse()
typeExamples := map[string]any{
"a": 2,
"b": 1,
}
err := mexpr.TypeCheck(ast, typeExamples)
err = mexpr.TypeCheck(ast, typeExamples)
interpreter := mexpr.NewInterpreter(ast)
result1, err := interpreter.Run(map[string]interface{}{
result1, err := interpreter.Run(map[string]any{
"a": 1,
"b": 2,
})
result2, err := interpreter.Run(map[string]interfae{}{
result2, err := interpreter.Run(map[string]any{
"a": 150,
"b": 30,
})
Expand All @@ -80,10 +80,11 @@ When running the interpreter a set of options can be passed in to change behavio

```go
// Using the top-level eval
mexpr.Eval(expression, inputObj, StrictMode)
result, err := mexpr.Eval(expression, inputObj, mexpr.StrictMode)

// Using an interpreter instance
interpreter.Run(inputObj, StrictMode)
interpreter := mexpr.NewInterpreter(ast, mexpr.StrictMode)
result, err = interpreter.Run(inputObj)
```

## Syntax
Expand Down Expand Up @@ -189,7 +190,7 @@ Indexes are zero-based. Slice indexes are optional and are _inclusive_. `foo[1:2

Any value concatenated with a string will result in a string. For example `"id" + 1` will result in `"id1"`.

There is no distinction between strings, bytes, or runes. Everything is treated as a string.
String length, indexing, and slicing are Unicode-aware and operate on runes rather than raw bytes.

#### Date Comparisons

Expand Down
60 changes: 49 additions & 11 deletions conversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import (
"fmt"
"reflect"
"time"
"unicode/utf8"
)

func isNumber(v interface{}) bool {
func isNumber(v any) bool {
switch v.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
return true
Expand All @@ -22,7 +23,7 @@ func isNumber(v interface{}) bool {
return false
}

func toNumber(ast *Node, v interface{}) (float64, Error) {
func toNumber(ast *Node, v any) (float64, Error) {
switch n := v.(type) {
case float64:
return n, nil
Expand Down Expand Up @@ -76,7 +77,7 @@ func toNumber(ast *Node, v interface{}) (float64, Error) {
return 0, NewError(ast.Offset, ast.Length, "unable to convert to number: %v", v)
}

func isString(v interface{}) bool {
func isString(v any) bool {
switch v.(type) {
case string, rune, byte, []byte:
return true
Expand All @@ -86,7 +87,7 @@ func isString(v interface{}) bool {
return false
}

func toString(v interface{}) string {
func toString(v any) string {
switch s := v.(type) {
case string:
return s
Expand All @@ -102,9 +103,46 @@ func toString(v interface{}) string {
return fmt.Sprintf("%v", v)
}

func stringLength(v string) int {
return utf8.RuneCountInString(v)
}

func runeRangeToByteOffsets(v string, startIdx, endIdx int) (int, int) {
if startIdx <= 0 {
startIdx = 0
}
if endIdx < startIdx {
endIdx = startIdx
}

offset := 0
for i := 0; i < startIdx && offset < len(v); i++ {
_, size := utf8.DecodeRuneInString(v[offset:])
offset += size
}

startOffset := offset
for i := startIdx; i < endIdx && offset < len(v); i++ {
_, size := utf8.DecodeRuneInString(v[offset:])
offset += size
}

return startOffset, offset
}

func stringIndex(v string, idx int) string {
start, end := runeRangeToByteOffsets(v, idx, idx+1)
return v[start:end]
}

func stringSlice(v string, start, end int) string {
from, to := runeRangeToByteOffsets(v, start, end+1)
return v[from:to]
}

// toTime converts a string value into a time.Time if possible, otherwise
// returns a zero time.
func toTime(v interface{}) time.Time {
func toTime(v any) time.Time {
vStr := toString(v)
if t, err := time.Parse(time.RFC3339, vStr); err == nil {
return t
Expand All @@ -118,14 +156,14 @@ func toTime(v interface{}) time.Time {
return time.Time{}
}

func isSlice(v interface{}) bool {
if _, ok := v.([]interface{}); ok {
func isSlice(v any) bool {
if _, ok := v.([]any); ok {
return true
}
return false
}

func toBool(v interface{}) bool {
func toBool(v any) bool {
switch n := v.(type) {
case bool:
return n
Expand Down Expand Up @@ -157,9 +195,9 @@ func toBool(v interface{}) bool {
return len(n) > 0
case []byte:
return len(n) > 0
case []interface{}:
case []any:
return len(n) > 0
case map[string]interface{}:
case map[string]any:
return len(n) > 0
case map[any]any:
return len(n) > 0
Expand All @@ -170,7 +208,7 @@ func toBool(v interface{}) bool {
// normalize an input for equality checks. All numbers -> float64, []byte to
// string, etc. Since `rune` is an alias for int32, we can't differentiate it
// for comparison with strings.
func normalize(v interface{}) interface{} {
func normalize(v any) any {
switch n := v.(type) {
case int:
return float64(n)
Expand Down
2 changes: 1 addition & 1 deletion error.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (e *exprErr) Pretty(source string) string {
}

// NewError creates a new error at a specific location.
func NewError(offset uint16, length uint8, format string, a ...interface{}) Error {
func NewError(offset uint16, length uint8, format string, a ...any) Error {
return &exprErr{
offset: offset,
length: length,
Expand Down
Loading
Loading