Shorthands

Write less: how commands are named, WITH blocks on engine objects, dot notation, $"…" interpolation, and the PureBasic spellings the compiler accepts.

In one line#

A sprite's id is the first argument of every sprite command, and its name is inside every command name. The shorthands on this page remove that repetition. None of them is a new API: the compiler rewrites each one into ordinary BASIC (catalogue calls, Str() concatenations, FUNCTION, TYPE) before it compiles the program.

ShorthandYou writeThe compiler sees
WITH <family>\position 100, 200SpritePosition(s, 100, 200)
dot notations.position(100, 200)SpritePosition(s, 100, 200)
interpolation$"score: {points}"("score: " + Str(points))
PureBasic wordsProcedure … EndProcedureFUNCTION … ENDFUNCTION

How commands are named#

The two block shorthands rebuild command names, so they rely on one rule.

The object first, the property second, no Set or Get. SpritePosition, SpriteX, MusicVolume, CameraPosition.

Kind of commandSpellingExample
a property you write<Object><Property>(id, value…)SpritePosition(s, 10, 20)
a property you read<Object><Property>(id)SpriteWidth(s)
both, on one namethe argument count decidesSpriteX(s) reads, SpriteX(s, 10) writes
something comes into being or endsthe verb stays firstCreateSprite, LoadImage, CloneSprite, DeleteSprite

This is PureBasic's convention, not AGK's. AGK writes SetSpritePosition and GetSpriteX; here both are unknown commands, and a compile error. Drop the Set or the Get.

The exceptions, counted from the catalogue:

  • No command starts with Get.
  • 4 commands start with Set, because the bare name is already another command with the same number of arguments, so the count cannot tell them apart: SetRandomSeed (next to RandomSeed), SetMemblockString (next to MemblockString, which reads), SetCloudDataVariable and SetTerrainBiome.
  • 29 commands end with Set (not counting LSet and RSet, which pad strings). 18 are the Text writes: TextPositionSet, TextStringSet, TextXSet, TextSizeSet, TextVisibleSet and thirteen more, while TextX, TextString and TextSize read. The other eleven are SoundInstanceVolumeSet, SoundInstanceRateSet, MusicOggLoopCountSet, FilePositionSet, HttpHostSet, XmlNodeNameSet, XmlNodeTextSet, XmlAttributeSet, AtmosphereSet, WeatherSet and DayNightSet.

When in doubt, the command reference is generated from the catalogue and has a search.

WITH <family> blocks#

WITH <FAMILY> <expression>
  \<property> [<arguments>]      ' at the head of a statement: writes
  … \<property> …                ' anywhere else: reads
ENDWITH                          ' or END WITH
img = LoadImage("hero.png")
s = CreateSprite(img)
WITH SPRITE s
  \position 100, 200        ' SpritePosition(s, 100, 200)
  \size 64, 64              ' SpriteSize(s, 64, 64)
  \x 50                     ' SpriteX(s, 50)
  x# = \x                   ' SpriteX(s)
  \depth 5                  ' SpriteDepth(s, 5)
