visitPrefixExpression method

  1. @override
Object? visitPrefixExpression(
  1. SPrefixExpression node
)
override

Implementation

@override
Object? visitPrefixExpression(SPrefixExpression node) {
  final operatorType = node.operator;
  final operandNode = node.operand;
  final operandValue = operandNode!.accept<Object?>(this);
  final bridgedInstance = toBridgedInstance(operandValue);
  final operand = bridgedInstance.$2
      ? bridgedInstance.$1!.nativeObject
      : operandValue;

  switch (operatorType) {
    case '!': // Logical NOT
      // Use unwrapped operand for check
      if (operand is bool) {
        return !operand;
      } else {
        // Error uses original value type
        throw RuntimeD4rtException(
          "Operand for '!' must be a boolean, but was ${operandValue?.runtimeType}.",
        );
      }

    case '-': // Unary minus (negation)
      // Use unwrapped operand for check
      if (operand is num) {
        return -operand;
      } else if (operand is BigInt) {
        return -operand;
      } else if (operandValue is InterpretedInstance) {
        // Check for class operator - method
        final operatorMethod = operandValue.findOperator('-');
        if (operatorMethod != null) {
          Logger.debug(
            "[PrefixExpr] Found class operator '-' on ${operandValue.klass.name}. Calling...",
          );
          try {
            return operatorMethod.bind(operandValue).call(this, [], {});
          } on ReturnException catch (e) {
            return e.value;
          } catch (e) {
            throw RuntimeD4rtException(
              "Error executing class operator '-': $e",
            );
          }
        }
        // No class operator found, try extensions
      }

      // Check for bridged operator methods (unary -)
      if (toBridgedInstance(operandValue).$2) {
        final bridgedInstance = toBridgedInstance(operandValue).$1!;
        final bridgedClass = bridgedInstance.bridgedClass;
        final methodAdapter = bridgedClass.findInstanceMethodAdapter('-');
        if (methodAdapter != null) {
          Logger.debug(
            "[PrefixExpr] Found bridged unary operator '-' for ${bridgedClass.name}. Calling adapter...",
          );
          try {
            // Unary operator - call with empty positional args
            return methodAdapter(
              this,
              bridgedInstance.nativeObject,
              [],
              {},
              null,
            );
          } catch (e, s) {
            Logger.error(
              "[PrefixExpr] Native exception during bridged unary operator '-' on ${bridgedClass.name}: $e\\n$s",
            );
            throw RuntimeD4rtException(
              "Native error during bridged unary operator '-' on ${bridgedClass.name}: $e",
              originalException: e,
            );
          }
        }
      }

      const operatorName = '-';
      try {
        final extensionOperator = environment.findExtensionMember(
          operandValue,
          operatorName,
        );
        if (extensionOperator is ExtensionMemberCallable &&
            extensionOperator.isOperator) {
          Logger.debug(
            "[PrefixExpr] Found extension operator '-' for type ${operandValue?.runtimeType}. Calling...",
          );
          // Args: receiver (operandValue)
          try {
            return extensionOperator.call(this, [operandValue], {});
          } on ReturnException catch (e) {
            return e.value;
          } catch (e) {
            throw RuntimeD4rtException(
              "Error executing extension operator '-': $e",
            );
          }
        }
      } on RuntimeD4rtException catch (findError) {
        Logger.debug(
          "[PrefixExpr] Extension operator '-' not found for type ${operandValue?.runtimeType}. Error: ${findError.message}",
        );
        // Fall through
      }
      // Error uses original value type if extension not found/failed
      throw RuntimeD4rtException(
        "Operand for unary '-' must be a number or have an operator defined, but was ${operandValue?.runtimeType}.",
      );

    case '~': // Bitwise NOT (~)
      if (operand is int) {
        return ~operand;
      } else if (operand is BigInt) {
        // BigInt does not have a standard unary ~ operator in Dart
        // We rely solely on extensions for BigInt bitwise NOT
      } else if (operandValue is InterpretedInstance) {
        // Check for class operator ~ method
        final operatorMethod = operandValue.findOperator('~');
        if (operatorMethod != null) {
          Logger.debug(
            "[PrefixExpr] Found class operator '~' on ${operandValue.klass.name}. Calling...",
          );
          try {
            return operatorMethod.bind(operandValue).call(this, [], {});
          } on ReturnException catch (e) {
            return e.value;
          } catch (e) {
            throw RuntimeD4rtException(
              "Error executing class operator '~': $e",
            );
          }
        }
        // No class operator found, try extensions
      }

      // Check for bridged operator methods (unary ~)
      if (toBridgedInstance(operandValue).$2) {
        final bridgedInstance = toBridgedInstance(operandValue).$1!;
        final bridgedClass = bridgedInstance.bridgedClass;
        final methodAdapter = bridgedClass.findInstanceMethodAdapter('~');
        if (methodAdapter != null) {
          Logger.debug(
            "[PrefixExpr] Found bridged unary operator '~' for ${bridgedClass.name}. Calling adapter...",
          );
          try {
            // Unary operator - call with empty positional args
            return methodAdapter(
              this,
              bridgedInstance.nativeObject,
              [],
              {},
              null,
            );
          } catch (e, s) {
            Logger.error(
              "[PrefixExpr] Native exception during bridged unary operator '~' on ${bridgedClass.name}: $e\\n$s",
            );
            throw RuntimeD4rtException(
              "Native error during bridged unary operator '~' on ${bridgedClass.name}: $e",
              originalException: e,
            );
          }
        }
      }

      // Try Extension Operator '~' (for non-int or BigInt)
      const operatorNameTilde = '~';
      try {
        final extensionOperator = environment.findExtensionMember(
          operandValue,
          operatorNameTilde,
        );
        if (extensionOperator is ExtensionMemberCallable &&
            extensionOperator.isOperator) {
          Logger.debug(
            "[PrefixExpr] Found extension operator '~' for type ${operandValue?.runtimeType}. Calling...",
          );
          // Args: receiver (operandValue)
          try {
            return extensionOperator.call(this, [operandValue], {});
          } on ReturnException catch (e) {
            return e.value;
          } catch (e) {
            throw RuntimeD4rtException(
              "Error executing extension operator '~': $e",
            );
          }
        }
      } on RuntimeD4rtException catch (findError) {
        Logger.debug(
          "[PrefixExpr] Extension operator '~' not found for type ${operandValue?.runtimeType}. Error: ${findError.message}",
        );
        // Fall through
      }
      // Error if neither standard nor extension worked
      throw RuntimeD4rtException(
        "Operand for unary '~' must be an int or have an operator defined, but was ${operandValue?.runtimeType}.",
      );

    case '++': // Prefix increment (++x)
    case '--': // Prefix decrement (--x)
      // Re-evaluate and unwrap operand specifically for ++/--
      final operandValue = operandNode.accept<Object?>(this);
      final bridgedInstance = toBridgedInstance(operandValue);
      final assignOperand = bridgedInstance.$2
          ? bridgedInstance.$1!.nativeObject
          : operandValue;

      // Check if AST node is assignable (SSimpleIdentifier or SPropertyAccess for now)
      if (operandNode is SSimpleIdentifier) {
        final variableName = operandNode.name;
        // We need the current value (already got it as assignOperand)
        final currentValue = assignOperand;

        if (currentValue is num) {
          final newValue = operatorType == '++'
              ? currentValue + 1
              : currentValue - 1;
          // Assign the new value back to the variable
          environment.assign(variableName, newValue);
          // Return the *new* value
          return newValue;
        } else if (operandValue is InterpretedInstance) {
          // Use custom + operator with literal 1
          final operatorMethod = operandValue.findOperator('+');
          if (operatorMethod != null) {
            try {
              // For ++x, we create appropriate operand and call x + operand
              final operand = _createIncrementOperand(
                currentValue,
                operatorType == '++',
              );
              // Note: For --, we could either call x + (-1) or x - 1
              // Let's use + with -1 for consistency
              final newValue = operatorMethod.bind(operandValue).call(this, [
                operand,
              ], {});
              // Assign the new value back to the variable
              environment.assign(variableName, newValue);
              // Return the *new* value
              return newValue;
            } on ReturnException catch (e) {
              final newValue = e.value;
              // Assign the new value back to the variable
              environment.assign(variableName, newValue);
              // Return the *new* value
              return newValue;
            } catch (e) {
              throw RuntimeD4rtException(
                "Error executing custom operator '+' for prefix '${operatorType == '++' ? '++' : '--'}': $e",
              );
            }
          } else {
            throw RuntimeD4rtException(
              "Cannot increment/decrement object of type '${operandValue.klass.name}': No operator '+' found.",
            );
          }
        } else {
          // Requires finding operator +/-, then assigning back.
          // Complex, skip for now.
          // Error uses original value type
          throw RuntimeD4rtException(
            "Operand for prefix '${operatorType == '++' ? '++' : '--'}' must be a number, but was ${operandValue?.runtimeType}. Extension support TBD.",
          );
        }
      } else if (operandNode is SPropertyAccess) {
        // Handle property access like obj.field++
        final targetValue = operandNode.target?.accept<Object?>(this);
        final propertyName = operandNode.propertyName!.name;

        if (targetValue is InterpretedInstance) {
          // Get current value via getter or field
          final currentValue = targetValue.get(propertyName);

          // Calculate new value
          Object? newValue;
          if (currentValue is num) {
            newValue = operatorType == '++'
                ? currentValue + 1
                : currentValue - 1;
          } else if (currentValue is InterpretedInstance) {
            // Use custom + operator with literal 1
            final operatorMethod = currentValue.findOperator('+');
            if (operatorMethod != null) {
              try {
                // For ++x, we create a literal 1 and call x + 1
                operatorType == '++' ? 1 : -1;
                // Note: For --, we could either call x + (-1) or x - 1
                // Let's use + with -1 for consistency
                newValue = operatorMethod.bind(currentValue).call(this, [
                  operand,
                ], {});
              } on ReturnException catch (e) {
                newValue = e.value;
              } catch (e) {
                throw RuntimeD4rtException(
                  "Error executing custom operator '+' for prefix '${operatorType == '++' ? '++' : '--'}': $e",
                );
              }
            } else {
              throw RuntimeD4rtException(
                "Cannot increment/decrement object of type '${currentValue.klass.name}': No operator '+' found.",
              );
            }
          } else {
            throw RuntimeD4rtException(
              "Cannot increment/decrement property '$propertyName' of type '${currentValue?.runtimeType}': Expected number or object with '+' operator.",
            );
          }

          // Set new value via setter or field
          final setter = targetValue.klass.findInstanceSetter(propertyName);
          if (setter != null) {
            setter.bind(targetValue).call(this, [newValue], {});
          } else {
            targetValue.set(propertyName, newValue, this);
          }

          // Return the *new* value for prefix operators
          return newValue;
        } else {
          throw RuntimeD4rtException(
            "Cannot increment/decrement property on non-instance object of type '${targetValue?.runtimeType}'.",
          );
        }
      } else if (operandNode is SPrefixedIdentifier) {
        // Handle prefixed identifier like obj.field++ (parsed as SPrefixedIdentifier)
        final targetValue = operandNode.prefix!.accept<Object?>(this);
        final propertyName = operandNode.identifier!.name;

        if (targetValue is InterpretedInstance) {
          // Get current value via getter or field
          final currentValue = targetValue.get(propertyName);

          // Calculate new value
          Object? newValue;
          if (currentValue is num) {
            newValue = operatorType == '++'
                ? currentValue + 1
                : currentValue - 1;
          } else if (currentValue is InterpretedInstance) {
            // Use custom + operator with literal 1
            final operatorMethod = currentValue.findOperator('+');
            if (operatorMethod != null) {
              try {
                // For ++x, we create a literal 1 and call x + 1
                operatorType == '++' ? 1 : -1;
                // Note: For --, we could either call x + (-1) or x - 1
                // Let's use + with -1 for consistency
                newValue = operatorMethod.bind(currentValue).call(this, [
                  operand,
                ], {});
              } on ReturnException catch (e) {
                newValue = e.value;
              } catch (e) {
                throw RuntimeD4rtException(
                  "Error executing custom operator '+' for prefix '${operatorType == '++' ? '++' : '--'}': $e",
                );
              }
            } else {
              throw RuntimeD4rtException(
                "Cannot increment/decrement object of type '${currentValue.klass.name}': No operator '+' found.",
              );
            }
          } else {
            throw RuntimeD4rtException(
              "Cannot increment/decrement property '$propertyName' of type '${currentValue?.runtimeType}': Expected number or object with '+' operator.",
            );
          }

          // Set new value via setter or field
          final setter = targetValue.klass.findInstanceSetter(propertyName);
          if (setter != null) {
            setter.bind(targetValue).call(this, [newValue], {});
          } else {
            targetValue.set(propertyName, newValue, this);
          }

          // Return the *new* value for prefix operators
          return newValue;
        } else if (targetValue is InterpretedExtension) {
          // Handle static field/getter increment/decrement on extension (prefix)
          final extension = targetValue;

          // Get current value via static getter or field
          Object? currentValue;
          final staticGetter = extension.findStaticGetter(propertyName);
          if (staticGetter != null) {
            currentValue = staticGetter.call(this, [], {});
          } else if (extension.staticFields.containsKey(propertyName)) {
            currentValue = extension.getStaticField(propertyName);
          } else {
            throw RuntimeD4rtException(
              "Extension '${extension.name}' has no static field or getter named '$propertyName'.",
            );
          }

          // Calculate new value
          Object? newValue;
          if (currentValue is num) {
            newValue = operatorType == '++'
                ? currentValue + 1
                : currentValue - 1;
          } else {
            throw RuntimeD4rtException(
              "Cannot increment/decrement static property '$propertyName' of type '${currentValue?.runtimeType}': Expected number.",
            );
          }

          // Set new value via static setter or field
          final staticSetter = extension.findStaticSetter(propertyName);
          if (staticSetter != null) {
            staticSetter.call(this, [newValue], {});
          } else if (extension.staticFields.containsKey(propertyName)) {
            extension.setStaticField(propertyName, newValue);
          } else {
            throw RuntimeD4rtException(
              "Extension '${extension.name}' has no static setter or field named '$propertyName'.",
            );
          }

          // Return the *new* value for prefix operators
          return newValue;
        } else {
          throw RuntimeD4rtException(
            "Cannot increment/decrement property on non-instance object of type '${targetValue?.runtimeType}'.",
          );
        }
      } else if (operandNode is SIndexExpression) {
        // Handle index access like ++array[i]
        final targetValue = operandNode.target?.accept<Object?>(this);
        final indexValue = operandNode.index!.accept<Object?>(this);

        // Get current value via [] operator or direct access
        Object? currentValue;
        if (targetValue is List) {
          final index = indexValue as int;
          currentValue = targetValue[index];
        } else if (targetValue is Map) {
          currentValue = targetValue[indexValue];
        } else if (targetValue is InterpretedInstance) {
          // Use class operator [] if available
          final operatorMethod = targetValue.findOperator('[]');
          if (operatorMethod != null) {
            try {
              currentValue = operatorMethod.bind(targetValue).call(this, [
                indexValue,
              ], {});
            } on ReturnException catch (e) {
              currentValue = e.value;
            } catch (e) {
              throw RuntimeD4rtException(
                "Error executing class operator '[]' for prefix '${operatorType == '++' ? '++' : '--'}': $e",
              );
            }
          } else {
            throw RuntimeD4rtException(
              "Cannot read index for prefix increment/decrement on ${targetValue.klass.name}: No operator '[]' found.",
            );
          }
        } else {
          throw RuntimeD4rtException(
            "Cannot apply prefix '${operatorType == '++' ? '++' : '--'}' to index of type '${targetValue?.runtimeType}'.",
          );
        }

        // Calculate new value
        Object? newValue;
        if (currentValue is num) {
          newValue = operatorType == '++'
              ? currentValue + 1
              : currentValue - 1;
        } else if (currentValue is InterpretedInstance) {
          // Use custom + operator with literal 1
          final operatorMethod = currentValue.findOperator('+');
          if (operatorMethod != null) {
            try {
              final operand = _createIncrementOperand(
                currentValue,
                operatorType == '++',
              );
              newValue = operatorMethod.bind(currentValue).call(this, [
                operand,
              ], {});
            } on ReturnException catch (e) {
              newValue = e.value;
            } catch (e) {
              throw RuntimeD4rtException(
                "Error executing custom operator '+' for prefix '${operatorType == '++' ? '++' : '--'}': $e",
              );
            }
          } else {
            throw RuntimeD4rtException(
              "Cannot increment/decrement object at index of type '${currentValue.klass.name}': No operator '+' found.",
            );
          }
        } else {
          throw RuntimeD4rtException(
            "Cannot increment/decrement value at index of type '${currentValue?.runtimeType}': Expected number or object with '+' operator.",
          );
        }

        // Set new value via []= operator or direct access
        if (targetValue is List) {
          final index = indexValue as int;
          targetValue[index] = newValue;
        } else if (targetValue is Map) {
          targetValue[indexValue] = newValue;
        } else if (targetValue is InterpretedInstance) {
          // Use class operator []= if available
          final operatorMethod = targetValue.findOperator('[]=');
          if (operatorMethod != null) {
            try {
              operatorMethod.bind(targetValue).call(this, [
                indexValue,
                newValue,
              ], {});
            } on ReturnException catch (_) {
              // []= should not return a value, but assignment expression returns assigned value
            } catch (e) {
              throw RuntimeD4rtException(
                "Error executing class operator '[]=' for prefix '${operatorType == '++' ? '++' : '--'}': $e",
              );
            }
          } else {
            throw RuntimeD4rtException(
              "Cannot write index for prefix increment/decrement on ${targetValue.klass.name}: No operator '[]=' found.",
            );
          }
        }

        // Return the *new* value for prefix operators
        return newValue;
      } else {
        Logger.debug("Operand type: ${operandNode.runtimeType}");
        throw RuntimeD4rtException(
          "Operand for prefix '${operatorType == '++' ? '++' : '--'}' must be an assignable variable, property, or index.",
        );
      }
    default:
      // Check for class operators first for any other unary operators
      final String operatorLexeme = node.operator;
      if (operandValue is InterpretedInstance) {
        final operatorMethod = operandValue.findOperator(operatorLexeme);
        if (operatorMethod != null) {
          Logger.debug(
            "[PrefixExpr] Found class operator '$operatorLexeme' on ${operandValue.klass.name}. Calling...",
          );
          try {
            return operatorMethod.bind(operandValue).call(this, [], {});
          } on ReturnException catch (e) {
            return e.value;
          } catch (e) {
            throw RuntimeD4rtException(
              "Error executing class operator '$operatorLexeme': $e",
            );
          }
        }
      }

      // Check for extension operators if no class operator found
      try {
        final extensionOperator = environment.findExtensionMember(
          operandValue,
          operatorLexeme,
        );
        if (extensionOperator is ExtensionMemberCallable &&
            extensionOperator.isOperator) {
          Logger.debug(
            "[PrefixExpr] Found generic extension operator '$operatorLexeme' for type ${operandValue?.runtimeType}. Calling...",
          );
          try {
            return extensionOperator.call(this, [operandValue], {});
          } on ReturnException catch (e) {
            return e.value;
          } catch (e) {
            throw RuntimeD4rtException(
              "Error executing extension operator '$operatorLexeme': $e",
            );
          }
        }
      } on RuntimeD4rtException {
        // Fall through if no generic extension op found
      }
      throw UnimplementedD4rtException(
        'Unary prefix operator not handled: ${node.operator} ($operatorType)',
      );
  }
}