LoginLogin
Might make SBS readonly: thread

SmileBASIC 4 language overview

Root / Submissions / [.]

niconiiCreated:
This guide gives an overview of SmileBASIC's language design. When I started writing this guide, I set out to write something that would help people with previous programming experience get up to speed with SmileBASIC 4 as a language quickly. At the same time, I tried to put in enough details that someone just starting out would have a chance at following along. In the end, I believe I've ended up with something that will please neither group! Unfortunately, I'm out of time, and SmileBASIC 4 is already out worldwide, so this is the best you're gonna get for now. It's at least a step up from reading random parts of the reference. Because this guide is focused on language design and syntax, it leaves out all of the fun stuff. No controllers, no graphics, and no audio. All this guide covers is text input and output. Sorry about that. Also, even for the subjects I do cover, I won't be covering every single minute detail about them. This is an overview, not a reference. For the rest, you'll want to check out other sources. There's the official reference, of course, and my guide on the differences between SB3 and SB4 delves into all the cool new features, although it's written with users of the 3DS version of SmileBASIC in mind, and may be a bit outdated in spots. If you've got any questions, comments, or corrections, feel free to post them below. Finally, I just want to say to anyone reading this who has never programmed before: don't be discouraged if you can't understand this guide! This guide just isn't designed very well for beginners, and better guides will show up before too long. In the meantime, there's a lot of games that other SB4 users have made, so go check them out!

Comments

Anything written after ' is a line comment.
'SmileBASIC ignores all comments, so use them as notes to yourself
'and other people to make your code easier to read and understand!
There's another way to write comments called REM.
REM This is also a comment!
REM exists because older kinds of BASIC used it instead of ', but nowadays you should just use '. This guide will use comments beginning with '=> to show what's printed to the screen. For example, PRINT 100 prints 100 to the screen, so this guide will show that like this:
PRINT 100       '=> 100
If it's obvious what will be printed or otherwise isn't relevant, the output may not be shown, however.

Printing to the screen

The PRINT command lets you display text on the screen.
PRINT "Hello, world!" '=> Hello, world!
The quotes around "Hello, world!" mark it as a type of value called a string. String is just a programming term that means a text value. You can also print numbers!
PRINT 123       '=> 123
PRINT 5+5       '=> 10
To print a blank line, you can just use PRINT without any value.
PRINT
You can print multiple values on one line by putting , between each value. This puts gaps between them, so if you don't want that, you can use ; instead.
PRINT 1,2,3     '=> 1   2   3
PRINT 1;2;3     '=> 123
Code is executed in order from top to bottom, so this code prints the line Hello, then the line World.
PRINT "Hello"
PRINT "World"
You can also put multiple commands on a single line by using :.
PRINT "Hello":PRINT "World"
PRINT automatically advances to the next line on the screen after printing, which is why Hello and World are printed on two separate lines. However, sometimes you don't want it to do that. You can stop it from advancing to the next line by putting a ; at the very end. This code prints one line: HelloWorld.
PRINT "Hello";
PRINT "World"
PRINT is used a lot, so there's a shorter way to write it. These two lines do the same thing.
PRINT "Hello, world!"
?"Hello, world!"
More advanced text formatting can be done using the FORMAT$ function. This uses similar syntax to printf in C.
?FORMAT$("The number is %04D.",1) '=> The number is 0001.
If you want to clear all the text off the screen, you can use CLS. CLS stands for "CLear Screen".
CLS
You can also pick a specific place on the screen to print with LOCATE.
LOCATE 5,5
?"Hello!"

Types

There are seven types of values in SmileBASIC 4.
  • Empty: This is the type used when there's no value at all. SmileBASIC also refers to this as the "default type". This type doesn't come up very often, so it will be covered later in the "Functions and empty type" section.
  • Integer: This is a number without a decimal point. 32-bit.
    ?5
    ?-100
  • Real number: This is a number with a decimal point, also called a "floating-point number" or "float" for short. 64-bit.
    ?5.0
    ?-123.456789
  • String: This stores some text using the UCS-2 character encoding.
    ?"Hello, world!"
    ?"ใ“ใ‚“ใซใกใฏ๏ผ"
  • Integer, real number, and string arrays: These store a list of values inside of them. There's a lot to talk about with these, so they'll be covered later in the "Arrays" section.
