PureBasic Survival Guide III - Primer I
PureBasic Survival Guide
a tutorial for using purebasic for windows 5.70 LTS

Part 0 - TOC
Part I - General
Part II - Converts
Part III - Primer I
Part IV - Primer II
Part V - Advanced
Part VI - 2D Graphics I
Part VII - 2D Graphics II
Part X - Assembly
Part XI - Debugger
Part XII - VirtualBox
Part XIII - Databases
Part XIV - Networking
Part XV - Regular Expressions
Part XVI - Application Data
Part XVII - DPI
Part XXVII - Irregular Expressions
Part XXIX - Projects
 

Part III - Primer I
v6.04 26.01.2019

3.0 Wanna'be a programmer
3.1 Hello world
3.2 Basic PureBasic
3.3 Variable (types)
3.4 Flow control
3.5 Procedures and variable scope
3.6 Constants
3.7 Peek, Poke, strings and bytes
3.8 Hex, Bin and Val
3.9 Boolean and binary
3.10 Arrays
3.11 Using strings


3.0 So... You wanna' be a programmer, huh?
 

First things first. Get the proper tools. You will need:

  • a computer and accessories (pretty obvious isn't it)
  • a decent working place (it's gonna' be long and lonely hours, so work comfortably)
  • the right software
  • proper documentation
  • an understanding spouse
This is not an extensive reference guide. It's intended to give newcomers a step by step introduction to PureBasic, and it might elaborate a little on the intricate art of programming, now and again... This is why the different subjects are presented to you in a certain sequence, where every part builds up on the previous. (At least, that was the idea when I started with this guide :-))

Because these pages have grown 'organic' they sometimes resemble a plate of spaghetti. Well, so does my code :-) If you're looking for the ultimate reference guide, this is the wrong place to be, though the TOC may be of some help. (I use it myself now and again, to be honest.)

If you want a downloadable version of this guide get it here. Note that it also may :-) contain a bunch of (hopefully) working .pb files, containing some of the samples you will find throughout these pages.

The online version will be more up to date though! I only update the archives once I've done a large number of changes.
 

Setup

First install all software and helpfiles and any tools you like. Then come back here.

Note: remember, use the tools YOU feel comfortable with. Although I am using certain tools in this primer, that doesn't stop you from using something else...


3.1 Hello world

It looks like the most basic (no pun intended) program in every (programming) language is the legendary 'hello world' program.
 

PB Ide

Once PureBasic is installed you'll find an icon with the name PureBasic. That's the regular editor for PureBasic. Once you doubleclick it, it's going to look something like this (from 4.xx on Windows XP using the 'classic' theme):

On the left side is the main code window, on the right side is a list of all procedures in your code. The button bar at the top provides you with a number of shortcuts. Obviously, the line numbers are there only to provide you an extra visual indication where you are in the code.

At first glance, from just looking at the IDE, not much has changed since the early days. You'd be wrong though... Here's the IDE from 5.11 (on Windows 7):

I must say it even looks sexier on a Mac, but I have no intention buying one, just for the looks :-)
 

jaPBe

jaPBe was my favourite editor until 4.xx. Although jaPBe offers (offered?) some unique functions, I now prefer the regular PB IDE.


Autocomplete

When you're entering code in the PB editor, they try to recognize what you enter and will show the syntax of the command you are entering at the bottom of the screen. You may have to enable some option to get this to work. (I love it, though I immediately changed the default [Tab] with [Enter].)


Hello world code

Enter the following little program:

OpenConsole()
PrintN("hello world")
Repeat
  Delay(1)
Until Inkey() <> ""
... and run it by pressing F5 or F6. The editor saves your source and starts the PureBasic compiler, the PB compiler translates this into machinecode and runs it.

Of course, this will compile so fast we won't see a thing. We'll deal with that later, it's the thought that counts :-)

With the debugger on you can start / stop code, and display results in a special window using the Debug statement. Obviously when you run code without the debugger there will be no 'real time' checks on certain errors, and you cannot use the Debug statement. By default, the debugger is on though.

Here's my work environment, a few years ago (before I got addicted to multi monitor setups and started messing around with my SqueezeBox)...
 

You may notice a few things when you look at the screenshot above...

1. You (the programmer) have to provide PureBasic with a place to output any information. In the example above, a 'console' (sort of Dos box) is opened, and the text 'hello world' is printed in there. You could also open a regular Windows window, but you cannot use the PrintN() command there but have to use a different set of commands. So, PrintN() outputs to a console that you first have to open with OpenConsole(). Aha...

2. The sample program contains a bogus loop. It's only there to show an important concept of programming under Windows. As Windows is (more or less) a multi tasking operating system, more than one program runs at the same time. When your program is not doing anything, it's best to give 'processing time' back to the system so other programs can use it. The Delay(1) instruction tells Windows to halt execution for 1 millisec and go and do something else.

3. And last but not least you may notice that (besides this being a very old screenshot of a very old version of PureBasic) I like to have some music in the background whilst working :-)

Do the following: in the sourcecode move the cursor to the word 'Delay' and press [F1]. A help on that keyword will pop up. Cool uh? Move the cursor on different keywords and see the results.

For PB IDE users: select a block of text, then hit [Ctrl]+[I] and the selected section will be reformatted.

