github.com/maypok86/otter@v1.2.1/cmd/generator/main.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  )
    11  
    12  type feature struct {
    13  	name string
    14  }
    15  
    16  func newFeature(name string) feature {
    17  	return feature{
    18  		name: name,
    19  	}
    20  }
    21  
    22  func (f feature) alias() string {
    23  	return string(f.name[0])
    24  }
    25  
    26  var (
    27  	expiration = newFeature("expiration")
    28  	cost       = newFeature("cost")
    29  
    30  	declaredFeatures = []feature{
    31  		expiration,
    32  		cost,
    33  	}
    34  
    35  	nodeTypes      []string
    36  	aliasToFeature map[string]feature
    37  )
    38  
    39  func init() {
    40  	aliasToFeature = make(map[string]feature, len(declaredFeatures))
    41  	for _, f := range declaredFeatures {
    42  		aliasToFeature[f.alias()] = f
    43  	}
    44  
    45  	enabled := make([][]bool, len(declaredFeatures))
    46  	for i := 0; i < len(enabled); i++ {
    47  		enabled[i] = []bool{false, true}
    48  	}
    49  
    50  	// cartesian product
    51  	total := len(enabled)
    52  	totalCombinations := 1 << total
    53  	combinations := make([][]bool, 0, totalCombinations)
    54  	for i := 0; i < totalCombinations; i++ {
    55  		combination := make([]bool, 0, total)
    56  		for j := 0; j < total; j++ {
    57  			if ((i >> j) & 1) == 1 {
    58  				combination = append(combination, enabled[j][0])
    59  			} else {
    60  				combination = append(combination, enabled[j][1])
    61  			}
    62  		}
    63  		combinations = append(combinations, combination)
    64  	}
    65  
    66  	nodeTypes = make([]string, 0, len(combinations))
    67  	for _, combination := range combinations {
    68  		var sb strings.Builder
    69  		sb.WriteString("b")
    70  		for i := 0; i < len(combination); i++ {
    71  			if combination[i] {
    72  				sb.WriteString(declaredFeatures[i].alias())
    73  			}
    74  		}
    75  		nodeTypes = append(nodeTypes, sb.String())
    76  	}
    77  }
    78  
    79  func getFeatures(nodeType string) map[feature]bool {
    80  	features := make(map[feature]bool, len(nodeType)-1)
    81  	for _, alias := range nodeType[1:] {
    82  		feature, ok := aliasToFeature[string(alias)]
    83  		if !ok {
    84  			panic("not valid node alias")
    85  		}
    86  
    87  		features[feature] = true
    88  	}
    89  	return features
    90  }
    91  
    92  type writer struct {
    93  	buf    bytes.Buffer
    94  	indent string
    95  }
    96  
    97  func newWriter() *writer {
    98  	return &writer{}
    99  }
   100  
   101  func (w *writer) p(format string, args ...any) {
   102  	fmt.Fprintf(&w.buf, w.indent+format+"\n", args...)
   103  }
   104  
   105  func (w *writer) in() {
   106  	w.indent += "\t"
   107  }
   108  
   109  func (w *writer) out() {
   110  	if w.indent != "" {
   111  		w.indent = w.indent[0 : len(w.indent)-1]
   112  	}
   113  }
   114  
   115  func (w *writer) output() []byte {
   116  	return w.buf.Bytes()
   117  }
   118  
   119  type generator struct {
   120  	*writer
   121  
   122  	structName string
   123  	features   map[feature]bool
   124  }
   125  
   126  func newGenerator(nodeType string) *generator {
   127  	return &generator{
   128  		writer:     newWriter(),
   129  		structName: strings.ToUpper(nodeType),
   130  		features:   getFeatures(nodeType),
   131  	}
   132  }
   133  
   134  func (g *generator) printImports() {
   135  	g.p("import (")
   136  	g.in()
   137  	g.p("\"sync/atomic\"")
   138  	g.p("\"unsafe\"")
   139  	if g.features[expiration] {
   140  		g.p("")
   141  		g.p("\"github.com/maypok86/otter/internal/unixtime\"")
   142  	}
   143  	g.out()
   144  	g.p(")")
   145  	g.p("")
   146  }
   147  
   148  func (g *generator) printStructComment() {
   149  	g.p("// %s is a cache entry that provide the following features:", g.structName)
   150  	g.p("//")
   151  	g.p("// 1. Base")
   152  	i := 2
   153  	for _, f := range declaredFeatures {
   154  		if g.features[f] {
   155  			featureTitle := strings.Title(strings.ToLower(f.name))
   156  			g.p("//")
   157  			g.p("// %d. %s", i, featureTitle)
   158  			i++
   159  		}
   160  	}
   161  }
   162  
   163  func (g *generator) printStruct() {
   164  	g.printStructComment()
   165  
   166  	// print struct definition
   167  	g.p("type %s[K comparable, V any] struct {", g.structName)
   168  	g.in()
   169  	g.p("key        K")
   170  	g.p("value      V")
   171  	g.p("prev       *%s[K, V]", g.structName)
   172  	g.p("next       *%s[K, V]", g.structName)
   173  
   174  	if g.features[expiration] {
   175  		g.p("prevExp    *%s[K, V]", g.structName)
   176  		g.p("nextExp    *%s[K, V]", g.structName)
   177  		g.p("expiration uint32")
   178  	}
   179  	if g.features[cost] {
   180  		g.p("cost       uint32")
   181  	}
   182  
   183  	g.p("state      uint32")
   184  	g.p("frequency  uint8")
   185  	g.p("queueType  uint8")
   186  	g.out()
   187  	g.p("}")
   188  	g.p("")
   189  }
   190  
   191  func (g *generator) printConstructors() {
   192  	g.p("// New%s creates a new %s.", g.structName, g.structName)
   193  	g.p("func New%s[K comparable, V any](key K, value V, expiration, cost uint32) Node[K, V] {", g.structName)
   194  	g.in()
   195  	g.p("return &%s[K, V]{", g.structName)
   196  	g.in()
   197  	g.p("key:        key,")
   198  	g.p("value:      value,")
   199  	if g.features[expiration] {
   200  		g.p("expiration: expiration,")
   201  	}
   202  	if g.features[cost] {
   203  		g.p("cost:       cost,")
   204  	}
   205  	g.p("state:      aliveState,")
   206  	g.out()
   207  	g.p("}")
   208  	g.out()
   209  	g.p("}")
   210  	g.p("")
   211  
   212  	g.p("// CastPointerTo%s casts a pointer to %s.", g.structName, g.structName)
   213  	g.p("func CastPointerTo%s[K comparable, V any](ptr unsafe.Pointer) Node[K, V] {", g.structName)
   214  	g.in()
   215  	g.p("return (*%s[K, V])(ptr)", g.structName)
   216  	g.out()
   217  	g.p("}")
   218  	g.p("")
   219  }
   220  
   221  func (g *generator) printFunctions() {
   222  	g.p("func (n *%s[K, V]) Key() K {", g.structName)
   223  	g.in()
   224  	g.p("return n.key")
   225  	g.out()
   226  	g.p("}")
   227  	g.p("")
   228  
   229  	g.p("func (n *%s[K, V]) Value() V {", g.structName)
   230  	g.in()
   231  	g.p("return n.value")
   232  	g.out()
   233  	g.p("}")
   234  	g.p("")
   235  
   236  	g.p("func (n *%s[K, V]) AsPointer() unsafe.Pointer {", g.structName)
   237  	g.in()
   238  	g.p("return unsafe.Pointer(n)")
   239  	g.out()
   240  	g.p("}")
   241  	g.p("")
   242  
   243  	g.p("func (n *%s[K, V]) Prev() Node[K, V] {", g.structName)
   244  	g.in()
   245  	g.p("return n.prev")
   246  	g.out()
   247  	g.p("}")
   248  	g.p("")
   249  
   250  	g.p("func (n *%s[K, V]) SetPrev(v Node[K, V]) {", g.structName)
   251  	g.in()
   252  	g.p("if v == nil {")
   253  	g.in()
   254  	g.p("n.prev = nil")
   255  	g.p("return")
   256  	g.out()
   257  	g.p("}")
   258  	g.p("n.prev = (*%s[K, V])(v.AsPointer())", g.structName)
   259  	g.out()
   260  	g.p("}")
   261  	g.p("")
   262  
   263  	g.p("func (n *%s[K, V]) Next() Node[K, V] {", g.structName)
   264  	g.in()
   265  	g.p("return n.next")
   266  	g.out()
   267  	g.p("}")
   268  	g.p("")
   269  
   270  	g.p("func (n *%s[K, V]) SetNext(v Node[K, V]) {", g.structName)
   271  	g.in()
   272  	g.p("if v == nil {")
   273  	g.in()
   274  	g.p("n.next = nil")
   275  	g.p("return")
   276  	g.out()
   277  	g.p("}")
   278  	g.p("n.next = (*%s[K, V])(v.AsPointer())", g.structName)
   279  	g.out()
   280  	g.p("}")
   281  	g.p("")
   282  
   283  	g.p("func (n *%s[K, V]) PrevExp() Node[K, V] {", g.structName)
   284  	g.in()
   285  	if g.features[expiration] {
   286  		g.p("return n.prevExp")
   287  	} else {
   288  		g.p("panic(\"not implemented\")")
   289  	}
   290  	g.out()
   291  	g.p("}")
   292  	g.p("")
   293  
   294  	g.p("func (n *%s[K, V]) SetPrevExp(v Node[K, V]) {", g.structName)
   295  	g.in()
   296  	if g.features[expiration] {
   297  		g.p("if v == nil {")
   298  		g.in()
   299  		g.p("n.prevExp = nil")
   300  		g.p("return")
   301  		g.out()
   302  		g.p("}")
   303  		g.p("n.prevExp = (*%s[K, V])(v.AsPointer())", g.structName)
   304  	} else {
   305  		g.p("panic(\"not implemented\")")
   306  	}
   307  	g.out()
   308  	g.p("}")
   309  	g.p("")
   310  
   311  	g.p("func (n *%s[K, V]) NextExp() Node[K, V] {", g.structName)
   312  	g.in()
   313  	if g.features[expiration] {
   314  		g.p("return n.nextExp")
   315  	} else {
   316  		g.p("panic(\"not implemented\")")
   317  	}
   318  	g.out()
   319  	g.p("}")
   320  	g.p("")
   321  
   322  	g.p("func (n *%s[K, V]) SetNextExp(v Node[K, V]) {", g.structName)
   323  	g.in()
   324  	if g.features[expiration] {
   325  		g.p("if v == nil {")
   326  		g.in()
   327  		g.p("n.nextExp = nil")
   328  		g.p("return")
   329  		g.out()
   330  		g.p("}")
   331  		g.p("n.nextExp = (*%s[K, V])(v.AsPointer())", g.structName)
   332  	} else {
   333  		g.p("panic(\"not implemented\")")
   334  	}
   335  	g.out()
   336  	g.p("}")
   337  	g.p("")
   338  
   339  	g.p("func (n *%s[K, V]) HasExpired() bool {", g.structName)
   340  	g.in()
   341  	if g.features[expiration] {
   342  		g.p("return n.expiration <= unixtime.Now()")
   343  	} else {
   344  		g.p("return false")
   345  	}
   346  	g.out()
   347  	g.p("}")
   348  	g.p("")
   349  
   350  	g.p("func (n *%s[K, V]) Expiration() uint32 {", g.structName)
   351  	g.in()
   352  	if g.features[expiration] {
   353  		g.p("return n.expiration")
   354  	} else {
   355  		g.p("panic(\"not implemented\")")
   356  	}
   357  	g.out()
   358  	g.p("}")
   359  	g.p("")
   360  
   361  	g.p("func (n *%s[K, V]) Cost() uint32 {", g.structName)
   362  	g.in()
   363  	if g.features[cost] {
   364  		g.p("return n.cost")
   365  	} else {
   366  		g.p("return 1")
   367  	}
   368  	g.out()
   369  	g.p("}")
   370  
   371  	const otherFunctions = `
   372  func (n *%s[K, V]) IsAlive() bool {
   373  	return atomic.LoadUint32(&n.state) == aliveState
   374  }
   375  
   376  func (n *%s[K, V]) Die() {
   377  	atomic.StoreUint32(&n.state, deadState)
   378  }
   379  
   380  func (n *%s[K, V]) Frequency() uint8 {
   381  	return n.frequency
   382  }
   383  
   384  func (n *%s[K, V]) IncrementFrequency() {
   385  	n.frequency = minUint8(n.frequency+1, maxFrequency)
   386  }
   387  
   388  func (n *%s[K, V]) DecrementFrequency() {
   389  	n.frequency--
   390  }
   391  
   392  func (n *%s[K, V]) ResetFrequency() {
   393  	n.frequency = 0
   394  }
   395  
   396  func (n *%s[K, V]) MarkSmall() {
   397  	n.queueType = smallQueueType
   398  }
   399  
   400  func (n *%s[K, V]) IsSmall() bool {
   401  	return n.queueType == smallQueueType
   402  }
   403  
   404  func (n *%s[K, V]) MarkMain() {
   405  	n.queueType = mainQueueType
   406  }
   407  
   408  func (n *%s[K, V]) IsMain() bool {
   409  	return n.queueType == mainQueueType
   410  }
   411  
   412  func (n *%s[K, V]) Unmark() {
   413  	n.queueType = unknownQueueType
   414  }`
   415  
   416  	count := strings.Count(otherFunctions, "%s")
   417  	args := make([]any, 0, count)
   418  	for i := 0; i < count; i++ {
   419  		args = append(args, g.structName)
   420  	}
   421  
   422  	g.p(otherFunctions, args...)
   423  }
   424  
   425  func run(nodeType, dir string) error {
   426  	g := newGenerator(nodeType)
   427  	g.p("// Code generated by NodeGenerator. DO NOT EDIT.")
   428  	g.p("")
   429  	g.p("// Package node is a generated generator package.")
   430  	g.p("package node")
   431  	g.p("")
   432  
   433  	g.printImports()
   434  
   435  	g.printStruct()
   436  	g.printConstructors()
   437  
   438  	g.printFunctions()
   439  
   440  	fileName := fmt.Sprintf("%s.go", nodeType)
   441  	filePath := filepath.Join(dir, fileName)
   442  
   443  	f, err := os.Create(filePath)
   444  	if err != nil {
   445  		return fmt.Errorf("create file %s: %w", filePath, err)
   446  	}
   447  	defer f.Close()
   448  
   449  	if _, err := f.Write(g.output()); err != nil {
   450  		return fmt.Errorf("write output: %w", err)
   451  	}
   452  
   453  	return nil
   454  }
   455  
   456  func printManager(dir string) error {
   457  	const nodeManager = `// Code generated by NodeGenerator. DO NOT EDIT.
   458  
   459  // Package node is a generated generator package.
   460  package node
   461  
   462  import (
   463  	"strings"
   464  	"unsafe"
   465  )
   466  
   467  const (
   468  	unknownQueueType uint8 = iota
   469  	smallQueueType
   470  	mainQueueType
   471  	
   472  	maxFrequency uint8 = 3
   473  )
   474  
   475  const (
   476  	aliveState uint32 = iota
   477  	deadState
   478  )
   479  
   480  // Node is a cache entry.
   481  type Node[K comparable, V any] interface {
   482  	// Key returns the key.
   483  	Key() K
   484  	// Value returns the value.
   485  	Value() V
   486  	// AsPointer returns the node as a pointer.
   487  	AsPointer() unsafe.Pointer
   488  	// Prev returns the previous node in the eviction policy.
   489  	Prev() Node[K, V]
   490  	// SetPrev sets the previous node in the eviction policy.
   491  	SetPrev(v Node[K, V])
   492  	// Next returns the next node in the eviction policy.
   493  	Next() Node[K, V]
   494  	// SetNext sets the next node in the eviction policy.
   495  	SetNext(v Node[K, V])
   496  	// PrevExp returns the previous node in the expiration policy.
   497  	PrevExp() Node[K, V]
   498  	// SetPrevExp sets the previous node in the expiration policy.
   499  	SetPrevExp(v Node[K, V])
   500  	// NextExp returns the next node in the expiration policy.
   501  	NextExp() Node[K, V]
   502  	// SetNextExp sets the next node in the expiration policy.
   503  	SetNextExp(v Node[K, V])
   504  	// HasExpired returns true if node has expired.
   505  	HasExpired() bool
   506  	// Expiration returns the expiration time.
   507  	Expiration() uint32
   508  	// Cost returns the cost of the node.
   509  	Cost() uint32
   510  	// IsAlive returns true if the entry is available in the hash-table.
   511  	IsAlive() bool
   512  	// Die sets the node to the dead state.
   513  	Die()
   514  	// Frequency returns the frequency of the node.
   515  	Frequency() uint8
   516  	// IncrementFrequency increments the frequency of the node.
   517  	IncrementFrequency()
   518  	// DecrementFrequency decrements the frequency of the node.
   519  	DecrementFrequency()
   520  	// ResetFrequency resets the frequency.
   521  	ResetFrequency()
   522  	// MarkSmall sets the status to the small queue.
   523  	MarkSmall()
   524  	// IsSmall returns true if node is in the small queue.
   525  	IsSmall() bool
   526  	// MarkMain sets the status to the main queue.
   527  	MarkMain()
   528  	// IsMain returns true if node is in the main queue.
   529  	IsMain() bool
   530  	// Unmark sets the status to unknown.
   531  	Unmark()
   532  }
   533  
   534  func Equals[K comparable, V any](a, b Node[K, V]) bool {
   535  	if a == nil {
   536  		return b == nil || b.AsPointer() == nil
   537  	}
   538  	if b == nil {
   539  		return a.AsPointer() == nil
   540  	}
   541  	return a.AsPointer() == b.AsPointer()
   542  }
   543  
   544  type Config struct {
   545  	WithExpiration bool
   546  	WithCost       bool
   547  }
   548  
   549  type Manager[K comparable, V any] struct {
   550  	create      func(key K, value V, expiration, cost uint32) Node[K, V]
   551  	fromPointer func(ptr unsafe.Pointer) Node[K, V]
   552  }
   553  
   554  func NewManager[K comparable, V any](c Config) *Manager[K, V] {
   555  	var sb strings.Builder
   556  	sb.WriteString("b")
   557  	if c.WithExpiration {
   558  		sb.WriteString("e")
   559  	}
   560  	if c.WithCost {
   561  		sb.WriteString("c")
   562  	}
   563  	nodeType := sb.String()
   564  	m := &Manager[K, V]{}
   565  `
   566  
   567  	const nodeFooter = `return m
   568  }
   569  
   570  func (m *Manager[K, V]) Create(key K, value V, expiration, cost uint32) Node[K, V] {
   571  	return m.create(key, value, expiration, cost)
   572  }
   573  
   574  func (m *Manager[K, V]) FromPointer(ptr unsafe.Pointer) Node[K, V] {
   575  	return m.fromPointer(ptr)
   576  }
   577  
   578  func minUint8(a, b uint8) uint8 {
   579  	if a < b {
   580  		return a
   581  	}
   582  
   583  	return b
   584  }`
   585  	w := newWriter()
   586  
   587  	w.p(nodeManager)
   588  	w.in()
   589  	w.p("switch nodeType {")
   590  	for _, nodeType := range nodeTypes {
   591  		w.p("case \"%s\":", nodeType)
   592  		w.in()
   593  		structName := strings.ToUpper(nodeType)
   594  		w.p("m.create = New%s[K, V]", structName)
   595  		w.p("m.fromPointer = CastPointerTo%s[K, V]", structName)
   596  		w.out()
   597  	}
   598  	w.p("default:")
   599  	w.in()
   600  	w.p("panic(\"not valid nodeType\")")
   601  	w.out()
   602  	w.p("}")
   603  	w.p(nodeFooter)
   604  
   605  	managerPath := filepath.Join(dir, "manager.go")
   606  	f, err := os.Create(managerPath)
   607  	if err != nil {
   608  		return fmt.Errorf("create file %s: %w", managerPath, err)
   609  	}
   610  	defer f.Close()
   611  
   612  	if _, err := f.Write(w.output()); err != nil {
   613  		return fmt.Errorf("write output: %w", err)
   614  	}
   615  
   616  	return nil
   617  }
   618  
   619  func main() {
   620  	dir := os.Args[1]
   621  
   622  	if err := os.RemoveAll(dir); err != nil {
   623  		log.Fatalf("remove dir: %s\n", err.Error())
   624  	}
   625  
   626  	if err := os.MkdirAll(dir, os.ModePerm); err != nil {
   627  		log.Fatalf("create dir %s: %s", dir, err.Error())
   628  	}
   629  
   630  	for _, nodeType := range nodeTypes {
   631  		if err := run(nodeType, dir); err != nil {
   632  			log.Fatal(err)
   633  		}
   634  	}
   635  
   636  	if err := printManager(dir); err != nil {
   637  		log.Fatal(err)
   638  	}
   639  }