#include "Operand.h" #include "Exception.h" #include #include #include #include #include #include #include #include namespace jet { Operand::Operand(coreutils::ZString &in, Tag &tag, bool stop) : in(in), tag(tag) { doubleValue = 0; in.skipWhitespace(); if(in.startsWith("$[") || in.startsWith("#[")) { string = tag.getVariable(in); doubleValue = string.asDouble(); isNumber = string.eod(); string.reset(); if((string == "false") || (string == "true")) boolean = (string == "true"); } else if(in.ifNext("(")) { Operand op(in, tag); string = op.string; doubleValue = op.doubleValue; isNumber = op.isNumber; // A parenthesized sub-expression's own comparison result was never // propagated to the wrapping Operand - only string/doubleValue/ // isNumber were copied, leaving this Operand's own `boolean` // genuinely uninitialized (Operand.h declares it with no default // value). Confirmed via debug tracing: printed as garbage stack // bytes, which "and"/"or" then combined instead of the real // true/false result. boolean = op.boolean; if(!in.ifNext(")")) throw coreutils::Exception("expected ) in expression."); } else if(in.ifNextIgnoreCase("SUBSTRING_INDEX")) { // MySQL SUBSTRING_INDEX(str, delim, count): with a positive count, // returns everything before the count-th occurrence of delim // (counting from the left); with a negative count, everything // after the |count|-th occurrence (counting from the right). If // delim doesn't occur that many times, the whole string is // returned unchanged. Genuinely missing (like LOCATE was) - real // content (index2.cgi's own URI-parsing logic, ported as part of // the /var/www/barant jet-2.0 conversion) relies on it to peel a // path apart one segment at a time: // substring_index(substring_index('$[REQUEST_URI]','?',1),'/',2). if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for SUBSTRING_INDEX parameters."); Operand parm1(in, tag); if(!in.ifNext(",")) throw coreutils::Exception("Expecting , in SUBSTRING_INDEX expression."); Operand parm2(in, tag); if(!in.ifNext(",")) throw coreutils::Exception("Expecting , in SUBSTRING_INDEX expression."); Operand parm3(in, tag); if(in.ifNext(")")) { coreutils::MString haystack = parm1.string; haystack.reset(); coreutils::MString delim = parm2.string; delim.reset(); int count = parm3.string.asInteger(); int delimLen = (int)delim.getLength(); std::vector positions; if(delimLen > 0) { int base = 0; while(true) { coreutils::MString scan = haystack.substring(base); scan.reset(); int pos = scan.find(delim); if(pos == -1) break; positions.push_back(base + pos); base += pos + delimLen; } } if(count >= 0) { size_t n = (size_t)count; if(n == 0 || positions.size() < n) { string = haystack.substring(0); } else { string = haystack.substring(0, positions[n - 1]); } } else { size_t n = (size_t)(-count); if(positions.size() < n) { string = haystack.substring(0); } else { int idx = positions[positions.size() - n]; string = haystack.substring(idx + delimLen); } } } else throw coreutils::Exception("Expecting ) at end of SUBSTRING_INDEX expression."); } else if(in.ifNextIgnoreCase("SUBSTRING")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for SUBSTRING parameters."); Operand parm1(in, tag); if(!in.ifNext(",")) throw coreutils::Exception("Expecting , in SUBSTRING expression."); Operand parm2(in, tag); if(in.ifNext(")")) { string = parm1.string.substring(parm2.string.asInteger()); } else { // Real, pre-existing bug fixed here: the 3-arg-only branch below // used to run unconditionally (missing braces around this // else-if), so even after the 2-arg form above already // succeeded, it fell through into trying to parse a third // argument from whatever was left (nothing, since the closing // ")" was already consumed) - always throwing "operand is not // valid." right after computing the correct 2-arg result. // Confirmed via real debug tracing showing an extra Operand // constructed on empty input immediately after a successful // 2-arg substring(str, start) call - the 2-arg form was // completely unusable before this fix. if(!in.ifNext(",")) throw coreutils::Exception("Expecting , in SUBSTRING expression."); Operand parm3(in, tag); if(in.ifNext(")")) { string = parm1.string.substring(parm2.string.asInteger(), parm3.string.asInteger()); } else throw coreutils::Exception("Expecting ) at end of substring expression."); } } else if(in.ifNextIgnoreCase("LOCATE")) { // MySQL-style LOCATE(needle, haystack): 1-based position of the // first occurrence of needle within haystack, or 0 if not found - // a genuinely missing function (like was a genuinely // missing tag). Real 1.4 content depends on it for exactly this // "is #[name] present in the space-padded $[errfield] list" // pattern (barant.tag's own definition: // locate(' #[name] ','$[errfield]') != 0), used throughout for // marking a field's input as erroring after a failed validation. if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for LOCATE parameters."); Operand parm1(in, tag); if(!in.ifNext(",")) throw coreutils::Exception("Expecting , in LOCATE expression."); Operand parm2(in, tag); if(in.ifNext(")")) { coreutils::MString haystack = parm2.string; haystack.reset(); int pos = haystack.find(parm1.string); doubleValue = (pos == -1) ? 0 : (pos + 1); isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else { // Optional 3rd start_pos argument (MySQL LOCATE(substr, str, // pos)): search begins at the given 1-based position instead of // the start of the string. Needed by index2.cgi's own real // URI-parsing logic (walking a path one '/' at a time), ported // as part of the /var/www/barant jet-2.0 conversion. if(!in.ifNext(",")) throw coreutils::Exception("Expecting , in LOCATE expression."); Operand parm3(in, tag); if(in.ifNext(")")) { coreutils::MString haystack = parm2.string; haystack.reset(); int totalLen = (int)haystack.getLength(); int startPos = parm3.string.asInteger() - 1; if(startPos < 0) startPos = 0; if(startPos >= totalLen) { doubleValue = 0; } else { coreutils::MString sub = haystack.substring(startPos); sub.reset(); int pos = sub.find(parm1.string); doubleValue = (pos == -1) ? 0 : (startPos + pos + 1); } isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("Expecting ) at end of LOCATE expression."); } } else if(in.ifNextIgnoreCase("LEFT")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for LEFT parameters."); Operand parm1(in, tag); if(!in.ifNext(",")) throw coreutils::Exception("Expecting , in LEFT expression."); Operand parm2(in, tag); if(in.ifNext(")")) { string = parm1.string.substring(0, parm2.string.asInteger()); } else throw coreutils::Exception("Expecting ) at end of LEFT expression."); } else if(in.ifNextIgnoreCase("EXPR")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for EXPR parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { Operand op(parm1.string, tag); string = op.string; isNumber = op.isNumber; if(isNumber) { doubleValue = op.doubleValue; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } boolean = op.boolean; } else throw coreutils::Exception("Expecting ) at end of EXPR expression."); } else if(in.ifNextIgnoreCase("RIGHT")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for RIGHT parameters."); Operand parm1(in, tag); if(!in.ifNext(",")) throw coreutils::Exception("Expecting , in RIGHT expression."); Operand parm2(in, tag); if(in.ifNext(")")) { int len = parm1.string.getLength(); int size = parm2.string.asInteger(); int start = len - size; string = parm1.string.substring(start, size); } else throw coreutils::Exception("Expecting ) at end of RIGHT expression."); } else if(in.ifNextIgnoreCase("TRIM")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for TRIM parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { string = parm1.string.trim(); } else throw coreutils::Exception("Expecting ) at end of TRIM expression."); } else if(in.ifNextIgnoreCase("TOUPPER")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for TOUPPER parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { string = parm1.string.toUpper(); } else throw coreutils::Exception("Expecting ) at end of TOUPPER expression."); } else if(in.ifNextIgnoreCase("TOLOWER")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for TOLOWER parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { string = parm1.string.toLower(); } else throw coreutils::Exception("Expecting ) at end of TOLOWER expression."); } else if(in.ifNextIgnoreCase("REVERSE")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for REVERSE parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { std::string reversed = parm1.string.str(); std::reverse(reversed.begin(), reversed.end()); string = reversed; } else throw coreutils::Exception("Expecting ) at end of REVERSE expression."); } else if(in.ifNextIgnoreCase("CONCAT")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for CONCAT parameters."); Operand parm1(in, tag); string = parm1.string; while(in.ifNext(",")) { Operand parm(in, tag); string << parm.string; } if(!in.ifNext(")")) throw coreutils::Exception("Expecting ) at end of CONCAT expression."); } else if(in.ifNextIgnoreCase("INTEGER")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for INTEGER parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { string = parm1.string.asInteger(); } else throw coreutils::Exception("Expecting ) at end of INTEGER expression."); } else if(in.ifNextIgnoreCase("ROUND")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for ROUND parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { doubleValue = round(parm1.doubleValue); isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("Expecting ) at end of ROUND expression."); } else if(in.ifNextIgnoreCase("RANDOM")) { unsigned int seed = (unsigned int)clock(); doubleValue = (double) rand_r(&seed) / (RAND_MAX + 1.0); isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else if(in.ifNextIgnoreCase("ABS")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for ABS parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { doubleValue = abs(parm1.doubleValue); isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("Expecting ) at end of ABS expression."); } else if(in.ifNextIgnoreCase("MAX")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for MAX parameters."); Operand *parm = new Operand(in, tag); string = parm->string; doubleValue = parm->doubleValue; isNumber = parm->isNumber; delete parm; while(in.ifNext(",")) { parm = new Operand(in, tag); if(isNumber != parm->isNumber) throw coreutils::Exception("All parameters in MAX expression must be same type."); if(isNumber) { if(parm->doubleValue > doubleValue) { doubleValue = parm->doubleValue; string = parm->string; } } else { if(parm->string > string) { doubleValue = parm->doubleValue; string = parm->string; } } delete parm; } if(!in.ifNext(")")) { throw coreutils::Exception("Expecting ) at end of MAX expression."); } string.removeTrailingZeros(); } else if(in.ifNextIgnoreCase("MIN")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for MIN parameters."); Operand *parm = new Operand(in, tag); string = parm->string; doubleValue = parm->doubleValue; isNumber = parm->isNumber; delete parm; while(in.ifNext(",")) { parm = new Operand(in, tag); if(isNumber != parm->isNumber) throw coreutils::Exception("All parameters in MIN expression must be same type."); if(isNumber) { if(parm->doubleValue < doubleValue) { doubleValue = parm->doubleValue; string = parm->string; } } else { if(parm->string < string) { doubleValue = parm->doubleValue; string = parm->string; } } delete parm; } if(!in.ifNext(")")) { throw coreutils::Exception("Expecting ) at end of MIN expression."); } string.removeTrailingZeros(); } else if(in.ifNextIgnoreCase("POW")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for POW parameters."); Operand parm1(in, tag); if(!in.ifNext(",")) throw coreutils::Exception("Expecting , in POW expression."); Operand parm2(in, tag); if(in.ifNext(")")) { doubleValue = pow(parm1.doubleValue, parm2.doubleValue); isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("Expecting ) at end of substring expression."); } else if(in.ifNextIgnoreCase("SIN")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for SIN parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { doubleValue = sin(parm1.doubleValue); isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("Expecting ) at end of SIN expression."); } else if(in.ifNextIgnoreCase("ASIN")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for ASIN parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { doubleValue = asin(parm1.doubleValue); isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("Expecting ) at end of ASIN expression."); } else if(in.ifNextIgnoreCase("COS")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for COS parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { doubleValue = cos(parm1.doubleValue); isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("Expecting ) at end of COS expression."); } else if(in.ifNextIgnoreCase("ACOS")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for ACOS parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { doubleValue = acos(parm1.doubleValue); isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("Expecting ) at end of ACOS expression."); } else if(in.ifNextIgnoreCase("ATAN")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for ATAN parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { doubleValue = atan(parm1.doubleValue); isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("Expecting ) at end of ATAN expression."); } else if(in.ifNextIgnoreCase("SQRT")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for SQRT parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { doubleValue = sqrt(parm1.doubleValue); isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("Expecting ) at end of SQRT expression."); } else if(in.ifNextIgnoreCase("DEG")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for DEG parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { doubleValue = parm1.doubleValue * 180 / 3.14159; isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("Expecting ) at end of DEG expression."); } else if(in.ifNextIgnoreCase("RAD")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for RAD parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { doubleValue = parm1.doubleValue * 3.14159 / 180; isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("Expecting ) at end of RAD expression."); } else if(in.ifNextIgnoreCase("TAN")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for TAN parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { doubleValue = tan(parm1.doubleValue); isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("Expecting ) at end of TAN expression."); } else if(in.ifNextIgnoreCase("TRUNC")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for TRUNC parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { doubleValue = trunc(parm1.doubleValue); isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("Expecting ) at end of TRUNC expression."); } else if(in.ifNextIgnoreCase("CEIL")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for CEIL parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { doubleValue = ceil(parm1.doubleValue); isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("Expecting ) at end of CEIL expression."); } else if(in.ifNextIgnoreCase("FLOOR")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for FLOOR parameters."); Operand parm1(in, tag); if(in.ifNext(")")) { doubleValue = floor(parm1.doubleValue); isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("Expecting ) at end of FLOOR expression."); } else if(in.ifNextIgnoreCase("UNIXTIME") || in.ifNextIgnoreCase("UNIX_TIMESTAMP")) { // Real content (cart.page's own ) // calls MySQL's real function name, not this codebase's own // "UNIXTIME" - same value, just accept both spellings. if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for UNIXTIME."); if(in.ifNext(")")) { auto now = std::chrono::system_clock::now(); auto duration_since_epoch = now.time_since_epoch(); auto seconds_since_epoch = std::chrono::duration_cast(duration_since_epoch); int unixtime = seconds_since_epoch.count(); string << unixtime; } else throw coreutils::Exception("Expecting ) at end of UNIXTIME."); } else if(in.ifNextIgnoreCase("NOW")) { // MySQL's now() - real content (SESSIONCTL.tag's own // createsession definition: , // embedded directly into a DATETIME column via a raw SQL literal: // timestamp='$[_timestamp]') expects a MySQL DATETIME-formatted // string ("YYYY-MM-DD HH:MM:SS"), not a Unix timestamp (that's // UNIX_TIMESTAMP, just above). if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for NOW."); if(in.ifNext(")")) { std::time_t t = std::time(nullptr); std::tm tmVal; gmtime_r(&t, &tmVal); char buf[32]; std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tmVal); string = buf; isNumber = false; } else throw coreutils::Exception("Expecting ) at end of NOW."); } else if(in.ifNextIgnoreCase("UUID")) { // MySQL's uuid() - real content (SESSIONCTL.tag's own // createsession definition: ) // uses this to mint a new session id, stored as sessions.uuid and // handed back to the browser as the _session_id cookie. A real // random (v4) UUID, not MySQL's actual v1 (time+MAC-derived) // algorithm - the schema only ever treats this as an opaque // unique string, nothing depends on v1's specific bit layout. // Seeded from std::random_device (real entropy), not RANDOM's // existing clock()-seeded rand_r() above - this value doubles as // a session token, so it needs to be unguessable, not just unique. if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for UUID."); if(in.ifNext(")")) { static thread_local std::mt19937_64 rng(std::random_device{}()); std::uniform_int_distribution hexDigit(0, 15); const char *hex = "0123456789abcdef"; char buf[37]; int p = 0; for(int i = 0; i < 36; i++) { if(i == 8 || i == 13 || i == 18 || i == 23) { buf[p++] = '-'; continue; } int v; if(i == 14) v = 4; // version 4 else if(i == 19) v = 8 + hexDigit(rng) % 4; // variant 10xx else v = hexDigit(rng); buf[p++] = hex[v]; } buf[p] = '\0'; string = buf; isNumber = false; } else throw coreutils::Exception("Expecting ) at end of UUID."); } else if(in.ifNextIgnoreCase("DATEDIFF")) { if(!in.ifNext("(")) throw coreutils::Exception("Expecting ( for DATEDIFF."); Operand parm1(in, tag); if(!in.ifNext(",")) throw coreutils::Exception("Expecting , in DATEDIFF expression."); Operand parm2(in, tag); if(in.ifNext(")")) { // Matches MySQL's DATEDIFF(expr1, expr2): expr1 - expr2 expressed // as a whole number of days, ignoring any time-of-day component. // Dates are parsed as YYYY-MM-DD (a leading date portion of a // longer "YYYY-MM-DD HH:MM:SS" value parses fine too, since // strptime only consumes what the format string asks for) and // interpreted as UTC via timegm() (not mktime(), which is // local-timezone-dependent) so the result doesn't shift with the // server's own timezone setting. struct tm tm1 = {}; struct tm tm2 = {}; if(!strptime(parm1.string.c_str(), "%Y-%m-%d", &tm1)) throw coreutils::Exception("DATEDIFF: first argument is not a valid date (expected YYYY-MM-DD)."); if(!strptime(parm2.string.c_str(), "%Y-%m-%d", &tm2)) throw coreutils::Exception("DATEDIFF: second argument is not a valid date (expected YYYY-MM-DD)."); time_t t1 = timegm(&tm1); time_t t2 = timegm(&tm2); doubleValue = difftime(t1, t2) / 86400.0; isNumber = true; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("Expecting ) at end of DATEDIFF expression."); } else if(in.ifNextIgnoreCase("true")) { boolean = true; string = "true"; } else if(in.ifNextIgnoreCase("false")) { boolean = false; string = "false"; } else if(in.startsWithNumber()) { doubleValue = in.asDouble(); string = std::format("{:.12f}", doubleValue); isNumber = true; } else if(in.ifNext("'")) { coreutils::MString raw = in.getTokenExclude("'"); in.ifNext("'"); coreutils::MString resolved; raw.reset(); while(!raw.eod()) { if(raw.startsWith("$[") || raw.startsWith("#[")) resolved.write(tag.getVariable(raw)); else { resolved.write(raw.charAt(0)); raw.nextChar(); } } string = resolved; isNumber = false; } else throw coreutils::Exception("operand is not valid."); in.skipWhitespace(); if(!stop) parseOperator(); return; } int Operand::parseOperator() { if(in.ifNext("!=") || in.ifNext("<>")) { Operand op(in, tag); if(isNumber && op.isNumber) { if(doubleValue != op.doubleValue) { boolean = true; isNumber = false; string = "true"; } else { boolean = false; isNumber = false; string = "false"; } } else if(!isNumber && !op.isNumber) { if(string != op.string) { boolean = true; isNumber = false; string = "true"; } else { boolean = false; isNumber = false; string = "false"; } } } if(in.ifNext("<=")) { Operand op(in, tag); if(isNumber && op.isNumber) { if(doubleValue <= op.doubleValue) { boolean = true; isNumber = false; string = "true"; } else { boolean = false; isNumber = false; string = "false"; } } else if(!isNumber && !op.isNumber) { if(string <= op.string) { boolean = true; isNumber = false; string = "true"; } else { boolean = false; isNumber = false; string = "false"; } } } if(in.ifNext(">=")) { Operand op(in, tag); if(isNumber && op.isNumber) { if(doubleValue >= op.doubleValue) { boolean = true; isNumber = false; string = "true"; } else { boolean = false; isNumber = false; string = "false"; } } else if(!isNumber && !op.isNumber) { if(string >= op.string) { boolean = true; isNumber = false; string = "true"; } else { boolean = false; isNumber = false; string = "false"; } } } if(in.ifNext("=")) { Operand op(in, tag); if(isNumber && op.isNumber) { if(doubleValue == op.doubleValue) { boolean = true; isNumber = false; string = "true"; } else { boolean = false; isNumber = false; string = "false"; } } else if(!isNumber && !op.isNumber) { if(string == op.string) { boolean = true; isNumber = false; string = "true"; } else { boolean = false; isNumber = false; string = "false"; } } } if(in.ifNext("<")) { Operand op(in, tag); if(isNumber && op.isNumber) { if(doubleValue < op.doubleValue) { boolean = true; isNumber = false; string = "true"; } else { boolean = false; isNumber = false; string = "false"; } } else if(!isNumber && !op.isNumber) { if(string < op.string) { boolean = true; isNumber = false; string = "true"; } else { boolean = false; isNumber = false; string = "false"; } } } if(in.ifNext(">")) { Operand op(in, tag); if(isNumber && op.isNumber) { if(doubleValue > op.doubleValue) { boolean = true; isNumber = false; string = "true"; } else { boolean = false; isNumber = false; string = "false"; } } else if(!isNumber && !op.isNumber) { if(string > op.string) { boolean = true; isNumber = false; string = "true"; } else { boolean = false; isNumber = false; string = "false"; } } } if(in.ifNext("+")) { if(isNumber) { Operand op(in, tag); if(op.isNumber) { doubleValue += op.doubleValue; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("operand is not a number."); } else throw coreutils::Exception("operand is not a number."); } else if(in.ifNext("-")) { if(isNumber) { Operand op(in, tag); if(op.isNumber) { doubleValue -= op.doubleValue; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); } else throw coreutils::Exception("operand is not a number."); } else throw coreutils::Exception("operand is not a number."); } else if(in.ifNext("*")) { if(isNumber) { Operand op(in, tag, true); if(op.isNumber) { doubleValue *= op.doubleValue; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); parseOperator(); } else throw coreutils::Exception("operand is not a number."); } else throw coreutils::Exception("operand is not a number."); } else if(in.ifNext("/")) { if(isNumber) { Operand op(in, tag, true); if(op.isNumber) { doubleValue /= op.doubleValue; string = std::format("{:.12f}", doubleValue); string.removeTrailingZeros(); parseOperator(); } else throw coreutils::Exception("operand is not a number."); } else throw coreutils::Exception("operand is not a number."); } in.skipWhitespace(); if(in.ifNextIgnoreCase("and")) { Operand op(in, tag); boolean = boolean && op.boolean; isNumber = false; string = boolean ? "true" : "false"; } else if(in.ifNextIgnoreCase("or")) { Operand op(in, tag); boolean = boolean || op.boolean; isNumber = false; string = boolean ? "true" : "false"; } return true; } }