summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--flake.lock12
-rw-r--r--meson.build26
-rw-r--r--src/meson.build77
-rw-r--r--src/services/quick-answer-service.c15
-rw-r--r--src/services/quick-answer-service.h2
-rw-r--r--src/services/quick-answers/math.c142
-rw-r--r--src/services/quick-answers/math.h23
-rw-r--r--src/services/quick-answers/math.test.c153
-rw-r--r--src/services/quick-answers/meson.build4
-rw-r--r--subprojects/mutest.wrap4
10 files changed, 364 insertions, 94 deletions
diff --git a/flake.lock b/flake.lock
index 5005946..1ccee2c 100644
--- a/flake.lock
+++ b/flake.lock
@@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
- "lastModified": 1710146030,
- "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+ "lastModified": 1726560853,
+ "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide",
"repo": "flake-utils",
- "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+ "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github"
},
"original": {
@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1719506693,
- "narHash": "sha256-C8e9S7RzshSdHB7L+v9I51af1gDM5unhJ2xO1ywxNH8=",
+ "lastModified": 1729880355,
+ "narHash": "sha256-RP+OQ6koQQLX5nw0NmcDrzvGL8HDLnyXt/jHhL1jwjM=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "b2852eb9365c6de48ffb0dc2c9562591f652242a",
+ "rev": "18536bf04cd71abd345f9579158841376fdd0c5a",
"type": "github"
},
"original": {
diff --git a/meson.build b/meson.build
index 457286b..f1cdb9d 100644
--- a/meson.build
+++ b/meson.build
@@ -10,16 +10,22 @@ json_dep = dependency('json-glib-1.0', version: '>=1.8')
nm_dep = dependency('libnm', version: '>=1.44')
gmp_dep = dependency('gmp', version: '>=6.3')
m_dep = meson.get_compiler('c').find_library('m', required : false)
-layershell_dep = dependency('gtk4-layer-shell', required: false)
-gvc_dep = dependency('gvc', required: false)
-if not layershell_dep.found()
- layershell_proj = subproject('gtk4-layer-shell')
- layershell_dep = layershell_proj.get_variable('gtk_layer_shell')
-endif
-if not gvc_dep.found()
- gvc_proj = subproject('gvc')
- gvc_dep = gvc_proj.get_variable('libgvc_dep')
-endif
+layershell_dep = dependency(
+ 'gtk4-layer-shell',
+ fallback: [ 'gtk4-layer-shell', 'gtk_layer_shell' ],
+ default_options: [ 'default_library=static' ],
+)
+gvc_dep = dependency(
+ 'gvc',
+ fallback: [ 'gvc', 'libgvc_dep' ],
+ default_options: [ 'default_library=static' ],
+)
+mutest_dep = dependency(
+ 'mutest',
+ fallback: [ 'mutest', 'mutest_dep' ],
+ default_options: [ 'static=true' ],
+ required: false,
+)
subdir('res')
subdir('src')
diff --git a/src/meson.build b/src/meson.build
index 9c6ef09..b9383fb 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,5 +1,4 @@
foobar_sources = files(
- 'main.c',
'application.c',
'utils.c',
'panel.c',
@@ -8,36 +7,68 @@ foobar_sources = files(
'control-center.c',
'notification-area.c',
)
+foobar_tests = {}
subdir('dbus')
subdir('services')
subdir('widgets')
+c_args = [
+ '-Wall',
+ '-Werror',
+ '-Wswitch-enum',
+ '-Wswitch-default',
+ '-Wunused',
+ '-Wuninitialized',
+]
+
+deps = [
+ glib_dep,
+ gio_dep,
+ gio_unix_dep,
+ gtk_dep,
+ json_dep,
+ gmp_dep,
+ nm_dep,
+ m_dep,
+ layershell_dep,
+ gvc_dep,
+]
+
+libfoobar = static_library(
+ meson.project_name(),
+ foobar_sources,
+ include_directories: include_directories('.'),
+ dependencies: deps,
+ install: false,
+ c_args: c_args
+)
+
+libfoobar_dep = declare_dependency(
+ link_with: libfoobar,
+ include_directories: include_directories('.'),
+ dependencies: deps,
+)
+
executable(
meson.project_name(),
+ 'main.c',
styles_resource,
icons_resource,
- foobar_sources,
- include_directories: include_directories('.'),
- dependencies: [
- glib_dep,
- gio_dep,
- gio_unix_dep,
- gtk_dep,
- json_dep,
- gmp_dep,
- nm_dep,
- m_dep,
- layershell_dep,
- gvc_dep,
- ],
+ dependencies: [ libfoobar_dep ],
install: true,
- c_args: [
- '-Wall',
- '-Werror',
- '-Wswitch-enum',
- '-Wswitch-default',
- '-Wunused',
- '-Wuninitialized',
- ],
+ c_args: c_args
)
+
+if mutest_dep.found()
+ foreach name, sources : foobar_tests
+ test(
+ name,
+ executable(
+ 'foobar-test-' + name,
+ sources,
+ dependencies: [ libfoobar_dep, mutest_dep ],
+ ),
+ )
+ endforeach
+endif
diff --git a/src/services/quick-answer-service.c b/src/services/quick-answer-service.c
index 8071998..670a72f 100644
--- a/src/services/quick-answer-service.c
+++ b/src/services/quick-answer-service.c
@@ -3,6 +3,12 @@
#include "launcher-item.h"
#include <gdk/gdk.h>
+//
+// FoobarQuickAnswer:
+//
+// A launcher item representing a quick, one-line answer to the query entered by the user.
+//
+
struct _FoobarQuickAnswer
{
GObject parent_instance;
@@ -48,6 +54,13 @@ G_DEFINE_FINAL_TYPE_WITH_CODE(
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE( FOOBAR_TYPE_LAUNCHER_ITEM, foobar_quick_answer_launcher_item_interface_init ) )
+//
+// FoobarQuickAnswerService:
+//
+// Service providing quick answers for search queries in the launcher (if available). This includes evaluating
+// mathematical expressions.
+//
+
struct _FoobarQuickAnswerService
{
GObject parent_instance;
@@ -324,7 +337,7 @@ FoobarQuickAnswer* foobar_quick_answer_service_query_math( gchar const* query )
FoobarMathExpression* expr = foobar_math_parse( tokens, token_count );
if ( expr )
{
- foobar_math_expression_print( expr, 0 );
+ // foobar_math_expression_print( expr, 0 );
FoobarMathValue val = { 0 };
if ( foobar_math_evaluate( expr, &val ) )
diff --git a/src/services/quick-answer-service.h b/src/services/quick-answer-service.h
index 02a8e10..adb0701 100644
--- a/src/services/quick-answer-service.h
+++ b/src/services/quick-answer-service.h
@@ -16,6 +16,6 @@ G_DECLARE_FINAL_TYPE( FoobarQuickAnswerService, foobar_quick_answer_service, FOO
FoobarQuickAnswerService* foobar_quick_answer_service_new ( void );
FoobarQuickAnswer* foobar_quick_answer_service_query ( FoobarQuickAnswerService* self,
- gchar const* query );
+ gchar const* query );
G_END_DECLS
diff --git a/src/services/quick-answers/math.c b/src/services/quick-answers/math.c
index 42bc8bd..e813808 100644
--- a/src/services/quick-answers/math.c
+++ b/src/services/quick-answers/math.c
@@ -310,72 +310,81 @@ void foobar_math_value_negate( FoobarMathValue* value )
}
gboolean foobar_math_value_add(
- FoobarMathValue lhs,
- FoobarMathValue rhs,
+ FoobarMathValue* lhs,
+ FoobarMathValue* rhs,
FoobarMathValue* out_value )
{
- if ( foobar_math_value_is_simple_int( lhs ) && foobar_math_value_is_simple_int( rhs ) )
+ if ( foobar_math_value_unify_integers( lhs, rhs ) )
{
foobar_math_value_new_int( out_value );
- mpz_add( out_value->int_value.v, lhs.int_value.v, rhs.int_value.v );
+ out_value->int_value.decimal_places = lhs->int_value.decimal_places;
+ mpz_add( out_value->int_value.v, lhs->int_value.v, rhs->int_value.v );
return TRUE;
}
- long double lhs_float = foobar_math_value_to_float( lhs );
- long double rhs_float = foobar_math_value_to_float( rhs );
+ long double lhs_float = foobar_math_value_to_float( *lhs );
+ long double rhs_float = foobar_math_value_to_float( *rhs );
foobar_math_value_from_float( lhs_float + rhs_float, out_value );
return TRUE;
}
gboolean foobar_math_value_sub(
- FoobarMathValue lhs,
- FoobarMathValue rhs,
+ FoobarMathValue* lhs,
+ FoobarMathValue* rhs,
FoobarMathValue* out_value )
{
- if ( foobar_math_value_is_simple_int( lhs ) && foobar_math_value_is_simple_int( rhs ) )
+ if ( foobar_math_value_unify_integers( lhs, rhs ) )
{
foobar_math_value_new_int( out_value );
- mpz_sub( out_value->int_value.v, lhs.int_value.v, rhs.int_value.v );
+ out_value->int_value.decimal_places = lhs->int_value.decimal_places;
+ mpz_sub( out_value->int_value.v, lhs->int_value.v, rhs->int_value.v );
return TRUE;
}
- long double lhs_float = foobar_math_value_to_float( lhs );
- long double rhs_float = foobar_math_value_to_float( rhs );
+ long double lhs_float = foobar_math_value_to_float( *lhs );
+ long double rhs_float = foobar_math_value_to_float( *rhs );
foobar_math_value_from_float( lhs_float - rhs_float, out_value );
return TRUE;
}
gboolean foobar_math_value_mul(
- FoobarMathValue lhs,
- FoobarMathValue rhs,
+ FoobarMathValue* lhs,
+ FoobarMathValue* rhs,
FoobarMathValue* out_value )
{
- if ( foobar_math_value_is_simple_int( lhs ) && foobar_math_value_is_simple_int( rhs ) )
+ if ( lhs->type == FOOBAR_MATH_VALUE_INT && rhs->type == FOOBAR_MATH_VALUE_INT )
{
foobar_math_value_new_int( out_value );
- mpz_mul( out_value->int_value.v, lhs.int_value.v, rhs.int_value.v );
+ out_value->int_value.decimal_places = lhs->int_value.decimal_places + rhs->int_value.decimal_places;
+ mpz_mul( out_value->int_value.v, lhs->int_value.v, rhs->int_value.v );
return TRUE;
}
- long double lhs_float = foobar_math_value_to_float( lhs );
- long double rhs_float = foobar_math_value_to_float( rhs );
+ long double lhs_float = foobar_math_value_to_float( *lhs );
+ long double rhs_float = foobar_math_value_to_float( *rhs );
foobar_math_value_from_float( lhs_float * rhs_float, out_value );
return TRUE;
}
gboolean foobar_math_value_div(
- FoobarMathValue lhs,
- FoobarMathValue rhs,
+ FoobarMathValue* lhs,
+ FoobarMathValue* rhs,
FoobarMathValue* out_value )
{
- if ( foobar_math_value_is_simple_int( lhs ) && foobar_math_value_is_simple_int( rhs ) )
+ if ( lhs->type == FOOBAR_MATH_VALUE_INT && rhs->type == FOOBAR_MATH_VALUE_INT )
{
- if ( mpz_sgn( rhs.int_value.v ) == 0 ) { return FALSE; }
+ if ( mpz_sgn( rhs->int_value.v ) == 0 ) { return FALSE; }
+
+ if ( rhs->int_value.decimal_places > lhs->int_value.decimal_places )
+ {
+ foobar_math_value_unify_integers( lhs, rhs );
+ }
foobar_math_value_new_int( out_value );
+ out_value->int_value.decimal_places = lhs->int_value.decimal_places - rhs->int_value.decimal_places;
mpz_t rem;
mpz_init( rem );
- mpz_divmod( out_value->int_value.v, rem, lhs.int_value.v, rhs.int_value.v );
+ mpz_divmod( out_value->int_value.v, rem, lhs->int_value.v, rhs->int_value.v );
gboolean result_is_int = mpz_sgn( rem ) == 0;
mpz_clear( rem );
@@ -383,8 +392,8 @@ gboolean foobar_math_value_div(
foobar_math_value_free( *out_value );
}
- long double lhs_float = foobar_math_value_to_float( lhs );
- long double rhs_float = foobar_math_value_to_float( rhs );
+ long double lhs_float = foobar_math_value_to_float( *lhs );
+ long double rhs_float = foobar_math_value_to_float( *rhs );
long double result = lhs_float / rhs_float;
if ( isnanl( result ) || isinfl( result ) ) { return FALSE; }
foobar_math_value_from_float( result, out_value );
@@ -392,29 +401,60 @@ gboolean foobar_math_value_div(
}
gboolean foobar_math_value_pow(
- FoobarMathValue lhs,
- FoobarMathValue rhs,
+ FoobarMathValue* lhs,
+ FoobarMathValue* rhs,
FoobarMathValue* out_value )
{
- if ( foobar_math_value_is_simple_int( lhs ) && foobar_math_value_is_simple_int( rhs ) &&
- mpz_fits_ulong_p( rhs.int_value.v ) )
+ if ( lhs->type == FOOBAR_MATH_VALUE_INT &&
+ rhs->type == FOOBAR_MATH_VALUE_INT &&
+ rhs->int_value.decimal_places == 0 &&
+ mpz_fits_ulong_p( rhs->int_value.v ) )
{
+ unsigned long exp = mpz_get_ui( rhs->int_value.v );
foobar_math_value_new_int( out_value );
- mpz_pow_ui( out_value->int_value.v, lhs.int_value.v, mpz_get_ui( rhs.int_value.v ) );
+ out_value->int_value.decimal_places = lhs->int_value.decimal_places * exp;
+ mpz_pow_ui( out_value->int_value.v, lhs->int_value.v, exp );
return TRUE;
}
- long double lhs_float = foobar_math_value_to_float( lhs );
- long double rhs_float = foobar_math_value_to_float( rhs );
+ long double lhs_float = foobar_math_value_to_float( *lhs );
+ long double rhs_float = foobar_math_value_to_float( *rhs );
long double result = powl( lhs_float, rhs_float );
if ( isnanl( result ) || isinfl( result ) ) { return FALSE; }
foobar_math_value_from_float( result, out_value );
return TRUE;
}
-gboolean foobar_math_value_is_simple_int( FoobarMathValue value )
+gboolean foobar_math_value_unify_integers(
+ FoobarMathValue* a,
+ FoobarMathValue* b )
{
- return value.type == FOOBAR_MATH_VALUE_INT && value.int_value.decimal_places == 0;
+ if ( a->type != FOOBAR_MATH_VALUE_INT || b->type != FOOBAR_MATH_VALUE_INT ) { return FALSE; }
+
+ // Extend the less precise representation so both numbers have the same number of decimal places.
+
+ gsize decimal_places = MAX( a->int_value.decimal_places, b->int_value.decimal_places );
+
+ mpz_t exp;
+ mpz_init( exp );
+
+ if ( decimal_places > a->int_value.decimal_places )
+ {
+ mpz_ui_pow_ui( exp, 10, decimal_places - a->int_value.decimal_places );
+ mpz_mul( a->int_value.v, a->int_value.v, exp );
+ }
+
+ if ( decimal_places > b->int_value.decimal_places )
+ {
+ mpz_ui_pow_ui( exp, 10, decimal_places - b->int_value.decimal_places );
+ mpz_mul( b->int_value.v, b->int_value.v, exp );
+ }
+
+ mpz_clear( exp );
+
+ a->int_value.decimal_places = b->int_value.decimal_places = decimal_places;
+
+ return TRUE;
}
gchar* foobar_math_value_to_string( FoobarMathValue value )
@@ -423,10 +463,23 @@ gchar* foobar_math_value_to_string( FoobarMathValue value )
{
case FOOBAR_MATH_VALUE_INT:
{
- size_t len = mpz_sizeinbase( value.int_value.v, 10 );
- gchar* result = g_new0( gchar, len + 3 ); // length + null terminator + sign + separator
+ gsize cap = mpz_sizeinbase( value.int_value.v, 10 );
+ cap = MAX( cap, value.int_value.decimal_places + 1 ); // pad with zeroes if necessary
+ gchar* result = g_new0( gchar, cap + 3 ); // length + null terminator + sign + separator
mpz_get_str( result, 10, value.int_value.v );
- len = strlen( result );
+ gsize len = strlen( result ); // actual size may be 1 char less (+ len now also includes sign)
+ gboolean has_sign = result[0] == '-';
+ if ( has_sign )
+ {
+ // add the sign back later, makes everything else easier
+ result += 1;
+ len -= 1;
+ }
+ // pad with leading zeroes if necessary
+ gsize padded_len = MAX( len, value.int_value.decimal_places + 1 );
+ memmove( &result[padded_len - len], result, len + 1 );
+ memset( result, '0', padded_len - len );
+ len = padded_len;
gboolean trailing_zeros = TRUE;
gsize decimal_places = value.int_value.decimal_places;
gsize actual_decimal_places = decimal_places;
@@ -447,6 +500,11 @@ gchar* foobar_math_value_to_string( FoobarMathValue value )
memmove( &result[len + 1 - decimal_places], &result[len - decimal_places], decimal_places + 1 );
result[len - decimal_places] = '.';
}
+ if ( has_sign )
+ {
+ result -= 1;
+ len += 1;
+ }
return result;
}
case FOOBAR_MATH_VALUE_FLOAT:
@@ -617,19 +675,19 @@ gboolean foobar_math_evaluate(
switch ( expr->operation.o )
{
case FOOBAR_MATH_OPERATION_ADD:
- success = foobar_math_value_add( lhs, rhs, out_value );
+ success = foobar_math_value_add( &lhs, &rhs, out_value );
break;
case FOOBAR_MATH_OPERATION_SUB:
- success = foobar_math_value_sub( lhs, rhs, out_value );
+ success = foobar_math_value_sub( &lhs, &rhs, out_value );
break;
case FOOBAR_MATH_OPERATION_MUL:
- success = foobar_math_value_mul( lhs, rhs, out_value );
+ success = foobar_math_value_mul( &lhs, &rhs, out_value );
break;
case FOOBAR_MATH_OPERATION_DIV:
- success = foobar_math_value_div( lhs, rhs, out_value );
+ success = foobar_math_value_div( &lhs, &rhs, out_value );
break;
case FOOBAR_MATH_OPERATION_POW:
- success = foobar_math_value_pow( lhs, rhs, out_value );
+ success = foobar_math_value_pow( &lhs, &rhs, out_value );
break;
default:
g_warn_if_reached( );
diff --git a/src/services/quick-answers/math.h b/src/services/quick-answers/math.h
index 6bc07fe..c66a2bf 100644
--- a/src/services/quick-answers/math.h
+++ b/src/services/quick-answers/math.h
@@ -123,22 +123,23 @@ void foobar_math_value_copy ( FoobarMathValue
FoobarMathValue* out_value );
void foobar_math_value_free ( FoobarMathValue value );
void foobar_math_value_negate ( FoobarMathValue* value );
-gboolean foobar_math_value_add ( FoobarMathValue lhs,
- FoobarMathValue rhs,
+gboolean foobar_math_value_add ( FoobarMathValue* lhs,
+ FoobarMathValue* rhs,
FoobarMathValue* out_value );
-gboolean foobar_math_value_sub ( FoobarMathValue lhs,
- FoobarMathValue rhs,
+gboolean foobar_math_value_sub ( FoobarMathValue* lhs,
+ FoobarMathValue* rhs,
FoobarMathValue* out_value );
-gboolean foobar_math_value_mul ( FoobarMathValue lhs,
- FoobarMathValue rhs,
+gboolean foobar_math_value_mul ( FoobarMathValue* lhs,
+ FoobarMathValue* rhs,
FoobarMathValue* out_value );
-gboolean foobar_math_value_div ( FoobarMathValue lhs,
- FoobarMathValue rhs,
+gboolean foobar_math_value_div ( FoobarMathValue* lhs,
+ FoobarMathValue* rhs,
FoobarMathValue* out_value );
-gboolean foobar_math_value_pow ( FoobarMathValue lhs,
- FoobarMathValue rhs,
+gboolean foobar_math_value_pow ( FoobarMathValue* lhs,
+ FoobarMathValue* rhs,
FoobarMathValue* out_value );
-gboolean foobar_math_value_is_simple_int ( FoobarMathValue value );
+gboolean foobar_math_value_unify_integers ( FoobarMathValue* a,
+ FoobarMathValue* b );
gchar* foobar_math_value_to_string ( FoobarMathValue value );
long double foobar_math_value_to_float ( FoobarMathValue value );
gboolean foobar_math_evaluate ( FoobarMathExpression const* expr,
diff --git a/src/services/quick-answers/math.test.c b/src/services/quick-answers/math.test.c
new file mode 100644
index 0000000..416b576
--- /dev/null
+++ b/src/services/quick-answers/math.test.c
@@ -0,0 +1,153 @@
+#include "services/quick-answers/math.h"
+#include <mutest.h>
+
+#define SERIALIZATION_TEST( value, identifier ) \
+ static void serialization_value_##identifier##_spec( void ) \
+ { \
+ FoobarMathValue val; \
+ \
+ gchar const input[] = #value; \
+ mutest_expect( \
+ "parse result", \
+ mutest_bool_value( foobar_math_value_from_string( input, sizeof(input) - 1, &val) ), \
+ mutest_to_be_true, \
+ NULL ); \
+ g_autofree gchar* output = foobar_math_value_to_string( val ); \
+ mutest_expect( \
+ "string representation", \
+ mutest_string_value( output ), \
+ mutest_to_be, \
+ input, \
+ NULL ); \
+ \
+ foobar_math_value_free( val ); \
+ }
+
+SERIALIZATION_TEST( 0, 0 )
+SERIALIZATION_TEST( 1, 1 )
+SERIALIZATION_TEST( -1, neg_1 )
+SERIALIZATION_TEST( 1.2, 1_2 )
+SERIALIZATION_TEST( -1.2, neg_1_2 )
+SERIALIZATION_TEST( 1.30002, 1_30002 )
+SERIALIZATION_TEST( -1.30002, neg_1_30002 )
+SERIALIZATION_TEST( 0.2, 0_2 )
+SERIALIZATION_TEST( -0.2, neg_0_2 )
+SERIALIZATION_TEST( 0.00002, 0_00002 )
+SERIALIZATION_TEST( -0.00002, neg_0_00002 )
+SERIALIZATION_TEST( 123, 123 )
+SERIALIZATION_TEST( 123.456, 123_456 )
+SERIALIZATION_TEST( -123, neg_123 )
+SERIALIZATION_TEST( -123.456, neg_123_456 )
+
+#undef SERIALIZATION_TEST
+
+static void serialization_suite( void )
+{
+ mutest_it( "value 0", serialization_value_0_spec );
+ mutest_it( "value 1", serialization_value_1_spec );
+ mutest_it( "value -1", serialization_value_neg_1_spec );
+ mutest_it( "value 1.2", serialization_value_1_2_spec );
+ mutest_it( "value -1.2", serialization_value_neg_1_2_spec );
+ mutest_it( "value 1.30002", serialization_value_1_30002_spec );
+ mutest_it( "value -1.30002", serialization_value_neg_1_30002_spec );
+ mutest_it( "value 0.2", serialization_value_0_2_spec );
+ mutest_it( "value -0.2", serialization_value_neg_0_2_spec );
+ mutest_it( "value 0.00002", serialization_value_0_00002_spec );
+ mutest_it( "value -0.00002", serialization_value_neg_0_00002_spec );
+ mutest_it( "value 123", serialization_value_123_spec );
+ mutest_it( "value 123.456", serialization_value_123_456_spec );
+ mutest_it( "value -123", serialization_value_neg_123_spec );
+ mutest_it( "value -123.456", serialization_value_neg_123_456_spec );
+}
+#define OPERATION_TEST( op, value_a, value_b, value_res, identifier ) \
+ static void operation_##identifier##_spec( void ) \
+ { \
+ FoobarMathValue a, b, res; \
+ \
+ gchar const input_a[] = #value_a; \
+ gchar const input_b[] = #value_b; \
+ mutest_expect( \
+ "input a parsed", \
+ mutest_bool_value( foobar_math_value_from_string( input_a, sizeof(input_a) - 1, &a) ), \
+ mutest_to_be_true, \
+ NULL ); \
+ mutest_expect( \
+ "input b parsed", \
+ mutest_bool_value( foobar_math_value_from_string( input_b, sizeof(input_b) - 1, &b) ), \
+ mutest_to_be_true, \
+ NULL ); \
+ foobar_math_value_##op( &a, &b, &res ); \
+ g_autofree gchar* output = foobar_math_value_to_string( res ); \
+ mutest_expect( \
+ "result", \
+ mutest_string_value( output ), \
+ mutest_to_be, \
+ #value_res, \
+ NULL ); \
+ \
+ foobar_math_value_free( a ); \
+ foobar_math_value_free( b ); \
+ foobar_math_value_free( res ); \
+ }
+
+OPERATION_TEST( add, 123, 456, 579, 123_plus_456)
+OPERATION_TEST( add, 123.456, 11, 134.456, 123_456_plus_11)
+OPERATION_TEST( add, 11, 123.456, 134.456, 11_plus_123_456)
+OPERATION_TEST( sub, 123, 456, -333, 123_minus_456)
+OPERATION_TEST( sub, 123.456, 11, 112.456, 123_456_minus_11)
+OPERATION_TEST( sub, 11, 123.456, -112.456, 11_minus_123_456)
+OPERATION_TEST( mul, 123, 3, 369, 123_times_3 )
+OPERATION_TEST( mul, 12, 0.05, 0.6, 12_times_0_05 )
+OPERATION_TEST( mul, 0.05, 12, 0.6, 0_05_times_12 )
+OPERATION_TEST( div, 369, 3, 123, 369_div_3 )
+OPERATION_TEST( div, 369, 2, 184.5, 369_div_2 )
+OPERATION_TEST( div, 12, 0.05, 240, 12_div_0_05 )
+OPERATION_TEST( div, 0.08, 4, 0.02, 0_08_div_4 )
+OPERATION_TEST( pow, 2, 3, 8, 2_pow_3 )
+OPERATION_TEST( pow, 4, 0.5, 2, 4_pow_0_5 )
+
+#undef OPERATION_TEST
+
+static void addition_suite( void )
+{
+ mutest_it( "calculate 123 + 456", operation_123_plus_456_spec );
+ mutest_it( "calculate 123.456 + 11", operation_123_456_plus_11_spec );
+ mutest_it( "calculate 11 + 123.456", operation_11_plus_123_456_spec );
+}
+
+static void subtraction_suite( void )
+{
+ mutest_it( "calculate 123 - 456", operation_123_minus_456_spec );
+ mutest_it( "calculate 123.456 - 11", operation_123_456_minus_11_spec );
+ mutest_it( "calculate 11 - 123.456", operation_11_minus_123_456_spec );
+}
+
+static void multiplication_suite( void )
+{
+ mutest_it( "calculate 123 * 3", operation_123_times_3_spec );
+ mutest_it( "calculate 12 * 0.05", operation_12_times_0_05_spec );
+ mutest_it( "calculate 0.05 * 12", operation_0_05_times_12_spec );
+}
+
+static void division_suite( void )
+{
+ mutest_it( "calculate 369 / 3", operation_369_div_3_spec );
+ mutest_it( "calculate 369 / 2", operation_369_div_2_spec );
+ mutest_it( "calculate 12 / 0.05", operation_12_div_0_05_spec );
+ mutest_it( "calculate 0.08 / 4", operation_0_08_div_4_spec );
+}
+
+static void power_suite( void )
+{
+ mutest_it( "calculate 2 ^ 3", operation_2_pow_3_spec );
+ mutest_it( "calculate 4 ^ 0.5", operation_4_pow_0_5_spec );
+}
+
+MUTEST_MAIN(
+ mutest_describe( "Serialization", serialization_suite );
+ mutest_describe( "Addition", addition_suite );
+ mutest_describe( "Subtraction", subtraction_suite );
+ mutest_describe( "Multiplication", multiplication_suite );
+ mutest_describe( "Division", division_suite );
+ mutest_describe( "Power", power_suite );
+)
diff --git a/src/services/quick-answers/meson.build b/src/services/quick-answers/meson.build
index a1cc573..ae468c2 100644
--- a/src/services/quick-answers/meson.build
+++ b/src/services/quick-answers/meson.build
@@ -3,3 +3,7 @@ foobar_sources += files(
'math-parser.c',
'math.c',
)
+
+foobar_tests += {
+ 'math': files('math.test.c'),
+}
diff --git a/subprojects/mutest.wrap b/subprojects/mutest.wrap
new file mode 100644
index 0000000..6e99973
--- /dev/null
+++ b/subprojects/mutest.wrap
@@ -0,0 +1,4 @@
+[wrap-git]
+url = https://github.com/ebassi/mutest.git
+revision = 18a20071773f7c4b75e82a931ef9b916b273b3e5
+depth = 1