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). -+---+-- | |-- | +----+---+-- 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 }