github.com/ipld/go-ipld-prime@v0.21.0/schema/gen/go/genpartsCommon.go (about)

     1  package gengo
     2  
     3  import (
     4  	"io"
     5  )
     6  
     7  /*
     8  	This file is full of "typical" templates.
     9  	They may not be used by *every* type and representation,
    10  	but if they're extracted here, they're at least used by *many*.
    11  */
    12  
    13  // The codegen do-not-edit warning comment.
    14  // Follows the pattern in https://golang.org/s/generatedcode / https://github.com/golang/go/issues/13560#issuecomment-288457920 .
    15  // Should appear somewhere near the top of every file (though precise order doesn't matter).
    16  const doNotEditComment = `// Code generated by go-ipld-prime gengo.  DO NOT EDIT.`
    17  
    18  // emitNativeMaybe turns out to be completely agnostic to pretty much everything;
    19  // it doesn't vary by kind at all, and has never yet ended up needing specialization.
    20  func emitNativeMaybe(w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
    21  	doTemplate(`
    22  		type _{{ .Type | TypeSymbol }}__Maybe struct {
    23  			m schema.Maybe
    24  			v {{if not (MaybeUsesPtr .Type) }}_{{end}}{{ .Type | TypeSymbol }}
    25  		}
    26  		type Maybe{{ .Type | TypeSymbol }} = *_{{ .Type | TypeSymbol }}__Maybe
    27  
    28  		func (m Maybe{{ .Type | TypeSymbol }}) IsNull() bool {
    29  			return m.m == schema.Maybe_Null
    30  		}
    31  		func (m Maybe{{ .Type | TypeSymbol }}) IsAbsent() bool {
    32  			return m.m == schema.Maybe_Absent
    33  		}
    34  		func (m Maybe{{ .Type | TypeSymbol }}) Exists() bool {
    35  			return m.m == schema.Maybe_Value
    36  		}
    37  		func (m Maybe{{ .Type | TypeSymbol }}) AsNode() datamodel.Node {
    38  			switch m.m {
    39  				case schema.Maybe_Absent:
    40  					return datamodel.Absent
    41  				case schema.Maybe_Null:
    42  					return datamodel.Null
    43  				case schema.Maybe_Value:
    44  					return {{if not (MaybeUsesPtr .Type) }}&{{end}}m.v
    45  				default:
    46  					panic("unreachable")
    47  			}
    48  		}
    49  		func (m Maybe{{ .Type | TypeSymbol }}) Must() {{ .Type | TypeSymbol }} {
    50  			if !m.Exists() {
    51  				panic("unbox of a maybe rejected")
    52  			}
    53  			return {{if not (MaybeUsesPtr .Type) }}&{{end}}m.v
    54  		}
    55  	`, w, adjCfg, data)
    56  }
    57  
    58  func emitNativeType_scalar(w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
    59  	// Using a struct with a single member is the same size in memory as a typedef,
    60  	//  while also having the advantage of meaning we can block direct casting,
    61  	//   which is desirable because the compiler then ensures our validate methods can't be evaded.
    62  	doTemplate(`
    63  		{{- if Comments -}}
    64  		// {{ .Type | TypeSymbol }} matches the IPLD Schema type "{{ .Type.Name }}".  It has {{ .Kind }} kind.
    65  		{{- end}}
    66  		type {{ .Type | TypeSymbol }} = *_{{ .Type | TypeSymbol }}
    67  		type _{{ .Type | TypeSymbol }} struct{ x {{ .Kind | KindPrim }} }
    68  	`, w, adjCfg, data)
    69  }
    70  
    71  func emitNativeAccessors_scalar(w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
    72  	// The node interface's `AsFoo` method is almost sufficient... but
    73  	//  this method unboxes without needing to return an error that's statically impossible,
    74  	//   which makes it easier to use in chaining.
    75  	doTemplate(`
    76  		func (n {{ .Type | TypeSymbol }}) {{ .Kind.String | title }}() {{ .Kind | KindPrim }} {
    77  			return n.x
    78  		}
    79  	`, w, adjCfg, data)
    80  }
    81  
    82  func emitNativeBuilder_scalar(w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
    83  	// Generate a single-step construction function -- this is easy to do for a scalar,
    84  	//  and all representations of scalar kind can be expected to have a method like this.
    85  	// The function is attached to the NodePrototype for convenient namespacing;
    86  	//  it needs no new memory, so it would be inappropriate to attach to the builder or assembler.
    87  	// FUTURE: should engage validation flow.
    88  	doTemplate(`
    89  		func (_{{ .Type | TypeSymbol }}__Prototype) From{{ .Kind.String | title }}(v {{ .Kind | KindPrim }}) ({{ .Type | TypeSymbol }}, error) {
    90  			n := _{{ .Type | TypeSymbol }}{v}
    91  			return &n, nil
    92  		}
    93  	`, w, adjCfg, data)
    94  }
    95  
    96  func emitNodeTypeAssertions_typical(w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
    97  	doTemplate(`
    98  		var _ datamodel.Node = ({{ .Type | TypeSymbol }})(&_{{ .Type | TypeSymbol }}{})
    99  		var _ schema.TypedNode = ({{ .Type | TypeSymbol }})(&_{{ .Type | TypeSymbol }}{})
   100  	`, w, adjCfg, data)
   101  }
   102  
   103  func emitNodeMethodAsKind_scalar(w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
   104  	doTemplate(`
   105  		func (n {{ .Type | TypeSymbol }}) As{{ .Kind.String | title }}() ({{ .Kind | KindPrim }}, error) {
   106  			return n.x, nil
   107  		}
   108  	`, w, adjCfg, data)
   109  }
   110  
   111  func emitNodeMethodPrototype_typical(w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
   112  	doTemplate(`
   113  		func ({{ if .IsRepr }}_{{end}}{{ .Type | TypeSymbol }}{{ if .IsRepr }}__Repr{{end}}) Prototype() datamodel.NodePrototype {
   114  			return _{{ .Type | TypeSymbol }}__{{ if .IsRepr }}Repr{{end}}Prototype{}
   115  		}
   116  	`, w, adjCfg, data)
   117  }
   118  
   119  // nodePrototype doesn't really vary textually at all between types and kinds
   120  // because it's just builders and standard resets.
   121  func emitNodePrototypeType_typical(w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
   122  	doTemplate(`
   123  		type _{{ .Type | TypeSymbol }}__{{ if .IsRepr }}Repr{{end}}Prototype struct{}
   124  
   125  		func (_{{ .Type | TypeSymbol }}__{{ if .IsRepr }}Repr{{end}}Prototype) NewBuilder() datamodel.NodeBuilder {
   126  			var nb _{{ .Type | TypeSymbol }}__{{ if .IsRepr }}Repr{{end}}Builder
   127  			nb.Reset()
   128  			return &nb
   129  		}
   130  	`, w, adjCfg, data)
   131  }
   132  
   133  // emitTypicalTypedNodeMethodRepresentation does... what it says on the tin.
   134  //
   135  // For most types, the way to get the representation node pointer doesn't
   136  // textually depend on either the node implementation details nor what the representation strategy is,
   137  // or really much at all for that matter.
   138  // It only depends on that they have the same structure, so this cast works.
   139  //
   140  // Most (all?) types can use this.  However, it's here rather in the mixins, for two reasons:
   141  // one, it still seems possible to imagine we'll have a type someday for which this pattern won't hold;
   142  // and two, mixins are also used in the repr generators, and it wouldn't be all sane for this method to end up also on reprs.
   143  func emitTypicalTypedNodeMethodRepresentation(w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
   144  	doTemplate(`
   145  		func (n {{ .Type | TypeSymbol }}) Representation() datamodel.Node {
   146  			return (*_{{ .Type | TypeSymbol }}__Repr)(n)
   147  		}
   148  	`, w, adjCfg, data)
   149  }
   150  
   151  // Turns out basically all builders are just an embed of the corresponding assembler.
   152  func emitEmitNodeBuilderType_typical(w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
   153  	doTemplate(`
   154  		type _{{ .Type | TypeSymbol }}__{{ if .IsRepr }}Repr{{end}}Builder struct {
   155  			_{{ .Type | TypeSymbol }}__{{ if .IsRepr }}Repr{{end}}Assembler
   156  		}
   157  	`, w, adjCfg, data)
   158  }
   159  
   160  // Builder build and reset methods are common even when some parts of the assembler vary.
   161  // We count on the zero value of any addntl non-common fields of the assembler being correct.
   162  func emitNodeBuilderMethods_typical(w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
   163  	doTemplate(`
   164  		func (nb *_{{ .Type | TypeSymbol }}__{{ if .IsRepr }}Repr{{end}}Builder) Build() datamodel.Node {
   165  			if *nb.m != schema.Maybe_Value {
   166  				panic("invalid state: cannot call Build on an assembler that's not finished")
   167  			}
   168  			return nb.w
   169  		}
   170  		func (nb *_{{ .Type | TypeSymbol }}__{{ if .IsRepr }}Repr{{end}}Builder) Reset() {
   171  			var w _{{ .Type | TypeSymbol }}
   172  			var m schema.Maybe
   173  			*nb = _{{ .Type | TypeSymbol }}__{{ if .IsRepr }}Repr{{end}}Builder{_{{ .Type | TypeSymbol }}__{{ if .IsRepr }}Repr{{end}}Assembler{w: &w, m: &m}}
   174  		}
   175  	`, w, adjCfg, data)
   176  }
   177  
   178  // emitNodeAssemblerType_scalar emits a NodeAssembler that's typical for a scalar.
   179  // Types that are recursive tend to have more state and custom stuff, so won't use this
   180  // (although the 'm' and 'w' variable names may still be presumed universally).
   181  func emitNodeAssemblerType_scalar(w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
   182  	doTemplate(`
   183  		type _{{ .Type | TypeSymbol }}__Assembler struct {
   184  			w *_{{ .Type | TypeSymbol }}
   185  			m *schema.Maybe
   186  		}
   187  
   188  		func (na *_{{ .Type | TypeSymbol }}__Assembler) reset() {}
   189  	`, w, adjCfg, data)
   190  }
   191  
   192  func emitNodeAssemblerMethodAssignNull_scalar(w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
   193  	doTemplate(`
   194  		func (na *_{{ .Type | TypeSymbol }}__{{ if .IsRepr }}Repr{{end}}Assembler) AssignNull() error {
   195  			switch *na.m {
   196  			case allowNull:
   197  				*na.m = schema.Maybe_Null
   198  				return nil
   199  			case schema.Maybe_Absent:
   200  				return mixins.{{ .Kind.String | title }}Assembler{TypeName: "{{ .PkgName }}.{{ .TypeName }}{{ if .IsRepr }}.Repr{{end}}"}.AssignNull()
   201  			case schema.Maybe_Value, schema.Maybe_Null:
   202  				panic("invalid state: cannot assign into assembler that's already finished")
   203  			}
   204  			panic("unreachable")
   205  		}
   206  	`, w, adjCfg, data)
   207  }
   208  
   209  // almost the same as the variant for scalars, but also has to check for midvalue state.
   210  func emitNodeAssemblerMethodAssignNull_recursive(w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
   211  	doTemplate(`
   212  		func (na *_{{ .Type | TypeSymbol }}__{{ if .IsRepr }}Repr{{end}}Assembler) AssignNull() error {
   213  			switch *na.m {
   214  			case allowNull:
   215  				*na.m = schema.Maybe_Null
   216  				return nil
   217  			case schema.Maybe_Absent:
   218  				return mixins.{{ .Kind.String | title }}Assembler{TypeName: "{{ .PkgName }}.{{ .TypeName }}{{ if .IsRepr }}.Repr{{end}}"}.AssignNull()
   219  			case schema.Maybe_Value, schema.Maybe_Null:
   220  				panic("invalid state: cannot assign into assembler that's already finished")
   221  			case midvalue:
   222  				panic("invalid state: cannot assign null into an assembler that's already begun working on recursive structures!")
   223  			}
   224  			panic("unreachable")
   225  		}
   226  	`, w, adjCfg, data)
   227  }
   228  
   229  // works for the AssignFoo methods for scalar kinds that are just boxing a thing.
   230  // There's no equivalent of this at all for recursives -- they're too diverse.
   231  func emitNodeAssemblerMethodAssignKind_scalar(w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
   232  	// This method contains a branch to support MaybeUsesPtr because new memory may need to be allocated.
   233  	//  This allocation only happens if the 'w' ptr is nil, which means we're being used on a Maybe;
   234  	//  otherwise, the 'w' ptr should already be set, and we fill that memory location without allocating, as usual.
   235  	doTemplate(`
   236  		func (na *_{{ .Type | TypeSymbol }}__Assembler) Assign{{ .Kind.String | title }}(v {{ .Kind | KindPrim }}) error {
   237  			switch *na.m {
   238  			case schema.Maybe_Value, schema.Maybe_Null:
   239  				panic("invalid state: cannot assign into assembler that's already finished")
   240  			}
   241  			{{- if .Type | MaybeUsesPtr }}
   242  			if na.w == nil {
   243  				na.w = &_{{ .Type | TypeSymbol }}{}
   244  			}
   245  			{{- end}}
   246  			na.w.x = v
   247  			*na.m = schema.Maybe_Value
   248  			return nil
   249  		}
   250  	`, w, adjCfg, data)
   251  }
   252  
   253  // leans heavily on the fact all the AsFoo and AssignFoo methods follow a very consistent textual pattern.
   254  // FUTURE: may be able to get this to work for recursives, too -- but maps and lists each have very unique bottom thirds of this function.
   255  func emitNodeAssemblerMethodAssignNode_scalar(w io.Writer, adjCfg *AdjunctCfg, data interface{}) {
   256  	// AssignNode goes through three phases:
   257  	// 1. is it null?  Jump over to AssignNull (which may or may not reject it).
   258  	// 2. is it our own type?  Handle specially -- we might be able to do efficient things.
   259  	// 3. is it the right kind to morph into us?  Do so.
   260  	doTemplate(`
   261  		func (na *_{{ .Type | TypeSymbol }}__Assembler) AssignNode(v datamodel.Node) error {
   262  			if v.IsNull() {
   263  				return na.AssignNull()
   264  			}
   265  			if v2, ok := v.(*_{{ .Type | TypeSymbol }}); ok {
   266  				switch *na.m {
   267  				case schema.Maybe_Value, schema.Maybe_Null:
   268  					panic("invalid state: cannot assign into assembler that's already finished")
   269  				}
   270  				{{- if .Type | MaybeUsesPtr }}
   271  				if na.w == nil {
   272  					na.w = v2
   273  					*na.m = schema.Maybe_Value
   274  					return nil
   275  				}
   276  				{{- end}}
   277  				*na.w = *v2
   278  				*na.m = schema.Maybe_Value
   279  				return nil
   280  			}
   281  			if v2, err := v.As{{ .Kind.String | title }}(); err != nil {
   282  				return err
   283  			} else {
   284  				return na.Assign{{ .Kind.String | title }}(v2)
   285  			}
   286  		}
   287  	`, w, adjCfg, data)
   288  }