blob: c0d03c8fb31dc5bf0d08a67cb265074c5adf7926 [file] [log] [blame]
Marcel van Lohuizenb8cad1a2020-02-03 16:55:24 +01001// Copyright 2020 CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package internal
16
17import (
18 "fmt"
19 "strconv"
20 "strings"
21
22 "cuelang.org/go/cue/errors"
23 "cuelang.org/go/cue/literal"
24 "cuelang.org/go/cue/token"
25)
26
27// Attr holds positional information for a single Attr.
28type Attr struct {
29 Fields []keyValue
30 Err error
31}
32
33// NewNonExisting creates a non-existing attribute.
34func NewNonExisting(key string) Attr {
35 const msgNotExist = "attribute %q does not exist"
36 return Attr{Err: errors.Newf(token.NoPos, msgNotExist, key)}
37}
38
39type keyValue struct {
40 data string
41 equal int // index of equal sign or 0 if non-existing
42}
43
44func (kv *keyValue) Text() string { return kv.data }
45func (kv *keyValue) Key() string { return kv.data[:kv.equal] }
46func (kv *keyValue) Value() string {
47 return strings.TrimSpace(kv.data[kv.equal+1:])
48}
49
50func (a *Attr) hasPos(p int) error {
51 if a.Err != nil {
52 return a.Err
53 }
54 if p >= len(a.Fields) {
55 return fmt.Errorf("field does not exist")
56 }
57 return nil
58}
59
60// String reports the possibly empty string value at the given position or
61// an error the attribute is invalid or if the position does not exist.
62func (a *Attr) String(pos int) (string, error) {
63 if err := a.hasPos(pos); err != nil {
64 return "", err
65 }
66 return a.Fields[pos].Text(), nil
67}
68
69// Int reports the integer at the given position or an error if the attribute is
70// invalid, the position does not exist, or the value at the given position is
71// not an integer.
72func (a *Attr) Int(pos int) (int64, error) {
73 if err := a.hasPos(pos); err != nil {
74 return 0, err
75 }
76 // TODO: use CUE's literal parser once it exists, allowing any of CUE's
77 // number types.
78 return strconv.ParseInt(a.Fields[pos].Text(), 10, 64)
79}
80
81// Flag reports whether an entry with the given name exists at position pos or
82// onwards or an error if the attribute is invalid or if the first pos-1 entries
83// are not defined.
84func (a *Attr) Flag(pos int, key string) (bool, error) {
85 if err := a.hasPos(pos - 1); err != nil {
86 return false, err
87 }
88 for _, kv := range a.Fields[pos:] {
89 if kv.Text() == key {
90 return true, nil
91 }
92 }
93 return false, nil
94}
95
96// Lookup searches for an entry of the form key=value from position pos onwards
97// and reports the value if found. It reports an error if the attribute is
98// invalid or if the first pos-1 entries are not defined.
99func (a *Attr) Lookup(pos int, key string) (val string, found bool, err error) {
100 if err := a.hasPos(pos - 1); err != nil {
101 return "", false, err
102 }
103 for _, kv := range a.Fields[pos:] {
104 if kv.Key() == key {
105 return kv.Value(), true, nil
106 }
107 }
108 return "", false, nil
109}
110
111func ParseAttrBody(pos token.Pos, s string) (a Attr) {
112 i := 0
113 for {
114 // always scan at least one, possibly empty element.
115 n, err := scanAttributeElem(pos, s[i:], &a)
116 if err != nil {
117 return Attr{Err: err}
118 }
119 if i += n; i >= len(s) {
120 break
121 }
122 if s[i] != ',' {
123 return Attr{Err: errors.Newf(pos, "invalid attribute: expected comma")}
124 }
125 i++
126 }
127 return a
128}
129
130func scanAttributeElem(pos token.Pos, s string, a *Attr) (n int, err errors.Error) {
131 // try CUE string
132 kv := keyValue{}
133 if n, kv.data, err = scanAttributeString(pos, s); n == 0 {
134 // try key-value pair
135 p := strings.IndexAny(s, ",=") // ) is assumed to be stripped.
136 switch {
137 case p < 0:
138 kv.data = s
139 n = len(s)
140
141 default: // ','
142 n = p
143 kv.data = s[:n]
144
145 case s[p] == '=':
146 kv.equal = p
147 offset := p + 1
148 var str string
149 if p, str, err = scanAttributeString(pos, s[offset:]); p > 0 {
150 n = offset + p
151 kv.data = s[:offset] + str
152 } else {
153 n = len(s)
154 if p = strings.IndexByte(s[offset:], ','); p >= 0 {
155 n = offset + p
156 }
157 kv.data = s[:n]
158 }
159 }
160 }
161 if a != nil {
162 a.Fields = append(a.Fields, kv)
163 }
164 return n, err
165}
166
167func scanAttributeString(pos token.Pos, s string) (n int, str string, err errors.Error) {
168 if s == "" || (s[0] != '#' && s[0] != '"' && s[0] != '\'') {
169 return 0, "", nil
170 }
171
172 nHash := 0
173 for {
174 if nHash < len(s) {
175 if s[nHash] == '#' {
176 nHash++
177 continue
178 }
179 if s[nHash] == '\'' || s[nHash] == '"' {
180 break
181 }
182 }
183 return nHash, s[:nHash], errors.Newf(pos, "invalid attribute string")
184 }
185
186 // Determine closing quote.
187 nQuote := 1
188 if c := s[nHash]; nHash+6 < len(s) && s[nHash+1] == c && s[nHash+2] == c {
189 nQuote = 3
190 }
191 close := s[nHash:nHash+nQuote] + s[:nHash]
192
193 // Search for closing quote.
194 index := strings.Index(s[len(close):], close)
195 if index == -1 {
196 return len(s), "", errors.Newf(pos, "attribute string not terminated")
197 }
198
199 index += 2 * len(close)
200 s, err2 := literal.Unquote(s[:index])
201 if err2 != nil {
202 return index, "", errors.Newf(pos, "invalid attribute string: %v", err2)
203 }
204 return index, s, nil
205}