LET BASIC
From iPodLinux
LET BASIC is jonrelay's BASIC interpreter.
The iPodLinux version of LET BASIC will be hooked up to podzilla2 as the default handler for files of type '.bas'. BASIC programs will be able to be launched in a Terminal window as a text-based application or directly inside podzilla itself for graphical applications.
Contents |
Downloads
Also see the forum release announcement.
LetBasic-src.tgz - LET BASIC 1.0b source code
LetBasic-ipl.tgz - LET BASIC 1.0b binary for iPodLinux
LetBasic-osxppc.tgz - LET BASIC 1.0b binary for Mac OS X PowerPC
LetBasic-bas.tgz - LET BASIC 1.0b test programs
Basic-11pr-osxppc.tgz - LET BASIC 1.1 prerelease binary for Mac OS X PowerPC
All downloads are currently command line applications. There is nothing graphical for podzilla2 yet. The iPodLinux version incorporates iPodRead.c so you can input text as in sash and minix-sh.
If you have the BASIC interpreter installed in /bin, you can execute all the test programs directly from the shell, since they all start with this line:
#!/bin/basic
:D
Features
- Supports both primitive (GOTO, GOSUB, ON, IF, FOR, ONERR, DEF FN) and structured (IF THEN ELSE, SELECT CASE, FOR, FOREACH, WHILE, DO LOOP WHILE, DO LOOP UNTIL, TRY CATCH, SUB, FUNCTION) program control
- Supports both line numbers and labels, or any mix of the two
- Unlimited identifier (variable name, label, etc.) length
- Unlimited string length
- Unlimited array size
- Unlimited line number size
- Extensive set of built-in math, string, terminal control, and file I/O functions
- User-defined commands and functions
- C-style dynamic memory allocation
- Interface to machine language routines via C calling conventions
- Object-oriented extensions
- Complete access to TTK and PZ2 APIs (in progress)
Language
Basic Syntax
A LET BASIC program is composed of several statements. Statements may be separated with a new line or a colon (:). Statements beginning with an apostrophe ('), a slash (/), a pound symbol thingie (#), or the keyword REM are considered comments, and the rest of the line following those characters is ignored (including any colons or semicolons).
Keywords, variable names, command names, labels, etc. (identifiers in general) are case-insensitive, so 'X' is the same as 'x' and 'for', 'FOR', 'For', 'fOr' are all the same.
Program Control
You can use classical GOTO and GOSUB, although this is regarded as a very bad thing to do:
LABEL top PRINT "Hello" GOTO top
LET A$ = "Hello" GOSUB printit LET A$ = "World" GOSUB printit END REM LABEL printit PRINT A$ RETURN
LET BASIC can use either line numbers or labels. A label is indicated by the keyword LABEL and is case-insensitive.
Conditionals can be one-liners or whole structures:
IF x=1 THEN PRINT x+" kangaroo" ELSE PRINT x+" kangaroos"
IF someVar = 426 THEN GOTO foo
IF x=0 THEN PRINT "Hello" ELSE IF x=1 THEN PRINT "Good Morning" ELSEIF x=2 THEN PRINT "Good Afternoon" ENDIF
ELSE IF and ELSEIF are equivalent, as are END IF and ENDIF. Variable names are case-insensitive.
You can do the original primitive switch with ON:
ON x GOTO foo, bar, baz, 40 ON y GOSUB chelsea, anya, sara
For the first command, LET BASIC gotos to the label 'foo' if x is 1, the label 'bar' if x is 2, the label 'baz' if x is 3, or the line numbered 40 if x is 4. For the second command, LET BASIC gosubs the label 'chelsea' is y is 1, blah blah blah. You can also do the better kind of switch:
SELECT x CASE 1: GOTO foo CASE 2: GOTO bar CASE 3: GOTO baz CASE 4: GOTO 40 CASE 5: PRINT "Oh no!" DEFAULT: PRINT "wtf??" END SELECT
This is equivalent to the ON GOTO above, except that it prints "Oh no!" if x is 5 and 'wtf??' if x is anything else. SWITCH is a synonym for SELECT. SELECT works on anything, including strings and objects.
There are four kinds of loops. This is a FOR loop:
FOR X=1 TO 10 PRINT x NEXT X
FOR x=2 TO 10 STEP 2 PRINT X NEXT
FOR x=3 TO 10 STEP 3 PRint x end for
As mentioned before, indentifiers are case-insensitive. A FOR loop may end with NEXT or END FOR. LET BASIC will ignore anything following the NEXT keyword.
LET BASIC also supports the while and do loops:
WHILE x<10 PRINT x x = x+1 WEND
DO PRINT x x=x+1 LOOP WHILE x<10
DO PRINT x x=x+1 LOOP UNTIL x=10
WHILE and DO are very similar; the only difference is that a DO loop will execute at least once. A WHILE loop may end with WEND or END WHILE. A DO loop may only end with LOOP; there is no END DO. There is no UNTIL loop either; only DO LOOP UNTIL. You can leave the condition off a DO loop entirely to get the fastest infinite loop:
DO PRINT "Hello" LOOP
The last kind of loop needs some setup first. You need to have an array of some kind.
DIM nums[10] as integer LET nums[0]=5, nums[1]=1, nums[2]=3, nums[3]=7, nums[4]=12, nums[5]=15, nums[6]=19, nums[7]=2, nums[8]=10, nums[9]=8 FOREACH r IN nums PRINT r NEXT
This will give you this as output:
5 1 3 7 12 15 19 2 10 8
A FOREACH loop may end with NEXT or END FOREACH.
Any of these loops may be broken out of with the BREAK keyword. The following will print "Hello" once:
WHILE 1 PRINT "Hello" BREAK END WHILE
You can chain BASIC programs together using the four commands RUN, SUBRUN, CHAIN, and INCLUDE.
INCLUDE "mymath.bas" RUN "a.bas" SUBRUN "b.bas" LET filename$ = "c.bas" CHAIN filename$
The difference between the various RUN commands is related to your program's variable space. A program executed with RUN has no access to your variables. A program executed with SUBRUN can read and set your variables, but not delete them or add new ones. A program executed with CHAIN has full access to your variables, and any variables it adds will be added to yours. A program executed with INCLUDE has full access to the global environment: any variables it sets will be available to you and any programs you call. INCLUDE will typically only be used with code libraries that declare user-defined subroutines and functions.
There are two ways to handle errors: the primitive way and the structured way. The primitive way is just to tell LET BASIC what to do when an error occurs in your program:
ONERR <statement>
The statement can be anything, though it's more useful to make it a GOTO or GOSUB.
An even better way to handle errors is a TRY structure:
TRY INCLUDE "myfile.bas" LET x = myfunction(4) END TRY PRINT "Done."
If the INCLUDE command fails, the interpreter will skip over the LET line and continue the program after the END TRY.
TRY INCLUDE "myfile.bas" LET x = myfunction(4) CATCH e PRINT "Oops!" PRINT errstr$(e) END TRY PRINT "Done."
If the INCLUDE command fails, the interpreter will skip over the LET line and execute the stuff under CATCH. If both INCLUDE and LET execute successfully, the interpreter will skip over the stuff under CATCH and continue the program after the END TRY. The identifier after the CATCH keyword is a variable of type exception (basically a specialized integer) containing the kind of error that occurred. The ERRSTR$ (or ERRSTR, they're equivalent) function will give you the error message that would otherwise be shown.
For a neat table of error messages and their corresponding values as integers, try this:
for x=1 to 48
try
throw x
catch e
print x,errstr$(e)
end try
next
Variable Declaration
Arrays and optionally scalar variables are allocated with the DIM command:
DIM a[10] AS integer DIM x AS integer DIM b$ as Integer DIM a$ : REM a$ becomes a string DIM y : REM y becomes a float DIM q[2,3,4] as string
The DIM command accepts the following types: int, integer, float, double, string, object, exception. There is no difference between int and integer, nor between float and double. It will support more types in the future when object-oriented stuff is implemented. Variable types are case-insensitive. If a type is not specified, it is determined by the last character of the identifier name: a dollar sign ($) will make it a string, a pound sign (#) will make it an integer, an at sign (@) will make it an object, and any other character will make it a float. The DIM command can be used to create arrays as in the above example, or it can be used to create simple variables (although this is optional). Arrays can be up to three dimensions.
Starting with version 1.1, you can change an array's dimensions with REDIM without destroying existing data:
REDIM a[5] REDIM q[3,3,3]
In the above example, a[5] through a[9] and q[x,x,3] would be deleted, and q[2,x,x] would be added as blank strings.
Variables can be changed with LET:
LET y=4 LET a$ = "Hello" LET c = SQRT(a^2 + b^2) LET a[0] = 1, a[1] = 26, a[2] = 52
If the variable exists and the value being assigned is of an incompatible type, an error will occur. If the variable does not exist, it is created with the type and value being assigned.
If you use CONST instead of LET, the value of the variable cannot be changed later:
CONST pi=3.1415926535 PRINT pi LET pi = 3.14 : REM throws an error
You can delete a variable with DEL:
DEL y DEL a[] DEL a$, c
The variables y, a$, and c and the array a will no longer be available.
You can delete all variables with CLEAR:
CLEAR
The SWAP command will swap the values of two variables, regardless of type:
SWAP y, A$
User-Defined Commands and Functions
You can define your own commands with SUB:
SUB printHello() PRINT "Hello" END SUB SUB printIt(s as string) PRINT s END SUB
All these will now do the same thing:
print "Hello" printHello printIt "Hello"
You can define your own functions with FUNCTION:
FUNCTION randomDigit() AS integer RETURN floor(rnd(10)) END FUNCTION FUNCTION recip(n) AS float RETURN 1.0/n END FUNCTION
PRINT randomDigit() will print a random digit from 0 to 9, and PRINT recip(2) will print 0.5 as expected.
User Interaction
Output text with PRINT. Parameters followed by a comma will be followed by a tab when printed out, parameters followed by a semicolon will not be followed by anything. If the PRINT statement ends with a comma, a tab will be printed at the end; if the PRINT statement ends with a semicolon, nothing will be printed at the end; otherwise, a newline will be printed at the end.
PRINT "Hello, my name is Rebecca. But you can call me Becky." : rem puts newline at end PRINT "Numbers: "; : rem does not put newline at end PRINT a,b,c,d, : rem puts tab at end, leaving room for more tab-delimited values
INPUT will get a line of input from the user and put it in a variable. If the variable is not declared it will be assumed to be a string. An INPUT command can optionally have a prompt.
INPUT a$ : REM no prompt INPUT "Please enter your name: ", name$ : REM with a prompt INPUT "Name"; name$ : REM with a prompt with a question mark (Name? will be printed) - version 1.1 or later
GET will get just one character of input from the user:
GET a$ : REM only gets one character
If you don't want the character entered to be printed to the screen, you can turn off local echo:
ECHO OFF
Make sure to turn it back on afterward:
ECHO ON
(GET and ECHO are not possible in 100% pure portable C, so LET BASIC uses its own set of functions in the file stdiox.c to do this; this is one of the few things that is platform-specific.)
You can clear the screen with:
HOME
or
CLS
You can change the position of the cursor with these:
HTAB 8 : # moves to the eighth column of text VTAB 20 : # moves to the 20th row GOTOXY 10,5 : # moves to the tenth column of the fifth row
You can get the position of the cursor:
X = HPOS() : REM or POS() Y = VPOS()
(This also depends on platform-specific code in stdiox.c.)
You can also do some special effects:
NORMAL BOLD UNDERLINE FLASH INVERSE LOWINTENSITY INVISIBLE FGCOLOR <number> : REM 0=black, 1=red, 2=green, 3=yellow BGCOLOR <number> : REM 4=blue, 5=magenta, 6=cyan, 7=white
This depends on whether your terminal supports these attributes (the podzilla Terminal only supports bold, underline, and inverse).
Inline Data
LET BASIC supports the archaic method of storing data inside a program itself. You can put the data in lines that look like this:
DATA 1,2,3,4,7,2,3.14,9.87,3.01 DATA "+","-","*","/","\","%" DATA Alpha, Bravo, Charlie, Delta DATA a+1, i+2, 15, a, "Hi"
You can have any expression in a DATA statement, but if the expression is a lone identifier it is evaluated to a string containing that identifier. The last two DATA lines above will evaluate to "Alpha", "Bravo", "Charlie", "Delta", a+1, i+2, 15, "a", and "Hi" when read.
To read these data, use the READ command:
READ a$ READ i,j,k
To start over at the first DATA statement, use the RESTORE command:
RESTORE
Objects
That's right! I have taken the world's worst programming language and combined it with the world's worst programming paradigm!
To declare a class, use a construct like this:
CLASS fraction
dim num as integer
dim denom as integer
SUB setNumerator(i as integer)
num = i
END SUB
SUB setDenominator(i as integer)
denom = i
END SUB
FUNCTION getNumerator() as integer
return num
END FUNCTION
FUNCTION getDenominator() as integer
return denom
END FUNCTION
FUNCTION getValue() as float
dim v as float
let v=num
let v=v/denom
return v
END FUNCTION
END CLASS
Now, you can use this class as a type and create instances of it:
DIM f as fraction f = new(fraction) : REM must create a new object or a null object error occurs f.setNumerator 1 f.setDenominator 2 PRINT f.getValue() : REM prints 0.5 DIM g as fraction g = f g.setDenominator 4 PRINT g.getValue() : REM prints 0.25 PRINT f.getValue() : REM prints 0.25; g refers to the same object as f g = clone(f) g.setDenominator 8 PRINT g.getValue() : REM prints 0.125 PRINT f.getValue() : REM prints 0.25; g refers to a clone of f
Starting with version 1.1, objects can have constructors and destructors:
CLASS printyThing
print "PrintyThing class loaded."
SUB created()
print "PrintyThing created."
END SUB
SUB cloned()
print "PrintyThing cloned."
END SUB
SUB destroyed()
print "PrintyThing destroyed."
END SUB
END CLASS
dim x as printyThing
dim y as printyThing
x = new(printyThing)
y = clone(x)
del x
print "End."
The above program will have the following output:
PrintyThing class loaded. PrintyThing created. PrintyThing cloned. PrintyThing destroyed. End. PrintyThing destroyed.
First x is destroyed by the DEL command, then y is automatically destroyed when the program terminates.
Memory
You can get a block of memory using malloc or calloc:
LET X=malloc(64) LET Y=calloc(128)
X will contain the address of a block of 64 bytes, uninitialized. Y will contain the address of a block of 128 bytes, all cleared to zero. If the address returned is zero, that means the system did not have enough memory to allocate that many bytes.
Now that you've got the address of something, you can peek and poke to it:
POKE X,72 POKE X+1,101 POKE X+2,108 POKE X+3,108 POKE X+4,111 POKE X+5,0 : REM X now points to the string "Hello" POKE Y,255 : REM poke without a size parameter assumes it's a byte POKE Y+1,255,1 : REM poke a byte POKE Y+2,65535,2 : REM poke a short word POKE Y+4,999999,4 : REM poke a long word POKEF Y+8,3.14159 : REM pokef without a size parameter assumes it's a single-precision float POKEF Y+12,3.14159,4 : REM poke a single-precision float POKEF Y+16,3.1415926535,8 : REM poke a double-precision float POKES Y+24,"Hello" : REM faster way to do what we did with X POKES Y+32,"Hello",3 : REM cuts off string at 3 characters PRINT CHR$(PEEK(X)) : REM prints 'H' PRINT PEEKS(X) : REM prints "Hello" PRINT PEEK(Y) : REM prints 255 PRINT PEEK(Y+1,1) : REM prints 255 PRINT PEEK(Y+2,2) : REM prints -1 PRINT PEEK(Y+4,4) : REM prints 999999 PRINT PEEKF(Y+8) : REM prints 3.14159 PRINT PEEKF(Y+12,4) : REM prints 3.14159 PRINT PEEKF(Y+16,8) : REM prints 3.14159265 PRINT PEEKS(Y+24) : REM prints "Hello" PRINT PEEKS(Y+32) : REM prints "Hel" PRINT PEEKS(Y+24,2) : REM prints "He"
You can copy one whole block of memory to another:
POKEM X,Y,64 : REM moves 64 bytes from Y to X
When you're done with that block of memory, make sure to free it:
FREE X FREE Y
You can also peek and poke at whatever other memory addresses you like, as long as you know what you're doing. If you don't know what you're doing, you'll make it crash.
The WAIT command will wait until a memory address reaches a certain state. This is only useful when you're doing device I/O.
WAIT ADDR,A,X,L
ADDR is an address. X is a value to XOR with. A is a value to AND with. WAIT will halt the program until (((*ADDR)^X)&A) is nonzero. L is 1 for a byte, 2 for a short, 4 for a long. L can be left off, in which case it is assumed to be 1. X can be left off, in which case it is assumed to be 0.
The CALL command calls a machine language routine. Since LET BASIC is written in C, it uses C calling conventions. CALL calls it as a function taking no parameters returning void. You can pass up to 3 numeric parameters to the routine, in which case it is called as a function taking that number of ints returning void. Do not use this, it's useless.
The USR function also calls a machine language routine in the same manner as CALL, but as a function returning int. Do not use this either.
File I/O
To open a file, use something like this:
OPEN "O",#1,"stuff.txt"
The first parameter is "O" or "W" for output, "I" or "R" for input, "A" for appending. Any of these may be followed by "+" (i.e. "O+", "I+") for read/write access and/or by "B" for binary files. The second parameter is an integer variable to hold a file reference; any variable may be used, but it is recommended that it start with #. The third parameter is a file name.
To read or write to the file:
WRITE #1,"Hello" WRITE #1,1,2,3,4 WRITE #1,1;2;3;4 READ #1, a, b, c READ #1, a$
WRITE uses the same syntax as PRINT, except the first parameter is a file reference. READ uses the same syntax as the READ for inline data. Any READ starting with # is assumed to be file I/O, otherwise it is assumed to be inline data; this is why it is recommended that variables used for file references start with #.
You can write the binary representations of integers, floats, and strings:
WRITEB #1,0,1,2,3 WRITEB #1,3.14159 WRITEB #1,"Hello" READB #1,a#,b#,c#,d# READB #1,f READB #1,s$
If the variables being read into are undeclared, they are created and their types are determined by the ending character of their name.
You can also read and write to memory allocated with MALLOC:
WRITEM #1,X,64 : REM writes 64 bytes from X to file #1 READM #1,Y,128 : REM reads 128 bytes from file #1 into Y
To flush the buffer:
FLUSH #1
You can change the current position in the file:
LET p = TELL(#1) : REM p contains our position LET q = EOF(#1) : REM q is true if we're at the end of file SEEK #1,0 : REM beginning of the file REWIND #1 : REM does the same thing as the above line SEEK #1,12 : REM moves somewhere else
To close the file when you're all done:
CLOSE #1
You can also manipulate the file system itself:
CREATE "stuff.txt" : REM creates a blank file called "stuff.txt" DELETE "stuff.txt" : REM deletes that file RENAME "myfile.bas", "Donkey2000.bas" MKDIR "mydir" : REM creates a new directory RMDIR "mydir" : REM removes the directory just created CHDIR "/Users/jonrelay/Desktop" : REM the current directory is now my desktop LSDIR : REM prints the contents of the current directory LET A$ = DIR() : REM A$ now contains the path of the current directory LET L$ = LSDIR() : REM L$ now contains a list of all the files in the current directory CHMOD "podzilla",493 : REM does some Unixy stuff (493 is decimal for octal 755) CHOWN "whatever",501,1 : REM more Unixy stuff LINK "podzilla2","podzilla" : REM create hard link from podzilla to podzilla2 SYMLINK "podzilla2","podzilla" : REM create symbolic link from podzilla to podzilla2 SYSTEM "ls -l" : REM execute a shell command
Functions
LET BASIC currently supports the following functions:
IF, IF$, ? UBOUND, NEW, CLONE CEIL, FLOOR, ROUND, INT, TRUNC ABS, SGN, SQR, SQRT, CBRT LN, LOG, LOG10, EXP SIN, COS, TAN, COT, SEC, CSC ASN, ASIN, ACS, ACOS, ATN, ATAN, ATAN2, ACOT, ASEC, ACSC SINH, COSH, TANH, COTH, SECH, CSCH ASINH, ACOSH, ATANH, ACOTH, ASECH, ACSCH MAX, MIN, SUM, AVG RND, GAMMA, PICK, CHOOSE, GCD, LCM SPC, TAB, POS, HPOS, VPOS, ROWS, COLS LEN TRIM, TRIM$, LTRIM, LTRIM$, RTRIM, RTRIM$ LEFT, LEFT$, MID, MID$, RIGHT, RIGHT$ INSTR, RINSTR UCASE, UCASE$, LCASE, LCASE$, TCASE, TCASE$ VAL, EVAL, EVAL$, STR, STR$ ASC, CHR, CHR$ UNI, UCHR, UCHR$ HEX, HEX$, OCT, OCT$, BIN, BIN$ COUNTFIELDS, NTHFIELD, NTHFIELD$ REPLACE, REPLACE$, REPLACEALL, REPLACEALL$ EOF, TELL, DIR, DIR$, LSDIR, LSDIR$ PEEK, PEEKF, PEEKS, MALLOC, CALLOC, USR TIME, TIME$, GMTIME, GMTIME$, TICKS ERRSTR, ERRSTR$ ABOUT, ABOUT$
Version 1.1 adds the following:
INCLUDEPATH, INCLUDEPATH$ COMMANDS, COMMANDS$, FUNCTIONS, FUNCTIONS$
There is no difference between the functions with dollar signs and the corresponding functions without dollar signs. There is also no difference between shortened versions of function names, i.e. SQR and SQRT, ATN and ATAN, etc. LN and LOG are both base e.
UBOUND(a) will return the highest valid index for the first dimension. UBOUND(a,0) will return the number of dimensions. UBOUND(a,1) will return the highest valid index for the first dimension. UBOUND(a,2) will return the highest valid index for the second dimension. UBOUND(a,3) will return the highest valid index for the third dimension.
VAL will only convert strings to numbers, EVAL will evaluate whole expressions; e.g., VAL("2*3*4") = 2 but EVAL("2*3*4") = 24.
The MAX, MIN, SUM, and AVG functions take any number of parameters.
The RND function with no arguments returns a random number from 0 to 1, with one argument A returns a random number from 0 to A, and with two arguments A and B returns a random number from A to B. The random number generator can be seeded with the command RANDOMIZE.
RANDOMIZE 1337 RANDOMIZE TIME(0) : REM this line is the best way to guarantee a different random sequence every time
UNI and UCHR act similarly to ASC and CHR, except they work with UTF-8 encoded text. UCHR(406) will return the UTF-8 encoded string consisting of Unicode character 406. UNI will take a UTF-8 encoded string and return the code point of the first Unicode character. If the beginning of the string is an invalid UTF-8 sequence, UNI will return -1. UNI doesn't check for overlong sequences.
ABOUT takes in an integer as its parameter and returns some information about the interpreter. It's also a convenient place to stick easter eggs.
IF and ? are the same function, and deserve some special attention.
IF(a,b,c) ?(a,b,c,d,e)
In the first case, if a is true, the function evaluates to b. Otherwise, it evaluates to c. In the second case, if a is true, the function evaluates to b. Otherwise, if c is true, the function evaluates to d. Otherwise, it evaluates to e. This function may be called as either IF or ? and can take any number of parameters. If an odd number of parameters is given, the last parameter is the result to be returned if none of the odd-numbered parameters (starting from 1) are true. If an even number of parameters is given, and none of the odd-numbered parameters are true, a syntax error occurs.
iPod-Specific Commands
Version 1.1 adds some commands and functions specific to iPodLinux. These are only supported by the iPodLinux build and not any platform-native builds.
CONTRAST 120
will set the contrast to 120.
PRINT CONTRAST()
will print the current contrast setting.
BACKLIGHT "ON" BACKLIGHT 1
will both turn the backlight on.
BACKLIGHT "OFF" BACKLIGHT 0
will both turn the backlight off.
BACKLIGHT
by itself will toggle it.
PRINT BACKLIGHT()
will print 1 if it's on and 0 if it's off.
The command FONT and the function FONTS only work in the podzilla Terminal, and are used to change the font.
PRINT FONTS()
prints the number of fonts.
PRINT FONTS(5)
prints the name of fifth font (fonts are numbered from zero).
PRINT FONTS("Sabine Doscbthm Smallcaps")
prints the font number associated with Sabine Doscbthm Smallcaps.
PRINT FONTS(6,12)
prints the font number associated with Fixed 6x12.
The FONT command actually changes the displayed font.
FONT 5 FONT "Sabine Doscbthm Smallcaps" FONT 6,12
Podzilla2/TTK Commands
If a BASIC program is being executed in the graphical podzilla environment, rather than from the podzilla Terminal or the shell outside of podzilla, normal ways of getting input and producing output will not work. Instead, you get to use the pz2 and TTK APIs! Oh joy!
You can get dirty working with the API directly through commands and functions with names similar to those in the API documentation, but I went and made it a lot nicer with a bunch of object-oriented stuff. The direct commands and functions will not be documented; only the object-oriented stuff will be.
To start off, you need this little line at the top of your program:
include "pz.bi"
This section will be completed later.
Operators
LET BASIC supports the following operators, in decreasing precedence:
[] grouping or array reference () grouping or function call ^ ** exponentiation ! factorial (postfix unary operator) & address of (prefix unary operator) * dereference (prefix unary operator) + does nothing (prefix unary operator) - negation (prefix unary operator) ~ bitwise not (prefix unary operator) NOT ! logical not (prefix unary operator) ISA test to see if a variable ISA certain type; same types as DIM MOD % modulus DIV \ integer division / division (integer if both arguments are integers, floating-point otherwise) * multiplication - subtraction + addition (if both arguments are numeric) or string concatenation (if at least one is a string) << shift left (integers only) >> shift right (integers only) < <= > >= relational operators = == equal != <> not equal & bitwise and (integers only) ^^ bitwise xor (integers only) | bitwise or (integers only) AND && logical and XOR logical xor OR || logical or
+ is used to add numbers and to concatenate strings. * is used to multiply numbers, but can also be used on strings:
print 3 * 4 : REM prints 12 print "Hello"*3 : REM prints "HelloHelloHello" print 20 * "*" : REM prints 20 stars print "Hi"*"Lo" : REM throws an error
For boolean operations, 0, an empty string, or a null object are considered false, and any other values are considered true.
The & and * unary operators can be used to do some evil stuff. The & operator will return the memory address of the structure used to hold the value of a variable. The * operator takes an address and treats that the address of a variable structure (if it's not actually the address of a variable structure, LET BASIC will most likely crash). You can really mess with this stuff with POKE and screw things up. The variable structure looks like:
typedef struct b_varl {
b_type type; /* type */
struct b_scope * otyp; /* object type, aka class */
int dyna; /* dynamically allocated */
int readOnly; /* constant */
double fval; /* value as a floating point */
signed long int ival; /* value as an integer */
char * sval; /* value as a string (pointer to char) */
b_object * oval; /* value as an object (pointer to b_object) */
b_exception eval; /* value as an exception */
} b_varl;
Type is an int describing which type the variable is (integer, float, string, object, or exception). Dyna is 0 if the variable is actually a variable or an element of an array, or 1 if it is the result of evaluating an expression. ReadOnly is 1 if the variable was declared with CONST. More about this later.