#pragma once #include #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< Null, Int, Double, String, Bool, Function, StructType, StructInstance>; VariantType data; Object() : data(Null{}) {} Object(const Null &n) : data(std::in_place_type, n) {} Object(const Int &i) : data(std::in_place_type, i) {} Object(const Double &d) { ValueType::IntClass casted = static_cast(d.getValue()); if (casted == d.getValue()) data.emplace(casted); else data.emplace(d); } Object(const String &s) : data(std::in_place_type, s) {} Object(const Bool &b) : data(std::in_place_type, b) {} Object(const Function &f) : data(std::in_place_type, f) {} Object(const StructType &s) : data(std::in_place_type, s) {} Object(const StructInstance &s) : data(std::in_place_type, s) {} template || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v>> Object(const T &val) { if constexpr (std::is_same_v) data.emplace(val); else if constexpr (std::is_same_v) { ValueType::IntClass casted = static_cast(val); if (casted == val) data.emplace(casted); else data.emplace(val); } else if constexpr (std::is_same_v) data.emplace(val); else if constexpr (std::is_same_v) data.emplace(val); else if constexpr (std::is_same_v) data.emplace(val); else if constexpr (std::is_same_v) data.emplace(val); else if constexpr (std::is_same_v) data.emplace(val); } 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(Int(0)); else if (ti == ValueType::Double) return Object(Double(0.0)); else if (ti == ValueType::String) return Object(String(u8"")); else if (ti == ValueType::Bool) return Object(Bool(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 Object getNullInstance() { static Object v(Null{}); return v; } 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().getValue()); else if (is()) return as().getValue(); 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().getValue())); if (is()) return FString(std::to_string(as().getValue())); if (is()) return as().getValue(); if (is()) return as().getValue() ? 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 lhsIsInt = lhs.is(); bool rhsIsInt = rhs.is(); ValueType::DoubleClass result = lhs.getNumericValue() + rhs.getNumericValue(); if (lhsIsInt && rhsIsInt && !isNumberExceededIntLimit(result)) return Object(static_cast(result)); return Object(ValueType::DoubleClass(result)); } if (lhs.is() && rhs.is()) return Object(ValueType::StringClass(lhs.as().getValue() + rhs.as().getValue())); 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 lhsIsInt = lhs.is(); bool rhsIsInt = rhs.is(); ValueType::DoubleClass result = lhs.getNumericValue() - rhs.getNumericValue(); if (lhsIsInt && rhsIsInt && !isNumberExceededIntLimit(result)) return Object(static_cast(result)); return Object(ValueType::DoubleClass(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 lhsIsInt = lhs.is(); bool rhsIsInt = rhs.is(); ValueType::DoubleClass result = lhs.getNumericValue() * rhs.getNumericValue(); if (lhsIsInt && rhsIsInt && !isNumberExceededIntLimit(result)) return Object(static_cast(result)); return Object(ValueType::DoubleClass(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()) { ValueType::DoubleClass rnv = rhs.getNumericValue(); if (rnv == 0) throw ValueError(FStringView(makeTypeErrorMessage("Division by zero", "/", lhs, rhs))); ValueType::DoubleClass result = lhs.getNumericValue() / rnv; bool lhsIsInt = lhs.is(); bool rhsIsInt = rhs.is(); if (lhsIsInt && rhsIsInt && !isNumberExceededIntLimit(result)) return Object(static_cast(result)); return Object(ValueType::DoubleClass(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()) { ValueType::DoubleClass rnv = rhs.getNumericValue(); if (rnv == 0) throw ValueError(FStringView(makeTypeErrorMessage("Modulo by zero", "%", lhs, rhs))); ValueType::DoubleClass result = fmod(lhs.getNumericValue(), rnv); bool lhsIsInt = lhs.is(); bool rhsIsInt = rhs.is(); if (lhsIsInt && rhsIsInt && !isNumberExceededIntLimit(result)) return Object(static_cast(result)); return Object(ValueType::DoubleClass(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().getValue() && rhs.as().getValue()); } 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().getValue() || rhs.as().getValue()); } 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().getValue()); } friend Object operator-(const Object &v) { if (v.isNull()) throw ValueError(FStringView(std::format("Unary minus cannot be applied to null"))); if (v.is()) return Object(-v.as().getValue()); if (v.is()) return Object(-v.as().getValue()); 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().getValue()); } // compare 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().getValue() < rhs.as().getValue(); 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().getValue() > rhs.as().getValue(); 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().getValue() & rhs.as().getValue()); } 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().getValue() | rhs.as().getValue()); } 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().getValue() ^ rhs.as().getValue()); } 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().getValue()); } 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().getValue() << rhs.as().getValue()); } 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().getValue() >> rhs.as().getValue()); } 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 baseIsInt = base.is(); bool expIsInt = exp.is(); ValueType::DoubleClass result = std::pow(base.getNumericValue(), exp.getNumericValue()); if (baseIsInt && expIsInt && isDoubleInteger(result) && !isNumberExceededIntLimit(result)) return Object(static_cast(result)); return Object(ValueType::DoubleClass(result)); } throw ValueError(FStringView(makeTypeErrorMessage("Unsupported operation", "**", base, exp))); } }; using Any = Object; } // namespace Fig