For CodeCaddy users: move the cursor over a word and hit [Ctrl]+[F1] to look for that word in your code (depending on the paths you've set up in CodeCaddy). Try [Alt]+[R] to reformat all (!) your code.
 

Compiler vs. Interpreter

PB is a compiler. It first translates the sourcecode into machinecode before it executes it. This means that errors that the compiler does not spot can cause severe crashes. The debugger helps a little but is not flawless. Also, errors may be reported in the wrong line. (Hey, anybody can say C++?) The advantage of a compiler is, obviously, the final speed of the executable.

If you feel adventurous you can include assembly code directly into your source code, for that last little speed increase. (Pretty much an advanced topic that I've dabbled only a little in here.))
 

skipTrue beginners, true experts

Did you notice the skip and top symbols on these pages?

skip gives you the option to skip the next section, which may come in handy for beginners

top will jump back to the top of the page, where you will find a list of contents


3.2 Some very basic PureBasic
 

It's basic, Jim, but not as we know it.

PureBasic looks like basic, smells like basic, feels like basic, some even claim it tastes like basic. It just isn't. Then again, is VisualBasic a basic? Some aspects of the PureBasic syntax may come as a surprise to people that 'know' basic. Pay good attention to the help file.

For now, let's keep these things in mind:

  • if the first word of a line is not a variable, it's considered to be a command
  • the compiler does NOT spot all our errors
  • spaces don't mean much, most of the time
  • keep one command per line (not entirely necessary, but it keeps code so much more readable!)
Not entirely clear? It will be... after a while :-)
 

skipComputer programs

A program is nothing more but a list of instructions, telling the computer to do something. These programs are build up using three building blocks:

  • data, often stored in variables of different flavours
  • commands, used to tell PureBasic to do specific things
  • functions (or procedures) which are groups of instructions together that we can execute by calling their name
PureBasic programs are exectured left to right, top to bottom. Here's the example program again:
OpenConsole()
PrintN("hello world")
Repeat
  Delay(1)
Until Inkey() <> ""
And here is what it actually does:
  1. first of all it opens a 'Command Prompt' also known as a 'Dos Box'
  2. then it shows the words 'hello world' on the screen
  3. then it tells the computer to keep repeating the next section
  4. then it waits for 1 millisecond
  5. then it tells the computer to go back to the last 'Repeat' statement UNLESS a key has been pressed
Everything between the 'Repeat' and 'Until' instruction will be executed, time and time again, until the condition after the 'Until' statement is met.
 

Those Incredible ISzes..

(Ever seen MTV's 'The Max'? No? Go work on your education!)

In algebra, the '=' sign means something different than in a programming language like PureBasic. In algebra the '=' means that both expressions left and right of the '=' are equal. Not so in PureBasic, where it depends on the context.

When assigning a value to a variable, we 'store' everything right of the '=' character inside the variable. Other programming languages may do things different:

a = 5          PureBasic
a := 5         Pascal
a = 5          C
Rephrasing the above: put the value of 'five' (5) in the box called 'a'.

Of course, later on we want to control the flow of our program so we need to be able to compare two variables, or two expressions. In those cases we are not assigning the right side to the variable name on the left side, but we are COMPARING the right side with the left side, typically a comparison is preceded by the 'If' statement. Different languages do things differently:

a = 5          PureBasic
a = 5          Pascal
a == 5         C
Rephrasing the above: compare what's left of the '=' character with that on the right side. In PureBasic, this requires the keyword Bool(), If, While, etcetera.

PureBasic knows when it's an assignment and when it's a comparison, as comparisons only exist in combination with certain (conditional) instructions. Yep. Bool(), If, While, etcetera.
 

PureBasic is not C++. The following C++ style of programming DOES NOT work in PureBasic:

a = 5*(b=c)
If you're that desperate for such a coding style, try this:
a = 5* Bool(b=c)
Now if you still ask this question in fhe forums then we all know you haven't payed much attention to your homework here...


3.3 Variables (types)
 


skipNewbies

For the newbies...

a = 1
a = a+1
PrintN(Str(a))
'a' is a variable, a placeholder for a value. In this case, we store the numeric value 1 in it. In the next line, we add 1 to it, so a = a+1 = 1+1 = 2. Then, finally, we print it. The command PrintN() only accepts strings so we first have to convert the numeric value a to a string. (More about strings later...) Think about variables as named boxes containing data.

Unfortunately, if you try to run the code above, it will not work. Why? Because it can't output the result anywhere (compare it with the hello world example). To make this code run, we have to add a a few things:

OpenConsole()
;
a = 1
a = a+1
PrintN(Str(a))
;
Repeat
  Delay(1)
Until Inkey() <> ""
To make it easier to read, I've added some 'empty' lines. All information behind  a ';' semicolon is ignored by the compiler. It's a good place to store comments / documentation, and a few sprinkeld semicolon's will certainly make your code easier to read.
OpenConsole()                         ; let's open a console
;
a = 1                                 ; a = 1
a = a+1                               ; a = a+1 = 2
PrintN(Str(a))                        ; turn it into a string and output it to the console
;
Repeat                                ; keep on doing this
  Delay(1)                            ; give some time back to windows
Until Inkey() <> ""                   ; until a key has been hit

skipDebug

Why start with the Debug command when talking about variables? Simple. You need to understand what Debug dus, and how it is used in the examples that follow... However, some parts in the next section may be confusing if this is your first time around. No problem. Skip the parts you don't understand, continue, then come back later. If neccessary more than once :-)

(The full story on the Debugger you'll find here.)

The Debug statement can send output to the debugger. Of course, nothing is shown when the debugger is turned off, but you need to pay a little attention, or you could be in for some very hard-to-track-down bugs...

  • Code after a Debug statement is NOT executed when running a program with debugger off.
  • Variable assignments do not work when placed behind a Debug keyword.
First run the next code twice, with and without the debugger. Obviously nothing is shown with the debugger off...
a = 1 
a = a+1 
Debug a 

b.s = "123"
Debug's behaviour changed with newer versions of PureBasic...
; survival guide 3_2_110 debugger 
; pb 4.40b1 

OpenConsole()                         ; let's open a console 

a = 1
a = a+1 
Debug a                               ; this is fine
;
a = 1
Debug a+1                             ; compiles on 5.11
;
a = 1
Debug a = a+1                         ; works on older versions but throws an error on 5.11

Repeat                                ; keep on doing this 
  Delay(1)                            ; give some time back to windows 
Until Inkey() <> ""                   ; until a key has been hit
Well, that's enough delaying and avoiding for now. Let's face the real thing... variables.


Integers

Variables (aka. placeholders for values) can be of different types. Some are used for integer numbers, some for floating point numbers, some for strings, and all may be of different length.

Integers are whole numbers, no fractions. Valid integer numbers are -1, 256, 65536178. Fractions etc. are not allowed, such as 1/3 or 0.447234.

PureBasic recognizes a few different types of integers:

  • bytes, signed 8 bits, variable.b
  • bytes, unsigned 8 bits, variable.a
  • words, signed 16 bits, variable.w
  • words, unsigned 16 bits, variable.u
  • longs, signed 32 bits, variable.l
  • integers, signed 32 or 64 bits, depending on platform, variable.i
  • quads, signed 64 bits, variable.q
  • floats, 4 bytes, floating point, variable.f
  • double, 8 bytes, double precision floating point, variable.d
  • string, variable.s
  • fixed length string, variable.s{length}


In PureBasic the term 'integer' can be used for two different things: either a value without fractions, or a variable type.

(For the experts, chars .c and pointers * are also 'integers' .)
 

In PureBasic, most integers are signed. This means they can have values ranging from minus to plus. For example, the PureBasic type 'byte' can hold values ranging from -128 to +127, whilst in most other languages a 'byte' wll range from 0 to 255. It's an important difference that we will explore a little later. Also note that the length of certain types depend on the 'platform' you are using.
 

Platform: x86 versus x64

The term 'platform' is often used for the kind of processor / Windows your program will run on. The processors in older PC's would only handle 32 bits. Older versions of Windows were only available in 32 bits. Another term used for the older systems is x86, derived from the older CPU names 8086, 80286, 80386, 80486. Summarizing: x86 refers to 32 bits.

Newer processors (it all started with the AMD64) can handle 64 bits. You can still run 32 bits versions of Windows on them. If you do, you cannot programs designed for 64 bits. If you run a 64 bits version of Windows you can run both 32 bits and 64 bits software. The term x64 refers to (you guessed it) 64 bits. 64 bits offers more memory space, and is sometimes faster.

In other words: on 32 bits versions of Windows you can only use 32 bits aka. x86 software. On 64 bits versions of Windows you can use 32 (x86) AND 64 bits (x64) software.
 

Postfix

To specify what type a variable is, you give it a 'postfix', an extra dot plus letter that tells the compiler that that variable is now of that type.

; survival guide 3_3_120 integers
; pb 4.50rc2
;
a.l = 1        ; tells the compiler a is a signed long, 32 bits signed
b.w = 1        ; tells the compiler b is a signed word, 16 bits signed
c.b = 1        ; tells the compiler c is a signed byte, 8 bits signed
d.q = 1        ; tells the compiler d is a signed quad, 64 bits signed
e.i = 1        ; tells the compiler e is a signed integer, either 32 or 64 bits long, depending on platform
f.a = 1        ; tells the compiler f is an unsigned byte, 8 bits long
g.u = 1        ; tells the compiler g is an unsigned word, 16 bits long
h.c = 1        ; tells the compiler h is an unsigned integer, 16 bits long
Once you have told the compiler what the type is, you don't have to keep on telling it over and over again.
a.l = 1
a.l = a.l+1    ; this...
a = a+1        ; will do the same as this...
a+1            ; as well as this...
If you don't tell the compiler what type it is, it is automatically assumed to be a .i integer the first time you use it.
a.i = 1        ; explicitly tell the compiler a is an .i integer
b = 1          ; if not otherwise defined now or before b is going to be a .i integer
Once you have told the compiler a variable has a certain type, you cannot change it!
a.l = 1
a.b = 1        ; sorry, you cannot change the type on the fly
This is important to know, as in many basics you can use the same variable name for different types. You cannot in PureBasic.

In contrast with some other languages, you can mix and match any variable types in an expression without forcing type:

a.l = 1
b.w = 2
c.b = 3
d.l = a+b+c    ; 1+2+3 = 6
Note that mixing types could have an effect on the outcome! Experts, see here...
  • The default type is set to integer or .i, but that you can change that using the Define keyword. See the help file for more information.
  • The size of the .i type depends on the version of PureBasic you installed which again depends on the platform you use, ie. 32 bits or 64 bits.
  • In PureBasic the word 'integer' has two meanings: either the regular meaning 'whole number', or refering to any unsigned variable types.

Unsigned Integers

With 4.40b1 we have two additional types which are unsigned.

; survival guide 3_3_130 unsigned integers
; pb 4.40b1
;
a.b = 1                 ; a signed byte, 8 bits, holds -128 to +127
b.a = 1                 ; an unsigned byte, 8 bits, holds 0 to 255
c.w = 1                 ; a signed word, 16 bits, holds -32768 to +32767
d.u = 1                 ; an unsigned word, 16 bits, holds 0 to +65535
;
x.a = 247               ; 247 unsigned or -9 signed
Debug PeekB(@x)         ; returns -9
Debug PeekB(@x) & $FF   ; the pre 4.40b1 solution
Debug PeekA(@x)         ; finally, peeking an unsigned byte!
Signed and unsigned variables are stored in the same way in memory, it's the way they are interpreted that differs.

Confused? You don't have to be. Just keep in mind that PureBasic will take care of the type and the consequences. Use .b if you need to read or store a single memory location and you expect something in the range from -128 to +127, and use .a if you expect something in the range from 0 to +255, that's all there is to it.

Remember, a char .c is also unsigned, but its size depends on Unicode mode... In non-Unicode mode a .c equals .a, in Unicode mode a .c equals a .u...

(According to the PureBasic developers these are primarily aimed at easier processing of Ascii and Unicode characters in memory, hence the .a which stands for Ascii and refers to 8 bits, as well as the .u which stands for Unicode and refers to 16 bits. Personally, I would have preferred .ub and .uw but I have little to say in the matter :-))


Strings

A string is a bunch of bytes that form together a piece of text. It's one of the interesting characteristics of any 'Basic' variant. It's also part of your (?) beach outfit, and the ultimate focus of fanglez' attention. But stick to its meaning in PureBasic, it's better for your health...
 

Zero terminated strings

Regular PureBasic strings look like this:

a.s = "this is a test"
a$ = "this is another test"
To tell the compiler a variable is a string, you have two options at hand: either you use .s or $. There's something special about using $, as the symbol '$' it is considered part of the variable name. Take the following two examples:
a.s = "this is a test"
a.l = 1
Whoops! The above gives out an error. The variable a was first said to be a string, then suddenly changes to a long and that cannot be. The following sample is different:
a$ = "this is a test"
a.l = 1
That will work. Why? Because the variable a$ (the $ is part of the name, remember?) is NOT the same variable as a.

Strings can be added together:

a.s = "123"
b.s = "456"
c.s = a+b     ;  so c = "123456"
Regular strings are zero terminated and thus cannot contain a Chr(0) aka NULL character!
 

Pre 5.50 PuraBasic support Ascii mode and Unicode mode. As of 5.50 strings are always Unicode. More about that later, for now just remember that each character in a string takes up 2 bytes in Unicode.

You can (of course) manipulate strings, slice, dice, glue, trim, and more...


Fixed length strings

As of PB 4.00 there is a new type of string, that is allowed to contain zeroes, and that always occupies the same amount of space in memory. That may need a little clarification...

A fixed length strings always occupies the same space in memory, no matter what. So...

b.s{7} = "this is a test"           ; b.s has a length of 7 characters or 14 bytes, so it's contents are clipped to "this is"
PB trims all string data that would be too long for the fixed length string to the proper length.
b.s{7} = "1"                        ; now it would only contain 1 character "1" FOLLOWED BY a zero (!)
Verify this yourself... (If you use an older pre-5.50 version of PureBasic switch Unicode ON via the menu Compiler / Compiler Options / Create Unicode Executable before running this.)
b.s{7} = "this is a test"
Debug PeekB(@b+2)                   ; 104, that's the decimal value for 'h' from 'this is a test'
b.s{7} = "X"                        ; now the string would only contain 1 character "X" followed by a zero (!)
Debug PeekB(@b+2)                   ; and indeed, on that memory position there's a zero
Fixed length strings work well in structures that need constant length character fields. Fixed length strings don't work well with PokeS() due to the trailing zero that it, euh, 'pokes'... See also here. Update: PokeS() has some optoins now! 

All string functions still act on encountered zeroes, ie. for all string functions the fixed length string is more or less 'converted' into a normal string before processed, everything after the first encountered zero is simply ignored.

  • Fixed length strings can contain NULL characters (Ascii code 0)
  • Fixed length strings will be truncated on the first NULL (Ascii code 0) character when manipulated using regular PureBasic string functions.
The Unicode mode in Windows muddles things a little. See here for more details.

Fixed length strings come in handy if you want or need to store your strings inside structures or specific memory blocks. This makes it easier to deal with certain communication protocols, certain DLL's, or situations where you want to store string information INSIDE a structure itself.


Chars

Another newcomer since PB v4.00... chars. A char is the smallest part of a string. Sinnce 5.50 a char is always two bytes long and equivalent to an unsigned word.

The section on Unicode contains an example of how to create mode independent programs using the .c type. You can force unsigned bytes using the .a type, and unsigned words using the .u type.


Val() and Str()

You can convert strings to integers, and vice verse, using these two commands:

a.s = "123"   ; a = "123"
b.l = Val(a)  ; b = 123
c.s = Str(b)  ; c = "123"
These come in very handy. Some functions only accept strings, some other functions only accept integers. For example, the PrintN() command from above would only accept a string, so to print out a numeric variable we first convert it to a string before printing.
a.l = 123
b.s = Str(a)
Debug b.s
As of 4.20 Val() converts other types as well:
a.l = Val("16")
a.l = Val("%10000")
a.l = Val("$10")
See also ValD() and ValF() in the help file, as well as their counterparts StrD() and StrF() and StrU().

Need more conversions? Try the x_val() procedure further down, it does &H &O &0 &B % \ $ and 0X as well.


Floats and doubles

Integers are whole numbers. Sometimes you may need fractional or exponential numbers. For this purpose there are floats.

a.f = 1.34567892
As floats are, by default, NOT exacting numbers, you may run into surprises when rounding them or using them in certain calculations. However, you may have to use them for certain things. For even higher accuracy, you may use a variant of floats called doubles.
a.d = 1.34567892234723974
Floats and integers can be freely mixed, but there's one catch: when evaluating an expression or calculation, PureBasic may do some type conversions... see here.
a.l = 1
b.l = 2
c.f = a/b
As a small note, I'm not entirely sure the PureBasic implementation of mathematics is the best in the world. Oh well. Can't have all, can we?


Booleans

These don't exist in pure, at least not as a variable type. You can use any other integer type for it, and use the following logic: if the result of an expression (or the value of a variable) is zero it's false, if it is not zero then it is true.

  • zero is false
  • non-zero is true
See also the Bool() keyword.


Calculations (expressions)

You can do all sorts of calculations in PureBasic. Here are some examples:

a.f = 1/2     ; divide
b.l = 1+2     ; add
c.l = 1-2     ; subtract
d.l = 1*2     ; multiply
Use brackets and spaces if expressions get too complex:
a.f = ( b/2 ) + ( 22*c ) * ( q/z+12*2-1 )
Remember: stuff between brackets has priority.

Some special functions:

e.l = 10 % 3         ; modulo
f.l = Pow(2,2)       ; exponent, this is equivalent to 2^2
g.f = Sqr(5)         ; square root
a+1                  ; this is a special way of writing a = a+1
See also the help file included with PureBasic [F1] and look for the sections 'general libraries / math' and 'general topics / variables, types and operators'.

Use brackets liberally. See below or here for type conversion issues. Stick to the same type throughout and there are no issues.


Expression evaluation

PureBasic's way of evaluating an expression differs from other programming programming languages.

Which means? Well, that the outcome of an expression may not be the same as what you would expect. For example...

z.f = 0
a.l = 2/3 + 2/3 + z.f   ; this returns 0 
b.l = z.f + 2/3 + 2/3   ; this returns 1
;
Debug a
Debug b
Use the following rules:
  1. expressions are evaluated left to right 
  2. the type used depends on the variables, functions and keywords used 
  3. some keywords / operators will change the way an expression is evaluated 
  4. types 'change upwards' in this sequence: long > quad > float > double 
  5. types never change 'downwards' 
  6. the types 'byte' and 'word' are always converted to longs whilst evaluating an expression
I've added a separate section to discuss this in detail with a some examples, look here...


Swap

In the old days :-) you had to do this to swap two variables:

a = 1
b = 2
;
c = a
a = b
b = c
Now things are better :-)
Swap a , b
Remember you can only swap variables of the same (non-structured) type.


