github.com/ipld/go-ipld-prime@v0.21.0/schema/gen/go/HACKME_templates.md (about) 1 Notes about how Templates are used in this package 2 ================================================== 3 4 This package makes heavy use of go's `text/template`. 5 6 Some of it is not pretty. But beware of trying to trade elegance for legibility. 7 8 An overview of the choices that got us here: 9 10 ### string templating is fine 11 12 String templating for code is fine, actually. 13 14 An alternative would be to use the golang AST packages. 15 While those are nice... for our purposes, they're a bit verbose. 16 It's not necessarily helpful to have the visual distance between our 17 generation code and the actual result _increase_ (at least, without a good reason). 18 19 ### don't make indirections around uncomplicated munges 20 21 Don't make indirections that aren't needed for simple string operations. 22 (This goes for both the template funcmap, or in the form of reusable templates.) 23 24 One very common situation is that for some type `T`, there's a related thing to be generated 25 called `T__Foo`, where "Foo" is some kind of modifier that is completely predictable 26 and not user-selected nor configurable. 27 28 In this situation: simply conjoining `{{ .T }}__Foo` as string in the template is fine! 29 Don't turn it into a shell game with functions that make the reader jump around more to see what's happening. 30 (Even when refactoring, it's easy enough to use simple string replace on these patterns; 31 extracting a function to enable "changing it in one place" doesn't really add much value.) 32 33 If there's something more complicated going on, or it's user-configurable? 34 Fine, get more functions involved. But be judicious about it. 35 36 (An earlier draft of the code introduced a new method for each modifier form in the example above. 37 The result was... just repeating the modifiers in the function name in the template! 38 It produced more code, yet no additional flexibility. 39 It is advisable to resist making this mistake again ;)) 40 41 ### maintain distinction between **symbol** versus **name** 42 43 - **symbol** is what is used in type and function names in code. 44 - **name** is the string that comes from the schema; it is never modified nor overridable. 45 46 Symbols can change based on adjunct config. 47 (In theory, they could also be munged to avoid collisions with language constructs 48 or favor certain library conventions... however, this is currently not automatic.) 49 50 Names can't change. They're _exactly_ what was written in in the Schema. 51 52 Types and functions and most of the _code_ part of codegen use Symbols. 53 Error messages, reflectable type information, etc, use Names. 54 55 Symbols may also need to be _generated_ for types that don't have names. 56 For example, `type Foo struct { field {String:Int} }` might result in codegen 57 creating *two* symbols: `Foo`, and `Map__String__Int`. 58 59 One way to check that the goal is being satisfied is to consider that 60 someone just experiencing error messages from a program should not end up exposed to any information about: 61 62 - what language the program is written in, 63 - or which codegen tool was used (or even *if* a codegen tool was used), 64 - or what adjunct config was present when codegen was performed. 65 66 (n.b. this consideration does not include errors with stack traces -- then 67 of course you will unavoidably see symbols appear.) 68 69 ### anything that is configurable goes through adjunctCfg; adjunctCfg is accessed via the template funcmap 70 71 For example, all Symbol processing all pipes through an adjunct configuration object. 72 We make this available in the templates via a funcmap so it's available context-free as a nice tidy pipe syntax. 73 74 ### there are several kinds of reuse 75 76 (Oh, boy.) 77 78 It may be wise to read the ["values we can balance"](./HACKME_tradeoffs.md#values-we-can-balance) document before continuing. 79 There's also a _seventh_ tradeoff to consider, in addition to those from that document: 80 how much reuse there is in our _template_ code, and (_eighth!_) how _readable_ and maintainable the template code is. 81 Also, (_ninth!!_) how _fast_ the template code is. 82 83 We've generally favored *all* of the priorities for the output code (speed, size, allocation count, etc) over the niceness of the codegen. 84 We've also _completely_ disregarded speed of the template code (it's always going to be "fast enough"; you don't run this in a hot loop!). 85 When there's a tension between readability and reuse, we've often favored readability. 86 That means sometimes text is outright duplicated, if it seemed like extracting it would make it harder to read the template. 87 88 Here's what we've ended up with: 89 90 - There are mixins from the `node/mixins` package, which save some amount of code and standardize some behaviors. 91 - These end up in the final result code. 92 - It doesn't save *much* code, and they generally don't save *any* binary size (because it all gets inlined). 93 - The consistency is the main virtue of these. 94 - These are used mainly for error handling (specifically, returning of errors for methods called on nodes that have the wrong kind for that method). 95 - There are mixins from the `schema/gen/go/mixins` package. 96 - These are in the template code only -- they don't show up in the final result code. 97 - These attempt to make it easier to create new 'generator' types. (There's a 'generator' type for each kind-plus-representation.) 98 - They only attempt to take away some boilerplate, and you don't _have_ to use them. 99 - There are functions in the template funcmap. 100 - ... not many of them, though. 101 - There's the idea of using associated templates (templates that are invoked by other templates). 102 - There's currently none of this in use. Might it be helpful? Maybe. 103 - There are functions which apply well-known templates to a generator. 104 - These compose at the golang level, so it's easy to have the compiler check that they're all in order without running them (unlike templates, which have to be exercised in order to detect even basic problems like "does this named template exist"). 105 - Many of these assume some pattern of methods on the generator objects. (Not of all these are super well documented.) 106 - Generators usually call out to one or more of these from within the methods that their interface requires them to have. 107 - The generator types themselves are usually split into two parts: the mechanisms for type-plus-repr, and just mechanisms for the type. 108 - The mechanisms for the type alone aren't actually a full generator. The type-plus-repr thing just embeds the type-level semantics in itself. 109 110 *Mostly*, it's general aim has been to keep relatively close to the structure of the code being generated. 111 When reading a generator, one generally has to do *zero* or *one* jump-to-definition in order to see the fulltext of a template -- no more than that. 112 (And so far, all those jump-to-definition lookups are on _go code_, not inside the template -- so an IDE can help you.) 113 114 By example: if there are things which turn out common between _representation kinds_, 115 those will probably end up in a function containing a well-known template, 116 and that will end up being called from the generator type in one of the functions its required to have per its interface contract. 117 118 This all probably has plenty of room for improvement! 119 But know you know the reasoning that got things to be in the shape they are.