Skip to content

Commit

Permalink
feat: add parser
Browse files Browse the repository at this point in the history
  • Loading branch information
jimlambrt committed Aug 7, 2023
1 parent 6f78ed7 commit ce29a26
Show file tree
Hide file tree
Showing 8 changed files with 677 additions and 0 deletions.
5 changes: 5 additions & 0 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,8 @@ func panicIfNil(a any, caller, missing string) {
panic(fmt.Sprintf("%s: missing %s", caller, missing))
}
}

func pointer[T any](input T) *T {
ret := input
return &ret
}
12 changes: 12 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,17 @@ package mql
import "errors"

var (
ErrInvalidParameter = errors.New("invalid parameter")
ErrInvalidNotEqual = errors.New(`invalid "!=" token`)
ErrMissingExpr = errors.New("missing expression")
ErrUnexpectedExpr = errors.New("unexpected expression")
ErrUnexpectedClosingParen = errors.New("unexpected closing paren")
ErrMissingClosingParen = errors.New("missing closing paren")
ErrUnexpectedLogicalOp = errors.New("unexpected logical operator")
ErrUnexpectedToken = errors.New("unexpected token")
ErrInvalidComparisonOp = errors.New("invalid comparison operator")
ErrMissingComparisonOp = errors.New("missing comparison operator")
ErrInvalidLogicalOp = errors.New("invalid logical operator")
ErrMissingLogicalOp = errors.New("missing logical operator")
ErrMissingRightSideExpr = errors.New("logical operator without a right side expr")
)
117 changes: 117 additions & 0 deletions expr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright (c) HashiCorp, Inc.

package mql

import (
"fmt"
)

type exprType int

const (
comparisonExprType exprType = iota
logicalExprType
)

var exprTypeToString = map[exprType]string{
comparisonExprType: "Comparison Expression",
logicalExprType: "Logical Expression",
}

type expr interface {
Type() exprType
}

type comparisonOp string

const (
greaterThanOp comparisonOp = ">"
greaterThanOrEqualOp = ">="
lessThanOp = "<"
lessThanOrEqualOp = "<="
equalOp = "="
notEqualOp = "!="
containsOp = "%"
)

func newComparisonOp(s string) (comparisonOp, error) {
const op = "newComparisonOp"
switch s {
case
string(greaterThanOp),
string(greaterThanOrEqualOp),
string(lessThanOp),
string(lessThanOrEqualOp),
string(equalOp),
string(notEqualOp),
string(containsOp):
return comparisonOp(s), nil
default:
return "", fmt.Errorf("%s: %w %q", op, ErrInvalidComparisonOp, s)
}
}

type comparisonExpr struct {
column string
comparisonOp comparisonOp
value *string
}

func (e *comparisonExpr) Type() exprType {
return comparisonExprType
}

type logicalOp string

const (
andOp logicalOp = "and"
orOp = "or"
)

func newLogicalOp(s string) (logicalOp, error) {
const op = "newLogicalOp"
switch s {
case
string(andOp),
string(orOp):
return logicalOp(s), nil
default:
return "", fmt.Errorf("%s: %w %q", op, ErrInvalidLogicalOp, s)
}
}

type logicalExpr struct {
leftExpr expr
logicalOp logicalOp
rightExpr expr
}

func (c *logicalExpr) Type() exprType {
return logicalExprType
}

// root will return the root of the expr tree
func root(lExpr *logicalExpr, raw string) (expr, error) {
const op = "mql.root"
switch {
// intentionally not checking raw, since can be an empty string
case lExpr == nil:
return nil, fmt.Errorf("%s: %w (missing expression)", op, ErrInvalidParameter)
}
logicalOp := lExpr.logicalOp
if logicalOp != "" && lExpr.rightExpr == nil {
return nil, fmt.Errorf("%s: %w in: %q", op, ErrMissingRightSideExpr, raw)
}

for lExpr.logicalOp == "" {
switch {
case lExpr.leftExpr == nil:
return nil, fmt.Errorf("%s: %w nil in: %q", op, ErrMissingExpr, raw)
case lExpr.leftExpr.Type() == comparisonExprType:
return lExpr.leftExpr, nil
default:
lExpr = lExpr.leftExpr.(*logicalExpr)
}
}
return lExpr, nil
}
32 changes: 32 additions & 0 deletions expr_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package mql

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// Test_root will focus on error conditions
func Test_root(t *testing.T) {
t.Parallel()
t.Run("missing-expr", func(t *testing.T) {
e, err := root(nil, "raw")
require.Error(t, err)
assert.Empty(t, e)
assert.ErrorIs(t, err, ErrInvalidParameter)
assert.ErrorContains(t, err, "invalid parameter (missing expression)")
})
t.Run("missing-left-expr", func(t *testing.T) {
e, err := root(&logicalExpr{
leftExpr: nil,
logicalOp: "",
rightExpr: &comparisonExpr{},
}, "raw")
require.Error(t, err)
assert.Empty(t, e)
assert.ErrorIs(t, err, ErrMissingExpr)
assert.ErrorContains(t, err, "missing expression nil in: \"raw\"")
})

}
34 changes: 34 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) HashiCorp, Inc.

package mql

type options struct {
witSkipWhitespace bool
}

// Option - how options are passed as args
type Option func(*options) error

func getDefaultOptions() options {
return options{}
}

func getOpts(opt ...Option) (options, error) {
opts := getDefaultOptions()

for _, o := range opt {
if err := o(&opts); err != nil {
return opts, err
}
}
return opts, nil
}

// withSkipWhitespace provides an option to request that whitespace be skipped
func withSkipWhitespace() Option {
const op = "mql.WithSkipWhitespace"
return func(o *options) error {
o.witSkipWhitespace = true
return nil
}
}
Loading

0 comments on commit ce29a26

Please sign in to comment.