improve the doxygen documentation of the var-part of mupp.

This commit is contained in:
2025-11-25 17:56:04 +01:00
parent 4cbf625b84
commit 322aa58fbc
14 changed files with 931 additions and 116 deletions

View File

@@ -42,40 +42,89 @@
namespace mupp
{
///////////////////////////////////////////////////////////////////////////////
// The annotation handler links the AST to a map of iterator positions
// for the purpose of subsequent semantic error handling when the
// program is being compiled.
/**
* @brief The PAnnotation struct links AST nodes to source code positions.
*
* The annotation handler associates each AST node with its corresponding
* iterator position in the source code. This mapping enables accurate error
* reporting during semantic analysis and compilation by allowing the error
* handler to pinpoint the exact location of errors in the source text.
*
* The annotation is performed after successful parsing but before semantic
* analysis, using Boost.Phoenix function objects integrated with the parser.
*
* @tparam Iterator the iterator type for traversing the source code (typically std::string::const_iterator)
*/
///////////////////////////////////////////////////////////////////////////////
template <typename Iterator>
struct PAnnotation
{
/// Result type specification required by Boost.Phoenix function protocol
template <typename, typename>
struct result { typedef void type; };
std::vector<Iterator>& iters;
std::vector<Iterator>& iters; ///< Reference to vector storing iterator positions indexed by AST node IDs
/**
* @brief Constructor that initializes the annotation handler.
* @param iters reference to vector that will store iterator positions for each annotated AST node
*/
PAnnotation(std::vector<Iterator>& iters) : iters(iters) {}
/**
* @brief Helper struct to set ID tags on AST nodes.
*
* Uses template metaprogramming to selectively assign IDs only to nodes
* that inherit from ast::tagged, avoiding overhead for non-tagged nodes.
*/
struct set_id
{
typedef void result_type;
typedef void result_type; ///< Return type for Boost.Phoenix compatibility
int id;
int id; ///< The ID to assign to tagged AST nodes
/**
* @brief Constructor that stores the ID to be assigned.
* @param id the unique identifier corresponding to a source position
*/
set_id(int id) : id(id) {}
/**
* @brief Dispatches to the appropriate handler based on node type.
*
* Uses boost::is_base_of to determine at compile time whether the node
* inherits from ast::tagged.
* @tparam T the AST node type
* @param x reference to the AST node to potentially tag
*/
template <typename T>
void operator()(T& x) const
{
this->dispatch(x, boost::is_base_of<ast::tagged, T>());
}
// This will catch all nodes except those inheriting from ast::tagged
/**
* @brief No-op handler for non-tagged AST nodes.
*
* This overload is selected at compile time for nodes that don't
* inherit from ast::tagged.
* @tparam T the non-tagged AST node type
* @param x reference to the AST node (unused)
*/
template <typename T>
void dispatch(T& x, boost::mpl::false_) const
{
// (no-op) no need for tags
}
// This will catch all nodes inheriting from ast::tagged
/**
* @brief Assigns ID to tagged AST nodes.
*
* This overload is selected at compile time for nodes that inherit
* from ast::tagged, setting their id field.
* @tparam T the tagged AST node type
* @param x reference to the AST node whose id field will be set
*/
template <typename T>
void dispatch(T& x, boost::mpl::true_) const
{
@@ -83,6 +132,14 @@ namespace mupp
}
};
/**
* @brief Annotates an operand AST node with its source position.
*
* Stores the iterator position and assigns a unique ID to the operand node
* by applying the set_id visitor to handle different operand variant types.
* @param ast reference to the operand AST node to annotate
* @param pos iterator pointing to the operand's position in the source code
*/
void operator()(ast::operand& ast, Iterator pos) const
{
int id = iters.size();
@@ -90,6 +147,14 @@ namespace mupp
boost::apply_visitor(set_id(id), ast);
}
/**
* @brief Annotates an assignment AST node with its source position.
*
* Stores the iterator position and assigns a unique ID to the left-hand
* side variable of the assignment.
* @param ast reference to the assignment AST node to annotate
* @param pos iterator pointing to the assignment's position in the source code
*/
void operator()(ast::assignment& ast, Iterator pos) const
{
int id = iters.size();

View File

@@ -43,119 +43,243 @@
namespace mupp { namespace ast
{
///////////////////////////////////////////////////////////////////////////
// The AST
/**
* @brief Abstract Syntax Tree (AST) definitions for the variable parser.
*
* This namespace defines the complete AST structure used to represent
* parsed variable expressions. The AST is built using Boost.Variant for
* type-safe unions and Boost.Spirit's automatic AST generation from
* grammar rules.
*
* The AST supports:
* - Arithmetic operations: +, -, *, /
* - Unary operations: +, -
* - Mathematical functions: sin, cos, tan, exp, log, sqrt, etc.
* - Power operations: pow(base, exponent)
* - Variable declarations and assignments
* - Expression evaluation with proper precedence
*/
///////////////////////////////////////////////////////////////////////////
/**
* @brief Base struct for AST nodes that require position annotation.
*
* Tagged nodes store an ID that maps to their position in the source code.
* This enables accurate error reporting by the error handler. Not all AST
* nodes need to be tagged; only those that may generate semantic errors.
*/
struct tagged
{
int id; // Used to annotate the AST with the iterator position.
// This id is used as a key to a map<int, Iterator>
// (not really part of the AST.)
int id; ///< ID used as key to map to iterator position in source code (not part of the logical AST structure)
};
/**
* @brief Enumeration of arithmetic and unary operators.
*
* These tokens represent the fundamental operations supported by the
* expression evaluator.
*/
enum optoken
{
op_plus,
op_minus,
op_times,
op_divide,
op_positive,
op_negative,
op_plus, ///< Addition operator (+)
op_minus, ///< Subtraction operator (-)
op_times, ///< Multiplication operator (*)
op_divide, ///< Division operator (/)
op_positive, ///< Unary plus operator (+x)
op_negative, ///< Unary minus operator (-x)
};
/**
* @brief Enumeration of supported mathematical functions.
*
* These function identifiers map to standard mathematical operations
* evaluated during the semantic analysis phase.
*/
enum funid
{
fun_max,
fun_min,
fun_abs,
fun_sin,
fun_cos,
fun_tan,
fun_sinh,
fun_cosh,
fun_tanh,
fun_asin,
fun_acos,
fun_atan,
fun_exp,
fun_log,
fun_ln,
fun_sqrt
fun_max, ///< Maximum value function
fun_min, ///< Minimum value function
fun_abs, ///< Absolute value function
fun_sin, ///< Sine function
fun_cos, ///< Cosine function
fun_tan, ///< Tangent function
fun_sinh, ///< Hyperbolic sine function
fun_cosh, ///< Hyperbolic cosine function
fun_tanh, ///< Hyperbolic tangent function
fun_asin, ///< Arcsine function
fun_acos, ///< Arccosine function
fun_atan, ///< Arctangent function
fun_exp, ///< Exponential function
fun_log, ///< Base-10 logarithm function
fun_ln, ///< Natural logarithm function
fun_sqrt ///< Square root function
};
/**
* @brief Represents an empty/null AST node.
*
* Used as a placeholder in variant types where no value is present.
*/
struct nil {};
// Forward declarations for recursive AST structures
struct unary;
struct expression;
struct function;
struct power;
/**
* @brief Represents a variable in an expression.
*
* Variables are identifiers prefixed with '$' in the source code.
* They reference parameters from the collection or previously declared
* variables. Inherits from tagged to support error reporting.
*/
struct variable : tagged
{
/**
* @brief Constructor for variable node.
* @param name the variable name (without the leading '$')
*/
variable(std::string const& name = "") : name(name) {}
std::string name;
std::string name; ///< Variable name without the '$' prefix
};
/**
* @brief Variant type representing any operand in an expression.
*
* An operand can be a literal number, variable, function call, power
* operation, unary expression, or parenthesized expression. The variant
* uses recursive_wrapper for types that contain expressions to handle
* recursive grammar structures.
*/
typedef boost::variant<
nil
, double
, variable
, boost::recursive_wrapper<function>
, boost::recursive_wrapper<power>
, boost::recursive_wrapper<unary>
, boost::recursive_wrapper<expression>
nil ///< Empty placeholder
, double ///< Numeric literal
, variable ///< Variable reference
, boost::recursive_wrapper<function> ///< Function call (recursive)
, boost::recursive_wrapper<power> ///< Power operation (recursive)
, boost::recursive_wrapper<unary> ///< Unary operation (recursive)
, boost::recursive_wrapper<expression> ///< Parenthesized expression (recursive)
>
operand;
/**
* @brief Represents a unary operation applied to an operand.
*
* Examples: -x, +y
*/
struct unary
{
optoken operator_;
operand operand_;
optoken operator_; ///< The unary operator (positive or negative)
operand operand_; ///< The operand the operator is applied to
};
/**
* @brief Represents a binary operation with an operator and right operand.
*
* Used in expression chains where the left operand is the accumulated
* result from previous operations.
*/
struct operation
{
optoken operator_;
operand operand_;
optoken operator_; ///< The binary operator (+, -, *, /)
operand operand_; ///< The right-hand operand
};
/**
* @brief Represents a complete expression with operator precedence.
*
* Expressions are built as a first operand followed by a sequence of
* operations. This structure naturally encodes operator precedence as
* determined by the grammar rules.
*/
struct expression
{
operand first;
std::list<operation> rest;
operand first; ///< The first operand in the expression
std::list<operation> rest; ///< Sequence of operations applied left-to-right
};
/**
* @brief Represents a function call with a single argument.
*
* Examples: sin($x), sqrt($y), abs($z)
*/
struct function
{
funid func_id;
expression arg;
funid func_id; ///< The function identifier
expression arg; ///< The argument expression
};
/**
* @brief Represents a power operation.
*
* Syntax: pow(base, exponent)
* Both base and exponent are full expressions.
*/
struct power
{
expression base;
expression pow;
expression base; ///< The base expression
expression pow; ///< The exponent expression
};
/**
* @brief Represents an assignment statement.
*
* Syntax: var_name = expression
* Assigns the result of evaluating the right-hand expression to an
* existing variable.
*/
struct assignment
{
variable lhs;
expression rhs;
variable lhs; ///< The left-hand side variable being assigned to
expression rhs; ///< The right-hand side expression to evaluate
};
/**
* @brief Represents a variable declaration with optional initialization.
*
* Syntax: var var_name = expression (with initialization)
* var var_name (without initialization)
* Declares a new variable and optionally initializes it with an expression.
*/
struct variable_declaration
{
variable lhs;
boost::optional<expression> rhs;
variable lhs; ///< The variable being declared
boost::optional<expression> rhs; ///< Optional initialization expression
};
/**
* @brief Variant type representing a single statement.
*
* A statement can be either a variable declaration or an assignment.
*/
typedef boost::variant<
variable_declaration
, assignment>
variable_declaration ///< Variable declaration statement
, assignment> ///< Assignment statement
statement;
/**
* @brief Type alias for a list of statements forming a program.
*
* The parser builds this list from the input, and the semantic analyzer
* processes it sequentially.
*/
typedef std::list<statement> statement_list;
// print functions for debugging
/**
* @brief Stream output operator for nil nodes (debugging support).
* @param out the output stream
* @return the output stream for chaining
*/
inline std::ostream& operator<<(std::ostream& out, nil) { out << std::string("nil"); return out; }
/**
* @brief Stream output operator for variable nodes (debugging support).
* @param out the output stream
* @param var the variable node to output
* @return the output stream for chaining
*/
inline std::ostream& operator<<(std::ostream& out, variable const& var) { out << var.name; return out; }
}}

View File

@@ -41,17 +41,49 @@
namespace mupp
{
///////////////////////////////////////////////////////////////////////////////
// The error handler
/**
* @brief The PErrorHandler struct handles parsing and semantic errors.
*
* This error handler logs detailed error information to a file, including
* the error message, line number, source line, and a visual pointer to the
* error position. It integrates with the Boost.Spirit parser error handling
* mechanism and works in conjunction with the PAnnotation handler to map
* AST node IDs back to source positions.
*
* Errors are appended to: ~/.musrfit/mupp/mupp_err.log
*
* @tparam Iterator the iterator type for the source code (typically std::string::const_iterator)
*/
///////////////////////////////////////////////////////////////////////////////
template <typename Iterator>
struct PErrorHandler
{
/// Result type specification required by Boost.Phoenix function protocol
template <typename, typename, typename>
struct result { typedef void type; };
/**
* @brief Constructor that stores the source code range.
* @param first iterator pointing to the beginning of the source code
* @param last iterator pointing to the end of the source code
*/
PErrorHandler(Iterator first, Iterator last)
: first(first), last(last) {}
/**
* @brief Function call operator that logs an error to file.
*
* Determines the line number and position of the error, formats a
* user-friendly error message with a visual indicator (^~~), and appends
* it to the error log file. Handles both mid-file errors and unexpected
* end-of-file errors.
*
* @tparam Message the message type (typically std::string)
* @tparam What the expected element type (typically std::string)
* @param message the error message prefix (e.g., "**ERROR** Expecting ")
* @param what description of what was expected at the error position
* @param err_pos iterator pointing to the position where the error occurred
*/
template <typename Message, typename What>
void operator()(
Message const& message,
@@ -76,6 +108,16 @@ namespace mupp
}
}
/**
* @brief Finds the start of the line containing an error and computes line number.
*
* Scans from the beginning of the source to the error position, counting
* line breaks (CR, LF, or CRLF) to determine the line number.
*
* @param err_pos iterator pointing to the error position
* @param line output parameter that will contain the 1-based line number
* @return iterator pointing to the start of the line containing the error
*/
Iterator get_pos(Iterator err_pos, int& line) const
{
line = 1;
@@ -99,6 +141,14 @@ namespace mupp
return line_start;
}
/**
* @brief Extracts the complete line containing an error.
*
* Returns the substring from the start of the line to the next line break.
*
* @param err_pos iterator pointing to the start of the line
* @return string containing the complete line (without line break characters)
*/
std::string get_line(Iterator err_pos) const
{
Iterator i = err_pos;
@@ -108,9 +158,9 @@ namespace mupp
return std::string(err_pos, i);
}
Iterator first;
Iterator last;
std::vector<Iterator> iters;
Iterator first; ///< Iterator to the beginning of the source code
Iterator last; ///< Iterator to the end of the source code
std::vector<Iterator> iters; ///< Vector mapping AST node IDs to source positions (used by PAnnotation)
};
}

View File

@@ -59,22 +59,51 @@ namespace mupp { namespace parser
namespace ascii = boost::spirit::ascii;
///////////////////////////////////////////////////////////////////////////////
// The expression grammar
/**
* @brief The PExpression grammar for parsing mathematical expressions.
*
* This Boost.Spirit grammar defines the syntax for parsing arithmetic
* expressions with proper operator precedence and associativity:
* - Primary expressions: numbers, variables, functions, parenthesized expressions
* - Unary operators: +, - (highest precedence)
* - Multiplicative operators: *, / (medium precedence)
* - Additive operators: +, - (lowest precedence)
*
* The grammar supports:
* - Numeric literals (double)
* - Variable identifiers (prefixed with '$')
* - Mathematical functions: sin, cos, tan, exp, log, sqrt, etc.
* - Power function: pow(base, exponent)
* - Arithmetic operations with standard precedence
*
* Example expressions:
* - 3.14 * $radius
* - sin($theta) + cos($phi)
* - pow($x, 2.0) + pow($y, 2.0)
*
* @tparam Iterator the iterator type for the input (typically std::string::const_iterator)
*/
///////////////////////////////////////////////////////////////////////////////
template <typename Iterator>
struct PExpression : qi::grammar<Iterator, ast::expression(), PSkipper<Iterator> >
{
/**
* @brief Constructor that initializes the grammar rules.
* @param error_handler reference to the error handler for reporting parse errors
*/
PExpression(PErrorHandler<Iterator>& error_handler);
qi::symbols<char, ast::optoken> additive_op, multiplicative_op, unary_op;
qi::symbols<char, ast::funid> fun_tok;
qi::symbols<char, ast::optoken> additive_op; ///< Symbol table for additive operators (+, -)
qi::symbols<char, ast::optoken> multiplicative_op; ///< Symbol table for multiplicative operators (*, /)
qi::symbols<char, ast::optoken> unary_op; ///< Symbol table for unary operators (+, -)
qi::symbols<char, ast::funid> fun_tok; ///< Symbol table for function names
qi::rule<Iterator, ast::expression(), PSkipper<Iterator> > expr;
qi::rule<Iterator, ast::expression(), PSkipper<Iterator> > additive_expr;
qi::rule<Iterator, ast::expression(), PSkipper<Iterator> > multiplicative_expr;
qi::rule<Iterator, ast::operand(), PSkipper<Iterator> > unary_expr;
qi::rule<Iterator, ast::operand(), PSkipper<Iterator> > primary_expr;
qi::rule<Iterator, std::string(), PSkipper<Iterator> > identifier;
qi::rule<Iterator, ast::expression(), PSkipper<Iterator> > expr; ///< Top-level expression rule
qi::rule<Iterator, ast::expression(), PSkipper<Iterator> > additive_expr; ///< Additive expression rule (lowest precedence)
qi::rule<Iterator, ast::expression(), PSkipper<Iterator> > multiplicative_expr; ///< Multiplicative expression rule (medium precedence)
qi::rule<Iterator, ast::operand(), PSkipper<Iterator> > unary_expr; ///< Unary expression rule
qi::rule<Iterator, ast::operand(), PSkipper<Iterator> > primary_expr; ///< Primary expression rule (highest precedence)
qi::rule<Iterator, std::string(), PSkipper<Iterator> > identifier; ///< Identifier rule (variables prefixed with '$')
};
}}

View File

@@ -8,6 +8,19 @@
Based on Joel de Guzman example on calc7,
see https://github.com/boostorg/spirit
This file contains the implementation (definition) of the PExpression
template grammar. It defines the actual grammar rules and their semantic
actions using Boost.Spirit Qi.
The grammar implements expression parsing with proper operator precedence:
- Primary expressions (literals, variables, functions)
- Unary operations (+, -)
- Multiplicative operations (*, /)
- Additive operations (+, -)
Symbol tables are populated with operators and function names, and error
handling is integrated for reporting parse failures.
***************************************************************************/
/***************************************************************************

View File

@@ -48,39 +48,128 @@
namespace mupp { namespace prog {
///////////////////////////////////////////////////////////////////////////
// Variable Handler
/**
* @brief The PVarHandler class manages variable data during evaluation.
*
* This class stores a variable's name, values, and associated errors.
* Variables are vector-valued, where each element corresponds to data
* from a different run in the collection. The values and errors vectors
* must have the same size for the variable to be valid.
*
* This class is used by both the semantic analyzer (PProgram) to track
* declared variables and by the evaluator (PProgEval) to store and
* retrieve computed results.
*/
///////////////////////////////////////////////////////////////////////////
class PVarHandler
{
public:
/**
* @brief Default constructor initializing an unnamed variable.
*/
PVarHandler() : fName("") {}
/**
* @brief Sets the variable name.
* @param name the name to assign to this variable
*/
void SetName(std::string name) { fName = name; }
/**
* @brief Sets all values for this variable.
* @param dval vector of values to assign
*/
void SetValue(std::vector<double> &dval) { fValue = dval; }
/**
* @brief Sets a single value at a specific index.
* @param dval the value to set
* @param idx the index at which to set the value
*/
void SetValue(double dval, unsigned idx);
/**
* @brief Sets all error values for this variable.
* @param dval vector of error values to assign
*/
void SetError(std::vector<double> &dval) { fError = dval; }
/**
* @brief Sets a single error value at a specific index.
* @param dval the error value to set
* @param idx the index at which to set the error value
*/
void SetError(double dval, unsigned idx);
/**
* @brief Gets the variable name.
* @return the name of this variable
*/
std::string GetName() { return fName; }
/**
* @brief Gets the size of the variable data.
* @return the size if value and error vectors match, 0 otherwise
*/
unsigned int GetSize() { return (fValue.size() == fError.size()) ? fValue.size() : 0; }
/**
* @brief Gets all values.
* @return vector of all values
*/
std::vector<double> GetValue() { return fValue; }
/**
* @brief Gets a single value at a specific index.
* @param idx the index of the value to retrieve
* @return the value at the specified index, or 0 if index is out of range
*/
double GetValue(unsigned int idx) { return (idx < fValue.size()) ? fValue[idx] : 0; }
/**
* @brief Gets all error values.
* @return vector of all error values
*/
std::vector<double> GetError() { return fError; }
/**
* @brief Gets a single error value at a specific index.
* @param idx the index of the error value to retrieve
* @return the error value at the specified index, or 0 if index is out of range
*/
double GetError(unsigned int idx) { return (idx < fError.size()) ? fError[idx] : 0; }
private:
std::string fName;
std::vector<double> fValue;
std::vector<double> fError;
std::string fName; ///< Variable name
std::vector<double> fValue; ///< Vector of values (one per run)
std::vector<double> fError; ///< Vector of error values (one per run)
};
///////////////////////////////////////////////////////////////////////////
// Program Semantic Analysis
/**
* @brief The PProgram struct performs semantic analysis on the AST.
*
* This visitor pattern struct traverses the AST generated by the parser
* and performs semantic checks:
* - Verifies that variables are declared before use
* - Prevents duplicate variable declarations
* - Validates function and operator usage
* - Resolves variable references and position-based lookups
*
* After successful semantic analysis, the variable table can be used by
* PProgEval for expression evaluation. Semantic errors are reported via
* the error handler with source position information.
*/
///////////////////////////////////////////////////////////////////////////
struct PProgram
{
typedef bool result_type;
typedef bool result_type; ///< Return type for all visitor methods
/**
* @brief Constructor that sets up the error handler.
* @tparam PErrorHandler the error handler type
* @param error_handler_ reference to the error handler for reporting semantic errors
*/
template <typename PErrorHandler>
PProgram(PErrorHandler& error_handler_)
{
@@ -91,67 +180,278 @@ namespace mupp { namespace prog {
"**ERROR** ", _2, phx::cref(error_handler_.iters)[_1]);
}
/**
* @brief Visitor for nil AST nodes (should never be called).
* @return always false (assertion failure)
*/
bool operator()(ast::nil) { BOOST_ASSERT(0); return false; }
/**
* @brief Visitor for numeric literal nodes.
* @param x the numeric value
* @return true (literals are always valid)
*/
bool operator()(double x);
/**
* @brief Visitor for assignment statements.
* @param x the assignment AST node
* @return true if variable exists and RHS is valid, false otherwise
*/
bool operator()(ast::assignment const &x);
/**
* @brief Visitor for expression nodes.
* @param x the expression AST node
* @return true if all operands and operations are valid, false otherwise
*/
bool operator()(ast::expression const &x);
/**
* @brief Visitor for function call nodes.
* @param x the function AST node
* @return true if function is valid and argument is valid, false otherwise
*/
bool operator()(ast::function const &x);
/**
* @brief Visitor for binary operation nodes.
* @param x the operation AST node
* @return true if operation and operand are valid, false otherwise
*/
bool operator()(ast::operation const &x);
/**
* @brief Visitor for power operation nodes.
* @param x the power AST node
* @return true if base and exponent are valid, false otherwise
*/
bool operator()(ast::power const &x);
/**
* @brief Visitor for statement nodes (variant wrapper).
* @param x the statement AST node
* @return result of visiting the underlying statement type
*/
bool operator()(ast::statement const &x);
/**
* @brief Visitor for statement lists (program entry point).
* @param x the statement list AST node
* @return true if all statements are valid, false if any statement fails
*/
bool operator()(ast::statement_list const &x);
/**
* @brief Visitor for unary operation nodes.
* @param x the unary AST node
* @return true if operator and operand are valid, false otherwise
*/
bool operator()(ast::unary const &x);
/**
* @brief Visitor for variable reference nodes.
* @param x the variable AST node
* @return true if variable is declared, false otherwise
*/
bool operator()(ast::variable const &x);
/**
* @brief Visitor for variable declaration nodes.
* @param x the variable declaration AST node
* @return true if declaration is valid and RHS (if present) is valid, false otherwise
*/
bool operator()(ast::variable_declaration const &x);
/**
* @brief Injects predefined variable values from collection data.
* @param name the variable name
* @param val vector of values for this variable
* @param err vector of error values for this variable
*/
void add_predef_var_values(const std::string &name,
std::vector<double> &val,
std::vector<double> &err);
/**
* @brief Adds a variable to the symbol table.
* @param name the variable name to add
*/
void add_var(std::string const& name);
/**
* @brief Checks if a variable exists in the symbol table.
* @param name the variable name to find (with or without leading '$')
* @return true if variable exists, false otherwise
*/
bool find_var(std::string const &name);
/**
* @brief Finds a variable and returns its index.
* @param name the variable name to find (with or without leading '$')
* @param idx output parameter for the variable index
* @return true if variable exists, false otherwise
*/
bool find_var(std::string const &name, unsigned int &idx);
/**
* @brief Converts position-based variable reference to variable name.
*
* Supports syntax like $0, $1, etc. to reference variables by position.
* @param name the variable reference (name or position number)
* @param ok output parameter indicating whether conversion succeeded
* @return the resolved variable name, or "??" if position is out of range
*/
std::string pos_to_var(std::string const &name, bool &ok);
/**
* @brief Gets all variables from the symbol table.
* @return vector of all variable handlers
*/
std::vector<PVarHandler> getVars() { return fVariable; }
private:
std::vector<PVarHandler> fVariable;
std::map<int, std::string> fVarPos;
std::vector<PVarHandler> fVariable; ///< Symbol table of declared variables
std::map<int, std::string> fVarPos; ///< Map from position index to variable name
boost::function<
void(int tag, std::string const& what)>
error_handler;
error_handler; ///< Error handler function for reporting semantic errors
};
///////////////////////////////////////////////////////////////////////////
// Program Evaluation
/**
* @brief The PProgEval struct evaluates expressions using the AST.
*
* This visitor pattern struct traverses the AST and computes numerical
* results for all variables. It operates on vector-valued variables where
* each vector element corresponds to a different run in the collection.
*
* The evaluator:
* - Evaluates arithmetic operations element-wise on vectors
* - Computes mathematical functions on each vector element
* - Handles both regular variables and error variables (suffixed with 'Err')
* - Stores results back into the variable table
*
* After evaluation, variable values and errors can be extracted using
* getVar() for use in the GUI.
*/
///////////////////////////////////////////////////////////////////////////
struct PProgEval
{
typedef std::vector<double> result_type;
typedef std::vector<double> result_type; ///< Return type for all visitor methods (vector of values)
/**
* @brief Constructor that initializes the evaluator with variables.
* @param var vector of variable handlers from semantic analysis
*/
PProgEval(std::vector<PVarHandler> var) : fVariable(var) {}
/**
* @brief Evaluates a nil node (should never be called).
* @return zero-filled vector
*/
std::vector<double> operator()(ast::nil);
/**
* @brief Evaluates a numeric literal.
* @param x the numeric value
* @return vector filled with the literal value
*/
std::vector<double> operator()(double x);
/**
* @brief Evaluates an assignment statement.
* @param x the assignment AST node
* @return the computed RHS values, also stored in the variable
*/
std::vector<double> operator()(ast::assignment const &x);
/**
* @brief Evaluates an expression.
* @param x the expression AST node
* @return vector of computed values
*/
std::vector<double> operator()(ast::expression const &x);
/**
* @brief Evaluates a function call.
* @param x the function AST node
* @return vector of function results applied element-wise
*/
std::vector<double> operator()(ast::function const &x);
/**
* @brief Evaluates a binary operation.
* @param x the operation AST node
* @param lhs vector of left-hand side values
* @return vector of operation results applied element-wise
*/
std::vector<double> operator()(ast::operation const &x, std::vector<double> lhs);
/**
* @brief Evaluates a power operation.
* @param x the power AST node
* @return vector of pow(base, exponent) computed element-wise
*/
std::vector<double> operator()(ast::power const &x);
/**
* @brief Evaluates a statement.
* @param x the statement AST node
* @return zero-filled vector (statements have side effects only)
*/
std::vector<double> operator()(ast::statement const &x);
/**
* @brief Evaluates a statement list (program entry point).
* @param x the statement list AST node
* @return zero-filled vector (evaluation has side effects on variables)
*/
std::vector<double> operator()(ast::statement_list const &x);
/**
* @brief Evaluates a unary operation.
* @param x the unary AST node
* @return vector of unary operation results applied element-wise
*/
std::vector<double> operator()(ast::unary const &x);
/**
* @brief Evaluates a variable reference.
* @param x the variable AST node
* @return vector of variable values (or errors for variables ending with 'Err')
*/
std::vector<double> operator()(ast::variable const &x);
/**
* @brief Evaluates a variable declaration.
* @param x the variable declaration AST node
* @return vector of initialization values (or predefined values for injected variables)
*/
std::vector<double> operator()(ast::variable_declaration const &x);
/**
* @brief Retrieves a variable by name after evaluation.
* @param name the variable name to find
* @param ok output parameter indicating whether variable was found
* @return the variable handler, or the first variable if not found
*/
PVarHandler getVar(const std::string name, bool &ok);
/**
* @brief Prints all variable results to standard output (debugging).
*/
void print_result();
private:
std::vector<PVarHandler> fVariable;
std::vector<PVarHandler> fVariable; ///< Variable table with values and errors
/**
* @brief Finds a variable index by name.
* @param name the variable name (with or without leading '$')
* @return the index of the variable, or 0 if not found
*/
unsigned int find_var(std::string const &name);
};
}}

