Program structure

Write a program the compiler accepts: lines and colons, the six comment forms, names and case, strings, the parentheses rule, and the guards that refuse a line.

In one line#

A BASIC program is a list of lines. Each line holds one statement, or several separated by :. The compiler reads the whole program before it runs, and it refuses a line it cannot understand instead of guessing.

This chapter covers the shape of a program. The values it works on are in Variables and types, and the operators in Constants and operators.

Lines and statements#

<statement>
<statement> : <statement> [: <statement> ...]
<label>:
<label>: <statement> [: <statement> ...]
x = 10
y = 20 : total = x + y
Console(Str(total))   ' 30
  • One statement per line, or several separated by :.
  • Indentation is free. Spaces and tabs at the start of a line mean nothing. Blank lines are allowed anywhere.
  • A : inside a string or inside brackets does not split the line: "time: 12:30" stays one string.
  • A block can live on one line: FOR i = 1 TO 5 : total = total + i : NEXT.

A single-line IF keeps everything after THEN#

After THEN, every : belongs to the IF. All the statements run only when the condition is true.

lives = 0
IF lives = 0 THEN Console("Game over") : lives = 3
Console(Str(lives))   ' 3

Details in Control flow.

Labels#

A name followed by : at the start of a line is a label, the target of GOSUB and GOTO. The label may carry statements on the same line.

GOSUB Greet
Console("back")   ' printed second
END
Greet: Console("hello") : RETURN

⚠️ Because of this rule, a : right after the first word of a line always declares a label. start: x = 1 is the label start with x = 1 as its body, and the compiler asks for its RETURN: BASIC label start requires RETURN.

What does not exist#

Classic featureIn aukimi
Line numbers (10 x = 1)refused: Not a BASIC statement
Line continuation (_ at the end of a line)refused: write the statement on one line
; as a commentnot a comment: the text after it is compiled as code

Lines that are only a Markdown code fence (three backticks, with or without a language name) are ignored. Code copied from a web page with its fence still runs, and its line numbers stay right.

Comments#

Six forms are accepted.

FormWhereExample
'anywhere; the rest of the line is a commentx = 1 ' note
//anywhere; the rest of the line is a commentx = 1 // note
REMstart of a line, or after a statementREM note
` (backtick)start of a line, or after a statementx = 1 ` note
REMSTART … REMENDblock; both words start their own linesee below
/* … */block; /* starts its linesee below
' A full-line comment
REM Another full-line comment
// A C-style line comment
x = 1   ' after a statement
y = 2   // after a statement too
z = 3   ` the backtick form
REMSTART
A block of notes
over several lines
REMEND
/* A C-style block
   over several lines */
Console(Str(x + y + z))   ' 6

Use ' or //. They are the only two forms that are safe everywhere. The other four exist so that code from other dialects keeps compiling.

⚠️ A : inside a REM or backtick comment splits the line#

The line splitter stops at ' and //. It does not stop at REM or at a backtick. A colon after them ends the comment, and the rest is compiled as code.

x = 1 REM note: this part is compiled
Not a BASIC statement: this part is compiled

The same happens with a full-line backtick comment that contains a colon. A full-line REM is safe; a REM after a statement is not.

⚠️ Block comments have three rules#

  1. REMSTART, REMEND and /* start their line. x = 1 REMSTART is not a block comment.

  2. Everything on the line of /* is comment, and so is the rest of the line after */. Code written there never runs:

    /* setup */ Console("never shown")
    Console("shown")   ' shown
  3. A line inside a block must not start with one word followed by :. The compiler reads TODO: or Note: as a label and refuses the program.

    REMSTART
    TODO: tidy this up
    REMEND
    BASIC label TODO requires RETURN
    

    Write TODO - tidy this up, or use ' lines.

A block that is never closed is an error: REMSTART requires REMEND, or /* requires */.

Case and names#

Everything is case-insensitive: keywords, command names, variables, user functions, record fields and constants. IF, If and if are the same word. Console, CONSOLE and console are the same command.

The first spelling of a name is kept, and every later spelling is rewritten to it.

Score = 1
SCORE = SCORE + 1
score = score + 1
Console(Str(sCoRe))   ' 3
if score = 3 then Console("keywords in lower case work too")

Two exceptions:

NameRule
PIupper case only. pi or Pi is an unknown name: the program stops with … is not defined.
TRUE, FALSE, NULLany case.
area = PI * 2 ^ 2
Console(Str(area))   ' 12.566370614359172

Writing a name#

  • Letters, digits and _. It starts with a letter or _.
  • A variable may end with a sigil: $, #, % or &. What a sigil does is in Variables and types.
  • A constant is written with # in front: #MAX_LIVES. See Constants and operators.

Which names can share a spelling#