There are no user-defined types or structs in SmileBASIC. You can check the type of a value with TYPEOF.
?TYPEOF(5)      '=> 1
?TYPEOF(1.234)  '=> 2
?TYPEOF("Hi!")  '=> 3
There's also a handy command called INSPECT that lets us view a value in detail. It's helpful when debugging programs.
INSPECT 5       '=> INT: 5
INSPECT 1.234   '=> REAL: 1.23400000
INSPECT "Hi!"   '=> STRING: (3)"Hi!"
Like PRINT, there's a shorter way to write this command.
??5             '=> INT: 5
It's possible to convert between these types as well. INT converts from a real number to an integer.
??INT(2.5)      '=> INT: 2
FLOAT converts from an integer to a real number.
??FLOAT(5)      '=> REAL: 5.00000000
STR$ converts from either number type to a string.
??STR$(1.234)   '=> STRING: (5)"1.234"
VAL converts from a string to an integer, if possible. Otherwise, it converts to a real number. If neither are possible, it returns 0.
??VAL("123")    '=> INT: 123
??VAL("1.23")   '=> REAL: 1.23000000
??VAL("56hi")   '=> INT: 0
SmileBASIC's typing is not very strict when it comes to numbers. If the result of some operation doesn't fit into an integer, it will become a real number automatically.

Arithmetic

Arithmetic in SmileBASIC works pretty much as you'd expect. + means add, - means subtract, * means multiply, and / means divide. All of these lines set X to 12.
X=8+4
X=15-3
X=2*(3+3)
X=24/2
+ and * can also be used with strings.
?"Good"+"Job"   '=> GoodJob
?"Ha"*5         '=> HaHaHaHaHa
There's also integer division and remainder, using DIV and MOD. Using normal division, 19 divided by 8 is 2โ…œ. Using integer division, 19 divided by 8 is 2 with a remainder of 3. DIV gives you the first number, and MOD gives you the second number.
A=19 DIV 8
B=19 MOD 8
?A,B            '=> 2   3
Exponents can be calculated using the POW function. This code calculates 2 to the power of 8.
?POW(2,8)       '=> 256
Bitwise operators also exist, like AND, OR, XOR, and NOT. These are helpful in dealing with binary numbers, but don't worry if you don't know how to use them.
?1 OR 3                '=> 3
?(1 XOR 6) AND NOT 3   '=> 4
The other bitwise operators are various kinds of bit shifts.
  • << and >> are arithmetic (signed) shift.
  • <<< and >>> are logical (unsigned) shift.
  • <<+ and >>+ are rotate left and right.
?1<<4           '=> 16
?-1>>>30        '=> 3
?5>>+1          '=> -2147483646
When using bitwise operators, it's helpful to be able to write numbers in hexadecimal or binary. In BASIC, these are done with the &H and &B prefixes.
?&HFF           '=> 255
?&B1000         '=> 8

Variables

Values can be given names, called variables. To set a variable to a specific value, you can use =. This is called assigning a value. This code sets the variable SALLY to 123.
SALLY=123
Once a variable is set, you can use it with commands like PRINT.
?SALLY          '=> 123
You can set a variable as many times as you want, and it will use the latest value you've set it to.
SALLY=150
SALLY=SALLY+1
?SALLY          '=> 151
To increase or decrease a value by some number, you can also use the INC and DEC commands.
SALLY=100
SALLY=SALLY+10
??SALLY         '=> INT: 110

SALLY=100
INC SALLY,10
??SALLY         '=> INT: 110
Variables don't care about what type of value is stored in them, so this code is okay.
FRED=100
FRED="Hello!"
However, it's worth noting that previous versions of SmileBASIC were stricter about variables, marking the type with a suffix on the name.
FRED=100.0
FRED%=100
FRED#=100.0
FRED$="Hello!"
These suffixes still matter in certain circumstances. For example, if you try to read a variable that hasn't been assigned a value yet, it will use a default value depending on the variable name.
??UNUSED        '=> REAL: 0.00000000
??UNUSED%       '=> INT: 0
??UNUSED#       '=> REAL: 0.00000000
??UNUSED$       '=> STRING: (0)""
Speaking of that, some people don't like being able to read unassigned variables, because if you make a typo, like writing SALY instead of SALLY, you'll just get 0.0 instead of the program giving an error message. To change this behavior, you can put this at the top of the file:
OPTION STRICT
Once OPTION STRICT is used, variables need to be declared with the VAR command before you can use them. Once they're declared, you can use them like normal.
VAR X=123
X=456
?X              '=> 456
You can use VAR without a value, in which case it uses the default for that type, as determined by the suffix.
VAR Y%
??Y             '=> INT: 0
You can even declare multiple variables at once.
VAR A=10,B=20,C
?A,B,C          '=> 10  20  0
Instead of VAR, you can write DIM, which does the same thing.
DIM BOB=12345
?BOB            '=> 12345
VAR has a function form that lets you access variables by passing their name in as a string. VAR is special in that it can be used before =.
?BOB            '=> 12345
?VAR("BOB")     '=> 12345