View File

@@ -41,11 +41,38 @@ namespace mupp { namespace parser
namespace ascii = boost::spirit::ascii;
///////////////////////////////////////////////////////////////////////////////
// The skipper grammar
/**
* @brief The PSkipper grammar for skipping whitespace and comments.
*
* This grammar defines what the parser should skip (ignore) between tokens.
* It handles:
* - Whitespace: spaces, tabs, carriage returns, line feeds
* - C-style block comments: /* comment * /
* - Single-line comments: % comment, # comment, // comment
*
* The skipper is used automatically by the parser between all grammar rules,
* allowing flexible formatting and documentation of input expressions.
*
* Example valid comments:
* @code
* var x = $y + 1.0 // this is a comment
* var z = $x * 2.0 # another comment
* var a = $z / 3.0 % yet another comment
* /* This is a
* multi-line comment * /
* @endcode
*
* @tparam Iterator the iterator type for the input (typically std::string::const_iterator)
*/
///////////////////////////////////////////////////////////////////////////////
template <typename Iterator>
struct PSkipper : qi::grammar<Iterator>
{
/**
* @brief Constructor that initializes the skipper grammar rules.
*
* Sets up the grammar to recognize whitespace and various comment styles.
*/
PSkipper() : PSkipper::base_type(start)
{
qi::char_type char_;
@@ -63,8 +90,8 @@ namespace mupp { namespace parser
;
}
qi::rule<Iterator> single_line_comment;
qi::rule<Iterator> start;
qi::rule<Iterator> single_line_comment; ///< Rule for single-line comments (%, #, //)
qi::rule<Iterator> start; ///< Top-level skipper rule
};
}}

