2009-01-12:

BAT scripts and objective programming

bat:windows:medium
Today post is for all You Batmans out there ;>

The .BAT scripts (sometimes called batch scripts) are as old as DOS. First time I've met bats on my old 286 PC, and they were used there very commonly. You wanted to run a CGA game on a Hercules card (black and white, standard resolution 720 x 348)? You had to write a script! The script would be a three-liner - it run the CGA emulator, then the game, and then it disabled the emulation. An example script looked like this:

color 5
gra.exe
color 7


Then came the times where the user had to fight for every kilobyte of operational, XMS and EMS memory. Times where everyone had a few sets of autoexec.bat+config.sys scripts (or one with a menu). And then came Windows 95, 98, DirectX, and users forgot about .BAT scripts (today Monad aka Windows PowerShell makes even the power users forget about .AT).

Well, that was the short history of BATMAN^H^H^H scripts... Now for some objective programming.

As You may know, batch has limited features. Well, there is goto, and a quite powerful for loop, and also one can call internal and external functions, and there are also variables which one can read in two different ways (%the_normal_way% or the !delayed_expansion_way! - to use the last one You have to either use setlocal enabledelayedexpansion at the beginning of Your script, or set the key {HKLM,HKCU}\Software\Microsoft\Command Processor\DelayedExpansion to a DWORD 1). But classe ? Objects? Inheritance? Nope, non of that stuff.

But... Yep, there is always a "but"... You can e m u l a t e these features. And they even work ;>

The emulation is possible thanks to the internal mechanism of the variables - yep, the ones set using set name=value, and which we read using % or ! (see above). Let's consider the following example:

set a=ver
echo %a%


The cmd.exe interpreter (it's responsible for the execution of the .BAT files) will open the file with the above script, read a line, store the file position, and close the file (really, the file is opened and closed for every single line except for the bracketed expressions, which are treated as a single line; this is slow, but allows some Self Modifying), then it will check if there are any variables in the line, and switch them for their values (this is done until all the variables are gone). In case of the above script, the first line does not contain a variable to be read, but the second one does. just before the second line is executed, it looks like this: echo ver.

Let's change the above code a little:

set a=ver
%a%


The first line is same, but the second contains only the "a" variable read. After reading that line, %a% will be switched to ver, and the ver command will be executed. So as You can see, the variables make pretty good function/command containers.

And now, let's use this mechanism to create a class, that will read a text file into an array (what? no arrays in .BAT? will emulate them! just like in mIRC script).
Let's call the class "File", and let's name the class constructor (well, it's a factory more then a constructor) the same way - "File". The class will contain the following fields/variables/whatever:

lines - array of lines
name - name of the file
count - number of lines in the array

It will also contain the following methods:

read - read the file

Let's checkout the factory. It takes two arguments - the name of the object we want to create, and the name of the file we want to read:

:File
set %1.name=%2
set %1.read=call :File.read %1
goto :EOF


The first line if, of course, a label marking a place in the script (it can be considered a function name). In the second line there is an interesting construction - we create the variable name using another variable, and a suffix .name, and we assign a value to this variable - the second argument (file name). The third line is used to "link" the method read to the class by assigning the variable object.read the a command like value call :File.read object - as You can see, we provide the object name as a "secret" argument, it's done just like in the old python or C++ (the first argument of File.read is of course a this/self type "pointer"). The fourth line is a .BAT kind of return.

An example usage of this factory/constructor looks like this:

call :File a abc.txt

After executing the above call, an object a is created, and it "has" two additional variables:
a.name=abc.txt
a.read=call :File.read a

To actually read the file, the user has to call the read method, writing %a.read%. The read method implementation looks like this:

:File.read
set j=0
for /f "delims=" %%i in ('type !%1.name!') do (
 set %1.lines.!j!=%%i
 set /a j=!j!+1
)
set %1.count=%j%
goto :EOF


The first line is the name of the method of course. The dot is a part of the name, it is not any special character, it's not treated as some kind of a separator, it's just a standard name character just as letter 'a' for example. I've used it to separate the name, but it's not required, and can be changed to an underscore _ or something else.
Next interesting thing is the for loop, which reads the file (!%1.name! is at first changed to !object.name!, and then to the value of that, for example abc.exe) line by line, and stores it in the "array", using the naming schema %1.lines.!j!, which is resolved to object.lines.0, object.lines.1, etc. There is also incrementation of the "j" variable in the loop: set /a j=!j!+1.
After the loop is done, the final value of "j" is stored in the %1.count variable.

Now, how to use this class? Let's look on the example:

call :File a abc.txt
%a.read%
echo Line count  : %a.count%
echo Second line : %a.lines.1%
echo Third line  : %a.lines.2%

goto :EOF


OK, we have the class, an object, what about pointers to objects? Well, its very simple. Look on the following example:

@echo off

call :File a abc.txt
%a.read%

call :File b edf.txt
%b.read%

echo File A: %a.name%
echo File B: %b.name%

set ptr=a
echo File *Ptr: %%ptr%.name%
set ptr=b
echo File *Ptr: %%ptr%.name%

goto :EOF


As one can see, using nested variable names solves the problem (we've used them earlier in the .read method too).

OK, next thing. The inheritance! Inheritance is very simple in interpreted languages - just call the factory of the parent class in the inheriting class, them reset what You want.
The below example has a class Figure, and two inheriting classes: Square and Triangle.

@echo off

call :Square a 10
call :Triangle b 10 5

!a.field!
!b.field!
echo Mother class: a !a.mother!, b !b.mother!

set ptr=a
!%ptr%.field!

set ptr=b
!%ptr%.field!

goto :EOF


rem Class : Figure name a b
rem Method: Figure, field (virtual)
rem Vars   : a, b

:Figure
set %1.a=%2
set %1.b=%3
set %1.mother=Figure
goto :EOF


rem Class : Square name a b (inheriting from Figure)
rem Method: Square, field
:Square
rem Call the super constructor
call :Figure %*
set %1.field=call :Square.field %1
goto :EOF

:Square.field
set /a field=!%1.a! * !%1.a!
echo Field of the square %1: %field%
goto :EOF


rem Class : Triangle name a b (inheriting Figure)
rem Method: Triangle, field
:Triangle
rem Call the super constructor
call :Figure %*
set %1.field=call :Triangle.field %1
goto :EOF

:Triangle.field
set /a field=!%1.a! * !%1.b! / 2
echo Field of triangle %1: %field%
goto :EOF


Well, and thats all for today. In the next post for Batmans, I'll show how to write an OpenGL app using .BAT ;>

P.S. don't use objective .BAT for bigger projects anyway ;p

Comments:

2009-01-12 08:27:27 = asf
{
fun stuff
}
2009-01-13 22:27:51 = Gynvael Coldwind
{
@asf
Glad You like it;>
}
2015-10-29 11:23:28 = Bazic
{
You've exploited what is almost nonexploitable :)
}

Add a comment:

Nick:
URL (optional):
Math captcha: 8 ∗ 6 + 3 =