Skip to content

Commit

Permalink
policy: split out the parsing for readability
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelMure committed Sep 1, 2024
1 parent bd7ff3d commit 431a9f0
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 154 deletions.
160 changes: 160 additions & 0 deletions capability/policy/selector/parsing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package selector

import (
"fmt"
"strconv"
"strings"
)

func Parse(str string) (Selector, error) {
if len(str) == 0 {
return nil, newParseError("empty selector", str, 0, "")
}
if string(str[0]) != "." {
return nil, newParseError("selector must start with identity segment '.'", str, 0, string(str[0]))
}

col := 0
var sel Selector
for _, tok := range tokenize(str) {
seg := tok
opt := strings.HasSuffix(tok, "?")
if opt {
seg = tok[0 : len(tok)-1]
}
switch seg {
case ".":
if len(sel) > 0 && sel[len(sel)-1].Identity() {
return nil, newParseError("selector contains unsupported recursive descent segment: '..'", str, col, tok)
}
sel = append(sel, Identity)
case "[]":
sel = append(sel, segment{tok, false, opt, true, nil, "", 0})
default:
if strings.HasPrefix(seg, "[") && strings.HasSuffix(seg, "]") {
lookup := seg[1 : len(seg)-1]

if indexRegex.MatchString(lookup) { // index
idx, err := strconv.Atoi(lookup)
if err != nil {
return nil, newParseError("invalid index", str, col, tok)
}
sel = append(sel, segment{str: tok, optional: opt, index: idx})
} else if strings.HasPrefix(lookup, "\"") && strings.HasSuffix(lookup, "\"") { // explicit field
sel = append(sel, segment{str: tok, optional: opt, field: lookup[1 : len(lookup)-1]})
} else if sliceRegex.MatchString(lookup) { // slice [3:5] or [:5] or [3:]
var rng []int
splt := strings.Split(lookup, ":")
if splt[0] == "" {
rng = append(rng, 0)
} else {
i, err := strconv.Atoi(splt[0])
if err != nil {
return nil, newParseError("invalid slice index", str, col, tok)
}
rng = append(rng, i)
}
if splt[1] != "" {
i, err := strconv.Atoi(splt[1])
if err != nil {
return nil, newParseError("invalid slice index", str, col, tok)
}
rng = append(rng, i)
}
sel = append(sel, segment{str: tok, optional: opt, slice: rng})
} else {
return nil, newParseError(fmt.Sprintf("invalid segment: %s", seg), str, col, tok)
}
} else if fieldRegex.MatchString(seg) {
sel = append(sel, segment{str: tok, optional: opt, field: seg[1:]})
} else {
return nil, newParseError(fmt.Sprintf("invalid segment: %s", seg), str, col, tok)
}
}
col += len(tok)
}
return sel, nil
}

func MustParse(sel string) Selector {
s, err := Parse(sel)
if err != nil {
panic(err)
}
return s
}

func tokenize(str string) []string {
var toks []string
col := 0
ofs := 0
ctx := ""

for col < len(str) {
char := string(str[col])

if char == "\"" && string(str[col-1]) != "\\" {
col++
if ctx == "\"" {
ctx = ""
} else {
ctx = "\""
}
continue
}

if ctx == "\"" {
col++
continue
}

if char == "." || char == "[" {
if ofs < col {
toks = append(toks, str[ofs:col])
}
ofs = col
}
col++
}

if ofs < col && ctx != "\"" {
toks = append(toks, str[ofs:col])
}

return toks
}

type parseerr struct {
msg string
src string
col int
tok string
}

func (p parseerr) Name() string {
return "ParseError"
}

func (p parseerr) Message() string {
return p.msg
}

func (p parseerr) Column() int {
return p.col
}

func (p parseerr) Error() string {
return p.msg
}

func (p parseerr) Source() string {
return p.src
}

func (p parseerr) Token() string {
return p.tok
}