View File

@@ -38,18 +38,45 @@
namespace mupp { namespace parser
{
///////////////////////////////////////////////////////////////////////////////
// The statement grammar
/**
* @brief The PStatement grammar for parsing variable statements.
*
* This grammar defines the syntax for variable declarations and assignments.
* It builds on top of the PExpression grammar to parse complete statements
* that form a variable definition program.
*
* Supported statement types:
* - Variable declaration: var <identifier> = <expression>
* - Variable declaration without initialization: var <identifier>
* - Assignment: <identifier> = <expression>
*
* Example statements:
* @code
* var sigma = pow(abs(pow($T1,2.0)-pow(0.11,2.0)),0.5)
* var sigmaErr = 0.01
* result = $sigma * 2.0
* @endcode
*
* The grammar parses a statement list (one or more statements) and builds
* an AST that can be analyzed and evaluated by the semantic analyzer.
*
* @tparam Iterator the iterator type for the input (typically std::string::const_iterator)
*/
///////////////////////////////////////////////////////////////////////////////
template <typename Iterator>
struct PStatement : qi::grammar<Iterator, ast::statement_list(), PSkipper<Iterator> >
{
/**
* @brief Constructor that initializes the statement grammar rules.
* @param error_handler reference to the error handler for reporting parse errors
*/
PStatement(PErrorHandler<Iterator>& error_handler);
PExpression<Iterator> expr;
qi::rule<Iterator, ast::statement_list(), PSkipper<Iterator> > statement_list;
qi::rule<Iterator, ast::variable_declaration(), PSkipper<Iterator> > variable_declaration;
qi::rule<Iterator, ast::assignment(), PSkipper<Iterator> > assignment;
qi::rule<Iterator, std::string(), PSkipper<Iterator> > identifier;
PExpression<Iterator> expr; ///< Expression grammar for parsing right-hand sides
qi::rule<Iterator, ast::statement_list(), PSkipper<Iterator> > statement_list; ///< Rule for parsing a list of statements
qi::rule<Iterator, ast::variable_declaration(), PSkipper<Iterator> > variable_declaration; ///< Rule for variable declarations
qi::rule<Iterator, ast::assignment(), PSkipper<Iterator> > assignment; ///< Rule for assignment statements
qi::rule<Iterator, std::string(), PSkipper<Iterator> > identifier; ///< Rule for identifiers (without '$' prefix)
};
}}