PHIL=500
VAR("PHIL")=500
It's also possible to declare constants in SmileBASIC, using CONST. A constant is a variable that never changes. Their names always start with #.
CONST #WIDTH=1280
It's a good idea to use constants whenever you need to give a name to a value that won't ever change while the code is running.

Text input

Programs aren't very useful without any input from the user, so let's go over that now. LINPUT prints a prompt to the screen and waits for the user to type in some text and press Enter. This code prints What is X? , reads a line, and puts it into the X variable.
LINPUT "What is X? ";X
If you want, you can leave off the prompt entirely.
LINPUT X
INPUT is similar to LINPUT, but it reads a series of values separated by commas, instead of taking the line as is. It decides whether to read a string or convert it into a number based on the type of the variable used.
INPUT "Name and age";NAME$,AGE%
?NAME$,AGE%
Many other ways to get input exist, but they aren't covered in this guide.

Comparisons

Not only is arithmetic possible, but it's also possible to check if some condition is true or not. In SmileBASIC, 1 is used to mean "true", and 0 is used to mean "false". To make things easier to read, you can write #TRUE or #FALSE instead.
?#TRUE          '=> 1
?#FALSE         '=> 0
To check if a number is equal to another, you can use ==. The reason SmileBASIC uses == instead of = is because = is used for assigning values.
?6==6           '=> 1
?9==3           '=> 0
To check if a number is NOT equal to another, use !=.
?4!=5           '=> 1
?5!=5           '=> 0
To check if a number is less than another, use <. To check if a number is greater than another, use >.
?3<7            '=> 1
?3>7            '=> 0
To check if a number is less than or equal to another, use <=. To check if a number is greater than or equal to another, use >=.
?3<=7           '=> 1
?3<=3           '=> 1
?7>=3           '=> 1
?7>=7           '=> 1
If you want to check multiple conditions, you can use && or ||. A && B is true only when A and B are both true. A || B is true if either A or B are true.
?10<20 && 5==5  '=> 1
?50>3 || 2==7   '=> 1
To invert a condition, so that true becomes false and false becomes true, use !. For instance, these two lines are equivalent.
?!(2<3)         '=> 0
?2>=3           '=> 0

Branching

To do something only if a condition is true, use IF...THEN. This code prints Hello! if X is equal to 5, or nothing if it's not equal.
IF X==5 THEN ?"Hello!"
If you want to do something else if the condition is false, use ELSE. This code prints Hello! if X is equal to 5, or Bye! if it's not equal.
IF X==5 THEN ?"Hello!" ELSE ?"Bye!"
If you want to have multiple commands in an IF statement, there is a multi-line version. ENDIF tells SmileBASIC where the IF statement ends.
IF X==5 THEN
 ?"Hello!"
 ?"X is equal to 5!"
ENDIF
ELSE can also be used with this form.
IF X==5 THEN
 ?"X is equal to 5!"
ELSE
 ?"X is not equal to 5!"
ENDIF
If you want to check a list of conditions, and do the first one that's true, you can use ELSEIF. You can have as many ELSEIFs as you want.
IF X==5 THEN
 ?"X is equal to 5!"
ELSEIF X==6 THEN
 ?"X wasn't equal to 5, but it is equal to 6!"
ELSE
 ?"X wasn't equal to 5 or 6."
ENDIF
If you want to check if a variable is equal to a bunch of different values, you can use CASE. If you've used languages like C or JavaScript, keep in mind that there is no fallthrough in CASE.
CASE X
 WHEN 0: ?"X is 0!"
 WHEN 1: ?"X is 1!"
 WHEN 2: ?"X is 2!"
 OTHERWISE: ?"X is something else!"
ENDCASE

Looping

LOOP...ENDLOOP is an infinite loop. This code prints Hello, world! over and over, forever.
LOOP
 ?"Hello, world!"
ENDLOOP
You can also loop while a condition is true by using WHILE...WEND.
X=10
WHILE X<100
 ?"X is ";X
 INC X,5
WEND
You can loop over a range of numbers with FOR...NEXT.
FOR X=1 TO 10
 ?"X is ";X
