"A Japanse person goes to a junkfood restaurant, and asks
'Hakkie tippo wawa kili Coca Cola?'
The restaurant owner reacts: 'You want a can of WHAT?'"
YOUR SECOND GFA BASIC 3.XX MANUAL
- or -
HOW I LEARNED TO STOP WORRYING AND LOVE GFA-BASIC
PART 4
CHAPTER THREE - VARIABLES
by Han Kempen
Variable-type
In GFA-Basic we use the expressions 'byte', 'word' and 'integer'
for the three integer-variables (postfixes '|', '&' and '%').
Because all three are integers, the expression 'integer' for the
4-byte integer-variable is sligthly confusing. In other languages
this variable is called 'longword' or simply 'long'. An address
in RAM should always be a 4-byte integer. A nibble is half a byte
(4 bits; in hexadecimal notation each digit represents a nibble),
but there is no corresponding variable-type.
DEFWRD
I prefer to declare word-variables as the default, so all
variables without postfix are 2-byte word-variables:
DEFWRD "a-z"
IMPORTANT: if a number-variable has no postfix in the Manual,
you should assume it's a word-variable. Please note that the
interpreter assumes a number-variable without postfix is a
floating point variable, unless you use DEFWRD "a-z".
I strongly recommend the use of word-variables (2 bytes). In
calculations, the use of the special integer-operators (ADD, SUB,
INC, etc.) speeds the program up considerably. Calculations with
word-integers in a compiled program are usually faster than other
calculations.
If you insist on using the regular operators (+, -, etc.) you
should use floating point variables instead of integer-variables.
Using the regular operators, the interpreter converts integer-
variables to floating point, does the calculation and converts
the result back to integer again:
a&=b&*c& ! slow (in interpreted program)
a#=b#*c# ! faster
a&=MUL(b&,c&) ! much faster
It takes some time to recognize what an exotic expression like
'DIV(a,MUL(ADD(a,b),SUB(b,c)))' means. I suggest you use the
regular operators if the calculation-time is not critical. In
loops, the gain in calculation time really counts, so you should
use the integer-operators. That way you will learn Polish too.
Be careful if you use integer-operators with floating point
variables, as a floating point number is converted to an integer
before the calculation:
MUL x%,y# ! x% * INT(y#)
If you multiply an integer with a floating point variable you
usually want the result to be converted to integer after the
calculation:
x%=x%*y# ! INT(x% * y#)
If the range of word-variables (-32768 to 32767) is not
sufficient, you could use 4-byte integer-variables (-2147483648
to 2147483647) instead. Think twice before you use floating point
variables.
Boolean
The following five lines :
IF number>0
test!=TRUE
ELSE
test!=FALSE
ENDIF
can be shortened to just one line :
test!=(number>0)
This works, because the '>'-operator returns TRUE or FALSE
(actually -1 or 0).
Another little trick :
IF i=1
n=n*2
ELSE IF i=2
n=n*5
ELSE
n=0
ENDIF
This could be shortened to :
n = n*-2*(i=1) + n*-5*(i=2)
The example is ridiculous, but the principle involved could be
useful. The expressions 'i=1' and 'i=2' are either 0 (FALSE) or -
1 (TRUE).
It is not necessary to use something like:
IF flag!=TRUE
(...)
ENDIF
You can simply use :
IF flag!
(...)
ENDIF
Integer
You can't assign 2^31 to a 4-byte integer-variable. Although an
integer contains 32 bits, you can't use bit 31 (bit 0 is the
first bit) because this bit is a flag for a negative integer. The
largest positive number you can assign to an integer-variable is
therefore 2^31-1 (2147483647). I could have written an analogue
paragraph about the 2-byte word-variables, but I didn't.
Floating Point
The range of floating point variables (postfix #) is:
-1.0E1000 < x# < 1.0E1000
Larger or smaller numbers can be used in calculations, but not
printed with PRINT, because the exponent may contain not more
than 3 digits (1.0E+1000 is displayed as 1.0E;00). Also, the
editor refuses to accept numbers larger than 1.0E+1000. Try this:
x#=1.0E+999
PRINT x# ! no problem
x#=x#*100
PRINT x# ! 1.0E+1001 can't be PRINTed
PRINT x#/100 ! proof that x# really is
! 1.0E+1001
y#=1.0E+1000 ! editor accepts it, but can't
! print it
y#=1.0E+1001 ! editor does not accept this
It is possible to print numbers with four digits in the exponent
with PRINT USING:
PRINT USING "#.#^^^^^^",x# ! 1.0E+1001 is now printed
! correctly
You have to use six '^' in the exponent: one for 'E', one for
'+' and four for the 4-digit exponent.
The largest number that can be used seems to be 1.0E+2158:
z#=1.0E+999*1.0E+999*1.0E160
PRINT USING "#.#^^^^^^",z# ! 1.0E+2158
z#=z#*10 ! overflow
If your life depends on it you should not exceed the upper limit
that is officially guaranteed by GFA (probably a relic from GFA-
Basic 2.x days):
x# < 3.59E+308
The smallest positive number is 2.22E-308. Smaller positive
numbers are treated as 0.
String
A string is stored in memory as folllows:
b0|b1|b2|b3|b4|b5| <string> [&H0] |b6|b7|b8|b9
descriptor | string |backtrailer
The descriptor contains the string-address in the bytes b0-b3
(used by V:) and the string-length in the bytes b4-b5 (used by
LEN). The actual string is followed by the null-byte (CHR$(0)) if
the string-length is an odd number. If you are going to use CHAR,
a string must always end with CHR$(0), so you must add the null-
byte:
t$=t$+CHR$(0)
This is the easy way, you could also test if the string-length
is even and add the null-byte only if that is the case. The
backtrailer contains the descriptor-address in the bytes b6-b9.
The backtrailer is used during garbage-collection: after finding
the descriptor-address the exact position of the entire string is
known immediately and the string can be moved to a new place in
memory.
VAR
If you call a Procedure and use VAR (call by reference), all
variables and/or arrays after VAR are called by reference. An
example to clarify this:
@test(10,5,number%,array%())
(...)
PROCEDURE test(a,b,VAR x%,y%())
' now a=10 and b=5 (call by value)
' number% and array%() can now be used as x% and y%()
x%=a+b ! global variable number% is now 15
ARRAYFILL y%(),1 ! all elements of array%() now 1
RETURN
In GFA-Basic 2.x you would have to use SWAP for calling an array
by reference, but VAR makes life much easier in GFA-Basic 3.x:
@test(*a%()) ! GFA-Basic 2.x (also possible in
3.x)
(...)
PROCEDURE test(ptr%)
SWAP *ptr%,x%() ! array a%() temporarily renamed
as x%()
(...) ! do something with the array x%()
SWAP *ptr%,x%() ! restore pointer before leaving
Procedure
RETURN
'
@test(a%()) ! GFA-Basic 3.x
(...)
PROCEDURE test(VAR x%())
(...) ! do something with the array x%()
RETURN
FUNCTION
You can only leave a FUNCTION by RETURNing a value or a string.
This value/string is usually assigned to a variable. If the
FUNCTION returns a string, the function-name has to end with '$':
PRINT @test
FUNCTION test
RETURN 126
ENDFUNC
'
PRINT @test$
FUNCTION test$
RETURN "this is a string"
ENDFUNC
You can use as many RETURNs within a FUNCTION as you like.
CLEAR
Because CLEAR is automatically executed when you run a program,
it's not necessary to start your program with this command.
ERASE
It's impossible to reDIMension an existing array. You first have
to ERASE the existing array and then you can DIMension a new
array. It is not necessary to test for the existence of an array
with DIM?() before you use ERASE. In other words, you can use
ERASE even if the array doesn't exist:
ERASE array$() ! just in case this array already exists
DIM array$(200)
Some people think it's a sin to ERASE a non-existing array. If
you have the same strong feelings, you should ERASE the proper
way:
IF DIM?(array$())=0
DIM array$(200)
ELSE
ERASE array$()
DIM array$(200)
ENDIF
After ERASEing an array, GFA rearranges the remaining arrays.
All arrays that have been DIMensioned after the deleted array are
moved in order to fill the gap of the deleted array. This is
important if you use an address like 'V:array(0)' in your program
(see paragraph 'Storing data in RAM' in chapter 4).
DUMP
Examine all variables in your program by typing 'DUMP' in Direct
Mode. Press <CapsLock> to slow down the scrolling-speed, or press
the <Right Shift>-key to stop the scrolling temporarily.
<CapsLock> - scroll slowly
<Right Shift> - stop scrolling (release to scroll
further)
This is the best way to discover those nasty typo-bugs in a
variable-name. You'll probably be surprised to see the names of
deleted variables as well. Also, any variable-name you used in
Direct Mode appears. All these names are Saved with the program!
Delete all unwanted names by temporarily saving the program as a
LST-file:
- Load the file
- Save,A (press <Return> in Fileselector)
- New
- Merge (press <Return> again)
- Save (press <Return> once more)
The file could be much shorter after this operation.
TYPE-bug
The command TYPE does not always work properly if you use local
variables. TYPE sometimes returns -1 (= error) instead of the
proper TYPE-number. In a compiled program TYPE always returns -1
for any global/local variable. Buy a bottle of TYPE-Ex and erase
TYPE completely from your manual.
READ
As a rule, I always RESTORE the appropriate label before READing
DATA-lines. That way I can use DATA-lines in Procedures:
PROCEDURE read_data
RESTORE these.data
READ a,b,c,d
these.data:
DATA 1,2,3,4
RETURN
SWAP
The 52 cards in bridge (or another card-game) can be represented
by a byte-array. Fill the elements 0-51 of the array with the
value 0-51. The values 0-12 would represent the Club-cards
(2,3,4,...,Q,K,A), values 13-25 the Diamonds, values 26-38 the
Hearts and values 39-51 the Spades. Shuffling the cards can now
be simulated as follows:
DIM deck|(51)
FOR i=0 TO 51
deck|(i)=i
NEXT i
FOR i=DIM?(deck|())-1 DOWNTO 0
j=RAND(i)+1
SWAP deck|(j),deck|(i)
NEXT i
TIME$
You can use TIME$ to print the current time on the screen. In
the Procedure Clock you'll find a way to print the time every
second, although TIME$ is updated every two seconds:
EVERY 200 GOSUB clock
PROCEDURE clock
LOCAL t$
t$=TIME$
IF t$=clock$
MID$(clock$,8)=SUCC(RIGHT$(clock$))
ELSE
clock$=t$
ENDIF
PRINT AT(1,1);clock$
RETURN
That's pretty clever, although the use of a global variable
(clock$) in a Procedure is not to be recommended. Unfortunately
the clock stops completely during the execution of time-consuming
commands like PAUSE or BLOAD.
DATE$
Find the day of the week with Zeller's Congruence:
day=VAL(LEFT$(day.date$,2))
mp=INSTR(day.date$,".")
month=VAL(MID$(day.date$,mp+1,2))
year=VAL(RIGHT$(day.date$,4))
IF month<=2
m=10+month
year=year-1
ELSE
m=month-2
ENDIF
h=year/100
y=year-100*h
w=(TRUNC(2.6*m-0.2)+day+y+TRUNC(y/4)+TRUNC(h/4)-2*h) MOD 7
RESTORE weekdays
FOR n=0 TO w
READ weekday$
NEXT n
'
weekdays:
DATA Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday
SETTIME
You can use SETTIME to change the system-time or the system-date
or both:
SETTIME new.time$,DATE$ ! change system-time
SETTIME TIME$,new.date$ ! change system-date
SETTIME new.time$,new.date$ ! change both
Of course you can also use DATE$ and TIME$ to change time and
date:
DATE$=new.date$
TIME$=new.time$
The format for the time-string is "hh:mm:ss", but you can also
use one digit for hours and/or minutes ("1:1:00").
The format for the date-string is "dd.mm.yy" or "dd.mm.yyyy",
but you can use one digit for day and/or month ("1.1.92").
Every ST has two independent clocks. A Mega-ST has a third
clock, but this battery-operated clock actually overrides the
other clocks. The system-clock can be accessed with GEMDOS 42-45
(Tgetdate, Tsetdate, Tgettime and Tsettime) and is also used by
GFA-Basic. The keyboard-clock is run independently by the
keyboard and can be accessed with XBIOS 22 and 23 (Settime and
Gettime). If you use SETTIME or DATE$/TIME$ you use the system-
clock only. After a warm reset the keyboard-clock is useful,
because it keeps running while the system-clock starts all over
again (unless your ST has a battery-operated clock):
d%=GEMDOS(42)*65536 ! system-date
t%=GEMDOS(44) AND 65535 ! system-time
~XBIOS(22,L:d%+t%) ! set keyboard-clock
~XBIOS(38,L:LPEEK(4)) ! warm reset
x%=XBIOS(23) ! read keyboard-clock
~GEMDOS(43,x% DIV 65536) ! restore system-date
~GEMDOS(45,x% AND 65535) ! restore system-time
After a cold reset the keyboard-clock can't be used. You could
save the current date/time temporarily in a file or you'll have
to ask the user to enter the date and time after the cold reset.
Procedures (CHAPTER.03)
Array_shuffle ARRSHUFL
Shuffle the numbers in a word-array:
@array_shuffle(array&())
Clock CLOCK
Print time every second in upper right corner of TOS-screen:
EVERY 200 GOSUB clock
Clock_wait CLCKWAIT
Show a clock on the screen and wait until the user presses a key
or mouse-button:
@clock_wait
Date_input and Date_input_error DATE_INP
Enter date at current cursor-position:
PRINT "Enter the date: "; ! any format (day month year)
@date_input(new.date$)
PRINT "Date: ";new.date$ ! date-format "dd.mm.yyyy"
Accepts a wide variety of date-formats and is therefore far more
flexible than the Procedure Date_new. Uses ERROR to catch
unexpected errors.
Date_new DATE_NEW
Enter new system-date at current cursor-position:
PRINT "Enter new system-";
@date_new
Date_print DATE_PRT
Print a date at the current cursor position:
PRINT "System-date is: ";
@date_print(DATE$)
Uses the Function Day_of_week to print something like:
Friday 8 January 1988
Mem_test MEM_TEST
Examine how many bytes are used by variables and arrays. Use
this number to reserve the proper amount of memory for a compiled
program (especially accessories):
mem.free%=FRE()
EVERY 200 GOSUB mem_test
(...) ! the program you're testing
Millitimer_init and Millitimer MILLITIM
Procedure uses a FOR-NEXT loop to measure time in milliseconds:
@millitimer_init
PRINT "Press any key ";
PAUSE RAND(4*50)
PRINT "NOW"
@millitimer(ms#)
PRINT "Your reaction time was ";ms#;" milliseconds."
Stopwatch and Stopwatch_print STOPWTCH
The Procedure Stopwatch can be used as, you guessed it, a
stopwatch. The elapsed time can be printed at the current cursor-
position by the Procedure Stopwatch_print:
@stopwatch ! start the stopwatch
' Do something interesting that takes some time
@stopwatch ! stop the stopwatch; s.seconds# s.
PRINT "Elapsed time: ";
@stopwatch_print ! print elapsed time
Time is printed as follows:
1 h 24 m - no seconds if ? 1 hour
3 m 21 s - no decimals if ? 1 minute
34.6 s - 1 decimal if ? 10 seconds
6.28 s - 2 decimals if < 10 seconds
Of course you can change the format in Stopwatch_print, or you
could use the global variable s.seconds# from Stopwatch to do
your own calculations.
Time_input and Time_input_error TIME_INP
Enter time at current cursor-position:
PRINT "Enter the time: "; ! any format (hours minutes
[seconds])
@time_input(new.time$)
PRINT "Time: ";new.time$ ! time-format "hh:mm:ss"
Accepts a wide variety of time-formats and is therefore far more
flexible than the Procedure Time_new. Uses ERROR to catch
unexpected errors.
Time_new TIME_NEW
Enter new system-time at current cursor-position:
PRINT "Enter system-";
@time_new(TRUE) ! colon as separator
'
PRINT "Enter system-";
@time_new(FALSE) ! point as separator
If you use the point as separator, you can enter the time
quickly without leaving the numeric keypad. But you have to use
the format "hh.mm.ss" (two digits for hours, minutes and
seconds). If you press <Return> immediately, the system-time is
not changed.
Functions (CHAPTER.03)
Date_integer DATE_INT
Convert date "dd.mm.yyyy" to integer yyyymmdd to make it easier
to compare two dates:
IF @date_integer(d1$) > @date_integer(d2$)
' Date d1$ later than d2$
ELSE
' Date d1$ equal to or earlier than d2$
ENDIF
Day_of_week DAY_WEEK
Returns day of week for a date "dd.mm.yyyy":
PRINT "System-date ";DATE$;" is a ";@day_of_week(DATE$)
Days_passed DAY_PASS
Returns number of days between two dates (format "dd.mm.yyyy"):
d1$="1.1.1991"
d2$="1.1.1992"
PRINT "There are ";@days_passed(d1$,d2$);" days ";
PRINT "between the dates ";d1$;" and ";d2$
Disclaimer
The text of the articles is identical to the originals like they appeared
in old ST NEWS issues. Please take into consideration that the author(s)
was (were) a lot younger and less responsible back then. So bad jokes,
bad English, youthful arrogance, insults, bravura, over-crediting and
tastelessness should be taken with at least a grain of salt. Any contact
and/or payment information, as well as deadlines/release dates of any
kind should be regarded as outdated. Due to the fact that these pages are
not actually contained in an Atari executable here, references to scroll
texts, featured demo screens and hidden articles may also be irrelevant.