Difference between revisions of "Introduction to SourcePawn (legacy syntax)"

From AlliedModders Wiki
Jump to: navigation, search
m
m
Line 708: Line 708:
 
Dynamic arrays are only valid at the local scope level, since code cannot exist globally.
 
Dynamic arrays are only valid at the local scope level, since code cannot exist globally.
  
[[Category:SourceMod Development]]
+
[[Category:SourceMod Scripting]]

Revision as of 18:15, 15 November 2007

This guide is designed to give you a very basic overview to fundamentals of scripting in SourcePawn. Pawn is a "scripting" language used to embed functionality in other programs. That means it is not a standalone language, like C++ or Java, and its details will differ based on the application. SourcePawn is the version of Pawn used in SourceMod.

This guide does not tell you how to write SourceMod plugins; it is intended as an overview of the syntax and semantics of the language instead. Read the separate article, Introduction to SourceMod Plugins for SourceMod API specifics.


Non-Programmer Intro

This section is intended for non-programmers. If you're still confused, you may want to pick up a book on another language, such as PHP, Python, or Java, to get a better idea of what programming is like.

Symbols/Keywords

A symbol is a series of letters, numbers, and/or underscores, that uniquely represents something. Symbols are case-sensitive (unlike PHP, where sometimes they are not). Symbols do not start with any special character, though they must start with a letter.

There are a few reserved symbols that have special meaning. For example, if, for, and return are special constructs in the language that will explained later. They cannot be used as symbol names.

Variables

There a few important constructs you should know before you begin to script. The first is a variable. A variable is a symbol, or name, that holds data. For example, the variable "a" could hold the number "2", "16", "0", et cetera. Variables are created for storage space throughout a program. Variables must be declared before being used, using the "new" keyword. Data is assigned to variables using the equal sign (=). Example:

new a,b,c,d

a=5
b=16
c=0
d=500

In SourcePawn, variables have two types, which will be explained in more detail further on.

  • Cells (arbitrary numerical data), as shown above.
  • Strings (a series of text characters)

Functions

The next important concept is functions. Functions are symbols or names that perform an action. That means when you activate them, they carry out a specific sequence of code. There are a few types of functions, but every function is activated the same way. "Calling a function" is the term for invoking a function's action. Function calls are constructed like this:

function(<parameters>)

Examples:

show(56)   //Activates "show" function, and gives the number 56 to it
show()     //Activates "show" function with no data, blank
show(a)    //Activates "show" function, gives a variable's contents as data

Every piece of data passed to a function is called a parameter. A function can have any number of parameters (there is a "reasonable" limit of 32 in SourceMod). Parameters will be explained further in the article.

Comments

Note any text that appears after a "//" is considered a "comment" and is not actual code. There are two comment styles:

  • // - Double slash, everything following on that line is ignored.
  • /* */ - Multi-line comment, everything in between the asterisks is ignored. You cannot nest these.


Block Coding

The next concept is block coding. You can group code into "blocks" separated by { and }. This effectively makes one large block of code act as one statement. For example:

{
   here
   is
   some
   code
}

Block coding using braces is used everywhere in programming. Blocks of code can be nested within each other. It is a good idea to adapt a consistent and readable indentation style early on to prevent spaghetti-looking code.


Language Paradigms

Pawn may seem similar to other languages, like C, but it has fundamental differences. It is not important that you immediately understand these differences, but they may be helpful if you're familiar with another language already.

  • Pawn is not typed. Pawn only has one data type, the cell. This will be explained in detail later.
  • Pawn is not garbage collected. Pawn, as a language, has no built-in memory allocation, and thus has no garbage. If a function allocates memory, you may be responsible for freeing it.
  • Pawn is not object oriented. Pawn is procedural, and relies on subroutines. It also does not have C structs.
  • Pawn is not functional. Pawn is procedural, and does not support lambda functions or late binding or anything else you might find in a very high-level language, like Python or Ruby.
  • Pawn is single-threaded. As of this writing, Pawn is not thread safe.
  • Pawn is not interpreted. Well, it "sort of" is. It gets interpreted at a very low level. You must run your code through a compiler, which produces a binary. This binary will work on any platform that the host application uses. This speeds up loading time and lets you check errors easier.

These languages design decisions were made by ITB CompuPhase. It is designed for low-level embedded devices and is thus very small and very fast.


Variables

In Pawn there are two variable types: the cell and the String. A cell can store 32 bits of numerical data. A String is a sequential/flat list of UTF-8 text characters.