func newParseError(message string, source string, column int, token string) error {
return parseerr{message, source, column, token}
}
154 changes: 0 additions & 154 deletions capability/policy/selector/selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package selector
import (
"fmt"
"regexp"
"strconv"
"strings"

"github.com/ipld/go-ipld-prime"
Expand Down Expand Up @@ -76,159 +75,6 @@ func (s segment) Index() int {
return s.index
}

func Parse(str string) (Selector, error) {
if len(str) == 0 {
return nil, newParseError("empty selector", str, 0, "")
}
if string(str[0]) != "." {
return nil, newParseError("selector must start with identity segment '.'", str, 0, string(str[0]))
}

col := 0
var sel Selector
for _, tok := range tokenize(str) {
seg := tok
opt := strings.HasSuffix(tok, "?")
if opt {
seg = tok[0 : len(tok)-1]
}
switch seg {
case ".":
if len(sel) > 0 && sel[len(sel)-1].Identity() {
return nil, newParseError("selector contains unsupported recursive descent segment: '..'", str, col, tok)
}
sel = append(sel, Identity)
case "[]":
sel = append(sel, segment{tok, false, opt, true, nil, "", 0})
default:
if strings.HasPrefix(seg, "[") && strings.HasSuffix(seg, "]") {
lookup := seg[1 : len(seg)-1]

if indexRegex.MatchString(lookup) { // index
idx, err := strconv.Atoi(lookup)
if err != nil {
return nil, newParseError("invalid index", str, col, tok)
}
sel = append(sel, segment{str: tok, optional: opt, index: idx})
} else if strings.HasPrefix(lookup, "\"") && strings.HasSuffix(lookup, "\"") { // explicit field
sel = append(sel, segment{str: tok, optional: opt, field: lookup[1 : len(lookup)-1]})
} else if sliceRegex.MatchString(lookup) { // slice [3:5] or [:5] or [3:]
var rng []int
splt := strings.Split(lookup, ":")
if splt[0] == "" {
rng = append(rng, 0)
} else {
i, err := strconv.Atoi(splt[0])
if err != nil {
return nil, newParseError("invalid slice index", str, col, tok)
}
rng = append(rng, i)
}
if splt[1] != "" {
i, err := strconv.Atoi(splt[1])
if err != nil {
return nil, newParseError("invalid slice index", str, col, tok)
}
rng = append(rng, i)
}
sel = append(sel, segment{str: tok, optional: opt, slice: rng})
} else {
return nil, newParseError(fmt.Sprintf("invalid segment: %s", seg), str, col, tok)
}
} else if fieldRegex.MatchString(seg) {
sel = append(sel, segment{str: tok, optional: opt, field: seg[1:]})
} else {
return nil, newParseError(fmt.Sprintf("invalid segment: %s", seg), str, col, tok)
}
}
col += len(tok)
}
return sel, nil
}

func tokenize(str string) []string {
var toks []string
col := 0
ofs := 0
ctx := ""

for col < len(str) {
char := string(str[col])

if char == "\"" && string(str[col-1]) != "\\" {
col++
if ctx == "\"" {
ctx = ""
} else {
ctx = "\""
}
continue
}

if ctx == "\"" {
col++
continue
}

if char == "." || char == "[" {
if ofs < col {
toks = append(toks, str[ofs:col])
}
ofs = col
}
col++
}

if ofs < col && ctx != "\"" {
toks = append(toks, str[ofs:col])
}

return toks
}

type parseerr struct {
msg string
src string
col int
tok string
}

func (p parseerr) Name() string {
return "ParseError"
}

func (p parseerr) Message() string {
return p.msg
}

func (p parseerr) Column() int {
return p.col
}

func (p parseerr) Error() string {
return p.msg
}

func (p parseerr) Source() string {
return p.src
}

func (p parseerr) Token() string {
return p.tok
}

func newParseError(message string, source string, column int, token string) error {
return parseerr{message, source, column, token}
}

func MustParse(sel string) Selector {
s, err := Parse(sel)
if err != nil {
panic(err)
}
return s
}

// Select uses a selector to extract an IPLD node or set of nodes from the
// passed subject node.
func Select(sel Selector, subject ipld.Node) (ipld.Node, []ipld.Node, error) {
Expand Down

0 comments on commit 431a9f0

Please sign in to comment.