Basic4GL, Copyright (C) 2003-2007 Tom Mulgrew

Programmer's guide

26-Jul-2008
Tom Mulgrew

This document

This document describes the various functions, and how to use them to do something useful in Basic4GL.
It does not go into great detail about the language syntax itself (see the Language Guide if that is what you need).
It does not go into detail about OpenGL programming (see the OpenGL guide for that.)

Text output

Basic text output

Print

Basic text output is performed using the "Print" or "Printr" function.

Format:

Print text-parameters ;

Or:

Print text-parameters

Where text-parameters is a list of parameters, separated by semicolons (;).

"Print" leaves the cursor positioned after the last character printed.

"Printr" will automatically move the cursor to the start of the next line after the text has been printed.
If the cursor reaches the bottom of the screen, the text will scroll up the screen to make room for the new line.

Example:

Printr "Hello ";
Printr "and welcome to ";
Printr "Basic4GL"
Print
Print "Have a nice day"
Traditional BASIC syntax

"Print" behaves slightly differently when "traditional BASIC" syntax is enabled, or "Basic4GL with traditional print" syntax is enabled.
(You can enable the "traditional BASIC" syntax by placing a

language traditional

line at the top of your program, or

language traditional_print

for just the print command syntax.)

In this mode, the "print" command will move the cursor to the next line if it does not end with a trailing semicolon (;)

For example:

language traditional
print "Line 1"
print "Line 2"
print "Line 3"

Will print:

Line1
Line2
Line3

If the "print" command does end with a semicolon, then the cursor will remain on the same line. So:

language traditional
print "Welcome ";
print "to ";
print "Basic4GL"

Will print:

Welcome to Basic4GL

Thus the "printr" command is not required in this syntax (but it is still available for compatibility sake).

Locate

Locate positions the text cursor on the screen.

Format:

Locate X-position, Y-position

The Basic4GL text cursor is invisible. It determines to where on the screen "Print" and "Printr" will write.

By default the Basic4GL displays 40 characters across by 25 characters down (this can be changed using the "ResizeText()" function).

The topmost row is row 0.
The leftmost column is column 0.

Example:

