LBASIC Language Guide

 

LBASIC LANGUAGE GUIDE

 

LBASIC LANGUAGE GUIDE

Introduction

Yes! Another scripting language. First question on mind is whether we need it. There is a number of similar
things out there, often within the ‘freeware’ domain. Why creating a new one?

There is a few reasons why. One is the size. The script language is supposed to enhance the program, not to
replace it in memory. Second reasons are strings attached to quite a few ‘free’ programs. Nonetheless, these two
reasons are relatively minor objections. More important is to follow.

LBASIC is supposed to be the script language to control the flow of simulation in LIMS 4. To accomplish this,
the interpret has to be able to do computations in floating point arithmetic. Unfortunately, most macro languages
out there use only integer values, since they are oriented more towards the processing of text or direct control
of digital devices. While it might be possible to adjust existing code, the required effort might be quite significant.

The second reason are users of LIMS, which are people with engineering background. These folks are scared of
recursion, worship FORTRAN 66, and do not like pre- and post-fix notations. Consequently we would rather avoid
direct LISP and FORTH clones and try to make the script language as close to simple BASIC as possible. The FORTRAN
syntax is a way too obsolete for an efficient macro programming.

The second question is why a script language at all. However, in order to modify the location of inlets and
vents or control inlet pressure according to the program in the real-word injection setup the user has to have
the ability to process ‘signal’ in fairly complex way. In RTM simulation, these signals are pressure, temperature
etc. at various points of the simulated mold. Processing will possibly consists of a fair amount of algebra, some
logic selections and some repetitive steps. To accomplish this user must create a ‘script’ or ‘program’ that describes
the process control applied to the specific injection process. The only alternative is the adjustment of the program
code itself. The experience shows that such approach should be avoided if either the program size exceeds a couple
of thousand lines of code or the program is used by several independent users. This is mainly because of undue
complications associated with the program maintenance.

The more elementary reason to create a complex command interpret is the desire to create ‘macro’ commands that
simplify the elementary operations during filling simulation. It allows to replace a set of commands by a single
command – without modifying the program code. Thus, to replace the sequence

SETOUTTYPE "TPLT"
WRITE Filename,3

by

PLOT Filename

we might, instead of digging in the program code itself, create

PROC PLOT
  SETOUTTYPE "TPLT"
  WRITE ARGUMENT(1),3
ENDPROC

or more bulletproof

PROC PLOT
  IF ((ARGC=1) THEN
    IF (ISSTRING(ARGUMENT(1)))) THEN
      SETOUTTYPE "TPLT"
      WRITE ARGUMENT(1),3
    ENDIF
  ELSE
    MESSAGE("Call as PLOT Filename")
  ENDIF
ENDPROC

and load the procedure during the program initialization process. Note that the second version checks whether
the calling syntax was proper and the argument is indeed a string value. We might have added some additional checking.
For example, Filename should be at least one character long and we could have checked this easily, but this is
described in more detail later.

Elements of LBASIC

Directives

Directives are commands that influence the way the interpret interacts with user/input file. There are directives
to obtain help, load a file, echo input lines into file and to provide conditional interpretation/compilation.
Syntax of arguments for these is a little bit different than that for the language commands and they are not available
for compiled procedures. However, their implementation is similar to that of commands and we will occasionally
call them commands.

Commands

Commands concerning simulation, input, output or the workings of the interpret can be issued either at the command
line (prompt) or in procedures. These can result either direct calls to system functions (primitive commands) or
calls to defined procedures. Most of the built-in commands, all variables and all user defined procedures (programs,
words) are available at this level.

Variables

LBASIC uses variables for temporary storage of data. The data types supported are: double precision floating
point numbers, strings, 1-dimensional floating point arrays (vectors) and 2 dimensional arrays of floating point
values (matrices). Large variety of functions and operators allows algebraic or logical manipulation of these variables.
Moreover, the language structure provides for extremely easy addition of new functions or operators into the source
code.

Variables has to be declared/dimensioned prior to use. System allows for selective erasing of variables as well
as for the temporary redefinition.

Note that there is nothing like a “system” variable in LBASIC. Values associated with the problem
being solved, such as the number of nodes or pressure at node 0, are available as functions with no argument. This
is, by the way, the usual definition of PI in BASIC dialects. This approach allows the modules within
the LIMS program itself to control access to their private data. If the user is entitled to change the value, an
adequate SET… command is provided.

Procedures

LBASIC programming consists of creating new procedures (or words). These are individual programs
that can enhance the capability of the basic system by combining multiple operations or repetitive tasks into a
single command. Algebraic and logical operations on simulation data can be done within procedures and used to control
program flow via conditional statements and loops.

