github.com/ipld/go-ipld-prime@v0.21.0/schema/gen/go/genStructReprTuple.go (about) 1 package gengo 2 3 import ( 4 "io" 5 "strconv" 6 7 "github.com/ipld/go-ipld-prime/schema" 8 "github.com/ipld/go-ipld-prime/schema/gen/go/mixins" 9 ) 10 11 var _ TypeGenerator = &structReprTupleGenerator{} 12 13 // Optional fields for tuple representation are only allowed at the end, and contiguously. 14 // Present fields are matched greedily: if the struct has five fields, 15 // and the last two are optional, and there's four values, then they will be mapped onto the first four fields, period. 16 // In theory, it would be possible to support a variety of fancier modes, configurably; 17 // in practice, let's not: the ROI would be atrocious: 18 // few people seem to want this; 19 // the implementation complexity would rise dramatically; 20 // and the next nearest substitutes for such behavior are already available, and cheap (and also sturdier). 21 // It would make about as much sense to support implicits as it does trailing optionals, 22 // which means we probably should consider that someday, 23 // but it's not implemented today. 24 25 func NewStructReprTupleGenerator(pkgName string, typ *schema.TypeStruct, adjCfg *AdjunctCfg) TypeGenerator { 26 return structReprTupleGenerator{ 27 structGenerator{ 28 adjCfg, 29 mixins.MapTraits{ 30 PkgName: pkgName, 31 TypeName: string(typ.Name()), 32 TypeSymbol: adjCfg.TypeSymbol(typ), 33 }, 34 pkgName, 35 typ, 36 }, 37 } 38 } 39 40 type structReprTupleGenerator struct { 41 structGenerator 42 } 43 44 func (g structReprTupleGenerator) GetRepresentationNodeGen() NodeGenerator { 45 return structReprTupleReprGenerator{ 46 g.AdjCfg, 47 mixins.ListTraits{ 48 PkgName: g.PkgName, 49 TypeName: string(g.Type.Name()) + ".Repr", 50 TypeSymbol: "_" + g.AdjCfg.TypeSymbol(g.Type) + "__Repr", 51 }, 52 g.PkgName, 53 g.Type, 54 } 55 } 56 57 type structReprTupleReprGenerator struct { 58 AdjCfg *AdjunctCfg 59 mixins.ListTraits 60 PkgName string 61 Type *schema.TypeStruct 62 } 63 64 func (structReprTupleReprGenerator) IsRepr() bool { return true } // hint used in some generalized templates. 65 66 func (g structReprTupleReprGenerator) EmitNodeType(w io.Writer) { 67 // The type is structurally the same, but will have a different set of methods. 68 doTemplate(` 69 type _{{ .Type | TypeSymbol }}__Repr _{{ .Type | TypeSymbol }} 70 `, w, g.AdjCfg, g) 71 } 72 73 func (g structReprTupleReprGenerator) EmitNodeTypeAssertions(w io.Writer) { 74 doTemplate(` 75 var _ datamodel.Node = &_{{ .Type | TypeSymbol }}__Repr{} 76 `, w, g.AdjCfg, g) 77 } 78 79 func (g structReprTupleReprGenerator) EmitNodeMethodLookupByIndex(w io.Writer) { 80 doTemplate(` 81 func (n *_{{ .Type | TypeSymbol }}__Repr) LookupByIndex(idx int64) (datamodel.Node, error) { 82 switch idx { 83 {{- range $i, $field := .Type.Fields }} 84 case {{ $i }}: 85 {{- if $field.IsOptional }} 86 if n.{{ $field | FieldSymbolLower }}.m == schema.Maybe_Absent { 87 return datamodel.Absent, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfInt(idx)} 88 } 89 {{- end}} 90 {{- if $field.IsNullable }} 91 if n.{{ $field | FieldSymbolLower }}.m == schema.Maybe_Null { 92 return datamodel.Null, nil 93 } 94 {{- end}} 95 {{- if $field.IsMaybe }} 96 return n.{{ $field | FieldSymbolLower }}.v.Representation(), nil 97 {{- else}} 98 return n.{{ $field | FieldSymbolLower }}.Representation(), nil 99 {{- end}} 100 {{- end}} 101 default: 102 return nil, schema.ErrNoSuchField{Type: nil /*TODO*/, Field: datamodel.PathSegmentOfInt(idx)} 103 } 104 } 105 `, w, g.AdjCfg, g) 106 } 107 108 func (g structReprTupleReprGenerator) EmitNodeMethodLookupByNode(w io.Writer) { 109 doTemplate(` 110 func (n *_{{ .Type | TypeSymbol }}__Repr) LookupByNode(key datamodel.Node) (datamodel.Node, error) { 111 ki, err := key.AsInt() 112 if err != nil { 113 return nil, err 114 } 115 return n.LookupByIndex(ki) 116 } 117 `, w, g.AdjCfg, g) 118 } 119 120 func (g structReprTupleReprGenerator) EmitNodeMethodListIterator(w io.Writer) { 121 // DRY: much of this precalcuation about doneness is common with the map representation. 122 // (or at least: it is for now: the addition of support for implicits in the map representation may bamboozle that.) 123 // Some of the templating also experiences the `.HaveTrailingOptionals` branching, 124 // but not quite as much as the map representation: since we always know those come at the end 125 // (and in particular, once we hit one absent, we're done!), some simplifications can be made. 126 127 // The 'idx' int is what field we'll yield next. 128 // Note that this iterator doesn't mention fields that are absent. 129 // This makes things a bit trickier -- especially the 'Done' predicate, 130 // since it may have to do lookahead if there's any optionals at the end of the structure! 131 132 // Count how many trailing fields are optional. 133 // The 'Done' predicate gets more complex when in the trailing optionals. 134 fields := g.Type.Fields() 135 fieldCount := len(fields) 136 beginTrailingOptionalField := fieldCount 137 for i := fieldCount - 1; i >= 0; i-- { 138 if !fields[i].IsOptional() { 139 break 140 } 141 beginTrailingOptionalField = i 142 } 143 haveTrailingOptionals := beginTrailingOptionalField < fieldCount 144 145 // Now: finally we can get on with the actual templating. 146 doTemplate(` 147 func (n *_{{ .Type | TypeSymbol }}__Repr) ListIterator() datamodel.ListIterator { 148 {{- if .HaveTrailingOptionals }} 149 end := {{ len .Type.Fields }}`+ 150 func() string { // this next part was too silly in templates due to lack of reverse ranging. 151 v := "\n" 152 for i := fieldCount - 1; i >= beginTrailingOptionalField; i-- { 153 v += "\t\t\tif n." + g.AdjCfg.FieldSymbolLower(fields[i]) + ".m == schema.Maybe_Absent {\n" 154 v += "\t\t\t\tend = " + strconv.Itoa(i) + "\n" 155 v += "\t\t\t} else {\n" 156 v += "\t\t\t\tgoto done\n" 157 v += "\t\t\t}\n" 158 } 159 return v 160 }()+`done: 161 return &_{{ .Type | TypeSymbol }}__ReprListItr{n, 0, end} 162 {{- else}} 163 return &_{{ .Type | TypeSymbol }}__ReprListItr{n, 0} 164 {{- end}} 165 } 166 167 type _{{ .Type | TypeSymbol }}__ReprListItr struct { 168 n *_{{ .Type | TypeSymbol }}__Repr 169 idx int 170 {{if .HaveTrailingOptionals }}end int{{end}} 171 } 172 173 func (itr *_{{ .Type | TypeSymbol }}__ReprListItr) Next() (idx int64, v datamodel.Node, err error) { 174 if itr.idx >= {{ len .Type.Fields }} { 175 return -1, nil, datamodel.ErrIteratorOverread{} 176 } 177 switch itr.idx { 178 {{- range $i, $field := .Type.Fields }} 179 case {{ $i }}: 180 idx = int64(itr.idx) 181 {{- if $field.IsOptional }} 182 if itr.n.{{ $field | FieldSymbolLower }}.m == schema.Maybe_Absent { 183 return -1, nil, datamodel.ErrIteratorOverread{} 184 } 185 {{- end}} 186 {{- if $field.IsNullable }} 187 if itr.n.{{ $field | FieldSymbolLower }}.m == schema.Maybe_Null { 188 v = datamodel.Null 189 break 190 } 191 {{- end}} 192 {{- if $field.IsMaybe }} 193 v = itr.n.{{ $field | FieldSymbolLower}}.v.Representation() 194 {{- else}} 195 v = itr.n.{{ $field | FieldSymbolLower}}.Representation() 196 {{- end}} 197 {{- end}} 198 default: 199 panic("unreachable") 200 } 201 itr.idx++ 202 return 203 } 204 {{- if .HaveTrailingOptionals }} 205 func (itr *_{{ .Type | TypeSymbol }}__ReprListItr) Done() bool { 206 return itr.idx >= itr.end 207 } 208 {{- else}} 209 func (itr *_{{ .Type | TypeSymbol }}__ReprListItr) Done() bool { 210 return itr.idx >= {{ len .Type.Fields }} 211 } 212 {{- end}} 213 214 `, w, g.AdjCfg, struct { 215 Type *schema.TypeStruct 216 HaveTrailingOptionals bool 217 }{ 218 g.Type, 219 haveTrailingOptionals, 220 }) 221 } 222 223 func (g structReprTupleReprGenerator) EmitNodeMethodLength(w io.Writer) { 224 // This is fun: it has to count down for any unset optional fields. 225 doTemplate(` 226 func (rn *_{{ .Type | TypeSymbol }}__Repr) Length() int64 { 227 l := {{ len .Type.Fields }} 228 {{- range $field := .Type.Fields }} 229 {{- if $field.IsOptional }} 230 if rn.{{ $field | FieldSymbolLower }}.m == schema.Maybe_Absent { 231 l-- 232 } 233 {{- end}} 234 {{- end}} 235 return int64(l) 236 } 237 `, w, g.AdjCfg, g) 238 } 239 240 func (g structReprTupleReprGenerator) EmitNodeMethodPrototype(w io.Writer) { 241 emitNodeMethodPrototype_typical(w, g.AdjCfg, g) 242 } 243 244 func (g structReprTupleReprGenerator) EmitNodePrototypeType(w io.Writer) { 245 emitNodePrototypeType_typical(w, g.AdjCfg, g) 246 } 247 248 // --- NodeBuilder and NodeAssembler ---> 249 250 func (g structReprTupleReprGenerator) GetNodeBuilderGenerator() NodeBuilderGenerator { 251 return structReprTupleReprBuilderGenerator{ 252 g.AdjCfg, 253 mixins.ListAssemblerTraits{ 254 PkgName: g.PkgName, 255 TypeName: g.TypeName, 256 AppliedPrefix: "_" + g.AdjCfg.TypeSymbol(g.Type) + "__Repr", 257 }, 258 g.PkgName, 259 g.Type, 260 } 261 } 262 263 type structReprTupleReprBuilderGenerator struct { 264 AdjCfg *AdjunctCfg 265 mixins.ListAssemblerTraits 266 PkgName string 267 Type *schema.TypeStruct 268 } 269 270 func (structReprTupleReprBuilderGenerator) IsRepr() bool { return true } // hint used in some generalized templates. 271 272 func (g structReprTupleReprBuilderGenerator) EmitNodeBuilderType(w io.Writer) { 273 emitEmitNodeBuilderType_typical(w, g.AdjCfg, g) 274 } 275 func (g structReprTupleReprBuilderGenerator) EmitNodeBuilderMethods(w io.Writer) { 276 emitNodeBuilderMethods_typical(w, g.AdjCfg, g) 277 } 278 func (g structReprTupleReprBuilderGenerator) EmitNodeAssemblerType(w io.Writer) { 279 // - 'w' is the "**w**ip" pointer. 280 // - 'm' is the **m**aybe which communicates our completeness to the parent if we're a child assembler. 281 // - 'state' is what it says on the tin. this is used for the list state (the broad transitions between null, start-list, and finish are handled by 'm' for consistency with other types). 282 // - contrasted to the map representation, there's no 's' bitfield for what's been **s**et -- because we know things must procede in order, it would be redundant with 'f'. 283 // - 'f' is the **f**ocused field that will be assembled next. 284 // 285 // - 'cm' is **c**hild **m**aybe and is used for the completion message from children that aren't allowed to be nullable (for those that are, their own maybe.m is used). 286 // - the 'ca_*' fields embed **c**hild **a**ssemblers -- these are embedded so we can yield pointers to them without causing new allocations. 287 // 288 // Note that this textually similar to the type-level assembler, but because it embeds the repr assembler for the child types, 289 // it might be *significantly* different in size and memory layout in that trailing part of the struct. 290 doTemplate(` 291 type _{{ .Type | TypeSymbol }}__ReprAssembler struct { 292 w *_{{ .Type | TypeSymbol }} 293 m *schema.Maybe 294 state laState 295 f int 296 297 cm schema.Maybe 298 {{range $field := .Type.Fields -}} 299 ca_{{ $field | FieldSymbolLower }} _{{ $field.Type | TypeSymbol }}__ReprAssembler 300 {{end -}} 301 } 302 303 func (na *_{{ .Type | TypeSymbol }}__ReprAssembler) reset() { 304 na.state = laState_initial 305 na.f = 0 306 {{- range $field := .Type.Fields }} 307 na.ca_{{ $field | FieldSymbolLower }}.reset() 308 {{- end}} 309 } 310 `, w, g.AdjCfg, g) 311 } 312 func (g structReprTupleReprBuilderGenerator) EmitNodeAssemblerMethodBeginList(w io.Writer) { 313 // Future: This could do something strict with the sizehint; it currently ignores it. 314 doTemplate(` 315 func (na *_{{ .Type | TypeSymbol }}__ReprAssembler) BeginList(int64) (datamodel.ListAssembler, error) { 316 switch *na.m { 317 case schema.Maybe_Value, schema.Maybe_Null: 318 panic("invalid state: cannot assign into assembler that's already finished") 319 case midvalue: 320 panic("invalid state: it makes no sense to 'begin' twice on the same assembler!") 321 } 322 *na.m = midvalue 323 {{- if .Type | MaybeUsesPtr }} 324 if na.w == nil { 325 na.w = &_{{ .Type | TypeSymbol }}{} 326 } 327 {{- end}} 328 return na, nil 329 } 330 `, w, g.AdjCfg, g) 331 } 332 func (g structReprTupleReprBuilderGenerator) EmitNodeAssemblerMethodAssignNull(w io.Writer) { 333 emitNodeAssemblerMethodAssignNull_recursive(w, g.AdjCfg, g) 334 } 335 func (g structReprTupleReprBuilderGenerator) EmitNodeAssemblerMethodAssignNode(w io.Writer) { 336 emitNodeAssemblerMethodAssignNode_listoid(w, g.AdjCfg, g) 337 } 338 func (g structReprTupleReprBuilderGenerator) EmitNodeAssemblerOtherBits(w io.Writer) { 339 g.emitListAssemblerChildTidyHelper(w) 340 g.emitListAssemblerChildListAssemblerMethods(w) 341 } 342 func (g structReprTupleReprBuilderGenerator) emitListAssemblerChildTidyHelper(w io.Writer) { 343 doTemplate(` 344 func (la *_{{ .Type | TypeSymbol }}__ReprAssembler) valueFinishTidy() bool { 345 switch la.f { 346 {{- range $i, $field := .Type.Fields }} 347 case {{ $i }}: 348 {{- if $field.IsMaybe }} 349 switch la.w.{{ $field | FieldSymbolLower }}.m { 350 case schema.Maybe_Value: 351 {{- if (MaybeUsesPtr $field.Type) }} 352 la.w.{{ $field | FieldSymbolLower }}.v = la.ca_{{ $field | FieldSymbolLower }}.w 353 {{- end}} 354 la.state = laState_initial 355 la.f++ 356 return true 357 {{- else}} 358 switch la.cm { 359 case schema.Maybe_Value: 360 la.cm = schema.Maybe_Absent 361 la.state = laState_initial 362 la.f++ 363 return true 364 {{- end}} 365 {{- if $field.IsNullable }} 366 case schema.Maybe_Null: 367 la.state = laState_initial 368 la.f++ 369 return true 370 {{- end}} 371 default: 372 return false 373 } 374 {{- end}} 375 default: 376 panic("unreachable") 377 } 378 } 379 `, w, g.AdjCfg, g) 380 } 381 func (g structReprTupleReprBuilderGenerator) emitListAssemblerChildListAssemblerMethods(w io.Writer) { 382 doTemplate(` 383 func (la *_{{ .Type | TypeSymbol }}__ReprAssembler) AssembleValue() datamodel.NodeAssembler { 384 switch la.state { 385 case laState_initial: 386 // carry on 387 case laState_midValue: 388 if !la.valueFinishTidy() { 389 panic("invalid state: AssembleValue cannot be called when still in the middle of assembling the previous value") 390 } // if tidy success: carry on 391 case laState_finished: 392 panic("invalid state: AssembleValue cannot be called on an assembler that's already finished") 393 } 394 if la.f >= {{ len .Type.Fields }} { 395 return _ErrorThunkAssembler{schema.ErrNoSuchField{Type: nil /*TODO*/, Field: datamodel.PathSegmentOfInt({{ len .Type.Fields }})}} 396 } 397 la.state = laState_midValue 398 switch la.f { 399 {{- range $i, $field := .Type.Fields }} 400 case {{ $i }}: 401 {{- if $field.IsMaybe }} 402 la.ca_{{ $field | FieldSymbolLower }}.w = {{if not (MaybeUsesPtr $field.Type) }}&{{end}}la.w.{{ $field | FieldSymbolLower }}.v 403 la.ca_{{ $field | FieldSymbolLower }}.m = &la.w.{{ $field | FieldSymbolLower }}.m 404 {{- if $field.IsNullable }} 405 la.w.{{ $field | FieldSymbolLower }}.m = allowNull 406 {{- end}} 407 {{- else}} 408 la.ca_{{ $field | FieldSymbolLower }}.w = &la.w.{{ $field | FieldSymbolLower }} 409 la.ca_{{ $field | FieldSymbolLower }}.m = &la.cm 410 {{- end}} 411 return &la.ca_{{ $field | FieldSymbolLower }} 412 {{- end}} 413 default: 414 panic("unreachable") 415 } 416 } 417 `, w, g.AdjCfg, g) 418 // Surprisingly, the Finish method doesn't have anything to do regarding any trailing optionals: 419 // if they weren't assigned yet, their Maybe state is still the zero value: absent. And that's correct. 420 // DRY: okay, this finish component is actually identical, both textually and in terms of linking, to lists. This we should actually extract. 421 doTemplate(` 422 func (la *_{{ .Type | TypeSymbol }}__ReprAssembler) Finish() error { 423 switch la.state { 424 case laState_initial: 425 // carry on 426 case laState_midValue: 427 if !la.valueFinishTidy() { 428 panic("invalid state: Finish cannot be called when in the middle of assembling a value") 429 } // if tidy success: carry on 430 case laState_finished: 431 panic("invalid state: Finish cannot be called on an assembler that's already finished") 432 } 433 la.state = laState_finished 434 *la.m = schema.Maybe_Value 435 return nil 436 } 437 `, w, g.AdjCfg, g) 438 doTemplate(` 439 func (la *_{{ .Type | TypeSymbol }}__ReprAssembler) ValuePrototype(_ int64) datamodel.NodePrototype { 440 panic("todo structbuilder tuplerepr valueprototype") 441 } 442 `, w, g.AdjCfg, g) 443 }