ENDWITH
Console(Str(x#))            ' 50

The place decides the meaning. At the head of a statement, \prop writes, and what follows it on the line are the arguments, without parentheses. Anywhere else, \prop reads. There is no second notation to learn.

  • The name is rebuilt, then looked up. WITH SPRITE plus \position gives SpritePosition. A property the catalogue does not have is a compile error on the spot: Unknown property in WITH SPRITE: \positon.
  • The expression can be anything: WITH SPRITE ships[3], WITH CAMERA 1.
  • WITH hero, a single word, is still the record block described in Records and methods. Two words, the first one a family, select the catalogue.
  • Lines that do not start with \ are ordinary statements.
  • No nesting. A WITH inside a WITH is refused, record or family.

⚠️ A write has no =. Write \x 50. The line \x = 50 is not refused, but the program it produces fails to start.

⚠️ A write must start the statement. In IF … THEN \visible 1, the \visible is not at the head, so it is read as a read, and the program fails to start. Use a block IF:

s = CreateSprite(0)
WITH SPRITE s
  IF \visible = 0
    \visible 1              ' SpriteVisible(s, 1)
  ENDIF
ENDWITH
s = CreateSprite(0)
WITH SPRITE s
  \positon 100, 200         ' refused: the family has no such property
ENDWITH

Which families exist#

A family is not a fixed list. Any word that starts catalogue command names is a family: Sprite because of SpriteX, Camera because of CameraPosition. The ones you will use most:

FamilySome propertiesA variable gets it from
SPRITEposition, x, y, size, angle, depth, visible, colorCreateSprite, LoadSprite, CloneSprite
TEXTreads: x, string, size; writes: positionSet, stringSet, sizeSetCreateText
OBJECT (3D)position, positionY, rotationLoadObject, or AS Object
CAMERAposition, rotation, xAS Camera
PARTICLESposition, frequency, life, sizeCreateParticles
IMAGEwidth, heightLoadImage
MUSICvolumeLoadMusic
EDITBOXposition, textCreateEditBox
MEMBLOCKsize, byte, int, floatCreateMemblock
DICTIONARYsize, hasCreateDictionary
VIRTUALBUTTON, VIRTUALJOYSTICKposition, sizeAS VirtualButton, AS VirtualJoystick
COMPUTEBUFFER, COMPUTESHADERsize, type, bufferCreateComputeBuffer, CreateComputeShader

⚠️ A few families are global, not per object: VideoPlaying() and VideoWidth() take no id, so a video variable has nothing to put in front of the dot.

In the editor, typing \ inside a WITH <family> block completes that family's properties.

Dot notation#

WITH removes the repetition inside a block. Dot notation removes it everywhere: the family is attached to the variable.

<var> = Create<Family>(…)        ' or Load<Family>(…), Clone<Family>(…)
<var> AS <Family>                ' when the id comes from elsewhere

<var>.<property>(<arguments>)    ' a call: writes, or reads with arguments
<var>.<property> = <value>       ' a write, as a whole statement
… <var>.<property> …             ' a read
img = LoadImage("hero.png")
s = CreateSprite(img)              ' s now belongs to the Sprite family
s.position(100, 200)               ' SpritePosition(s, 100, 200)
s.visible = 1                      ' SpriteVisible(s, 1)
s.x(s.x + 10)                      ' SpriteX(s, SpriteX(s) + 10)
Console(Str(s.x))                  ' 110
  • The family is deduced from Create<Family>(, Load<Family>( or Clone<Family>( when it is the whole right side of an assignment, or taken from an AS <Family> declaration.
  • Reads inside call arguments are rewritten too: s.position(s.x + 1, s.y) becomes SpritePosition(s, SpriteX(s) + 1, SpriteY(s)).
  • The family follows the variable's name through the whole program, FUNCTION bodies included.
  • An unknown property is a compile error: Unknown property on sprite: .positon.
  • Only properties. s.delete() is refused: write DeleteSprite(s).
RewrittenLeft alone
a variable whose family is knowna variable with no family
s.x, s.x(10), s.visible = 1a record field: hero.health
reads nested in call argumentsa list or map method: scores.insert(1)
SELF.field inside a METHOD
the inside of a string: "sprite.png"

⚠️ The = write copies its right side as written. In s.angle = s.angle + 5, the s.angle on the right is not rewritten, and the sprite receives something that is not a number. No error is shown. Use the call form, which rewrites its arguments:

s = CreateSprite(0)
s.angle(s.angle + 5)               ' SpriteAngle(s, SpriteAngle(s) + 5)
IF s.visible = 0 THEN s.visible(1) ' the call form also works on one line

⚠️ The = write must be a whole statement. After THEN on a one-line IF, use the call form, as above.

When the family is not deduced#

Some creation commands carry more than the family in their name: CreateObjectBox, CreatePointLight. They give the variable no family. Declare it first:

box AS Object
box = CreateObjectBox(1, 1, 1)
box.position(0, 2, 0)              ' ObjectPosition(box, 0, 2, 0)
Console(Str(box.positionY))        ' ObjectPositionY(box)

⚠️ CreateObjectSphere is worse: it gives the variable a wrong family (objectsphere), which wins over AS Object. Use the form that takes an id:

ball AS Object
ball = 1
CreateObjectSphere(ball, 1, 16, 16)
ball.position(0, 1, 0)             ' ObjectPosition(ball, 0, 1, 0)

A typed parameter is not recognised#

' refused: Unknown BASIC parameter type: Sprite
FUNCTION Nudge(s AS Sprite)
  s.x(s.x + 1)
ENDFUNCTION

A family is not a parameter type. Take the parameter untyped and declare its family on the first line of the body:

FUNCTION Nudge(spr)
  spr AS Sprite
  spr.x(spr.x + 1)                 ' SpriteX(spr, SpriteX(spr) + 1)
ENDFUNCTION

hero = CreateSprite(0)
Nudge(hero)

$"…" interpolation#

$"<text>{<expression>}<text>…"
{{  and  }}                      ' a literal { and }
points = 1250
name$ = "Ada"
lives = 3
Console($"{name$} has {lives} lives")      ' Ada has 3 lives
Console($"next life at {points + 750}")    ' next life at 2000
Console($"{{braces}} and {Len(name$)}")    ' {braces} and 3

Each hole becomes Str(expression), and the string becomes a concatenation. Str gives a string back unchanged, so a hole takes a number or a string.

The $ is mandatory, and it is not decoration. Many world commands take JSON written by hand, where the braces are data:

CreateTerrain("island", "{\"preset\":\"island\",\"size\":256}")

A string without $ is left untouched, whatever it contains. The $ must touch the opening quote.

  • A hole ends at the first }. It cannot contain a string literal: a " inside a hole ends the whole string, and the compiler reports an unclosed {. Put the text in a variable first.
  • A hole can hold a call: {Upper(name$)}, {Str(speed#, 2)}.
  • An empty hole {} or an unclosed { is a compile error. A lone } outside a hole is kept as is.
  • ⚠️ An AS float variable shows every digit of its 32-bit value: 0.1 prints as 0.10000000149011612. Round it in the hole: {Str(f, 2)}.
n = 1
Console($"oops {n")              ' refused: Unclosed { in interpolated string

AGK, DarkBASIC and PureBasic have no interpolation: they glue with + and Str(). The $"…" spelling is C#'s.

PureBasic spellings#

Keywords are case-insensitive, so many PureBasic lines already read as BASIC: Global, If … ElseIf … EndIf, Repeat … Until, Select … EndSelect, With … EndWith, ForEach, NewList v.i() (see Arrays, lists and maps) and the \field shorthand.

The words below are accepted too. Each one is an alias: it is rewritten into the construct the compiler already knows, so both spellings always behave the same.

PureBasicSame asNotes
Procedure name(…) … EndProcedureFUNCTION … ENDFUNCTIONa return type is AS <type> after the parentheses
ProcedureReturn [value]EXITFUNCTION [value]
Structure Name … EndStructureTYPE … ENDTYPEalso on one line: Structure Vec x y EndStructure
WendENDWHILE
Define, ProtectedLOCALuse them inside a Procedure
Debug(value)Console(value)with parentheses
x.f = 1.5x AS float then x = 1.5the type suffix on a variable, see below
Structure Vec
  x AS float
  y AS float
EndStructure

Procedure Twice(n AS integer) AS integer
  Protected result = n * 2
  ProcedureReturn result
EndProcedure

v AS Vec
v.x = 1.5
count.i = 0
While count < 3
  count = count + 1
Wend
Debug(Str(Twice(count)))           ' 6

The aliases work at the head of a statement only. structure = 3 is still a variable named structure, and Print("Procedure") prints the word.

The type suffix#

The first name.<suffix> declares the variable with that type. After that, write the bare name. The types themselves are described in Variables and types.

SuffixTypeSuffixType
.bint8.auint8
.wint16.u, .cuint16
.l, .iint32.qint64
.ffloat32.dfloat64
.sstring

⚠️ .i is 32 bits here. In 64-bit PureBasic it is 64 bits. Use .q for 64 bits.

What differs from PureBasic#

PureBasicIn aukimi
Procedure.i Twice(n)refused: write Procedure Twice(n) AS integer
EndProcedure valuerefused: ProcedureReturn value, then EndProcedure
x.f as a Structure fieldrefused: write x AS float
p.Point to declare a recordrefused: write p AS Point
Define.i a, brefused: write a.i = 0 and b.i = 0
n.i on a parameternot typed: write n AS integer
EnableExplicitOPTION EXPLICIT
Repeat … ForEverDO … LOOP, or REPEAT … UNTIL
Default in a SelectCASE DEFAULT. A bare Default is not recognised: it is not refused either, and the Select misbehaves at run time.
Shared, Staticrefused
; commentnot a comment here: write '

⚠️ At the top level of the program, prefer GLOBAL to Define. A variable declared with Define (or LOCAL) outside any Procedure, then assigned again, currently produces a program that fails to start. Inside a Procedure, Define and Protected are fine.

What is deliberately not adopted#

Two DarkBASIC habits and one PureBasic feature stay out, on purpose.

Commands without parentheses. PRINT "hello", SYNC, Debug x: the parentheses are what tells a command from a variable, and what lets the compiler catch a misspelt command.

PRINT "hello"                    ' refused: BASIC commands require parentheses

Commands of several words. DarkBASIC's MAKE OBJECT CUBE 1, 10 or SYNC ON are not statements here. The catalogue spells each command as one word: CreateObjectBox(1, 10, 10, 10).

MAKE OBJECT CUBE 1, 10           ' refused: Not a BASIC statement

Macro … EndMacro. A PureBasic macro pastes text at the call site. It is a preprocessor, not a keyword alias, and half of it would look like the real thing without behaving like it. Write a FUNCTION, or use the preprocessor for conditional code.

Macro Square(x)                  ' refused
  x * x
EndMacro

Limits and common snags#

  • SetSpritePosition and GetSpriteX do not exist. Drop the Set and the Get.
  • A WITH write has no =, and starts its statement.
  • A dot write with = does not rewrite its right side. Use s.x(s.x + 1).
  • CreateObjectBox and friends give no family. Declare AS Object first.
  • A family is not a parameter type. Declare it inside the function.
  • $ before the quote, or no interpolation at all. No string literal inside a hole.
  • .i is 32 bits.