Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implemented if expression parser #22

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions Numscript.g4
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ REMAINING: 'remaining';
ALLOWING: 'allowing';
UNBOUNDED: 'unbounded';
OVERDRAFT: 'overdraft';
IF: 'if';
ELSE: 'else';
KEPT: 'kept';
SAVE: 'save';
LPARENS: '(';
Expand Down Expand Up @@ -50,14 +52,20 @@ portion:
| PERCENTAGE_PORTION_LITERAL # percentage;

valueExpr:
VARIABLE_NAME # variableExpr
| ASSET # assetLiteral
| STRING # stringLiteral
| ACCOUNT # accountLiteral
| NUMBER # numberLiteral
| monetaryLit # monetaryLiteral
| portion # portionLiteral
| left = valueExpr op = ('+' | '-') right = valueExpr # infixExpr;
VARIABLE_NAME # variableExpr
| ASSET # assetLiteral
| STRING # stringLiteral
| ACCOUNT # accountLiteral
| NUMBER # numberLiteral
| monetaryLit # monetaryLiteral
| portion # portionLiteral
| '!' valueExpr # notExpr
| left = valueExpr op = ('+' | '-') right = valueExpr # infixAddSubExpr
| left = valueExpr op = ('==' | '!=') right = valueExpr # infixEqExpr
| left = valueExpr op = ('<' | '<=' | '>' | '>=') right = valueExpr # infixCompExpr
| left = valueExpr op = '||' right = valueExpr # infixOrExpr
| left = valueExpr op = '&&' right = valueExpr # infixAndExpr
| '(' valueExpr ')' # parensExpr;

functionCallArgs: valueExpr ( COMMA valueExpr)*;
functionCall:
Expand All @@ -79,6 +87,7 @@ allotment:

source:
address = valueExpr ALLOWING UNBOUNDED OVERDRAFT # srcAccountUnboundedOverdraft
| ifBranch = source IF valueExpr ELSE elseBranch = source # sourceIf
| address = valueExpr ALLOWING OVERDRAFT UP TO maxOvedraft = valueExpr #
srcAccountBoundedOverdraft
| valueExpr # srcAccount
Expand All @@ -94,6 +103,7 @@ destinationInOrderClause: MAX valueExpr keptOrDestination;

destination:
valueExpr # destAccount
| ifBranch = destination IF valueExpr ELSE elseBranch = destination # destIf
| LBRACE allotmentClauseDest+ RBRACE # destAllotment
| LBRACE destinationInOrderClause* REMAINING keptOrDestination RBRACE # destInorder;
allotmentClauseDest: allotment keptOrDestination;
Expand Down
1 change: 1 addition & 0 deletions internal/analysis/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
const TypeMonetary = "monetary"
const TypeAccount = "account"
const TypePortion = "portion"
const TypeBool = "bool"
const TypeAsset = "asset"
const TypeNumber = "number"
const TypeString = "string"
Expand Down
13 changes: 13 additions & 0 deletions internal/interpreter/batch_balances_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,19 @@
}
return nil

case *parser.IfExpr[parser.Source]:
err := st.findBalancesQueries(source.IfBranch)
if err != nil {
return err
}

Check warning on line 148 in internal/interpreter/batch_balances_query.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/batch_balances_query.go#L147-L148

Added lines #L147 - L148 were not covered by tests

err = st.findBalancesQueries(source.ElseBranch)
if err != nil {
return err
}

return nil

Check warning on line 155 in internal/interpreter/batch_balances_query.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/batch_balances_query.go#L152-L155

Added lines #L152 - L155 were not covered by tests

default:
utils.NonExhaustiveMatchPanic[error](source)
return nil
Expand Down
171 changes: 169 additions & 2 deletions internal/interpreter/evaluate_expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import (
"math/big"
"reflect"

"github.com/formancehq/numscript/internal/parser"
"github.com/formancehq/numscript/internal/utils"
Expand Down Expand Up @@ -42,16 +43,38 @@
}
return value, nil

// TypeError
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.InfixOperatorEq:
return st.eqOp(expr.Left, expr.Right)

case parser.InfixOperatorNeq:
return st.neqOp(expr.Left, expr.Right)

case parser.InfixOperatorGt:
return st.gtOp(expr.Left, expr.Right)

case parser.InfixOperatorGte:
return st.gteOp(expr.Left, expr.Right)

case parser.InfixOperatorLt:
return st.ltOp(expr.Left, expr.Right)

case parser.InfixOperatorLte:
return st.lteOp(expr.Left, expr.Right)

case parser.InfixOperatorAnd:
return st.andOp(expr.Left, expr.Right)

case parser.InfixOperatorOr:
return st.orOp(expr.Left, expr.Right)

