Merge branch 'apgar-test'

This commit is contained in:
Álvaro González 2026-01-25 19:07:44 +01:00
commit b053fc3900
31 changed files with 306 additions and 152 deletions

21
Dockerfile Normal file
View file

@ -0,0 +1,21 @@
# Use the official Flutter SDK image
FROM ghcr.io/cirruslabs/flutter:stable
# Install cmake, ninja, clang, pkg-config for flutter linux
RUN apt-get update && apt-get install -y cmake ninja-build clang pkg-config libgtk-3-dev liblzma-dev
WORKDIR /app
# Configure cache directories
ENV PUB_CACHE=/cache/pub-cache
ENV GRADLE_USER_HOME=/cache/gradle-cache
RUN mkdir -p $PUB_CACHE $GRADLE_USER_HOME
# Copy pubspec files and get dependencies
# COPY pubspec.yaml pubspec.lock ./
# RUN flutter pub get
# Copy the rest of the application code and build
# Commented out to avoid building the app during image creation, this will be handled externally by makefile
# COPY . .
# RUN flutter build apk --release

25
Makefile Normal file
View file

@ -0,0 +1,25 @@
flutter-container-exec = podman-compose run --entrypoint "$(1)" flutter
all: clean-podman build-linux-debug-podman build-linux-debug-podman
build-podman:
podman-compose build
clean-podman: build-podman
$(call flutter-container-exec, flutter clean)
pub-get-podman: build-podman
$(call flutter-container-exec, flutter pub get)
build-android-release-podman: pub-get-podman
$(call flutter-container-exec, flutter build apk --release)
build-linux-debug-podman: pub-get-podman
$(call flutter-container-exec, flutter build linux --debug)
run-linux-debug: build-linux-debug-podman
build/linux/x64/debug/bundle/d4rt_formulas
run-web-release-podman: build-web-release-podman
cd build/web && python3 -m http.server 8080

View file

@ -1,3 +1,5 @@
https://github.com/Shahxad-Akram/flutter_tex/blob/master/example/lib/tex_view_markdown_example.dart
# Math Formulae Manager # Math Formulae Manager
A comprehensive command-line application for managing and computing mathematical formulas across various disciplines including mathematics, physics, medicine, and engineering. A comprehensive command-line application for managing and computing mathematical formulas across various disciplines including mathematics, physics, medicine, and engineering.
@ -123,15 +125,11 @@ Each formula includes:
- **Images** - Visual diagrams, graphs, or illustrations - **Images** - Visual diagrams, graphs, or illustrations
- **Examples** - Sample calculations and use cases - **Examples** - Sample calculations and use cases
## Project Structure
- `bin/` - Main executable and entry point
- `lib/` - Core library code and formula engine
- `test/` - Unit tests and formula validation tests
## Getting Started ## Getting Started
[Installation and usage instructions to be added] This project uses `flutter`, so a valid installation is needed in order to build it.
For convenience, a containerized build is provided. It is based on `podman` and `podman-compose`. See [Makefile](Makefile) for details.
## Contributing ## Contributing

View file

@ -120,4 +120,33 @@ Where:
"d4rtCode": "F = m * a;", "d4rtCode": "F = m * a;",
"tags": ["physics", "mechanics", "newton"] "tags": ["physics", "mechanics", "newton"]
}, },
// Apgar Score
{
"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", "values": ["Absent", "< 100 bpm>", "> 100 bpm"] },
{"name": "Breathing", "values": ["Absent", "Weak, irregular", "Strong, robust cry"] },
{"name": "MuscleTone", "values": ["None", "Some", "Flexed arms/leg, resists extension"] },
{"name": "Reflexes", "values": ["No response", "Grimace on aggressive stimulation", "Cry on stimulation"] },
{"name": "SkinColor", "values": ["Blue or pale", "Blue extremities, pink body", "Pink"] }
],
"output": {"name": "Result", "unit": "scalar"},
"d4rtCode": """
var total = HeartRate + Breathing + MuscleTone + Reflexes + SkinColor;
late var interpretation;
if( total < 4 ) {
interpretation = 'Critical condition';
}
else if( total < 7 ){
interpretation = 'Needs assistance';
}
else {
interpretation = 'Normal';
}
Result = 'Score: \$total - \$interpretation';
""",
"tags": ["medical", "pediatrics", "assessment"]
}
] ]

