@@ -373,6 +373,11 @@ func (res *CheckResult) checkTypeOf(lit parser.ValueExpr) string {
373373 case parser .InfixOperatorMinus :
374374 return res .checkInfixOverload (lit , []string {TypeNumber , TypeMonetary })
375375
376+ case parser .InfixOperatorDiv :
377+ res .checkExpression (lit .Left , TypeNumber )
378+ res .checkExpression (lit .Right , TypeNumber )
379+ return TypePortion
380+
376381 default :
377382 // we should never get here
378383 // but just to be sure
@@ -383,7 +388,7 @@ func (res *CheckResult) checkTypeOf(lit parser.ValueExpr) string {
383388
384389 case * parser.AccountLiteral :
385390 return TypeAccount
386- case * parser.RatioLiteral :
391+ case * parser.PercentageLiteral :
387392 return TypePortion
388393 case * parser.AssetLiteral :
389394 return TypeAsset
@@ -525,18 +530,22 @@ func (res *CheckResult) checkSource(source parser.Source) {
525530 }
526531
527532 var remainingAllotment * parser.RemainingAllotment = nil
528- var variableLiterals []parser.Variable
533+ var variableLiterals []parser.ValueExpr
529534
530535 sum := big .NewRat (0 , 1 )
531536 for i , allottedItem := range source .Items {
532537 isLast := i == len (source .Items )- 1
533538
534539 switch allotment := allottedItem .Allotment .(type ) {
535- case * parser.Variable :
536- variableLiterals = append (variableLiterals , * allotment )
537- res .checkExpression (allotment , TypePortion )
538- case * parser.RatioLiteral :
539- sum .Add (sum , allotment .ToRatio ())
540+ case * parser.ValueExprAllotment :
541+ res .checkExpression (allotment .Value , TypePortion )
542+ rat := res .tryEvaluatingPortionExpr (allotment .Value )
543+ if rat == nil {
544+ variableLiterals = append (variableLiterals , allotment .Value )
545+ } else {
546+ sum .Add (sum , rat )
547+ }
548+
540549 case * parser.RemainingAllotment :
541550 if isLast {
542551 remainingAllotment = allotment
@@ -560,6 +569,92 @@ func (res *CheckResult) checkSource(source parser.Source) {
560569 }
561570}
562571
572+ // Try evaluating an expression, if it can be done statically.
573+ //
574+ // Returns nil when the expression contains variables, fn calls, or anything
575+ // that cannot be computed statically.
576+ //
577+ // For example:
578+ //
579+ // 1 + 2 => 3
580+ // 1 + $x => nil
581+ func (res CheckResult ) tryEvaluatingNumberExpr (expr parser.ValueExpr ) * big.Int {
582+ switch expr := expr .(type ) {
583+
584+ case * parser.NumberLiteral :
585+ return big .NewInt (int64 (expr .Number ))
586+
587+ case * parser.BinaryInfix :
588+ switch expr .Operator {
589+ case parser .InfixOperatorPlus :
590+ left := res .tryEvaluatingNumberExpr (expr .Left )
591+ if left == nil {
592+ return nil
593+ }
594+ right := res .tryEvaluatingNumberExpr (expr .Right )
595+ if right == nil {
596+ return nil
597+ }
598+ return new (big.Int ).Add (left , right )
599+
600+ case parser .InfixOperatorMinus :
601+ left := res .tryEvaluatingNumberExpr (expr .Left )
602+ if left == nil {
603+ return nil
604+ }
605+ right := res .tryEvaluatingNumberExpr (expr .Right )
606+ if right == nil {
607+ return nil
608+ }
609+ return new (big.Int ).Sub (left , right )
610+
611+ default :
612+ return nil
613+ }
614+
615+ default :
616+ return nil
617+ }
618+ }
619+
620+ // Same as analysis.tryEvaluatingNumberExpr, for portion
621+ func (res * CheckResult ) tryEvaluatingPortionExpr (expr parser.ValueExpr ) * big.Rat {
622+ switch expr := expr .(type ) {
623+ case * parser.PercentageLiteral :
624+ return expr .ToRatio ()
625+
626+ case * parser.BinaryInfix :
627+ switch expr .Operator {
628+ case parser .InfixOperatorDiv :
629+ right := res .tryEvaluatingNumberExpr (expr .Right )
630+ if right == nil {
631+ return nil
632+ }
633+
634+ if right .Cmp (big .NewInt (0 )) == 0 {
635+ res .Diagnostics = append (res .Diagnostics , Diagnostic {
636+ Kind : & DivByZero {},
637+ Range : expr .Range ,
638+ })
639+ return nil
640+ }
641+
642+ left := res .tryEvaluatingNumberExpr (expr .Left )
643+ if left == nil {
644+ return nil
645+ }
646+
647+ return new (big.Rat ).SetFrac (left , right )
648+
649+ default :
650+ return nil
651+ }
652+
653+ default :
654+ return nil
655+ }
656+ }
657+
563658func (res * CheckResult ) checkDestination (destination parser.Destination ) {
564659 if destination == nil {
565660 return
@@ -585,18 +680,25 @@ func (res *CheckResult) checkDestination(destination parser.Destination) {
585680
586681 case * parser.DestinationAllotment :
587682 var remainingAllotment * parser.RemainingAllotment
588- var variableLiterals []parser.Variable
683+ var variableLiterals []parser.ValueExpr
589684 sum := big .NewRat (0 , 1 )
590685
591686 for i , allottedItem := range destination .Items {
592687 isLast := i == len (destination .Items )- 1
593688
594689 switch allotment := allottedItem .Allotment .(type ) {
595- case * parser.Variable :
596- variableLiterals = append (variableLiterals , * allotment )
597- res .checkExpression (allotment , TypePortion )
598- case * parser.RatioLiteral :
599- sum .Add (sum , allotment .ToRatio ())
690+ case * parser.ValueExprAllotment :
691+ res .checkExpression (allotment .Value , TypePortion )
692+ rat := res .tryEvaluatingPortionExpr (allotment .Value )
693+ if rat == nil {
694+ variableLiterals = append (variableLiterals , allotment .Value )
695+ } else {
696+ sum .Add (sum , rat )
697+ }
698+
699+ // res.checkExpression(allotment, TypePortion)
700+ // case *parser.PortionLiteral:
701+ // sum.Add(sum, allotment.ToRatio())
600702 case * parser.RemainingAllotment :
601703 if isLast {
602704 remainingAllotment = allotment
@@ -628,7 +730,7 @@ func (res *CheckResult) checkHasBadAllotmentSum(
628730 sum big.Rat ,
629731 rng parser.Range ,
630732 remaining * parser.RemainingAllotment ,
631- variableLiterals []parser.Variable ,
733+ variableLiterals []parser.ValueExpr ,
632734) {
633735 cmp := sum .Cmp (big .NewRat (1 , 1 ))
634736 switch cmp {
@@ -640,7 +742,7 @@ func (res *CheckResult) checkHasBadAllotmentSum(
640742 if cmp == - 1 && len (variableLiterals ) == 1 {
641743 var value big.Rat
642744 res .Diagnostics = append (res .Diagnostics , Diagnostic {
643- Range : variableLiterals [0 ].Range ,
745+ Range : variableLiterals [0 ].GetRange () ,
644746 Kind : & FixedPortionVariable {
645747 Value : * value .Sub (big .NewRat (1 , 1 ), & sum ),
646748 },
@@ -658,7 +760,7 @@ func (res *CheckResult) checkHasBadAllotmentSum(
658760 case 0 :
659761 for _ , varLit := range variableLiterals {
660762 res .Diagnostics = append (res .Diagnostics , Diagnostic {
661- Range : varLit .Range ,
763+ Range : varLit .GetRange () ,
662764 Kind : & FixedPortionVariable {
663765 Value : * big .NewRat (0 , 1 ),
664766 },
0 commit comments