Merge branch 'web-server'

This commit is contained in:
Álvaro González 2026-02-01 16:17:34 +01:00
commit 6fe64c1d85
10 changed files with 109 additions and 18 deletions

View file

@ -10,6 +10,16 @@ ENV PUB_CACHE=/cache/pub-cache
ENV GRADLE_USER_HOME=/cache/gradle-cache
RUN mkdir -p $PUB_CACHE $GRADLE_USER_HOME
# To avoid: fatal: detected dubious ownership in repository at '/sdks/flutter'
# Pass this during build: --build-arg USER_ID=$(id -u) --build-arg GROUP_ID=$(id -g)
ARG USER_ID
ARG GROUP_ID
RUN echo "Using UID: $USER_ID and GID: $GROUP_ID"
RUN chown -R $USER_ID:$GROUP_ID $PUB_CACHE $GRADLE_USER_HOME
RUN chown -R $USER_ID:$GROUP_ID /sdks/flutter
USER $USER_ID:$GROUP_ID
# Copy pubspec files and get dependencies
# Commented out to avoid building the app during image creation, this will be handled externally by makefile
# COPY pubspec.yaml pubspec.lock ./

View file

@ -19,11 +19,14 @@ build-linux-debug-container: pub-get-container
build-web-debug-container: pub-get-container
./docker-exec.sh exec flutter build web --debug
run-linux-debug-container: build-linux-debug-container
run-linux-debug-container: pub-get-container
./docker-exec.sh exec flutter run -d linux
run-linux-debug: build-linux-debug-container
run-web-debug-container: pub-get-container
./docker-exec.sh exec flutter run --web-port $${WEB_PORT:-8081} -d web-server
run-linux-debug-native: build-linux-debug-container
./build/linux/x64/debug/bundle/d4rt_formulas
run-web-debug-container: build-web-debug-container
cd build/web && python3 -m http.server 8080
run-web-debug-native: build-web-debug-container
cd build/web && python3 -m http.server $${WEB_PORT:-8081}

View file

@ -132,9 +132,9 @@ Where:
{"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"},
"output": {"name": "Result", "unit": "string"},
"d4rtCode": """
var total = HeartRate + Breathing + MuscleTone + Reflexes + SkinColor;
var total = indexOf("HeartRate") + indexOf("Breathing") + indexOf("MuscleTone") + indexOf("Reflexes") + indexOf("SkinColor");
late var interpretation;
if( total < 4 ) {
interpretation = 'Critical condition';
@ -154,12 +154,12 @@ Where:
"name": "Compare price per mass",
"description": "Compares two products by their price per mass and returns which is cheaper.",
"input": [
{"name": "price1", "unit": "scalar"},
{"name": "price1", "unit": "currency"},
{"name": "mass1", "unit": "kilogram"},
{"name": "price2", "unit": "scalar"},
{"name": "price2", "unit": "currency"},
{"name": "mass2", "unit": "kilogram"}
],
"output": {"name": "Result", "unit": "scalar"},
"output": {"name": "Result", "unit": "string"},
"d4rtCode": """
var p1 = price1 / mass1;
var p2 = price2 / mass2;

View file

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

View file

@ -1,3 +1,4 @@
[
{"name": "scalar", "symbol": "", "isBase": true},
{"name": "scalar", "symbol": "㊷", "isBase": true},
{"name": "string", "symbol": "🔤", "isBase": true}
]

View file