View file

@ -0,0 +1,3 @@
[
{"name": "scalar", "symbol": "", "isBase": true},
]

13
docker-compose.yml Normal file
View file

@ -0,0 +1,13 @@
version: '3.8'
services:
flutter:
build:
context: .
dockerfile: Dockerfile
image: d4rt-formulas-builder
volumes:
- ./.build-container-cache:/cache:z
- .:/app:z # Link the current directory to /app in the container
environment:
- FLUTTER_FLAVOR=prod # Example environment variable, adjust as needed

View file

@ -37,8 +37,8 @@ void main() {
print(' Mass: 10.0 kg'); print(' Mass: 10.0 kg');
print(' Acceleration: 9.8 m/s²'); print(' Acceleration: 9.8 m/s²');
print(' Calculated Force: $force N'); print(' Calculated Force: $force N');
print(' Output variable: ${evaluator.getOutputVariableName(newtonFormula)}'); print(' Output variable: ${newtonFormula.output.name}');
print(' Output magnitude: ${evaluator.getOutputVariableMagnitude(newtonFormula)}'); print(' Output magnitude: ${newtonFormula.output.unit}');
} catch (e) { } catch (e) {
print(' Error: $e'); print(' Error: $e');
} }

View file

@ -1,11 +0,0 @@
# launch as: firejail --profile=firejail-warp-terminal.profile /opt/warpdotdev/warp-terminal/warp
private /home/alvaro/repos/d4rt_formulas/
blacklist /datos-1T
blacklist /datos-luks/
blacklist /media
# net none # disable network
# noroot # don't run as root
caps.drop all # drop Linux capabilities
# seccomp # enable syscall filtering
First version of a formula widget

View file

