#pragma once #include #include #include #include #include #include #include #include #include namespace Fig { inline bool isDoubleInteger(ValueType::DoubleClass d) { return std::floor(d) == d; } inline bool isNumberExceededIntLimit(ValueType::DoubleClass d) { static constexpr ValueType::DoubleClass intMaxAsDouble = static_cast(std::numeric_limits::max()); static constexpr ValueType::DoubleClass intMinAsDouble = static_cast(std::numeric_limits::min()); return d > intMaxAsDouble || d < intMinAsDouble; } class Object { public: using VariantType = std::variant< ValueType::NullClass, ValueType::IntClass, ValueType::DoubleClass, ValueType::StringClass, ValueType::BoolClass, Function, StructType, StructInstance>; VariantType data; Object() : data(ValueType::NullClass{}) {} Object(const ValueType::NullClass &n) : data(n) {} Object(const ValueType::IntClass &i) : data(i) {} Object(const ValueType::DoubleClass &d) { ValueType::IntClass casted = static_cast(d); if (casted == d) data = casted; else data = d; } Object(const ValueType::StringClass &s) : data(s) {} Object(const ValueType::BoolClass &b) : data(b) {} Object(const Function &f) : data(f) {} Object(const StructType &s) : data(s) {} Object(const StructInstance &s) : data(s) {} Object(const Object &) = default; Object(Object &&) noexcept = default; Object &operator=(const Object &) = default; Object &operator=(Object &&) noexcept = default; static Object defaultValue(TypeInfo ti) { if (ti == ValueType::Int) return Object(ValueType::IntClass(0)); else if (ti == ValueType::Double) return Object(ValueType::DoubleClass(0.0)); else if (ti == ValueType::String) return Object(ValueType::StringClass(u8"")); else if (ti == ValueType::Bool) return Object(ValueType::BoolClass(false)); else return *getNullInstance(); } template bool is() const { return std::holds_alternative(data); } template T &as() { return std::get(data); } template const T &as() const { return std::get(data); } static std::shared_ptr getNullInstance() { static std::shared_ptr n = std::make_shared(ValueType::NullClass{}); return n; } static std::shared_ptr getTrueInstance() { static std::shared_ptr t = std::make_shared(true); return t; } static std::shared_ptr getFalseInstance() { static std::shared_ptr f = std::make_shared(false); return f; } TypeInfo getTypeInfo() const { return std::visit([](auto &&val) -> TypeInfo { using T = std::decay_t; if constexpr (std::is_same_v) return ValueType::Null; else if constexpr (std::is_same_v) return ValueType::Int; else if constexpr (std::is_same_v) return ValueType::Double; else if constexpr (std::is_same_v) return ValueType::String; else if constexpr (std::is_same_v) return ValueType::Bool; else if constexpr (std::is_same_v) return ValueType::Function; else if constexpr (std::is_same_v) return ValueType::StructType; else if constexpr (std::is_same_v) return ValueType::StructInstance; else return ValueType::Any; }, data); } bool isNull() const { return is(); } bool isNumeric() const { return is() || is(); } ValueType::DoubleClass getNumericValue() const { if (is()) return static_cast(as()); else if (is()) return as(); else throw RuntimeError(u8"getNumericValue: Not a numeric value"); } FString toString() const { if (is()) return FString(u8"null"); if (is()) return FString(std::to_string(as())); if (is()) return FString(std::format("{}", as())); if (is()) return as(); if (is()) return as() ? FString(u8"true") : FString(u8"false"); if (is()) return FString(std::format("", as().id, static_cast(&as()))); if (is()) return FString(std::format("", as().id, static_cast(&as()))); if (is()) return FString(std::format("", as().parentId, static_cast(&as()))); return FString(u8""); } private: static std::string makeTypeErrorMessage(const char *prefix, const char *op, const Object &lhs, const Object &rhs) { auto lhs_type = lhs.getTypeInfo().name.toBasicString(); auto rhs_type = rhs.getTypeInfo().name.toBasicString(); return std::format("{}: {} '{}' {}", prefix, lhs_type, op, rhs_type); } public: // math friend Object operator+(const Object &lhs, const Object &rhs) { if (lhs.isNull() || rhs.isNull()) throw ValueError(FStringView(makeTypeErrorMessage("Cannot add", "+", lhs, rhs))); if (lhs.isNumeric() && rhs.isNumeric()) { bool bothInt = lhs.is() && rhs.is(); auto result = lhs.getNumericValue() + rhs.getNumericValue(); if (bothInt && !isNumberExceededIntLimit(result)) return Object(static_cast(result)); return Object(result); } if (lhs.is() && rhs.is()) return Object(FString(lhs.as() + rhs.as())); throw ValueError(FStringView(makeTypeErrorMessage("Unsupported operation", "+", lhs, rhs))); } friend Object operator-(const Object &lhs, const Object &rhs) { if (lhs.isNull() || rhs.isNull()) throw ValueError(FStringView(makeTypeErrorMessage("Cannot subtract", "-", lhs, rhs))); if (lhs.isNumeric() && rhs.isNumeric()) { bool bothInt = lhs.is() && rhs.is(); auto result = lhs.getNumericValue() - rhs.getNumericValue(); if (bothInt && !isNumberExceededIntLimit(result)) return Object(static_cast(result)); return Object(result); } throw ValueError(FStringView(makeTypeErrorMessage("Unsupported operation", "-", lhs, rhs))); } friend Object operator*(const Object &lhs, const Object &rhs) { if (lhs.isNull() || rhs.isNull()) throw ValueError(FStringView(makeTypeErrorMessage("Cannot multiply", "*", lhs, rhs))); if (lhs.isNumeric() && rhs.isNumeric()) { bool bothInt = lhs.is() && rhs.is(); auto result = lhs.getNumericValue() * rhs.getNumericValue(); if (bothInt && !isNumberExceededIntLimit(result)) return Object(static_cast(result)); return Object(result); } throw ValueError(FStringView(makeTypeErrorMessage("Unsupported operation", "*", lhs, rhs))); } friend Object operator/(const Object &lhs, const Object &rhs) { if (lhs.isNull() || rhs.isNull()) throw ValueError(FStringView(makeTypeErrorMessage("Cannot divide", "/", lhs, rhs))); if (lhs.isNumeric() && rhs.isNumeric()) { auto rnv = rhs.getNumericValue(); if (rnv == 0) throw ValueError(FStringView(makeTypeErrorMessage("Division by zero", "/", lhs, rhs))); bool bothInt = lhs.is() && rhs.is(); auto result = lhs.getNumericValue() / rnv; if (bothInt && !isNumberExceededIntLimit(result)) return Object(static_cast(result)); return Object(result); } throw ValueError(FStringView(makeTypeErrorMessage("Unsupported operation", "/", lhs, rhs))); } friend Object operator%(const Object &lhs, const Object &rhs) { if (lhs.isNull() || rhs.isNull()) throw ValueError(FStringView(makeTypeErrorMessage("Cannot modulo", "%", lhs, rhs))); if (lhs.isNumeric() && rhs.isNumeric()) { auto rnv = rhs.getNumericValue(); if (rnv == 0) throw ValueError(FStringView(makeTypeErrorMessage("Modulo by zero", "/", lhs, rhs))); bool bothInt = lhs.is() && rhs.is(); auto result = std::fmod(lhs.getNumericValue(), rnv); if (bothInt && !isNumberExceededIntLimit(result)) return Object(static_cast(result)); return Object(result); } throw ValueError(FStringView(makeTypeErrorMessage("Unsupported operation", "%", lhs, rhs))); } // logic friend Object operator&&(const Object &lhs, const Object &rhs) { if (!lhs.is() || !rhs.is()) throw ValueError(FStringView(makeTypeErrorMessage("Logical AND requires bool", "&&", lhs, rhs))); return Object(lhs.as() && rhs.as()); } friend Object operator||(const Object &lhs, const Object &rhs) { if (!lhs.is() || !rhs.is()) throw ValueError(FStringView(makeTypeErrorMessage("Logical OR requires bool", "||", lhs, rhs))); return Object(lhs.as() || rhs.as()); } friend Object operator!(const Object &v) { if (!v.is()) throw ValueError(FStringView(std::format("Logical NOT requires bool: '{}'", v.getTypeInfo().name.toBasicString()))); return Object(!v.as()); } friend Object operator-(const Object &v) { if (v.isNull()) throw ValueError(FStringView(u8"Unary minus cannot be applied to null")); if (v.is()) return Object(-v.as()); if (v.is()) return Object(-v.as()); throw ValueError(FStringView(std::format("Unary minus requires int or double: '{}'", v.getTypeInfo().name.toBasicString()))); } friend Object operator~(const Object &v) { if (!v.is()) throw ValueError(FStringView(std::format("Bitwise NOT requires int: '{}'", v.getTypeInfo().name.toBasicString()))); return Object(~v.as()); } // comparison friend bool operator==(const Object &lhs, const Object &rhs) { return lhs.data == rhs.data; } friend bool operator!=(const Object &lhs, const Object &rhs) { return !(lhs == rhs); } friend bool operator<(const Object &lhs, const Object &rhs) { if (lhs.isNumeric() && rhs.isNumeric()) return lhs.getNumericValue() < rhs.getNumericValue(); if (lhs.is() && rhs.is()) return lhs.as() < rhs.as(); throw ValueError(FStringView(makeTypeErrorMessage("Unsupported comparison", "<", lhs, rhs))); } friend bool operator<=(const Object &lhs, const Object &rhs) { return lhs == rhs || lhs < rhs; } friend bool operator>(const Object &lhs, const Object &rhs) { if (lhs.isNumeric() && rhs.isNumeric()) return lhs.getNumericValue() > rhs.getNumericValue(); if (lhs.is() && rhs.is()) return lhs.as() > rhs.as(); throw ValueError(FStringView(makeTypeErrorMessage("Unsupported comparison", ">", lhs, rhs))); } friend bool operator>=(const Object &lhs, const Object &rhs) { return lhs == rhs || lhs > rhs; } // bitwise friend Object bit_and(const Object &lhs, const Object &rhs) { if (!lhs.is() || !rhs.is()) throw ValueError(FStringView(makeTypeErrorMessage("Bitwise AND requires int", "&", lhs, rhs))); return Object(lhs.as() & rhs.as()); } friend Object bit_or(const Object &lhs, const Object &rhs) { if (!lhs.is() || !rhs.is()) throw ValueError(FStringView(makeTypeErrorMessage("Bitwise OR requires int", "|", lhs, rhs))); return Object(lhs.as() | rhs.as()); } friend Object bit_xor(const Object &lhs, const Object &rhs) { if (!lhs.is() || !rhs.is()) throw ValueError(FStringView(makeTypeErrorMessage("Bitwise XOR requires int", "^", lhs, rhs))); return Object(lhs.as() ^ rhs.as()); } friend Object bit_not(const Object &v) { if (!v.is()) throw ValueError(FStringView(std::format("Bitwise NOT requires int: '{}'", v.getTypeInfo().name.toBasicString()))); return Object(~v.as()); } friend Object shift_left(const Object &lhs, const Object &rhs) { if (!lhs.is() || !rhs.is()) throw ValueError(FStringView(makeTypeErrorMessage("Shift left requires int", "<<", lhs, rhs))); return Object(lhs.as() << rhs.as()); } friend Object shift_right(const Object &lhs, const Object &rhs) { if (!lhs.is() || !rhs.is()) throw ValueError(FStringView(makeTypeErrorMessage("Shift right requires int", ">>", lhs, rhs))); return Object(lhs.as() >> rhs.as()); } friend Object power(const Object &base, const Object &exp) { if (base.isNull() || exp.isNull()) throw ValueError(FStringView(makeTypeErrorMessage("Cannot exponentiate", "**", base, exp))); if (base.isNumeric() && exp.isNumeric()) { bool bothInt = base.is() && exp.is(); auto result = std::pow(base.getNumericValue(), exp.getNumericValue()); if (bothInt && !isNumberExceededIntLimit(result)) return Object(static_cast(result)); return Object(result); } throw ValueError(FStringView(makeTypeErrorMessage("Unsupported operation", "**", base, exp))); } }; using ObjectPtr = std::shared_ptr; } // namespace Fig