central-arch-spec-models/query/ref.go

230 lines
5.2 KiB
Go

package query
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
/*
A reference is composed of one or more matchers separated by periods.
A matcher can be composed by a single container name or by both a
container name and an element identifier.
A matcher only composed of a container name can only appear as the last
matcher of a reference.
A reference the last matcher of which is composed of both a container
name and an element identifier is a fully qualified reference.
A reference the last matcher of which is only composed of a container
returns all elements inside the container or an empty list.
A reference the last matcher of which is composed of both a container
name and an element identifier returns only the referenced element or
an empty list.
Example, given the following query tree:
(with "r:" meaning "root", "c:" container and "e:" element).
<r: />-+--<c: format>-+--<e: T1>
| |--<e: T2>
|
+--<c: instr>--+--<e: BKPT>-+--<c: binding>
Ref: "format"
Returns: [T1, T2]
Ref: "format:T1"
Returns: [T1]
Ref: "instr:BKPT"
Returns: [BKPT]
Ref: "instr:BKPT.binding"
Returns: []
Ref: "instr:BKPT.binding:base"
Returns: []
Ref: "instr.binding"
Returns: Error
=> Container-only matcher can only be used as the last matcher in a reference.
*/
type Reference string
func (ref *Reference) Parse() (*Query, error) {
var container, err = parseContainerComponent(*ref, 0)
if err != nil {
return nil, err
}
return &Query{Container: *container}, nil
}
func (ref *Reference) IsValid() bool {
var _, err = ref.Parse()
return err == nil
}
func (ref *Reference) IsFullyQualified() bool {
var query, err = ref.Parse()
if err != nil {
return false
}
return query.IsFullyQualified()
}
type RefParseError struct {
Ref Reference
Index int
Msg string
}
func (err *RefParseError) Error() string {
var error_fmt = `Parsing error at character %d in the following query reference:
"%s"
%s^
Error: %s`
return fmt.Sprintf(error_fmt, err.Index+1, err.Ref, strings.Repeat(" ", err.Index), err.Msg)
}
func isValidNameChar(r rune) bool {
return unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '-'
}
func isMatcherSeparator(r rune) bool {
return r == '.'
}
func isElementIdSeparator(r rune) bool {
return r == ':'
}
func parseContainerComponent(ref Reference, index int) (*Container, error) {
var currentName = ""
if index >= len(ref) {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: "Expected a container name but reached end of reference",
}
}
for i, w := index, 0; i < len(ref); i += w {
var runeValue, width = utf8.DecodeRuneInString(string(ref[i:]))
w = width
if isElementIdSeparator(runeValue) {
if len(currentName) > 0 {
var e, err = parseElementComponent(ref, i+w)
if err != nil {
return nil, err
}
return &Container{Name: currentName, NextElement: e}, nil
} else {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: "Expected a container name before an element identifier",
}
}
}
if isMatcherSeparator(runeValue) {
if len(currentName) > 0 {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: "Expected an element identifier before another container name",
}
} else {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: "Expected both a container name and an element identifier before another container name",
}
}
}
if isValidNameChar(runeValue) {
currentName += string(runeValue)
} else {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: fmt.Sprintf("Character '%c' is not permitted in a container name", runeValue),
}
}
}
return &Container{Name: currentName, NextElement: nil}, nil
}
func parseElementComponent(ref Reference, index int) (*Element, error) {
var currentName = ""
if index >= len(ref) {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: "Expected an element identifier but reached end of reference",
}
}
for i, w := index, 0; i < len(ref); i += w {
var runeValue, width = utf8.DecodeRuneInString(string(ref[i:]))
w = width
if isElementIdSeparator(runeValue) {
if len(currentName) > 0 {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: "Expected a container name before another element identifier",
}
} else {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: "Expected both a container name and an element identifier before another element identifier",
}
}
}
if isMatcherSeparator(runeValue) {
if len(currentName) > 0 {
var e, err = parseContainerComponent(ref, i+w)
if err != nil {
return nil, err
}
return &Element{Name: currentName, NextContainer: e}, nil
} else {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: "Expected an element identifier before a container name",
}
}
}
if isValidNameChar(runeValue) {
currentName += string(runeValue)
} else {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: fmt.Sprintf("Character '%c' is not permitted in an element identifier", runeValue),
}
}
}
return &Element{Name: currentName, NextContainer: nil}, nil
}