@ -1,6 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import '../formula_models.dart'; import '../formula_models.dart';
import '../formula_evaluator.dart'; import '../formula_evaluator.dart';
@ -11,24 +9,22 @@ class FormulaScreen extends StatefulWidget {
final Formula formula; final Formula formula;
final Corpus corpus; final Corpus corpus;
const FormulaScreen({ const FormulaScreen({super.key, required this.formula, required this.corpus});
super.key,
required this.formula,
required this.corpus,
});
@override @override
State<FormulaScreen> createState() => _FormulaScreenState(); State<FormulaScreen> createState() => _FormulaScreenState();
} }
// TODO: Create VariableWidget. Depending on VariableSpec.values, it can be a ValueDropdown or a D4rtEditingController
// The d4rtValue will be FormulaResult?
//// Start of D4rtEditingController class //// //// Start of D4rtEditingController class ////
class D4rtEditingController extends TextEditingController { class D4rtEditingController extends TextEditingController {
String? _lastError; String? _lastError;
String? get lastError => _lastError; String? get lastError => _lastError;
FormulaResult? _lastValue; FormulaResult? _lastValue;
D4rtEditingController({String? text}) : super(text: text); D4rtEditingController({super.text});
bool validate() { bool validate() {
try { try {
@ -44,9 +40,8 @@ class D4rtEditingController extends TextEditingController {
} }
} }
get d4rtValue => _lastValue; FormulaResult? get d4rtValue => _lastValue;
@override
set text(String newText) { set text(String newText) {
super.text = newText; super.text = newText;
validate(); validate();
@ -97,38 +92,51 @@ class _FormulaScreenState extends State<FormulaScreen> {
final inputValues = <String, dynamic>{}; final inputValues = <String, dynamic>{};
for (final input in widget.formula.input) { for (final input in widget.formula.input) {
final controller = _inputControllers[input.name]!; final controller = _inputControllers[input.name]!;
if( controller.d4rtValue == null ){ if (controller.d4rtValue == null) {
throw FormulaEvaluationException( "Field ${input.name} is invalid" ); //throw FormulaEvaluationException("Field ${input.name} is invalid");
_result = "";
return;
} }
final value = controller.d4rtValue.value;
// Convert input to base unit if needed late final dynamic convertedValue;
// Always convert from dropdown unit to variable's base unit
late final convertedValue; switch (controller.d4rtValue) {
if( value is Number ) { case NumberResult nr:
convertedValue = widget.corpus.convert( // Convert input to base unit if needed
value, // Always convert from dropdown unit to variable's base unit
_selectedUnits[input.name]!, if (input.unit != null) {
input.unit, convertedValue = widget.corpus.convert(
); nr.value,
} _selectedUnits[input.name]!,
else{ input.unit as String,
convertedValue = value; );
} else {
convertedValue = nr.value;
}
case StringResult sr:
convertedValue = sr.value;
default:
throw FormulaEvaluationException(
"Field ${input.name} has unsupported type ${controller.d4rtValue!.runtimeType}",
);
} }
inputValues[input.name] = convertedValue; inputValues[input.name] = convertedValue;
} }
final evaluator = FormulaEvaluator(); final evaluator = FormulaEvaluator();
final result = evaluator.evaluate(widget.formula, inputValues); final result = evaluator.evaluate(widget.formula, inputValues);
// Convert output to selected unit if needed // Convert output to selected unit if needed
_result = widget.corpus.convert( String? unit = widget.formula.output.unit;
result, if (unit != null) {
widget.formula.output.unit, _result = widget.corpus
_selectedOutputUnit!, .convert(result, unit, _selectedOutputUnit!)
).toStringAsFixed(2); .toStringAsFixed(2);
} else {
_result = result;
}
//print( "_evaluateFormula: result:${result} _result:${_result}"); //print( "_evaluateFormula: result:${result} _result:${_result}");
@ -136,7 +144,7 @@ class _FormulaScreenState extends State<FormulaScreen> {
} catch (e, stack) { } catch (e, stack) {
debugPrint('Formula evaluation error: $e'); debugPrint('Formula evaluation error: $e');
debugPrint('Stack trace: $stack'); debugPrint('Stack trace: $stack');
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Error: ${e.toString()}\n${stack.toString()}'), content: Text('Error: ${e.toString()}\n${stack.toString()}'),
@ -149,9 +157,7 @@ class _FormulaScreenState extends State<FormulaScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(title: Text(widget.formula.name)),
title: Text(widget.formula.name),
),
body: Form( body: Form(
key: _formKey, key: _formKey,
child: Padding( child: Padding(
@ -170,7 +176,7 @@ class _FormulaScreenState extends State<FormulaScreen> {
} }
Widget _buildDescriptionSection() { Widget _buildDescriptionSection() {
if (widget.formula.description == null || if (widget.formula.description == null ||
widget.formula.description!.isEmpty) { widget.formula.description!.isEmpty) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
@ -180,9 +186,9 @@ class _FormulaScreenState extends State<FormulaScreen> {
children: [ children: [
Text( Text(
'Description', 'Description',
style: Theme.of(context).textTheme.titleMedium?.copyWith( style: Theme.of(
fontWeight: FontWeight.bold, context,
), ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Container( Container(
@ -207,9 +213,9 @@ class _FormulaScreenState extends State<FormulaScreen> {
children: [ children: [
Text( Text(
'Input Variables', 'Input Variables',
style: Theme.of(context).textTheme.titleMedium?.copyWith( style: Theme.of(
fontWeight: FontWeight.bold, context,
), ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
...widget.formula.input.map((variable) => _buildVariableRow(variable)), ...widget.formula.input.map((variable) => _buildVariableRow(variable)),
@ -223,9 +229,9 @@ class _FormulaScreenState extends State<FormulaScreen> {
children: [ children: [
Text( Text(
'Result', 'Result',
style: Theme.of(context).textTheme.titleMedium?.copyWith( style: Theme.of(
fontWeight: FontWeight.bold, context,
), ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Row( Row(
@ -280,6 +286,7 @@ class _FormulaScreenState extends State<FormulaScreen> {
decoration: const InputDecoration( decoration: const InputDecoration(
border: UnderlineInputBorder(), border: UnderlineInputBorder(),
), ),
autovalidateMode: AutovalidateMode.always,
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Required'; return 'Required';

View file

@ -34,7 +34,7 @@ class Corpus{
if( checkUnits ){ if( checkUnits ){
for( final inputVar in formula.input + [formula.output] ){ 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}"); throw ArgumentError( "Unit not found: ${inputVar.unit}");
} }
} }
@ -58,6 +58,10 @@ class Corpus{
return _allFormulas.values.toList(growable:false); return _allFormulas.values.toList(growable:false);
} }
Formula? getFormula(String name) {
return _allFormulas.get(name);
}
final Multimap<String, String> _baseToUnits = Multimap.create(); final Multimap<String, String> _baseToUnits = Multimap.create();
final Map<String, UnitSpec> _allUnits = {}; final Map<String, UnitSpec> _allUnits = {};
@ -71,7 +75,10 @@ class Corpus{
} }
} }
List<String> unitsOfSameMagnitude(String unit){ List<String> unitsOfSameMagnitude(String? unit){
if( unit == null ){
return ["scalar"];
}
final base = getUnit(unit).baseUnit; final base = getUnit(unit).baseUnit;
return _baseToUnits[base] as List<String>; return _baseToUnits[base] as List<String>;
} }
@ -87,7 +94,7 @@ class Corpus{
String _converterFromCodeStringAsExpression(Number x, String codeString) { String _converterFromCodeStringAsExpression(Number x, String codeString) {
final buffer = StringBuffer(); final buffer = StringBuffer();
buffer.writeln("final x = ${x};"); buffer.writeln("final x = $x;");
buffer.writeln("main(){return $codeString;}"); buffer.writeln("main(){return $codeString;}");
final code = buffer.toString(); final code = buffer.toString();
return code; return code;
@ -95,7 +102,7 @@ class Corpus{
String _converterFromCodeStringAsStatement(Number x, String codeString) { String _converterFromCodeStringAsStatement(Number x, String codeString) {
final buffer = StringBuffer(); final buffer = StringBuffer();
buffer.writeln("final x = ${x};"); buffer.writeln("final x = $x;");
buffer.writeln("main(){ $codeString; return x; }"); buffer.writeln("main(){ $codeString; return x; }");
final code = buffer.toString(); final code = buffer.toString();
return code; return code;
@ -113,7 +120,7 @@ class Corpus{
} }
final ret = _convertUsingCode(x, unit.codeFromUnitToBase as String); final ret = _convertUsingCode(x, unit.codeFromUnitToBase as String);
return ret as Number; return ret;
} }
Number _convertFromBase(Number x, String toUnit) { Number _convertFromBase(Number x, String toUnit) {
@ -128,7 +135,7 @@ class Corpus{
} }
final ret = _convertUsingCode(x, unit.codeFromBaseToUnit as String); final ret = _convertUsingCode(x, unit.codeFromBaseToUnit as String);
return ret as Number; return ret;
} }
@ -140,17 +147,19 @@ class Corpus{
final ret = _evaluate(completeSourceExpression); final ret = _evaluate(completeSourceExpression);
return ret; return ret;
} }
catch(e1,stack){ catch(e1, stack1){
try{ try{
completeSourceStatement = _converterFromCodeStringAsStatement(x, code); completeSourceStatement = _converterFromCodeStringAsStatement(x, code);
final ret = _evaluate(completeSourceStatement); final ret = _evaluate(completeSourceStatement);
return ret; return ret;
} }
catch( e2, stack ){ catch( e2, stack2 ){
print(completeSourceExpression); print(completeSourceExpression);
print(e1); print(e1);
print(stack1);
print(completeSourceStatement); print(completeSourceStatement);
print(e2); print(e2);
print(stack2);
throw FormulaEvaluationException( "Evaluation as statement and expression failed" ); throw FormulaEvaluationException( "Evaluation as statement and expression failed" );
} }
} }

View file

@ -1,41 +1,48 @@
import 'dart:async';
import 'dart:convert' show utf8; import 'dart:convert' show utf8;
import 'package:flutter/services.dart' show rootBundle;
import 'package:resource_portable/resource_portable.dart' show Resource; import 'package:resource_portable/resource_portable.dart' show Resource;
import '../corpus.dart'; import '../corpus.dart';
import '../formula_models.dart'; import '../formula_models.dart';
Future<Corpus> createDefaultCorpus() async{ Future<Corpus> createDefaultCorpus() async{
final corpus = Corpus(); final corpus = Corpus();
Future<String> loadResourceAsString(String path) async {
return await rootBundle.loadString(path, cache: false);
}
Future<void> loadUnits() async { Future<void> loadUnits() async {
final unitResources = [ final unitResources = [
"lib/defaults/units/angle.d4rt.units", "assets/units/angle.d4rt.units",
"lib/defaults/units/area.d4rt.units", "assets/units/area.d4rt.units",
"lib/defaults/units/distance.d4rt.units", "assets/units/distance.d4rt.units",
"lib/defaults/units/energy.d4rt.units", "assets/units/energy.d4rt.units",
"lib/defaults/units/force.d4rt.units", "assets/units/force.d4rt.units",
"lib/defaults/units/mass.d4rt.units", "assets/units/mass.d4rt.units",
"lib/defaults/units/pressure.d4rt.units", "assets/units/pressure.d4rt.units",
"lib/defaults/units/temperature.d4rt.units", "assets/units/scalar.d4rt.units",
"lib/defaults/units/time.d4rt.units", "assets/units/temperature.d4rt.units",
"lib/defaults/units/velocity.d4rt.units", "assets/units/time.d4rt.units",
"assets/units/velocity.d4rt.units",
]; ];
for (final unitRes in unitResources) { for (final unitRes in unitResources) {
final resource = Resource(unitRes); final literal = await loadResourceAsString(unitRes);
final literal = await resource.readAsString(encoding: utf8);
final units = UnitSpec.fromArrayStringLiteral(literal); final units = UnitSpec.fromArrayStringLiteral(literal);
corpus.loadUnits(units); corpus.loadUnits(units);
} }
} }
Future<void> loadFormulas() async { Future<void> loadFormulas() async {
final formulaResources = ["lib/defaults/formulas.d4rt"]; final formulaResources = ["assets/formulas/formulas.d4rt"];
for (final formRes in formulaResources) { for (final formRes in formulaResources) {
final resource = Resource(formRes); final literal = await loadResourceAsString(formRes);
final literal = await resource.readAsString(encoding: utf8);
final formulas = Formula.fromArrayStringLiteral(literal); final formulas = Formula.fromArrayStringLiteral(literal);
corpus.loadFormulas(formulas); corpus.loadFormulas(formulas);
} }

View file

@ -76,14 +76,14 @@ class FormulaEvaluator {
final d4rtInterpreter = interpreter ?? createDefaultInterpreter(); final d4rtInterpreter = interpreter ?? createDefaultInterpreter();
prepareInterpreter(d4rtInterpreter); prepareInterpreter(d4rtInterpreter);
final d4rtCode = """ final d4rtCode = """
${d4rtImports} $d4rtImports
main() main()
{ {
late var result; late var result;
result = $code; result = $code;
return result; return result;
}"""; }""";
//print("evaluateExpression:\n$d4rtCode"); print("evaluateExpression:\n$d4rtCode");
final result = d4rtInterpreter.execute(source: d4rtCode); final result = d4rtInterpreter.execute(source: d4rtCode);
switch ( result ){ switch ( result ){
case int value: case int value:
@ -100,8 +100,17 @@ class FormulaEvaluator {
dynamic evaluate(Formula formula, Map<String, dynamic> inputValues) { dynamic evaluate(Formula formula, Map<String, dynamic> inputValues) {
_validateInputValues(formula, inputValues); _validateInputValues(formula, inputValues);
final completeSource = _buildCompleteSource(formula, inputValues); final completeSource = _buildCompleteSource(formula, inputValues);
final result = _interpreter.execute(source: completeSource); try {
return result; final result = _interpreter.execute(source: completeSource);
return result;
}
catch (e) {
print( "Error evaluating formula source:\n$completeSource" );
throw FormulaEvaluationException(
'Error evaluating formula "${formula.name}": $e',
e,
);
}
} }
void _validateInputValues(Formula formula, Map<String, dynamic> inputValues) { void _validateInputValues(Formula formula, Map<String, dynamic> inputValues) {
@ -121,13 +130,7 @@ class FormulaEvaluator {
} }
} }
String getOutputVariableName(Formula formula) {
return formula.output.name;
}
String getOutputVariableMagnitude(Formula formula) {
return formula.output.unit;
}
List<String> getInputVariableOrder(Formula formula) { List<String> getInputVariableOrder(Formula formula) {
return formula.inputVarNames()..sort(); return formula.inputVarNames()..sort();
@ -166,9 +169,9 @@ class FormulaEvaluator {
} }
} }
buffer.writeln(""" buffer.writeln("""
late var ${getOutputVariableName(formula)}; late var ${formula.output.name};
${formula.d4rtCode} ${formula.d4rtCode}
return ${getOutputVariableName(formula)}; return ${formula.output.name};
} }
""" """
); );

View file

@ -101,12 +101,18 @@ class UnitSpec {
class VariableSpec { class VariableSpec {
final String name; final String name;
final String unit; final String? unit;
final List<dynamic>? values;
VariableSpec({required this.name, required this.unit}); VariableSpec({required this.name, this.unit, this.values}){
final valuesValid = values != null && values?.isNotEmpty == true;
if( unit == null && !valuesValid ){
throw ArgumentError("$name: at least unit or allowedValues should be valid");
}
}
@override @override
String toString() => 'var($name: $unit)'; String toString() => 'var($name: $unit${values != null ? ' allowed: $values' : ''})';
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@ -114,10 +120,11 @@ class VariableSpec {
other is VariableSpec && other is VariableSpec &&
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
unit == other.unit && unit == other.unit &&
name == other.name; name == other.name &&
const DeepCollectionEquality().equals(values, other.values);
@override @override
int get hashCode => Object.hash(unit, name); int get hashCode => Object.hash(unit, name, values != null ? const DeepCollectionEquality().hash(values!) : 0);
} }
class Formula { class Formula {
@ -139,7 +146,7 @@ class Formula {
validate(); validate();
} }
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');
} }
@ -196,8 +203,25 @@ class Formula {
factory Formula.fromSet(Map<Object?, Object?> theSet) { factory Formula.fromSet(Map<Object?, Object?> theSet) {
VariableSpec parseVar(Map<Object?, Object?> varSpec) { VariableSpec parseVar(Map<Object?, Object?> varSpec) {
String name = SetUtils.stringValue(varSpec, "name"); String name = SetUtils.stringValue(varSpec, "name");
String unit = SetUtils.stringValue(varSpec, "unit"); String? unit;
return VariableSpec(name: name, unit: 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) {
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,
values: allowed?.toList(growable: false),
);
} }
String name = SetUtils.stringValue(theSet, "name"); String name = SetUtils.stringValue(theSet, "name");

View file

@ -6,6 +6,7 @@ import 'corpus.dart';
import 'defaults/default_corpus.dart'; import 'defaults/default_corpus.dart';
void main() { void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MaterialApp( runApp(MaterialApp(
home: FutureBuilder<Corpus>( home: FutureBuilder<Corpus>(
future: createDefaultCorpus(), future: createDefaultCorpus(),

View file

@ -109,10 +109,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: crypto name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.6" version: "3.0.7"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -125,18 +125,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: d4rt name: d4rt
sha256: "1eb626145e2ed97332f9e6e842e626f973d3969ce30e2794efb4744bd8aeba63" sha256: eff6a10f31e9e5b60b99146a33204c5f2d74e20ac3eeb14132d8a8ed0921c6e1
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.7" version: "0.1.9"
equatable: equatable:
dependency: transitive dependency: transitive
description: description:
name: equatable name: equatable
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.7" version: "2.0.8"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -244,10 +244,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: http name: http
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.0" version: "1.6.0"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@ -356,10 +356,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.16.0" version: "1.17.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@ -585,34 +585,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: "5c8b6c2d89a78f5a1cca70a73d9d5f86c701b36b42f9c9dac7bad592113c28e9" sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.24" version: "6.3.28"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_ios name: url_launcher_ios
sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.4" version: "6.3.6"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_linux name: url_launcher_linux
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.1" version: "3.2.2"
url_launcher_macos: url_launcher_macos:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_macos name: url_launcher_macos
sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.3" version: "3.2.5"
url_launcher_platform_interface: url_launcher_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -625,18 +625,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.1" version: "2.4.2"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_windows name: url_launcher_windows
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.4" version: "3.1.5"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -657,10 +657,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: watcher name: watcher
sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.4" version: "1.2.1"
web: web:
dependency: transitive dependency: transitive
description: description:
@ -702,5 +702,5 @@ packages:
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
sdks: sdks:
dart: ">=3.9.0 <4.0.0" dart: ">=3.10.7 <4.0.0"
flutter: ">=3.35.0" flutter: ">=3.38.0"

View file

@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: ^3.8.1 sdk: ^3.10.7
# Dependencies specify other packages that your package needs in order to work. # Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions # To automatically upgrade your package dependencies to the latest versions
@ -50,8 +50,8 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your # activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint # package. See that file for information about deactivating specific lint
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^6.0.0 flutter_lints:
test: ^1.26.3 test:
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec
@ -68,6 +68,9 @@ flutter:
# assets: # assets:
# - images/a_dot_burr.jpeg # - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg # - images/a_dot_ham.jpeg
assets:
- assets/units/
- assets/formulas/
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images # https://flutter.dev/to/resolution-aware-images

View file

@ -3,7 +3,7 @@ import 'package:d4rt/d4rt.dart';
import 'dart:math' as Math; import 'dart:math' as Math;
main(){ void main(){
test('Access to Math', () { test('Access to Math', () {
final completeSource = """ final completeSource = """
@ -29,6 +29,7 @@ main(){
} }
"""; """;
final interpreter = D4rt(); final interpreter = D4rt();
interpreter.grant(FilesystemPermission.readPath("/etc/passwd"));
final result = interpreter.execute(source: completeSource); final result = interpreter.execute(source: completeSource);
expect(result, contains("root")); expect(result, contains("root"));

View file

@ -3,7 +3,7 @@ import 'package:d4rt/d4rt.dart';
import 'dart:math' as Math; import 'dart:math' as Math;
main(){ void main(){
test('for dart grammar tests', () { test('for dart grammar tests', () {
}); });

View file

@ -152,7 +152,7 @@ void main() {
d4rtCode: 'force = x;', d4rtCode: 'force = x;',
); );
expect(evaluator.getOutputVariableName(formula), 'force'); expect(formula.output.name, 'force');
}); });
test( test(
@ -165,7 +165,7 @@ void main() {
d4rtCode: 'force = x;', d4rtCode: 'force = x;',
); );
expect(evaluator.getOutputVariableMagnitude(formula), 'Newton'); expect(formula.output.unit, 'Newton');
}, },
); );
@ -177,8 +177,8 @@ void main() {
d4rtCode: 'result = x;', d4rtCode: 'result = x;',
); );
expect(evaluator.getOutputVariableName(validFormula), 'result'); expect(validFormula.output.name, 'result');
expect(evaluator.getOutputVariableMagnitude(validFormula), 'Newton'); expect(validFormula.output.unit, 'Newton');
}); });
}); });

View file

@ -1,16 +1,21 @@
import 'package:d4rt_formulas/corpus.dart'; import 'package:d4rt_formulas/corpus.dart';
import 'package:d4rt_formulas/defaults/default_corpus.dart'; import 'package:d4rt_formulas/defaults/default_corpus.dart';
import 'package:d4rt_formulas/formula_evaluator.dart'; import 'package:d4rt_formulas/formula_evaluator.dart';
import 'package:test/test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:d4rt_formulas/formula_models.dart'; import 'package:d4rt_formulas/formula_models.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized();
Future<Corpus> createTestCorpus() async { Future<Corpus> createTestCorpus() async {
return createDefaultCorpus(); return createDefaultCorpus();
} }
Future<Corpus> testCorpus = createTestCorpus();
test("Parses unit", () { test("Parses unit", () {
final setLiteral = {"name": "kilometer", "symbol": "km", "baseUnit": "meter", "factor": 1000}; final setLiteral = {"name": "kilometer", "symbol": "km", "baseUnit": "meter", "factor": 1000};
final unit = UnitSpec.fromSet(setLiteral); final unit = UnitSpec.fromSet(setLiteral);
@ -23,37 +28,37 @@ void main() {
}); });
test("From km to in", () async { test("From km to in", () async {
final corpus = await createTestCorpus(); final corpus = await testCorpus;
final inches = corpus.convert(1, "kilometer", "inch"); final inches = corpus.convert(1, "kilometer", "inch");
expect( inches, closeTo(39370.078,0.001) ); expect( inches, closeTo(39370.078,0.001) );
}); });
test("From furlong to base", () async { test("From furlong to base", () async {
final corpus = await createTestCorpus(); final corpus = await testCorpus;
final m = corpus.convert(1, "furlong", "meter"); final m = corpus.convert(1, "furlong", "meter");
expect(m,closeTo(201.168,0.001)); expect(m,closeTo(201.168,0.001));
}); });
test("From base to furlong", () async { test("From base to furlong", () async {
final corpus = await createTestCorpus(); final corpus = await testCorpus;
final m = corpus.convert(201.168, "meter", "furlong"); final m = corpus.convert(201.168, "meter", "furlong");
expect(m,closeTo(1,0.001)); expect(m,closeTo(1,0.001));
}); });
test("From C to F", () async { test("From C to F", () async {
final corpus = await createTestCorpus(); final corpus = await testCorpus;
final m = corpus.convert(37, "Celsius", "Fahrenheit"); final m = corpus.convert(37, "Celsius", "Fahrenheit");
expect(m,closeTo(98.6,0.001)); expect(m,closeTo(98.6,0.001));
}); });
test("From K to F", () async { test("From K to F", () async {
final corpus = await createTestCorpus(); final corpus = await testCorpus;
final m = corpus.convert(37, "Kelvin", "Fahrenheit"); final m = corpus.convert(37, "Kelvin", "Fahrenheit");
expect(m,closeTo(-393.07,0.001)); expect(m,closeTo(-393.07,0.001));
}); });
test("From C to K", () async { test("From C to K", () async {
final corpus = await createTestCorpus(); final corpus = await testCorpus;
final m = corpus.convert(100, "Celsius", "Kelvin"); final m = corpus.convert(100, "Celsius", "Kelvin");
expect(m,closeTo(373.15,0.001)); expect(m,closeTo(373.15,0.001));
}); });
@ -108,5 +113,22 @@ void main() {
expect(result, 98.0); // F = m * a = 10 * 9.8 = 98 N expect(result, 98.0); // F = m * a = 10 * 9.8 = 98 N
}); });
group('APGAR Score', () {
test('evaluates APGAR score formula - Normal case', () async {
final corpus = await testCorpus;
final formula = corpus.getFormula("Apgar Score")!;
final evaluator = FormulaEvaluator();
final result = evaluator.evaluate(formula, {
'HeartRate': 2,
'Breathing': 2,
'MuscleTone': 2,
'Reflexes': 2,
'SkinColor': 2
});
expect(result, 'Score: 10 - Normal');
});
});
} }

View file

@ -1 +0,0 @@
firejail --profile=firejail-warp-terminal.profile warp-terminal