Procedures can have scalar arguments and create local variables. Support is provided for variable number of
arguments and for non-standard format of arguments (e.g., string without quotes). Recursion is basically possible,
but not encouraged, since there is a limit on both the depth of call nesting and the number of variables available.

Once defined, a procedure becomes a new command of the system that can be used in further procedures as well.
Defined procedures can be erased and redefined if it is necessary.

Basic Operation

Basic operations of the LIMS 4 control shell vary with the environment the program runs in. The basis is, however,
the same. Commands are input on line-by-line basis from console. In the slave version, the master program supplies
the command lines. Each line can contain a single command, optionally followed by arguments and comment
delimited by a single quote (‘). The later is obviously of any use only in scripts (programs), not in commands
entered directly. LIMS interprets the command and waits for another line of input.

The console might actually be a window, or it might cover the whole screen and work as a teletype. If this is
the case, various parts of the program try to label ‘their’ console by text preceding output lines and, possibly,
by a different prompt.

In some cases the program sends messages, expects Yes/No type answers or wants you to input a file name. On
a teletype style window messages and prompts will just print on the screen, separating themselves by a line of
underscores above, and once all the input is provided,below.

Console Input

The commands, as well as procedure code, can be entered in one of two ways. First, you can type it from console
on line-by-line basis. Second, it can be loaded from file.

Console input is the same as that used in usual UNIX or DOS shell. User types a line and hits ENTER
key. System processes the line and prompts user for another one. Editing of the input line is completely dependent
on the platform. Minimum is teletype writing with the only possible editing via a BACKSPACE or DELETE
key. This is not the best possible way to enter program code, but the program can be loaded from pre-prepared files.
For individual commands, these rudimentary capabilities are essentially sufficient.

Some console implementations (say Windows NT 4) go much further than that, providing full command line
editing and history. Also, the capabilities may be better due to installed utilities (such as ced under
MS-DOS) that provide command line history, editing etc.

The system allows to log all given commands (including these read from files) into a file via ECHO
Filename directive. Logging is disabled by a simple ECHO directive without a parameter. Since ECHO
is directive, its argument is not a string, i.e., there are no leading/terminating quotes.

If error occurs during input, LBASIC displays a message and waits for another line. If procedure is being defined,
the definition mode is abandoned, but the unfinished procedure remains in memory (see FORGET),
with Execution flag OFF, so that it can not be triggered by accident (see section on Procedure Management).

Most errors that happen can be easily reversed. Unwanted or unfinished procedures may be FORGETed and
mistakenly created variables ERASEd. Unwanted changes to existing values
are hard to undo unless you have an echo file. If an existing code is overwritten, correction requires re-loading
of the original version.

Loading Files

File containing either procedure definitions or direct code can be loaded using

LOAD Filename

directive. This actually causes console to read lines from that file as long as the file is not terminated.
The moment end of file is reached, the program starts reading from keyboard once more.

Note that it is possible to load files recursively, i.e., the ‘loaded’ lines may load another file that will
be processed prior to continuing with the current file. This feature is useful, since file containing some useful
definitions may load other necessary useful definitions. On the other hand, it should not be abused because:

  • There is usually some limit on how many files you can keep open.
  • It is nearly impossible to untangle what went wrong with deeply nested file loads.

Be forewarned that error handling is adjusted to manual input. Consequently, errors within file are likely to
cause lots of trouble. If error happens during loading of file, the following lines are processed despite the error.
Thus the error may lead to a string of additional errors – something very common with older (and simpler) programming
language compilers. The remedy is to prepare individual procedures carefully and – above all – not to create really
large files. Also, careful use of conditional interpreting and LASTERR()
function can help.

Identifiers

Identifier is used to label either a variable or a procedure. It has to begin with letter followed optionally
by additional alphanumeric characters. Maximal length is 15 characters. If more is given, the name is truncated
and it may lead to undesirable effects. Note that the case of letters is not important – all searches and comparisons
convert identifiers to uppercase.

Uniqueness is important only within a context. Procedure should not be named as a system command, as in such
a case system would try to execute the system command first, albeit it should still be possible to explicitly CALL the procedure. Similarly, variable name should not duplicate name of system function,
as Evaluate would try to execute the function instead. Having a variable and procedure with the same name should
not cause any problems. However, it is poor practice to do such things.

Redefining a variable creates the second variable of the same name (but possibly conflicting type). The following
searches will always return the newer variable and it will be used in expressions, assignments, etc. However, once
the new variable is erased (command ERASE will obviously find it first
as well), the old variable reappears. This allows local variables to exist.