EnableExplicit and Define

The most often encountered 'bug' is probably the typo...

PureBasic can help reducing the number of typos if you start your code with the following keyword: EnableExplicit. When used PureBasic wants you to explicitly define variables, you can't just use them 'on the fly'.

Try to run the following program:

a = 32
a = a+1
This program will run without problems. PureBasic creates a variable 'a' of type Integer (that's the default PureBasic type for untyped variables) and stores '32' in it, then increases it with 1.

Now if we would make a typo, and misspell our variable name, we would have a bug. The following program would compile and run perfectly, it's just that the results would not be as expected...

a = 32
a = aa+1
EnableExplicit to the rescue. Once EnableExplicit has been encountered, the compiler will only accept variables that are declared in a Procedure() statement, or are declared using Define, Global, Local etc. Put otherwise, if EnableExplicit was used, the error above would be spotted. The following will not compile: we've told the compiler to check if we defined variables, and we haven't defined 'aa'...
; survival guide 3_3_150 enable explicit
; pb 4.40b1
;
EnableExplicit
;
Define a.i
;
a = 32
a = aa+1   ; this line should throw an error
... and thus we spot the bug, and correct it:
EnableExplicit
;
Define a.i
;
a = 32
a = a+1
The Define keyword has two other uses: it can be used to inform the compiler of an upcoming Procedure(). It can also be used to set the default type for all untyped variables:
a = 32     ; default type is long, so it's a.l
;
Define.f   ; we change the default type to float...
;
b = 2.7    ; so now it's b.f


Variable browser

Explore the IDE. It has a build-in variable browser that shows you a list of all variables, constants, structures etc. in use...


3.4 Flow control
 

It would be a bit boring if a program would work like a calculator: you start at point a, go to point b, do some calculations on the way and that's it. We want to jump forward, do things conditionally, repeat some other things, and in general enjoy ourselves letting the computer running around in circles. (Sometimes around us...)

That can be done. Just like most other programming languages PureBasic contains a number of keywords dealing with flow control.


If / Endif

Here's the first one, a conditional statement. If the condition is true, a certain part of the code is executed...

a.l = 1                        ; this is an assignment, we store 1 in a
If a = 1                       ; this is an evaluation, we compare a with 1
  Debug "ah, a is 1"
Endif
In PureBasic it is not allowed to make an evaluation part of an expression!

Sorry. PureBasic is not C(++). The following MAY work but is NOT supported. See also here. Just don't do it. (I've been repeating this throughout this Survival Guide, as it is one of the most asked questions on the forum.)

a.l = 100 + (b = c) * 100     ; this is NOT supported!

If / ElseIf / Else / Endif

And what else? Else indeed!

a.l = 2
If a = 1
 Debug "ah, a is 1"
Else
  Debug "ok, so a is not 1"
Endif
And there's ElseIf as well...
a.l = 1
If a = 1
  Debug "a is 1"
ElseIf a = 2
  Debug "a is 2"
Else
  Debug "a is something else"
Endif
As may be clear by now, I do not like multiple statements on a line. In several basic dialects you find the following construction:
If a = 1 Then b = 1       ; doesn't work in pureBasic
In PureBasic you could do it like this:
If a = 1 : b = 1 : EndIf  ; works in purebasic but i'm not sure i like it
That line above does the same as:
If a = 1                  ; the 'en vogue' way of doing it :-)
  b = 1
Endif

