Categorygithub.com/mgudov/logic-expression-parser
modulepackage
0.1.4
Repository: https://github.com/mgudov/logic-expression-parser.git
Documentation: pkg.go.dev

# README

logic-expression-parser

Build Status Go Report Card Codecov

This library provide generic boolean expression parser to go structures.

Installation

$ go get -u github.com/mgudov/logic-expression-parser

(optional) Run unit tests

$ make test

(optional) Run benchmarks

$ make bench

Examples

package main

import (
	"github.com/davecgh/go-spew/spew"
	lep "github.com/mgudov/logic-expression-parser"
)

func main() {
	expression := `a=false && b>=c && (d<1000 || e in [1,2,3])`

	result, err := lep.ParseExpression(expression)
	if err != nil {
		panic(err)
	}

	dump := spew.NewDefaultConfig()
	dump.DisablePointerAddresses = true
	dump.DisableMethods = true
	dump.Dump(result)
}

This library would parse the expression and return the following struct:

(*lep.AndX)({
  Conjuncts: ([]lep.Expression) (len=3 cap=4) {
    (*lep.EqualsX)({
      Param: (*lep.ParamX)({
        Name: (string) (len=1) "a"
      }),
      Value: (*lep.BooleanX)({
        Val: (bool) false
      })
    }),
    (*lep.GreaterThanEqualX)({
      Param: (*lep.ParamX)({
        Name: (string) (len=1) "b"
      }),
      Value: (*lep.ParamX)({
        Name: (string) (len=1) "c"
      })
    }),
    (*lep.OrX)({
      Disjunctions: ([]lep.Expression) (len=2 cap=2) {
        (*lep.LessThanX)({
          Param: (*lep.ParamX)({
            Name: (string) (len=1) "d"
          }),
          Value: (*lep.IntegerX)({
            Val: (int64) 1000
          })
        }),
        (*lep.InSliceX)({
          Param: (*lep.ParamX)({
            Name: (string) (len=1) "e"
          }),
          Slice: (*lep.SliceX)({
            Values: ([]lep.Value) (len=3 cap=4) {
              (*lep.IntegerX)({
                Val: (int64) 1
              }),
              (*lep.IntegerX)({
                Val: (int64) 2
              }),
              (*lep.IntegerX)({
                Val: (int64) 3
              })
            }
          })
        })
      }
    })
  }
})

Use can also create expression string from code:

package main

import (
	"fmt"
	lep "github.com/mgudov/logic-expression-parser"
)

func main() {
	expression := lep.And(
		lep.Equals(lep.Param("a"), lep.Boolean(false)),
		lep.GreaterThanEqual(lep.Param("b"), lep.Param("c")),
		lep.Or(
			lep.LessThan(lep.Param("d"), lep.Integer(1000)),
			lep.InSlice(
				lep.Param("e"),
				lep.Slice(lep.Integer(1), lep.Integer(2), lep.Integer(3)),
			),
		),
	)

	fmt.Println(expression.String())
}
a=false && b>=c && (d<1000 || e in [1,2,3])

Real life examples

Create SQL query from expression string
package main

import (
	"fmt"
	"github.com/davecgh/go-spew/spew"
	sb "github.com/huandu/go-sqlbuilder"
	lep "github.com/mgudov/logic-expression-parser"
)

func traverse(sql *sb.SelectBuilder, expr lep.Expression) (string, error) {
	switch e := expr.(type) {
	default:
		return "", fmt.Errorf("not implemented: %T", e)
	case *lep.OrX:
		var args []string
		for _, disjunction := range e.Disjunctions {
			arg, err := traverse(sql, disjunction)
			if err != nil {
				return "", err
			}
			args = append(args, arg)
		}
		return sql.Or(args...), nil
	case *lep.AndX:
		var args []string
		for _, conjunct := range e.Conjuncts {
			arg, err := traverse(sql, conjunct)
			if err != nil {
				return "", err
			}
			args = append(args, arg)
		}
		return sql.And(args...), nil
	case *lep.EqualsX:
		value := e.Value.Value()
		if value == nil {
			return sql.IsNotNull(e.Param.String()), nil
		}
		return sql.Equal(e.Param.String(), value), nil
	case *lep.NotEqualsX:
		value := e.Value.Value()
		if value == nil {
			return sql.IsNotNull(e.Param.String()), nil
		}
		return sql.NotEqual(e.Param.String(), value), nil
	case *lep.GreaterThanX:
		return sql.GreaterThan(e.Param.String(), e.Value.Value()), nil
	case *lep.InSliceX:
		var items []interface{}
		for _, value := range e.Slice.Values {
			items = append(items, value.Value())
		}
		return sql.In(e.Param.String(), items...), nil

		// TODO: other cases
	}
}

