github.com/ipld/go-ipld-prime@v0.21.0/schema/gen/go/genUnionReprKeyed.go (about) 1 package gengo 2 3 import ( 4 "io" 5 6 "github.com/ipld/go-ipld-prime/schema" 7 "github.com/ipld/go-ipld-prime/schema/gen/go/mixins" 8 ) 9 10 var _ TypeGenerator = &unionReprKeyedGenerator{} 11 12 // General observation: many things about the keyed representation of unions is *very* similar to the type-level code, 13 // because the type level code effective does espouse keyed-style behavior (just with type names as the keys). 14 // Be advised that this similarity does not hold at *all* true of any of the other representation modes of unions! 15 16 func NewUnionReprKeyedGenerator(pkgName string, typ *schema.TypeUnion, adjCfg *AdjunctCfg) TypeGenerator { 17 return unionReprKeyedGenerator{ 18 unionGenerator{ 19 adjCfg, 20 mixins.MapTraits{ 21 PkgName: pkgName, 22 TypeName: string(typ.Name()), 23 TypeSymbol: adjCfg.TypeSymbol(typ), 24 }, 25 pkgName, 26 typ, 27 }, 28 } 29 } 30 31 type unionReprKeyedGenerator struct { 32 unionGenerator 33 } 34 35 func (g unionReprKeyedGenerator) GetRepresentationNodeGen() NodeGenerator { 36 return unionReprKeyedReprGenerator{ 37 g.AdjCfg, 38 mixins.MapTraits{ 39 PkgName: g.PkgName, 40 TypeName: string(g.Type.Name()) + ".Repr", 41 TypeSymbol: "_" + g.AdjCfg.TypeSymbol(g.Type) + "__Repr", 42 }, 43 g.PkgName, 44 g.Type, 45 } 46 } 47 48 type unionReprKeyedReprGenerator struct { 49 AdjCfg *AdjunctCfg 50 mixins.MapTraits 51 PkgName string 52 Type *schema.TypeUnion 53 } 54 55 func (unionReprKeyedReprGenerator) IsRepr() bool { return true } // hint used in some generalized templates. 56 57 func (g unionReprKeyedReprGenerator) EmitNodeType(w io.Writer) { 58 // The type is structurally the same, but will have a different set of methods. 59 doTemplate(` 60 type _{{ .Type | TypeSymbol }}__Repr _{{ .Type | TypeSymbol }} 61 `, w, g.AdjCfg, g) 62 63 // We do also want some constants for our discriminant values; 64 // they'll make iterators able to work faster. 65 doTemplate(` 66 var ( 67 {{- range $member := .Type.Members }} 68 memberName__{{ dot.Type | TypeSymbol }}_{{ $member.Name }}_serial = _String{"{{ $member | dot.Type.RepresentationStrategy.GetDiscriminant }}"} 69 {{- end }} 70 ) 71 `, w, g.AdjCfg, g) 72 } 73 74 func (g unionReprKeyedReprGenerator) EmitNodeTypeAssertions(w io.Writer) { 75 doTemplate(` 76 var _ datamodel.Node = &_{{ .Type | TypeSymbol }}__Repr{} 77 `, w, g.AdjCfg, g) 78 } 79 80 func (g unionReprKeyedReprGenerator) EmitNodeMethodLookupByString(w io.Writer) { 81 // Similar to the type-level method, except uses discriminant values as keys instead of the member type names. 82 doTemplate(` 83 func (n *_{{ .Type | TypeSymbol }}__Repr) LookupByString(key string) (datamodel.Node, error) { 84 switch key { 85 {{- range $i, $member := .Type.Members }} 86 case "{{ $member | dot.Type.RepresentationStrategy.GetDiscriminant }}": 87 {{- if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "embedAll") }} 88 if n.tag != {{ add $i 1 }} { 89 return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfString(key)} 90 } 91 return n.x{{ add $i 1 }}.Representation(), nil 92 {{- else if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "interface") }} 93 if n2, ok := n.x.({{ $member | TypeSymbol }}); ok { 94 return n2.Representation(), nil 95 } else { 96 return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfString(key)} 97 } 98 {{- end}} 99 {{- end}} 100 default: 101 return nil, schema.ErrNoSuchField{Type: nil /*TODO*/, Field: datamodel.PathSegmentOfString(key)} 102 } 103 } 104 `, w, g.AdjCfg, g) 105 } 106 107 func (g unionReprKeyedReprGenerator) EmitNodeMethodLookupByNode(w io.Writer) { 108 doTemplate(` 109 func (n *_{{ .Type | TypeSymbol }}__Repr) LookupByNode(key datamodel.Node) (datamodel.Node, error) { 110 ks, err := key.AsString() 111 if err != nil { 112 return nil, err 113 } 114 return n.LookupByString(ks) 115 } 116 `, w, g.AdjCfg, g) 117 } 118 119 func (g unionReprKeyedReprGenerator) EmitNodeMethodMapIterator(w io.Writer) { 120 // Similar to the type-level method, except yields discriminant values as keys instead of the member type names. 121 doTemplate(` 122 func (n *_{{ .Type | TypeSymbol }}__Repr) MapIterator() datamodel.MapIterator { 123 return &_{{ .Type | TypeSymbol }}__ReprMapItr{n, false} 124 } 125 126 type _{{ .Type | TypeSymbol }}__ReprMapItr struct { 127 n *_{{ .Type | TypeSymbol }}__Repr 128 done bool 129 } 130 131 func (itr *_{{ .Type | TypeSymbol }}__ReprMapItr) Next() (k datamodel.Node, v datamodel.Node, _ error) { 132 if itr.done { 133 return nil, nil, datamodel.ErrIteratorOverread{} 134 } 135 {{- if (eq (.AdjCfg.UnionMemlayout .Type) "embedAll") }} 136 switch itr.n.tag { 137 {{- range $i, $member := .Type.Members }} 138 case {{ add $i 1 }}: 139 k, v = &memberName__{{ dot.Type | TypeSymbol }}_{{ $member.Name }}_serial, itr.n.x{{ add $i 1 }}.Representation() 140 {{- end}} 141 {{- else if (eq (.AdjCfg.UnionMemlayout .Type) "interface") }} 142 switch n2 := itr.n.x.(type) { 143 {{- range $member := .Type.Members }} 144 case {{ $member | TypeSymbol }}: 145 k, v = &memberName__{{ dot.Type | TypeSymbol }}_{{ $member.Name }}_serial, n2.Representation() 146 {{- end}} 147 {{- end}} 148 default: 149 panic("unreachable") 150 } 151 itr.done = true 152 return 153 } 154 func (itr *_{{ .Type | TypeSymbol }}__ReprMapItr) Done() bool { 155 return itr.done 156 } 157 158 `, w, g.AdjCfg, g) 159 } 160 161 func (g unionReprKeyedReprGenerator) EmitNodeMethodLength(w io.Writer) { 162 doTemplate(` 163 func (_{{ .Type | TypeSymbol }}__Repr) Length() int64 { 164 return 1 165 } 166 `, w, g.AdjCfg, g) 167 } 168 169 func (g unionReprKeyedReprGenerator) EmitNodeMethodPrototype(w io.Writer) { 170 emitNodeMethodPrototype_typical(w, g.AdjCfg, g) 171 } 172 173 func (g unionReprKeyedReprGenerator) EmitNodePrototypeType(w io.Writer) { 174 emitNodePrototypeType_typical(w, g.AdjCfg, g) 175 } 176 177 // --- NodeBuilder and NodeAssembler ---> 178 179 func (g unionReprKeyedReprGenerator) GetNodeBuilderGenerator() NodeBuilderGenerator { 180 return unionReprKeyedReprBuilderGenerator{ 181 g.AdjCfg, 182 mixins.MapAssemblerTraits{ 183 PkgName: g.PkgName, 184 TypeName: g.TypeName, 185 AppliedPrefix: "_" + g.AdjCfg.TypeSymbol(g.Type) + "__Repr", 186 }, 187 g.PkgName, 188 g.Type, 189 } 190 } 191 192 type unionReprKeyedReprBuilderGenerator struct { 193 AdjCfg *AdjunctCfg 194 mixins.MapAssemblerTraits 195 PkgName string 196 Type *schema.TypeUnion 197 } 198 199 func (unionReprKeyedReprBuilderGenerator) IsRepr() bool { return true } // hint used in some generalized templates. 200 201 func (g unionReprKeyedReprBuilderGenerator) EmitNodeBuilderType(w io.Writer) { 202 emitEmitNodeBuilderType_typical(w, g.AdjCfg, g) 203 } 204 func (g unionReprKeyedReprBuilderGenerator) EmitNodeBuilderMethods(w io.Writer) { 205 emitNodeBuilderMethods_typical(w, g.AdjCfg, g) 206 } 207 func (g unionReprKeyedReprBuilderGenerator) EmitNodeAssemblerType(w io.Writer) { 208 // Nearly identical to the type-level system, except it embeds the Repr variant of child assemblers 209 // (which is a very minor difference textually, but means this structure can end up with a pretty wildly different resident memory size than the type-level one). 210 doTemplate(` 211 type _{{ .Type | TypeSymbol }}__ReprAssembler struct { 212 w *_{{ .Type | TypeSymbol }} 213 m *schema.Maybe 214 state maState 215 216 cm schema.Maybe 217 {{- range $i, $member := .Type.Members }} 218 ca{{ add $i 1 }} {{ if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "interface") }}*{{end}}_{{ $member | TypeSymbol }}__ReprAssembler 219 {{end -}} 220 ca uint 221 } 222 `, w, g.AdjCfg, g) 223 224 // Reset methods: also nearly identical to the type-level ones. 225 doTemplate(` 226 func (na *_{{ .Type | TypeSymbol }}__ReprAssembler) reset() { 227 na.state = maState_initial 228 switch na.ca { 229 case 0: 230 return 231 {{- range $i, $member := .Type.Members }} 232 case {{ add $i 1 }}: 233 na.ca{{ add $i 1 }}.reset() 234 {{end -}} 235 default: 236 panic("unreachable") 237 } 238 na.ca = 0 239 na.cm = schema.Maybe_Absent 240 } 241 `, w, g.AdjCfg, g) 242 } 243 func (g unionReprKeyedReprBuilderGenerator) EmitNodeAssemblerMethodBeginMap(w io.Writer) { 244 emitNodeAssemblerMethodBeginMap_strictoid(w, g.AdjCfg, g) 245 } 246 func (g unionReprKeyedReprBuilderGenerator) EmitNodeAssemblerMethodAssignNull(w io.Writer) { 247 // It might sound a bit odd to call a union "recursive", since it's so very trivially so (no fan-out), 248 // but it's functionally accurate: the generated method should include a branch for the 'midvalue' state. 249 emitNodeAssemblerMethodAssignNull_recursive(w, g.AdjCfg, g) 250 } 251 func (g unionReprKeyedReprBuilderGenerator) EmitNodeAssemblerMethodAssignNode(w io.Writer) { 252 // DRY: this is once again not-coincidentally very nearly equal to the type-level method. Would be good to dedup them... after we do the get-to-the-point-in-phase-3 improvement. 253 doTemplate(` 254 func (na *_{{ .Type | TypeSymbol }}__ReprAssembler) AssignNode(v datamodel.Node) error { 255 if v.IsNull() { 256 return na.AssignNull() 257 } 258 if v2, ok := v.(*_{{ .Type | TypeSymbol }}); ok { 259 switch *na.m { 260 case schema.Maybe_Value, schema.Maybe_Null: 261 panic("invalid state: cannot assign into assembler that's already finished") 262 case midvalue: 263 panic("invalid state: cannot assign null into an assembler that's already begun working on recursive structures!") 264 } 265 {{- if .Type | MaybeUsesPtr }} 266 if na.w == nil { 267 na.w = v2 268 *na.m = schema.Maybe_Value 269 return nil 270 } 271 {{- end}} 272 *na.w = *v2 273 *na.m = schema.Maybe_Value 274 return nil 275 } 276 if v.Kind() != datamodel.Kind_Map { 277 return datamodel.ErrWrongKind{TypeName: "{{ .PkgName }}.{{ .Type.Name }}.Repr", MethodName: "AssignNode", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: v.Kind()} 278 } 279 itr := v.MapIterator() 280 for !itr.Done() { 281 k, v, err := itr.Next() 282 if err != nil { 283 return err 284 } 285 if err := na.AssembleKey().AssignNode(k); err != nil { 286 return err 287 } 288 if err := na.AssembleValue().AssignNode(v); err != nil { 289 return err 290 } 291 } 292 return na.Finish() 293 } 294 `, w, g.AdjCfg, g) 295 } 296 func (g unionReprKeyedReprBuilderGenerator) EmitNodeAssemblerOtherBits(w io.Writer) { 297 g.emitMapAssemblerChildTidyHelper(w) 298 g.emitMapAssemblerMethods(w) 299 g.emitKeyAssembler(w) 300 } 301 func (g unionReprKeyedReprBuilderGenerator) emitMapAssemblerChildTidyHelper(w io.Writer) { 302 // Nearly identical to the type-level equivalent. 303 doTemplate(` 304 func (ma *_{{ .Type | TypeSymbol }}__ReprAssembler) valueFinishTidy() bool { 305 switch ma.cm { 306 case schema.Maybe_Value: 307 {{- /* nothing to do for memlayout=embedAll; the tag is already set and memory already in place. */ -}} 308 {{- /* nothing to do for memlayout=interface either; same story, the values are already in place. */ -}} 309 ma.state = maState_initial 310 return true 311 default: 312 return false 313 } 314 } 315 `, w, g.AdjCfg, g) 316 } 317 func (g unionReprKeyedReprBuilderGenerator) emitMapAssemblerMethods(w io.Writer) { 318 // All of these: shamelessly similar to the type-level equivalent, modulo a few appearances of "Repr". 319 // Alright, and also the "discriminant values as keys instead of the member type names" thing. 320 // DRY: the number of times these `ma.state` switches are appearing is truly intense! This is starting to look like one of them most important things to shrink the GSLOC/ASM size of! 321 322 doTemplate(` 323 func (ma *_{{ .Type | TypeSymbol }}__ReprAssembler) AssembleEntry(k string) (datamodel.NodeAssembler, error) { 324 switch ma.state { 325 case maState_initial: 326 // carry on 327 case maState_midKey: 328 panic("invalid state: AssembleEntry cannot be called when in the middle of assembling another key") 329 case maState_expectValue: 330 panic("invalid state: AssembleEntry cannot be called when expecting start of value assembly") 331 case maState_midValue: 332 if !ma.valueFinishTidy() { 333 panic("invalid state: AssembleEntry cannot be called when in the middle of assembling a value") 334 } // if tidy success: carry on for the moment, but we'll still be erroring shortly. 335 case maState_finished: 336 panic("invalid state: AssembleEntry cannot be called on an assembler that's already finished") 337 } 338 if ma.ca != 0 { 339 return nil, schema.ErrNotUnionStructure{TypeName:"{{ .PkgName }}.{{ .Type.Name }}.Repr", Detail: "cannot add another entry -- a union can only contain one thing!"} 340 } 341 {{- if .Type.Members }} 342 switch k { 343 {{- range $i, $member := .Type.Members }} 344 case "{{ $member | dot.Type.RepresentationStrategy.GetDiscriminant }}": 345 ma.state = maState_midValue 346 ma.ca = {{ add $i 1 }} 347 {{- if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "embedAll") }} 348 ma.w.tag = {{ add $i 1 }} 349 ma.ca{{ add $i 1 }}.w = &ma.w.x{{ add $i 1 }} 350 ma.ca{{ add $i 1 }}.m = &ma.cm 351 return &ma.ca{{ add $i 1 }}, nil 352 {{- else if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "interface") }} 353 x := &_{{ $member | TypeSymbol }}{} 354 ma.w.x = x 355 if ma.ca{{ add $i 1 }} == nil { 356 ma.ca{{ add $i 1 }} = &_{{ $member | TypeSymbol }}__ReprAssembler{} 357 } 358 ma.ca{{ add $i 1 }}.w = x 359 ma.ca{{ add $i 1 }}.m = &ma.cm 360 return ma.ca{{ add $i 1 }}, nil 361 {{- end}} 362 {{- end}} 363 } 364 {{- end}} 365 return nil, schema.ErrInvalidKey{TypeName:"{{ .PkgName }}.{{ .Type.Name }}.Repr", Key:&_String{k}} 366 } 367 `, w, g.AdjCfg, g) 368 369 doTemplate(` 370 func (ma *_{{ .Type | TypeSymbol }}__ReprAssembler) AssembleKey() datamodel.NodeAssembler { 371 switch ma.state { 372 case maState_initial: 373 // carry on 374 case maState_midKey: 375 panic("invalid state: AssembleKey cannot be called when in the middle of assembling another key") 376 case maState_expectValue: 377 panic("invalid state: AssembleKey cannot be called when expecting start of value assembly") 378 case maState_midValue: 379 if !ma.valueFinishTidy() { 380 panic("invalid state: AssembleKey cannot be called when in the middle of assembling a value") 381 } // if tidy success: carry on for the moment, but we'll still be erroring shortly... or rather, the keyassembler will be. 382 case maState_finished: 383 panic("invalid state: AssembleKey cannot be called on an assembler that's already finished") 384 } 385 ma.state = maState_midKey 386 return (*_{{ .Type | TypeSymbol }}__ReprKeyAssembler)(ma) 387 } 388 `, w, g.AdjCfg, g) 389 390 doTemplate(` 391 func (ma *_{{ .Type | TypeSymbol }}__ReprAssembler) AssembleValue() datamodel.NodeAssembler { 392 switch ma.state { 393 case maState_initial: 394 panic("invalid state: AssembleValue cannot be called when no key is primed") 395 case maState_midKey: 396 panic("invalid state: AssembleValue cannot be called when in the middle of assembling a key") 397 case maState_expectValue: 398 // carry on 399 case maState_midValue: 400 panic("invalid state: AssembleValue cannot be called when in the middle of assembling another value") 401 case maState_finished: 402 panic("invalid state: AssembleValue cannot be called on an assembler that's already finished") 403 } 404 ma.state = maState_midValue 405 switch ma.ca { 406 {{- range $i, $member := .Type.Members }} 407 case {{ add $i 1 }}: 408 {{- if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "embedAll") }} 409 ma.ca{{ add $i 1 }}.w = &ma.w.x{{ add $i 1 }} 410 ma.ca{{ add $i 1 }}.m = &ma.cm 411 return &ma.ca{{ add $i 1 }} 412 {{- else if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "interface") }} 413 x := &_{{ $member | TypeSymbol }}{} 414 ma.w.x = x 415 if ma.ca{{ add $i 1 }} == nil { 416 ma.ca{{ add $i 1 }} = &_{{ $member | TypeSymbol }}__ReprAssembler{} 417 } 418 ma.ca{{ add $i 1 }}.w = x 419 ma.ca{{ add $i 1 }}.m = &ma.cm 420 return ma.ca{{ add $i 1 }} 421 {{- end}} 422 {{- end}} 423 default: 424 panic("unreachable") 425 } 426 } 427 `, w, g.AdjCfg, g) 428 429 doTemplate(` 430 func (ma *_{{ .Type | TypeSymbol }}__ReprAssembler) Finish() error { 431 switch ma.state { 432 case maState_initial: 433 // carry on 434 case maState_midKey: 435 panic("invalid state: Finish cannot be called when in the middle of assembling a key") 436 case maState_expectValue: 437 panic("invalid state: Finish cannot be called when expecting start of value assembly") 438 case maState_midValue: 439 if !ma.valueFinishTidy() { 440 panic("invalid state: Finish cannot be called when in the middle of assembling a value") 441 } // if tidy success: carry on 442 case maState_finished: 443 panic("invalid state: Finish cannot be called on an assembler that's already finished") 444 } 445 if ma.ca == 0 { 446 return schema.ErrNotUnionStructure{TypeName:"{{ .PkgName }}.{{ .Type.Name }}.Repr", Detail: "a union must have exactly one entry (not none)!"} 447 } 448 ma.state = maState_finished 449 *ma.m = schema.Maybe_Value 450 return nil 451 } 452 `, w, g.AdjCfg, g) 453 454 doTemplate(` 455 func (ma *_{{ .Type | TypeSymbol }}__ReprAssembler) KeyPrototype() datamodel.NodePrototype { 456 return _String__Prototype{} 457 } 458 func (ma *_{{ .Type | TypeSymbol }}__ReprAssembler) ValuePrototype(k string) datamodel.NodePrototype { 459 switch k { 460 {{- range $i, $member := .Type.Members }} 461 case "{{ $member.Name }}": 462 return _{{ $member | TypeSymbol }}__ReprPrototype{} 463 {{- end}} 464 default: 465 return nil 466 } 467 } 468 `, w, g.AdjCfg, g) 469 } 470 func (g unionReprKeyedReprBuilderGenerator) emitKeyAssembler(w io.Writer) { 471 doTemplate(` 472 type _{{ .Type | TypeSymbol }}__ReprKeyAssembler _{{ .Type | TypeSymbol }}__ReprAssembler 473 `, w, g.AdjCfg, g) 474 stubs := mixins.StringAssemblerTraits{ 475 PkgName: g.PkgName, 476 TypeName: g.TypeName + ".KeyAssembler", // ".Repr" is already in `g.TypeName`, so don't stutter the "Repr" part. 477 AppliedPrefix: "_" + g.AdjCfg.TypeSymbol(g.Type) + "__ReprKey", 478 } 479 // This key assembler can disregard any idea of complex keys because we know that our discriminants are just strings! 480 stubs.EmitNodeAssemblerMethodBeginMap(w) 481 stubs.EmitNodeAssemblerMethodBeginList(w) 482 stubs.EmitNodeAssemblerMethodAssignNull(w) 483 stubs.EmitNodeAssemblerMethodAssignBool(w) 484 stubs.EmitNodeAssemblerMethodAssignInt(w) 485 stubs.EmitNodeAssemblerMethodAssignFloat(w) 486 doTemplate(` 487 func (ka *_{{ .Type | TypeSymbol }}__ReprKeyAssembler) AssignString(k string) error { 488 if ka.state != maState_midKey { 489 panic("misuse: KeyAssembler held beyond its valid lifetime") 490 } 491 if ka.ca != 0 { 492 return schema.ErrNotUnionStructure{TypeName:"{{ .PkgName }}.{{ .Type.Name }}.Repr", Detail: "cannot add another entry -- a union can only contain one thing!"} 493 } 494 switch k { 495 {{- range $i, $member := .Type.Members }} 496 case "{{ $member | dot.Type.RepresentationStrategy.GetDiscriminant }}": 497 ka.ca = {{ add $i 1 }} 498 {{- if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "embedAll") }} 499 ka.w.tag = {{ add $i 1 }} 500 {{- end}} 501 ka.state = maState_expectValue 502 return nil 503 {{- end}} 504 } 505 return schema.ErrInvalidKey{TypeName:"{{ .PkgName }}.{{ .Type.Name }}.Repr", Key:&_String{k}} // TODO: error quality: ErrInvalidUnionDiscriminant ? 506 } 507 `, w, g.AdjCfg, g) 508 stubs.EmitNodeAssemblerMethodAssignBytes(w) 509 stubs.EmitNodeAssemblerMethodAssignLink(w) 510 doTemplate(` 511 func (ka *_{{ .Type | TypeSymbol }}__ReprKeyAssembler) AssignNode(v datamodel.Node) error { 512 if v2, err := v.AsString(); err != nil { 513 return err 514 } else { 515 return ka.AssignString(v2) 516 } 517 } 518 func (_{{ .Type | TypeSymbol }}__ReprKeyAssembler) Prototype() datamodel.NodePrototype { 519 return _String__Prototype{} 520 } 521 `, w, g.AdjCfg, g) 522 }