SCRIPTING LANGUAGE TUTORIAL (intermediate level)
by Alyce Watson

Home

Remembering Logo
Turtle graphics tutorial
Scripting language tutorial
Changing window icons at runtime
A real spinner control
Tip Corner - Select Case
Hocus Focus
Illuminated Radiobuttons

SCRIPTER.BAS
Please see the zip file attached to this newsletter for a copy of scripter.bas.

What is a scripting language? It is a language that allows users to write a script -- often in the form of a plain text file -- and it interprets the commands in the script and performs actions according to the commands. Many installation programs read scripts. The installer might be called "setup.exe" and the script might be "setup.lst", for example.

We can write scripting languages in Liberty BASIC. We can create a program that will read a script and proceed according to the instructions contained in the script. Some people have written successful scripting languages using Liberty BASIC. Brandon Watts wrote one called Panther, and Philip Richmond wrote a very popular game creation scripting language called Creative Adventure Toolkit.

Creative Adventure Toolkit is available at Phil's Liberty BASIC Adventure Page:
http://www.richmond62.freeserve.co.uk/index.htm

The Wattsware Website appears to be offline. You can download Panther at Download.com, here:
http://download.com.com/3000-2069-5404551.html?tag=lst-0-5

A scripting language needs three things. It requires a way for users to write scripts. Scripts may often be written in Notepad, since they are likely to be simple text files. We can provide a utility to help users write scripts. We can keep it very simple and basic, or we can get very fancy.

A scripting language also needs an interpreter. The interpreter is the heart of the language. It must read the commands written by the user in his script and translate them to commands that can be recognized by Liberty BASIC.

A scripting language must also have a way to implement the commands from the script after it has translated them to
Liberty BASIC syntax.

We've provided a program that contains all three of these needed functions. It is very simple, and it is called "scripter.bas".

The program contains a texteditor where users will write their scripts. The scripts can even be saved and loaded
again at a later time.

It also contains a parser to translate the script commands into Liberty BASIC commands.

We've based this scripting language on turtle graphics, similar to Logo. The window contains a graphicbox to implement the scripted drawing commands.

Because the program uses a feature that was added in Liberty BASIC v3.01, you must have LB 301 to run it. It is a good idea to do a version check in a case like this:

If Val(Version$)<3.01 Then
     Notice "You must have at least LB3.01 to run this program."
     End
     End If


It is always a good idea to initialize all variables that will be used in a program, and document their uses:

 ForegroundColor$ = "Black"
     BackgroundColor$ = "Buttonface"
     dsteps = 0 'number of drawing steps
     cstep = 0 'current drawing step
     Dim steps$(20) 'array to hold drawing script info
     crlf$=Chr$(13)+Chr$(10) 'carriage return/line feed

The Scripter window will contain some menus and buttons:

 WindowWidth = 606 : WindowHeight = 480
     UpperLeftX = Int((DisplayWidth-WindowWidth)/2)
     UpperLeftY = Int((DisplayHeight-WindowHeight)/2)
[ControlSetup]
     Menu #1, "&File", "&Open", [open], "&Save",      [save],_
     "E&xit", [quit]
     Menu #1, "Edit" 'position automatic Edit menu in second spot
     Menu #1, "&Run", "R&un",[runit]
     Menu #1, "&Help", "&Instructions", [help], "&Commands",_
     [commands], "&About",[about]
     Button #1.open, "Open",[open],UL,10,6,80,24
     Button #1.run, "Run",[runit],UL,92,6,80,24
     Statictext #1.s, "", 210,4,200,30
     Statictext #1.pen, "Pen is down!",411,4,200,30

It will also contain a graphicbox to implement the drawing commands and a texteditor for users to write the script.

     Graphicbox #1.g, 200,38,400,400
     Texteditor #1.t, 0,38,198,400

Always issue a TRAPCLOSE command, and be sure that the closing routine closes all open files and windows and ends the program with an END command:

[quit]
     Timer 0
     Close #1 : End


The program will allow the user to save and open scripts in the texteditor. See the program code for details on these operations. We'll focus here on the heart of the program, which is interpreting and implementing the actual script.

We must set up the commands that will be recognized by our simple scripting language. We'll stick to a few turtle graphics commands, and add some functionality for the user:

[commands]
     center = center the pen
     north = turn north
     west = turn west 
     south = turn south 
     east = turn east
     turn a = turn number of degrees in a
     moveto x y = goto point indicated
     move n = go distance of n in current direction
     up = pen up, move without drawing
     down = pen down, draw while moving

If you look at the command list, you will see that some commands are identical to LB graphics commands. "North" is one such command. Some others are standing in for LB graphics commands. "Move" is the same as the LB command "Go". There are also some commands that do not have an equivalent LB command, like "east". It will be up to our interpreter to read these commands and to translate them into Liberty BASIC commands.

When the user has loaded or typed a script into the texteditor, he can click a button or use a menu to run his script. The first thing the Scripter program will do is clear the drawing area for the start of the new script graphics.

      #1.g "cls; fill Darkblue; color cyan ; size 2"
      #1.g "north; home"