Redefining a procedure with a given name is impossible. However, procedure can be FORGETed
and then defined again. In such case, it is the responsibility of programmer to make sure nothing depended on the
erased procedure doing what it originally did or that the new procedure is functionally the same.

In case the script author wants to prevent the user from FORGETing a procedure, procedure can be protected
from being FORGETed by command PROTECT.

Expressions

Expression is formula that can be evaluated to either a number or a string value. Expression uses standard (i.e.,
infix) notation and can contain any operators and functions available to the interpret (the most current list is
in the tables of the functns.c module). It can also use variables, including array elements. Brackets
and ‘standard’ evaluation priorities apply and recursive evaluation (such as the evaluation of function arguments
or array indices) is handled properly.

In LBASIC, expression can stand anywhere where string or number is expected. Exceptions are the directives that
expect filename, such as LOAD, and the commands that expect variable or
procedure identifier, such as LET or CALL.
Note that number is actually expected even when logical operations are being conducted. In such a case, zero is
false, any non-zero value is true.

If a set of expressions is needed, the expressions should be separated by comma and bracketed. The later is
actually not needed when expressions are arguments to a command, only when they are used as arguments for function
or indices in array.

The non-expression arguments appear for a limited set of keywords. These can be either identifiers, i.e., legal
names of variables or procedures, specific tokens, or filenames.

Issuing Commands

Commands, or words, are typed on command line and can be immediately executed. Like in BASIC (and unlike in
FORTH which expects arguments already on stack) arguments follow the command. Thus, to print a value, one types
something like

PRINT "Pressure at node",i,"is", SoPressure(i)

or

PRINT("Pressure at node",i,"is", SoPressure(i))

In most cases, word is followed by a set of expressions, either numbers or strings, separated by commas and
optionally bracketed. Occasionally, a list of identifiers or additional tokens are required instead of values.
For example, in

LET MaxPres = SoPressure(0)

the command LET expects an identifier (MaxPres), then a specific
token (=) and then an expression. Additionally, it requires that the identifier is associated with a variable
of the same type as the following expression.

Most commands have fixed number of parameters. Some commands will use defaults for parameters that are missing
(see WRITE). Some allow for a variable length list. Thus, DEFDBL,
DEFINT, DEFSTRING, DIM, ERASE or INPUT
need list of variable identifiers to define, delete or read. PRINT expects
variable length (empty is OK) list of expressions

Generally, if input is being directly interpreted, the command is executed. If, however, a procedure is being
defined – i.e., PROC Procname command was issued and not terminated
by ENDPROC command, direct commands are being compiled into procedure
Procname and will be executed only when this is done and Procname is called. There are some exceptions
to this rule; directives, such as QUIT or LOAD
Filename
are executed even if procedure is being defined. These might be, in FORTH tradition, called
‘immediate’ words. Unlike in FORTH, there is absolutely no way to compile these into procedure.

Some commands can be executed only when procedure is not being defined. Most obvious is PROC Procname.
These are ‘Interpret-only’ words.

On the other hand, some words are limited to be compiled into procedures. These are the obvious ENDPROC
as well as IF … THEN … ELSE , control loops or RETURN.
These are ‘compiling’ words.

Note that, unlike in FORTH, there is no ‘state smart’ word. In FORTH, for example, compiling words are usually
immediate words that test the state of interpret and – dependent on the state – either do the compilation work
or abort with error. In LBASIC the interpret loop is considerably more complex and it decides what can and what
cannot be done and calls the words with appropriate parameters. Consequently, we can evaluate the parameters prior
to calling a command in interpret loop and this allows for much simpler and shorter program source. Obviously,
some flexibility is sacrificed. Then, LBASIC is not FORTH, does not use RPN and has a very different purpose.

Defining a ‘Macro’ Type Procedure

Macro procedure is just a series of commands to be run by typing a single word. Thus, to print a system information
we can define procedure SI.

PROC SI
  DEFSTRING Message
  LET Message = "Number of Nodes ="+STR(SONUMBERNODES())
  LET Message = Message+" Number of Elements ="+STR(SONUMBERELEMENTS())
  MESSAGE(Message)
  ERASE Message
ENDPROC

This procedure actually does not do very much – just displays a formatted information within the message box,
but it is certainly easier to type

SI

than

PRINT SONUMBERNODES()
PRINT SONUMBERELEMENTS()

Note the declaration and prompt removal of variable Message. First, it should not matter if you decide to name
your variable the same as keyword (or procedure) as the search in tables depends on context. Second, if a variable
– string or otherwise – message exists and is used by other procedures, it is protected by the local definition
and reappears once ERASEing is done and the procedure returns.

Autoload File

