diff --git a/example/formula_evaluation_example.dart b/example/formula_evaluation_example.dart index 068c699..0b60770 100644 --- a/example/formula_evaluation_example.dart +++ b/example/formula_evaluation_example.dart @@ -37,8 +37,8 @@ void main() { print(' Mass: 10.0 kg'); print(' Acceleration: 9.8 m/s²'); print(' Calculated Force: $force N'); - print(' Output variable: ${evaluator.getOutputVariableName(newtonFormula)}'); - print(' Output magnitude: ${evaluator.getOutputVariableMagnitude(newtonFormula)}'); + print(' Output variable: ${newtonFormula.output.name}'); + print(' Output magnitude: ${newtonFormula.output.unit}'); } catch (e) { print(' Error: $e'); } diff --git a/lib/ai/formula_screen.dart b/lib/ai/formula_screen.dart index 81d82ad..7b12262 100644 --- a/lib/ai/formula_screen.dart +++ b/lib/ai/formula_screen.dart @@ -105,11 +105,11 @@ class _FormulaScreenState extends State { // Convert input to base unit if needed // Always convert from dropdown unit to variable's base unit late final convertedValue; - if( value is Number ) { + if( value is Number && input.unit != null ) { convertedValue = widget.corpus.convert( value, _selectedUnits[input.name]!, - input.unit, + input.unit as String, ); } else{ @@ -124,11 +124,17 @@ class _FormulaScreenState extends State { final result = evaluator.evaluate(widget.formula, inputValues); // Convert output to selected unit if needed - _result = widget.corpus.convert( - result, - widget.formula.output.unit, - _selectedOutputUnit!, - ).toStringAsFixed(2); + String? unit = widget.formula.output.unit; + if( unit != null ) { + _result = widget.corpus.convert( + result, + unit, + _selectedOutputUnit!, + ).toStringAsFixed(2); + } + else{ + _result = result; + } //print( "_evaluateFormula: result:${result} _result:${_result}"); diff --git a/lib/corpus.dart b/lib/corpus.dart index c9c724f..0710d8b 100644 --- a/lib/corpus.dart +++ b/lib/corpus.dart @@ -34,7 +34,7 @@ class Corpus{ if( checkUnits ){ for( final inputVar in formula.input + [formula.output] ){ - if( !_allUnits.containsKey(inputVar.unit) ){ + if( inputVar.unit != null && !_allUnits.containsKey(inputVar.unit) ){ throw ArgumentError( "Unit not found: ${inputVar.unit}"); } } @@ -71,7 +71,10 @@ class Corpus{ } } - List unitsOfSameMagnitude(String unit){ + List unitsOfSameMagnitude(String? unit){ + if( unit == null ){ + return ["unitless"]; + } final base = getUnit(unit).baseUnit; return _baseToUnits[base] as List; } @@ -113,7 +116,7 @@ class Corpus{ } final ret = _convertUsingCode(x, unit.codeFromUnitToBase as String); - return ret as Number; + return ret; } Number _convertFromBase(Number x, String toUnit) { @@ -128,7 +131,7 @@ class Corpus{ } final ret = _convertUsingCode(x, unit.codeFromBaseToUnit as String); - return ret as Number; + return ret; } @@ -140,17 +143,19 @@ class Corpus{ final ret = _evaluate(completeSourceExpression); return ret; } - catch(e1,stack){ + catch(e1, stack1){ try{ completeSourceStatement = _converterFromCodeStringAsStatement(x, code); final ret = _evaluate(completeSourceStatement); return ret; } - catch( e2, stack ){ + catch( e2, stack2 ){ print(completeSourceExpression); print(e1); + print(stack1); print(completeSourceStatement); print(e2); + print(stack2); throw FormulaEvaluationException( "Evaluation as statement and expression failed" ); } } diff --git a/lib/defaults/default_corpus.dart b/lib/defaults/default_corpus.dart index 7df8d82..d740ba8 100644 --- a/lib/defaults/default_corpus.dart +++ b/lib/defaults/default_corpus.dart @@ -17,6 +17,7 @@ Future createDefaultCorpus() async{ "lib/defaults/units/force.d4rt.units", "lib/defaults/units/mass.d4rt.units", "lib/defaults/units/pressure.d4rt.units", + "lib/defaults/units/scalar.d4rt.units", "lib/defaults/units/temperature.d4rt.units", "lib/defaults/units/time.d4rt.units", "lib/defaults/units/velocity.d4rt.units", diff --git a/lib/defaults/formulas.d4rt b/lib/defaults/formulas.d4rt index b0ea338..fafa242 100644 --- a/lib/defaults/formulas.d4rt +++ b/lib/defaults/formulas.d4rt @@ -126,13 +126,13 @@ Where: "name": "Apgar Score", "description": "Newborn health assessment scoring system\n\nScores 0-2 for:\n1. Heart rate\n2. Breathing\n3. Muscle tone\n4. Reflexes\n5. Skin color\nTotal score 0-10", "input": [ - {"name": "HeartRate", "unit": "integer"}, - {"name": "Breathing", "unit": "integer"}, - {"name": "MuscleTone", "unit": "integer"}, - {"name": "Reflexes", "unit": "integer"}, - {"name": "SkinColor", "unit": "integer"} + {"name": "HeartRate", "values": ["hr1", "hr2", "hr3"] }, + {"name": "Breathing", "values": ["hr1", "hr2", "hr3"] }, + {"name": "MuscleTone", "values": ["hr1", "hr2", "hr3"] }, + {"name": "Reflexes", "values": ["hr1", "hr2", "hr3"] }, + {"name": "SkinColor", "values": ["hr1", "hr2", "hr3"] } ], - "output": {"name": "Result", "unit": "Apgar score"}, + "output": {"name": "Result", "unit": "scalar"}, "d4rtCode": """ var total = HeartRate + Breathing + MuscleTone + Reflexes + SkinColor; var interpretation = switch (total) { diff --git a/lib/defaults/units/scalar.d4rt.units b/lib/defaults/units/scalar.d4rt.units new file mode 100644 index 0000000..c314f66 --- /dev/null +++ b/lib/defaults/units/scalar.d4rt.units @@ -0,0 +1,3 @@ +[ + {"name": "scalar", "symbol": "", "isBase": true} +] \ No newline at end of file diff --git a/lib/formula_evaluator.dart b/lib/formula_evaluator.dart index 0665b38..9a844b9 100644 --- a/lib/formula_evaluator.dart +++ b/lib/formula_evaluator.dart @@ -121,13 +121,7 @@ class FormulaEvaluator { } } - String getOutputVariableName(Formula formula) { - return formula.output.name; - } - String getOutputVariableMagnitude(Formula formula) { - return formula.output.unit; - } List getInputVariableOrder(Formula formula) { return formula.inputVarNames()..sort(); @@ -166,9 +160,9 @@ class FormulaEvaluator { } } buffer.writeln(""" - late var ${getOutputVariableName(formula)}; + late var ${formula.output.name}; ${formula.d4rtCode} - return ${getOutputVariableName(formula)}; + return ${formula.output.name}; } """ ); diff --git a/lib/formula_models.dart b/lib/formula_models.dart index ebdf047..05bb6a8 100644 --- a/lib/formula_models.dart +++ b/lib/formula_models.dart @@ -101,12 +101,18 @@ class UnitSpec { class VariableSpec { final String name; - final String unit; + final String? unit; + final List? allowedValues; - VariableSpec({required this.name, required this.unit}); + VariableSpec({required this.name, this.unit, this.allowedValues}){ + final valuesValid = allowedValues != null && allowedValues?.isNotEmpty == true; + if( unit == null && !valuesValid ){ + throw new ArgumentError("$name: at least unit or allowedValues should be valid"); + } + } @override - String toString() => 'var($name: $unit)'; + String toString() => 'var($name: $unit${allowedValues != null ? ' allowed: $allowedValues' : ''})'; @override bool operator ==(Object other) => @@ -114,10 +120,11 @@ class VariableSpec { other is VariableSpec && runtimeType == other.runtimeType && unit == other.unit && - name == other.name; + name == other.name && + const DeepCollectionEquality().equals(allowedValues, other.allowedValues); @override - int get hashCode => Object.hash(unit, name); + int get hashCode => Object.hash(unit, name, allowedValues != null ? const DeepCollectionEquality().hash(allowedValues!) : 0); } class Formula { @@ -196,8 +203,25 @@ class Formula { factory Formula.fromSet(Map theSet) { VariableSpec parseVar(Map varSpec) { String name = SetUtils.stringValue(varSpec, "name"); - String unit = SetUtils.stringValue(varSpec, "unit"); - return VariableSpec(name: name, unit: unit); + String? unit; + if (varSpec.containsKey("unit")) { + unit = SetUtils.stringValue(varSpec, "unit"); + } + final allowed = varSpec['values'] as List?; + 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, + allowedValues: allowed?.toList(growable: false), + ); } String name = SetUtils.stringValue(theSet, "name"); diff --git a/test/d4rt_test.dart b/test/d4rt_test.dart index bc68e6c..db0aeb4 100644 --- a/test/d4rt_test.dart +++ b/test/d4rt_test.dart @@ -29,6 +29,7 @@ main(){ } """; final interpreter = D4rt(); + interpreter.grant(FilesystemPermission.readPath("/etc/passwd")); final result = interpreter.execute(source: completeSource); expect(result, contains("root")); diff --git a/test/formula_evaluator_test.dart b/test/formula_evaluator_test.dart index 727759f..0b4c04d 100644 --- a/test/formula_evaluator_test.dart +++ b/test/formula_evaluator_test.dart @@ -152,7 +152,7 @@ void main() { d4rtCode: 'force = x;', ); - expect(evaluator.getOutputVariableName(formula), 'force'); + expect(formula.output.name, 'force'); }); test( @@ -165,7 +165,7 @@ void main() { d4rtCode: 'force = x;', ); - expect(evaluator.getOutputVariableMagnitude(formula), 'Newton'); + expect(formula.output.unit, 'Newton'); }, ); @@ -177,8 +177,8 @@ void main() { d4rtCode: 'result = x;', ); - expect(evaluator.getOutputVariableName(validFormula), 'result'); - expect(evaluator.getOutputVariableMagnitude(validFormula), 'Newton'); + expect(validFormula.output.name, 'result'); + expect(validFormula.output.unit, 'Newton'); }); });