A cell has no inherent type, however, cells can be tagged. A tag lets you enforce where certain cells can be used. The default tags are:

  • (nothing), or _ - No tag. Usually used for whole numbers (Integers).
  • Float - Used for floating point (fractional) numbers.
  • bool - Used for storing either true or false.

Strings are different and will be explained in the next sections.

Declaration

Examples of different valid variable declarations:

new a = 5
new Float:b = 5.0
new bool:c = true
new bool:d = 0      //Works because 0 is false

Invalid variable usage:

new a = 5.0         //Tag mismatch.  5.0 is tagged as Float
new Float:b = 5     //Tag mismatch.  5 is not tagged.

If a variable is not assigned upon declaration, it will be set to 0. For example:

new a        //Set to 0
new Float:b  //Set to 0.0
new bool:c   //Set to false

Assignment

Variables can be re-assigned data after they are created. For example:

new a, Float:b, bool:c
 
a = 5
b = 5.0
c = true


Arrays

An array is a sequence of data in a sequential list. Arrays are useful for storing multiple pieces of data in one variable, and often greatly simplify many tasks.

Declaration

An array is declared using brackets. Some examples of arrays:

new players[32]     //Stores 32 cells (numbers)
new Float:origin[3] //Stores 3 floating point numbers

By default, arrays are initialized to 0. You can assign them different default values, however:

new numbers[5] = {1, 2, 3, 4, 5}       //Stores 1, 2, 3, 4, 5 in the cells.
new Float:origin[3] = {1.0, 2.0, 3.0}  //Stores 1.0, 2.0, 3.0 in the cells.

You can leave out the array size if you're going to pre-assign data to it. For example:

new numbers[] = {1, 3, 5, 7, 9}

The compiler will automatically deduce that you intended an array of size 5.

Usage

Using an array is just like using a normal variable. The only difference is the array must be indexed. Indexing an array means choosing the element which you wish to use.

For example, here is an example of the above code using indexes:

new numbers[5], Float:origin[3]
 
numbers[0] = 1
numbers[1] = 2
numbers[2] = 3
numbers[3] = 4
numbers[4] = 5
origin[0] = 1.0
origin[1] = 2.0
origin[2] = 3.0

Note that the index is what's in between the brackets. The index always starts from 0. That is, if an array has N elements, its valid indexes are from 0 to N-1. Accessing the data at these indexes works like a normal variable.

To use an incorrect index will cause an error. For example:

new numbers[5]
 
numbers[5] = 20

This may look correct, but 5 is not a valid index. The highest valid index is 4.

You can use any expression as an index. For example:

new a, numbers[5]
 
a = 1                   //Set a = 1
numbers[a] = 4          //Set numbers[1] = 3
numbers[numbers[a]] = 2 //Set numbers[4] = 2

Expressions will be discussed in depth later in the article.


Strings

Strings are a convenient method of storing text. The characters are stored in an array. The string is terminated by a null terminator, or a 0. Without a null terminator, Pawn would not know where to stop reading the string. All strings are UTF-8 in SourcePawn.

Notice that Strings are a combination of arrays and cells. Unlike other languages, this means you must know how much space a string will use in advance. That is, strings are not dynamic. They can only grow to the space you allocate for them.

Note for experts: They're not actually cells. SourcePawn uses 8-bit storage for String arrays as an optimization. This is what makes String a type and not a tag.

Usage

Strings are declared almost equivalently to arrays. For example:

new String:message[] = "Hello!"
new String:clams[6] = "Clams"

These are equivalent to doing:

new String:message[7], String:clams[6]
 
message[0] = 'H'
message[1] = 'e'
message[2] = 'l'
message[3] = 'l'
message[4] = 'o'
message[5] = '!'
message[6] = 0
clams[0] = 'C'
clams[1] = 'l'
clams[2] = 'a'
clams[3] = 'm'
clams[4] = 's'
clams[5] = 0

Although strings are rarely initialized in this manner, it is very important to remember the concept of the null terminator, which signals the end of a string. The compiler, and most SourceMod functions will automatically null-terminate for you, so it is mainly important when manipulating strings directly.

Note that a string is enclosed in double-quotes, but a character is enclosed in single quotes.

Characters

A character of text can be used in either a String or a cell. For example:

new String:text[] = "Crab"
new clam
 
clam = 'D'         //Set clam to 'D'
text[0] = 'A'      //Change the 'C' to 'A', it is now 'Arab'
clam = text[0]     //Set clam to 'A'
text[1] = clam     //Change the 'r' to 'A', is is now 'AAab'

