blob: a00982d23a7a28b221dd5ecb30dec7a7ad95e516 [file] [log] [blame]
Marcel van Lohuizenb9b62d32019-03-14 23:50:15 +01001// Copyright 2019 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 cue
16
17import (
18 "sort"
19 "strings"
20
21 "cuelang.org/go/cue/ast"
22 "cuelang.org/go/cue/literal"
23)
24
25// This file includes functionality for parsing attributes.
26// These functions are slightly more permissive than the spec. Together with the
27// scanner and parser the full spec is implemented, though.
28
29// attributes is used to store per-key attribute text for a fields.
30// It deliberately does not implement the value interface, as it should
31// never act as a value in any way.
32type attributes struct {
33 attr []attr
34}
35type attr struct {
36 text string
37 offset int
38}
39
40func (a *attr) key() string {
41 return a.text[1:a.offset]
42}
43
44func (a *attr) body() string {
45 return a.text[a.offset+1 : len(a.text)-1]
46}
47
48func createAttrs(ctx *context, src source, attrs []*ast.Attribute) (a *attributes, err evaluated) {
49 if len(attrs) == 0 {
50 return nil, nil
51 }
52 as := []attr{}
53 for _, a := range attrs {
54 index := strings.IndexByte(a.Text, '(')
55 n := len(a.Text)
56 if index < 2 || a.Text[0] != '@' || a.Text[n-1] != ')' {
57 return nil, ctx.mkErr(newNode(a), "invalid attribute %q", a.Text)
58 }
59 as = append(as, attr{a.Text[:n], index})
60 }
61
62 sort.Slice(as, func(i, j int) bool { return as[i].text < as[j].text })
63 for i := 1; i < len(as); i++ {
64 if ai, aj := as[i-1], as[i]; ai.key() == aj.key() {
65 n := newNode(attrs[0])
66 return nil, ctx.mkErr(n, "multiple attributes for key %q", ai.key())
67 }
68 }
69
70 for _, a := range attrs {
71 if err := parseAttrBody(ctx, src, a.Text, nil); err != nil {
72 return nil, err
73 }
74 }
75 return &attributes{as}, nil
76}
77
78// unifyAttrs merges the attributes from a and b. It may return either a or b
79// if a and b are identical.
80func unifyAttrs(ctx *context, src source, a, b *attributes) (atrs *attributes, err evaluated) {
81 if a == b {
82 return a, nil
83 }
84 if a == nil {
85 return b, nil
86 }
87 if b == nil {
88 return a, nil
89 }
90
91 if len(a.attr) == len(b.attr) {
92 for i, x := range a.attr {
93 if x != b.attr[i] {
94 goto notSame
95 }
96 }
97 return a, nil
98 }
99
100notSame:
101 as := append(a.attr, b.attr...)
102
103 // remove duplicates and error on conflicts
104 sort.Slice(as, func(i, j int) bool { return as[i].text < as[j].text })
105 k := 0
106 for i := 1; i < len(as); i++ {
107 if ak, ai := as[k], as[i]; ak.key() == ai.key() {
108 if ak.body() == ai.body() {
109 continue
110 }
111 return nil, ctx.mkErr(src, "conflicting attributes for key %q", ai.key())
112 }
113 k++
114 as[k] = as[i]
115 }
116
117 return &attributes{as[:k+1]}, nil
118}
119
120// parsedAttr holds positional information for a single parsedAttr.
121type parsedAttr struct {
122 fields []keyValue
123}
124
125type keyValue struct {
126 data string
127 equal int // index of equal sign or 0 if non-existing
128}
129
130func (kv *keyValue) text() string { return kv.data }
131func (kv *keyValue) key() string { return kv.data[:kv.equal] }
132func (kv *keyValue) value() string { return kv.data[kv.equal+1:] }
133
134func parseAttrBody(ctx *context, src source, s string, a *parsedAttr) (err evaluated) {
135 i := 0
136 for {
137 // always scan at least one, possibly empty element.
138 n, err := scanAttributeElem(ctx, src, s[i:], a)
139 if err != nil {
140 return err
141 }
142 if i += n; i >= len(s) {
143 break
144 }
145 if s[i] != ',' {
146 return ctx.mkErr(src, "invalid attribute: expected comma")
147 }
148 i++
149 }
150 return nil
151}
152
153func scanAttributeElem(ctx *context, src source, s string, a *parsedAttr) (n int, err evaluated) {
154 // try CUE string
155 kv := keyValue{}
156 if n, kv.data, err = scanAttributeString(ctx, src, s); n == 0 {
157 // try key-value pair
158 p := strings.IndexAny(s, ",=") // ) is assumed to be stripped.
159 switch {
160 case p < 0:
161 kv.data = s
162 n = len(s)
163
164 default: // ','
165 n = p
166 kv.data = s[:n]
167
168 case s[p] == '=':
169 kv.equal = p
170 offset := p + 1
171 var str string
172 if p, str, err = scanAttributeString(ctx, src, s[offset:]); p > 0 {
173 n = offset + p
174 kv.data = s[:offset] + str
175 } else {
176 n = len(s)
177 if p = strings.IndexByte(s[offset:], ','); p >= 0 {
178 n = offset + p
179 }
180 kv.data = s[:n]
181 }
182 }
183 }
184 if a != nil {
185 a.fields = append(a.fields, kv)
186 }
187 return n, err
188}
189
190func scanAttributeString(ctx *context, src source, s string) (n int, str string, err evaluated) {
191 if s == "" || (s[0] != '#' && s[0] != '"' && s[0] != '\'') {
192 return 0, "", nil
193 }
194
195 nHash := 0
196 for {
197 if nHash < len(s) {
198 if s[nHash] == '#' {
199 nHash++
200 continue
201 }
202 if s[nHash] == '\'' || s[nHash] == '"' {
203 break
204 }
205 }
206 return nHash, s[:nHash], ctx.mkErr(src, "invalid attribute string")
207 }
208
209 // Determine closing quote.
210 nQuote := 1
211 if c := s[nHash]; nHash+6 < len(s) && s[nHash+1] == c && s[nHash+2] == c {
212 nQuote = 3
213 }
214 close := s[nHash:nHash+nQuote] + s[:nHash]
215
216 // Search for closing quote.
217 index := strings.Index(s[len(close):], close)
218 if index == -1 {
219 return len(s), "", ctx.mkErr(src, "attribute string not terminated")
220 }
221
222 index += 2 * len(close)
223 s, err2 := literal.Unquote(s[:index])
224 if err2 != nil {
225 return index, "", ctx.mkErr(src, "invalid attribute string: %v", err2)
226 }
227 return index, s, nil
228}