diff --git a/attrexp.go b/attrexp.go index 4a63887..6faf06b 100644 --- a/attrexp.go +++ b/attrexp.go @@ -3,25 +3,18 @@ package filter import ( "encoding/json" "fmt" + "strconv" + "strings" + "github.com/di-wu/parser" "github.com/di-wu/parser/ast" + "github.com/scim2/filter-parser/v2/internal/grammar" - "github.com/scim2/filter-parser/v2/internal/types" - "strconv" - "strings" + typ "github.com/scim2/filter-parser/v2/internal/types" ) // ParseAttrExp parses the given raw data as an AttributeExpression. -func ParseAttrExp(raw []byte) (AttributeExpression, error) { - return parseAttrExp(raw, config{}) -} - -// ParseAttrExpNumber parses the given raw data as an AttributeExpression with json.Number. -func ParseAttrExpNumber(raw []byte) (AttributeExpression, error) { - return parseAttrExp(raw, config{useNumber: true}) -} - -func parseAttrExp(raw []byte, c config) (AttributeExpression, error) { +func ParseAttrExp(raw []byte, options ...configOption) (AttributeExpression, error) { p, err := ast.New(raw) if err != nil { return AttributeExpression{}, err @@ -33,7 +26,14 @@ func parseAttrExp(raw []byte, c config) (AttributeExpression, error) { if _, err := p.Expect(parser.EOD); err != nil { return AttributeExpression{}, err } - return c.parseAttrExp(node) + return getConfig(options...).parseAttrExp(node) +} + +// ParseAttrExpNumber parses the given raw data as an AttributeExpression with json.Number. +// +// Deprecated - use ParseAttrExpNumber WithUseNumber option +func ParseAttrExpNumber(raw []byte) (AttributeExpression, error) { + return ParseAttrExp(raw, WithUseNumber()) } func (p config) parseAttrExp(node *ast.Node) (AttributeExpression, error) { @@ -80,11 +80,16 @@ func (p config) parseAttrExp(node *ast.Node) (AttributeExpression, error) { return AttributeExpression{}, err } compareValue = value - case typ.String: + case typ.StringWithDoubleQuotes: str := node.Value str = strings.TrimPrefix(str, "\"") str = strings.TrimSuffix(str, "\"") compareValue = str + case typ.StringWithSingleQuotes: + str := node.Value + str = strings.TrimPrefix(str, "'") + str = strings.TrimSuffix(str, "'") + compareValue = str default: return AttributeExpression{}, invalidChildTypeError(typ.AttrExp, node.Type) } diff --git a/config.go b/config.go index d108ee6..597d7d0 100644 --- a/config.go +++ b/config.go @@ -5,3 +5,21 @@ type config struct { // useNumber indicates that json.Number needs to be returned instead of int/float64 values. useNumber bool } + +type configOption func(config) config + +// WithUseNumber set use number to true +func WithUseNumber() configOption { + return func(c config) config { + c.useNumber = true + return c + } +} + +func getConfig(options ...configOption) config { + c := config{} + for _, opt := range options { + c = opt(c) + } + return c +} diff --git a/filter.go b/filter.go index 180a6f1..6579015 100644 --- a/filter.go +++ b/filter.go @@ -3,18 +3,32 @@ package filter import ( "github.com/di-wu/parser" "github.com/di-wu/parser/ast" + "github.com/scim2/filter-parser/v2/internal/grammar" - "github.com/scim2/filter-parser/v2/internal/types" + typ "github.com/scim2/filter-parser/v2/internal/types" ) // ParseFilter parses the given raw data as an Expression. -func ParseFilter(raw []byte) (Expression, error) { - return parseFilter(raw, config{}) +func ParseFilter(raw []byte, options ...configOption) (Expression, error) { + p, err := ast.New(raw) + if err != nil { + return nil, err + } + node, err := grammar.Filter(p) + if err != nil { + return nil, err + } + if _, err := p.Expect(parser.EOD); err != nil { + return nil, err + } + return getConfig(options...).parseFilterOr(node) } // ParseFilterNumber parses the given raw data as an Expression with json.Number. +// +// Deprecated - use ParseFilter WithUseNumber option func ParseFilterNumber(raw []byte) (Expression, error) { - return parseFilter(raw, config{useNumber: true}) + return ParseFilter(raw, WithUseNumber()) } func parseFilter(raw []byte, c config) (Expression, error) { diff --git a/filter_test.go b/filter_test.go index 342ea59..af23a78 100644 --- a/filter_test.go +++ b/filter_test.go @@ -42,13 +42,13 @@ func ExampleParseFilter_parentheses() { } func ExampleParseFilter_valuePath() { - fmt.Println(ParseFilter([]byte("emails[type eq \"work\" and value co \"@example.com\"]"))) + fmt.Println(ParseFilter([]byte("emails[type eq \"work\" and value co '@example.com']"))) // Output: // emails[type eq "work" and value co "@example.com"] } func Example_walk() { - expression, _ := ParseFilter([]byte("emails[type eq \"work\" and value co \"@example.com\"] or ims[type eq \"xmpp\" and value co \"@foo.com\"]")) + expression, _ := ParseFilter([]byte("emails[type eq \"work\" and value co \"@example.com\"] or ims[type eq \"xmpp\" and value co '@foo.com']")) var walk func(e Expression) error walk = func(e Expression) error { switch v := e.(type) { diff --git a/internal/grammar/attrexp.go b/internal/grammar/attrexp.go index c4780c6..41244af 100644 --- a/internal/grammar/attrexp.go +++ b/internal/grammar/attrexp.go @@ -4,7 +4,8 @@ import ( "github.com/di-wu/parser" "github.com/di-wu/parser/ast" "github.com/di-wu/parser/op" - "github.com/scim2/filter-parser/v2/internal/types" + + typ "github.com/scim2/filter-parser/v2/internal/types" ) func AttrExp(p *ast.Parser) (*ast.Node, error) { @@ -69,7 +70,7 @@ func CompareOp(p *ast.Parser) (*ast.Node, error) { } func CompareValue(p *ast.Parser) (*ast.Node, error) { - return p.Expect(op.Or{False, Null, True, Number, String}) + return p.Expect(op.Or{False, Null, True, Number, String(doubleQuote), String(singleQuote)}) } func NameChar(p *ast.Parser) (*ast.Node, error) { diff --git a/internal/grammar/filter_test.go b/internal/grammar/filter_test.go index 4cc6ba1..0ae3f40 100644 --- a/internal/grammar/filter_test.go +++ b/internal/grammar/filter_test.go @@ -2,6 +2,7 @@ package grammar import ( "fmt" + "github.com/di-wu/parser/ast" ) diff --git a/internal/grammar/string.go b/internal/grammar/string.go index bd12f78..4405c4d 100644 --- a/internal/grammar/string.go +++ b/internal/grammar/string.go @@ -4,25 +4,60 @@ import ( "github.com/di-wu/parser" "github.com/di-wu/parser/ast" "github.com/di-wu/parser/op" - "github.com/scim2/filter-parser/v2/internal/types" + + typ "github.com/scim2/filter-parser/v2/internal/types" ) -func Character(p *ast.Parser) (*ast.Node, error) { - return p.Expect( - op.Or{ - Unescaped, - op.And{ - "\\", - Escaped, +type quote int + +const ( + singleQuote quote = iota + doubleQuote +) + +func (q quote) getValue() string { + switch q { + case singleQuote: + return "'" + case doubleQuote: + fallthrough + default: + return "\"" + } +} + +func (q quote) getType() int { + switch q { + case singleQuote: + return typ.StringWithSingleQuotes + case doubleQuote: + fallthrough + default: + return typ.StringWithDoubleQuotes + } +} + +func Character(q quote) func(*ast.Parser) (*ast.Node, error) { + return func(p *ast.Parser) (*ast.Node, error) { + return p.Expect( + op.Or{ + Unescaped(q), + op.And{ + "\\", + q.getValue(), + }, + op.And{ + "\\", + Escaped, + }, }, - }, - ) + ) + } } func Escaped(p *ast.Parser) (*ast.Node, error) { return p.Expect( op.Or{ - "\"", "\\", "/", 0x0062, @@ -43,28 +78,49 @@ func Escaped(p *ast.Parser) (*ast.Node, error) { ) } -func String(p *ast.Parser) (*ast.Node, error) { - return p.Expect( - ast.Capture{ - Type: typ.String, - TypeStrings: typ.Stringer, - Value: op.And{ - "\"", - op.MinZero( - Character, - ), - "\"", +func String(q quote) func(*ast.Parser) (*ast.Node, error) { + return func(p *ast.Parser) (*ast.Node, error) { + return p.Expect( + ast.Capture{ + Type: q.getType(), + TypeStrings: typ.Stringer, + Value: op.And{ + q.getValue(), + op.MinZero( + Character(q), + ), + q.getValue(), + }, }, - }, - ) + ) + } } -func Unescaped(p *ast.Parser) (*ast.Node, error) { - return p.Expect( - op.Or{ - parser.CheckRuneRange(0x0020, 0x0021), - parser.CheckRuneRange(0x0023, 0x005B), - parser.CheckRuneRange(0x005D, 0x0010FFFF), - }, - ) +func Unescaped(q quote) func(*ast.Parser) (*ast.Node, error) { + switch q { + case singleQuote: + return func(p *ast.Parser) (*ast.Node, error) { + // 0x0027 : ' + return p.Expect( + op.Or{ + parser.CheckRuneRange(0x0020, 0x0026), + parser.CheckRuneRange(0x0028, 0x0010FFFF), + }, + ) + } + case doubleQuote: + fallthrough + default: + return func(p *ast.Parser) (*ast.Node, error) { + // 0x0023 : " + // 0x005C : \ + return p.Expect( + op.Or{ + parser.CheckRuneRange(0x0020, 0x0021), + parser.CheckRuneRange(0x0023, 0x005B), + parser.CheckRuneRange(0x005D, 0x0010FFFF), + }, + ) + } + } } diff --git a/internal/grammar/string_test.go b/internal/grammar/string_test.go index c8475c1..53a97c9 100644 --- a/internal/grammar/string_test.go +++ b/internal/grammar/string_test.go @@ -2,19 +2,34 @@ package grammar import ( "fmt" + "github.com/di-wu/parser/ast" ) func ExampleString() { p, _ := ast.New([]byte("\"2819c223-7f76-453a-919d-413861904646\"")) - fmt.Println(String(p)) + fmt.Println(String(doubleQuote)(p)) // Output: // ["String","\"2819c223-7f76-453a-919d-413861904646\""] } func ExampleString_complex() { p, _ := ast.New([]byte("\"W/\\\"990-6468886345120203448\\\"\"")) - fmt.Println(String(p)) + fmt.Println(String(doubleQuote)(p)) // Output: // ["String","\"W/\\\"990-6468886345120203448\\\"\""] } + +func ExampleStringSingleQuote() { + p, _ := ast.New([]byte("'2819c223-7f76-453a-919d-413861904646'")) + fmt.Println(String(singleQuote)(p)) + // Output: + // ["String","'2819c223-7f76-453a-919d-413861904646'"] +} + +func ExampleStringSingleQuote_complex() { + p, _ := ast.New([]byte("'W/\\\"990-6468886345120203448\\\"'")) + fmt.Println(String(singleQuote)(p)) + // Output: + // ["String","'W/\\\"990-6468886345120203448\\\"'"] +} diff --git a/internal/spec/grammar.pegn b/internal/spec/grammar.pegn index d76f0a8..8430bd6 100644 --- a/internal/spec/grammar.pegn +++ b/internal/spec/grammar.pegn @@ -56,7 +56,7 @@ Digits <-- [0-9]+ Frac <-- '.' Digits Int <-- '0' / [1-9] [0-9]* -String <-- '"' Character* '"' +String <-- '"' Character* '"' / "'" Character* "'" Character <- Unescaped / '\' Escaped Unescaped <- [x20-x21] / [x23-x5B] / [x5D-x10FFFF] Escaped <- '"' diff --git a/internal/types/types.go b/internal/types/types.go index be46eb9..9cb817b 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -31,7 +31,8 @@ const ( Frac Int - String + StringWithDoubleQuotes + StringWithSingleQuotes URI ) @@ -67,7 +68,8 @@ var Stringer = []string{ "Frac", "Int", - "String", + "StringWithDoubleQuotes", + "StringWithSingleQuotes", "URI", } diff --git a/path.go b/path.go index 59e75d1..f5f7fe8 100644 --- a/path.go +++ b/path.go @@ -3,21 +3,13 @@ package filter import ( "github.com/di-wu/parser" "github.com/di-wu/parser/ast" + "github.com/scim2/filter-parser/v2/internal/grammar" - "github.com/scim2/filter-parser/v2/internal/types" + typ "github.com/scim2/filter-parser/v2/internal/types" ) // ParsePath parses the given raw data as an Path. -func ParsePath(raw []byte) (Path, error) { - return parsePath(raw, config{}) -} - -// ParsePathNumber parses the given raw data as an Path with json.Number. -func ParsePathNumber(raw []byte) (Path, error) { - return parsePath(raw, config{useNumber: true}) -} - -func parsePath(raw []byte, c config) (Path, error) { +func ParsePath(raw []byte, options ...configOption) (Path, error) { p, err := ast.New(raw) if err != nil { return Path{}, err @@ -29,7 +21,14 @@ func parsePath(raw []byte, c config) (Path, error) { if _, err := p.Expect(parser.EOD); err != nil { return Path{}, err } - return c.parsePath(node) + return getConfig(options...).parsePath(node) +} + +// ParsePathNumber parses the given raw data as an Path with json.Number. +// +// Deprecated - use ParsePath WithUseNumber option +func ParsePathNumber(raw []byte) (Path, error) { + return ParsePath(raw, WithUseNumber()) } func (p config) parsePath(node *ast.Node) (Path, error) { diff --git a/valuepath.go b/valuepath.go index 6192e73..5bcb269 100644 --- a/valuepath.go +++ b/valuepath.go @@ -3,21 +3,13 @@ package filter import ( "github.com/di-wu/parser" "github.com/di-wu/parser/ast" + "github.com/scim2/filter-parser/v2/internal/grammar" - "github.com/scim2/filter-parser/v2/internal/types" + typ "github.com/scim2/filter-parser/v2/internal/types" ) // ParseValuePath parses the given raw data as an ValuePath. -func ParseValuePath(raw []byte) (ValuePath, error) { - return parseValuePath(raw, config{}) -} - -// ParseValuePathNumber parses the given raw data as an ValuePath with json.Number. -func ParseValuePathNumber(raw []byte) (ValuePath, error) { - return parseValuePath(raw, config{useNumber: true}) -} - -func parseValuePath(raw []byte, c config) (ValuePath, error) { +func ParseValuePath(raw []byte, options ...configOption) (ValuePath, error) { p, err := ast.New(raw) if err != nil { return ValuePath{}, err @@ -29,7 +21,14 @@ func parseValuePath(raw []byte, c config) (ValuePath, error) { if _, err := p.Expect(parser.EOD); err != nil { return ValuePath{}, err } - return c.parseValuePath(node) + return getConfig(options...).parseValuePath(node) +} + +// ParseValuePathNumber parses the given raw data as an ValuePath with json.Number. +// +// Deprecated - use ParseValuePathNumber WithUseNumber option +func ParseValuePathNumber(raw []byte) (ValuePath, error) { + return ParseValuePath(raw, WithUseNumber()) } func (p config) parseValueFilter(node *ast.Node) (Expression, error) {