Dim d#
While True
Cls
Locate Sin (d#) * 15 + 18, 10
Print "Hello"
Sleep (100)
d# = d# + 0.1
Wend

CursorCol, CursorRow

CursorCol() returns the column the cursor is on.
CursorRow() returns the row the cursor is on.

The topmost row is row 0.
The leftmost column is column 0.

Color

Sets the text colour.

Format:

Color (red, green, blue)

Where red, green and blue are integers between 0 and 255 inclusive indicating the intensity of their respective colour component.

Once the text colour is set, any text printed will be in that colour until the text colour is changed.

Example:

dim t
TextMode (TEXT_BUFFERED)
while true
for t = 1 to 10: color (rnd()%255, rnd()%255, rnd()%255): print chr$(rnd()%255): next
DrawText ()
wend

Cls

Cls clears all text from the screen and repositions the cursor to the top left.

ClearLine

ClearLine () clears the current line (the one which the cursor is on).

Example:

dim i
SetTextScroll (false)
for i = 0 to 24: printr i: next
locate 0, 10
ClearLine ()		' Line 10 is cleared

ClearRegion

Cears a rectangular region of the screen.

Format:

ClearRegion (x1, y1, x2, y2)

Where x1, y1, x2, y2 are integers that define the top left column and row (x1, y1) and the bottom right column and row (x2, y2) of the rectangular region to be cleared.

Example:

dim x, y
SetTextScroll (false)
TextMode (TEXT_BUFFERED)
for y = 1 to TextRows ()
    for x = 1 to TextCols ()
        print "#"
    next
next
ClearRegion (5, 5, 35, 9)
locate 13, 7: print "Cleared region"
DrawText ()

TextRows, TextCols and ResizeText

TextRows () returns the number of text columns.
TextCols () returns the number of text rows.

ResizeText (x, y) resizes the text display to y rows by x columns and clears the text.

Example:

dim i, a$
a$ = "Basic4GL"
i = 100
while i >= 4
ResizeText (i * 2 + 1, i + 1)
Locate (TextCols() - Len(a$)) / 2, TextRows() / 2
Print a$
Sleep (50)
i = i - 2
wend

Text scrolling

Advancing the cursor past the end of the line causes it to wrap around onto the next line.

Advancing the cursor past the end of the bottom-most line, or performing a Printr on the bottom-most line causes the text to scroll up by one line.

Example 1:

Print glGetString (GL_EXTENSIONS)

Example 2:

dim d#
while true
locate sin(d#)*15+17, TextRows()-1
Printr "Hello"
Sleep (50)
d# = d# + 0.3
wend

Alternatively you can disable text scrolling with the TextScroll command.

SetTextScroll

SetTextScroll () enables or disables text scrolling when the cursor reaches the bottom of the text screen.

Format:

SetTextScroll (scroll)

Where scroll can equal true to enable text scrolling or false to disable it. Text scrolling is enabled by default.

Example:

SetTextScroll (false)
dim row
print "########################################"
for row = 2 to 24
    print "#                                      #"
next
print "########################################"

TextScroll

TextScroll () returns true if text scrolling is enabled, or false if it isn't.

Fonts

Basic4GL fonts are special transparent images, consisting of a 16 x 16 grid of characters. You can set a new font by calling:

Font (texture)

Where texture is an OpenGL texture handle (usually returned from LoadTex()).

Example:

printr "Normal font"
dim texture
texture = LoadTex("data\charset2.png")
Font (texture)
printr "charset2.png font"

To get the texture handle for the default font, call:

DefaultFont ()

Example:

dim texture
texture = LoadTex("data\charset2.png")
Font (texture)
printr "charset2.png font"
Font (DefaultFont ())
printr "Normal font"

Text modes

Basic4GL has 3 different modes for rendering text on the screen. You choose one by executing the appropriate TextMode() call:

The default mode is TEXT_SIMPLE.
In this mode, Basic4GL redraws the screen after each "Print", "Printr", "Cls" or "ResizeText()".

This mode is easy to use, and the results are instant. However there are a number of situations where you may find it favourable to use TEXT_BUFFERED.

In TEXT_BUFFERED mode, Basic4GL does not update the screen until you call DrawText ().
This has advantages if you are animating a large amount of text:

  1. Reduces flicker.
    The screen is only updated once all text has been drawn.
  2. Reduces screen resync delay.
    Depending on your video card and OpenGL settings, your OpenGL system may wait for vertical syncronisation before every screen update.
    This can lead to unnecessarily slow animations in TEXT_SIMPLE mode, as Basic4GL must stop and wait for vertical resync after every "Print" statement.

However, you must remember to call DrawText() or the user won't see any changes.

Example:

TextMode (TEXT_BUFFERED)
dim d#, t
while true
for t = 1 to 10
Locate sin(d#*t/19.0+t)*14+14,t*2+1
print " Thing "
next
DrawText ()
Sleep (10)
d# = d# + .1
wend

TEXT_OVERLAID mode is used to combine OpenGL graphics with text.
This mode is necessary if you wish to use OpenGL graphics commands and text at the same time.

This would cause problems in TEXT_SIMPLE or TEXT_BUFFERED mode, as both modes automatically clear the screen before rendering the text.

In TEXT_OVERLAID mode the DrawText() function will not clear the screen, or copy the result to the front buffer. It will simply render the current text transparently over the top of the current scene.
You must therefore manually clear the screen and swap it to the font buffer at the appropriate times.

The advantage of this mode is that it gives you a finer degree of control, and allows you to combine text and other graphics, such as OpenGL rendered objects.

Example:

TextMode (TEXT_OVERLAID)
locate 12, 12: print "This is a square"

dim a#
while true
glClear (GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT)
glLoadIdentity ()
glTranslatef (0, 0, -2)
glRotatef (a#, 0, 0, 1)
glBegin (GL_QUADS)
glColor3f (1, 0, 0): glVertex2f ( 1, 1)
glColor3f (0, 1, 0): glVertex2f (-1, 1)
glColor3f (0, 0, 1): glVertex2f (-1,-1)
glColor3f (1, 1, 1): glVertex2f ( 1,-1)
glEnd ()
DrawText ()
SwapBuffers ()
a# = a# + 0.3
wend

DrawText

Format:

DrawText()
DrawText(flags)

The DrawText command is used to draw text and/or sprites. The default (no parameter) version draws all text and sprites that are on the screen. Alternatively you can control what it draws by passing it a bitmask composed of one or more of the following flags:

DRAW_TEXT Draw text 
DRAW_SPRITES_BEHIND Draw all sprites behind the text 
DRAW_SPRITES_INFRONT Draw all sprites infront of the text 
DRAW_SPRITES Draw all sprites behind or infront of the text 

Example:

TextMode(TEXT_OVERLAID)
glDisable(GL_DEPTH_TEST)
' Create some bouncing balls
const ballcount = 100
dim tex = LoadTex("data/ball.png")
dim sprites(ballcount), i
for i = 1 to ballcount
    sprites(i) = NewSprite(tex)
    if rnd()%2 then SprSetZOrder(-1) endif
    SprSetPos(rnd() % 640, rnd() % 480)
    if rnd()%2 then SprSetXVel(1) else SprSetXVel(-1) endif
    if rnd()%2 then SprSetYVel(1) else SprSetYVel(-1) endif
next
do
' Clear the screen background           
    glBegin(GL_QUADS)
        glColor3f(.5, 0, 0)
        glVertex3f(-10,  10, -5)
        glVertex3f( 10,  10, -5)
        glColor3f(0, 0, .5)
        glVertex3f( 10, -10, -5)
        glVertex3f(-10, -10, -5)
    glEnd()
    ' Draw behind sprites and small text
    ResizeText(80, 50)
    locate 35, 20: print "Small text"
    DrawText(DRAW_SPRITES_BEHIND or DRAW_TEXT)
   
    ' Draw large text and infront sprites
ResizeText(20, 12)
    locate 6, 7: print "Big text"
    DrawText(DRAW_TEXT or DRAW_SPRITES_INFRONT)
    ' Show completed frame
SwapBuffers()
            
    ' Animate bouncing balls
while SyncTimer(10)
        AnimateSprites()
        for i = 1 to ballcount
            BindSprite(sprites(i))
            if SprX() < 0 or SprX() > 640 then
                SprSetXVel(-SprXVel())
            endif
            if SprY() < 0 or SprY() > 480 then
                SprSetYVel(-SprYVel())
            endif
        next
    wend
loop

Reading from the screen

CharAt$

CharAt$(x, y) returns the character at column x and row y.

Example:

TextMode(TEXT_BUFFERED)
dim d#, t, x, y, crash: crash = false: x = TextCols()/2
while not crash
for t = 1 to 5: locate sin(d#+t)*15+15,t*2+2: print" Thing! ": next
y=y-1
if y<0 then
y = TextRows()-1: cls
else
if ScanKeyDown(VK_LEFT) and x > 2 then x = x - 1 endif
if ScanKeyDown(VK_RIGHT) and x < 36 then x = x + 1 endif
crash = CharAt$(x,y)<>" "
locate x, y: print"X"
endif
DrawText()
WaitTimer (80)
d# = d#+0.06
wend

Timing

Sleep

Pauses execution for a number of milliseconds.

Format:

Sleep (milliseconds)

Note: The application is completely unresponsive while sleeping. Therefore Basic4GL will not sleep for more than 5000 msec (5 seconds) at a time.
To sleep for more than 5 seconds, use a loop.
For example:

Dim i
For i = 1 to 60: Sleep (1000): Next

Will pause for 60 seconds, but still give the user the opportunity to break out of the program if he/she wishes.

WaitTimer, SyncTimer and ResetTimer

WaitTimer

This function is similar to Sleep, and indeed has the same format:

WaitTimer (milliseconds)

The difference is that WaitTimer waits until milliseconds milliseconds has elapsed from the previous WaitTimer call.

This difference is significant if WaitTimer is used inside an animation loop, with other code that may take some time to execute (such as rendering a frame).
For example:

While true
Draw a frame
WaitTimer (100)
Wend

If Draw a frame were to take 40 milliseconds, then WaitTimer will pause for only 60 milliseconds, ensuring that the loop is correctly iterated 10 times a second.

Even simple animations can potentially take up to the resync period of the monitor (anything from 1/100th to 1/50th of a second), if the user's graphics card is configured to wait for retrace before drawing.

SyncTimer

SyncTimer returns true if you need to update the internal state of the application to catch up to the clock.

This can be used to force an animation to update internally so many times per second, regardless of a PC's rendering speed, and is intended to be used as follows:

While main-loop-condition
Render scene
While SyncTimer (delay)
Update state
Wend

For example, if delay was 10 milliseconds, then Update state will execute 100 times per second, regardless of whether the computer is capable of rendering 20 or 100 frames per second.

Example:

dim x, y, a#, b#
while true
glClear (GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT)
glLoadIdentity ()
glTranslatef (0, 0, -16)
glRotatef (a#, 0, 0, 1)
for y = -5 to 5: for x = -5 to 5
glPushMatrix ()
glTranslatef (x * 3, y * 3, 0)
glRotatef ((x + y) * 60 + b#, 1, 0, 0)
glBegin (GL_QUADS)
glColor3f (1, 0, 0): glVertex2f ( 1, 1)
glColor3f (0, 1, 0): glVertex2f (-1, 1)
glColor3f (0, 0, 1): glVertex2f (-1,-1)
glColor3f (1, 1, 1): glVertex2f ( 1,-1)
glEnd ()
glPopMatrix ()
Next: Next
SwapBuffers ()
while SyncTimer (10)
a# = a# + 0.9: b# = b# + 3.6
wend
wend

Keyboard input

Text input

Input

Reads a text string from the keyboard.

Format:

Input variable

Input "prompt"; variable

Input "prompt", variable

Input will pause the program and wait until the user types in some text and hits enter. The text will be displayed on the screen as the user types.
If a prompt is given, it will be displayed on the screen. The first format (with the semicolon) automatically displays a question mark after the prompt. The second format (with the comma) simply displays the prompt and nothing else.

Once the user has hit enter, the program will continue, and variable will contain the resulting text or number that the user entered.

Examples:

dim name$
input "What is your name"; name$
print "Hello " + name$
dim number
input "Please enter a number: ", number
print "The square root of " + number + " is " + sqrt (number)

Note: Be aware that Basic4GL's implementation of "input" is not as complete as other BASICs.
Basic4GL does not support inputting multiple variables with the same input command.
Also, Basic4GL will not prompt the user to "Redo from start" if the text he/she entered cannot be converted into the destination variable type. Instead it will simply set the destination variable to 0.

Note 2: There is an older Input$() function that has the syntax:

variable = Input$()

This is an old syntax, and kept only for backwards compatibility with older Basic4GL programs.

Key state

KeyDown and ScanKeyDown

Determines whether a key is currently pressed or released.

Format:

KeyDown(character)

ScanKeyDown(scan-code)

KeyDown takes the first character of the string argument passed to it.
ScanKeyDown takes a numeric virtual key code, often a VK_x constant (such as VK_UP e.t.c. Click "Help|Functions and Constants list..." then the "Constants" tab for a list).

Both functions return true (-1) if the key is being pressed or false (0) if otherwise.

Note: KeyDown("") will always return false.

Example 1:

ResizeText (5, 1)
while true
locate 0, 0
if KeyDown ("A") then print "Down"
else print " Up "
endif
wend

Example 2:

dim a#
while true
glClear (GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT)
glLoadIdentity ()
glTranslatef (0, 0, -5)
glRotatef (a#, 0, 0, 1)
glBegin (GL_TRIANGLES)
glVertex2f ( 0, 1.5)
glVertex2f (-1,-1)
glVertex2f ( 1,-1)
glEnd ()
SwapBuffers ()
while SyncTimer (10)
if ScanKeyDown (VK_LEFT) then a# = a# + 3: endif
if ScanKeyDown (VK_RIGHT) then a# = a# - 3: endif
wend
wend

Buffered input

Inkey$ and InScanKey

Format:

Inkey$ ()

InScanKey ()

Basic4GL buffers characters and raw scan codes typed into the output window.

Inkey$ () returns characters typed as single character strings. If no characters are buffered, Inkey$ () will return an empty string.

InScanKey () returns scan codes as integers. If no scan codes are buffered, InScanKey () returns 0.

Example:

while true: print Inkey$ (): wend

ClearKeys

Format:

ClearKeys ()

ClearKeys () clears the keyboard buffer, throwing away any keypresses that have yet to be handled by Inkey$ () or InScanKey ().

ClearKeys () is equivalent to the following code:

While Inkey$() <> "": wend
While InScanKey() <> 0: wend

Mouse input

Mouse functions

The following functions can be used to read the mouse.

Mouse_X, Mouse_Y

These functions return the position of the mouse in relation to the OpenGL window (if in windowed mode), or the screen (fullscreen mode).

Mouse_X() returns the X (horizontal) position.
Mouse_Y() returns the Y (vertical) position.

Both functions return a real value between 0 (far left, or top) and 1 (far right, or bottom).

Example 1:

print Mouse_X () + ", " + Mouse_Y (): run

Example 2:

ResizeText (80, 50)
dim x, y, char$
while true
    if not Mouse_Button (MOUSE_LBUTTON) then
        locate x, y: print char$
    endif
    x = Mouse_X () * TextCols ()
    y = Mouse_Y () * TextRows ()
    char$ = CharAt$ (x, y)
    locate x, y: print "X"
wend

Mouse_Button

Mouse_Button (index) returns true if button index is being pressed, or false if it isn't.

The left mouse button is index 0, the right is index 1 and the middle is index 2.
Alternatively you can use the following constants:

Left button: MOUSE_LBUTTON
Right button: MOUSE_RBUTTON
Middle button: MOUSE_MBUTTON

Example:

dim i               
print "Press the mouse buttons!"
while true
    locate 0, 2
    for i = 0 to 2: printr Mouse_Button (i) + " ": next
wend

Mouse_Wheel

Mouse_Wheel() returns how many notches the mouse wheel has turned since the last time Mouse_Wheel() was called (or the program started).

For example:

dim i
print "Turn the mouse wheel!"
while true
    i = i + Mouse_Wheel ()
    locate 0, 2: print i + "    "
wend

Mouse_XD(), Mouse_YD()

These functions return how far the mouse has moved since the last time Mouse_XD() or Mouse_YD() was called (respectively).

Mouse_XD() returns the X (horizontal) distance.
Mouse_YD() returns the Y (vertical) distance.

These functions are useful for first person shooter type movement, where the mouse is used to turn the player, instead of controlling a pointer on the screen.

Note: Mouse_XD() and Mouse_YD() work internally by positioning the mouse pointer in the middle of the window and measuring how far the mouse moves from that position. This means that using Mouse_X() or Mouse_Y() will produce unexpected results, and it is recommended you stick to one method or the other.

Example:

dim x#, z#
while true
    glClear (GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT)
    glLoadIdentity ()
    glTranslatef (0, 0, -4)
    glRotatef (z#, 0, 0, 1)
    glRotatef (x#, 1, 0, 0)
    glBegin (GL_TRIANGLES)
        glVertex2f (0, 1)
        glVertex2f (-.5, -1)
        glVertex2f ( .5, -1)
    glEnd ()
    SwapBuffers ()
    z# = z# - Mouse_XD () * 100
    x# = x# + Mouse_YD () * 100
wend

Joystick input

Note: A big thanks to Tyler Bingham for implementing the joystick support!

Basic4GL supports input from a single joystick. If more than one joystick is attached to a PC, Basic4GL will use whatever one the operating system says is first.

Joystick functions

The following functions can be used to read the joystick.

Joy_Keys

Joy_Keys() takes a snapshot of the joystick and generates appropriate keypresses. Arrow keys are generated for stick movement, and space bar and control (Ctrl) keypresses are generated for joystick buttons 0 and 1 respectively.

The keypresses can then be detected with the keyboard input functions:

Note that Inkey$() is not affected by Joy_Keys().

This effectively provides a simple and easy way of incorperating joystick and keyboard support into a program.

Example:

dim x, y
x = TextCols () / 2
y = TextRows () / 2
while true
    Joy_Keys ()
    if not ScanKeyDown (VK_SPACE) then
        locate x, y: print " "
    endif
    if ScanKeyDown (VK_LEFT)    and x > 0 then                  x = x - 1 endif
    if ScanKeyDown (VK_RIGHT)   and x < TextCols () - 1 then    x = x + 1 endif
    if ScanKeyDown (VK_UP)      and y > 0 then                  y = y - 1 endif
    if ScanKeyDown (VK_DOWN)    and y < TextRows () - 1 then    y = y + 1 endif
    locate x, y: print "X"
    Sleep (30)
wend

Joy_X, Joy_Y

Joy_X() returns the X (horizontal) position.
Joy_Y() returns the Y (vertical) position.

Both functions return a value from -32768 (far left, or top) to 32767 (far right or bottom).
0 is the centre of each axis. (If you have a stable, properly calibrated digital joystick.)

Example:

print Joy_X () + ", " + Joy_Y (): run

Joy_Button

Joy_Button(index) returns true if button index is currently being pressed, or false if isn't.

The first joystick button is index 0. The second is index 1 e.t.c

Example:

dim i
for i = 0 to 9
if joy_button (i) then print i: else print " ": endif
next
run

Joy_Left, Joy_Right, Joy_Up, Joy_Down

Joy_Left () returns true if the joystick is more than 100 units to the left. (This is equivalent to: Joy_X () < -100)
Joy_Right () returns true if the joystick is more than 100 units to the right. (This is equivalent to: Joy_X () > 100)
Joy_Up () returns true if the joystick is more than 100 units upwards. (This is equivalent to: Joy_Y () < -100)
Joy_Down () returns true if the joystick is more than 100 units downwards. (This is equivalent to: Joy_Y () > 100)

Joy_0, ..., Joy_9

There are also explicit functions for each joystick button from 0 through to 9.

Joy_0() returns true if the first joystick button is being pressed. (This is equivalent to: Joy_Button(0)).
...
Joy_9() returns true if the 10th joystick button is being pressed. (This is equivalent to: Joy_Button(9)).

Joystick polling

To "poll" the joystick means to take a snapshot of it's current state, including the readings of the X and Y axis and whether each button is up or down at the time of the poll.

Basic4GL automatically polls the joystick whenever one of the joystick functions is called, so you don't have to tell it to explicitly.
For example:

while true: printr Joy_X() + " " + Joy_Y () + " " + Joy_0() + " " + Joy_1 (): wend

You may want to explicitly tell Basic4GL when to poll the joystick, in order to make the program run faster.
Polling takes time (at least on older analogue joysticks). It is more efficient to poll the joystick once, and then act on the X axis, Y axis and button data captured in that poll than to poll the joystick for each axis and button that you read.

UpdateJoystick

UpdateJoystick () polls the joystick and takes a snapshot of the X and Y axis and the state of all the buttons.
Any Joy_? calls will now return the data captured at the time of the UpdateJoystick() call.
For example:

while true: UpdateJoystick (): printr Joy_X() + " " + Joy_Y () + " " + Joy_0() + " " + Joy_1 (): wend

Now instead of reading the joystick 4 times each time around the loop, we are only reading it once.
This runs significantly faster than the previous example on my PC (although my PC has an older analogue joystick attached to it.. I can't comment on digital joysticks.)

As soon as you call UpdateJoystick(), Basic4GL switches to explicit joystick updates, and stays that way until your program finishes executing. Therefore you must keep calling UpdateJoystick() at the appropriate times to ensure the joystick data is up to date.
If you don't, the joystick will appear frozen, for example:

UpdateJoystick ()
while true: printr Joy_X() + " " + Joy_Y () + " " + Joy_0() + " " + Joy_1 (): wend

Here we have moved the UpdateJoystick() call out of the main loop, so it is only called once at the start of the program.
Because we don't ever call it again, each joystick functions will simply return the same value each time, i.e the state of the joystick at the start of the program when UpdateJoystick() was called.

So manual polling can be faster, but you must do it right!

Command line

Basic4GL standalone programs can accept commands from the command line.
Command line arguments are entered after the program name when a program is run from the command line. For example, if we built a standalone exe called "CmdTest", and ran it in a command prompt window with the command:

cmdtest 1 banana 2 cucumber 3 "Tomato sandwich"

Then we have passed it 6 parameters:

  1. 1
  2. banana
  3. 2
  4. cucumber
  5. 3
  6. Tomato sandwich

We can access these parameters with the ArgCount and Arg functions.

ArgCount

ArgCount() returns the number of command line arguments.

Arg

Arg(index) returns parameter number index as a text string, where index is 0 to return the first parameter.
index should be between 0 and ArgCount() - 1, otherwise Arg(index) returns a blank string.

Setting command line arguments within Basic4GL

To set the command line arguments that a program will see when it is running inside the Basic4GL IDE, click Program->Arguments. Enter each argument on a separate line.

Some examples

Display all arguments

dim i
printr ArgCount(); " argument(s) found"
for i = 0 to ArgCount() - 1
    printr Arg(i)
next

Compile and run another program:

dim prog
if ArgCount() = 0 then
    printr "No program name!"
    end
endif
prog = CompileFile(Arg(0), "__")
if CompilerError() <> "" then
    printr CompilerError()
    end
endif
Execute(prog)
if CompilerError() <> "" then
    print CompilerError()
    end
endif

File I/O

A note on security

Basic4GL programs can only read and write files from the directory where the Basic4GL program was saved (or any subdirectory thereof).

This is for security, and is intended to protect people new to programming when trying out example programs from the internet and other sources. For all we know, the person who wrote the program might think that overwriting files in the Windows system directory is a hilarious practical joke. This way the potential damage is restricted to a small subfolder, and the people can download and run Basic4GL programs with confidence.

Obviously this means that if you distribute Basic4GL programs that use File I/O, you will have to ensure that the files read/written to end up in the appropriate directory so that they can be reached.

Note: This security restriction applies to the general purpose File I/O routines described below. Other functions that load data from disk are not subject to these restrictions, in particular the image and texture loading functions can load any file they want.

IMPORTANT:

As of version 2.4.12, you can switch off these safety features by unchecking "Safe mode" in the options screen.
If you do this, then you will need to make sure any program you run in Basic4GL will not damage your computer.

Also, standalone executables created with Basic4GL ALWAYS run with "safe mode" switched OFF.

Opening files

OpenFileRead and OpenFileWrite

Files are opened like so:

(For writing):

dim file
...

file = OpenFileWrite ("Files/filename.ext")

(For reading):

dim file
...

file = OpenFileRead ("Files/filename.ext")

Where filename.ext is the filename and extension that is to be opened.

file is an integer variable that will store the file handle. This is a number that Basic4GL generates to identify the file that was just opened, and will be passed to other file routines to read data from or write data to the file.

If a file is opened for writing, it replaces any file that was their previously. If no file exists, one is created.

Error handling

FileError

If a file I/O routine fails, the Basic4GL program simply keeps running, without performing the particular file operation that it attempted.

You can test whether the operation succeeded by calling the FileError () function. This is updated after every file operation. If the operation succeeded, it will be set to an error message, describing what went wrong.

For example:

dim file
file = OpenFileRead ("c:\autoexec.bat")
if FileError () <> "" then print FileError (): end endif
' Carry on...

Closing the file

CloseFile

It is good practice to close the file once you've finished with it as follows:

CloseFile (file)

If you forget, or your program stops for any reason before it can close the file, Basic4GL will close it automatically, the next time you run a Basic4GL program or when you close down Basic4GL.

File reading routines

The file must have been opened with OpenFileRead for these routines to work correctly.

ReadLine

ReadLine(file) reads a line from a text file and returns it as a string. The lines are separated by carriage return and/or newline characters.

ReadText

ReadText(file, skipEOL) skips over whitespace (spaces, tabs e.t.c) until it finds some text. It then returns all the consecutive text at that point until a whitespace character has been reached, as a string.
SkipEOL is a boolean (true/false) parameter. If it is true, then ReadText will skip over any end-of-line characters it finds in the file. If false, it will stop at the end-of-line and return a blank string.

This can be used to break up a text files into words.

ReadChar

ReadChar(file) reads a single character from the file and returns it as a string.

ReadByte

ReadByte(file) reads a single binary byte from the file and returns it as an integer.

ReadWord

ReadWord(file) reads a two byte "word" from the file and returns it as an integer.

ReadInt

ReadInt(file) reads a four byte integer from the file and returns it as an integer.

ReadFloat

ReadFloat(file) reads four bytes as a four byte floating point number and returns it as a real.

ReadDouble

ReadDouble(file) reads eight bytes as an eight byte floating point number and returns it as a real.

ReadReal

ReadReal(file) is a synonym for ReadFloat(file) in the current version of Basic4GL on the Windows platform. (Basic4GL's "real" type is equivalent to a "float" in C).

File writing routines

The file must have been opened with OpenFileWrite for these routines to work correctly.

WriteLine

WriteLine (file, text) writes text to the file and automatically appends a carriage return/newline pair.
text is a string value.

WriteString

WriteString (file, text) writes text to the file. No carriage return or linefeed is appended. A zero byte string terminator is NOT appended..
text is a string value.

WriteChar

WriteChar (file, text) writes the first character of text to the file as a single character.
text is a string value.

WriteByte

WriteByte (file, intval) writes intval to the file as a single byte value.
intval
is an integer value.

WriteWord

WriteWord (file, intval) writes intval to the file as a two byte "word" value.
intval
is an integer value.

WriteInt

WriteInt (file, intval) writes intval to the file as a four byte integer value.
intval
is an integer value.

WriteFloat

WriteFloat (file, realval) writes realval to the file as a four byte floating point value.
realval
is an real value.

WriteDouble

WriteDouble (file, realval) writes realval to the file as an eight byte floating point value.
realval
is an real value.

WriteReal

WriteReal (file, realval) is a synonym for WriteFloat (file, realval)

Other file I/O routines

EndOfFile

EndOfFile (file) applies to files opened for reading, and returns true if we have reached the end of the file.

Seek

Seek (file, offset) applies to files opened for reading, and attempts to reposition the reading position to offset bytes from the beginining of the file.

Deleting a file

DeleteFile

DeleteFile(filename) will delete a file. This routine is only available when "Safe mode" is switched OFF.
If the delete succeeds, DeleteFile() returns true. Otherwise DeleteFile() returns false, and FileError() can be used to retrieve the text of the error.

Directory listing routines

FindFirstFile

FindFirstFile(mask) returns the filename of the first file that matches the text string mask.

Example:

dim filename$
filename$ = FindFirstFile("*.gb")
print filename$

Example 2:

print FindFirstFile("files\*.*")

Directory listing is subject to the same restrictions as general file access. That is, the directory must be the same directory as where the Basic4GL program is saved, or a subdirectory.

If no matching file is found, "FindFirstFile" returns an empty string ("").

FindNextFile

FindNextFile() returns the filename of the next matching file in the directory.
This function uses the same mask as was passed to "FindFirstFile", and therefore will only work after a successful "FindFirstFile" call.

"FindNextFile" will keep returning the next filename until there are no more matching files, at which point it returns an empty string ("").

Example:

dim filename$
filename$ = FindFirstFile("*.gb")
while filename$ <> ""
    printr filename$
    filename$ = FindNextFile()
wend
FindClose()

FindClose

FindClose() will free resources after a FindFirstFile..FindNextFile directory search.

It is not strictly required as Basic4GL will do this for you automatically when the program finishes. However it is good practice.

Sound

Basic4GL uses the Audiere sound library, which supports a number of different sound formats such as .wav, streamed music formats such as Ogg Vorbis, and "mod" formats like .mod, .s3m, .xm and .it. See the Audiere home page for more information.

Standalone exe distribution

If you use sound or music functions in your program, and you wish to distribute it as a standalone executable (see Creating Standalone Exes) be aware that you must also distribute:

which must be placed in the same folder as your standalone .exe file. Otherwise your program will run silently.

You can test whether the Basic4GL sound engine has initialised correctly by placing the following code at the top of your program.

if SoundError() <> "" then
    print SoundError()
    end
endif

If the sound engine has not initialised correctly (because either of the dlls could not be loaded), it will print the message:

Sound playback requires Audiere.dll and B4GLSound.dll to be placed in the same folder

and stop.

Sound functions

LoadSound

Sounds are loaded as follows:

dim sound
...
sound = LoadSound (filename)

Filename must refer to a file of a supported sound format.

PlaySound

Once the sound has been loaded, it can be played as follows:

PlaySound (sound)

or

PlaySound(sound, volume, looped)

Here sound is the sound handle that was returned from LoadSound(...).
Volume is the sound volume, where 1 = full volume, 0.5 = half volume etc. (You can also use values greater than 1, but be warned that the sound may "clip" and become distorted.)
Setting looped to true will cause the sound to play continuously in a loop.

Note: If volume and looped are not specified they default to volume = 1 and looped = false.

PlaySound(...) returns the number of the "voice" that was chosen to play the sound. Basic4GL supports 10 voices, which defines the maximum number of sounds that can be played simultaneously.
This number is useful if you want to stop the sound later (especially for looped sounds like footsteps), as you can pass it to the StopSoundVoice(...) function.

DeleteSound

DeleteSound (sound) deletes the sound from memory.
If you don't explicitly delete them, Basic4GL will automatically do so when your program finishes.

StopSoundVoice

To stop a sound playing, use:

StopSoundVoice(voice)

Voice is the number of the voice you wish to stop playing.
This number is returned from PlaySound(...) when the sound was started.

StopSounds

You can also stop all sounds with:

StopSounds()

Music functions

These functions are used to stream in and play music files, such as Ogg Vorbis, or "mod" files (.mod, .s3m, .xm, .it etc).

PlayMusic

Start playing a music file with:

PlayMusic(filename)

or

PlayMusic(filename, volume, looped)

Filename must be a file of a supported music format. Volume and looped behave the same as with PlaySound(...).

This will open the file and start playing it immediately.
Unlike regular sound files music files are "streamed". This means that the file is not loaded into memory all at once. Instead the file is loaded in continuously while the music is playing.
Basic4GL supports playing one music file at a time only. If a music file is already playing, it will stop and the new file will play instead.

Example:

dim filename$
printr"Filename:": input filename$
PlayMusic(filename$)
if SoundError() <> "" then printr SoundError(): end endif
while MusicPlaying(): Sleep(100): wend

StopMusic

StopMusic() will stop music file from playing.

MusicPlaying

MusicPlaying() returns true while the music file is playing.

SetMusicVolume

To set the music volume while music is playing, use:

SetMusicVolume(volume)

Where volume behaves the same as with PlaySound() or PlayMusic().

Sound and music errors

If a sound or music function fails, Basic4GL will store a description of the error, which can be retrieved with the SoundError() function.

SoundError

SoundError() returns a text string describing the result of the last sound or music function call.

If the call was successful, SoundError() returns the empty string (""). Otherwise it returns the text of the error message.

Example:

dim sound, i
sound = LoadSound("c:\windows\media\chimes.wav")
if SoundError() <> "" then 
    printr SoundError()
else
    PlaySound(sound)
    Sleep(2000)
endif

General purpose functions

These functions are used for general purpose operations, such as mathematics equations and string manipulation.

abs

Abs(x) returns the absolute value of x.

arraymax

ArrayMax(array) returns the index of the highest element of array. Iterating elements 0..ArrayMax(array) will therefore visit every element inside the array.
ArrayMax is a special function in that array can be any type, so long as it is an array.

asc

Asc(x) takes a single string parameter x, and returns the ASCII value of the first character.
This is the opposite of the chr$ function

atn

Atn(x) returns the Arc Tangent value of x, in radians.

atnd

Atnd(x) returns the Arc Tangent value of x, in degrees.

atn2

Atn2(x, y) returns the Arc Tangent value of x, y, in radians.

atn2d

Atn2(x, y) returns the Arc Tangent value of x, y, in degrees.

beep

Beep() causes the computer to beep.

chr$

Chr$(x) takes a single integer parameter x, and returns a string character whose ASCII value is x.

Example:

Printr Chr$(72)+Chr$(101)+Chr$(108)+Chr$(108)+Chr$(111)

cos

Cos(x) returns the Cosine of x, where x is measured in radians.

cosd

Cosd(x) returns the Cosine of x, where x is measured in degrees.

exp

Exp(x) returns e raised to the power of x.

Exp is the inverse of Log.

int

Int(x) casts a real valued x to an integer.
The rounding is slightly different to the implicit type cast when a real value is assigned to an integer.
Int(x) rounds x towards negative infinity, whereas implicit type casting always rounds towards 0.

Example:

dim a#, i1, i2: a# = -5.1
i1 = a#
i2 = Int(a#)
printr "i1 = " + i1
printr "i2 = " + i2

left$

Left$(s,c) returns a string containing the first c characters of s.
s
is a string value, c is an integer value.

For example, Left$("ABCDEFG", 3) returns "ABC"

lcase$

LCase$ (x) returns x converted to lowercase.

len

Len(x) returns the length of the string x in characters.

log

Log(x) returns the natural logarithm of x.

Log is the inverse of Exp.

mid$

Mid$(s,i,c) returns a string containing c consecutive characters of string s, starting from the ith character.

For example, Mid$("ABCDEFG", 4, 3) returns "DEF".

performancecounter

PerformanceCounter() returns the number of milliseconds that have elapsed since the computer was turned on.
This function is very similar to TickCount(), except PerformanceCounter() is accurate to1 millisecond whereas TickCount() is only accurate to 10ms.

Therefore I strongly recommend using PerformanceCounter() for any timing operations.

The old TickCount() function is retained only for backwards compatibility with existing Basic4GL programs.

pow

Pow(x,y) returns x raised to the power of y.

right$

Right$(s,c) returns a string containing the last c characters of s.

For example, Right$("ABCDEFG", 3) returns "EFG"

rnd

Rnd() returns a random integer value, between 0 and RND_MAX.
(RND_MAX = 32767, but could be different in future ports of Basic4GL to different platforms or operating systems.)

To return a random number between 0 and x-1 (inclusive), use:

Rnd() % x

To return a random number between 1 and x (inclusive), use:

Rnd() % x + 1

sgn

Sgn(x) returns:

1, if x is greater than 0
0, if x equals 0
-1, if x is less than 0

sin

Sin(x) returns the Sine of x, where x is measured in radians.

sind

Sind(x) returns the Sine of x, where x is measured in degrees.

sqr

Sqr(x) returns the square root of x.

(Actually the square root of the absolute value of x.)

sqrt

Sqrt(x) is exactly the same as Sqr(x)

str$

Str$(x) converts an integer value x into a string representation of x.

For example, Str$(-13.4) returns "-13.4".

tan

Tan(x) returns the Tangent of x, where x is measured in radians.

tand

Tand(x) returns the Tangent of x, where x is measured in degrees.

tanh

Tanh(x) returns the Hyperbolic Tangent of x, where x is measured in radians.

tickcount

TickCount() returns the number of milliseconds that have elapsed since the computer was turned on.
Note: This function is only accurate to about 10ms. I strongly advise using PerformanceCounter() instead.

ucase$

UCase$ (x) returns x converted to uppercase.

val

Val(x) converts a string x into a numeric value.
If x cannot be converted into a number, then Val(x) returns 0.

For example, Val("27.2") returns 27.2.

Val is the opposite of Str$.

Vector and Matrix routines

Basic4GL contains built in support for matrix and vector arithmetic, through a library of trigonometry functions, and also through extensions to standard mathematical operators (+, -, * e.t.c) to work with vector and matrix types.

Vector storage format

Vectors are stored as an array of reals. For example:

dim vec#(3)
vec# = vec4 (1, 2, 3, 1) ' Create a vector and assign it to vec#

To be elegible for use with the built in trigonometry functions, the array must have 2, 3 or 4 elements. (Remember that declaring an array as size 3 actually results in 4 elements, 0 through 3 inclusive).

Element 0 stores the x component, element 1 stores y component, 2 stores z and 3 stores w.

Certain trigonometry functions that operate on 4 component vectors will automatically substitue z = 0 and/or w = 1 when short version vectors are passed in.

Matrix storage format

A matrix is a 4 x 4 array of reals, and must always be "DIM"med as:

matrixname#(3)(3)

Example:

dim matrix#(3)(3)
matrix# = IdentityMatrix () ' Assign a matrix to matrix#

The first array dimension corresponds to the x coordinate of the matrix, and the second to the y.

Basic4GL vector and matrix storage format and operations are designed to mirror those of OpenGL.
As such vectors are multiplied as column vectors on the right hand side of matrices. Matrices are stored as an array of column vectors.

Creating vectors

Vectors are just arrays, so you can read from and write to them like any other array.

dim v#(3), i
for i = 0 to 3: v#(i) = i: next ' Create a (0 1 2 3) vector
dim v1#(3), v2#(3), dotProd#
dotProd# = v1#(0)*v2#(0) + v1#(1)*v2#(1) + v1#(2)*v2#(2)
' Calculate the vector dot product
' (Note: we could also have said dotProd# = v1# * v2#)

However there are a set of routines for creating vectors quickly and simply:

vec4, vec3 and vec2

vec4(x, y, z, w) returns a 4 component vector with x, y, z and w components initialised accordingly.

vec3(x, y, z) returns a 3 component vector with x, y and z components initialised accordingly.

vec2(x, y) returns a 2 component vector with x and y components initialised accordingly.

Examples:

dim lightsource#(3)
lightsource# = vec4(0, 100, 0, 1) ' Lightsource at (0 100 0)

This is exactly equivalent to:

dim lightsource#(3)
lightsource#(0) = 0
lightsource#(1) = 100
lightsource#(2) = 0
lightsource#(3) = 1

The first version is simply a more compact alternative.

Extended mathematics operators

Certain mathematics operators have been extended to accept vectors and or matrices as input, and (where appropriate) return a vector or a matrix as a result.

vec = A vector
matrix = A matrix
real = A real value

Expression Result
-vec Returns vec negated. That is vec scaled by -1
-matrix Returns matrix negated. I.e matrix scaled by -1
vec * real
or
real * vec
Returns vector scaled by real
matrix * real
or
real * matrix
Returns matrix scaled by real
matrix * vec Returns vec multiplied as a column vector on the right hand side of matrix. The result is another vector.
matrix1 * matrix2 Returns matrix2 multiplied on the right hand side of matrix1. The result is another matrix.
vec1 * vec2 Returns the dot product of vec1 and vec2, as a real value.
vec / real Returns vec scaled by 1 / real
matrix / real Returns matrix scaled by 1 / real
vec1 + vec2 Returns vec2 added to vec1 as a vector
matrix1 + matrix2 Returns matrix2 added to matrix1 as matrix
vec1 - vec2 Returns vec2 subtracted from vec1 as a vector
matrix1 - matrix2 Returns matrix2 subtracted from matrix1 as a matrix

Matrix creation functions

These are based on the OpenGL matrix functions (glTranslate-, glRotate-, e.t.c).

MatrixZero

MatrixZero () returns a matrix where every element is zero.

dim m#(3)(3)
m# = MatrixZero ()

MatrixIdentity

MatrixIdentity () returns the identity matrix.

MatrixScale

MatrixScale (scale) returns a scale matrix

MatrixTranslate

MatrixTranslate (x, y, z) returns a translation matrix.

MatrixRotateX, MatrixRotateY and MatrixRotateZ

MatrixRotateX (angle) returns a matrix that rotates anticlockwise around the positive X axis by angle degrees.

Likewise MatrixRotateY (angle) and MatrixRotateZ (angle) return matrices that rotate around their respective axes.

There is no function for creating a rotation matrix around an arbitrary axis (like glRotate- in OpenGL) because I'm not smart enough! :-) (If anyone wants to send me the maths, I'll add one...)

MatrixBasis

MatrixBasis (vecx, vecy, vecz) creates a matrix from 3 basis vectors.

MatrixCrossProduct

MatrixCrossProduct (vec) creates a cross product matrix for vec. This matrix has the property that when multiplied with a vector v, the result is vec x v. That is the cross product of vec and v.

Using Matrices with OpenGL

glLoadMatrixf, glMultMatrixf

You can copy a standard matrix into OpenGL, replacing the perspective, model-view or texture matrix (whatever was last selected by glMatrixMode ()).
You can also multiply the current OpenGL matrix with a standard matrix.
The new matrix will transform vertices passed to OpenGL (or texture coordinates for the texture matrix), just as if you had built the matrix with glRotate-, glTranslate-, glScale-,... commands.

glLoadMatrixf (matrix) will replace the current OpenGL matrix with matrix.

glMultMatrixf (matrix) will multiply the current OpenGL matrix by matrix. The resulting matrix replaces the previous OpenGL matrix.

(Note: glLoadMatrixd and glMultMatrixd also work. However as Basic4GL works with floats internally rather than doubles, there is no particular advantage in using these functions.)

Examples:
The following examples all draw a square 10 units "into the screen", rotated anticlockwise by 20 degrees.

1.

' Standard OpenGL matrix routines
glLoadIdentity ()
glTranslatef (0, 0, -10)
glRotatef (20, 0, 0, 1)
glBegin (GL_QUADS)
glVertex2f (-1, 1): glVertex2f (-1, -1): glVertex2f (1, -1): glVertex2f (1, 1)
glEnd ()

2.

' Using glMultMatrixf to multiply in basic matrices
glLoadMatrixf (MatrixIdentity ())
glMultMatrixf (MatrixTranslate (0, 0, -10))
glMultMatrixf (MatrixRotateZ (20))
glBegin (GL_QUADS)
glVertex2f (-1, 1): glVertex2f (-1, -1): glVertex2f (1, -1): glVertex2f (1, 1)
glEnd ()

3.

' Build a complete matrix and load into OpenGL in one go
glLoadMatrixf (MatrixTranslate (0, 0, -10) * MatrixRotateZ (20))
glBegin (GL_QUADS)
glVertex2f (-1, 1): glVertex2f (-1, -1): glVertex2f (1, -1): glVertex2f (1, 1)
glEnd ()

4.

' Matrix stored in a variable
dim m#(3)(3)
m# = MatrixTranslate (0, 0, -10) * MatrixRotateZ (20)
glLoadMatrixf (m#)
glBegin (GL_QUADS)
glVertex2f (-1, 1): glVertex2f (-1, -1): glVertex2f (1, -1): glVertex2f (1, 1)
glEnd ()

Alternatively we could simply transform the vertices before passing them to OpenGL

dim m#(3)(3)
m# = MatrixTranslate (0, 0, -10) * MatrixRotateZ (20)
glBegin (GL_QUADS)
glVertex3fv (m# * vec3(-1, 1, 0))
glVertex3fv (m# * vec3(-1, -1, 0))
glVertex3fv (m# * vec3(1, -1, 0))
glVertex3fv (m# * vec3(1, 1, 0))
glEnd ()

Which works just as well.
However, keep in mind that if we perform the transformations ourselves we deny OpenGL the opportunity to perform the transformations, and make use of any optimisations such as hardware transformations supported on modern 3D graphics cards.

Other trigonometry functions

CrossProduct

CrossProduct (vec1, vec2) returns the vector cross product of vec1 and vec2. The result is a vector.

Length

Length (vec) returns the length of vec.
This is equivalent to sqr(vec*vec)

Normalize

Normalize (vec) returns vec scaled to length 1.
This is equivalent to vec / Length(vec)

Determinant

Determinant (matrix) returns the matrix determinant of matrix. The result is a real value.

Transpose

Transpose (matrix) returns matrix transposed. (That is matrix mirrored about the diagonal.)

RTInvert

RTInvert (matrix) returns matrix inverted, for any matrix containing only rotations and translations.
If matrix contains any other transformations apart from rotations and translations then the result is undefined, and will not be the inverse of matrix.

Orthonormalize

Orthonormalize (matrix) returns an orthonormal matrix by performing a series of normalizations and cross products on the basis vectors of matrix.

This is useful for matrices that are nearly orthonormal. For example to ensure a matrix (that should be orthonormal) hasn't accumulated rounding errors after a large number of transformations.

Handling the w coordinate

Some of the above functions (such as CrossProduct) and operators (such as +) take two vectors and return a single result vector. Basic4GL sets the w coordinate of the resulting vector as follows:

If this is not the behaviour that you want, you will have to set the w coordinate manually.

There is no special treatment of w when multiplying a vector by a matrix, w is calculated like any other component. You will need to divide through by w manually if this is the behaviour you require.

dim vec#(3), matrix#(3)(3)
...
vec# = matrix# * vec#		' Multiply vector by matrix
vec# = vec# / vec#(3)		' Divide through by w

Runtime compilation

IMPORTANT:

As of version 2.5.1 the following functions are deprecated. If at all possible you should use the "comp" and "exec" commands that are documented in the language guide (as they are now built in language commands).

These old functions are still available for backwards compatibility with older Basic4GL programs. However they cannot be used in combination with user functions and subs, and you will get a runtime error if you try to do so.

The exception is the following functions: CompilerError, CompilerErrorLine and CompilerErrorCol.

These are still considered current, and can be used with the new "comp" command.

The following functions can be used to compile and execute code at runtime.

Compile

Compile(text) compiles text into program code, and returns an integer that identifies the compiled code.

Example:

dim code
code = Compile("printr " + chr$(34) + "Hello world" + chr$(34))
Execute(code)

Text can either be a string, or an array of strings.
If the text compiles successfully, "Compile" returns an integer handle that can be passed to the "Execute" function.
If the text does not compile, "Compile" returns 0, and the error description and position can be extracted using the CompilerError, CompilerErrorLine and CompilerErrorCol functions.

CompileFile

CompileFile(filename) loads a file from disk, compiles it, and returns an integer that identifies the compiled code.

Example:

dim code
code = CompileFile("md2viewer.gb")
if code = 0 lor not Execute(code) then
    printr CompilerError()
endif

Important:

CompileFile can compile just about any program that can be loaded into Basic4GL and compiled manually. However there are some limitations:

Execute

Execute(handle) executes a block of compiled code.
handle must be a valid handle returned from a successful "Compile" or "CompileFile".

If the code completes without any errors, "Execute" returns true. Otherwise "execute" returns false, and the error message can be read using the "CompilerError", "CompilerErrorLine" and "CompilerErrorCol" functions.

The compiled code will execute until one of the following occurs:

The program will then continue executing from the next instruction after the "Execute" call.

Compiling with a "symbol prefix"

These special versions of the above functions can be used to compile code with an automatic "symbol prefix":

The symbol prefix is a text string that is automatically prefixed to the front of every variable name, label name or structure name that the runtime compiled program refers to.

For example:

dim text$, program, i, __value
text$ = "value = value * value"
program = Compile(text$, "__")  
printr CompilerError()

for i = 1 to 10
    __value = i
    execute(program)
    printr i + ", " + __value
next

Here our code to compile at runtime is "value = value * value". But because we pass a symbol prefix of "__" (double underscore) to the Compile() command, what actually gets compiled is "__value = __value * __value".

The important thing here is that the runtime code cannot access any of the main program's variables except those that we have prefixed with "__". It cannot access "i" for example, as "i = 5" would effectively be compiled as "__i = 5".

We can use this to limit exactly which variables, labels and structure types the runtime compiled program has access to.

CompilerError, CompilerErrorLine, CompilerErrorCol

CompilerError() returns the error message generated by the last "Compile", "CompileFile" or "Execute" call. If there was no error (i.e. the call was successful), "CompilerError" returns an empty string ("").

CompilerErrorLine() and CompilerErrorCol() return the line and column of the last error (if applicable).
For a "Compile" or "CompileFile" call, this is the position of the error in the code being compiled.
For an "Execute" call, this is the instruction that caused the run-time error.

Runtime compilation issues

There are a some potential issues with runtime compiled code that need to be kept in mind.
It is tempting to think that runtime compiled code is crash-proof, because any runtime errors immediately return control to the calling program which can deal with the error appropriately.
This is true. However the runtime compiled code has access to everything in the parent program (by default, although this can be controlled by "symbol prefixes" - see above), including all variables, labels etc, and can often mess things up enough that the program will not run correctly when control is returned.

For example,the following program:

dim text$, code
text$ = "return"
gosub ExecuteIt
end

ExecuteIt:
    code = Compile(text$)
    if code = 0 lor not Execute(code) then
        printr CompilerError()
    endif
    return

stops with a runtime error "Return without gosub", because the runtime compiled code actually returns to the line after the "gosub", before finally ending when it reaches the "end" instruction. This returns control back to the instruction after the "Execute(code)" instruction which attempts to "return" again!

Another issue is that the executed code may never return at all!
Consider:

print "Starting"
Execute(Compile("while true: wend"))
print "Ending"

The last line is never executed, because the dynamically compiled code ("while true: wend") never exits, and control is never returned.