Variables and types
Create variables, choose a numeric type that wraps the way you expect, turn on OPTION EXPLICIT, share state with GLOBAL, and expose a setting in the Inspector.
In one line#
A variable exists as soon as you assign it. It holds any value, until you give
it a type with AS; from then on, every value stored in it is converted to that
type. OPTION EXPLICIT makes declarations compulsory, and GLOBAL shares a
variable with your functions.
Implicit variables#
<name> = <value>
Console(Str(count)) ' 0: read before its first assignment
count = 5
Console(Str(count)) ' 5
x = 1
x = "now text"
Console(x) ' now text
- The first assignment creates the variable. There is no
DIMorVARfor a plain value. - A variable read before its first assignment starts at 0.
- An untyped variable holds anything: a number, then a string, then a number again. Its numbers are JavaScript numbers (64-bit floating point).
⚠️ A name the program never assigns is not a variable. total = lives + 1,
with lives assigned nowhere, compiles. The program then stops at that line
with lives is not defined. This is how a misspelled name shows up.
⚠️ A $ variable read before its first assignment also starts at 0, not at
an empty string. Give it a value first.
Console("[" + title$ + "]") ' [0], not []
title$ = "Hero"Sigils#
A variable name may end with one sigil.
| Sigil | Classic meaning |
|---|---|
$ | text |
# | decimal number |
% | integer |
& | long integer |
What a sigil does#
- It picks the starting value of a variable declared without one:
GLOBAL a, b$gives0and"". - It picks what
READexpects from aDATAline: text for$, a decimal number for#, an integer otherwise. - It types a record field. In a
TYPE, a field with no sigil and noASis an integer. See Records and methods. - It picks the kind of an exposed setting. See
@exposebelow. - On a command call, it must match the result.
Str$(12)is accepted; a$on a command that returns a number is refused.
v$ = Val$("12")
Val does not return text: drop the $ sigil
What a sigil does not do#
A sigil converts nothing. To convert, give the variable a type with
AS.
n% = 2.7
Console(Str(n%)) ' 2.7: the % does not round
t$ = 5
Console(Str(t$ + 1)) ' 6: the $ does not make text
Two names that differ only by their sigil#
obj and obj$ are two variables. This is what classic programs expect.
obj = 1
obj$ = "mesh" + Str(obj) + ".obj"
Console(Str(obj) + " " + obj$) ' 1 mesh1.obj
⚠️ A record field with a sigil loses its type when the same name appears
without the sigil anywhere in the program: a variable speed, or s.speed
for a field declared speed#. The field then becomes an integer: 2.5 is
stored as 2, and text stops the program. Give such a field a name of its own,
or declare it with AS (speed AS float), which has no sigil to lose.
TYPE Ship
hp
speed#
name$
ENDTYPE
s AS Ship
s.hp = 2.7
s.speed# = 2.5
s.name$ = "Ada"
Console(Str(s.hp) + " " + Str(s.speed#) + " " + s.name$) ' 2 2.5 AdaTyped variables: AS#
[GLOBAL | LOCAL] <name> AS <type> [= <value>]
[GLOBAL | LOCAL] <name> AS <type>[<size>[, <size> ...]] [= [<value>, ...]]
lives AS integer = 3
speed AS float = 2.5
title AS string = "Hero"
hits AS ubyte
hits = 250
hits = hits + 10
Console(Str(hits)) ' 4: 260 wrapped past 255
- Without
= <value>, a number starts at 0, a string at"", a handle atNULL. Some type names need the value: see the ⚠️ in the table below. - The type acts each time a value is stored: with
=, with+=,-=,*=,/=, and withINCandDEC. - Arithmetic in between is not typed.
h / 2is computed in full precision; only the store converts it. - The name of a typed variable carries no sigil.
name$ AS stringcompiles, but the program does not start. Writename AS string. - The array form
AS <type>[<size>]is covered in Collections, andAS <record type>in Records and methods.
h AS integer = 7
h = h / 2
Console(Str(h)) ' 3: 3.5 is stored as 3
q = h / 2
Console(Str(q)) ' 1.5: q has no type
Every type#
| Type | Also written | Range | Notes |
|---|---|---|---|
int8 | byte ⚠️, b | −128 … 127 | |
uint8 | ubyte, ascii, a ⚠️ | 0 … 255 | |
int16 | word, short, w | −32 768 … 32 767 | |
uint16 | uword, ushort, unicode, u ⚠️, c ⚠️ | 0 … 65 535 | |
int32 | integer, int ⚠️, long, i, l ⚠️ | −2 147 483 648 … 2 147 483 647 | long is 32-bit |
uint32 | uint, uinteger, dword | 0 … 4 294 967 295 | |
int64 | quad, q | −2⁶³ … 2⁶³ − 1 | see 64-bit below |
uint64 | uquad | 0 … 2⁶⁴ − 1 | see 64-bit below |
float32 | float, single, f ⚠️ | about 7 significant digits | float is 32-bit |
float64 | double, d | about 15 significant digits | the precision of an untyped number |
string ⚠️ | s | text | any value is turned into text |
handle | pointer ⚠️ | a reference (@variable) | NULL by default |
taskhandle | job | a running task | NULL by default |
Type names are case-insensitive. The one-letter names are PureBasic's suffixes.
⚠️ The names marked ⚠️ need a value. Written alone (x AS byte,
x AS string), they lose their type, because the compiler reads them as the
start of a catalogue command name (ByteLen, StringToBase64…). The variable
then behaves like an untyped one that starts at 0. Write x AS byte = 0 and
x AS string = "", or use a name without ⚠️.
a AS byte
a = 300
Console(Str(a)) ' 300: no type was applied
b AS int8
b = 300
Console(Str(b)) ' 44
How a value is converted#
| Stored into | Rule | Example |
|---|---|---|
| an integer type | the decimals are dropped (toward zero) | 7.9 → 7, -7.9 → -7 |
| an integer type | out of range wraps around | int8: 127 + 1 → -128; uint16: -1 → 65535 |
| an integer type | text made of digits is read | "12" → 12 |
| an integer type | other text stops the program | "abc", "1.5" → BASIC integer requires an integral value |
| an integer type | infinity stops the program | 1 / 0 → BASIC integer requires a finite value |
float32 | rounded to the nearest 32-bit float | 0.1 → 0.10000000149011612 |
string | any value becomes text | 42 → "42" |
n AS integer = 7.9
m AS integer = -7.9
k AS integer = "12"
w AS word = 40000
Console(Str(n) + " " + Str(m) + " " + Str(k + 1) + " " + Str(w)) ' 7 -7 13 -25536
⚠️ A typed FOR counter does not wrap. FOR b = 250 TO 260 with
b AS uint8 counts to 260 and ends at 261. The type acts again at the next
ordinary assignment.
⚠️ float is 32-bit#
In aukimi, float and single are float32, as in AGK. A value like 0.1
cannot be stored exactly, and Str shows every digit of what is stored.
price AS float = 0.1
Console(Str(price)) ' 0.10000000149011612
exact AS double = 0.1
Console(Str(exact)) ' 0.1
Console(Str(price, 2)) ' 0.10
Use double when you need decimals to print cleanly, or give Str a number
of decimals. An untyped variable already has double precision.
⚠️ long is 32-bit#
long is int32, as in PureBasic's .l. Visual Basic's 64-bit Long is
quad (int64) here.
big AS long = 3000000000
Console(Str(big)) ' -1294967296
64-bit integers#
int64 and uint64 hold exact 64-bit values. They are a separate kind of
number, and they only mix with each other.
What works:
counter AS int64 = 5
counter = counter + 1
counter += 10
INC counter
Console(Str(counter)) ' 17
huge AS int64 = "9223372036854775807"
Console(Str(huge)) ' 9223372036854775807
five AS int64 = 5
IF counter > five THEN Console("greater")
| Form | Works? |
|---|---|
big = big + n, big += n, -=, *=, /=, INC, DEC | yes |
big2 = big + other64 (only 64-bit values) | yes |
big = 5, big = "123" | yes |
big > 4, big < 10 | yes |
⚠️ y = big + 1 (the result is not a 64-bit variable) | stops: Cannot mix BigInt and other types |
⚠️ other AS int64 = big * 3 | stops: same message |
⚠️ IF big = 5 | never true: compare with another 64-bit variable |
⚠️ big = big * 2 + 1 | wrong result: computed as big * (2 + 1); write big *= 2 then big += 1 |
| ⚠️ a literal above 9 007 199 254 740 991 | loses its last digits: write it as text, "9223372036854775807" |
Integer division of 64-bit values drops the decimals: big /= 4 keeps the
integer part. Use 64-bit types only when 32 bits are really too small.
Literals for each type#
| Type | Write | Note |
|---|---|---|
| integer types | 42, -7, 0x1F, 0c17, %1010 | a decimal value is cut: 7.9 → 7 |
| 64-bit | 42, or "9223372036854775807" | text for anything above 2⁵³ |
float, double | 2.5, .5, 1.25e2 | |
string | "text" | a number is turned into text |
handle | @variable, NULL |
TRUE and FALSE are not numbers: see
Constants and operators.
PureBasic type suffixes: .f, .i, .s…#
<name>.<suffix> = <value>
speed.f = 1.5
speed = speed * 2
count.i = 7.9
label.s = "x"
Console(Str(speed) + " " + Str(count) + " " + label) ' 3 7 x
- The first
name.suffixdeclares the variable with that type, as if you wrotename AS <type>. Later lines can drop the suffix. - The suffixes are the one-letter names of the table above:
b a w u c l i q f d s. - A variable that is already a record, or a catalogue object such as a sprite,
keeps its own fields:
s.xon a sprite is not a suffix.
⚠️ c is uint16 on a variable and in a list, but int32 on a record field.
OPTION EXPLICIT#
OPTION EXPLICIT [' comment]
OPTION EXPLICIT
GLOBAL lives AS integer = 3
GLOBAL score = 0
GLOBAL name$ = "Ada"
DIM grid(2)
FUNCTION Hit(damage)
LOCAL rest = lives - damage
lives = rest
ENDFUNCTION lives
Hit(1)
score = score + 10
grid(1) = score
Console(name$ + " " + Str(lives) + " " + Str(grid(1))) ' Ada 2 10
OPTION EXPLICIT is off by default, so imported programs still compile. Once
it is on, every name you write to must be declared first.
OPTION EXPLICIT
score = 1
OPTION EXPLICIT requires a declaration for variable: score
- It stands alone on its line. Only a
'comment may follow it;OPTION EXPLICIT // noteis refused. - It applies to the whole program, included files too, wherever the line is.
- All the aukimi demos use it.
What must be declared#
| Written by | Example |
|---|---|
| an assignment | score = 1, grid(1) = 4, hero.hp = 3 (the first name) |
| a FOR counter | FOR i = 1 TO 10 |
INC / DEC | INC score |
READ | READ level |
What counts as a declaration#
| Declaration | Example |
|---|---|
GLOBAL | GLOBAL score, GLOBAL score = 0, GLOBAL a, b$ |
LOCAL, inside a FUNCTION | LOCAL rest = 0 |
AS | lives AS integer, hero AS Player |
| a PureBasic suffix | speed.f = 1.5 |
DIM | DIM grid(10) |
NewList, NewMap | NewList names.s() |
| a FUNCTION parameter | FUNCTION Hit(damage) |
a CATCH variable | CATCH problem$ |
⚠️ OPTION EXPLICIT checks the names you write, not the names you read. A
misspelled name on the right of = still compiles, and stops the program when
it runs.
OPTION EXPLICIT
GLOBAL total = 0
total = totl + 1 ' compiles; stops when it runs: totl is not definedGLOBAL and LOCAL#
GLOBAL <name> [= <value>]
GLOBAL <name> AS <type> [= <value>]
GLOBAL <name>, <name> [, <name> ...]
LOCAL <name> [= <value>]
LOCAL <name> AS <type> [= <value>]
LOCAL <name>, <name> [, <name> ...]
GLOBAL DIM, LOCAL DIM, GLOBAL NewList and GLOBAL NewMap declare
collections the same way. PureBasic's Define and Protected are accepted as
LOCAL.
| Keyword | Where | Effect |
|---|---|---|
GLOBAL | anywhere, even inside a FUNCTION | one variable for the whole program, seen by every function |
LOCAL | inside a FUNCTION | a variable of that call only; it hides a program variable of the same name |
FUNCTION Setup()
GLOBAL best = 100
ENDFUNCTION
Setup()
Console(Str(best)) ' 100
⚠️ Give a single GLOBAL a value. GLOBAL hits alone starts empty, not at
0, and hits + 1 then gives NaN. The list form starts at 0 (or "").
GLOBAL hits
Console(Str(hits)) ' undefined, not 0
GLOBAL misses = 0
Console(Str(misses)) ' 0
GLOBAL a, b$
Console(Str(a) + "[" + b$ + "]") ' 0[]
⚠️ LOCAL belongs inside a FUNCTION. At the top of the program, a LOCAL
variable that is assigned again later breaks the program: it does not start.
Use GLOBAL or AS there. The same goes for Define.
LOCAL lives = 3
lives = 2Scope#
Blocks are not scopes#
A variable first assigned inside IF, FOR, WHILE or SELECT belongs to
the program. It is still there after the block. So is the FOR counter, one
step past the end.
IF 1 = 1
found = 42
ENDIF
Console(Str(found)) ' 42
FOR i = 1 TO 3
NEXT
Console(Str(i)) ' 4
Functions#
Inside a FUNCTION:
- Parameters and
LOCALnames belong to the call. - A
GLOBALname, or a name the program assigns above the function, is the program's variable. The function reads it and writes it. - Any other name assigned in the function is a new local variable.
score = 0
FUNCTION AddPoint()
score = score + 1
ENDFUNCTION
AddPoint()
AddPoint()
Console(Str(score)) ' 2
FUNCTION Bump()
LOCAL score = 99
score = score + 1
ENDFUNCTION score
score = 1
Console(Str(Bump()) + " " + Str(score)) ' 100 1
⚠️ The order of the lines matters. If the program assigns score only
below the function, the function makes its own score, and reading it
first stops the program.
FUNCTION AddPoint()
score = score + 1 ' a new local: score is assigned only below
ENDFUNCTION
score = 0
AddPoint() ' stops: Cannot access 'score' before initialization
⚠️ With DO … LOOP, functions do not see plain variables#
In a program with a frame loop, the setup lines above DO run inside the
loop's own function. A FUNCTION cannot see the variables they create, even when
it only reads them.
score = 10
FUNCTION Show()
Console(Str(score))
ENDFUNCTION
DO
Show() ' stops: score is not defined
Sync()
LOOP
The fix is GLOBAL. A GLOBAL variable is declared for the whole program,
whatever the shape of the program.
GLOBAL score = 10
FUNCTION Show()
Console(Str(score))
ENDFUNCTION
DO
Show() ' 10, then 11, 12…
score = score + 1
Sync()
LOOP
Constants have the same limit: see Constants and operators.
Declare with
GLOBALevery value a function shares with the program. It works in every program shape, and the order of the lines stops mattering.
@expose: a setting in the Inspector#
' @expose [range(<min>, <max>)]
[GLOBAL] <name>[<sigil>] [AS <type>] = <value>
A comment @expose on the line just above a top-level assignment turns that
variable into a setting. The Inspector shows it, and its value there
replaces the value written in the code when the program starts.
' @expose range(0, 20)
speed# = 5.5
' @expose
title$ = "Hero"
' @expose
lives = 3
' @expose
godMode = FALSE
Console(title$ + " " + Str(speed#) + " " + Str(lives) + " " + Str(godMode)) ' Hero 5.5 3 0
| The declaration | The setting | Read with |
|---|---|---|
a $ name, or AS string | text | PropString |
a # name, or AS float | decimal number | Prop |
TRUE or FALSE | on/off, stored as 1 or 0 | PropInteger |
| anything else | whole number | PropInteger |
- The setting is named after the variable, without its sigil:
speed#isspeed. range(a, b), or just(a, b), sets the limits shown in the Inspector.- The marker may be written with
',REM,//or a backtick. - Under
OPTION EXPLICIT, expose a declaration:GLOBAL lives = 3, orspeed AS float = 2.5.
⚠️ A decimal setting needs # or AS float. zoom AS double = 1.5 and
ratio = 0.75 become whole-number settings: they start at 1 and 0.
⚠️ The assignment must be at the top level. Inside a FUNCTION or a TYPE, the
marker is refused: @expose must be followed by a top-level assignment (name = value).
Coming from another BASIC#
| You expect | In aukimi |
|---|---|
x% = 2.7 stores 2 | stores 2.7: use x AS integer |
float is 64-bit | float is 32-bit: use double |
Long is 64-bit (Visual Basic) | long is 32-bit: use quad |
DIM x AS INTEGER | x AS integer: DIM without a size compiles, but the program does not start |
x.f (PureBasic) | accepted |
Define x = 1 at the top (PureBasic) | use GLOBAL x = 1 |
a $ variable read before use is "" | it is 0: assign it first |
OPTION EXPLICIT checks every name | it checks the names you write |