21#include <fmt/format.h> 
   34    static constexpr std::array<int, 12> 
daysInMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 
   35    static constexpr std::array<const char*, 12> 
monthNames = {
 
   36        "January", 
"February", 
"March", 
"April", 
"May", 
"June",
 
   37        "July", 
"August", 
"September", 
"October", 
"November", 
"December" 
 
   40        "Jan", 
"Feb", 
"Mar", 
"Apr", 
"May", 
"Jun",
 
   41        "Jul", 
"Aug", 
"Sep", 
"Oct", 
"Nov", 
"Dec" 
 
   44        "Monday", 
"Tuesday", 
"Wednesday", 
"Thursday", 
"Friday", 
"Saturday", 
"Sunday" 
 
   49        return ( aYear % 4 == 0 && aYear % 100 != 0 ) || ( aYear % 400 == 0 );
 
 
   66    static auto DaysToYmd( 
int aDaysSinceEpoch ) -> std::tuple<int, int, int>
 
   69        int remainingDays = aDaysSinceEpoch;
 
   71        if( remainingDays >= 0 )
 
   81            while( remainingDays < 0 )
 
   95        int day = remainingDays + 1;
 
   96        return {year, month, day};
 
 
   99    static auto YmdToDays( 
int aYear, 
int aMonth, 
int aDay ) -> 
int 
  114        for( 
int m = 1; m < aMonth; ++m )
 
  117        totalDays += aDay - 1;
 
 
  121    static auto ParseDate( 
const std::string& aDateStr ) -> std::optional<int>
 
  123        std::istringstream iss( aDateStr );
 
  125        std::vector<int> parts;
 
  128        bool isCjkFormat = 
false;
 
  131        bool hasChineseYear = aDateStr.find( 
"年" ) != std::string::npos;
 
  132        bool hasChineseMonth = aDateStr.find( 
"月" ) != std::string::npos;
 
  133        bool hasChineseDay = aDateStr.find( 
"日" ) != std::string::npos;
 
  134        bool hasKoreanYear = aDateStr.find( 
"년" ) != std::string::npos;
 
  135        bool hasKoreanMonth = aDateStr.find( 
"월" ) != std::string::npos;
 
  136        bool hasKoreanDay = aDateStr.find( 
"일" ) != std::string::npos;
 
  139        if( (hasChineseYear || hasKoreanYear) &&
 
  140            (hasChineseMonth || hasKoreanMonth) &&
 
  141            (hasChineseDay || hasKoreanDay) )
 
  146            size_t yearPos, monthPos, dayPos;
 
  150                yearPos = aDateStr.find( 
"年" );
 
  152                yearPos = aDateStr.find( 
"년" );
 
  155            if( hasChineseMonth )
 
  156                monthPos = aDateStr.find( 
"月" );
 
  158                monthPos = aDateStr.find( 
"월" );
 
  162                dayPos = aDateStr.find( 
"日" );
 
  164                dayPos = aDateStr.find( 
"일" );
 
  168                int year = std::stoi( aDateStr.substr( 0, yearPos ) );
 
  169                int month = std::stoi( aDateStr.substr( yearPos + 3, monthPos - yearPos - 3 ) ); 
 
  170                int day = std::stoi( aDateStr.substr( monthPos + 3, dayPos - monthPos - 3 ) ); 
 
  172                parts = { year, month, day };
 
  179        else if( aDateStr.find( 
'-' ) != std::string::npos )
 
  181        else if( aDateStr.find( 
'/' ) != std::string::npos )
 
  183        else if( aDateStr.find( 
'.' ) != std::string::npos )
 
  188            while( std::getline( iss, token, separator ) )
 
  192                    parts.push_back( std::stoi( token ) );
 
  200        else if( !isCjkFormat && aDateStr.length() == 8 )
 
  204                int dateNum = std::stoi( aDateStr );
 
  205                int year = dateNum / 10000;
 
  206                int month = ( dateNum / 100 ) % 100;
 
  207                int day = dateNum % 100;
 
  215        else if( !isCjkFormat )
 
  220        if( parts.empty() || parts.size() > 3 )
 
  223        int year, month, day;
 
  225        if( parts.size() == 1 )
 
  231        else if( parts.size() == 2 )
 
  246            else if( separator == 
'/' && parts[0] <= 12 && parts[1] <= 31 )
 
  252            else if( separator == 
'/' && parts[1] <= 12 )
 
  266        if( month < 1 || month > 12 )
 
 
  274    static auto FormatDate( 
int aDaysSinceEpoch, 
const std::string& aFormat ) -> std::string
 
  276        auto [year, month, day] = 
DaysToYmd( aDaysSinceEpoch );
 
  278        if( aFormat == 
"ISO" || aFormat == 
"iso" )
 
  279            return fmt::format( 
"{:04d}-{:02d}-{:02d}", year, month, day );
 
  280        else if( aFormat == 
"US" || aFormat == 
"us" )
 
  281            return fmt::format( 
"{:02d}/{:02d}/{:04d}", month, day, year );
 
  282        else if( aFormat == 
"EU" || aFormat == 
"european" )
 
  283            return fmt::format( 
"{:02d}/{:02d}/{:04d}", day, month, year );
 
  284        else if( aFormat == 
"long" )
 
  285            return fmt::format( 
"{} {}, {}", 
monthNames[month-1], day, year );
 
  286        else if( aFormat == 
"short" )
 
  287            return fmt::format( 
"{} {}, {}", 
monthAbbrev[month-1], day, year );
 
  288        else if( aFormat == 
"Chinese" || aFormat == 
"chinese" || aFormat == 
"CN" || aFormat == 
"cn" || aFormat == 
"中文" )
 
  289            return fmt::format( 
"{}年{:02d}月{:02d}日", year, month, day );
 
  290        else if( aFormat == 
"Japanese" || aFormat == 
"japanese" || aFormat == 
"JP" || aFormat == 
"jp" || aFormat == 
"日本語" )
 
  291            return fmt::format( 
"{}年{:02d}月{:02d}日", year, month, day );
 
  292        else if( aFormat == 
"Korean" || aFormat == 
"korean" || aFormat == 
"KR" || aFormat == 
"kr" || aFormat == 
"한국어" )
 
  293            return fmt::format( 
"{}년 {:02d}월 {:02d}일", year, month, day );
 
  295            return fmt::format( 
"{:04d}-{:02d}-{:02d}", year, month, day );
 
 
  300        int weekday = ( ( aDaysSinceEpoch + 3 ) % 7 ); 
 
 
  310        auto now = std::chrono::system_clock::now();
 
  311        auto timeT = std::chrono::system_clock::to_time_t( now );
 
  312        return static_cast<int>( timeT / ( 24 * 3600 ) );
 
 
  317        auto now = std::chrono::system_clock::now();
 
  318        auto timeT = std::chrono::system_clock::to_time_t( now );
 
  319        return static_cast<double>( timeT );
 
 
 
  341            const auto& varName = std::get<std::string>( aNode.data );
 
  347            return MakeError<Value>( fmt::format( 
"No variable resolver configured for: {}", varName ) );
 
  352            const auto& binop = std::get<BIN_OP_DATA>( aNode.data );
 
  353            auto leftResult = binop.left->Accept( *
this );
 
  357            auto rightResult = binop.right ?
 
  363            if( binop.op == 
'+' )
 
  365                const auto& leftVal = leftResult.GetValue();
 
  366                const auto& rightVal = rightResult.GetValue();
 
  369                if( std::holds_alternative<std::string>( leftVal ) ||
 
  370                    std::holds_alternative<std::string>( rightVal ) )
 
  382            const auto& func = std::get<FUNC_DATA>( aNode.data );
 
 
  393        const auto& 
name = aFunc.name;
 
  394        const auto& args = aFunc.args;
 
  399            if( 
name == 
"today" )
 
  401            else if( 
name == 
"now" )
 
  403            else if( 
name == 
"random" )
 
  405                std::uniform_real_distribution<double> dis( 0.0, 1.0 );
 
  411        std::vector<Value> argValues;
 
  412        argValues.reserve( args.size() );
 
  414        for( 
const auto& arg : args )
 
  416            auto result = arg->Accept( *
this );
 
  420            argValues.push_back( 
result.GetValue() );
 
  423        const auto argc = argValues.size();
 
  426        if( 
name == 
"format" && argc >= 1 )
 
  432            const auto value = numResult.GetValue();
 
  438                    decimals = 
static_cast<int>( decResult.GetValue() );
 
  443        else if( 
name == 
"currency" && argc >= 1 )
 
  449            const auto amount = numResult.GetValue();
 
  454        else if( 
name == 
"fixed" && argc >= 1 )
 
  460            const auto value = numResult.GetValue();
 
  466                    decimals = 
static_cast<int>( decResult.GetValue() );
 
  473        else if( 
name == 
"dateformat" && argc >= 1 )
 
  479            const auto days = 
static_cast<int>( dateResult.GetValue() );
 
  484        else if( 
name == 
"datestring" && argc == 1 )
 
  494        else if( 
name == 
"weekdayname" && argc == 1 )
 
  500            const auto days = 
static_cast<int>( dateResult.GetValue() );
 
  505        else if( 
name == 
"upper" && argc == 1 )
 
  508            std::transform( str.begin(), str.end(), str.begin(), ::toupper );
 
  511        else if( 
name == 
"lower" && argc == 1 )
 
  514            std::transform( str.begin(), str.end(), str.begin(), ::tolower );
 
  517        else if( 
name == 
"concat" && argc >= 2 )
 
  520            for( 
const auto& val : argValues )
 
  525        else if( 
name == 
"beforefirst" && argc == 2 )
 
  532        else if( 
name == 
"beforelast" && argc == 2 )
 
  539        else if( 
name == 
"afterfirst" && argc == 2 )
 
  546        else if( 
name == 
"afterlast" && argc == 2 )
 
  555        if( 
name == 
"if" && argc == 3 )
 
  559            if( !conditionResult )
 
  562            const auto condition = conditionResult.GetValue() != 0.0;
 
  567        std::vector<double> numArgs;
 
  568        for( 
const auto& val : argValues )
 
  574            numArgs.push_back( numResult.GetValue() );
 
  578        if( 
name == 
"abs" && argc == 1 )
 
  580        else if( 
name == 
"sum" && argc >= 1 )
 
  581            return MakeValue<Value>( std::accumulate( numArgs.begin(), numArgs.end(), 0.0 ) );
 
  582        else if( 
name == 
"round" && argc >= 1 )
 
  584            const auto value = numArgs[0];
 
  585            const auto precision = argc > 1 ? 
static_cast<int>( numArgs[1] ) : 0;
 
  586            const auto multiplier = std::pow( 10.0, precision );
 
  589        else if( 
name == 
"sqrt" && argc == 1 )
 
  596        else if( 
name == 
"pow" && argc == 2 )
 
  598        else if( 
name == 
"floor" && argc == 1 )
 
  600        else if( 
name == 
"ceil" && argc == 1 )
 
  602        else if( 
name == 
"min" && argc >= 1 )
 
  603            return MakeValue<Value>( *std::min_element( numArgs.begin(), numArgs.end() ) );
 
  604        else if( 
name == 
"max" && argc >= 1 )
 
  605            return MakeValue<Value>( *std::max_element( numArgs.begin(), numArgs.end() ) );
 
  606        else if( 
name == 
"avg" && argc >= 1 )
 
  608            const auto sum = std::accumulate( numArgs.begin(), numArgs.end(), 0.0 );
 
 
  616                        -> std::pair<std::string, bool>
 
  620        EVAL_VISITOR evaluator{ std::move( aVariableCallback ), localErrors };
 
  621        bool hadErrors = aDoc.HasErrors();
 
  623        for( 
const auto& node : aDoc.GetNodes() )
 
  628                result += std::get<std::string>( node->data );
 
  633                const auto& calcData = std::get<BIN_OP_DATA>( node->data );
 
  634                auto evalResult = calcData.left->Accept( evaluator );
 
  648                result += 
"[Unknown node type]";
 
  654        return { std::move( 
result ), hadErrors || localErrors.HasErrors() };
 
 
  658                                   -> std::tuple<std::string, std::vector<std::string>, 
bool>
 
  660    auto [
result, hadErrors] = 
Process( aDoc, std::move( aVariableCallback ) );
 
  661    auto allErrors = aDoc.GetErrors();
 
  663    return { std::move( 
result ), std::move( allErrors ), hadErrors };
 
 
 
static auto GetCurrentDays() -> int
 
static constexpr std::array< const char *, 12 > monthNames
 
static auto GetWeekdayName(int aDaysSinceEpoch) -> std::string
 
static auto FormatDate(int aDaysSinceEpoch, const std::string &aFormat) -> std::string
 
static constexpr std::array< const char *, 12 > monthAbbrev
 
static auto YmdToDays(int aYear, int aMonth, int aDay) -> int
 
static auto DaysToYmd(int aDaysSinceEpoch) -> std::tuple< int, int, int >
 
static auto ParseDate(const std::string &aDateStr) -> std::optional< int >
 
static auto daysInYear(int aYear) -> int
 
static constexpr int epochYear
 
static constexpr std::array< int, 12 > daysInMonth
 
static auto isLeapYear(int aYear) -> bool
 
static constexpr std::array< const char *, 7 > weekdayNames
 
static auto daysInMonthForYear(int aMonth, int aYear) -> int
 
static auto GetCurrentTimestamp() -> double
 
static auto ProcessWithDetails(const DOC &aDoc, VariableCallback aVariableCallback) -> std::tuple< std::string, std::vector< std::string >, bool >
Process document with detailed error reporting.
 
EVAL_VISITOR::VariableCallback VariableCallback
 
static auto Process(const DOC &aDoc, VariableCallback aVariableCallback) -> std::pair< std::string, bool >
Process document using callback for variable resolution.
 
auto operator()(const NODE &aNode) const -> Result< Value >
 
VariableCallback m_variableCallback
 
std::function< Result< Value >(const std::string &aVariableName)> VariableCallback
 
EVAL_VISITOR(VariableCallback aVariableCallback, ERROR_COLLECTOR &aErrorCollector)
Construct evaluator with variable callback function.
 
auto evaluateFunction(const FUNC_DATA &aFunc) const -> Result< Value >
 
ERROR_COLLECTOR & m_errors
 
static auto ArithmeticOp(const Value &aLeft, const Value &aRight, char aOp) -> Result< Value >
 
static auto ToString(const Value &aVal) -> std::string
 
static auto ToDouble(const Value &aVal) -> Result< double >
 
static auto ConcatStrings(const Value &aLeft, const Value &aRight) -> Value
 
static auto ToChar(const Value &aVal) -> char
 
thread_local ERROR_COLLECTOR * g_errorCollector
 
auto MakeValue(T aVal) -> Result< T >
 
auto MakeError(std::string aMsg) -> Result< T >
 
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
 
wxString result
Test unit parsing edge cases and error handling.