d4t_formulas/lib/formula_models.dart

430 lines
13 KiB
Dart
Raw Normal View History

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';
2026-03-01 12:51:14 +00:00
typedef Number = double;
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-03-01 12:51:14 +00:00
/// Parses a d4rt array literal (containing maps and arrays) to a List<Object?>
/// using d4rt
static List<Object?> parseD4rtLiteral(String arrayStringLiteral) {
var d4rt = D4rt();
final buffer = StringBuffer();
buffer.write("main(){ return $arrayStringLiteral; }");
final code = buffer.toString();
2026-02-09 16:34:20 +00:00
2026-03-01 12:51:14 +00:00
final List<Object?> list = d4rt.execute(source: code);
2026-02-09 16:34:20 +00:00
2026-03-01 12:51:14 +00:00
return list;
}
2026-02-09 16:34:20 +00:00
2026-03-01 12:51:14 +00:00
/// Escapes special characters in a string for use in D4RT literals
2026-03-04 18:09:48 +00:00
@deprecated
2026-03-01 12:51:14 +00:00
static String escapeD4rtString(String input) {
return input
.replaceAll(r'\\', r'\\\\') // escape backslashes first
.replaceAll('\n', r'\\n')
.replaceAll('\r', r'\\r')
.replaceAll('\t', r'\\t')
.replaceAll('"', r'\\"');
}
2026-03-01 12:51:14 +00:00
/// Parses corpus elements from an array string literal.
/// Determines if each element is a formula or a unit and converts accordingly.
static List<FormulaElement> parseCorpusElements(String arrayStringLiteral) {
final List<Object?> elements = parseD4rtLiteral(arrayStringLiteral);
final List<FormulaElement> result = [];
for (final element in elements) {
if (element is Map<Object?, Object?>) {
if (element.containsKey('d4rtCode')) {
result.add(Formula.fromSet(element));
} 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');
2026-02-09 16:34:20 +00:00
}
}
2026-02-11 08:07:13 +00:00
2026-03-01 12:51:14 +00:00
return result;
}
2026-03-04 18:09:48 +00:00
/// Pretty prints a dynamic value (Set, Array, string or number) as a Dart literal.
/// Uses JSON-like formatting but for Dart language, with proper indentation.
static String prettyPrint(dynamic value, {int indent = 0}) {
if (value is String) {
return _prettyPrintString(value, indent);
} else if (value is num) {
return _prettyPrintNumber(value, indent);
} else if (value is Set) {
return _prettyPrintSet(value, indent);
} else if (value is List) {
return _prettyPrintArray(value, indent);
} else if (value is Map) {
return _prettyPrintMap(value, indent);
} else {
return value.toString();
}
}
/// Pretty prints a simple string, escaping special characters if needed.
static String _prettyPrintString(String s, int indent) {
// Check if the string needs raw string formatting (newlines, $, backslashes, quotes)
final needsRawString = s.contains('\n') ||
s.contains(r'$') ||
s.contains(r'\') ||
s.contains('"');
if (needsRawString) {
return _prettyPrintRawString(s, indent);
}
// Simple string with escaped quotes
return '"${s.replaceAll('"', r'\"')}"';
//'
}
/// Pretty prints a number.
static String _prettyPrintNumber(num n, int indent) {
return n.toString();
}
/// Pretty prints a Set as a Dart set literal.
static String _prettyPrintSet(Set s, int indent) {
if (s.isEmpty) {
return '{}';
}
final indentStr = ' ' * indent;
final innerIndent = ' ' * (indent + 1);
final elements = s.map((e) => '$innerIndent${prettyPrint(e, indent: indent + 1)}').join(',\n');
return '{$elements\n$indentStr}';
}
/// Pretty prints an Array/List as a Dart list literal.
static String _prettyPrintArray(List a, int indent) {
if (a.isEmpty) {
return '[]';
}
final indentStr = ' ' * indent;
final innerIndent = ' ' * (indent + 1);
final elements = a.map((e) => '$innerIndent${prettyPrint(e, indent: indent + 1)}').join(',\n');
return '[\n$elements\n$indentStr]';
}
/// Pretty prints a Map as a Dart map literal.
static String _prettyPrintMap(Map m, int indent) {
if (m.isEmpty) {
return '{}';
}
final indentStr = ' ' * indent;
final innerIndent = ' ' * (indent + 1);
final entries = m.entries.map((e) {
final key = prettyPrint(e.key, indent: indent + 1);
final value = prettyPrint(e.value, indent: indent + 1);
return '$innerIndent$key: $value';
}).join(',\n');
return '{\n$entries\n$indentStr}';
}
/// Pretty prints a raw string (for strings containing newlines, $, backslashes, etc.)
/// Uses Dart's raw string syntax r"""..."""
static String _prettyPrintRawString(String s, int indent) {
// Escape triple quotes by replacing """ with ""\"
final escaped = s.replaceAll('"""', r'""\"');
return 'r"""$escaped"""';
}
2026-02-09 16:34:20 +00:00
}
2025-09-07 11:59:03 +00:00
2026-02-11 08:07:13 +00:00
/// Abstract base class for formula elements
2026-02-11 08:19:18 +00:00
abstract class FormulaElement {
2026-03-04 18:09:48 +00:00
Map<String,dynamic> toMap();
String toStringLiteral() {
final map = toMap();
return SetUtils.prettyPrint(map);
}
2026-02-11 08:19:18 +00:00
}
2025-09-07 11:59:03 +00:00
2026-03-04 18:09:48 +00:00
class UnitSpec extends FormulaElement {
2025-09-07 11:59:03 +00:00
final String name;
final String baseUnit;
final String symbol;
final Number? factorFromUnitToBase;
final String? codeFromUnitToBase;
final String? codeFromBaseToUnit;
2026-03-04 18:09:48 +00:00
@override
Map<String, dynamic> toMap() {
return {
"name": name,
"baseUnit": baseUnit,
"symbol": symbol,
if (factorFromUnitToBase != null) 'factor': factorFromUnitToBase,
if (codeFromUnitToBase != null) 'toBase': codeFromUnitToBase,
if (codeFromBaseToUnit != null) 'fromBase': codeFromBaseToUnit,
};
}
2025-09-07 11:59:03 +00:00
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");
2026-03-01 12:51:14 +00:00
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,
);
2026-03-01 12:51:14 +00:00
} else if (theSet.containsKey("toBase")) {
String codeFromBaseToUnit = SetUtils.stringValue(theSet, "fromBase");
String codeFromUnitToBase = SetUtils.stringValue(theSet, "toBase");
2025-09-07 11:59:03 +00:00
2026-03-01 12:51:14 +00:00
return UnitSpec(
name: name,
baseUnit: baseUnit,
symbol: symbol,
codeFromBaseToUnit: codeFromBaseToUnit,
codeFromUnitToBase: codeFromUnitToBase,
);
} else {
throw ArgumentError("Need factor or toBase/fromBase");
2025-09-07 11:59:03 +00:00
}
}
static List<UnitSpec> fromArrayStringLiteral(String arrayStringLiteral) {
2026-03-01 12:51:14 +00:00
final List<Object?> list = SetUtils.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);
}
}
2026-03-04 18:09:48 +00:00
class VariableSpec extends FormulaElement{
2025-08-24 09:52:34 +00:00
final String name;
2025-11-09 19:29:58 +00:00
final String? unit;
final List<dynamic>? values;
2026-03-04 18:09:48 +00:00
@override
Map<String, dynamic> toMap() {
return {
'name': name,
if (unit != null) 'unit': unit,
if (values != null) 'values': List.from(values!,growable: false),
};
}
2026-03-01 12:51:14 +00:00
VariableSpec({required this.name, this.unit, this.values}) {
2026-02-01 15:16:04 +00:00
validate();
}
2026-03-01 12:51:14 +00:00
void validate() {
if (FormulaEvaluator.reservedVariableNames.contains(name)) {
2026-02-01 15:16:04 +00:00
throw ArgumentError("$name: is a reserved variable name for FormulaEvaluator");
}
final valuesValid = values != null && values?.isNotEmpty == true;
2026-03-01 12:51:14 +00:00
if (unit == null && !valuesValid) {
throw ArgumentError("$name: at least unit or allowedValues should be valid");
2025-11-09 19:29:58 +00:00
}
}
@override
String toString() => 'var($name: $unit${values != null ? ' allowed: $values' : ''})';
@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 &&
const DeepCollectionEquality().equals(values, other.values);
@override
int get hashCode => Object.hash(unit, name, values != null ? const DeepCollectionEquality().hash(values!) : 0);
2026-02-11 07:56:43 +00:00
}
2026-03-04 18:09:48 +00:00
class Formula extends FormulaElement {
final String name;
final String? description;
2025-08-24 09:52:34 +00:00
final List<VariableSpec> input;
final VariableSpec output;
final String d4rtCode;
2025-09-16 16:22:29 +00:00
final List<String> tags;
2026-03-04 18:09:48 +00:00
@override
Map<String, dynamic> toMap() {
return {
'name': name,
if (description != null) 'description': description,
'input': input.map((v) => v.toMap()).toList(growable: false),
'output': output.toMap(),
'd4rtCode': d4rtCode,
if (tags.isNotEmpty) 'tags': List.from(tags, growable: false),
};
}
2025-08-22 15:47:06 +00:00
Formula({
required this.name,
this.description,
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();
}
void validate() {
2026-03-04 18:09:48 +00:00
if (name
.trim()
.isEmpty) {
2025-08-22 15:47:06 +00:00
throw ArgumentError('Formula name cannot be empty');
}
}
@override
String toString() =>
2025-09-16 16:22:29 +00:00
'Formula(name: $name, description: $description, input: $input, output: $output, d4rtCode: $d4rtCode, tags: $tags)';
@override
bool operator ==(Object other) =>
identical(this, other) ||
2026-03-04 18:09:48 +00:00
other is Formula &&
runtimeType == other.runtimeType &&
name == other.name &&
description == other.description &&
output == other.output &&
ListEquality().equals(input, other.input) &&
d4rtCode == other.d4rtCode &&
ListEquality().equals(tags, other.tags);
@override
int get hashCode =>
2026-03-04 18:09:48 +00:00
Object.hash(
name, description, ListEquality().hash(input), output, d4rtCode,
ListEquality().hash(tags));
2026-03-04 18:09:48 +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-03-01 12:51:14 +00:00
final List<Object?> list = SetUtils.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) {
2026-03-04 18:09:48 +00:00
throw ArgumentError(
'Allowed values must be all Strings or all Numbers');
2025-11-09 19:29:58 +00:00
}
2026-03-04 18:09:48 +00:00
if (!types.contains(String) && !types.contains(double) &&
!types.contains(int)) {
2025-11-09 19:29:58 +00:00
throw ArgumentError('Allowed values must be Strings or Numbers');
}
}
return VariableSpec(
name: name,
unit: unit,
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");
2026-03-01 12:51:14 +00:00
String? description = theSet["description"] as String?;
2026-03-04 18:09:48 +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");
2026-03-04 18:09:48 +00:00
List<VariableSpec> input = inputSet.map((v) => parseVar(v as Map)).toList(
growable: false);
2026-03-01 12:51:14 +00:00
Map<Object?, Object?> outputSet = theSet['output'] as Map<Object?, Object?>;
2025-08-26 14:37:28 +00:00
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,
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
}
}
2026-03-04 18:09:48 +00:00