default:
utils.NonExhaustiveMatchPanic[any](expr.Operator)
return nil, nil
Expand Down Expand Up @@ -124,3 +147,147 @@

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

func (st *programState) eqOp(left parser.ValueExpr, right parser.ValueExpr) (Value, InterpreterError) {
parsedLeft, err := evaluateExprAs(st, left, expectAnything)
if err != nil {
return nil, err
}

parsedRight, err := evaluateExprAs(st, right, expectAnything)
if err != nil {
return nil, err
}

Check warning on line 160 in internal/interpreter/evaluate_expr.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/evaluate_expr.go#L159-L160

Added lines #L159 - L160 were not covered by tests

// TODO remove reflect usage
return Bool(reflect.DeepEqual(parsedLeft, parsedRight)), nil
}

func (st *programState) neqOp(left parser.ValueExpr, right parser.ValueExpr) (Value, InterpreterError) {
parsedLeft, err := evaluateExprAs(st, left, expectAnything)
if err != nil {
return nil, err
}

Check warning on line 170 in internal/interpreter/evaluate_expr.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/evaluate_expr.go#L169-L170

Added lines #L169 - L170 were not covered by tests

parsedRight, err := evaluateExprAs(st, right, expectAnything)
if err != nil {
return nil, err
}

Check warning on line 175 in internal/interpreter/evaluate_expr.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/evaluate_expr.go#L174-L175

Added lines #L174 - L175 were not covered by tests

// TODO remove reflect usage
return Bool(!(reflect.DeepEqual(parsedLeft, parsedRight))), nil
}

func (st *programState) ltOp(left parser.ValueExpr, right parser.ValueExpr) (Value, InterpreterError) {
cmp, err := st.evaluateExprAsCmp(left)
if err != nil {
return nil, err
}

Check warning on line 185 in internal/interpreter/evaluate_expr.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/evaluate_expr.go#L184-L185

Added lines #L184 - L185 were not covered by tests

cmpResult, err := (*cmp).evalCmp(st, right)
if err != nil {
return nil, err
}

Check warning on line 190 in internal/interpreter/evaluate_expr.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/evaluate_expr.go#L189-L190

Added lines #L189 - L190 were not covered by tests

switch *cmpResult {
case -1:
return Bool(true), nil
default:
return Bool(false), nil
}

}

func (st *programState) gtOp(left parser.ValueExpr, right parser.ValueExpr) (Value, InterpreterError) {
cmp, err := st.evaluateExprAsCmp(left)
if err != nil {
return nil, err
}

Check warning on line 205 in internal/interpreter/evaluate_expr.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/evaluate_expr.go#L204-L205

Added lines #L204 - L205 were not covered by tests

cmpResult, err := (*cmp).evalCmp(st, right)
if err != nil {
return nil, err
}

Check warning on line 210 in internal/interpreter/evaluate_expr.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/evaluate_expr.go#L209-L210

Added lines #L209 - L210 were not covered by tests

switch *cmpResult {
case 1:
return Bool(true), nil
default:
return Bool(false), nil
}
}

func (st *programState) lteOp(left parser.ValueExpr, right parser.ValueExpr) (Value, InterpreterError) {
cmp, err := st.evaluateExprAsCmp(left)
if err != nil {
return nil, err
}

Check warning on line 224 in internal/interpreter/evaluate_expr.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/evaluate_expr.go#L223-L224

Added lines #L223 - L224 were not covered by tests

cmpResult, err := (*cmp).evalCmp(st, right)
if err != nil {
return nil, err
}

Check warning on line 229 in internal/interpreter/evaluate_expr.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/evaluate_expr.go#L228-L229

Added lines #L228 - L229 were not covered by tests

switch *cmpResult {
case -1, 0:
return Bool(true), nil
default:
return Bool(false), nil
}

}

func (st *programState) gteOp(left parser.ValueExpr, right parser.ValueExpr) (Value, InterpreterError) {
cmp, err := st.evaluateExprAsCmp(left)
if err != nil {
return nil, err
}

Check warning on line 244 in internal/interpreter/evaluate_expr.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/evaluate_expr.go#L243-L244

Added lines #L243 - L244 were not covered by tests

cmpResult, err := (*cmp).evalCmp(st, right)
if err != nil {
return nil, err
}

Check warning on line 249 in internal/interpreter/evaluate_expr.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/evaluate_expr.go#L248-L249

Added lines #L248 - L249 were not covered by tests

switch *cmpResult {
case 1, 0:
return Bool(true), nil
default:
return Bool(false), nil
}
}

