mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-07-31 08:46:30 +08:00
Fix #201: Add class inheritance support with Derived : Base syntax
Classes can now inherit methods and attributes from a base class using
C++-style syntax: `class Derived : Base { ... }`. Base class methods and
attributes are automatically available on derived objects. Derived classes
can override base methods by defining a method with the same name.
Inheritance relationships are tracked to support proper type matching
in the dispatch system.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2eb3279c39
commit
1334e02900
@ -44,6 +44,28 @@ namespace chaiscript {
|
||||
|
||||
Dynamic_Object() = default;
|
||||
|
||||
/// Register that derived_name inherits from base_name
|
||||
static void register_inheritance(const std::string &derived_name, const std::string &base_name) {
|
||||
inheritance_map()[derived_name] = base_name;
|
||||
}
|
||||
|
||||
/// Check if type_name is, or inherits from, base_name
|
||||
static bool type_matches(const std::string &type_name, const std::string &base_name) noexcept {
|
||||
if (type_name == base_name) {
|
||||
return true;
|
||||
}
|
||||
const auto &m = inheritance_map();
|
||||
auto it = m.find(type_name);
|
||||
while (it != m.end()) {
|
||||
if (it->second == base_name) {
|
||||
return true;
|
||||
}
|
||||
it = m.find(it->second);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool is_explicit() const noexcept { return m_option_explicit; }
|
||||
|
||||
void set_explicit(const bool t_explicit) noexcept { m_option_explicit = t_explicit; }
|
||||
@ -87,6 +109,11 @@ namespace chaiscript {
|
||||
std::map<std::string, Boxed_Value> get_attrs() const { return m_attrs; }
|
||||
|
||||
private:
|
||||
static std::map<std::string, std::string> &inheritance_map() {
|
||||
static std::map<std::string, std::string> s_map;
|
||||
return s_map;
|
||||
}
|
||||
|
||||
const std::string m_type_name = "";
|
||||
bool m_option_explicit = false;
|
||||
|
||||
|
||||
@ -72,6 +72,8 @@ namespace chaiscript {
|
||||
|
||||
bool is_attribute_function() const noexcept override { return m_is_attribute; }
|
||||
|
||||
const std::string &get_dynamic_object_type_name() const noexcept { return m_type_name; }
|
||||
|
||||
bool call_match(const chaiscript::Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept override {
|
||||
if (dynamic_object_typename_match(vals, m_type_name, m_ti, t_conversions)) {
|
||||
return m_func->call_match(vals, t_conversions);
|
||||
|
||||
@ -120,7 +120,7 @@ namespace chaiscript {
|
||||
if (bv.get_type_info().bare_equal(dynamic_object_type_info)) {
|
||||
try {
|
||||
const Dynamic_Object &d = boxed_cast<const Dynamic_Object &>(bv, &t_conversions);
|
||||
if (!(name == "Dynamic_Object" || d.get_type_name() == name)) {
|
||||
if (!(name == "Dynamic_Object" || Dynamic_Object::type_matches(d.get_type_name(), name))) {
|
||||
return std::make_pair(false, false);
|
||||
}
|
||||
} catch (const std::bad_cast &) {
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -827,14 +828,70 @@ namespace chaiscript {
|
||||
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
|
||||
chaiscript::eval::detail::Scope_Push_Pop spp(t_ss);
|
||||
|
||||
const auto &class_name = this->children[0]->text;
|
||||
|
||||
/// \todo do this better
|
||||
// put class name in current scope so it can be looked up by the attrs and methods
|
||||
t_ss.add_object("_current_class_name", const_var(this->children[0]->text));
|
||||
t_ss.add_object("_current_class_name", const_var(class_name));
|
||||
|
||||
this->children[1]->eval(t_ss);
|
||||
const bool has_base_class = (this->children.size() == 3);
|
||||
const auto &block = has_base_class ? this->children[2] : this->children[1];
|
||||
|
||||
// Evaluate the class block first (registers derived-specific methods/attrs)
|
||||
block->eval(t_ss);
|
||||
|
||||
if (has_base_class) {
|
||||
const auto &base_name = this->children[1]->text;
|
||||
dispatch::Dynamic_Object::register_inheritance(class_name, base_name);
|
||||
copy_base_functions(*t_ss, base_name, class_name);
|
||||
}
|
||||
|
||||
return void_var();
|
||||
}
|
||||
|
||||
private:
|
||||
/// Collect the set of method/attr names already defined for a given dynamic object type
|
||||
static std::set<std::string> get_defined_method_names(chaiscript::detail::Dispatch_Engine &t_engine,
|
||||
const std::string &type_name) {
|
||||
std::set<std::string> names;
|
||||
for (const auto &[func_name, func] : t_engine.get_functions()) {
|
||||
const auto *dof = dynamic_cast<const dispatch::detail::Dynamic_Object_Function *>(func.get());
|
||||
if (dof && dof->get_dynamic_object_type_name() == type_name) {
|
||||
names.insert(func_name);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/// Copy base class methods and attributes to derived class, skipping overrides
|
||||
static void copy_base_functions(chaiscript::detail::Dispatch_Engine &t_engine,
|
||||
const std::string &base_name,
|
||||
const std::string &derived_name) {
|
||||
const auto derived_methods = get_defined_method_names(t_engine, derived_name);
|
||||
const auto functions = t_engine.get_functions();
|
||||
|
||||
for (const auto &[func_name, func] : functions) {
|
||||
const auto *dof = dynamic_cast<const dispatch::detail::Dynamic_Object_Function *>(func.get());
|
||||
if (dof && dof->get_dynamic_object_type_name() == base_name) {
|
||||
// Skip if derived class already defines this method/attr
|
||||
if (derived_methods.count(func_name) > 0) {
|
||||
continue;
|
||||
}
|
||||
auto contained = dof->get_contained_functions();
|
||||
if (!contained.empty()) {
|
||||
try {
|
||||
t_engine.add(std::make_shared<dispatch::detail::Dynamic_Object_Function>(
|
||||
derived_name,
|
||||
std::const_pointer_cast<dispatch::Proxy_Function_Base>(contained[0]),
|
||||
dof->is_attribute_function()),
|
||||
func_name);
|
||||
} catch (const chaiscript::exception::name_conflict_error &) {
|
||||
// already registered, skip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
||||
@ -1908,6 +1908,13 @@ namespace chaiscript {
|
||||
|
||||
const auto class_name = m_match_stack.back()->text;
|
||||
|
||||
// Optionally parse ': BaseClassName' for inheritance
|
||||
if (Char(':')) {
|
||||
if (!Id(true)) {
|
||||
throw exception::eval_error("Missing base class name in definition", File_Position(m_position.line, m_position.col), *m_filename);
|
||||
}
|
||||
}
|
||||
|
||||
while (Eol()) {
|
||||
}
|
||||
|
||||
|
||||
60
unittests/class_inheritance.chai
Normal file
60
unittests/class_inheritance.chai
Normal file
@ -0,0 +1,60 @@
|
||||
|
||||
class Base
|
||||
{
|
||||
attr x
|
||||
|
||||
def Base()
|
||||
{
|
||||
this.x = 10
|
||||
}
|
||||
|
||||
def do_something()
|
||||
{
|
||||
return this.x * 2
|
||||
}
|
||||
}
|
||||
|
||||
class Derived : Base
|
||||
{
|
||||
attr y
|
||||
|
||||
def Derived()
|
||||
{
|
||||
this.x = 10
|
||||
this.y = 20
|
||||
}
|
||||
|
||||
def do_other()
|
||||
{
|
||||
return this.y * 3
|
||||
}
|
||||
}
|
||||
|
||||
// Test basic inheritance - derived can call base methods
|
||||
auto d = Derived()
|
||||
assert_equal(10, d.x)
|
||||
assert_equal(20, d.y)
|
||||
assert_equal(20, d.do_something())
|
||||
assert_equal(60, d.do_other())
|
||||
|
||||
// Test base class still works independently
|
||||
auto b = Base()
|
||||
assert_equal(10, b.x)
|
||||
assert_equal(20, b.do_something())
|
||||
|
||||
// Test method override
|
||||
class Derived2 : Base
|
||||
{
|
||||
def Derived2()
|
||||
{
|
||||
this.x = 5
|
||||
}
|
||||
|
||||
def do_something()
|
||||
{
|
||||
return this.x * 100
|
||||
}
|
||||
}
|
||||
|
||||
auto d2 = Derived2()
|
||||
assert_equal(500, d2.do_something())
|
||||
Loading…
x
Reference in New Issue
Block a user