Chapter 6 Concepts This chapter describes the technical details of the HiSOFT BASIC language, together with some of its more advanced features. It is intended for users who already have a good understanding of the BASIC language and want to get to grips with HiSOFT BASIC quickly. If you are new to BASIC please read the Tutorials chapter first. Character Set HiSOFT BASIC uses plain ASCII characters in its input files. The following characters have special meanings: a-z, A-Z The letters, which are used in reserved words and the user's variable names, labels and sub-program names. Lower and upper case are treated as the same in variable and reserved word definitions so that THEN, then and Then are all the same reserved word. E, e, D, d These are also used for exponents in numbers. 0-9 The digits, which are used in numbers and can also be used in names as long as they are not the first character. . The full stop or period, which is used as the decimal point in numbers and can also be used in names as long as it is not the first character. % The percentage sign, which is used to indicate that a variable is a 16 bit integer i.e. whose values must be in the range -32768 to 32767. & The ampersand, which is used to indicate that variables are long integers i.e. whose values must be in the range -2^31 to 2^31-1. Also used to introduce hexadecimal, octal and binary constants. ! The exclamation mark, which is used to indicate that a variable is a single-precision floating point number. # The hash or number sign, which is used to indicate that a variable is a double precision floating point number and also used to indicate that certain input/output operations are to be directed to channels rather than the screen (e.g. PRINT #). $ Used to indicate string variables. _ The underline character, which can be used in variables after the first character assuming the Underlines in Names (UNDERLINES) option has not been turned off. If it appears at the start of a symbol or the underlines has been disabled (NOUNDERLINES) then it indicates the rest of the line is to be ignored and that the following line is to be considered part of the current one. " The quotation mark or double quote which is used to delimit string literals. Ô The apostrophe or single quote which is used to indicate that the rest of this line is to be regarded as a comment. ( ) The parentheses or round brackets, which are used to enclose function arguments, over-ride the priority of operators and indicate arrays. + - * / The basic arithmetic operators. = The assignment operator and equality operator. < > Less than and greater than comparison operators - also used as parts of the shift operators. ^ Exponentiation operator. \ The back-slash character, which is used as the integer division operator. , Comma. ; Semi-colon. ? Used as an abbreviation for PRINT. Other characters with ASCII values lower than 32 are treated as white space, and ignored so you may, for example, include form-feed (chr$(12)) characters to give a new page on your printer when listed. Other characters may be used in strings, but otherwise will generate a warning and will be ignored. Program lines and labels Program lines consist of an optional line number or label, one or more statements separated by colons and an optional comment, which starts with an apostrophe or single quote. Line numbers may be any number between 1 and 65529 inclusive. (65529 may seem a strange number, it is the maximum allowed by Microsoft BASIC). Line labels consist of any valid variable name that is not used as a variable or a sub-program and labels are followed by a colon. There is no limit to the number of characters in a line label but lower- and upper-case letters are treated as the same and the characters must not be a reserved word. Thus the following line labels are allowed: Label9999: A.very.long.label.that.causes.problems.to.other.BASICs: Hello: The following line labels are the same: Start: START: start: Line numbers and labels may be preceded by white space. White space is not required after the last digit or colon. Line numbers may not contain spaces. Note: For compatibility with other BASICs we recommend that you do not use full stops (.) or underlines ( _ ) in labels and keep them to less than 40 characters. Line number 0 is not allowed because it would be confused when using ON ERROR GOTO 0 which does not mean go to line 0 if an error occurs. In general we do not recommend the use of line numbers since line labels are much more readable. The exception to this is when using ERL when the use of line numbers is useful since otherwise you must change the line numbers in your program each time you insert or delete lines in your program. Most of the time you do not need to use line numbers or line labels because HiSOFT BASIC has such a rich set of structured statements (much better than Pascal and even more flexible than C and Modula 2). You may have many statements per line provided each is separated by a colon. HiSOFT BASIC has an extension to call sub-programs without the CALL keyword. However you cannot do this if the sub-program has no parameters and is the first statement of a multi-statement line. For example, if you have: SUB john STATIC PRINT "John"; END SUB then john PRINT " Smith" will print John Smith as will call john: print " Smith" However john: print " Smith" is wrong because the john: could be a label definition; normally the compiler will warn you on the parsing phase if you make this error. This problem does not apply if the sub-program has a parameter, for example: SUB john(para$) static PRINT "John ";para$; END SUB john "David": print " Smith" is fine because it cannot be mistaken for a label. If an apostrophe or single quote (Ô) appears on a line then the rest of the line is treated as comment and ignored. The only exception to this is DATA statements which treat the apostrophe as part of the data. If you want a comment on such a line precede it with a colon; this will terminate the DATA statement. Program lines may be any length theoretically, but it is generally a good idea to keep them less than 80 characters so that the whole line is displayed at once. If you need a line that is significantly longer than this then the chances are that the line is more complicated than it should be as far as ease of understanding is concerned. There are two exceptions to this. The FIELD statement where, for large records, you need many more than 80 characters and the TAGLIST statement where you need to set up many tags when calling the operating system. To get around this the underline character ( _ ) may be used to cause lines to be continued on the next physical line. Anything after the underline is ignored. For example: FIELD #3,20 AS name$, _ Ô surname only 5 AS initials$ ,_ 50 AS street$, _ Ô include name or number here 20 AS town$, _ 20 AS county$ ,_ Ô or state if applicable 20 AS country$ which would be much more readable than the one-line equivalent where you would also have to leave out the comments. Normally HiSOFT BASIC allows underlines in variable names, unlike traditional BASICs. Underlines are treated as continuation characters if they are not part of a variable name. If you are porting programs that have continuation characters immediately after identifiers or reserved words then use the NOUNDERLINES option or de-select the appropriate box in the Compiler Options requester. For example: IF x THEN_ PRINT "hello" will be accepted in some BASICs as a one-line IF statement (no need for an END IF). Without the NOUNDERLINES option HiSOFT BASIC will give an error because it thinks you are using a variable called THEN_. To solve this use UNDERLINES or insert a space in front of the _ character. If you use NOUNDERLINES and inadvertently use an identifier containing an underline you may get a very strange error message because the compiler has ignored the rest of the line. Data TypesThere are five types of data in HiSOFT BASIC: StringsA string is a sequence of characters that may be up to 16 megabytes long assuming you have enough memory. Strings may contain any character with a value of 0 to 255 inclusive. IntegersIntegers are numeric and consist of the whole numbers from -32768 to 32767. Long IntegersLong integers are numeric and consist of the whole numbers between -2147483648 and 2147483647. Single precision numbersSingle-precision numbers have approximately seven digits of precision and a range of 5.4E-20 to 9.2E-18 for positive values and -2.7E-20 to -9.2E18 for negative values. Double precision numbersDouble precision numbers have approximately 16 digits of precision and a range of 4.9E-324 to 1.8E308 for positive numbers and -4.9E-324 to -1.8E308 for negative numbers. There is loss of precision with numbers of magnitude less than 2.2E-308. Constants Constants are values which do not change during program execution. Constants may be of all 5 types. A string constant is a sequence of ASCII characters enclosed in double quotes ("). These can be any character between ASCII values 32 (space) and 255. To obtain a double-quote in a string repeat it, so that, for example, the string consisting of one double quote character is """". The first is the start of the string, the second and third form the character itself and the last is the closing quote. Numeric constants are formed in one of the following ways: Decimal numbersA sequence of decimal digits followed optionally by a decimal point (.) and more digits and/or an exponent. An exponent consists of the letter d, D, e or E followed by a decimal integer. E indicates single precision and D indicates double precision. The number may be preceded by a minus sign as may the numeric part of the exponent. The number before the decimal point may be omitted. The number may be followed by a type specifier (%,!,& or #). Hexadecimal ConstantsHexadecimal constants start with &H or &h and are followed by hexadecimal digits(0-9, a-f, A-F). The number may be followed by a type specifier (%,!,& or #). Hexadecimal integer constants between &h8000 and &hFFFF are taken as signed 16 bit integers. Hexadecimal long integer constants between &h80000000 and &hFFFFFFFF are treated as signed 32 bit constants so that for example: &h7FFF = 32767 integer &h8000 = -32768 integer &h8001 = -32767 integer &hFFFF = -1 integer &h10000 = 65536 long integer &h7FFFFFFF = 2147483647 long integer &h80000000 = -2147483648 long integer &hFFFFFFFF = -1 long integer &h100000000 = 4294967296 double If you want &h8000 to be treated as +32768 then follow the number with & and it will be treated as a long and thus positive e.g. &h8000& = 32768 long integer Octal ConstantsOctal constants start with &O or &o or just simply &. and are followed by octal digits (0-7). The number may be followed by a type specifier (%, !, & or #). The type of an un-terminated octal constant is determined by the same rules as for hexadecimal constants (see above). Binary ConstantsBinary constants start with &B or &b and are followed by the digits 0 or 1. The number may be followed by a type specifier (%,!,& or #). The type of an un-terminated binary constant is determined by the same rules as for hexadecimal constants described previously. Character constantsThese start like strings of only one character and are followed by the % character and have a value equivalent to the ASC() of the character. However they are generally easier to read and more efficient than the ASC() equivalent. Types of Constants The rules regarding what type a constant is are rather complicated but in general you should find that normally the compiler does what you expect. The most common problem is that some hexadecimal constants are treated as negative. If this is a problem please see the Hexadecimal Constants section above. The following are in decreasing order of importance: 1. A terminating character is used. If the number is terminated by: % it is taken as an integer & it is taken as a long integer ! as a single precision floating point number # as a double precision floating point number. 2. If the number is hexadecimal or octal and lies in the following range: 0 to &hFFFF it is taken as an integer &h10000 to &hFFFFFFFF it is taken as a long integer &h100000000 upwards it is taken as a double. For the rules concerning whether a constant is treated as negative see above. 3. If the number is decimal and it is not a whole number and has more than 6 digits in the whole number and decimal parts, then it is a double. 4. If it has an exponent of the form D or d then it is a double. 5. If it has an exponent of the form E or e then it is an integer. 6. If it has a decimal point then it is a single precision number. 7. If it is a whole number less than or equal to 32767 it is an integer. 8. If it is a whole number less than or equal to 2147483647 it is a long integer. Examples: 1 integer 1.0 single precision (.) 1.0E0 single precision(. and E) 1.00000 single precision 1.000000 double precision (7 digits) 1.000000E0 single precision(E overrides 7 digits) 1D0 double precision (D) 1D0% integer (% over-rides D) 1.0& long integer (& overrides .) 1D0! single precision (! overrides D) 1# double precision. Variables and Reserved Words Variable names start with a letter and subsequent characters may be letters, digits, or full stops (.). In addition underlines (_) may be included if you haven't switched this off using the NOUNDERLINES option. For maximum compatibility with other BASICs don't use underlines or full stops. Lower and upper case are treated as the same in variable names and reserved words so that PRINT, Print and Print are all the same reserved word. Variables may be terminated with a type specifier % (integer), & (long integer), ! (single precision floating point) or # (double precision floating point). If there is no type specifier then the type is determined by the current DEFtype statement for the first letter of the variable. If there have been no DEFtype statements then single precision (!) is used. Compiler error messages specifying variable names always include the type specifier that has been assumed. For example, the following gives the types of the respective variables: DEFINT i-k DEFSTR s DEFDBL q-r i% integer i integer i% (same as above) I (also the same as above) i& long integer (different) str1 string (same as str1$) real_value1 double (same as real_value1#) You can not use reserved words as the names of variables or sub-programs. The reserved words are listed in full in Appendix C. Reserved words and variables may be entered in upper or lower case or a mixture of both. In general using reserved words with type specifiers should be avoided for compatibility reasons. GO is not a reserved word. However if GO is followed by TO or SUB then it is made into GOTO or GOSUB respectively; so you can have white space between GO and TO and it will still be treated as GOTO. Thus you can use GO as a variable name if you like but some strange things can happen, such as FOR i=go TO from STEP 2 is misunderstood because the compiler considers this to be FOR I = GOTO from STEP 2 Variables must not start with FN because they would be treated as function names. The same rules for determining the type of a variable are used to determine the types of functions. The following are FN function names: FNtest FNsine& FNget.one.character Sub-program names must not have a type specifier because they do not have a type. You may use the same name for a variable and a sub-program although the type specifier of the variable must be used explicitly. However, this can be very confusing. For example SUB john john%=42 END SUB john%=52 : john ArraysArray names follow the same rules as for variables and their types are determined in the same way. You may use the same name for an array and an ordinary variable. Normally an array name is followed by an open parenthesis, except in the ERASE statement and the UBOUND and LBOUND functions when this is assumed automatically. Arrays are tables of values each of the same type. Normally the number of elements in an array and the number of dimensions is specified with a DIM statement. There are no restrictions on the size of arrays other than available memory and subscripts may be long integer values if applicable. The maximum number of dimensions for an array is 31 (which would take up a minimum of 4 gigabytes of memory if each index had more than one element). If the DIM statement is not used, the maximum subscript is assumed to be 10. If you have switched off array checks using compiler option NOARRAY then you must use the DIM statement. To check whether you have inadvertently auto-dimensioned an array then you should use the Array Checks Warnings option - this will give a run-time error when an array is used before it has been DIMensioned. The minimum value of subscripts is 0 unless an OPTION BASE statement is used. When referenced, the element of the array to be accessed is specified by one or more expressions inside parentheses and separated by commas. The expressions may be of any numeric type although single and double precision real values will be converted to long integers. For example, given: DIM A(30), B$(table_entries,4), table&(100000) DIM t%(fred*fred),c(n,n,n) then the following are valid array references: A(i) B$(j*3,2) table&(i&) t%(k-l) c(i,j,k) By default HiSOFT BASIC arrays are static i.e. you cannot re-dimension them; this can be changed through the use of the REM $dynamic compiler option - for more information on this and other features of arrays see the Advanced Arrays section later in this chapter. Operators Expressions are made up of constants, variables, array variables, function calls and operators. The order of priority is listed below with the highest priority first: 1. Exponentiation ( to the power of ) (^) 2. Unary Minus (-) 3. Multiplication (*) and Floating Point Division (/) 4. Integer Division (\) 5. Modulus ( MOD) 6. Addition (+) and Subtraction (-) 7. Shift left (<<) and Shift right (>>) 8. Comparisons (=,<>,>,<,>=,<=, ==) 9. NOT10. AND11. OR and XOR (exclusive or)12. EQV13. IMPThe only exception to this is that x^-y is evaluated as x^(-y). To change the order of evaluation use parentheses (round brackets). The guiding principle for the precision used when evaluating expressions is that the minimum precision is used that will ensure that accuracy is not lost. The exponentiation operator (^) always has its operands converted to either single or double precision floating point and returns a result of the same type. Single precision is used if the operands are either integer or single precision. If either operand is a long integer or is double precision then it is evaluated in double precision for accuracy. See 6-2 below. This operator uses logarithms to give its result and as such is slow and inaccurate if the second operand is a small integer. The multiplication, addition, subtraction and unary minus operators may have operands of any numeric type with the following table giving the result of the expressions: integer long single double integer integer long single double long long long double double single single double single double double double double double double 6-1 Type conversion for most operators Addition may be also used for strings when it means concatenation so that, for example: "ABC" +"DEF" ="ABCDEF" Floating point division operands are always converted to single or double precision floating point numbers, the following table gives the result of the expression: integer long single double integer single double single double long double double double double single single double single double double double double double double 6-2 Type conversion for division operators The integer division operator \ uses long integer (32-bit) arithmetic unless both operands are integers in which case integer (16-bit) arithmetic is used. The comparison operators always return an integer value of -1 for true and 0 for false. The comparison is evaluated using the type given in 6-1 above for numeric types. Strings may also be compared. The comparison operators are = equality <> inequality > greater than < less than >= greater or equals <= less than or equals == almost equals (two equals signs) The Ôalmost-equals' operator is a HiSOFT BASIC extension for single or double precision floating point comparisons and it is defined as follows: x==y calculates ABS(x-y) <= ABS(y * 1E-6) Thus == can be used to check for near equality even if a small number of rounding errors have been introduced. For integers and long integers the comparison is the same as equals and for strings the comparison is the same as equals except that lower case letters are treated as equal to their uppercase counterparts. For example: 2.0==2.0 is true 2.0==1.999999 is true 2.0==1.99999 is false A string is considered less than another if its first character that differs is lower in the ASCII set than the corresponding character in the first string. If the strings are the same until one string is exhausted then the shorter string is less. All the following examples are true: Fred"<"Hello" because "F"<"H" "Frederick"<"Hello" because "F"<"H" "fred">"Hello" because "f">"H". The lower case letters come after the upper. "Frederick">"Fred" because "Frederick" is longer The logical shift operators, <<,>> shift their first operand left or right respectively by the number of bits given as their second operand. The shift operations are unsigned and the first operand should be of either integer type - if you use a single or double this will be converted to a long integer. The resulting type is the same as the first operand after any conversion. Note that i<<1 is the same as i*2 except when would occur and that i>>1 is the same as i\2 except when i is negative. For example: 1<<8 &h100 256 (-1)<<8 &hFF00 -256 1<<15 &h8000 -32768 &h1234<<4 &h2340 9024 &h12345678<<4 &h23456780 591751940 &h12345678>>4 &h1234567 305419896 Note that the shift operators are not supported by Microsoft BASIC on the Macintosh or PC. All the logical operators NOT, AND, OR, XOR, EQV and IMP use long integer arithmetic (32-bit) unless both operands are integers in which case integer arithmetic is used. These operators work bitwise, with each bit affected as shown below. X Y NOT AND OR XOR IMP EQV 0 0 1 0 0 0 1 1 0 1 1 0 1 1 1 0 1 0 0 0 1 1 0 0 1 1 0 1 1 0 1 1 Although these operations work on the individual bits they have the same affect as the corresponding logical operators if you use -1 for TRUE and 0 for FALSE. Examples: -1 OR -1 =-1 4 OR 3 = 7 (100 and 011 in binary) -1 XOR 0 =-1 8 AND 4 = 0 (1000 and 100 in binary) These logical operators can be particularly useful when writing routines that interface directly to the operating system as many of the flag parameters are based on bits. Sub-programs and User Defined Functions Sub-programs and user defined functions are one of the most powerful features of many modern BASICs and HiSOFT BASIC takes these ideas even further. The idea of using a sub-program is to isolate part of the code of your program in a way that makes it easy to call and easy to ensure that it is not interfering with variables that are, logically, not to do with the sub-program's code. The simplest definition of a sub-program is something like SUB hello PRINT "hello" END SUB The SUB statement defines the name of the sub-program that we are defining and the END SUB indicates that we have finished. Sub-program definitions may not contain other sub-program definitions. The hello sub-program can be called using call hello or even just hello and will print the word hello. You can call sub-programs before or after their declarations. So far this doesn't give us anything that you can't do with old-style BASIC GOSUB...RETURN statements. However by passing parameters to sub-programs we can make the sub-program work on different variables or values. Variable Parameters Sub-programs may take two different sorts of parameters, value and variable parameters. By default parameters are variable parameters and are passed by reference. This means that if the sub-program changes the value of the variable parameter, its value will be modified globally. For example: SUB TimesTwo(v) v=v*2 END SUB If we call this using INPUT " Enter a number";i CALL TimesTwo(i) PRINT i and enter the number 42, i will be modified and then twice this, 84, will be printed. The shortened form of the CALL statement above is: TimesTwo i Note that the brackets are not used when omitting the CALL keyword. When using variable parameters, if you pass an expression rather than a variable of the required type then any modifications to the parameter are lost. In order for a variable parameter to be modified globally by the sub-program to which it is passed, the type of the passed variable must be the same as the type of the parameter and it must be a simple variable. If we changed the calling code to be: INPUT i# TimesTwo i# PRINT i# then the variable i# would not be modified. You can pass array elements as variable parameters; this causes the subscripting expression to be calculated before the sub-program is called e.g. TimesTwo a(3) would double the value of a(3). However this should be avoided if you are using ERASE and REDIM APPEND/PRESERVE inside sub-programs; see the Advanced Arrays section in this chapter for more information. If you want to call a sub-program that normally would modify the variable, but on this occasion you don't want this to happen, then enclose the variable name in parentheses e.g. TimesTwo (i) This forces the parameter to be passed by value. If you use a CALL statement instead you use the same method e.g. CALL TimesTwo ((i)) Ôpasses i by value If you have more than one parameter for a sub-program they should be separated by commas in both the call and the definition. For example: SUB Multiply(i,j,k) k=i*j END SUB Multiply 2,3,i PRINT i This will print 6. Note that the i that is a parameter and used in the sub-program is an entirely different entity to the i in the main program. Value ParametersParameters may also be called by value, which means that the parameter will not be modified by the sub-program or function. To indicate that a parameter is passed by value precede it in the definition with the keyword BYVAL. So the above example could be coded as: SUB Multiply(BYVAL i, BYVAL j,k) k=i*j END SUB Multiply 2,3,i PRINT i Value parameters are more efficient than variable parameters and are a HiSOFT BASIC extension. In most other BASICs with sub-programs you must use variable parameters and enclose them in parentheses. This works fine unless you forget the brackets, when you can modify your main program variables by mistake. In general make a parameter a BYVAL parameter unless you want to return a value. By default, parameters to sub-programs are passed by reference, as variable parameters. For compatibility with earlier versions of HiSOFT BASIC you can use VAL instead of BYVAL; we do not recommend this for new programs as BYVAL is used by the latest Microsoft BASICs for the PC. STATIC variables In the examples so far we have only used parameters inside sub-programs. However sub-programs may have their own variables. For example SUB Sum(BYVAL n, k) STATIC count,total total=0 FOR count=1 TO n total=total+count NEXT count k=count END SUB Sum 4,result PRINT result will print 10 which is 1+2+3+4. The word STATIC is used to introduce ordinary local variables. You can use commas to separate them. In fact if you omit the STATIC statement, the above will still work because STATIC is assumed by the compiler. However we recommend strongly that you use this statement together with the variable checks flag (V+). This will warn you if you misspell variables in sub-programs. For example if we had typed k=k+cont in the example above, the compiler would complain that the variable cont was not declared. STATIC variables are zeroed when the program starts running but are not modified between different calls to the procedure. In the example above if we called the Sum sub-program again Total would have a value of 10 so we must zero it each time. SHARED variablesYou can also use variables from your main program inside sub-programs by using the SHARED statement. For example we could code the example above as: SUB Sum(BYVAL n) STATIC count,total SHARED k total=0 FOR count=1 TO n total=total+count NEXT count k=count END SUB Sum 4 PRINT k This is however less flexible than the original example because it modifies only one particular variable. Using SHARED variables with variable parameters which should be value parameters can lead to the following difficult-to-spot bug shown below: SUB process(t) SHARED token IF t=3 OR t=4 THEN . . token=5 . . IF t=4 THEN Ô problem . . END IF END IF One would naturally expect t to be 3 or 4 at the point marked problem since t was 3 or 4 in the previous IF statement. However if the sub-program process was called as process token then this would not be the case because the modification of token will also change t. This can be solved by enclosing t in parentheses or, even better, by making the parameter a value parameter. This problem can also occur if the variable token was modified by a sub-program that is called inside process, which is even more difficult to spot. If you have some variables that are imported into many sub-programs and you wish to avoid having SHARED statements each time, you can use the DIM SHARED statement which causes the variable to be SHARED with every sub-program. For example, if you have DIM SHARED debug_flag then you can use debug_flag anywhere in your program. Recursion and Local variablesSub-programs may be called recursively i.e. they may call themselves. SUB Fibonacci(BYVAL n, r) LOCAL temp1,temp2 SELECT CASE n CASE 0: r=0 CASE 1: r=1 CASE REMAINDER: Fibonacci n-1, temp1 Fibonacci n-2, temp2 r=temp1*temp2 END SELECT END SUB FOR i=0 TO 15 Fibonacci i,res PRINT res; NEXT i This prints the first few numbers in the Fibonacci sequence, in which the nth term is the sum of the two previous terms with the sequence starting with 0, 1, ....... This is, in fact not the most efficient way to code this algorithm in HiSOFT BASIC; the algorithm can also be improved very easily. The above example also introduces LOCAL variables. These are like STATIC variables in that they cannot be accessed outside the sub-program. However a new variable is created for each invocation of the sub-program. This becomes important when you have recursive calls. In the example above if there was only one variable temp1 then it would be corrupted during the second recursive call. Try it and see. The memory for use of local scalar numeric variables is allocated on the machine stack. If you make heavy use of recursive calls with large numbers of local variables it is possible to run out of stack. Use the MINSTACK option, see Chapter 4 for details. Strings may also be used as parameters and local variables in exactly the same way as numbers. The only difference is that the actual data in the strings is allocated on the heap and not on the machine stack. User-Defined Functions As well as sub-programs you can also have user-defined functions. In HiSOFT BASIC there are two methods of defining user-defined functions, DEF FN and FUNCTION. The latter is the preferred modern form so we will discuss this first. Using FUNCTION The FUNCTION syntax makes user-defined functions use the same syntax as for sub-programs. User defined functions return results by assigning to a pseudo-variable with the name of the function. For example, here's another coding of the Fibonacci example: FUNCTION Fibonacci(BYVAL n) SELECT CASE n CASE 0: Fibonacci=0 CASE 1: Fibonacci=1 CASE REMAINDER: Fibonacci= Fibonacci(n-1)+Fibonacci(n-2) END SELECT END FUNCTION FOR i=0 TO 15 PRINT Fibonacci (i); NEXT i This gives probably the neatest solution to this classic problem. There is no restriction on the name of the function and the rules for parameters and local variables are the same as for sub-programs. FUNCTIONs must be declared before they are used. The normal way to this is to ensure that the FUNCTION END FUNCTION statements are before any calls of the function. If you wish to use a function before you define it then you can use the DECLARE statement. This specifies the parameters of a function in the same way as a FUNCTION statement but does not actually contain any code. For example, DECLARE FUNCTION Fibonacci(BYVAL n) FOR i=0 TO 15 PRINT Fibonacci (i); NEXT i FUNCTION Fibonacci(BYVAL n) SELECT CASE n CASE 0: Fibonacci=0 CASE 1: Fibonacci=1 CASE REMAINDER: Fibonacci= Fibonacci(n-1)+Fibonacci(n-2) END SELECT END FUNCTION FOR i=0 TO 15 PRINT Fibonacci (i); NEXT i This is our final Fibonacci example (honestly!). A couple of points to note: * The form of the DECLARE statement is exactly the same as the FUNCTION statement with the word DECLARE at the front. * DECLARE statements are needed for two reasons. Firstly, they enable the compiler to check that the correct number and type of parameters have been used. Secondly if DECLARE was not used, the compiler might think that Fibonacci(i) in the example above was referring to an array Fibonacci(). In fact probably the only advantage of the FN syntax is that you can instantly see the difference between a function call and an array access. Of course, you could easily decide to use the similar conventions with FUNCTION definitions. DECLARE statements can also be used for sub-programs. They are not required by HiSOFT BASIC but can be useful for compatibility with other modern BASICs such as QuickBASIC. You can also use them as a documentation aid by having DECLARE statements at the front of your program for all the sub-programs and functions. If you call a function which performs input/output inside another statement that performs input/output strange things may happen. There is no good reason for doing this and it should be avoided. Using the FN notation In the past you could only define a function by using FN as a prefix to the function name; old-style BASICs restricted you even further in that the function could only be one line long. For example, DEF FNrad(x)=x*3.141592653589793/180 which converts an angle in radians to degrees and could be used as follows PRINT SIN(FNrad(45)) to give the sine of 45 degrees. The names of such user-defined functions must start with FN. However, in HiSOFT BASIC, DEF FN functions may have all the facilities of sub-programs with the following differences: User defined functions return results by assigning to a pseudo-variable with the name of the function. For example DEF FNfactorial(n) IF n<=1 THEN FNfactorial=1 ELSE FNfactorial=n*FNfactorial(n-1) END IF END DEF which calculates the famous factorial function. Note that the definition finishes with END DEF and that on the right hand side of the assignment FNfactorial causes the function to be called again recursively. The big difference between user-defined FN functions and sub-programs is that, in functions, parameters are call-by-value by default and to specify call-by-variable you should precede them with VARPTR. If you do not use variable checks, variables are assumed to be SHARED rather than STATIC. Naturally this difference can be confusing and so we recommend using the FUNCTION syntax instead. Here is yet another coding of the Fibonacci example: DEF FNfibonacci( n) SELECT CASE n CASE 0: FNfibonacci=0 CASE 1: FNfibonacci=1 CASE REMAINDER: FNfibonacci= FNfibonacci(n-1)+FNfibonacci(n-2) END SELECT END DEF FOR i=0 TO 15 PRINT FNfibonacci (i); NEXT i Incidentally you can insert a space between the FN and the function name. Arrays and Sub-programs Arrays may be used as parameters to sub-programs and user-defined functions. They are specified both in call statements and definitions with open and close parentheses after their names. The definition should contain the number of dimensions of the array. Arrays are always passed by reference. DIM b(3,6) MatSum b(),res PRINT res SUB MatSum(a(2),x) STATIC i,j,x x=0 F OR i=LBOUND(a,1) TO UBOUND(a,1) FOR j=LBOUND(a,2) TO UBOUND(a,2) x=x+a(i,j) NEXT j NEXT i END SUB This sums all the elements of the two dimensional array. The corresponding function definition would be: DIM b(3,6) PRINT fnMatSum( b()) DEF fnMatSum(a(2)) STATIC i,j,x x=0 FOR i=LBOUND(a,1) TO UBOUND(a,1) FOR j=LBOUND(a,2) TO UBOUND(a,2) x=x+a(i,j) NEXT j NEXT i fnMatSum=x END SUB Sub-programs may share arrays with the main program. The SHARED and DIM SHARED statements may be used as for scalar variables. The DIM SHARED statement when used with arrays also dimensions them. The SHARED statement should specify the number of dimensions of the array although this is not enforced. For example, DIM SHARED table(100) 'table() can now be access anywhere in the program. or alternatively DIM table(10) table(10)=42 : Silly SUB Silly SHARED table(1) PRINT table(10) END SUB This will print 42. Local Arrays Arrays may also be local to a sub-program and both STATIC and LOCAL varieties are supported. When using STATIC you need to make sure that the array is not dimensioned more than once. In the STATIC statement the number of dimensions may be included in parentheses. For example, Ôconstants for Table Handler operations CONST init=0, insert=1, find =2, replace=3 TableHandler init,0,0 Ôinitialise the table SUB TableHandler(operation, index, value) STATIC table(1), first_free SELECT CASE operation CASE init DIM table(100) first_free=0 CASE insert . . In this example the array will only be dimensioned once, as long as the TableHandler sub-program is not called with a parameter of init more than once. Using the LOCAL statement arrays may be created for the duration of this call to the sub-program. They are erased automatically at the end of the call. The actual dimensions are given in the LOCAL statement. For example: SUB Recursive 'a temporary array with elements up to temp(40). LOCAL temp(40) . . END SUB Advanced Arrays As well as the DIM, SHARED, STATIC and LOCAL statements described above there are a number of other array facilities that are not available in primitive BASICs. The UBOUND and LBOUND functions return the size of arrays. See the example MatSum previously. The lower-bound of arrays created by the DIM statement can be changed from the default value of 0 to 1 by the OPTION BASE statement, for example: OPTION BASE 1 Ôarrays now start at one. OPTION BASE is an executable statement, and so its effect depends on the order of execution in the program, not the order of the program text. It is thus possible for an array to have different dimensions if it is ERASEd and then REDIMmed. Using OPTION BASE normally only saves a considerable amount of memory if you are using 3 or more dimensions in an array. When array checks are switched off OPTION BASE statements are ignored. Dynamic and static arrays Normally arrays that are DIMensioned using a constant value, for example DIM A(100) are treated as static arrays. Static arrays have a constant size specified by the DIM statement and may not be re-dimensioned or erased which means the compiler can generate code that will access them very quickly. If you have an array that you will subsequently want to re-dimension or erase then you'll need to use REM $dynamic which causes the compiler to make all DIMension statements declare dynamic arrays from this point in your program. For example: REM $dynamic CONST arrsize=200 DIM arr(arrsize) ... REDIM arrsize(300) You can revert to the normal behaviour by using REM $static. Dynamic arrays can grow and shrink in size and therefore HiSOFT BASIC has to generate more code to handle this possible behaviour - if you want arrays to be accessed as quickly as possible using the minimum amount of code, use static arrays, which is the default. Static arrays are stored in the program's global space rather than on the heap. Note that you must have used REM $DYNAMIC before you DIM the array for the first time; you cannot change the type of an array after it is declared. Other array features ERASE works in two different ways, depending on the type of the array (dynamic or static). For static arrays, ERASE simply clears all the elements to 0 or to the null string. For dynamic arrays, ERASE may be used to free the space used by the array when it is no longer required. This is particularly useful if you have temporary results stored in an array. Once a dynamic array has been ERASEd you can DIM it again. For example, REM $dynamic DIM temp(10000) Ô 10000 temporary results . . ERASE temp Ô note that temp is not followed by parentheses. This ' anomaly is present for compatibility with other BASICs The REDIM statement for dynamic arrays gives the equivalent of an ERASE followed by a DIM in one statement. Thus REDIM temp(100) is equivalent to ERASE temp: DIM temp(100) REDIM cannot be used for static arrays. HiSOFT BASIC has a powerful extension to let you change the size of dynamic arrays whilst retaining their data called REDIM PRESERVE. For example: REM $dynamic SUB AddElement(value) SHARED table(1), maxentries, nextentry IF nextentry> maxentries THEN Ô no room for this entry maxentries=maxentries+100 REDIM PRESERVE table(maxentries) Ô the above makes the array 100 elements larger END IF table(nextentry)=value Ôenter the value nextentry=nextentry+1 Ôready to store the next one END SUB This example shows how you can avoid fixed limits on the sizes of dynamic arrays. If you run out of room just make it bigger. REDIM PRESERVE requires enough memory to make a copy of the array. You can also use REDIM PRESERVE to make dynamic arrays smaller; again a copy of the array is made. Normally the ERASE, REDIM and REDIM PRESERVE statements cause dynamic arrays to be moved in memory. As a result, if there are any pending array elements that have been used in variable parameters, then these will be become invalid. The best way to avoid this is by not passing array elements by reference. For example the following may not work as intended: REM $dynamic DIM x(50), a(30) . . Subprog a(3) Ô note variable parameter. . SUB Subprog(b) Ô note variable parameter ERASE x Ô a() will now become corrupt, it has Ô been moved because it was declared Ô after x() which has been erased . . END SUB Unlike many BASIC compilers, HiSOFT BASIC will let you change the number of dimensions of dynamic arrays with a REDIM statement. This may prove useful when porting certain programs that were developed with interpreters, however we recommend strongly that you avoid this as it can make programs almost un-maintainable. For compatibility with earlier versions of HiSOFT BASIC, you can use REDIM APPEND instead of REDIM PRESERVE. However we recommend the use of the latter as it is the form used by Microsoft BASIC 7.1 on the PC. Limitations Imposed by the Compiler We have tried to avoid placing limits on the programs you can write. For example, most compilers have a limit on the number of characters that are significant in an identifier; HiSOFT BASIC does not impose any limit on this so that A_very_long_identifier_indeed_which_goes_on_and_on is different from A_very_long_identifier_indeed_which_goes_on_and_on_and_is_different This sort of limitation may not seem important to you, but such possible restrictions have the annoying habit of appearing when you think you have nearly finished a large program. This section intends to list the remaining limitations other than the total workspace of the compiler. If you find these restrictive please tell us. A program may not have more than 16383 lines. If you hit this limit you can probably get round it by having more than one statement per line. We have a 5000 line program which is about 135K bytes of source. If you exceed this you will be given a Too many lines in program compiler error. The total number of active labels in the code generation phase of the compiler, as specified by the Label Table option (LABEL) may not exceed 5641. A label is generated for each line number or label that is referenced (not for those that are un-used) together with 2-3 or each sub-program, 2 for each CASE in SELECT statements plus 2 for the SELECT itself and 2 for most structure statements. For example our 5000 line program requires about 1100 such labels. There is a limit of approximately 8000 on the number of sub-programs, shared and local variables and parameters in the entire program; if you exceed this you will be given a Structure Table Full error message. The total number of different names in your program may not exceed 14000. The total code of a SELECT statement may not exceed 32k bytes. To avoid this make some of the alternatives into sub-programs. The total size of some FOR...NEXT loops may not exceed 32k bytes. To avoid this make some or all of the loop into a sub-program. Sub-programs and user defined functions may not have more than 128 parameters. The total space for global and STATIC local variables and the descriptor table may not exceed approximately 29K bytes. The amount of storage, in bytes, in this area required for the different types is: 2 integers 4 long integers, single precision numbers 8 strings, double-precision numbers, all arrays The data in strings and arrays is not stored in this area. Static arrays are stored at the end of this static array but they are not subject to this 29K restriction. Local variable stack space may not be more than 32k bytes per invocation. The different types require the number of bytes given in the table above. There are limits of 255 channels, 14 user libraries, 4 screens opened via the SCREEN statement and 12 windows opened via the WINDOW statement. ON...GOTO and ON...GOSUB statements may not have more than 8190 line numbers each (!)