visitPrefixExpression method
Object?
visitPrefixExpression(
- 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)');
}
}