230 lines
5.2 KiB
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, index)
|
|
|
|
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, index)
|
|
|
|
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
|
|
}
|