What you can't do is mix character arrays with strings. The internal storage is different. For example:

new clams[] = "Clams"                       //Invalid, needs String: type
new clams[] = {'C', 'l', 'a', 'm', 's', 0}  //Valid, but NOT A STRING.


Functions

Functions, as stated before, are isolated blocks of code that perform an action. They can be invoked, or called, with parameters that give specific options.

There are two types of ways functions are called:

  • direct call - You specifically call a function in your code.
  • callback - The application calls a function in your code, as if it were an event trigger.

There are five types of functions:

  • native: A direct, internal function provided by the application.
  • public: A callback function that can is visible to the application and other scripts.
  • normal: A normal function that only you can call.
  • stock: A normal function provided by an include file. If unused, it won't be compiled.
  • forward: This function is a global event provided by the application. If you implement it, it will be a callback.

All code in Pawn must exist in functions. This is contrast to languages like PHP, Perl, or Python, which let you write global code. That is because Pawn is a callback-based language; it responds to actions from a parent application, and functions must be built to handle those actions. Although our examples often contain free-floating code, this is purely for demonstration purposes. Free-floating code in our examples implies the code is part of some function.

Declaration

Unlike variables, functions do not need to be declared before you use them. Functions have two pieces, the prototype and the body. The prototype contains the name of your function and the parameters it will accept. The body is the contents of its code.

Example of a function:

AddTwoNumbers(first, second)
{
  new sum = first + second
 
  return sum
}

This is a simple function. The prototype is this line:

AddTwoNumbers(first, second)

Broken down, it means:

  • AddTwoNumbers - Name of the function.
  • first - Name of the first parameter, which is a simple cell.
  • second - Name of the second parameter, which is a simple cell.

The body is a simple block of code. It creates a new variable, called sum, and assigns it the value of the two parameters added together (more on expressions later). The important thing to notice is the return statement, which tells the function to end. All functions return a cell upon completion. That means, for example:

new sum = AddTwoNumbers(4, 5)

The above code will assign the number 9 to sum. The function adds the two inputs, and the sum is given as the return value. If a function has no return statement, or does not place a value in the return statement, it returns 0 by default.

A function can accept any type of input. It can return any cell, but not arrays or strings. Example:

Float:AddTwoFloats(Float:a, Float:b)
{
   new Float:sum = a + b
 
   return sum
}

Note that if in the above function, you returned a non-Float, you would get a tag mismatch.

You can, of course, pass variables to functions:

new numbers[3] = {1, 2, 0}
 
numbers[2] = AddTwoNumbers(numbers[0], numbers[1])

Note that cells are passed by value. That is, their value cannot be changed by the function. For example:

new a = 5
 
ChangeValue(a)
 
ChangeValue(b)
{
   b = 5
}

This code would not change the value of a. That is because a copy of the value in a is passed instead of a itself.

More examples of functions will be provided throughout the article.

Publics

Public functions are used to implement callbacks. You should not create a public function unless it is specifically implementing a callback. For example, here are two callbacks from sourcemod.inc:

forward OnPluginStart();
forward OnClientDisconnected(client);

To implement and receive these two events, you would write functions as such:

public OnPluginStart()
{
   /* Code here */
}
 
public OnClientDisconnected(client)
{
   /* Code here */
}

The public keyword exposes the function publicly, and allows the parent application to directly call the function.

Natives

Natives are builtin functions provided by the application. You can call them as if they were a normal function. For example, SourceMod has the following function:

native FloatRound(Float:num);

It can be called like so:

new num = FloatRound(5.0)     //Results in num = 5

Array Parameters

You can pass arrays or Strings as parameters. It is important to note that these are passed by reference. That is, rather than making a copy of the data, the data is referenced directly. There is a simple way of explaining this more concretely.

new example[] = {1, 2, 3, 4, 5}
 
ChangeArray(example, 2, 29)
 
ChangeArray(array[], index, value)
{
   array[index] = value
}

The function sets the given index in the array to a given value. When it is run on our example array, it changes index 2 to from the value 3 to 29. I.e.:

example[2] = 29

This is only possible because the array can be directly modified. To prevent an array from being modified, you can mark it as const. This will raise an error on code that attempts to modify it. For example:

CantChangeArray(const array[], index, value)
{
   array[index] = value    //Won't compile
}

It is a good idea to use const in array parameters if you know the array won't be modified; this can prevent coding mistakes.


Expressions