View File

@@ -8,6 +8,18 @@
Based on Joel de Guzman example on calc7,
see https://github.com/boostorg/spirit
This file contains the implementation (definition) of the PStatement
template grammar. It defines the grammar rules for parsing variable
declarations and assignments using Boost.Spirit Qi.
The grammar supports:
- Variable declarations: var <identifier> = <expression>
- Variable declarations without initialization: var <identifier>
- Assignments: <identifier> = <expression>
The grammar integrates with PExpression for parsing right-hand side
expressions and includes error handling and AST annotation.
***************************************************************************/
/***************************************************************************

View File

@@ -39,30 +39,125 @@
#include "PAst.hpp"
#include "PProgram.hpp"
//-----------------------------------------------------------------------------
/**
* @brief The PVarHandler class handles variable parsing, evaluation, and data management.
*
* This class provides the main interface for parsing variable definition strings,
* evaluating expressions using the Boost.Spirit parser framework, and managing
* the resulting computed values and errors. It integrates with PmuppCollection
* to access run data and inject predefined variables for use in expressions.
*
* The variable syntax supports:
* - Variable declarations: var <name> = <expression>
* - Identifiers (prefixed with '$'): $varName
* - Mathematical functions: sin, cos, tan, exp, log, sqrt, pow, etc.
* - Arithmetic operations: +, -, *, /
* - Error variables (suffixed with 'Err'): must be defined for each variable
*
* Example usage:
* @code
* PmuppCollection *coll = ...;
* std::string expr = "var sigma = pow(abs(pow($T1,2.0)-pow(0.11,2.0)),0.5)";
* PVarHandler handler(coll, expr, "sigma");
* if (handler.isValid()) {
* std::vector<double> values = handler.getValues();
* std::vector<double> errors = handler.getErrors();
* }
* @endcode
*/
class PVarHandler {
public:
/**
* @brief Default constructor.
*
* Creates an invalid PVarHandler instance with null pointers and empty strings.
*/
PVarHandler();
/**
* @brief Constructor that parses and evaluates a variable expression.
*
* Parses the provided expression string, performs semantic analysis, injects
* predefined variables from the collection, evaluates the expression, and
* stores the results.
*
* @param coll pointer to the PmuppCollection containing run data and parameters
* @param parse_str the variable definition string to parse (e.g., "var x = $T1 + 1.0")
* @param var_name optional variable name to extract from the evaluation results; if empty, only parsing/checking is performed
*/
PVarHandler(PmuppCollection *coll, std::string parse_str, std::string var_name="");
/**
* @brief Checks if the parsing and evaluation were successful.
* @return true if the expression was parsed and evaluated successfully, false otherwise
*/
bool isValid() { return fIsValid; }
/**
* @brief Gets the collection name.
* @return QString containing the name of the associated collection
*/
QString getCollName() { return fColl->GetName(); }
/**
* @brief Gets the variable name.
* @return QString containing the variable name
*/
QString getVarName() { return QString(fVarName.c_str()); }
/**
* @brief Gets the computed values for the variable.
* @return vector of double values computed from the expression evaluation
*/
std::vector<double> getValues();
/**
* @brief Gets the computed errors for the variable.
* @return vector of double error values computed from the expression evaluation
*/
std::vector<double> getErrors();
private:
PmuppCollection *fColl; ///< collection needed for parsing and evaluation
std::string fParseStr; ///< the variable input to be parsed
std::string fVarName; ///< variable name
mupp::prog::PVarHandler fVar; ///< values of the evaluation
PmuppCollection *fColl; ///< pointer to collection containing run data needed for parsing and evaluation
std::string fParseStr; ///< the variable input string to be parsed
std::string fVarName; ///< name of the variable to extract from evaluation results
mupp::prog::PVarHandler fVar; ///< variable handler storing the computed values and errors
bool fIsValid;
mupp::ast::statement_list fAst; ///< the AST
bool fIsValid; ///< flag indicating whether parsing and evaluation succeeded
mupp::ast::statement_list fAst; ///< Abstract Syntax Tree generated from parsing
/**
* @brief Injects predefined variables from the collection into the AST.
*
* Extracts all parameter names from the collection's first run and creates
* variable declarations for both the parameter names and their corresponding
* error variables (suffixed with 'Err'). These declarations are prepended
* to the AST before evaluation.
*/
void injectPredefVariables();
/**
* @brief Gets the variable name at a specific parameter index.
* @param idx the parameter index in the collection
* @return variable name as a string, or "??" if index is out of range
*/
std::string getVarName(int idx);
/**
* @brief Gets the data values for a specific parameter across all runs.
* @param idx the parameter index in the collection
* @return vector of data values from all runs for the specified parameter
*/
std::vector<double> getData(int idx);
/**
* @brief Gets the error values for a specific parameter across all runs.
*
* Computes the geometric mean of positive and negative errors for each run.
* @param idx the parameter index in the collection
* @return vector of error values (geometric mean of pos/neg errors) from all runs
*/
std::vector<double> getDataErr(int idx);
};