Select / Case / Endselect

If we want to test a single variable that can have a few values, we use the Select statement:

a = 1
Select a
  Case 1
    Debug "a is 1"
  Case 2
    Debug "a is 2"
  Default
    Debug "a is something else"
Endselect
Or we could do strings...
a.s = "appel"
Select a
Case "appel"
  ...
Case "peer"
  ....
EndSelect
Cool! Starting with PB v4.00 we can do multiple cases and even ranges!
a = 1
Select a
  ...
Case 1,2
  ...
Case 3 To 5
  ...
EndSelect

For / Next

Want to repeat something 5 times?

For n = 1 to 5
  Debug n
Next n
ForEach is a special case that only applies to linked lists.

PureBasic is different. Which means you may not always be able to do things the 'standard' way (as if there is a standard, hmpf). Anyway, in PureBasic there are two limitations to the For / Next loop that are not that obvious if you come from another Basic variant.

  • For / Next only works with integers!
  • if you use a 'Step' value, it has to be either a constant or a fixed number, not a variable
    For n = 1 to 5 Step 2
      Debug n
    Next n
Now don't start whining. Have a good look at While / Wend. Here's how NOT to do it in Pure:
For n.f = 1.1 to 5.2 Step 0.3      ; not going to work
  Debug n
Next n
And here's how to do it with While / Wend:
n.f = 1.1
While n.f <= 5.2
  Debug n
  n.f = n.f+0.3
Wend
I think For / Next is highly overrated and in many cases While / Wend or Repeat / Until make more sense. Here's a quick and unfair :-) comparison (I know there are other ways to exit a loop):
  • For / Next - fixed steps, only one exit condition, unconditional start of the loop
  • Repeat / Until - variable steps, multiple exit conditions, unconditional start of the loop (always runs 1 time)
  • While / Wend - variable steps, multiple exit conditions, conditional start of the loop

Repeat / Until

Want to repeat something until a condition is met?

n = 0
Repeat
  n = n+1
  Debug n
Until n = 10
Debug "done"
A variant of Repeat / Until is Repeat / Forever.


While / Wend

Want to execute something if a condition has been met, and continue as long as the condition is being met?

n = 0
While n < 5
  n = n+1
  Debug n
Wend
I personally prefer to use While / Wend over Repeat / Until. It tends to keep complex code a little more readable (well, sometimes, not always) but more importantly, I belief it simplifies debugging. With a While / Wend the conditions are set BEFORE executing / entering the loop. But, as usual, it depends.


Nesting

The above will bring us to another important aspect: nesting. We can nest conditions as deep as we like, for example with If commands...

a = 1
b = 2
c = 3
If a = 1
  If b = 2
    If c = 3
      Debug "yeah baby!"
    Endif
  Endif
Endif
Or for example some For / Next commands...
For a = 1 to 5
  For b = 1 to 5
    Debug a
    Debug b
  Next b
Next a
You may want to format your code a little, proper indenting makes it a lot easier to read. Visit the forum for some of the tools that can (re)format your code, or mark the section of your code you want to reformat and press [Alt] + [I].


No stepping or breaking...

In many cases While / Wend will do a better job than a For / Next. Which statement to use where is more of a mindset than a language implementation. If you need a quick and dirty and unconditional loop use a For / Next. Eventually with a Step. I use For / Next to quickly test something, or for running (small) unconditional loops.

b.l = 0
For a.l = 1 to 10 Step 2
  b = b*a
Next a
One of the statements I've pretty much never ever use is Break. It's a mindset thing just like Step. If you can live without it your coding life may become easier. It may also NOT become any easier, after all it's your own choice :-)

Try to rewrite the above to break out of the loop when b.l goes over 100:

b.l = 1
For a.l = 1 To 10 Step 2
  b = b*a
  If b > 100
    Break
  EndIf
Next a
And compare it with this:
b.l = 1
a.l = 1
While a <= 10 And b < 100
  b = b*a
  a+2
Wend
The reason why I don't like Break is that it's sometimes unclear what the Break belongs to. At least to me it is :-) It's your call.

There's one situation, however, where you absolutely need Break: when doing a Repeat / Forever.


Evaluations

Normally, we 'assign' a value to a variable, as in:

a.l = 1
When assigning, you'll only find a single variable name left of the '=' sign, while on the right side there is a (complex) calculation or 'expression'.

When using conditional statements, we are 'evaluating' an 'expression'. In other words, we check if something is true, or not true. In some more other words, we compare the 'expression' left of the '=' sign with the expression right of the '=' sign.

a.l = 1
If a = 1
  Debug "told you so"
Endif
In these 'conditional' cases, left and right of the '=' sign there can be a full (complex) expression:
a.l = 1
b.l = 2
If ( a-2+1-3 ) = ( b-a*2 )
  Debug "not a clue"
Endif
We could check if things are the same, but also if they're not, or if they're larger or smaller:
; survival guide 3_4_150 evaluations
; pb 4.40b1
;
a.l = 1
b.l = 2
;
If a > b
  Debug "a is larger than b"
Elseif a < b
  Debug "a is smaller than b"
Endif
If a >= b
  Debug "a is larger than or equal to b"
Elseif a <= b
  Debug "a is smaller than or equal to b"
Endif
;
If a <> b
  Debug "but it is definitely not the same as b"
Endif
PureBasic is not C, so assigning the result of an evaluation to a variable is not allowed, see also the Bool() command.
a = (b <> c)      ; this is soooo wrong
a = Bool(b <> c)  ; that's the way to do it

3.5 Procedures and variable scope
 

Defining procedures.

Get ready for one of the most essential parts of PureBasic: procedures.

A procedure is a small block of code that does something, can be called with parameters, and might even return a result to us. It allows us to reuse parts of our code, and keeps things organized and readable. Reasons enough to use them then :-)

Here's sample code: (I've added line numbers to explain how things work, don't copy them if you want to try out this code.)
 
1
2
3
Procedure sample(a.l)
  Debug a.l
Endprocedure

Now, what does it do? From top to bottom...

1... first, we tell the compiler we're going to create a procedure with the name 'sample'...
1... then we tell the compiler this procedure has one parameter called 'a'...
1... and that parameter is a long integer 'a.l'...
2... all the procedure does is show the value of that integer in the Debug window
3... and continue the regular program via EndProcedure
What will happen when we run the code above? Nothing.

Why not? Because we have told PureBasic we have a procedure, but we never call it. It sits there ready for us, but we don't use it. We forgot to 'call' our procedure
 

Calling procedures

Here's how we call the procedure:

Procedure sample(a.l)
  Debug a.l
Endprocedure
;
sample(1)
Check that last line. It's not a regular keyword such as PrintN() so PureBasic knows it has to be a procedure. PureBasic looks back to see if it already saw this name, and if so it will call it with the given parameter.

You can use as many parameters as you like:

Procedure sample3(a.l,b.l,c.l,d.l,e.l)
  Debug a
  Debug b
  Debug c
  Debug d
  Debug e
EndProcedure
;
sample3(1,2,3,4,5)
Obviously, we can reuse the procedure as many times as we like.
Procedure sample(a.l)
  Debug a.l
Endprocedure
;
For n = 1 to 5
  sample(n)
Next n
In the code above we call the procedure 5 times. As you can see, we can either pass values (as '1') or variable names (as 'n') as parameters. We also notice something else: inside the procedure we use a different name than inside our main code. Is this correct? We didn't get an error...

In fact, yes. This is correct. These are local variables, that only exist inside the procedure. More on that a little later, first we deal with a great feature that arrived with PureBasic v4.00... optional procedure parameters! Yes!


Optional parameters and default values

In the procedure definition you normally specify how many parameters of what type are expected. For example:

Procedure sample(a.l,b.l,c.l)
  Debug a.l
  Debug b.l
  Debug c.l
Endprocedure
;
Sample(1,2,3)
Starting with PB v4.00 we now can assign default values to each parameter, and this effectively also means they have become optional!
Procedure sample(a.l,b.l,c.l,d.l=4)
  Debug a.l
  Debug b.l
  Debug c.l
  Debug d.l
Endprocedure
;
Sample(1,2,3)
Sample(1,2,3,7)

Local vs. global

All variable names WITHIN a procedure are considered local UNLESS we have told the compiler otherwise. That is, variables only exist within the procedure. They will not affect any variables with the same name defined elsewhere. A sample makes this even more clear.
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Global c.l
;
Procedure sample2()
  b.l = 5
  a.l = 5
  c.l = 5
EndProcedure
;
a.l = 1
b.l = 1
c.l = 1
;
sample2()
;
Debug a
Debug b
Debug c

Let's step this through...

1... we start by telling the compiler variable c is a global variable, that is it exists inside all procedures everywhere
3... then we create a procedure, in this case the procedure takes no parameters...
4... inside the procedure, we change the values of a, b and c
9... then comes the normal code, first we assign certain values to a, b and c...
13... and call the procedure...
15... and display the results
What we will notice is that c has changed after calling the procedure whilst a and b still have their original value. Why? Because the variables a and b INSIDE the procedure have nothing to do with the variables outside the procedure. They are local. You can create, change and destroy them at wish within the procedure as they are effectively different variables. Then, when we exit the procedure, the original a and b are restored to their old values.