You might want to load some procedure definitions or variable declarations every time you run LIMS. You might
do one of the following:

  • Create a file and load it manually by LOAD File every time
    you start.
  • Use the fact that config.lim is loaded and interpreted prior to any user input and add your definitions
    to that file.
  • Add just the following line
LOAD File

into config.lim in order to keep that file simple and loaded routines current.

Getting Help

There are three directives to list the defined identifiers. They are available even when you are in the middle
of procedure definition.

The first one is:

VARS

It displays list of variables available currently in the system include type and size (for arrays). The last
number on line shows how many MARK commands were issued (see below)

The second one is:

WORDS

and it shows what procedures are defined, how large they are, their attributes and number of MARKs as
well. Note that ‘compiled’ lines may be somehow different from the number of source lines as some source lines
produce no code and some do produce several lines.

WORDS also shows three procedure attributes – execute, read only and ignore error. The execute attribute
(shows X) means that the compilation was successful and procedure is ready to run. If ‘-‘ shows as the first attribute,
procedure compilation was terminated untimely and it can not be run. User should FORGET
and redefine this procedure. The other attributes are advanced topics.

Note on Using ‘Features’

LBASIC was written for simplicity first. There is a considerable amount of error checking going on and most
‘garbled’ input will be refused. However, due to the inner workings of interpret, there might be ways to submit
input that is not according to the documentation, yet obtain some results. This approach is discouraged
as:

  • There is nothing to be gained, with exception of possibly few keystrokes.
  • The code using ‘features’ is unlikely to work in any future release.
  • Occasionally, unpleasant surprises might await those who draw conclusions too soon.

For example, the separation of arguments by comma is not enforced, but is advisable. If you prefer spaces, it
might seem that

PRINT 1 2

works exactly as

PRINT 1,2

but

PRINT 1+2 3

will output

1 5

instead of

3 3

as expected. This is because the expression evaluator will apply the operators when expression is terminated
by comma, bracket or end of line. Since multiple argument values are supported by PRINT,
it does not break the program run. Obviously,

PRINT 1+2,3

as well as undesirable

PRINT (1+2) 3

will yield the proper result.

True Procedures

Procedures are building blocks of any script (program) as well as means to extend the rather limited repertoire
of built-in controls. Once defined, procedure differs little from a built-in command. Since it can have ‘local’
variables and take arguments, the script program can be built in a reasonably structured style.

You have seen an example of a simple procedures in previous section. However, procedures are not limited to
unconditional execution of a series of primitive commands. They can also:

  • Call other user procedures.
  • Define, use and erase data variables, including arrays.
  • Take a variable number of scalar (number, string) arguments.
  • Execute commands conditionally or repetitively (in loops).

Only the second point has been already illustrated. We will look at these points in more detail in following
sections.

Procedure is identified by an identifier – string of up to 15 alphanumeric characters beginning with letter.
Letter case is not significant. Convention in this manual is to use all caps words to denote keywords
and initial caps – lowercase words for identifiers, but this is merely authors preference.

When interpret receives command line, it tries to identify the first word as a built-in command. If this is
not the case, it interprets the line as

ProcName Argument1, ....,

searches for a procedure ProcName and executes it, passing it the arguments from the command line.
In other words, it prepends CALL to whatever is on the line. Of course,writing
directly

CALL ProcName Argument1, ....

can be used to accomplish the same and improve the readability of your script.

Call to ProcName can be coded into other procedures. If a line such as above is encountered during
the procedure compilation, reference to CALL function is recorded together with the procedure name and
argument expressions. There is no search for the procedure ProcName at this point, so it does not have
to exist yet, but can be defined later. The call is de-referenced only when the code is run – then the procedure
ProcName is searched for and executed.

The procedure compilation starts with line

PROC ProcName

When this line is encountered, LBASIC switches to compilation mode. In this mode commands are added to the currently
defined procedure instead of being executed. Conditional execution and loops are translated into commands within
the recorded code of the procedure ProcName. The compilation continues until

ENDPROC

command is encountered (or an error occurs).

Not only the procedure can be defined, it can be also erased from the LBASIC list by

FORGET ProcName

When this happens, the procedure ProcName is wiped out and its memory is freed. While this might seem
a redundant capability, there is a number of uses of this:

  • To save memory and speed up execution. Procedures take some dynamically allocated memory that can be used for
    better purpose. Also, as any request to execute a procedure has to find it, the fewer procedures in list, the better
    performance, albeit we can hardly imagine a case where this would be significant.
  • There can not be two procedures of the same name – basically because of what is called late binding.
    Thus, if the functionality is to be changed or enhanced, one has to erase the old definition and supply a new one.
  • To get rid of procedures whose definition was aborted because of error. This procedure has to be redefined,
    since it cannot be run.

