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  }