And c? Well, we told the compiler it was a global variable. So if something inside the procedure does something to c that will show outside the procedure as well.

One more example:
 
1
2
3
4
5
6
7
8
9
10
a.s = "this is a string"
;
Procedure sample5(b)
  Debug b
  a.l = 10
EndProcedure
;
x = 12
sample5(x)
Debug "done"

Let's break this one down as well...

1... we start by defining a string variable called 'a' and fill it...
3... then we define the procedure...
8... we assign 12 to x...
9... and call the procedure with (the value of) x as a parameter.
When we run it the following will happen...
1... a.s is filled with the text...
8... x is filled with 12...
9... the procedure is called
9.. and the value of x (12) is passed on as the first parameter
3... the procedure has as a first parameter 'b', which becomes 12
4... we display it...
5... then create a new variable a.l and store 10 in it...
6... and end the procedure to return where we were when we called it
10... and we're "done"
You see, the string variable a.s only exists outside the procedure whilst a.l only exists within. They're both local, so the compiler does not generate an error. It would if, for example, they would both exist inside the same procedure, as then we would try to change the type of a variable (a variable cannot be a string and an integer at the same time, it's not a politician :-)).

Don't forget: you have to declare a variable as global before using it at all, you can't change something later down the line from local to global... (For the experts: PureBasic is a single pass compiler.)

As of PB 4.xx arrays and lists defined inside the procedure itself are considered local and protected within the procedure.


Protected, Shared, Static, Global, Threaded

There's more to local and global variables, you can also use the Protected, Static and Shared keywords. A short list:

  • every normal variable is considered local unless otherwise specified
  • as of PB v4.00 linked lists and arrays have scope, ie. are by default local
  • all local variables are reset to zero every time the procedure is called unless the Static keyword is used
  • Global makes a variable global, accessible from within any procedure
  • Shared allows access to a variable from within a procedure if it wasn't defined global yet (I'd suggest not to use it)
  • Protected makes sure a locally declared variable does not interfere with a global declared variable with the same name
  • Static makes a local variable retain it's value when returning to the procedure
  • Threaded is a form of Global that is exclusive to threads.
As of PB 4.00 we are now allowed to immediately assign a value to a variable behind each of these keywords:
Global b = 5
;
Procedure Dummy()
  Static a = 2
  Protected c = 3
EndProcedure

Protected

(Also known as 'local' in other languages.) A 'protected' variable is a local variable that will not exist outside the procedure, nor will it be affected if another variable with the same name is declared global elsewhere. Every time the instruction is encountered in combination with an assignment, the variable will change value, ie.

Protected a.l                   ; doesn't change the value of a.l
Protected a.l = 2               ; assigns 2 to a.l on every execution
Local (protected) variables are, when not otherwise specified so, set to zero upon creation.


Global

A 'global' variable can be changed from anywhere in the program, except under the following conditions:

  • another variable with the same name is declared Protected or Static inside a procedure
  • the same variable name was used as a parameter in a procedure definition
In code:
Global a.l                   ; doesn't change the value of a.l
Global a.l = 2               ; assigns 2 to a.l on every execution
If you want each thread to have its own global vairables use the Threaded keyword.


Shared

If a variable is declared ouside the procedures and not declared global, a procedure can access it by declaring it shared from within the procedure.

a.l = 5
;
Procedure test()
  Shared a
  Debug a
EndProcedure
I would advise against using this keyword. It's a good starting point for hard to find and fix bugs...


Static

Static is a very interesting variant that only since PB v4.00 makes much sense... Ahum. That's not totally honest, let's say it now makes more sense :-)

Static variables exist only within the procedure, and not outside. However, upon every execution they retain their previous value. Code shows this better:

Procedure test()
  Static a.l = 1
  a = a+1
  Debug a
EndProcedure
;
test()
test()
When the procedure is first time ever run, it will assign 1 to a.l, if ever executed again the line Static a.l = 1 wll no longer affect the value of the variable. Try the code above, it will output 2 and 3.

Why not use globals in those cases where you want to 'remember' certain values inside your procedure? Because the static variable is 'invisible' the the rest of your program, it only exists inside your procedure, and because it is intialised upon the first call of the procedure.
 

Nesting

Variables are always local to the procedure they are defined in. Here's one more example, and this time we nest the procedure calls...
 
1
2
3
4
5
6
7
8
9
10
11
12
13
Procedure sample6()
  a.l = 2
EndProcedure
;
Procedure sample7()
  a.l = 3
  sample6()
  Debug a.l
Endprocedure
;
a.l = 1
sample7()
Debug a.l
When run:

1... nothing happens here, all procedure definitions...
11... ah, things happening!
11... first a.l becomes 1...
12... then sample7() is called
6... inside sample7() a new a.l is created with the value of 3
7... now sample6() is called...
2... and inside sample6() a new a.l is created with the value 2
3... sample6() ends so it returns to the line after the call
8... we return to sample7() and then display a.l...
8... our own local a.l which has still the value of 3 (!)
9... the EndProcedure in sample7() sends us back to our main loop
13... where we continue and display our initial a.l (which is still 1)
Although not explicitly mentioned, the above shows another very important aspect... the correct sequence of defining and calling.

YOU HAVE TO DEFINE SOMETHING BEFORE YOU CAN USE IT.

If you want to use a procedure in your code, you have to define it first before you can call it later. Logical, isn't it?


Returning values

You cannot only pass a value to a procedure, a procedure can pass a value back as well. A simple example shows it best:

Procedure.l up(z.l)
  z = z+1
  ProcedureReturn z
EndProcedure
You can see that we added a 'postfix' to the keyword Procedure. What type of variable the procedure returns is defined this way, just like we define a regular variable type. To return a long integer we use Procedure.l, for strings we use Procedure.s etcetera.

Actually passing back the variable is done using the ProcedureReturn statement. In the example above we return the value of 'z'.

In contrast with some other languages, the statement ProcedureReturn may exist anywhere in the procedure, and will immediately terminate the execution of the procedure and 'empties' any stuff on the stack... Forget about those terms, here's a good example:

; survival guide 3_5_150 returning values
; pb 4.40b1
;
Procedure.l gfastyle(z.l)
  If z > 10
    z = z-1
  Else
    z = z+1
  Endif
  ProcedureReturn z
Endprocedure
;
Procedure.l purestyle(z.l)
  If z > 10
    ProcedureReturn z-1
  Else
    ProcedureReturn z+1
  Endif
Endprocedure
Both procedures are correct and valid and will work.


skipPassing arrays, lists and maps as procedure parameters

Ouch. We haven't touched arrays or lists yet... Well, I could move this section (but I've never been too organized myself :-)) so I'll just give you the opions...

As of PB v4.00 arrays and lists are no longer automatically global. If you want to access them from within a procedure, you either have to define them as global or you have to pass them as a parameter during the procedure call.

However, be aware, you are not passing the full contents of the array to the procedure, but you are telling PureBasic which array to use. In other words, any change you make to the array from inside the procedure is affecting the array outside! Another way to think of this is that you're using a different name for the same array inside the procedure. This is called 'by reference'.

The example below shows using arrays and lists as parameters in procedure calls.

Note that as of 4.30 (or earlier, I might have missed this in earlier incarnations of the Survival Guide) you have to add some information to the procedure definition and the procedure call.

  • Preceed the procedure parameter in the Procedure definition with the keyword List, Array or Map.
  • Specify in the Procedure definition the number of dimensions of the array being passed.
Like this:
; survival guide 3_5_200 passing arrays maps lists
; pb 4.40b1
;
OpenConsole()
;
; try a list as a parameter
; to identify a parameter as a list we preceed it with the keyword List and specify variable type or structure
;
Procedure test_list(List y.l()) 
  SelectElement(y(),0)             ; select element 0
  y() = 2                          ; and overwrite its old value (was 1, now became 2)
EndProcedure
;
NewList x.l()                      ; create a list of longs
AddElement(x())                    ; so we added an element 0
x() = 1                            ; and store 1 in it
test_list(x())                     ; call the procedure
SelectElement(x(),0)               ; select element 0
PrintN("list: "+Str(x()))          ; and show its contents
;
; same thing for an array
; to identify a parameter as an array preceed it by the keyword Array, specify variable type or structure, and specify its dimensions
;
Procedure test_array(Array q.l(1))
  q(1) = 2                         ; store 2 in position 1
EndProcedure
;
Dim p.l(10)                        ; create an array of 10 positions
p(1) = 1                           ; store 1 in position 1
test_array(p())                    ; call the procedure
PrintN("array: "+Str(p(1)))        ; and show the contents of position 1
;
; same thing for a map
; to identify a parameter as a map preceed it by the keyword Map and specify variable type or structure
;
Procedure test_map(Map m.s()) 
  m("1") = "2"
EndProcedure
;
NewMap n.s()                       ; create a new map
n("1") = "1"                       ; store "1" in field "1"
test_map(n())                      ; call the procedure
PrintN("map: "+n("1"))             ; and show the contents of field "1"
;
Input()                            ; just hit enter to exit the program
When passing an array as a parameter, you have to specify the number of indices. In the example above the array had one dimension, so in the procedure definition it says '1'. For example:
Dim a(10)
Procedure test_a(q(1))
...
Dim b(10,10)
Procedure test_b(q(2))
...
Dim c(10,10,10)
Procedure test_c(q(3))

