PCC2 Interpreter Quick Documentation

This document contains a brief introduction into the scripting language as implemented so far. See PCC2 Quick Documentation for documentation about PCC2 as a whole.

Introduction

Like PCC 1.x, PCC2 now contains a script interpreter. The script interpreter understands a simple programming language remotely based upon newer BASIC dialects, and extended to be useful in a VGAP context.

The scripting language is intended to be mostly identical to the PCC 1.x language. However, the underlying implementation is completely different (and, as I hope, more logical and consistent).

As of beta 13, PCC2 is able to evaluate expressions and execute commands. You can therefore use the search function, the console ("calculator"), and unit labels, and bind key using the Bind command. See How to add your own scripts to PCC for information about, well, how to add your own scripts to PCC.

! This document is still incomplete. In particular, it is missing the description of object properties. You can refer to the PCC 1.x scripting manual for those. However, please be aware that it is not yet 100% implemented yet. Use the console to try out expressions if in doubt.
Remarks about future features of PCC2 have been formatted in this style.

Basics

Names and Context

Names identify things. Many programming languages therefore call them identifiers. A name can contain letters, digits, and the special characters "." (dot), "$" (dollar), and "_" (underscore); it may not start with a digit or dot, and may not end with a dot, though. PCC2 predefines many names you can use in various ways. Those are called properties (properties of objects, such as the name of a ship) and functions. You can also define your own names: variables and subroutines.

The meaning of a name depends on the current context. For example, the word Mission can stand for the mission of a ship or for the mission of a starbase, which are governed by completely different rules. However, names have been chosen in such a way that similar things are represented by similar names. For example, you can use Name="Ares" to search for ships and planets having that name, and Owner$=7 searches for everything owned by player 7.

The current context is determined by the place you're invoking a script from. For example, a planet label expression is always evaluated in the context of the planet. A search expression is evaluated in all possible contexts, as defined by your selection in the search dialog.

Contexts stack. You can temporarily open another context to look at another object's properties. For example, using the Find function to find a ship, as in Find(Ship, Name="Ares", Id), will evaluate the test and return expressions (second and third parameter) in ship context. Names not found in that context will be looked up in the previous contexts.

There are important exceptions to these rules. For example, in Sin(30), Sin always means the sine function, no matter what context is active. Those exceptions are:

Those always mean the same thing, regardless of the current context, and cannot be redefined.

Naming Conventions

Property names follow some simple guidelines that aim to help you memorize properties.

The shortest name always gives a nice human-readable summary of an item. For example, Mission yields a ship's mission in text form ("Tow a ship"). By adding more words, you can ask for details. For example, Mission.Tow yields the "Tow" parameter of the mission. Alternatively, adding a dollar sign gives the internal representation. For example, Mission$ yields the mission number (7).

For programming, writing search queries, etc., you will probably use the "$" forms most often. For formatting text, you will most likely use the short standard form.

When you define your own names, it makes sense to give them a unique prefix to avoid clashes with others' names (in case you're using scripts developed by others).

Internally, PCC2 uses some names starting with CC$. Those names are undocumented and subject to change without notice. You should not use them. Currently, many keybindings are implemented using a CC$ name. If you want to modify such a keybinding, it's best to copy the existing one. For example, as of beta 9, the "change primary enemy" function (E on ship screen) is implemented as CC$ChangePE. To assign this function to the P key, you should write

Bind Ship "p" := Key(Ship, "e")

instead of Bind Ship "p" := "CC$ChangePE". This is guaranteed to keep working in future versions.

Types

All names, values and expressions have a type. The type defines what you can do with the thing. PCC2 internally tracks these types. If you use a value in a way its type does not allow, PCC2 will show you an appropriate error message. The following types exist:

Numbers
PCC2 supports fractional and whole numbers (integer). Those can be used to compute new values using the well-known arithmetic operators and functions. PCC internally tracks for each value whether it's fractional or integer. In general, operations between integers yield integers, whereas operations involving a fractional number yield a fractional number. Some operations require their operands to be integers, most accept either kind.
For techno-geeks: fractional numbers are 64-bit double precision numbers during script execution (stored in files with 48-bit precision), and integers are 32-bit signed numbers.
Boolean
Boolean values can be True or False and are used to represent yes/no answers.
Whenever PCC2 needs a boolean value, it also accepts numbers (0 means False (everything below 0.000001, actually), other values mean True) or strings (the empty string "" means False, other values mean True).
Likewise, PCC2 also accepts booleans when it needs a number, False converts into zero, True converts into 1.
Strings
Strings can contain arbitrary text, such as the name of a starship. Strings can be concatenated (appended) or split using various functions. In PCC2, strings have no intrinsic length limit. In PCC 1.x, the limit was 255 characters.
At most places where PCC2 needs a string, it will also accept a value of any other type. That value will be converted into its textual representation.
Since PCC2 beta 12, strings can contain arbitrary Unicode text. Note, however, that things stored in the starchart file (most importantly, planet comments) are limited to your game character set; characters outside that character set will be dropped.
Arrays
Arrays contain other objects. For example, Ship is an array containing all ships. You can access individual array elements using notation like Ship(17), and you can iterate over arrays containing objects (see below) using functions like Find or the ForEach command. You can make your own arrays using the Dim command.
Functions
Functions compute values from other values. For example, Sqrt is a function, and Sqrt(9) computes square-root-of-9. You can also define your own functions using the Function command.
Objects
The result of an array reference like Ship(17) is the object, ship #17. You can access that object's properties by adding a dot followed by a property name, as in Ship(17).Name, or by using it with the With command.

