From 1e87be859d06cb186dbb42d5571790f49fb3f5b6 Mon Sep 17 00:00:00 2001
From: Iskander Sharipov <quasilyte@gmail.com>
Date: Fri, 2 Feb 2024 17:09:00 +0400
Subject: [PATCH] pkg/hintrunner/zero: allow multiplication binary ops in
 references

References may include the multiplication inside them.

Let's take this like of code for the example:

    https://github.com/starkware-libs/cairo-lang/blob/caba294d82eeeccc3d86a158adb8ba209bf2d8fc/src/starkware/cairo/common/math.cairo#L193

It will produce a reference like this:
```json
    {
        "cairo_type": "felt",
        "full_name": "starkware.cairo.common.math.assert_le_felt.arc_prod",
        "references": [
            {
                "ap_tracking_data": {
                    "group": 1,
                    "offset": 8
                },
                "pc": 14,
                "value": "cast([ap + (-5)] * [ap + (-1)], felt)"
            }
        ],
        "type": "reference"
    }
```
---
 pkg/hintrunner/zero/hintparser.go      | 33 +++++++++++++++++++++++---
 pkg/hintrunner/zero/hintparser_test.go | 20 ++++++++++++++++
 2 files changed, 50 insertions(+), 3 deletions(-)

diff --git a/pkg/hintrunner/zero/hintparser.go b/pkg/hintrunner/zero/hintparser.go
index 19113d1cd..15f8fe919 100644
--- a/pkg/hintrunner/zero/hintparser.go
+++ b/pkg/hintrunner/zero/hintparser.go
@@ -3,6 +3,7 @@ package zero
 import (
 	"fmt"
 
+	"github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/hinter"
 	op "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/hinter"
 	"github.com/alecthomas/participle/v2"
 )
@@ -22,6 +23,7 @@ var parser *participle.Parser[IdentifierExp] = participle.MustBuild[IdentifierEx
 // 2 dereferences off1 omitted: cast([reg] + [reg + off2], type)
 // 2 dereferences off2 omitted: cast([reg + off1] + [reg], type)
 // 2 dereferences both offs omitted: cast([reg] + [reg], type)
+// 2 dereferences with multiplication: cast([reg + off1] * [reg + off2], felt)
 // Reference no dereference 2 offsets - + : cast(reg - off1 + off2, type)
 
 // Note: The same cases apply with an external dereference. Example: [cast(number, type)]
@@ -62,7 +64,8 @@ type DerefExp struct {
 }
 
 type BinOpExp struct {
-	LeftExp  *LeftExp  `@@ "+"`
+	LeftExp  *LeftExp  `@@`
+	Operator string    `@("+" | "*")`
 	RightExp *RightExp `@@`
 }
 
@@ -83,10 +86,12 @@ type RightExp struct {
 
 type DerefOffset struct {
 	Deref  op.Deref
+	Op     op.Operator
 	Offset *int
 }
 type DerefDeref struct {
 	LeftDeref  op.Deref
+	Op         op.Operator
 	RightDeref op.Deref
 }
 
@@ -141,8 +146,9 @@ func (expression CastExp) Evaluate() (any, error) {
 		return result, nil
 	case DerefOffset:
 		return op.BinaryOp{
-			Operator: 0,
+			Operator: result.Op,
 			Lhs:      result.Deref.Deref,
+			// TODO: why we're not using something like f.NewElement here?
 			Rhs: op.Immediate{
 				uint64(0),
 				uint64(0),
@@ -152,7 +158,7 @@ func (expression CastExp) Evaluate() (any, error) {
 		}, nil
 	case DerefDeref:
 		return op.BinaryOp{
-			Operator: 0,
+			Operator: result.Op,
 			Lhs:      result.LeftDeref.Deref,
 			Rhs:      result.RightDeref,
 		}, nil
@@ -238,8 +244,16 @@ func (expression BinOpExp) Evaluate() (any, error) {
 		return nil, err
 	}
 
+	operation, err := parseOperator(expression.Operator)
+	if err != nil {
+		return nil, err
+	}
+
 	switch lResult := leftExp.(type) {
 	case op.CellRefer:
+		// Right now we assume that there is no expression like `reg - off1 * off2`,
+		// but if there are, we would need to come up with an idea how to handle it.
+		// Right now we only cover `off1 + off2` expressions here.
 		offset, ok := rightExp.(*int)
 		if !ok {
 			return nil, fmt.Errorf("invalid type operation")
@@ -267,11 +281,13 @@ func (expression BinOpExp) Evaluate() (any, error) {
 		case op.Deref:
 			return DerefDeref{
 				lResult,
+				operation,
 				rResult,
 			}, nil
 		case *int:
 			return DerefOffset{
 				lResult,
+				operation,
 				rResult,
 			}, nil
 		}
@@ -308,3 +324,14 @@ func ParseIdentifier(value string) (any, error) {
 
 	return identifierExp.Evaluate()
 }
+
+func parseOperator(op string) (hinter.Operator, error) {
+	switch op {
+	case "+":
+		return hinter.Add, nil
+	case "*":
+		return hinter.Mul, nil
+	default:
+		return 0, fmt.Errorf("unexpected op: %q", op)
+	}
+}
diff --git a/pkg/hintrunner/zero/hintparser_test.go b/pkg/hintrunner/zero/hintparser_test.go
index 39b8fd8a1..0fcbf5a63 100644
--- a/pkg/hintrunner/zero/hintparser_test.go
+++ b/pkg/hintrunner/zero/hintparser_test.go
@@ -45,6 +45,26 @@ func TestHintParser(t *testing.T) {
 				},
 			},
 		},
+		{
+			Parameter:         "cast([ap + (-5)] * [ap + (-1)], felt)",
+			ExpectedCellRefer: nil,
+			ExpectedResOperander: hinter.BinaryOp{
+				Operator: hinter.Mul,
+				Lhs:      hinter.ApCellRef(-5),
+				Rhs: hinter.Deref{
+					Deref: hinter.ApCellRef(-1),
+				},
+			},
+		},
+		{
+			Parameter:         "cast([ap] * 3, felt)",
+			ExpectedCellRefer: nil,
+			ExpectedResOperander: hinter.BinaryOp{
+				Operator: hinter.Mul,
+				Lhs:      hinter.ApCellRef(0),
+				Rhs:      hinter.Immediate{0, 0, 0, 3},
+			},
+		},
 	}
 
 	for _, test := range testSet {