func main() {
	query := `active=true && email!=null && (last_login>dt:"2010-01-01" || role in ["client","customer"])`

	expr, err := lep.ParseExpression(query)
	if err != nil {
		panic(err)
	}

	sql := sb.Select("*").From("users")
	where, err := traverse(sql, expr)
	if err != nil {
		panic(err)
	}
	sql.Where(where)

	spew.Dump(sql.Build())
}
(string) (len=99) "SELECT * FROM users WHERE (active = ? AND email IS NOT NULL AND (last_login > ? OR role IN (?, ?)))"
([]interface {}) (len=4 cap=4) {
 (bool) true,
 (time.Time) 2010-01-01 00:00:00 +0000 UTC,
 (string) (len=6) "client",
 (string) (len=8) "customer"
}

Operators and types

  • Comparators: = != > >= < <= (left - param, right - param or value)
  • Logical operations: || && (left, right - any statements)
  • Numeric constants: integer 64-bit (12345678), float 64-bit with floating point (12345.678)
  • String constants (double quotes: "foo bar", "foo "bar"")
  • String operations: starts_with, ends_with (left - param, right - param or string)
  • Regexp operations: =~ (match regexp a =~ /[a-z]+/), !~ (not match b !~ /[0-9]+/)
  • Date constants (double quotes after dt:): dt:"2020-03-04 10:20:30" (for parsing datetime used dateparse)
  • Arrays (any values separated by , within square bracket: [1,2,"foo",dt:"1999-09-09"])
  • Array operations: in not_in (a in [1,2,3])
  • Boolean constants: true false
  • Null constant: null

Benchmarks

Here are the results output from a benchmark run on a Macbook Pro 2018:

go test -benchmem -bench=.
goos: darwin
goarch: amd64
pkg: github.com/mgudov/logic-expression-parser
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
BenchmarkSmallQuery-16                     26547             45293 ns/op           19353 B/op        332 allocs/op
BenchmarkMediumQuery-16                    10000            106334 ns/op           42931 B/op        807 allocs/op
BenchmarkLargeQuery-16                      3268            331438 ns/op          114500 B/op       2385 allocs/op
BenchmarkSmallQueryWithMemo-16             14696             79791 ns/op           82590 B/op        276 allocs/op
BenchmarkMediumQueryWithMemo-16             4924            246504 ns/op          257072 B/op        746 allocs/op
BenchmarkLargeQueryWithMemo-16              2071            590092 ns/op          594584 B/op       1627 allocs/op
PASS
ok      github.com/mgudov/logic-expression-parser       8.744s

Used Libraries

For parsing string the pigeon parser generator is used (Licensed under BSD 3-Clause).

# Functions

AllowInvalidUTF8 creates an Option to allow invalid UTF-8 bytes.
No description provided by the author
No description provided by the author
No description provided by the author
Debug creates an Option to set the debug flag to b.
No description provided by the author
Entrypoint creates an Option to set the rule name to use as entrypoint.
No description provided by the author
No description provided by the author
GlobalStore creates an Option to set a key to a certain value in the globalStore.
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
InitState creates an Option to set a key to a certain value in the global "state" store.
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
MaxExpressions creates an Option to stop parsing after the provided number of expressions have been parsed, if the value is 0 then the parser will parse for as many steps as needed (possibly an infinite number).
Memoize creates an Option to set the memoize flag to b.
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
Parse parses the data from b using filename as information in the error messages.
No description provided by the author
ParseFile parses the file identified by filename.
ParseReader parses the data from r using filename as information in the error messages.
Recover creates an Option to set the recover flag to b.
No description provided by the author
No description provided by the author
No description provided by the author
Statistics adds a user provided Stats struct to the parser to allow the user to process the results after the parsing has finished.
No description provided by the author

# Structs

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
Stats stores some statistics, gathered during parsing.
No description provided by the author

# Interfaces

Cloner is implemented by any value that has a Clone method, which returns a copy of the value.
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author

# Type aliases

Option is a function that can set an option on the parser.