Step-by-step tutorial: a very simple example
Let's describe a very simple Windows structure, RECT, which is defined as follows:
typedef struct tagRECT { LONG left; LONG top; LONG right; LONG bottom; } RECT;
A very naive and simple (however, fully-functional and working) implementation of TempLuator template would be
LONG "left" LONG "top" LONG "right" LONG "bottom"
Easy, isn't it?... Create and test this template (in all our examples we start Hiew as "hiew32.exe hiew32.exe" and the offset is 0. If you open a different file, you'll see different data but this is not really important because in this tutorial we don't pay much attention to the actual data but just trying to get the ideas behind the TempLuator scripts). Here is the output:
00000000 LONG left = 9460301;
00000004 LONG top = 3;
00000008 LONG right = 4;
0000000C LONG bottom = 65535;
Let's polish our template a bit. First, let's make the output look as a real structure:
function _Rect() LONG "left" LONG "top" LONG "right" LONG "bottom" end Rect = struct("RECT", _Rect) Rect "MyRect"
Here we define a function (_Rect()) that does the actual processing of data. Next, using the internal function struct() we define a new function Rect() that formats _Rect()'s output as a structure of type RECT. At last, we execute our Rect() function giving it the name of the structure instance ("MyRect"). It's time to remind that in Lua Rect "MyRect" and Rect("MyRect") forms of function call in case of one string argument are identical so the last line in our example is actually a function call. Well, look what we get:
00000000 struct RECT MyRect =
00000000 {
00000000 LONG left = 9460301;
00000004 LONG top = 3;
00000008 LONG right = 4;
0000000C LONG bottom = 65535;
00000010 };
00000010
Excellent! Well... Almost. One more step just in order to please the viewer's eye: let's align everything neatly. There are two variables, TypeWidth and NameWidth, which do exactly what we want, namely define the width (in characters) of type and name of a variable correspondingly:
function _Rect() TypeWidth = 4 NameWidth = 7 LONG "left" LONG "top" LONG "right" LONG "bottom" end Rect = struct("RECT", _Rect) Rect "MyRect"
How do you know these widths? Well, you either find the largest string and count its length manually or, better and easier, fill in any arbitrary numbers, run the script, see all misalignments, change the numbers and repeat until satisfied.
An important note: even though it is possible to set alignment variables anywhere in your template, it is better to do alignment in a structure-defining function. This way the output of this particular function is not affected by execution of any other functions and/or inclusion of any libraries. In addition, it's important to note that all functions defined via struct() (like our own Rect() function) preserve caller's alignment even if these functions change the alignment internally. Therefore, structure nesting does not break any alignment defined in upper-level calls. Well, here is the output:
00000000 struct RECT MyRect =
00000000 {
00000000 LONG left = 9460301;
00000004 LONG top = 3;
00000008 LONG right = 4;
0000000C LONG bottom = 65535;
00000010 };
00000010
Next step is to get a bit more user-friendly. Let's add comments! Quite naturally, a variable exists that defines the position of comments and its name is - surprise, surprise! - CommentPos. By default it is set to 70 but you can set it to anything else. Unlike the TypeWidth and NameWidth variables, it's better to set CommentPos just once in your script. This way all comments get aligned independently of the structure nesting level.
function _Rect() TypeWidth = 4 NameWidth = 7 LONG( "left", "x-coordinate of the upper-left corner of the rectangle") LONG( "top", "y-coordinate of the upper-left corner of the rectangle") LONG( "right", "x-coordinate of the lower-right corner of the rectangle") LONG("bottom", "y-coordinate of the lower-right corner of the rectangle") end Rect = struct("RECT", _Rect) CommentPos = 40 Rect("MyRect", "Defined in WINDEF.H")
The output looks now like a perfection:
00000000 struct RECT MyRect = // Defined in WINDEF.H 00000000 { 00000000 LONG left = 9460301; // x-coordinate of the upper-left corner of the rectangle 00000004 LONG top = 3; // y-coordinate of the upper-left corner of the rectangle 00000008 LONG right = 4; // x-coordinate of the lower-right corner of the rectangle 0000000C LONG bottom = 65535; // y-coordinate of the lower-right corner of the rectangle 00000010 }; 00000010
The last idea is pretty straightforward. You have a perfect definition of the RECT structure. What if you want to use it multiple times and just reuse this single definition? Quite naturally, TempLuator supports libraries. Everything you have to do is to put your beloved definitions into a library, include this library in your current template and - voila - you can use all publicly visible (non-local) definitions from that library in your script. Let's put our RECT definition into MyLibrary.lua library. This library must be placed anywhere in the %HIEW32%\LuaScripts\TempLuator\Include folder or any of its subfolders.
// // this is placed in %Hiew32%\LuaScripts\TempLuator\Include\MyLibs\MyLibrary.lua // we use "local" for functions/variables that should not be visible outside this module in order // to prevent global namespace pollution // local function _Rect() TypeWidth = 4 NameWidth = 7 -- always show RECT members as decimal numbers Dec() LONG( "left", "x-coordinate of the upper-left corner of the rectangle") LONG( "top", "y-coordinate of the upper-left corner of the rectangle") LONG( "right", "x-coordinate of the lower-right corner of the rectangle") LONG("bottom", "y-coordinate of the lower-right corner of the rectangle") end Rect = struct("RECT", _Rect)
Please note the important addition of Dec() call. This way our Rect() definition always displays decimal numbers independently of the number view mode set by its caller. Quite naturally, functions defined via struct() restore previous number view mode at exit.
And your main script should look like this:
-- -- import RECT definition -- require("MyLibs\\MyLibrary") CommentPos = 40 Rect "MyRect"
And this is not all! For example, we can add members checking to our RECT definition. In this example we add two checks: the first sees if RECT is normalized (that is, left <= right and top <= bottom), the second one checks that the RECT is zero-based (that is, both its left and top members are equal to 0):
-- -- check RECT structure in all possible ways -- local function CheckRect(CheckNormalized, CheckZero) -- are we requested to perform any checks? if CheckNormalized or CheckZero then -- remember current file position local OldPos = GetFilePosition() -- get all rect members local Left = GetI32() local Top = GetI32() local Right = GetI32() local Bottom = GetI32() -- restore file position SetFilePosition(OldPos) -- we will accumulate error messages here local Msg = "" -- perform normalization check if requested if CheckNormalized then if Left > Right then Msg = Msg .. "\rRECT is not normalized (left > right)" end if Top > Bottom then Msg = Msg .. "\rRECT is not normalized (top > bottom)" end end -- perform zero-based check if requested if CheckZero then if Left ~= 0 then Msg = Msg .. "\rRECT is not zero-based (left != 0)" end if Top ~= 0 then Msg = Msg .. "\rRECT is not zero-based (top != 0)" end end -- see if we got any errors and print them neatly if this is the case if string.len(Msg) > 0 then BlockComment(Msg .. "\r\r", true) end end end local function _Rect(CheckNormalized, CheckZero) TypeWidth = 4 NameWidth = 7 -- perform all necessary checks CheckRect(CheckNormalized, CheckZero) -- always show RECT members as decimal numbers Dec() LONG( "left", "x-coordinate of the upper-left corner of the rectangle") LONG( "top", "y-coordinate of the upper-left corner of the rectangle") LONG( "right", "x-coordinate of the lower-right corner of the rectangle") LONG("bottom", "y-coordinate of the lower-right corner of the rectangle") end Rect = struct("RECT", _Rect)
Great. Let's test our library. Suppose our file consist of four RECTs. We don't need any checks for the first one. The second must be normalized, the third must be zero-based, and the last must be both normalized and zero-based. Here is our script:
-- -- import RECT definition -- require("MyLibs\\MyLibrary") CommentPos = 40 Rect("MyRect", "an arbitraty RECT") CrLf() Rect("NormalRect", "must be normalized", true) CrLf() Rect("ZeroRect", "must be zero-based", nil, true) CrLf() Rect("NormalZeroRect", "must be both normalized and zero-based", true, true) CrLf()
Here we exploit the fact that all parameters to a struct()-defined function given after the first two special parameters (Name and Comment) are directly passed to that function's processing function. Try the last example with any arbitrary data and I bet you will be quite impressed.
I hope that now you are very close to the understanding of the fact that the possibilities are countless and everything is limited only with your imagination. Good luck!