Procedure Atributes

The procedure behavior can be affected by three attributes. These show up when the procedures are listed using
WORDS directive and are explained below

Execute

Shows like a ‘X’.

Allows execution of the procedure. This attribute is not controlled by user, but by LBASIC. When a new procedure
is defined, this attribute is reset. When its compilation finishes by successful ENDPROC
it is set to allow the procedure to run. If the compilation fails because of an error, it remains reset and that’s
it.

Read-Only

Shows like a ‘R’.

Prevents FORGETing the procedure. This attribute is set by

PROTECT ProcName

and once set it can not be reset. May be used to protect a key definitions in autoload file (config.lim)
from overactive users.

Ignore-Error

Shows like ‘I’.

Prevents interpret from aborting when error occurs during execution of a primitive command within the procedure.
If LASTERR() is called, it will return error number from the last executed
line, but otherwise nothing happens. Of course, if error occurs, the previous statement probably did not do what
it should – and the system might be in questionable state. This attribute allows to write error proof version of
some commands, but it is for experienced users only. Note that if error happens in procedure called by the procedure
that has Ignore-Error flag set, the system will abort as the procedure error handling depends on the precious little
self and not the caller. This attribute is set by:

IGNOREERR Procname

and again, there is no way to reset it, baring FORGETing and re-loading.

Arguments

Procedures can be called with scalar arguments following its name. It obtains them via system functions ARGC()
and ARGUMENT(i). the first one returns number of arguments passed
to the function, the second one returns i-th argument that might be either number or string. If the procedure wishes
to handle its arguments at its own, ARGSTRING() function will return
just the string following the procedure without any parsing. Thus the value of AGRSTRING() may contain
remarks or leading and trailing spaces, depending whether the routine was called directly or from other routine
(compilation removes remarks everywhere).

Procedure also shares any data that happen to exist when it is called. It can define its own variables and erase
them, but it can also define variable and leave it in the variable list. Obviously, it can erase some variables
that do not belong to it, but it is not likely to be a smart move. The above mentioned capability allows user to
write recursive procedures but we do not encourage the practice.

Listing of Available Procedures

List of existing procedures, their size and attributes can be obtained by typing

WORDS

on command line. Which procedures are displayed can be specified by typing one or more characters following
the keyword. Only procedures with names starting with the given combination of letters are displayed, i.e.:

WORDS AU

will display information about AUTO or AUTOSOLVE – assuming they are defined – but not about
NI or ALTER.

Function ISDEFINED(String) allows to detect whether a procedure
with a given name exists, as long as there is no variable with conflicting name.

Preserving Interpret State

It would be nice to be able to load a number of procedures, run the script, and be able to wipe the procedures
with the data they created to prevent interference with whatever is the next script to load. Well, you have almost
get it. The state of the ‘dictionary’ and ‘variables’ can be marked using

MARK

directive. You can use it multiple times. Any time you list WORDS or
VARS you will see a strange number at the end of each listing line. It shows
you how many ‘marks’ were active when the procedure or variable was created.

The good news is that you can discard all procedures and variables defined since the last MARK by issuing
command

RELEASE

You can, of course, repeat it if there are more marks available. This feature allows you to run multiple scripts
containing the procedures with same name and prevents running out of variable space. Nonetheless, the bad and ugly
are there as well:

  • The data values changed since last MARK are not restored
  • Any procedures FORGETed since last MARK are lost, even if they
    existed prior to MARK.
  • Any variables ERASEd are lost.
  • MARK and RELEASE kill even PROTECTed procedures!

Data and Variables

Data can be temporarily stored in variable. Variable is identified by an identifier of up to 15 alphanumeric
characters, starting with letter. Variable has type associated with it – it can be a scalar type (floating point
number or string) or an array of floating point values.

Numbers are stored as double precision floating point numbers, i.e., the precision should be sufficient for
most applications (at least 10 digits by ANSI C [1], 15-16 digits in native 8 byte IEEE format on Intel chips [2]).
The exponent range is uncertain(only up to 10+38 by standard), but in most cases it is much higher(up to 10+308
in 8 byte IEEE format [2]). Integers are stored as doubles as well. This should not limit the integer precision
at all, does not affect memory usage (as scalar variable is stored as union) and, on machines with hardware floating
point operations, it should not entail any significant performance penalty.

Any sequence of numbers that starts with number is considered constant number. Decimal ‘.’ and exponent part
labeled by ‘E’ are fine. The numbers might also start with decimal ‘.’.

