Skip to content

Commit

Permalink
streamline hint parser
Browse files Browse the repository at this point in the history
  • Loading branch information
mandelsoft committed Dec 29, 2024
1 parent f6efc75 commit c1a8ca2
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 144 deletions.
146 changes: 2 additions & 144 deletions api/ocm/refhints/hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ func (h DefaultReferenceHint) GetReference() string {
return h[HINT_REFERENCE]
}

// Serialize see ParseHints for serialization rules.
func (h DefaultReferenceHint) Serialize(implicit ...bool) string {
if h == nil {
return ""
Expand Down Expand Up @@ -312,150 +313,7 @@ func newHint(impl bool) DefaultReferenceHint {
return h
}

// ParseHints parses a string containing servialized reference hints,
// If implicit is set to true, the implicit attribute is set
func ParseHints(v string, implicit ...bool) ReferenceHints {
var hints ReferenceHints

var prop string
var val string

var hint DefaultReferenceHint
state := -1
start := 0
mask := false
impl := general.Optional(implicit...)
for i, c := range v {
switch state {
case -1:
if c == '"' {
hint = newHint(impl)
prop = HINT_REFERENCE
start = i + 1
state = 5
} else {
state = 0
}
fallthrough
case 0: // type or plain value
if c == ':' {
state = 1
}
if c == '=' {
hint = DefaultReferenceHint{}
prop = v[start:i]
start = i + 1
state = 3
}
if c == ',' || c == ';' {
hint = DefaultReferenceHint{}
hint[HINT_REFERENCE] = v[start:i]
start = i + 1
if c == ',' {
state = 7
}
if c == ';' {
hints = append(hints, hint)
hint = nil
state = -1
}
}
case 1: // colon
if c == ':' {
hint = newHint(impl).SetProperty(HINT_TYPE, v[start:i-1])
start = i + 1
state = 7
} else {
state = 0
}
case 7: // prop start
if c == '"' {
val = ""
prop = HINT_REFERENCE
state = 5
start = i + 1
continue
}
state = 2
fallthrough
case 2: // prop
switch c {
case '=':
prop = v[start:i]
start = i + 1
state = 3
case ';':
hint[HINT_REFERENCE] = v[start:i]
hints = append(hints, hint)
hint = nil
state = -1
start = i + 1
}
case 3: // value start
if c == '"' {
val = ""
state = 5
start = i + 1
} else {
state = 4
start = i
}
case 4: // plain value
if c == ',' || c == ';' {
hint[prop] = v[start:i]
start = i + 1
if c == ';' {
hints = append(hints, hint)
hint = nil
state = -1
} else {
state = 7
}
}
case 5: // escaped value
if mask {
mask = false
} else {
if c == '\\' {
mask = true
continue
}
if c == '"' {
hint[prop] = val
state = 6
}
}
val += string(c)
case 6: // end escaped
if c == ',' {
start = i + 1
state = 2
}
if c == ';' {
hints = append(hints, hint)
hint = nil
start = i + 1
state = -1
}
}
}

switch state {
case 0, 1:
hint = newHint(impl).SetProperty(HINT_REFERENCE, v[start:])
case 2:
hint[HINT_REFERENCE] = v[start:]
case 3:
hint[prop] = ""
case 4:
hint[prop] = v[start:]
case 5:
hint[prop] = v[start:]
case 6:
}
hints = append(hints, hint)
return hints
}
////////////////////////////////////////////////////////////////////////////////#

// JoinUnique joins multiple hint lists, where the first occurrence of a
// hint type takes precedence.
Expand Down
190 changes: 190 additions & 0 deletions api/ocm/refhints/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package refhints

import (
"github.com/mandelsoft/goutils/general"
)

type state int

const (
sStart state = iota
sTypeOrValue
sColonInType
sPropStart
sProp
sValueStart
sPlainValue
sEscapedValue
sEscapedEnd
)

// ParseHints parses a string containing servialized reference hints,
// If implicit is set to true, the implicit attribute is set.
//
// In general a hint is serialized to the following string:
//
// [<*type*>`::]`<*attribute*>`=`<*value*>{`,`<*attribute*>`=`<*value*>}
//
// The type is not serialized as attribute, but as prefix separated by a ::.
// The implicit attribute is never serialized if the string is stored in an
// access specification.
// If no type is known the type part is omitted.
//
// A list of hints is serialized to
//
// <*hint*>{`;`<*hint*>}
//
// Attributes names consist of alphanumeric characters, only.
// A value (or type) may not contain a ::. If it contains a ;, , or "
// character it must be given in double quotes.
// In the double-quoted form any " or \ character has to be escaped by
// a preceding \ character.
//
// To be as compatible as possible, a single attribute hint with the attribute
// reference is serialized as naked value (as before) if there are no special
// characters enforcing a quoted form.
//
// see DefaultReferenceHint.Serialize.
func ParseHints(v string, implicit ...bool) ReferenceHints {
var hints ReferenceHints

var prop string
var val string

var hint DefaultReferenceHint
state := sStart
start := 0
mask := false
impl := general.Optional(implicit...)
for i, c := range v {
switch state {
case sStart:
if c == '"' {
hint = newHint(impl)
prop = HINT_REFERENCE
start = i + 1
state = sEscapedValue
} else {
state = sTypeOrValue
}
fallthrough
case sTypeOrValue: // type or plain value
if c == ':' {
state = sColonInType
}
if c == '=' {
hint = DefaultReferenceHint{}
prop = v[start:i]
start = i + 1
state = sValueStart
}
if c == ',' || c == ';' {
hint = DefaultReferenceHint{}
hint[HINT_REFERENCE] = v[start:i]
start = i + 1
if c == ',' {
state = sPropStart
}
if c == ';' {
hints = append(hints, hint)
hint = nil
state = sStart
}
}
case sColonInType: // colon
if c == ':' {
hint = newHint(impl).SetProperty(HINT_TYPE, v[start:i-1])
start = i + 1
state = sPropStart
} else {
state = sTypeOrValue
}
case sPropStart: // prop start
if c == '"' {
val = ""
prop = HINT_REFERENCE
state = sEscapedValue
start = i + 1
continue
}
state = sProp
fallthrough
case sProp: // prop
switch c {
case '=':
prop = v[start:i]
start = i + 1
state = sValueStart
case ';':
hint[HINT_REFERENCE] = v[start:i]
hints = append(hints, hint)
hint = nil
state = sStart
start = i + 1
}
case sValueStart: // value start
if c == '"' {
val = ""
state = sEscapedValue
start = i + 1
} else {
state = sPlainValue
start = i
}
case sPlainValue: // plain value
if c == ',' || c == ';' {
hint[prop] = v[start:i]
start = i + 1
if c == ';' {
hints = append(hints, hint)
hint = nil
state = sStart
} else {
state = sPropStart
}
}
case sEscapedValue: // escaped value
if mask {
mask = false
} else {
if c == '\\' {
mask = true
continue
}
if c == '"' {
hint[prop] = val
state = sEscapedEnd
}
}
val += string(c)
case sEscapedEnd: // end escaped
if c == ',' {
start = i + 1
state = sProp
}
if c == ';' {
hints = append(hints, hint)
hint = nil
start = i + 1
state = sStart
}
}
}

switch state {
case sTypeOrValue, sColonInType:
hint = newHint(impl).SetProperty(HINT_REFERENCE, v[start:])
case sProp:
hint[HINT_REFERENCE] = v[start:]
case sValueStart:
hint[prop] = ""
case sPlainValue:
hint[prop] = v[start:]
case sEscapedValue:
hint[prop] = v[start:]
case sPropStart:
case sEscapedEnd:
}
hints = append(hints, hint)
return hints
}

0 comments on commit c1a8ca2

Please sign in to comment.