1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still haven't received your rank award? Then please contact the administration.
    Dismiss Notice
  3. Join the 6th Melee Mapping Contest for a chance to have your map featured in this year's Hive Cup!
    Dismiss Notice
  4. Shoot to thrill, play to kill. Sate your hunger with the 33rd Modeling Contest!
    Dismiss Notice
  5. Do you hear boss music? It's the 17th Mini Mapping Contest!
    Dismiss Notice
  6. Let your favorite entries duke it out in the 15th Techtree Contest Poll.
    Dismiss Notice
  7. Weave light to take you to your highest hopes - the 6th Special Effect Contest is here!
    Dismiss Notice
  8. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[Lua] Very simply trick to help Lua users track syntax errors.

Discussion in 'The Lab' started by Macadamia, Jul 28, 2020.

  1. Macadamia

    Macadamia

    Joined:
    Jan 30, 2020
    Messages:
    867
    Resources:
    0
    Resources:
    0
    Hello everyone !

    As most of Lua users, especially in the wc3 context, there is a big problem we often encounter.
    Lua is extremely permissive as its variables have no type.
    You all know that only values are typed in Lua.

    Declaring globals not being mandatory in Lua, combined with this lack of type safety, could make your life really difficult.
    A single syntax error will silently break your map, and don't count on Lua to be verbose and tell you that something went wrong. Most of the time the thread with the syntax error will just crash with no apparent effect (and prevent the rest of the actions in the same thread from running).

    There is a simple trick, directly borrowed from the free first edition of Programming In Lua.

    All you have to do is add this line to your map triggers.
    Don't put it in the Lua root or it will prevent your map from running.

    Just put the line somewhere after map init, or even better, when your map game actually starts :

    Code (Lua):
    setmetatable(_G,{__index=function(_, n) print("Trying to read undeclared global : "..tostring(n)) end,})
    For example, in my map, I place this at the end of a "GameStarted" function, before the first level's countdown is started.

    -> every time your script uses a global variable that is not defined, something that very often is caused by a syntax error, the script will display a warning with the name of the faulty global variable.


    That's all, I hope this will help you.

    IMPORTANT NOTE :

    What this function does is intercept the index operator of tables in the script.
    This means it won't catch attempts to access the value of a nil global variable, but rather the attempt to access the index of a global table that has not been defined as a table.

    This said, non-table variable never require to be declared.

    Note 2 :

    I have to correct my previous note.
    The way Lua works can sometimes be a bit confusing :
    the reason it will catch attempts on tables only is not because the other variables are not tables, as every global variable is a part of the globals table called _G.
    The reason is that there is no inherent problem with accessing globals as non-tables. They will simply be considered as nil.
    Comparing to nil is a recurrent way to check if a variable is referring to a value or not.
    But if you try to access a value indexed in a global while this global has not been defined as a table, then Lua cannot guess it is a table, and thus this will crash the current thread.

    Sorry if this was a bit confusing. Fact is this simple line of code is really effective for recurrent issues with tables that have not been defined.
     
    Last edited: Aug 2, 2020
  2. GhostWolf

    GhostWolf

    Joined:
    Jul 29, 2007
    Messages:
    4,947
    Resources:
    2
    Tools:
    1
    Tutorials:
    1
    Resources:
    2
    That's a cute idea exploiting the global object directly.

    Won't it break for any handle/etc. globals which don't have an initial value though? Lua doesn't allow to set a global to nil in its declaration.
    i.e.
    Code (Lua):

    myVar = nil
     
    at global scope is a syntax error.
     
  3. Macadamia

    Macadamia

    Joined:
    Jan 30, 2020
    Messages:
    867
    Resources:
    0
    Resources:
    0
    Hello !

    Well as you know, globals don't need to be declared when they have no structure, and thats why people can straight away assign them values anywhere in the script.

    But if you try to assign an indexed value to a global variable that has not been defined as a table (this also affects nested tables), Lua won't be able to assign that value and the thread will crash.

    This scenario is Lua is more than common as nearly everything that is not native in Lua is a table.

    I was not aware of this problem with nilling global variables in the Lua root, as I never set globals to nil in there (what would the point be, apart from making a note of its existence ?).

    This said, nothing prevents anyone from defining all his globals in the Lua root.
    That is exactly where I define most of my globals initial values.

    Of course, none of these values are nil, but for people wondering, nothing prevents you from defining tables with nil values inside.
    The point is to tell Lua in advance about the number of entries the table will have, and thus allocate the minimal amount of memory to its creation.

    here is an excerpt from my Root Globals

    Code (Lua):

    MapArea=Rect(-8192.0, -8192.0, 8192.0, 8192.0)
    Rad2Deg=180.0/math.pi
    Pi2=math.pi*2.00
    Pi8th=math.pi/8.00
    -- Game tables
    Events={}
    TimerData={}
    -- Player tables
    PlayerNb={0,0,0,0}
    R ={255,  0,  0,150,128,255}
    G ={  0,  0,255,  0,128,255}
    B ={  0,255,255,150,128,  0}
    RB={100,  0,  0, 75, 75,128}
    GB={  0,  0,128,  0, 75,128}
    BB={  0,128,128, 75, 75,  0}
    Color={
        PLAYER_COLOR_RED,
        PLAYER_COLOR_BLUE,
        PLAYER_COLOR_TEAL,
        PLAYER_COLOR_VIOLET,
        PLAYER_COLOR_LIGHT_GRAY,
        PLAYER_COLOR_YELLOW
    }
    Playing={false,false,false,false,true}
    StartX={-2752.0, 6528.0, 2752.0,-6528.0,0.0}
    StartY={ 6528.0, 2752.0,-6528.0,-2752.0,0.0}
    Select={
        {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil},
        {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil},
        {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil},
        {nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,nil},
    }
    SelectCount={0,0,0,0}
     
    So yes, maybe you are not allowed to define a nil global variable in the Lua root, but this has little impact, and no one should actually do it but rather give the global a default value or not declare it at all if it is supposed to reference a handle from the game.


    I understand you are talking about these specific globals, but it would probably be more advisable to only mention them in a comment sections.
    As you have noticed, all this function does is hook the _index operator to display the name of the table whose index the script tried to access.
    So yes it only works with tables, and I will add this information to the main post.

    Thanks !