Strings are stored as counted strings with length limited to 255 characters. This should not be much of restriction
as long as the text operation are primarily aimed at file names. String constants must be enclosed in double quotation
marks. The quotation mark character can be inserted by pre-pending escape character – ‘\’ (backslash) to it or
using the CHR(Code) function.

Vector (one dimensional numeric array) and matrix (two dimensional one) are the only compound types used. Numbers
are stored as double precision floating point again. The index range is given during declaration; indices are 1-based
only. This is traditional for BASICs but inconvenient with ‘C’ indexing (0-based) that is used elsewhere in the
program. However, the user interface uses 1-based numbers for entities that are internally (and in .DMP files)
stored as 0 based. Call it a feature…

Variables have to be declared prior to use. The keywords to use are

DEFINT Var1 , Var2, ...

and

DEFDBL Var1 , Var2, ...

to declare numeric variable(s),

DEFSTRING Var1 , Var2, ...

to declare string variable(s) and

DIM Var1(D1[,D2]), Var2(D1[,D2]), ...

to declare and size array(s)

Once existent, any type variable might be erased using command

ERASE Var1, Var2, ...

The available variables are linked together in some fashion to expedite search for variable by name. When a
new variable is created, it is added to the linked structure. This happens regardless of whether the variable with
this name already exists. Thus, multiple variables with same name might coexist. To prove this, we can run:

##> DEFINT i,j,k
##> DEFDBL k,l,m
##> DEFSTRING m,n
##> DIM n(4),o(3,3)
##> VARS

The last command should print (ignoring MARK counts):

O : Matrix of 3x3 elements
N : Vector of 4 elements
N : String value
M : String value
M : Floating point value
L : Floating point value
K : Floating point value
K : Floating point value
J : Floating point value
I : Floating point value

Well, they coexist. Now which of ‘K‘s or ‘M‘s is valid? When the evaluation routine within
the interpret searches for a variable by name, it finds the one that was defined last. That’s it. Thus, running:

PRINT M

should reply with an empty string, instead of ‘0’ output from a non-initialized number. This behavior allows
an important feature to exist. If procedure declares variable at the beginning of its execution and erases it before
it returns control, the variable is, for all practical purposes, ‘local’. The difference from the ‘auto’ class
variables in C is that the called procedures also inherit the local variables of their caller, not only some ‘global’
or ‘extern’ values.

The keyword VARS used in the previous example prints a list of currently
available variables together with the information about type and – in the case of arrays – size. As with the directive
WORDS, the range of variables that are displayed might be limited by
typing in beginning of the name:

VARS M

will list only

M : String value
M : Floating point value

for the variables created in the previous example.

Mark and Release

These directives work simulaneously for variables and procedures. See
above
.

Flow Control

Flow control within the procedures (the command line (direct) input can not be controlled too much) rests on
a few traditional structures. Conditional execution is accomplished by a usual

IF Condition THEN
REM Whatever is to be done when Condition returns non-zero
......
ENDIF

or

IF Condition THEN
REM Whatever is to be done when Condition returns non-zero
......
ELSE
REM or when condition evaluates to zero
......
ENDIF

Obviously, condition might be any numeric expression, presumably produced by numerical comparation. Zero is
false, all other values are true. If you test a string return value then empty string is false. The commands to
be executed conditionally might span multiple lines and can contain another IF … ENDIF block(s).

There is no equivalent for SWITCH or CASE statement found in some other languages including QBASIC.

Repetitive execution is handled by the means of loops. The construct

LOOP n
.....
NEXT

of LIMS prior to 4.0 does not exist any more. Its successor is more or less standard BASIC

FOR i=Start TO End
.....
NEXT i

I‘ has to be an existing simple numerical variable (any numerical variable can be used). Its value
is set to Start and incremented by 1 each step – until it exceeds End. During the loop execution,
this variable is accessible to other commands, i.e., program knows which pass through the loop takes place. Note
that there is no way to change the step size or orientation. In other words, STEP or DOWNTO as
known from most BASIC dialects or PASCAL, respectively, will not be accepted and have to be programmed using the
DOLOOP construct described
below.

The unconditional loop might be coded by:

DO
REM Do whatever you want to do forever .....
....
LOOP

Since it is rarely useful to do things infinitely, one can add conditions to leave the loop at the beginning
or the end if certain Condition is (or is not) met. This is done by appending WHILE Condition
or UNTIL Condition modifier to either DO or LOOP
or both, for example:

DO WHILE Condition
....
LOOP

or

DO
.....
LOOP UNTIL Condition

All loop types might be exited from middle by

EXITIF Condition

which works for both FORNEXT
and DOLOOP constructs.
In case of unconditional DO … LOOP structure this is the only way to terminate the loop. In most cases
this command should be used only for error handling.

