Skip to content

Commit

Permalink
moved main file and updated goreleaser yaml
Browse files Browse the repository at this point in the history
WIP

more test for get balances

changed public API

removed duplicate struct

added ctx param to interpreter run

re-export function

export more types

removed some todos

added more tests for balance

moved main file

run go mod tidy

updated gorelease config

handle big ints
  • Loading branch information
ascandone committed Oct 7, 2024
1 parent 6b6a69c commit c4c396a
Show file tree
Hide file tree
Showing 16 changed files with 925 additions and 293 deletions.
1 change: 1 addition & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ before:
builds:
- env:
- CGO_ENABLED=0
main: ./internal/numscript/numscript.go
goos:
- linux
- windows
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ require (
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect
golang.org/x/exp v0.0.0-20240707233637-46b078467d37
)
23 changes: 12 additions & 11 deletions internal/cmd/run.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"context"
"encoding/json"
"fmt"
"io"
Expand All @@ -26,10 +27,10 @@ var runStdinFlag bool
var runOutFormatOpt string

type inputOpts struct {
Script string `json:"script"`
Variables map[string]string `json:"variables"`
Meta map[string]interpreter.Metadata `json:"metadata"`
Balances interpreter.StaticStore `json:"balances"`
Script string `json:"script"`
Variables map[string]string `json:"variables"`
Meta interpreter.AccountsMetadata `json:"metadata"`
Balances interpreter.Balances `json:"balances"`
}

