diff --git a/src/classes/PFunction.cpp b/src/classes/PFunction.cpp index 3d0f8b963..50faccbc1 100644 --- a/src/classes/PFunction.cpp +++ b/src/classes/PFunction.cpp @@ -5,10 +5,12 @@ Author: Andreas Suter e-mail: andreas.suter@psi.ch + Modernized implementation using Boost.Spirit X3 with full PMetaData support. + ***************************************************************************/ /*************************************************************************** - * Copyright (C) 2007-2025 by Andreas Suter * + * Copyright (C) 2007-2026 by Andreas Suter * * andreas.suter@psi.ch * * * * This program is free software; you can redistribute it and/or modify * @@ -27,815 +29,638 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ -#include - +#include #include +#include -#include // for stripping leading whitespace in std::string +#include +#include #include "PMusr.h" #include "PFunction.h" -//-------------------------------------------------------------------------- -// Constructor +namespace x3 = boost::spirit::x3; + //-------------------------------------------------------------------------- /** * \brief Constructor that initializes the function from a parsed AST. * - * This constructor performs the following steps: - * 1. Stores the AST generated by the Boost.Spirit parser - * 2. Extracts the function number from the function label (FUNx) - * 3. Converts the AST into an efficient evaluation tree structure - * 4. Generates a human-readable string representation of the function + * This constructor stores the AST generated by the Boost.Spirit X3 parser, + * extracts the function number, and prepares the function for evaluation. + * The X3 AST is directly suitable for evaluation using the visitor pattern, + * eliminating the need for conversion to an intermediate tree structure. * - * The input AST has the following structure: - * \verbatim - * assignment (root node) - * |_ 'FUNx' (function label) - * |_ '=' (assignment operator) - * |_ expression (mathematical expression tree) - * |_ term - * |_ factor - * |_ ... - * \endverbatim - * - * Direct evaluation of the AST would be inefficient due to its verbose structure. - * Therefore, it is converted to a more compact evaluation tree (fFunc) optimized - * for repeated evaluation with different parameter values during fitting. - * - * \param info AST parse tree from Boost.Spirit containing a parsed msr-function - * - * \see SetFuncNo for function number extraction - * \see GenerateFuncEvalTree for AST to evaluation tree conversion - * \see EvalTreeForString for string representation generation + * \param assignment AST from parsing a function expression (FUN# = expression) */ -PFunction::PFunction(tree_parse_info<> info) : fInfo(info) +PFunction::PFunction(const musrfit::ast::assignment& assignment) { - // init class variables fValid = true; - fFuncNo = -1; + fFuncNo = assignment.func_number; + fAst = assignment.rhs; - // set the function number - SetFuncNo(); - - // generate function evaluation tree - if (!GenerateFuncEvalTree()) { - fValid = false; - } - - EvalTreeForString(info); + // Generate human-readable string representation + fFuncString = "FUN"; + fFuncString += fFuncNo; + fFuncString += " = "; + fFuncString += GenerateString(fAst); } -//-------------------------------------------------------------------------- -// Destructor //-------------------------------------------------------------------------- /** - * \brief Destructor that releases all resources. + * \brief Destructor that releases resources. * - * Cleans up: - * - Parameter vector - * - Map vector - * - Evaluation tree (recursively frees all nodes and their children) + * Cleans up parameter and map vectors. */ PFunction::~PFunction() { fParam.clear(); fMap.clear(); - - CleanupFuncEvalTree(); } -//-------------------------------------------------------------------------- -// InitNode (protected) //-------------------------------------------------------------------------- /** - *

Initializes the node of the evaluation function tree. + * \brief Validates that all parameter and map references are within valid ranges. * - * \param node to be initialized - */ -void PFunction::InitNode(PFuncTreeNode &node) -{ - node.fID = 0; - node.fOperatorTag = 0; - node.fFunctionTag = 0; - node.fIvalue = 0; - node.fSign = false; - node.fDvalue = 0.0; -} - -//------------------------------------------------------------- -// SetFuncNo (protected) -//------------------------------------------------------------- -/** - * \brief Extracts the function number from the AST tree. + * Recursively traverses the AST to find all parameter (PAR#) and map (MAP#) + * references and checks that they are within the specified bounds. * - * Navigates to the function label node in the AST and parses the numeric - * identifier from the "FUNx" label. For example, "FUN3" yields 3. - * - * \return true if the function number was successfully extracted, false otherwise - * - * \note The function number is stored in the member variable fFuncNo - */ -Bool_t PFunction::SetFuncNo() -{ - Int_t funNo = -1; - Int_t status; - Bool_t success = true; - - // get root - iter_t i = fInfo.trees.begin(); // assignement - i = i->children.begin(); // FUNx - - // get string from tree - std::string str(i->value.begin(), i->value.end()); - boost::algorithm::trim(str); - - // extract function number from string - status = sscanf(str.c_str(), "FUN%d", &funNo); - if (status == 1) { // found 1 Int_t - fFuncNo = funNo; - } else { // wrong string - success = false; - } - - return success; -} - -//------------------------------------------------------------- -// GenerateFuncEvalTree (protected) -//------------------------------------------------------------- -/** - * \brief Initiates conversion of AST to evaluation tree. - * - * This function initializes the root node and triggers the recursive - * conversion process. The evaluation tree is a more efficient representation - * than the AST for repeated function evaluations during fitting. - * - * \return true (currently always successful) - * - * \see FillFuncEvalTree for the recursive conversion algorithm - */ -Bool_t PFunction::GenerateFuncEvalTree() -{ - InitNode(fFunc); - FillFuncEvalTree(fInfo.trees.begin(), fFunc); - - return true; -} - -//------------------------------------------------------------- -// FillFuncEvalTree (protected) -//------------------------------------------------------------- -/** - * \brief Recursively converts AST nodes to evaluation tree nodes. - * - * This function traverses the AST and builds a corresponding evaluation tree. - * It handles all node types defined in PFunctionGrammar: - * - Leaf nodes: real numbers, constants (PI, GAMMA_MU, B, EN, T), parameters, maps - * - Operator nodes: arithmetic operations (+, -, *, /) - * - Function nodes: mathematical functions (cos, sin, exp, etc.) - * - Power nodes: POW(base, exponent) - * - Composite nodes: factors, terms, expressions - * - * The resulting tree structure is optimized for efficient evaluation. - * - * \param i Iterator pointing to the current AST node to process - * \param node Evaluation tree node to populate with data from the AST - * - * \see PFunctionGrammar for AST node type definitions - * \see EvalNode for the evaluation algorithm that uses this tree - */ -void PFunction::FillFuncEvalTree(iter_t const& i, PFuncTreeNode &node) -{ - Double_t dvalue; - Int_t ivalue; - Int_t status; - std::string str; - PFuncTreeNode child; - - InitNode(child); - - if (i->value.id() == PFunctionGrammar::realID) { // handle number - str = std::string(i->value.begin(), i->value.end()); // get string - boost::algorithm::trim(str); - status = sscanf(str.c_str(), "%lf", &dvalue); // convert string to Double_t - node.fID = PFunctionGrammar::realID; // keep the ID - node.fDvalue = dvalue; // keep the value - } else if (i->value.id() == PFunctionGrammar::constPiID) { // handle constant pi - node.fID = PFunctionGrammar::constPiID; // keep the ID - node.fDvalue = 3.14159265358979323846; // keep the value - } else if (i->value.id() == PFunctionGrammar::constGammaMuID) { // handle constant gamma_mu - node.fID = PFunctionGrammar::constGammaMuID; // keep the ID - node.fDvalue = GAMMA_BAR_MUON; // keep the value - } else if (i->value.id() == PFunctionGrammar::constFieldID) { // handle constant field from meta data - str = std::string(i->value.begin(), i->value.end()); // get string - boost::algorithm::trim(str); - if (strstr(str.c_str(), "-")) - node.fSign = true; - node.fID = PFunctionGrammar::constFieldID; // keep the ID - } else if (i->value.id() == PFunctionGrammar::constEnergyID) { // handle constant energy from meta data - str = std::string(i->value.begin(), i->value.end()); // get string - boost::algorithm::trim(str); - if (strstr(str.c_str(), "-")) - node.fSign = true; - node.fID = PFunctionGrammar::constEnergyID; // keep the ID - } else if (i->value.id() == PFunctionGrammar::constTempID) { // handle constant temperature from meta data - str = std::string(i->value.begin(), i->value.end()); // get string - boost::algorithm::trim(str); - std::string pstr; - if (strstr(str.c_str(), "-")) { - node.fSign = true; - pstr = "-T%d"; - } else { - pstr = "T%d"; - } - status = sscanf(str.c_str(), pstr.c_str(), &ivalue); // convert string to temperature index - node.fID = PFunctionGrammar::constTempID; // keep the ID - node.fIvalue = ivalue; // Temp idx - } else if (i->value.id() == PFunctionGrammar::parameterID) { // handle parameter number - str = std::string(i->value.begin(), i->value.end()); // get string - boost::algorithm::trim(str); - if (strstr(str.c_str(), "-")) { - node.fSign = true; - status = sscanf(str.c_str(), "-PAR%d", &ivalue); // convert string to parameter number - } else { - status = sscanf(str.c_str(), "PAR%d", &ivalue); // convert string to parameter number - } - node.fID = PFunctionGrammar::parameterID; // keep the ID - node.fIvalue = ivalue; // keep the value - } else if (i->value.id() == PFunctionGrammar::mapID) { // handle map number - str = std::string(i->value.begin(), i->value.end()); // get string - boost::algorithm::trim(str); - status = sscanf(str.c_str(), "MAP%d", &ivalue); // convert string to map number - node.fID = PFunctionGrammar::mapID; // keep the ID - node.fIvalue = ivalue; // keep the value - } else if (i->value.id() == PFunctionGrammar::functionID) { // handle function like cos ... - // keep the id - node.fID = PFunctionGrammar::functionID; - // keep function tag - str = std::string(i->value.begin(), i->value.end()); // get string - if (!strcmp(str.c_str(), "COS")) - node.fFunctionTag = FUN_COS; - else if (!strcmp(str.c_str(), "SIN")) - node.fFunctionTag = FUN_SIN; - else if (!strcmp(str.c_str(), "TAN")) - node.fFunctionTag = FUN_TAN; - else if (!strcmp(str.c_str(), "COSH")) - node.fFunctionTag = FUN_COSH; - else if (!strcmp(str.c_str(), "SINH")) - node.fFunctionTag = FUN_SINH; - else if (!strcmp(str.c_str(), "TANH")) - node.fFunctionTag = FUN_TANH; - else if (!strcmp(str.c_str(), "ACOS")) - node.fFunctionTag = FUN_ACOS; - else if (!strcmp(str.c_str(), "ASIN")) - node.fFunctionTag = FUN_ASIN; - else if (!strcmp(str.c_str(), "ATAN")) - node.fFunctionTag = FUN_ATAN; - else if (!strcmp(str.c_str(), "ACOSH")) - node.fFunctionTag = FUN_ACOSH; - else if (!strcmp(str.c_str(), "ASINH")) - node.fFunctionTag = FUN_ASINH; - else if (!strcmp(str.c_str(), "ATANH")) - node.fFunctionTag = FUN_ATANH; - else if (!strcmp(str.c_str(), "LOG")) - node.fFunctionTag = FUN_LOG; - else if (!strcmp(str.c_str(), "LN")) - node.fFunctionTag = FUN_LN; - else if (!strcmp(str.c_str(), "EXP")) - node.fFunctionTag = FUN_EXP; - else if (!strcmp(str.c_str(), "SQRT")) - node.fFunctionTag = FUN_SQRT; - else { - std::cerr << std::endl << "**PANIC ERROR**: function " << str << " doesn't exist, but you never should have reached this point!"; - std::cerr << std::endl; - assert(0); - } - // add node - node.children.push_back(child); - // i: '(', 'expression', ')' - FillFuncEvalTree(i->children.begin()+1, node.children[0]); - } else if (i->value.id() == PFunctionGrammar::powerID) { - // keep the id - node.fID = PFunctionGrammar::powerID; - // keep function tag - str = std::string(i->value.begin(), i->value.end()); // get string - - if (!strcmp(str.c_str(), "POW")) - node.fFunctionTag = FUN_POW; - else { - std::cerr << std::endl << "**PANIC ERROR**: function " << str << " doesn't exist, but you never should have reached this point!"; - std::cerr << std::endl; - assert(0); - } - // i: '(', 'expression', ',', expression, ')' - // add node - node.children.push_back(child); - FillFuncEvalTree(i->children.begin()+1, node.children[0]); // base - // add node - node.children.push_back(child); - FillFuncEvalTree(i->children.begin()+3, node.children[1]); // exponent - } else if (i->value.id() == PFunctionGrammar::factorID) { - // keep the id - node.fID = PFunctionGrammar::factorID; - // add child lhs - node.children.push_back(child); - FillFuncEvalTree(i->children.begin(), node.children[0]); - } else if (i->value.id() == PFunctionGrammar::termID) { - // keep the id - node.fID = PFunctionGrammar::termID; - // keep operator tag - if (*i->value.begin() == '*') - node.fOperatorTag = OP_MUL; - else - node.fOperatorTag = OP_DIV; - // add child lhs - node.children.push_back(child); - FillFuncEvalTree(i->children.begin(), node.children[0]); - // add child rhs - node.children.push_back(child); - FillFuncEvalTree(i->children.begin()+1, node.children[1]); - } else if (i->value.id() == PFunctionGrammar::expressionID) { // handle expression - // keep the id - node.fID = PFunctionGrammar::expressionID; - // keep operator tag - if (*i->value.begin() == '+') - node.fOperatorTag = OP_ADD; - else - node.fOperatorTag = OP_SUB; - // add child lhs - node.children.push_back(child); - FillFuncEvalTree(i->children.begin(), node.children[0]); - // add child rhs - node.children.push_back(child); - FillFuncEvalTree(i->children.begin()+1, node.children[1]); - } else if (i->value.id() == PFunctionGrammar::assignmentID) { - // nothing to be done except to pass the next element in the ast - // i: 'funx', '=', 'expression' - FillFuncEvalTree(i->children.begin()+2, node); - } -} - -//------------------------------------------------------------- -// CheckMapAndParamRange (public) -//------------------------------------------------------------- -/** - * \brief Validates all map and parameter references in the function. - * - * Initiates a recursive traversal of the evaluation tree to verify that all - * MAP# and PAR# references fall within valid ranges. This prevents runtime - * errors during evaluation caused by out-of-bounds array access. - * - * \param mapSize Number of available map entries (MAP1 to MAP) - * \param paramSize Number of available fit parameters (PAR1 to PAR) - * \return true if all references are valid, false if any are out of range - * - * \see FindAndCheckMapAndParamRange for the recursive validation algorithm + * \param mapSize Number of available map entries + * \param paramSize Number of available fit parameters + * \return true if all references are valid, false otherwise */ Bool_t PFunction::CheckMapAndParamRange(UInt_t mapSize, UInt_t paramSize) { - return FindAndCheckMapAndParamRange(fFunc, mapSize, paramSize); + Bool_t valid = true; + + // Check first operand + if (!FindAndCheckMapAndParamRange(fAst.first, mapSize, paramSize)) { + valid = false; + } + + // Check remaining operations + for (const auto& op : fAst.rest) { + if (!FindAndCheckMapAndParamRange(op.operand_, mapSize, paramSize)) { + valid = false; + } + } + + return valid; } -//------------------------------------------------------------- -// FindAndCheckMapAndParamRange (protected) -//------------------------------------------------------------- +//-------------------------------------------------------------------------- /** - * \brief Recursively validates map and parameter references in the tree. + * \brief Recursively validates parameter and map references in an operand. * - * Traverses the evaluation tree depth-first, checking each node: - * - For parameter nodes (PAR#): verifies # <= paramSize - * - For map nodes (MAP#): verifies # <= mapSize - * - For other nodes: recursively checks all children + * Helper visitor for CheckMapAndParamRange that handles different operand types. * - * The validation ensures that array indices will be valid during evaluation, - * preventing potential segmentation faults or undefined behavior. - * - * \param node Current evaluation tree node being checked + * \param operand Current operand being checked * \param mapSize Number of available map entries * \param paramSize Number of available fit parameters - * \return true if this node and all descendants have valid references, false otherwise + * \return true if all references are valid, false otherwise */ -Bool_t PFunction::FindAndCheckMapAndParamRange(PFuncTreeNode &node, UInt_t mapSize, UInt_t paramSize) +Bool_t PFunction::FindAndCheckMapAndParamRange(const musrfit::ast::operand& operand, + UInt_t mapSize, UInt_t paramSize) { - if (node.fID == PFunctionGrammar::realID) { - return true; - } else if (node.fID == PFunctionGrammar::constPiID) { - return true; - } else if (node.fID == PFunctionGrammar::constGammaMuID) { - return true; - } else if (node.fID == PFunctionGrammar::constFieldID) { - return true; - } else if (node.fID == PFunctionGrammar::constEnergyID) { - return true; - } else if (node.fID == PFunctionGrammar::constTempID) { - return true; - } else if (node.fID == PFunctionGrammar::parameterID) { - if (node.fIvalue <= static_cast(paramSize)) + struct CheckVisitor : public boost::static_visitor + { + UInt_t fMapSize; + UInt_t fParamSize; + PFunction* fFunc; + + CheckVisitor(UInt_t mapSize, UInt_t paramSize, PFunction* func) + : fMapSize(mapSize), fParamSize(paramSize), fFunc(func) {} + + Bool_t operator()(const musrfit::ast::nil&) const { return true; } + Bool_t operator()(double) const { return true; } + Bool_t operator()(const musrfit::ast::constant&) const { return true; } + + Bool_t operator()(const musrfit::ast::parameter& p) const { + if (p.number < 1 || static_cast(p.number) > fParamSize) { + std::cerr << "**ERROR** Parameter PAR" << p.number << " out of range (1-" << fParamSize << ")" << std::endl; + return false; + } return true; - else - return false; - } else if (node.fID == PFunctionGrammar::mapID) { - if (node.fIvalue <= static_cast(mapSize)) + } + + Bool_t operator()(const musrfit::ast::map_ref& m) const { + if (m.number < 1 || static_cast(m.number) > fMapSize) { + std::cerr << "**ERROR** Map MAP" << m.number << " out of range (1-" << fMapSize << ")" << std::endl; + return false; + } return true; - else - return false; - } else if (node.fID == PFunctionGrammar::functionID) { - return FindAndCheckMapAndParamRange(node.children[0], mapSize, paramSize); - } else if (node.fID == PFunctionGrammar::powerID) { - if (FindAndCheckMapAndParamRange(node.children[0], mapSize, paramSize)) - return FindAndCheckMapAndParamRange(node.children[1], mapSize, paramSize); - else - return false; - } else if (node.fID == PFunctionGrammar::factorID) { - return FindAndCheckMapAndParamRange(node.children[0], mapSize, paramSize); - } else if (node.fID == PFunctionGrammar::termID) { - if (FindAndCheckMapAndParamRange(node.children[0], mapSize, paramSize)) - return FindAndCheckMapAndParamRange(node.children[1], mapSize, paramSize); - else - return false; - } else if (node.fID == PFunctionGrammar::expressionID) { - if (FindAndCheckMapAndParamRange(node.children[0], mapSize, paramSize)) - return FindAndCheckMapAndParamRange(node.children[1], mapSize, paramSize); - else - return false; - } else { - std::cerr << std::endl << ">> **PANIC ERROR**: PFunction::FindAndCheckMapAndParamRange: you never should have reached this point!"; - std::cerr << std::endl << ">> node.fID = " << node.fID; - std::cerr << std::endl; - assert(0); - } - return true; + } + + Bool_t operator()(const musrfit::ast::function_call& f) const { + return fFunc->FindAndCheckMapAndParamRange(f.arg.first, fMapSize, fParamSize); + } + + Bool_t operator()(const musrfit::ast::power_call& p) const { + Bool_t valid = true; + if (!fFunc->FindAndCheckMapAndParamRange(p.base.first, fMapSize, fParamSize)) + valid = false; + if (!fFunc->FindAndCheckMapAndParamRange(p.pow.first, fMapSize, fParamSize)) + valid = false; + return valid; + } + + Bool_t operator()(const musrfit::ast::expression& expr) const { + Bool_t valid = true; + if (!fFunc->FindAndCheckMapAndParamRange(expr.first, fMapSize, fParamSize)) + valid = false; + for (const auto& op : expr.rest) { + if (!fFunc->FindAndCheckMapAndParamRange(op.operand_, fMapSize, fParamSize)) + valid = false; + } + return valid; + } + }; + + CheckVisitor checker(mapSize, paramSize, this); + return boost::apply_visitor(checker, operand); } -//------------------------------------------------------------- -// Eval (public) -//------------------------------------------------------------- +//-------------------------------------------------------------------------- /** * \brief Evaluates the function with given parameters and metadata. * - * This is the main evaluation entry point. It: - * 1. Stores the current parameter values and metadata - * 2. Initiates recursive evaluation of the tree - * 3. Returns the computed result + * Uses the visitor pattern to traverse the AST and compute the numeric result. + * The evaluation applies operators with proper precedence as encoded in the AST. * - * The function can be evaluated multiple times with different parameter - * values without re-parsing or rebuilding the evaluation tree, making it - * efficient for iterative fitting algorithms. - * - * \param param Vector of fit parameter values (PAR1, PAR2, ...) - * \param metaData Experimental metadata (field, energy, temperature, etc.) - * \return Computed function value - * - * \see EvalNode for the recursive evaluation algorithm - * - * \warning Ensure CheckMapAndParamRange has been called before evaluation - * to prevent out-of-bounds access + * \param param Vector of fit parameter values + * \param metaData Metadata containing field, energy, temperature, etc. + * \return Computed function value, or 0.0 if invalid */ Double_t PFunction::Eval(std::vector param, PMetaData metaData) { + if (!fValid) return 0.0; + fParam = param; fMetaData = metaData; - return EvalNode(fFunc); + EvalVisitor evaluator(fMap, fParam, fMetaData); + + // Evaluate first operand + Double_t result = boost::apply_visitor(evaluator, fAst.first); + + // Apply operations + for (const auto& op : fAst.rest) { + Double_t rhs = boost::apply_visitor(evaluator, op.operand_); + + switch (op.operator_) { + case musrfit::ast::op_plus: + result += rhs; + break; + case musrfit::ast::op_minus: + result -= rhs; + break; + case musrfit::ast::op_times: + result *= rhs; + break; + case musrfit::ast::op_divide: + if (std::abs(rhs) > 1.0e-20) + result /= rhs; + else + result = 0.0; + break; + } + } + + return result; } -//------------------------------------------------------------- -// EvalNode (protected) -//------------------------------------------------------------- +//-------------------------------------------------------------------------- /** - * \brief Recursively evaluates an evaluation tree node. + * \brief Generates a human-readable string representation of an expression. * - * This is the core evaluation algorithm that computes the function value - * by recursively processing the tree structure. Each node type is handled - * appropriately: - * - * - Leaf nodes (constants, parameters, maps): Return their values directly - * - Operator nodes (+, -, *, /): Evaluate children and apply operation - * - Function nodes (cos, sin, exp, etc.): Evaluate argument and apply function - * - Power nodes: Evaluate base and exponent, compute power - * - * Special handling: - * - Division by zero: Triggers an error and assertion - * - Negative bases in power: Takes absolute value if exponent is non-integer - * - Logarithms and square roots: Use absolute value to avoid complex numbers - * - Maps: Returns 0 if map value is 0, otherwise returns the mapped parameter - * - * \param node Current evaluation tree node to evaluate - * \return Computed value for this node - * - * \see Eval for the public evaluation entry point + * \param expr Expression AST to convert + * \return Formatted string */ -Double_t PFunction::EvalNode(PFuncTreeNode &node) +TString PFunction::GenerateString(const musrfit::ast::expression& expr) { - if (node.fID == PFunctionGrammar::realID) { - return node.fDvalue; - } else if (node.fID == PFunctionGrammar::constPiID) { - return node.fDvalue; - } else if (node.fID == PFunctionGrammar::constGammaMuID) { - return node.fDvalue; - } else if (node.fID == PFunctionGrammar::constFieldID) { - Double_t dval = fMetaData.fField; - if (node.fSign) - dval *= -1.0; - return dval; - } else if (node.fID == PFunctionGrammar::constEnergyID) { - if (fMetaData.fEnergy == PMUSR_UNDEFINED) { - std::cerr << std::endl << "**PANIC ERROR**: PFunction::EvalNode: energy meta data not available." << std::endl; - std::cerr << std::endl; - exit(0); + TString result; + + // Check if we have any high-precedence operations (multiplication/division) + bool hasHighPrecOps = false; + for (const auto& op : expr.rest) { + if (op.operator_ == musrfit::ast::op_times || op.operator_ == musrfit::ast::op_divide) { + hasHighPrecOps = true; + break; } - Double_t dval = fMetaData.fEnergy; - if (node.fSign) - dval *= -1.0; - return dval; - } else if (node.fID == PFunctionGrammar::constTempID) { - if (node.fIvalue >= fMetaData.fTemp.size()) { - std::cerr << std::endl << "**PANIC ERROR**: PFunction::EvalNode: Temp idx=" << node.fIvalue << " requested which is >= #Temp(s)=" << fMetaData.fTemp.size() << " available." << std::endl; - std::cerr << std::endl; - exit(0); - } - Double_t dval = fMetaData.fTemp[node.fIvalue]; - if (node.fSign) - dval *= -1.0; - return dval; - } else if (node.fID == PFunctionGrammar::parameterID) { - Double_t dval; - if (node.fSign) - dval = -fParam[node.fIvalue-1]; - else - dval = fParam[node.fIvalue-1]; - return dval; - } else if (node.fID == PFunctionGrammar::mapID) { - if (fMap[node.fIvalue-1] == 0) // map == 0 - return 0.0; - else - return fParam[fMap[node.fIvalue-1]-1]; - } else if (node.fID == PFunctionGrammar::functionID) { - if (node.fFunctionTag == FUN_COS) { - return cos(EvalNode(node.children[0])); - } else if (node.fFunctionTag == FUN_SIN) { - return sin(EvalNode(node.children[0])); - } else if (node.fFunctionTag == FUN_TAN) { - return tan(EvalNode(node.children[0])); - } else if (node.fFunctionTag == FUN_COSH) { - return cosh(EvalNode(node.children[0])); - } else if (node.fFunctionTag == FUN_SINH) { - return sinh(EvalNode(node.children[0])); - } else if (node.fFunctionTag == FUN_TANH) { - return tanh(EvalNode(node.children[0])); - } else if (node.fFunctionTag == FUN_ACOS) { - return acos(EvalNode(node.children[0])); - } else if (node.fFunctionTag == FUN_ASIN) { - return asin(EvalNode(node.children[0])); - } else if (node.fFunctionTag == FUN_ATAN) { - return atan(EvalNode(node.children[0])); - } else if (node.fFunctionTag == FUN_ACOSH) { - return acosh(EvalNode(node.children[0])); - } else if (node.fFunctionTag == FUN_ASINH) { - return asinh(EvalNode(node.children[0])); - } else if (node.fFunctionTag == FUN_ATANH) { - return atanh(EvalNode(node.children[0])); - } else if (node.fFunctionTag == FUN_LOG) { - return log(fabs(EvalNode(node.children[0])))/log(10.0); - } else if (node.fFunctionTag == FUN_LN) { - return log(fabs(EvalNode(node.children[0]))); - } else if (node.fFunctionTag == FUN_EXP) { - return exp(EvalNode(node.children[0])); - } else if (node.fFunctionTag == FUN_SQRT) { - return sqrt(fabs(EvalNode(node.children[0]))); - } else { - std::cerr << std::endl << "**PANIC ERROR**: PFunction::EvalNode: node.fID == PFunctionGrammar::functionID: you never should have reached this point!"; - std::cerr << std::endl; - assert(0); - } - } else if (node.fID == PFunctionGrammar::powerID) { - if (node.fFunctionTag == FUN_POW) { - Double_t base = EvalNode(node.children[0]); - Double_t expo = EvalNode(node.children[1]); - // check that no complex number will result - if (base < 0.0) { // base is negative which might be fatal - if (expo-floor(expo) != 0.0) // exponent is not an integer number, hence take -base (positive) to avoid complex numbers, i.e. nan - base = -base; + } + + // Generate first operand - check if it's a wrapped expression + if (const musrfit::ast::expression* inner = boost::get(&expr.first)) { + + // Check if inner expression has low-precedence ops that need parentheses + bool needsParens = false; + if (hasHighPrecOps && !inner->rest.empty()) { + for (const auto& innerOp : inner->rest) { + if (innerOp.operator_ == musrfit::ast::op_plus || innerOp.operator_ == musrfit::ast::op_minus) { + needsParens = true; + break; + } } - return pow(base, expo); - } else { - std::cerr << std::endl << "**PANIC ERROR**: PFunction::EvalNode: node.fID == PFunctionGrammar::powerID: you never should have reached this point!"; - std::cerr << std::endl; - assert(0); } - } else if (node.fID == PFunctionGrammar::factorID) { - return EvalNode(node.children[0]); - } else if (node.fID == PFunctionGrammar::termID) { - if (node.fOperatorTag == OP_MUL) { - return EvalNode(node.children[0]) * EvalNode(node.children[1]); + + if (needsParens) { + result = "(" + GenerateString(*inner) + ")"; } else { - Double_t denominator = EvalNode(node.children[1]); - if (denominator == 0.0) { - std::cerr << std::endl << "**PANIC ERROR**: PFunction::EvalNode: division by 0.0"; - std::cerr << std::endl << "**PANIC ERROR**: PFunction::EvalNode: requested operation: " << EvalNode(node.children[0]) << "/" << EvalNode(node.children[1]); - std::cerr << std::endl << ">> " << fFuncString.Data() << std::endl; - std::cerr << std::endl; - assert(0); - } - return EvalNode(node.children[0]) / denominator; - } - } else if (node.fID == PFunctionGrammar::expressionID) { - if (node.fOperatorTag == OP_ADD) { - return EvalNode(node.children[0]) + EvalNode(node.children[1]); - } else { - return EvalNode(node.children[0]) - EvalNode(node.children[1]); + result = GenerateString(*inner); } } else { - std::cerr << std::endl << "**PANIC ERROR**: PFunction::EvalNode: you never should have reached this point!"; - std::cerr << std::endl; - assert(0); + result = GenerateStringOperand(expr.first); } + + // Generate operations and operands + for (const auto& op : expr.rest) { + switch (op.operator_) { + case musrfit::ast::op_plus: result += " + "; break; + case musrfit::ast::op_minus: result += " - "; break; + case musrfit::ast::op_times: result += " * "; break; + case musrfit::ast::op_divide: result += " / "; break; + } + + // Check if operand is a wrapped expression + if (const musrfit::ast::expression* inner = boost::get(&op.operand_)) { + + // Check if inner expression has low-precedence ops that need parentheses + bool needsParens = false; + if ((op.operator_ == musrfit::ast::op_times || op.operator_ == musrfit::ast::op_divide) && + !inner->rest.empty()) { + for (const auto& innerOp : inner->rest) { + if (innerOp.operator_ == musrfit::ast::op_plus || innerOp.operator_ == musrfit::ast::op_minus) { + needsParens = true; + break; + } + } + } + + if (needsParens) { + result += "(" + GenerateString(*inner) + ")"; + } else { + result += GenerateString(*inner); + } + } else { + result += GenerateStringOperand(op.operand_); + } + } + + return result; +} + +//-------------------------------------------------------------------------- +/** + * \brief Generates a string representation of an operand. + * + * \param operand Operand AST to convert + * \return Formatted string + */ +TString PFunction::GenerateStringOperand(const musrfit::ast::operand& operand) +{ + struct StringVisitor : public boost::static_visitor + { + PFunction* fFunc; + StringVisitor(PFunction* func) : fFunc(func) {} + + TString operator()(const musrfit::ast::nil&) const { return "nil"; } + + TString operator()(double val) const { + std::ostringstream oss; + oss << val; + return TString(oss.str().c_str()); + } + + TString operator()(const musrfit::ast::constant& c) const { + TString str; + if (c.sign) str = "-"; + + switch (c.const_type) { + case musrfit::ast::constant::pi: str += "PI"; break; + case musrfit::ast::constant::gamma_mu: str += "GAMMA_MU"; break; + case musrfit::ast::constant::field: str += (c.sign ? "B" : "B"); break; // Sign already added + case musrfit::ast::constant::energy: str += (c.sign ? "EN" : "EN"); break; + case musrfit::ast::constant::temp: + str += "T"; + str += c.index; + break; + } + return str; + } + + TString operator()(const musrfit::ast::parameter& p) const { + TString str; + if (p.sign) str = "-"; + str += "PAR"; + str += p.number; + return str; + } + + TString operator()(const musrfit::ast::map_ref& m) const { + TString str; + if (m.sign) str = "-"; + str += "MAP"; + str += m.number; + return str; + } + + TString operator()(const musrfit::ast::function_call& f) const { + TString str; + switch (f.func_id) { + case musrfit::ast::fun_cos: str = "COS"; break; + case musrfit::ast::fun_sin: str = "SIN"; break; + case musrfit::ast::fun_tan: str = "TAN"; break; + case musrfit::ast::fun_cosh: str = "COSH"; break; + case musrfit::ast::fun_sinh: str = "SINH"; break; + case musrfit::ast::fun_tanh: str = "TANH"; break; + case musrfit::ast::fun_acos: str = "ACOS"; break; + case musrfit::ast::fun_asin: str = "ASIN"; break; + case musrfit::ast::fun_atan: str = "ATAN"; break; + case musrfit::ast::fun_acosh: str = "ACOSH"; break; + case musrfit::ast::fun_asinh: str = "ASINH"; break; + case musrfit::ast::fun_atanh: str = "ATANH"; break; + case musrfit::ast::fun_log: str = "LOG"; break; + case musrfit::ast::fun_ln: str = "LN"; break; + case musrfit::ast::fun_exp: str = "EXP"; break; + case musrfit::ast::fun_sqrt: str = "SQRT"; break; + } + str += "("; + str += fFunc->GenerateString(f.arg); + str += ")"; + return str; + } + + TString operator()(const musrfit::ast::power_call& p) const { + TString str = "POW("; + str += fFunc->GenerateString(p.base); + str += ", "; + str += fFunc->GenerateString(p.pow); + str += ")"; + return str; + } + + TString operator()(const musrfit::ast::expression& expr) const { + // If no operations, just return the first operand without parentheses + if (expr.rest.empty()) { + return fFunc->GenerateStringOperand(expr.first); + } else { + // Has operations - this means we have a parenthesized expression from the grammar + // (e.g., from factor = '(' expression ')'), so we need to preserve the parentheses + return "(" + fFunc->GenerateString(expr) + ")"; + } + } + }; + + StringVisitor visitor(this); + return boost::apply_visitor(visitor, operand); +} + +//========================================================================== +// EvalVisitor Implementation +//========================================================================== + +/** + * \brief Evaluates a nil (empty) AST node. + * + * \return Always returns 0.0 + */ +Double_t PFunction::EvalVisitor::operator()(const musrfit::ast::nil&) const +{ return 0.0; } -//------------------------------------------------------------- -// CleanupFuncEvalTree (protected) -//------------------------------------------------------------- /** - * \brief Initiates cleanup of the evaluation tree. + * \brief Evaluates a numeric literal. * - * Starts the recursive cleanup process from the root node to free all - * dynamically allocated child nodes in the tree. - * - * \see CleanupNode for the recursive cleanup algorithm + * \param val The literal value from the AST + * \return The literal value unchanged */ -void PFunction::CleanupFuncEvalTree() +Double_t PFunction::EvalVisitor::operator()(double val) const { - // clean up all children - CleanupNode(fFunc); + return val; } -//------------------------------------------------------------- -// CleanupNode (protected) -//------------------------------------------------------------- /** - * \brief Recursively cleans up evaluation tree nodes and their children. + * \brief Evaluates a constant (PI, GAMMA_MU, B, EN, T#). * - * Performs depth-first traversal to clean up all child nodes before - * clearing the current node's children vector. This ensures proper - * resource deallocation and prevents memory leaks. + * Converts symbolic constants to their numeric values, including full support + * for experimental metadata (magnetic field, energy, temperature). * - * \param node Current evaluation tree node to clean up + * \param c The constant AST node with type, sign, and optional index + * \return The constant's numeric value (with sign applied if negative) */ -void PFunction::CleanupNode(PFuncTreeNode &node) +Double_t PFunction::EvalVisitor::operator()(const musrfit::ast::constant& c) const { - if (node.children.size() != 0) { - for (UInt_t i=0; i= 0 && static_cast(c.index) < fMetaData.fTemp.size()) { + val = fMetaData.fTemp[c.index]; + } else { + std::cerr << "**ERROR** Temperature index T" << c.index << " out of range" << std::endl; + val = 0.0; + } + break; + + default: + val = 0.0; } + + return c.sign ? -val : val; } -//------------------------------------------------------------- -// EvalTreeForString (private) -//------------------------------------------------------------- /** - * \brief Initiates generation of human-readable function string from AST. + * \brief Evaluates a parameter reference (PAR#). * - * Creates a formatted, readable string representation of the function by - * traversing the AST. The resulting string is stored in fFuncString and - * can be used for display, logging, or debugging purposes. + * Looks up the parameter value from the fParam vector using the + * parameter number (1-based indexing in syntax, 0-based in vector). + * Validates bounds and applies sign. * - * \param info AST parse tree to convert to string - * - * \see EvalTreeForStringExpression for the recursive string generation algorithm + * \param p The parameter AST node containing number and sign + * \return The parameter value (with sign applied), or 0.0 if out of bounds */ -void PFunction::EvalTreeForString(tree_parse_info<> info) +Double_t PFunction::EvalVisitor::operator()(const musrfit::ast::parameter& p) const { - fFuncString = ""; - EvalTreeForStringExpression(info.trees.begin()); -} + Int_t idx = p.number - 1; -//------------------------------------------------------------- -// EvalTreeForStringExpression (private) -//------------------------------------------------------------- -/** - * \brief Recursively generates formatted function string from AST. - * - * Traverses the AST and builds a human-readable string representation. - * The function handles: - * - Proper parenthesization to maintain operator precedence - * - Formatting of constants (PI -> Pi, GAMMA_MU -> gamma_mu) - * - Proper spacing around operators - * - Special handling for division denominators (adds parentheses if needed) - * - Function call formatting with arguments - * - * The resulting string is formatted for readability while maintaining - * mathematical correctness. - * - * \param i Iterator pointing to current AST node - * \param funcFlag Flag indicating if currently processing inside a function argument - * (affects parenthesization rules) - * - * \note Uses a static variable termOp to track operator nesting depth - */ -void PFunction::EvalTreeForStringExpression(iter_t const& i, bool funcFlag) -{ - static Int_t termOp = 0; - - if (i->value.id() == PFunctionGrammar::realID) { - assert(i->children.size() == 0); - if (*i->value.begin() == '-') - fFuncString += "("; - fFuncString += boost::algorithm::trim_copy(std::string(i->value.begin(), i->value.end())).c_str(); - if (*i->value.begin() == '-') - fFuncString += ")"; - } else if (i->value.id() == PFunctionGrammar::constPiID) { - fFuncString += "Pi"; - } else if (i->value.id() == PFunctionGrammar::constGammaMuID) { - fFuncString += "gamma_mu"; - } else if (i->value.id() == PFunctionGrammar::constFieldID) { - fFuncString += boost::algorithm::trim_copy(std::string(i->value.begin(), i->value.end())).c_str(); - } else if (i->value.id() == PFunctionGrammar::constEnergyID) { - fFuncString += boost::algorithm::trim_copy(std::string(i->value.begin(), i->value.end())).c_str(); - } else if (i->value.id() == PFunctionGrammar::constTempID) { - assert(i->children.size() == 0); - fFuncString += boost::algorithm::trim_copy(std::string(i->value.begin(), i->value.end())).c_str(); - } else if (i->value.id() == PFunctionGrammar::funLabelID) { - assert(i->children.size() == 0); - //SetFuncNo(i); - fFuncString += std::string(i->value.begin(), i->value.end()).c_str(); // funx - } else if (i->value.id() == PFunctionGrammar::parameterID) { - assert(i->children.size() == 0); - fFuncString += boost::algorithm::trim_copy(std::string(i->value.begin(), i->value.end())).c_str(); - } else if (i->value.id() == PFunctionGrammar::mapID) { - assert(i->children.size() == 0); - fFuncString += boost::algorithm::trim_copy(std::string(i->value.begin(), i->value.end())).c_str(); - } else if (i->value.id() == PFunctionGrammar::functionID) { - assert(i->children.size() == 3); - fFuncString += std::string(i->value.begin(), i->value.end()).c_str(); // keep function name - fFuncString += "("; - // '(', expression, ')' - EvalTreeForStringExpression(i->children.begin()+1, true); // the real stuff - fFuncString += ")"; - } else if (i->value.id() == PFunctionGrammar::powerID) { - assert(i->children.size() == 5); - fFuncString += std::string(i->value.begin(), i->value.end()).c_str(); // keep function name - fFuncString += "("; - // '(', expression, ',' expression, ')' - EvalTreeForStringExpression(i->children.begin()+1, true); // base expression - fFuncString += ", "; - EvalTreeForStringExpression(i->children.begin()+3, true); // exponent expression - fFuncString += ")"; - } else if (i->value.id() == PFunctionGrammar::factorID) { - EvalTreeForStringExpression(i->children.begin()); - } else if (i->value.id() == PFunctionGrammar::termID) { - termOp++; - if (*i->value.begin() == '*') { - assert(i->children.size() == 2); - EvalTreeForStringExpression(i->children.begin()); - fFuncString += " * "; - EvalTreeForStringExpression(i->children.begin()+1); - } else if (*i->value.begin() == '/') { - assert(i->children.size() == 2); - EvalTreeForStringExpression(i->children.begin()); - fFuncString += " / "; - if (((i->children.begin()+1)->children.size()>1) && - ((i->children.begin()+1)->value.id() != PFunctionGrammar::functionID) && - (i->children.begin()+1)->value.id() != PFunctionGrammar::powerID) // check if denominator is non-trivial and not a function - fFuncString += "("; - EvalTreeForStringExpression(i->children.begin()+1, true); - if (((i->children.begin()+1)->children.size()>1) && - ((i->children.begin()+1)->value.id() != PFunctionGrammar::functionID) && - (i->children.begin()+1)->value.id() != PFunctionGrammar::powerID) // check if denominator is non-trivial and not a function - fFuncString += ")"; - } else { - assert(0); - } - termOp--; - } else if (i->value.id() == PFunctionGrammar::expressionID) { - if ((termOp > 0) && !funcFlag) - fFuncString += "("; - if (*i->value.begin() == '+') { - assert(i->children.size() == 2); - EvalTreeForStringExpression(i->children.begin()); - fFuncString += " + "; - EvalTreeForStringExpression(i->children.begin()+1); - } else if (*i->value.begin() == '-') { - assert(i->children.size() == 2); - EvalTreeForStringExpression(i->children.begin()); - fFuncString += " - "; - EvalTreeForStringExpression(i->children.begin()+1); - } else { - assert(0); - } - if ((termOp > 0) && !funcFlag) - fFuncString += ")"; - } else if (i->value.id() == PFunctionGrammar::assignmentID) { - assert(i->children.size() == 3); - EvalTreeForStringExpression(i->children.begin()); - EvalTreeForStringExpression(i->children.begin()+1); // this is the string "=" - EvalTreeForStringExpression(i->children.begin()+2); // this is the real stuff - } else if (*i->value.begin() == '=') { - fFuncString += " = "; - } else { - assert(0); + if (idx < 0 || static_cast(idx) >= fParam.size()) { + std::cerr << "**ERROR** Parameter PAR" << p.number << " out of range during evaluation" << std::endl; + return 0.0; } + + Double_t val = fParam[idx]; + return p.sign ? -val : val; +} + +/** + * \brief Evaluates a map reference (MAP#) for indirect parameter lookup. + * + * Performs a two-level lookup: first looks up the map index in fMap, + * then uses that value to index into fParam. This allows for flexible + * parameter remapping. Validates both lookups and applies sign. + * + * \param m The map reference AST node containing map number and sign + * \return The indirectly referenced parameter value (with sign), or 0.0 if invalid + */ +Double_t PFunction::EvalVisitor::operator()(const musrfit::ast::map_ref& m) const +{ + Int_t mapIdx = m.number - 1; + + if (mapIdx < 0 || static_cast(mapIdx) >= fMap.size()) { + std::cerr << "**ERROR** Map MAP" << m.number << " out of range during evaluation" << std::endl; + return 0.0; + } + + Int_t paramIdx = fMap[mapIdx] - 1; + + if (paramIdx < 0 || static_cast(paramIdx) >= fParam.size()) { + std::cerr << "**ERROR** Mapped parameter out of range during evaluation" << std::endl; + return 0.0; + } + + Double_t val = fParam[paramIdx]; + return m.sign ? -val : val; +} + +/** + * \brief Evaluates a function call (COS, SIN, EXP, SQRT, etc.). + * + * Recursively evaluates the argument expression, then applies the + * specified mathematical function. Supports trigonometric, hyperbolic, + * inverse, exponential, and logarithmic functions. + * + * \param f The function call AST node with function ID and argument expression + * \return The result of applying the mathematical function to the argument + */ +Double_t PFunction::EvalVisitor::operator()(const musrfit::ast::function_call& f) const +{ + // Evaluate argument + Double_t arg = boost::apply_visitor(*this, f.arg.first); + for (const auto& op : f.arg.rest) { + Double_t rhs = boost::apply_visitor(*this, op.operand_); + switch (op.operator_) { + case musrfit::ast::op_plus: arg += rhs; break; + case musrfit::ast::op_minus: arg -= rhs; break; + case musrfit::ast::op_times: arg *= rhs; break; + case musrfit::ast::op_divide: + if (std::abs(rhs) > 1.0e-20) arg /= rhs; + break; + } + } + + // Apply function + Double_t result = 0.0; + switch (f.func_id) { + case musrfit::ast::fun_cos: result = std::cos(arg); break; + case musrfit::ast::fun_sin: result = std::sin(arg); break; + case musrfit::ast::fun_tan: result = std::tan(arg); break; + case musrfit::ast::fun_cosh: result = std::cosh(arg); break; + case musrfit::ast::fun_sinh: result = std::sinh(arg); break; + case musrfit::ast::fun_tanh: result = std::tanh(arg); break; + case musrfit::ast::fun_acos: result = std::acos(arg); break; + case musrfit::ast::fun_asin: result = std::asin(arg); break; + case musrfit::ast::fun_atan: result = std::atan(arg); break; + case musrfit::ast::fun_acosh: result = std::acosh(arg); break; + case musrfit::ast::fun_asinh: result = std::asinh(arg); break; + case musrfit::ast::fun_atanh: result = std::atanh(arg); break; + case musrfit::ast::fun_log: result = std::log10(std::abs(arg)); break; + case musrfit::ast::fun_ln: result = std::log(std::abs(arg)); break; + case musrfit::ast::fun_exp: result = std::exp(arg); break; + case musrfit::ast::fun_sqrt: result = std::sqrt(std::abs(arg)); break; + default: result = 0.0; + } + + return result; +} + +/** + * \brief Evaluates a power operation POW(base, exponent). + * + * Recursively evaluates both the base and exponent expressions, + * then computes base^exponent using std::pow. Uses absolute value + * of base to avoid complex number issues with negative bases. + * + * \param p The power call AST node with base and exponent expressions + * \return The result of raising the base to the exponent power + */ +Double_t PFunction::EvalVisitor::operator()(const musrfit::ast::power_call& p) const +{ + // Evaluate base + Double_t base = boost::apply_visitor(*this, p.base.first); + for (const auto& op : p.base.rest) { + Double_t rhs = boost::apply_visitor(*this, op.operand_); + switch (op.operator_) { + case musrfit::ast::op_plus: base += rhs; break; + case musrfit::ast::op_minus: base -= rhs; break; + case musrfit::ast::op_times: base *= rhs; break; + case musrfit::ast::op_divide: + if (std::abs(rhs) > 1.0e-20) base /= rhs; + break; + } + } + + // Evaluate exponent + Double_t exponent = boost::apply_visitor(*this, p.pow.first); + for (const auto& op : p.pow.rest) { + Double_t rhs = boost::apply_visitor(*this, op.operand_); + switch (op.operator_) { + case musrfit::ast::op_plus: exponent += rhs; break; + case musrfit::ast::op_minus: exponent -= rhs; break; + case musrfit::ast::op_times: exponent *= rhs; break; + case musrfit::ast::op_divide: + if (std::abs(rhs) > 1.0e-20) exponent /= rhs; + break; + } + } + + return std::pow(std::abs(base), exponent); +} + +/** + * \brief Evaluates a complete expression with binary operators. + * + * Evaluates the first operand, then applies each operation in sequence + * from left to right. The AST structure encodes operator precedence, + * so this left-to-right evaluation produces correct results. + * + * Handles division by zero by checking if divisor magnitude is greater + * than 1e-20; otherwise returns 0.0 for that sub-expression. + * + * \param e The expression AST node with first operand and operation list + * \return The computed result of the expression + */ +Double_t PFunction::EvalVisitor::operator()(const musrfit::ast::expression& e) const +{ + Double_t result = boost::apply_visitor(*this, e.first); + + for (const auto& op : e.rest) { + Double_t rhs = boost::apply_visitor(*this, op.operand_); + switch (op.operator_) { + case musrfit::ast::op_plus: result += rhs; break; + case musrfit::ast::op_minus: result -= rhs; break; + case musrfit::ast::op_times: result *= rhs; break; + case musrfit::ast::op_divide: + if (std::abs(rhs) > 1.0e-20) result /= rhs; + break; + } + } + + return result; } diff --git a/src/classes/PFunctionHandler.cpp b/src/classes/PFunctionHandler.cpp index 093fde63b..6c2c13fc2 100644 --- a/src/classes/PFunctionHandler.cpp +++ b/src/classes/PFunctionHandler.cpp @@ -30,8 +30,12 @@ #include #include +#include + #include "PFunctionHandler.h" +namespace x3 = boost::spirit::x3; + //------------------------------------------------------------- // Constructor //------------------------------------------------------------- @@ -67,7 +71,6 @@ PFunctionHandler::~PFunctionHandler() Bool_t PFunctionHandler::DoParse() { Bool_t success = true; - PFunctionGrammar function; TString line; // feed the function block into the parser. Start with i=1, since i=0 is FUNCTIONS @@ -89,14 +92,32 @@ Bool_t PFunctionHandler::DoParse() } line.ToUpper(); - // do parsing - tree_parse_info<> info = ast_parse(line.Data(), function, space_p); + // do parsing with X3 + musrfit::ast::assignment assignment; + std::string str(line.Data()); + auto iter = str.begin(); + auto end = str.end(); - if (info.full) { // parsing successful - PFunction func(info); // generate an evaluation function object based on the AST tree - fFuncs.push_back(func); // feeds it to the functions vector - } else { + // Get the X3 grammar + auto const& grammar = musrfit::function_grammar(); + + try { + bool parseSuccess = x3::phrase_parse(iter, end, grammar, x3::space, assignment); + + if (parseSuccess && iter == end) { // parsing successful + PFunction func(assignment); // generate an evaluation function object based on the AST + fFuncs.push_back(func); // feeds it to the functions vector + } else { + std::cerr << std::endl << "**ERROR**: FUNCTIONS parse failed in line " << fLines[i].fLineNo << std::endl; + if (iter != end) { + std::cerr << "**ERROR**: Stopped at: " << std::string(iter, end) << std::endl; + } + success = false; + break; + } + } catch (x3::expectation_failure const& e) { std::cerr << std::endl << "**ERROR**: FUNCTIONS parse failed in line " << fLines[i].fLineNo << std::endl; + std::cerr << "**ERROR**: Expected: " << e.which() << std::endl; success = false; break; } diff --git a/src/include/PFunction.h b/src/include/PFunction.h index 8f624deb6..55f68c759 100644 --- a/src/include/PFunction.h +++ b/src/include/PFunction.h @@ -8,7 +8,7 @@ ***************************************************************************/ /*************************************************************************** - * Copyright (C) 2007-2025 by Andreas Suter * + * Copyright (C) 2007-2026 by Andreas Suter * * andreas.suter@psi.ch * * * * This program is free software; you can redistribute it and/or modify * @@ -33,85 +33,23 @@ #include #include - -#if BOOST_VERSION >= 103800 -# include - using namespace BOOST_SPIRIT_CLASSIC_NS; -#else -# include - using namespace boost::spirit; -#endif +#include +#include #include #include "PMusr.h" +#include "PFunctionAst.h" #include "PFunctionGrammar.h" -//---------------------------------------------------------------------------- -// Operator tags for arithmetic operations -//---------------------------------------------------------------------------- -#define OP_ADD 0 ///< Addition operator tag -#define OP_SUB 1 ///< Subtraction operator tag -#define OP_MUL 2 ///< Multiplication operator tag -#define OP_DIV 3 ///< Division operator tag - -//---------------------------------------------------------------------------- -// Function tags for mathematical functions -//---------------------------------------------------------------------------- -#define FUN_COS 0 ///< Cosine function tag -#define FUN_SIN 1 ///< Sine function tag -#define FUN_TAN 2 ///< Tangent function tag -#define FUN_COSH 3 ///< Hyperbolic cosine function tag -#define FUN_SINH 4 ///< Hyperbolic sine function tag -#define FUN_TANH 5 ///< Hyperbolic tangent function tag -#define FUN_ACOS 6 ///< Inverse cosine (arccos) function tag -#define FUN_ASIN 7 ///< Inverse sine (arcsin) function tag -#define FUN_ATAN 8 ///< Inverse tangent (arctan) function tag -#define FUN_ACOSH 9 ///< Inverse hyperbolic cosine function tag -#define FUN_ASINH 10 ///< Inverse hyperbolic sine function tag -#define FUN_ATANH 11 ///< Inverse hyperbolic tangent function tag -#define FUN_LOG 12 ///< Base-10 logarithm function tag -#define FUN_LN 13 ///< Natural logarithm function tag -#define FUN_EXP 14 ///< Exponential function tag -#define FUN_SQRT 15 ///< Square root function tag -#define FUN_POW 16 ///< Power function tag (base^exponent) - -//---------------------------------------------------------------------------- -/** - * \brief Tree node structure for efficient function evaluation. - * - * This structure represents a node in the evaluation tree used to compute - * function values. The abstract syntax tree (AST) generated by the parser - * is converted into this more efficient tree structure for faster evaluation. - * - * Each node can represent: - * - A leaf node (constant, parameter, map reference) - * - An operator node (arithmetic operation) - * - A function node (mathematical function) - * - * The tree is evaluated recursively by traversing from the root to the leaves. - * - * \see PFunction::EvalNode for the recursive evaluation algorithm - */ -typedef struct func_tree_node { - Int_t fID; ///< Node type identifier (from PFunctionGrammar constants) - Int_t fOperatorTag; ///< Operator type: OP_ADD, OP_SUB, OP_MUL, OP_DIV - Int_t fFunctionTag; ///< Function type: FUN_COS, FUN_SIN, FUN_EXP, etc. - Int_t fIvalue; ///< Integer value for parameter numbers, map indices, or temperature indices - Bool_t fSign; ///< Sign flag: true for negative, false for positive - Double_t fDvalue; ///< Numeric value for constants and real number literals - std::vector children; ///< Child nodes forming the sub-tree -} PFuncTreeNode; - //---------------------------------------------------------------------------- /** * \brief Class for parsing and evaluating mathematical functions from msr-file FUNCTIONS blocks. * * This class handles the complete lifecycle of a function definition: * 1. Parses the function string using PFunctionGrammar into an AST - * 2. Converts the AST into an efficient evaluation tree - * 3. Validates parameter and map references - * 4. Evaluates the function with given parameters and metadata + * 2. Validates parameter and map references + * 3. Evaluates the function with given parameters and metadata using the visitor pattern * * Functions can reference: * - Fit parameters (PAR1, PAR2, ...) @@ -126,7 +64,7 @@ typedef struct func_tree_node { * \endcode * * \see PFunctionGrammar for the grammar definition - * \see PFuncTreeNode for the evaluation tree structure + * \see musrfit::ast for the AST structure */ class PFunction { public: @@ -134,13 +72,13 @@ class PFunction { /** * \brief Constructor that parses and prepares a function for evaluation. * - * \param info Abstract syntax tree (AST) from parsing a function expression + * \param assignment Abstract syntax tree (AST) from parsing a function expression */ - PFunction(tree_parse_info<> info); + PFunction(const musrfit::ast::assignment& assignment); //------------------------------------------------------------------------ /** - * \brief Destructor that cleans up the evaluation tree. + * \brief Destructor that cleans up resources. */ virtual ~PFunction(); @@ -202,101 +140,72 @@ class PFunction { protected: //------------------------------------------------------------------------ /** - * \brief Initializes all fields of an evaluation tree node to default values. + * \brief Recursively validates parameter and map references in the AST. * - * \param node Node to initialize - */ - virtual void InitNode(PFuncTreeNode &node); - - //------------------------------------------------------------------------ - /** - * \brief Extracts the function number from the AST. - * - * Parses the function label (FUN#) to extract the numeric identifier. - * - * \return true if successful, false otherwise - */ - virtual Bool_t SetFuncNo(); - - //------------------------------------------------------------------------ - /** - * \brief Recursively validates parameter and map references in the tree. - * - * \param node Current node being checked + * \param operand Current operand being checked * \param mapSize Number of available map entries * \param paramSize Number of available fit parameters * \return true if all references are valid, false otherwise */ - virtual Bool_t FindAndCheckMapAndParamRange(PFuncTreeNode &node, UInt_t mapSize, UInt_t paramSize); + virtual Bool_t FindAndCheckMapAndParamRange(const musrfit::ast::operand& operand, + UInt_t mapSize, UInt_t paramSize); //------------------------------------------------------------------------ /** - * \brief Initiates the conversion from AST to evaluation tree. + * \brief Generates a human-readable string representation of the AST. * - * \return true if successful, false otherwise + * \param expr Expression AST to convert to string + * \return Formatted string representation */ - virtual Bool_t GenerateFuncEvalTree(); + virtual TString GenerateString(const musrfit::ast::expression& expr); //------------------------------------------------------------------------ /** - * \brief Recursively builds the evaluation tree from the AST. + * \brief Generates a string representation of an operand. * - * \param i Iterator pointing to current AST node - * \param node Evaluation tree node to fill + * \param operand Operand AST to convert to string + * \return Formatted string representation */ - virtual void FillFuncEvalTree(iter_t const& i, PFuncTreeNode &node); - - //------------------------------------------------------------------------ - /** - * \brief Recursively evaluates an evaluation tree node. - * - * \param node Node to evaluate - * \return Computed value for this node and its subtree - */ - virtual Double_t EvalNode(PFuncTreeNode &node); - - //------------------------------------------------------------------------ - /** - * \brief Initiates cleanup of the evaluation tree. - */ - virtual void CleanupFuncEvalTree(); - - //------------------------------------------------------------------------ - /** - * \brief Recursively cleans up evaluation tree nodes and their children. - * - * \param node Node to clean up - */ - virtual void CleanupNode(PFuncTreeNode &node); + virtual TString GenerateStringOperand(const musrfit::ast::operand& operand); private: - tree_parse_info<> fInfo; ///< AST parse tree from Boost.Spirit parser - std::vector fParam; ///< Current fit parameter values for evaluation - std::vector fMap; ///< Map vector for indirect parameter references - PFuncTreeNode fFunc; ///< Root node of the evaluation tree - - Bool_t fValid; ///< Validity flag: true if function parsed and initialized successfully - Int_t fFuncNo; ///< Function number extracted from label (x in FUNx) - - //------------------------------------------------------------------------ /** - * \brief Initiates generation of human-readable function string. + * \brief Visitor class for evaluating the AST. * - * \param info AST parse tree to convert to string + * This visitor traverses the AST and computes the numeric value of the expression. + * It uses the visitor pattern with boost::static_visitor to handle different + * AST node types. */ - virtual void EvalTreeForString(tree_parse_info<> info); + class EvalVisitor : public boost::static_visitor + { + public: + EvalVisitor(const std::vector& map, + const std::vector& param, + const PMetaData& metaData) + : fMap(map), fParam(param), fMetaData(metaData) {} - //------------------------------------------------------------------------ - /** - * \brief Recursively generates formatted function string from AST. - * - * \param i Iterator pointing to current AST node - * \param funcFlag Flag indicating if currently inside a function call - */ - virtual void EvalTreeForStringExpression(iter_t const& i, bool funcFlag=false); + Double_t operator()(const musrfit::ast::nil&) const; + Double_t operator()(double val) const; + Double_t operator()(const musrfit::ast::constant& c) const; + Double_t operator()(const musrfit::ast::parameter& p) const; + Double_t operator()(const musrfit::ast::map_ref& m) const; + Double_t operator()(const musrfit::ast::function_call& f) const; + Double_t operator()(const musrfit::ast::power_call& p) const; + Double_t operator()(const musrfit::ast::expression& expr) const; + private: + const std::vector& fMap; + const std::vector& fParam; + const PMetaData& fMetaData; + }; + + musrfit::ast::expression fAst; ///< Abstract syntax tree for the expression + std::vector fParam; ///< Current fit parameter values for evaluation + std::vector fMap; ///< Map vector for indirect parameter references + + Bool_t fValid; ///< Validity flag: true if function parsed and initialized successfully + Int_t fFuncNo; ///< Function number extracted from label (x in FUNx) TString fFuncString; ///< Formatted, human-readable function representation - PMetaData fMetaData; ///< Metadata from experimental data (field, energy, temperature, etc.) }; diff --git a/src/include/PFunctionGrammar.h b/src/include/PFunctionGrammar.h index b3c530cef..b8f00b3b0 100644 --- a/src/include/PFunctionGrammar.h +++ b/src/include/PFunctionGrammar.h @@ -1,14 +1,17 @@ /*************************************************************************** - PFunctionGrammer.h + PFunctionGrammar.h Author: Andreas Suter e-mail: andreas.suter@psi.ch + Header-only grammar for parsing function entries in msr-file FUNCTION blocks. + This version uses Boost.Spirit X3 in header-only mode for maximum compatibility. + ***************************************************************************/ /*************************************************************************** - * Copyright (C) 2007-2025 by Andreas Suter * + * Copyright (C) 2007-2026 by Andreas Suter * * andreas.suter@psi.ch * * * * This program is free software; you can redistribute it and/or modify * @@ -30,223 +33,296 @@ #ifndef _PFUNCTIONGRAMMAR_H_ #define _PFUNCTIONGRAMMAR_H_ -//#define BOOST_SPIRIT_DEBUG - +// Check Boost version - require 1.61+ for Spirit X3 #include - -#if BOOST_VERSION >= 103800 -# include -# include - using namespace BOOST_SPIRIT_CLASSIC_NS; -#else -# include -# include - using namespace boost::spirit; +#if BOOST_VERSION < 106100 +# error "Boost version 1.61.0 or higher is required for Spirit X3. Please upgrade Boost." #endif -//-------------------------------------------------------------------------- -/** - * \brief Iterator type for parsing characters. - */ -typedef char const* iterator_t; +#include "PFunctionAst.h" +#include -//-------------------------------------------------------------------------- -/** - * \brief Type for parse tree matching results. - */ -typedef tree_match parse_tree_match_t; +namespace x3 = boost::spirit::x3; -//-------------------------------------------------------------------------- /** - * \brief Iterator type for traversing the parse tree. + * @namespace musrfit::grammar + * @brief Boost.Spirit X3 grammar definitions for parsing function expressions. + * + * This namespace contains the complete grammar for parsing msr-file FUNCTION + * block entries. The grammar supports arithmetic expressions with operator + * precedence, mathematical functions, constants, and parameter/map references. + * + * The grammar is header-only for maximum portability and uses X3's modern + * attribute propagation system to automatically build the AST during parsing. */ -typedef parse_tree_match_t::tree_iterator iter_t; - -//-------------------------------------------------------------------------- -/** - * \brief EBNF-like grammar definition for parsing function entries in msr-file FUNCTION blocks. - * - * This grammar defines the syntax for parsing mathematical function expressions in msr-files. - * It supports: - * - Basic arithmetic operations (+, -, *, /) - * - Mathematical functions (trigonometric, hyperbolic, logarithmic, exponential, etc.) - * - Constants (PI, GAMMA_MU, field B, energy EN, temperature T) - * - Parameters (PAR) and maps (MAP) - * - Function references (FUN) - * - * The grammar follows an EBNF-like structure with the following hierarchy: - * \verbatim - * assignment = fun_label '=' expression - * expression = term { ('+' | '-') term } - * term = factor { ('*' | '/') factor } - * factor = real | constant | parameter | map | function | power | '(' expression ')' - * \endverbatim - * - * \see PFunction for the class that uses this grammar to evaluate parsed expressions - */ -struct PFunctionGrammar : public grammar +namespace musrfit { namespace grammar { - static const int realID = 1; ///< Identifier for real number literals - static const int constPiID = 2; ///< Identifier for PI constant - static const int constGammaMuID = 3; ///< Identifier for GAMMA_MU constant (muon gyromagnetic ratio) - static const int constFieldID = 4; ///< Identifier for magnetic field constant (B or -B) - static const int constEnergyID = 5; ///< Identifier for energy constant (EN or -EN) - static const int constTempID = 6; ///< Identifier for temperature constant (T# or -T#) - static const int funLabelID = 7; ///< Identifier for function label (FUN#) - static const int parameterID = 8; ///< Identifier for parameter reference (PAR# or -PAR#) - static const int mapID = 9; ///< Identifier for map reference (MAP#) - static const int functionID = 10; ///< Identifier for mathematical functions (cos, sin, exp, etc.) - static const int powerID = 11; ///< Identifier for power function (POW) - static const int factorID = 12; ///< Identifier for factor in expression - static const int termID = 13; ///< Identifier for term in expression - static const int expressionID = 14; ///< Identifier for expression - static const int assignmentID = 15; ///< Identifier for assignment statement + using x3::int_; + using x3::double_; + using x3::lit; + using x3::lexeme; + using x3::attr; - //------------------------------------------------------------------------ - /** - * \brief Inner template structure defining the grammar rules. - * - * This template structure contains the actual grammar rule definitions - * using Boost.Spirit syntax. It defines how the parser should recognize - * and build an abstract syntax tree (AST) for function expressions. - * - * \tparam ScannerT Scanner type used by Boost.Spirit for parsing - */ - template - struct definition + /////////////////////////////////////////////////////////////////////////// + // Symbol tables - using inline to avoid multiple definition errors + /////////////////////////////////////////////////////////////////////////// + + /** + * @brief Symbol table for additive operators (+ and -). + * + * Maps operator characters to their corresponding AST token types + * for addition and subtraction operations. + */ + struct additive_op_ : x3::symbols + { + additive_op_() { - //-------------------------------------------------------------------- - /** - * \brief Constructor that defines all grammar rules. - * - * Sets up the complete grammar hierarchy for parsing function expressions: - * - Terminals: real numbers, constants (PI, GAMMA_MU, B, EN, T), parameters (PAR), maps (MAP) - * - Functions: trigonometric (cos, sin, tan), hyperbolic (cosh, sinh, tanh), - * inverse trigonometric (acos, asin, atan), inverse hyperbolic (acosh, asinh, atanh), - * logarithmic (log, ln), exponential (exp), square root (sqrt), power (pow) - * - Operators: addition (+), subtraction (-), multiplication (*), division (/) - * - Expressions: Hierarchical structure following operator precedence - * - * \param self Reference to the enclosing PFunctionGrammar (unused but required by Boost.Spirit) - */ - definition(PFunctionGrammar const& /*self*/) - { - // Start grammar definition - real = leaf_node_d[ real_p ]; + add("+", ast::op_plus)("-", ast::op_minus); + } + }; - const_pi = leaf_node_d[ str_p("PI") ]; + inline additive_op_ additive_op; ///< Global instance of additive operator symbol table - const_gamma_mu = leaf_node_d[ str_p("GAMMA_MU") ]; + /** + * @brief Symbol table for multiplicative operators (* and /). + * + * Maps operator characters to their corresponding AST token types + * for multiplication and division operations. + */ + struct multiplicative_op_ : x3::symbols + { + multiplicative_op_() + { + add("*", ast::op_times)("/", ast::op_divide); + } + }; - const_field = leaf_node_d[ str_p("B") ] | - leaf_node_d[ str_p("-B") ]; + inline multiplicative_op_ multiplicative_op; ///< Global instance of multiplicative operator symbol table - const_energy = leaf_node_d[ str_p("EN") ] | - leaf_node_d[ str_p("-EN") ]; + /** + * @brief Symbol table for mathematical function names. + * + * Maps uppercase function names (COS, SIN, EXP, etc.) to their + * corresponding AST function identifiers. Supports trigonometric, + * hyperbolic, inverse, exponential, and logarithmic functions. + */ + struct fun_tok_ : x3::symbols + { + fun_tok_() + { + add + ("COS", ast::fun_cos) + ("SIN", ast::fun_sin) + ("TAN", ast::fun_tan) + ("COSH", ast::fun_cosh) + ("SINH", ast::fun_sinh) + ("TANH", ast::fun_tanh) + ("ACOS", ast::fun_acos) + ("ASIN", ast::fun_asin) + ("ATAN", ast::fun_atan) + ("ACOSH", ast::fun_acosh) + ("ASINH", ast::fun_asinh) + ("ATANH", ast::fun_atanh) + ("LOG", ast::fun_log) + ("LN", ast::fun_ln) + ("EXP", ast::fun_exp) + ("SQRT", ast::fun_sqrt); + } + }; - const_temp = leaf_node_d[ ( lexeme_d[ "T" >> +digit_p ] ) ] | - leaf_node_d[ ( lexeme_d[ "-T" >> +digit_p ] ) ]; + inline fun_tok_ fun_tok; ///< Global instance of function name symbol table - fun_label = leaf_node_d[ ( lexeme_d[ "FUN" >> +digit_p ] ) ]; + /** + * @brief Symbol table for named constants. + * + * Maps constant names (PI, GAMMA_MU) to their corresponding + * AST constant type identifiers. + */ + struct const_tok_ : x3::symbols + { + const_tok_() + { + add("PI", ast::constant::pi)("GAMMA_MU", ast::constant::gamma_mu); + } + }; - parameter = leaf_node_d[ ( lexeme_d[ "PAR" >> +digit_p ] ) | - ( lexeme_d[ "-PAR" >> +digit_p ] ) ]; + inline const_tok_ const_tok; ///< Global instance of constant name symbol table - map = leaf_node_d[ ( lexeme_d[ "MAP" >> +digit_p ] ) ]; + /////////////////////////////////////////////////////////////////////////// + // Grammar Rules - Forward Declarations + /////////////////////////////////////////////////////////////////////////// - function = lexeme_d[ root_node_d[ str_p("COS") ] >> ch_p('(') ] >> expression >> ch_p(')') - | lexeme_d[ root_node_d[ str_p("SIN") ] >> ch_p('(') ] >> expression >> ch_p(')') - | lexeme_d[ root_node_d[ str_p("TAN") ] >> ch_p('(') ] >> expression >> ch_p(')') - | lexeme_d[ root_node_d[ str_p("COSH") ] >> ch_p('(') ] >> expression >> ch_p(')') - | lexeme_d[ root_node_d[ str_p("SINH") ] >> ch_p('(') ] >> expression >> ch_p(')') - | lexeme_d[ root_node_d[ str_p("TANH") ] >> ch_p('(') ] >> expression >> ch_p(')') - | lexeme_d[ root_node_d[ str_p("ACOS") ] >> ch_p('(') ] >> expression >> ch_p(')') - | lexeme_d[ root_node_d[ str_p("ASIN") ] >> ch_p('(') ] >> expression >> ch_p(')') - | lexeme_d[ root_node_d[ str_p("ATAN") ] >> ch_p('(') ] >> expression >> ch_p(')') - | lexeme_d[ root_node_d[ str_p("ACOSH") ] >> ch_p('(') ] >> expression >> ch_p(')') - | lexeme_d[ root_node_d[ str_p("ASINH") ] >> ch_p('(') ] >> expression >> ch_p(')') - | lexeme_d[ root_node_d[ str_p("ATANH") ] >> ch_p('(') ] >> expression >> ch_p(')') - | lexeme_d[ root_node_d[ str_p("LOG") ] >> ch_p('(') ] >> expression >> ch_p(')') - | lexeme_d[ root_node_d[ str_p("LN") ] >> ch_p('(') ] >> expression >> ch_p(')') - | lexeme_d[ root_node_d[ str_p("EXP") ] >> ch_p('(') ] >> expression >> ch_p(')') - | lexeme_d[ root_node_d[ str_p("SQRT") ] >> ch_p('(') ] >> expression >> ch_p(')') - ; + /// Top-level rule: FUN# = expression + x3::rule const assignment = "assignment"; - power = lexeme_d[ root_node_d[ str_p("POW") ] >> ch_p('(') ] >> expression >> ch_p(',') >> expression >> ch_p(')') - ; + /// Expression with addition/subtraction (lowest precedence) + x3::rule const expression = "expression"; - factor = real - | const_pi - | const_gamma_mu - | const_field - | const_energy - | const_temp - | parameter - | map - | function - | power - | inner_node_d[ch_p('(') >> expression >> ch_p(')')] - ; + /// Term with multiplication/division (higher precedence) + x3::rule const term = "term"; - term = factor >> - *( (root_node_d[ch_p('*')] >> factor) - | (root_node_d[ch_p('/')] >> factor) - ); + /// Factor: literal, constant, parameter, function, or parenthesized expression (highest precedence) + x3::rule const factor = "factor"; - expression = term >> - *( (root_node_d[ch_p('+')] >> term) - | (root_node_d[ch_p('-')] >> term) - ); + /// Constant: PI, GAMMA_MU, B, EN, or T# + x3::rule const constant = "constant"; - assignment = (fun_label >> ch_p('=') >> expression); - // End grammar definition + /// Parameter reference: PAR# or -PAR# + x3::rule const parameter = "parameter"; - // turn on the debugging info. - BOOST_SPIRIT_DEBUG_RULE(real); - BOOST_SPIRIT_DEBUG_RULE(const_pi); - BOOST_SPIRIT_DEBUG_RULE(const_gamma_mu); - BOOST_SPIRIT_DEBUG_RULE(const_field); - BOOST_SPIRIT_DEBUG_RULE(const_energy); - BOOST_SPIRIT_DEBUG_RULE(const_temp); - BOOST_SPIRIT_DEBUG_RULE(fun_label); - BOOST_SPIRIT_DEBUG_RULE(parameter); - BOOST_SPIRIT_DEBUG_RULE(map); - BOOST_SPIRIT_DEBUG_RULE(function); - BOOST_SPIRIT_DEBUG_RULE(power); - BOOST_SPIRIT_DEBUG_RULE(factor); - BOOST_SPIRIT_DEBUG_RULE(term); - BOOST_SPIRIT_DEBUG_RULE(expression); - BOOST_SPIRIT_DEBUG_RULE(assignment); - } + /// Map reference: MAP# or -MAP# + x3::rule const map = "map"; - rule, parser_tag > assignment; ///< Rule for assignment: FUN# = expression - rule, parser_tag > expression; ///< Rule for expression: term { ('+' | '-') term } - rule, parser_tag > term; ///< Rule for term: factor { ('*' | '/') factor } - rule, parser_tag > factor; ///< Rule for factor: operand or parenthesized expression - rule, parser_tag > function; ///< Rule for mathematical functions - rule, parser_tag > power; ///< Rule for power function POW(base, exponent) - rule, parser_tag > map; ///< Rule for map reference MAP# - rule, parser_tag > parameter; ///< Rule for parameter reference PAR# or -PAR# - rule, parser_tag > fun_label; ///< Rule for function label FUN# - rule, parser_tag > const_temp; ///< Rule for temperature constant T# or -T# - rule, parser_tag > const_energy; ///< Rule for energy constant EN or -EN - rule, parser_tag > const_field; ///< Rule for field constant B or -B - rule, parser_tag > const_gamma_mu;///< Rule for muon gyromagnetic ratio constant - rule, parser_tag > const_pi; ///< Rule for PI constant - rule, parser_tag > real; ///< Rule for real number literals + /// Function call: FUNC(expression) + x3::rule const function = "function"; - //-------------------------------------------------------------------- - /** - * \brief Returns the starting rule for the grammar. - * - * The parser begins by matching the assignment rule, which represents - * a complete function definition (e.g., FUN1 = PAR1 * cos(PAR2 * PI)). - * - * \return Reference to the assignment rule as the grammar entry point - */ - rule, parser_tag > const& - start() const { return assignment; } - }; -}; + /// Power operation: POW(base, exponent) + x3::rule const power = "power"; + + /////////////////////////////////////////////////////////////////////////// + // Grammar Rule Definitions + /////////////////////////////////////////////////////////////////////////// + + /** + * @brief Assignment rule: FUN# = expression + * + * Parses a function assignment statement, extracting the function number + * and the expression to evaluate. + */ + auto const assignment_def = + lit("FUN") >> int_ >> '=' >> expression; + + /** + * @brief Expression rule: term ((+|-) term)* + * + * Handles addition and subtraction with left-associative evaluation. + * Lower precedence than multiplication/division. + */ + auto const expression_def = + term >> *(additive_op >> term); + + /** + * @brief Term rule: factor ((*|/) factor)* + * + * Handles multiplication and division with left-associative evaluation. + * Higher precedence than addition/subtraction. + */ + auto const term_def = + factor >> *(multiplicative_op >> factor); + + /** + * @brief Factor rule: the atomic elements of expressions. + * + * Matches numeric literals, constants, parameters, maps, function calls, + * power operations, or parenthesized sub-expressions. Parentheses allow + * overriding operator precedence. + */ + auto const factor_def = + double_ + | constant + | parameter + | map + | function + | power + | ('(' >> expression >> ')'); + + /** + * @brief Constant rule: PI | GAMMA_MU | B | -B | EN | -EN | T# | -T# + * + * Parses symbolic constants, optionally with negation. Temperature + * constants include an index number (T0, T1, T2, etc.). + */ + auto const constant_def = + (const_tok >> attr(false) >> attr(0)) + | (lit("B") >> attr(ast::constant::field) >> attr(false) >> attr(0)) + | (lit("-B") >> attr(ast::constant::field) >> attr(true) >> attr(0)) + | (lit("EN") >> attr(ast::constant::energy) >> attr(false) >> attr(0)) + | (lit("-EN") >> attr(ast::constant::energy) >> attr(true) >> attr(0)) + | (lit('T') >> attr(ast::constant::temp) >> attr(false) >> int_) + | (lit("-T") >> attr(ast::constant::temp) >> attr(true) >> int_); + + /** + * @brief Parameter rule: PAR# | -PAR# + * + * Parses parameter references with 1-based indexing. The lexeme directive + * for -PAR# ensures the minus sign is treated as part of the token, + * not as a separate operator. + */ + auto const parameter_def = + (lexeme[lit("-PAR") >> int_] >> attr(true)) + | (lit("PAR") >> int_ >> attr(false)); + + /** + * @brief Map rule: MAP# | -MAP# + * + * Parses map references for indirect parameter lookup with 1-based indexing. + * The lexeme directive for -MAP# ensures the minus sign is part of the token. + */ + auto const map_def = + (lexeme[lit("-MAP") >> int_] >> attr(true)) + | (lit("MAP") >> int_ >> attr(false)); + + /** + * @brief Function rule: FUNC(expression) + * + * Parses mathematical function calls. The function name is matched by + * fun_tok and the argument is a full expression. + */ + auto const function_def = + fun_tok >> '(' >> expression >> ')'; + + /** + * @brief Power rule: POW(base, exponent) + * + * Parses power operations with two expression arguments separated by comma. + * Both base and exponent can be arbitrary expressions. + */ + auto const power_def = + lit("POW") >> '(' >> expression >> ',' >> expression >> ')'; + + /** + * @brief Links rule names to their definitions. + * + * Required by Boost.Spirit X3 to connect the forward-declared rules + * with their actual parsing logic. + */ + BOOST_SPIRIT_DEFINE( + assignment, + expression, + term, + factor, + constant, + parameter, + map, + function, + power + ) + +}} + +/** + * @namespace musrfit + * @brief Top-level namespace for musrfit components. + */ +namespace musrfit { + /** + * @brief Provides access to the top-level grammar rule. + * + * Returns a reference to the assignment rule, which is the entry point + * for parsing complete function assignment statements (FUN# = expression). + * Use this function to obtain the grammar for parsing with Spirit X3. + * + * @return Constant reference to the assignment grammar rule + * + * @code + * auto const& grammar = musrfit::function_grammar(); + * bool success = x3::phrase_parse(iter, end, grammar, x3::space, result); + * @endcode + */ + inline auto const& function_grammar() + { + return grammar::assignment; + } +} #endif // _PFUNCTIONGRAMMAR_H_