Expression Evaluator

Many times we noted an evaluation of an expression. Expression is a formula containing numbers or strings that
is evaluated according to the rules presented in this section. Usually, the result of an expression is either number
or string, but if more expressions are combined, a list of resulting values might be acceptable in the following
circumstances:

  • Arguments to procedure
  • Arguments to certain commands
  • Indices of two-dimensional array

Obviously, these might be considered a list of expressions, separated by commas.

Within the expression, one can use:

  • Absolute values, such as integer or floating point constants (12, 1E10, 12.3,
    .23
    ) or quoted strings (“File0001.txt”)
  • Simple numeric or string variables (a, Variable1)
  • Operators (+, , *,
    /, ^, DIV,
    MOD, AND, OR,
    =, <>, <,
    >, <=, >=).
    Only addition and equal comparison makes sense for string values. DIV and MOD are integer division
    and modulo. AND and OR are logical operations that return numeric 1 (true) or 0 (false).
  • Functions (too many to list). Some return strings, some numerical values. Check reference for number and type
    of arguments. Many values provided by the system controlled by LBASIC are returned by functions (such as SONUMBERNODES()
    that returns the number of nodes within the current system).
  • Array elements (Array1(1), Array2(3,4))

Evaluation proceeds according to ‘usual’ precedence rules. Two operators with the same precedence are evaluated
from left. The order can be changed by brackets. For the sake of readability we recommend (but do not enforce)
bracketing of function arguments even when this is not necessary. The order of precedence for evaluation is:

  1. Constant arguments
  2. Functions without any parameters
  3. Array de-referencing
  4. Functions with parameters
  5. Unary minus (-)
  6. Exponentiation (^)
  7. Multiplication and division (*, /, DIV, MOD)
  8. Addition and substraction (+, -)
  9. Comparison (<>, =, <, >, <=, >=)
  10. Logical negation (NOT)
  11. Logical multiplication (AND)
  12. Logical addition (OR)

Logical interpretation of numerical result is that non-zero value is TRUE, zero FALSE. All
values are stored as double floating point numbers, so a certain care is needed when there is a possible roundoff.
For example:

LET Result=SOFLOWRATE(0)
IF (NOT(Result)) THEN
  PRINT "Mass Balance at Node 0 is Fine"
ELSE
  PRINT "Mass Balance Violated at Node 0"
ENDIF

will work but will not give expected answers. The program is not a mind reader and does not know that a value
of 10E-20 is effectively 0. Thus the previous part should be coded as:

LET Result=SOFLOWRATE(0)
IF (ABS(Result) < 1.0E-15) THEN
  PRINT "Mass Balance at Node 0 is Fine"
ELSE
  PRINT "Mass Balance Violated at Node 0"
ENDIF

String values do not have a logical value. If a test for empty string is required, use the LEN
function on string.

Error Handling

The errors are, most of the time, fatal from the point of view of interpret, in the sense that triggering error
results in whatever was being done being aborted, giving a report and getting ready to interpret the following
line. Only errors that invoke program termination (via unhandled signal/exception) will actually terminate the
interpret itself.

Note that, if the error happens during reading of a new procedure, it results in compilation being terminated
while the miscreant procedure remains in memory. It has no eXecute flag, i.e., it cannot be called, but
in order to correct it the procedure must be forgotten and redefined from beginning.

Error Prevention

There is a number of ways to prevent or control errors. First, the last error code is available in function
LASTERR(). It is meaningful:

  • At the top level (command line). Together with conditional compilation this can check whether procedure was
    defined properly (otherwise ENDPROC triggers the error). This makes sense in loaded files. Note that in
    this case you get the error message anyway.
  • In procedures marked as IGNOREERR. There it enables
    you to follow, say, file reading, with an action to be taken if the READ
    command failed. It will not trap errors in called procedures, though, only in the built-in commands. In this case
    the error message is suppressed.

Second, there is a potential to check whether a variable or a procedure has been defined. It is function ISDEFINED(“Identifier”). Note that, since this is
a function, Identifier has to be quoted to be valid string. The function returns 2 if identifier is defined as
scalar numeric value, 3 if it is a string variable, 4 if it is an array and 1 if it is procedure. 0 is returned
if anything with this identifier does not exist.

Problem is that if both procedure and variable share the same name – which is valid but not recommended – variable
is reported. Obviously, if there are more variables of the same name, the visible one is reported.

Third useful facility is the function ISSTRING(expression)
which returns true (1) if Expression – which is likely to be just a variable identifier – is a string, false (0)
otherwise.

The two previous commands are useful with the conditional compilation, for example:

#IF IsDefined("ControlGate")<>1
  Load CNTRGATE.LB