@ -19,9 +19,12 @@ detect_container(){
fi
}
clean_build_cache(){
$DOCKER builder prune --all --force
}
build_image(){
$DOCKER build -t d4rt-formulas-builder -f Dockerfile .
$DOCKER build --build-arg USER_ID=$(id -u) --build-arg GROUP_ID=$(id -g) --progress=plain -t d4rt-formulas-builder -f Dockerfile .
}
graphic_options(){
@ -45,19 +48,37 @@ graphic_options(){
fi
}
exec_in_container(){
local SPIOPTIONS="--env AT_SPI_BUS=/run/user/$(id -u)/at-spi/bus_0 --volume=/run/user/$(id -u)/at-spi:/run/user/$(id -u)/at-spi --device=/dev/dri"
spi_options(){
if [ -e /run/user/$(id -u)/at-spi/bus_0 ]
then
printf " %s " "--env AT_SPI_BUS=/run/user/$(id -u)/at-spi/bus_0"
fi
if [ -e /run/user/$(id -u)/at-spi ]
then
printf " %s " "--volume=/run/user/$(id -u)/at-spi:/run/user/$(id -u)/at-spi"
fi
if [ -e /dev/dri ]
then
printf " %s " "--device /dev/dri"
fi
}
exec_in_container(){
SPIOPTIONS=$(spi_options)
local GRAPHICOPTIONS=$(graphic_options)
local BUILDCACHE=./.build-container-cache
mkdir -p $BUILDCACHE
$DOCKER run \
-it \
--userns=keep-id \
--user $(id -u):$(id -g) \
--init \
--rm \
$GRAPHICOPTIONS \
$SPIOPTIONS \
-p ${WEBPORT:-8081}:8081 \
-v $BUILDCACHE:/cache:z \
-v .:/app:z \
-e FLUTTER_FLAVOR=prod \
@ -73,12 +94,18 @@ main(){
return $?
fi
if [ "$1" = "cleancache" ]; then
clean_build_cache
return $?
fi
if [ "$1" = "exec" ]; then
exec_in_container ${@:2}
return $?
fi
echo "Usage: $0 {build|exec <command>}"
echo "Usage: $0 {build|cleancache|exec <command>}"
return 1
}

View file

@ -94,7 +94,6 @@ class _FormulaScreenState extends State<FormulaScreen> {
void _evaluateFormula() {
print( "EVALUATE FORMULA");
if (!_formKey.currentState!.validate()) return;
try {
final inputValues = <String, dynamic>{};
@ -145,7 +144,7 @@ class _FormulaScreenState extends State<FormulaScreen> {
// Convert output to selected unit if needed
String? unit = widget.formula.output.unit;
if (unit != null && unit is Number) {
if (unit != null && result is Number) {
final converted = widget.corpus.convert(result, unit, _selectedOutputUnit!);
if (converted is num) {
_result = converted.toStringAsFixed(2);
@ -274,6 +273,7 @@ class _FormulaScreenState extends State<FormulaScreen> {
onUnitChanged: (unit) {
_selectedOutputUnit = unit;
_evaluateFormula();
print( "En output unit changed to $unit: $_result");
setState(() {
});
},

View file

@ -20,6 +20,7 @@ Future<Corpus> createDefaultCorpus() async{
final unitResources = [
"assets/units/angle.d4rt.units",
"assets/units/area.d4rt.units",
"assets/units/currency.d4rt.units",
"assets/units/distance.d4rt.units",
"assets/units/energy.d4rt.units",
"assets/units/force.d4rt.units",
@ -32,6 +33,7 @@ Future<Corpus> createDefaultCorpus() async{
];
for (final unitRes in unitResources) {
print( "Loading units from $unitRes");
final literal = await loadResourceAsString(unitRes);
final units = UnitSpec.fromArrayStringLiteral(literal);
corpus.loadUnits(units);

View file

@ -104,8 +104,9 @@ class FormulaEvaluator {
final result = _interpreter.execute(source: completeSource);
return result;
}
catch (e) {
catch (e, stack) {
print( "Error evaluating formula source:\n$completeSource" );
print( stack );
throw FormulaEvaluationException(
'Error evaluating formula "${formula.name}": $e',
e,
@ -141,6 +142,8 @@ class FormulaEvaluator {
import "package:d4rt_formulas.dart";
""";
static const reservedVariableNames = { "variableValues", "indexOf", "variableAllowedValues"} ;
String _buildCompleteSource(Formula formula, Map<String, dynamic> inputValues) {
final buffer = StringBuffer();
@ -168,6 +171,29 @@ class FormulaEvaluator {
""");
}
}
buffer.writeln("""
final variableValues = <String, dynamic>{
""");
for (final entry in inputValues.entries) {
final varName = entry.key;
final value = entry.value;
if (value is String) {
final escapedValue = value.replaceAll('"', '\\"');
buffer.writeln("""
"$varName": "$escapedValue",
""");
} else {
buffer.writeln("""
"$varName": "$value",
""");
}
}
buffer.writeln("""
};
""");
// Build a Map<String, List<String>> named `variableValues` that exposes allowed values
// for each VariableSpec (inputs and output) to the interpreted code. Values are
// converted to strings and quoted in the produced d4rt source.
@ -187,13 +213,24 @@ class FormulaEvaluator {
}
// Write the variableValues map into the generated source without escaping names/values
buffer.writeln("final variableValues = {");
buffer.writeln("final variableAllowedValues = {");
variableValuesMap.forEach((name, list) {
final listLiteral = list.map((s) => '"' + s + '"').join(', ');
buffer.writeln(' "' + name + '": [' + listLiteral + '],');
});
buffer.writeln('};');
// Some functions to deal with string values
buffer.writeln("""
// If return type is int, there is an error converting double to int 🤷
dynamic indexOf(String inputName) {
String value = variableValues[inputName];
String allowedValues = variableAllowedValues[inputName];
dynamic ret = allowedValues.indexOf(value) as int;
return ret as int;
}
""");
buffer.writeln("""
late var ${formula.output.name};
${formula.d4rtCode}

View file

@ -1,5 +1,6 @@
import 'package:d4rt/d4rt.dart';
import 'package:collection/collection.dart';
import 'package:d4rt_formulas/d4rt_formulas.dart';
abstract class SetUtils {
static Object safeGet(Map<Object?, Object?> map, String key) {
@ -105,6 +106,13 @@ class VariableSpec {
final List<dynamic>? values;
VariableSpec({required this.name, this.unit, this.values}){
validate();
}
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 ){
throw ArgumentError("$name: at least unit or allowedValues should be valid");