func (st *programState) andOp(left parser.ValueExpr, right parser.ValueExpr) (Value, InterpreterError) {
parsedLeft, err := evaluateExprAs(st, left, expectBool)
if err != nil {
return nil, err
}

Check warning on line 263 in internal/interpreter/evaluate_expr.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/evaluate_expr.go#L262-L263

Added lines #L262 - L263 were not covered by tests

if !*parsedLeft {
return Bool(false), nil
}

parsedRight, err := evaluateExprAs(st, right, expectBool)
if err != nil {
return nil, err
}

Check warning on line 272 in internal/interpreter/evaluate_expr.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/evaluate_expr.go#L271-L272

Added lines #L271 - L272 were not covered by tests

return Bool(*parsedRight), nil
}

func (st *programState) orOp(left parser.ValueExpr, right parser.ValueExpr) (Value, InterpreterError) {
parsedLeft, err := evaluateExprAs(st, left, expectBool)
if err != nil {
return nil, err
}

Check warning on line 281 in internal/interpreter/evaluate_expr.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/evaluate_expr.go#L280-L281

Added lines #L280 - L281 were not covered by tests

if *parsedLeft {
return Bool(true), nil
}

parsedRight, err := evaluateExprAs(st, right, expectBool)
if err != nil {
return nil, err
}

Check warning on line 290 in internal/interpreter/evaluate_expr.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/evaluate_expr.go#L289-L290

Added lines #L289 - L290 were not covered by tests

return Bool(*parsedRight), nil
}
50 changes: 50 additions & 0 deletions internal/interpreter/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,23 @@
return mon, nil
}

func parseBool(source string) (Bool, InterpreterError) {
switch source {
case "true":
return Bool(true), nil
case "false":
return Bool(false), nil

default:
return Bool(false), InvalidBoolLiteral{Source: source}

Check warning on line 94 in internal/interpreter/interpreter.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/interpreter.go#L93-L94

Added lines #L93 - L94 were not covered by tests
}
}

func parseVar(type_ string, rawValue string, r parser.Range) (Value, InterpreterError) {
switch type_ {
// TODO why should the runtime depend on the static analysis module?
case analysis.TypeBool:
return parseBool(rawValue)
case analysis.TypeMonetary:
return parseMonetary(rawValue)
case analysis.TypeAccount:
Expand Down Expand Up @@ -490,6 +504,18 @@
case *parser.SourceAllotment:
return nil, InvalidAllotmentInSendAll{}

case *parser.IfExpr[parser.Source]:
cond, err := evaluateExprAs(s, source.Condition, expectBool)
if err != nil {
return nil, err
}

Check warning on line 511 in internal/interpreter/interpreter.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/interpreter.go#L510-L511

Added lines #L510 - L511 were not covered by tests

if *cond {
return s.sendAll(source.IfBranch)
} else {
return s.sendAll(source.ElseBranch)
}

default:
utils.NonExhaustiveMatchPanic[error](source)
return nil, nil
Expand Down Expand Up @@ -595,6 +621,18 @@
}
return s.trySendingUpTo(source.From, cappedAmount)

case *parser.IfExpr[parser.Source]:
cond, err := evaluateExprAs(s, source.Condition, expectBool)
if err != nil {
return nil, err
}

Check warning on line 628 in internal/interpreter/interpreter.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/interpreter.go#L627-L628

Added lines #L627 - L628 were not covered by tests

if *cond {
return s.trySendingUpTo(source.IfBranch, amount)
} else {
return s.trySendingUpTo(source.ElseBranch, amount)
}

default:
utils.NonExhaustiveMatchPanic[any](source)
return nil, nil
Expand Down Expand Up @@ -675,6 +713,18 @@
// passing "remainingAmount" directly breaks the code
return handler(destination.Remaining, remainingAmountCopy)

case *parser.IfExpr[parser.Destination]:
cond, err := evaluateExprAs(s, destination.Condition, expectBool)
if err != nil {
return err
}

Check warning on line 720 in internal/interpreter/interpreter.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/interpreter.go#L719-L720

Added lines #L719 - L720 were not covered by tests

if *cond {
return s.receiveFrom(destination.IfBranch, amount)
} else {
return s.receiveFrom(destination.ElseBranch, amount)
}

default:
utils.NonExhaustiveMatchPanic[any](destination)
return nil
Expand Down
9 changes: 9 additions & 0 deletions internal/interpreter/interpreter_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
return fmt.Sprintf("invalid monetary literal: '%s'", e.Source)
}

type InvalidBoolLiteral struct {
parser.Range
Source string
}

func (e InvalidBoolLiteral) Error() string {
return fmt.Sprintf("invalid bool literal: '%s'", e.Source)

Check warning on line 36 in internal/interpreter/interpreter_error.go

View check run for this annotation

Codecov / codecov/patch

internal/interpreter/interpreter_error.go#L35-L36

Added lines #L35 - L36 were not covered by tests
}

type InvalidNumberLiteral struct {
parser.Range
Source string
Expand Down
Loading
Loading