diff --git a/README.md b/README.md index e310e3c..57c9139 100644 --- a/README.md +++ b/README.md @@ -127,9 +127,6 @@ b.yell(sound: 'Ouch, you stole my HP!', loud: true) a.yell(sound: 'Well, take better care of your public attributes!') ``` -The syntax stays mostly the same as in Crystal, except for the keyword arguments. -These might be added in the future, but technically you can always wrap the generated methods in pure Ruby methods with keywords. - The example above gives a good overview over the things you can already do with Anyolite. More features will be added in the future. @@ -156,6 +153,20 @@ The term 'anyoli' means 'green' in the Maasai language, thus naming 'anyolite'. ## Releases +### Version 0.2.1 + +#### Usability +* Operator suffixes as general optional argument for MrbWrap functions +* Option to inspect reference table +* Reference counting in reference table +* Reference table can be cleared + +#### Bugfixes +* Fixed structs not being able to be wrapped +* Fixed example in documentation +* Fixed memory leak when returning nontrivial objects in mruby +* Removed constructor limitations for types being able to be used as return values + ### Version 0.2.0 #### Major features @@ -223,19 +234,7 @@ The term 'anyoli' means 'green' in the Maasai language, thus naming 'anyolite'. ## Upcoming releases -### Version 0.2.1 - -#### Usability -* [ ] Operator suffixes as general optional argument for MrbWrap functions -* [X] Logging of any reference table operations for debugging -* [X] Reference counting in reference table -* [X] Reference table can be cleared - -#### Bugfixes -* [X] Fixed structs not being able to be wrapped -* [X] Fixed example in documentation -* [X] Fixed memory leak when returning nontrivial objects in mruby -* [X] Removed constructor limitations for types being able to be used as return values +None planned yet ### Future updates @@ -247,6 +246,8 @@ The term 'anyoli' means 'green' in the Maasai language, thus naming 'anyolite'. * [ ] MrbClass/MrbModule and Class can be both used as arguments * [ ] More stable type casting * [ ] More stable struct reference table management +* [ ] Logging options for reference table +* [ ] Crystal specs for testing ### Possible future updates diff --git a/src/MrbCast.cr b/src/MrbCast.cr index da88a7e..5e38f2b 100644 --- a/src/MrbCast.cr +++ b/src/MrbCast.cr @@ -59,8 +59,6 @@ module MrbCast ptr = Pointer(typeof(value)).malloc(size: 1, value: value) - puts "Returning ... #{ptr} -> #{value.to_s}" - new_ruby_object = MrbInternal.new_empty_object(mrb, ruby_class, ptr.as(Void*), MrbTypeCache.register(typeof(value), destructor)) MrbMacro.convert_from_ruby_object(mrb, new_ruby_object, typeof(value)).value = value @@ -70,8 +68,6 @@ module MrbCast MrbRefTable.add(value.hash, ptr.as(Void*)) end - puts "> Added class #{value.class} (cast)" - return new_ruby_object end diff --git a/src/MrbMacro.cr b/src/MrbMacro.cr index 6890670..9c4d85e 100644 --- a/src/MrbMacro.cr +++ b/src/MrbMacro.cr @@ -126,14 +126,14 @@ module MrbMacro ptr.as({{crystal_type}}*) end - macro call_and_return(mrb, proc, proc_args, converted_args) - return_value = {{proc}}(*{{converted_args}}) + macro call_and_return(mrb, proc, proc_args, converted_args, operator = "") + return_value = {{proc}}{{operator.id}}(*{{converted_args}}) MrbCast.return_value({{mrb}}, return_value) end - macro call_and_return_keyword_method(mrb, proc, converted_regular_args, keyword_args, kw_args, empty_regular = false) + macro call_and_return_keyword_method(mrb, proc, converted_regular_args, keyword_args, kw_args, operator = "", empty_regular = false) {% if empty_regular %} - return_value = {{proc}}( + return_value = {{proc}}{{operator.id}}( {% c = 0 %} {% for keyword in keyword_args.keys %} {{keyword.id}}: MrbMacro.convert_keyword_arg({{mrb}}, {{kw_args}}.values[{{c}}], {{keyword_args[keyword]}}), @@ -141,7 +141,7 @@ module MrbMacro {% end %} ) {% else %} - return_value = {{proc}}(*{{converted_regular_args}}, + return_value = {{proc}}{{operator.id}}(*{{converted_regular_args}}, {% c = 0 %} {% for keyword in keyword_args.keys %} {{keyword.id}}: MrbMacro.convert_keyword_arg({{mrb}}, {{kw_args}}.values[{{c}}], {{keyword_args[keyword]}}), @@ -235,7 +235,7 @@ module MrbMacro {{mrb_state}}.define_module_function({{name}}, {{under_module}}, wrapped_method) end - macro wrap_module_function_with_keyword_args(mrb_state, under_module, name, proc, keyword_args, regular_args = [] of Class) + macro wrap_module_function_with_keyword_args(mrb_state, under_module, name, proc, keyword_args, regular_args = [] of Class, operator = "") {% if regular_args.class_name == "ArrayLiteral" %} {% regular_arg_array = regular_args %} {% else %} @@ -252,16 +252,16 @@ module MrbMacro converted_regular_args = MrbMacro.convert_args(mrb, regular_arg_tuple, {{regular_arg_array}}) {% if regular_arg_array.size == 0 %} - MrbMacro.call_and_return_keyword_method(mrb, {{proc}}, converted_regular_args, {{keyword_args}}, kw_args, empty_regular: true) + MrbMacro.call_and_return_keyword_method(mrb, {{proc}}, converted_regular_args, {{keyword_args}}, kw_args, operator: {{operator}}, empty_regular: true) {% else %} - MrbMacro.call_and_return_keyword_method(mrb, {{proc}}, converted_regular_args, {{keyword_args}}, kw_args) + MrbMacro.call_and_return_keyword_method(mrb, {{proc}}, converted_regular_args, {{keyword_args}}, kw_args, operator: {{operator}}) {% end %} end {{mrb_state}}.define_module_function({{name}}, {{under_module}}, wrapped_method) end - macro wrap_class_method_with_args(mrb_state, crystal_class, name, proc, proc_args = [] of Class) + macro wrap_class_method_with_args(mrb_state, crystal_class, name, proc, proc_args = [] of Class, operator = "") {% if proc_args.class_name == "ArrayLiteral" %} {% proc_arg_array = proc_args %} {% else %} @@ -270,13 +270,13 @@ module MrbMacro wrapped_method = MrbFunc.new do |mrb, obj| converted_args = MrbMacro.get_converted_args(mrb, {{proc_arg_array}}) - MrbMacro.call_and_return(mrb, {{proc}}, {{proc_arg_array}}, converted_args) + MrbMacro.call_and_return(mrb, {{proc}}, {{proc_arg_array}}, converted_args, operator: {{operator}}) end {{mrb_state}}.define_class_method({{name}}, MrbClassCache.get({{crystal_class}}), wrapped_method) end - macro wrap_class_method_with_keyword_args(mrb_state, crystal_class, name, proc, keyword_args, regular_args = [] of Class) + macro wrap_class_method_with_keyword_args(mrb_state, crystal_class, name, proc, keyword_args, regular_args = [] of Class, operator = "") {% if regular_args.class_name == "ArrayLiteral" %} {% regular_arg_array = regular_args %} {% else %} @@ -293,9 +293,9 @@ module MrbMacro converted_regular_args = MrbMacro.convert_args(mrb, regular_arg_tuple, {{regular_arg_array}}) {% if regular_arg_array.size == 0 %} - MrbMacro.call_and_return_keyword_method(mrb, {{proc}}, converted_regular_args, {{keyword_args}}, kw_args, empty_regular: true) + MrbMacro.call_and_return_keyword_method(mrb, {{proc}}, converted_regular_args, {{keyword_args}}, kw_args, operator: {{operator}}, empty_regular: true) {% else %} - MrbMacro.call_and_return_keyword_method(mrb, {{proc}}, converted_regular_args, {{keyword_args}}, kw_args) + MrbMacro.call_and_return_keyword_method(mrb, {{proc}}, converted_regular_args, {{keyword_args}}, kw_args, operator: {{operator}}) {% end %} end @@ -312,7 +312,7 @@ module MrbMacro wrapped_method = MrbFunc.new do |mrb, obj| converted_args = MrbMacro.get_converted_args(mrb, {{proc_arg_array}}) converted_obj = MrbMacro.convert_from_ruby_object(mrb, obj, {{crystal_class}}).value - MrbMacro.call_and_return_instance_method(mrb, {{proc}}, converted_obj, converted_args, {{operator}}) + MrbMacro.call_and_return_instance_method(mrb, {{proc}}, converted_obj, converted_args, operator: {{operator}}) end {{mrb_state}}.define_method({{name + operator}}, MrbClassCache.get({{crystal_class}}), wrapped_method) @@ -336,16 +336,16 @@ module MrbMacro converted_obj = MrbMacro.convert_from_ruby_object(mrb, obj, {{crystal_class}}).value {% if regular_arg_array.size == 0 %} - MrbMacro.call_and_return_keyword_instance_method(mrb, {{proc}}, converted_obj, converted_regular_args, {{keyword_args}}, kw_args, {{operator}}, empty_regular: true) + MrbMacro.call_and_return_keyword_instance_method(mrb, {{proc}}, converted_obj, converted_regular_args, {{keyword_args}}, kw_args, operator: {{operator}}, empty_regular: true) {% else %} - MrbMacro.call_and_return_keyword_instance_method(mrb, {{proc}}, converted_obj, converted_regular_args, {{keyword_args}}, kw_args, {{operator}}) + MrbMacro.call_and_return_keyword_instance_method(mrb, {{proc}}, converted_obj, converted_regular_args, {{keyword_args}}, kw_args, operator: {{operator}}) {% end %} end {{mrb_state}}.define_method({{name + operator}}, MrbClassCache.get({{crystal_class}}), wrapped_method) end - macro wrap_constructor_function_with_args(mrb_state, crystal_class, proc, proc_args = [] of Class) + macro wrap_constructor_function_with_args(mrb_state, crystal_class, proc, proc_args = [] of Class, operator = "") {% if proc_args.class_name == "ArrayLiteral" %} {% proc_arg_array = proc_args %} {% else %} @@ -355,7 +355,7 @@ module MrbMacro wrapped_method = MrbFunc.new do |mrb, obj| # Create local object converted_args = MrbMacro.get_converted_args(mrb, {{proc_arg_array}}) - new_obj = {{proc}}(*converted_args) + new_obj = {{proc}}{{operator.id}}(*converted_args) if new_obj.responds_to?(:mrb_initialize) new_obj.mrb_initialize(mrb) @@ -369,8 +369,6 @@ module MrbMacro MrbRefTable.add(new_obj_ptr.value.hash, new_obj_ptr.as(Void*)) end - puts "> Added class #{{{crystal_class}}} (regular): #{new_obj_ptr} -> #{new_obj_ptr.value.to_s}" - destructor = MrbTypeCache.destructor_method({{crystal_class}}) MrbInternal.set_data_ptr_and_type(obj, new_obj_ptr, MrbTypeCache.register({{crystal_class}}, destructor)) @@ -381,7 +379,7 @@ module MrbMacro {{mrb_state}}.define_method("initialize", MrbClassCache.get({{crystal_class}}), wrapped_method) end - macro wrap_constructor_function_with_keyword_args(mrb_state, crystal_class, proc, keyword_args, regular_args = [] of Class) + macro wrap_constructor_function_with_keyword_args(mrb_state, crystal_class, proc, keyword_args, regular_args = [] of Class, operator = "") {% if regular_args.class_name == "ArrayLiteral" %} {% regular_arg_array = regular_args %} {% else %} @@ -398,7 +396,7 @@ module MrbMacro converted_regular_args = MrbMacro.convert_args(mrb, regular_arg_tuple, {{regular_arg_array}}) {% if regular_arg_array.size == 0 %} - new_obj = {{proc}}( + new_obj = {{proc}}{{operator.id}}( {% c = 0 %} {% for keyword in keyword_args.keys %} {{keyword.id}}: MrbMacro.convert_keyword_arg(mrb, kw_args.values[{{c}}], {{keyword_args[keyword]}}), @@ -406,7 +404,7 @@ module MrbMacro {% end %} ) {% else %} - new_obj = {{proc}}(*converted_regular_args, + new_obj = {{proc}}{{operator.id}}(*converted_regular_args, {% c = 0 %} {% for keyword in keyword_args.keys %} {{keyword.id}}: MrbMacro.convert_keyword_arg(mrb, kw_args.values[{{c}}], {{keyword_args[keyword]}}), @@ -427,8 +425,6 @@ module MrbMacro MrbRefTable.add(new_obj_ptr.value.hash, new_obj_ptr.as(Void*)) end - puts "> Added class #{{{crystal_class}}} (keyword): #{new_obj_ptr} -> #{new_obj_ptr.value.to_s}" - destructor = MrbTypeCache.destructor_method({{crystal_class}}) MrbInternal.set_data_ptr_and_type(obj, new_obj_ptr, MrbTypeCache.register({{crystal_class}}, destructor)) diff --git a/src/MrbRefTable.cr b/src/MrbRefTable.cr index 47b249f..924ce30 100644 --- a/src/MrbRefTable.cr +++ b/src/MrbRefTable.cr @@ -12,7 +12,6 @@ module MrbRefTable end def self.add(identification, value) - puts "* Added ref #{identification} -> #{value}" if @@content[identification]? if value != @@content[identification][0] puts "WARNING: Value #{identification} replaced pointers." @@ -24,7 +23,6 @@ module MrbRefTable def self.delete(identification) if @@content[identification]? - puts "* Deleted ref #{identification} -> #{@@content[identification]}" @@content[identification] = {@@content[identification][0], @@content[identification][1] - 1} if @@content[identification][1] <= 0 @@content.delete(identification) diff --git a/src/MrbTypeCache.cr b/src/MrbTypeCache.cr index c9ab732..be61f8f 100644 --- a/src/MrbTypeCache.cr +++ b/src/MrbTypeCache.cr @@ -19,11 +19,8 @@ module MrbTypeCache crystal_value.mrb_finalize(mrb) end - puts "> Destructor for #{crystal_ptr} with value #{crystal_ptr}." - # Delete the Crystal reference to this object MrbRefTable.delete(crystal_ptr.value.object_id) - } end end diff --git a/src/MrbWrap.cr b/src/MrbWrap.cr index ed51cf3..feb5ce4 100644 --- a/src/MrbWrap.cr +++ b/src/MrbWrap.cr @@ -60,8 +60,8 @@ module MrbWrap # # The constructor for the Crystal class *crystal_class* will be integrated into the `MrbState` *mrb_state*, # with the arguments *proc_args* as an `Array of Class`. - macro wrap_constructor(mrb_state, crystal_class, proc_args = [] of Class) - MrbMacro.wrap_constructor_function_with_args({{mrb_state}}, {{crystal_class}}, {{crystal_class}}.new, {{proc_args}}) + macro wrap_constructor(mrb_state, crystal_class, proc_args = [] of Class, operator = "") + MrbMacro.wrap_constructor_function_with_args({{mrb_state}}, {{crystal_class}}, {{crystal_class}}.new, {{proc_args}}, operator: {{operator}}) end # Wraps the constructor of a Crystal class into mruby, using keyword arguments. @@ -69,8 +69,8 @@ module MrbWrap # The constructor for the Crystal class *crystal_class* will be integrated into the `MrbState` *mrb_state*, # with the arguments *regular_args* as an `Array of Class` and *keyword_args* as a `Hash of Symbol => Class`. # Alternatively, a `Tuple` with the `Class` and a default value for the `Class` can be used instead of the `Class`. - macro wrap_constructor_with_keywords(mrb_state, crystal_class, keyword_args, regular_args = [] of Class) - MrbMacro.wrap_constructor_function_with_keyword_args({{mrb_state}}, {{crystal_class}}, {{crystal_class}}.new, {{keyword_args}}, {{regular_args}}) + macro wrap_constructor_with_keywords(mrb_state, crystal_class, keyword_args, regular_args = [] of Class, operator = "") + MrbMacro.wrap_constructor_function_with_keyword_args({{mrb_state}}, {{crystal_class}}, {{crystal_class}}.new, {{keyword_args}}, {{regular_args}}, operator: {{operator}}) end # Wraps a module function into mruby. @@ -79,8 +79,8 @@ module MrbWrap # with the arguments *proc_args* as an `Array of Class`. # # Its new name will be *name*. - macro wrap_module_function(mrb_state, under_module, name, proc, proc_args = [] of Class) - MrbMacro.wrap_module_function_with_args({{mrb_state}}, {{under_module}}, {{name}}, {{proc}}, {{proc_args}}) + macro wrap_module_function(mrb_state, under_module, name, proc, proc_args = [] of Class, operator = "") + MrbMacro.wrap_module_function_with_args({{mrb_state}}, {{under_module}}, {{name}}, {{proc}}, {{proc_args}}, operator: {{operator}}) end # Wraps a module function into mruby, using keyword arguments. @@ -90,8 +90,8 @@ module MrbWrap # Alternatively, a `Tuple` with the `Class` and a default value for the `Class` can be used instead of the `Class`. # # Its new name will be *name*. - macro wrap_module_function_with_keywords(mrb_state, under_module, name, proc, keyword_args, regular_args = [] of Class) - MrbMacro.wrap_module_function_with_keyword_args({{mrb_state}}, {{under_module}}, {{name}}, {{proc}}, {{keyword_args}}, {{regular_args}}) + macro wrap_module_function_with_keywords(mrb_state, under_module, name, proc, keyword_args, regular_args = [] of Class, operator = "") + MrbMacro.wrap_module_function_with_keyword_args({{mrb_state}}, {{under_module}}, {{name}}, {{proc}}, {{keyword_args}}, {{regular_args}}, operator: {{operator}}) end # Wraps a class method into mruby. @@ -100,8 +100,8 @@ module MrbWrap # with the arguments *proc_args* as an `Array of Class`. # # Its new name will be *name*. - macro wrap_class_method(mrb_state, crystal_class, name, proc, proc_args = [] of Class) - MrbMacro.wrap_class_method_with_args({{mrb_state}}, {{crystal_class}}, {{name}}, {{proc}}, {{proc_args}}) + macro wrap_class_method(mrb_state, crystal_class, name, proc, proc_args = [] of Class, operator = "") + MrbMacro.wrap_class_method_with_args({{mrb_state}}, {{crystal_class}}, {{name}}, {{proc}}, {{proc_args}}, operator: {{operator}}) end # Wraps a class method into mruby, using keyword arguments. @@ -111,8 +111,8 @@ module MrbWrap # Alternatively, a `Tuple` with the `Class` and a default value for the `Class` can be used instead of the `Class`. # # Its new name will be *name*. - macro wrap_class_method_with_keywords(mrb_state, crystal_class, name, proc, keyword_args, regular_args = [] of Class) - MrbMacro.wrap_class_method_with_keyword_args({{mrb_state}}, {{crystal_class}}, {{name}}, {{proc}}, {{keyword_args}}, {{regular_args}}) + macro wrap_class_method_with_keywords(mrb_state, crystal_class, name, proc, keyword_args, regular_args = [] of Class, operator = "") + MrbMacro.wrap_class_method_with_keyword_args({{mrb_state}}, {{crystal_class}}, {{name}}, {{proc}}, {{keyword_args}}, {{regular_args}}, operator: {{operator}}) end # Wraps an instance method into mruby. @@ -121,8 +121,8 @@ module MrbWrap # with the arguments *proc_args* as an `Array of Class`. # # Its new name will be *name*. - macro wrap_instance_method(mrb_state, crystal_class, name, proc, proc_args = [] of Class) - MrbMacro.wrap_instance_function_with_args({{mrb_state}}, {{crystal_class}}, {{name}}, {{proc}}, {{proc_args}}) + macro wrap_instance_method(mrb_state, crystal_class, name, proc, proc_args = [] of Class, operator = "") + MrbMacro.wrap_instance_function_with_args({{mrb_state}}, {{crystal_class}}, {{name}}, {{proc}}, {{proc_args}}, operator: {{operator}}) end # Wraps an instance method into mruby, using keyword arguments. @@ -132,8 +132,8 @@ module MrbWrap # Alternatively, a `Tuple` with the `Class` and a default value for the `Class` can be used instead of the `Class`. # # Its new name will be *name*. - macro wrap_instance_method_with_keywords(mrb_state, crystal_class, name, proc, keyword_args, regular_args = [] of Class) - MrbMacro.wrap_instance_function_with_keyword_args({{mrb_state}}, {{crystal_class}}, {{name}}, {{proc}}, {{keyword_args}}, {{regular_args}}) + macro wrap_instance_method_with_keywords(mrb_state, crystal_class, name, proc, keyword_args, regular_args = [] of Class, operator = "") + MrbMacro.wrap_instance_function_with_keyword_args({{mrb_state}}, {{crystal_class}}, {{name}}, {{proc}}, {{keyword_args}}, {{regular_args}}, operator: {{operator}}) end # Wraps a setter into mruby. @@ -142,8 +142,8 @@ module MrbWrap # with the argument *proc_arg* as its respective `Class`. # # Its new name will be *name*. - macro wrap_setter(mrb_state, crystal_class, name, proc, proc_arg) - MrbMacro.wrap_instance_function_with_args({{mrb_state}}, {{crystal_class}}, {{name}}, {{proc}}, {{proc_arg}}, "=") + macro wrap_setter(mrb_state, crystal_class, name, proc, proc_arg, operator = "=") + MrbMacro.wrap_instance_function_with_args({{mrb_state}}, {{crystal_class}}, {{name}}, {{proc}}, {{proc_arg}}, operator: {{operator}}) end # Wraps a getter into mruby. @@ -151,8 +151,8 @@ module MrbWrap # The getter *proc* of the Crystal class *crystal_class* will be integrated into the `MrbState` *mrb_state*. # # Its new name will be *name*. - macro wrap_getter(mrb_state, crystal_class, name, proc) - MrbMacro.wrap_instance_function_with_args({{mrb_state}}, {{crystal_class}}, {{name}}, {{proc}}) + macro wrap_getter(mrb_state, crystal_class, name, proc, operator = "") + MrbMacro.wrap_instance_function_with_args({{mrb_state}}, {{crystal_class}}, {{name}}, {{proc}}, operator: {{operator}}) end # Wraps a property into mruby. @@ -161,9 +161,9 @@ module MrbWrap # with the argument *proc_arg* as its respective `Class`. # # Its new name will be *name*. - macro wrap_property(mrb_state, crystal_class, name, proc, proc_arg) - MrbWrap.wrap_getter({{mrb_state}}, {{crystal_class}}, {{name}}, {{proc}}) - MrbWrap.wrap_setter({{mrb_state}}, {{crystal_class}}, {{name}}, {{proc}}, {{proc_arg}}) + macro wrap_property(mrb_state, crystal_class, name, proc, proc_arg, operator_getter = "", operator_setter = "=") + MrbWrap.wrap_getter({{mrb_state}}, {{crystal_class}}, {{name}}, {{proc}}, operator: {{operator_getter}}) + MrbWrap.wrap_setter({{mrb_state}}, {{crystal_class}}, {{name}}, {{proc}}, {{proc_arg}}, operator: {{operator_setter}}) end # Wraps a constant value into mruby. diff --git a/test.cr b/test.cr index b9a9897..168445c 100644 --- a/test.cr +++ b/test.cr @@ -44,19 +44,11 @@ class Test end def +(other) - ret = Test.new(@x + other.x) - puts "+ returning: #{ret} with #{ret.object_id}" - ret + Test.new(@x + other.x) end def add(other) ret = self + other - puts "Add returning: #{ret} with #{ret.object_id}" - ret - end - - def to_s - "Test obj with x = #{@x}" end def keyword_test(strvar : String, intvar : Int32, floatvar = 0.123, strvarkw : String = "nothing", boolvar : Bool = true, othervar : Test = Test.new(17)) @@ -69,25 +61,6 @@ class Test end end -struct XXX - property x = 3 -end - -a = XXX.new - -puts pointerof(a) -puts a.hash - -b = XXX.new - -puts pointerof(b) -puts b.hash - -b.x = 5 - -puts pointerof(b) -puts b.hash - MrbState.create do |mrb| MrbWrap.wrap_module(mrb, SomeModule, "TestModule") MrbWrap.wrap_module_function_with_keywords(mrb, MrbModuleCache.get(SomeModule), "test_method", SomeModule.test_method, {:int => Int32, :str => String}) @@ -145,7 +118,7 @@ end # TODO: Accept MrbClass and Class -puts MrbRefTable.inspect +puts "Reference table: #{MrbRefTable.inspect}" MrbRefTable.reset puts "------------------------------" @@ -168,4 +141,4 @@ MrbState.create do |mrb| mrb.load_script_from_file("examples/hp_example.rb") end -puts MrbRefTable.inspect \ No newline at end of file +puts "Reference table: #{MrbRefTable.inspect}" \ No newline at end of file