3.6 Constants
 

With all that easy stuff behind us, it's time to add some more serious elements to the mix. First constants. A constant is a variable that cannot change, hence the name constant :-)

(There's another difference: a constant is used 'by value', that is every time you use a constant, the compiler is actually inserting the value of the constant in the code, while when you use a variable, it is using the location where the variable is stored and takes the value from that location. But... forget it, just remember that constants are things that never change once you defined them.)

a.l = #True             ; which is by default 1
b.l = #False            ; which is by default 0
;
If a <> b
  Debug "a <> b ... this is very likely"
EndIf
You cannot change a constant and you don't have to define its type. If you try to change a constant, the compiler will give an error.

Constants are often used in combination with (external) libraries, and can store parameters, options, etc. In PureBasic there are many constants already pre-defined, for PureBasic itself as well as for Windows. (See also here.)

PureBasic has a great feature that helps you with 'automatic increasing numbers' for constants called Enumeration.


3.7 Peek, poke, strings and bytes
 

No. We're not going to do anything difficult yet. You'll have to wait for stuff with pointers and structures... We're only going to touch some small things, don't worry if you get it all now, we'll get back to this when it gets more important. (If you want you can skip this paragraph for now and come back to it later.)

First, the warning:

WHEN YOU READ FROM OR WRITE TO THE WRONG PLACE IN MEMORY, YOUR PROGRAM AND / OR COMPUTER MAY CRASH. SO BE CAREFUL.

That's why Windows XP and everything later are better platforms to work on... Even if a program crashes, the whole machine may stay up and running (not always though)... Under Windows 98 you had to reboot a lot...

(C++ adepts and Basic haters: peek and poke have nothing to do with bad programming, they are just a way of writing to and reading from memory. Although I'd personally wouldn't mind a different syntax or some alternatives... Then again, you can always use pointers :-))


Strings

First some code:

a.s = "test"                 ; a is a string
b.l = @a                     ; where is it in memory
Debug b                      ; show the address
PokeS(b,"1234")              ; overwrite that memory with new information
Debug a                      ; show the new value of a
We start creating a string called a. Then we find the place where the string is located in memory using the '@' sign. We then overwrite that part of memory with new information and finally display it.

Some more code, now reading directly from memory...

a.s = "test"                 ; a$ is a string
Debug PeekS(@a)              ; show it
We create a string, then display what's at its location in memory.

