Tests passing, all formulas validated

This commit is contained in:
Álvaro González 2025-08-22 17:47:06 +02:00
parent eb38cfcb0e
commit 75fad84cac
5 changed files with 103 additions and 102 deletions

2
.gitignore vendored
View file

@ -11,3 +11,5 @@
.local/ .local/
.zcompdump .zcompdump
.zshrc .zshrc
.idea
/d4rt-formulas.iml

View file

@ -5,7 +5,6 @@ A comprehensive command-line application for managing and computing mathematical
This project uses dart language, and flutter framework. It leverages d4rt library to execute formulas. This project uses dart language, and flutter framework. It leverages d4rt library to execute formulas.
# Development guidelines # Development guidelines
If you are a contributor or an agent, please follow [CLAUDE.md](./CLAUDE.md) for development guidelines. If you are a contributor or an agent, please follow [CLAUDE.md](./CLAUDE.md) for development guidelines.

View file

@ -32,7 +32,7 @@ class FormulaEvaluator {
/// Evaluates a formula with the given input values. /// Evaluates a formula with the given input values.
/// ///
/// The [formula] must have exactly one output variable. /// The [formula] must have exactly one output variable (validated during construction).
/// The [inputValues] map must contain values for all input variables defined /// The [inputValues] map must contain values for all input variables defined
/// in the formula. /// in the formula.
/// ///
@ -48,11 +48,9 @@ class FormulaEvaluator {
/// Returns the computed value of the single output variable. /// Returns the computed value of the single output variable.
/// ///
/// Throws [FormulaEvaluationException] if: /// Throws [FormulaEvaluationException] if:
/// - The formula has zero or more than one output variable
/// - Required input variables are missing /// - Required input variables are missing
/// - The d4rt code execution fails /// - The d4rt code execution fails
dynamic evaluate(Formula formula, Map<String, dynamic> inputValues) { dynamic evaluate(Formula formula, Map<String, dynamic> inputValues) {
_validateFormula(formula);
_validateInputValues(formula, inputValues); _validateInputValues(formula, inputValues);
try { try {
@ -71,16 +69,6 @@ class FormulaEvaluator {
} }
} }
/// Validates that the formula has exactly one output variable
void _validateFormula(Formula formula) {
if (formula.output.length != 1) {
throw FormulaEvaluationException(
'Formula "${formula.name}" must have exactly one output variable, '
'but has ${formula.output.length}',
);
}
}
/// Validates that all required input variables are provided /// Validates that all required input variables are provided
void _validateInputValues(Formula formula, Map<String, dynamic> inputValues) { void _validateInputValues(Formula formula, Map<String, dynamic> inputValues) {
final missingVars = <String>[]; final missingVars = <String>[];
@ -101,13 +89,13 @@ class FormulaEvaluator {
/// Gets the name of the single output variable from the formula /// Gets the name of the single output variable from the formula
String getOutputVariableName(Formula formula) { String getOutputVariableName(Formula formula) {
_validateFormula(formula); // Formula construction already ensures exactly one output variable
return formula.output.keys.first; return formula.output.keys.first;
} }
/// Gets the magnitude of the single output variable from the formula /// Gets the magnitude of the single output variable from the formula
String getOutputVariableMagnitude(Formula formula) { String getOutputVariableMagnitude(Formula formula) {
_validateFormula(formula); // Formula construction already ensures exactly one output variable
return formula.output.values.first.magnitude; return formula.output.values.first.magnitude;
} }

View file

@ -46,12 +46,26 @@ class Formula {
final Map<String, VariableSpec> output; // Supports multiple output variables final Map<String, VariableSpec> output; // Supports multiple output variables
final String d4rtCode; final String d4rtCode;
const Formula({ Formula({
required this.name, required this.name,
required this.input, required this.input,
required this.output, required this.output,
required this.d4rtCode, required this.d4rtCode,
}); }){
validate();
}
validate() {
if (name.trim().isEmpty) {
throw ArgumentError('Formula name cannot be empty');
}
if (output.length != 1) {
throw ArgumentError(
'Formula "$name" must have exactly one output variable, '
'but has ${output.length}',
);
}
}
factory Formula.fromJson(Map<String, dynamic> json) { factory Formula.fromJson(Map<String, dynamic> json) {
final name = json['name']; final name = json['name'];

View file

@ -18,9 +18,7 @@ void main() {
'm': VariableSpec(magnitude: 'mass'), 'm': VariableSpec(magnitude: 'mass'),
'a': VariableSpec(magnitude: 'acceleration'), 'a': VariableSpec(magnitude: 'acceleration'),
}, },
output: { output: {'F': VariableSpec(magnitude: 'force')},
'F': VariableSpec(magnitude: 'force'),
},
d4rtCode: ''' d4rtCode: '''
main() { main() {
return a * m; return a * m;
@ -29,8 +27,8 @@ void main() {
); );
final result = evaluator.evaluate(formula, { final result = evaluator.evaluate(formula, {
'm': 10.0, // 10 kg 'm': 10.0, // 10 kg
'a': 9.8, // 9.8 m/s² 'a': 9.8, // 9.8 m/s²
}); });
expect(result, 98.0); // F = m * a = 10 * 9.8 = 98 N expect(result, 98.0); // F = m * a = 10 * 9.8 = 98 N
@ -43,9 +41,7 @@ void main() {
'x': VariableSpec(magnitude: 'scalar'), 'x': VariableSpec(magnitude: 'scalar'),
'y': VariableSpec(magnitude: 'scalar'), 'y': VariableSpec(magnitude: 'scalar'),
}, },
output: { output: {'result': VariableSpec(magnitude: 'scalar')},
'result': VariableSpec(magnitude: 'scalar'),
},
d4rtCode: ''' d4rtCode: '''
main() { main() {
return x + y; return x + y;
@ -53,10 +49,7 @@ void main() {
''', ''',
); );
final result = evaluator.evaluate(formula, { final result = evaluator.evaluate(formula, {'x': 5, 'y': 3});
'x': 5,
'y': 3,
});
expect(result, 8); expect(result, 8);
}); });
@ -64,12 +57,8 @@ void main() {
test('handles single input variable', () { test('handles single input variable', () {
final formula = Formula( final formula = Formula(
name: 'Square function', name: 'Square function',
input: { input: {'n': VariableSpec(magnitude: 'scalar')},
'n': VariableSpec(magnitude: 'scalar'), output: {'result': VariableSpec(magnitude: 'scalar')},
},
output: {
'result': VariableSpec(magnitude: 'scalar'),
},
d4rtCode: ''' d4rtCode: '''
main() { main() {
return n * n; return n * n;
@ -89,9 +78,7 @@ void main() {
'b': VariableSpec(magnitude: 'scalar'), 'b': VariableSpec(magnitude: 'scalar'),
'c': VariableSpec(magnitude: 'scalar'), 'c': VariableSpec(magnitude: 'scalar'),
}, },
output: { output: {'discriminant': VariableSpec(magnitude: 'scalar')},
'discriminant': VariableSpec(magnitude: 'scalar'),
},
d4rtCode: ''' d4rtCode: '''
main() { main() {
return b * b - 4 * a * c; return b * b - 4 * a * c;
@ -99,11 +86,7 @@ void main() {
''', ''',
); );
final result = evaluator.evaluate(formula, { final result = evaluator.evaluate(formula, {'a': 1, 'b': 5, 'c': 6});
'a': 1,
'b': 5,
'c': 6,
});
expect(result, 1); // b² - 4ac = 25 - 24 = 1 expect(result, 1); // b² - 4ac = 25 - 24 = 1
}); });
@ -118,9 +101,7 @@ void main() {
'a': VariableSpec(magnitude: 'scalar'), 'a': VariableSpec(magnitude: 'scalar'),
'b': VariableSpec(magnitude: 'scalar'), 'b': VariableSpec(magnitude: 'scalar'),
}, },
output: { output: {'result': VariableSpec(magnitude: 'scalar')},
'result': VariableSpec(magnitude: 'scalar'),
},
d4rtCode: 'main() { return a + b + z; }', d4rtCode: 'main() { return a + b + z; }',
); );
@ -136,9 +117,7 @@ void main() {
'a': VariableSpec(magnitude: 'scalar'), 'a': VariableSpec(magnitude: 'scalar'),
'y': VariableSpec(magnitude: 'scalar'), 'y': VariableSpec(magnitude: 'scalar'),
}, },
output: { output: {'result': VariableSpec(magnitude: 'scalar')},
'result': VariableSpec(magnitude: 'scalar'),
},
d4rtCode: ''' d4rtCode: '''
main() { main() {
// Variables: a=1, y=2, z=3 // Variables: a=1, y=2, z=3
@ -147,47 +126,44 @@ void main() {
''', ''',
); );
final result = evaluator.evaluate(formula, { final result = evaluator.evaluate(formula, {'z': 3, 'a': 1, 'y': 2});
'z': 3,
'a': 1,
'y': 2,
});
expect(result, 123); // 1*100 + 2*10 + 3 = 123 expect(result, 123); // 1*100 + 2*10 + 3 = 123
}); });
}); });
group('Error handling', () { group('Error handling', () {
test('throws exception for formula with no output variables', () { test(
final formula = Formula( 'throws exception for formula with no output variables during construction',
name: 'Invalid formula', () {
input: {'x': VariableSpec(magnitude: 'scalar')}, expect(() {
output: {}, // No output variables return Formula(
d4rtCode: 'main() { return x; }', name: 'Invalid formula',
); input: {'x': VariableSpec(magnitude: 'scalar')},
output: {}, // No output variables
d4rtCode: 'main() { return x; }',
);
}, throwsA(isA<ArgumentError>()));
},
);
expect( test(
() => evaluator.evaluate(formula, {'x': 1}), 'throws exception for formula with multiple output variables during construction',
throwsA(isA<FormulaEvaluationException>()), () {
); expect(
}); () => Formula(
name: 'Invalid formula',
test('throws exception for formula with multiple output variables', () { input: {'x': VariableSpec(magnitude: 'scalar')},
final formula = Formula( output: {
name: 'Invalid formula', 'y': VariableSpec(magnitude: 'scalar'),
input: {'x': VariableSpec(magnitude: 'scalar')}, 'z': VariableSpec(magnitude: 'scalar'),
output: { },
'y': VariableSpec(magnitude: 'scalar'), d4rtCode: 'main() { return x; }',
'z': VariableSpec(magnitude: 'scalar'), ),
}, throwsA(isA<ArgumentError>()),
d4rtCode: 'main(x) { return x; }', );
); },
);
expect(
() => evaluator.evaluate(formula, {'x': 1}),
throwsA(isA<FormulaEvaluationException>()),
);
});
test('throws exception for missing input variables', () { test('throws exception for missing input variables', () {
final formula = Formula( final formula = Formula(
@ -233,33 +209,55 @@ void main() {
expect(evaluator.getOutputVariableName(formula), 'force'); expect(evaluator.getOutputVariableName(formula), 'force');
}); });
test('getOutputVariableMagnitude returns the output variable magnitude', () { test(
final formula = Formula( 'getOutputVariableMagnitude returns the output variable magnitude',
name: 'Test', () {
final formula = Formula(
name: 'Test',
input: {'x': VariableSpec(magnitude: 'scalar')},
output: {'force': VariableSpec(magnitude: 'Newton')},
d4rtCode: 'main() { return x; }',
);
expect(evaluator.getOutputVariableMagnitude(formula), 'Newton');
},
);
test('utility methods work correctly with valid formulas', () {
final validFormula = Formula(
name: 'Valid Formula',
input: {'x': VariableSpec(magnitude: 'scalar')}, input: {'x': VariableSpec(magnitude: 'scalar')},
output: {'force': VariableSpec(magnitude: 'Newton')}, output: {'result': VariableSpec(magnitude: 'Newton')},
d4rtCode: 'main() { return x; }', d4rtCode: 'main() { return x; }',
); );
expect(evaluator.getOutputVariableMagnitude(formula), 'Newton'); expect(evaluator.getOutputVariableName(validFormula), 'result');
expect(evaluator.getOutputVariableMagnitude(validFormula), 'Newton');
}); });
test('utility methods throw exception for invalid formulas', () { test('validates formula construction with factory method', () {
final invalidFormula = Formula( // Test the factory method validation
name: 'Invalid', expect(
input: {'x': VariableSpec(magnitude: 'scalar')}, () => Formula(
output: {}, // No output variables name: 'Invalid',
d4rtCode: 'main() { return x; }', input: {'x': VariableSpec(magnitude: 'scalar')},
output: {}, // No output variables
d4rtCode: 'main() { return x; }',
),
throwsA(isA<ArgumentError>()),
); );
expect( expect(
() => evaluator.getOutputVariableName(invalidFormula), () => Formula(
throwsA(isA<FormulaEvaluationException>()), name: 'Invalid',
); input: {'x': VariableSpec(magnitude: 'scalar')},
output: {
expect( 'y': VariableSpec(magnitude: 'scalar'),
() => evaluator.getOutputVariableMagnitude(invalidFormula), 'z': VariableSpec(magnitude: 'scalar'),
throwsA(isA<FormulaEvaluationException>()), },
d4rtCode: 'main() { return x; }',
),
throwsA(isA<ArgumentError>()),
); );
}); });
}); });