From 1af3d14f3f995645a0de08a54b244c19840709e9 Mon Sep 17 00:00:00 2001 From: Hannes Schulze Date: Thu, 19 Jun 2025 23:17:58 +0200 Subject: init hs project --- src/services/quick-answers/math-lexer.c | 248 --------- src/services/quick-answers/math-parser.c | 414 --------------- src/services/quick-answers/math.c | 851 ------------------------------- src/services/quick-answers/math.h | 148 ------ src/services/quick-answers/math.test.c | 153 ------ src/services/quick-answers/meson.build | 9 - 6 files changed, 1823 deletions(-) delete mode 100644 src/services/quick-answers/math-lexer.c delete mode 100644 src/services/quick-answers/math-parser.c delete mode 100644 src/services/quick-answers/math.c delete mode 100644 src/services/quick-answers/math.h delete mode 100644 src/services/quick-answers/math.test.c delete mode 100644 src/services/quick-answers/meson.build (limited to 'src/services/quick-answers') diff --git a/src/services/quick-answers/math-lexer.c b/src/services/quick-answers/math-lexer.c deleted file mode 100644 index 7fbe877..0000000 --- a/src/services/quick-answers/math-lexer.c +++ /dev/null @@ -1,248 +0,0 @@ -#include "services/quick-answers/math.h" - -// -// FoobarMathToken: -// -// A token in a mathematical expression. The token contents are left as-is and need not be null-terminated. For example, -// "sin(pi) - 3" would be represented as: -// - IDENTIFIER: "sin" -// - PAREN_OPEN: "(" -// - IDENTIFIER: "pi" -// - PAREN_CLOSE: ")" -// - MINUS: "-" -// - NUMBER: "3" -// - -// -// Lexer: -// -// The lexer context/state which is passed around. -// - -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 ); - -// --------------------------------------------------------------------------------------------------------------------- -// Lexing -// --------------------------------------------------------------------------------------------------------------------- - -// -// Divide an input string into a list of tokens. -// -// On success, this will return a newly allocated array of tokens which should be freed using g_free. The number of -// elements will be written into out_count. -// -// On error, this will return NULL. -// -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 ); -} - -// --------------------------------------------------------------------------------------------------------------------- -// Character Classification -// --------------------------------------------------------------------------------------------------------------------- - -// -// Check if a character is a whitespace symbol (space, tab, carriage return). -// -gboolean char_is_whitespace( char c ) -{ - return c == ' ' || c == '\t' || c == '\r'; -} - -// -// Check if a character is a decimal digit. -// -gboolean char_is_digit( char c ) -{ - return c >= '0' && c <= '9'; -} - -// -// Check if a character belongs to an identifier (like "sin", "pi", etc.). -// -gboolean char_is_identifier( char c ) -{ - // Identifiers are all lowercase ASCII. - - return c >= 'a' && c <= 'z'; -} - -// --------------------------------------------------------------------------------------------------------------------- -// State Helpers -// --------------------------------------------------------------------------------------------------------------------- - -// -// Peek at the current character without consuming it. -// -// If this is the end of the input string, return 0. -// -char lexer_peek( - Lexer const* ctx, - gsize offset ) -{ - gsize position = ctx->position + offset; - if ( position >= ctx->input_length ) { return 0; } - - return ctx->input[position]; -} - -// -// Consume the current character, moving forward to the next one. -// -// If this is the end of the input string, return 0. -// -char lexer_pop( Lexer* ctx ) -{ - if ( ctx->position >= ctx->input_length ) { return 0; } - - return ctx->input[ctx->position++]; -} - -// -// Start recording a token by remembering the current position. -// -// The current character will be the first one in the token. -// -void lexer_begin_token( Lexer* ctx ) -{ - ctx->token_start = ctx->position; -} - -// -// Stop recording a token, saving the string between the call to lexer_begin_token as the specified type. -// -// The current character will not be a part of the token. -// -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 ); -} - -// -// Consume the current character and record it as a token. -// -// This is equivalent to calling lexer_begin_token, lexer_pop, and lexer_commit_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 deleted file mode 100644 index 17d976d..0000000 --- a/src/services/quick-answers/math-parser.c +++ /dev/null @@ -1,414 +0,0 @@ -#include "services/quick-answers/math.h" - -// -// Parser: -// -// The parser context/state which is passed around. -// - -typedef struct _Parser Parser; - -struct _Parser -{ - FoobarMathToken const* tokens; - gsize tokens_count; - gsize position; -}; - -// -// InfixPrecedence: -// -// Value indicating how strongly infix operators should bind. The higher the value, the stronger they should bind. -// - -typedef enum -{ - INFIX_PRECEDENCE_INVALID = -1, - INFIX_PRECEDENCE_NONE = 0, - INFIX_PRECEDENCE_ADDITIVE = 1, - INFIX_PRECEDENCE_MULTIPLICATIVE = 2, - INFIX_PRECEDENCE_EXPONENTS = 3, -} InfixPrecedence; - -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 FoobarMathToken const* parser_peek ( Parser const* ctx ); -static FoobarMathToken const* parser_pop ( 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 ); - -// --------------------------------------------------------------------------------------------------------------------- -// Parsing -// --------------------------------------------------------------------------------------------------------------------- - -// -// Transform a sequence of tokens into a mathematical expression. -// -// On success, this will return a newly allocated expression which should be freed using foobar_math_expression_free. -// -// On error, this will return NULL. -// -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; -} - -// -// Process tokens until the end of the current precedence scope (indicated by min_precedence), starting at an operator. -// -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_new_operation( parser_operator( op ), lhs, rhs ); - - op = parser_peek( ctx ); - op_precedence = parser_operator_precedence( op ); - } - - return lhs; -} - -// -// Process a single value which may be either a constant, function, number, expression enclosed in parenthesis or -// negation. -// -// If an unexpected token is encountered (i.e. an operator), this will return FALSE. -// -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; - } -} - -// -// Parse an identifier. -// -// If the identifier is a function, this will expected parenthesis and an expression afterwards. -// -FoobarMathExpression* parser_process_identifier( Parser* ctx ) -{ - FoobarMathToken const* token = parser_pop( ctx ); - if ( parser_match_identifier( token, "pi" ) ) - { - return foobar_math_expression_new_constant( FOOBAR_MATH_CONSTANT_PI ); - } - else if ( parser_match_identifier( token, "e" ) ) - { - return foobar_math_expression_new_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; - } -} - -// -// Process a numeric value. -// -FoobarMathExpression* parser_process_number( Parser* ctx ) -{ - FoobarMathToken const* token = parser_pop( ctx ); - FoobarMathValue value = { 0 }; - if ( !foobar_math_value_from_string( token->data, token->length, &value ) ) - { - return NULL; - } - - return foobar_math_expression_new_value( value ); -} - -// -// Process a function's parameter which should be enclosed in parenthesis. -// -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_new_function( function, arg ); -} - -// -// Process an expression enclosed in parenthesis. -// -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; -} - -// -// Process a sign (- or +) which may be used for negation of an expression. -// -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_new_function( FOOBAR_MATH_FUNCTION_NEGATE, result ); - } - else - { - return result; - } -} - -// --------------------------------------------------------------------------------------------------------------------- -// State Helpers -// --------------------------------------------------------------------------------------------------------------------- - -// -// Peek at the current token without consuming it. -// -// If this is the end of the token list, return NULL. -// -FoobarMathToken const* parser_peek( Parser const* ctx ) -{ - if ( ctx->position >= ctx->tokens_count ) { return NULL; } - - return &ctx->tokens[ctx->position]; -} - -// -// Consume the current token. -// -// If this is the end of the token list, return NULL. -// -FoobarMathToken const* parser_pop( Parser* ctx ) -{ - if ( ctx->position >= ctx->tokens_count ) { return NULL; } - - return &ctx->tokens[ctx->position++]; -} - -// --------------------------------------------------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------------------------------------------------- - -// -// Match a token's string against the given identifier string. -// -// This is necessary because the token's content is not a null-terminated string. -// -gboolean parser_match_identifier( - FoobarMathToken const* token, - gchar const* identifier ) -{ - if ( strlen( identifier ) != token->length ) { return FALSE; } - - return !strncmp( token->data, identifier, token->length ); -} - -// -// Get the precedence of an infix operator token or INVALID. -// -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; -} - -// -// Get the operation for an infix operator token. This requires the token to be valid. -// -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 deleted file mode 100644 index 69219e2..0000000 --- a/src/services/quick-answers/math.c +++ /dev/null @@ -1,851 +0,0 @@ -#define _GNU_SOURCE - -#include "services/quick-answers/math.h" -#include - -// -// FoobarMathExpression: -// -// A fully parsed mathematical expression which can be evaluated, for example "sin(pi) - 3" would be represented as: -// - Operation: SUB -// - Function: SIN -// - Constant: PI -// - Value: 3 -// - -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; - }; -}; - -// -// FoobarMathValue: -// -// Representation of a value. The goal of this memory structure is to store the value as an integer for as long as -// possible. Because of this, there is a "decimal_places" field, so the actual value of an integer is -// $v * 10^(-decimal_places)$. -// -// If absolutely necessary (for example, because we use a function like sin), the value is converted to a floating-point -// value. -// -// Note that we use GMP to store the integer value to allow for arbitrarily large values. Because of this, the structure -// should always be freed using foobar_math_value_free. -// - -// --------------------------------------------------------------------------------------------------------------------- -// Expressions -// --------------------------------------------------------------------------------------------------------------------- - -// -// Helper to allocate a new FoobarMathExpression representing a value. -// -// Ownership of "value" is transferred to the new expression. -// -FoobarMathExpression* foobar_math_expression_new_value( FoobarMathValue value ) -{ - FoobarMathExpression* res = g_new0( FoobarMathExpression, 1 ); - res->type = FOOBAR_MATH_EXPRESSION_VALUE; - res->value.v = value; - return res; -} - -// -// Helper to allocate a new FoobarMathExpression representing a function to be evaluated with another expression as its -// parameter. -// -// Ownership of "input" is transferred to the new expression. -// -FoobarMathExpression* foobar_math_expression_new_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; -} - -// -// Helper to allocate a new FoobarMathExpression representing a known constant. -// -FoobarMathExpression* foobar_math_expression_new_constant( FoobarMathConstant constant ) -{ - FoobarMathExpression* res = g_new0( FoobarMathExpression, 1 ); - res->type = FOOBAR_MATH_EXPRESSION_CONSTANT; - res->constant.c = constant; - return res; -} - -// -// Helper to allocate a new FoobarMathExpression representing an operation to be evaluated with two expressions as its -// parameters. -// -// Ownership of "lhs" and "rhs" is transferred to the new expression. -// -FoobarMathExpression* foobar_math_expression_new_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; -} - -// -// Release the memory associated with a mathematical expression, along with the expressions it owns. -// -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 ); - } -} - -// -// Print a tree representation of a mathematical expression. -// -// This is mainly useful for debugging. "indentation" describes the current indentation level for the recursive call -// (should be 0 initially). -// -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_to_string( 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; - } -} - -// -// Evaluate an expression, producing a value. -// -// On success (indicated by the return value TRUE), the value should be freed using foobar_math_value_free. -// -gboolean foobar_math_evaluate( - FoobarMathExpression const* expr, - FoobarMathValue* out_value ) -{ - switch ( expr->type ) - { - case FOOBAR_MATH_EXPRESSION_VALUE: - foobar_math_value_copy( expr->value.v, out_value ); - return TRUE; - case FOOBAR_MATH_EXPRESSION_CONSTANT: - switch ( expr->constant.c ) - { - case FOOBAR_MATH_CONSTANT_PI: - foobar_math_value_from_float( M_PIl, out_value ); - return TRUE; - case FOOBAR_MATH_CONSTANT_E: - foobar_math_value_from_float( M_El, out_value ); - return TRUE; - default: - g_warn_if_reached( ); - return FALSE; - } - case FOOBAR_MATH_EXPRESSION_FUNCTION: - { - FoobarMathValue input = { 0 }; - if ( !foobar_math_evaluate( expr->function.input, &input ) ) { return FALSE; } - - if ( expr->function.f == FOOBAR_MATH_FUNCTION_NEGATE ) - { - *out_value = input; - foobar_math_value_negate( out_value ); - return TRUE; - } - - long double input_float = foobar_math_value_to_float( input ); - foobar_math_value_free( input ); - - long double result = NAN; - switch ( expr->function.f ) - { - case FOOBAR_MATH_FUNCTION_SIN: - result = sinl( input_float ); - break; - case FOOBAR_MATH_FUNCTION_COS: - result = cosl( input_float ); - break; - case FOOBAR_MATH_FUNCTION_SEC: - result = 1.L / cosl( input_float ); - break; - case FOOBAR_MATH_FUNCTION_CSC: - result = 1.L / sinl( input_float ); - break; - case FOOBAR_MATH_FUNCTION_TAN: - result = tanl( input_float ); - break; - case FOOBAR_MATH_FUNCTION_COT: - result = 1.L / tanl( input_float ); - break; - case FOOBAR_MATH_FUNCTION_ARCSIN: - result = asinl( input_float ); - break; - case FOOBAR_MATH_FUNCTION_ARCCOS: - result = acosl( input_float ); - break; - case FOOBAR_MATH_FUNCTION_ARCSEC: - result = acosl( 1.L / input_float ); - break; - case FOOBAR_MATH_FUNCTION_ARCCSC: - result = asinl( 1.L / input_float ); - break; - case FOOBAR_MATH_FUNCTION_ARCTAN: - result = atanl( input_float ); - break; - case FOOBAR_MATH_FUNCTION_ARCCOT: - result = atanl( 1.L / input_float ); - break; - case FOOBAR_MATH_FUNCTION_EXP: - result = expl( input_float ); - break; - case FOOBAR_MATH_FUNCTION_SQRT: - result = sqrtl( input_float ); - break; - case FOOBAR_MATH_FUNCTION_NEGATE: - default: - g_warn_if_reached( ); - return FALSE; - } - - if ( isnanl( result ) || isinfl( result ) ) { return FALSE; } - - foobar_math_value_from_float( result, out_value ); - return TRUE; - } - case FOOBAR_MATH_EXPRESSION_OPERATION: - { - FoobarMathValue lhs = { 0 }; - if ( !foobar_math_evaluate( expr->operation.lhs, &lhs ) ) { return FALSE; } - FoobarMathValue rhs = { 0 }; - if ( !foobar_math_evaluate( expr->operation.rhs, &rhs ) ) - { - foobar_math_value_free( lhs ); - return FALSE; - } - - gboolean success = FALSE; - switch ( expr->operation.o ) - { - case FOOBAR_MATH_OPERATION_ADD: - success = foobar_math_value_add( &lhs, &rhs, out_value ); - break; - case FOOBAR_MATH_OPERATION_SUB: - success = foobar_math_value_sub( &lhs, &rhs, out_value ); - break; - case FOOBAR_MATH_OPERATION_MUL: - success = foobar_math_value_mul( &lhs, &rhs, out_value ); - break; - case FOOBAR_MATH_OPERATION_DIV: - success = foobar_math_value_div( &lhs, &rhs, out_value ); - break; - case FOOBAR_MATH_OPERATION_POW: - success = foobar_math_value_pow( &lhs, &rhs, out_value ); - break; - default: - g_warn_if_reached( ); - break; - } - - foobar_math_value_free( lhs ); - foobar_math_value_free( rhs ); - - return success; - } - default: - g_warn_if_reached( ); - return FALSE; - } -} - -// --------------------------------------------------------------------------------------------------------------------- -// Values -// --------------------------------------------------------------------------------------------------------------------- - -// -// Initialize a new integer value to zero. -// -// After this, the structure should be freed using foobar_math_value_free. -// -void foobar_math_value_new_int( FoobarMathValue* out_value ) -{ - out_value->type = FOOBAR_MATH_VALUE_INT; - mpz_init( out_value->int_value.v ); - out_value->int_value.decimal_places = 0; -} - -// -// Initialize a new value from its floating point representation. -// -// Although technically not necessary, you should still call foobar_math_value_free on the returned value. -// -void foobar_math_value_from_float( - long double value, - FoobarMathValue* out_value ) -{ - out_value->type = FOOBAR_MATH_VALUE_FLOAT; - out_value->float_value.v = value; -} - -// -// Parse a value from its string representation. This will always return an integer representation. -// -// On success (return value TRUE), the structure should be freed using foobar_math_value_free. -// -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]; - } - } - - foobar_math_value_new_int( out_value ); - out_value->int_value.decimal_places = input_length - 1 - sep_pos; - 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; -} - -// -// Create a copy of a value, allocating necessary memory. -// -// After this, the structure should be freed using foobar_math_value_free. -// -void foobar_math_value_copy( - FoobarMathValue value, - FoobarMathValue* out_value ) -{ - *out_value = value; - - switch ( value.type ) - { - case FOOBAR_MATH_VALUE_INT: - mpz_init_set( out_value->int_value.v, value.int_value.v ); - break; - case FOOBAR_MATH_VALUE_FLOAT: - break; - default: - g_warn_if_reached( ); - break; - } -} - -// -// Release memory associated with a value. -// -// This is necessary because the integer representation is stored using GMP, which can store arbitrarily large values. -// -void foobar_math_value_free( FoobarMathValue value ) -{ - switch ( value.type ) - { - case FOOBAR_MATH_VALUE_INT: - mpz_clear( value.int_value.v ); - break; - case FOOBAR_MATH_VALUE_FLOAT: - break; - default: - g_warn_if_reached( ); - break; - } -} - -// --------------------------------------------------------------------------------------------------------------------- -// Operations -// --------------------------------------------------------------------------------------------------------------------- - -// -// Operation equivalent to "value *= -1". -// -void foobar_math_value_negate( FoobarMathValue* value ) -{ - switch ( value->type ) - { - case FOOBAR_MATH_VALUE_INT: - mpz_neg( value->int_value.v, value->int_value.v ); - break; - case FOOBAR_MATH_VALUE_FLOAT: - value->float_value.v = -value->float_value.v; - break; - default: - g_warn_if_reached( ); - break; - } -} - -// -// Operation setting "out_value = lhs + rhs". -// -// After this, the result value should be freed using foobar_math_value_free. -// -gboolean foobar_math_value_add( - FoobarMathValue* lhs, - FoobarMathValue* rhs, - FoobarMathValue* out_value ) -{ - if ( 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; - 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 ); - foobar_math_value_from_float( lhs_float + rhs_float, out_value ); - return TRUE; -} - -// -// Operation setting "out_value = lhs - rhs". -// -// After this, the result value should be freed using foobar_math_value_free. -// -gboolean foobar_math_value_sub( - FoobarMathValue* lhs, - FoobarMathValue* rhs, - FoobarMathValue* out_value ) -{ - if ( 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; - 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 ); - foobar_math_value_from_float( lhs_float - rhs_float, out_value ); - return TRUE; -} - -// -// Operation setting "out_value = lhs * rhs". -// -// After this, the result value should be freed using foobar_math_value_free. -// -gboolean foobar_math_value_mul( - FoobarMathValue* lhs, - FoobarMathValue* rhs, - FoobarMathValue* out_value ) -{ - if ( lhs->type == FOOBAR_MATH_VALUE_INT && rhs->type == FOOBAR_MATH_VALUE_INT ) - { - foobar_math_value_new_int( out_value ); - 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 ); - foobar_math_value_from_float( lhs_float * rhs_float, out_value ); - return TRUE; -} - -// -// Operation setting "out_value = lhs / rhs". -// -// After this, the result value should be freed using foobar_math_value_free. -// -gboolean foobar_math_value_div( - FoobarMathValue* lhs, - FoobarMathValue* rhs, - FoobarMathValue* out_value ) -{ - if ( lhs->type == FOOBAR_MATH_VALUE_INT && rhs->type == FOOBAR_MATH_VALUE_INT ) - { - 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 ); - gboolean result_is_int = mpz_sgn( rem ) == 0; - mpz_clear( rem ); - - if ( result_is_int ) { return TRUE; } - 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 result = lhs_float / rhs_float; - if ( isnanl( result ) || isinfl( result ) ) { return FALSE; } - foobar_math_value_from_float( result, out_value ); - return TRUE; -} - -// -// Operation setting "out_value = lhs ^ rhs". -// -// After this, the result value should be freed using foobar_math_value_free. -// -gboolean foobar_math_value_pow( - FoobarMathValue* lhs, - FoobarMathValue* rhs, - FoobarMathValue* out_value ) -{ - 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 ); - 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 result = powl( lhs_float, rhs_float ); - if ( isnanl( result ) || isinfl( result ) ) { return FALSE; } - foobar_math_value_from_float( result, out_value ); - return TRUE; -} - -// -// Ensure that two integer values have the same number of decimal places. -// -// This may change the in-memory-representation of a value, but not its actual value. It only increases the precision of -// one value -// -// If either a or b is not stored as an integer, this will return FALSE. -// -gboolean foobar_math_value_unify_integers( - FoobarMathValue* a, - FoobarMathValue* b ) -{ - 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; -} - -// --------------------------------------------------------------------------------------------------------------------- -// Conversions -// --------------------------------------------------------------------------------------------------------------------- - -// -// Format a value as a string. -// -// The result is a null-terminated string which should be freed using g_free. -// -gchar* foobar_math_value_to_string( FoobarMathValue value ) -{ - switch ( value.type ) - { - case FOOBAR_MATH_VALUE_INT: - { - 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 ); - 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; - 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] = '.'; - } - if ( has_sign ) - { - result -= 1; - len += 1; - } - return result; - } - case FOOBAR_MATH_VALUE_FLOAT: - { - gchar* result = g_strdup_printf( "%.30Lf", value.float_value.v ); - - // trim trailing zeros - size_t len = strlen( result ); - for ( gsize i = 0; i < len; ++i ) - { - if ( result[len - 1 - i] == '.' ) - { - result[len - 1 - i] = '\0'; - break; - } - else if ( result[len - 1 - i] == '0' ) - { - result[len - 1 - i] = '\0'; - } - else - { - break; - } - } - - return result; - } - default: - g_warn_if_reached( ); - return NULL; - } -} - -// -// Convert a value to its floating-point representation. -// -// This may be a lossy conversion and should only be done if absolutely necessary. -// -long double foobar_math_value_to_float( FoobarMathValue value ) -{ - switch ( value.type ) - { - case FOOBAR_MATH_VALUE_INT: - { - mpz_t exp, integer, frac; - mpz_inits( exp, integer, frac, NULL ); - mpz_ui_pow_ui( exp, 10, value.int_value.decimal_places ); - mpz_mod( frac, value.int_value.v, exp ); - mpz_div( integer, value.int_value.v, exp ); - mpf_t f_exp, f_frac; - mpf_inits( f_exp, f_frac, NULL ); - mpf_set_z( f_exp, exp ); - mpf_set_z( f_frac, frac ); - mpf_div( f_frac, f_frac, f_exp ); - long double result = mpz_get_d( integer ) + mpf_get_d( f_frac ); - mpf_clears( f_exp, f_frac, NULL ); - mpz_clears( exp, integer, frac, NULL ); - return result; - } - case FOOBAR_MATH_VALUE_FLOAT: - return value.float_value.v; - default: - g_warn_if_reached( ); - return 0; - } -} diff --git a/src/services/quick-answers/math.h b/src/services/quick-answers/math.h deleted file mode 100644 index fafa73d..0000000 --- a/src/services/quick-answers/math.h +++ /dev/null @@ -1,148 +0,0 @@ -#pragma once - -#include -#include - -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_new_value ( FoobarMathValue value ); -FoobarMathExpression* foobar_math_expression_new_function ( FoobarMathFunction function, - FoobarMathExpression* input ); -FoobarMathExpression* foobar_math_expression_new_constant ( FoobarMathConstant constant ); -FoobarMathExpression* foobar_math_expression_new_operation( FoobarMathOperation operation, - FoobarMathExpression* lhs, - FoobarMathExpression* rhs ); -void foobar_math_expression_print ( FoobarMathExpression const* expr, - gint indentation ); -gboolean foobar_math_evaluate ( FoobarMathExpression const* expr, - FoobarMathValue* out_value ); -void foobar_math_expression_free ( FoobarMathExpression* expression ); -void foobar_math_value_new_int ( FoobarMathValue* out_value ); -void foobar_math_value_from_float ( long double value, - FoobarMathValue* out_value ); -gboolean foobar_math_value_from_string ( gchar const* input, - gsize input_length, - FoobarMathValue* out_value ); -void foobar_math_value_copy ( FoobarMathValue value, - 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, - FoobarMathValue* out_value ); -gboolean foobar_math_value_sub ( FoobarMathValue* lhs, - FoobarMathValue* rhs, - FoobarMathValue* out_value ); -gboolean foobar_math_value_mul ( FoobarMathValue* lhs, - FoobarMathValue* rhs, - FoobarMathValue* out_value ); -gboolean foobar_math_value_div ( FoobarMathValue* lhs, - FoobarMathValue* rhs, - FoobarMathValue* out_value ); -gboolean foobar_math_value_pow ( FoobarMathValue* lhs, - FoobarMathValue* rhs, - FoobarMathValue* out_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 ); - -G_END_DECLS diff --git a/src/services/quick-answers/math.test.c b/src/services/quick-answers/math.test.c deleted file mode 100644 index 416b576..0000000 --- a/src/services/quick-answers/math.test.c +++ /dev/null @@ -1,153 +0,0 @@ -#include "services/quick-answers/math.h" -#include - -#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 deleted file mode 100644 index ae468c2..0000000 --- a/src/services/quick-answers/meson.build +++ /dev/null @@ -1,9 +0,0 @@ -foobar_sources += files( - 'math-lexer.c', - 'math-parser.c', - 'math.c', -) - -foobar_tests += { - 'math': files('math.test.c'), -} -- cgit v1.3.1