this repo has no description
at master 440 lines 10 kB view raw
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}