A very special value is Empty. This value is used for unknown values. For example, when you don't own a ship, it will yield Empty for its Cargo.N (fuel amount). As a general rule, an operation will yield Empty if one of their operands are Empty, and a command will not do anything if a mandatory argument is Empty, but exceptions exist. In a boolean context (If), Empty counts as False.

Technically, subroutines and regular (=not elementary) commands are also represented as values. The only thing you can do with those, however, is invoke them in a command. You cannot use them meaningfully in expressions

PCC2 currently permits all sorts of interesting (and dangerous) things. For example, you can "assign" arrays, functions, and commands, as in Ship := Planet or NewLine := NewRectangle. This will, of course, break a lot of things. For example, when you do Ship := Planet, ships will no longer be accessible, and the expression Ship(3) will refer to planet #3. On the other hand, this can also be used for good, for example to pass a function name to another function ("function pointer" or "closure" as known from other programming languages). PCC2 doesn't yet block those things. Future PCC2 versions may block them to avoid getting you into difficult situations. Rules have not yet been decided.

Specifying Cargo Amounts

Some commands require you to specify an amount of cargo. They will accept a specially-formatted string: the string consists of zero or more components separated by spaces. Each component starts with a cargo amount followed by one or more cargo type letters:

LetterMeaning LetterMeaning LetterMeaning
T Tritanium N Neutronium S Supplies
D Duranium F Fighters $ Cash
M MolybdenumW Torpedoes C Colonist clans

For example, the cost of a starbase can be specified as "900$ 402T 120D 340M". The cost of a Mark 5 Photon can be specified as "1TDM 31$".

Accessing Files

Since beta 12, scripts can access files. The commands work almost identical to the commands used in PCC 1.x; many limits have been raised. Well-written code can be used unchanged.

Files must be opened using the Open command before you can work with them. When opening, you associate a file number with the file. The file number is an integer between 1 and 100. You can assign your own file number, or use the FreeFile() function to get an unused one. When you're done with the file, you close it using the Close command. This makes the file number available for future re-use.

In all commands, the file number is, by convention, written with a leading #. This is optional for all file-related commands except for Print (which needs the # sign to know that this is a file number, not a value to print).

Text files can be accessed using the Input and Print commands. PCC2 will automatically convert them from the game character set. As an exception, if they start with a UTF-8 byte order mark, they will be read as UTF-8.

Binary files can be accessed using the Get and Put commands. Those transfer data to and from a special data block object stored in a variable. You can dissect this data block using the GetWord() function and its relatives, and modify it using SetWord and relatives.

Pitfalls

In PCC 1.x, a data block was just a string. In PCC2, it is a whole new data type which cannot be used with string manipulation functions (which was possible but deprecated in PCC 1.x).

File I/O is buffered. When you write something to a file, the change may not immediately show up on the disk. If you open the same file on another file number, that other file number will also not immediately see the changes. Changes will be written out when you call Close.

How to add your own scripts to PCC

There are a few ways to have PCC2 execute your scripts automatically. This part of PCC2 is not yet complete and will change.

These scripts can contain your code. In particular, it makes sense to put the following things into your startup scripts:

In addition, you can load script files from the console by typing a command such as Load "/path/to/script.q", and you can execute subroutines by simply calling them from the console.

Expressions

Expressions compute values. They do so by combining elementary values (literals, properties, variables) using operators and functions. The following sections describe expressions in an informal style. For people who prefer it, there's a formal grammar at the end.

Literals

Decimal numbers can be used to specify integers or real numbers. The "." is used as the decimal point. A number is integer if it fits into a 32-bit integer number, otherwise it's treated as real.

Strings can be specified in two ways:

Special values are Pi (=3.14159265..., circumference/diameter ratio of a circle), True and False.

Some General Rules

Most operations return Empty when one of their arguments is Empty. Operations taking more than one argument usually evaluate them all, even if the result would be known after evaluating only some. Exceptions are explicitly specified.

String operations are case-insensitive by default. That is, "A" compares equal to "a". To get case-sensitive operations, use the StrCase() function. Note that only the basic latin letters are treated case-insensitively; special characters such as "ä" or "д" are always handled case-sensitive.

Some operations taking numerical operands only work with integers. When doing arithmetic, PCC2 keeps track of which values are integers and which are fractions. Arithmetic operators and "+", "-", "*" yield integers when their operands are integers, others like "/" may generate fractions even when used on integers.

Operators

The following table shows all operators supported by PCC2. Operators have a certain precedence or binding strength, which determines which parts of the expression belong to that operator. For example, in 2 + 3 * 4, the precedence of * is higher than the precedence of +, so the multiplication is performed first. The addition then adds 2 and the result of 3 * 4. The table shows operators in increasing order of precedence, i.e. later operators are evaluated before those shown first.

