github.com/ipld/go-ipld-prime@v0.21.0/schema/gen/go/genUnion.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 // The generator for unions is a bit more wild than most others: 11 // it has at three major branches for how its internals are laid out: 12 // 13 // - all possible children are embedded. 14 // - all possible children are pointers... in which case we collapse to one interface resident. 15 // (n.b. this does give up some inlining potential as well as gives up on alloc amortization, but it does make resident memory size minimal.) 16 // - some children are emebedded and some are pointers, and of the latter set, they may be either in one interface field or several discrete pointers. 17 // (discrete fields of pointer type makes inlining possible in some paths, whereas an interface field blocks it). 18 // 19 // ... We're not doing that last one at all right now. The pareto-prevalence of these concerns is extremely low compared to the effort required. 20 // But the first two are both very reasonable, and both are often wanted. 21 // 22 // These choices are made from adjunct config (which should make sense, because they're clearly all "golang" details -- not type semantics). 23 // We still tackle all the generation for all these strategies this in one file, 24 // because all of the interfaces we export are the same, regardless of the internals (and it just seems easiest to do this way). 25 26 type unionGenerator struct { 27 AdjCfg *AdjunctCfg 28 mixins.MapTraits 29 PkgName string 30 Type *schema.TypeUnion 31 } 32 33 func (unionGenerator) IsRepr() bool { return false } // hint used in some generalized templates. 34 35 // --- native content and specializations ---> 36 37 func (g unionGenerator) EmitNativeType(w io.Writer) { 38 // We generate *two* types: a struct which acts as the union node, 39 // and also an interface which covers the members (and has an unexported marker function to make sure the set can't be extended). 40 // 41 // The interface *mostly* isn't used... except for in the return type of a speciated function which can be used to do golang-native type switches. 42 // 43 // The interface also includes a requirement for an errorless primitive access method (such as `String() string`) 44 // if our representation strategy is one that has that semantic (e.g., stringprefix repr does). 45 // 46 // A note about index: in all cases the index of a member type is used, we increment it by one, to avoid using zero. 47 // We do this because it's desirable to reserve the zero in the 'tag' field (if we generate one) as a sentinel value 48 // (see further comments in the EmitNodeAssemblerType function); 49 // and since we do it in that one case, it's just as well to do it uniformly. 50 doTemplate(` 51 {{- if Comments -}} 52 // {{ .Type | TypeSymbol }} matches the IPLD Schema type "{{ .Type.Name }}". 53 // {{ .Type | TypeSymbol }} has {{ .Type.TypeKind }} typekind, which means its data model behaviors are that of a {{ .Kind }} kind. 54 {{- end}} 55 type {{ .Type | TypeSymbol }} = *_{{ .Type | TypeSymbol }} 56 type _{{ .Type | TypeSymbol }} struct { 57 {{- if (eq (.AdjCfg.UnionMemlayout .Type) "embedAll") }} 58 tag uint 59 {{- range $i, $member := .Type.Members }} 60 x{{ add $i 1 }} _{{ $member | TypeSymbol }} 61 {{- end}} 62 {{- else if (eq (.AdjCfg.UnionMemlayout .Type) "interface") }} 63 x _{{ .Type | TypeSymbol }}__iface 64 {{- end}} 65 } 66 type _{{ .Type | TypeSymbol }}__iface interface { 67 _{{ .Type | TypeSymbol }}__member() 68 {{- if (eq (.Type.RepresentationStrategy | printf "%T") "schema.UnionRepresentation_Stringprefix") }} 69 String() string 70 {{- end}} 71 } 72 73 {{- range $member := .Type.Members }} 74 func (_{{ $member | TypeSymbol }}) _{{ dot.Type | TypeSymbol }}__member() {} 75 {{- end}} 76 `, w, g.AdjCfg, g) 77 } 78 79 func (g unionGenerator) EmitNativeAccessors(w io.Writer) { 80 doTemplate(` 81 func (n _{{ .Type | TypeSymbol }}) AsInterface() _{{ .Type | TypeSymbol }}__iface { 82 {{- if (eq (.AdjCfg.UnionMemlayout .Type) "embedAll") }} 83 switch n.tag { 84 {{- range $i, $member := .Type.Members }} 85 case {{ add $i 1 }}: 86 return &n.x{{ add $i 1 }} 87 {{- end}} 88 default: 89 panic("invalid union state; how did you create this object?") 90 } 91 {{- else if (eq (.AdjCfg.UnionMemlayout .Type) "interface") }} 92 return n.x 93 {{- end}} 94 } 95 `, w, g.AdjCfg, g) 96 } 97 98 func (g unionGenerator) EmitNativeBuilder(w io.Writer) { 99 // Unclear as yet what should go here. 100 } 101 102 func (g unionGenerator) EmitNativeMaybe(w io.Writer) { 103 emitNativeMaybe(w, g.AdjCfg, g) 104 } 105 106 // --- type info ---> 107 108 func (g unionGenerator) EmitTypeConst(w io.Writer) { 109 doTemplate(` 110 // TODO EmitTypeConst 111 `, w, g.AdjCfg, g) 112 } 113 114 // --- TypedNode interface satisfaction ---> 115 116 func (g unionGenerator) EmitTypedNodeMethodType(w io.Writer) { 117 doTemplate(` 118 func ({{ .Type | TypeSymbol }}) Type() schema.Type { 119 return nil /*TODO:typelit*/ 120 } 121 `, w, g.AdjCfg, g) 122 } 123 124 func (g unionGenerator) EmitTypedNodeMethodRepresentation(w io.Writer) { 125 emitTypicalTypedNodeMethodRepresentation(w, g.AdjCfg, g) 126 } 127 128 // --- Node interface satisfaction ---> 129 130 func (g unionGenerator) EmitNodeType(w io.Writer) { 131 // No additional types needed. Methods all attach to the native type. 132 133 // We do, however, want some constants for our member names; 134 // they'll make iterators able to work faster. So let's emit those. 135 // These are a bit perplexing, because they're... type names. 136 // However, oddly enough, we don't have type names available *as nodes* anywhere else centrally available, 137 // so... we generate some values for them here with scoped identifers and get on with it. 138 // Maybe this could be elided with future work. 139 doTemplate(` 140 var ( 141 {{- range $member := .Type.Members }} 142 memberName__{{ dot.Type | TypeSymbol }}_{{ $member.Name }} = _String{"{{ $member.Name }}"} 143 {{- end }} 144 ) 145 `, w, g.AdjCfg, g) 146 } 147 148 func (g unionGenerator) EmitNodeTypeAssertions(w io.Writer) { 149 emitNodeTypeAssertions_typical(w, g.AdjCfg, g) 150 } 151 152 func (g unionGenerator) EmitNodeMethodLookupByString(w io.Writer) { 153 doTemplate(` 154 func (n {{ .Type | TypeSymbol }}) LookupByString(key string) (datamodel.Node, error) { 155 switch key { 156 {{- range $i, $member := .Type.Members }} 157 case "{{ $member.Name }}": 158 {{- if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "embedAll") }} 159 if n.tag != {{ add $i 1 }} { 160 return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfString(key)} 161 } 162 return &n.x{{ add $i 1 }}, nil 163 {{- else if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "interface") }} 164 if n2, ok := n.x.({{ $member | TypeSymbol }}); ok { 165 return n2, nil 166 } else { 167 return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfString(key)} 168 } 169 {{- end}} 170 {{- end}} 171 default: 172 return nil, schema.ErrNoSuchField{Type: nil /*TODO*/, Field: datamodel.PathSegmentOfString(key)} 173 } 174 } 175 `, w, g.AdjCfg, g) 176 } 177 178 func (g unionGenerator) EmitNodeMethodLookupByNode(w io.Writer) { 179 doTemplate(` 180 func (n {{ .Type | TypeSymbol }}) LookupByNode(key datamodel.Node) (datamodel.Node, error) { 181 ks, err := key.AsString() 182 if err != nil { 183 return nil, err 184 } 185 return n.LookupByString(ks) 186 } 187 `, w, g.AdjCfg, g) 188 } 189 190 func (g unionGenerator) EmitNodeMethodMapIterator(w io.Writer) { 191 // This is kind of a hilarious "iterator": it has to count all the way up to... 1. 192 doTemplate(` 193 func (n {{ .Type | TypeSymbol }}) MapIterator() datamodel.MapIterator { 194 return &_{{ .Type | TypeSymbol }}__MapItr{n, false} 195 } 196 197 type _{{ .Type | TypeSymbol }}__MapItr struct { 198 n {{ .Type | TypeSymbol }} 199 done bool 200 } 201 202 func (itr *_{{ .Type | TypeSymbol }}__MapItr) Next() (k datamodel.Node, v datamodel.Node, _ error) { 203 if itr.done { 204 return nil, nil, datamodel.ErrIteratorOverread{} 205 } 206 {{- if (eq (.AdjCfg.UnionMemlayout .Type) "embedAll") }} 207 switch itr.n.tag { 208 {{- range $i, $member := .Type.Members }} 209 case {{ add $i 1 }}: 210 k, v = &memberName__{{ dot.Type | TypeSymbol }}_{{ $member.Name }}, &itr.n.x{{ add $i 1 }} 211 {{- end}} 212 {{- else if (eq (.AdjCfg.UnionMemlayout .Type) "interface") }} 213 switch n2 := itr.n.x.(type) { 214 {{- range $member := .Type.Members }} 215 case {{ $member | TypeSymbol }}: 216 k, v = &memberName__{{ dot.Type | TypeSymbol }}_{{ $member.Name }}, n2 217 {{- end}} 218 {{- end}} 219 default: 220 panic("unreachable") 221 } 222 itr.done = true 223 return 224 } 225 func (itr *_{{ .Type | TypeSymbol }}__MapItr) Done() bool { 226 return itr.done 227 } 228 229 `, w, g.AdjCfg, g) 230 } 231 232 func (g unionGenerator) EmitNodeMethodLength(w io.Writer) { 233 doTemplate(` 234 func ({{ .Type | TypeSymbol }}) Length() int64 { 235 return 1 236 } 237 `, w, g.AdjCfg, g) 238 } 239 240 func (g unionGenerator) EmitNodeMethodPrototype(w io.Writer) { 241 emitNodeMethodPrototype_typical(w, g.AdjCfg, g) 242 } 243 244 func (g unionGenerator) EmitNodePrototypeType(w io.Writer) { 245 emitNodePrototypeType_typical(w, g.AdjCfg, g) 246 } 247 248 // --- NodeBuilder and NodeAssembler ---> 249 250 func (g unionGenerator) GetNodeBuilderGenerator() NodeBuilderGenerator { 251 return unionBuilderGenerator{ 252 g.AdjCfg, 253 mixins.MapAssemblerTraits{ 254 PkgName: g.PkgName, 255 TypeName: g.TypeName, 256 AppliedPrefix: "_" + g.AdjCfg.TypeSymbol(g.Type) + "__", 257 }, 258 g.PkgName, 259 g.Type, 260 } 261 } 262 263 type unionBuilderGenerator struct { 264 AdjCfg *AdjunctCfg 265 mixins.MapAssemblerTraits 266 PkgName string 267 Type *schema.TypeUnion 268 } 269 270 func (unionBuilderGenerator) IsRepr() bool { return false } // hint used in some generalized templates. 271 272 func (g unionBuilderGenerator) EmitNodeBuilderType(w io.Writer) { 273 emitEmitNodeBuilderType_typical(w, g.AdjCfg, g) 274 } 275 func (g unionBuilderGenerator) EmitNodeBuilderMethods(w io.Writer) { 276 emitNodeBuilderMethods_typical(w, g.AdjCfg, g) 277 } 278 func (g unionBuilderGenerator) EmitNodeAssemblerType(w io.Writer) { 279 // Assemblers for unions are not unlikely those for structs or maps: 280 // 281 // - 'w' is the "**w**ip" pointer. 282 // - 'm' is the pointer to a **m**aybe which communicates our completeness to the parent if we're a child assembler. 283 // Like any other structure, a union can be nullable in the context of some enclosing object, and we'll have the usual branches for handling that in our various Assign methods. 284 // - 'state' is what it says on the tin. Unions use maState to sequence the transitions between a new assembler, the map having been started, key insertions, value insertions, and finish. 285 // Most of this is just like the way struct and map use maState. 286 // However, we also need to guard to make sure a second entry never begins; after the first, finish is the *only* valid transition. 287 // In structs, this is done using the "set" bitfield; in maps, the state resides in the wip map itself. 288 // Unions are more like the latter: depending on which memory layout we're using, either the `na.w.tag` value, or, a non-nil `na.w.x`, is indicative that one key has been entered. 289 // (The zero value for `na.w.tag` is reserved, and all for this reason. 290 // - There is no additional state need to store "focus" (in contrast to structs); 291 // information during the AssembleValue phase about which member is selected is also just handled in `na.w.tag`, or, in the type info of `na.w.x`, again depending on memory layout strategy. 292 // (This is subverted a bit by the 'ca' field, however... which effectively mirrors `na.w.tag`, and is only active in the resetting process, but is necessary because it outlives its twin inside 'w'.) 293 // 294 // - 'cm' is **c**hild **m**aybe and is used for the completion message from children. 295 // - 'ca*' fields embed **c**hild **a**ssemblers -- these are embedded so we can yield pointers to them during recusion into child value assembly without causing new allocations. 296 // In unions, only one of these will every be used! However, we don't know *which one* in advance, so, we have to embed them all. 297 // (It's ironic to note that if the golang compiler had an understanding of unions itself (either tagged or untagged would suffice), we could compile this down into *much* more minimal amounts of resident memory reservation. Alas!) 298 // The 'ca*' fields are pointers (and allocated on demand) instead of embeds for unions with memlayout=interface mode. (Arguably, this is overloading that config; PRs for more granular configurability welcome.) 299 // - 'ca' (with no further suffix) identifies which child assembler was previously used. 300 // This is for minimizing the amount of work that resetting has to do: it will only recurse into resetting that child assembler. 301 doTemplate(` 302 type _{{ .Type | TypeSymbol }}__Assembler struct { 303 w *_{{ .Type | TypeSymbol }} 304 m *schema.Maybe 305 state maState 306 307 cm schema.Maybe 308 {{- range $i, $member := .Type.Members }} 309 ca{{ add $i 1 }} {{ if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "interface") }}*{{end}}_{{ $member | TypeSymbol }}__Assembler 310 {{end -}} 311 ca uint 312 } 313 `, w, g.AdjCfg, g) 314 315 // Reset methods for unions are a tad more involved than for most other assemblers: 316 // we only want to bother to reset whichever child assembler (if any) we actually used last. 317 // We *could* blithely reset *all* child assemblers every time; but, trading an extra bit of state in our assembler 318 // for the privledge of trimming off a potentially sizable amount of unnecessary zeroing efforts seems preferrable. 319 // Also, although go syntax makes it not textually obvious here, note that it's possible for the child assemblers to be either pointers or embeds: 320 // on consequence of this is that just zeroing this struct would be both unreliable and undesirable in the pointer case 321 // (it would leave orphan child assemblers that might still have pointers into us, which could be guarded against but is nonetheless is considerably scary in complexity; 322 // and it would also mean that we can't keep ahold of the child assemblers across resets and thus amortize allocations, which... is the whole reason the reset system exists in the first place). 323 doTemplate(` 324 func (na *_{{ .Type | TypeSymbol }}__Assembler) reset() { 325 na.state = maState_initial 326 switch na.ca { 327 case 0: 328 return 329 {{- range $i, $member := .Type.Members }} 330 case {{ add $i 1 }}: 331 na.ca{{ add $i 1 }}.reset() 332 {{end -}} 333 default: 334 panic("unreachable") 335 } 336 na.ca = 0 337 na.cm = schema.Maybe_Absent 338 } 339 `, w, g.AdjCfg, g) 340 } 341 func (g unionBuilderGenerator) EmitNodeAssemblerMethodBeginMap(w io.Writer) { 342 emitNodeAssemblerMethodBeginMap_strictoid(w, g.AdjCfg, g) 343 } 344 func (g unionBuilderGenerator) EmitNodeAssemblerMethodAssignNull(w io.Writer) { 345 // It might sound a bit odd to call a union "recursive", since it's so very trivially so (no fan-out), 346 // but it's functionally accurate: the generated method should include a branch for the 'midvalue' state. 347 emitNodeAssemblerMethodAssignNull_recursive(w, g.AdjCfg, g) 348 } 349 func (g unionBuilderGenerator) EmitNodeAssemblerMethodAssignNode(w io.Writer) { 350 // AssignNode goes through three phases: 351 // 1. is it null? Jump over to AssignNull (which may or may not reject it). 352 // 2. is it our own type? Handle specially -- we might be able to do efficient things. 353 // 3. is it the right kind to morph into us? Do so. 354 // 355 // We do not set m=midvalue in phase 3 -- it shouldn't matter unless you're trying to pull off concurrent access, which is wrong and unsafe regardless. 356 // 357 // DRY: this turns out to be textually identical to the method for structs! (At least, for now. It could/should probably be optimized to get to the point faster in phase 3.) 358 doTemplate(` 359 func (na *_{{ .Type | TypeSymbol }}__Assembler) AssignNode(v datamodel.Node) error { 360 if v.IsNull() { 361 return na.AssignNull() 362 } 363 if v2, ok := v.(*_{{ .Type | TypeSymbol }}); ok { 364 switch *na.m { 365 case schema.Maybe_Value, schema.Maybe_Null: 366 panic("invalid state: cannot assign into assembler that's already finished") 367 case midvalue: 368 panic("invalid state: cannot assign null into an assembler that's already begun working on recursive structures!") 369 } 370 {{- if .Type | MaybeUsesPtr }} 371 if na.w == nil { 372 na.w = v2 373 *na.m = schema.Maybe_Value 374 return nil 375 } 376 {{- end}} 377 *na.w = *v2 378 *na.m = schema.Maybe_Value 379 return nil 380 } 381 if v.Kind() != datamodel.Kind_Map { 382 return datamodel.ErrWrongKind{TypeName: "{{ .PkgName }}.{{ .Type.Name }}", MethodName: "AssignNode", AppropriateKind: datamodel.KindSet_JustMap, ActualKind: v.Kind()} 383 } 384 itr := v.MapIterator() 385 for !itr.Done() { 386 k, v, err := itr.Next() 387 if err != nil { 388 return err 389 } 390 if err := na.AssembleKey().AssignNode(k); err != nil { 391 return err 392 } 393 if err := na.AssembleValue().AssignNode(v); err != nil { 394 return err 395 } 396 } 397 return na.Finish() 398 } 399 `, w, g.AdjCfg, g) 400 } 401 func (g unionBuilderGenerator) EmitNodeAssemblerOtherBits(w io.Writer) { 402 g.emitMapAssemblerChildTidyHelper(w) 403 g.emitMapAssemblerMethods(w) 404 g.emitKeyAssembler(w) 405 } 406 func (g unionBuilderGenerator) emitMapAssemblerChildTidyHelper(w io.Writer) { 407 // This function attempts to clean up the state machine to acknolwedge child assembly finish. 408 // If the child was finished and we just collected it, return true and update state to maState_initial. 409 // Otherwise, if it wasn't done, return false; 410 // and the caller is almost certain to emit an error momentarily. 411 // The function will only be called when the current state is maState_midValue. 412 // (In general, the idea is that if the user is doing things correctly, 413 // this function will only be called when the child is in fact finished.) 414 // This is a *lot* simpler than the tidy behaviors needed for any of the other recursive kinds: 415 // unions don't allow either nullable nor optional members, so there's no need to process anything except Maybe_Value state, 416 // and the lack of need to consider nullable nor optionals also means we never need to worry about moving memory in the case of MaybeUsePtr modes. 417 // (FUTURE: this may get a bit more conditional if we support members that are of unit ype and have null as a representation. Unsure how that would work out exactly, but should be possible.) 418 // We don't bother to nil the child assembler's 'w' pointer: it's not necessary, 419 // because we'll never "share" 'cm' (as some systems, like maps and lists, do) or change its value (short of the whole assembler resetting), 420 // and therefore we should be able to rely on the child assembler to be reasonable and never start acting again after finish. 421 // (This *does* mean some care is required in the reset logic: we have to be absolutely sure that resetting propagates to all child assemblers, 422 // even if they're in other regions of the heap; otherwise, they might end up still holding actionable 'w' and 'm' pointers into bad times!) 423 // (If you want to compare this to the logic in struct assemblers: it's similar to how only children that don't have maybes need an active 'w' nil'ing; 424 // but the salient reason there isn't "because the don't have maybes"; it's "because they have a potentially-reused 'cm'". We don't have the former; but we *also* don't have the latter, for other reasons.) 425 doTemplate(` 426 func (ma *_{{ .Type | TypeSymbol }}__Assembler) valueFinishTidy() bool { 427 switch ma.cm { 428 case schema.Maybe_Value: 429 {{- /* nothing to do for memlayout=embedAll; the tag is already set and memory already in place. */ -}} 430 {{- /* nothing to do for memlayout=interface either; same story, the values are already in place. */ -}} 431 ma.state = maState_initial 432 return true 433 default: 434 return false 435 } 436 } 437 `, w, g.AdjCfg, g) 438 } 439 func (g unionBuilderGenerator) emitMapAssemblerMethods(w io.Writer) { 440 // DRY: I did an interesting thing here: the `switch ma.state` block remains textually identical to the one for structs, 441 // even though the branch by valueFinishTidy could jump directly to an error state. 442 // That same semantic error state gets checked separately a few lines later in a different mechanism. 443 // The later check is needed either way (the assembler needs to *keep* erroring if some derp calls AssembleEntry *again* after a previous call already did the tidy and got rejected), 444 // but we could arguably save a step there. It would probably trade more assembly size for the cycles saved, too, though. 445 // Ah, tradeoffs. I think the textually simple approach here is probably in fact the best. But it could be done differently, yes. 446 // Note that calling AssembleEntry again when it's not for the first entry *returns* an error; it doesn't panic. 447 // This is subtle but important: trying to add more data than is acceptable is a data mismatch, not a system misuse, and must error accordingly politely. 448 doTemplate(` 449 func (ma *_{{ .Type | TypeSymbol }}__Assembler) AssembleEntry(k string) (datamodel.NodeAssembler, error) { 450 switch ma.state { 451 case maState_initial: 452 // carry on 453 case maState_midKey: 454 panic("invalid state: AssembleEntry cannot be called when in the middle of assembling another key") 455 case maState_expectValue: 456 panic("invalid state: AssembleEntry cannot be called when expecting start of value assembly") 457 case maState_midValue: 458 if !ma.valueFinishTidy() { 459 panic("invalid state: AssembleEntry cannot be called when in the middle of assembling a value") 460 } // if tidy success: carry on for the moment, but we'll still be erroring shortly. 461 case maState_finished: 462 panic("invalid state: AssembleEntry cannot be called on an assembler that's already finished") 463 } 464 if ma.ca != 0 { 465 return nil, schema.ErrNotUnionStructure{TypeName:"{{ .PkgName }}.{{ .Type.Name }}", Detail: "cannot add another entry -- a union can only contain one thing!"} 466 } 467 {{- if .Type.Members }} 468 switch k { 469 {{- range $i, $member := .Type.Members }} 470 case "{{ $member.Name }}": 471 ma.state = maState_midValue 472 ma.ca = {{ add $i 1 }} 473 {{- if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "embedAll") }} 474 ma.w.tag = {{ add $i 1 }} 475 ma.ca{{ add $i 1 }}.w = &ma.w.x{{ add $i 1 }} 476 ma.ca{{ add $i 1 }}.m = &ma.cm 477 return &ma.ca{{ add $i 1 }}, nil 478 {{- else if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "interface") }} 479 x := &_{{ $member | TypeSymbol}}{} 480 ma.w.x = x 481 if ma.ca{{ add $i 1 }} == nil { 482 ma.ca{{ add $i 1 }} = &_{{ $member | TypeSymbol }}__Assembler{} 483 } 484 ma.ca{{ add $i 1 }}.w = x 485 ma.ca{{ add $i 1 }}.m = &ma.cm 486 return ma.ca{{ add $i 1 }}, nil 487 {{- end}} 488 {{- end}} 489 {{- end}} 490 } 491 return nil, schema.ErrInvalidKey{TypeName:"{{ .PkgName }}.{{ .Type.Name }}", Key:&_String{k}} 492 } 493 `, w, g.AdjCfg, g) 494 495 // AssembleKey has a similar DRY note as the AssembleEntry above had. 496 // One misfortune in this method: we may know that we're doomed to errors because the caller is trying to start a second entry, 497 // but we can't report it from this method: we have to sit on our tongue, slide to midKey state (even though we're doomed!), 498 // and let the keyAssembler return the error later. 499 // This sucks, but panicking wouldn't be correct (see remarks about error vs panic on the AssembleEntry method), 500 // and we don't want to make this call unchainable for everyone everywhere, either, so it can't be rewritten to have an immmediate error return. 501 // The transition to midKey state is particularly irritating because it means this assembler will be perma-wedged; but I see no alternative. 502 doTemplate(` 503 func (ma *_{{ .Type | TypeSymbol }}__Assembler) AssembleKey() datamodel.NodeAssembler { 504 switch ma.state { 505 case maState_initial: 506 // carry on 507 case maState_midKey: 508 panic("invalid state: AssembleKey cannot be called when in the middle of assembling another key") 509 case maState_expectValue: 510 panic("invalid state: AssembleKey cannot be called when expecting start of value assembly") 511 case maState_midValue: 512 if !ma.valueFinishTidy() { 513 panic("invalid state: AssembleKey cannot be called when in the middle of assembling a value") 514 } // if tidy success: carry on for the moment, but we'll still be erroring shortly... or rather, the keyassembler will be. 515 case maState_finished: 516 panic("invalid state: AssembleKey cannot be called on an assembler that's already finished") 517 } 518 ma.state = maState_midKey 519 return (*_{{ .Type | TypeSymbol }}__KeyAssembler)(ma) 520 } 521 `, w, g.AdjCfg, g) 522 523 // As with structs, the responsibilties of this are similar to AssembleEntry, but with some of the burden split into the key assembler (which should have acted earlier), 524 // and some of the logical continuity bounces through state in the form of 'ma.ca'. 525 // The potential to DRY up some of this should be plentiful, but it's a bit heady. 526 doTemplate(` 527 func (ma *_{{ .Type | TypeSymbol }}__Assembler) AssembleValue() datamodel.NodeAssembler { 528 switch ma.state { 529 case maState_initial: 530 panic("invalid state: AssembleValue cannot be called when no key is primed") 531 case maState_midKey: 532 panic("invalid state: AssembleValue cannot be called when in the middle of assembling a key") 533 case maState_expectValue: 534 // carry on 535 case maState_midValue: 536 panic("invalid state: AssembleValue cannot be called when in the middle of assembling another value") 537 case maState_finished: 538 panic("invalid state: AssembleValue cannot be called on an assembler that's already finished") 539 } 540 ma.state = maState_midValue 541 switch ma.ca { 542 {{- range $i, $member := .Type.Members }} 543 case {{ add $i 1 }}: 544 {{- if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "embedAll") }} 545 ma.ca{{ add $i 1 }}.w = &ma.w.x{{ add $i 1 }} 546 ma.ca{{ add $i 1 }}.m = &ma.cm 547 return &ma.ca{{ add $i 1 }} 548 {{- else if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "interface") }} 549 x := &_{{ $member | TypeSymbol}}{} 550 ma.w.x = x 551 if ma.ca{{ add $i 1 }} == nil { 552 ma.ca{{ add $i 1 }} = &_{{ $member | TypeSymbol }}__Assembler{} 553 } 554 ma.ca{{ add $i 1 }}.w = x 555 ma.ca{{ add $i 1 }}.m = &ma.cm 556 return ma.ca{{ add $i 1 }} 557 {{- end}} 558 {{- end}} 559 default: 560 panic("unreachable") 561 } 562 } 563 `, w, g.AdjCfg, g) 564 565 // Finish checks are nice and easy. Is the maState in the right place now and was a 'ca' ever marked? 566 // If yes and yes, then together with the rules elsewhere, we must've processed and accepted exactly one entry; perfect. 567 doTemplate(` 568 func (ma *_{{ .Type | TypeSymbol }}__Assembler) Finish() error { 569 switch ma.state { 570 case maState_initial: 571 // carry on 572 case maState_midKey: 573 panic("invalid state: Finish cannot be called when in the middle of assembling a key") 574 case maState_expectValue: 575 panic("invalid state: Finish cannot be called when expecting start of value assembly") 576 case maState_midValue: 577 if !ma.valueFinishTidy() { 578 panic("invalid state: Finish cannot be called when in the middle of assembling a value") 579 } // if tidy success: carry on 580 case maState_finished: 581 panic("invalid state: Finish cannot be called on an assembler that's already finished") 582 } 583 if ma.ca == 0 { 584 return schema.ErrNotUnionStructure{TypeName:"{{ .PkgName }}.{{ .Type.Name }}", Detail: "a union must have exactly one entry (not none)!"} 585 } 586 ma.state = maState_finished 587 *ma.m = schema.Maybe_Value 588 return nil 589 } 590 `, w, g.AdjCfg, g) 591 592 doTemplate(` 593 func (ma *_{{ .Type | TypeSymbol }}__Assembler) KeyPrototype() datamodel.NodePrototype { 594 return _String__Prototype{} 595 } 596 func (ma *_{{ .Type | TypeSymbol }}__Assembler) ValuePrototype(k string) datamodel.NodePrototype { 597 switch k { 598 {{- range $i, $member := .Type.Members }} 599 case "{{ $member.Name }}": 600 return _{{ $member | TypeSymbol }}__Prototype{} 601 {{- end}} 602 default: 603 return nil 604 } 605 } 606 `, w, g.AdjCfg, g) 607 } 608 func (g unionBuilderGenerator) emitKeyAssembler(w io.Writer) { 609 doTemplate(` 610 type _{{ .Type | TypeSymbol }}__KeyAssembler _{{ .Type | TypeSymbol }}__Assembler 611 `, w, g.AdjCfg, g) 612 stubs := mixins.StringAssemblerTraits{ 613 PkgName: g.PkgName, 614 TypeName: g.TypeName + ".KeyAssembler", 615 AppliedPrefix: "_" + g.AdjCfg.TypeSymbol(g.Type) + "__Key", 616 } 617 // This key assembler can disregard any idea of complex keys because we're fronting for a union! 618 // Union member names must be strings (and quite simple ones at that). 619 stubs.EmitNodeAssemblerMethodBeginMap(w) 620 stubs.EmitNodeAssemblerMethodBeginList(w) 621 stubs.EmitNodeAssemblerMethodAssignNull(w) 622 stubs.EmitNodeAssemblerMethodAssignBool(w) 623 stubs.EmitNodeAssemblerMethodAssignInt(w) 624 stubs.EmitNodeAssemblerMethodAssignFloat(w) 625 doTemplate(` 626 func (ka *_{{ .Type | TypeSymbol }}__KeyAssembler) AssignString(k string) error { 627 if ka.state != maState_midKey { 628 panic("misuse: KeyAssembler held beyond its valid lifetime") 629 } 630 if ka.ca != 0 { 631 return schema.ErrNotUnionStructure{TypeName:"{{ .PkgName }}.{{ .Type.Name }}", Detail: "cannot add another entry -- a union can only contain one thing!"} 632 } 633 switch k { 634 {{- range $i, $member := .Type.Members }} 635 case "{{ $member.Name }}": 636 ka.ca = {{ add $i 1 }} 637 {{- if (eq (dot.AdjCfg.UnionMemlayout dot.Type) "embedAll") }} 638 ka.w.tag = {{ add $i 1 }} 639 {{- end}} 640 ka.state = maState_expectValue 641 return nil 642 {{- end}} 643 } 644 return schema.ErrInvalidKey{TypeName:"{{ .PkgName }}.{{ .Type.Name }}", Key:&_String{k}} // TODO: error quality: ErrInvalidUnionDiscriminant ? 645 } 646 `, w, g.AdjCfg, g) 647 stubs.EmitNodeAssemblerMethodAssignBytes(w) 648 stubs.EmitNodeAssemblerMethodAssignLink(w) 649 doTemplate(` 650 func (ka *_{{ .Type | TypeSymbol }}__KeyAssembler) AssignNode(v datamodel.Node) error { 651 if v2, err := v.AsString(); err != nil { 652 return err 653 } else { 654 return ka.AssignString(v2) 655 } 656 } 657 func (_{{ .Type | TypeSymbol }}__KeyAssembler) Prototype() datamodel.NodePrototype { 658 return _String__Prototype{} 659 } 660 `, w, g.AdjCfg, g) 661 }