1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
|
package query
import (
"fmt"
"regexp"
"strings"
"unicode"
"unicode/utf8"
"github.com/aptly-dev/aptly/deb"
)
type parser struct {
name string // used only for error reports.
input *lexer // the input lexer
err error // error stored while parsing
}
func parse(input *lexer) (deb.PackageQuery, error) {
p := &parser{
name: input.name,
input: input,
}
query := p.parse()
if p.err != nil {
return nil, p.err
}
return query, nil
}
// Entry into parser
func (p *parser) parse() deb.PackageQuery {
defer func() {
if r := recover(); r != nil {
p.err = fmt.Errorf("parsing failed: %s", r)
}
}()
q := p.Query()
if p.input.Current().typ != itemEOF {
panic(fmt.Sprintf("unexpected token %s: expecting end of query", p.input.Current()))
}
return q
}
// Query := A | A '|' Query
func (p *parser) Query() deb.PackageQuery {
q := p.A()
if p.input.Current().typ == itemOr {
p.input.Consume()
return &deb.OrQuery{L: q, R: p.Query()}
}
return q
}
// A := B | B ',' A
func (p *parser) A() deb.PackageQuery {
q := p.B()
if p.input.Current().typ == itemAnd {
p.input.Consume()
return &deb.AndQuery{L: q, R: p.A()}
}
return q
}
// B := C | '!' B
func (p *parser) B() deb.PackageQuery {
if p.input.Current().typ == itemNot {
p.input.Consume()
return &deb.NotQuery{Q: p.B()}
}
return p.C()
}
// C := '(' Query ')' | D
func (p *parser) C() deb.PackageQuery {
if p.input.Current().typ == itemLeftParen {
p.input.Consume()
q := p.Query()
if p.input.Current().typ != itemRightParen {
panic(fmt.Sprintf("unexpected token %s: expecting ')'", p.input.Current()))
}
p.input.Consume()
return q
}
return p.D()
}
func operatorToRelation(operator itemType) int {
switch operator {
case 0:
return deb.VersionDontCare
case itemLt:
return deb.VersionLess
case itemLtEq:
return deb.VersionLessOrEqual
case itemGt:
return deb.VersionGreater
case itemGtEq:
return deb.VersionGreaterOrEqual
case itemEq:
return deb.VersionEqual
case itemPatMatch:
return deb.VersionPatternMatch
case itemRegexp:
return deb.VersionRegexp
}
panic("unable to map token to relation")
}
// isPackageRef returns ok true if field has format pkg_version_arch
func parsePackageRef(query string) (pkg, version, arch string, ok bool) {
i := strings.Index(query, "_")
if i != -1 {
pkg, query = query[:i], query[i+1:]
j := strings.LastIndex(query, "_")
if j != -1 {
version, arch = query[:j], query[j+1:]
ok = true
}
}
return
}
// D := <field> <condition> <arch_condition> | <package>_<version>_<arch>
// field := <package-name> | <field> | $special_field
func (p *parser) D() deb.PackageQuery {
if p.input.Current().typ != itemString {
panic(fmt.Sprintf("unexpected token %s: expecting field or package name", p.input.Current()))
}
field := p.input.Current().val
p.input.Consume()
operator, value := p.Condition()
r, _ := utf8.DecodeRuneInString(field)
if strings.HasPrefix(field, "$") || (unicode.IsUpper(r) && !strings.ContainsRune(field, '_')) {
// special field or regular field
q := &deb.FieldQuery{Field: field, Relation: operatorToRelation(operator), Value: value}
if q.Relation == deb.VersionRegexp {
var err error
q.Regexp, err = regexp.Compile(q.Value)
if err != nil {
panic(fmt.Sprintf("regexp compile failed: %s", err))
}
}
return q
} else if operator == 0 && value == "" {
if pkg, version, arch, ok := parsePackageRef(field); ok {
// query for specific package
return &deb.PkgQuery{Pkg: pkg, Version: version, Arch: arch}
}
}
// regular dependency-like query
q := &deb.DependencyQuery{Dep: deb.Dependency{
Pkg: field,
Relation: operatorToRelation(operator),
Version: value,
Architecture: p.ArchCondition()}}
if q.Dep.Relation == deb.VersionRegexp {
var err error
q.Dep.Regexp, err = regexp.Compile(q.Dep.Version)
if err != nil {
panic(fmt.Sprintf("regexp compile failed: %s", err))
}
}
return q
}
// condition := '(' <operator> value ')' |
// operator := | << | < | <= | > | >> | >= | = | % | ~
func (p *parser) Condition() (operator itemType, value string) {
if p.input.Current().typ != itemLeftParen {
return
}
p.input.Consume()
if p.input.Current().typ == itemLt ||
p.input.Current().typ == itemGt ||
p.input.Current().typ == itemLtEq ||
p.input.Current().typ == itemGtEq ||
p.input.Current().typ == itemEq ||
p.input.Current().typ == itemPatMatch ||
p.input.Current().typ == itemRegexp {
operator = p.input.Current().typ
p.input.Consume()
} else {
operator = itemEq
}
if p.input.Current().typ != itemString {
panic(fmt.Sprintf("unexpected token %s: expecting value", p.input.Current()))
}
value = p.input.Current().val
p.input.Consume()
if p.input.Current().typ != itemRightParen {
panic(fmt.Sprintf("unexpected token %s: expecting ')'", p.input.Current()))
}
p.input.Consume()
return
}
// arch_condition := '{' arch '}' |
func (p *parser) ArchCondition() (arch string) {
if p.input.Current().typ != itemLeftCurly {
return
}
p.input.Consume()
if p.input.Current().typ != itemString {
panic(fmt.Sprintf("unexpected token %s: expecting architecture", p.input.Current()))
}
arch = p.input.Current().val
p.input.Consume()
if p.input.Current().typ != itemRightCurly {
panic(fmt.Sprintf("unexpected token %s: expecting '}'", p.input.Current()))
}
p.input.Consume()
return
}
|