From 23d895737792821e670e0cf314e26ac4c8298601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Gonz=C3=A1lez?= Date: Sun, 1 Mar 2026 10:49:46 +0100 Subject: [PATCH 1/4] refactor shared formula --- lib/ai/formula_list.dart | 29 +++++++++++++---------------- lib/formula_models.dart | 2 +- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/lib/ai/formula_list.dart b/lib/ai/formula_list.dart index a9f6675..fce2c1d 100644 --- a/lib/ai/formula_list.dart +++ b/lib/ai/formula_list.dart @@ -53,16 +53,20 @@ class _FormulaListState extends State { }).toList(); } + String _formulaAndDependenciesToStringLiteral(Formula formula) { + // Get the formula and its dependencies + final dependencies = widget.corpus.withDependencies(formula); + + // Convert each dependency to its string literal representation + final literals = dependencies.map((element) => element.toStringLiteral()).toList(); + + // Create an array string literal containing all the elements + return '[${literals.join(', ')}]'; + } + void _shareFormula(Formula formula) async { try { - // Get the formula and its dependencies - final dependencies = widget.corpus.withDependencies(formula); - - // Convert each dependency to its string literal representation - final literals = dependencies.map((element) => element.toStringLiteral()).toList(); - - // Create an array string literal containing all the elements - final exportString = '[${literals.join(', ')}]'; + final exportString = _formulaAndDependenciesToStringLiteral(formula); // Share the string await share_plus.SharePlus.instance.share( @@ -90,14 +94,7 @@ class _FormulaListState extends State { void _copyFormula(Formula formula) async { try { - // Get the formula and its dependencies - final dependencies = widget.corpus.withDependencies(formula); - - // Convert each dependency to its string literal representation - final literals = dependencies.map((element) => element.toStringLiteral()).toList(); - - // Create an array string literal containing all the elements - final exportString = '[${literals.join(', ')}]'; + final exportString = _formulaAndDependenciesToStringLiteral(formula); // Copy to clipboard await Clipboard.setData(ClipboardData(text: exportString)); diff --git a/lib/formula_models.dart b/lib/formula_models.dart index 12ceaba..7c79574 100644 --- a/lib/formula_models.dart +++ b/lib/formula_models.dart @@ -358,7 +358,7 @@ class Formula implements FormulaElement { buffer.write(', "input": [${inputStrings.join(", ")}]'); buffer.write(', "output": ${output.toStringLiteral()}'); - + buffer.write(', "d4rtCode": r"""$d4rtCode"""'); if (tags.isNotEmpty) { From aadfc9dac5db7b1c7f9fc8c7d6eb28a549882ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Gonz=C3=A1lez?= Date: Sun, 1 Mar 2026 11:03:13 +0100 Subject: [PATCH 2/4] refactor shared formula --- lib/ai/formula_screen.dart | 4 ++-- test/dart_test.dart | 1 - test/database_test.dart | 1 - test/physics_trigonometry_formulas_test.dart | 1 - 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/ai/formula_screen.dart b/lib/ai/formula_screen.dart index dc1089f..6535ae5 100644 --- a/lib/ai/formula_screen.dart +++ b/lib/ai/formula_screen.dart @@ -63,10 +63,10 @@ class D4rtEditingController extends TextEditingController { if( _validateAsD4rtExpression(text) && _lastValue is StringResult ){ return true; } - if( _validateAsD4rtExpression('"' + text + '"') && _lastValue is StringResult ){ + if( _validateAsD4rtExpression('"$text"') && _lastValue is StringResult ){ return true; } - if( _validateAsD4rtExpression("'" + text + "'") && _lastValue is StringResult ){ + if( _validateAsD4rtExpression("'$text'") && _lastValue is StringResult ){ return true; } return false; diff --git a/test/dart_test.dart b/test/dart_test.dart index 9e78acc..ccedad0 100644 --- a/test/dart_test.dart +++ b/test/dart_test.dart @@ -1,6 +1,5 @@ import 'package:test/test.dart'; import 'package:d4rt/d4rt.dart'; -import 'dart:math' as Math; void main(){ diff --git a/test/database_test.dart b/test/database_test.dart index e3541e7..c38e774 100644 --- a/test/database_test.dart +++ b/test/database_test.dart @@ -1,5 +1,4 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:d4rt_formulas/database/database_service.dart'; import 'package:d4rt_formulas/service_locator.dart'; void main() { diff --git a/test/physics_trigonometry_formulas_test.dart b/test/physics_trigonometry_formulas_test.dart index 2b606f8..d3e3171 100644 --- a/test/physics_trigonometry_formulas_test.dart +++ b/test/physics_trigonometry_formulas_test.dart @@ -2,7 +2,6 @@ import 'package:d4rt_formulas/corpus.dart'; import 'package:d4rt_formulas/defaults/default_corpus.dart'; import 'package:d4rt_formulas/formula_evaluator.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:d4rt_formulas/formula_models.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); From bb468ff60131c27eae8728080e575c4fbcba3273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Gonz=C3=A1lez?= Date: Sun, 1 Mar 2026 13:51:14 +0100 Subject: [PATCH 3/4] setutils: static util methods --- bin/pruebas_d4rt.dart | 17 --- lib/database/database_service.dart | 3 +- lib/formula_models.dart | 167 +++++++++++++---------------- lib/main.dart | 1 - test/dart_test.dart | 2 +- test/formula_models_test.dart | 2 +- 6 files changed, 80 insertions(+), 112 deletions(-) delete mode 100644 bin/pruebas_d4rt.dart diff --git a/bin/pruebas_d4rt.dart b/bin/pruebas_d4rt.dart deleted file mode 100644 index 90f6e8a..0000000 --- a/bin/pruebas_d4rt.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:d4rt/d4rt.dart'; - -void main() { - final code = ''' - int fib(int n) { - if (n <= 1) return n; - return fib(n - 1) + fib(n - 2); - } - main() { - return fib(6); - } - '''; - - final interpreter = D4rt(); - final result = interpreter.execute(source: code); - print('Result: $result'); // Result: 8 -} diff --git a/lib/database/database_service.dart b/lib/database/database_service.dart index 06dc034..54033d2 100644 --- a/lib/database/database_service.dart +++ b/lib/database/database_service.dart @@ -1,3 +1,4 @@ +import '../formula_models.dart'; import 'corpus_database_interface.dart'; import 'formulas_database.dart'; import 'package:d4rt_formulas/formula_models.dart' as models; @@ -11,7 +12,7 @@ extension CorpusDatabaseExtension on FormulasDatabase { for (final element in elements) { try { - final parsed = models.parseCorpusElements('[${element.elementText}]'); + final parsed = SetUtils.parseCorpusElements('[${element.elementText}]'); print("PARSED:$element"); parsedElements.addAll(parsed); } catch (e) { diff --git a/lib/formula_models.dart b/lib/formula_models.dart index 7c79574..faeab2e 100644 --- a/lib/formula_models.dart +++ b/lib/formula_models.dart @@ -2,6 +2,8 @@ import 'package:d4rt/d4rt.dart'; import 'package:collection/collection.dart'; import 'package:d4rt_formulas/d4rt_formulas.dart'; +typedef Number = double; + abstract class SetUtils { static Object safeGet(Map map, String key) { if (!map.containsKey(key)) { @@ -21,60 +23,55 @@ abstract class SetUtils { static Number numberValue(Map map, String key) { return double.parse(stringValue(map, key)); } -} -/// Parses a d4rt array literal (containing maps and arrays) to a List -/// using d4rt -List parseD4rtLiteral(String arrayStringLiteral) { - var d4rt = D4rt(); - final buffer = StringBuffer(); - buffer.write("main(){ return $arrayStringLiteral; }"); - final code = buffer.toString(); + /// Parses a d4rt array literal (containing maps and arrays) to a List + /// using d4rt + static List parseD4rtLiteral(String arrayStringLiteral) { + var d4rt = D4rt(); + final buffer = StringBuffer(); + buffer.write("main(){ return $arrayStringLiteral; }"); + final code = buffer.toString(); - final List list = d4rt.execute(source: code); + final List list = d4rt.execute(source: code); - return list; -} - -/// Escapes special characters in a string for use in D4RT literals -String escapeD4rtString(String input) { - return input - .replaceAll(r'\', r'\\') // Escape backslashes first - .replaceAll('\n', r'\n') // Escape newlines - .replaceAll('\r', r'\r') // Escape carriage returns - .replaceAll('\t', r'\t') // Escape tabs - .replaceAll('"', r'\"'); // Escape quotes last -} - -/// Parses corpus elements from an array string literal. -/// Determines if each element is a formula or a unit and converts accordingly. -List parseCorpusElements(String arrayStringLiteral) { - final List elements = parseD4rtLiteral(arrayStringLiteral); - - final List result = []; - for (final element in elements) { - if (element is Map) { - // 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 list; } - return result; + /// Escapes special characters in a string for use in D4RT literals + 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'\\"'); + } + + /// Parses corpus elements from an array string literal. + /// Determines if each element is a formula or a unit and converts accordingly. + static List parseCorpusElements(String arrayStringLiteral) { + final List elements = parseD4rtLiteral(arrayStringLiteral); + + final List result = []; + for (final element in elements) { + if (element is Map) { + 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'); + } + } + + return result; + } } -typedef Number = double; /// Abstract base class for formula elements abstract class FormulaElement { @@ -104,7 +101,7 @@ class UnitSpec implements FormulaElement { String name = SetUtils.stringValue(theSet, "name"); String symbol = SetUtils.stringValue(theSet, "symbol"); - if( theSet.containsKey("isBase") ){ + if (theSet.containsKey("isBase")) { return UnitSpec(name: name, baseUnit: name, symbol: symbol, factorFromUnitToBase: 1); } @@ -118,32 +115,24 @@ class UnitSpec implements FormulaElement { symbol: symbol, factorFromUnitToBase: factorFromUnitToBase, ); - } - else if( theSet.containsKey("toBase")) { - String codeFromBaseToUnit = SetUtils.stringValue( - theSet, - "fromBase", + } 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, ); - 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"); } - else{ - throw ArgumentError( "Need factor or toBase/fromBase"); - } - - } static List fromArrayStringLiteral(String arrayStringLiteral) { - final List list = parseD4rtLiteral(arrayStringLiteral); + final List list = SetUtils.parseD4rtLiteral(arrayStringLiteral); final units = list.map((set) => UnitSpec.fromSet(set as Map)); @@ -153,18 +142,17 @@ class UnitSpec implements FormulaElement { @override String toStringLiteral() { final buffer = StringBuffer('{'); - buffer.write('"name": "${escapeD4rtString(name)}", "symbol": "${escapeD4rtString(symbol)}"'); + buffer.write('"name": "${SetUtils.escapeD4rtString(name)}", "symbol": "${SetUtils.escapeD4rtString(symbol)}"'); if (name == baseUnit && factorFromUnitToBase == 1) { - // This is a base unit buffer.write(', "isBase": true'); } else { - buffer.write(', "baseUnit": "${escapeD4rtString(baseUnit)}"'); + buffer.write(', "baseUnit": "${SetUtils.escapeD4rtString(baseUnit)}"'); if (factorFromUnitToBase != null) { buffer.write(', "factor": $factorFromUnitToBase'); } else if (codeFromUnitToBase != null && codeFromBaseToUnit != null) { - buffer.write(', "toBase": "${escapeD4rtString(codeFromUnitToBase!)}", "fromBase": "${escapeD4rtString(codeFromBaseToUnit!)}"'); + buffer.write(', "toBase": "${SetUtils.escapeD4rtString(codeFromUnitToBase!)}", "fromBase": "${SetUtils.escapeD4rtString(codeFromBaseToUnit!)}"'); } } @@ -178,16 +166,16 @@ class VariableSpec { final String? unit; final List? values; - VariableSpec({required this.name, this.unit, this.values}){ + VariableSpec({required this.name, this.unit, this.values}) { validate(); } - void validate(){ - if( FormulaEvaluator.reservedVariableNames.contains(name) ){ + void validate() { + if (FormulaEvaluator.reservedVariableNames.contains(name)) { throw ArgumentError("$name: is a reserved variable name for FormulaEvaluator"); } final valuesValid = values != null && values?.isNotEmpty == true; - if( unit == null && !valuesValid ){ + if (unit == null && !valuesValid) { throw ArgumentError("$name: at least unit or allowedValues should be valid"); } } @@ -210,20 +198,20 @@ class VariableSpec { @override String toStringLiteral() { final buffer = StringBuffer('{'); - buffer.write('"name": "${escapeD4rtString(name)}"'); + buffer.write('"name": "${SetUtils.escapeD4rtString(name)}"'); if (unit != null) { - buffer.write(', "unit": "${escapeD4rtString(unit!)}"'); + buffer.write(', "unit": "${SetUtils.escapeD4rtString(unit!)}"'); } if (values != null && values!.isNotEmpty) { buffer.write(', "values": [${values!.map((value) { if (value is String) { - return '"${escapeD4rtString(value)}"'; + return '"${SetUtils.escapeD4rtString(value)}"'; } else { return value.toString(); } - }).join(", ")}]'); + }).join(", ")} ]'); } buffer.write('}'); @@ -276,8 +264,7 @@ class Formula implements FormulaElement { int get hashCode => Object.hash(name, description, ListEquality().hash(input), output, d4rtCode, ListEquality().hash(tags)); - List inputVarNames() => - input.map((v) => v.name).toList(growable: false); + List inputVarNames() => input.map((v) => v.name).toList(growable: false); factory Formula.fromStringLiteral(String setStringLiteral) { var d4rt = D4rt(); @@ -291,7 +278,7 @@ class Formula implements FormulaElement { } static List fromArrayStringLiteral(String arrayStringLiteral) { - final List list = parseD4rtLiteral(arrayStringLiteral); + final List list = SetUtils.parseD4rtLiteral(arrayStringLiteral); final formulas = list.map((set) => Formula.fromSet(set as Map)); @@ -323,13 +310,11 @@ class Formula implements FormulaElement { } String name = SetUtils.stringValue(theSet, "name"); - String? description = theSet ["description"] as String?; + String? description = theSet["description"] as String?; List tags = (theSet["tags"] as List? ?? []).map((t) => t.toString()).toList(); final List inputSet = SetUtils.listValue(theSet, "input"); - List input = inputSet - .map((v) => parseVar(v as Map)) - .toList(growable: false); - Map outputSet = theSet.get("output"); + List input = inputSet.map((v) => parseVar(v as Map)).toList(growable: false); + Map outputSet = theSet['output'] as Map; VariableSpec output = parseVar(outputSet); String d4rtCode = SetUtils.stringValue(theSet, "d4rtCode"); @@ -350,7 +335,7 @@ class Formula implements FormulaElement { final inputStrings = input.map((varSpec) => varSpec.toStringLiteral()).toList(); final buffer = StringBuffer('{'); - buffer.write('"name": "$name"'); + buffer.write('"name": "${SetUtils.escapeD4rtString(name)}"'); if (description != null) { buffer.write(', "description": r"""${description!}"""'); @@ -362,7 +347,7 @@ class Formula implements FormulaElement { buffer.write(', "d4rtCode": r"""$d4rtCode"""'); if (tags.isNotEmpty) { - buffer.write(', "tags": [${tags.map((tag) => '"${escapeD4rtString(tag)}"').join(", ")}]'); + buffer.write(', "tags": [${tags.map((tag) => '"${SetUtils.escapeD4rtString(tag)}"').join(", ")}]'); } buffer.write('}'); diff --git a/lib/main.dart b/lib/main.dart index a1e6684..bb2421a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,7 +2,6 @@ import 'package:d4rt_formulas/d4rt_formulas.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'database/database_service.dart'; -import 'package:drift/drift.dart' as drift; import 'service_locator.dart'; import 'ai/formula_list.dart'; diff --git a/test/dart_test.dart b/test/dart_test.dart index ccedad0..64802b5 100644 --- a/test/dart_test.dart +++ b/test/dart_test.dart @@ -1,5 +1,5 @@ import 'package:test/test.dart'; -import 'package:d4rt/d4rt.dart'; + void main(){ diff --git a/test/formula_models_test.dart b/test/formula_models_test.dart index 01a9953..af71609 100644 --- a/test/formula_models_test.dart +++ b/test/formula_models_test.dart @@ -154,7 +154,7 @@ void main() { ); final literal = originalUnit.toStringLiteral(); - final parsedList = parseD4rtLiteral('[${literal}]'); + final parsedList = SetUtils.parseD4rtLiteral('[${literal}]'); final parsedMap = parsedList[0] as Map; final parsedUnit = UnitSpec.fromSet(parsedMap); From 803b98d7aca3642c01d667fefd1c9ae87efd0fe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Gonz=C3=A1lez?= Date: Wed, 4 Mar 2026 19:09:48 +0100 Subject: [PATCH 4/4] prettyPrint done --- TODO.md | 6 + lib/ai/formula_list.dart | 7 +- lib/formula_models.dart | 249 ++++++++++++++++++++++------------ test/formula_models_test.dart | 17 +-- 4 files changed, 177 insertions(+), 102 deletions(-) diff --git a/TODO.md b/TODO.md index 5a4f019..fbcac80 100644 --- a/TODO.md +++ b/TODO.md @@ -47,5 +47,11 @@ - [R] There is one row for the ouput variable, similar to the row for the input variable - [R] d4rtCode is a text area with dart syntax highligthing - [R] At the botton, a button allows to test the edited Formula, launching a FormulaScreen +- [X] Create SetUtils.prettyPrint(): receives a dynamic Set, Array, string or number. Convert to a dart representation of than value (a set/array literal), json-like, but for dart language. Do it recursivelly on local functions to that method: + - _prettyPrintString(String s, int indent): Only for simple strings + - _prettyPrintNumber(Number n, int indent) + - _prettyPrintSet(Set s, int indent) + - _prettyPrintArray(dynamic[] a, int indent) + - _prettyPrintRawString(String s, int indent): Use _prettyPrintRawString when the string contains newlines, $, backlash... - [ ] When _FormulaScreenState._evaluateFormula() detect an error, instead of show an SnackBar, show a ExpansionTile with "⚠️ There were an error. Show details..." with the details of the exception. The ExpansionTile will be invisible if there is no error. - [ ] Investigate https://pub.dev/packages/quantity diff --git a/lib/ai/formula_list.dart b/lib/ai/formula_list.dart index fce2c1d..bd99cac 100644 --- a/lib/ai/formula_list.dart +++ b/lib/ai/formula_list.dart @@ -56,12 +56,7 @@ class _FormulaListState extends State { String _formulaAndDependenciesToStringLiteral(Formula formula) { // Get the formula and its dependencies final dependencies = widget.corpus.withDependencies(formula); - - // Convert each dependency to its string literal representation - final literals = dependencies.map((element) => element.toStringLiteral()).toList(); - - // Create an array string literal containing all the elements - return '[${literals.join(', ')}]'; + return SetUtils.prettyPrint(dependencies.map((f) => f.toMap()).toList()); } void _shareFormula(Formula formula) async { diff --git a/lib/formula_models.dart b/lib/formula_models.dart index faeab2e..5f80397 100644 --- a/lib/formula_models.dart +++ b/lib/formula_models.dart @@ -38,6 +38,7 @@ abstract class SetUtils { } /// Escapes special characters in a string for use in D4RT literals + @deprecated static String escapeD4rtString(String input) { return input .replaceAll(r'\\', r'\\\\') // escape backslashes first @@ -70,17 +71,112 @@ abstract class SetUtils { return result; } + + /// 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"""'; + } } /// Abstract base class for formula elements abstract class FormulaElement { - /// Creates a string literal representation of the FormulaElement that can be parsed - /// by the D4RT parser to recreate the same FormulaElement object. - String toStringLiteral(); + Map toMap(); + + String toStringLiteral() { + final map = toMap(); + return SetUtils.prettyPrint(map); + } } -class UnitSpec implements FormulaElement { +class UnitSpec extends FormulaElement { final String name; final String baseUnit; final String symbol; @@ -88,6 +184,19 @@ class UnitSpec implements FormulaElement { final String? codeFromUnitToBase; final String? codeFromBaseToUnit; + @override + Map toMap() { + return { + "name": name, + "baseUnit": baseUnit, + "symbol": symbol, + if (factorFromUnitToBase != null) 'factor': factorFromUnitToBase, + if (codeFromUnitToBase != null) 'toBase': codeFromUnitToBase, + if (codeFromBaseToUnit != null) 'fromBase': codeFromBaseToUnit, + }; + } + + UnitSpec({ required this.name, required this.baseUnit, @@ -139,33 +248,22 @@ class UnitSpec implements FormulaElement { return units.toList(growable: false); } - @override - String toStringLiteral() { - final buffer = StringBuffer('{'); - buffer.write('"name": "${SetUtils.escapeD4rtString(name)}", "symbol": "${SetUtils.escapeD4rtString(symbol)}"'); - - if (name == baseUnit && factorFromUnitToBase == 1) { - buffer.write(', "isBase": true'); - } else { - buffer.write(', "baseUnit": "${SetUtils.escapeD4rtString(baseUnit)}"'); - - if (factorFromUnitToBase != null) { - buffer.write(', "factor": $factorFromUnitToBase'); - } else if (codeFromUnitToBase != null && codeFromBaseToUnit != null) { - buffer.write(', "toBase": "${SetUtils.escapeD4rtString(codeFromUnitToBase!)}", "fromBase": "${SetUtils.escapeD4rtString(codeFromBaseToUnit!)}"'); - } - } - - buffer.write('}'); - return buffer.toString(); - } } -class VariableSpec { +class VariableSpec extends FormulaElement{ final String name; final String? unit; final List? values; + @override + Map toMap() { + return { + 'name': name, + if (unit != null) 'unit': unit, + if (values != null) 'values': List.from(values!,growable: false), + }; + } + VariableSpec({required this.name, this.unit, this.values}) { validate(); } @@ -195,31 +293,10 @@ class VariableSpec { @override int get hashCode => Object.hash(unit, name, values != null ? const DeepCollectionEquality().hash(values!) : 0); - @override - String toStringLiteral() { - final buffer = StringBuffer('{'); - buffer.write('"name": "${SetUtils.escapeD4rtString(name)}"'); - if (unit != null) { - buffer.write(', "unit": "${SetUtils.escapeD4rtString(unit!)}"'); - } - - if (values != null && values!.isNotEmpty) { - buffer.write(', "values": [${values!.map((value) { - if (value is String) { - return '"${SetUtils.escapeD4rtString(value)}"'; - } else { - return value.toString(); - } - }).join(", ")} ]'); - } - - buffer.write('}'); - return buffer.toString(); - } } -class Formula implements FormulaElement { +class Formula extends FormulaElement { final String name; final String? description; final List input; @@ -227,6 +304,18 @@ class Formula implements FormulaElement { final String d4rtCode; final List tags; + @override + Map 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), + }; + } + Formula({ required this.name, this.description, @@ -239,7 +328,9 @@ class Formula implements FormulaElement { } void validate() { - if (name.trim().isEmpty) { + if (name + .trim() + .isEmpty) { throw ArgumentError('Formula name cannot be empty'); } } @@ -251,20 +342,23 @@ class Formula implements FormulaElement { @override bool operator ==(Object other) => identical(this, other) || - 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); + 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 => - Object.hash(name, description, ListEquality().hash(input), output, d4rtCode, ListEquality().hash(tags)); + Object.hash( + name, description, ListEquality().hash(input), output, d4rtCode, + ListEquality().hash(tags)); - List inputVarNames() => input.map((v) => v.name).toList(growable: false); + List inputVarNames() => + input.map((v) => v.name).toList(growable: false); factory Formula.fromStringLiteral(String setStringLiteral) { var d4rt = D4rt(); @@ -296,9 +390,11 @@ class Formula implements FormulaElement { 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'); + throw ArgumentError( + 'Allowed values must be all Strings or all Numbers'); } - if (!types.contains(String) && !types.contains(double) && !types.contains(int)) { + if (!types.contains(String) && !types.contains(double) && + !types.contains(int)) { throw ArgumentError('Allowed values must be Strings or Numbers'); } } @@ -311,9 +407,11 @@ class Formula implements FormulaElement { String name = SetUtils.stringValue(theSet, "name"); String? description = theSet["description"] as String?; - List tags = (theSet["tags"] as List? ?? []).map((t) => t.toString()).toList(); + List tags = (theSet["tags"] as List? ?? []).map((t) => + t.toString()).toList(); final List inputSet = SetUtils.listValue(theSet, "input"); - List input = inputSet.map((v) => parseVar(v as Map)).toList(growable: false); + List input = inputSet.map((v) => parseVar(v as Map)).toList( + growable: false); Map outputSet = theSet['output'] as Map; VariableSpec output = parseVar(outputSet); String d4rtCode = SetUtils.stringValue(theSet, "d4rtCode"); @@ -327,30 +425,5 @@ class Formula implements FormulaElement { d4rtCode: d4rtCode, ); } - - /// Creates a string literal representation of the Formula that can be parsed - /// by the D4RT parser to recreate the same Formula object. - @override - String toStringLiteral() { - final inputStrings = input.map((varSpec) => varSpec.toStringLiteral()).toList(); - - final buffer = StringBuffer('{'); - buffer.write('"name": "${SetUtils.escapeD4rtString(name)}"'); - - if (description != null) { - buffer.write(', "description": r"""${description!}"""'); - } - - buffer.write(', "input": [${inputStrings.join(", ")}]'); - buffer.write(', "output": ${output.toStringLiteral()}'); - - buffer.write(', "d4rtCode": r"""$d4rtCode"""'); - - if (tags.isNotEmpty) { - buffer.write(', "tags": [${tags.map((tag) => '"${SetUtils.escapeD4rtString(tag)}"').join(", ")}]'); - } - - buffer.write('}'); - return buffer.toString(); - } } + diff --git a/test/formula_models_test.dart b/test/formula_models_test.dart index af71609..fb1ae67 100644 --- a/test/formula_models_test.dart +++ b/test/formula_models_test.dart @@ -246,22 +246,22 @@ void main() { test('Corpus.withDependencies returns formula and its dependencies', () async { final corpus = await testCorpus; - + // Get a formula that has units associated with it final formula = corpus.getFormula("Newton's Second Law"); expect(formula, isNotNull); - + // Call withDependencies method final dependencies = corpus.withDependencies(formula!); - + // Check that the formula itself is included expect(dependencies.any((element) => element is Formula && element.name == formula.name), true); - + // Check that units from input and output are included for (final inputVar in formula.input) { if (inputVar.unit != null) { expect(dependencies.any((element) => element is UnitSpec && element.name == inputVar.unit), true); - + // Check that units with same base unit are included final unitsWithSameBase = corpus.unitsOfSameMagnitude(inputVar.unit!); for (final unitName in unitsWithSameBase) { @@ -269,20 +269,21 @@ void main() { } } } - + if (formula.output.unit != null) { expect(dependencies.any((element) => element is UnitSpec && element.name == formula.output.unit), true); - + // Check that units with same base unit as output are included final outputUnitsWithSameBase = corpus.unitsOfSameMagnitude(formula.output.unit!); for (final unitName in outputUnitsWithSameBase) { expect(dependencies.any((element) => element is UnitSpec && element.name == unitName), true); } } - + // Verify that there are no duplicates by checking the length of the list vs the set final uniqueDependencies = dependencies.toSet(); expect(dependencies.length, equals(uniqueDependencies.length)); }); + }