NEXT
You can use STEP in FOR loops to increment by a specified number instead of 1. This code is equivalent to the WHILE loop from earlier.
FOR X=10 TO 100 STEP 5
 ?"X is ";X
NEXT
BREAK lets you end a loop early. This loop ends when X is 5 because of the BREAK.
FOR X=1 TO 10
 ?"X is ";X
 IF X==5 THEN BREAK
NEXT
CONTINUE lets you skip to the next iteration of the loop. This loop doesn't print X is 7 because CONTINUE skips past it.
FOR X=1 TO 10
 IF X==7 THEN CONTINUE
 ?"X is ";X
NEXT

END and STOP

END ends the program. You usually don't need to write it, since the program will end anyway once it reaches the end of your code, but sometimes you want to end it early.
END
STOP ends the program with an error message.
STOP "Number must be between 0 and 100"

A very simple game

Believe it or not, it's possible to make a simple game just from the stuff we've seen so far. Here is a small game where you try to guess a random number:
'This line sets N to a random number between 1 and 100.
N=RND(100)+1

?"I'm thinking of a number between 1 and 100."
?"Try to guess which number it is!"

LOOP
 INPUT "Guess";GUESS%
 IF GUESS%>N THEN
  ?"Too high, try again!"
 ELSEIF GUESS%<N THEN
  ?"Too low, try again!"
 ELSE
  ?"You did it!"
  BREAK
 ENDIF
ENDLOOP

Labels, GOTO, and GOSUB

These are features that come from old versions of BASIC, so they're not really recommended, but they're included here because you're likely to run into them when reading other people's code. Labels are used to give a name to a specific spot in your code. They don't do anything by themselves, but GOTO, GOSUB, and a few other commands use them.
@NAME
GOTO jumps to a specific label. For instance, this code prints 1 and 3, skipping 2.
?1
GOTO @DENNIS
?2
@DENNIS
?3
Note that when using a label as a value, it is actually a string. In other words, these lines are equivalent:
S$="@TEST"
S$=@TEST
It's also possible to jump backwards, making a loop. It's preferred to write this with LOOP...ENDLOOP instead, though.
@HELLO
?"Hello, world!"
GOTO @HELLO
GOSUB is similar to GOTO, but you can RETURN after jumping to a label, which brings you back to right after the GOSUB. This code prints Hello!, then Yay!, then Goodbye!. Notice how we use END here to keep SmileBASIC from running the code under @YAY a second time.
?"Hello!"
GOSUB @YAY
?"Goodbye!"
END

@YAY
?"Yay!"
RETURN
If you're already experienced with programming, you might recognize GOSUB as a subroutine call. However, SmileBASIC offers proper functions, which are covered later, so there isn't much use for GOSUB. ON isn't necessary now that CASE exists, but for reference, here's the equivalent of the CASE example from earlier. ON can also use GOSUB instead of GOTO.
ON X GOTO @0,@1,@2
?"X is something else!":GOTO @E
@0:?"X is 0!":GOTO @E
@1:?"X is 1!":GOTO @E
@2:?"X is 2!":GOTO @E
@E
As you may notice, these GOTO and GOSUB examples are often hard to read, which is why they aren't really recommended. There are usually better ways to accomplish the same things.

Functions and the empty type

Sometimes code gets repetitive, and you want to give a certain set of actions a name, so that you don't have to write it all out every time. For example, consider this code:
LOOP
 INPUT "First number";A
 IF A<0 || A>9 THEN ?"Number must be a single digit" ELSE BREAK
ENDLOOP

LOOP
 INPUT "Second number";B
 IF B<0 || B>9 THEN ?"Number must be a single digit" ELSE BREAK
ENDLOOP

LOOP
 INPUT "Third number";C
 IF C<0 || C>9 THEN ?"Number must be a single digit" ELSE BREAK
ENDLOOP

?"The sum is ";A+B+C
This code works, but it's very repetitive, which can lead to mistakes if you have to change it in the future. Instead, using DEF, we can turn the repetitive code into a function.
DEF DIGIT(PROMPT)
 LOOP
  INPUT PROMPT;N
  IF N<0 || N>9 THEN ?"Number must be a single digit" ELSE BREAK
 ENDLOOP
 RETURN N
END

A=DIGIT("First number")
B=DIGIT("Second number")
C=DIGIT("Third number")
?"The sum is ";A+B+C
Functions give part of our code a name, and allow us to reuse it in multiple places within our code. It doesn't matter whether DEF comes before or after the place the function is used. Notice that the code within DEF doesn't execute until the function is actually used. Also, note that the END here is used to mark the end of a DEF. If there wasn't a DEF, it would end the program instead. The syntax for defining a function is somewhat complicated. A function has a set of arguments (the inputs of the function) and a set of return values (the outputs of the function). The basic syntax for defining a function is:
DEF name arg1,arg2,arg3... OUT ret1,ret2,ret3...
 ...