PokeS(), PeekS() etc. automatically adjust to the Unicode mode we're in, unless we specify how to they should operate by adding a specific flag. (Check here for more details.) You can always check how many bytes a string actually occupies (excluding any trailing zeroes, we're typically talking about zero terminated strings) using StringByteLength(). (There's also fixed length strings, see here.)

So far, so good. There are a few things with strings we have to be careful of...


Zero terminated strings

PureBasic uses something called 'zero terminated strings'. That means a string (in memory) always ends with a zero. So when you tell PureBasic that a string is 4 characters long and contains "test", it is stored into memory as TEN bytes, each of the four characters takes 2 bytes (totalling 8) plus another two bytes for the 'terminating zero'. Note that, in Unicode, each character occupies TWO bytes.(More on strings in memory.)

Normally you won't notice. When you're dealing with PeekS() and PokeS(), however, you need to pay some attention. 

PeekS reads data from the memory and returns it in a string. It keeps on reading until it hits a zero, unless you specify a maximal length.

a.s = "test"                 ; a$ is a string
Debug PeekS(@a)              ; read and show it
Debug PeekS(@a,2)            ; read and show it, but don't read more than two characters
PokeS is the risky sibling to PeekS. It writes a string into memory. You can specify a length, BUT... it will always write the terminating zero!
; survival guide 3_7_110 zero terminated strings
; pb 5.70 LTS
;
a.s = "1234567890"                   ; a.s is a string, 10 characters long, followed by a zero
Debug Len(a)                         ; length in characters, 10
Debug StringByteLength(a)            ; length in bytes EXCLUDING terminating zero, 20
Debug a
Debug ""
;
PokeS(@a,"abcd")                     ; write 4 characters in memory, followed by a zero, ie. 10 bytes in total
Debug Len(a)                         ; length in characters, 4
Debug StringByteLength(a)            ; length in bytes EXCLUDING terminating zero, 8
Debug a                              ; because of the zero written by PokeS() the string is shortened
Debug ""
;
PokeS(@a,"pqrs",2)                   ; write 2 characters in memory, followed by a zero (!)
Debug Len(a)                         ; length in characters, 2
Debug StringByteLength(a)            ; length in bytes EXCLUDING terminating zero, 4
Debug a                              ; pq 
Debug ""
;
PokeS(@a,"x",-1,#PB_String_NoZero)   ; do not write terminating zero
Debug Len(a)                         ; length in characters, 2
Debug StringByteLength(a)            ; length in bytes EXCLUDING terminating zero, 4
Debug a                              ; we did not write a zero so the string was not shortened

Bytes, words, longs, quads...

For each of these, PureBasic has a read and write variant. With the @ character we indicate the address of a variable, the place where it is stored in memory.

; survival guide 3_7_111 types peek and poke
; pb 4.40b1
;
a.b = 1
b.w = 1
c.l = 1
d.q = 1
e.i = 1
f.a = 1
g.u = 1
PokeB(@a,2)
PokeW(@b,2)
PokeL(@c,2)
PokeQ(@d,2)
PokeI(@e,2)
PokeA(@f,2)
PokeU(@g,2)
Debug a
Debug b
Debug c
Debug d
Debug e
Debug f
Debug g
Note that on 64 bit platforms @ will return a 64 bit value! So the following would work on a 32 bit machine, but not on a 64 bit machine!
a.l = 1
address.l = @a            ; will fail in 64 bit code
PokeB(address,2)
The proper way to do it is like this:
a.l = 1
address.i = @a            ; will auto adjust to the platform, either 32 or 64 bit
PokeB(address,2)

skipWhy and how...

(For those who care...)

The smalles thing a computer can handle is a bit. A bit is a 1 or a 0. That's it.

Eight of those bits together form a 'byte'. A byte can store any number from 0 to 255. A computer's memory is made up from thousands or millions of those bytes. (1024 bytes is called a kilobyte, 1048576 bytes is called a megabyte.) There are eight bits in a byte, and they are numbered 0 to 7 from right to left:

bit 7 - if set to one equals 128
bit 6 - if set to one equals 64
bit 5 - if set to one equals 32
bit 4 - if set to one equals 16
bit 3 - if set to one equals 8
bit 2 - if set to one equals 4
bit 1 - if set to one equals 2
bit 0 - if set to one equals 1
So, a single memory location is called a byte and can contain 8 bits. Two memory locations together are called a word and can contain 16 bits, etcetera.

For example, the computer stores the number 132 like this: 10000100, which means 1 x 128 + 0 x 64 + 0 x 32 + 0 x 16 +0 x 8 + 1 x 4 + 0 x 2 + 0 x 1 = 132.

Two bytes together are called a 'word'. All bits in a word are numbered from 0 to 15, with bits 0 to 7 in the first byte, and bits 8 to 15 in the second.

Four bytes together are called a 'long'. The first byte contains bits 0 to 7, the second 8 to 15, the third 16 to 23, the fourth 24 to 31, for a total of 32 bits.

Eight bytes together are called a 'quad'. The first byte contains bits 0 to 7, the cecond bits 8 to 15, etc. etc.

  • A memory location is called a 'byte'. There's also a variable type called a 'byte'. One is stored in the other.
  • A variable of type .b (byte) needs one byte of storage space, a variable of type .w (word) needs two subsequent bytes of storage space, and so on.

Binary numbers

Read the part of bits above? Right. You could build up a byte with for example bits 5 and 6 set to one using this apporach:

b.b = 64+32
Or you could simply do it this way:
b.b = %01100000
If you know about hexadecimal then this would do the job as well:
b.b = $60
To set, for example, bit number 2 of an existing byte, try this:
b.b = %01100000
b.b. = b.b | %00000010
Debug Bin(b.b)
Or to clear bits 5 and 6:
b.b = %01100000
b.b. = b.b & %10011111
Debug Bin(b.b)
Look here for more on binary operators...


More signed and unsigned...

We've already touched upon the subject of signed and unsigned variable types. Just to keep in mind: both types are stored in memory the same way, they're just interpreted differently. This little piece of code shows that %11111100 means -4 in signed bytes, and 252 in unsigned bytes. (The % character indicates it's a binary number, see below.)

unsigned.b = %11111100
Debug Str(unsigned)+" = %"+Right(Bin(unsigned),8)
;
signed.a = %11111100
Debug Str(signed)+" = %"+Right(Bin(signed),8)
The way signed and unsigned are stored is the same, the way they are interpreted is different. This depends on the variable type.


Little Endian, Big Endian

An Intel based system (anything called a 'PC' that runs Dos or Windows) stores information in memory in 'little endian' format, the lower bytes (the ones that contain the lower bits) go first, followed by the higher bytes. Some other CPU's (for example Motorola's 68000) use 'big endian' format, where the higher bytes go first. For now just forget the term, simply remember that information is stored in 'low to high' order on your Windows box:

; survival guide 3_7_200 little endian signed bytes
; pb 4.31
;
a.l = 837645420        ; that's a random long that will take four bytes in memory
b0.i = @a              ; this is the place in memory of the first byte, bits 0 to 7
b1.i = @a+1            ; the place of the second byte, bits 8 to 15
b2.i = @a+2            ; the place of the third byte, bits 16 to 23
b3.i = @a+3            ; the place of the fourth byte, bits 24 to 31
;
Debug a
;
Debug PeekB(b0)
Debug PeekB(b1)
Debug PeekB(b2)
Debug PeekB(b3)
;
Debug PeekL(@a)
Debug PeekB(b0) + PeekB(b1)&$FF * 256 + PeekB(b2)&$FF * 65536 + PeekB(b3)&$FF * 16777216
You may notice that reading the individual bytes will return some NEGATIVE numbers. This is because the PureBasic type .b is a signed type. When using unsigned numbers a byte can hold anything from 0 to 255, when using signed numbers a byte can hold anything from -128 to +127. Actually the memory itself doesn't hold different information, it's just our way of interpreting it. (Hmmm. Must have said that before a couple of times...)

There is a way to fix that, using a binary AND with &$FF, which is shown in the last line... (Don't worry, more on binary And is coming up later...)

The good news is that as of 4.40b1 we now can read unsigned stuff as well. The above, now using unsigned bytes, may be easier to read (no more ugly &$FF parts)...

; survival guide 3_7_201 little endian unsigned bytes
; pb 4.40b1
;
a.l = 837645420        ; that's a random long that will take four bytes in memory
b0.i = @a              ; this is the place in memory of the first byte, bits 0 to 7
b1.i = @a+1            ; the place of the second byte, bits 8 to 15
b2.i = @a+2            ; the place of the third byte, bits 16 to 23
b3.i = @a+3            ; the place of the fourth byte, bits 24 to 31
;
Debug a
;
Debug PeekA(b0)
Debug PeekA(b1)
Debug PeekA(b2)
Debug PeekA(b3)
;
Debug PeekL(@a)
Debug PeekA(b0) + PeekA(b1)*256 + PeekA(b2)*65536 + PeekA(b3)*16777216


File functions like ReadWord() etc. are also using 'little endian' format.


3.8 Hex, Bin and Val
 

A number is a number, stored as a few bits (and bytes) in memory. Sometimes you might need a number as part of a string before displaying it. Or you get information from another source and you need to convert a string (aka text) to a number...

a.s = "17521"
b.l = Val(a.s)
c.s = Str(b.l)
Debug a.s
Debug b.l
Debug c.s
Computers are good at sequences of zeroes and ones. We poor humans are not. We could write down every binary number in its components, but to write, for example, 17521 as 00000000000000000100010001110001 (that's binary or base 2) is somewhat... cumbersome... Fortunately, there are alternatives, such as hexadecimal (base 16).
a.l = 17521
a.l = %00000000000000000100010001110001
a.l = $4471
;
Debug a.l
Debug Bin(a.l)
Debug Hex(a.l)

Hex(), Bin() and x_val()

PureBasic has some built-in functionality that can help us here. On compilation time, the compiler can translate binary and hexadecimal numbers in the source code automatically if we precede them with a '%' or a '$' symbol. During run-time we can turn any value into a string, either decimal, hexadecimal or binary, using Str(), Hex() and Bin(). Unfortunately, there's only Val() for decimal numbers for the reverse direction. Ooops. Strike that. As of 4.20 Val() now supports binary and hex as well. Still, here's a little help for other cases, and pre 4.20...

; survival guide 3_8_110 hex bin val
; pb 4.40b1
;
Procedure.i x_val(string.s)
  Protected p,l,b,t,c
  Global x_val_type.l
  ;
  ; *** as normal val() except it accepts also &H &O &0 &B % \ $ 0X but no NEGATIVE number support
  ;
  string = UCase(Trim(string))
  l = Len(string)
  t = 0
  ;
  If Left(string,1) = "$"
    p = 1
   b = 16
  ElseIf Left(string,1) = "%"
    p = 1
    b = 2
  ElseIf Left(string,1) = "\"
    p = 1
    b = 8
  ElseIf Left(string,1) = "&B"
    p = 2
    b = 2
  ElseIf Left(string,1) = "&O"
    p = 2
    b = 8
  ElseIf Left(string,1) = "&0" 
    p = 2
    b = 8
  ElseIf Left(string,2) = "&H"
    p = 2
    b = 16
  ElseIf Left(string,2) = "0X"
    p = 2
    b = 16
    ;
    ; ElseIf Left(string,1) = "0"         ; i don't like this one, as i often use
    ;    p = 1                              ; preceding zeroes in front of decimals while
    ;    b = 8                              ; c(++) would turn those into octals... brrr...
    ;                                       ; well, it's up to you to uncomment these lines 
  Else
    p = 0
    b = 10
  EndIf
  ; 
  While p < l
    p = p+1
    c = Asc(Mid(string,p,1))-48
    If c > 9
      c = c-7
    EndIf
    If c >= 0 And c < b
      t = t*b+c
    Else
      l = p
    EndIf
  Wend
  x_val_type = b
  ;
  ProcedureReturn t
EndProcedure
If you want to test the above, you can use the following code::
a.l = 1234
Debug a.l
b.s = "$"+Hex(a.l)
Debug b.s
Debug x_val(b.s)

3.9 Boolean and binary


Boolean table

First an overview of some 'logical' 'binary' ''boolean' functions...

 A   B  AND OR  XOR
--- --- --- --- ---
 0   0   0   0   0
 0   1   0   1   1
 1   0   0   1   1
 1   1   1   1   0

Boolean logic: And

When a few conditions apply, for example a must be one and b must be one, we could code it like this:

a.l = 1
b.l = 1
If a =1
  If b = 1
    Debug "a is one and b is one"
  Endif
Endif
But we could combine these on a single line, using the And keyword:
a.l = 1
b.l = 1
If a = 1 And b = 1
    Debug "a is one and b is one"
Endif

Boolean logic: Or

How about the following condition? If a is one, or b is one, or both are one? You guessed it...

a = 1
b = 0
If a = 1
  Debug "a is one, perhaps b is one"
Endif
If b = 1
  Debug "b is one, perhaps a is one"
Endif
;
If a = 1 or b = 1
  Debug "dunno which one, but a is one, b is one, or both are one"
Endif

Brackets

When creating more complex logic, we can use brackets to group conditions together to create lines such as:

If ( ( a=1 Or b=1 ) And ( c=1 Or d=1 ) ) And e = 1
  Debug "yeah... right"
Endif
Always add brackets when code might be interpreted in different ways... Compare the following three lines:
If a=1 Or b=2 And c=3
If a=1 Or (b=2 And c=3)
If (a=1 Or b=2) And c=3
What was the programmer's intention? The intention is clear in the second and third line, but the first line just might cause some confusion. And even though PureBasic follows a certain logic when evaluation such a combined expression it just might be wise to add a bunch of brackets, if only to be able to read code ten years later...

Test variables against zero

When creating more complex logic, we can use brackets to group conditions together to create lines such as:

If ( ( a=1 Or b=1 ) And ( c=1 Or d=1 ) ) And e=1
  Debug "yeah... right"
Endif
(Euh, I think I just repeated myself... must be my old age...)

While we're at it, there is a way to make our code shorter. If we don't feed the If statement a real evaluation, such as a = 1, but instead give it only the variable it will still work! Why? Because what If and other statements do is this: they check if the result of an expression is zero or not... If it's 0 it's false, otherwise it's true. A little piece of code shows it best:

a = 0
If a = 0
  Debug "a is false (aka 0)"
Endif
;
a = 1
If a <> 0
  Debug "a is true (aka <> 0)"
Endif
If a
  Debug "we already know a is true (aka <> 0)"
Endif
So, anything zero is false, anything else is true. This will come in very handy with some of the PureBasic commands. You see, a large number of them return 0 (when they fail) or something else (when they succeed).

If we take the more complex logic from a few lines back, we might be able to rewrite it as:

If ( ( a Or b ) And ( c Or d ) ) And e
  Debug "hmmm... okay"
Endif
If the result of a test is zero it means it's false, if the result of a test is non-zero it's true

Here's one more example:

a.l = #True             ; which is by default 1
b.l = #False            ; which is by default 0
;
If a <> b
  Debug "a <> b ... this is very likely"
EndIf
If b = 0
  Debug "b = 0 ... b is false, thought so"
EndIf
If b = #False
  Debug "b = #False ... b is still false"
EndIf
If Not b
  Debug "Not b ... b is false, you knew it!"
EndIf
If #False = 0
  Debug "#False = 0 ... so you can say that a 'false' condition equals zero"
EndIf
If a.l = #True
  Debug "a = #True ... however a 'true' condition equals any non-zero value, so this is a bit dangerous"
EndIf
If a.l
  Debug "a.l ... read this as 'if a is true' or 'if a is not false' or 'if a is not zero'"
EndIf
a.l = 2
If a.l 
  Debug "a.l ... see? 'false' means zero, 'true' means not zero"
EndIf
If a.l <> 0
  Debug "a <> 0 ... you should be confused by now :-)"
EndIf

Boolean logic: the case of the missing Not...

... does not apply anymore! Finally! Looks like we finally got this little keyword that makes our life a little easier... 'Not' simply turns #True into its opposite #False.

Procedure check_everything()
  ProcedureReturn 0
EndProcedure
;
all_is_okay = check_everything()
;
If Not all_is_ok
  Debug "hmmm... not so okay I guess"
Endif
We could also write it this way: (for the Not haters :-))
Procedure check_everything()
  ProcedureReturn 0
EndProcedure
;
all_is_okay = check_everything()
;
If all_is_ok
Else
  Debug "hmmm... not so okay I guess no 1"
EndIf
;
If all_is_okay = 0
  Debug "hmmm... not so okay I guess no 2"
EndIf
;
If 0 = all_is_okay 
  Debug "hmmm... not so okay I guess no 3"
EndIf
 Tip: make sure you sprinkle enough brackets to keep your code readable... and if expressions get too long, break them up in parts, but make sure you understand the impact of type conversion.


Bool()

a = (a = b) does not work. Don't even try or ask for it. Don't. Please. Don't. Ask. Ever. PureBasic is not C or C++. It just won't work. Don't ask for it. Use Bool().

If you're in desperate need for constructions like a = (a = b) then Purebasic 5.11 now provides the Bool() keyword:

OpenConsole()
;
a.l = #True
b.l = #True
;
If Bool(a = b)
  PrintN("both equal")
EndIf
;
Repeat
Until Inkey() <> ""

Binary operators

The logic above can also be applied to bytes. A sample goes a long way...

a.l = %11110000
b.l = %11001100
;
Debug Bin(~a)          ; NOT a
Debug Bin(a & b)       ; a AND b
Debug Bin(a | b)       ; a OR b
Debug Bin(a ! b)       ; a XOR b
You can compare this with the little table at the beginning of this section...

Now, as we are dealing with binary operators, let's address the shift instruction right now:

a.l = %11110000
Debug Bin(a)
a = a << 2
Debug Bin(a)
a = a >> 2
Debug Bin(a)
Binary operators are often used to combine 'flags' for other parts of the program. You can also use them to test if bits have been set or reset:
#flag1 = %00000001  ; $01 or decimal 1
#flag2 = %00000010  ; $02 or decimal 2
#flag1 = %00000100  ; $04 or decimal 4
#flag2 = %00001000  ; $08 or decimal 8
;
options.l = #flag1 | #flag2
;
If options & #flag1
  Debug "flag 1 is set"
Endif


Boolean versus bitwise

Boolean is used in combination with conditions (If) and bitwise is used to operate on variables.
 

Type Boolean Bitwise Note
And And & both must be 'true' or '1'
Or Or | at least one side must be 'true' or '1'
Xor XOr ! both sides must be different
Not Not ~ invert, 'true' becomes 'false', 'false' becomes 'true'
<< shift all bits 'n' steps left (multiply 'n' types by 2)
>> shift all bits 'n' steps right (divide 'n' times by 2)

Some examples:

a.a = %00000011 & %00000110     ; %00000010
a.a = %00000011 | %00000110     ; %00000111
a.a = %00000011 ! %00000110     ; %00000101
a.a = ~%00000011                ; %11111100
a.a = %00000100 << 1            ; %00001000
a.a = %00000100 >> 2            ; %00000001

3.10 Arrays
 

(It's late but I want to get this over with...)

The (almost) final section of Primer I... arrays. Arrays are... easy :-)

Think of arrays as tables. Tables where every box of the table can contain a value. We can have tables of one dimension (like a list), two dimensions (a regular table) or even more than two dimensions (a book with on every page a table, a bookcase with books with on every page a table, a library with bookcases, a city with libraries)...

Sample:

Dim x.l(10)                          ; this is a one dimensional table of 11 longs
Dim y.b(9,9)                         ; here's a 2 dimensional table of 100 bytes (10 by 10)
Dim z.s(9,9,9)                       ; and a three dimensional table of 1000 strings (10x10x10)
;
x(0) = 1                             ; fill the first field in the table with '1'
y(5,5) = 2                           ; fill box 5,5 with 2
z(1,2,3)= "test"                     ; fill box 1,2,3 with "test"
Arrays allow us to store and organize larger numbers of similar data. Before we can use an array we first have to dimension it. An array always starts with 0, so Dim x.l(10) creates a list of 11 positions, from x.l(0) to x.l(10).

It is possible to redimension existing arrays and keeping the data they contain using the ReDim() command. There is one limitation: you can only redimension the last indice.

Dim x.l(10,10)
x(3,3) = 1
Debug x(3,3)
;
Dim x(10,11)
Debug x(3,3)
;
Dim x(10,9)
Debug x(3,3)
In short:
  • arrays can have any known variable type, bytes, words, longs, strings, structs, they all work
  • arrays can be global as well as local
  • to free the memory an array uses, you use another Dim instruction and tell PureBasic to use 0 fields
  • you can redim arrays and maintain data if you only change the last indice
  • arrays in structs are static and cannot be redimensioned
  • when arrays are passed through a procedure call they are passed by reference, not by value, and you need to use the Array keyword

3.11 Using strings
 

Ha! And I thought I would keep this page alone! And you thought you were done!

Ha! Again! :-)

Actually, I was updating the TOC, and I just realized there's something missing: a section on strings, ore more precisely the manipulation of strings. So there ya' go...

Strings are, you guessed it, not the basic building blocks of the universe (though Stephen Hawking may disagree :-) nor are they the kind of adult underwear the other half would get excited over... (I tried to insert a funny link to www.strings.com or www.underwear.com but neither worked...) Strings are variables that we use to store text.

There are many different flavours of strings (zero terminated strings, fixed length strings, Unicode etc) but if we stick to the  regular PureBasic commands (and don't care much how strings are stored in memory) then we're lucky... PureBasic will take care of all the tricky stuff and we just slice and dice to our hearts delight. (It's late and there's a cooking program on the television, you can tell.)


Splice and dice

Let's start with regular string manipulation of regular strings. It doesn't matter much if they are Unicode or not.

a.s = "12"
b.s = "345678"
a = a + b
Debug a
;
Debug Len(a)          ; length of the string
Debug Left(a,4)       ; left 4 characters of the string, ie. '1234'
Debug Right(a,4)      ; right 4 characters of the string, ie. '5678'
Debug Mid(a,4,2)      ; 2 characters starting at the 4th position, ie. '45'
As we can see it's possible to 'add' two strings together. We can also find out it's length (in characters), and we can retrieve some characters from the left, the right, or the middle of the string.

The command Space(10) creates a string of 10 spaces (I bet you didn't see that one coming). If we have a string with some unwanted spaces left or right, we can 'trim' these spaces.

a.s = Space(10) + "abcABC" + Space(10)
;
Debug ">"+a+"<"
Debug ">"+RTrim(a)+"<"
Debug ">"+LTrim(a)+"<"
Debug ">"+Trim(a)+"<"

Change is the name of the game

We can easily change the case of the string to upper- or lowercase. Note that you cannot change the case of special characters. If it's not part of the regular (english) characterset, it's a no-no.

a.s = "ABC abc ABC abc"
Debug a
;
Debug UCase(a)
Debug LCase(a)
;
Debug ReplaceString(a,"abc","pqrstuvwxyz")
Debug ReplaceString(a,"abc","")
Debug RemoveString(a,"abc")
The ReplaceString() function allows us to replace part of a string with another string. Unless you use the #PB_String_InPlace flag the replacement doesn't have to be the same length. You could replace part of a string with nothing, and PureBasic even has a command for that: RemoveString().

The LSet() and RSet() commands allow some 'formatting'. For example, if we have a integer '456' that we want to turn into a 6 digit number with leading zeroes, we could do something like this:

n.l = 456
a.s = RSet(Str(n),6,"0")
Debug a
The (in other Basic languages) very common String$() can be replaced like this:
a.s = Rset("",6,"-")

And I still haven't found...

FindString() allows you to find one string inside another:

a.s = "abcdefghi"
Debug FindString(a,"def",1)
Unfortunately there is no native RFindString() in PureBasic, but here is a replacement (not the fastest way, but it works):
Procedure.l x_rfindstring(string.s,find.s)
  Protected l.l , p.l
  ;
  l.l = Len(find.s)
  p.l = Len(string.s)
  If l > 0 And p > 0
    p = p + l
    Repeat
      p = p - 1
    Until p.l = 0 Or Mid(string,p,l) = find
  EndIf
  ProcedureReturn p
EndProcedure
A command that finds-and-slice'n'dices-all-in-one is StringField(). StringField() allows you to take strings to pieces, using a given 'seperator'. For example:
Debug StringField("This is a test",1," ")
... would result in 'This'. In the example above StringField() slices the strang on every occurence of the seperator (which is in this case a space). Now the first element found is 'This', to find the second element we increase the index:
Debug StringField("This is a test",2," ")


(You can skip the next section if you're new to PureBasic, and continue with Primer II. Don't forget to check out the PureBasic help file!)
 

skipSpecialists...

... should especially check out the following subjects, if they haven't already :-)


When you feel familiar enough with PureBasic you may continue with Primer II... (I strongly suggest it :-))

For any questions related to programming in PureBasic do not ask me but visit the forum! (There you will find people who are much more knowledgable about PureBasic than I am. I admit that's rather easy :-))