21#include <fmt/format.h>
33 static constexpr std::array<int, 12>
daysInMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
34 static constexpr std::array<const char*, 12>
monthNames = {
35 "January",
"February",
"March",
"April",
"May",
"June",
36 "July",
"August",
"September",
"October",
"November",
"December"
39 "Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
40 "Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
43 "Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
48 return ( aYear % 4 == 0 && aYear % 100 != 0 ) || ( aYear % 400 == 0 );
65 static auto DaysToYmd(
int aDaysSinceEpoch ) -> std::tuple<int, int, int>
68 int remainingDays = aDaysSinceEpoch;
70 if( remainingDays >= 0 )
80 while( remainingDays < 0 )
94 int day = remainingDays + 1;
95 return {year, month, day};
98 static auto YmdToDays(
int aYear,
int aMonth,
int aDay ) ->
int
113 for(
int m = 1; m < aMonth; ++m )
116 totalDays += aDay - 1;
120 static auto ParseDate(
const std::string& aDateStr ) -> std::optional<int>
122 std::istringstream iss( aDateStr );
124 std::vector<int> parts;
127 bool isCjkFormat =
false;
130 bool hasChineseYear = aDateStr.find(
"年" ) != std::string::npos;
131 bool hasChineseMonth = aDateStr.find(
"月" ) != std::string::npos;
132 bool hasChineseDay = aDateStr.find(
"日" ) != std::string::npos;
133 bool hasKoreanYear = aDateStr.find(
"년" ) != std::string::npos;
134 bool hasKoreanMonth = aDateStr.find(
"월" ) != std::string::npos;
135 bool hasKoreanDay = aDateStr.find(
"일" ) != std::string::npos;
138 if( (hasChineseYear || hasKoreanYear) &&
139 (hasChineseMonth || hasKoreanMonth) &&
140 (hasChineseDay || hasKoreanDay) )
145 size_t yearPos, monthPos, dayPos;
149 yearPos = aDateStr.find(
"年" );
151 yearPos = aDateStr.find(
"년" );
154 if( hasChineseMonth )
155 monthPos = aDateStr.find(
"月" );
157 monthPos = aDateStr.find(
"월" );
161 dayPos = aDateStr.find(
"日" );
163 dayPos = aDateStr.find(
"일" );
167 int year = std::stoi( aDateStr.substr( 0, yearPos ) );
168 int month = std::stoi( aDateStr.substr( yearPos + 3, monthPos - yearPos - 3 ) );
169 int day = std::stoi( aDateStr.substr( monthPos + 3, dayPos - monthPos - 3 ) );
171 parts = { year, month, day };
178 else if( aDateStr.find(
'-' ) != std::string::npos )
180 else if( aDateStr.find(
'/' ) != std::string::npos )
182 else if( aDateStr.find(
'.' ) != std::string::npos )
187 while( std::getline( iss, token, separator ) )
191 parts.push_back( std::stoi( token ) );
199 else if( !isCjkFormat && aDateStr.length() == 8 )
203 int dateNum = std::stoi( aDateStr );
204 int year = dateNum / 10000;
205 int month = ( dateNum / 100 ) % 100;
206 int day = dateNum % 100;
214 else if( !isCjkFormat )
219 if( parts.empty() || parts.size() > 3 )
222 int year, month, day;
224 if( parts.size() == 1 )
230 else if( parts.size() == 2 )
245 else if( separator ==
'/' && parts[0] <= 12 && parts[1] <= 31 )
251 else if( separator ==
'/' && parts[1] <= 12 )
265 if( month < 1 || month > 12 )
273 static auto FormatDate(
int aDaysSinceEpoch,
const std::string& aFormat ) -> std::string
275 auto [year, month, day] =
DaysToYmd( aDaysSinceEpoch );
277 if( aFormat ==
"ISO" || aFormat ==
"iso" )
278 return fmt::format(
"{:04d}-{:02d}-{:02d}", year, month, day );
279 else if( aFormat ==
"US" || aFormat ==
"us" )
280 return fmt::format(
"{:02d}/{:02d}/{:04d}", month, day, year );
281 else if( aFormat ==
"EU" || aFormat ==
"european" )
282 return fmt::format(
"{:02d}/{:02d}/{:04d}", day, month, year );
283 else if( aFormat ==
"long" )
284 return fmt::format(
"{} {}, {}",
monthNames[month-1], day, year );
285 else if( aFormat ==
"short" )
286 return fmt::format(
"{} {}, {}",
monthAbbrev[month-1], day, year );
287 else if( aFormat ==
"Chinese" || aFormat ==
"chinese" || aFormat ==
"CN" || aFormat ==
"cn" || aFormat ==
"中文" )
288 return fmt::format(
"{}年{:02d}月{:02d}日", year, month, day );
289 else if( aFormat ==
"Japanese" || aFormat ==
"japanese" || aFormat ==
"JP" || aFormat ==
"jp" || aFormat ==
"日本語" )
290 return fmt::format(
"{}年{:02d}月{:02d}日", year, month, day );
291 else if( aFormat ==
"Korean" || aFormat ==
"korean" || aFormat ==
"KR" || aFormat ==
"kr" || aFormat ==
"한국어" )
292 return fmt::format(
"{}년 {:02d}월 {:02d}일", year, month, day );
294 return fmt::format(
"{:04d}-{:02d}-{:02d}", year, month, day );
299 int weekday = ( ( aDaysSinceEpoch + 3 ) % 7 );
309 auto now = std::chrono::system_clock::now();
310 auto timeT = std::chrono::system_clock::to_time_t( now );
311 return static_cast<int>( timeT / ( 24 * 3600 ) );
316 auto now = std::chrono::system_clock::now();
317 auto timeT = std::chrono::system_clock::to_time_t( now );
318 return static_cast<double>( timeT );
340 const auto& varName = std::get<std::string>( aNode.data );
346 return MakeError<Value>( fmt::format(
"No variable resolver configured for: {}", varName ) );
351 const auto& binop = std::get<BIN_OP_DATA>( aNode.data );
352 auto leftResult = binop.left->Accept( *
this );
356 auto rightResult = binop.right ?
362 if( binop.op ==
'+' )
364 const auto& leftVal = leftResult.GetValue();
365 const auto& rightVal = rightResult.GetValue();
368 if( std::holds_alternative<std::string>( leftVal ) ||
369 std::holds_alternative<std::string>( rightVal ) )
381 const auto& func = std::get<FUNC_DATA>( aNode.data );
392 const auto&
name = aFunc.name;
393 const auto& args = aFunc.args;
398 if(
name ==
"today" )
400 else if(
name ==
"now" )
402 else if(
name ==
"random" )
404 std::uniform_real_distribution<double> dis( 0.0, 1.0 );
410 std::vector<Value> argValues;
411 argValues.reserve( args.size() );
413 for(
const auto& arg : args )
415 auto result = arg->Accept( *
this );
419 argValues.push_back(
result.GetValue() );
422 const auto argc = argValues.size();
425 if(
name ==
"format" && argc >= 1 )
431 const auto value = numResult.GetValue();
437 decimals =
static_cast<int>( decResult.GetValue() );
442 else if(
name ==
"currency" && argc >= 1 )
448 const auto amount = numResult.GetValue();
453 else if(
name ==
"fixed" && argc >= 1 )
459 const auto value = numResult.GetValue();
465 decimals =
static_cast<int>( decResult.GetValue() );
472 else if(
name ==
"dateformat" && argc >= 1 )
478 const auto days =
static_cast<int>( dateResult.GetValue() );
483 else if(
name ==
"datestring" && argc == 1 )
493 else if(
name ==
"weekdayname" && argc == 1 )
499 const auto days =
static_cast<int>( dateResult.GetValue() );
504 else if(
name ==
"upper" && argc == 1 )
507 std::transform( str.begin(), str.end(), str.begin(), ::toupper );
510 else if(
name ==
"lower" && argc == 1 )
513 std::transform( str.begin(), str.end(), str.begin(), ::tolower );
516 else if(
name ==
"concat" && argc >= 2 )
519 for(
const auto& val : argValues )
526 if(
name ==
"if" && argc == 3 )
530 if( !conditionResult )
533 const auto condition = conditionResult.GetValue() != 0.0;
538 std::vector<double> numArgs;
539 for(
const auto& val : argValues )
545 numArgs.push_back( numResult.GetValue() );
549 if(
name ==
"abs" && argc == 1 )
551 else if(
name ==
"sum" && argc >= 1 )
552 return MakeValue<Value>( std::accumulate( numArgs.begin(), numArgs.end(), 0.0 ) );
553 else if(
name ==
"round" && argc >= 1 )
555 const auto value = numArgs[0];
556 const auto precision = argc > 1 ?
static_cast<int>( numArgs[1] ) : 0;
557 const auto multiplier = std::pow( 10.0, precision );
560 else if(
name ==
"sqrt" && argc == 1 )
567 else if(
name ==
"pow" && argc == 2 )
569 else if(
name ==
"floor" && argc == 1 )
571 else if(
name ==
"ceil" && argc == 1 )
573 else if(
name ==
"min" && argc >= 1 )
574 return MakeValue<Value>( *std::min_element( numArgs.begin(), numArgs.end() ) );
575 else if(
name ==
"max" && argc >= 1 )
576 return MakeValue<Value>( *std::max_element( numArgs.begin(), numArgs.end() ) );
577 else if(
name ==
"avg" && argc >= 1 )
579 const auto sum = std::accumulate( numArgs.begin(), numArgs.end(), 0.0 );
587 -> std::pair<std::string, bool>
591 EVAL_VISITOR evaluator{ std::move( aVariableCallback ), localErrors };
592 bool hadErrors = aDoc.HasErrors();
594 for(
const auto& node : aDoc.GetNodes() )
599 result += std::get<std::string>( node->data );
604 const auto& calcData = std::get<BIN_OP_DATA>( node->data );
605 auto evalResult = calcData.left->Accept( evaluator );
619 result +=
"[Unknown node type]";
625 return { std::move(
result ), hadErrors || localErrors.HasErrors() };
629 -> std::tuple<std::string, std::vector<std::string>,
bool>
631 auto [
result, hadErrors] =
Process( aDoc, std::move( aVariableCallback ) );
632 auto allErrors = aDoc.GetErrors();
634 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
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.