END
For example, here is a function with two inputs and two outputs.
DEF DIVMOD X,Y OUT D,M
 D=X DIV Y
 M=X MOD Y
END

DIVMOD 11,7 OUT A,B
?A,B            '=> 1   4
If there are no outputs, the OUT part can be omitted.
DEF HELLO
 ?"Hello, world!"
END

HELLO           '=> Hello, world!
And if there's only one output, there's another special form.
DEF AVG(X,Y)
 RETURN (X+Y)/2
END

A=AVG(10,20)
?A              '=> 15
This is the same as writing the following code.
DEF AVG X,Y OUT A
 A=(X+Y)/2
END

AVG 10,20 OUT A
?A              '=> 15
The advantage of the AVG(10,20) form is that it can be used in math expressions, and as an input to other functions.
?AVG(10,20)
X=10+AVG(5,8)
IF X>AVG(4,11) THEN ?"Yay!"
Y=AVG(4,AVG(15,25))
Any return values that are left unset become the empty type. This could be useful if you only want to return a value sometimes.
DEF NULL()
END

??NULL()        '=> EMPTY
You can check for this type with TYPEOF, as usual.
?TYPEOF(NULL()) '=> 0
It's also possible to omit arguments when calling a function, in which case they will be the empty type.
DEF TEST A,B
 ??A
 ??B
END

TEST 100,200    '=> INT: 100
                '=> INT: 200

TEST 100,       '=> INT: 100
                '=> EMPTY

TEST ,200       '=> EMPTY
                '=> INT: 200

TEST ,          '=> EMPTY
                '=> EMPTY
There's also a way to have a function that can take any number of arguments, and/or return any number of return values. That will be covered later, in the "Variadic functions" section.

Arrays

Now, let's say we want to read three lines from input, and then print them in reverse order. That's pretty simple.
LINPUT A$
LINPUT B$
LINPUT C$
?C$
?B$
?A$
This worked fine, since we're only reversing three lines. But what if we wanted to reverse a hundred lines? Of course we could keep going, and add D$, E$, F$, G$, H$... but it's best to avoid repetitive code as much as possible. What we really need now is a list of strings. In SmileBASIC, lists of values are called "arrays". To create an array, you can declare it with VAR (or DIM). The type of the array is determined by the name suffix, and the number within the brackets specifies how many values should be in the array.
VAR NUMBERS%[4]
You can check the length of an array (or string) with LEN.
?LEN(NUMBERS%)  '=> 4
At first, they will all be the default value for that type.
??NUMBERS%      '=> INT[4]:
                '=>  [0]: 0
                '=>  [1]: 0
                '=>  [2]: 0
                '=>  [3]: 0
The values in the array are numbered starting from 0. These position numbers are called an "index". To set the value at index 2 within the array, you can do this:
NUMBERS%[2]=123
??NUMBERS%      '=> INT[4]:
                '=>  [0]: 0
                '=>  [1]: 0
                '=>  [2]: 123
                '=>  [3]: 0
You can access a specific value in the array the same way.
??NUMBERS%[2]   '=> INT: 123
Unlike some other languages, arrays in SmileBASIC can only store one type. It's not possible to have an array that contains both a string and an integer, for instance. However, arrays are resizable in SmileBASIC. For example, PUSH adds a value to the end of an array.
VAR TEST#[2]

??TEST#         '=> REAL[2]:
                '=>  [0]: 0.00000000
                '=>  [1]: 0.00000000

PUSH TEST#,567.89
??TEST#         '=> REAL[3]:
                '=>  [0]: 0.00000000
                '=>  [1]: 0.00000000
                '=>  [2]: 567.89000000
You can also resize an array with RESIZE.
RESIZE TEST#,5
??TEST#         '=> REAL[5]:
                '=>  [0]: 0.00000000
                '=>  [1]: 0.00000000
                '=>  [2]: 567.89000000
                '=>  [3]: 0.00000000
                '=>  [4]: 0.00000000

RESIZE TEST#,1
??TEST#         '=> REAL[1]:
                '=>  [0]: 0.00000000
So, let's go back to our previous example again. With arrays, we can write something like this:
VAR LINES$[100]
FOR I=0 TO 99
 LINPUT L$
 LINES$[I]=L$
