d4t_formulas/lib/formula_evaluator.dart

244 lines
6.8 KiB
Dart
Raw Normal View History

import 'dart:math' as Math;
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)' : ''}';
}
class MyMath{
static Number myLog(Number x) => Math.log(x);
static Number myPow(Number b, Number e) => Math.pow(b,e) as Number;
}
class FormulaResult{
const FormulaResult();
}
class StringResult extends FormulaResult{
final String value;
const StringResult(this.value);
}
class NumberResult extends FormulaResult{
final Number value;
const NumberResult(this.value);
}
class FormulaEvaluator {
final D4rt _interpreter;
static D4rt createDefaultInterpreter() => D4rt();
FormulaEvaluator([D4rt? interpreter]) : _interpreter = interpreter ?? createDefaultInterpreter(){
prepareInterpreter(_interpreter);
}
static Number _getNumberValueOf(String s){
return double.parse(s);
}
static void prepareInterpreter(D4rt interpreter){
final myMathDefinition = BridgedClass(
nativeType: MyMath,
name: 'MyMath',
staticMethods: {
'myPow': (visitor, positionalArgs, namedArgs) {
final Number base = _getNumberValueOf( positionalArgs[0].toString() );
final Number exp = _getNumberValueOf( positionalArgs[1].toString() );
return MyMath.myPow(base,exp);
},
'myLog': (visitor, positionalArgs, namedArgs) {
final Number x = _getNumberValueOf( positionalArgs[0].toString() );
return MyMath.myLog(x);
2025-09-22 15:00:34 +00:00
},
}
);
2025-09-22 15:00:34 +00:00
interpreter.registerBridgedClass(myMathDefinition, "package:d4rt_formulas.dart");
}
static FormulaResult evaluateExpression(String code, [D4rt? interpreter]) {
final d4rtInterpreter = interpreter ?? createDefaultInterpreter();
prepareInterpreter(d4rtInterpreter);
final d4rtCode = """
$d4rtImports
main()
{
late var result;
result = $code;
return result;
}""";
print("evaluateExpression:\n$d4rtCode");
final result = d4rtInterpreter.execute(source: d4rtCode);
switch ( result ){
case int value:
return NumberResult(value.toDouble());
case Number value:
return NumberResult(value);
case String value:
return StringResult(value);
default:
throw FormulaEvaluationException( "Unexpected result type: ${result.runtimeType} -- $result" );
}
}
dynamic evaluate(Formula formula, Map<String, dynamic> inputValues) {
_validateInputValues(formula, inputValues);
2025-09-22 15:00:34 +00:00
final completeSource = _buildCompleteSource(formula, inputValues);
2026-01-25 18:03:57 +00:00
try {
final result = _interpreter.execute(source: completeSource);
return result;
}
catch (e, stack) {
2026-01-25 18:03:57 +00:00
print( "Error evaluating formula source:\n$completeSource" );
print( stack );
2026-01-25 18:03:57 +00:00
throw FormulaEvaluationException(
'Error evaluating formula "${formula.name}": $e',
e,
);
}
}
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()) {
if (!inputValues.containsKey(inputVar)) {
missingVars.add(inputVar);
}
}
if (missingVars.isNotEmpty) {
throw FormulaEvaluationException(
'Missing required input variables for formula "${formula.name}": '
'${missingVars.join(', ')}',
);
}
}
List<String> getInputVariableOrder(Formula formula) {
2025-08-24 09:52:34 +00:00
return formula.inputVarNames()..sort();
}
static final String d4rtImports = """
import 'dart:math';
import "package:d4rt_formulas.dart";
""";
2026-02-01 15:16:04 +00:00
static const reservedVariableNames = { "variableValues", "indexOf", "variableAllowedValues"} ;
String _buildCompleteSource(Formula formula, Map<String, dynamic> inputValues) {
final buffer = StringBuffer();
2025-09-22 15:00:34 +00:00
buffer.writeln("""
$d4rtImports
2025-09-22 15:00:34 +00:00
main()
{
"""
);
for (final entry in inputValues.entries) {
final varName = entry.key;
final value = entry.value;
if (value is String) {
final escapedValue = value.replaceAll('"', '\\"');
2025-09-22 15:00:34 +00:00
buffer.writeln("""
final $varName = "$escapedValue";
""");
} else {
2025-09-22 15:00:34 +00:00
buffer.writeln("""
final $varName = $value;
""");
}
}
buffer.writeln("""
final variableValues = <String, dynamic>{
""");
for (final entry in inputValues.entries) {
final varName = entry.key;
final value = entry.value;
if (value is String) {
final escapedValue = value.replaceAll('"', '\\"');
buffer.writeln("""
"$varName": "$escapedValue",
""");
} else {
buffer.writeln("""
"$varName": "$value",
""");
}
}
buffer.writeln("""
};
""");
// Build a Map<String, List<String>> named `variableValues` that exposes allowed values
// for each VariableSpec (inputs and output) to the interpreted code. Values are
// converted to strings and quoted in the produced d4rt source.
final variableValuesMap = <String, List<String>>{};
// Include input VariableSpecs when they have allowed values
for (final vs in formula.input) {
final values = vs.values;
if (values != null && values.isNotEmpty) {
variableValuesMap[vs.name] = values.map((v) => v.toString()).toList(growable: false);
}
}
// Explicitly include the output VariableSpec if it has allowed values
final outValues = formula.output.values;
if (outValues != null && outValues.isNotEmpty) {
variableValuesMap[formula.output.name] = outValues.map((v) => v.toString()).toList(growable: false);
}
// Write the variableValues map into the generated source without escaping names/values
buffer.writeln("final variableAllowedValues = {");
variableValuesMap.forEach((name, list) {
final listLiteral = list.map((s) => '"' + s + '"').join(', ');
buffer.writeln(' "' + name + '": [' + listLiteral + '],');
});
buffer.writeln('};');
// Some functions to deal with string values
buffer.writeln("""
2026-02-01 15:16:04 +00:00
// If return type is int, there is an error converting double to int 🤷‍
dynamic indexOf(String inputName) {
String value = variableValues[inputName];
String allowedValues = variableAllowedValues[inputName];
2026-02-01 15:16:04 +00:00
dynamic ret = allowedValues.indexOf(value) as int;
return ret as int;
}
""");
2025-09-22 15:00:34 +00:00
buffer.writeln("""
2025-11-09 19:29:58 +00:00
late var ${formula.output.name};
2025-09-22 15:00:34 +00:00
${formula.d4rtCode}
2025-11-09 19:29:58 +00:00
return ${formula.output.name};
2025-09-22 15:00:34 +00:00
}
""");
return buffer.toString();
}
}