summaryrefslogtreecommitdiff
path: root/src/services/quick-answers
diff options
context:
space:
mode:
authorHannes Schulze <haschu0103@outlook.de>2024-11-04 12:51:05 +0100
committerHannes Schulze <haschu0103@outlook.de>2024-11-04 12:51:05 +0100
commit83d650b82e5209d2ca24cc1e6bf04e0644b3f615 (patch)
tree29e89bf90cd56d26f7432a52bf4bc90c51c632ce /src/services/quick-answers
parente36217208802c001f1fbc758e5f103cbc4f339a2 (diff)
add docs
Diffstat (limited to 'src/services/quick-answers')
-rw-r--r--src/services/quick-answers/math-lexer.c73
-rw-r--r--src/services/quick-answers/math-parser.c110
-rw-r--r--src/services/quick-answers/math.c437
-rw-r--r--src/services/quick-answers/math.h8
4 files changed, 462 insertions, 166 deletions
diff --git a/src/services/quick-answers/math-lexer.c b/src/services/quick-answers/math-lexer.c
index b0d24b7..7fbe877 100644
--- a/src/services/quick-answers/math-lexer.c
+++ b/src/services/quick-answers/math-lexer.c
@@ -1,5 +1,24 @@
#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
@@ -24,6 +43,18 @@ static void lexer_commit_token ( Lexer* ctx,
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,
@@ -115,16 +146,29 @@ FoobarMathToken* foobar_math_lex(
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.
@@ -132,6 +176,15 @@ gboolean char_is_identifier( char c )
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 )
@@ -142,6 +195,11 @@ char lexer_peek(
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; }
@@ -149,11 +207,21 @@ char lexer_pop( Lexer* ctx )
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 )
@@ -165,6 +233,11 @@ void lexer_commit_token(
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 )
diff --git a/src/services/quick-answers/math-parser.c b/src/services/quick-answers/math-parser.c
index 7f93552..17d976d 100644
--- a/src/services/quick-answers/math-parser.c
+++ b/src/services/quick-answers/math-parser.c
@@ -1,5 +1,11 @@
#include "services/quick-answers/math.h"
+//
+// Parser:
+//
+// The parser context/state which is passed around.
+//
+
typedef struct _Parser Parser;
struct _Parser
@@ -9,6 +15,12 @@ struct _Parser
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,
@@ -18,8 +30,6 @@ typedef enum
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 );
@@ -31,11 +41,24 @@ static FoobarMathExpression* parser_process_function ( Parser*
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 )
@@ -59,20 +82,9 @@ FoobarMathExpression* foobar_math_parse(
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++];
-}
-
+//
+// 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,
@@ -113,6 +125,12 @@ FoobarMathExpression* parser_process(
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 )
@@ -143,6 +161,11 @@ FoobarMathExpression* parser_process_single(
}
}
+//
+// 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 );
@@ -216,6 +239,9 @@ FoobarMathExpression* parser_process_identifier( Parser* ctx )
}
}
+//
+// Process a numeric value.
+//
FoobarMathExpression* parser_process_number( Parser* ctx )
{
FoobarMathToken const* token = parser_pop( ctx );
@@ -228,6 +254,9 @@ FoobarMathExpression* parser_process_number( Parser* ctx )
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 )
@@ -244,6 +273,9 @@ FoobarMathExpression* parser_process_function(
return foobar_math_expression_new_function( function, arg );
}
+//
+// Process an expression enclosed in parenthesis.
+//
FoobarMathExpression* parser_process_parens( Parser* ctx )
{
parser_pop( ctx );
@@ -260,6 +292,9 @@ FoobarMathExpression* parser_process_parens( Parser* ctx )
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 );
@@ -276,6 +311,43 @@ FoobarMathExpression* parser_process_sign( Parser* ctx )
}
}
+// ---------------------------------------------------------------------------------------------------------------------
+// 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 )
@@ -285,6 +357,9 @@ gboolean parser_match_identifier(
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 )
@@ -311,6 +386,9 @@ InfixPrecedence parser_operator_precedence( FoobarMathToken const* token )
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 )
diff --git a/src/services/quick-answers/math.c b/src/services/quick-answers/math.c
index e813808..69219e2 100644
--- a/src/services/quick-answers/math.c
+++ b/src/services/quick-answers/math.c
@@ -3,6 +3,16 @@
#include "services/quick-answers/math.h"
#include <math.h>
+//
+// 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;
@@ -34,6 +44,29 @@ struct _FoobarMathExpression
};
};
+//
+// 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 );
@@ -42,6 +75,12 @@ FoobarMathExpression* foobar_math_expression_new_value( FoobarMathValue 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 )
@@ -53,6 +92,9 @@ FoobarMathExpression* foobar_math_expression_new_function(
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 );
@@ -61,6 +103,12 @@ FoobarMathExpression* foobar_math_expression_new_constant( FoobarMathConstant co
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,
@@ -74,6 +122,9 @@ FoobarMathExpression* foobar_math_expression_new_operation(
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 )
@@ -101,6 +152,12 @@ void foobar_math_expression_free( FoobarMathExpression* 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 )
@@ -213,6 +270,158 @@ void foobar_math_expression_print(
}
}
+//
+// 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;
@@ -220,6 +429,24 @@ void foobar_math_value_new_int( FoobarMathValue* out_value )
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,
@@ -251,14 +478,11 @@ gboolean foobar_math_value_from_string(
return TRUE;
}
-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;
-}
-
+//
+// 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 )
@@ -278,6 +502,11 @@ void foobar_math_value_copy(
}
}
+//
+// 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 )
@@ -293,6 +522,13 @@ void foobar_math_value_free( FoobarMathValue value )
}
}
+// ---------------------------------------------------------------------------------------------------------------------
+// Operations
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// Operation equivalent to "value *= -1".
+//
void foobar_math_value_negate( FoobarMathValue* value )
{
switch ( value->type )
@@ -309,6 +545,11 @@ void foobar_math_value_negate( FoobarMathValue* value )
}
}
+//
+// 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,
@@ -328,6 +569,11 @@ gboolean foobar_math_value_add(
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,
@@ -347,6 +593,11 @@ gboolean foobar_math_value_sub(
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,
@@ -366,6 +617,11 @@ gboolean foobar_math_value_mul(
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,
@@ -400,6 +656,11 @@ gboolean foobar_math_value_div(
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,
@@ -425,6 +686,14 @@ gboolean foobar_math_value_pow(
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 )
@@ -457,6 +726,15 @@ gboolean foobar_math_value_unify_integers(
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 )
@@ -538,6 +816,11 @@ gchar* foobar_math_value_to_string( FoobarMathValue value )
}
}
+//
+// 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 )
@@ -566,141 +849,3 @@ long double foobar_math_value_to_float( FoobarMathValue value )
return 0;
}
}
-
-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;
- }
-}
diff --git a/src/services/quick-answers/math.h b/src/services/quick-answers/math.h
index c66a2bf..fafa73d 100644
--- a/src/services/quick-answers/math.h
+++ b/src/services/quick-answers/math.h
@@ -112,13 +112,15 @@ FoobarMathExpression* foobar_math_expression_new_operation( FoobarMathOperation
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_from_float ( long double value,
- FoobarMathValue* out_value );
void foobar_math_value_copy ( FoobarMathValue value,
FoobarMathValue* out_value );
void foobar_math_value_free ( FoobarMathValue value );
@@ -142,7 +144,5 @@ gboolean foobar_math_value_unify_integers ( FoobarMathValue*
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,
- FoobarMathValue* out_value );
G_END_DECLS