visitPrefixExpression method
Visit a SPrefixExpression.
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)',
);
}
}