Expressions are exactly the same as they are in mathematics. They are groups of operators/symbols which evaluate to one piece of data. They are often parenthetical (comprised of parenthesis). They contain a strict "order of operations." They can contain variables, functions, numbers, and expressions themselves can be nested inside other expressions, or even passed as parameters.

The simplest expression is a single number. For example:

0   //Returns the number 0
(0) //Returns the number 0 as well

Although expressions can return any value, they are also said to either return zero or non-zero. In that sense, zero is false, and non-zero is true. For example, -1 is true in Pawn, since it is non-zero. Do not assume negative numbers are false.

The order of operations for expressions is similar to C. PMDAS: Parenthesis, Multiplication, Division, Addition, Subtraction. Here are some example expressions:

5 + 6                   //Evaluates to 11
5 * 6 + 3               //Evaluates to 33
5 * (6 + 3)             //Evaluates to 45
5.0 + 2.3               //Evaluates to 7.3
(5 * 6) % 7             //Modulo operator, evaluates to 2
(5 + 3) / 2 * 4 - 9     //Evaluates to 5

As noted, expressions can contain variables, or even functions:

new a = 5 * 6
new b = a * 3      //Evaluates to 90
new c = AddTwoNumbers(a, b) + (a * b)

Operators

There are a few extra helpful operators in Pawn. The first set simplifies self-aggregation expressions. For example:

new a = 5
 
a = a + 5

Can be rewritten as:

new a = 5
a += 5

This is true of the following operators in Pawn:

  • Four-function: *, /, -, +
  • Bit-wise: |, &, ^, ~, <<, >>

Additionally, there are increment/decrement operators:

a = a + 1
a = a - 1

Can be simplified as:

a++
a--

As an advanced note, the ++ or -- can be before the variable (preincrement, predecrement) or after the varaible (postincrement, postdecrement). The difference is in the order of execution. For example:

new a = 5
new b = a++
new c = ++a

In this example, b will be equal to 5, after which a will be incremented to 6. But then a will be incremented to 7, and c will receive the value of 7.

Truth Operators

As noted earlier, expressions can either be true or false depending on if they're non-zero or zero. This is especially useful with truth operators. There are five important truth operators:

  • && - Tests whether two expressions are both true.
  • || - Tests whether one of two expressions is true.
  • ! - Flips the truth value of an expression.
  • == - Tests whether two expressions are numerically equivalent.
  • != - Tests whether two expressions are numerically inequivalent.

There are also some mathematical truth operators (L is left hand, R is right hand):

  • > - True if L is greater than R
  • >= - True if L is greater than or equal to R
  • < - True if L is less than R
  • <= - True if L is less than or equal to R

For example:

(1 || 0)         //Evaluates to true because the expression 1 is true
(1 && 0)         //Evaluates to false because the expression 0 is false
(!1 || 0)        //Evaluates to false because !1 is false.
(1 != 3)         //Evaluates to true because 1 is not equal to 3
(3 + 3 == 6)     //Evaluates to true because 3+3 is 6.

Note that these operators do not work on arrays. That is, you cannot compare strings or arrays using ==.

Left/Right Values

Two important concepts are left-hand and right-hand values, or l-values and r-values. An l-value is what appears on the left-hand side of a variable assignment, and an r-value is what appears on the right side of a variable assignment.

For example:

new a = 5

In this example a is an l-value and 5 is an r-value.

The rules:

  • Expressions are never l-values.
  • Variables are both l-values and r-values.


Conditionals

Conditional statements let you only run code if a certain condition is matched.

If Statements

If statements test one or more conditions. For example:

if (a == 5)
{
   /* Code that will run if the expression was true */
}

They can be extended to handle more cases as well:

if (a == 5)
{
   /* Code */
}
else if (a == 6)
{
   /* Code  */
}
else if (a == 7)
{
   /* Code */
}

You can also handle the case of no expression being matched. For example:

if (a == 5)
{
   /* Code */
}
else
{
   /* Code that will run if no expressions were true */
}

Switch Statements

Switch statements are restricted if statements. They test one expression for a series of possible values. For example:

switch (a)
{
   case 5:
   {
      /* code */
   }
   case 6:
   {
      /* code */
   }
   case 7:
   {
      /* code */
   }
   case 8, 9, 10:
   {
      /* Code */
   }
   default:
   {
      /* will run if no case matched */
   }
}

Unlike some other languages, switches are not fall-through. That is, multiple cases will never be ran. If a single case matches, its code is ran, and the switch is then immediately terminated.


Loops

Loops allow you to conveniently repeat a block of code while a given condition remains true.