NEXT

FOR I=99 TO 0 STEP -1
 ?LINES$[I]
NEXT
Note how we write FOR I=0 TO 99. The first index is 0, so the last item has an index of 99, not 100. Rather than writing out 99, though, it would be better to use LEN instead.
FOR I=0 TO LEN(LINES$)-1
 LINPUT L$
 LINES$[I]=L$
NEXT
LEN(...)-1 is so common in SmileBASIC that there's an even shorter way to write it. Note that LAST(LINES$) is the index of the last value in LINES$, not the last value itself.
FOR I=0 TO LAST(LINES$)
 LINPUT L$
 LINES$[I]=L$
NEXT
But we can do more with this. What if, instead of reading a set number of lines, we keep reading until the user enters the line END? As mentioned earlier, PUSH adds a value to the end of a list, so if we start with an empty array, we can do this:
VAR LINES$[0]

?"Enter some lines, then 'END' once you're done:"
LOOP
 LINPUT L$
 IF L$=="END" THEN BREAK ELSE PUSH LINES$,L$
ENDLOOP
Then, to print them in reverse order, we can take lines off the end of the array with POP.
WHILE LEN(LINES$)>0
 ?POP(LINES$)
WEND
Now let's talk about making arrays with pre-defined data. We could write something like this:
VAR NUMBERS%[4]
NUMBERS%[0]=100
NUMBERS%[1]=1234
NUMBERS%[2]=5
NUMBERS%[3]=256
But it's kind of annoying to write it like that. There's special syntax that makes this easier.
VAR NUMBERS%[4]=[100,1234,5,256]
Because it knows from the list of numbers we give that the length should be 4, we can leave it out.
VAR NUMBERS%[]=[100,1234,5,256]
If you're coming from another language, note that the number list is part of the VAR syntax, and you can't use it on its own.
?[1,2,3] 'DOES NOT WORK
Still, although this syntax is nice, it has some shortcomings when we're dealing with complex data. For that, we'll have to look at the appropriately-named DATA command in the next section.

DATA

DATA lets you define data within your program. For example, say we're making an RPG, and we want data for a bunch of enemy names and their HP. We can write something like this:
DATA "Slime",10
DATA "Goblin",50
DATA "Werewolf",200
Now, by itself, this data doesn't do anything. To use it, we have to use READ. Each READ will read one value from the defined data in the program, starting from the very first DATA.
READ X
?X              '=> Slime
READ X
?X              '=> 10
READ X
?X              '=> Goblin
READ X
?X              '=> 50
We can also read multiple values at once.
READ NAME$,HP%
?NAME$          '=> Werewolf
?HP%            '=> 200
We run into a problem if we want to READ the data again after we've already read it. To do that, we can use RESTORE. First, we need to put a label above our DATA.
@ENEMIES
DATA "Slime",10
DATA "Goblin",50
DATA "Werewolf",200
Then we can use RESTORE to change where the next READ will read from. The next READ will use the first data after the label given to RESTORE, and so on.
RESTORE @ENEMIES
READ NAME$,HP%
?NAME$,HP%      '=> Slime   10
READ NAME$,HP%
?NAME$,HP%      '=> Goblin  50
RESTORE @ENEMIES
READ NAME$,HP%
?NAME$,HP%      '=> Slime   10
Where this really becomes useful is when we're dealing with arrays. For example, let's define two arrays for enemy names and HP.
VAR ENEMY_NAME$[0]
VAR ENEMY_HP%[0]
Then we can READ the data into these arrays.
RESTORE @ENEMIES
FOR I=1 TO 3
 READ NAME$,HP%
 PUSH ENEMY_NAME$,NAME$
 PUSH ENEMY_HP%,HP%
NEXT

??ENEMY_NAME$   '=> STRING[3]:
                '=>  [0]: (5)"Slime"
                '=>  [1]: (6)"Goblin"
                '=>  [2]: (8)"Werewolf"
                
??ENEMY_HP%     '=> INT[3]:
                '=>  [0]: 10
                '=>  [1]: 50
                '=>  [2]: 200
We used FOR I=1 TO 3 to make sure we wouldn't run out of data, but we could check for a special ending value instead.
@ENEMIES
DATA "Slime",10
DATA "Goblin",50
DATA "Werewolf",200
DATA ""

VAR ENEMY_NAME$[0]
VAR ENEMY_HP%[0]

RESTORE @ENEMIES
LOOP
 READ NAME$
 IF NAME$="" THEN BREAK
 READ HP%
 PUSH ENEMY_NAME$,NAME$
 PUSH ENEMY_HP%,HP%
