Files
zeekayandClaude Opus 5 2c2f0e6bf3 Go 1.27.1, and the code it can say more simply
Modules declare go 1.27.1 and the build images follow. go fix applied the
modernizers, and the interpreter's parseArg, evaluateExprAs and
evaluateOptExprAs are now generic methods on the parser and program state
they take. omitzero is not applied, since it changes what the JSON says.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-16 19:52:45 -07:00

259 lines
6.0 KiB
Go

package interpreter
import (
"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) {
switch expr := expr.(type) {
case *parser.AssetLiteral:
return Asset(expr.Asset), nil
case *parser.AccountInterpLiteral:
var parts []string
for _, part := range expr.Parts {
switch part := part.(type) {
case parser.AccountTextPart:
parts = append(parts, part.Name)
case *parser.Variable:
err := st.checkFeatureFlag(flags.ExperimentalAccountInterpolationFlag)
if err != nil {
return nil, err
}
value, err := st.evaluateExpr(part)
if err != nil {
return nil, err
}
strValue, err := castToString(value, expr.Range)
if err != nil {
return nil, err
}
parts = append(parts, strValue)
}
}
name := strings.Join(parts, "")
return NewAccountAddress(name)
case *parser.StringLiteral:
return String(expr.String), nil
case *parser.PercentageLiteral:
return Portion(*expr.ToRatio()), nil
case *parser.NumberLiteral:
return MonetaryInt(*expr.Number), nil
case *parser.MonetaryLiteral:
asset, err := st.evaluateExprAs(expr.Asset, expectAsset)
if err != nil {
return nil, err
}
amount, err := st.evaluateExprAs(expr.Amount, expectNumber)
if err != nil {
return nil, err
}
return Monetary{Asset: Asset(*asset), Amount: MonetaryInt(*amount)}, nil
case *parser.Variable:
value, ok := st.ParsedVars[expr.Name]
if !ok {
return nil, UnboundVariableErr{
Name: expr.Name,
Range: expr.Range,
}
}
return value, nil
case *parser.BinaryInfix:
switch expr.Operator {
case parser.InfixOperatorPlus:
return st.plusOp(expr.Left, expr.Right)
case parser.InfixOperatorMinus:
return st.subOp(expr.Left, expr.Right)
case parser.InfixOperatorDiv:
return st.divOp(expr.Range, expr.Left, expr.Right)
default:
utils.NonExhaustiveMatchPanic[any](expr.Operator)
return nil, nil
}
case *parser.Prefix:
switch expr.Operator {
case parser.PrefixOperatorMinus:
return st.unaryNegOp(expr.Expr)
default:
utils.NonExhaustiveMatchPanic[any](expr.Operator)
return nil, nil
}
case *parser.FnCall:
if !st.varOriginPosition {
err := st.checkFeatureFlag(flags.ExperimentalMidScriptFunctionCall)
if err != nil {
return nil, err
}
}
return st.handleFnCall(nil, *expr)
default:
utils.NonExhaustiveMatchPanic[any](expr)
return nil, nil
}
}
func (st *programState) evaluateOptExprAs[T any](expr parser.ValueExpr, expect func(Value, parser.Range) (*T, InterpreterError)) (*T, InterpreterError) {
if expr == nil {
return nil, nil
}
return st.evaluateExprAs(expr, expect)
}
func (st *programState) evaluateExprAs[T any](expr parser.ValueExpr, expect func(Value, parser.Range) (*T, InterpreterError)) (*T, InterpreterError) {
value, err := st.evaluateExpr(expr)
if err != nil {
return nil, err
}
res, err := expect(value, expr.GetRange())
if err != nil {
return nil, err
}
return res, nil
}
func (st *programState) evaluateExpressions(literals []parser.ValueExpr) ([]Value, InterpreterError) {
var values []Value
for _, argLit := range literals {
value, err := st.evaluateExpr(argLit)
if err != nil {
return nil, err
}
values = append(values, value)
}
return values, nil
}
func (s *programState) evaluateColor(colorExpr parser.ValueExpr) (*string, InterpreterError) {
color, err := s.evaluateOptExprAs(colorExpr, expectString)
if err != nil {
return nil, err
}
if color == nil {
col := ""
return &col, nil
}
isValidColor := colorRe.Match([]byte(*color))
if !isValidColor {
return nil, InvalidColor{
Range: colorExpr.GetRange(),
Color: *color,
}
}
return color, nil
}
func (st *programState) plusOp(left parser.ValueExpr, right parser.ValueExpr) (Value, InterpreterError) {
leftValue, err := st.evaluateExprAs(left, expectOneOf(
expectMapped(expectMonetary, func(m Monetary) opAdd {
return m
}),
// while "x.map(identity)" is the same as "x", just writing "expectNumber" would't typecheck
expectMapped(expectNumber, func(bi big.Int) opAdd {
return MonetaryInt(bi)
}),
))
if err != nil {
return nil, err
}
return (*leftValue).evalAdd(st, right)
}
func (st *programState) subOp(left parser.ValueExpr, right parser.ValueExpr) (Value, InterpreterError) {
leftValue, err := st.evaluateExprAs(left, expectOneOf(
expectMapped(expectMonetary, func(m Monetary) opSub {
return m
}),
expectMapped(expectNumber, func(bi big.Int) opSub {
return MonetaryInt(bi)
}),
))
if err != nil {
return nil, err
}
return (*leftValue).evalSub(st, right)
}
func (st *programState) divOp(rng parser.Range, left parser.ValueExpr, right parser.ValueExpr) (Value, InterpreterError) {
leftValue, err := st.evaluateExprAs(left, expectNumber)
if err != nil {
return nil, err
}
rightValue, err := st.evaluateExprAs(right, expectNumber)
if err != nil {
return nil, err
}
if rightValue.Cmp(big.NewInt(0)) == 0 {
return nil, DivideByZero{
Range: rng,
Numerator: leftValue,
}
}
rat := new(big.Rat).SetFrac(leftValue, rightValue)
return Portion(*rat), nil
}
func (st *programState) unaryNegOp(expr parser.ValueExpr) (Value, InterpreterError) {
evExpr, err := st.evaluateExprAs(expr, expectOneOf(
expectMapped(expectMonetary, func(m Monetary) opNeg {
return m
}),
// while "x.map(identity)" is the same as "x", just writing "expectNumber" would't typecheck
expectMapped(expectNumber, func(bi big.Int) opNeg {
return MonetaryInt(bi)
}),
))
if err != nil {
return nil, err
}
return (*evExpr).evalNeg(st)
}
func castToString(v Value, rng parser.Range) (string, InterpreterError) {
switch v := v.(type) {
case AccountAddress:
return v.String(), nil
case String:
return v.String(), nil
case MonetaryInt:
return v.String(), nil
default:
// No asset nor ratio can be implicitly cast to string
return "", CannotCastToString{Value: v, Range: rng}
}
}