#ENDIF

will test if there is a defined procedure ControlGate. If there is none, it loads the definition from
file. Also, procedure can check whether there is support code present at the moment it is run. Since, when procedure
is running, you can not load (i.e., interpret file), it has to display message and wait for user to furnish proper
module, e.g.,

IF IsDefined("CalculateIt")<>1 THEN
  MESSAGE ("Load Module \"CALCULAT.LB\" First !")
  STOP
ENDIF
REM Whatever the procedure does .....

Listing Commands

The directive

VARS Letter(s)

displays list of all defined variables starting with Letter(s). If no Letter(s) are given,
all variables are listed. Each variable is described on a single line in format

VariableName : VariableType [Size] : Mark Level

VariableType can be numerical value, string or array. In the last case, dimensions are displayed as
well. Mark level deals with MARK/RELEASE utility.

The order of variables is from the newest to the oldest. This means that if several variables have the same
name, the first one is accessible.

The directive

WORDS Letters(s)

displays (possibly in its own window) list of all defined procedures in the following format:

ProcName [X/-][R/-][I/-] nn compiled lines : Mark Level

where the three attributes (XRI) are discussed in detail in the section on procedures. Mark level deals
with MARK/RELEASE utility.

Number of compiled lines is different than the number of source lines, but it can give some information about
how large the procedure is.

Finally, the directive

REGIONS Wildcard

lists all the regions whose name matches to Wildcard and their type. At this point Wildcard consists
of a full name (lists the particular region), partial name ended with ‘*’ (lists all regions with names started
by partial name) or ‘*’ (to get all regions).

Loading a File

LBASIC script in a file is loaded using a directive

LOAD Filename

Note that this is a directive and cannot be embedded into procedures. The reason is that the procedure
interpretation differs from the interpretation of command line. However, file can be loaded from another file that
is being loaded. Conditional interpretation gives some control over what is loaded and what is not.

Loaded file can contain anything you can enter from command line: directives and commands, declaration of variables
and definition and execution of procedures. Individual lines from the file are fed into the interpret just as if
the user typed them. Once the file is exhausted (no more lines) it is closed and next input line is read from the
source that issued load directive – either a command line or another loaded file (nesting).

Nesting of loaded files can be useful but should not be abused. First, while the implementation of program does
not restrict the depth of nesting, each nested file remains open. Operating systems tend to be picky as far as
a number of opened files is concerned, thus too many nested files may cause program to abort or worse. Second,
logically there is not much reason to go beyond two levels: source file loading support definitions. Too much nesting
can cause problems when something goes wrong.

By the way, the last mentioned problem might be solved by using an echo file by

ECHO Filename

this will log all lines handled by the interpret (including any nested files) into the file Filename.
When a directive

ECHO

without any parameters is encountered, recording stops and the file is closed. This file then provides the exact
series of commands as they were issued to interpreter. The file does not contain any lines printed by interpreter
and prompt strings.

Conditional Interpretation

Sometimes it is desirable that the script (a.k.a. program) undertakes actions dependent on the existing state
of the interpret. Since IF…THEN…ELSE…ENDIF block can exist
only in procedures, we can not use it to control the interpretation on top level, i.e., to include/exclude lines
from being passed into the interpret. To do so, a set of directives exists:

#IF Expression
REM Lines to interpret if Expression is true
#ELSE
REM Lines to interpret if it is not
#ENDIF

Expression is any valid expression that evaluates to number or string. Nonzero number and non-empty string are
assumed to be true as everywhere else. The #ELSE clause can be omitted.

These are most useful to handle existence of certain procedures or variables. For example:

#IF (ISDEFINED("OS") <> 1)
REM Now we know the procedure OS is not defined
PROC OS
  DEFINT i
  SOLVE
  FOR i=1 TO SONUMBERFILLED()
    PRINT SONEXTFILLED
  NEXT i
  ERASE i
ENDPROC
#ELSE
PRINT "Procedure OS already defined; hope it is the right one"
#ENDIF

This code will define a procedure OS (for One Step) that will solve the problem and then print the
indices of nodes filled within this step. However, it first tries to ascertain whether the procedure exists. If
it does, it assumes it is the right one and prints a note. Obviously, a different approach is possible:

#IF (ISDEFINED("OS") = 1)
  FORGET OS
#ENDIF

PROC OS
DEFINT i
SOLVE
FOR i=1 TO SONUMBERFILLED()
PRINT SONEXTFILLED
NEXT i
ERASE i
ENDPROC

which will erase the existing definition and replace it. Both approaches assume, that the functionality of both
the old and new OS is the same; otherwise either the new or old code might not work.