Skip to content
Open
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
2 changes: 1 addition & 1 deletion Lexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ PERCENTAGE_PORTION_LITERAL: [0-9]+ ('.' [0-9]+)? '%';
STRING: '"' ('\\"' | ~[\r\n"])* '"';

IDENTIFIER: [a-z]+ [a-z_]*;
NUMBER: MINUS? [0-9]+ ('_' [0-9]+)*;
NUMBER: [0-9]+ ('_' [0-9]+)*;
ASSET: [A-Z][A-Z0-9]* ('/' [0-9]+)?;

ACCOUNT_START: '@' -> pushMode(ACCOUNT_MODE);
Expand Down
1 change: 1 addition & 0 deletions Numscript.g4
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ valueExpr:
| NUMBER # numberLiteral
| PERCENTAGE_PORTION_LITERAL # percentagePortionLiteral
| monetaryLit # monetaryLiteral
| op=MINUS valueExpr # prefixExpr
| left = valueExpr op = DIV right = valueExpr # infixExpr
| left = valueExpr op = (PLUS | MINUS) right = valueExpr # infixExpr
| LPARENS valueExpr RPARENS # parenthesizedExpr
Expand Down
37 changes: 37 additions & 0 deletions internal/analysis/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,16 @@ func (res *CheckResult) checkTypeOf(lit parser.ValueExpr, typeHint string) strin
return TypeAny
}

case *parser.Prefix:
switch lit.Operator {
case parser.PrefixOperatorMinus:
res.unifyNodeWith(lit, res.GetExprType(lit.Expr))
return res.checkHasOneOfTypes(lit.Expr, []string{TypeNumber, TypeMonetary})

default:
return TypeAny
}

case *parser.AccountInterpLiteral:
res.checkAccountInterpolationVersion(*lit)

Expand Down Expand Up @@ -511,6 +521,20 @@ func (res *CheckResult) checkInfixOverload(bin *parser.BinaryInfix, allowed []st
return TypeAny
}

func (res *CheckResult) checkHasOneOfTypes(expr parser.ValueExpr, allowed []string) string {
exprType := res.checkTypeOf(expr, allowed[0])

if exprType == TypeAny || slices.Contains(allowed, exprType) {
return exprType
}

res.pushDiagnostic(expr.GetRange(), TypeMismatch{
Expected: strings.Join(allowed, "|"),
Got: exprType,
})
return TypeAny
}

func (res *CheckResult) assertHasType(lit parser.ValueExpr, requiredType string, actualType string) {
if requiredType == TypeAny || actualType == TypeAny || requiredType == actualType {
return
Expand Down Expand Up @@ -671,6 +695,19 @@ func (res CheckResult) tryEvaluatingNumberExpr(expr parser.ValueExpr) *big.Int {
case *parser.NumberLiteral:
return big.NewInt(int64(expr.Number))

case *parser.Prefix:
switch expr.Operator {
case parser.PrefixOperatorMinus:
evExpr := res.tryEvaluatingNumberExpr(expr.Expr)
if evExpr == nil {
return nil
}
return new(big.Int).Neg(evExpr)

default:
return nil
}

case *parser.BinaryInfix:
switch expr.Operator {
case parser.InfixOperatorPlus:
Expand Down
3 changes: 3 additions & 0 deletions internal/analysis/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ func hoverOnExpression(lit parser.ValueExpr, position parser.Position) Hover {
return hover
}

case *parser.Prefix:
return hoverOnExpression(lit.Expr, position)

case *parser.FnCall:
return hoverOnFnCall(*lit, position)
}
Expand Down
30 changes: 29 additions & 1 deletion internal/interpreter/evaluate_expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ func (st *programState) evaluateExpr(expr parser.ValueExpr) (Value, InterpreterE
return value, nil

case *parser.BinaryInfix:

switch expr.Operator {
case parser.InfixOperatorPlus:
return st.plusOp(expr.Left, expr.Right)
Expand All @@ -85,6 +84,16 @@ func (st *programState) evaluateExpr(expr parser.ValueExpr) (Value, InterpreterE
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)
Expand Down Expand Up @@ -214,6 +223,25 @@ 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(
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:
Expand Down
23 changes: 23 additions & 0 deletions internal/interpreter/infix_overloads.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,26 @@ func (m Monetary) evalSub(st *programState, other parser.ValueExpr) (Value, Inte
}, nil

}

type opNeg interface {
evalNeg(st *programState) (Value, InterpreterError)
}

var _ opNeg = (*MonetaryInt)(nil)
var _ opNeg = (*Monetary)(nil)

func (m MonetaryInt) evalNeg(st *programState) (Value, InterpreterError) {
m1 := big.Int(m)
neg := new(big.Int).Neg(&m1)
return MonetaryInt(*neg), nil
}

func (m Monetary) evalNeg(st *programState) (Value, InterpreterError) {
m1 := big.Int(m.Amount)
neg := new(big.Int).Neg(&m1)
return Monetary{
Asset: m.Asset,
Amount: MonetaryInt(*neg),
}, nil

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
send [EUR/2 100] - [EUR/2 1] (
source = @world
destination = @dest
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://raw.githubusercontent.com/formancehq/numscript/main/specs.schema.json",
"testCases": [
{
"it": "should subtract the monetaries",
"expect.postings": [
{
"source": "world",
"destination": "dest",
"amount": 99,
"asset": "EUR/2"
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
send [EUR/2 100-1] (
source = @world
destination = @dest
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://raw.githubusercontent.com/formancehq/numscript/main/specs.schema.json",
"testCases": [
{
"it": "should subtract the numbers",
"expect.postings": [
{
"source": "world",
"destination": "dest",
"amount": 99,
"asset": "EUR/2"
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
send -[EUR/2 -100] (
source = @world
destination = @dest
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://raw.githubusercontent.com/formancehq/numscript/main/specs.schema.json",
"testCases": [
{
"it": "should subtract the numbers",
"expect.postings": [
{
"source": "world",
"destination": "dest",
"amount": 100,
"asset": "EUR/2"
}
]
}
]
}
11 changes: 9 additions & 2 deletions internal/parser/__snapshots__/parser_test.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2173,12 +2173,19 @@ parser.Program{
},
Asset: "EUR/2",
},
Amount: &parser.NumberLiteral{
Amount: &parser.Prefix{
Range: parser.Range{
Start: parser.Position{Character:12, Line:0},
End: parser.Position{Character:16, Line:0},
},
Number: -100,
Operator: "-",
Expr: &parser.NumberLiteral{
Range: parser.Range{
Start: parser.Position{Character:13, Line:0},
End: parser.Position{Character:16, Line:0},
},
Number: 100,
},
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion internal/parser/antlrParser/Lexer.interp

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion internal/parser/antlrParser/Numscript.interp

Large diffs are not rendered by default.

Loading
Loading