prettyPrint done
This commit is contained in:
parent
bb468ff601
commit
803b98d7ac
4 changed files with 177 additions and 102 deletions
6
TODO.md
6
TODO.md
|
|
@ -47,5 +47,11 @@
|
||||||
- [R] There is one row for the ouput variable, similar to the row for the input variable
|
- [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] d4rtCode is a text area with dart syntax highligthing
|
||||||
- [R] At the botton, a button allows to test the edited Formula, launching a FormulaScreen
|
- [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.
|
- [ ] 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
|
- [ ] Investigate https://pub.dev/packages/quantity
|
||||||
|
|
|
||||||
|
|
@ -56,12 +56,7 @@ class _FormulaListState extends State<FormulaList> {
|
||||||
String _formulaAndDependenciesToStringLiteral(Formula formula) {
|
String _formulaAndDependenciesToStringLiteral(Formula formula) {
|
||||||
// Get the formula and its dependencies
|
// Get the formula and its dependencies
|
||||||
final dependencies = widget.corpus.withDependencies(formula);
|
final dependencies = widget.corpus.withDependencies(formula);
|
||||||
|
return SetUtils.prettyPrint(dependencies.map((f) => f.toMap()).toList());
|
||||||
// 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 {
|
void _shareFormula(Formula formula) async {
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ abstract class SetUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Escapes special characters in a string for use in D4RT literals
|
/// Escapes special characters in a string for use in D4RT literals
|
||||||
|
@deprecated
|
||||||
static String escapeD4rtString(String input) {
|
static String escapeD4rtString(String input) {
|
||||||
return input
|
return input
|
||||||
.replaceAll(r'\\', r'\\\\') // escape backslashes first
|
.replaceAll(r'\\', r'\\\\') // escape backslashes first
|
||||||
|
|
@ -70,17 +71,112 @@ abstract class SetUtils {
|
||||||
|
|
||||||
return result;
|
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 base class for formula elements
|
||||||
abstract class FormulaElement {
|
abstract class FormulaElement {
|
||||||
/// Creates a string literal representation of the FormulaElement that can be parsed
|
Map<String,dynamic> toMap();
|
||||||
/// by the D4RT parser to recreate the same FormulaElement object.
|
|
||||||
String toStringLiteral();
|
String toStringLiteral() {
|
||||||
|
final map = toMap();
|
||||||
|
return SetUtils.prettyPrint(map);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnitSpec implements FormulaElement {
|
class UnitSpec extends FormulaElement {
|
||||||
final String name;
|
final String name;
|
||||||
final String baseUnit;
|
final String baseUnit;
|
||||||
final String symbol;
|
final String symbol;
|
||||||
|
|
@ -88,6 +184,19 @@ class UnitSpec implements FormulaElement {
|
||||||
final String? codeFromUnitToBase;
|
final String? codeFromUnitToBase;
|
||||||
final String? codeFromBaseToUnit;
|
final String? codeFromBaseToUnit;
|
||||||
|
|
||||||
|
@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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
UnitSpec({
|
UnitSpec({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.baseUnit,
|
required this.baseUnit,
|
||||||
|
|
@ -139,33 +248,22 @@ class UnitSpec implements FormulaElement {
|
||||||
return units.toList(growable: false);
|
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('}');
|
class VariableSpec extends FormulaElement{
|
||||||
return buffer.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class VariableSpec {
|
|
||||||
final String name;
|
final String name;
|
||||||
final String? unit;
|
final String? unit;
|
||||||
final List<dynamic>? values;
|
final List<dynamic>? values;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> 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}) {
|
VariableSpec({required this.name, this.unit, this.values}) {
|
||||||
validate();
|
validate();
|
||||||
}
|
}
|
||||||
|
|
@ -195,31 +293,10 @@ class VariableSpec {
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(unit, name, values != null ? const DeepCollectionEquality().hash(values!) : 0);
|
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) {
|
class Formula extends FormulaElement {
|
||||||
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 {
|
|
||||||
final String name;
|
final String name;
|
||||||
final String? description;
|
final String? description;
|
||||||
final List<VariableSpec> input;
|
final List<VariableSpec> input;
|
||||||
|
|
@ -227,6 +304,18 @@ class Formula implements FormulaElement {
|
||||||
final String d4rtCode;
|
final String d4rtCode;
|
||||||
final List<String> tags;
|
final List<String> tags;
|
||||||
|
|
||||||
|
@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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Formula({
|
Formula({
|
||||||
required this.name,
|
required this.name,
|
||||||
this.description,
|
this.description,
|
||||||
|
|
@ -239,7 +328,9 @@ class Formula implements FormulaElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
void validate() {
|
void validate() {
|
||||||
if (name.trim().isEmpty) {
|
if (name
|
||||||
|
.trim()
|
||||||
|
.isEmpty) {
|
||||||
throw ArgumentError('Formula name cannot be empty');
|
throw ArgumentError('Formula name cannot be empty');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -262,9 +353,12 @@ class Formula implements FormulaElement {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
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<String> inputVarNames() => input.map((v) => v.name).toList(growable: false);
|
List<String> inputVarNames() =>
|
||||||
|
input.map((v) => v.name).toList(growable: false);
|
||||||
|
|
||||||
factory Formula.fromStringLiteral(String setStringLiteral) {
|
factory Formula.fromStringLiteral(String setStringLiteral) {
|
||||||
var d4rt = D4rt();
|
var d4rt = D4rt();
|
||||||
|
|
@ -296,9 +390,11 @@ class Formula implements FormulaElement {
|
||||||
if (allowed != null) {
|
if (allowed != null) {
|
||||||
final types = allowed.map((v) => v.runtimeType).toSet();
|
final types = allowed.map((v) => v.runtimeType).toSet();
|
||||||
if (types.length > 1) {
|
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');
|
throw ArgumentError('Allowed values must be Strings or Numbers');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -311,9 +407,11 @@ class Formula implements FormulaElement {
|
||||||
|
|
||||||
String name = SetUtils.stringValue(theSet, "name");
|
String name = SetUtils.stringValue(theSet, "name");
|
||||||
String? description = theSet["description"] as String?;
|
String? description = theSet["description"] as String?;
|
||||||
List<String> tags = (theSet["tags"] as List<Object?>? ?? []).map((t) => t.toString()).toList();
|
List<String> tags = (theSet["tags"] as List<Object?>? ?? []).map((t) =>
|
||||||
|
t.toString()).toList();
|
||||||
final List<Object?> inputSet = SetUtils.listValue(theSet, "input");
|
final List<Object?> inputSet = SetUtils.listValue(theSet, "input");
|
||||||
List<VariableSpec> input = inputSet.map((v) => parseVar(v as Map)).toList(growable: false);
|
List<VariableSpec> input = inputSet.map((v) => parseVar(v as Map)).toList(
|
||||||
|
growable: false);
|
||||||
Map<Object?, Object?> outputSet = theSet['output'] as Map<Object?, Object?>;
|
Map<Object?, Object?> outputSet = theSet['output'] as Map<Object?, Object?>;
|
||||||
VariableSpec output = parseVar(outputSet);
|
VariableSpec output = parseVar(outputSet);
|
||||||
String d4rtCode = SetUtils.stringValue(theSet, "d4rtCode");
|
String d4rtCode = SetUtils.stringValue(theSet, "d4rtCode");
|
||||||
|
|
@ -327,30 +425,5 @@ class Formula implements FormulaElement {
|
||||||
d4rtCode: d4rtCode,
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -285,4 +285,5 @@ void main() {
|
||||||
expect(dependencies.length, equals(uniqueDependencies.length));
|
expect(dependencies.length, equals(uniqueDependencies.length));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue