# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

# Function Call Instructions Test Suite  
# Tests the VM's function call mechanism with user-defined function rules
# Covers FunctionCall instruction, argument passing, and return values

cases:
  # Basic Function Call Tests
  - note: simple_function_call
    description: Test basic function call instruction with one argument
    example_rego: "add_ten(5) where add_ten(x) := x + 10"
    literals:
      - {}
      - 5  # Argument value
      - 10 # Constant 10 for addition
    instruction_params:
      object_create_params:
        - dest: 6
          template_literal_idx: 0
          literal_key_fields: []
          fields: []
      function_call_params:
        - func: 0      # Rule index for add_ten function (rule 0)
          dest: 2      # Destination register for result
          args: [1]    # Argument registers
    rule_infos:
      - rule_type: Complete
        definitions:
          - [3]  # Entry point for add_ten function body (PC 3)
    instructions:
      - "Load { dest: 1, literal_idx: 1 }"      # Load argument 5 into register 1
      - "FunctionCall { params_index: 0 }"      # Call function
      - "Return { value: 2 }"                   # Return result
      # Function body starts at PC 3 (entry point)
      - "RuleInit { result_reg: 0, rule_index: 0 }"  # Initialize result register
      - "Load { dest: 3, literal_idx: 2 }"      # Load constant 10 into register 3
      - "Add { dest: 0, left: 1, right: 3 }"   # Add argument + 10 (result in register 0)
      - "RuleReturn {}"                         # Return from function
    want_result: 15

  - note: function_call_multiple_args
    description: Test function call with multiple arguments
    example_rego: "add(7, 3) where add(x, y) := x + y"
    literals:
      - {}
      - 0  # Rule index for add function
      - 7  # First argument
      - 3  # Second argument
    instruction_params:
      function_call_params:
        - func: 0      # Register containing function rule index
          dest: 3      # Destination register for result
          args: [1, 2] # Argument registers
    rule_infos:
      - rule_type: Complete
        definitions:
          - [5]  # Entry point for add function body
    instructions:
      - "Load { dest: 0, literal_idx: 1 }"      # Load rule index
      - "Load { dest: 1, literal_idx: 2 }"      # Load first argument
      - "Load { dest: 2, literal_idx: 3 }"      # Load second argument
      - "FunctionCall { params_index: 0 }"      # Call function
      - "Return { value: 3 }"                   # Return result
      # Function body
      - "RuleInit { result_reg: 0, rule_index: 0 }"  # Initialize result register
      - "Add { dest: 0, left: 1, right: 2 }"   # Add arguments (result in register 0)
      - "RuleReturn {}"                         # Return from function
    want_result: 10

  - note: function_call_no_args
    description: Test function call with no arguments
    example_rego: "get_constant() where get_constant() := 42"
    literals:
      - {}
      - 0  # Rule index for get_constant function
      - 42 # Constant value
    instruction_params:
      function_call_params:
        - func: 0      # Register containing function rule index
          dest: 1      # Destination register for result
          args: []     # No arguments
    rule_infos:
      - rule_type: Complete
        definitions:
          - [3]  # Entry point for get_constant function body
    instructions:
      - "Load { dest: 0, literal_idx: 1 }"      # Load rule index
      - "FunctionCall { params_index: 0 }"      # Call function
      - "Return { value: 1 }"                   # Return result
      # Function body
      - "RuleInit { result_reg: 0, rule_index: 0 }"  # Initialize result register
      - "Load { dest: 0, literal_idx: 2 }"      # Load constant 42 (result in register 0)
      - "RuleReturn {}"                         # Return from function
    want_result: 42

  - note: function_call_with_multiplication
    description: Test function that performs multiplication
    example_rego: "square(6) where square(x) := x * x"
    literals:
      - {}
      - 0  # Rule index for square function
      - 6  # Argument value
    instruction_params:
      function_call_params:
        - func: 0      # Register containing function rule index
          dest: 2      # Destination register for result
          args: [1]    # Argument registers
    rule_infos:
      - rule_type: Complete
        definitions:
          - [4]  # Entry point for square function body
    instructions:
      - "Load { dest: 0, literal_idx: 1 }"      # Load rule index
      - "Load { dest: 1, literal_idx: 2 }"      # Load argument 6
      - "FunctionCall { params_index: 0 }"      # Call function
      - "Return { value: 2 }"                   # Return result
      # Function body
      - "RuleInit { result_reg: 0, rule_index: 0 }"  # Initialize result register
      - "Mul { dest: 0, left: 1, right: 1 }"   # Multiply x * x (result in register 0)
      - "RuleReturn {}"                         # Return from function
    want_result: 36

  - note: nested_function_calls
    description: Test nested function calls
    example_rego: "double(add_one(5)) where double(x) := x * 2; add_one(x) := x + 1"
    literals:
      - {}
      - 1  # Rule index for add_one function
      - 0  # Rule index for double function  
      - 5  # Initial argument
      - 1  # Constant 1
      - 2  # Constant 2
    instruction_params:
      function_call_params:
        - func: 1      # First call: add_one (rule index 1)
          dest: 4      # Temporary result
          args: [2]    # Argument register
        - func: 0      # Second call: double (rule index 0)
          dest: 5      # Final result
          args: [4]    # Use result from first call
    rule_infos:
      - rule_type: Complete  # double function
        definitions:
          - [10]   # Entry point for double function body (updated)
      - rule_type: Complete  # add_one function
        definitions:
          - [6]   # Entry point for add_one function body
    instructions:
      - "Load { dest: 0, literal_idx: 1 }"      # Load add_one rule index
      - "Load { dest: 1, literal_idx: 2 }"      # Load double rule index
      - "Load { dest: 2, literal_idx: 3 }"      # Load argument 5
      - "FunctionCall { params_index: 0 }"      # Call add_one(5)
      - "FunctionCall { params_index: 1 }"      # Call double(result)
      - "Return { value: 5 }"                   # Return final result
      # add_one function body (starts at PC 6)
      - "RuleInit { result_reg: 0, rule_index: 1 }"  # Initialize result register for add_one
      - "Load { dest: 3, literal_idx: 4 }"      # Load constant 1
      - "Add { dest: 0, left: 1, right: 3 }"   # x + 1 (result in register 0)
      - "RuleReturn {}"                         # Return result
      # double function body (starts at PC 10)
      - "RuleInit { result_reg: 0, rule_index: 0 }"  # Initialize result register for double
      - "Load { dest: 3, literal_idx: 5 }"      # Load constant 2  
      - "Mul { dest: 0, left: 1, right: 3 }"   # x * 2 (result in register 0)
      - "RuleReturn {}"                         # Return result
    want_result: 12

  - note: function_call_undefined_result
    description: Test function call that returns undefined due to failed condition
    example_rego: "safe_div(1, 0) where safe_div(x, y) := x / y if y != 0"
    literals:
      - {}
      - 0  # Rule index for safe_div function
      - 1  # Numerator
      - 0  # Denominator (zero)
    instruction_params:
      function_call_params:
        - func: 0      # Register containing function rule index
          dest: 3      # Destination register for result
          args: [1, 2] # Argument registers
    rule_infos:
      - rule_type: Complete
        definitions:
          - [5]  # Entry point for safe_div function body
    instructions:
      - "Load { dest: 0, literal_idx: 1 }"      # Load rule index
      - "Load { dest: 1, literal_idx: 2 }"      # Load numerator
      - "Load { dest: 2, literal_idx: 3 }"      # Load denominator
      - "FunctionCall { params_index: 0 }"      # Call function
      - "Return { value: 3 }"                   # Return result
      # Function body with condition that fails
      - "RuleInit { result_reg: 0, rule_index: 0 }"  # Initialize result register
      - "LoadFalse { dest: 4 }"                 # Condition fails (simulated y == 0)
      - "AssertCondition { condition: 4 }"      # Assert fails, body fails
      - "Div { dest: 0, left: 1, right: 2 }"   # This won't execute
      - "RuleReturn {}"                         # This won't execute
    want_result: "#undefined"

  - note: function_call_with_object_result
    description: Test function call that returns an object
    example_rego: "make_pair(1, 2) where make_pair(x, y) := {\"first\": x, \"second\": y}"
    literals:
      - {}
      - 0         # Rule index for make_pair function
      - 1         # First value
      - 2         # Second value
      - "first"   # Key for first element
      - "second"  # Key for second element
    instruction_params:
      object_create_params:
        - dest: 6
          template_literal_idx: 0
          literal_key_fields: []
          fields: []
      function_call_params:
        - func: 0      # Register containing function rule index
          dest: 5      # Destination register for result
          args: [1, 2] # Argument registers
    rule_infos:
      - rule_type: Complete
        definitions:
          - [5]  # Entry point for make_pair function body
    instructions:
      - "Load { dest: 0, literal_idx: 1 }"      # Load rule index
      - "Load { dest: 1, literal_idx: 2 }"      # Load first value
      - "Load { dest: 2, literal_idx: 3 }"      # Load second value
      - "FunctionCall { params_index: 0 }"      # Call function
      - "Return { value: 5 }"                   # Return result
      # Function body
      - "RuleInit { result_reg: 0, rule_index: 0 }"  # Initialize result register
      - "ObjectCreate { params_index: 0 }"                 
      - "Load { dest: 3, literal_idx: 4 }"      # Load "first" key
      - "ObjectSet { obj: 6, key: 3, value: 1 }" # Set first: x
      - "Load { dest: 4, literal_idx: 5 }"      # Load "second" key  
      - "ObjectSet { obj: 6, key: 4, value: 2 }" # Set second: y
      - "Move { dest: 0, src: 6 }"             # Move object to result register
      - "RuleReturn {}"                         # Return object
    want_result: {"first": 1, "second": 2}

  - note: function_call_with_array_result
    description: Test function call that returns an array
    example_rego: "make_range(3, 5) where make_range(start, end) := [start, end]"
    literals:
      - {}
      - 0  # Rule index for make_range function
      - 3  # Start value
      - 5  # End value
    instruction_params:
      function_call_params:
        - func: 0      # Register containing function rule index
          dest: 3      # Destination register for result
          args: [1, 2] # Argument registers
    rule_infos:
      - rule_type: Complete
        definitions:
          - [5]  # Entry point for make_range function body
    instructions:
      - "Load { dest: 0, literal_idx: 1 }"      # Load rule index
      - "Load { dest: 1, literal_idx: 2 }"      # Load start value
      - "Load { dest: 2, literal_idx: 3 }"      # Load end value
      - "FunctionCall { params_index: 0 }"      # Call function
      - "Return { value: 3 }"                   # Return result
      # Function body
      - "RuleInit { result_reg: 0, rule_index: 0 }"  # Initialize result register
      - "ArrayNew { dest: 4 }"                  # Create new array
      - "ArrayPush { arr: 4, value: 1 }"       # Push start value
      - "ArrayPush { arr: 4, value: 2 }"       # Push end value
      - "Move { dest: 0, src: 4 }"             # Move array to result register
      - "RuleReturn {}"                         # Return array
    want_result: [3, 5]

  - note: function_call_with_comparison
    description: Test function that performs comparison
    example_rego: "max(7, 3) where max(x, y) := x if x >= y"
    literals:
      - {}
      - 0  # Rule index for max function
      - 7  # First value
      - 3  # Second value
    instruction_params:
      function_call_params:
        - func: 0      # Register containing function rule index
          dest: 3      # Destination register for result
          args: [1, 2] # Argument registers
    rule_infos:
      - rule_type: Complete
        definitions:
          - [5]  # Entry point for max function body
    instructions:
      - "Load { dest: 0, literal_idx: 1 }"      # Load rule index
      - "Load { dest: 1, literal_idx: 2 }"      # Load first value (7)
      - "Load { dest: 2, literal_idx: 3 }"      # Load second value (3)
      - "FunctionCall { params_index: 0 }"      # Call function
      - "Return { value: 3 }"                   # Return result
      # Function body: return x if x >= y
      - "RuleInit { result_reg: 0, rule_index: 0 }"  # Initialize result register
      - "Ge { dest: 4, left: 1, right: 2 }"    # x >= y
      - "AssertCondition { condition: 4 }"      # Assert condition
      - "Move { dest: 0, src: 1 }"             # Return x (move to result register)
      - "RuleReturn {}"                         # Return result
    want_result: 7

  - note: function_call_three_args
    description: Test function call with three arguments  
    example_rego: "sum_three(2, 3, 4) where sum_three(a, b, c) := a + b + c"
    literals:
      - {}
      - 0  # Rule index for sum_three function
      - 2  # First argument
      - 3  # Second argument
      - 4  # Third argument
    instruction_params:
      function_call_params:
        - func: 0         # Register containing function rule index
          dest: 4         # Destination register for result
          args: [1, 2, 3] # Argument registers
    rule_infos:
      - rule_type: Complete
        definitions:
          - [6]  # Entry point for sum_three function body
    instructions:
      - "Load { dest: 0, literal_idx: 1 }"      # Load rule index
      - "Load { dest: 1, literal_idx: 2 }"      # Load first argument (2)
      - "Load { dest: 2, literal_idx: 3 }"      # Load second argument (3)
      - "Load { dest: 3, literal_idx: 4 }"      # Load third argument (4)
      - "FunctionCall { params_index: 0 }"      # Call function
      - "Return { value: 4 }"                   # Return result
      # Function body: a + b + c
      - "RuleInit { result_reg: 0, rule_index: 0 }"  # Initialize result register
      - "Add { dest: 5, left: 1, right: 2 }"   # a + b
      - "Add { dest: 0, left: 5, right: 3 }"   # (a + b) + c (result in register 0)
      - "RuleReturn {}"                         # Return result
    want_result: 9

  - note: function_call_inconsistent_definitions
    description: Test function with multiple definitions that produce different values (should fail)
    example_rego: "f(5) where f(x) := x + 1; f(x) := x + 2"
    literals:
      - {}
      - 0  # Rule index for f function  
      - 5  # Argument value
      - 1  # Constant 1
      - 2  # Constant 2
    instruction_params:
      function_call_params:
        - func: 0      # Register containing function rule index
          dest: 2      # Destination register for result
          args: [1]    # Argument registers
    rule_infos:
      - rule_type: Complete
        definitions:
          - [4]  # First definition: x + 1
          - [8]  # Second definition: x + 2 (should produce different result)
    entry_points:
      "data.test.compute": [4, 8]  # Multiple definitions for the same function
    instructions:
      - "Load { dest: 0, literal_idx: 1 }"      # Load rule index
      - "Load { dest: 1, literal_idx: 2 }"      # Load argument (5)
      - "FunctionCall { params_index: 0 }"      # Call function
      - "Return { value: 2 }"                   # Return result
      # First definition: x + 1 (entry point 4)
      - "RuleInit { result_reg: 0, rule_index: 0 }"  # Initialize result register
      - "Load { dest: 3, literal_idx: 3 }"      # Load constant 1
      - "Add { dest: 0, left: 1, right: 3 }"   # x + 1 = 6 (result in register 0)
      - "RuleReturn {}"                         # Return result
      # Second definition: x + 2 (entry point 8)  
      - "RuleInit { result_reg: 0, rule_index: 0 }"  # Initialize result register
      - "Load { dest: 4, literal_idx: 4 }"      # Load constant 2
      - "Add { dest: 0, left: 1, right: 4 }"   # x + 2 = 7 (result in register 0)
      - "RuleReturn {}"                         # Return result
    want_result: "#undefined"
