This is the 2nd part of learning QBasic, still figuring out how QBasic works in order to write an interpreter for it.
Quick-ish summary of what was covered in the previous post:
- 5 types. There are 5 built-in types: integer, long, single, double, string (so 4 numeric types and one type for strings).
- Implicit & explicit variables. A variable can be explicitly declared with
the
DIM
statement, but it can also be implicitly declared simply by using it (e.g.A = 42
). - Compact and extended DIM. A variable declaration can specify the type with
the
AS type
clause, in which case I call it extended. Example:DIM A AS STRING
. If it’s without theAS type
clause, I call it compact. Example:DIM A
,DIM A$
. Implicit variables are by definition compact. - Bare and qualified variables. A variable name can be optionally followed
by a type qualifier to indicate its type. The qualifiers are
%
for integer,&
for long,!
for single,#
for double, and$
for string. Example:Msg$ = "Hello, world!"
. - Resolving type. The type of a bare compact variable is determined by its
name (in all other cases the type is known). The default type is single. The
DEFxxx
statements can change the default, based on the first letter of the variable name (e.g. a common idiom isDEFINT A-Z
to make all bare compact variables integers).
You can read the previous post for a bit more context on how all these behave on edge cases.
More on the numeric types
The LEN
built-in function can be used to measure the size of a variable. For
the built-in numeric types, it shows how many bytes they use. For the string
type, it shows the length of the string. Let’s use it to see how big the
built-in types are:
PRINT "Size of integer is:", LEN(A%)
PRINT "Size of long is:", LEN(B&)
PRINT "Size of single is:", LEN(C!)
PRINT "Size of double is:", LEN(D#)
This will print out 2 for integer (16 bits), 4 for long (32 bits), 4 for single and 8 for double (64 bits). For the two integer types, the minimum and maximum represented values are:
Type | Min | Max |
---|---|---|
Integer | -32,768 | 32,767 |
Long | -2,147,483,648 | 2,147,483,647 |
Side note: In the current implementation of rusty basic, I use 32 and 64 bit types internally (i32 and i64) and the min-max limits for each type is enforced programmatically. This might change in the future.
Literal types
Given the min-max values described above, consider the following program:
PRINT 32767 ' max int
PRINT 32768
PRINT 32767 + 1 ' oops!
PRINT 32768 + 1
The program gives an overflow error on the third line. This might be counterintuitive. If it can print the value 32,768 on the second line, why can’t it print 32,767 + 1? It’s discoveries like these that shed some light on how QBasic evaluates expressions under the hood (and help me with re-implementing them in rusty basic). What (probably) happens is that QBasic tries to use the smaller type that can hold the literal expression. On the first line, it sees 32,767 and it stores it in a integer as it fits. On the second line, it uses a long type. On the third line, there are two values which both fit in the integer type on their own. Then, at runtime, it tries to add two integer values together, leading to an overflow as the result doesn’t fit anymore.
It is possible to qualify a number with a type qualifier to be explicit about its intended type (I haven’t fully implemented this in rusty basic yet):
'PRINT 32767 + 1 ' overflow error!
PRINT 32767& + 1 ' works
Constants
QBasic supports constants with the CONST
statement, e.g. CONST Answer = 42
.
What we’ve discussed so far regarding variable names and types goes a bit
different for constants.
Type comes from the right side
For bare constants, i.e. where the name isn’t followed by a type qualifier, it
is the constant value that determines the type of the constant. In the statement
CONST A = 42
, the type of A will be an integer, despite the fact that we
haven’t added DEFINT A-Z
in the program. We can even do CONST A = "hello"
.
It is the type of the right hand expression that determines the type of the
constant.
Let’s see this with an example:
DEFINT A-Z ' bare variables are integers
A = 32768 ' overflow!
CONST B = 32768 ' allowed, B is long
In the variable A
, the type is derived from the name. We have a DEFINT A-Z
statement, which means any unqualified variable starting with a letter A-Z (so
all variables) is an integer. Since A is an integer, assigning the value 32768
to it results to an overflow error, as it doesn’t fit the integer type. For B
however, the type is determined by the const value. The smallest type that can
accommodate 32768 is long, so B becomes a long constant.
Constants always follow extended naming rules
We saw in the previous post that it’s possible to do this with compact style variables:
DIM A$
DIM A%
A$ = "The answer is"
A% = 42
PRINT A$, A%
but not with extended style variables:
DIM A AS STRING
DIM A% ' duplicate definition!
Constants behave always like extended style variables in this case, even though
they can only be declared in the compact style (there is no such thing as
CONST A AS STRING = "hello"
).
So if we have a constant with CONST Msg = "Hello"
(or even
CONST Msg$ = "Hello"
):
Msg
andMsg$
are both valid ways to reference that constant- It is not possible to reference
Msg
with any other qualifier, e.g.Msg%
gives a duplicate definition error - It is not possible to declare a variable named
Msg
(with or without qualifier) -
Within the same sub-program, it is not possible to declare another constant named
Msg
(with or without qualifier). It is possible however to re-define a constant within a sub-program (because why not):CONST Msg = "hi" PRINT Msg ' prints hi Greetings ' prints 42 SUB Greetings ' shadow global constant with a local constant CONST Msg = 42 PRINT Msg END SUB SUB Oops ' error: cannot shadow global constant with a variable DIM Msg$ END SUB
Wrap up
While writing rusty basic, one of the most challenging things was to identify what a name is, and do it in the same way QBasic does. This is what we have so far:
Defining a constant
- Type determined by value, not from name
- Cannot co-exist with constant or variable of the same name, qualified or unqualified
- Can shadow global constant inside subprogram (
FUNCTION
orSUB
)
Defining an extended variable
- Type provided by
AS type
clause - Cannot co-exist with constant of the same name, qualified or unqualified, local or global
- Cannot co-exist with variable of the same name, qualified or unqualified
Defining a compact variable
- Type provided by qualifier if qualified, resolved by name if bare (affected by
DEFxxx
statements) - Cannot co-exist with constant of the same name, qualified or unqualified, local or global
- Cannot co-exist with extended variable of the same name
- Can co-exist with compact variables of the same name and different type