Next, we'll retrieve the number of lines in the script by issuing a "!lines" command to the texteditor:

      #1.t "!lines dsteps" 'get number of lines in texteditor

We COULD simply read the script from the texteditor, a line at a time, implementing the commands as we go along. This might cause an uneven performance, halting for each step while the interpreter routine does its job. To make for a smoother graphics display, we'll interpret the commands and store the resulting LB syntax commands in an array, which we can then loop through to display the graphics at a nice, steady speed.

We'll need enough array elements to contain all of the lines of the script, so we'll REDIM the array to be the same size as the number of lines contained in the texteditor:

     ReDim steps$(dsteps) 'redim array to number of lines

Now we can fill the array with the scripted commands, in preparation for their translation into LB commands:

 'fill array with drawing commands
     For i = 1 to dsteps
     #1.t "!line ";i;" txt$"
     'trap blank lines:
     If Len(txt$)=0 Then txt$="move 0"
     steps$(i)=txt$
     Next

Did you notice the line:

      If Len(txt$)=0 then txt$="move 0"

We've included that to trap any possible errors that a blank line might cause in our interpreter.


We've now filled the string array with the lines of text from the texteditor. It is time to translate the steps to LB syntax. To do this, we will parse the text in the commands. We've written a parsing function, which will return 0 if there were no problems, but otherwise it will return a positive number that indicates the line number where a problem is encountered.

 okay = Parse(dsteps) 'the heart of the scripter program
 If okay>0 Then 'there was an error
     Notice "An error occured on line ";okay
     Wait
     End If

We'll discuss the parsing routine in detail shortly, but for now, notice that we've stopped the program from executing and given the user a message telling him where to find his error, if an error occurs.

If there is no error, we'll clear the screen again to make extra sure that no drawing remnants remain, then set the counter for the steps to be 0, and start the timer that will draw one step each time it is activated:

 'everything is okay, start the drawing process cursor hourglass 'to show      user something is happening
     #1.g "cls; fill Darkblue; color cyan ; size 2"
     #1.g "north; home"
     cstep=0
     Timer 500, [draw]


The timer routine increments the counter, and draws the current step by accessing the array item that matches the counter value.

 cstep=cstep+1 'increment drawing step number
 'draw the current step
     #1.g " " + steps$(cstep) + " "

To draw the current step, we've simply printed it to the graphicbox as a string. LB3 allows us to omit the word "print". We could also write the command above as follows. Either is correct. We've chosen to use the shorthand version that omits the word "print".

 'another way to send the drawing command:
     print #1.g, " " + steps$(cstep) + " "

The drawing step has now taken place in the drawing area, and we'll scroll the code window to remain in sync with the drawing step. This isn't necessary, but it is a nice way to give the user feedback about what is happening.

 'scroll the code window in sync with current drawing step
     #1.t "!origin ";cstep;" 1";

The following step is also not necessary, but it gives the user another bit of feedback while his script is running. We use a statictext command to display the current script command:

 'get current script command, write to statictext
     #1.t "!line ";cstep;" txt$"
     #1.s txt$

Did you notice that we've used two different ways to add strings together? Adding strings together to form one larger string is called "concatenation". Liberty BASIC allows us to do this with a plus sign "+" or with a semi colon. To use the "+" operator to concatenate strings, make sure than any numbers are converted to strings first. The following two commands are equivalent.

     #1.t "!origin ";cstep;" 1";
     #1.t "!origin " + str$(cstep) + " 1"

If the drawing pen is up, it may look like nothing is happening in the drawing area. Let's alert the user by letting him know when the pen changes to UP or to DOWN by placing that information into a statictext control:

 'let user know if pen is up or down
     t$=Lower$(Word$(txt$,1))
     If Lower$(t$)="down" Then Print #1.pen, "Pen is DOWN!"
     If Lower$(t$)="up" Then Print #1.pen, "Pen is UP!"


The last part of the drawing routine evaluates the current step number against the total number of steps to discover when all drawing steps have been accomplished. When the script is finished running, the timer is turned off and the code window is scrolled back to the top:

 If (cstep>=dsteps) Then 'last step was reached
     Timer 0 'turn timer off
     cursor normal 'to show user drawing is done
     #1.g "color red; circlefilled 4"
     #1.g "flush"
     #1.t "!origin 1 1" 'reset code window to top line
     End If
     Wait


The heart of the Scripter program is the parser. It reads the script commands one by one and evaluates them. If it understands the command, it can translate it into LB syntax. If it does not understand the command, it will halt the parsing process at the current step and return the line number of the errant script line to the calling function. The parser uses the Select Case command. If you are not familiar with Select Case, then see the Tip Corner section of this newsletter.

The following description deals with the parser in this scripter.bas program. Every scripting program will have different needs, so I've tried to show a simple method to evaluate text input and use it to cause something to happen in Liberty BASIC.

