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 DIM or VAR for 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.

SigilClassic 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$ gives 0 and "".
  • It picks what READ expects from a DATA line: text for $, a decimal number for #, an integer otherwise.
  • It types a record field. In a TYPE, a field with no sigil and no AS is an integer. See Records and methods.
  • It picks the kind of an exposed setting. See @expose below.
  • 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 Ada

Typed 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 at NULL. Some type names need the value: see the ⚠️ in the table below.
  • The type acts each time a value is stored: with =, with +=, -=, *=, /=, and with INC and DEC.
  • Arithmetic in between is not typed. h / 2 is computed in full precision; only the store converts it.
  • The name of a typed variable carries no sigil. name$ AS string compiles, but the program does not start. Write name AS string.
  • The array form AS <type>[<size>] is covered in Collections, and AS <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#

TypeAlso writtenRangeNotes
int8byte ⚠️, b−128 … 127
uint8ubyte, ascii, a ⚠️0 … 255
int16word, short, w−32 768 … 32 767
uint16uword, ushort, unicode, u ⚠️, c ⚠️0 … 65 535
int32integer, int ⚠️, long, i, l ⚠️−2 147 483 648 … 2 147 483 647long is 32-bit
uint32uint, uinteger, dword0 … 4 294 967 295
int64quad, q−2⁶³ … 2⁶³ − 1see 64-bit below
uint64uquad0 … 2⁶⁴ − 1see 64-bit below
float32float, single, f ⚠️about 7 significant digitsfloat is 32-bit
float64double, dabout 15 significant digitsthe precision of an untyped number
string ⚠️stextany value is turned into text
handlepointer ⚠️a reference (@variable)NULL by default
taskhandlejoba running taskNULL 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 intoRuleExample
an integer typethe decimals are dropped (toward zero)7.9 → 7, -7.9 → -7
an integer typeout of range wraps aroundint8: 127 + 1 → -128; uint16: -1 → 65535
an integer typetext made of digits is read"12" → 12
an integer typeother text stops the program"abc", "1.5" → BASIC integer requires an integral value
an integer typeinfinity stops the program1 / 0 → BASIC integer requires a finite value
float32rounded to the nearest 32-bit float0.1 → 0.10000000149011612
stringany value becomes text42 → "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")
FormWorks?
big = big + n, big += n, -=, *=, /=, INC, DECyes
big2 = big + other64 (only 64-bit values)yes
big = 5, big = "123"yes
big > 4, big < 10yes
⚠️ y = big + 1 (the result is not a 64-bit variable)stops: Cannot mix BigInt and other types
⚠️ other AS int64 = big * 3stops: same message
⚠️ IF big = 5never true: compare with another 64-bit variable
⚠️ big = big * 2 + 1wrong result: computed as big * (2 + 1); write big *= 2 then big += 1
⚠️ a literal above 9 007 199 254 740 991loses 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#

TypeWriteNote
integer types42, -7, 0x1F, 0c17, %1010a decimal value is cut: 7.9 → 7
64-bit42, or "9223372036854775807"text for anything above 2⁵³
float, double2.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.suffix declares the variable with that type, as if you wrote name 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.x on 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 // note is 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 byExample
an assignmentscore = 1, grid(1) = 4, hero.hp = 3 (the first name)
a FOR counterFOR i = 1 TO 10
INC / DECINC score
READREAD level

What counts as a declaration#

DeclarationExample
GLOBALGLOBAL score, GLOBAL score = 0, GLOBAL a, b$
LOCAL, inside a FUNCTIONLOCAL rest = 0
ASlives AS integer, hero AS Player
a PureBasic suffixspeed.f = 1.5
DIMDIM grid(10)
NewList, NewMapNewList names.s()
a FUNCTION parameterFUNCTION Hit(damage)
a CATCH variableCATCH 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 defined

GLOBAL 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.

KeywordWhereEffect
GLOBALanywhere, even inside a FUNCTIONone variable for the whole program, seen by every function
LOCALinside a FUNCTIONa 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 = 2

Scope#

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 LOCAL names belong to the call.
  • A GLOBAL name, 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 GLOBAL every 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 declarationThe settingRead with
a $ name, or AS stringtextPropString
a # name, or AS floatdecimal numberProp
TRUE or FALSEon/off, stored as 1 or 0PropInteger
anything elsewhole numberPropInteger
  • The setting is named after the variable, without its sigil: speed# is speed.
  • 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, or speed 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 expectIn aukimi
x% = 2.7 stores 2stores 2.7: use x AS integer
float is 64-bitfloat is 32-bit: use double
Long is 64-bit (Visual Basic)long is 32-bit: use quad
DIM x AS INTEGERx 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 nameit checks the names you write