Records and methods
Group fields into TYPE records, copy them as values, reach their fields with WITH, and give them methods, a constructor and a destructor.
In one line#
TYPE declares a record: a group of named fields, each with its own type. A
record is a value, like a number: assigning it to a declared variable makes a
copy. METHOD adds functions that belong to the record, with SELF inside them.
TYPE Ship
name AS string
fuel AS integer
speed AS float
ENDTYPE
hero AS Ship
hero.name = "Comet"
hero.fuel = 80
hero.speed = 2.5
Console(hero.name + " " + Str(hero.fuel) + " " + Str(hero.speed)) ' Comet 80 2.5TYPE … ENDTYPE#
TYPE <Name>
<field>
…
[METHOD …]
ENDTYPE
| Field form | The field holds |
|---|---|
name | a 32-bit integer |
name$ | a string |
name# | a decimal number |
name AS <type> | that type: integer, float, string, uint8… or another record type |
name AS <type> [<last index>, …] | an array of that type |
name [<last index>, …] | an array of integers |
LIST name.<type>() | a list, see Collections |
MAP name.<type>([<slots>]) | a map |
ARRAY name.<type>(<last index>, …) | an array, declared the PureBasic way |
TYPE Player
name AS string
score
lives AS uint8
slots AS integer [3]
LIST items.s()
MAP counts.i()
ENDTYPE
p AS Player
p.name = "Ada"
p.score = 2.9
p.lives = 260
p.slots[3] = 7
AddElement(p.items())
p.items() = "Potion"
p.counts("Potion") = 3
Console(p.name + " " + Str(p.score) + " " + Str(p.lives) + " " + Str(p.slots[3])) ' Ada 2 4 7
Console(p.items() + " " + Str(p.counts("Potion"))) ' Potion 3
A field with no sigil and no
ASis a whole number.scoreabove stored 2.9 as 2. Positions and speeds needAS float(32-bit) orAS float64. AGK and DarkBASIC have the same default.
- A new record starts with 0 in numbers,
""in strings, and empty collections. - Writing a field converts the value to the field's type:
lives AS uint8turned 260 into 4. - Field names ignore case, and two fields with the same name are a compile error: "Duplicate BASIC field in Player: …". Two types with the same name are refused too.
- A field can use a record type declared later in the program.
- An unknown field type is a compile error: "Unknown BASIC field type".
- The closing word is
ENDTYPE.END TYPE(QBasic, VB) is refused:
TYPE Point
x
END TYPE
⚠️ Prefer AS to the sigils. A field written name$ keeps its type only
while the same name is never used without the $ anywhere in the program.
A variable called name elsewhere is enough: the field then becomes a whole
number, and storing text in it stops the program. name AS string has no such
problem.
⚠️ Avoid one-letter field names among b a w u l i q f d s c. These letters
are PureBasic type suffixes. Inside a method, SELF.w = 10 is read as "declare a
16-bit variable", and the field is never written. Name it width.
PureBasic's field suffix (x.f) is not accepted inside a TYPE. Write
x AS float.
The one-line form#
TYPE Point x AS float y AS float ENDTYPE
TYPE Hero name AS string hp AS integer bag AS integer [2] ENDTYPE
p AS Point
p.x = 1.5
h AS Hero
h.bag[2] = 4
Console(Str(p.x) + " " + Str(h.bag[2])) ' 1.5 4
The whole TYPE fits on one line when its fields are simple: a name, an optional
AS <type>, an optional [bounds]. LIST, MAP and ARRAY fields and methods need
the long form.
Records inside records#
A field can be a record. A TYPE can even be declared inside another one; its name is still usable everywhere.
TYPE Vec
x AS float
y AS float
ENDTYPE
TYPE Ship
pos AS Vec
vel AS Vec
ENDTYPE
s AS Ship
s.pos.x = 3
s.vel.y = -1.5
Console(Str(s.pos.x) + " " + Str(s.vel.y)) ' 3 -1.5
Array fields#
Index an array field with one pair of square brackets per dimension:
b.grid[1][2]. The other spellings compile and go wrong: b.grid(1, 2) stops
the program when it runs, and b.grid[1, 2] silently reaches another cell.
TYPE Board
ARRAY grid.i(2, 2)
marks AS integer [4]
ENDTYPE
b AS Board
b.grid[1][2] = 5
b.marks[4] = 1
Console(Str(b.grid[1][2]) + " " + Str(b.marks.length)) ' 5 4
The array members (.length, .insert, .sort()…) work on array fields too.
See Collections.
Declaring a record#
<name> AS <Type>
<name> AS <Type> = <record>
<name> AS <Type> = NEW <Type>(<arguments>)
<name> AS <Type>[<last index>]
DIM <name>(<last index>) AS <Type>
NewList <name>.<Type>()
NewMap <name>.<Type>()
- A record variable must be declared with
AS. Writingq.x = 1on a name never declared compiles, then stops the program: "q is not defined". - Type names ignore case:
hero AS shipis fine. - Every declaration makes a fresh record and runs its
Newmethod, if it has one. So does every cell of a record array, everyAddElementon a record list, every new map entry, and every record field inside a new record.
Reaching a field#
| Where the record is | Spelling |
|---|---|
| a variable | hero.fuel |
| a record inside a record | s.pos.x |
| an array cell | fleet(2).fuel or fleet[2].fuel |
| the current list element | foes().hp |
| a map entry | bag("sword").qty |
The backslash spelling hero\fuel belongs to WITH blocks. Outside one, it is
refused with "Not a BASIC statement".
⚠️ A write through a list element or a map entry is not converted to the field's
type: foes().hp = 3.7 keeps 3.7 in an integer field. Writes through a variable
or an array cell are converted.
Records are values#
TYPE Vec
x AS float
ENDTYPE
TYPE Ship
pos AS Vec
name AS string
ENDTYPE
a AS Ship
a.pos.x = 3
b AS Ship
b = a
b.pos.x = 9
Console(Str(a.pos.x) + " " + Str(b.pos.x)) ' 3 9
Assigning a record to a declared variable makes a deep copy. Nested records, arrays, lists and maps are copied with it. The two records are then independent.
The same copy happens for x AS T = other, for a list element (l() = rec) and
for a map entry (m("k") = rec). Passing a record to a FUNCTION copies it too,
unless the parameter is REF, see Functions.
⚠️ Three assignments do not copy, and share instead:
| Assignment | Result |
|---|---|
fleet(1) = hero, an array cell | the cell and hero are the same record |
s.pos = v, a record field | the field and v are the same record |
w = hero, with w never declared AS Ship | w and hero are the same record |
After any of these, a change through one name shows through the other. To store an independent copy in an array cell or a field, copy the fields one by one:
TYPE Vec
x AS float
y AS float
ENDTYPE
DIM path(1) AS Vec
v AS Vec
v.x = 1
v.y = 2
path(0).x = v.x
path(0).y = v.y
v.x = 7
Console(Str(path(0).x)) ' 1, the cell kept its own value
Comparing records. a = b on two records asks whether they are the same
record, not whether their fields are equal. Two separate records, even with
equal fields, are never =. Compare the fields you care about.
WITH#
WITH <record>
\<field> …
ENDWITH
TYPE Vec
x AS float
y AS float
ENDTYPE
TYPE Ship
pos AS Vec
name AS string
fuel AS integer
ENDTYPE
s AS Ship
WITH s
\name = "Comet"
\fuel = 80
\pos\x = 1.5
\pos.y = 2.5
Console(\name + " " + Str(\fuel) + " " + Str(\pos\x) + " " + Str(\pos\y)) ' Comet 80 1.5 2.5
ENDWITH
- Inside the block,
\fieldmeans<record>.field, anywhere on the line. - A chain is written
\pos\xor\pos.x. - The block closes with
ENDWITHorEND WITH. - The record can be any expression:
WITH fleet(i),WITH foes(). - Text in strings and comments is left alone:
"C:\games"stays a path. - WITH blocks do not nest. A WITH inside a WITH is a compile error: "Nested WITH blocks are not supported".
⚠️ The record expression is re-read at every \. The compiler writes the
expression in front of each field. With WITH fleet(i), changing i inside
the block moves the following lines to another cell.
WITH hero is not WITH SPRITE s. One word after WITH makes a record
block. Two words, where the first names a command family, make the command
shorthand: \position 100, 200 then calls SpritePosition(s, 100, 200). See
Shorthands.
METHOD#
TYPE <Name>
<fields>
METHOD <method>([<parameters>])
<statements>
[EXITMETHOD [<value>]]
ENDMETHOD
ENDTYPE
TYPE Counter
value AS integer
METHOD Add(amount AS integer)
SELF.value = SELF.value + amount
EXITMETHOD SELF.value
ENDMETHOD
METHOD Reset()
SELF.value = 0
ENDMETHOD
ENDTYPE
c AS Counter
Console(Str(c.Add(5))) ' 5
Console(Str(c.Add(3))) ' 8
c.Reset()
Console(Str(c.value)) ' 0
- A method is declared inside its TYPE, among the fields.
SELFis the record the method was called on. WritingSELF.valuechanges that record, also when it is an array cell or a list element:counters(2).Reset(),counters().Add(3).- Call it with
record.method(arguments), as a statement or inside an expression. Method names ignore case. EXITMETHODleaves the method.EXITMETHOD <value>returns a value.RETURN <value>does the same.- The parentheses are required, even empty:
METHOD Reset(). - A method is a FUNCTION underneath: typed parameters, default values,
LOCALvariables andOPTIONALall work. See Functions. - A method can call another one through
SELF:SELF.Reset(). - A copy of a record has the same methods as the original.
- In a program with a DO loop, a program variable that a method uses must be
declared
GLOBAL, as for any FUNCTION.
Refused at compile time: a METHOD outside a TYPE ("METHOD requires a containing
TYPE"), a METHOD inside a METHOD, two methods with the same name in one TYPE, and
a value after ENDMETHOD. Unlike ENDFUNCTION, ENDMETHOD takes no value, and
END METHOD is not a spelling:
TYPE Counter
value AS integer
METHOD Get()
ENDMETHOD SELF.value
ENDTYPE
⚠️ Do not name a method Length, Find, Sort, Insert or Remove. These
names belong to the array members, and the call stops the program when it runs.
New: the constructor#
A method named New runs every time a record of that type is created.
TYPE Counter
value AS integer
METHOD New(start AS integer = 0)
SELF.value = start
ENDMETHOD
ENDTYPE
a AS Counter
b AS Counter = NEW Counter(7)
Console(Str(a.value) + " " + Str(b.value)) ' 0 7
NEW <Type>(<arguments>)creates a record and passes the arguments toNew. It works in a declaration and in a later assignment:b = NEW Counter(2).- Every other creation calls
Newwith no argument:x AS T, each cell ofDIM a(n) AS T,AddElementon a record list, a new map entry, a record field of that type. - A copy does not run
New:b = aandx AS T = othercopy the fields as they are.
⚠️ Give every parameter of New a default value. Since most creations pass
no argument, a typed parameter without a default stops the program ("BASIC
integer requires an integral value"), and an untyped one leaves the field
without a value.
DELETE and the Delete method#
DELETE <record variable>
DELETE <array>[<index>]
DELETE <record>.<field>
alive = 0
TYPE Enemy
hp AS integer
METHOD New()
alive = alive + 1
ENDMETHOD
METHOD Delete()
alive = alive - 1
ENDMETHOD
ENDTYPE
e AS Enemy
f AS Enemy
Console(Str(alive)) ' 2
DELETE e
Console(Str(alive)) ' 1
DELETEcalls the record'sDeletemethod, if it has one, then marks the record as deleted.- On a variable,
DELETEalso empties it. Reading a field of it afterwards stops the program. - On an array cell or a field, the cell stays, but calling one of its methods
stops the program: "BASIC record has been deleted". The path takes square
brackets:
DELETE fleet[2].DELETE fleet(2)is refused. Deleteruns only throughDELETE. Reassigning a variable, or reaching the end of the program, does not call it.
PureBasic's Structure#
Structure … EndStructure is accepted as another spelling of TYPE … ENDTYPE,
one-line form included. See Shorthands.
Structure Size width height EndStructure
s AS Size
s.width = 2
Console(Str(s.width)) ' 2Coming from another BASIC#
| You may write | In aukimi |
|---|---|
Type … End Type | TYPE … ENDTYPE |
x.f in a Structure (PureBasic) | x AS float |
p\x outside a With (PureBasic) | p.x |
AllocateStructure (PureBasic) | p AS Point, or NEW Point() |
FreeStructure (PureBasic) | DELETE p |
END METHOD, ENDMETHOD value | ENDMETHOD, with EXITMETHOD value before it |
nested With | one WITH at a time |