PairSame spelling allowed?
a variable and a record field (speed and s.speed)yes, they never meet
a variable and a user FUNCTIONyes
a variable and a catalogue command (left and Left)yes, once the program assigns the variable with name = …
a user FUNCTION and a catalogue commandyes, and the user function wins
a constant and a variable (#SPEED and SPEED)⚠️ no: spelled alike, the program does not start
TYPE Ship
  speed
ENDTYPE
s AS Ship
s.speed = 4
speed = 9
Console(Str(s.speed) + " " + Str(speed))   ' 4 9
left = 10
x = left + 1
Console(Str(x) + Left("abc", 1))   ' 11a
FUNCTION Sin(v)
ENDFUNCTION v * 2
Console(Str(Sin(90)))   ' 180: your Sin, not the catalogue's

⚠️ JavaScript words are not free#

BASIC compiles to JavaScript. A variable named after a JavaScript keyword compiles, but the program does not start.

class = 3

Avoid these names: class, const, debugger, default, enum, export, extends, import, in, instanceof, let, new, super, switch, this, throw, typeof, var, void, await. Also avoid names that begin with _b or __basic, and nextFrame: the compiler uses them for itself.

Strings#

"<text>"
  • Double quotes only. A ' starts a comment, so 'text' is not a string.
  • Two escapes exist: \" for a quote, and \\ for a backslash.
  • Every other backslash is a plain backslash. A Windows path works as written, and \n is two characters, not a new line.
  • A string stays on one line.
path$ = "media\background5.jpg"
quote$ = "She said \"hi\""
slash$ = "a\\b"
Console(path$)    ' media\background5.jpg
Console(quote$)   ' She said "hi"
Console(slash$)   ' a\b

For a new line or a quote, build the string with Chr:

twoLines$ = "first" + Chr(10) + "second"
quoted$ = Chr(34) + "aukimi" + Chr(34)
Console(quoted$)   ' "aukimi"

⚠️ The doubled quote of QBasic and Visual Basic ("") is not supported. It compiles, but the program does not start. Write \" or Chr(34).

say$ = "say ""hi"""

⚠️ After a \", do not put a : or a ' in the same string. The line splitter does not know the escape, so it thinks the string has ended there.

label$ = "a\"b: c"
Not a BASIC statement: c"

Use Chr(34) in such a string. Strings with {holes} are written $"…": see Shorthands.

Literals#

KindExamples
integer42, -7
decimal3.5, .5, 5.
exponent1.25e2, 1.5E-3
hexadecimal0x1F, 0XFF
octal0c17
binary%1010
string"text"
truth and nothingTRUE, FALSE, NULL
πPI
d = 1.25e2
e = 0x1F
f = 0c17
g = %1010
Console(Str(d) + " " + Str(e) + " " + Str(f) + " " + Str(g))   ' 125 31 15 10

A malformed literal is refused on its line: Malformed BASIC numeric literal: 0x1G.

⚠️ $FF (PureBasic) and &HFF (QBasic, Visual Basic) are not hexadecimal here. Write 0xFF.

How each type stores these values is in Variables and types.

The parentheses rule#

A command call always carries parentheses, even with no argument.

<Command>([<argument> [, <argument> ...]])
elapsed# = Timer()
width = DeviceWidth()
Sync()

The bare forms of AGK, DarkBASIC and PureBasic are refused:

PRINT "hello"
Sync
x = DeviceWidth
BASIC commands require parentheses: Print(...)
BASIC commands require parentheses: Sync(...)
BASIC commands require parentheses: DeviceWidth(...)

This is what lets the compiler tell a command from a variable. A name the program assigns (left = 10) is a variable; a name followed by ( is a call.

⚠️ A variable used only through GLOBAL, LOCAL or CATCH, and never assigned with a plain name = …, is still read as the command of the same name. LOCAL left = 3 in a function is refused with BASIC commands require parentheses: Left(...). Pick another name.

The two guards#

Two checks run on every line.

Unknown command#

A call whose name is not a catalogue command, a compiler built-in, a user FUNCTION, an array, a list or a map is refused. When a catalogue command is close in spelling, the compiler names it.

Prnt("hello")
Unknown BASIC command: Prnt() — did you mean Print()?
Blorp(1, 2)
Unknown BASIC command: Blorp()

The suggestion is offered when the name is at most one letter off (short names, up to four letters) or two letters off (longer names).

Not a statement#

A statement must be an assignment, a call, or start with a keyword. Anything else is refused, and the message quotes the start of the line.

go to the shop
VAR n = 5
Not a BASIC statement: go to the shop
Not a BASIC statement: VAR n = 5

What the guards do not catch#

  • A call after a dot (obj.method(...)) is not checked against the catalogue. That is how JavaScript objects stay reachable.
  • An unfinished expression, such as x = 1 + or x =, passes both guards. The program then does not start.
  • A name that is read but never assigned compiles, and stops the program when it runs (… is not defined). See Variables and types.
CommandWhat it does
Print(...)draws text on the game screen; a game calls it every frame
Console(...)writes a line in the editor's console
Debug(...)the same as Console, under PureBasic's name
ConsoleWarning(...), ConsoleError(...)a warning or an error line in the console
ClearConsole()empties the console

To check a value while you work, use Console. Every example in this manual does.

A minimal program#

Straight-line: runs once#

Without a frame loop, the program runs from top to bottom once, then stops. This is the right shape for a test, a tool, or anything that writes to the console.

name$ = "aukimi"
Console("Hello, " + name$)   ' Hello, aukimi

With a frame loop: runs every frame#

A game puts its setup above DO, and the frame body between DO and LOOP. Sync() ends each frame.

frames = 0
DO
  frames = frames + 1
  Print("Frame " + Str(frames))
  Sync()
LOOP

A loop without Sync() would never let the screen draw, so it is refused:

DO
  x = x + 1
LOOP
DO/LOOP requires SYNC() so the loop can yield a frame

⚠️ In a program with DO … LOOP, a FUNCTION does not see the plain variables set above DO. Declare shared values with GLOBAL. The reason, and the fix, are in Variables and types.

There is one top-level DO … LOOP per program. Everything else about frames, Wait, Sleep and tasks is in Game loop and tasks.

Coming from another BASIC#

You writeIn aukimi
PRINT "x", Sync, Debug "x"Print("x"), Sync(), Debug("x")
PRINT to see a valueConsole(...): Print draws on the game screen
10 GOTO 20no line numbers; use labels
_ to continue a lineone statement per line
"" inside a string\" or Chr(34)
&HFF, $FF0xFF
piPI
; comment' comment