For Loops

For loops are loops which have four properties:

  • The begin statement, which is ran before the first loop occurs.
  • The condition statement, which checks whether the next loop should run.
  • The iterator statement, which is ran after each loop runs.
  • The code block itself, which is what's run every loop.

A simple example is a function to sum an array:

new array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
new sum = SumArray(array, 10)
 
SumArray(const array[], count)
{
   new total
 
   for (new i = 0; i < count; i++)
   {
      total += array[i]
   }
 
   return total
}

Broken down:

  • new i = 0 - Creates a new variable for the loop, sets it to 0.
  • i < count - Only runs the loop if i is less than count. This ensures that the loop stops reading at a certain point. In this case, we don't want to read invalid indexes in the array.
  • i++ - Increments i by one after each loop. This ensures that the loop doesn't run forever; eventually i will become too big and the loop will end.

Thus, the SumArray function will loop through each valid index of the array, each time adding that value of the array into a sum. For loops are very common for processing arrays like this.

While Loops

While loops are less common than for loops, but are actually the simplest possible loop. It only has two properties:

  • The condition, which is checked before each loop.
  • The code, which is what's run each loop.

As long as the condition expression remains true, the loop will continue. Here is an example of the previous for loop as a while loop:

SumArray(const array[], count)
{
   new total, i
 
   while (i < count)
   {
      total += array[i]
      i++
   }
 
   return total
}

There are also do...while loops which are even less common. These are the same as while loops except the condition check is AFTER each loop, rather than before. This means the loop is always run at least once. For example:

do
{
   /* Code */
} while(CONDITIoN)

Loop Control

There are two cases in which you want to selectively control a loop:

  • skipping one iteration of the loop but continuing as normal, or;
  • breaking the loop entirely before it's finished.

Let's say you have a function which takes in an array and searches for a matching number. You want it to stop once the number is found:

/**
 * Returns the array index where the value is, or -1 if not found.
 */
SearchInArray(const array[], count, value)
{
   new index = -1
 
   for (new i = 0; i < count; i++)
   {
      if (array[i] == value)
      {
         index = i
         break
      }
   }
 
   return index
}

Certainly, this function could simply return i instead, but the example shows how break will terminate the loop.

Similarly, the continue keyword skips an iteration of a loop. For example, let's say we wanted to sum all even numbers:

SumEvenNumbers(const array[], count)
{
   new sum
 
   for (new i = 0; i < count; i++)
   {
      /* If divisibility by 2 is 1, we know it's odd */
      if (array[i] % 2 == 1)
      {
         /* Skip the rest of this loop iteration */
         continue
      }
      sum += array[i]
   }
 
   return sum
}


Scope

Scope refers to the visibility of code. That is, code at one level may not be "visible" to code at another level. For example:

new A, B, C
 
Function1()
{
   new B
 
   Function2()
}
 
Function2()
{
   new C
}

In this example, A, B, and C exist at global scope. They can be seen by any function. However, the B in Function1 is not the same variable as the B at the global level. Instead, it is at local scope, and is thus a local variable.

Similarly, Function1 and Function2 know nothing about each other's variables.

Not only is the variable private to Function1, but it is re-created each time the function is invoked. Imagine this:

Function1()
{
   new B
 
   Function1()
}

In the above example, Function1 calls itself. Of course, this is infinite recursion (a bad thing), but the idea is that each time the function runs, there is a new copy of B. When the function ends, B is destroyed, and the value is lost.

This property can be simplified by saying that a variable's scope is equal to the nesting level it is in. That is, a variable at global scope is visible globally to all functions. A variable at local scope is visible to all code blocks "beneath" its nesting level. For example:

Function1()
{
   new A
 
   if (A)
   {
      A = 5
   }
}

The above code is valid since A's scope extends throughout the function. The following code, however, is not valid:

Function1()
{
   new A
 
   if (A)
   {
      new B = 5
   }
 
   B = 5
}

Notice that B is declared in a new code block. That means B is only accessible to that code block (and all sub-blocks nested within). As soon as the code block terminates, B is no longer valid.


Dynamic Arrays

Dynamic arrays are arrays which don't have a hardcoded size. For example:

Function1(size)
{
   new array[size]
 
   /* Code */
}

Dynamic arrays can have any expression as their size as long as the expression evaluates to a number larger than 0. Like normal arrays, SourcePawn does not know the array size after it is created; you have to save it if you want it later.

Dynamic arrays are only valid at the local scope level, since code cannot exist globally.