In the parsing routine, we will loop through the array of script commands, evaluating them one at a time.

 For i = 1 to num
 s$=Lower$(steps$(i)) 'entire command line
     c$=Lower$(Word$(steps$(i),1)) 'command is first word
     Select Case c$ 'use script command to determine needed Liberty BASIC drawing command

First, we extract the current step into a variable, called s$. Then we check the first word$() in s$ to extract the command part of the script line to the variable c$. We'll put this first word$() into a variable c$. This is the variable we will evaluate in our Select Case routine.

The first command we check for is "center":

 Case "center" 'equivalent to LB home command
     steps$(i)="home"

If we find a match, then we translate this to the LB "home" command and replace the array element with "home".

We then check for "north". This has an exact equivalent to an LB drawing command, so we simply write it back into the array:

 Case "north" 'equivalent to LB north command
     steps$(i)="north"

The next command we check for is "south". There is no equivalent command in LB, but we can implement this ourselves by setting the direction to "north" then turning 180 degrees:

 Case "south" 'south=north+180
     steps$(i)="north; turn 180"

Again, there is no LB "east" command, so we implement it by setting the direction to "north", then turning 90 degrees:

 Case "east" 'east=north+90
     steps$(i)="north; turn 90"

"West" is implemented by setting the direction to "north" and turning 270 degrees:

 Case "west" 'west=north+270
     steps$(i)="north; turn 270"

Since "turn" has an exact equivalent in LB, we simply write it back to the array, but in this case we need to do more parsing to discover the angle used in the "turn" command. We'll retrieve the second word$() in the command string. It would be easy to place this directly into our angle string variable, a$, but what would happen if the user had written a command:

turn hello

"Hello" is not a numeric value and we wouldn't want to try to use it in an LB command that required a number. We'll instead get the numeric value of the second word$() with the VAL() function. If the second word$() isn't a number, then VAL() will return 0. Once we have the value, we convert it to a string so that we can write the string back into our array:

 Case "turn" 'equivalent to LB turn command
     'check for value, in case second word has
     'no numeric equivalent - it will then be 0
     a=Val(Word$(s$,2))
     a$=Str$(a)
     steps$(i)="turn ";a$

"Moveto" is not a Liberty BASIC command, but we are using it as an exact equivalent of "goto". "Goto" requires two parameters: an X and a Y location are needed. We'll get these in a similar fashion to getting the angle for "turn", only this time we need to get a value for both the second and third words in the user's command string. The second word$() will be the X value and the third word$() will be the Y value:

 Case "moveto" 'goto point indicated
     'trap x,y values that are larger
     'than drawing area
     x=Val(Word$(s$,2))
     If x>396 Then x=396
     x$=Str$(x)
 y=Val(Word$(s$,3))
     If y>396 Then y=396
     y$=Str$(y)
 steps$(i)="goto "+x$+" "+y$

If you look at the routine above, you'll see that we tried to do some more error-trapping. We know that our drawing area is contained within a box that is 400x400 pixels, so we've checked for values greater than the visible area of the graphicbox and changed the value if it is greater than that to keep the drawing visible.


"Move" is not a Liberty BASIC command, but it has a direct equivalent in "go". As in the two above examples, we also need to check for a numeric value in the command string to see the desired distance to travel:

 Case "move" 'travel set distance in current direction
     d=Val(Word$(s$,2))
     'not perfect, but trap distances outside
     'of drawing area:
     #1.g "posxy x y"
     if x+d>396 then d=396-x
     if y+d>396 then d=396-y
 d$=Str$(d)
     steps$(i)="go "+d$


The last two scripter.bas commands for are "up" and "down" which are also Liberty BASIC commands and they can be written back into the array just as they are:

 Case "down" 'equivalent to LB down command
     #1.g "down"
 Case "up" 'equivalent to LB up command
     #1.g "up"


Remember, we called the return from this parsing function "okay" and if it is greater than 0, it indicates the line number of the problem, but if it is 0, then everything worked properly and it is okay to start drawing. The error handler is in the "Case Else" case:

 Case Else 'script command not understood by parser
     result=i 'return line number of error
     'scroll to error line:
     #1.t "!origin ";i;" 1 "; 'row, column
     #1.t "!select 1 ";i 'column, row
     exit for 'error, exit for/next loop

If an error is encountered, then "result" is set to the value of the current line, or "i". The code window is scrolled to that line to make it easy for the user to see where he made his mistake. Since an error was encountered, there is no need to continue parsing the commands, so an "exit for" command is issued which causes execution to jump out of the parsing loop and to return the line number with the error to the calling function.

 Parse=result 'return the result
     End Function


Please try scripter.bas to see the program in action. Why not enlarge upon the sample program, or go in a different direction and write a scripting language of your own?

Home

Remembering Logo
Turtle graphics tutorial
Scripting language tutorial
Changing window icons at runtime
A real spinner control
Tip Corner - Select Case
Hocus Focus
Illuminated Radiobuttons