Announcement

Collapse
No announcement yet.

32LogEx

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • 32LogEx

    I decided to modify the wondeful 32 Lines of Goodness to suit my own project and it turned out to be pretty useful for more than that one project. So, I thought I'd share it here for anyone else who might need such functionality. I know, it's not 32 lines anymore. I left that in the name to honor the original author.

    32LoGEx now allows for all metamethods for your custom class.
    32LoGEx now creates a pseudo-type for your custom class which can be returned by the lua type() function.

    For those of you who've never used 32log, it's a system designed for creating classes with much OOP behaviour.

    Example:
    Code:
    class "MyClass" {};
    local obj = MyClass:new();
    Dialog.Message("Type", type(obj));
    Code:
    --[[
    This is a modification of the '32 Lines of Goodness' found at https://love2d.org/wiki/32_lines_of_goodness.
    
    Added Features:
        Allows the creation of all known metamethods for each class.
            E.g.,
            class "MyClass" {};
            function MyClass:__tostring() return "This is what I want to return." end
            local obj = MyClass:new();        
            print(tostring(obj));
            -> Output would be "This is what I want to return."
        Creates pseudo-types of each class created by overriding the lua type() function.
            E.g.:
            class "CoolClass" {};
            local obj = CoolClass:new();
            print(type(obj));        
            -> output would be "CoolClass"
            
    
    Copyright © 2019 AMSPublic.org
    
    This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
    
    Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
    
        1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
        2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
        3. This notice may not be removed or altered from any source distribution.
    ]]
    
    local mt_class = {}
     
    function mt_class:extends(parent)
        self.super = parent
        setmetatable(mt_class, {__index = parent})
        parent.__members__ = parent.__members__ or {}
      
        return self
    end
     
    local function define(class, members)
        class.__members__ = class.__members__ or {}
      
        for k, v in pairs(members) do
            class.__members__[k] = v
        end
      
        function class:new(...)
            local newvalue = {}
            
            for k, v in pairs(class.__members__) do
                newvalue[k] = v
            end
                  
            setmetatable(newvalue,
                            {
                                __index =         class,
                                __add =         class.__add,
                                __concat =        class.__concat,
                                __div =            class.__div,
                                __eq =            class.__eq,
                                __le =            class.__le,
                                __len =            class.__len,
                                __lt =            class.__lt,
                                __mod =            class.__mod,
                                __mul =            class.__mul,
                                __newindex =     class.__newindex,
                                __pow =            class.__pow,
                                __sub =            class.__sub,
                                __tostring =     class.__tostring,
                                __type =        class.__type,
                                __unm =            class.__unm,
                                
                            }
                        );
          
            if newvalue.__init then
                newvalue:__init(...);
            end
          
            return newvalue;
        end
        
    end
    
    function class(name)
        local newclass = {
            __add =         function() end,
            __concat =         function() end,
            __div =         function() end,
            __eq =             function() end,
            __le =             function() end,
            __len =         function() end,        
            __lt =             function() end,
            __mod =         function() end,
            __mul =         function() end,
            __newindex =     function() end,
            __pow =         function() end,
            __sub =         function() end,
            __tostring =     function() end,
            __type =        name,
            __unm =         function() end,        
        };
        
        _G[name] = newclass;
      
        return setmetatable(newclass, {__index = mt_class, __call = define});
    end
    
    local _type = type;
    function type(vObj)
        local sType = _type(vObj);
        
        if (sType == "table") then
        
            local tMeta = getmetatable(vObj);
            
            if (tMeta) then
            
                if (tMeta["__type"]) then
                    sType = tMeta["__type"];
                end
                
            end
            
        end
    
        return sType;
    end
    Last edited by Centauri Soldier; 01-16-2020, 01:09 PM.
    https://github.com/CentauriSoldier

  • #2
    Well poo. After about 2 hours of debuggin my code, I discovered that there's nothing wrong with my code. It works fine with modern lua, it just doesn't work with AMS. The lua shipped with AMS is several years out of date :(.
    https://github.com/CentauriSoldier

    Comment


    • #3
      Originally posted by Centauri Soldier View Post
      Well poo. After about 2 hours of debuggin my code, I discovered that there's nothing wrong with my code. It works fine with modern lua, it just doesn't work with AMS. The lua shipped with AMS is several years out of date :(.
      AMS ships with Lua 5.1, which is extremely out of date. It's still the most used Lua version I believe, mainly because LuaJIT is so popular (which implements the 5.1 syntax)

      What didn't work in the AMS version?
      Bas Groothedde
      Imagine Programming :: Blog

      AMS8 Plugins
      IMXLH Compiler

      Comment


      • #4
        It throws an error about a loop in gettable here...

        Code:
        if newvalue.__init then
            newvalue:__init(...);
        end
        But when I test it with lua 5.4 it works fine.

        EDIT: just to be clear, the error happens when using the :extends() function. But this error does not occur when using newer versions of lua.
        Last edited by Centauri Soldier; 01-18-2020, 12:07 PM.
        https://github.com/CentauriSoldier

        Comment


        • #5
          Originally posted by Centauri Soldier View Post
          It throws an error about a loop in gettable here...

          Code:
          if newvalue.__init then
          newvalue:__init(...);
          end
          But when I test it with lua 5.4 it works fine.

          EDIT: just to be clear, the error happens when using the :extends() function. But this error does not occur when using newer versions of lua.
          Code:
                  if rawget(newvalue, "__init") then
                      rawget(newvalue, "__init")(newvalue, ...);
                  end
          Try that. newvalue.__init causes newvalue_metatable.__index to be indexed for the key "__init", so it might loop indefinitely trying to get a value that does not exist. When you use rawget, you bypass the metatable and get the "__init" key directly from the table. I've noticed that it happens more though, not just at that point. This code, was it written for Lua 5.1?
          Bas Groothedde
          Imagine Programming :: Blog

          AMS8 Plugins
          IMXLH Compiler

          Comment


          • #6
            Try this, I made one based on something quite big I'm working on (unrelated to AMS). This one basically does what you want.
            Mind you though, I think you were using the other version wrong initially, it requires you to define the members in the `{}` block.

            Anyhow, let me know what you think. See globals and on show
            Attached Files
            Bas Groothedde
            Imagine Programming :: Blog

            AMS8 Plugins
            IMXLH Compiler

            Comment


            • #7
              I'm looking at your example now, it's quite extensive, I'll be a little while figuring it out. Thanks for taking the time to make this!
              Also, I think you might be right about the original script. After looking over some other code I'm working on, I've noticed a few things that may have been causing problems in this code.

              EDIT: I was in Globals when I posted initially, then just noticed your code in the OnShow event with the debug opening. So epic, thanks again, Bas.
              https://github.com/CentauriSoldier

              Comment


              • #8
                Well, that code is pretty **** amazing. I'm getting it work with my old classes but I have one confusing item I can't seem to sort out. I've noticed that I can declare method elements by fiat within a member method but I can't seem to figure out how to delcare elements outside a member method within a class table. When I do, I get an error saying that settings those values will overwrite a method (even though the values are numbers). If I declare them within a table it works but, for some reason, an error is thrown whenever I try to declare something like Var = 12;

                Code:
                class "Iota" {
                    
                    Years = 0;
                    Days = 0;
                    Hours = 0;
                    Minutes = 0;
                    Seconds = 0;
                    
                    __construct = function(...)
                        --AAA.CheckCount("Iota", "__construct", arg, 5);
                        --AAA.CheckTypes("Iota", "__construct", {self}, 1, {"Iota"});
                        local this = arg[1];
                        local nYears = AAA.CheckTypes("Iota", "__construct", arg, 2, {"number"});
                        local nDays = AAA.CheckTypes("Iota", "__construct", arg, 3, {"number"});
                        local nHours = AAA.CheckTypes("Iota", "__construct", arg, 4, {"number"});
                        local nMinutes = AAA.CheckTypes("Iota", "__construct", arg, 5, {"number"});
                        local nSeconds = AAA.CheckTypes("Iota", "__construct", arg, 6, {"number"});
                        
                        this.Years = nYears;
                        this.Days = nDays;
                        this.Hours = nHours;
                        this.Minutes = nMinutes;
                        this.Seconds = nSeconds;
                        this:LevelValues();
                    end;
                    
                    
                    __tostring = function(...)
                    local this = arg[1];
                    
                    return this.Years..':'..
                           string.format("%03d:%02d:%02d:%02d",
                           this.Days, this.Hours,
                           this.Minutes, this.Seconds);
                    end
                    
                    --[[Years = 0,
                    Days = 0,
                    Hours = 0,
                    Minutes = 0,
                    Seconds = 0,    ]]
                }

                EDIT: After a few hours of working with this code in my project I think I'm starting to understand it a bit more. It's very powerful!
                Do you think there'd be anyway to implement this.SOMEVAR as a local? Currently all variables set in the contructor are global. I'm not seeing a way to make them local but maybe you can see something I don't. I know I could do it with methods and variables declared outside of the class declaration, I was just wondering if it were possible with 'this' (the class passed).

                EDIT 2: I found a pretty good way to do it. I create a local table in the parent class and every class constructor has this inside.
                For my Card class...
                Code:
                tCard[this] = {};
                My solution to my problem of encapsulation is not a perfect one but it works rather well. I can access the data from inside the classes but not from outside.
                Last edited by Centauri Soldier; 01-19-2020, 04:32 PM.
                https://github.com/CentauriSoldier

                Comment


                • #9
                  I'm not quite sure what you mean by setting it as local vs global;

                  Any property specified in any member (constructor or method) using `this.name = value` is a property of the current object, so neither local or global.

                  Regarding the error you see about overwriting an existing member, I didn't implement declaration of properties (your Years, Days, ... properties). Those are
                  considered members and members shouldn't be overwritten in this implementation. I followed the Python/Javascript/PHP approach; you can declare all your
                  members in any method at any point.

                  Code:
                  __construct = function(...)
                      --AAA.CheckCount("Iota", "__construct", arg, 5);
                      --AAA.CheckTypes("Iota", "__construct", {self}, 1, {"Iota"});
                      local this = arg[1];
                      local nYears = AAA.CheckTypes("Iota", "__construct", arg, 2, {"number"});
                      local nDays = AAA.CheckTypes("Iota", "__construct", arg, 3, {"number"});
                      local nHours = AAA.CheckTypes("Iota", "__construct", arg, 4, {"number"});
                      local nMinutes = AAA.CheckTypes("Iota", "__construct", arg, 5, {"number"});
                      local nSeconds = AAA.CheckTypes("Iota", "__construct", arg, 6, {"number"});
                      
                      this.Years = nYears;
                      this.Days = nDays;
                      this.Hours = nHours;
                      this.Minutes = nMinutes;
                      this.Seconds = nSeconds;
                      this:LevelValues();
                  end;
                  That alone should be enough
                  Bas Groothedde
                  Imagine Programming :: Blog

                  AMS8 Plugins
                  IMXLH Compiler

                  Comment


                  • #10
                    Thanks for clarifying that for me. I see what you're going for now.

                    In answer to your question, I was referring to something like Ammo.Amount being accessible globally. But I figured out to provide encapsulation when needed using 'this' in local tables.

                    This really helped me a lot, thanks again, Bas, you're a life saver .
                    https://github.com/CentauriSoldier

                    Comment


                    • #11
                      Originally posted by Centauri Soldier View Post
                      Thanks for clarifying that for me. I see what you're going for now.

                      In answer to your question, I was referring to something like Ammo.Amount being accessible globally. But I figured out to provide encapsulation when needed using 'this' in local tables.

                      This really helped me a lot, thanks again, Bas, you're a life saver .
                      Glad I could help!

                      Edit - Some low-level optimization tips; https://luac.nl/s/161a434799211397f9b878ab1
                      Bas Groothedde
                      Imagine Programming :: Blog

                      AMS8 Plugins
                      IMXLH Compiler

                      Comment


                      • #12
                        Wow, that's neat! I didn't even know there a tool like that out there. Good to know, thanks.

                        Oh, I noticed a strange bug the class code. When you try to extend a class more than once (such as Creature -> Human -> Soldier) it creates an infinite callback loop when calling this:super() in the __constructor metamethod of each class. It's because the "this" that comes through is the sub-class and so it just keeps calling itself over and over until it hits a stack overflow. I have found a partial solution by calling the parent-to-be's class directly (instead of using super()) in the child constructor but I wonder if it's possible to make it work natively in the class code. What do you think?

                        EDIT: I found an even better way to do it as-is.

                        Since calling this:super() creates an infinite loop and calling MyClass() in the constructor does not associate it
                        with the subclass, niether option is a viable, working solution; however, by calling MyParentClass:super() explicitly in each constructor (as opposed to this:super()), the infinite loop is avoided and the classes are correctly associated.
                        Last edited by Centauri Soldier; 01-26-2020, 12:08 AM.
                        https://github.com/CentauriSoldier

                        Comment


                        • #13
                          Originally posted by Centauri Soldier View Post
                          Wow, that's neat! I didn't even know there a tool like that out there. Good to know, thanks.

                          Oh, I noticed a strange bug the class code. When you try to extend a class more than once (such as Creature -> Human -> Soldier) it creates an infinite callback loop when calling this:super() in the __constructor metamethod of each class. It's because the "this" that comes through is the sub-class and so it just keeps calling itself over and over until it hits a stack overflow. I have found a partial solution by calling the parent-to-be's class directly (instead of using super()) in the child constructor but I wonder if it's possible to make it work natively in the class code. What do you think?

                          EDIT: I found an even better way to do it as-is.

                          Since calling this:super() creates an infinite loop and calling MyClass() in the constructor does not associate it
                          with the subclass, niether option is a viable, working solution; however, by calling MyParentClass:super() explicitly in each constructor (as opposed to this:super()), the infinite loop is avoided and the classes are correctly associated.
                          Nice find, yes in my original implementation each object has a reference to its concrete class implementation; that way I always have the valid super class available. I missed that in this implementation.

                          The luac.nl tool is reasonably new, by the way. It's something I released a little while ago.
                          Bas Groothedde
                          Imagine Programming :: Blog

                          AMS8 Plugins
                          IMXLH Compiler

                          Comment


                          • #14
                            Lol, I just noticed I have about 3 million grammar and spelling errors in my last post :P. I'll just pretend I didn't see those.

                            So, the solution I thought I found doesn't actually work because 'this' in any constructor past the first parent is considered a type() "class". I did really find a solution this time, albeit not a very elegant or robust one.

                            Code:
                                       class_object.super2nd = function(instance, ...)
                                            local parent = getmetatable(class_object).__parent;
                                            if (parent) then
                                                local grandParent = getmetatable(parent).__parent;                
                                                if (grandParent and type(grandParent.__construct) == "function") then
                                                    grandParent.__construct(instance, ...);                    
                                                    return;
                                                end
                                            end
                                        end
                                        
                                        class_object.super3rd = function(instance, ...)
                                            local parent = getmetatable(class_object).__parent;
                                            if (parent) then
                                                local grandParent = getmetatable(parent).__parent;
                                                if (grandParent) then
                                                    local greatGrandParent = getmetatable(grandParent).__parent;
                                                    if (greatGrandParent and type(greatGrandParent.__construct) == "function") then
                                                        greatGrandParent.__construct(instance, ...);                    
                                                        return;
                                                    end
                                                end
                                            end
                                        end
                            https://github.com/CentauriSoldier

                            Comment


                            • #15
                              Can you share code that reproduces the issue you were initially talking about?
                              Bas Groothedde
                              Imagine Programming :: Blog

                              AMS8 Plugins
                              IMXLH Compiler

                              Comment

                              Working...
                              X