From 220ff5a47595d93e1d453d85cb430a5587940848 Mon Sep 17 00:00:00 2001 From: ThePhD Date: Tue, 7 Jun 2016 20:32:10 -0400 Subject: [PATCH] Fixes the key value being left on the stack when using iterators (the other case that's not "and we're not actually using this for iteration", asides from the empty table case). Closes #111 - fixed Closes #110 - need to test exactly how much extra speed was gained Closes #108 - seems to be fixed, albeit std::mutex is a butt on VC++ --- sol/call.hpp | 78 +++++++++++++--------- sol/debug.hpp | 2 +- sol/stack_core.hpp | 4 +- sol/stack_field.hpp | 40 ++++++------ sol/stack_push.hpp | 12 ++-- sol/state_view.hpp | 10 ++- sol/table_core.hpp | 20 ++++++ sol/table_iterator.hpp | 14 ++-- sol/usertype_metatable.hpp | 23 ++++++- sol/wrapper.hpp | 4 +- test_tables.cpp | 25 +++++++ test_usertypes.cpp | 130 +++++++++++++++++++++++++++++++++++++ 12 files changed, 293 insertions(+), 69 deletions(-) diff --git a/sol/call.hpp b/sol/call.hpp index ba010637..1a2d397c 100644 --- a/sol/call.hpp +++ b/sol/call.hpp @@ -85,7 +85,6 @@ namespace call_detail { typedef meta::tuple_types return_types; typedef typename traits::free_args_list args_list; typedef typename args_list::indices args_indices; - int farity = traits::free_arity; // compile-time eliminate any functions that we know ahead of time are of improper arity if (meta::find_in_pack_v, index_value...>::value) { return overload_match_arity(types(), std::index_sequence(), std::index_sequence(), std::forward(matchfx), L, fxarity, start, std::forward(args)...); @@ -150,14 +149,16 @@ namespace call_detail { typedef wrapper> wrap; typedef typename wrap::returns_list returns_list; typedef typename wrap::free_args_list args_list; - return stack::call_into_lua(returns_list(), args_list(), L, is_index ? 2 : 3, wrap::caller(), f); + typedef typename wrap::caller caller; + return stack::call_into_lua(returns_list(), args_list(), L, is_index ? 2 : 3, caller(), f); } static int var_call(std::false_type, lua_State* L, F f) { typedef wrapper> wrap; typedef typename wrap::free_args_list args_list; typedef typename wrap::returns_list returns_list; - return stack::call_into_lua(returns_list(), args_list(), L, 1, wrap::caller(), f); + typedef typename wrap::caller caller; + return stack::call_into_lua(returns_list(), args_list(), L, 1, caller(), f); } static int call(lua_State* L, F f) { @@ -171,20 +172,36 @@ namespace call_detail { typedef wrapper> wrap; typedef typename wrap::returns_list returns_list; typedef typename wrap::args_list args_list; + typedef typename wrap::caller caller; + #ifdef SOL_SAFE_USERTYPE typedef typename wrap::object_type object_type; object_type* o = stack::get(L, 1); if (o == nullptr) { return luaL_error(L, "sol: received null for 'self' argument (use ':' for accessing member functions, make sure member variables are preceeded by the actual object with '.' syntax)"); } - return stack::call_into_lua(returns_list(), args_list(), L, is_variable ? 3 : 2, wrap::caller(), f, *o); + return stack::call_into_lua(returns_list(), args_list(), L, is_variable ? 3 : 2, caller(), f, *o); #else object_type& o = stack::get(L, 1); - return stack::call_into_lua(returns_list(), args_list(), L, is_variable ? 3 : 2, wrap::caller(), f); + return stack::call_into_lua(returns_list(), args_list(), L, is_variable ? 3 : 2, caller(), f); #endif // Safety } }; + template + struct agnostic_lua_call_wrapper { + static int call(lua_State* L, lua_r_CFunction f) { + return f(L); + } + }; + + template + struct agnostic_lua_call_wrapper { + static int call(lua_State* L, lua_CFunction f) { + return f(L); + } + }; + template struct agnostic_lua_call_wrapper { static int call(lua_State* L, no_prop) { @@ -200,6 +217,7 @@ namespace call_detail { typedef wrapper> wrap; typedef typename wrap::args_list args_list; typedef typename wrap::object_type object_type; + typedef typename wrap::caller caller; #ifdef SOL_SAFE_USERTYPE object_type* o = stack::get(L, 1); if (o == nullptr) { @@ -208,14 +226,14 @@ namespace call_detail { } return luaL_error(L, "sol: received nil for 'self' argument (pass 'self' as first argument)"); } - return stack::call_into_lua(types(), args_list(), L, is_variable ? 3 : 2, wrap::caller(), f, *o); + return stack::call_into_lua(types(), args_list(), L, is_variable ? 3 : 2, caller(), f, *o); #else object_type& o = stack::get(L, 1); - return stack::call_into_lua(types(), args_list(), L, is_variable ? 3 : 2, wrap::caller(), f, o); + return stack::call_into_lua(types(), args_list(), L, is_variable ? 3 : 2, caller(), f, o); #endif // Safety } - static int call_assign(std::false_type, lua_State* L, F f) { + static int call_assign(std::false_type, lua_State* L, F) { return luaL_error(L, "sol: cannot write to this variable: copy assignment/constructor not available"); } @@ -224,7 +242,7 @@ namespace call_detail { return call_assign(std::is_assignable>, R>(), L, f); } - static int call_const(std::true_type, lua_State* L, F f) { + static int call_const(std::true_type, lua_State* L, F) { return luaL_error(L, "sol: cannot write to a readonly (const) variable"); } @@ -241,6 +259,7 @@ namespace call_detail { typedef wrapper> wrap; typedef typename wrap::object_type object_type; typedef typename wrap::returns_list returns_list; + typedef typename wrap::caller caller; #ifdef SOL_SAFE_USERTYPE object_type* o = stack::get(L, 1); if (o == nullptr) { @@ -249,30 +268,14 @@ namespace call_detail { } return luaL_error(L, "sol: 'self' argument is nil (pass 'self' as first argument)"); } - return stack::call_into_lua(returns_list(), types<>(), L, is_variable ? 3 : 2, wrap::caller(), f, *o); + return stack::call_into_lua(returns_list(), types<>(), L, is_variable ? 3 : 2, caller(), f, *o); #else object_type& o = stack::get(L, 1); - return stack::call_into_lua(returns_list(), types<>(), L, is_variable ? 3 : 2, wrap::caller(), f, o); + return stack::call_into_lua(returns_list(), types<>(), L, is_variable ? 3 : 2, caller(), f, o); #endif // Safety } }; - template - struct agnostic_lua_call_wrapper, is_index, is_variable, C> { - typedef sol::overload_set F; - - template - static int select_call(sol::types, sol::index_value, sol::types r, sol::types a, lua_State* L, int, int start, F& fx) { - auto& f = std::get(fx.set); - return agnostic_lua_call_wrapper{}.call(L, f); - } - - static int call(lua_State* L, F& fx) { - auto mfx = [&](auto&&... args) { return select_call(std::forward(args)...); }; - return overload_match_arity(mfx, L, lua_gettop(L), 1, fx); - } - }; - template struct agnostic_lua_call_wrapper { static int call(lua_State* L, no_construction&) { @@ -325,7 +328,7 @@ namespace call_detail { struct lua_call_wrapper, is_index, is_variable, C> { typedef sol::constructor_wrapper F; - struct matchfx { + struct onmatch { template int operator()(types, index_value, types r, types a, lua_State* L, int, int start, F& f) { T** pointerpointer = reinterpret_cast(lua_newuserdata(L, sizeof(T*) + sizeof(T))); @@ -355,7 +358,7 @@ namespace call_detail { call_syntax syntax = stack::get_call_syntax(L, usertype_traits::metatable); int syntaxval = static_cast(syntax); int argcount = lua_gettop(L) - syntaxval; - return construct>...>(matchfx{}, L, argcount, 1 + syntaxval, f); + return construct>...>(onmatch(), L, argcount, 1 + syntaxval, f); } }; @@ -380,6 +383,23 @@ namespace call_detail { } }; + template + struct lua_call_wrapper, is_index, is_variable, C> { + typedef overload_set F; + + struct on_match { + template + int operator()(types, index_value, types, types, lua_State* L, int, int, F& fx) { + auto& f = std::get(fx.set); + return lua_call_wrapper{}.call(L, f); + } + }; + + static int call(lua_State* L, F& fx) { + return overload_match_arity(on_match(), L, lua_gettop(L), 1, fx); + } + }; + template int call_wrapped(lua_State* L, Fx&& fx) { return lua_call_wrapper, is_index, is_variable>{}.call(L, std::forward(fx)); diff --git a/sol/debug.hpp b/sol/debug.hpp index 3d33e867..061792f6 100644 --- a/sol/debug.hpp +++ b/sol/debug.hpp @@ -35,7 +35,7 @@ inline std::string dump_types(lua_State* L) { if(i != 1) { visual += " | "; } - visual += type_name(L, stack::get(L, i)); + visual += type_name(L, stack::get(L, static_cast(i))); } return visual; } diff --git a/sol/stack_core.hpp b/sol/stack_core.hpp index b27224cf..813b04e3 100644 --- a/sol/stack_core.hpp +++ b/sol/stack_core.hpp @@ -223,12 +223,12 @@ inline decltype(auto) pop(lua_State* L) { return popper>{}.pop(L); } -template +template void get_field(lua_State* L, Key&& key) { field_getter, global, raw>{}.get(L, std::forward(key)); } -template +template void get_field(lua_State* L, Key&& key, int tableindex) { field_getter, global, raw>{}.get(L, std::forward(key), tableindex); } diff --git a/sol/stack_field.hpp b/sol/stack_field.hpp index 1081e27e..bc274505 100644 --- a/sol/stack_field.hpp +++ b/sol/stack_field.hpp @@ -213,32 +213,32 @@ struct field_setter { template struct field_setter, b, raw, C> { - template - void apply(std::index_sequence, lua_State* L, Key&& keys, Value&& value, int tableindex) { - I < 1 ? - set_field(L, detail::forward_get(keys), std::forward(value), tableindex) : - set_field(L, detail::forward_get(keys), std::forward(value)); - } + template + void apply(std::index_sequence, lua_State* L, Key&& keys, Value&& value, int tableindex) { + I < 1 ? + set_field(L, detail::forward_get(keys), std::forward(value), tableindex) : + set_field(L, detail::forward_get(keys), std::forward(value)); + } - template - void apply(std::index_sequence, lua_State* L, Keys&& keys, Value&& value, int tableindex) { - I0 < 1 ? get_field(L, detail::forward_get(keys), tableindex) : get_field(L, detail::forward_get(keys), -1); - apply(std::index_sequence(), L, std::forward(keys), std::forward(value), -1); - } + template + void apply(std::index_sequence, lua_State* L, Keys&& keys, Value&& value, int tableindex) { + I0 < 1 ? get_field(L, detail::forward_get(keys), tableindex) : get_field(L, detail::forward_get(keys), -1); + apply(std::index_sequence(), L, std::forward(keys), std::forward(value), -1); + } - template - void set(lua_State* L, Keys&& keys, Value&& value, int tableindex = -3) { - apply(std::make_index_sequence(), L, std::forward(keys), std::forward(value), tableindex); - } + template + void set(lua_State* L, Keys&& keys, Value&& value, int tableindex = -3) { + apply(std::make_index_sequence(), L, std::forward(keys), std::forward(value), tableindex); + } }; template struct field_setter, b, raw, C> { - template - void set(lua_State* L, Keys&& keys, Value&& value, int tableindex = -1) { - get_field(L, detail::forward_get<0>(keys), tableindex); - set_field(L, detail::forward_get<1>(keys), std::forward(value)); - } + template + void set(lua_State* L, Keys&& keys, Value&& value, int tableindex = -1) { + get_field(L, detail::forward_get<0>(keys), tableindex); + set_field(L, detail::forward_get<1>(keys), std::forward(value)); + } }; } // stack } // sol diff --git a/sol/stack_push.hpp b/sol/stack_push.hpp index e3b88a5b..5159fbba 100644 --- a/sol/stack_push.hpp +++ b/sol/stack_push.hpp @@ -32,7 +32,7 @@ namespace stack { template struct pusher { template - static int push_keyed(lua_State* L, metatable_registry_key k, Args&&... args) { + static int push_keyed(lua_State* L, K&& k, Args&&... args) { // Basically, we store all user-data like this: // If it's a movable/copyable value (no std::ref(x)), then we store the pointer to the new // data in the first sizeof(T*) bytes, and then however many bytes it takes to @@ -44,32 +44,32 @@ struct pusher { referencereference = allocationtarget; std::allocator alloc{}; alloc.construct(allocationtarget, std::forward(args)...); - luaL_newmetatable(L, &k.key[0]); + luaL_newmetatable(L, &k[0]); lua_setmetatable(L, -2); return 1; } template static int push(lua_State* L, Args&&... args) { - return push_keyed(L, meta_registry_key(&usertype_traits::metatable[0]), std::forward(args)...); + return push_keyed(L, usertype_traits::metatable, std::forward(args)...); } }; template struct pusher { template - static int push_keyed(lua_State* L, metatable_registry_key k, T* obj) { + static int push_keyed(lua_State* L, K&& k, T* obj) { if (obj == nullptr) return stack::push(L, nil); T** pref = static_cast(lua_newuserdata(L, sizeof(T*))); *pref = obj; - luaL_getmetatable(L, &k.key[0]); + luaL_getmetatable(L, &k[0]); lua_setmetatable(L, -2); return 1; } static int push(lua_State* L, T* obj) { - return push_keyed(L, meta_registry_key(&usertype_traits::metatable[0]), obj); + return push_keyed(L, usertype_traits::metatable, obj); } }; diff --git a/sol/state_view.hpp b/sol/state_view.hpp index 523d8842..3a9c4eff 100644 --- a/sol/state_view.hpp +++ b/sol/state_view.hpp @@ -329,10 +329,16 @@ public: return *this; } + template + state_view& new_enum(const std::string& name, Args&&... args) { + global.new_enum(name, std::forward(args)...); + return *this; + } + template state_view& new_usertype(const std::string& name, constructors ctor, Args&&... args) { - global.new_usertype(name, ctor, std::forward(args)...); - return *this; + global.new_usertype(name, ctor, std::forward(args)...); + return *this; } template diff --git a/sol/table_core.hpp b/sol/table_core.hpp index 0bce4127..0cc8967d 100644 --- a/sol/table_core.hpp +++ b/sol/table_core.hpp @@ -33,6 +33,9 @@ namespace detail { template struct clean { lua_State* L; clean(lua_State* L) : L(L) {} ~clean() { lua_pop(L, static_cast(n)); } }; struct ref_clean { lua_State* L; int& n; ref_clean(lua_State* L, int& n) : L(L), n(n) {} ~ref_clean() { lua_pop(L, static_cast(n)); } }; +inline int fail_on_newindex(lua_State* L) { + return luaL_error(L, "sol: cannot modify the elements of an enumeration table"); +} } template @@ -269,6 +272,23 @@ public: return *this; } + template + basic_table_core& new_enum(const std::string& name, Args&&... args) { + if (read_only) { + table idx = create_with(std::forward(args)...); + table x = create_with( + meta_function::new_index, detail::fail_on_newindex, + meta_function::index, idx + ); + table target = create_named(name); + target[metatable_key] = x; + } + else { + create_named(name, std::forward(args)... ); + } + return *this; + } + template void for_each( Fx&& fx ) const { typedef meta::is_invokable )> is_paired; diff --git a/sol/table_iterator.hpp b/sol/table_iterator.hpp index 25eef1cf..9742aff1 100644 --- a/sol/table_iterator.hpp +++ b/sol/table_iterator.hpp @@ -45,11 +45,12 @@ private: std::pair kvp; reference_type ref; int tableidx = 0; + int keyidx = 0; std::ptrdiff_t idx = 0; public: - basic_table_iterator () : idx(-1) { + basic_table_iterator () : idx(-1), keyidx(-1) { } @@ -70,6 +71,7 @@ public: if (lua_next(ref.lua_state(), tableidx) == 0) { idx = -1; + keyidx = -1; return *this; } ++idx; @@ -77,6 +79,7 @@ public: kvp.second = object(ref.lua_state(), -1); lua_pop(ref.lua_state(), 1); // leave key on the stack + keyidx = lua_gettop(ref.lua_state()); return *this; } @@ -103,9 +106,12 @@ public: } ~basic_table_iterator() { - if (ref.valid()) { - stack::remove(ref.lua_state(), tableidx, 1); - } + if (keyidx != -1) { + stack::remove(ref.lua_state(), keyidx, 1); + } + if (ref.valid()) { + stack::remove(ref.lua_state(), tableidx, 1); + } } }; diff --git a/sol/usertype_metatable.hpp b/sol/usertype_metatable.hpp index 606f0973..4681a347 100644 --- a/sol/usertype_metatable.hpp +++ b/sol/usertype_metatable.hpp @@ -125,6 +125,23 @@ namespace sol { l[index] = { name_of(meta_function::garbage_collect).c_str(), destructfunc }; ++index; } + if (baseclasscast != nullptr) + return index; +#ifndef SOL_NO_EXCEPTIONS + static_assert(sizeof(void*) <= sizeof(detail::throw_cast), "The size of this data pointer is too small to fit the inheritance checking function: file a bug report."); + baseclasscheck = baseclasscast = (void*)&detail::throw_as; +#elif !defined(SOL_NO_RTTI) + static_assert(sizeof(void*) <= sizeof(detail::inheritance_check_function), "The size of this data pointer is too small to fit the inheritance checking function: file a bug report."); + static_assert(sizeof(void*) <= sizeof(detail::inheritance_cast_function), "The size of this data pointer is too small to fit the inheritance checking function: file a bug report."); + baseclasscheck = (void*)&detail::inheritance::type_check; + baseclasscast = (void*)&detail::inheritance::type_cast; +#else + static_assert(sizeof(void*) <= sizeof(detail::inheritance_check_function), "The size of this data pointer is too small to fit the inheritance checking function: file a bug report."); + static_assert(sizeof(void*) <= sizeof(detail::inheritance_cast_function), "The size of this data pointer is too small to fit the inheritance checking function: file a bug report."); + baseclasscheck = (void*)&detail::inheritance::type_check; + baseclasscast = (void*)&detail::inheritance::type_cast; +#endif // No Runtime Type Information vs. Throw-Style Inheritance + return index; } @@ -143,7 +160,7 @@ namespace sol { return endindex; #ifndef SOL_NO_EXCEPTIONS static_assert(sizeof(void*) <= sizeof(detail::throw_cast), "The size of this data pointer is too small to fit the inheritance checking function: file a bug report."); - baseclasscast = (void*)&detail::throw_as; + baseclasscheck = baseclasscast = (void*)&detail::throw_as; #elif !defined(SOL_NO_RTTI) static_assert(sizeof(void*) <= sizeof(detail::inheritance_check_function), "The size of this data pointer is too small to fit the inheritance checking function: file a bug report."); static_assert(sizeof(void*) <= sizeof(detail::inheritance_cast_function), "The size of this data pointer is too small to fit the inheritance checking function: file a bug report."); @@ -186,7 +203,7 @@ namespace sol { } template - int find_call(std::integral_constant, std::index_sequence<>, lua_State* L, const sol::string_detail::string_shim& accessor) { + int find_call(std::integral_constant, std::index_sequence<>, lua_State* L, const sol::string_detail::string_shim&) { if (is_index) return indexfunc(L); else @@ -308,7 +325,7 @@ namespace sol { lua_createtable(L, 0, 1); stack_reference metabehind(L, -1); if (um.callconstructfunc != nullptr) { - stack::set_field(L, sol::meta_function::call_function, um.callconstructfunc, metabehind.stack_index()); + stack::set_field(L, sol::meta_function::call_function, make_closure(um.callconstructfunc, make_light(um)), metabehind.stack_index()); } stack::set_field(L, metatable_key, metabehind, t.stack_index()); metabehind.pop(); diff --git a/sol/wrapper.hpp b/sol/wrapper.hpp index 55a70be4..df637263 100644 --- a/sol/wrapper.hpp +++ b/sol/wrapper.hpp @@ -112,12 +112,12 @@ namespace sol { typedef meta::tuple_types returns_list; template - static R invoke(O& mem, Args... args) { + static R invoke(O& mem, Args&&... args) { return (mem.*fx)(std::forward(args)...); } template - static R call(Fx&& fx, O& mem, Args... args) { + static R call(Fx&& fx, O& mem, Args&&... args) { return (mem.*fx)(std::forward(args)...); } diff --git a/test_tables.cpp b/test_tables.cpp index 0e58d0c2..e6eba074 100644 --- a/test_tables.cpp +++ b/test_tables.cpp @@ -85,6 +85,31 @@ TEST_CASE("tables/as-enum-classes", "Making sure enums can be put in and gotten REQUIRE(dir == direction::up); } +TEST_CASE("tables/new_enum", "Making sure enums can be put in and gotten out as values") { + enum class direction { + up, + down, + left, + right + }; + + sol::state lua; + lua.open_libraries(sol::lib::base); + + lua.new_enum( "direction", + "up", direction::up, + "down", direction::down, + "left", direction::left, + "right", direction::right + ); + + direction d = lua["direction"]["left"]; + REQUIRE(d == direction::left); + REQUIRE_THROWS(lua.script("direction.left = 50")); + d = lua["direction"]["left"]; + REQUIRE(d == direction::left); +} + TEST_CASE("tables/for-each", "Testing the use of for_each to get values from a lua table") { sol::state lua; lua.open_libraries(sol::lib::base); diff --git a/test_usertypes.cpp b/test_usertypes.cpp index 6f27a4d5..2e7f0bbb 100644 --- a/test_usertypes.cpp +++ b/test_usertypes.cpp @@ -174,7 +174,51 @@ struct self_test { } }; +struct ext_getset { + int bark = 24; + const int meow = 56; + + ext_getset() = default; + ext_getset(int v) : bark(v) {} + ext_getset(ext_getset&&) = default; + ext_getset(const ext_getset&) = delete; + ext_getset& operator=(ext_getset&&) = default; + ext_getset& operator=(const ext_getset&) = delete; + ~ext_getset() { + + } + + std::string x() { + return "bark bark bark"; + } + + int x2(std::string x) { + return static_cast(x.length()); + } + + void set(sol::variadic_args, sol::this_state, int x) { + bark = x; + } + + int get(sol::this_state, sol::variadic_args) { + return bark; + } + + static void s_set(int) { + + } + + static int s_get(int x) { + return x + 20; + } + +}; + +template +void des(T& e) { + e.~T(); +} TEST_CASE("usertype/usertype", "Show that we can create classes from usertype and use them") { sol::state lua; @@ -864,3 +908,89 @@ TEST_CASE("usertype/no_constructor", "make sure lua types cannot be constructed REQUIRE_THROWS(lua.script("t = thing.new()")); } +TEST_CASE("usertype/coverage", "try all the things") { + sol::state lua; + lua.open_libraries(sol::lib::base); + + lua.new_usertype("ext_getset", + sol::call_constructor, sol::constructors, sol::types>(), + sol::meta_function::garbage_collect, sol::destructor(des), + "x", sol::overload(&ext_getset::x, &ext_getset::x2, [](ext_getset& m, std::string x, int y) { return m.meow + 50 + y + x.length(); }), + "bark", &ext_getset::bark, + "meow", &ext_getset::meow, + "readonlybark", sol::readonly(&ext_getset::bark), + "set", &ext_getset::set, + "get", &ext_getset::get, + "sset", &ext_getset::s_set, + "sget", &ext_getset::s_get, + "propbark", sol::property(&ext_getset::set, &ext_getset::get), + "readonlypropbark", sol::property(&ext_getset::get), + "writeonlypropbark", sol::property(&ext_getset::set) + ); + + lua.script(R"( +e = ext_getset() +w = e:x(e:x(), e:x(e:x())) +print(w) +)"); + + int w = lua["w"]; + REQUIRE(w == 27); + + lua.script(R"( +e:set(500) +e.sset(24) +x = e:get() +y = e.sget(20) +)"); + + int x = lua["x"]; + int y = lua["y"]; + REQUIRE(x == 500); + REQUIRE(y == 40); + + lua.script(R"( +e.bark = 5001 +a = e:get() +print(e.bark) +print(a) + +e.propbark = 9700 +b = e:get() +print(e.propbark) +print(b) +)"); + int a = lua["a"]; + int b = lua["b"]; + + REQUIRE(a == 5001); + REQUIRE(b == 9700); + + lua.script(R"( +c = e.readonlybark +d = e.meow +print(e.readonlybark) +print(c) +print(e.meow) +print(d) +)"); + + int c = lua["c"]; + int d = lua["d"]; + REQUIRE(a == 5001); + REQUIRE(b == 9700); + + lua.script(R"( +e.writeonlypropbark = 500 +z = e.readonlypropbark +print(e.readonlybark) +print(e.bark) +)"); + + int z = lua["z"]; + REQUIRE(z == 500); + + REQUIRE_THROWS(lua.script("e.readonlybark = 24")); + REQUIRE_THROWS(lua.script("e.readonlypropbark = 500")); + REQUIRE_THROWS(lua.script("y = e.writeonlypropbark")); +} \ No newline at end of file