Files
JetCore/Operand.cpp
T
Brad Arant 5c703f5620 Implement reverse(str) and concat(a, b, ...) in the expr engine
Both matched their function name but had empty branch bodies, silently
producing nothing regardless of arguments. reverse(str) follows the same
single-argument shape as trim/toupper/tolower; concat(a, b, ...) follows
the same variadic comma-separated shape as max/min.

reverse() converts to std::string and reverses in place via the already-
imported <algorithm> (std::reverse), then assigns back through MString's
std::string constructor. concat() starts from the first argument and
appends each subsequent one via MString's own operator<<(ZString).

Verified via real jet-2.0 runs: reverse('hello') -> "olleh",
concat('a','b','c') -> "abc", concat('x','y') -> "xy". Full 9-script
regression suite (custom tag scoping, dump tag tree, try/catch, if/else,
keyword sigil) still passes unchanged.
2026-08-26 10:45:43 -07:00

636 lines
21 KiB
C++

#include "Operand.h"
#include "Exception.h"
#include <format>
#include <iostream>
#include <algorithm>
#include <time.h>
#include <chrono>
#include <cmath>
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;
if(!in.ifNext(")"))
throw coreutils::Exception("expected ) in 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("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")) {
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<std::chrono::seconds>(duration_since_epoch);
int unixtime = seconds_since_epoch.count();
string << unixtime;
} else
throw coreutils::Exception("Expecting ) at end of UNIXTIME.");
}
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(")")) {
// TODO: Implement DATEDIFF.
} 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("'")) {
string = in.getTokenExclude("'");
in.ifNext("'");
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.");
}
return true;
}
}