ENDLOOP
Finally, if we're only dealing with a single array, we can use COPY to read a bunch of data into an array automatically. Note that in this case, we need to set the array size to the amount of data we want to read.
VAR NUMBERS%[10]
COPY NUMBERS%,@NUMBERS

@NUMBERS
DATA 0,1,1,2,3
DATA 5,8,13,21,34

Multi-dimensional arrays

Although we've only looked at arrays that are one-dimensional lists so far, it's also possible to make multi-dimensional arrays.
VAR BOARD%[3,2]=[10,20,30,40,50,60]
??BOARD%        '=> INT[3,2]:
                '=>  [0,0]: 10
                '=>  [0,1]: 20
                '=>  [1,0]: 30
                '=>  [1,1]: 40
                '=>  [2,0]: 50
                '=>  [2,1]: 60

?BOARD%[1,0]     '=> 30
Multi-dimensional arrays can still be indexed as if they were one-dimensional.
?BOARD%[2]       '=> 30
Some commands like COPY and RESIZE still work with these arrays, but others like PUSH and POP only work for one-dimensional arrays. LEN will give you the total number of values in the array.
?LEN(BOARD%)    '=> 9
To find the size of each dimension, you can use the DIM function. This is different from using DIM as a command, which is the same as VAR. DIM with one argument gives you the number of dimensions in the array.
?DIM(BOARD%)    '=> 2
Adding a second argument lets you see the size of each dimension.
?DIM(BOARD%,0)  '=> 3
?DIM(BOARD%,1)  '=> 2

Strings in-depth

We've used strings a lot already, but in fact, strings have some similarities to arrays. Strings are made up of characters, after all. For example, strings have a length too.
?LEN("Hello!")  '=> 6
You can even use an index with them! Doing so gives you a new string containing the character at that index.
HELLO$="Hello!"
?HELLO$[0]      '=> H
?HELLO$[1]      '=> e

HELLO$[1]="a"
?HELLO$         '=> Hallo!
Each character has a numeric code. For example, A is 65, and e is 101. You can get this code with the ASC function.
?ASC("A")       '=> 65
You can turn a code into a character with the CHR$ function.
?CHR$(65)       '=> A
These character codes come from a standard called Unicode. For those who are familiar with Unicode, strings use the UCS-2 encoding (UTF-16 without surrogate pairs). In other words, you are limited to characters within the Basic Multilingual Plane, which are characters 0 through 65535. In some languages, there are escape sequences to put special characters in strings, but in SmileBASIC, you must use CHR$. For example, to put a newline character in a string, use CHR$(10).
?"Hello,"+CHR$(10)+"world!"     '=> Hello,
                                '=> world!
There are a few functions used to work with strings. LEFT$ and RIGHT$ allow you to get a number of characters from the left or right side of a string.
?LEFT$("Hello, world!",6)       '=> Hello,
?RIGHT$("Hello, world!",6)      '=> world!
MID$ allows you to do the same from the middle of a string.
?MID$("Hello, world!",2,4)      '=> llo,
INSTR finds a string within another string, returning an index.
?INSTR("Hello, world!","el")    '=> 1
?INSTR("Hello, world!","world") '=> 7
?INSTR("Hello, world!","Hi!")   '=> -1
SUBST$ replaces part of a string with another string.
?SUBST$("Hello!",2,2,"ww")      '=> Hewwo!
INSTR and SUBST$ can be combined to make a find-and-replace function.
DEF REPLACE$(TEXT$,BEFORE$,AFTER$)
 I=INSTR(TEXT$,BEFORE$)
 IF I<0 RETURN TEXT$
 RETURN SUBST$(TEXT$,I,LEN(BEFORE$),AFTER$)
END

Files

