cuelang.org/go@v0.13.0/internal/filetypes/generate.go (about) 1 // Copyright 2025 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 15 //go:build ignore 16 17 package main 18 19 import ( 20 "bytes" 21 "cmp" 22 _ "embed" 23 "fmt" 24 goformat "go/format" 25 "iter" 26 "log" 27 "maps" 28 "os" 29 "path/filepath" 30 "slices" 31 "sort" 32 "strings" 33 "text/template" 34 35 "cuelang.org/go/cue" 36 "cuelang.org/go/cue/build" 37 "cuelang.org/go/cue/cuecontext" 38 "cuelang.org/go/cue/errors" 39 "cuelang.org/go/cue/format" 40 "cuelang.org/go/cue/token" 41 "cuelang.org/go/internal/filetypes" 42 "cuelang.org/go/internal/filetypes/internal" 43 "cuelang.org/go/internal/filetypes/internal/genfunc" 44 "cuelang.org/go/internal/filetypes/internal/genstruct" 45 ) 46 47 type tmplParams struct { 48 TagTypes map[string]filetypes.TagType 49 ToFileParams *genToFileParams 50 ToFileResult *genToFileResult 51 FromFileParams *genFromFileParams 52 FromFileResult *genFromFileResult 53 SubsidiaryBoolTagFuncCount int 54 SubsidiaryTagFuncCount int 55 Data string 56 // Generated is used by the generation code to avoid 57 // generating the same global identifier twice. 58 Generated map[string]bool 59 } 60 61 var ( 62 //go:embed types_gen.go.tmpl 63 typesGenCode string 64 65 //go:embed types.cue 66 typesCUE string 67 ) 68 69 var tmpl = template.Must(template.New("types_gen.go.tmpl").Parse(typesGenCode)) 70 71 type tagInfo struct { 72 name string 73 typ filetypes.TagType 74 value cue.Value // only set when kind is filetypes.TagTopLevel 75 } 76 77 // fileResult represents a possible result for toFile. 78 type fileResult struct { 79 bits uint64 80 81 mode string 82 fileVal cue.Value 83 filename string 84 file *build.File 85 86 subsidiaryTags cue.Value 87 subsidiaryBoolTags cue.Value 88 89 subsidiaryBoolTagFuncIndex int // valid if subsidiaryBoolTags.Exists 90 subsidiaryTagFuncIndex int // valid if subsidiaryTags.Exists 91 92 err internal.ErrorKind 93 94 tags []string 95 } 96 97 func (r *fileResult) appendRecord(data []byte, paramsStruct *genToFileParams, resultStruct *genToFileResult) []byte { 98 recordSize := paramsStruct.Size() + resultStruct.Size() 99 data = slices.Grow(data, recordSize) 100 data = data[:len(data)+recordSize] 101 record := data[len(data)-recordSize:] 102 103 // Write the key part of the record. 104 param := slices.Clip(record[:paramsStruct.Size()]) 105 paramsStruct.FileExt.Put(param, fileExt(r.filename)) 106 paramsStruct.Tags.Put(param, genstruct.ElemsFromBits(r.bits, r.tags)) 107 var mode filetypes.Mode 108 switch r.mode { 109 case "input": 110 mode = filetypes.Input 111 case "export": 112 mode = filetypes.Export 113 case "def": 114 mode = filetypes.Def 115 case "eval": 116 mode = filetypes.Eval 117 default: 118 panic(fmt.Errorf("unknown mode %q", r.mode)) 119 } 120 paramsStruct.Mode.Put(param, mode) 121 122 result := slices.Clip(record[paramsStruct.Size():]) 123 // Write the result part of the record. 124 if r.err != internal.ErrNoError { 125 resultStruct.Error.Put(result, r.err) 126 return data 127 } 128 resultStruct.Encoding.Put(result, r.file.Encoding) 129 resultStruct.Interpretation.Put(result, r.file.Interpretation) 130 resultStruct.Form.Put(result, r.file.Form) 131 if r.subsidiaryBoolTags.Exists() { 132 resultStruct.SubsidiaryBoolTagFuncIndex.Put(result, r.subsidiaryBoolTagFuncIndex+1) 133 } 134 if r.subsidiaryTags.Exists() { 135 resultStruct.SubsidiaryTagFuncIndex.Put(result, r.subsidiaryTagFuncIndex+1) 136 } 137 return data 138 } 139 140 func main() { 141 if err := generate(); err != nil { 142 log.Fatal(err) 143 } 144 } 145 146 var top cue.Value 147 148 func generate() error { 149 ctx := cuecontext.New() 150 top = ctx.CompileString("_") 151 rootVal := ctx.CompileString(typesCUE, cue.Filename("types.cue")) 152 153 toFile, err := generateToFile(rootVal) 154 if err != nil { 155 return err 156 } 157 fromFile, err := generateFromFile(rootVal) 158 if err != nil { 159 return err 160 } 161 if err := generateCode(toFile, fromFile); err != nil { 162 return err 163 } 164 return nil 165 } 166 167 // toFileInfo holds the information needed to generate the toFile implementation code. 168 type toFileInfo struct { 169 paramsStruct *genToFileParams 170 resultStruct *genToFileResult 171 tagTypes map[string]filetypes.TagType 172 subsidiaryBoolTagsByCUE map[string]cueValue 173 subsidiaryTagsByCUE map[string]cueValue 174 subsidiaryBoolTagKeys []string 175 subsidiaryTagKeys []string 176 } 177 178 // fromFileInfo holds the information needed to generate the fromFile implementation code. 179 type fromFileInfo struct { 180 paramsStruct *genFromFileParams 181 resultStruct *genFromFileResult 182 } 183 184 func generateToFile(rootVal cue.Value) (toFileInfo, error) { 185 count := 0 186 errCount := 0 187 tags, topLevelTags, _ := allTags(rootVal) 188 189 results := slices.Collect(allCombinations(rootVal, topLevelTags, tags)) 190 subsidiaryTagsByCUE := make(map[string]cueValue) 191 subsidiaryTagKeysMap := make(map[string]bool) 192 subsidiaryBoolTagsByCUE := make(map[string]cueValue) 193 subsidiaryBoolTagKeysMap := make(map[string]bool) 194 for i, r := range results { 195 if v := r.subsidiaryBoolTags; v.Exists() { 196 results[i].subsidiaryBoolTagFuncIndex = addCUELogic(v, subsidiaryBoolTagsByCUE, subsidiaryBoolTagKeysMap) 197 } 198 if v := r.subsidiaryTags; v.Exists() { 199 results[i].subsidiaryTagFuncIndex = addCUELogic(v, subsidiaryTagsByCUE, subsidiaryTagKeysMap) 200 } 201 } 202 subsidiaryBoolTagKeys := slices.Sorted(maps.Keys(subsidiaryBoolTagKeysMap)) 203 subsidiaryTagKeys := slices.Sorted(maps.Keys(subsidiaryTagKeysMap)) 204 toFileParams := newToFileParamsStruct( 205 topLevelTags, 206 // Note: add ".unknown" as a proxy for any unknown file extension, 207 // and make sure that the empty file extension is also present 208 // even though it's not mentioned in the extensions struct. 209 append(allKeys[string](rootVal, "all", "extensions"), ".unknown", ""), 210 ) 211 toFileResult := newToFileResultStruct( 212 append(allKeys[build.Encoding](rootVal, "all", "encodings"), ""), 213 append(allKeys[build.Interpretation](rootVal, "all", "interpretations"), ""), 214 append(allKeys[build.Form](rootVal, "all", "forms"), ""), 215 len(subsidiaryBoolTagsByCUE), 216 len(subsidiaryTagsByCUE), 217 ) 218 219 tagTypes := make(map[string]filetypes.TagType) 220 for name, info := range tags { 221 tagTypes[name] = info.typ 222 } 223 224 var recordData []byte 225 for _, r := range results { 226 count++ 227 if r.err != internal.ErrNoError { 228 errCount++ 229 } 230 recordData = r.appendRecord(recordData, toFileParams, toFileResult) 231 } 232 genstruct.SortRecords(recordData, toFileParams.Size()+toFileResult.Size(), toFileParams.Size()) 233 if err := os.WriteFile("fileinfo.dat", recordData, 0o666); err != nil { 234 return toFileInfo{}, err 235 } 236 237 return toFileInfo{ 238 paramsStruct: toFileParams, 239 resultStruct: toFileResult, 240 tagTypes: tagTypes, 241 subsidiaryBoolTagsByCUE: subsidiaryBoolTagsByCUE, 242 subsidiaryTagsByCUE: subsidiaryTagsByCUE, 243 subsidiaryBoolTagKeys: subsidiaryBoolTagKeys, 244 subsidiaryTagKeys: subsidiaryTagKeys, 245 }, nil 246 } 247 248 func generateCode( 249 toFile toFileInfo, 250 fromFile fromFileInfo, 251 ) error { 252 params := tmplParams{ 253 ToFileParams: toFile.paramsStruct, 254 ToFileResult: toFile.resultStruct, 255 FromFileParams: fromFile.paramsStruct, 256 FromFileResult: fromFile.resultStruct, 257 TagTypes: toFile.tagTypes, 258 Data: "fileInfoDataBytes", 259 SubsidiaryBoolTagFuncCount: len(toFile.subsidiaryBoolTagsByCUE), 260 SubsidiaryTagFuncCount: len(toFile.subsidiaryTagsByCUE), 261 Generated: make(map[string]bool), 262 } 263 var buf bytes.Buffer 264 if err := tmpl.Execute(&buf, params); err != nil { 265 return err 266 } 267 268 // Now generate the subsidiary tag logic; we generate 269 // a type for each class of subsidiary tag, containing all the possible 270 // tags for that class. Then we generate a function for each 271 // distinct piece of CUE logic that implements that logic 272 // in Go. 273 genfunc.GenerateGoTypeForFields(&buf, "subsidiaryTags", toFile.subsidiaryTagKeys, "string") 274 genfunc.GenerateGoTypeForFields(&buf, "subsidiaryBoolTags", toFile.subsidiaryBoolTagKeys, "bool") 275 276 for _, k := range slices.Sorted(maps.Keys(toFile.subsidiaryTagsByCUE)) { 277 v := toFile.subsidiaryTagsByCUE[k] 278 genfunc.GenerateGoFuncForCUEStruct(&buf, fmt.Sprintf("unifySubsidiaryTags_%d", v.index), "subsidiaryTags", v.v, toFile.subsidiaryTagKeys, "string") 279 } 280 281 for _, k := range slices.Sorted(maps.Keys(toFile.subsidiaryBoolTagsByCUE)) { 282 v := toFile.subsidiaryBoolTagsByCUE[k] 283 genfunc.GenerateGoFuncForCUEStruct(&buf, fmt.Sprintf("unifySubsidiaryBoolTags_%d", v.index), "subsidiaryBoolTags", v.v, toFile.subsidiaryBoolTagKeys, "bool") 284 } 285 286 data, err := goformat.Source(buf.Bytes()) 287 if err != nil { 288 if err := os.WriteFile("types_gen.go", buf.Bytes(), 0o666); err != nil { 289 return err 290 } 291 return fmt.Errorf("malformed source in types_gen.go:%v", err) 292 } 293 if err := os.WriteFile("types_gen.go", data, 0o666); err != nil { 294 return err 295 } 296 return nil 297 } 298 299 func generateFromFile(rootVal cue.Value) (fromFileInfo, error) { 300 allEncodings := append(allKeys[build.Encoding](rootVal, "all", "encodings"), "") 301 allInterpretations := append(allKeys[build.Interpretation](rootVal, "all", "interpretations"), "") 302 allForms := append(allKeys[build.Form](rootVal, "all", "forms"), "") 303 paramsStruct := newFromFileParamsStruct( 304 allEncodings, 305 allInterpretations, 306 allForms, 307 ) 308 resultStruct := newFromFileResult( 309 allEncodings, 310 allInterpretations, 311 allForms, 312 ) 313 var recordData []byte 314 for mode := range filetypes.NumModes { 315 for _, encoding := range allEncodings { 316 for _, interpretation := range allInterpretations { 317 for _, form := range allForms { 318 f := &build.File{ 319 Encoding: encoding, 320 Interpretation: interpretation, 321 Form: form, 322 } 323 fi, err := fromFileOrig(rootVal, f, mode) 324 if err != nil { 325 continue 326 } 327 recordData = appendFromFileRecord(recordData, paramsStruct, resultStruct, mode, f, fi) 328 } 329 } 330 } 331 } 332 genstruct.SortRecords(recordData, paramsStruct.Size()+resultStruct.Size(), paramsStruct.Size()) 333 if err := os.WriteFile("fromfile.dat", recordData, 0o666); err != nil { 334 return fromFileInfo{}, err 335 } 336 337 return fromFileInfo{ 338 paramsStruct: paramsStruct, 339 resultStruct: resultStruct, 340 }, nil 341 } 342 343 func appendFromFileRecord( 344 data []byte, 345 paramsStruct *genFromFileParams, 346 resultStruct *genFromFileResult, 347 mode filetypes.Mode, 348 f *build.File, 349 fi *filetypes.FileInfo, 350 ) []byte { 351 recordSize := paramsStruct.Size() + resultStruct.Size() 352 data = slices.Grow(data, recordSize) 353 data = data[:len(data)+recordSize] 354 record := data[len(data)-recordSize:] 355 356 // Write the key part of the record. 357 param := slices.Clip(record[:paramsStruct.Size()]) 358 paramsStruct.Mode.Put(param, mode) 359 paramsStruct.Encoding.Put(param, f.Encoding) 360 paramsStruct.Interpretation.Put(param, f.Interpretation) 361 paramsStruct.Form.Put(param, f.Form) 362 363 result := slices.Clip(record[paramsStruct.Size():]) 364 resultStruct.Encoding.Put(result, fi.Encoding) 365 resultStruct.Interpretation.Put(result, fi.Interpretation) 366 resultStruct.Form.Put(result, fi.Form) 367 resultStruct.Aspects.Put(result, fi.Aspects()) 368 return data 369 } 370 371 func fromFileOrig(rootVal cue.Value, b *build.File, mode filetypes.Mode) (*filetypes.FileInfo, error) { 372 modeVal := lookup(rootVal, "modes", mode.String()) 373 fileVal := lookup(modeVal, "FileInfo") 374 if b.Encoding == "" { 375 return nil, errors.Newf(token.NoPos, "no encoding specified") 376 } 377 fileVal = fileVal.FillPath(cue.MakePath(cue.Str("encoding")), b.Encoding) 378 if b.Interpretation != "" { 379 fileVal = fileVal.FillPath(cue.MakePath(cue.Str("interpretation")), b.Interpretation) 380 } 381 if b.Form != "" { 382 fileVal = fileVal.FillPath(cue.MakePath(cue.Str("form")), b.Form) 383 } 384 var errs errors.Error 385 var interpretation string 386 if b.Form != "" { 387 fileVal, errs = unifyWith(errs, fileVal, rootVal, "forms", string(b.Form)) 388 if errs != nil { 389 return nil, errs 390 } 391 interpretation, _ = lookup(fileVal, "interpretation").String() 392 // may leave some encoding-dependent options open in data mode. 393 } else { 394 interpretation, _ = lookup(fileVal, "interpretation").String() 395 if interpretation != "" { 396 // always sets form=*schema 397 fileVal, errs = unifyWith(errs, fileVal, rootVal, "interpretations", interpretation) 398 } 399 } 400 if interpretation == "" { 401 encoding, err := lookup(fileVal, "encoding").String() 402 if err != nil { 403 return nil, err 404 } 405 fileVal, errs = unifyWith(errs, fileVal, modeVal, "encodings", encoding) 406 } 407 408 fi := &filetypes.FileInfo{} 409 if err := fileVal.Decode(fi); err != nil { 410 return nil, errors.Wrapf(err, token.NoPos, "could not parse arguments") 411 } 412 fi.Filename = b.Filename 413 return fi, errs 414 } 415 416 // unifyWith returns the equivalent of `v1 & v2[field][value]`. 417 func unifyWith(errs errors.Error, v1, v2 cue.Value, field, value string) (cue.Value, errors.Error) { 418 v1 = v1.Unify(lookup(v2, field, value)) 419 if err := v1.Err(); err != nil { 420 errs = errors.Append(errs, 421 errors.Newf(token.NoPos, "unknown %s %s", field, value)) 422 } 423 return v1, errs 424 } 425 426 // cueValue holds a CUE value and an index that will be used 427 // to distinguish that value in the generated source. 428 type cueValue struct { 429 v cue.Value 430 index int 431 } 432 433 // addCUELogic records the given CUE value as something that 434 // we will need to generate Go logic for into byCUE, 435 // and also adds any struct fields into keys. 436 // 437 // It returns the index recorded for the logic. 438 func addCUELogic(v cue.Value, byCUE map[string]cueValue, keys map[string]bool) int { 439 data, err := format.Node(v.Syntax(cue.Raw())) 440 if err != nil { 441 panic(fmt.Errorf("cannot format CUE: %v", err)) 442 } 443 for name := range structFields(v, cue.Optional(true)) { 444 keys[name] = true 445 } 446 src := string(data) 447 if v, ok := byCUE[src]; ok { 448 return v.index 449 } 450 index := len(byCUE) 451 byCUE[src] = cueValue{ 452 v: v, 453 index: index, 454 } 455 return index 456 } 457 458 func allCombinations(rootVal cue.Value, topLevelTags []string, tagInfo map[string]tagInfo) iter.Seq[fileResult] { 459 return func(yield func(fileResult) bool) { 460 var filenames []string 461 for ext := range structFields(lookup(rootVal, "modes", "input", "extensions")) { 462 filename := ext 463 if filename != "-" { 464 filename = "x" + filename 465 } 466 filenames = append(filenames, filename) 467 } 468 filenames = append(filenames, "x.unknown", "withoutextension") 469 470 for r := range tagCombinations(top, topLevelTags, tagInfo) { 471 for mode, modeVal := range structFields(lookup(rootVal, "modes")) { 472 fileVal := r.fileVal.Unify(lookup(modeVal, "FileInfo")) 473 for _, filename := range filenames { 474 r.mode = mode 475 r.filename = filename 476 r.file, r.fileVal, r.err = toFile1(modeVal, fileVal, filename) 477 r.subsidiaryBoolTags = lookup(r.fileVal, "boolTags") 478 r.subsidiaryTags = lookup(r.fileVal, "tags") 479 if !yield(r) { 480 return 481 } 482 } 483 } 484 } 485 } 486 } 487 488 func toFile1(modeVal, fileVal cue.Value, filename string) (*build.File, cue.Value, internal.ErrorKind) { 489 if !lookup(fileVal, "encoding").Exists() { 490 if ext := fileExt(filename); ext != "" { 491 extFile := lookup(modeVal, "extensions", ext) 492 if !extFile.Exists() { 493 return nil, cue.Value{}, internal.ErrUnknownFileExtension 494 } 495 fileVal = fileVal.Unify(extFile) 496 } else { 497 return nil, cue.Value{}, internal.ErrNoEncodingSpecified 498 } 499 } 500 501 // Note that the filename is only filled in the Go value, and not the CUE value. 502 // This makes no difference to the logic, but saves a non-trivial amount of evaluator work. 503 f := &build.File{Filename: filename} 504 if err := fileVal.Decode(f); err != nil { 505 return nil, cue.Value{}, internal.ErrCouldNotDetermineFileType 506 } 507 return f, fileVal, internal.ErrNoError 508 } 509 510 // allTags returns all tags that can be used and their types; 511 // It also returns slices of the top level and subsidiary tag names. 512 func allTags(rootVal cue.Value) (_ map[string]tagInfo, topLevel []string, subsid []string) { 513 tags := make(map[string]tagInfo) 514 add := func(name string, typ filetypes.TagType, v cue.Value) { 515 if other, ok := tags[name]; ok { 516 if typ != other.typ { 517 panic("tag redefinition") 518 } 519 return 520 } 521 info := tagInfo{ 522 name: name, 523 typ: typ, 524 value: v, 525 } 526 if typ == filetypes.TagTopLevel { 527 topLevel = append(topLevel, name) 528 } else { 529 subsid = append(subsid, name) 530 } 531 tags[name] = info 532 } 533 addSubsidiary := func(v cue.Value) { 534 for tagName := range structFields(lookup(v, "boolTags")) { 535 add(tagName, filetypes.TagSubsidiaryBool, cue.Value{}) 536 } 537 for tagName := range structFields(lookup(v, "tags")) { 538 add(tagName, filetypes.TagSubsidiaryString, cue.Value{}) 539 } 540 } 541 for tagName, v := range structFields(lookup(rootVal, "tagInfo")) { 542 add(tagName, filetypes.TagTopLevel, v) 543 addSubsidiary(v) 544 } 545 addSubsidiary(lookup(rootVal, "interpretations")) 546 addSubsidiary(lookup(rootVal, "forms")) 547 sort.Strings(topLevel) 548 sort.Strings(subsid) 549 return tags, topLevel, subsid 550 } 551 552 func tagCombinations(initial cue.Value, topLevelTags []string, tagInfo map[string]tagInfo) iter.Seq[fileResult] { 553 return func(yield func(fileResult) bool) { 554 if len(topLevelTags) > 64 { 555 panic("too many tags") 556 } 557 type bitsValue struct { 558 bits uint64 559 v cue.Value 560 } 561 evaluate := func(v bitsValue, tagIndex int, _ int) (bitsValue, bool) { 562 v.v = v.v.Unify(tagInfo[topLevelTags[tagIndex]].value) 563 v.bits |= 1 << tagIndex 564 return v, v.v.Validate() == nil 565 } 566 567 for v := range walkSpace(len(topLevelTags), 1, bitsValue{0, initial}, evaluate) { 568 if !yield(fileResult{ 569 bits: v.bits, 570 fileVal: v.v, 571 tags: topLevelTags, 572 }) { 573 return 574 } 575 } 576 } 577 } 578 579 func (ts fileResult) String() string { 580 if ts.bits == 0 { 581 return "<none>" 582 } 583 var buf strings.Builder 584 for i, tag := range ts.tags { 585 if ts.bits&(1<<i) != 0 { 586 if buf.Len() > 0 { 587 buf.WriteByte('|') 588 } 589 buf.WriteString(tag) 590 } 591 } 592 return buf.String() 593 } 594 595 func (ts fileResult) Compare(ts1 fileResult) int { 596 return cmp.Compare(ts.String(), ts1.String()) 597 } 598 599 // structFields returns an iterator over the names of all the fields 600 // in v and their values. 601 func structFields(v cue.Value, opts ...cue.Option) iter.Seq2[string, cue.Value] { 602 return func(yield func(string, cue.Value) bool) { 603 if !v.Exists() { 604 return 605 } 606 iter, err := v.Fields(opts...) 607 if err != nil { 608 return 609 } 610 for iter.Next() { 611 if !yield(iter.Selector().Unquoted(), iter.Value()) { 612 break 613 } 614 } 615 } 616 } 617 618 type genFromFileParams struct { 619 genstruct.Struct 620 Mode genstruct.Accessor[filetypes.Mode] 621 Encoding genstruct.Accessor[build.Encoding] 622 Interpretation genstruct.Accessor[build.Interpretation] 623 Form genstruct.Accessor[build.Form] 624 } 625 626 type genFromFileResult struct { 627 genstruct.Struct 628 Encoding genstruct.Accessor[build.Encoding] 629 Interpretation genstruct.Accessor[build.Interpretation] 630 Form genstruct.Accessor[build.Form] 631 Aspects genstruct.Accessor[internal.Aspects] 632 } 633 634 func newFromFileParamsStruct( 635 encodings []build.Encoding, 636 interpretations []build.Interpretation, 637 forms []build.Form, 638 ) *genFromFileParams { 639 r := &genFromFileParams{} 640 r.Mode = genstruct.AddInt(&r.Struct, filetypes.NumModes, "Mode") 641 r.Encoding = genstruct.AddEnum(&r.Struct, encodings, "", "allEncodings", "build.Encoding", nil) 642 r.Interpretation = genstruct.AddEnum(&r.Struct, interpretations, "", "allInterpretations", "build.Interpretation", nil) 643 r.Form = genstruct.AddEnum(&r.Struct, forms, "", "allForms", "build.Form", nil) 644 return r 645 } 646 647 func newFromFileResult( 648 encodings []build.Encoding, 649 interpretations []build.Interpretation, 650 forms []build.Form, 651 ) *genFromFileResult { 652 r := &genFromFileResult{} 653 r.Encoding = genstruct.AddEnum(&r.Struct, encodings, "", "allEncodings", "build.Encoding", nil) 654 r.Interpretation = genstruct.AddEnum(&r.Struct, interpretations, "", "allInterpretations", "build.Interpretation", nil) 655 r.Form = genstruct.AddEnum(&r.Struct, forms, "", "allForms", "build.Form", nil) 656 r.Aspects = genstruct.AddInt(&r.Struct, internal.AllAspects, "internal.Aspects") 657 return r 658 } 659 660 func newToFileParamsStruct(topLevelTags, fileExts []string) *genToFileParams { 661 r := &genToFileParams{} 662 r.Mode = genstruct.AddInt(&r.Struct, filetypes.NumModes, "Mode") 663 // Note: "" is a member of the set: we'll default to that if the extension isn't 664 // part of the known set. 665 r.FileExt = genstruct.AddEnum(&r.Struct, fileExts, ".unknown", "allFileExts", "string", nil) 666 r.Tags = genstruct.AddSet(&r.Struct, topLevelTags, "allTopLevelTags") 667 return r 668 } 669 670 type genToFileParams struct { 671 genstruct.Struct 672 Tags genstruct.Accessor[iter.Seq[string]] 673 FileExt genstruct.Accessor[string] 674 Mode genstruct.Accessor[filetypes.Mode] 675 } 676 677 type genToFileResult struct { 678 genstruct.Struct 679 Encoding genstruct.Accessor[build.Encoding] 680 Interpretation genstruct.Accessor[build.Interpretation] 681 Form genstruct.Accessor[build.Form] 682 683 // Note: the indexes below are one more than the actual index 684 // so that we can use the zero value to communicate "no tags". 685 SubsidiaryTagFuncIndex genstruct.Accessor[int] 686 SubsidiaryBoolTagFuncIndex genstruct.Accessor[int] 687 688 Error genstruct.Accessor[internal.ErrorKind] 689 } 690 691 func newToFileResultStruct( 692 encodings []build.Encoding, 693 interpretations []build.Interpretation, 694 forms []build.Form, 695 subsidiaryBoolTagFuncCount int, 696 subsidiaryTagFuncCount int, 697 ) *genToFileResult { 698 r := &genToFileResult{} 699 r.Encoding = genstruct.AddEnum(&r.Struct, encodings, "", "allEncodings", "build.Encoding", nil) 700 r.Interpretation = genstruct.AddEnum(&r.Struct, interpretations, "", "allInterpretations", "build.Interpretation", nil) 701 r.Form = genstruct.AddEnum(&r.Struct, forms, "", "allForms", "build.Form", nil) 702 r.Error = genstruct.AddInt(&r.Struct, internal.NumErrorKinds, "internal.ErrorKind") 703 r.SubsidiaryTagFuncIndex = genstruct.AddInt(&r.Struct, subsidiaryTagFuncCount, "int") 704 r.SubsidiaryBoolTagFuncIndex = genstruct.AddInt(&r.Struct, subsidiaryBoolTagFuncCount, "int") 705 706 return r 707 } 708 709 type dimspace[V any] struct { 710 evaluate func(v V, dim, item int) (V, bool) 711 numDimensions int 712 numValues int 713 yield func(V) bool 714 } 715 716 // walkSpace explores the values that are possible to reach from the given initial 717 // value within the given number of dimensions (numDimensions), where each point in space 718 // can have the given number of possible item values (numItems). 719 // It calls evaluate to derive further values as it walks the space, and 720 // truncates the tree whereever evaluate returns false. 721 // 722 // Note that evaluate will always be called with arguments in the range [0, numDimensions) 723 // and [0, numItems]. 724 // 725 // Note also that this exploration relies on the property that evaluate is commutative; 726 // that is, for a given point in the space, the result does not depend on the path 727 // taken to reach that point. 728 func walkSpace[V any](numDimensions, numValues int, initial V, evaluate func(v V, dim, item int) (V, bool)) iter.Seq[V] { 729 return func(yield func(V) bool) { 730 b := &dimspace[V]{ 731 evaluate: evaluate, 732 numDimensions: numDimensions, 733 numValues: numValues, 734 yield: yield, 735 } 736 b.walk(initial, 0) 737 } 738 } 739 740 func (b *dimspace[V]) walk(v V, maxDim int) bool { 741 if !b.yield(v) { 742 return false 743 } 744 for i := maxDim; i < b.numDimensions; i++ { 745 for j := range b.numValues { 746 if v1, ok := b.evaluate(v, i, j); ok { 747 if !b.walk(v1, i+1) { 748 return false 749 } 750 } 751 } 752 } 753 return true 754 } 755 756 func keys[K, V any](seq iter.Seq2[K, V]) iter.Seq[K] { 757 return func(yield func(K) bool) { 758 for k := range seq { 759 if !yield(k) { 760 return 761 } 762 } 763 } 764 } 765 766 func lookup(v cue.Value, elems ...string) cue.Value { 767 sels := make([]cue.Selector, len(elems)) 768 for i := range elems { 769 sels[i] = cue.Str(elems[i]) 770 } 771 return v.LookupPath(cue.MakePath(sels...)) 772 } 773 774 func allKeys[T ~string](v cue.Value, elems ...string) []T { 775 return slices.Sorted( 776 seqMap(keys(structFields(lookup(v, elems...))), fromString[T]), 777 ) 778 } 779 780 func fileExt(f string) string { 781 if f == "-" { 782 return "-" 783 } 784 e := filepath.Ext(f) 785 if e == "" || e == filepath.Base(f) { 786 return "" 787 } 788 return e 789 } 790 791 func seqMap[T1, T2 any](it iter.Seq[T1], f func(T1) T2) iter.Seq[T2] { 792 return func(yield func(T2) bool) { 793 for t := range it { 794 if !yield(f(t)) { 795 return 796 } 797 } 798 } 799 } 800 801 func fromString[T ~string](s string) T { 802 return T(s) 803 }