github.com/ipld/go-ipld-prime@v0.21.0/schema/gen/go/genMap.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 type mapGenerator struct { 11 AdjCfg *AdjunctCfg 12 mixins.MapTraits 13 PkgName string 14 Type *schema.TypeMap 15 } 16 17 func (mapGenerator) IsRepr() bool { return false } // hint used in some generalized templates. 18 19 // --- native content and specializations ---> 20 21 func (g mapGenerator) EmitNativeType(w io.Writer) { 22 // Maps do double bookkeeping. 23 // - 'm' is used for quick lookup. 24 // - 't' is used for both for order maintainence, and for allocation amortization for both keys and values. 25 // Note that the key in 'm' is *not* a pointer. 26 // The value in 'm' is a pointer into 't' (except when it's a maybe; maybes are already pointers). 27 doTemplate(` 28 {{- if Comments -}} 29 // {{ .Type | TypeSymbol }} matches the IPLD Schema type "{{ .Type.Name }}". It has {{ .Kind }} kind. 30 {{- end}} 31 type {{ .Type | TypeSymbol }} = *_{{ .Type | TypeSymbol }} 32 type _{{ .Type | TypeSymbol }} struct { 33 m map[_{{ .Type.KeyType | TypeSymbol }}]{{if .Type.ValueIsNullable }}Maybe{{else}}*_{{end}}{{ .Type.ValueType | TypeSymbol }} 34 t []_{{ .Type | TypeSymbol }}__entry 35 } 36 `, w, g.AdjCfg, g) 37 // - address of 'k' is used when we return keys as nodes, such as in iterators. 38 // Having these in the 't' slice above amortizes moving all of them to heap at once, 39 // which makes iterators that have to return them as an interface much (much) lower cost -- no 'runtime.conv*' pain. 40 // - address of 'v' is used in map values, to return, and of course also in iterators. 41 doTemplate(` 42 type _{{ .Type | TypeSymbol }}__entry struct { 43 k _{{ .Type.KeyType | TypeSymbol }} 44 v _{{ .Type.ValueType | TypeSymbol }}{{if .Type.ValueIsNullable }}__Maybe{{end}} 45 } 46 `, w, g.AdjCfg, g) 47 } 48 49 func (g mapGenerator) EmitNativeAccessors(w io.Writer) { 50 // Generate a speciated Lookup as well as LookupMaybe method. 51 // The Lookup method returns nil in case of *either* an absent value or a null value, 52 // and so should only be used if the map type doesn't allow nullable keys or if the caller doesn't care about the difference. 53 // The LookupMaybe method returns a MaybeT type for the map value, 54 // and is needed if the map allows nullable values and the caller wishes to distinguish between null and absent. 55 // (The Lookup method should be preferred for maps that have non-nullable keys, because LookupMaybe may incur additional costs; 56 // boxing something into a maybe when it wasn't already stored that way costs an alloc(!), 57 // and may additionally incur a memcpy if the maybe for the value type doesn't use pointers internally). 58 doTemplate(` 59 func (n *_{{ .Type | TypeSymbol }}) Lookup(k {{ .Type.KeyType | TypeSymbol }}) {{ .Type.ValueType | TypeSymbol }} { 60 v, exists := n.m[*k] 61 if !exists { 62 return nil 63 } 64 {{- if .Type.ValueIsNullable }} 65 if v.m == schema.Maybe_Null { 66 return nil 67 } 68 return {{ if not (MaybeUsesPtr .Type.ValueType) }}&{{end}}v.v 69 {{- else}} 70 return v 71 {{- end}} 72 } 73 func (n *_{{ .Type | TypeSymbol }}) LookupMaybe(k {{ .Type.KeyType | TypeSymbol }}) Maybe{{ .Type.ValueType | TypeSymbol }} { 74 v, exists := n.m[*k] 75 if !exists { 76 return &_{{ .Type | TypeSymbol }}__valueAbsent 77 } 78 {{- if .Type.ValueIsNullable }} 79 return v 80 {{- else}} 81 return &_{{ .Type.ValueType | TypeSymbol }}__Maybe{ 82 m: schema.Maybe_Value, 83 v: {{ if not (MaybeUsesPtr .Type.ValueType) }}*{{end}}v, 84 } 85 {{- end}} 86 } 87 88 var _{{ .Type | TypeSymbol }}__valueAbsent = _{{ .Type.ValueType | TypeSymbol }}__Maybe{m:schema.Maybe_Absent} 89 `, w, g.AdjCfg, g) 90 91 // Generate a speciated iterator. 92 // The main advantage of this over the general datamodel.MapIterator is of course keeping types visible (and concrete, to the compiler's eyes in optimizations, too). 93 // It also elides the error return from the iterator's Next method. (Overreads will result in nil keys; this is both easily avoidable, and unambiguous if you do goof and hit it.) 94 doTemplate(` 95 func (n {{ .Type | TypeSymbol }}) Iterator() *{{ .Type | TypeSymbol }}__Itr { 96 return &{{ .Type | TypeSymbol }}__Itr{n, 0} 97 } 98 99 type {{ .Type | TypeSymbol }}__Itr struct { 100 n {{ .Type | TypeSymbol }} 101 idx int 102 } 103 104 func (itr *{{ .Type | TypeSymbol }}__Itr) Next() (k {{ .Type.KeyType | TypeSymbol }}, v {{if .Type.ValueIsNullable }}Maybe{{end}}{{ .Type.ValueType | TypeSymbol }}) { 105 if itr.idx >= len(itr.n.t) { 106 return nil, nil 107 } 108 x := &itr.n.t[itr.idx] 109 k = &x.k 110 v = &x.v 111 itr.idx++ 112 return 113 } 114 func (itr *{{ .Type | TypeSymbol }}__Itr) Done() bool { 115 return itr.idx >= len(itr.n.t) 116 } 117 118 `, w, g.AdjCfg, g) 119 } 120 121 func (g mapGenerator) EmitNativeBuilder(w io.Writer) { 122 // Not yet clear what exactly might be most worth emitting here. 123 } 124 125 func (g mapGenerator) EmitNativeMaybe(w io.Writer) { 126 emitNativeMaybe(w, g.AdjCfg, g) 127 } 128 129 // --- type info ---> 130 131 func (g mapGenerator) EmitTypeConst(w io.Writer) { 132 doTemplate(` 133 // TODO EmitTypeConst 134 `, w, g.AdjCfg, g) 135 } 136 137 // --- TypedNode interface satisfaction ---> 138 139 func (g mapGenerator) EmitTypedNodeMethodType(w io.Writer) { 140 doTemplate(` 141 func ({{ .Type | TypeSymbol }}) Type() schema.Type { 142 return nil /*TODO:typelit*/ 143 } 144 `, w, g.AdjCfg, g) 145 } 146 147 func (g mapGenerator) EmitTypedNodeMethodRepresentation(w io.Writer) { 148 emitTypicalTypedNodeMethodRepresentation(w, g.AdjCfg, g) 149 } 150 151 // --- Node interface satisfaction ---> 152 153 func (g mapGenerator) EmitNodeType(w io.Writer) { 154 // No additional types needed. Methods all attach to the native type. 155 } 156 157 func (g mapGenerator) EmitNodeTypeAssertions(w io.Writer) { 158 emitNodeTypeAssertions_typical(w, g.AdjCfg, g) 159 } 160 161 func (g mapGenerator) EmitNodeMethodLookupByString(w io.Writer) { 162 // What should be coercible in which directions (and how surprising that is) is an interesting question. 163 // Most of the answer comes from considering what needs to be possible when working with PathSegment: 164 // we *must* be able to accept a string in a PathSegment and be able to use it to navigate a map -- even if the map has complex keys. 165 // For that to work out, it means if the key type doesn't have a string type kind, we must be willing to reach into its representation and use the fromString there. 166 // If the key type *does* have a string kind at the type level, we'll use that; no need to consider going through the representation. 167 doTemplate(` 168 func (n {{ .Type | TypeSymbol }}) LookupByString(k string) (datamodel.Node, error) { 169 var k2 _{{ .Type.KeyType | TypeSymbol }} 170 {{- if eq .Type.KeyType.TypeKind.String "String" }} 171 if err := (_{{ .Type.KeyType | TypeSymbol }}__Prototype{}).fromString(&k2, k); err != nil { 172 return nil, err // TODO wrap in some kind of ErrInvalidKey 173 } 174 {{- else}} 175 if err := (_{{ .Type.KeyType | TypeSymbol }}__ReprPrototype{}).fromString(&k2, k); err != nil { 176 return nil, err // TODO wrap in some kind of ErrInvalidKey 177 } 178 {{- end}} 179 v, exists := n.m[k2] 180 if !exists { 181 return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfString(k)} 182 } 183 {{- if .Type.ValueIsNullable }} 184 if v.m == schema.Maybe_Null { 185 return datamodel.Null, nil 186 } 187 return {{ if not (MaybeUsesPtr .Type.ValueType) }}&{{end}}v.v, nil 188 {{- else}} 189 return v, nil 190 {{- end}} 191 } 192 `, w, g.AdjCfg, g) 193 } 194 195 func (g mapGenerator) EmitNodeMethodLookupByNode(w io.Writer) { 196 // LookupByNode will procede by cast if it can; or simply error if that doesn't work. 197 // There's no attempt to turn the node (or its repr) into a string and then reify that into a key; 198 // if you used a Node here, you should've meant it. 199 // REVIEW: by comparison structs will coerce anything stringish silently...! so we should figure out if that inconsistency is acceptable, and at least document it if so. 200 doTemplate(` 201 func (n {{ .Type | TypeSymbol }}) LookupByNode(k datamodel.Node) (datamodel.Node, error) { 202 k2, ok := k.({{ .Type.KeyType | TypeSymbol }}) 203 if !ok { 204 panic("todo invalid key type error") 205 // 'schema.ErrInvalidKey{TypeName:"{{ .PkgName }}.{{ .Type.Name }}", Key:&_String{k}}' doesn't quite cut it: need room to explain the type, and it's not guaranteed k can be turned into a string at all 206 } 207 v, exists := n.m[*k2] 208 if !exists { 209 return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfString(k2.String())} 210 } 211 {{- if .Type.ValueIsNullable }} 212 if v.m == schema.Maybe_Null { 213 return datamodel.Null, nil 214 } 215 return {{ if not (MaybeUsesPtr .Type.ValueType) }}&{{end}}v.v, nil 216 {{- else}} 217 return v, nil 218 {{- end}} 219 } 220 `, w, g.AdjCfg, g) 221 } 222 223 func (g mapGenerator) EmitNodeMethodMapIterator(w io.Writer) { 224 doTemplate(` 225 func (n {{ .Type | TypeSymbol }}) MapIterator() datamodel.MapIterator { 226 return &_{{ .Type | TypeSymbol }}__MapItr{n, 0} 227 } 228 229 type _{{ .Type | TypeSymbol }}__MapItr struct { 230 n {{ .Type | TypeSymbol }} 231 idx int 232 } 233 234 func (itr *_{{ .Type | TypeSymbol }}__MapItr) Next() (k datamodel.Node, v datamodel.Node, _ error) { 235 if itr.idx >= len(itr.n.t) { 236 return nil, nil, datamodel.ErrIteratorOverread{} 237 } 238 x := &itr.n.t[itr.idx] 239 k = &x.k 240 {{- if .Type.ValueIsNullable }} 241 switch x.v.m { 242 case schema.Maybe_Null: 243 v = datamodel.Null 244 case schema.Maybe_Value: 245 v = {{ if not (MaybeUsesPtr .Type.ValueType) }}&{{end}}x.v.v 246 } 247 {{- else}} 248 v = &x.v 249 {{- end}} 250 itr.idx++ 251 return 252 } 253 func (itr *_{{ .Type | TypeSymbol }}__MapItr) Done() bool { 254 return itr.idx >= len(itr.n.t) 255 } 256 257 `, w, g.AdjCfg, g) 258 } 259 260 func (g mapGenerator) EmitNodeMethodLength(w io.Writer) { 261 doTemplate(` 262 func (n {{ .Type | TypeSymbol }}) Length() int64 { 263 return int64(len(n.t)) 264 } 265 `, w, g.AdjCfg, g) 266 } 267 268 func (g mapGenerator) EmitNodeMethodPrototype(w io.Writer) { 269 emitNodeMethodPrototype_typical(w, g.AdjCfg, g) 270 } 271 272 func (g mapGenerator) EmitNodePrototypeType(w io.Writer) { 273 emitNodePrototypeType_typical(w, g.AdjCfg, g) 274 } 275 276 // --- NodeBuilder and NodeAssembler ---> 277 278 func (g mapGenerator) GetNodeBuilderGenerator() NodeBuilderGenerator { 279 return mapBuilderGenerator{ 280 g.AdjCfg, 281 mixins.MapAssemblerTraits{ 282 PkgName: g.PkgName, 283 TypeName: g.TypeName, 284 AppliedPrefix: "_" + g.AdjCfg.TypeSymbol(g.Type) + "__", 285 }, 286 g.PkgName, 287 g.Type, 288 } 289 } 290 291 type mapBuilderGenerator struct { 292 AdjCfg *AdjunctCfg 293 mixins.MapAssemblerTraits 294 PkgName string 295 Type *schema.TypeMap 296 } 297 298 func (mapBuilderGenerator) IsRepr() bool { return false } // hint used in some generalized templates. 299 300 func (g mapBuilderGenerator) EmitNodeBuilderType(w io.Writer) { 301 emitEmitNodeBuilderType_typical(w, g.AdjCfg, g) 302 } 303 func (g mapBuilderGenerator) EmitNodeBuilderMethods(w io.Writer) { 304 emitNodeBuilderMethods_typical(w, g.AdjCfg, g) 305 } 306 func (g mapBuilderGenerator) EmitNodeAssemblerType(w io.Writer) { 307 // - 'w' is the "**w**ip" pointer. 308 // - 'm' is the **m**aybe which communicates our completeness to the parent if we're a child assembler. 309 // - 'state' is what it says on the tin. this is used for the map state (the broad transitions between null, start-map, and finish are handled by 'm' for consistency.) 310 // - there's no equivalent of the 'f' (**f**ocused next) field in struct assemblers -- that's implicitly the last row of the 'w.t'. 311 // 312 // - 'cm' is **c**hild **m**aybe and is used for the completion message from children. 313 // It's used for values if values aren't allowed to be nullable and thus don't have their own per-value maybe slot we can use. 314 // It's always used for key assembly, since keys are never allowed to be nullable and thus etc. 315 // - 'ka' and 'va' are the key assembler and value assembler respectively. 316 // Perhaps surprisingly, we can get away with using the assemblers for each type just straight up, no wrappers necessary; 317 // All of the required magic is handled through maybe pointers and some tidy methods used during state transitions. 318 doTemplate(` 319 type _{{ .Type | TypeSymbol }}__Assembler struct { 320 w *_{{ .Type | TypeSymbol }} 321 m *schema.Maybe 322 state maState 323 324 cm schema.Maybe 325 ka _{{ .Type.KeyType | TypeSymbol }}__Assembler 326 va _{{ .Type.ValueType | TypeSymbol }}__Assembler 327 } 328 329 func (na *_{{ .Type | TypeSymbol }}__Assembler) reset() { 330 na.state = maState_initial 331 na.ka.reset() 332 na.va.reset() 333 } 334 `, w, g.AdjCfg, g) 335 } 336 func (g mapBuilderGenerator) EmitNodeAssemblerMethodBeginMap(w io.Writer) { 337 emitNodeAssemblerMethodBeginMap_mapoid(w, g.AdjCfg, g) 338 } 339 func (g mapBuilderGenerator) EmitNodeAssemblerMethodAssignNull(w io.Writer) { 340 emitNodeAssemblerMethodAssignNull_recursive(w, g.AdjCfg, g) 341 } 342 func (g mapBuilderGenerator) EmitNodeAssemblerMethodAssignNode(w io.Writer) { 343 emitNodeAssemblerMethodAssignNode_mapoid(w, g.AdjCfg, g) 344 } 345 func (g mapBuilderGenerator) EmitNodeAssemblerOtherBits(w io.Writer) { 346 emitNodeAssemblerHelper_mapoid_keyTidyHelper(w, g.AdjCfg, g) 347 emitNodeAssemblerHelper_mapoid_valueTidyHelper(w, g.AdjCfg, g) 348 emitNodeAssemblerHelper_mapoid_mapAssemblerMethods(w, g.AdjCfg, g) 349 }