Variables are great, but they don't stick around between runs of a program, so you may want to save them to a file so that you can load them later. For example, if you're making a game, you may want to let people save their progress, If you're making an image editor, you'll want people to be able to save their artwork. In SmileBASIC, there are six commands used for loading and saving, depending on what you want to load or save. LOAD loads an editor slot from a text file.
LOAD "MY_PROGRAM",3
SAVE saves an editor slot to a text file.
SAVE "MY_PROGRAM",3
LOADG loads a graphics page from a graphics file.
LOADG "MY_SPRITES",2
SAVEG saves a graphics page to a graphics file.
SAVEG "MY_SPRITES",2
LOADV loads a string or array from a file.
MY_TEXT$=LOADV("TXT:MY_TEXT")
MY_DATA%=LOADV("DAT:MY_DATA")
SAVEV saves a string or array to a file.
SAVEV "TXT:MY_TEXT",MY_TEXT$
SAVEV "DAT:MY_DATA",MY_DATA%
If we're making a save file for a game, SAVEV is what we'll want to use. Ideally we want just one file, since SmileBASIC asks the user if they want to save a file, and we don't want to bother them with multiple confirmation dialogs in a row. That said, games have a lot of different pieces of data, and you can only save one string or array in a file, so sometimes it takes a little creativity. One easy way to do it is by creating a string array, and converting everything to a string. For example, if we have this data we want to save:
PLAYER_NAME$="Doc"
PLAYER_LEVEL%=30
PLAYER_MAX_HP%=250
We could have load and save code like this:
DEF SAVE_GAME
 VAR SAVE$[3]
 SAVE$[0]=PLAYER_NAME$
 SAVE$[1]=STR$(PLAYER_LEVEL%)
 SAVE$[2]=STR$(PLAYER_MAX_HP%)
 SAVEV "DAT:SAVE",SAVE$
END

DEF LOAD_GAME
 SAVE$=LOADV("DAT:SAVE")
 PLAYER_NAME$=SAVE$[0]
 PLAYER_LEVEL%=VAL(SAVE$[1])
 PLAYER_MAX_HP%=VAL(SAVE$[2])
END

Variadic functions

Earlier, we covered functions with a set number of arguments or return values, but SmileBASIC 4 supports variadic functions as well. To define a variadic function, we use * instead of having a list of arguments or return values.
DEF SUM * OUT N
 'code goes here
END
To access variadic arguments and return values, there are a few functions we can use.
  • DEFARGC gives you the number of arguments.
  • DEFARG gets a specific argument by index.
  • DEFOUTC gives you the number of return values.
  • DEFOUT sets a specific return value by index.
So, if we wanted to write a variadic function that takes any number of arguments and returns the sum, we can do this:
DEF SUM * OUT N
 N=0
 FOR I=0 TO DEFARGC()-1
  N=N+DEFARG(I)
 NEXT
END

?SUM(1,2,3)     '=> 6
If we wanted to write a function that gives any number of return values, we can do something like this:
DEF FIBONACCI OUT *
 A=0
 B=1
 FOR I=0 TO DEFOUTC()-1
  DEFOUT I,A
  SWAP A,B
  B=A+B
 NEXT
END

FIBONACCI OUT A,B,C,D,E,F,G
?A,B,C,D,E,F,G  '=> 0   1   1   2   3   5   8
And of course, both arguments and return values can be variadic.
DEF DOUBLE * OUT *
 IF DEFARGC()!=DEFOUTC() THEN
  STOP "Must have same number of arguments and return values"
 ENDIF
 FOR I=0 TO DEFARGC()-1
  DEFOUT I,2*DEFARG(I)
 NEXT
END

DOUBLE 2,7,6 OUT A,B,C
?A,B,C          '=> 4   14  12

EXEC and COMMON DEF

SmileBASIC allows for six code files to be loaded at once, in the editor slots 0 through 5. To have one code file load and run another, you can use EXEC. This loads the text file MYLIBRARY into slot 1 and runs it.
EXEC "MYLIBRARY",1
In fact, this is short for:
LOAD "MYLIBRARY",1
EXEC 1
When one slot executes another, END will return back to after the EXEC that executed the slot it's in, rather than ending the program. For example, if slot 0 uses EXEC to run slot 1, and slot 1 reaches END, execution will return back to slot 0, immediately after the EXEC. Put another way, EXEC allows you to call another slot as a subroutine, which returns using END. It's possible to access variables from other slots using VAR with a slot number.
?VAR("2:FOO")
VAR("0:BAR")=123
It's also possible to specify labels in other slots.
COPY ARR%,"0:@ARR"
Normally, a function can't be accessed outside of the slot it's in. However, COMMON DEF will export that function so that it's available in any slot.
COMMON DEF TEST A,B
 ?A+B
END
The basic structure of a library in SmileBASIC ends up looking like this:
'globals and initialization code...
END

'DEF functions that are internal to the library...

'COMMON DEF functions that represent the library interface...

Dang, man. This is really good. Thanks!

This is incredible. I'm really sorry if I made you wait or inconvenienced you or worse with getting a new website set up. Thank you for your amazing contributions

A nice refresher for some of us just getting into SmileBASIC 4 who have done BASIC before, but not for a long time.