a; b Sequence: evaluate a, discard its result, then evaluate b.
a := b Assignment: evaluate b, and assign it to a. Returns b.
a Or b Logical Or: return True if either operand is True. If the result is known after evaluating a, does not evaluate b (short-circuit evaluation).
a Xor b Logical Exclusive-Or: return True if one operand is True and one is False. If the result is known after evaluating a, does not evaluate b (short-circuit evaluation).
a And b Logical And: return False if either operand is False. If the result is known after evaluating a, does not evaluate b (short-circuit evaluation).
Not a Logical Not: return True if, False if operand is True.
a = b Comparison: return True if a is identical to b.
a <> bComparison: return True if a is different from b.
a < b Comparison: return True if a is less than b.
a > b Comparison: return True if a is greater than b.
a <= bComparison: return True if a less or equal to b.
a >= b Comparison: return True if a is greater or equal to b.
a # b Concatenation: convert a and b to strings and concatenate them.
a & bConcatenation: convert a and b to strings and concatenate them. If either is Empty but the other is not, treat the Empty one as empty string.
a + b Addition: add a and b, which may be numbers or strings.
a - b Subtraction: subtract b from a, which must both be numbers.
a * b Multiplication: multiply a and b, which must both be numbers.
a /  Division: divide a by b, which must both be numbers.
a \ b Integer division: divide a by b, which must both be integers, and discard the remainder.
a Mod b Integer remainder: divide a by b and return the remainder. Both operands must be integers.
-a Negation: change sign of a, which must be a number.
+a Unary plus: do not change sign of a, which must be a number. Exists for symmetry with "-".
a ^ b Power: compute a-to-the-bth. Both operands must be numbers.

Elementary Functions

Elementary functions are recognized whenever their name is followed by a parenthesized expression list. Elementary functions thus bypass normal name-lookup.

Most functions are regular, that is, they evaluate all their arguments, and yield Empty if an argument is Empty. Functions that do not follow these rules are marked as irregular.

Most functions expect their parameters of a particular type. In the descriptions, this is denoted by ":type" behind their name: Int=integer, Num=any numerical value, Str=string.

Abs(value:Num)
Yields the absolute value of value.
Asc(value)
If the argument is a string, return the Unicode value of its first character (see Chr()). If the argument is not a string, it is converted into one first. If the argument is an empty string, yields Empty.
ATan(value:Num)
ATan(x:Num, y:Num)
Compute the arc-tangent (=angle, in degrees) of x/y resp. value. With two arguments, the angle will have the correct quadrant. ATan(0,0) is Empty.
Atom(value:Str)
Places the string into an internal table, and returns an integer you can use to find it again later. When asking for the same string again, return the same integer again. This is used internally at a number of places in PCC2.
AtomStr(n:Int)
Performs the inverse mapping of Atom, i.e. return a previously stored string.
BitAnd(value:Int, ...)
Takes any number of integer arguments and returns the bitwise And of them.
BitNot(value:Int)
Returns the bitwise negation of its argument.
BitOr(value:Int, ...)
Takes any number of integer arguments and returns the bitwise Or of them.
BitXor(value:Int, ...)
Takes any number of integer arguments and returns the bitwise Xor of them.
Chr(value:Int)
Chr$(value:Int)
Returns a string consisting of the character with the given Unicode value. For values below 128, those are equal to the usual ASCII codes. For example, Chr(65) returns "A", and Chr(8745) returns "∩". In PCC 1.x, and PCC2 up to beta 11, Chr takes just values between 0 and 255, referring to characters from the game character set. For techno-geeks: PCC2 supports the WGL4 character repertoire, plus some additional characters.
Cos(angle:Num)
Returns the cosine of the angle, given in degrees.
Count(array, matchExpr) [irregular]
Iterate over array, and evaluate matchExpr for every element. Returns the number of elements where that expression yields true (nonzero). If the matchExpr is omitted, counts all objects, as if the expression were True.
CountPlanets(matchExpr) [irregular]
Returns the number of planets where the matchExpr yields true (nonzero). This is (almost) identical to Count(Planet, matchExpr).
CountShips(matchExpr) [irregular]
Returns the number of ships where the matchExpr yields true (nonzero). This is (almost) identical to Count(Ship, matchExpr).
Dim(array, index:Int)
Figures out the dimension (size) of a user-defined array (see Dim command). The second parameter is optional and specifies which dimension to query; it defaults to 1. For example, after the command Dim a(3,4,5), Dim(a,1) would yield 3, Dim(a,2) would yield 4, and Dim(a,3) would yield 5.
Eval(expr:Str)
Interprets the specified string as an expression, and evaluates that. For example, Eval("1+1") yields 2. This is an expression version of the Eval statement.
Exp(value:Num)
Returns the base-e exponential of the given value.
Find(array, matchExpr, valueExpr) [irregular]
Iterate over array, and evaluate matchExpr for every element. If that yields true (nonzero), return valueExpr. If no element matches, return Empty.
FindPlanet(matchExpr) [irregular]
Find the first planet for which matchExpr is true (nonzero), and returns its Id number. This is (almost) identical to Find(Planet, matchExpr, Id).
FindShip(matchExpr) [irregular]
Find the first ship for which matchExpr is true (nonzero), and returns its Id number. This is (almost) identical to Find(Ship, matchExpr, Id).
First(line:Str, delim:Str)
Locate the first occurence of delim in line, and return everything before that. If delim does not occur in line, return the whole line.
If(cond:Bool, exprIfTrue) [irregular]
If(cond:Bool, exprIfTrue, exprIfFalse) [irregular]
Evaluate cond. If it returns true (nonzero), evaluate exprIfTrue. Otherwise, evaluate exprIfFalse (or, if that is not specified, return Empty).
InStr(haystack:Str, needle:Str)
Return the position (1-based) of the first occurence of needle in haystack. If needle does not occur in haystack, returns 0.
Int(n:Num)
Convert n to an integer by truncating fractional digits (round towards zero).
IsArray(n)
Returns a nonzero value (treated as True by conditionals) if n is an array, otherwise 0 (treated as False). Actually, this function returns the number of dimensions of the array, i.e. after the command Dim a(7,8,9), IsArray(a) returns 3.
IsEmpty(n)
Returns True if n is Empty, otherwise False.
IsNum(n)
Returns True if n is numeric, otherwise False.
IsString(n)
Returns True if n is a string, otherwise False.
Key(keymap, k:Str)
Looks up the command bound to key k in the specified keymap. This is the opposite to the Bind statement. The result will be null or a number, which can be converted back into the command using AtomStr.
Left(s:Str, n:Int)
Returns the first n characters of s.
Len(s:Str)
Returns the number of characters in s.
Log(value:Num)
Returns the natural (base-e) logarithm of value.
LTrim(s:Str)
Removes left (leading) whitespace from s.
Max(value, ...)
Takes any number of arguments, which must be either all numeric or all strings. Returns the maximum value.
Mid(s:Str, start:Int)
Mid(s:Str, start:Int, len:Int)
Returns len characters from s starting at start (1-based). The two-argument form returns everything from start to the end of the string.
Min(value, ...)
Takes any number of arguments, which must be either all numeric or all strings. Returns the minimum value.
Rest(line:Str, delim:Str)
Locate the first occurence of delim in line, and return everything after that. If delim does not occur in line, return Empty.
Right(s:Str, n:Int)
Returns the last n characters of s.
Round(n:Num)
Convert n to an integer by rounding arithmetically (towards nearest integer).
RTrim(s:Str)
Removes right (trailing) whitespace from s.
Sin(angle:Num)
Returns the sine of the angle, given in degrees.
Sqr(value:Num)
Sqrt(value:Num)
Returns the square root of the value.
Str(value:Num)
Str(value:Num, digits:Int)
Convert value into a string. With two arguments, generate the specified number of fractional digits.
StrCase(expr)
Evaluates expr. All string operations in that expression are evaluated case-sensitively. This applies to comparisons and the Min/Max/InStr/First/Rest functions.
String(count:Int, str:Str)
String$(count:Int, str:Str)
Returns a string that contains count copies of str. If str is not specified, PCC2 uses a space character.
Tan(angle:Num)
Returns the tangent of the angle, given in degrees.
Trim(s:Str)
Removes all (leading and trailing) whitespace from s.
Val(s:Str)
Interprets the given string as a number and returns that. If the string cannot be interpreted as a number, returns Empty.
Z(value)
Zap(value)
If the value is 0 or an empty string, returns Empty, otherwise returns the value as is.

