Skip to content
16 changes: 8 additions & 8 deletions internal/interpreter/batch_balances_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func (st *programState) findBalancesQueriesInStatement(statement parser.Statemen
return nil

case *parser.SaveStatement:
asset, _, err := st.evaluateSentAmt(statement.SentValue)
asset, _, err := evaluateSentAmt(&st.evalEnv, statement.SentValue)
if err != nil {
return err
}
Expand All @@ -27,15 +27,15 @@ func (st *programState) findBalancesQueriesInStatement(statement parser.Statemen
//
// this would mean that the "save" statement was not needed in the first place,
// so preventing this query would hardly be an useful optimization
account, err := evaluateExprAs(st, statement.Account, expectAccount)
account, err := evaluateExprAs(&st.evalEnv, statement.Account, expectAccount)
if err != nil {
return err
}
st.batchQuery(account, asset, "")
return nil

case *parser.SendStatement:
asset, _, err := st.evaluateSentAmt(statement.SentValue)
asset, _, err := evaluateSentAmt(&st.evalEnv, statement.SentValue)
if err != nil {
return err
}
Expand Down Expand Up @@ -90,12 +90,12 @@ func (st *programState) runBalancesQuery() error {
func (st *programState) findBalancesQueries(source parser.Source) InterpreterError {
switch source := source.(type) {
case *parser.SourceAccount:
account, err := evaluateExprAs(st, source.ValueExpr, expectAccount)
account, err := evaluateExprAs(&st.evalEnv, source.ValueExpr, expectAccount)
if err != nil {
return err
}

color, err := evaluateOptExprAs(st, source.Color, expectString)
color, err := evaluateOptExprAs(&st.evalEnv, source.Color, expectString)
if err != nil {
return err
}
Expand All @@ -104,7 +104,7 @@ func (st *programState) findBalancesQueries(source parser.Source) InterpreterErr
return nil

case *parser.SourceWithScaling:
account, err := evaluateExprAs(st, source.Address, expectAccount)
account, err := evaluateExprAs(&st.evalEnv, source.Address, expectAccount)
if err != nil {
return err
}
Expand All @@ -119,11 +119,11 @@ func (st *programState) findBalancesQueries(source parser.Source) InterpreterErr
return nil
}

account, err := evaluateExprAs(st, source.Address, expectAccount)
account, err := evaluateExprAs(&st.evalEnv, source.Address, expectAccount)
if err != nil {
return err
}
color, err := evaluateOptExprAs(st, source.Color, expectString)
color, err := evaluateOptExprAs(&st.evalEnv, source.Color, expectString)
if err != nil {
return err
}
Expand Down
172 changes: 130 additions & 42 deletions internal/interpreter/evaluate_expr.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,106 @@
package interpreter

import (
"context"
"math/big"
"strings"

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

func (st *programState) evaluateExpr(expr parser.ValueExpr) (Value, InterpreterError) {
type evalEnv struct {
ctx context.Context
Store Store
FeatureFlags map[string]struct{}

vars map[string]Value
CachedBalances InternalBalances
CachedAccountsMeta InternalAccountsMetadata
}

func newEvalEnv(ctx context.Context, store Store, featureFlags map[string]struct{}, varDecls *parser.VarDeclarations, rawVars map[string]string) (evalEnv, InterpreterError) {
env := evalEnv{
ctx: ctx,
Store: store,
FeatureFlags: featureFlags,
vars: map[string]Value{},
CachedBalances: InternalBalances{},
CachedAccountsMeta: InternalAccountsMetadata{},
}
if err := bindVars(&env, varDecls, rawVars); err != nil {
return evalEnv{}, err
}
return env, nil
}

func (env *evalEnv) checkFeatureFlag(flag string) InterpreterError {
// a nil set enables every feature (e.g. dependency resolution)
if env.FeatureFlags == nil {
return nil
}
if _, ok := env.FeatureFlags[flag]; ok {
return nil
}
return ExperimentalFeature{FlagName: flag}
}

func (env *evalEnv) getBalance(account AccountAddress, asset Asset) (*big.Int, InterpreterError) {
color := String("")
if !env.CachedBalances.has(account, string(asset), string(color)) {
rows, err := env.Store.GetBalances(env.ctx, BalanceQuery{
{Account: account.Name, Asset: string(asset), Color: string(color), Scope: account.Scope},
})
if err != nil {
return nil, QueryBalanceError{WrappedError: err}
}
env.CachedBalances.Merge(rows)
}
return env.CachedBalances.fetchBalance(account, asset, color), nil
}

func (env *evalEnv) getMetadata(account AccountAddress, key string) (string, bool, InterpreterError) {
if value, ok := env.CachedAccountsMeta.Get(account.Name, account.Scope, key); ok {
return value, true, nil
}

rows, err := env.Store.GetAccountsMetadata(env.ctx, MetadataQuery{
{Account: account.Name, Scope: account.Scope, Keys: []string{key}},
})
if err != nil {
return "", false, QueryMetadataError{WrappedError: err}
}
env.CachedAccountsMeta.Merge(rows)

value, ok := env.CachedAccountsMeta.Get(account.Name, account.Scope, key)
return value, ok, nil
}

func bindVars(env *evalEnv, varDecls *parser.VarDeclarations, rawVars map[string]string) InterpreterError {
if varDecls == nil {
return nil
}
for _, decl := range varDecls.Declarations {
var value Value
var err InterpreterError
if decl.Origin == nil {
raw, ok := rawVars[decl.Name.Name]
if !ok {
return MissingVariableErr{Name: decl.Name.Name}
}
value, err = parseVar(decl.Type.Name, raw, decl.Type.Range)
} else {
value, err = evaluateVarOrigin(env, decl.Type.Name, *decl.Origin)
}
if err != nil {
return err
}
env.vars[decl.Name.Name] = value
}
return nil
}

func evaluateExpr(env *evalEnv, expr parser.ValueExpr) (Value, InterpreterError) {
switch expr := expr.(type) {
case *parser.AssetLiteral:
return Asset(expr.Asset), nil
Expand All @@ -20,12 +111,12 @@ func (st *programState) evaluateExpr(expr parser.ValueExpr) (Value, InterpreterE
case parser.AccountTextPart:
parts = append(parts, part.Name)
case *parser.Variable:
err := st.checkFeatureFlag(flags.ExperimentalAccountInterpolationFlag)
err := env.checkFeatureFlag(flags.ExperimentalAccountInterpolationFlag)
if err != nil {
return nil, err
}

value, err := st.evaluateExpr(part)
value, err := evaluateExpr(env, part)
if err != nil {
return nil, err
}
Expand All @@ -34,6 +125,9 @@ func (st *programState) evaluateExpr(expr parser.ValueExpr) (Value, InterpreterE
return nil, err
}
parts = append(parts, strValue)

default:
return nil, unhandledErr(part)
}
}
name := strings.Join(parts, "")
Expand All @@ -46,21 +140,21 @@ func (st *programState) evaluateExpr(expr parser.ValueExpr) (Value, InterpreterE
case *parser.NumberLiteral:
return MonetaryInt(*expr.Number), nil
case *parser.MonetaryLiteral:
asset, err := evaluateExprAs(st, expr.Asset, expectAsset)
asset, err := evaluateExprAs(env, expr.Asset, expectAsset)
if err != nil {
return nil, err
}

amount, err := evaluateExprAs(st, expr.Amount, expectNumber)
amount, err := evaluateExprAs(env, expr.Amount, expectNumber)
if err != nil {
return nil, err
}

return Monetary{Asset: asset, Amount: amount}, nil

case *parser.Variable:
value, ok := st.ParsedVars[expr.Name]
if !ok {
value := env.vars[expr.Name]
if value == nil {
return nil, UnboundVariableErr{
Name: expr.Name,
Range: expr.Range,
Expand All @@ -71,53 +165,47 @@ func (st *programState) evaluateExpr(expr parser.ValueExpr) (Value, InterpreterE
case *parser.BinaryInfix:
switch expr.Operator {
case parser.InfixOperatorPlus:
return st.plusOp(expr.Left, expr.Right)
return plusOp(env, expr.Left, expr.Right)

case parser.InfixOperatorMinus:
return st.subOp(expr.Left, expr.Right)
return subOp(env, expr.Left, expr.Right)

case parser.InfixOperatorDiv:
return st.divOp(expr.Range, expr.Left, expr.Right)
return divOp(env, expr.Range, expr.Left, expr.Right)

default:
utils.NonExhaustiveMatchPanic[any](expr.Operator)
return nil, nil
return nil, unhandledErr(expr.Operator)
}

case *parser.Prefix:
switch expr.Operator {
case parser.PrefixOperatorMinus:
return st.unaryNegOp(expr.Expr)
return unaryNegOp(env, expr.Expr)

default:
utils.NonExhaustiveMatchPanic[any](expr.Operator)
return nil, nil
return nil, unhandledErr(expr.Operator)
}

case *parser.FnCall:

if err := st.checkFeatureFlag(flags.ExperimentalMidScriptFunctionCall); err != nil {
return nil, err
}
return st.handleFnCall(nil, *expr)
// nil type: not a direct var origin, hence a mid-script call.
return evaluateFnCall(env, nil, *expr)

default:
utils.NonExhaustiveMatchPanic[any](expr)
return nil, nil
return nil, unhandledErr(expr)
}
}

func evaluateOptExprAs[T any](st *programState, expr parser.ValueExpr, expect func(Value, parser.Range) (T, InterpreterError)) (T, InterpreterError) {
func evaluateOptExprAs[T any](env *evalEnv, expr parser.ValueExpr, expect func(Value, parser.Range) (T, InterpreterError)) (T, InterpreterError) {
var t T
if expr == nil {
return t, nil
}
return evaluateExprAs(st, expr, expect)
return evaluateExprAs(env, expr, expect)
}

func evaluateExprAs[T any](st *programState, expr parser.ValueExpr, expect func(Value, parser.Range) (T, InterpreterError)) (T, InterpreterError) {
func evaluateExprAs[T any](env *evalEnv, expr parser.ValueExpr, expect func(Value, parser.Range) (T, InterpreterError)) (T, InterpreterError) {
var default_ T
value, err := st.evaluateExpr(expr)
value, err := evaluateExpr(env, expr)
if err != nil {
return default_, err
}
Expand All @@ -130,10 +218,10 @@ func evaluateExprAs[T any](st *programState, expr parser.ValueExpr, expect func(
return res, nil
}

func (st *programState) evaluateExpressions(literals []parser.ValueExpr) ([]Value, InterpreterError) {
func evaluateExpressions(env *evalEnv, literals []parser.ValueExpr) ([]Value, InterpreterError) {
var values []Value
for _, argLit := range literals {
value, err := st.evaluateExpr(argLit)
value, err := evaluateExpr(env, argLit)
if err != nil {
return nil, err
}
Expand All @@ -143,7 +231,7 @@ func (st *programState) evaluateExpressions(literals []parser.ValueExpr) ([]Valu
}

func (s *programState) evaluateColor(colorExpr parser.ValueExpr) (String, InterpreterError) {
color, err := evaluateOptExprAs(s, colorExpr, expectString)
color, err := evaluateOptExprAs(&s.evalEnv, colorExpr, expectString)
if err != nil {
return "", err
}
Expand All @@ -159,9 +247,9 @@ func (s *programState) evaluateColor(colorExpr parser.ValueExpr) (String, Interp
return color, nil
}

func (st *programState) plusOp(left parser.ValueExpr, right parser.ValueExpr) (Value, InterpreterError) {
func plusOp(env *evalEnv, left parser.ValueExpr, right parser.ValueExpr) (Value, InterpreterError) {

leftValue, err := evaluateExprAs(st, left, expectOneOf(
leftValue, err := evaluateExprAs(env, left, expectOneOf(
expectMapped(expectMonetary, func(m Monetary) opAdd {
return m
}),
Expand All @@ -176,11 +264,11 @@ func (st *programState) plusOp(left parser.ValueExpr, right parser.ValueExpr) (V
return nil, err
}

return leftValue.evalAdd(st, right)
return leftValue.evalAdd(env, right)
}

func (st *programState) subOp(left parser.ValueExpr, right parser.ValueExpr) (Value, InterpreterError) {
leftValue, err := evaluateExprAs(st, left, expectOneOf(
func subOp(env *evalEnv, left parser.ValueExpr, right parser.ValueExpr) (Value, InterpreterError) {
leftValue, err := evaluateExprAs(env, left, expectOneOf(
expectMapped(expectMonetary, func(m Monetary) opSub {
return m
}),
Expand All @@ -193,16 +281,16 @@ func (st *programState) subOp(left parser.ValueExpr, right parser.ValueExpr) (Va
return nil, err
}

return leftValue.evalSub(st, right)
return leftValue.evalSub(env, right)
}

func (st *programState) divOp(rng parser.Range, left parser.ValueExpr, right parser.ValueExpr) (Value, InterpreterError) {
leftValue, err := evaluateExprAs(st, left, expectNumber)
func divOp(env *evalEnv, rng parser.Range, left parser.ValueExpr, right parser.ValueExpr) (Value, InterpreterError) {
leftValue, err := evaluateExprAs(env, left, expectNumber)
if err != nil {
return nil, err
}

rightValue, err := evaluateExprAs(st, right, expectNumber)
rightValue, err := evaluateExprAs(env, right, expectNumber)
if err != nil {
return nil, err
}
Expand All @@ -221,8 +309,8 @@ func (st *programState) divOp(rng parser.Range, left parser.ValueExpr, right par
return Portion(*rat), nil
}

func (st *programState) unaryNegOp(expr parser.ValueExpr) (Value, InterpreterError) {
evExpr, err := evaluateExprAs(st, expr, expectOneOf(
func unaryNegOp(env *evalEnv, expr parser.ValueExpr) (Value, InterpreterError) {
evExpr, err := evaluateExprAs(env, expr, expectOneOf(
expectMapped(expectMonetary, func(m Monetary) opNeg {
return m
}),
Expand All @@ -237,7 +325,7 @@ func (st *programState) unaryNegOp(expr parser.ValueExpr) (Value, InterpreterErr
return nil, err
}

return evExpr.evalNeg(st)
return evExpr.evalNeg(env)
}

func castToString(v Value, rng parser.Range) (string, InterpreterError) {
Expand Down
Loading