View File

@@ -1,6 +1,6 @@
/***************************************************************************
PExpression.hpp
PExpression.cpp
Author: Andreas Suter
e-mail: andreas.suter@psi.ch
@@ -8,6 +8,11 @@
Based on Joel de Guzman example on calc7,
see https://github.com/boostorg/spirit
This file explicitly instantiates the PExpression template grammar for
std::string::const_iterator. Template instantiation in a separate
compilation unit reduces compile times and allows the grammar
implementation to be hidden from clients.
***************************************************************************/
/***************************************************************************

View File

@@ -8,6 +8,24 @@
Based on Joel de Guzman example on calc7,
see https://github.com/boostorg/spirit
This file implements the semantic analysis (PProgram) and evaluation
(PProgEval) visitor classes for the variable expression AST.
PProgram performs semantic checks:
- Variable declaration validation
- Variable reference resolution
- Type and operator validation
- Building the symbol table
PProgEval performs expression evaluation:
- Element-wise vector operations
- Mathematical function evaluation
- Variable value and error computation
- Result storage in variable handlers
Both classes use the visitor pattern to traverse the AST generated by
the parser and process each node type appropriately.
***************************************************************************/
/***************************************************************************

View File

@@ -8,6 +8,11 @@
Based on Joel de Guzman example on calc7,
see https://github.com/boostorg/spirit
This file explicitly instantiates the PStatement template grammar for
std::string::const_iterator. Template instantiation in a separate
compilation unit reduces compile times and allows the grammar
implementation to be hidden from clients.
***************************************************************************/
/***************************************************************************

View File

@@ -38,7 +38,10 @@
//--------------------------------------------------------------------------
/**
* @brief PVarHandler::PVarHandler
* @brief Default constructor creating an invalid handler.
*
* Initializes all members to default/null values, resulting in an invalid
* handler that will return false from isValid().
*/
PVarHandler::PVarHandler() :
fColl(nullptr), fParseStr(""), fVarName(""), fIsValid(false)
@@ -48,7 +51,22 @@ PVarHandler::PVarHandler() :
//--------------------------------------------------------------------------
/**
* @brief PVarHandler::PVarHandler
* @brief Constructor that parses and evaluates a variable expression.
*
* Performs the complete variable processing pipeline:
* 1. Injects predefined variables from the collection into the AST
* 2. Parses the input string using the PStatement grammar
* 3. Performs semantic analysis using PProgram
* 4. Injects actual data values from the collection
* 5. Evaluates the expression using PProgEval
* 6. Extracts and stores the requested variable results
*
* If parsing, semantic analysis, or evaluation fails, fIsValid is set to false
* and error messages are logged to ~/.musrfit/mupp/mupp_err.log.
*
* @param coll pointer to the collection containing run data
* @param parse_str the variable definition string to parse
* @param var_name optional variable name to extract; if empty, only validation is performed
*/
PVarHandler::PVarHandler(PmuppCollection *coll, std::string parse_str, std::string var_name) :
fColl(coll), fParseStr(parse_str), fVarName(var_name), fIsValid(false)
@@ -95,8 +113,12 @@ PVarHandler::PVarHandler(PmuppCollection *coll, std::string parse_str, std::stri
//--------------------------------------------------------------------------
/**
* @brief PVarHandler::getValues
* @return
* @brief Gets the computed values for the variable.
*
* Returns the vector of values computed during expression evaluation.
* Each element corresponds to a different run in the collection.
*
* @return vector of computed values, or empty vector if not valid
*/
std::vector<double> PVarHandler::getValues()
{
@@ -109,8 +131,12 @@ std::vector<double> PVarHandler::getValues()
//--------------------------------------------------------------------------
/**
* @brief PVarHandler::getErrors
* @return
* @brief Gets the computed error values for the variable.
*
* Returns the vector of error values computed during expression evaluation.
* Each element corresponds to a different run in the collection.
*
* @return vector of computed error values, or empty vector if not valid
*/
std::vector<double> PVarHandler::getErrors()
{
@@ -123,7 +149,13 @@ std::vector<double> PVarHandler::getErrors()
//--------------------------------------------------------------------------
/**
* @brief PVarHandler::injectPredefVariables
* @brief Injects predefined variables from the collection into the AST.
*
* For each parameter in the collection's first run, creates variable
* declarations for both the parameter name and its corresponding error
* variable (with 'Err' suffix). These declarations are prepended to the
* AST so they are processed before user-defined variables, making all
* collection parameters available for use in expressions.
*/
void PVarHandler::injectPredefVariables()
{
@@ -148,9 +180,13 @@ void PVarHandler::injectPredefVariables()
//--------------------------------------------------------------------------
/**
* @brief PVarHandler::getVarName
* @param idx
* @return
* @brief Gets the variable name at a specific parameter index.
*
* Retrieves the parameter name from the collection's first run at the
* specified index.
*
* @param idx the parameter index (0-based)
* @return the parameter name, or "??" if index is out of range
*/
std::string PVarHandler::getVarName(int idx)
{
@@ -165,9 +201,13 @@ std::string PVarHandler::getVarName(int idx)
//--------------------------------------------------------------------------
/**
* @brief PVarHandler::getData
* @param idx
* @return
* @brief Gets the data values for a specific parameter across all runs.
*
* Collects the value of the specified parameter from every run in the
* collection, returning them as a vector.
*
* @param idx the parameter index (0-based)
* @return vector of parameter values from all runs, or empty vector if index is out of range
*/
std::vector<double> PVarHandler::getData(int idx)
{
@@ -188,9 +228,14 @@ std::vector<double> PVarHandler::getData(int idx)
//--------------------------------------------------------------------------
/**
* @brief PVarHandler::getDataErr
* @param idx
* @return
* @brief Gets the error values for a specific parameter across all runs.
*
* Collects the error of the specified parameter from every run in the
* collection. The error is computed as the geometric mean of the positive
* and negative errors: sqrt(abs(posErr * negErr)).
*
* @param idx the parameter index (0-based)
* @return vector of parameter errors from all runs, or empty vector if index is out of range
*/
std::vector<double> PVarHandler::getDataErr(int idx)
{