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  }