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 }