Functions and Arrays

Those items are found by regular name lookup. If you define a local variable named identical to one of these, using that name will find the local variable.

All items that are documented as func(...).prop are actually arrays that can also be used with the Find function, as Find(func, ...).

Beam(id:Int).prop
Access properties of a beam weapon.
CAdd(a:Str, ...)
This takes any number of cargospecs, adds them, and returns a new cargospec describing the result. For example, CAdd("10T","5TDM") yields "15T 5D 5M".
CCompare(a:Str, b:Str)
Both arguments are cargospecs. This function returns true if the amount described by a is enough to build an item the costs b. Supply sale is taken into account.
CDiv(a:Str, n:Int)
CDiv(a:Str, b:Str)
When used in the first form, divides cargospec a into n equal parts, and returns the size of one part (a cargospec). When used in the second form, checks how many times cargospec b fits into a, or, in simple words, computes how many items of price b you can build if you have a. Supply sale is taken into account. For example, CDiv("10T 5D",2) yields "5T 2D", CDiv("10T 5D", "3T") yields 3.
CExtract(a:Str, items:Str)
a is a cargospec, b is a list of cargo type letters. This extracts the cargo types described by b, and returns their total amount. For example, CExtract("10T 5M", "T") yields 10, CExtract("10T 5M", "TDM") yields 15.
Cfg(name:Str)
Cfg(name:Str, index:Int)
Access configuration (pconfig). For array options, the index must be specified to determine which array item to access. As an exception, it can be omitted if the option is indexed by player, you will access your own value in this case.
CMul(a:Str, n:Int)
Multiplies the cargospec a by the integer n. For example, CMul("10T 5M", 3) yields "30T 15M".
CRemove(a:Str, items:Str)
a is a cargospec, b is a list of cargo type letters. This removes the cargo types described by b from a, and returns the remainder. For example, CRemove("10T 5M", "T") yields "5M".
CSub(a:Str, b:Str, ...)
This takes any number of cargospecs, removes the second and all that follow from the first one,, and returns a new cargospec describing the result. Supply sale is taken into account. For example, CSub("10TDM","5T") yields "5T 10D 10M", CSub("100S", "50$") yields "50S".
Distance(x1:Int, y1:Int, x2:Int, y2:Int)
Distance(x1:Int, y1:Int, obj2)
Distance(obj1, x2:Int, y2:Int)
Distance(obj1, obj2)
Compute distance between two points. Each point can be specified either as an X/Y pair, or as an object. For example, Distance(1000,1000,Ship(10)) is the same as Distance(1000,1000,Ship(10).Loc.X,Ship(10).Loc.Y).
Engine(id:Int).prop
Access properties of an engine.
Format(fmt:Str, arg...)
Given a format string, interpolates the remaining arguments into the string (like the sprintf function in various other programming languages). The format string contains placeholders for the arguments. Some placeholders: You can specify a decimal number between the percent sign and the letter do format the result with at least that many places. At most 10 values can be interpolated into a string with each invocation.
FPos(#filenr)
Returns the current position in the specified file. Positions count from 0.
FreeFile()
Returns a currently unused (not open) file number. Note that this function returns the same value again and again until you use Open to use the file number.
FSize(#filenr)
Returns the size of the specified file, in bytes.
GetByte(dataBlock, pos:Int)
GetWord(dataBlock, pos:Int)
GetLong(dataBlock, pos:Int)
Extract a numerical value from the specified dataBlock at the given position. Positions count from 0. GetByte extracts a byte (0..255), GetWord extracts a two-byte word (-32768..32767), GetLong extracts a four-byte long.
GetStr(dataBlock, pos:Int, length:Int)
Extracts a string from the specified dataBlock at the given position. Up to length bytes are used. Reading stops at the first null byte. Trailing spaces are removed. This function thus can be used to read strings from VGA Planets files.
Hull(id:Int).prop
Access properties of a hull.
IsSpecialFCode(fc:Str)
Check whether the given friendly code is special.
Launcher(id:Int).prop
Access properties of a torpedo launcher.
Minefield(id:Int).prop
Access properties of a minefield.
Planet(id:Int).prop
Access properties of a planet.
PlanetAt(x:Int, y:Int[, flag:Bool])
Returns the Id number of the planet at the given x,y coordinates, if any. If the flag is specified and is true, also looks for planets whose gravity well covers that location. If no planet is found, returns 0.
PlanetName(id:Int)
Returns the name of the specified planet.
Player(id:Int).prop
Access properties of a player.
Random(a:Int, b:Int)
Random(b:Int)
Generate a random integer number. Using the first form, generates a random number between a and b, not including b (half-open interval). The second form behaves as if a were zero. For example, Random(10) generates random numbers between 0 and 9, as does Random(0, 10). Random(1,500) generates random numbers between 1 and 499, Random(500,1) generates random numbers between 2 and 500 (the first parameter always included in the range, the second one not).
RandomFCode()
Generate a random, non-special friendly code.
Ship(id:Int).prop
Access properties of a ship.
ShipName(id:Int)
Returns the name of the specified ship.
Storm(id:Int).prop
Access properties of an ion storm.
Torpedo(id:Int).prop
Access properties of a torpedo. Note that those are the same as the properties of the launcher, except for the Cost properties, which describe the actual torpedo here.
Translate(s:Str)
Translate the given English string into the user's language, using PCC2's translation database. Note that you cannot extend the database from a script yet, so you can only translate strings that already appear translated somewhere in PCC2.
Truehull(slot:Int)
Truehull(slot:Int, player:Int)
Returns the number of the slot-th hull which the specified player (or you, if no player argument given) can build. slot can range from 1 to 20. If the specified slot is not taken, the result will be 0, otherwise you can use the result as parameter to Hull() to fetch properties of the actual hull.
Ufo(id:Int).prop
Access properties of an Ufo.

Formal Grammar

sequence:
      assignment
      sequence ";" assignment

assignment:
      or-expr
      assignment ":=" or-expr

or-expr:
      and-expr
      or-expr "Or" and-expr
      or-expr "Xor" and-expr

and-expr:
      not-expr
      and-expr "And" not-expr

not-expr:
      comparison
      "Not" not-expr

comparison:
      concat-expr
      comparison "=" concat-expr
      comparison "<" concat-expr
      comparison ">" concat-expr
      comparison "<=" concat-expr
      comparison ">=" concat-expr
      comparison "<>" concat-expr

concat-expr:
      add-expr
      concat-expr "#" add-expr
      concat-expr "&" add-expr

add-expr:
      mult-expr
      add-expr "+" mult-expr
      add-expr "-" mult-expr

mult-expr:
      neg-expr
      mult-expr "*" neg-expr
      mult-expr "/" neg-expr
      mult-expr "\" neg-expr
      mult-expr "Mod" neg-expr

neg-expr:
      pow-expr
      "-" neg-expr
      "+" neg-expr

pow-expr:
      primary-expr
      primary-expr "^" neg-expr

primary-expr:
      "(" sequence ")"
      string-literal
      integer-literal
      float-literal
      "True"
      "False"
      "Pi"
      identifier invocation*

invocation:
      "(" arguments ")"
      "." identifier

arguments:
      nothing
      sequence ("," sequence)*

identifier:
      sequence of letters, "$", "_", digits, ".",
        not starting with a digit or period
        not ending with a period

string-literal:
      "'" (any character except for "'")* "'"
      """ (any character except for """ and "\", or "\" followed by any character) """

integer-literal, float-literal:
      digit digit*
      digit digit* "."
      digit* "." digit digit*
        A value is an integer if it has no decimal point and fits into
        32-bit range. Otherwise, it's float.

Statements

Basics

Statements tell PCC2 to do something. You can enter statements at the console, or place them in files for execution.

PCC2 distinguishes one-line commands and multi-line (block) commands. A one-line command can be used everywhere a multi-line command can be used, but not vice versa.

A one-line command can be one of the following:

Multi-line commands are always elementary commands. You cannot define own multi-line commands.

PCC2 decides a statement's kind by looking at the first one or two tokens. There is an ambiguity if the statement starts with If. Such a line is always interpreted as an If statement. If you have written an expression-If (If(a,b,c)) instead, which is not a valid If statement, you will get a syntax error. If you really wish to have an expression-If, enclose it in parentheses to make it unique.

command-list:
    (one-line-command | block-command)*

Elementary Commands

one-line-command:
    "Abort" [expression]

Abort: Generates an error. The expression is evaluated and produces the error message. If the expression is not specified, a default error message is generated. The error can be caught using a Try command.

If a=0 Then Abort "a must not be 0"
one-line-command:
    "Bind" identifier key-binding ("," key-binding)*

key-binding:
    expression ":=" expression

Bind: Defines a key. The identifier specifies a keymap (see CreateKeymap). The key-bindings assign a command (right side) to a keystroke (left side). Both are strings.

Keystrokes consist of zero or more modifiers (Ctrl-, Alt-, Shift-, Meta-, may be abbreviated to C-, A-, etc.), followed by a key name. A key name is either an ASCII character, or a special key name: F1 to F15, Backspace/BS, Pause, Del, Down, End, ESC, Home, Ins, Left, Num5, PgDn, PgUp, Print, Ret/Enter, Right, Space/Spc, Tab, Up, or WheelUp/WheelDown for mouse wheel events. In addition, Quit means the "close-me" button on the window frame ([X]).

As a general rule, PCC2 allows you to bind all printable ASCII characters, i.e. latin letters, digits, and various punctuation marks. This is guaranteed to be supported in all PCC versions.

In addition, as of PCC2 beta 12, you can bind all keys from the game character set. That is, when you play a Western European or American game using the cp437 or latin1 character set, you can bind the Ä key; when you play a Russian game using the cp866 or koi8r character set, you cannot. This restriction may be lifted in the future.

Unlike PCC 1.x, PCC2 is case-sensitive. When you bind Shift-A, you must actually type an upper-case A to trigger this function (i.e. press Shift-A). PCC 1.x didn't distinguish between upper and lower case for (latin) alphabetic keys. Otherwise, PCC2 ignores the Shift modifier for printable keys. Shift-4 generates a "$" sign, so you have to bind $, not Shift-4, if you want something to happen on Shift-4. When in doubt, use the keymap debugger.

Bind ShipScreen "alt-x" := "Print 'Alt-X pressed'"

As a special case, the right side of a key assigment can also be a value returned by the Key function. This will copy a function from one keymap to another. Unlike PCC 1.x, which had many uncopyable "magic" commands, almost anything sensible can be copied this way in PCC2.

Bind BaseScreen "s" := Key(PlanetScreen, "s")

This works because key commands are atoms internally.

Print AtomStr(Key(ShipScreen, "alt-x"))
one-line-command:
    "Break"

Break: This command is only valid within loops (Do, For, ForEach). It continues execution after the loop, cancelling all iterations that would normally follow.

For i:=1 To 100
  If i Mod 7 = 0 Then Break
  Print i, " is not divisible by 7"
Next
one-line-command:
    "Close" fileNr

Close: Closes the specified file. This will write all buffered data to the disk, and make the file number available for re-use.

one-line-command:
    "Continue"

Continue: This command is only valid within loops (Do, For, ForEach). It terminates the current iteration and continues with the next iteration of the loop, if any.

one-line-command:
    "CreateKeymap" keymap-definition ("," keymap-definition)*

keymap-definition:
    identifier
    identifier "(" identifier ("," identifier)* ")"

CreateKeymap: Create one or more keymaps. The first identifier in each keymap-definition is the name of the new keymap, which must not yet exist. Optionally, you can specify one or many parent keymaps in parentheses. Those are searched in left-to-right order if a key is not found in the keymap itself. Normally, PCC creates all necessary keymaps so you don't have to use CreateKeymap yourself.

CreateKeymap Global, Ship
CreateKeymap ControlScreen(Global)
CreateKeymap ShipScreen(Ship, ControlScreen)
one-line-command:
    "CreatePlanetProperty" identifier ("," identifier)*
    "CreateShipProperty" identifier ("," identifier)*

CreatePlanetProperty, CreateShipProperty: These commands create new planet or ship properties. You can access those like regular properties, and store arbitrary values (strings, numbers) in them. They are automatically stored in the starcharts file and restored when you reload your game.

CreatePlanetProperty Value
Planet(10).Value := 9000
one-line-command:
    "Dim" [storage-class] variable-definition ("," variable-definition)*
    storage-class variable-definition ("," variable-definition)*

storage-class:
    "Local"
    "Static"
    "Shared"

variable-definition:
    identifier
    identifier ("="|":=") expression
    identifier "(" expression ("," expression)* ")"

Dim/Local/Static/Shared: Defines new variables. If the variable is indeed new, it will be pre-initialized with the expression, if specified. If you try to redefine a variable that already exists, nothing will happen.

When you specify dimensions, Dim will make an array. You can specify as many dimensions as you want, but the total number of elements in the array must be less than about 100 million. Dimensions must be integers. Specifying a dimension of n means that the array will contain elements 0 .. n-1, i.e. Dim zz(10) will make an array containing ten elements, zz(0) up to zz(9). As usual, if you try to redefine a variable that already exists, nothing will happen.

The storage-class defines the lifetime of the variable:

The following three commands are synonyms:

Dim i=7
Local i=7
Dim Local i=7

When a variable name is mentioned in an expression or statement, PCC2 checks all active contexts:

This means that it's possible to define variables that immediately become inaccessible again because they're shadowed by an "inner" context. For example,

With Ship(10) Do
  Local Name="foo"
  Print Name
EndWith

will print ship 10's name, not "foo", because the With statement is checked first.

block-command:
    "Do" [loop-condition]
       command-list
    "Loop" [loop-condition]

loop-condition:
    "While" expression
    "Until" expression

Do: Repeats the command list. Before each iteration, checks the top loop-condition, if specified. The iteration is only performed if the While condition yields true or the Until condition yields false or empty. If no top condition is specified, the iteration is started unconditionally. After each iteration, checks the bottom loop-condition, if specified. Likewise, the loop continues with the next iteration if the While condition yields true or the Until condition yields false or empty. If no bottom condition is specified, the loop continues unconditionally.

i:=0
Do While i < 10
  Print i
  i := i+1
Loop
one-line-command:
    "End"

End: Terminates this script. This command normally makes no sense in regular code such as keybindings, but it may be useful in scripts intended to run stand-alone. To exit from a subroutine, use Return.

one-line-command:
    "Eval" expression ("," expression)*

Eval: Builds a temporary script from the parameters, and executes that. Each parameter is one script line.

Eval "For i:=1 To 10 Do Print x"
one-line-command:
    "For" identifier ":=" expression "To" expression "Do" one-line-command
block-command:
    "For" identifier ":=" expression "To" expression ["Do"]
       command-list
    "Next"

For: Counting loop. The identifier names a variable. The variable is set to the value of the first expression, and counts up in steps of one until it reaches the value of the second expression. Both expressions must be numeric.

For i:=1 To 10 Do Print x
one-line-command:
    "ForEach" expression "Do" one-line-command
block-command:
    "ForEach" expression ["Do"]
       command-list
    "Next"

ForEach: The expression must yield an array. The contained command is executed once for each element of the array. This is the command version of Find() and Count().

ForEach Ship Do
  Print Name
Next
block-command:
    "Function" identifier [parameter-list]
       command-list
    "EndFunction"

parameter-list:
    "(" parameter ("," parameter)* ")"
    "(" parameter ("," parameter)* "()" ")"

parameter:
    identifier
    "Optional" identifier

Function: Defines a function. Whenever the function name (the identifier) is invoked in an expression, the command-list will be executed. The command-list should contain a Return statement which determines the function result.

The parameters must be specified in parentheses at the function call. Their values will be available to the command-list as if they were local variables. One parameter can be preceded by Optional, which means that all following parameters may be omitted at the call. They will then be empty within the function body.

The last parameter may be followed by "()". This makes the function a varargs function which accepts any number of parameters. For example, Function foo(a,b,c()) starts a function that takes at least two parameters, a and b. All following parameters (zero or more) will be placed in an array c. You can use the Dim function to find out how many parameters you were given.

Note that function invocations always need parentheses.

Function Square(x)
  Return x*x
EndFunction

Print Square(7)
Function Sum(x())
  Local s:=0, i
  For i:=0 To Dim(x)-1 Do s:=s+x(i)
  Return s
EndFunction

Print Sum(1,2,3,4)
one-line-command:
    "Get" fileNr "," dataBlock "," length

Get: Read binary data from a file. Reads length bytes from the file fileNr, and stores them in dataBlock, which should be a variable. The dataBlock can then be examined using functions such as GetWord().

Local e
Open "xyplan.dat" For Input As #1
Get #1, e, 6
Print "X coordinate of planet #1 is ", GetWord(e, 0)
one-line-command:
    "If" expression "Then" one-line-command
block-command:
    "If" expression ["Then"]
       command-list
    ["Else If" expression ["Then"]
       command-list]*
    ["Else"
       command-list]
    "EndIf"

If: Conditional execution. Evaluates the expression. If it yields true, executes the command following Then. Otherwise, executes the first matching Else If part, if any. If none of those matches, executes the commands following Else, if specified.

If a Then
  Print "a is nonzero"
  If a>10 Then Print "a is greater than 10"
Else
  Print "a is zero or empty"
EndIf

If i=1 Then
  Print "One"
Else If i=2 Then
  Print "Two"
Else If i=3 Then
  Print "Three"
Else
  Print "None of one, two, three
EndIf
one-line-command:
    "Input" fileNr "," string ["," flag]

Input: Read text line from a file. Reads a line from the file fileNr and puts it into the string. When the end of the file has been reached, and no line can be read, this command generates an error by default. When the flag is given as True, however, the command succeeds in this case and sets string to Empty.

one-line-command:
    "Load" expression

Load: The expression yields a file name. That file is opened as a script file and executed.

one-line-command:
    "On" identifier "Do" one-line-command

On: Registers the one-line-command for execution upon the event named by the identifier. This feature is called hooks because it allows you to hook into various parts of PCC functionality.

Currently, PCC implements the following hooks. Note that this is incompatible to PCC 1.x, and still subject to change.

You can also define your own hooks which you can then invoke using RunHook. A hook is defined by simply using it with an On statement.

On Load Do MyInitialisation
one-line-command:
    "Open" name "For" open-mode "As" fileNr

open-mode:
    "Input"
    "Output"
    "Append"
    "Random"

Open: opens the file specified by the name, and associates it with the given file number. The open-mode specifies the desired operations:

When you try to open a file number which is already open, PCC2 closes it first.

block-command:
    "Option" option ("," option)

option:
    identifier ["(" parameters... ")"]

Option: This command is special. It does not by itself do something, but affects how the script interpreter reads and executes your script. It is similar to a pragma or compiler directive in other programming languages.

Option is followed by the option name, an identifier, optionally followed by parameters in parentheses. The actual content of the parentheses depends on the option, but in any case, parentheses must pair up. Multiple options can be combined into a single command. Options not known to the interpreter are ignored. In particular, PCC 1.x ignores the whole Option command.

Although Option takes only a single line of code, it is classified as a block-command. It can thus be only used alone on a line. This is to emphasize the effect that it is processed even when the particular piece of code actually never executes.

one-line-command:
    "Print" [expression ("," expression)*]
    "Print" "#" fileNr ["," expression ("," expression)*]

Print: Evaluate all expressions, concatenate their result, and write them to the console. If the first expression is a file number, output is written to that file instead.

one-line-command:
    "Put" fileNr "," dataBlock ["," length]

Put: Write binary data to a file. Writes length bytes from the dataBlock into file fileNr. If length is omitted, it defaults to the size (highest used index) of the data block. It is recommended to always specify the length.

one-line-command:
    "Return" [expression]

Return: Stops execution of the current Sub or Function and returns to the caller. If this is a function, the expression must be specified and contains the function result. For subs, the expression must not be specified.

one-line-command:
    "RunHook" identifier

RunHook: Runs the hook named by identifier. That is, if there are any commands associated with that event, those are run (in the context of your script). See On...Do for more information about hooks.

one-line-command:
    "Seek" fileNr "," position

Seek: Set the file pointer of the file. The position counts from 0. The file pointer specifies the next position from which data is read, or at which data is written.

block-command:
    "Select Case" expression
       ("Case" case-selector ("," case-selector)*
          command-list)*
       ["Case Else"
          command-list]
    "EndSelect"

case-selector:
    expression
    "Is <" expression
    "Is <=" expression
    "Is =" expression
    "Is >=" expression
    "Is >" expression
    "Is <>" expression

Select Case: Evaluates the Select Case expression. It then selects the first case whose selector matches the result of the expression, and executes the corresponding command-list. If no selector matches, executes the Case Else part, if given.

The expressions used in the selectors should be constants. This is currently not enforced.

Select Case i
  Case 1
    Print "one"
  Case 2,3,4
    Print "two to four"
  Case Is < 10
    Print "below ten, but not one to four"
  Case Else
    Print "anything else"
EndSelect
one-line-command:
    "SelectionExec" [selection-expr ":="] selection-expr

SelectionExec: evaluate a selection expression. If an assignment operator is used, assigns the right-hand side to the left-hand side, which must be a layer name or Current. Otherwise, assigns to the current layer. The selection expressions are the same as the ones used for the Selection Manager's "Copy" command, see there for more information.

one-line-command:
    "SetByte" dataBlock "," index "," value
    "SetWord" dataBlock "," index "," value
    "SetLong" dataBlock "," index "," value

SetByte/SetWord/SetLong: Store integer value in a data block. Those commands are the opposite of the GetWord function and its relatives. They store the given value as a 1/2/4-byte integer at the given index in the given dataBlock.

one-line-command:
    "SetStr" dataBlock "," index "," length "," value

SetStr: Store string in a data block. Those commands is the opposite of the GetStr function. It stores the given string in a length-byte field at the given index in the given dataBlock.

one-line-command:
    "Stop"

Stop: Suspend this script. Stops execution of this script until the script is woken up again.

The script's entire state is saved, and restored when the script wakes up. PCC2 wakes up all scripts once when it loads a turn. This can be used to wait for certain game events to happen, for example, a ship reaching a planet.

Local and static variables will be preserved. Shared variables are shared between all scripts, and may be changed by scripts that run while this script is suspended.

Some context cannot be saved. For example, when the script is currently displaying a dialog, it cannot suspend. If it attempts to suspend anyway, it will be forcibly terminated.

block-command:
    "Sub" identifier [parameter-list]
       command-list
    "EndSub"

Sub: Defines a subroutine. The identifier becomes a new one-line command. It can be invoked by specifying the identifier followed by the parameters. See Function for more details about the subroutine body.

Subroutines must be invoked as one-line command, without parentheses around the parameters.

Sub PrintSquares(from,to)
  Local i
  For i:=from To to Do
    Print "The square of ", i, " is ", i*i
  Next
EndSub

PrintSquares 1, 10
one-line-command:
    "Try" one-line-command
block-command:
    "Try"
       command-list
    ["Else"
       command-list]
    "EndTry"

Try: Executes the command following Try. If that command generates an error, and an Else part is specified, System.Err is set to contain the error, and the Else part is executed. Otherwise, the error is ignored.

Try
  a:=b/c
Else
  Print "Something happened: ", System.Err
  Print "Usually, division fails due to c being zero"
EndTry
one-line-command:
    "TryLoad" expression

TryLoad: The expression yields a file name. That file is opened as a script file and executed. It is not an error if the file does not exist.

one-line-command:
    "With" expression "Do" one-line-command
block-command:
    "With" expression ["Do"]
       command-list
    "EndWith"

With: Evaluates the expression. The result must be a context. That context is temporarily entered for executing the contained command.

With Ship(10) Do
  Print "The name of ship 10 is ", Name
EndWith
Print "The name of ship 10 is ", Ship(10).Name

Stefan Reuther
Best viewed with a CSS-capable browser.
Last modified: Fri Apr 22 13:56:57 CEST 2011