1// Copyright 2021 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 textproto
16
17import (
18 "fmt"
19 "strings"
20
21 "cuelang.org/go/cue"
22 "cuelang.org/go/cue/ast"
23 "cuelang.org/go/cue/errors"
24 "cuelang.org/go/cue/literal"
25 "cuelang.org/go/cue/token"
26 "cuelang.org/go/encoding/protobuf/pbinternal"
27 "cuelang.org/go/internal/core/adt"
28 "cuelang.org/go/internal/value"
29
30 pbast "github.com/protocolbuffers/txtpbfmt/ast"
31 "github.com/protocolbuffers/txtpbfmt/parser"
32 "github.com/protocolbuffers/txtpbfmt/unquote"
33)
34
35// Option defines options for the decoder.
36// There are currently no options.
37type Option func(*options)
38
39type options struct {
40}
41
42// NewDecoder returns a new [Decoder].
43func NewDecoder(option ...Option) *Decoder {
44 d := &Decoder{}
45 _ = d.m // work around linter bug.
46 return d
47}
48
49// A Decoder caches conversions of [cue.Value] between calls to its methods.
50type Decoder struct {
51 m map[*adt.Vertex]*mapping
52}
53
54type decoder struct {
55 *Decoder
56
57 // Reset on each call
58 errs errors.Error
59 file *token.File
60}
61
62// Parse parses the given textproto bytes and converts them to a CUE expression,
63// using schema as the guideline for conversion using the following rules:
64//
65// - the @protobuf attribute is optional, but is necessary for:
66// - interpreting protobuf maps
67// - using a name different from the CUE name
68// - fields in the textproto that have no corresponding field in
69// schema are ignored
70//
71// NOTE: the filename is used for associating position information. However,
72// currently no position information is associated with the text proto because
73// the position information of github.com/protocolbuffers/txtpbfmt is too
74// unreliable to be useful.
75func (d *Decoder) Parse(schema cue.Value, filename string, b []byte) (ast.Expr, error) {
76 dec := decoder{Decoder: d}
77
78 // dec.errs = nil
79
80 f := token.NewFile(filename, -1, len(b))
81 f.SetLinesForContent(b)
82 dec.file = f
83
84 cfg := parser.Config{}
85 nodes, err := parser.ParseWithConfig(b, cfg)
86 if err != nil {
87 return nil, errors.Newf(token.NoPos, "textproto: %v", err)
88 }
89
90 m := dec.parseSchema(schema)
91 if dec.errs != nil {
92 return nil, dec.errs
93 }
94
95 n := dec.decodeMsg(m, nodes)
96 if dec.errs != nil {
97 return nil, dec.errs
98 }
99
100 return n, nil
101}
102
103// Don't expose until the protobuf APIs settle down.
104// func (d *decoder) Decode(schema cue.Value, textpbfmt) (cue.Value, error) {
105// }
106
107type mapping struct {
108 children map[string]*fieldInfo
109}
110
111type fieldInfo struct {
112 pbinternal.Info
113 msg *mapping
114 // keytype, for now
115}
116
117func (d *decoder) addErr(err error) {
118 d.errs = errors.Append(d.errs, errors.Promote(err, "textproto"))
119}
120
121func (d *decoder) addErrf(pos pbast.Position, format string, args ...interface{}) {
122 err := errors.Newf(d.protoPos(pos), "textproto: "+format, args...)
123 d.errs = errors.Append(d.errs, err)
124}
125
126func (d *decoder) protoPos(p pbast.Position) token.Pos {
127 return d.file.Pos(int(p.Byte), token.NoRelPos)
128}
129
130// parseSchema walks over a CUE "type", converts it to an internal data
131// structure that is used for parsing text proto, and writes it to
132func (d *decoder) parseSchema(schema cue.Value) *mapping {
133 _, v := value.ToInternal(schema)
134 if v == nil {
135 return nil
136 }
137
138 if d.m == nil {
139 d.m = map[*adt.Vertex]*mapping{}
140 } else if m := d.m[v]; m != nil {
141 return m
142 }
143
144 m := &mapping{children: map[string]*fieldInfo{}}
145
146 i, err := schema.Fields(cue.Optional(true))
147 if err != nil {
148 d.addErr(err)
149 return nil
150 }
151
152 for i.Next() {
153 info, err := pbinternal.FromIter(i)
154 if err != nil {
155 d.addErr(err)
156 continue
157 }
158
159 var msg *mapping
160
161 switch info.CompositeType {
162 case pbinternal.Normal:
163 switch info.ValueType {
164 case pbinternal.Message:
165 msg = d.parseSchema(i.Value())
166 }
167
168 case pbinternal.List:
169 e := i.Value().LookupPath(cue.MakePath(cue.AnyIndex))
170 if e.IncompleteKind() == cue.StructKind {
171 msg = d.parseSchema(e)
172 }
173 case pbinternal.Map:
174 e := i.Value().LookupPath(cue.MakePath(cue.AnyString))
175 if e.IncompleteKind() == cue.StructKind {
176 msg = d.parseSchema(e)
177 }
178 }
179
180 m.children[info.Name] = &fieldInfo{
181 Info: info,
182 msg: msg,
183 }
184 }
185
186 d.m[v] = m
187 return m
188}
189
190func (d *decoder) decodeMsg(m *mapping, n []*pbast.Node) ast.Expr {
191 st := &ast.StructLit{}
192
193 var listMap map[string]*ast.ListLit
194
195 for _, x := range n {
196 if x.Values == nil && x.Children == nil {
197 if cg := addComments(x.PreComments...); cg != nil {
198 ast.SetRelPos(cg, token.NewSection)
199 st.Elts = append(st.Elts, cg)
200 continue
201 }
202 }
203 if m == nil {
204 continue
205 }
206 f, ok := m.children[x.Name]
207 if !ok {
208 continue // ignore unknown fields
209 }
210
211 var value ast.Expr
212
213 switch f.CompositeType {
214 default:
215 value = d.decodeValue(f, x)
216
217 case pbinternal.List:
218 if listMap == nil {
219 listMap = make(map[string]*ast.ListLit)
220 }
221
222 list := listMap[f.CUEName]
223 if list == nil {
224 list = &ast.ListLit{}
225 listMap[f.CUEName] = list
226 value = list
227 }
228
229 if len(x.Values) == 1 || f.ValueType == pbinternal.Message {
230 v := d.decodeValue(f, x)
231 if value == nil {
232 if cg := addComments(x.PreComments...); cg != nil {
233 cg.Doc = true
234 ast.AddComment(v, cg)
235 }
236 }
237 if cg := addComments(x.PostValuesComments...); cg != nil {
238 cg.Position = 4
239 ast.AddComment(v, cg)
240 }
241 list.Elts = append(list.Elts, v)
242 break
243 }
244
245 var last ast.Expr
246 // Handle [1, 2, 3]
247 for _, v := range x.Values {
248 if v.Value == "" {
249 if cg := addComments(v.PreComments...); cg != nil {
250 if last != nil {
251 cg.Position = 4
252 ast.AddComment(last, cg)
253 } else {
254 cg.Position = 1
255 ast.AddComment(list, cg)
256 }
257 }
258 continue
259 }
260 y := *x
261 y.Values = []*pbast.Value{v}
262 last = d.decodeValue(f, &y)
263 list.Elts = append(list.Elts, last)
264 }
265 if cg := addComments(x.PostValuesComments...); cg != nil {
266 if last != nil {
267 cg.Position = 4
268 ast.AddComment(last, cg)
269 } else {
270 cg.Position = 1
271 ast.AddComment(list, cg)
272 }
273 }
274 if cg := addComments(x.ClosingBraceComment); cg != nil {
275 cg.Position = 4
276 ast.AddComment(list, cg)
277 }
278
279 case pbinternal.Map:
280 // mapValue: {
281 // key: 123
282 // value: "string"
283 // }
284 if k := len(x.Values); k > 0 {
285 d.addErrf(x.Start, "values not allowed for Message type; found %d", k)
286 }
287
288 var (
289 key ast.Label
290 val ast.Expr
291 )
292
293 for _, c := range x.Children {
294 if len(c.Values) != 1 {
295 d.addErrf(x.Start, "expected 1 value, found %d", len(c.Values))
296 continue
297 }
298 s := c.Values[0].Value
299
300 switch c.Name {
301 case "key":
302 if strings.HasPrefix(s, `"`) {
303 key = &ast.BasicLit{Kind: token.STRING, Value: s}
304 } else {
305 key = ast.NewString(s)
306 }
307
308 case "value":
309 val = d.decodeValue(f, c)
310
311 if cg := addComments(x.ClosingBraceComment); cg != nil {
312 cg.Line = true
313 ast.AddComment(val, cg)
314 }
315
316 default:
317 d.addErrf(c.Start, "unsupported key name %q in map", c.Name)
318 continue
319 }
320 }
321
322 if key != nil && val != nil {
323 value = ast.NewStruct(key, val)
324 }
325 }
326
327 if value != nil {
328 // TODO: convert line number information. However, position
329 // information in textpbfmt packages is too wonky to be useful
330 f := &ast.Field{
331 Label: ast.NewStringLabel(f.CUEName),
332 Value: value,
333 // Attrs: []*ast.Attribute{{Text: f.attr.}},
334 }
335 if cg := addComments(x.PreComments...); cg != nil {
336 cg.Doc = true
337 ast.AddComment(f, cg)
338 }
339 st.Elts = append(st.Elts, f)
340 }
341 }
342
343 return st
344}
345
346func addComments(lines ...string) (cg *ast.CommentGroup) {
347 var a []*ast.Comment
348 for _, c := range lines {
349 if !strings.HasPrefix(c, "#") {
350 continue
351 }
352 a = append(a, &ast.Comment{Text: "//" + c[1:]})
353 }
354 if a != nil {
355 cg = &ast.CommentGroup{List: a}
356 }
357 return cg
358}
359
360func (d *decoder) decodeValue(f *fieldInfo, n *pbast.Node) (x ast.Expr) {
361 if f.ValueType == pbinternal.Message {
362 if k := len(n.Values); k > 0 {
363 d.addErrf(n.Start, "values not allowed for Message type; found %d", k)
364 }
365 x = d.decodeMsg(f.msg, n.Children)
366 if cg := addComments(n.ClosingBraceComment); cg != nil {
367 cg.Line = true
368 cg.Position = 4
369 ast.AddComment(x, cg)
370 }
371 return x
372 }
373
374 if len(n.Values) != 1 {
375 d.addErrf(n.Start, "expected 1 value, found %d", len(n.Values))
376 return nil
377 }
378 v := n.Values[0]
379
380 defer func() {
381 if cg := addComments(v.PreComments...); cg != nil {
382 cg.Doc = true
383 ast.AddComment(x, cg)
384 }
385 if cg := addComments(v.InlineComment); cg != nil {
386 cg.Line = true
387 cg.Position = 2
388 ast.AddComment(x, cg)
389 }
390 }()
391
392 switch f.ValueType {
393 case pbinternal.String, pbinternal.Bytes:
394 s, _, err := unquote.Unquote(n)
395 if err != nil {
396 d.addErrf(n.Start, "invalid string or bytes: %v", err)
397 }
398 if f.ValueType == pbinternal.String {
399 s = literal.String.Quote(s)
400 } else {
401 s = literal.Bytes.Quote(s)
402 }
403 return &ast.BasicLit{Kind: token.STRING, Value: s}
404
405 case pbinternal.Bool:
406 switch v.Value {
407 case "true":
408 return ast.NewBool(true)
409
410 case "false":
411 default:
412 d.addErrf(n.Start, "invalid bool %s", v.Value)
413 }
414 return ast.NewBool(false)
415
416 case pbinternal.Int, pbinternal.Float:
417 s := v.Value
418 switch s {
419 case "inf", "nan":
420 // TODO: include message.
421 return &ast.BottomLit{}
422 }
423
424 var info literal.NumInfo
425 if err := literal.ParseNum(s, &info); err != nil {
426 var x ast.BasicLit
427 if pbinternal.MatchBySymbol(f.Value, s, &x) {
428 return &x
429 }
430 d.addErrf(n.Start, "invalid number %s", s)
431 }
432 if !info.IsInt() {
433 return &ast.BasicLit{Kind: token.FLOAT, Value: s}
434 }
435 return &ast.BasicLit{Kind: token.INT, Value: info.String()}
436
437 default:
438 panic(fmt.Sprintf("unexpected type %v", f.ValueType))
439 }
440}