mirror of
https://github.com/hanzo-fi/numscript.git
synced 2026-10-30 20:54:52 +00:00
189 lines
4.4 KiB
Go
189 lines
4.4 KiB
Go
package interpreter
|
|
|
|
import (
|
|
"math/big"
|
|
)
|
|
|
|
type Sender struct {
|
|
Account AccountAddress
|
|
Amount *big.Int
|
|
Color string
|
|
}
|
|
|
|
type queue[T any] struct {
|
|
Head T
|
|
Tail *queue[T]
|
|
|
|
// Instead of keeping a single ref of the lastCell and updating the invariant on every push/pop operation,
|
|
// we keep a cache of the last cell on every cell.
|
|
// This makes code much easier and we don't risk breaking the invariant and producing wrong results and other subtle issues
|
|
//
|
|
// While, unlike keeping a single reference (like golang's queue `container/list` package does), this is not always O(1),
|
|
// the amortized time should still be O(1) (the number of steps of traversal while searching the last elem is not higher than the number of .Push() calls)
|
|
lastCell *queue[T]
|
|
}
|
|
|
|
func (s *queue[T]) getLastCell() *queue[T] {
|
|
// check if this is the last cell without reading cache first
|
|
if s.Tail == nil {
|
|
return s
|
|
}
|
|
|
|
// if not, check if cache is present
|
|
if s.lastCell != nil {
|
|
// even if it is, it may be a stale value (as more values could have been pushed), so we check the value recursively
|
|
lastCell := s.lastCell.getLastCell()
|
|
// we do path compression so that next time we get the path immediately
|
|
s.lastCell = lastCell
|
|
return lastCell
|
|
}
|
|
|
|
// if no last value is cached, we traverse recursively to find it
|
|
s.lastCell = s.Tail.getLastCell()
|
|
return s.lastCell
|
|
}
|
|
|
|
func fromSlice[T any](slice []T) *queue[T] {
|
|
var ret *queue[T]
|
|
// TODO use https://pkg.go.dev/slices#Backward in golang 1.23
|
|
for i := len(slice) - 1; i >= 0; i-- {
|
|
ret = &queue[T]{
|
|
Head: slice[i],
|
|
Tail: ret,
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
type fundsQueue struct {
|
|
senders *queue[Sender]
|
|
}
|
|
|
|
func newFundsQueue(senders []Sender) fundsQueue {
|
|
return fundsQueue{
|
|
senders: fromSlice(senders),
|
|
}
|
|
}
|
|
|
|
func (s *fundsQueue) compactTop() {
|
|
for s.senders != nil && s.senders.Tail != nil {
|
|
|
|
first := s.senders.Head
|
|
second := s.senders.Tail.Head
|
|
|
|
if second.Amount.Cmp(big.NewInt(0)) == 0 {
|
|
s.senders = &queue[Sender]{Head: first, Tail: s.senders.Tail.Tail}
|
|
continue
|
|
}
|
|
|
|
if first.Account != second.Account || first.Color != second.Color {
|
|
return
|
|
}
|
|
|
|
s.senders = &queue[Sender]{
|
|
Head: Sender{
|
|
Account: first.Account,
|
|
Color: first.Color,
|
|
Amount: new(big.Int).Add(first.Amount, second.Amount),
|
|
},
|
|
Tail: s.senders.Tail.Tail,
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *fundsQueue) PullAll() []Sender {
|
|
var senders []Sender
|
|
for s.senders != nil {
|
|
senders = append(senders, s.senders.Head)
|
|
s.senders = s.senders.Tail
|
|
}
|
|
return senders
|
|
}
|
|
|
|
func (s *fundsQueue) Push(senders ...Sender) {
|
|
newTail := fromSlice(senders)
|
|
if s.senders == nil {
|
|
s.senders = newTail
|
|
} else {
|
|
cell := s.senders.getLastCell()
|
|
cell.Tail = newTail
|
|
}
|
|
}
|
|
|
|
func (s *fundsQueue) PullAnything(requiredAmount *big.Int) []Sender {
|
|
return s.Pull(requiredAmount, nil)
|
|
}
|
|
|
|
func (s *fundsQueue) PullColored(requiredAmount *big.Int, color string) []Sender {
|
|
return s.Pull(requiredAmount, &color)
|
|
}
|
|
func (s *fundsQueue) PullUncolored(requiredAmount *big.Int) []Sender {
|
|
return s.PullColored(requiredAmount, "")
|
|
}
|
|
|
|
func (s *fundsQueue) Pull(requiredAmount *big.Int, color *string) []Sender {
|
|
// clone so that we can manipulate this arg
|
|
requiredAmount = new(big.Int).Set(requiredAmount)
|
|
|
|
// TODO preallocate for perfs
|
|
var out []Sender
|
|
|
|
for requiredAmount.Cmp(big.NewInt(0)) != 0 && s.senders != nil {
|
|
s.compactTop()
|
|
|
|
available := s.senders.Head
|
|
s.senders = s.senders.Tail
|
|
|
|
if color != nil && available.Color != *color {
|
|
out1 := s.Pull(requiredAmount, color)
|
|
s.senders = &queue[Sender]{
|
|
Head: available,
|
|
Tail: s.senders,
|
|
}
|
|
out = append(out, out1...)
|
|
break
|
|
}
|
|
|
|
switch available.Amount.Cmp(requiredAmount) {
|
|
case -1: // not enough:
|
|
out = append(out, available)
|
|
requiredAmount.Sub(requiredAmount, available.Amount)
|
|
|
|
case 1: // more than enough
|
|
s.senders = &queue[Sender]{
|
|
Head: Sender{
|
|
Account: available.Account,
|
|
Color: available.Color,
|
|
Amount: new(big.Int).Sub(available.Amount, requiredAmount),
|
|
},
|
|
Tail: s.senders,
|
|
}
|
|
fallthrough
|
|
|
|
case 0: // exactly the same
|
|
out = append(out, Sender{
|
|
Account: available.Account,
|
|
Color: available.Color,
|
|
Amount: new(big.Int).Set(requiredAmount),
|
|
})
|
|
return out
|
|
}
|
|
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
// Clone the queue so that you can safely mutate one without mutating the other
|
|
func (s fundsQueue) Clone() fundsQueue {
|
|
fq := newFundsQueue(nil)
|
|
|
|
senders := s.senders
|
|
for senders != nil {
|
|
fq.Push(senders.Head)
|
|
senders = senders.Tail
|
|
}
|
|
|
|
return fq
|
|
}
|