summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHannes Schulze <haschu0103@outlook.de>2024-07-03 15:05:18 +0200
committerHannes Schulze <haschu0103@outlook.de>2024-07-03 15:05:18 +0200
commit40939b3ebe359b19cc4ee1d9a7b831fada71ce75 (patch)
tree74c5ce2c6d22490bf35ed4dae778ae542f5fd228 /src
parent16483d65744488146e96d149b41f5869a2597a4c (diff)
implement a parser for mathematical expressions
Diffstat (limited to 'src')
-rw-r--r--src/meson.build1
-rw-r--r--src/services/meson.build2
-rw-r--r--src/services/quick-answer-service.c24
-rw-r--r--src/services/quick-answers/math-lexer.c175
-rw-r--r--src/services/quick-answers/math-parser.c336
-rw-r--r--src/services/quick-answers/math.c298
-rw-r--r--src/services/quick-answers/math.h124
-rw-r--r--src/services/quick-answers/meson.build5
8 files changed, 957 insertions, 8 deletions
diff --git a/src/meson.build b/src/meson.build
index 78e0575..9c6ef09 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -25,6 +25,7 @@ executable(
gio_unix_dep,
gtk_dep,
json_dep,
+ gmp_dep,
nm_dep,
m_dep,
layershell_dep,
diff --git a/src/services/meson.build b/src/services/meson.build
index e4f151d..368c2f1 100644
--- a/src/services/meson.build
+++ b/src/services/meson.build
@@ -11,3 +11,5 @@ foobar_sources += files(
'quick-answer-service.c',
'configuration-service.c',
)
+
+subdir('quick-answers')
diff --git a/src/services/quick-answer-service.c b/src/services/quick-answer-service.c
index b564a16..70d54b6 100644
--- a/src/services/quick-answer-service.c
+++ b/src/services/quick-answer-service.c
@@ -1,4 +1,5 @@
#include "services/quick-answer-service.h"
+#include "services/quick-answers/math.h"
#include "launcher-item.h"
#include <gdk/gdk.h>
@@ -302,16 +303,23 @@ FoobarQuickAnswer* foobar_quick_answer_service_query(
{
(void)self;
- if ( !g_strcmp0(query, "2+2") )
+ gsize token_count;
+ g_autofree FoobarMathToken* tokens = foobar_math_lex( query, strlen( query ), &token_count );
+ if ( tokens )
{
- g_autoptr(GIcon) icon = g_themed_icon_new("fluent-calculator-symbolic");
-
- FoobarQuickAnswer* answer = foobar_quick_answer_new();
- foobar_quick_answer_set_value(answer, "4");
- foobar_quick_answer_set_title(answer, "= 4");
- foobar_quick_answer_set_icon(answer, icon);
- return answer;
+ FoobarMathExpression* expr = foobar_math_parse( tokens, token_count );
+ if ( expr )
+ {
+ g_print( "---\n" );
+ foobar_math_expression_print( expr, 0 );
+ foobar_math_expression_free( expr );
+ }
}
+ (void)foobar_quick_answer_new;
+ (void)foobar_quick_answer_set_value;
+ (void)foobar_quick_answer_set_icon;
+ (void)foobar_quick_answer_set_title;
+
return NULL;
}
diff --git a/src/services/quick-answers/math-lexer.c b/src/services/quick-answers/math-lexer.c
new file mode 100644
index 0000000..b0d24b7
--- /dev/null
+++ b/src/services/quick-answers/math-lexer.c
@@ -0,0 +1,175 @@
+#include "services/quick-answers/math.h"
+
+typedef struct _Lexer Lexer;
+
+struct _Lexer
+{
+ GArray* result;
+ gchar const* input;
+ gsize input_length;
+ gsize position;
+ gsize token_start;
+};
+
+static gboolean char_is_whitespace( char c );
+static gboolean char_is_digit ( char c );
+static gboolean char_is_identifier( char c );
+
+static char lexer_peek ( Lexer const* ctx,
+ gsize offset );
+static char lexer_pop ( Lexer* ctx );
+static void lexer_begin_token ( Lexer* ctx );
+static void lexer_commit_token ( Lexer* ctx,
+ FoobarMathTokenType type );
+static void lexer_commit_single( Lexer* ctx,
+ FoobarMathTokenType type );
+
+FoobarMathToken* foobar_math_lex(
+ gchar const* input,
+ gsize input_length,
+ gsize* out_count )
+{
+ g_autoptr( GArray ) result = g_array_new( FALSE, TRUE, sizeof( FoobarMathToken ) );
+
+ Lexer ctx = { 0 };
+ ctx.result = result;
+ ctx.input = input;
+ ctx.input_length = input_length;
+
+ while ( ctx.position < input_length )
+ {
+ if ( char_is_whitespace( lexer_peek( &ctx, 0 ) ) )
+ {
+ // Skip whitespace.
+
+ lexer_pop( &ctx );
+ }
+ else if ( char_is_digit( lexer_peek( &ctx, 0 ) ) )
+ {
+ // Process numbers as a whole, don't allow whitespace inbetween.
+
+ lexer_begin_token( &ctx );
+ while ( char_is_digit( lexer_peek( &ctx, 0 ) ) )
+ {
+ lexer_pop( &ctx );
+ }
+
+ if ( lexer_peek( &ctx, 0 ) == '.' && char_is_digit( lexer_peek( &ctx, 1 ) ) )
+ {
+ lexer_pop( &ctx );
+ while ( char_is_digit( lexer_peek( &ctx, 0 ) ) )
+ {
+ lexer_pop( &ctx );
+ }
+ }
+
+ lexer_commit_token( &ctx, FOOBAR_MATH_TOKEN_NUMBER );
+ }
+ else if ( char_is_identifier( lexer_peek( &ctx, 0 ) ) )
+ {
+ // Don't validate identifiers, leave that to the parser.
+
+ lexer_begin_token( &ctx );
+ while ( char_is_identifier( lexer_peek( &ctx, 0 ) ) )
+ {
+ lexer_pop( &ctx );
+ }
+ lexer_commit_token( &ctx, FOOBAR_MATH_TOKEN_IDENTIFIER );
+ }
+ else if ( lexer_peek( &ctx, 0 ) == '(' )
+ {
+ lexer_commit_single( &ctx, FOOBAR_MATH_TOKEN_PAREN_OPEN );
+ }
+ else if ( lexer_peek( &ctx, 0 ) == ')' )
+ {
+ lexer_commit_single( &ctx, FOOBAR_MATH_TOKEN_PAREN_CLOSE );
+ }
+ else if ( lexer_peek( &ctx, 0 ) == '+' )
+ {
+ lexer_commit_single( &ctx, FOOBAR_MATH_TOKEN_PLUS );
+ }
+ else if ( lexer_peek( &ctx, 0 ) == '-' )
+ {
+ lexer_commit_single( &ctx, FOOBAR_MATH_TOKEN_MINUS );
+ }
+ else if ( lexer_peek( &ctx, 0 ) == '*' )
+ {
+ lexer_commit_single( &ctx, FOOBAR_MATH_TOKEN_MUL );
+ }
+ else if ( lexer_peek( &ctx, 0 ) == '/' )
+ {
+ lexer_commit_single( &ctx, FOOBAR_MATH_TOKEN_DIV );
+ }
+ else if ( lexer_peek( &ctx, 0 ) == '^' )
+ {
+ lexer_commit_single( &ctx, FOOBAR_MATH_TOKEN_POW );
+ }
+ else
+ {
+ // Unknown character.
+
+ return NULL;
+ }
+ }
+
+ return g_array_steal( result, out_count );
+}
+
+gboolean char_is_whitespace( char c )
+{
+ return c == ' ' || c == '\t' || c == '\r';
+}
+
+gboolean char_is_digit( char c )
+{
+ return c >= '0' && c <= '9';
+}
+
+gboolean char_is_identifier( char c )
+{
+ // Identifiers are all lowercase ASCII.
+
+ return c >= 'a' && c <= 'z';
+}
+
+char lexer_peek(
+ Lexer const* ctx,
+ gsize offset )
+{
+ gsize position = ctx->position + offset;
+ if ( position >= ctx->input_length ) { return 0; }
+
+ return ctx->input[position];
+}
+
+char lexer_pop( Lexer* ctx )
+{
+ if ( ctx->position >= ctx->input_length ) { return 0; }
+
+ return ctx->input[ctx->position++];
+}
+
+void lexer_begin_token( Lexer* ctx )
+{
+ ctx->token_start = ctx->position;
+}
+
+void lexer_commit_token(
+ Lexer* ctx,
+ FoobarMathTokenType type )
+{
+ FoobarMathToken token = { 0 };
+ token.data = &ctx->input[ctx->token_start];
+ token.length = ctx->position - ctx->token_start;
+ token.type = type;
+ g_array_append_val( ctx->result, token );
+}
+
+void lexer_commit_single(
+ Lexer* ctx,
+ FoobarMathTokenType type )
+{
+ lexer_begin_token( ctx );
+ lexer_pop( ctx );
+ lexer_commit_token( ctx, type );
+}
diff --git a/src/services/quick-answers/math-parser.c b/src/services/quick-answers/math-parser.c
new file mode 100644
index 0000000..bb94a66
--- /dev/null
+++ b/src/services/quick-answers/math-parser.c
@@ -0,0 +1,336 @@
+#include "services/quick-answers/math.h"
+
+typedef struct _Parser Parser;
+
+struct _Parser
+{
+ FoobarMathToken const* tokens;
+ gsize tokens_count;
+ gsize position;
+};
+
+typedef enum
+{
+ INFIX_PRECEDENCE_INVALID = -1,
+ INFIX_PRECEDENCE_NONE = 0,
+ INFIX_PRECEDENCE_ADDITIVE = 1,
+ INFIX_PRECEDENCE_MULTIPLICATIVE = 2,
+ INFIX_PRECEDENCE_EXPONENTS = 3,
+} InfixPrecedence;
+
+static FoobarMathToken const* parser_peek ( Parser const* ctx );
+static FoobarMathToken const* parser_pop ( Parser* ctx );
+static FoobarMathExpression* parser_process ( Parser* ctx,
+ FoobarMathExpression* lhs,
+ InfixPrecedence min_precedence );
+static FoobarMathExpression* parser_process_single ( Parser* ctx,
+ gboolean allow_sign );
+static FoobarMathExpression* parser_process_identifier ( Parser* ctx );
+static FoobarMathExpression* parser_process_number ( Parser* ctx );
+static FoobarMathExpression* parser_process_function ( Parser* ctx,
+ FoobarMathFunction function );
+static FoobarMathExpression* parser_process_parens ( Parser* ctx );
+static FoobarMathExpression* parser_process_sign ( Parser* ctx );
+static gboolean parser_match_identifier ( FoobarMathToken const* token,
+ gchar const* identifier );
+static InfixPrecedence parser_operator_precedence( FoobarMathToken const* token );
+static FoobarMathOperation parser_operator ( FoobarMathToken const* token );
+
+FoobarMathExpression* foobar_math_parse(
+ FoobarMathToken const* tokens,
+ gsize tokens_count )
+{
+ Parser ctx = { 0 };
+ ctx.tokens = tokens;
+ ctx.tokens_count = tokens_count;
+
+ FoobarMathExpression* lhs = parser_process_single( &ctx, TRUE );
+ if ( !lhs ) { return NULL; }
+
+ FoobarMathExpression* expr = parser_process( &ctx, lhs, INFIX_PRECEDENCE_NONE );
+ if ( !expr ) { return NULL; }
+
+ if ( parser_peek( &ctx ) )
+ {
+ foobar_math_expression_free( expr );
+ return NULL;
+ }
+
+ return expr;
+}
+
+FoobarMathToken const* parser_peek( Parser const* ctx )
+{
+ if ( ctx->position >= ctx->tokens_count ) { return NULL; }
+
+ return &ctx->tokens[ctx->position];
+}
+
+FoobarMathToken const* parser_pop( Parser* ctx )
+{
+ if ( ctx->position >= ctx->tokens_count ) { return NULL; }
+
+ return &ctx->tokens[ctx->position++];
+}
+
+FoobarMathExpression* parser_process(
+ Parser* ctx,
+ FoobarMathExpression* lhs,
+ InfixPrecedence min_precedence )
+{
+ FoobarMathToken const* op = parser_peek( ctx );
+ InfixPrecedence op_precedence = parser_operator_precedence( op );
+ while ( op_precedence >= min_precedence )
+ {
+ parser_pop( ctx );
+
+ FoobarMathExpression* rhs = parser_process_single( ctx, FALSE );
+ if ( !rhs )
+ {
+ foobar_math_expression_free( lhs );
+ return NULL;
+ }
+
+ FoobarMathToken const* next_op = parser_peek( ctx );
+ while ( parser_operator_precedence( next_op ) > op_precedence )
+ {
+ rhs = parser_process( ctx, rhs, op_precedence + 1 );
+ if ( !rhs )
+ {
+ foobar_math_expression_free( lhs );
+ return NULL;
+ }
+
+ next_op = parser_peek( ctx );
+ }
+
+ lhs = foobar_math_expression_operation( parser_operator( op ), lhs, rhs );
+
+ op = parser_peek( ctx );
+ op_precedence = parser_operator_precedence( op );
+ }
+
+ return lhs;
+}
+
+FoobarMathExpression* parser_process_single(
+ Parser* ctx,
+ gboolean allow_sign )
+{
+ FoobarMathToken const* token = parser_peek( ctx );
+ if ( !token ) { return NULL; }
+
+ switch ( token->type )
+ {
+ case FOOBAR_MATH_TOKEN_IDENTIFIER:
+ return parser_process_identifier( ctx );
+ case FOOBAR_MATH_TOKEN_NUMBER:
+ return parser_process_number( ctx );
+ case FOOBAR_MATH_TOKEN_PAREN_OPEN:
+ return parser_process_parens( ctx );
+ case FOOBAR_MATH_TOKEN_MINUS:
+ case FOOBAR_MATH_TOKEN_PLUS:
+ if ( !allow_sign ) { return NULL; }
+ return parser_process_sign( ctx );
+ case FOOBAR_MATH_TOKEN_PAREN_CLOSE:
+ case FOOBAR_MATH_TOKEN_MUL:
+ case FOOBAR_MATH_TOKEN_DIV:
+ case FOOBAR_MATH_TOKEN_POW:
+ return NULL;
+ default:
+ g_warn_if_reached( );
+ return NULL;
+ }
+}
+
+FoobarMathExpression* parser_process_identifier( Parser* ctx )
+{
+ FoobarMathToken const* token = parser_pop( ctx );
+ if ( parser_match_identifier( token, "pi" ) )
+ {
+ return foobar_math_expression_constant( FOOBAR_MATH_CONSTANT_PI );
+ }
+ else if ( parser_match_identifier( token, "e" ) )
+ {
+ return foobar_math_expression_constant( FOOBAR_MATH_CONSTANT_E );
+ }
+ else if ( parser_match_identifier( token, "exp" ) )
+ {
+ return parser_process_function( ctx, FOOBAR_MATH_FUNCTION_EXP );
+ }
+ else if ( parser_match_identifier( token, "sqrt" ) )
+ {
+ return parser_process_function( ctx, FOOBAR_MATH_FUNCTION_SQRT );
+ }
+ else if ( parser_match_identifier( token, "sin" ) )
+ {
+ return parser_process_function( ctx, FOOBAR_MATH_FUNCTION_SIN );
+ }
+ else if ( parser_match_identifier( token, "cos" ) )
+ {
+ return parser_process_function( ctx, FOOBAR_MATH_FUNCTION_COS );
+ }
+ else if ( parser_match_identifier( token, "sec" ) )
+ {
+ return parser_process_function( ctx, FOOBAR_MATH_FUNCTION_SEC );
+ }
+ else if ( parser_match_identifier( token, "csc" ) )
+ {
+ return parser_process_function( ctx, FOOBAR_MATH_FUNCTION_CSC );
+ }
+ else if ( parser_match_identifier( token, "tan" ) )
+ {
+ return parser_process_function( ctx, FOOBAR_MATH_FUNCTION_TAN );
+ }
+ else if ( parser_match_identifier( token, "cot" ) )
+ {
+ return parser_process_function( ctx, FOOBAR_MATH_FUNCTION_COT );
+ }
+ else if ( parser_match_identifier( token, "arcsin" ) )
+ {
+ return parser_process_function( ctx, FOOBAR_MATH_FUNCTION_ARCSIN );
+ }
+ else if ( parser_match_identifier( token, "arccos" ) )
+ {
+ return parser_process_function( ctx, FOOBAR_MATH_FUNCTION_ARCCOS );
+ }
+ else if ( parser_match_identifier( token, "arcsec" ) )
+ {
+ return parser_process_function( ctx, FOOBAR_MATH_FUNCTION_ARCSEC );
+ }
+ else if ( parser_match_identifier( token, "arccsc" ) )
+ {
+ return parser_process_function( ctx, FOOBAR_MATH_FUNCTION_ARCCSC );
+ }
+ else if ( parser_match_identifier( token, "arctan" ) )
+ {
+ return parser_process_function( ctx, FOOBAR_MATH_FUNCTION_ARCTAN );
+ }
+ else if ( parser_match_identifier( token, "arccot" ) )
+ {
+ return parser_process_function( ctx, FOOBAR_MATH_FUNCTION_ARCCOT );
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+FoobarMathExpression* parser_process_number( Parser* ctx )
+{
+ FoobarMathToken const* token = parser_pop( ctx );
+ FoobarMathValue value;
+ if ( !foobar_math_value_from_string( token->data, token->length, &value ) )
+ {
+ return NULL;
+ }
+
+ return foobar_math_expression_value( value );
+}
+
+FoobarMathExpression* parser_process_function(
+ Parser* ctx,
+ FoobarMathFunction function )
+{
+ FoobarMathToken const* opening_paren = parser_peek( ctx );
+ if ( !opening_paren || opening_paren->type != FOOBAR_MATH_TOKEN_PAREN_OPEN )
+ {
+ return NULL;
+ }
+
+ FoobarMathExpression* arg = parser_process_parens( ctx );
+ if ( !arg ) { return NULL; }
+
+ return foobar_math_expression_function( function, arg );
+}
+
+FoobarMathExpression* parser_process_parens( Parser* ctx )
+{
+ parser_pop( ctx );
+ FoobarMathExpression* lhs = parser_process_single( ctx, TRUE );
+ if ( !lhs ) { return NULL; }
+ FoobarMathExpression* result = parser_process( ctx, lhs, INFIX_PRECEDENCE_NONE );
+ if ( !result ) { return NULL; }
+ FoobarMathToken const* closing_paren = parser_pop( ctx );
+ if ( !closing_paren || closing_paren->type != FOOBAR_MATH_TOKEN_PAREN_CLOSE )
+ {
+ foobar_math_expression_free( result );
+ return NULL;
+ }
+ return result;
+}
+
+FoobarMathExpression* parser_process_sign( Parser* ctx )
+{
+ FoobarMathToken const* token = parser_pop( ctx );
+ FoobarMathExpression* result = parser_process_single( ctx, FALSE );
+ if ( !result ) { return NULL; }
+
+ if ( token->type == FOOBAR_MATH_TOKEN_MINUS )
+ {
+ return foobar_math_expression_function( FOOBAR_MATH_FUNCTION_NEGATE, result );
+ }
+ else
+ {
+ return result;
+ }
+}
+
+gboolean parser_match_identifier(
+ FoobarMathToken const* token,
+ gchar const* identifier )
+{
+ if ( strlen( identifier ) != token->length ) { return FALSE; }
+
+ return !strncmp( token->data, identifier, token->length );
+}
+
+InfixPrecedence parser_operator_precedence( FoobarMathToken const* token )
+{
+ if ( token )
+ {
+ switch ( token->type )
+ {
+ case FOOBAR_MATH_TOKEN_PLUS:
+ case FOOBAR_MATH_TOKEN_MINUS:
+ return INFIX_PRECEDENCE_ADDITIVE;
+ case FOOBAR_MATH_TOKEN_MUL:
+ case FOOBAR_MATH_TOKEN_DIV:
+ return INFIX_PRECEDENCE_MULTIPLICATIVE;
+ case FOOBAR_MATH_TOKEN_POW:
+ return INFIX_PRECEDENCE_EXPONENTS;
+ case FOOBAR_MATH_TOKEN_IDENTIFIER:
+ case FOOBAR_MATH_TOKEN_NUMBER:
+ case FOOBAR_MATH_TOKEN_PAREN_OPEN:
+ case FOOBAR_MATH_TOKEN_PAREN_CLOSE:
+ default:
+ break;
+ }
+ }
+
+ return INFIX_PRECEDENCE_INVALID;
+}
+
+FoobarMathOperation parser_operator( FoobarMathToken const* token )
+{
+ switch ( token->type )
+ {
+ case FOOBAR_MATH_TOKEN_PLUS:
+ return FOOBAR_MATH_OPERATION_ADD;
+ case FOOBAR_MATH_TOKEN_MINUS:
+ return FOOBAR_MATH_OPERATION_SUB;
+ case FOOBAR_MATH_TOKEN_MUL:
+ return FOOBAR_MATH_OPERATION_MUL;
+ case FOOBAR_MATH_TOKEN_DIV:
+ return FOOBAR_MATH_OPERATION_DIV;
+ case FOOBAR_MATH_TOKEN_POW:
+ return FOOBAR_MATH_OPERATION_POW;
+ case FOOBAR_MATH_TOKEN_IDENTIFIER:
+ case FOOBAR_MATH_TOKEN_NUMBER:
+ case FOOBAR_MATH_TOKEN_PAREN_OPEN:
+ case FOOBAR_MATH_TOKEN_PAREN_CLOSE:
+ default:
+ g_warn_if_reached( );
+ return 0;
+ }
+}
diff --git a/src/services/quick-answers/math.c b/src/services/quick-answers/math.c
new file mode 100644
index 0000000..5ec79c0
--- /dev/null
+++ b/src/services/quick-answers/math.c
@@ -0,0 +1,298 @@
+#include "services/quick-answers/math.h"
+
+struct _FoobarMathExpression
+{
+ FoobarMathExpressionType type;
+
+ union
+ {
+ struct
+ {
+ FoobarMathValue v;
+ } value;
+
+ struct
+ {
+ FoobarMathFunction f;
+ FoobarMathExpression* input;
+ } function;
+
+ struct
+ {
+ FoobarMathConstant c;
+ } constant;
+
+ struct
+ {
+ FoobarMathOperation o;
+ FoobarMathExpression* lhs;
+ FoobarMathExpression* rhs;
+ } operation;
+ };
+};
+
+FoobarMathExpression* foobar_math_expression_value( FoobarMathValue value )
+{
+ FoobarMathExpression* res = g_new0( FoobarMathExpression, 1 );
+ res->type = FOOBAR_MATH_EXPRESSION_VALUE;
+ res->value.v = value;
+ return res;
+}
+
+FoobarMathExpression* foobar_math_expression_function(
+ FoobarMathFunction function,
+ FoobarMathExpression* input )
+{
+ FoobarMathExpression* res = g_new0( FoobarMathExpression, 1 );
+ res->type = FOOBAR_MATH_EXPRESSION_FUNCTION;
+ res->function.f = function;
+ res->function.input = input;
+ return res;
+}
+
+FoobarMathExpression* foobar_math_expression_constant( FoobarMathConstant constant )
+{
+ FoobarMathExpression* res = g_new0( FoobarMathExpression, 1 );
+ res->type = FOOBAR_MATH_EXPRESSION_CONSTANT;
+ res->constant.c = constant;
+ return res;
+}
+
+FoobarMathExpression* foobar_math_expression_operation(
+ FoobarMathOperation operation,
+ FoobarMathExpression* lhs,
+ FoobarMathExpression* rhs )
+{
+ FoobarMathExpression* res = g_new0( FoobarMathExpression, 1 );
+ res->type = FOOBAR_MATH_EXPRESSION_OPERATION;
+ res->operation.o = operation;
+ res->operation.lhs = lhs;
+ res->operation.rhs = rhs;
+ return res;
+}
+
+void foobar_math_expression_free( FoobarMathExpression* expression )
+{
+ if ( expression )
+ {
+ switch ( expression->type )
+ {
+ case FOOBAR_MATH_EXPRESSION_VALUE:
+ foobar_math_value_free( expression->value.v );
+ break;
+ case FOOBAR_MATH_EXPRESSION_FUNCTION:
+ foobar_math_expression_free( expression->function.input );
+ break;
+ case FOOBAR_MATH_EXPRESSION_CONSTANT:
+ break;
+ case FOOBAR_MATH_EXPRESSION_OPERATION:
+ foobar_math_expression_free( expression->operation.lhs );
+ foobar_math_expression_free( expression->operation.rhs );
+ break;
+ default:
+ g_warn_if_reached( );
+ break;
+ }
+
+ g_free( expression );
+ }
+}
+
+void foobar_math_expression_print(
+ FoobarMathExpression const* expr,
+ gint indentation )
+{
+ for (gint i = 0; i < indentation; ++i) { g_print("| "); }
+ switch ( expr->type )
+ {
+ case FOOBAR_MATH_EXPRESSION_VALUE:
+ {
+ g_autofree gchar* val = foobar_math_value_format( expr->value.v );
+ g_print( "%s\n", val );
+ break;
+ }
+ case FOOBAR_MATH_EXPRESSION_FUNCTION:
+ switch ( expr->function.f )
+ {
+ case FOOBAR_MATH_FUNCTION_NEGATE:
+ g_print( "-\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_SIN:
+ g_print( "sin\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_COS:
+ g_print( "cos\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_SEC:
+ g_print( "sec\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_CSC:
+ g_print( "csc\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_TAN:
+ g_print( "tan\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_COT:
+ g_print( "cot\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCSIN:
+ g_print( "arcsin\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCCOS:
+ g_print( "arccos\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCSEC:
+ g_print( "arcsec\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCCSC:
+ g_print( "arccsc\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCTAN:
+ g_print( "arctan\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_ARCCOT:
+ g_print( "arccot\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_EXP:
+ g_print( "exp\n" );
+ break;
+ case FOOBAR_MATH_FUNCTION_SQRT:
+ g_print( "sqrt\n" );
+ break;
+ default:
+ g_warn_if_reached( );
+ break;
+ }
+ foobar_math_expression_print( expr->function.input, indentation + 1 );
+ break;
+ case FOOBAR_MATH_EXPRESSION_CONSTANT:
+ switch ( expr->constant.c )
+ {
+ case FOOBAR_MATH_CONSTANT_PI:
+ g_print( "pi\n" );
+ break;
+ case FOOBAR_MATH_CONSTANT_E:
+ g_print( "e\n" );
+ break;
+ default:
+ g_warn_if_reached( );
+ break;
+ }
+ break;
+ case FOOBAR_MATH_EXPRESSION_OPERATION:
+ switch ( expr->operation.o )
+ {
+ case FOOBAR_MATH_OPERATION_ADD:
+ g_print( "+\n" );
+ break;
+ case FOOBAR_MATH_OPERATION_SUB:
+ g_print( "-\n" );
+ break;
+ case FOOBAR_MATH_OPERATION_MUL:
+ g_print( "*\n" );
+ break;
+ case FOOBAR_MATH_OPERATION_DIV:
+ g_print( "/\n" );
+ break;
+ case FOOBAR_MATH_OPERATION_POW:
+ g_print( "^\n" );
+ break;
+ default:
+ g_warn_if_reached( );
+ break;
+ }
+ foobar_math_expression_print( expr->operation.lhs, indentation + 1 );
+ foobar_math_expression_print( expr->operation.rhs, indentation + 1 );
+ break;
+ default:
+ g_warn_if_reached( );
+ break;
+ }
+}
+
+gboolean foobar_math_value_from_string(
+ gchar const* input,
+ gsize input_length,
+ FoobarMathValue* out_value )
+{
+ g_autofree gchar* without_sep = g_new0( gchar, input_length + 1 );
+ gsize sep_pos = input_length - 1;
+ gsize j = 0;
+ for ( gsize i = 0; i < input_length; ++i )
+ {
+ if ( input[i] == '.' )
+ {
+ sep_pos = i;
+ }
+ else
+ {
+ without_sep[j++] = input[i];
+ }
+ }
+
+ out_value->type = FOOBAR_MATH_VALUE_INT;
+ out_value->int_value.decimal_places = input_length - 1 - sep_pos;
+ mpz_init( out_value->int_value.v );
+ if ( mpz_init_set_str( out_value->int_value.v, without_sep, 10 ) == -1 )
+ {
+ mpz_clear( out_value->int_value.v );
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void foobar_math_value_free( FoobarMathValue value )
+{
+ if ( value.type == FOOBAR_MATH_VALUE_INT )
+ {
+ mpz_clear( value.int_value.v );
+ }
+}
+
+gboolean foobar_math_evaluate(
+ FoobarMathExpression const* expression,
+ FoobarMathValue* out_value )
+{
+ return FALSE;
+}
+
+gchar* foobar_math_value_format( FoobarMathValue value )
+{
+ switch ( value.type )
+ {
+ 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
+ mpz_get_str( result, 10, value.int_value.v );
+ len = strlen( result );
+ gboolean trailing_zeros = TRUE;
+ gsize decimal_places = value.int_value.decimal_places;
+ gsize actual_decimal_places = decimal_places;
+ for ( gsize i = 0; i < decimal_places; ++i )
+ {
+ if ( trailing_zeros && result[len - 1 - i] == '0' )
+ {
+ result[len - 1 - i] = '\0';
+ actual_decimal_places -= 1;
+ }
+ else
+ {
+ trailing_zeros = FALSE;
+ }
+ }
+ if ( actual_decimal_places > 0 )
+ {
+ memmove( &result[len + 1 - decimal_places], &result[len - decimal_places],decimal_places + 1 );
+ result[len - decimal_places] = '.';
+ }
+ return result;
+ }
+ case FOOBAR_MATH_VALUE_FLOAT:
+ return g_strdup_printf( "%Lf", value.float_value.v );
+ default:
+ g_warn_if_reached( );
+ return NULL;
+ }
+}
diff --git a/src/services/quick-answers/math.h b/src/services/quick-answers/math.h
new file mode 100644
index 0000000..77c587a
--- /dev/null
+++ b/src/services/quick-answers/math.h
@@ -0,0 +1,124 @@
+#pragma once
+
+#include <gio/gio.h>
+#include <gmp.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ FOOBAR_MATH_VALUE_INT,
+ FOOBAR_MATH_VALUE_FLOAT,
+} FoobarMathValueType;
+
+typedef struct _FoobarMathValue FoobarMathValue;
+
+struct _FoobarMathValue
+{
+ FoobarMathValueType type;
+
+ union
+ {
+ struct
+ {
+ mpz_t v;
+ gsize decimal_places;
+ } int_value;
+
+ struct
+ {
+ long double v;
+ } float_value;
+ };
+};
+
+typedef enum
+{
+ FOOBAR_MATH_TOKEN_IDENTIFIER,
+ FOOBAR_MATH_TOKEN_NUMBER,
+ FOOBAR_MATH_TOKEN_PAREN_OPEN,
+ FOOBAR_MATH_TOKEN_PAREN_CLOSE,
+ FOOBAR_MATH_TOKEN_PLUS,
+ FOOBAR_MATH_TOKEN_MINUS,
+ FOOBAR_MATH_TOKEN_MUL,
+ FOOBAR_MATH_TOKEN_DIV,
+ FOOBAR_MATH_TOKEN_POW,
+} FoobarMathTokenType;
+
+typedef struct _FoobarMathToken FoobarMathToken;
+
+struct _FoobarMathToken
+{
+ FoobarMathTokenType type;
+ gchar const* data;
+ gsize length;
+};
+
+typedef enum
+{
+ FOOBAR_MATH_EXPRESSION_VALUE,
+ FOOBAR_MATH_EXPRESSION_FUNCTION,
+ FOOBAR_MATH_EXPRESSION_CONSTANT,
+ FOOBAR_MATH_EXPRESSION_OPERATION,
+} FoobarMathExpressionType;
+
+typedef enum
+{
+ FOOBAR_MATH_FUNCTION_NEGATE,
+ FOOBAR_MATH_FUNCTION_SIN,
+ FOOBAR_MATH_FUNCTION_COS,
+ FOOBAR_MATH_FUNCTION_SEC,
+ FOOBAR_MATH_FUNCTION_CSC,
+ FOOBAR_MATH_FUNCTION_TAN,
+ FOOBAR_MATH_FUNCTION_COT,
+ FOOBAR_MATH_FUNCTION_ARCSIN,
+ FOOBAR_MATH_FUNCTION_ARCCOS,
+ FOOBAR_MATH_FUNCTION_ARCSEC,
+ FOOBAR_MATH_FUNCTION_ARCCSC,
+ FOOBAR_MATH_FUNCTION_ARCTAN,
+ FOOBAR_MATH_FUNCTION_ARCCOT,
+ FOOBAR_MATH_FUNCTION_EXP,
+ FOOBAR_MATH_FUNCTION_SQRT,
+} FoobarMathFunction;
+
+typedef enum
+{
+ FOOBAR_MATH_CONSTANT_PI,
+ FOOBAR_MATH_CONSTANT_E,
+} FoobarMathConstant;
+
+typedef enum
+{
+ FOOBAR_MATH_OPERATION_ADD,
+ FOOBAR_MATH_OPERATION_SUB,
+ FOOBAR_MATH_OPERATION_MUL,
+ FOOBAR_MATH_OPERATION_DIV,
+ FOOBAR_MATH_OPERATION_POW,
+} FoobarMathOperation;
+
+typedef struct _FoobarMathExpression FoobarMathExpression;
+
+FoobarMathToken* foobar_math_lex ( gchar const* input,
+ gsize input_length,
+ gsize* out_count );
+FoobarMathExpression* foobar_math_parse ( FoobarMathToken const* tokens,
+ gsize tokens_count );
+FoobarMathExpression* foobar_math_expression_value ( FoobarMathValue value );
+FoobarMathExpression* foobar_math_expression_function ( FoobarMathFunction function,
+ FoobarMathExpression* input );
+FoobarMathExpression* foobar_math_expression_constant ( FoobarMathConstant constant );
+FoobarMathExpression* foobar_math_expression_operation( FoobarMathOperation operation,
+ FoobarMathExpression* lhs,
+ FoobarMathExpression* rhs );
+void foobar_math_expression_print ( FoobarMathExpression const* expr,
+ gint indentation );
+void foobar_math_expression_free ( FoobarMathExpression* expression );
+gboolean foobar_math_value_from_string ( gchar const* input,
+ gsize input_length,
+ FoobarMathValue* out_value );
+void foobar_math_value_free ( FoobarMathValue value );
+gchar* foobar_math_value_format ( FoobarMathValue value );
+gboolean foobar_math_evaluate ( FoobarMathExpression const* expression,
+ FoobarMathValue* out_value );
+
+G_END_DECLS
diff --git a/src/services/quick-answers/meson.build b/src/services/quick-answers/meson.build
new file mode 100644
index 0000000..a1cc573
--- /dev/null
+++ b/src/services/quick-answers/meson.build
@@ -0,0 +1,5 @@
+foobar_sources += files(
+ 'math-lexer.c',
+ 'math-parser.c',
+ 'math.c',
+)