func (o *inputOpts) fromRaw() {
Expand Down Expand Up @@ -100,8 +101,8 @@ func (o *inputOpts) fromOptions(path string) {
func run(path string) {
opt := inputOpts{
Variables: make(map[string]string),
Meta: make(map[string]interpreter.Metadata),
Balances: make(interpreter.StaticStore),
Meta: make(interpreter.AccountsMetadata),
Balances: make(interpreter.Balances),
}

opt.fromRaw()
Expand All @@ -114,11 +115,11 @@ func run(path string) {
os.Exit(1)
}

result, err := interpreter.RunProgram(parseResult.Value, interpreter.RunProgramOptions{
Vars: opt.Variables,
Store: opt.Balances,
Meta: opt.Meta,
result, err := interpreter.RunProgram(context.Background(), parseResult.Value, opt.Variables, interpreter.StaticStore{
Balances: opt.Balances,
Meta: opt.Meta,
})

if err != nil {
rng := err.GetRange()
os.Stderr.Write([]byte(err.Error()))
Expand Down Expand Up @@ -163,7 +164,7 @@ func showPretty(result *interpreter.ExecutionResult) {
fmt.Println()

fmt.Println(ansi.ColorCyan("Meta:"))
txMetaJson, err := json.MarshalIndent(result.TxMeta, "", " ")
txMetaJson, err := json.MarshalIndent(result.Metadata, "", " ")
if err != nil {
panic(err)
}
Expand Down
142 changes: 142 additions & 0 deletions internal/interpreter/batch_balances_query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package interpreter

import (
"slices"

"github.com/formancehq/numscript/internal/parser"
"github.com/formancehq/numscript/internal/utils"
"golang.org/x/exp/maps"
)

// traverse the script to batch in advance required balance queries

func (st *programState) findBalancesQueriesInStatement(statement parser.Statement) InterpreterError {
switch statement := statement.(type) {
case *parser.FnCall:
return nil

case *parser.SendStatement:
// set the current asset
switch sentValue := statement.SentValue.(type) {
case *parser.SentValueAll:
asset, err := evaluateLitExpecting(st, sentValue.Asset, expectAsset)
if err != nil {
return err
}
st.CurrentAsset = *asset

case *parser.SentValueLiteral:
monetary, err := evaluateLitExpecting(st, sentValue.Monetary, expectMonetary)
if err != nil {
return err
}
st.CurrentAsset = string(monetary.Asset)

default:
utils.NonExhaustiveMatchPanic[any](sentValue)
}

// traverse source
return st.findBalancesQueries(statement.Source)

default:
utils.NonExhaustiveMatchPanic[any](statement)
return nil
}
}

func (st *programState) batchQuery(account string, asset string) {
if account == "world" {
return
}

previousValues := st.CurrentBalanceQuery[account]
if !slices.Contains[[]string, string](previousValues, account) {
st.CurrentBalanceQuery[account] = append(previousValues, asset)
}
}

func (st *programState) runBalancesQuery() error {
filteredQuery := BalanceQuery{}
for accountName, queriedCurrencies := range st.CurrentBalanceQuery {

cachedCurrenciesForAccount := defaultMapGet(st.CachedBalances, accountName, func() AccountBalance {
return AccountBalance{}
})

for _, queriedCurrency := range queriedCurrencies {
isAlreadyCached := slices.Contains(maps.Keys(cachedCurrenciesForAccount), queriedCurrency)
if !isAlreadyCached {
filteredQuery[accountName] = queriedCurrencies
}
}

}

// avoid updating balances if we don't need to fetch new data
if len(filteredQuery) == 0 {
return nil
}

balances, err := st.Store.GetBalances(st.ctx, filteredQuery)
if err != nil {
return err
}
// reset batch query
st.CurrentBalanceQuery = BalanceQuery{}

st.CachedBalances = balances
return nil
}

func (st *programState) findBalancesQueries(source parser.Source) InterpreterError {
switch source := source.(type) {
case *parser.SourceAccount:
account, err := evaluateLitExpecting(st, source.Literal, expectAccount)
if err != nil {
return err
}

st.batchQuery(*account, st.CurrentAsset)
return nil

case *parser.SourceOverdraft:
// Skip balance tracking when balance is overdraft
if source.Bounded == nil {
return nil
}

account, err := evaluateLitExpecting(st, source.Address, expectAccount)
if err != nil {
return err
}
st.batchQuery(*account, st.CurrentAsset)
return nil

case *parser.SourceInorder:
for _, subSource := range source.Sources {
err := st.findBalancesQueries(subSource)
if err != nil {
return err
}
}
return nil

case *parser.SourceCapped:
// TODO can this be optimized in some cases?
return st.findBalancesQueries(source.From)

case *parser.SourceAllotment:
for _, item := range source.Items {
err := st.findBalancesQueries(item.From)
if err != nil {
return err
}
}
return nil

default:
utils.NonExhaustiveMatchPanic[error](source)
return nil
}
}
74 changes: 74 additions & 0 deletions internal/interpreter/evaluate_lit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package interpreter

import (
"math/big"

"github.com/formancehq/numscript/internal/parser"
"github.com/formancehq/numscript/internal/utils"
)

func (st *programState) evaluateLit(literal parser.Literal) (Value, InterpreterError) {
switch literal := literal.(type) {
case *parser.AssetLiteral:
return Asset(literal.Asset), nil
case *parser.AccountLiteral:
return AccountAddress(literal.Name), nil
case *parser.StringLiteral:
return String(literal.String), nil
case *parser.RatioLiteral:
return Portion(*literal.ToRatio()), nil
case *parser.NumberLiteral:
return MonetaryInt(*big.NewInt(int64(literal.Number))), nil
case *parser.MonetaryLiteral:
asset, err := evaluateLitExpecting(st, literal.Asset, expectAsset)
if err != nil {
return nil, err
}

amount, err := evaluateLitExpecting(st, literal.Amount, expectNumber)
if err != nil {
return nil, err
}

return Monetary{Asset: Asset(*asset), Amount: MonetaryInt(*amount)}, nil

case *parser.VariableLiteral:
value, ok := st.ParsedVars[literal.Name]
if !ok {
return nil, UnboundVariableErr{
Name: literal.Name,
Range: literal.Range,
}
}
return value, nil
default:
utils.NonExhaustiveMatchPanic[any](literal)
return nil, nil
}
}

func evaluateLitExpecting[T any](st *programState, literal parser.Literal, expect func(Value, parser.Range) (*T, InterpreterError)) (*T, InterpreterError) {
value, err := st.evaluateLit(literal)
if err != nil {
return nil, err
}

res, err := expect(value, literal.GetRange())
if err != nil {
return nil, err
}

return res, nil
}

func (st *programState) evaluateLiterals(literals []parser.Literal) ([]Value, InterpreterError) {
var values []Value
for _, argLit := range literals {
value, err := st.evaluateLit(argLit)
if err != nil {
return nil, err
}
values = append(values, value)
}
return values, nil
}
Loading

0 comments on commit c4c396a

Please sign in to comment.