Debugging Tutorial: Difference between revisions

From RPTools Wiki
Jump to navigation Jump to search
m (Taustin moved page debugging Tutorial to Debugging Tutorial over redirect)
 
(29 intermediate revisions by 5 users not shown)
Line 9: Line 9:
== What is this about? ==
== What is this about? ==


I've been working with MT for over 2 years now and I can still remember the initial struggle I had due to the lack of any debugger. As it turns out there are quite a few methods to debug your code but unfortunately they're not very obvious to find. So here a short summary of available tools.
I've been working with MT for over 2 years now and I can still remember the initial struggle I had due to the lack of any debugger. As it turns out there are quite a few methods to debug your code but unfortunately they're not very obvious to find. So here is a short summary of available tools.


I've marked this article as 'advanced' as it does assume that you know the basics of MT script, like [[Library Token]], [[onCampaignLoad]] and [[defineFunction|User defined functions]].
I've marked this article as 'advanced' as it does assume that you know the basics of MT script, like [[Library Token]], [[onCampaignLoad]] and [[defineFunction|User defined functions]].
== Notepad++ ==
The most basic form of debugging is using an editor that uses syntax highlights for your code. Np++ can help with that, especially in combination with [http://forums.rptools.net/viewtopic.php?f=8&t=16770 rpedit] created by Aliasmask. You can find the install for np++ and syntax highlighting  (also by AM)[http://forums.rptools.net/viewtopic.php?f=8&t=13690 here]. Once you've done that go to: Settings -> Preferences -> New Document/Default Directory to automatically convert ANSI files to UTF-8 without BOM on open (so both the radiobutton and the checkbox). This helps when copy pasting code straight from a website into np, sometimes ansi character creep in there with which MT cannot work giving errors like 'unknown character 0x0A'.


== Pause ==
== Pause ==
The most basic and I think most used method is the 'Pause' method as developed by zeAl. The only way in MT to interrupt the flow of the code is with the use of [[input|input()]]. zeAl created some clever code around [[input|input()]] to use for debugging. His full contribution can be found here: [http://forums.rptools.net/viewtopic.php?p=110935#p110935]. Its working is simple as shown in this example:
The most basic and I think most used method is the 'Pause' method as developed by zEal. The only way in MT to interrupt the flow of the code is with the use of {{func|input}}. zEal created some clever code around {{func|input}} to use for debugging. His full contribution can be found here: [http://forums.rptools.net/viewtopic.php?p=110935#p110935]. Its working is simple as shown in this example:
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
   [h:strength = 5]
   [h:strength = 5]
   [h:toughness = 10]
   [h:toughness = 10]
   [h:pause("strength", "toughness")]
   [h:pause("strength", "toughness")]
   [r:"This text you'll see AFTER the pause"]
   [r:"This text you'll see AFTER the pause"]
</source>
</syntaxhighlight>
The running code will stop after the toughness=10 line, show the two variables both name and value and after you've clicked ok the code will continue. Its possible to just run  
The running code will stop after the toughness=10 line, show the two variables both name and value and after you've clicked ok the code will continue. It is possible to just run:
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
  [h: pause()]
  [h: pause()]
</source>
</syntaxhighlight>
in several places in your code so you can check where it crashes.  
in several places in your code so you can check where it crashes.  


In order for pause to work in your campaign you will need a library token with an onCampaignLoad macro containing the following line:
In order for pause to work in your campaign you will need a library token with an [[onCampaignLoad]] macro containing the following line:
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
   [ defineFunction("pause", "pause@this", 1, 0 ) ]  
   [ defineFunction("pause", "pause@this", 1, 0 ) ]  
</source>
</syntaxhighlight>
and you will also need a macro called 'pause' on the same library token containing the following code:
and you will also need a macro called {{code|pause}} on the same library token containing the following code:
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
     [ toolkit.DebugVariableCount = argCount() ]
     [ toolkit.DebugVariableCount = argCount() ]
     [ toolkit.DebugInputParameter = ".|<html>" +
     [ toolkit.DebugInputParameter = ".|<html>" +
Line 66: Line 69:
     [ toolkit.DebugBreak = input( strformat( toolkit.DebugInputParameter ) )]
     [ toolkit.DebugBreak = input( strformat( toolkit.DebugInputParameter ) )]
     [ abort( toolkit.DebugBreak ) ]
     [ abort( toolkit.DebugBreak ) ]
</source>
</syntaxhighlight>


You can also find this code after the above link to zeAl's post.  
You can also find this code after the above link to zEal's post.  


'''Tip:''' if you want to copy paste the above code or the code from the post, then FIRST paste it into a simple text editor and copy it from there and THEN paste it into the MT macro. This prevents from unintentional copying e.g. linefeeds (0x0A).  
'''Tip:''' if you want to copy-paste the above code or the code from the post, then FIRST paste it into a simple text editor and copy it from there and THEN paste it into the MT macro. This prevents from unintentional copying e.g. linefeeds (0x0A). Even better is using np++ in UTF-8 setting (see header here above).


=== Where pause goes wrong ===
=== Where pause goes wrong ===
A couple of useful things to know when you start using pause().  
A couple of useful things to know when you start using {{code|pause()}}.  
* if you use it at the top of your macro to e.g. check the values of the passed on arguments like this:
* if you use it at the top of your macro to e.g. check the values of the passed on arguments like this:
<source line lang="mtmacro">
<syntaxhighlight  line lang="mtmacro">
   [tmp = macro.args]
   [tmp = macro.args]
   [pause("tmp")]
   [pause("tmp")]
   [var1 = arg(0)]
   [var1 = arg(0)]
   [var2 = arg(1)]
   [var2 = arg(1)]
</source>
</syntaxhighlight>
then arg(0) will no longer exist!! The value that macro.args contain changes as soon as pause has run as it has its own scope and redefines it. Usually this can lead to inexplicable errors so be ware of this! Its better to use it like this:
then {{code|arg(0)}} will no longer exist!! The value that {{code|macro.args}} contain changes as soon as {{code|pause()}} has run as it has its own scope and redefines it. Usually this can lead to inexplicable errors so beware of this! It is better to use it like this:
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
   [tmp = macro.args]
   [tmp = macro.args]
   [var1 = arg(0)]
   [var1 = arg(0)]
   [var2 = arg(1)]
   [var2 = arg(1)]
   [pause("tmp")]
   [pause("tmp")]
</source>
</syntaxhighlight>
* pause can only handle very simple html code, so if you want to debug a dynamic form which you have assigned to a variable I would suggest you use a combination of the [[Show_HTML]] method and put a pause() right after that.
* {{code|pause()}} can only handle very simple HTML code, so if you want to debug a dynamic form which you have assigned to a variable, I would suggest you use a combination of the [[Show_HTML]] method and put a {{code|pause()}} right after that.


==The log file==
==The log file==
Going to a slightly more advanced method you can start using the log file. First off if MT crashes you can always check the log.txt file which is located in your .maptool directory (varies per OS where that is, for win7 its: C:[username]\.maptool). You will also find a 'logging.xml' file in that directory. The XML file tells maptools what to send to the log.txt file. Per default MT install this will only log generated errors. You can however also replace this file from the one that you can find in the maptool install directory (the one you unzipped initially). In the directory 'Misc' you find a 'macros-logging.xml' file. You can replace the existing XML file in your .maptool directory with that one (don't forget to rename it to logging.xml !) and it will log ALL macro code. This can render into a HUGE log file and slows down MT a bit so be careful with it. In my case I have a PC and Laptop, the laptop I use for running the games, so no logging, the PC I use for coding so logging is always turned on on that PC.  
Going to a slightly more advanced method you can start using the log file.
If your code crashes or generates weird errors, you can check the log file to see where it went wrong.  
 
First off, if MT crashes you can always check the {{code|log.txt}} file which is located in your {{code|.maptool}} directory. Its location varies per OS:
* For win7 it is {{code|C:[username]\.maptool}}.
* For MacOS it's {{code|/users/[username]/.maptool/}} aka {{code|~/.maptool/}}. Be aware that, because this directory's name begins with a dot, it's invisible in the Finder. So, either you use a utility like Onyx to make the Finder show invisible files, either you use the "Go to > Go to directory…" Finder menu and type one of those path.
 
You should also find a {{code|logging.xml}} file in that directory. The XML file tells MapTool what to send to the {{code|log.txt}} file. Per default MT install, this will only log generated errors.
 
You can however also replace this file with the one that you can find in the MapTool install directory (the one you unzipped initially). In the directory {{code|Misc}} you find a {{code|macros-logging.xml}} file. You can replace the existing XML file in your {{code|.maptool}} directory with that one (don't forget to rename it to {{code|logging.xml}} !) and it will log ALL macro code. If your code crashes or generates weird errors, you can check the log file to see where it went wrong.
 
'''MAC Users''': upto b89 the {{code|.dmg}} does NOT contain these logging XML files. You will need to download the {{code|.zip}} file to get those.
 
This can render into a HUGE log file and slows down MT a bit, so be careful with it. In my case I have a PC and Laptop, the laptop I use for running the games, so no logging, the PC I use for coding so logging is always turned on on that PC.
 
BUILD 91 AND LATER
 
if your using b91 or later, there is a launcherxxxx.jar provided. When you run that you will have an advanced tab. There you see 'Macro Handling' checkbox. Check that. This will automatically replace the logging.xml file for the right one as described above. Don't forget to delete the log.txt file first, so its cleared.
 
if for some reason this does not work under b91 (as is with one of my PCs) you need to manually replace the logging.xml as described above AND you must start maptool WITHOUT the launcher, as the launcher will simple reset the logging.xml. Instead run maptool from a .bat file. To do this simply create a file called runMaptool.bat and edit it. Place in the .bat the line:
  java -Xmx1024M -Xss4M -jar maptool-1.3.b91.jar run
Save it and run it.
 
MAPTOOL 1.5.2 AND LATER
 
If you are using 1.5.2 or a later version, the {{code|logging.xml}} approach has been replaced by macro function [[log.setLevel]].


==The Console==
==The Console==
The console is the real kicker, I found out about this after a year or so and since I'm aware of it it has made coding and debugging in MT SO MUCH SIMPLER!!. To activate it you need to do 2 things
The console is the real kicker. I found out about this after a year or so and since I'm aware of it it has made coding and debugging in MT SO MUCH SIMPLER!!
* first you need to replace the logging.xml file with the macro-logging.xml as described here above.  
 
* second you need to edit your mt.cfg file. This file you will find in the install directory. The content will look like something like this:
'''On Windows'''
 
To activate it you need to do 2 things:
* first you need to replace the {{code|logging.xml}} file with the {{code|macro-logging.xml}} (or check 'macro handling' in the launcher for b91+) as described here above.  
* second you need to edit your {{code|mt.cfg}} file (called {{code|Launch MapTool.sh}} on *nix based OS's). This file you will find in the install directory. The content will look like something like this:
   MAXMEM=1024
   MAXMEM=1024
   MINMEM=64
   MINMEM=64
Line 103: Line 133:
   JVM=C:\Program Files\Java\jre6\bin\javaw
   JVM=C:\Program Files\Java\jre6\bin\javaw
   PROMPT=true
   PROMPT=true
depending on your settings and OS. You need to remove only ONE letter: the 'w' from 'javaw'. So it becomes
depending on your settings and OS. You need to remove only ONE letter: the {{code|w}} from {{code|javaw}}. So it becomes:
   JVM=[what it reads here differs per OS]java
   JVM=[what it reads here differs per OS]java
Now you will have a console which shows the MT script real-time. Combine this with strategically placed pause()'s and debugging becomes a breeze. Here's my usual screen layout when I'm debugging:
Now you will have a console which shows the MT script real-time. Combine this with strategically placed {{code|pause()}}'s and debugging becomes a breeze. Here's my usual screen layout when I'm debugging:
[[image:Wolph42_Debugging_Screenshot.jpg]]
[[image:Wolph42_Debugging_Screenshot.jpg]]
<br />
'''On MacOS'''
You'll find the Console utility in {{code|/applications/Utilities/}}. Just launch it before you launch MapTool.
All applications' messages, including MapTool, will appear in the "Console messages" part.
If you want to focus on MapTool's messages you just have to open the {{code|log.txt}} described in the Log File section above with the Console app. Just do a right click on the {{code|log.txt}} file and choose "Open with…" from the contextual menu, and then of course Console. Don't try to open the file from the Console app with the "File > Open" menu, as the directory is invisible and won't show.
You've got a convenient search field to filter out the messages. Don't forget it.


'''Notes:'''
'''Notes:'''
* Text and comment is NOT ported to the console. So
* Text and comment is NOT ported to the console. So
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
   Hello world
   Hello world
   <!-- this is comment -->
   <!-- this is comment -->
</source>
</syntaxhighlight>
won't show in the console nor in the log, however:
won't show in the console nor in the log. However:
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
   [r:'Hello world']
   [r:'Hello world']
   [h:'<!-- this is comment -->']
   [h:'<!-- this is comment -->']
</source>
</syntaxhighlight>
will show up! Note though the latter is slower then the former to execute (which becomes noticeable around 200 to 400 of these lines, so not much to worry about).  
will show up! Note though the latter is slower than the former to execute (which becomes noticeable around 200 to 400 of these lines, so not much to worry about).  
* I personally find it very useful to quickly see at which macro I'm looking so in the header of all my macro I add:
* I personally find it very useful to quickly see at which macro I'm looking, so in the header of all my macros I add:
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
   [h:'<!-- ------------------------------------MACRO NAME ---------------------------------------->']
   [h:'<!-- ------------------------------------MACRO NAME ---------------------------------------->']
</source>
</syntaxhighlight>
and sometimes I also add a  
and sometimes I also add a:
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
   [h:'<!-- ------------------------------------/END MACRO NAME ---------------------------------------->']
   [h:'<!-- ------------------------------------/END MACRO NAME ---------------------------------------->']
</source>
</syntaxhighlight>
at the bottom, e.g. for the pause function!
at the bottom, e.g. for the {{code|pause()}} function!
* If you're running heavy macro, especially ones with lots of loops then you will notice that the code runs a lot faster when the console is minimized. Keep this in mind!
* If you're running heavy macros, especially ones with lots of loops, then you will notice that the code runs a lot faster when the console is minimized. Keep this in mind!
* In windows you can change the console settings. Especially for the MT log this is very usefull as the default console settings are width: 80 characters and Height: 300 lines. This means that lines are likely wrapped making them hard to read and with a history of 300 lines you won't come far. You can change this by right clicking on the top bar of the console, a context menu should pop-up with 'properties'. Here you go to the 'layout' tab and you can edit the 'Screen Buffer Size'. My settings are Width:600 and Height:2000. Ffortunately the settings are remembered by windows so you only need to do this once
* In Windows you can change the console settings. Especially for the MT log this is very useful as the default console settings are {{code|width: 80 characters}} and {{code|Height: 300 lines}}. This means that lines are likely wrapped making them hard to read, and with a history of 300 lines you won't come far. You can change this by right clicking on the top bar of the console, a context menu should pop-up with {{code|properties}}. Here you go to the {{code|layout}} tab and you can edit the {{code|Screen Buffer Size}}. My settings are {{code|Width:600}} and {{code|Height:2000}}. Fortunately the settings are remembered by Windows so you only need to do this once.
 
'''MapTool 1.5.2 and later'''
 
If you are using MapTool 1.5.2 or a later version, you can access the log console by selecting "Open Log Console" in the "Tools" Menu.


==Broadcast==
==Broadcast==
[[broadcast|broadcast()]] is a fairly new function to MT and is great for debugging purposes. The advantage of [[broadcast|broadcast()]] is that it ports the result to the chat IMMEDIATELY. Usually all tekst to chat whether its 'this text' or [r:"this text"] will be accumulated until all macros are done and THEN the text is ported to the chatbox. So in case of an [abort] or [assert] or a crash in the code you will find either the assertion message, a bug report or nothing at all. All the accumulated text is discarded.  
{{func|broadcast}} is a fairly new function to MT and is great for debugging purposes. The advantage of {{func|broadcast}} is that it ports the result to the chat IMMEDIATELY. Usually all text to chat whether it is {{code|'this text'}} or {{code|[r:"this text"]}} will be accumulated until all macros are done and THEN the text is ported to the chatbox. So in case of an {{func|abort}} or {{func|assert}} or a crash in the code you will find either the assertion message, a bug report or nothing at all. All the accumulated text is discarded.  


Two useful useages for [[broadcast|broadcast()]]:
Two useful usages for {{func|broadcast}}:
===Using broadcast to track down a code crash===
===Using broadcast to track down a code crash===
lets say you have an macro of a few 100 lines, you run it and... nothing or some vague message like 'double : found'. If you want to pin point the crash you can simply put [[broadcast|broadcast()]] lines between the code and see how far it gets. From the output you can deduce the location of the crash. Here an example, lets say you have:
Let's say you have an macro of a few 100 lines, you run it and... nothing or some vague message like {{code|double : found}}. If you want to pin point the crash you can simply put {{func|broadcast}} lines between the code and see how far it gets. From the output you can deduce the location of the crash. Here an example, lets say you have:
   [a block of code]
   [a block of code]
   [another block of code]
   [another block of code]
Line 153: Line 199:
   1
   1
   2
   2
Then you'll know that the error is somewhere inside ''[and yet more blocks of code]''.  
Then you'll know that the error is somewhere inside {{code|[and yet more blocks of code]}}.  
===Using broadcast to track variable development===
===Using broadcast to track variable development===
I usually use pause() to check my variables, however if something goes wrong somewhere in a 500+ loop, you will be clicking 'ok' a lot. In these instances its much easier to add a
I usually use {{code|pause()}} to check my variables. However if something goes wrong somewhere in a 500+ loop, you will be clicking {{code|ok}} a lot. In these instances it is much easier to add a:
<syntaxhighlight line lang="mtmacro">
   [broadcast("variable_name: "+variable_name+"another_variable_name: "+another_variable_name)]
   [broadcast("variable_name: "+variable_name+"another_variable_name: "+another_variable_name)]
inside the loop. This way the code is not interrupted but you will get to see where the loop goes haywire.  
</soursyntaxhighlightce>
inside the loop. This way the code is not interrupted but you will get to see where the loop goes haywire.
 
Like with pause() there is also a more advanced debug macro created in maptool. This is bot_debugInfo() In principle it makes use of the broadcast method but it has a huge set of extra options to make more optimally use of this function and to easier track down issues. This function is part of the [http://forums.rptools.net/viewtopic.php?f=46&t=16066 Bag of Tricks]. Note that pause() is also part of this bag, so if you install it you don't need to create it seperately.
 
==Bug tracking by elimination==
This is a very basic method and also a 'if all else fails'. When you've used all the other methods and are still stuck, its time to start debugging by elimination.
Most common use is in a code:{} (or frame:{}, dialog:{}, etc.) block that has a bug inside the block but generates an error report on the 'code block' level, making the broadcast, pause, console methods useless.
 
The method is very simple and can take different forms. The first is simply deleting large chunks of code and running the macro again until you've eliminated the bug. Then you start placing code back again and so you can narrow down to the line(s) where the code crashes. Usually in a large macro with a big code block that crashes, I start with deleting the entire body (that is everything between the { } ) to see if the block itself works. If that's the case I start putting back lines of code until the macro crashes. Its tedious, but VERY effective.
 
Another more preferable approach (but not always possible) is to remove the code:{} block itself and run the code body with the required parameters. So e.g. run this:
  [foreach(element, elements), CODE:{
    [if(element == 1): bla]
    [bla]
  }]
 
as this
    [element = 1]
    [if(element == 1): bla]
    [bla]
this way the macro will crash on the specific line which allows you to use the console, pause, broadcasts methods again.
 
'Note' that in case of dialogs and frames, you can do the same thing. The generated HTML will be ported straight to the chat. This can however lead to a stack overflow (porting large portions of text straight from a macro to the chat will cause that), you can omit that by raising the stack to 20 (TEMPORARILY!, normally you should never go higher then 4 during a game).


==Typical Bugs==
==Typical Bugs==
Here I'll give a few examples of things that typically go wrong when coding, it useful to check this once in a while as a reminder and I hope that others will add to this list so this accumulates in a FOB (frequently occurring bugs)
Here I'll give a few examples of things that typically go wrong when coding. It is useful to check this once in a while as a reminder and I hope that others will add to this list so this accumulates in a FOB (frequently occurring bugs).
===My number one===
===My number one===
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
can't
can't
</source>
</syntaxhighlight>
or better recognized in
or better recognized in
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
<!-- this you can't do in MT script -->
<!-- this you can't do in MT script -->
</source>
</syntaxhighlight>
This is not the most occuring bug, but it certainly is the most annoying as it REALLY screws up you're code and its hell to debug. The issue is with the single quote, when strategically placed this can result in an entire section of code not being executed, picking it up later or dropping back to the parent macro altogether. IF you also close the quote (that is put a second one in the comment as well) then there will be no issue, also if you encapsulate it in a [h:" "] (and not [h:'  ']) it will run along nicely.  
This is not the most occurring bug, but it certainly is the most annoying as it REALLY screws up your code and it is hell to debug. The issue is with the single quote. When strategically placed this can result in an entire section of code not being executed, picking it up later or dropping back to the parent macro altogether. IF you also close the quote (that is put a second one in the comment as well) then there will be no issue, also if you encapsulate it in a {{code|[h:" "]}} (and not {{code|[h:'  ']}}) it will run along nicely.
 
About the latter, although the MT script allows you to stuff like this
  He hits for [r:1d6+strength]
I find it bad practice (and generally the first step in hard to debug code) to NOT follow the sacred 'code' code, which is: 1. input 2. process 3. output
 
example
  <!- input -->
  [h:input("strength")]
  <!- process -->
  [h:textOut = "He hits for 1d6 + "+strength+": "+1d6+strength]
  <!- output -->
  [r:textOut]
 
===THE number one===
===THE number one===
It remains a guess but I think its a safe assumption that two : in one line of code (with the exception of switch and code) is the most commonly made mistake. Fortunately MT generates clear debug info on this one the syntax is ALWAYS:
It remains a guess but I think it is a safe assumption that two {{code|:}} in one line of code (with the exception of switch and code) is the most commonly made mistake. Fortunately MT generates clear debug info on this one the syntax is ALWAYS:
  [option , option , option : function]
  [option , option , option : function]
===Stray semicolon ';' in the chat===
===Stray semicolon ';' in the chat===
I think this one is in second place, not a bug per se but annoying none the less. This occurs when you forget to include the false part in an if statement when using the code option e.g.
I think this one is in second place, not a bug per se, but annoying nonetheless. This occurs when you forget to include the ''false'' part in an {{code|if}} statement when using the code option, e.g.:
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
   [if(statement), CODE:{apparently the statement is true}]
   [if(statement), CODE:{apparently the statement is true}]
</source>
</soursyntaxhighlightce>
will generate the following output
will generate the following output:
   apparently the statement is true
   apparently the statement is true
   ;
   ;
this is easily prevented by adding ;{} :
this is easily prevented by adding {{code|';{}'}} :
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
   [if(statement), CODE:{apparently the statement is true};{}]
   [if(statement), CODE:{apparently the statement is true};{}]
</source>
</syntaxhighlight>


===Stray comma ',' in the chat===
===Stray comma ',' in the chat===
Another common 'bug' encountered regularly are stray comma's. Obviously there can be numerous reasons for this to happen but in most cases its because of a loop like foreach or for. This for example:
Another common 'bug' encountered regularly are stray comma's. Obviously there can be numerous reasons for this to happen but in most cases it is because of a loop like {{roll|foreach}} or {{roll|for}}. This for example:
<syntaxhighlight line lang="mtmacro">
   [foreach(number, "1,2,3,4"), CODE:{[h:"don't show this"]}]
   [foreach(number, "1,2,3,4"), CODE:{[h:"don't show this"]}]
will generate
</syntaxhighlight>
will generate:
   ,,,
   ,,,
For this particular example this is simply solved by hiding the output altogether:
For this particular example this is simply solved by hiding the output altogether:
<syntaxhighlight line lang="mtmacro">
   [h,foreach(number, "1,2,3,4"), CODE:{["don't show this"]}]
   [h,foreach(number, "1,2,3,4"), CODE:{["don't show this"]}]
</syntaxhighlight>
Note that when the outer loop is hidden, that the contents are hidden by default so the h: is not necessary.  
Note that when the outer loop is hidden, that the contents are hidden by default so the h: is not necessary.  
If you do wish to show the context BUT not the comma's, you need to define the delimiter e.g.:
If you do wish to show the context BUT not the comma's, you need to define the delimiter e.g.:
  Seperate by nothing:
* Seperate by nothing:
<syntaxhighlight line lang="mtmacro">
   [r,foreach(number, "1,2,3,4", ""), CODE:{[r:"Hello World"]}]
   [r,foreach(number, "1,2,3,4", ""), CODE:{[r:"Hello World"]}]
  Seperate by space:
</syntaxhighlight>
* Seperate by space:
<syntaxhighlight line lang="mtmacro">
   [r,foreach(number, "1,2,3,4", " "), CODE:{[r:"Hello World"]}]
   [r,foreach(number, "1,2,3,4", " "), CODE:{[r:"Hello World"]}]
  Seperate by break:
</syntaxhighlight>
   [r,foreach(number, "1,2,3,4", "&lt;br>"), CODE:{[r:"Hello World"]}]
* Seperate by break:
<syntaxhighlight  line lang="mtmacro">
   [r,foreach(number, "1,2,3,4", "<br>"), CODE:{[r:"Hello World"]}]
</syntaxhighlight>
'''Tip''': To pin point the origin of a stray comma in a large chunk of code, you can put numbers (1,2,3,4,etc) between the code. These numbers will appear in the chat and the comma will be among them, making it easier to find it.
'''Tip''': To pin point the origin of a stray comma in a large chunk of code, you can put numbers (1,2,3,4,etc) between the code. These numbers will appear in the chat and the comma will be among them, making it easier to find it.


===Closing syntax characters===
===Closing syntax characters===


A myriad of errors can be created by not closing off syntax characters: [ ] ( ) { } " " ' '
A myriad of errors can be created by not closing off syntax characters: {{code|[ ] ( ) { } " " ' '}}


To help diagnose this, copy your macro into a text editor [http://forums.rptools.net/viewtopic.php?t=13690| aliasmask's Notepad++ mod] is recommended as it has other uses for MapTool. Use the find/count function to count each of the characters and the totals should equal for each pair.
To help diagnose this, copy your macro into a text editor. [http://forums.rptools.net/viewtopic.php?t=13690| aliasmask's Notepad++ mod] is recommended as it has other uses for MapTool. Use the {{code|find/count}} function to count each of the characters, and the totals should equal for each pair.


Note, if you use strings which contain only one of the paired syntax characters e.g. "1) This is the first point." you should "close" off the pair in a comment:
Note, if you use strings which contain only one of the paired syntax characters e.g. {{code|"1) This is the first point."}} you should ''close'' off the pair in a comment:


<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
   [h: '<!-- This comment is to close off the bracket in the next line ( -->']
   [h: '<!-- This comment is to close off the bracket in the next line ( -->']
   [h: broadcast ("1) This is the first point.")]
   [h: broadcast ("1) This is the first point.")]
</source>
</syntaxhighlight>
 
other issues can arise with stray 'single quotes' in words like {{code|it's, didn't, don't, etc}}. If these are not enclosed in " " or closed of by another ', you will get bogus output or syntax errors.


===Cheater you have been reported===
===Cheater you have been reported===
This 'functionality' is embedded to prevent cheating...obviously, however this can also result in this error message (and only this error message) in your own code! This typically happens when [[broadcast|broadcast()]] the result of an [[evalMacro|evalmacro()]] call, where the evalmacro result contains « guillemonts »: . E.g. paste this into your chat:
This ''functionality'' is embedded to prevent cheating… obviously. However this can also result in this error message (and only this error message) in your own code! This typically happens when {{func|broadcast}} the result of an {{func|evalMacro}} call, where the {{func|evalMacro}} result contains « guillemots »: . E.g. paste this into your chat:
   [h:result = evalmacro(decode("4 The attack scoops out one of the target's eyes, inflicting <b>[Fat=1d5] level(s) of Fatigue"))]
<syntaxhighlight line lang="mtmacro">
   [h:result = evalMacro(decode("4 The attack scoops out one of the target's eyes, inflicting <b>[Fat=1d5] level(s) of Fatigue"))]
   [h:broadcast(result)]
   [h:broadcast(result)]
To debug this I store 'result' in a lib:token property before I do the broadcast. From the text its usually easy to find where the guillemonts have entered. So:
</syntaxhighlight>
   [h:result = evalmacro(decode("4 The attack scoops out one of the target's eyes, inflicting <b>[Fat=1d5] level(s) of Fatigue"))]
To debug this I store {{code|result}} in a [[Library_Token|lib:token]] property before I do the {{func|broadcast}}. From the text it is usually easy to find where the guillemots have entered. So:
<syntaxhighlight line lang="mtmacro">
   [h:result = evalMacro(decode("4 The attack scoops out one of the target's eyes, inflicting <b>[Fat=1d5] level(s) of Fatigue"))]
   [h:setLibProperty("debugOutput", result, "lib:Token")]
   [h:setLibProperty("debugOutput", result, "lib:Token")]
   [h:broadcast(result)]
   [h:broadcast(result)]
Then after running the macro I can copy paste that property inside a text editor and track down the guillemonts.
</syntaxhighlight>
Then after running the macro I can copy paste that property inside a text editor and track down the guillemots.


===The Switch Case===
===The Switch Case===
Another really annoying little bug. If you ever get stuck with the use of [[switch_(roll_option)]] cause it keeps generating error reports and you REALLY can't find the issue, then likely you have used 'Case' or 'CASE' or any other variant with a capital letter in it. The point is, of all the functions in MT script, it turns out that 'case' is case-sensitive (yes it almost looks intentional...). Anyway the [[switch_(roll_option)]] ONLY works with 'case' (so lower case only!).  
Another really annoying little bug. If you ever get stuck with the use of {{roll|switch}} cause it keeps generating error reports and you REALLY can't find the issue, then likely you have used {{code|Case}} or {{code|CASE}} or any other variant with a capital letter in it. As it happens {{code|case}} is case-sensitive (yes it almost looks intentional...). Anyway {{roll|switch}} ONLY works with {{code|case}} (so lower case only!).  


--[[User:Wolph42|Wolph42]] 12:23, 7 June 2012 (UTC)
--[[User:Wolph42|Wolph42]] 12:23, 7 June 2012 (UTC)

Latest revision as of 23:59, 15 March 2023


ADVANCED
THIS IS AN ADVANCED ARTICLE

Debug Methods For Maptool Scripting

A tutorial to squash little critters from your MT code.

What is this about?

I've been working with MT for over 2 years now and I can still remember the initial struggle I had due to the lack of any debugger. As it turns out there are quite a few methods to debug your code but unfortunately they're not very obvious to find. So here is a short summary of available tools.

I've marked this article as 'advanced' as it does assume that you know the basics of MT script, like Library Token, onCampaignLoad and User defined functions.

Notepad++

The most basic form of debugging is using an editor that uses syntax highlights for your code. Np++ can help with that, especially in combination with rpedit created by Aliasmask. You can find the install for np++ and syntax highlighting (also by AM)here. Once you've done that go to: Settings -> Preferences -> New Document/Default Directory to automatically convert ANSI files to UTF-8 without BOM on open (so both the radiobutton and the checkbox). This helps when copy pasting code straight from a website into np, sometimes ansi character creep in there with which MT cannot work giving errors like 'unknown character 0x0A'.

Pause

The most basic and I think most used method is the 'Pause' method as developed by zEal. The only way in MT to interrupt the flow of the code is with the use of input(). zEal created some clever code around input() to use for debugging. His full contribution can be found here: [1]. Its working is simple as shown in this example:

  [h:strength = 5]
  [h:toughness = 10]
  [h:pause("strength", "toughness")]
  [r:"This text you'll see AFTER the pause"]

The running code will stop after the toughness=10 line, show the two variables both name and value and after you've clicked ok the code will continue. It is possible to just run:

 [h: pause()]

in several places in your code so you can check where it crashes.

In order for pause to work in your campaign you will need a library token with an onCampaignLoad macro containing the following line:

  [ defineFunction("pause", "pause@this", 1, 0 ) ]

and you will also need a macro called pause on the same library token containing the following code:

    [ toolkit.DebugVariableCount = argCount() ]
    [ toolkit.DebugInputParameter = ".|<html>" +
        "<table cellspacing='2' cellpadding='0' style='background-color:#595751'>" +
        "<tr><td>" +
        "<table width='300px' cellspacing='0' cellpadding='2' style='background-color:#FAF9F5;'>" +
        "%{toolkit.DebugVariableRows}</table></td></tr></html>" +
        "|Debugger|LABEL|SPAN=TRUE"
    ]
    [ toolkit.DebugVariableRow = "<tr %{toolkit.DebugVariableRowStyle}><td>" +
        "<b>%{toolkit.DebugVariableName}</b></td><td>%{toolkit.DebugVariableContent}" +
        "</td></tr>"
    ]
    [ toolkit.DebugVariableRows = "<tr style='background-color:#E0DDD5; font-size:1.1em;'><td><b>Variable</b></td><td><b>Value</b></td></tr>" ]
    [ count( toolkit.DebugVariableCount ), code:
    {
        [ toolkit.DebugVariableRowStyle = "" ]
        [ toolkit.DebugVariableName = arg( roll.count ) ]
        [ toolkit.DebugVariableContent = eval( arg( roll.count ) ) ]
        [ if( floor( roll.count/2 ) == roll.count/2 ), code:
        {
            [ toolkit.DebugVariableRowStyle = "style='background-color:#EDECE8;'" ]
        } ]
        [ toolkit.DebugVariableRows = toolkit.DebugVariableRows +
            strformat( toolkit.DebugVariableRow )
        ]
    } ]
    [ if( toolkit.DebugVariableCount == 0 ), code:
    {
        [ toolkit.DebugVariableRows = "<tr><td style='font-size: 1.4em' align='center'><b>Pause</b></td></tr>" ]
    } ]

    [ toolkit.DebugBreak = input( strformat( toolkit.DebugInputParameter ) )]
    [ abort( toolkit.DebugBreak ) ]

You can also find this code after the above link to zEal's post.

Tip: if you want to copy-paste the above code or the code from the post, then FIRST paste it into a simple text editor and copy it from there and THEN paste it into the MT macro. This prevents from unintentional copying e.g. linefeeds (0x0A). Even better is using np++ in UTF-8 setting (see header here above).

Where pause goes wrong

A couple of useful things to know when you start using pause().

  • if you use it at the top of your macro to e.g. check the values of the passed on arguments like this:
  [tmp = macro.args]
  [pause("tmp")]
  [var1 = arg(0)]
  [var2 = arg(1)]

then arg(0) will no longer exist!! The value that macro.args contain changes as soon as pause() has run as it has its own scope and redefines it. Usually this can lead to inexplicable errors so beware of this! It is better to use it like this:

  [tmp = macro.args]
  [var1 = arg(0)]
  [var2 = arg(1)]
  [pause("tmp")]
  • pause() can only handle very simple HTML code, so if you want to debug a dynamic form which you have assigned to a variable, I would suggest you use a combination of the Show_HTML method and put a pause() right after that.

The log file

Going to a slightly more advanced method you can start using the log file.

First off, if MT crashes you can always check the log.txt file which is located in your .maptool directory. Its location varies per OS:

  • For win7 it is C:[username]\.maptool.
  • For MacOS it's /users/[username]/.maptool/ aka ~/.maptool/. Be aware that, because this directory's name begins with a dot, it's invisible in the Finder. So, either you use a utility like Onyx to make the Finder show invisible files, either you use the "Go to > Go to directory…" Finder menu and type one of those path.

You should also find a logging.xml file in that directory. The XML file tells MapTool what to send to the log.txt file. Per default MT install, this will only log generated errors.

You can however also replace this file with the one that you can find in the MapTool install directory (the one you unzipped initially). In the directory Misc you find a macros-logging.xml file. You can replace the existing XML file in your .maptool directory with that one (don't forget to rename it to logging.xml !) and it will log ALL macro code. If your code crashes or generates weird errors, you can check the log file to see where it went wrong.

MAC Users: upto b89 the .dmg does NOT contain these logging XML files. You will need to download the .zip file to get those.

This can render into a HUGE log file and slows down MT a bit, so be careful with it. In my case I have a PC and Laptop, the laptop I use for running the games, so no logging, the PC I use for coding so logging is always turned on on that PC.

BUILD 91 AND LATER

if your using b91 or later, there is a launcherxxxx.jar provided. When you run that you will have an advanced tab. There you see 'Macro Handling' checkbox. Check that. This will automatically replace the logging.xml file for the right one as described above. Don't forget to delete the log.txt file first, so its cleared.

if for some reason this does not work under b91 (as is with one of my PCs) you need to manually replace the logging.xml as described above AND you must start maptool WITHOUT the launcher, as the launcher will simple reset the logging.xml. Instead run maptool from a .bat file. To do this simply create a file called runMaptool.bat and edit it. Place in the .bat the line:

 java -Xmx1024M -Xss4M -jar maptool-1.3.b91.jar run

Save it and run it.

MAPTOOL 1.5.2 AND LATER

If you are using 1.5.2 or a later version, the logging.xml approach has been replaced by macro function log.setLevel.

The Console

The console is the real kicker. I found out about this after a year or so and since I'm aware of it it has made coding and debugging in MT SO MUCH SIMPLER!!

On Windows

To activate it you need to do 2 things:

  • first you need to replace the logging.xml file with the macro-logging.xml (or check 'macro handling' in the launcher for b91+) as described here above.
  • second you need to edit your mt.cfg file (called Launch MapTool.sh on *nix based OS's). This file you will find in the install directory. The content will look like something like this:
 MAXMEM=1024
 MINMEM=64
 STACKSIZE=4
 JVM=C:\Program Files\Java\jre6\bin\javaw
 PROMPT=true

depending on your settings and OS. You need to remove only ONE letter: the w from javaw. So it becomes:

 JVM=[what it reads here differs per OS]java

Now you will have a console which shows the MT script real-time. Combine this with strategically placed pause()'s and debugging becomes a breeze. Here's my usual screen layout when I'm debugging:


On MacOS

You'll find the Console utility in /applications/Utilities/. Just launch it before you launch MapTool.

All applications' messages, including MapTool, will appear in the "Console messages" part.

If you want to focus on MapTool's messages you just have to open the log.txt described in the Log File section above with the Console app. Just do a right click on the log.txt file and choose "Open with…" from the contextual menu, and then of course Console. Don't try to open the file from the Console app with the "File > Open" menu, as the directory is invisible and won't show.

You've got a convenient search field to filter out the messages. Don't forget it.

Notes:

  • Text and comment is NOT ported to the console. So
  Hello world
  <!-- this is comment -->

won't show in the console nor in the log. However:

  [r:'Hello world']
  [h:'<!-- this is comment -->']

will show up! Note though the latter is slower than the former to execute (which becomes noticeable around 200 to 400 of these lines, so not much to worry about).

  • I personally find it very useful to quickly see at which macro I'm looking, so in the header of all my macros I add:
  [h:'<!-- ------------------------------------MACRO NAME ---------------------------------------->']

and sometimes I also add a:

  [h:'<!-- ------------------------------------/END MACRO NAME ---------------------------------------->']

at the bottom, e.g. for the pause() function!

  • If you're running heavy macros, especially ones with lots of loops, then you will notice that the code runs a lot faster when the console is minimized. Keep this in mind!
  • In Windows you can change the console settings. Especially for the MT log this is very useful as the default console settings are width: 80 characters and Height: 300 lines. This means that lines are likely wrapped making them hard to read, and with a history of 300 lines you won't come far. You can change this by right clicking on the top bar of the console, a context menu should pop-up with properties. Here you go to the layout tab and you can edit the Screen Buffer Size. My settings are Width:600 and Height:2000. Fortunately the settings are remembered by Windows so you only need to do this once.

MapTool 1.5.2 and later

If you are using MapTool 1.5.2 or a later version, you can access the log console by selecting "Open Log Console" in the "Tools" Menu.

Broadcast

broadcast() is a fairly new function to MT and is great for debugging purposes. The advantage of broadcast() is that it ports the result to the chat IMMEDIATELY. Usually all text to chat whether it is 'this text' or [r:"this text"] will be accumulated until all macros are done and THEN the text is ported to the chatbox. So in case of an abort() or assert() or a crash in the code you will find either the assertion message, a bug report or nothing at all. All the accumulated text is discarded.

Two useful usages for broadcast():

Using broadcast to track down a code crash

Let's say you have an macro of a few 100 lines, you run it and... nothing or some vague message like double : found. If you want to pin point the crash you can simply put broadcast() lines between the code and see how far it gets. From the output you can deduce the location of the crash. Here an example, lets say you have:

 [a block of code]
 [another block of code]
 [and yet more blocks of code]
 [and finally a last block of code]

If you want to find out where the code stops:

 [a block of code]
 [h:broadcast("1")]
 [another block of code]
 [h:broadcast("2")]
 [and yet more blocks of code]
 [h:broadcast("3")]
 [and finally a last block of code]

If the output to the chat is eg:

 1
 2

Then you'll know that the error is somewhere inside [and yet more blocks of code].

Using broadcast to track variable development

I usually use pause() to check my variables. However if something goes wrong somewhere in a 500+ loop, you will be clicking ok a lot. In these instances it is much easier to add a:

  [broadcast("variable_name: "+variable_name+"another_variable_name: "+another_variable_name)]
</soursyntaxhighlightce>
inside the loop. This way the code is not interrupted but you will get to see where the loop goes haywire.

Like with pause() there is also a more advanced debug macro created in maptool. This is bot_debugInfo() In principle it makes use of the broadcast method but it has a huge set of extra options to make more optimally use of this function and to easier track down issues. This function is part of the [http://forums.rptools.net/viewtopic.php?f=46&t=16066 Bag of Tricks]. Note that pause() is also part of this bag, so if you install it you don't need to create it seperately.

==Bug tracking by elimination==
This is a very basic method and also a 'if all else fails'. When you've used all the other methods and are still stuck, its time to start debugging by elimination. 
Most common use is in a code:{} (or frame:{}, dialog:{}, etc.) block that has a bug inside the block but generates an error report on the 'code block' level, making the broadcast, pause, console methods useless. 

The method is very simple and can take different forms. The first is simply deleting large chunks of code and running the macro again until you've eliminated the bug. Then you start placing code back again and so you can narrow down to the line(s) where the code crashes. Usually in a large macro with a big code block that crashes, I start with deleting the entire body (that is everything between the { } ) to see if the block itself works. If that's the case I start putting back lines of code until the macro crashes. Its tedious, but VERY effective. 

Another more preferable approach (but not always possible) is to remove the code:{} block itself and run the code body with the required parameters. So e.g. run this:
  [foreach(element, elements), CODE:{
    [if(element == 1): bla]
    [bla]
  }]

as this
    [element = 1]
    [if(element == 1): bla]
    [bla]
this way the macro will crash on the specific line which allows you to use the console, pause, broadcasts methods again.

'Note' that in case of dialogs and frames, you can do the same thing. The generated HTML will be ported straight to the chat. This can however lead to a stack overflow (porting large portions of text straight from a macro to the chat will cause that), you can omit that by raising the stack to 20 (TEMPORARILY!, normally you should never go higher then 4 during a game).

==Typical Bugs==
Here I'll give a few examples of things that typically go wrong when coding. It is useful to check this once in a while as a reminder and I hope that others will add to this list so this accumulates in a FOB (frequently occurring bugs).
===My number one===
<syntaxhighlight line lang="mtmacro">
can't

or better recognized in

<!-- this you can't do in MT script -->

This is not the most occurring bug, but it certainly is the most annoying as it REALLY screws up your code and it is hell to debug. The issue is with the single quote. When strategically placed this can result in an entire section of code not being executed, picking it up later or dropping back to the parent macro altogether. IF you also close the quote (that is put a second one in the comment as well) then there will be no issue, also if you encapsulate it in a [h:" "] (and not [h:' ']) it will run along nicely.

About the latter, although the MT script allows you to stuff like this

 He hits for [r:1d6+strength]

I find it bad practice (and generally the first step in hard to debug code) to NOT follow the sacred 'code' code, which is: 1. input 2. process 3. output

example

 <!- input -->
 [h:input("strength")]
 <!- process -->
 [h:textOut = "He hits for 1d6 + "+strength+": "+1d6+strength]
 <!- output -->
 [r:textOut]

THE number one

It remains a guess but I think it is a safe assumption that two : in one line of code (with the exception of switch and code) is the most commonly made mistake. Fortunately MT generates clear debug info on this one the syntax is ALWAYS:

[option , option , option : function]

Stray semicolon ';' in the chat

I think this one is in second place, not a bug per se, but annoying nonetheless. This occurs when you forget to include the false part in an if statement when using the code option, e.g.:

  [if(statement), CODE:{apparently the statement is true}]
</soursyntaxhighlightce>
will generate the following output:
  apparently the statement is true
  ;
this is easily prevented by adding {{code|';{}'}} :
<syntaxhighlight line lang="mtmacro">
  [if(statement), CODE:{apparently the statement is true};{}]

Stray comma ',' in the chat

Another common 'bug' encountered regularly are stray comma's. Obviously there can be numerous reasons for this to happen but in most cases it is because of a loop like [foreach():] or [for():]. This for example:

  [foreach(number, "1,2,3,4"), CODE:{[h:"don't show this"]}]

will generate:

 ,,,

For this particular example this is simply solved by hiding the output altogether:

  [h,foreach(number, "1,2,3,4"), CODE:{["don't show this"]}]

Note that when the outer loop is hidden, that the contents are hidden by default so the h: is not necessary. If you do wish to show the context BUT not the comma's, you need to define the delimiter e.g.:

  • Seperate by nothing:
  [r,foreach(number, "1,2,3,4", ""), CODE:{[r:"Hello World"]}]
  • Seperate by space:
  [r,foreach(number, "1,2,3,4", " "), CODE:{[r:"Hello World"]}]
  • Seperate by break:
  [r,foreach(number, "1,2,3,4", "<br>"), CODE:{[r:"Hello World"]}]

Tip: To pin point the origin of a stray comma in a large chunk of code, you can put numbers (1,2,3,4,etc) between the code. These numbers will appear in the chat and the comma will be among them, making it easier to find it.

Closing syntax characters

A myriad of errors can be created by not closing off syntax characters: [ ] ( ) { } " " ' '

To help diagnose this, copy your macro into a text editor. aliasmask's Notepad++ mod is recommended as it has other uses for MapTool. Use the find/count function to count each of the characters, and the totals should equal for each pair.

Note, if you use strings which contain only one of the paired syntax characters e.g. "1) This is the first point." you should close off the pair in a comment:

  [h: '<!-- This comment is to close off the bracket in the next line ( -->']
  [h: broadcast ("1) This is the first point.")]

other issues can arise with stray 'single quotes' in words like it's, didn't, don't, etc. If these are not enclosed in " " or closed of by another ', you will get bogus output or syntax errors.

Cheater you have been reported

This functionality is embedded to prevent cheating… obviously. However this can also result in this error message (and only this error message) in your own code! This typically happens when broadcast() the result of an evalMacro() call, where the evalMacro() result contains « guillemots »: . E.g. paste this into your chat:

  [h:result = evalMacro(decode("4 The attack scoops out one of the target's eyes, inflicting <b>[Fat=1d5] level(s) of Fatigue"))]
  [h:broadcast(result)]

To debug this I store result in a lib:token property before I do the broadcast(). From the text it is usually easy to find where the guillemots have entered. So:

  [h:result = evalMacro(decode("4 The attack scoops out one of the target's eyes, inflicting <b>[Fat=1d5] level(s) of Fatigue"))]
  [h:setLibProperty("debugOutput", result, "lib:Token")]
  [h:broadcast(result)]

Then after running the macro I can copy paste that property inside a text editor and track down the guillemots.

The Switch Case

Another really annoying little bug. If you ever get stuck with the use of [switch():] cause it keeps generating error reports and you REALLY can't find the issue, then likely you have used Case or CASE or any other variant with a capital letter in it. As it happens case is case-sensitive (yes it almost looks intentional...). Anyway [switch():] ONLY works with case (so lower case only!).

--Wolph42 12:23, 7 June 2012 (UTC)