SolaBasic — reference manual
Every statement the compiler accepts, what it does, and where it stops. For looking things up. SOLABASIC-CHEATSHEET.md is the same ground on one page, for when you know what you want and not what it is called.
SolaBasic is BASIC in the shape QuickBASIC gave it — labels rather than line
numbers, SUB and FUNCTION, block IF and SELECT CASE — compiled to a
.sob file and run by bin/solvm. SOLABASIC.md is the language
definition: what the dialect is, where its boundary came from, and why. This
page is what the compiler accepts today, which is less, and says so in
What is not here yet.
The compiler is programs/sola.sol, and its own header is the argument for how it works.
Running one
./bin/solas programs/sola.sol # build the compiler, once
./bin/solvm programs/sola.sob prog.bas # prog.bas -> prog.bas.sob
./bin/solvm prog.bas.sob # run it
A second argument names the output. With no arguments at all the compiler compiles a demonstration and tells you how to run it.
./bin/solvm programs/sola.sob prog.bas out.sob
./bin/solvm --dump out.sob # the instructions it produced
Contents
- A program — lines · labels · comments
- Types — numbers · strings · what a name’s type is
- Variables
- Expressions
- The supplied functions
- Declaring —
CONST·DIM·OPTION BASE - Arrays
- Assigning —
LET - Printing —
PRINT - Choosing —
IF·SELECT CASE - Repeating —
FOR·DO·WHILE·EXIT - Jumping —
GOTO - Procedures —
SUB·FUNCTION·CALL·SHARED·STATIC·DECLARE - Reading —
INPUT·LINE INPUT - Files —
OPEN·CLOSE·PRINT #·WRITE #·INPUT #·EOF - Stopping —
END - What is not here yet
- Where this is not QBasic
- What it says when it refuses
A program
Lines
A : puts several statements on one line, and there is no continuation
onto the next: a line ends where it ends.
a = 1 : PRINT a : PRINT "two"
1
two
A one-line IF takes everything after THEN to the end of the line, so
both of these run when the condition holds and neither runs when it does not:
IF a = 1 THEN PRINT "yes" : PRINT "also"
Keywords and names are case-insensitive. PRINT, Print and print are one
word, and Total and TOTAL are one variable. Text inside a string is left
alone, so PRINT "Hello" still has its capital.
Spaces between words are required. FORI=1TO10 is not FOR I = 1 TO 10.
Labels
A label marks a line so a GOTO can name it. Either spelling:
Again:
PRINT "round and round"
GOTO Again
100 PRINT "at one hundred"
GOTO 100
A label is a string of characters and not a number. A number written at the
start of a line is a label, not a line number: labels need not ascend, need not
be present, and nothing sorts them. 100 and 100.0 are two different labels.
This is CB80’s rule and it is what lets an old listing through unaltered.
A label may stand on a line of its own, in which case it belongs to the next statement there is — or to the end of the program, if there is none.
Comments
REM this is a comment, and the rest of the line is not read at all
' so is this
PRINT "x" ' and this
x
REM takes the rest of its line as raw text, so it may hold anything — an
apostrophe, an unclosed quote. ' may follow a statement on the same line.
Types
Three, and no more.
| Type | Suffix | Backed by |
|---|---|---|
| Integer | % or & |
a 64-bit whole number |
| Double | # |
64-bit binary floating point |
| String | $ |
text of any length |
Numbers
A literal with no point and no exponent is an Integer; anything else is a
Double. That one rule is why 7 / 2 and 7 \ 2 differ without anything being
declared.
PRINT 42 ' an Integer
PRINT 3.14159 ' a Double
PRINT 1.5E-3 ' a Double
PRINT &HFF ' 255, written in hex
PRINT &O17 ' 15, written in octal
42
3.14159
.0015
255
15
Mixing the two in one expression gives a Double. Assigning a Double to an Integer rounds it.
n% = 3 / 2 ' 2, rounded from 1.5
Strings
Between double quotes. There is no escape, so a string cannot contain a
double quote — CHR$(34) is how you write one.
PRINT "hello, world"
PRINT "con" + "cat"
hello, world
concat
+ joins two strings. Strings compare with the same operators numbers do,
comparing their characters:
s$ = "abc"
IF s$ < "abd" THEN PRINT "less"
less
Text and numbers never turn into each other by themselves. STR$ and VAL
are how you cross.
What a name’s type is
In order:
- Its suffix, if it has one.
A%andA$are two different variables. - A
DEFstatement covering its first letter. - Otherwise Double.
DEFINT I-N ' every name starting I to N is an Integer
DEFLNG L ' ... a Long
DEFSTR S ' ... a String
DEFDBL A-H, O-Z ' ... a Double
There are four — DEFINT, DEFLNG, DEFDBL and DEFSTR — one per type, and
each takes a letter or a range of letters, comma-separated. A DEF applies to
the whole listing wherever it is written.
Variables
A variable springs into being on first use. Every variable starts at nought
— or at the empty string, if it is one — so reading one before assigning to it
is 0 and not an error.
PRINT z ' 0
z = z + 1
PRINT z ' 1
0
1
A name is a letter followed by letters and digits, optionally ending in a type suffix. See what a name’s type is.
Names beginning SOLA are reserved. PRINT’s rules are themselves written
in SolaBasic and compiled into your program, and those four letters are what
keeps its routines and its line buffer out of your way.
At module level every variable is global. Inside a SUB or FUNCTION every
variable is local to that call, unless SHARED or
STATIC says otherwise — see Procedures.
Expressions
Highest binding first. Every level is left-associative.
Tightest first.
^ |
raise to a power; always a Double, and right-associative |
- |
negation |
* / |
multiply, divide; / always answers a Double |
\ |
integer divide — both sides rounded first, and it cuts towards nought |
MOD |
remainder, taking the sign of the left-hand side |
+ - |
add, subtract; + also joins text |
= <> < <= > >= |
comparison |
NOT |
flip every bit |
AND |
bit-by-bit and |
OR XOR |
bit-by-bit or, exclusive or |
^ binds tighter than negation, so -2 ^ 2 is -4 and not 4.
Brackets group. (a + b) * c is what it looks like.
There is no boolean type. A comparison answers -1 when true and 0 when
false, which is why NOT, AND and OR are bit operations and still read
correctly:
PRINT (1 < 2) ' -1
IF a > 0 AND b > 0 THEN PRINT "both"
IF NOT (a = b) THEN PRINT "differ"
-1
A condition is true when it is not nought, so a plain number works where one is wanted:
IF count THEN PRINT "there are some"
The supplied functions
Twenty-seven. Each is compiled where it is called — there is no library in the file this writes, so a function is a short run of instructions rather than something to call.
Numbers
ABS(x) |
size without a sign; an Integer stays an Integer |
SGN(x) |
-1, 0 or 1 |
INT(x) |
the floor — INT(-2.5) is -3 |
FIX(x) |
cut towards nought — FIX(-2.5) is -2 |
SQR(x) |
square root |
EXP(x) LOG(x) |
e to the x, and the natural logarithm |
SIN(x) COS(x) TAN(x) ATN(x) |
radians |
RND |
a Double, at least 0 and always less than 1 |
RANDOMIZE n is a statement, and replaces the generator with one seeded to
repeat.
Text
LEN(s$) |
how many characters |
LEFT$(s$, n) RIGHT$(s$, n) |
the first or last n; clamps rather than failing |
MID$(s$, start) |
from start to the end |
MID$(s$, start, n) |
n characters from start; a start past the end is "" |
INSTR(s$, part$) |
where part$ first is, one-based, or 0 |
INSTR(start, s$, part$) |
the same, looking from start |
UCASE$(s$) LCASE$(s$) |
case folded |
LTRIM$(s$) RTRIM$(s$) |
spaces off the front, or off the back |
SPACE$(n) |
n spaces |
STRING$(n, s$) |
s$’s first character, n times |
ASC(s$) |
the number of the first character |
CHR$(n) |
the one-character string that number spells |
Between the two
STR$(x) |
the number as text |
VAL(s$) |
the number that text spells — strictly; see the divergences |
Declaring
CONST
A name for a value, worked out while compiling and never stored anywhere.
CONST Size = 10
CONST Pi# = 3.14159265358979
CONST Greeting$ = "hello"
A CONST may be built from literals and earlier CONSTs, and must come before
anything that uses it. It is what makes DIM a(Size) mean something.
OPTION BASE
The lowest subscript an array has when its DIM gives only an upper bound.
0 or 1, asked once, before the first DIM.
OPTION BASE 1
DIM
DIM a(10) ' OPTION BASE to 10
DIM Grid(1 TO 8, 1 TO 8) ' both bounds said
DIM Names$(100)
DIM Counts(20) AS INTEGER
DIM SHARED Totals(12)
DIM Balance AS DOUBLE ' a plain variable, given a type
Bounds are constant expressions — literals and CONSTs — because an array
is made once, at the size the listing wrote. Up to eight dimensions. There is
no REDIM, so an array is the size it was given.
AS INTEGER, AS LONG, AS DOUBLE or AS STRING says the type outright; a
suffix on the name says the same thing.
DIM SHARED makes an array visible inside every procedure. A plain DIM at
module level does not — a procedure names it with SHARED a() or does not see
it, exactly as with a plain variable. A DIM written inside a procedure makes a
fresh array on every call.
Arrays
DIM squares(10)
FOR i = 0 TO 10
squares(i) = i * i
NEXT i
PRINT squares(7) ' 49
49
Subscripts count from OPTION BASE unless the DIM said otherwise, and both
bounds are included.
An array is passed to a procedure by writing a(), at the declaration and
at the call. It is passed by reference — the procedure works on the caller’s
array, not a copy — and unlike a scalar this costs nothing, because an array is
already a reference.
SUB Sort (v(), count%)
...
v(j%) = v(j% + 1) ' the caller sees this
END SUB
CALL Sort(n(), 6)
An array parameter may have any number of dimensions. Its bounds are not written on it — they are read off the places it is called from, because that is where the arrays are. Every array handed to one parameter must be the same shape, and a listing that hands it two is refused rather than answering the wrong element.
Spell the array’s type on the parameter — g%() rather than g() — if the
listing is also to compile under QuickBASIC, which will not take the type from a
DEF there.
A subscript out of range is refused when the program runs. For an array of
more than one dimension the compiler checks each subscript by name and says
which one — subscript 2 of G is above 8 — because one out of range would
otherwise land on a different element rather than off the end. A
one-dimensional array needs no such check: there is nowhere for a bad subscript
to go except outside the array, and the machine refuses that itself.
Assigning
LET x = 1
x = 1
LET is optional and means nothing.
Printing
x = 42
a = 1 : b = 2 : c = 3
PRINT "answer: "; x
PRINT a, b, c
PRINT
answer: 42
1 2 3
A number is a sign character — a minus, or a space where one would go — then the digits, then a trailing space. A string gets neither. That is why BASIC output has its airy look, and why a negative number lines up under a positive one:
14
-7
2.5
.5
text
There is no nought before the point, and a number too large or small for plain
digits is written with a D exponent.
, moves to the next print zone; ; moves nowhere. A zone is 14 columns:
PRINT 1, 2, 3 ' 1 2 3
PRINT "a"; "b"; "c" ' abc
1 2 3
abc
A separator at the end of the line holds the line open for the next PRINT
to carry on, and PRINT with nothing after it ends the line it is on:
PRINT "open";
PRINT " continued" ' open continued
open continued
A line still open when the program stops is written out before it does.
TAB(n) puts the next thing in column n, counting from one, and starts a
new line if that column has gone by. SPC(n) is n spaces from wherever it
is. Both are only meaningful inside a PRINT.
PRINT TAB(10); "at ten"
PRINT "x"; SPC(5); "y"
at ten
x y
The margin is 80. An item that will not fit goes on the next line.
PRINT USING
PRINT USING "###.##"; 3.14159# ' 3.14
PRINT USING "value: ### units"; 9 ' value: 9 units
PRINT USING "###"; 1; 2; 3 ' 1 2 3
3.14
value: 9 units
1 2 3
The format is walked once per item, literal characters going out as they are. A format shorter than the list of items starts again from the beginning, and anything after the last field is written when the items run out.
| for a number | |
|---|---|
# |
a digit position |
. |
where the point goes; digits after it are decimals, rounded |
, |
thousands separators in the whole part |
+ |
in front: always show the sign, in its own position |
- |
at the end: a trailing minus for a negative, a space otherwise |
** |
two more positions, and the padding is asterisks |
$$ |
two more positions, one of them a $ that floats up against the digits |
**$ |
three more, both of the above |
^^^^ |
exponential — the mantissa in the positions given, then D±dd |
| for text | |
|---|---|
! |
the first character |
\ \ |
as many characters as the backslashes and the spaces between them |
& |
the whole string |
_ makes the next character literal, so _# prints a #.
A number too wide for its field is written in full behind a % rather than
cut — PRINT USING "###"; 1234 gives %1234.
Every one of these was measured against QuickBASIC 4.5 before it was written, and is compared against it on demand by oracle.sh.
Choosing
IF
Two shapes, and what follows THEN decides which. Nothing after it opens a
block; anything else is the one-line form.
x = 5
IF x > 0 THEN PRINT "positive"
IF x > 0 THEN PRINT "positive" ELSE PRINT "not"
x = -1
IF x > 0 THEN PRINT "positive" ELSE PRINT "not"
positive
positive
not
A bare label after THEN or ELSE is a GOTO:
IF i > 10 THEN Done
The block form runs until END IF, with any number of ELSEIF arms and at most
one ELSE:
n = 2
IF n = 1 THEN
PRINT "one"
ELSEIF n = 2 THEN
PRINT "two"
ELSE
PRINT "many"
END IF
two
A one-line IF may hold several statements, separated by :, and they all
belong to the arm. What it may not hold is a statement that opens a block,
whose closing line would have nowhere to be.
SELECT CASE
n = 4
SELECT CASE n
CASE 1, 2
PRINT "one or two"
CASE 3 TO 6
PRINT "somewhere in three to six"
CASE IS >= 10
PRINT "ten or more"
CASE ELSE
PRINT "none of those"
END SELECT
somewhere in three to six
The subject is evaluated once. A CASE takes any number of alternatives
separated by commas, and each is one of:
value |
equal to it |
low TO high |
between them, both ends included |
IS <op> value |
any of the six comparisons |
The first CASE that matches runs, and nothing after it. CASE ELSE must be
last. Nothing may come between SELECT CASE and its first CASE.
Repeating
FOR
FOR i = 1 TO 3
PRINT i
NEXT i
FOR i = 3 TO 1 STEP -1
PRINT i
NEXT
1
2
3
3
2
1
The limit and the step are evaluated once, when the loop starts, so a body that assigns to what they were computed from does not change the number of times it runs. The test is before the body, so a loop whose range is already empty runs no times.
NEXT may name its variable or not. NEXT j, i closes two loops, innermost
first:
FOR a = 1 TO 2
FOR b = 1 TO 2
PRINT a; ","; b
NEXT b, a
1 , 1
1 , 2
2 , 1
2 , 2
DO
The condition goes at the top, at the bottom, or nowhere — not at both ends.
DO WHILE more
...
LOOP
DO UNTIL done
...
LOOP
DO
...
LOOP WHILE more
DO
...
LOOP UNTIL done
DO
... ' only EXIT DO leaves this one
LOOP
A DO ... LOOP with the test at the bottom always runs its body at least once.
WHILE
WHILE k < 3
k = k + 1
WEND
WHILE/WEND is DO WHILE/LOOP under an older spelling. It has no EXIT.
EXIT
EXIT FOR and EXIT DO leave the innermost enclosing loop of that kind, so
one written inside an IF inside the loop leaves the loop:
FOR i = 1 TO 10
IF i = 3 THEN EXIT FOR
PRINT i
NEXT i
1
2
EXIT SUB and EXIT FUNCTION are in Procedures.
Jumping
GOTO Cleanup
Forwards or backwards, to any label in the same procedure — or anywhere at
module level, if that is where it is written. A GOTO may not cross between
module level and a procedure, or between two procedures.
It may leave a block from inside one, which is the ordinary way to give up on a
loop early when EXIT will not do:
FOR i = 1 TO 10
IF i = 3 THEN GOTO Escaped
PRINT i
NEXT i
Escaped:
And a label just before NEXT is how BASIC spells what a later language calls
continue:
FOR j = 1 TO 3
IF j = 2 THEN GOTO Skip
PRINT j
Skip:
NEXT j
1
3
A jump to a label that does not exist is refused before the program runs.
Procedures
A SUB does something; a FUNCTION answers something. Both may be written
anywhere at module level, and both may be called from above where they are
written — the whole listing is read before anything is compiled.
SUB
SUB Greet (who)
PRINT "hello, "; who
END SUB
CALL Greet("world")
Greet "world"
The two call forms are the same call. Brackets around the parameter list are optional in the declaration.
FUNCTION
A FUNCTION answers by assigning to its own name. Its answer is nought if it
never does.
FUNCTION Square (x)
Square = x * x
END FUNCTION
PRINT Square(5)
25
A call in an expression always takes brackets. Recursion works:
FUNCTION Fact (n)
IF n <= 1 THEN
Fact = 1
ELSE
Fact = n * Fact(n - 1)
END IF
END FUNCTION
Recursion stops at about 254 levels deep and says
call depth exceededwhen it does. A SolaBasic call is a real machine frame, and that is the machine’s limit — ROADMAP 3.5.
Parameters are passed by reference
Assigning to a parameter assigns to the caller’s variable.
SUB Double (v)
v = v * 2
END SUB
a = 21
Double a
PRINT a ' 42
42
It works through a chain, too: if A hands its parameter to B and B hands
it to C, and only C assigns to it, all three pass by reference and the write
reaches the original.
Brackets pass a copy instead, which is QBasic’s own way of spelling by value:
n = 10
Double (n)
PRINT n ' 10, unchanged
Note the difference between the two call forms here: CALL Double(n) passes n
itself, because those brackets are the argument list. Double (n) passes a copy,
because without CALL there is no argument list and the brackets group an
expression.
An argument that is not a plain variable — an expression, a literal — is passed as a copy whatever the parameter is.
EXIT SUB and EXIT FUNCTION
Leave at once. A FUNCTION answers whatever it has assigned to its name so far.
SUB Maybe (v)
IF v < 0 THEN EXIT SUB
PRINT "positive "; v
END SUB
SHARED
Inside a procedure, names a module-level variable to use instead of a local one. It is in force for the whole procedure wherever it is written.
total = 0
SUB AddOn (v)
SHARED total
total = total + v
END SUB
STATIC
A local that survives between calls, and starts at nought.
SUB Counted
STATIC seen
seen = seen + 1
PRINT "call number "; seen
END SUB
DECLARE
Read and dropped. QBasic needs one for a procedure used before it is defined; this compiler resolves every name in a pass over the whole listing first, so there is nothing for it to say. Listings that carry them still compile.
Reading
INPUT
INPUT n ' shows "? "
INPUT "NAME"; n$ ' shows "NAME? "
INPUT "NAME", n$ ' shows "NAME" and no question mark
INPUT "TWO NUMBERS"; a, b ' one line, split on the comma
A prompt followed by ; gets a question mark and one followed by , does
not. No prompt at all still gets one. The answer is read as a single line and
split on commas, so several variables are filled from one line.
What is filled may be an array element, which is what reading records into parallel arrays wants:
INPUT #1, nm$(count), qty(count), price#(count)
A field going into a numeric variable has to look like a number, and the count
has to match. When either is wrong, Redo from start is shown and the question
asked again — which is what BASIC has always done.
INPUT "TWO NUMBERS, SEPARATED BY A COMMA"; a, b
PRINT "SUM IS"; a + b
TWO NUMBERS, SEPARATED BY A COMMA? 3, 4
SUM IS 7
An answer read from a file is echoed, so a redirected session reads the way the interactive one looked. At a terminal it is not, the terminal having shown it already.
Running out of answers is an error — Input past end of file — and not a
question asked for ever.
LINE INPUT
Reads the line whole, commas and all, into one string variable.
LINE INPUT "A WHOLE LINE: "; whole$
Files
Sequential only.
OPEN "data.txt" FOR OUTPUT AS #1
PRINT #1, "a line"
WRITE #1, "Hans", 42
CLOSE #1
OPEN "data.txt" FOR INPUT AS #1
DO UNTIL EOF(1)
LINE INPUT #1, line$
PRINT line$
LOOP
CLOSE #1
a line
"Hans",42
OPEN path FOR INPUT AS #n |
reading, from the start |
OPEN path FOR OUTPUT AS #n |
writing, replacing what was there |
OPEN path FOR APPEND AS #n |
writing, onto the end |
CLOSE #n[, #n]... |
closes those; CLOSE on its own closes them all |
PRINT #n, ... |
as PRINT, zones and all, into the file |
PRINT #n, USING f; ... |
as PRINT USING |
WRITE #n, ... |
commas between the items and quotes round the text |
INPUT #n, var[, var]... |
one line, split on commas, quotes taken off; a target may be an array element |
LINE INPUT #n, var$ |
the line whole |
EOF(n) |
true once there is no more to read |
WRITE # is the form INPUT # reads back. A comma inside quotes does not
separate, so text with one in it survives the round trip. PRINT #’s spacing is
for a person to look at.
File numbers are 1 to 15, and the # is not part of the number — #N%
names a channel too.
A file open for writing is written when it is closed, and stopping the program closes it. There is no streaming underneath: a channel open for reading holds the file, and one open for writing holds what has been written.
Lines are ended with a line feed, where QBasic ends them with a carriage return and a line feed. A carriage return at the end of a line read back is taken off, so a file written by either can be read by this — but a file written here has DOS’s other convention.
Random access — GET, PUT, FIELD, LOF, LOC, SEEK — is not here.
Stopping
END stops the program. It may appear anywhere, including on a one-line IF.
What is not here yet
Named so that reaching for one gets an answer rather than a puzzle. The stage each belongs to is in SOLABASIC.md.
random-access files — GET, PUT, FIELD, LOF, SEEK |
not written yet; sequential files are here |
LBOUND, UBOUND |
not written yet — an array’s bounds are in the listing that DIMmed it |
ON ERROR, TYPE, REDIM, OPTION EXPLICIT |
listed as not yet in the language definition |
GOSUB, RETURN, ON n GOTO, DATA, READ, SINGLE and the whole of the PC
— SCREEN, PEEK, POKE — are not coming. See
SOLABASIC.md for why each.
Where this is not QBasic
The full list is in SOLABASIC.md; these are the ones that bite while typing.
- There is no
SINGLE. QBasic’s default numeric type is a 32-bit float; here the default is a Double, so a ported program prints more digits than it used to. WritingA!is refused by name rather than quietly widened. INTEGERis 64 bits, where QBasic’s is 16 and overflows at32767. A program relying on that failure will not fail here.- Spaces between words are required.
FORI=1TO10is not aFOR. - A string may not contain a double quote.
CHR$(34)is the way round it, as it is in QBasic. - A Double prints to the shortest text that reads back as the same number,
where QuickBASIC prints sixteen significant digits. Measured against
QuickBASIC 4.5:
1# / 3#is.3333333333333334there and.3333333333333333here. They agree whenever the shortest round-trip is sixteen digits or fewer and rounds the same way, and part company on a seventeenth digit or the last one. Print zones, the margin and theDexponent all agree exactly.STR$does not add the leading space that BASIC’s does —PRINTadds it, which is where BASIC puts it too. VALis strict. BASIC’s reads a number off the front of a string and answers0for junk; this one wants the whole string to be a number. Reading a number out of the front of text wants a scanner, and there is none in the file the compiler writes.DECLAREdoes nothing, because nothing needs declaring.- An array name means one array in the whole listing. QBasic lets two
procedures each
DIMaTempof their own; here the second one is dimensioned twice. Bounds are settled while compiling and are looked up by name, so the name has to be the whole of the question. - A file is written with line feeds, where QBasic writes a carriage return and a line feed. Reading takes a carriage return off, so a file written by either is readable here; one written here is not in DOS’s convention.
And two places where SolaBasic follows QBasic against the machine, which is
worth knowing because the machine’s answer is the one you would get by guessing.
\ cuts towards nought and MOD takes the sign of its left-hand side, where
SolVM’s own integer divide and remainder are floored: -7 \ 2 is -3 here
and -7 MOD 2 is -1, as QBasic says and not as the machine would. Both are
exact for every number an Integer can hold — there is no large-value corner
where they stop agreeing with QBasic.
What it says when it refuses
Compile-time refusals name the line. The ones worth recognising:
there is no label 'X' to jump to |
a GOTO naming a label that is nowhere in the same procedure |
the label 'X' is used twice |
two lines carry the same label |
this FOR is never closed by its NEXT |
reported on the line that opened the block, which is the one to go and look at |
NEXT closes the DO opened on line 1 |
a closing line that closes the wrong thing |
NEXT J closes the FOR on 1, which counts I |
NEXT naming a variable that is not the loop’s |
a DO tests at one end or at neither, not at both |
DO WHILE ... LOOP UNTIL |
EXIT FOR with no FOR loop around it |
EXIT looks for its own kind of loop, not the innermost block |
nothing may come between SELECT CASE and its first CASE |
there is nowhere for it to run |
'FOR' opens a block, and a one-line IF holds one statement |
a block statement after THEN on the same line |
there is no SUB or FUNCTION called 'X' |
including a misspelling, since a bare name with arguments is a call |
S takes 1 argument and was given 2 |
checked when it compiles, not when it runs |
a procedure cannot be written inside another |
SUB inside SUB |
more than 255 names in one procedure |
the machine’s frame is a byte wide |
a jump of N bytes: no jump reaches further than 65535 |
one procedure holding more code than a jump can cross |
Some things are refused when the program runs rather than when it compiles:
File not found for an OPEN ... FOR INPUT on something that is not there,
Input past end of file when the answers run out, subscript N of X is above M
for a multi-dimensional array, and call depth exceeded for recursion past about
254 levels.
A failure inside the runtime says so. PRINT, INPUT and the file
statements are written in SolaBasic and compiled into your program, so a trace
may name the SolaBasic runtime and one of its routines before it reaches a line
you wrote. The line you wrote is the one below it.