visitPrefixExpression method

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

Implementation

@override
Object? visitPrefixExpression(PrefixExpression node) {
  final operatorType = node.operator.type;
  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 TokenType.BANG: // 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 TokenType.MINUS: // 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
      } else if (operandValue is InterpretedExtensionTypeInstance) {
        // DFUB9: unary operator `-` declared on an extension type.
        final operatorMethod = operandValue.extensionType.methods['-'];
        if (operatorMethod != null) {
          Logger.debug(
              "[PrefixExpr] Found extension-type operator '-' on ${operandValue.extensionType.name}. Calling...");
          try {
            return operatorMethod.bind(operandValue).call(this, [], {});
          } on ReturnException catch (e) {
            return e.value;
          } catch (e) {
            throw RuntimeD4rtException(
                "Error executing extension-type operator '-': $e");
          }
        }
      }

      // 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 TokenType.TILDE: // 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
      } else if (operandValue is InterpretedExtensionTypeInstance) {
        // DFUB9: unary operator `~` declared on an extension type.
        final operatorMethod = operandValue.extensionType.methods['~'];
        if (operatorMethod != null) {
          Logger.debug(
              "[PrefixExpr] Found extension-type operator '~' on ${operandValue.extensionType.name}. Calling...");
          try {
            return operatorMethod.bind(operandValue).call(this, [], {});
          } on ReturnException catch (e) {
            return e.value;
          } catch (e) {
            throw RuntimeD4rtException(
                "Error executing extension-type operator '~': $e");
          }
        }
      }

      // Bridged enum unary operator `~` (e.g. `~WidgetState.disabled`, where
      // WidgetState is bridged as an enum with a `~` operator adapter).
      if (operandValue is BridgedEnumValue && operandValue.hasMethod('~')) {
        return operandValue.invoke(this, '~', const [], const {});
      }

      // 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 TokenType.PLUS_PLUS: // Prefix increment (++x)
    case TokenType.MINUS_MINUS: // 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 (SimpleIdentifier or PropertyAccess for now)
      if (operandNode is SimpleIdentifier) {
        final variableName = operandNode.name;
        // We need the current value (already got it as assignOperand)
        final currentValue = assignOperand;

        if (currentValue is num) {
          final newValue = operatorType == TokenType.PLUS_PLUS
              ? 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 == TokenType.PLUS_PLUS);
              // 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 == TokenType.PLUS_PLUS ? '++' : '--'}': $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 == TokenType.PLUS_PLUS ? '++' : '--'}' must be a number, but was ${operandValue?.runtimeType}. Extension support TBD.");
        }
      } else if (operandNode is PropertyAccess) {
        // 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 == TokenType.PLUS_PLUS
                ? 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 == TokenType.PLUS_PLUS ? 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 == TokenType.PLUS_PLUS ? '++' : '--'}': $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 PrefixedIdentifier) {
        // Handle prefixed identifier like obj.field++ (parsed as PrefixedIdentifier)
        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 == TokenType.PLUS_PLUS
                ? 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 == TokenType.PLUS_PLUS ? 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 == TokenType.PLUS_PLUS ? '++' : '--'}': $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 == TokenType.PLUS_PLUS
                ? 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 IndexExpression) {
        // 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 == TokenType.PLUS_PLUS ? '++' : '--'}': $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 == TokenType.PLUS_PLUS ? '++' : '--'}' to index of type '${targetValue?.runtimeType}'.");
        }

        // Calculate new value
        Object? newValue;
        if (currentValue is num) {
          newValue = operatorType == TokenType.PLUS_PLUS
              ? 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 == TokenType.PLUS_PLUS);
              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 == TokenType.PLUS_PLUS ? '++' : '--'}': $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 == TokenType.PLUS_PLUS ? '++' : '--'}': $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 == TokenType.PLUS_PLUS ? '++' : '--'}' must be an assignable variable, property, or index.");
      }
    default:
      // Check for class operators first for any other unary operators
      final String operatorLexeme = node.operator.lexeme;
      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.lexeme} ($operatorType)');
  }
}