2025-08-21 16:35:50 +00:00
|
|
|
|
|
|
|
|
import 'package:d4rt/d4rt.dart';
|
|
|
|
|
import 'formula_models.dart';
|
|
|
|
|
|
|
|
|
|
/// Exception thrown when formula evaluation fails
|
|
|
|
|
class FormulaEvaluationException implements Exception {
|
|
|
|
|
final String message;
|
|
|
|
|
final Object? cause;
|
|
|
|
|
|
|
|
|
|
const FormulaEvaluationException(this.message, [this.cause]);
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
String toString() => 'FormulaEvaluationException: $message'
|
|
|
|
|
'${cause != null ? ' (caused by: $cause)' : ''}';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Evaluates formulas using the d4rt interpreter
|
|
|
|
|
class FormulaEvaluator {
|
|
|
|
|
final D4rt _interpreter;
|
|
|
|
|
|
|
|
|
|
/// Creates a new formula evaluator with an optional d4rt interpreter instance.
|
|
|
|
|
/// If no interpreter is provided, a new one will be created.
|
|
|
|
|
FormulaEvaluator([D4rt? interpreter]) : _interpreter = interpreter ?? D4rt();
|
|
|
|
|
|
|
|
|
|
/// Evaluates a formula with the given input values.
|
|
|
|
|
///
|
2025-08-22 15:47:06 +00:00
|
|
|
/// The [formula] must have exactly one output variable (validated during construction).
|
2025-08-21 16:35:50 +00:00
|
|
|
/// The [inputValues] map must contain values for all input variables defined
|
|
|
|
|
/// in the formula.
|
|
|
|
|
///
|
|
|
|
|
/// The formula's d4rt_code should define a main function that uses the input
|
|
|
|
|
/// variable names directly. The evaluator will inject variable declarations
|
|
|
|
|
/// before the formula code. For example:
|
|
|
|
|
/// ```
|
|
|
|
|
/// main() {
|
|
|
|
|
/// return m * a; // Returns Force = mass * acceleration
|
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
|
|
|
|
///
|
|
|
|
|
/// Returns the computed value of the single output variable.
|
|
|
|
|
///
|
|
|
|
|
/// Throws [FormulaEvaluationException] if:
|
|
|
|
|
/// - Required input variables are missing
|
|
|
|
|
/// - The d4rt code execution fails
|
|
|
|
|
dynamic evaluate(Formula formula, Map<String, dynamic> inputValues) {
|
|
|
|
|
_validateInputValues(formula, inputValues);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Build the complete d4rt source code with variable declarations
|
|
|
|
|
final completeSource = _buildCompleteSource(formula, inputValues);
|
|
|
|
|
|
|
|
|
|
// Execute the code using d4rt (no args needed since variables are in source)
|
|
|
|
|
final result = _interpreter.execute(source: completeSource);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
throw FormulaEvaluationException(
|
|
|
|
|
'Failed to execute formula "${formula.name}"',
|
|
|
|
|
e,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Validates that all required input variables are provided
|
|
|
|
|
void _validateInputValues(Formula formula, Map<String, dynamic> inputValues) {
|
|
|
|
|
final missingVars = <String>[];
|
|
|
|
|
|
2025-08-24 09:52:34 +00:00
|
|
|
for (final inputVar in formula.inputVarNames()) {
|
2025-08-21 16:35:50 +00:00
|
|
|
if (!inputValues.containsKey(inputVar)) {
|
|
|
|
|
missingVars.add(inputVar);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (missingVars.isNotEmpty) {
|
|
|
|
|
throw FormulaEvaluationException(
|
|
|
|
|
'Missing required input variables for formula "${formula.name}": '
|
|
|
|
|
'${missingVars.join(', ')}',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gets the name of the single output variable from the formula
|
|
|
|
|
String getOutputVariableName(Formula formula) {
|
2025-08-24 09:52:34 +00:00
|
|
|
return formula.output.name;
|
2025-08-21 16:35:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gets the magnitude of the single output variable from the formula
|
|
|
|
|
String getOutputVariableMagnitude(Formula formula) {
|
2025-08-22 15:47:06 +00:00
|
|
|
// Formula construction already ensures exactly one output variable
|
2025-08-24 09:52:34 +00:00
|
|
|
return formula.output.magnitude;
|
2025-08-21 16:35:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gets the ordered list of input variable names (alphabetically sorted)
|
|
|
|
|
List<String> getInputVariableOrder(Formula formula) {
|
2025-08-24 09:52:34 +00:00
|
|
|
return formula.inputVarNames()..sort();
|
2025-08-21 16:35:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Builds the complete d4rt source code by injecting variable declarations
|
|
|
|
|
/// before the formula's d4rt code
|
|
|
|
|
String _buildCompleteSource(Formula formula, Map<String, dynamic> inputValues) {
|
|
|
|
|
final buffer = StringBuffer();
|
|
|
|
|
|
|
|
|
|
// Add variable declarations for all input variables
|
|
|
|
|
for (final entry in inputValues.entries) {
|
|
|
|
|
final varName = entry.key;
|
|
|
|
|
final value = entry.value;
|
|
|
|
|
|
|
|
|
|
// Handle different value types appropriately for d4rt
|
|
|
|
|
if (value is String) {
|
|
|
|
|
// Escape quotes in string values
|
|
|
|
|
final escapedValue = value.replaceAll('"', '\\"');
|
|
|
|
|
buffer.writeln('var $varName = "$escapedValue";');
|
|
|
|
|
} else {
|
|
|
|
|
// For numbers and other types, use direct representation
|
|
|
|
|
buffer.writeln('var $varName = $value;');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add a blank line for readability
|
|
|
|
|
buffer.writeln();
|
|
|
|
|
|
|
|
|
|
// Add the formula's d4rt code
|
|
|
|
|
buffer.write(formula.d4rtCode);
|
|
|
|
|
|
|
|
|
|
return buffer.toString();
|
|
|
|
|
}
|
|
|
|
|
}
|