2025-08-24 09:52:34 +00:00
|
|
|
import 'package:d4rt/d4rt.dart';
|
2025-08-28 10:34:49 +00:00
|
|
|
import 'package:collection/collection.dart';
|
2026-02-01 15:16:04 +00:00
|
|
|
import 'package:d4rt_formulas/d4rt_formulas.dart';
|
2025-08-21 15:15:00 +00:00
|
|
|
|
2025-09-07 11:59:03 +00:00
|
|
|
abstract class SetUtils {
|
|
|
|
|
static Object safeGet(Map<Object?, Object?> map, String key) {
|
|
|
|
|
if (!map.containsKey(key)) {
|
|
|
|
|
throw ArgumentError("Key not found: $key -- $map");
|
|
|
|
|
}
|
|
|
|
|
return map[key] ?? "Not possible!!!";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static String stringValue(Map<Object?, Object?> map, String key) {
|
|
|
|
|
return safeGet(map, key).toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static List<Object?> listValue(Map<Object?, Object?> map, String key) {
|
|
|
|
|
return safeGet(map, key) as List<Object?>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Number numberValue(Map<Object?, Object?> map, String key) {
|
|
|
|
|
return double.parse(stringValue(map, key));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-09 16:34:20 +00:00
|
|
|
/// Parses a d4rt array literal (containing maps and arrays) to a List<Object?>
|
|
|
|
|
/// using d4rt
|
|
|
|
|
List<Object?> parseD4rtLiteral(String arrayStringLiteral) {
|
|
|
|
|
var d4rt = D4rt();
|
|
|
|
|
final buffer = StringBuffer();
|
|
|
|
|
buffer.write("main(){ return $arrayStringLiteral; }");
|
|
|
|
|
final code = buffer.toString();
|
|
|
|
|
|
|
|
|
|
final List<Object?> list = d4rt.execute(source: code);
|
|
|
|
|
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parses corpus elements from an array string literal.
|
|
|
|
|
/// Determines if each element is a formula or a unit and converts accordingly.
|
|
|
|
|
List<Object> parseCorpusElements(String arrayStringLiteral) {
|
|
|
|
|
final List<Object?> elements = parseD4rtLiteral(arrayStringLiteral);
|
|
|
|
|
|
|
|
|
|
final List<Object> result = [];
|
|
|
|
|
for (final element in elements) {
|
|
|
|
|
if (element is Map<Object?, Object?>) {
|
|
|
|
|
// Check if it's a formula by looking for required formula properties
|
|
|
|
|
// Formulas typically have 'd4rtCode' and 'input'/'output' properties
|
|
|
|
|
if (element.containsKey('d4rtCode')) {
|
|
|
|
|
result.add(Formula.fromSet(element));
|
|
|
|
|
}
|
|
|
|
|
// Units typically have 'name', 'symbol', and 'baseUnit' properties
|
|
|
|
|
else if (element.containsKey('name') && element.containsKey('symbol')) {
|
|
|
|
|
result.add(UnitSpec.fromSet(element));
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
throw ArgumentError('Unknown element type: $element');
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
throw ArgumentError('Element must be a Map: $element');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-07 11:59:03 +00:00
|
|
|
typedef Number = double;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UnitSpec {
|
|
|
|
|
final String name;
|
|
|
|
|
final String baseUnit;
|
|
|
|
|
final String symbol;
|
|
|
|
|
final Number? factorFromUnitToBase;
|
|
|
|
|
final String? codeFromUnitToBase;
|
|
|
|
|
final String? codeFromBaseToUnit;
|
|
|
|
|
|
|
|
|
|
UnitSpec({
|
|
|
|
|
required this.name,
|
|
|
|
|
required this.baseUnit,
|
|
|
|
|
required this.symbol,
|
|
|
|
|
this.factorFromUnitToBase,
|
|
|
|
|
this.codeFromBaseToUnit,
|
|
|
|
|
this.codeFromUnitToBase,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
factory UnitSpec.fromSet(Map<Object?, Object?> theSet) {
|
|
|
|
|
String name = SetUtils.stringValue(theSet, "name");
|
|
|
|
|
String symbol = SetUtils.stringValue(theSet, "symbol");
|
|
|
|
|
|
|
|
|
|
if( theSet.containsKey("isBase") ){
|
2025-09-07 12:04:42 +00:00
|
|
|
return UnitSpec(name: name, baseUnit: name, symbol: symbol, factorFromUnitToBase: 1);
|
2025-09-07 11:59:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String baseUnit = SetUtils.stringValue(theSet, "baseUnit");
|
|
|
|
|
|
|
|
|
|
if (theSet.containsKey("factor")) {
|
|
|
|
|
Number factorFromUnitToBase = SetUtils.numberValue(theSet, "factor");
|
|
|
|
|
return UnitSpec(
|
|
|
|
|
name: name,
|
|
|
|
|
baseUnit: baseUnit,
|
|
|
|
|
symbol: symbol,
|
|
|
|
|
factorFromUnitToBase: factorFromUnitToBase,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
else if( theSet.containsKey("toBase")) {
|
|
|
|
|
String codeFromBaseToUnit = SetUtils.stringValue(
|
|
|
|
|
theSet,
|
|
|
|
|
"fromBase",
|
|
|
|
|
);
|
|
|
|
|
String codeFromUnitToBase = SetUtils.stringValue(
|
|
|
|
|
theSet,
|
|
|
|
|
"toBase",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return UnitSpec(name: name,
|
|
|
|
|
baseUnit: baseUnit,
|
|
|
|
|
symbol: symbol,
|
|
|
|
|
codeFromBaseToUnit: codeFromBaseToUnit,
|
|
|
|
|
codeFromUnitToBase: codeFromUnitToBase);
|
|
|
|
|
}
|
|
|
|
|
else{
|
|
|
|
|
throw ArgumentError( "Need factor or toBase/fromBase");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static List<UnitSpec> fromArrayStringLiteral(String arrayStringLiteral) {
|
2026-02-09 16:34:20 +00:00
|
|
|
final List<Object?> list = parseD4rtLiteral(arrayStringLiteral);
|
2025-09-07 11:59:03 +00:00
|
|
|
|
|
|
|
|
final units = list.map((set) => UnitSpec.fromSet(set as Map));
|
|
|
|
|
|
|
|
|
|
return units.toList(growable: false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-24 09:52:34 +00:00
|
|
|
class VariableSpec {
|
|
|
|
|
final String name;
|
2025-11-09 19:29:58 +00:00
|
|
|
final String? unit;
|
2026-01-21 07:49:56 +00:00
|
|
|
final List<dynamic>? values;
|
2025-08-21 15:15:00 +00:00
|
|
|
|
2026-01-21 07:49:56 +00:00
|
|
|
VariableSpec({required this.name, this.unit, this.values}){
|
2026-02-01 15:16:04 +00:00
|
|
|
validate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void validate(){
|
|
|
|
|
if( FormulaEvaluator.reservedVariableNames.contains(name) ){
|
|
|
|
|
throw ArgumentError("$name: is a reserved variable name for FormulaEvaluator");
|
|
|
|
|
}
|
2026-01-21 07:49:56 +00:00
|
|
|
final valuesValid = values != null && values?.isNotEmpty == true;
|
2025-11-09 19:29:58 +00:00
|
|
|
if( unit == null && !valuesValid ){
|
2026-01-21 07:49:56 +00:00
|
|
|
throw ArgumentError("$name: at least unit or allowedValues should be valid");
|
2025-11-09 19:29:58 +00:00
|
|
|
}
|
|
|
|
|
}
|
2025-08-21 15:15:00 +00:00
|
|
|
|
|
|
|
|
@override
|
2026-01-21 07:49:56 +00:00
|
|
|
String toString() => 'var($name: $unit${values != null ? ' allowed: $values' : ''})';
|
2025-08-21 15:15:00 +00:00
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
bool operator ==(Object other) =>
|
|
|
|
|
identical(this, other) ||
|
|
|
|
|
other is VariableSpec &&
|
|
|
|
|
runtimeType == other.runtimeType &&
|
2025-09-15 19:42:15 +00:00
|
|
|
unit == other.unit &&
|
2025-11-09 19:29:58 +00:00
|
|
|
name == other.name &&
|
2026-01-21 07:49:56 +00:00
|
|
|
const DeepCollectionEquality().equals(values, other.values);
|
2025-08-21 15:15:00 +00:00
|
|
|
|
|
|
|
|
@override
|
2026-01-21 07:49:56 +00:00
|
|
|
int get hashCode => Object.hash(unit, name, values != null ? const DeepCollectionEquality().hash(values!) : 0);
|
2025-08-21 15:15:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class Formula {
|
|
|
|
|
final String name;
|
2025-09-15 19:58:11 +00:00
|
|
|
final String? description;
|
2025-08-24 09:52:34 +00:00
|
|
|
final List<VariableSpec> input;
|
|
|
|
|
final VariableSpec output;
|
2025-08-21 15:15:00 +00:00
|
|
|
final String d4rtCode;
|
2025-09-16 16:22:29 +00:00
|
|
|
final List<String> tags;
|
2025-08-21 15:15:00 +00:00
|
|
|
|
2025-08-22 15:47:06 +00:00
|
|
|
Formula({
|
2025-08-21 15:15:00 +00:00
|
|
|
required this.name,
|
2025-09-15 19:58:11 +00:00
|
|
|
this.description,
|
2025-08-21 15:15:00 +00:00
|
|
|
required this.input,
|
|
|
|
|
required this.output,
|
|
|
|
|
required this.d4rtCode,
|
2025-09-16 16:22:29 +00:00
|
|
|
this.tags = const [],
|
2025-08-26 14:37:28 +00:00
|
|
|
}) {
|
2025-08-22 15:47:06 +00:00
|
|
|
validate();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 07:49:56 +00:00
|
|
|
void validate() {
|
2025-08-22 15:47:06 +00:00
|
|
|
if (name.trim().isEmpty) {
|
|
|
|
|
throw ArgumentError('Formula name cannot be empty');
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-21 15:15:00 +00:00
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
String toString() =>
|
2025-09-16 16:22:29 +00:00
|
|
|
'Formula(name: $name, description: $description, input: $input, output: $output, d4rtCode: $d4rtCode, tags: $tags)';
|
2025-08-21 15:15:00 +00:00
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
bool operator ==(Object other) =>
|
|
|
|
|
identical(this, other) ||
|
|
|
|
|
other is Formula &&
|
|
|
|
|
runtimeType == other.runtimeType &&
|
|
|
|
|
name == other.name &&
|
2025-09-15 19:58:11 +00:00
|
|
|
description == other.description &&
|
2025-08-24 09:52:34 +00:00
|
|
|
output == other.output &&
|
|
|
|
|
ListEquality().equals(input, other.input) &&
|
2025-09-16 16:22:29 +00:00
|
|
|
d4rtCode == other.d4rtCode &&
|
|
|
|
|
ListEquality().equals(tags, other.tags);
|
2025-08-21 15:15:00 +00:00
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
int get hashCode =>
|
2025-09-16 16:22:29 +00:00
|
|
|
Object.hash(name, description, ListEquality().hash(input), output, d4rtCode, ListEquality().hash(tags));
|
2025-08-21 15:15:00 +00:00
|
|
|
|
2025-08-26 14:37:28 +00:00
|
|
|
List<String> inputVarNames() =>
|
|
|
|
|
input.map((v) => v.name).toList(growable: false);
|
2025-08-24 10:33:21 +00:00
|
|
|
|
2025-09-07 11:59:03 +00:00
|
|
|
factory Formula.fromStringLiteral(String setStringLiteral) {
|
2025-08-26 14:54:35 +00:00
|
|
|
var d4rt = D4rt();
|
|
|
|
|
final buffer = StringBuffer();
|
2025-09-07 11:59:03 +00:00
|
|
|
buffer.write("main(){ return $setStringLiteral; }");
|
2025-08-26 14:54:35 +00:00
|
|
|
final code = buffer.toString();
|
|
|
|
|
|
|
|
|
|
final Map<Object?, Object?> setLiteral = d4rt.execute(source: code);
|
|
|
|
|
|
|
|
|
|
return Formula.fromSet(setLiteral);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-07 11:59:03 +00:00
|
|
|
static List<Formula> fromArrayStringLiteral(String arrayStringLiteral) {
|
2026-02-09 16:34:20 +00:00
|
|
|
final List<Object?> list = parseD4rtLiteral(arrayStringLiteral);
|
2025-08-26 15:17:42 +00:00
|
|
|
|
2025-09-07 11:59:03 +00:00
|
|
|
final formulas = list.map((set) => Formula.fromSet(set as Map));
|
2025-08-26 15:17:42 +00:00
|
|
|
|
2025-09-05 16:53:06 +00:00
|
|
|
return formulas.toList(growable: false);
|
2025-08-26 15:17:42 +00:00
|
|
|
}
|
|
|
|
|
|
2025-08-26 14:37:28 +00:00
|
|
|
factory Formula.fromSet(Map<Object?, Object?> theSet) {
|
|
|
|
|
VariableSpec parseVar(Map<Object?, Object?> varSpec) {
|
2025-09-07 11:59:03 +00:00
|
|
|
String name = SetUtils.stringValue(varSpec, "name");
|
2025-11-09 19:29:58 +00:00
|
|
|
String? unit;
|
|
|
|
|
if (varSpec.containsKey("unit")) {
|
|
|
|
|
unit = SetUtils.stringValue(varSpec, "unit");
|
|
|
|
|
}
|
|
|
|
|
final allowed = varSpec['values'] as List<dynamic>?;
|
|
|
|
|
if (allowed != null) {
|
|
|
|
|
final types = allowed.map((v) => v.runtimeType).toSet();
|
|
|
|
|
if (types.length > 1) {
|
|
|
|
|
throw ArgumentError('Allowed values must be all Strings or all Numbers');
|
|
|
|
|
}
|
|
|
|
|
if (!types.contains(String) && !types.contains(double) && !types.contains(int)) {
|
|
|
|
|
throw ArgumentError('Allowed values must be Strings or Numbers');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return VariableSpec(
|
|
|
|
|
name: name,
|
|
|
|
|
unit: unit,
|
2026-01-21 07:49:56 +00:00
|
|
|
values: allowed?.toList(growable: false),
|
2025-11-09 19:29:58 +00:00
|
|
|
);
|
2025-08-26 14:37:28 +00:00
|
|
|
}
|
|
|
|
|
|
2025-09-07 11:59:03 +00:00
|
|
|
String name = SetUtils.stringValue(theSet, "name");
|
2025-09-15 19:58:11 +00:00
|
|
|
String? description = theSet ["description"] as String?;
|
2025-09-16 16:22:29 +00:00
|
|
|
List<String> tags = (theSet["tags"] as List<Object?>? ?? []).map((t) => t.toString()).toList();
|
2025-09-07 11:59:03 +00:00
|
|
|
final List<Object?> inputSet = SetUtils.listValue(theSet, "input");
|
|
|
|
|
List<VariableSpec> input = inputSet
|
|
|
|
|
.map((v) => parseVar(v as Map))
|
|
|
|
|
.toList(growable: false);
|
2025-08-26 14:37:28 +00:00
|
|
|
Map<Object?, Object?> outputSet = theSet.get("output");
|
|
|
|
|
VariableSpec output = parseVar(outputSet);
|
2025-09-07 11:59:03 +00:00
|
|
|
String d4rtCode = SetUtils.stringValue(theSet, "d4rtCode");
|
2025-08-26 14:37:28 +00:00
|
|
|
|
2025-09-05 16:53:06 +00:00
|
|
|
return Formula(
|
2025-08-26 14:37:28 +00:00
|
|
|
name: name,
|
2025-09-15 19:58:11 +00:00
|
|
|
description: description,
|
2025-10-05 14:53:46 +00:00
|
|
|
tags: tags,
|
2025-08-26 14:37:28 +00:00
|
|
|
input: input,
|
|
|
|
|
output: output,
|
|
|
|
|
d4rtCode: d4rtCode,
|
|
|
|
|
);
|
2025-08-24 10:33:21 +00:00
|
|
|
}
|
2025-08-21 15:15:00 +00:00
|
|
|
}
|