The complete source code of IdealIRC http://www.idealirc.org/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
idealirc/Script/ValueHolder.cpp

835 lines
24 KiB

/*
* IdealIRC Script Engine - Scripting tailored for IRC clients.
* Copyright (C) 2021 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "ValueHolder.h"
#include "ScriptException.h"
#include "ParserOperator.h"
#include <sstream>
#include <functional>
#include <locale>
#include <iostream>
namespace {
ValueHolder VoidItem;
inline char decimalSeparator()
{
return std::use_facet<std::numpunct<char>>(std::cout.getloc()).decimal_point();
}
}
ValueHolder::ValueHolder(const ValueHolder& other)
{
if (other.isReference())
m_val = std::get<ValueRef>(*other.m_val);
else
*this = other;
}
ValueHolder::ValueHolder(ValueHolder&& other)
: m_val(std::move(other.m_val))
, m_const(other.m_const)
{}
ValueHolder::~ValueHolder() = default;
ValueHolder::ValueHolder(int val)
: m_val(val)
{}
ValueHolder::ValueHolder(double val)
: m_val(val)
{}
ValueHolder::ValueHolder(bool val)
: m_val(val)
{}
ValueHolder::ValueHolder(const std::string& val)
: m_val(val)
{}
ValueHolder::ValueHolder(const ValueArray& array)
: m_val(ValueArray())
{
auto& valref = std::get<ValueArray>(*m_val);
for (const auto& [key,val] : array)
valref.insert_or_assign(key, std::make_unique<ValueHolder>(*val));
*this = array;
}
ValueHolder::ValueHolder(ValueArray&& array)
: m_val(std::move(array))
{
}
ValueHolder::ValueHolder(const ValueRef& ref)
: m_val(ref)
{}
ValueHolder& ValueHolder::operator=(int val)
{
mval() = val;
return *this;
}
ValueHolder& ValueHolder::operator=(double val)
{
mval() = val;
return *this;
}
ValueHolder& ValueHolder::operator=(bool val)
{
mval() = val;
return *this;
}
ValueHolder& ValueHolder::operator=(const std::string& val)
{
mval() = val;
return *this;
}
ValueHolder& ValueHolder::operator=(const ValueHolder& other)
{
switch (other.getType()) {
case Type::Integer:
mval() = std::get<int>(*other.mval());
break;
case Type::Real:
mval() = std::get<double>(*other.mval());
break;
case Type::Bool:
mval() = std::get<bool>(*other.mval());
break;
case Type::String:
mval() = std::get<std::string>(*other.mval());
break;
case Type::Map:
{
mval() = ValueArray();
auto& array = std::get<ValueArray>(*mval());
auto& otherArray = std::get<ValueArray>(*other.mval());
for (const auto& [key,val] : otherArray)
array.emplace(key, std::make_unique<ValueHolder>(*val));
break;
}
case Type::Void:
mval() = {};
break;
}
m_const = other.m_const;
return *this;
}
ValueHolder& ValueHolder::operator=(const ValueArray& val)
{
if (getType() != Type::Map)
mval() = ValueArray();
auto& valref = std::get<ValueArray>(*mval());
for (auto& [k,v] : val)
valref.emplace(k, new ValueHolder(*v));
return *this;
}
ValueHolder& ValueHolder::operator[](const std::string& subscript)
{
if (getType() != Type::Map) {
VoidItem.clear();
return VoidItem;
}
auto& valref = std::get<ValueArray>(*mval());
auto it = valref.find(subscript);
if (it == valref.end()) {
VoidItem.clear();
return VoidItem;
}
return *it->second;
}
const ValueHolder& ValueHolder::operator[](const std::string& subscript) const
{
if (getType() != Type::Map) {
VoidItem.clear();
return VoidItem;
}
auto& valref = std::get<ValueArray>(*mval());
auto it = valref.find(subscript);
if (it == valref.end()) {
VoidItem.clear();
return VoidItem;
}
return *it->second;
}
bool ValueHolder::rangedOperator(ParserOperator op, const ValueHolder& other) const
{
std::function<bool(int,int)> intOp = [](bool,bool){ return false; };
std::function<bool(double,double)> realOp = [](double,double){ return false; };
std::function<bool(std::string,std::string)> stringOp = [](std::string,std::string){ return false; };
using Op = ParserOperator;
switch (op) {
case Op::Lt:
intOp = std::less<int>();
realOp = std::less<double>();
stringOp = std::less<std::string>();
break;
case Op::Gt:
intOp = std::greater<int>();
realOp = std::greater<double>();
stringOp = std::greater<std::string>();
break;
case Op::Lte:
intOp = std::less_equal<int>();
realOp = std::less_equal<double>();
stringOp = std::less_equal<std::string>();
break;
case Op::Gte:
intOp = std::greater_equal<int>();
realOp = std::greater_equal<double>();
stringOp = std::greater_equal<std::string>();
break;
default:
break;
}
const Type mtype = getType();
switch (mtype) {
case Type::Integer:
{
const int mv_i = std::get<int>(*mval());
switch (other.getType()) {
case Type::Integer:
return intOp(mv_i, std::get<int>(*other.mval()));
case Type::Real:
return realOp(double(mv_i), std::get<double>(*other.mval()));
default:
return false;
}
}
case Type::Real:
{
const double mv_d = std::get<double>(*mval());
switch (other.getType()) {
case Type::Integer:
return realOp(mv_d, double(std::get<int>(*other.mval())));
case Type::Real:
return realOp(mv_d, std::get<double>(*other.mval()));
default:
return false;
}
}
case Type::String:
{
const auto& mv_s = std::get<std::string>(*mval());
switch (other.getType()) {
case Type::String:
return stringOp(mv_s, std::get<std::string>(*other.mval()));
default:
return false;
}
}
default:
return false;
}
}
bool ValueHolder::equalityOperator(ParserOperator op, const ValueHolder& other) const
{
std::function<bool(int,int)> intOp;
std::function<bool(double,double)> realOp;
std::function<bool(bool,bool)> boolOp;
std::function<bool(std::string,std::string)> stringOp;
using Op = ParserOperator;
switch (op) {
case Op::Eq:
intOp = std::equal_to<int>();
realOp = std::equal_to<double>();
boolOp = std::equal_to<bool>();
stringOp = std::equal_to<std::string>();
break;
case Op::Ne:
intOp = std::not_equal_to<int>();
realOp = std::not_equal_to<double>();
boolOp = std::not_equal_to<bool>();
stringOp = std::not_equal_to<std::string>();
break;
default:
return false;
}
const Type mtype = getType();
switch (mtype) {
case Type::Integer:
{
const int mv_i = std::get<int>(*mval());
switch (other.getType()) {
case Type::Integer:
return intOp(mv_i, std::get<int>(*other.mval()));
case Type::Real:
return realOp(double(mv_i), std::get<double>(*other.mval())); // TODO use some comparison with an epsilon value
case Type::Bool:
return boolOp(mv_i != 0, std::get<bool>(*other.mval()));
case Type::Void:
return op == Op::Ne;
default:
return false;
}
}
case Type::Real:
{
const double mv_d = std::get<double>(*mval());
switch (other.getType()) {
case Type::Integer:
return realOp(mv_d, double(std::get<int>(*other.mval()))); // TODO use some comparison with an epsilon value
case Type::Real:
return realOp(mv_d, std::get<double>(*other.mval())); // TODO use some comparison with an epsilon value
case Type::Void:
return op == Op::Ne;
default:
return false;
}
}
case Type::Bool:
{
const int mv_b = std::get<bool>(*mval());
switch (other.getType()) {
case Type::Integer:
return boolOp(mv_b, std::get<int>(*other.mval()) != 0);
case Type::Bool:
return boolOp(mv_b, std::get<bool>(*other.mval()));
case Type::Void:
return op == Op::Ne;
default:
return false;
}
}
case Type::String:
{
const std::string& mv_s = std::get<std::string>(*mval());
switch (other.getType()) {
case Type::String:
return stringOp(mv_s, std::get<std::string>(*other.mval()));
case Type::Void:
return op == Op::Ne;
default:
return false;
}
}
case Type::Map:
{
auto otherType = other.getType();
if (otherType == Type::Void)
return op == Op::Ne;
if (otherType != Type::Map)
return false;
const ValueArray& my_a = std::get<ValueArray>(*mval());
const ValueArray& oth_a = std::get<ValueArray>(*other.mval());
// Compare 'this' values
for (const auto& [key,pval] : my_a) {
auto itOth = oth_a.find(key);
if (itOth == oth_a.end() || *pval != *itOth->second)
return op == ParserOperator::Ne;
}
// Compare 'other' values
for (const auto& [key,pval] : oth_a) {
auto itThs = my_a.find(key);
if (itThs == my_a.end() || *pval != *itThs->second)
return op == ParserOperator::Ne;
}
return true;
}
/* TODO for now this is basically comparing types, ie, if two values is void */
case Type::Void:
if (op == Op::Eq)
return mtype == other.getType();
else if (op == Op::Ne)
return mtype != other.getType();
}
return false;
}
ValueHolder ValueHolder::arithmeticOperator(ParserOperator op, const ValueHolder& other) const
{
// modulo (%) has special handling.
std::function<int(int,int)> intOp = [](bool,bool){ return false; };
std::function<double(double,double)> realOp = [](double,double){ return false; };
std::function<std::string(const std::string&, const std::string&)> stringOp
= [](const std::string&, const std::string&) -> std::string { throw ScriptException("Unsuitable operator for string operation"); };
using Op = ParserOperator;
switch (op) {
case Op::Plus:
intOp = std::plus<int>();
realOp = std::plus<double>();
stringOp = std::plus<std::string>();
break;
case Op::Minus:
intOp = std::minus<int>();
realOp = std::minus<double>();
break;
case Op::Multi:
intOp = std::multiplies<int>();
realOp = std::multiplies<double>();
break;
case Op::Div:
intOp = std::divides<int>();
realOp = std::divides<double>();
break;
default:
break;
}
const Type mtype = getType();
const Type otherType = other.getType();
if (otherType == Type::Void)
throw ScriptException("Right hand side is void");
ValueHolder ret;
switch (mtype) {
case Type::Void:
throw ScriptException("Left hand side is void");
case Type::Integer:
{
const int mv_i = std::get<int>(*mval());
if (otherType == Type::Integer)
ret = intOp(mv_i, std::get<int>(*other.mval()));
else if (otherType == Type::Real)
ret = realOp(double(mv_i), std::get<double>(*other.mval()));
else if (otherType == Type::String)
ret = stringOp(std::to_string(mv_i), std::get<std::string>(*other.mval()));
else
throw ScriptException("Unsuitable operator for right hand operand");
}
break;
case Type::Real:
{
const double mv_d = std::get<double>(*mval());
if (otherType == Type::Integer)
ret = realOp(mv_d, double(std::get<int>(*other.mval())));
else if (otherType == Type::Real)
ret = realOp(mv_d, std::get<double>(*other.mval()));
else if (otherType == Type::String)
ret = stringOp(std::to_string(mv_d), std::get<std::string>(*other.mval()));
else
throw ScriptException("Unsuitable operator for right hand operand");
}
break;
case Type::String:
if (op == Op::Plus) {
const std::string& mv_s = std::get<std::string>(*mval());
const Type otherType = other.getType();
if (otherType == Type::Integer)
ret = mv_s + std::to_string(std::get<int>(*other.mval()));
else if (otherType == Type::Real)
ret = mv_s + std::to_string(std::get<double>(*other.mval()));
else if (otherType == Type::String)
ret = mv_s + std::get<std::string>(*other.mval());
}
else
throw ScriptException("Unsuitable operator for string operation");
break;
default:
throw ScriptException("Unsuitable operator for left hand operand");
}
return ret;
}
bool ValueHolder::logicOperator(ParserOperator op, const ValueHolder& other) const
{
std::function<bool(int,int)> intOp = [](bool,bool){ return false; };
std::function<bool(double,double)> realOp = [](double,double){ return false; };
std::function<bool(bool,bool)> boolOp = [](bool,bool){ return false; };
std::function<bool(std::string,bool)> stringOpL = [](std::string,bool){ return false; };
std::function<bool(bool,std::string)> stringOpR = [](bool,std::string){ return false; };
std::function<bool(std::string,std::string)> stringOpB = [](std::string,std::string){ return false; };
using Op = ParserOperator;
switch (op) {
case Op::LAnd:
intOp = std::logical_and<int>();
realOp = std::logical_and<double>();
boolOp = std::logical_and<bool>();
stringOpL = [](std::string l, bool r){ return !l.empty() && r; };
stringOpR = [](bool l, std::string r){ return l && !r.empty(); };
stringOpB = [](std::string l, std::string r){ return !l.empty() && r.empty(); };
break;
case Op::LOr:
intOp = std::logical_or<int>();
realOp = std::logical_or<double>();
boolOp = std::logical_or<bool>();
stringOpL = [](std::string l, bool r){ return !l.empty() || r; };
stringOpR = [](bool l, std::string r){ return l || !r.empty(); };
stringOpB = [](std::string l, std::string r){ return !l.empty() || r.empty(); };
break;
default:
break;
}
const Type mtype = getType();
bool ret;
switch (mtype) {
case Type::Integer:
{
const auto mv_i = std::get<int>(*mval());
const Type otherType = other.getType();
if (otherType == Type::Integer)
ret = intOp(mv_i, std::get<int>(*other.mval()));
else if (otherType == Type::Real)
ret = realOp(static_cast<double>(mv_i), std::get<double>(*other.mval())); // TODO use some comparison with an epsilon value
else if (otherType == Type::Bool)
ret = boolOp(mv_i, std::get<bool>(*other.mval()));
else if (otherType == Type::Void)
ret = 0;
else if (otherType == Type::String)
ret = stringOpR(mv_i, other.toStdString());
else
throw ScriptException("Unsuitable operator for right hand operand");
}
break;
case Type::Real:
{
const auto mv_d = std::get<double>(*mval());
const Type otherType = other.getType();
if (otherType == Type::Integer)
ret = realOp(mv_d, std::get<int>(*other.mval())); // TODO use some comparison with an epsilon value
else if (otherType == Type::Real)
ret = realOp(mv_d, std::get<double>(*other.mval())); // TODO use some comparison with an epsilon value
else if (otherType == Type::Bool)
ret = realOp(mv_d, std::get<bool>(*other.mval())); // TODO use some comparison with an epsilon value
else if (otherType == Type::Void)
ret = 0;
else if (otherType == Type::String)
ret = stringOpR(mv_d != 0, other.toStdString()); // TODO use some comparison with an epsilon value
else
throw ScriptException("Unsuitable operator for right hand operand");
}
break;
case Type::Bool:
{
const auto mv_b = std::get<bool>(*mval());
const Type otherType = other.getType();
if (otherType == Type::Integer)
ret = boolOp(mv_b, std::get<int>(*other.mval()));
else if (otherType == Type::Real)
ret = boolOp(mv_b, std::get<double>(*other.mval()) != 0); // TODO use some comparison with an epsilon value
else if (otherType == Type::Bool)
ret = boolOp(mv_b, std::get<bool>(*other.mval()));
else if (otherType == Type::Void)
ret = 0;
else if (otherType == Type::String)
ret = boolOp(mv_b, !other.toStdString().empty());
else
throw ScriptException("Unsuitable operator for right hand operand");
}
break;
case Type::Void:
{
const Type otherType = other.getType();
if (otherType == Type::Void)
ret = 1;
else
ret = 0;
}
break;
case Type::String:
{
const auto mv_s = std::get<std::string>(*mval());
const Type otherType = other.getType();
if (otherType == Type::Integer)
ret = stringOpL(mv_s, std::get<int>(*other.mval()));
else if (otherType == Type::Real)
ret = stringOpL(mv_s, std::get<double>(*other.mval()) != 0); // TODO use some comparison with an epsilon value
else if (otherType == Type::Bool)
ret = boolOp(!mv_s.empty(), std::get<bool>(*other.mval()));
else if (otherType == Type::Void)
ret = 0;
else if (otherType == Type::String)
ret = stringOpB(mv_s, other.toStdString());
else
throw ScriptException("Unsuitable operator for right hand operand");
}
break;
case Type::Map:
throw ScriptException("Unsuitable operator for left hand operand");
}
return ret;
}
bool ValueHolder::operator<(const ValueHolder& other) const
{
return rangedOperator(ParserOperator::Lt, other);
}
bool ValueHolder::operator>(const ValueHolder& other) const
{
return rangedOperator(ParserOperator::Gt, other);
}
bool ValueHolder::operator<=(const ValueHolder& other) const
{
return rangedOperator(ParserOperator::Lte, other);
}
bool ValueHolder::operator>=(const ValueHolder& other) const
{
return rangedOperator(ParserOperator::Gte, other);
}
bool ValueHolder::operator==(const ValueHolder& other) const
{
return equalityOperator(ParserOperator::Eq, other);
}
bool ValueHolder::operator!=(const ValueHolder& other) const
{
return equalityOperator(ParserOperator::Ne, other);
}
ValueHolder ValueHolder::operator+(const ValueHolder& other) const
{
return arithmeticOperator(ParserOperator::Plus, other);
}
ValueHolder ValueHolder::operator-(const ValueHolder& other) const
{
return arithmeticOperator(ParserOperator::Minus, other);
}
ValueHolder ValueHolder::operator*(const ValueHolder& other) const
{
return arithmeticOperator(ParserOperator::Multi, other);
}
ValueHolder ValueHolder::operator/(const ValueHolder& other) const
{
return arithmeticOperator(ParserOperator::Div, other);
}
ValueHolder ValueHolder::operator%(const ValueHolder& other) const
{
const Type mtype = getType();
ValueHolder ret;
switch (mtype) {
case Type::Integer:
{
const int mv_i = std::get<int>(*mval());
const Type otherType = other.getType();
if (otherType == Type::Integer)
ret = mv_i % std::get<int>(*other.mval());
else if (otherType == Type::Real)
ret = mv_i % static_cast<int>(std::get<double>(*other.mval()));
else if (otherType == Type::Void)
throw ScriptException("Right hand side is void");
else
throw ScriptException("Unsuitable operator for right hand operand");
}
break;
case Type::Real:
{
const int mv_d_i_trunc = static_cast<int>(std::get<double>(*mval()));
const Type otherType = other.getType();
if (otherType == Type::Integer)
ret = mv_d_i_trunc % std::get<int>(*other.mval());
else if (otherType == Type::Real)
ret = mv_d_i_trunc % static_cast<int>(std::get<double>(*other.mval()));
else if (otherType == Type::Void)
throw ScriptException("Right hand side is void");
else
throw ScriptException("Unsuitable operator for right hand operand");
}
break;
case Type::Void:
throw ScriptException("Left hand side is void");
default:
throw ScriptException("Unsuitable operator for left hand operand");
}
return ret;
}
bool ValueHolder::operator&&(const ValueHolder& other) const
{
return logicOperator(ParserOperator::LAnd, other);
}
bool ValueHolder::operator||(const ValueHolder& other) const
{
return logicOperator(ParserOperator::LOr, other);
}
void ValueHolder::clear()
{
mval().reset();
}
void ValueHolder::reset()
{
m_val.reset();
}
bool ValueHolder::hasValue() const
{
return mval().has_value();
}
ValueHolder::Type ValueHolder::getType() const
{
if (!m_val.has_value())
return Type::Void;
std::size_t valIdx = mval()->index();
if (valIdx < 1 || valIdx > static_cast<std::size_t>(Type::Map))
return Type::Void;
else
return static_cast<Type>(valIdx);
}
std::string ValueHolder::toStdString() const
{
const Type mtype = getType();
switch (mtype) {
case Type::Integer:
return std::to_string(std::get<int>(*mval()));
case Type::Real:
{
std::string val = std::to_string(std::get<double>(*mval()));
char ds = decimalSeparator();
if (ds != '.') {
auto dspos = val.find_first_of(ds);
if (dspos == val.npos)
return val;
else
return val.replace(dspos, 1, ".");
}
else
return val;
}
case Type::Bool:
return std::get<bool>(*mval()) ? "true" : "false";
case Type::String:
return std::get<std::string>(*mval());
case Type::Map:
{
auto& array = std::get<ValueArray>(*mval());
std::stringstream ss;
ss << "[Array of " << array.size() << "]";
return ss.str();
}
case Type::Void:
return "[void]";
}
// Compiler complains even if all cases are covered... So this return is highly unlikely to happen... unless a new type is introduced.
return "[???]";
}
void ValueHolder::setArray(const std::string& subscript, const ValueHolder& val)
{
if (getType() != Type::Map)
m_val = ValueArray();
auto& array = std::get<ValueArray>(*mval());
try {
auto& valptr = array.at(subscript);
*valptr = val;
} catch (std::out_of_range&) {
array.emplace(subscript, new ValueHolder(val));
}
}
bool ValueHolder::isReference() const
{
if (!m_val)
return false;
return m_val->index() == 0;
}
void ValueHolder::markAsConst()
{
m_const = true;
}
bool ValueHolder::isConst() const
{
return m_const;
}
ValueHolder::ValueTypes& ValueHolder::mval()
{
if (m_val.has_value() && m_val->index() == 0) // ValueRef
return std::get<ValueRef>(*m_val).get().m_val;
else
return m_val;
}
const ValueHolder::ValueTypes& ValueHolder::mval() const
{
if (m_val.has_value() && m_val->index() == 0) // ValueRef
return std::get<ValueRef>(*m_val).get().m_val;
else
return m_val;
}