#pragma once #include #include #include #include #include #include #include #include #include #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 auto intMaxAsDouble = static_cast(std::numeric_limits::max()); static constexpr auto intMinAsDouble = static_cast(std::numeric_limits::min()); return d > intMaxAsDouble || d < intMinAsDouble; } inline bool nearlyEqual(ValueType::DoubleClass l, ValueType::DoubleClass r, ValueType::DoubleClass epsilon = 1e-9) { return std::abs(l - r) < epsilon; } TypeInfo actualType(std::shared_ptr obj); FString prettyType(std::shared_ptr obj); bool operator==(const Object &, const Object &); struct Element { ObjectPtr value; Element(ObjectPtr _value) : value(_value) {} bool operator==(const Element &other) const { return *value == *other.value; } void deepCopy(const Element &e) { value = std::make_shared(*e.value); } }; using List = std::vector; struct ValueKey { ObjectPtr value; ValueKey(ObjectPtr _value) : value(_value) {} void deepCopy(const ValueKey &vk) { value = std::make_shared(*vk.value); } }; struct ValueKeyHash { size_t operator()(const ValueKey &key) const; }; using Map = std::unordered_map; bool isTypeMatch(const TypeInfo &, ObjectPtr, ContextPtr); bool implements(const TypeInfo &, const TypeInfo &, ContextPtr); using BuiltinTypeMemberFn = std::function)>; class Object : public std::enable_shared_from_this { public: using VariantType = std::variant; static std::unordered_map, TypeInfoHash> getMemberTypeFunctions() { static const std::unordered_map, TypeInfoHash> memberTypeFunctions{ {ValueType::Null, {}}, {ValueType::Int, {}}, {ValueType::Double, {}}, {ValueType::String, { {u8"length", [](ObjectPtr object, std::vector args) -> ObjectPtr { if (args.size() != 0) throw RuntimeError( FString(std::format("`length` expects 0 arguments, {} got", args.size()))); const FString &str = object->as(); return std::make_shared(static_cast(str.length())); }}, {u8"replace", [](ObjectPtr object, std::vector args) -> ObjectPtr { if (args.size() != 2) throw RuntimeError( FString(std::format("`replace` expects 2 arguments, {} got", args.size()))); FString &str = object->as(); ObjectPtr arg1 = args[0]; ObjectPtr arg2 = args[1]; if (!arg1->is()) { throw RuntimeError(FString("`replace` arg 1 expects type Int")); } if (!arg2->is()) { throw RuntimeError(FString("`replace` arg 2 expects type String")); } str.realReplace(arg1->as(), arg2->as()); return Object::getNullInstance(); }}, {u8"erase", [](ObjectPtr object, std::vector args) -> ObjectPtr { if (args.size() != 2) throw RuntimeError( FString(std::format("`erase` expects 2 arguments, {} got", args.size()))); FString &str = object->as(); ObjectPtr arg1 = args[0]; ObjectPtr arg2 = args[1]; if (!arg1->is()) { throw RuntimeError(FString("`erase` arg 1 expects type Int")); } if (!arg2->is()) { throw RuntimeError(FString("`erase` arg 2 expects type Int")); } ValueType::IntClass index = arg1->as(); ValueType::IntClass n = arg2->as(); if (index < 0 || n < 0) { throw RuntimeError(FString("`erase`: index and n must greater or equal to 0")); } if (index + n > str.length()) { throw RuntimeError(FString("`erase`: length is not long enough to erase")); } str.realErase(arg1->as(), arg2->as()); return Object::getNullInstance(); }}, {u8"insert", [](ObjectPtr object, std::vector args) -> ObjectPtr { if (args.size() != 2) throw RuntimeError( FString(std::format("`insert` expects 2 arguments, {} got", args.size()))); FString &str = object->as(); ObjectPtr arg1 = args[0]; ObjectPtr arg2 = args[1]; if (!arg1->is()) { throw RuntimeError(FString("`insert` arg 1 expects type Int")); } if (!arg2->is()) { throw RuntimeError(FString("`insert` arg 2 expects type String")); } str.realInsert(arg1->as(), arg2->as()); return Object::getNullInstance(); }}, }}, {ValueType::Function, {}}, {ValueType::StructType, {}}, {ValueType::StructInstance, {}}, {ValueType::List, { {u8"length", [](ObjectPtr object, std::vector args) -> ObjectPtr { if (args.size() != 0) throw RuntimeError( FString(std::format("`length` expects 0 arguments, {} got", args.size()))); const List &list = object->as(); return std::make_shared(static_cast(list.size())); }}, {u8"get", [](ObjectPtr object, std::vector args) -> ObjectPtr { if (args.size() != 1) throw RuntimeError( FString(std::format("`get` expects 1 arguments, {} got", args.size()))); ObjectPtr arg = args[0]; if (arg->getTypeInfo() != ValueType::Int) throw RuntimeError( FString(std::format("`get` argument 1 expects Int, {} got", arg->getTypeInfo().toString().toBasicString()))); ValueType::IntClass i = arg->as(); const List &list = object->as(); if (i >= list.size()) return Object::getNullInstance(); return list[i].value; }}, {u8"push", [](ObjectPtr object, std::vector args) -> ObjectPtr { if (args.size() != 1) throw RuntimeError( FString(std::format("`push` expects 1 arguments, {} got", args.size()))); ObjectPtr arg = args[0]; List &list = object->as(); list.push_back(arg); return Object::getNullInstance(); }}, }}, {ValueType::Map, { {u8"get", [](ObjectPtr object, std::vector args) -> ObjectPtr { if (args.size() != 1) throw RuntimeError( FString(std::format("`get` expects 1 arguments, {} got", args.size()))); ObjectPtr index = args[0]; const Map &map = object->as(); if (!map.contains(index)) return Object::getNullInstance(); return map.at(index); }}, {u8"contains", [](ObjectPtr object, std::vector args) -> ObjectPtr { if (args.size() != 1) throw RuntimeError( FString(std::format("`contains` expects 1 arguments, {} got", args.size()))); ObjectPtr index = args[0]; const Map &map = object->as(); return std::make_shared(map.contains(index)); }}, }}, {ValueType::Module, {}}, {ValueType::InterfaceType, {}}, }; return memberTypeFunctions; } static std::unordered_map, TypeInfoHash> getMemberTypeFunctionsParas() { static const std::unordered_map, TypeInfoHash> memberTypeFunctionsParas{ {ValueType::Null, {}}, {ValueType::Int, {}}, {ValueType::Double, {}}, {ValueType::String, { {u8"length", 0}, {u8"replace", 2}, {u8"erase", 2}, {u8"insert", 2}, }}, {ValueType::Function, {}}, {ValueType::StructType, {}}, {ValueType::StructInstance, {}}, {ValueType::List, {{u8"length", 0}, {u8"get", 1}, {u8"push", 1}}}, {ValueType::Map, { {u8"get", 1}, {u8"contains", 1}, }}, {ValueType::Module, {}}, {ValueType::InterfaceType, {}}, }; return memberTypeFunctionsParas; } bool hasMemberFunction(const FString &name) const { return getMemberTypeFunctions().at(getTypeInfo()).contains(name); } BuiltinTypeMemberFn getMemberFunction(const FString &name) const { return getMemberTypeFunctions().at(getTypeInfo()).at(name); } int getMemberFunctionParaCount(const FString &name) const { return getMemberTypeFunctionsParas().at(getTypeInfo()).at(name); } VariantType data; Object() : data(ValueType::NullClass{}) {} Object(const ValueType::NullClass &n) : data(n) {} Object(const ValueType::IntClass &i) : data(i) {} explicit Object(const ValueType::DoubleClass &d) : 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 List &l) : data(l) {} Object(const Map &m) : data(m) {} Object(const Module &m) : data(m) {} Object(const InterfaceType &i) : data(i) {} 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 if (ti == ValueType::List) return Object(List{}); else if (ti == ValueType::Map) return Object(Map{}); 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 if constexpr (std::is_same_v) return ValueType::List; else if constexpr (std::is_same_v) return ValueType::Map; else if constexpr (std::is_same_v) return ValueType::Module; else if constexpr (std::is_same_v) return ValueType::InterfaceType; 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 toStringIO() const { if (is()) return as(); return toString(); } FString toString(std::unordered_set &visited) 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 FString(u8"\"" + as() + u8"\""); 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().type.toString().toBasicString(), static_cast(&as()))); if (is()) return FString(std::format("", as().parentType.toString().toBasicString(), static_cast(&as()))); if (is()) { if (visited.contains(this)) { return u8"[...]"; } visited.insert(this); FString output(u8"["); const List &list = as(); bool first_flag = true; for (auto &ele : list) { if (!first_flag) output += u8", "; output += ele.value->toString(visited); first_flag = false; } output += u8"]"; return output; } if (is()) { if (visited.contains(this)) { return u8"{...}"; } visited.insert(this); FString output(u8"{"); const Map &map = as(); bool first_flag = true; for (auto &[key, value] : map) { if (!first_flag) output += u8", "; output += key.value->toString(visited) + FString(u8" : ") + value->toString(visited); first_flag = false; } output += u8"}"; return output; } if (is()) { return FString(std::format("", as().name.toBasicString(), static_cast(&as()))); } if (is()) { return FString(std::format("", as().type.toString().toBasicString(), static_cast(&as()))); } return FString(u8""); } FString toString() const { std::unordered_set visited{}; return toString(visited); } 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(FString(makeTypeErrorMessage("Cannot add", "+", lhs, rhs))); if (lhs.isNumeric() && rhs.isNumeric()) { bool bothInt = lhs.is() && rhs.is(); auto result = lhs.getNumericValue() + rhs.getNumericValue(); if (bothInt) return Object(static_cast(result)); return Object(result); } if (lhs.is() && rhs.is()) return Object(FString(lhs.as() + rhs.as())); throw ValueError(FString(makeTypeErrorMessage("Unsupported operation", "+", lhs, rhs))); } friend Object operator-(const Object &lhs, const Object &rhs) { if (lhs.isNull() || rhs.isNull()) throw ValueError(FString(makeTypeErrorMessage("Cannot subtract", "-", lhs, rhs))); if (lhs.isNumeric() && rhs.isNumeric()) { bool bothInt = lhs.is() && rhs.is(); auto result = lhs.getNumericValue() - rhs.getNumericValue(); if (bothInt) return Object(static_cast(result)); return Object(result); } throw ValueError(FString(makeTypeErrorMessage("Unsupported operation", "-", lhs, rhs))); } friend Object operator*(const Object &lhs, const Object &rhs) { if (lhs.isNull() || rhs.isNull()) throw ValueError(FString(makeTypeErrorMessage("Cannot multiply", "*", lhs, rhs))); if (lhs.isNumeric() && rhs.isNumeric()) { bool bothInt = lhs.is() && rhs.is(); auto result = lhs.getNumericValue() * rhs.getNumericValue(); if (bothInt) return Object(static_cast(result)); return Object(result); } if (lhs.is() && rhs.is()) { FString result; const FString &l = lhs.as(); for (size_t i = 0; i < rhs.getNumericValue(); ++i) { result += l; } return Object(result); } throw ValueError(FString(makeTypeErrorMessage("Unsupported operation", "*", lhs, rhs))); } friend Object operator/(const Object &lhs, const Object &rhs) { if (lhs.isNull() || rhs.isNull()) throw ValueError(FString(makeTypeErrorMessage("Cannot divide", "/", lhs, rhs))); if (lhs.isNumeric() && rhs.isNumeric()) { auto rnv = rhs.getNumericValue(); if (rnv == 0) throw ValueError(FString(makeTypeErrorMessage("Division by zero", "/", lhs, rhs))); // bool bothInt = lhs.is() && rhs.is(); auto result = lhs.getNumericValue() / rnv; // if (bothInt) // return Object(static_cast(result)); // int / int maybe decimals // DO NOT convert it to INT return Object(result); } throw ValueError(FString(makeTypeErrorMessage("Unsupported operation", "/", lhs, rhs))); } friend Object operator%(const Object &lhs, const Object &rhs) { if (lhs.isNull() || rhs.isNull()) throw ValueError(FString(makeTypeErrorMessage("Cannot modulo", "%", lhs, rhs))); if (lhs.is() && rhs.is()) { ValueType::IntClass lv = lhs.as(); ValueType::IntClass rv = lhs.as(); if (rv == 0) throw ValueError(FString(makeTypeErrorMessage("Modulo by zero", "/", lhs, rhs))); ValueType::IntClass q = lv / rv; ValueType::IntClass r = lv % rv; if (r != 0 && ((lv < 0) != (rv < 0))) { q -= 1; } return q; } if (lhs.isNumeric() && rhs.isNumeric()) { auto rnv = rhs.getNumericValue(); if (rnv == 0) throw ValueError(FString(makeTypeErrorMessage("Modulo by zero", "/", lhs, rhs))); auto result = std::fmod(lhs.getNumericValue(), rnv); return Object(result); } throw ValueError(FString(makeTypeErrorMessage("Unsupported operation", "%", lhs, rhs))); } // logic friend Object operator&&(const Object &lhs, const Object &rhs) { if (!lhs.is() || !rhs.is()) throw ValueError(FString(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(FString(makeTypeErrorMessage("Logical OR requires bool", "||", lhs, rhs))); return Object(lhs.as() || rhs.as()); } friend Object operator!(const Object &v) { if (!v.is()) throw ValueError( FString(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(FString(u8"Unary minus cannot be applied to null")); if (v.is()) return Object(-v.as()); if (v.is()) return Object(-v.as()); throw ValueError( FString(std::format("Unary minus requires int or double: '{}'", v.getTypeInfo().name.toBasicString()))); } friend Object operator~(const Object &v) { if (!v.is()) throw ValueError( FString(std::format("Bitwise NOT requires int: '{}'", v.getTypeInfo().name.toBasicString()))); return Object(~v.as()); } // comparison friend bool operator==(const Object &lhs, const Object &rhs) { if (lhs.isNumeric() && rhs.isNumeric()) { return nearlyEqual(lhs.getNumericValue(), rhs.getNumericValue()); } 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(FString(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(FString(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(FString(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(FString(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(FString(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( FString(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(FString(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(FString(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(FString(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) return Object(static_cast(result)); return Object(result); } throw ValueError(FString(makeTypeErrorMessage("Unsupported operation", "**", base, exp))); } }; inline bool isBoolObjectTruthy(ObjectPtr obj) { assert(obj->is()); return obj->as(); } using RvObject = ObjectPtr; inline bool operator==(const ValueKey &l, const ValueKey &r) { return *l.value == *r.value; } } // namespace Fig