Pascal language reference
This chapter describes the Pascal language of TMS Scripter, for the people who write scripts. The language follows Delphi (Object Pascal) closely, with the simplifications and differences described here. Each language of TMS Scripter has its own reference chapter with the same structure: see also the Basic and JavaScript references.
The objects, functions and libraries available to a script depend on the application that runs it. This chapter describes the language and the routines that are always available; the documentation of your application tells which additional objects and libraries it provides.
A first example
uses
Classes;
var
Names: TStringList;
I: Integer;
function Greeting(Name: string): string;
begin
Result := 'Hello, ' + Name + '!';
end;
begin
Names := TStringList.Create;
try
Names.Add('Anna');
Names.Add('Bruno');
for I := 0 to Names.Count - 1 do
ShowMessage(Greeting(Names[I]));
finally
Names.Free;
end;
end;
Script structure
A script is made of these parts, in this order, all of them optional:
- A
programheader:program MyScript;(ignored). - A
usesclause listing the libraries and other scripts the script uses (see Using libraries and other scripts). - Global declarations:
varandconstsections. - Procedures and functions.
- The main block: the statements that run when the script is executed.
The main block can be a begin..end block, optionally followed by a dot, or just a list of
statements:
procedure SayHello;
begin
ShowMessage('Hello');
end;
begin
SayHello;
end.
ShowMessage('This script is a single statement');
Statements are separated by semicolons. A script can also contain only procedures and functions; then the application calls them directly, for example as event handlers.
Script result
The main block works like a function: assign a value to Result to return it to the
application. Applications use this to evaluate expressions and formulas. If Result is not
assigned, the script returns Null.
Result := (10 + 6) / 4;
Lexical elements
Case sensitivity
Pascal is not case-sensitive: begin, Begin and BEGIN are the same keyword, and MyVar,
myvar and MYVAR are the same identifier. String values are case-sensitive.
Comments
// a line comment, up to the end of the line
{ a block comment,
which can span several lines }
(* another block comment *)
Comments do not nest: a { inside a { } comment is part of the comment, and the first }
ends it. Comment markers inside strings are ordinary characters. Separate a comment from the
code before it with a space.
Identifiers and reserved words
Identifiers (names of variables, constants, procedures, functions and parameters) start with a
letter or an underscore, followed by letters, digits or underscores. Letters are the ASCII
letters A to Z.
Total _temp Item2 first_name // valid
2Items first-name my name // invalid
These reserved words cannot be used as identifiers: and, as, begin, case, const,
div, do, end, except, finally, for, forward, function, if, is, mod, not,
or, procedure, program, repeat, shl, shr, step, then, to, try, until,
var, while, with, xor. They are allowed as member names after a dot (Obj.End). An
identifier can start with a reserved word, like Format or Ending.
Numbers
42 // integer
-7 // negative integer
3.25 // floating-point number
1.5e-2 // floating-point number with exponent (0.015)
$FF // hexadecimal integer (255)
A number without fraction and exponent is an integer; integers are 64-bit values. A
floating-point number needs digits on both sides of the dot: write 0.5, not .5.
Strings
Strings are enclosed in single quotes. Two consecutive quotes inside a string represent one quote. Double quotes are ordinary characters.
S := 'Hello, world';
S := 'It''s here'; // It's here
S := 'Say "hi"'; // Say "hi"
S := ''; // empty string
# followed by a character code (decimal, or hexadecimal with #$) is a character. Character
codes can be written right next to quoted parts, without +:
S := 'First line'#13#10'Second line';
S := #65#66#67; // ABC
S := 'Euro: '#$20AC;
Special values
| Value | Meaning |
|---|---|
True, False |
Boolean values. |
nil |
No object. Object variables that refer to no object are nil. |
Null |
The null value. A variable that is never assigned is Null. |
Unassigned |
The unassigned value (converted to an empty string or zero). |
Variables
Declaring variables
Variables are declared in var sections. A type is optional: every variable can hold values
of any type (see Values and types).
var
Count;
Name: string;
A, B, C;
A declared type documents the variable and, for object types, lets the script editor offer code completion for the members of the object. It does not restrict the values the variable can hold.
Variables must be declared before they are used. Some applications relax this rule: then assigning a value to an undeclared name creates the variable.
Scope
Variables declared outside procedures and functions are global: they are visible in the main block and in every procedure and function. Variables declared inside a procedure or function are local to it, and a local variable hides a global variable with the same name.
var
Counter; // global
procedure Increment;
var
Step; // local
begin
Step := 1;
Counter := Counter + Step;
end;
begin
Counter := 0;
Increment;
Increment;
ShowMessage(IntToStr(Counter)); // 2
end;
Array variables
A variable declared with an array type is created as an array, ready to use. Arrays are always zero-based, so only the upper bound is needed; the type of the items is optional:
var
Names: array[0..9] of string; // 10 items, indexes 0 to 9
Values: array[9]; // the same size
begin
Names[0] := 'first';
end;
A lower bound other than zero is not allowed. See also Arrays.
Constants
Constants are declared in const sections, globally or inside a procedure or function. Their
value can be an expression. A constant cannot be changed.
const
MaxItems = 100;
Title = 'Report';
HalfMax = MaxItems div 2;
Values and types
Variables, parameters and function results hold values of any type, and the type of the value can change when a new value is assigned. Scripts work with these kinds of values:
- integers (64-bit) and floating-point numbers;
- strings;
- Boolean values (
True,False); - dates and times (floating-point numbers: the integer part is the date, the fraction is the time of day);
- objects (or
nil); - arrays;
NullandUnassigned.
Values are converted automatically when an operation needs another type, following the rules
of the Delphi Variant type:
- A string that contains a number is converted to a number in arithmetic:
'1' + 2is3. - A string that does not contain a number cannot be used in arithmetic:
'Total: ' + 5is an error. Convert the number to a string first:'Total: ' + IntToStr(5). +between two strings concatenates them:'1' + '2'is'12'.- Comparisons between strings and numbers compare the numbers:
'10' = 10isTrue. - Arithmetic with
NullgivesNull.
Use the conversion routines (IntToStr, StrToInt, FloatToStr, VarToStr and others, see
Built-in routines) to convert values explicitly.
Expressions and operators
Arithmetic operators
| Operator | Operation | Example | Result |
|---|---|---|---|
+ |
Addition (or string concatenation) | 7 + 2 |
9 |
- |
Subtraction | 7 - 2 |
5 |
* |
Multiplication | 7 * 2 |
14 |
/ |
Division, always a floating-point result | 7 / 2 |
3.5 |
div |
Integer division, truncated toward zero | 7 div 2 |
3 |
mod |
Remainder of the integer division | 7 mod 2 |
1 |
^ |
Power, always a floating-point result | 2 ^ 3 |
8.0 |
- (unary) |
Negation | -X |
Division by zero with div or mod is an error.
Comparison operators
| Operator | Meaning |
|---|---|
= |
Equal |
<> |
Not equal |
<, > |
Less than, greater than |
<=, >= |
Less than or equal, greater than or equal |
String comparisons are case-sensitive: 'abc' = 'ABC' is False. Use CompareText or
UpperCase to compare strings without case sensitivity.
Logical and bitwise operators
| Operator | Operation |
|---|---|
not |
Logical negation |
and |
Logical and; bitwise and on integers |
or |
Logical or; bitwise or on integers |
xor |
Logical exclusive or; bitwise exclusive or on integers |
shl, shr |
Bitwise shift left and right |
and and or are short-circuit operators: when the left operand decides the result, the right
operand is not evaluated. This makes expressions like the following safe:
if (Count > 0) and (Total div Count > 10) then
ShowMessage('Average above 10');
Note
and, or and xor work as integer operations: with Boolean operands, the result may be the
integer -1 (true) or 0 (false) instead of True or False. Both work in any condition
(if, while, until). not is always a logical operation: not X is True when X is
False or zero.
String operators
+ concatenates strings. To add a number to a string, convert it first:
Msg := 'You have ' + IntToStr(Count) + ' new messages';
Object operators
| Operator | Operation |
|---|---|
is |
Tests if an object is an instance of a class, or of a descendant class: Sender is TButton |
as |
Treats an object as an instance of a class: Sender as TButton |
as must be the last part of an expression. To call a member of the class, assign the result
to a variable first.
Operator precedence
Operators with higher precedence are evaluated first. Operators with the same precedence are evaluated from left to right. Use parentheses to change the order.
| Precedence | Operators |
|---|---|
| 1 (highest) | not, unary - and + |
| 2 | ^, is |
| 3 | *, /, div, mod, and, shl, shr |
| 4 | +, -, or, xor |
| 5 (lowest) | =, <>, <, >, <=, >= |
As in Delphi, comparisons have the lowest precedence, so put them in parentheses when
combining them with and and or: write (A > 0) and (B > 0), not A > 0 and B > 0.
Write a unary minus or plus right before its operand, without a space: -X, -(A + B).
Statements
Assignment
The assignment operator is :=. The target can be a variable, an object property, an array
item or a string character:
Total := Price * Quantity;
Button1.Caption := 'OK';
Items[2] := 'third';
Name[1] := 'J';
Compound statements
begin..end groups statements where a single statement is expected:
if Total > 100 then
begin
Discount := 10;
ShowMessage('Discount applied');
end;
if statement
if Total > 100 then
Discount := 10;
if Total > 100 then
Discount := 10
else
Discount := 0;
if Score >= 90 then
Grade := 'A'
else if Score >= 80 then
Grade := 'B'
else
Grade := 'C';
An else belongs to the nearest if. The condition can be a Boolean value or a number (zero
is false).
case statement
case runs the branch whose value matches the selector. Unlike Delphi, the selector and the
values can be of any type, including strings, and the values can be expressions. Several
values can share a branch, separated by commas. The optional else part runs when no value
matches.
case UpperCase(Fruit) of
'LIME', 'KIWI': Color := 'green';
'LEMON': Color := 'yellow';
'APPLE':
begin
Color := 'red';
Found := True;
end;
else
Color := 'unknown';
end;
Only the first matching branch runs, and the selector is evaluated once. Each branch, and the
else part, has one statement: use begin..end for more. Ranges such as 1..5 are not
supported: list the values.
for statement
for I := 1 to 10 do
Sum := Sum + I;
for I := 10 downto 1 do
ShowMessage(IntToStr(I));
for I := 0 to 100 step 10 do
Total := Total + I;
tocounts up anddowntocounts down, by one.stepsets another increment fortoloops. It must be a number (negative or floating-point numbers are allowed):for I := 10 to 0 step -2 do.downtodoes not acceptstep.- The initial and final values are evaluated once, before the loop starts.
- If the range is empty (
for I := 5 to 1 do), the loop body does not run. - After the loop, the counter variable holds the final value plus one step.
while statement
while repeats a statement while a condition is true. The condition is tested before each
iteration, so the statement may not run at all.
while I < Count do
begin
Process(I);
I := I + 1;
end;
repeat statement
repeat repeats statements until a condition is true. The condition is tested after each
iteration, so the statements run at least once. repeat..until does not need begin..end.
repeat
K := I mod J;
I := J;
J := K;
until J = 0;
break and continue
break leaves the innermost loop. continue skips the rest of the current iteration and goes
to the next one (in a repeat loop, to the until test).
for I := 0 to List.Count - 1 do
begin
if List[I] = '' then
continue;
if List[I] = 'END' then
break;
Process(List[I]);
end;
exit statement
exit leaves the current procedure or function immediately. In a function, the value already
assigned to Result is returned. In the main block, exit ends the script.
function FindIndex(List, Value);
var
I;
begin
Result := -1;
for I := 0 to List.Count - 1 do
if List[I] = Value then
begin
Result := I;
exit;
end;
end;
with statement
with gives direct access to the members of an object, without repeating its name:
with Button1 do
begin
Caption := 'OK';
Width := 100;
end;
The class of the object must be known when the script is compiled: an object provided by the
application, or a variable declared with a class type (var B: TButton;).
Procedures and functions
Declaring procedures and functions
A procedure performs an action; a function also returns a value. Declare them before the main block. Parameter and result types are optional.
procedure ShowTotal(Value);
begin
ShowMessage('Total: ' + FloatToStr(Value));
end;
function Max(A, B);
begin
if A > B then
Result := A
else
Result := B;
end;
function Average(Total: Double; Count: Integer): Double;
begin
Result := Total / Count;
end;
A routine without parameters is declared without parentheses: procedure Refresh;.
Parameter groups can be separated by commas or semicolons. A routine can declare its own
var and const sections before its begin.
Returning a value
A function returns the value of its implicit Result variable. Result can be assigned and
read any number of times; if it is not assigned, the function returns Null.
function Factorial(N);
begin
Result := 1;
while N > 1 do
begin
Result := Result * N;
N := N - 1;
end;
end;
Parameters by value and by reference
Parameters are passed by value: the routine receives a copy, and changing the parameter does
not change the variable passed by the caller. Declare a parameter with var to pass it by
reference: then the routine changes the variable of the caller. Only a variable can be passed
to a var parameter (not an expression, an array item or a property).
procedure Swap(var A, B);
var
Temp;
begin
Temp := A;
A := B;
B := Temp;
end;
var
X, Y;
begin
X := 1;
Y := 2;
Swap(X, Y); // now X is 2 and Y is 1
end;
Calling procedures and functions
Arguments are given in parentheses. A routine without parameters is called with or without
empty parentheses (Refresh or Refresh()). The number of arguments must match the
declaration.
A routine must be declared before the code that calls it. To call a routine that is declared
later, for example in two routines that call each other, declare it first with forward:
function IsOdd(N); forward;
function IsEven(N);
begin
if N = 0 then
Result := True
else
Result := IsOdd(N - 1);
end;
function IsOdd(N);
begin
if N = 0 then
Result := False
else
Result := IsEven(N - 1);
end;
Routines can call themselves (recursion).
Error handling
try..except
The statements between try and except run; if an error occurs, execution continues in the
except part. Errors raised by the script, by the routines it calls and by the application
are all handled.
try
Value := StrToInt(Text);
except
ShowMessage('Invalid number: ' + LastExceptionMessage);
Value := 0;
end;
try..finally
The statements of the finally part always run, whether an error occurred or not; the error,
if any, continues after the finally part. Use it to release resources:
List := TStringList.Create;
try
List.LoadFromFile(FileName);
Process(List);
finally
List.Free;
end;
try..except and try..finally blocks can be nested to combine both behaviors.
Raising errors
Raise raises an error with a message. The script stops at that point, unless the error is
handled by a try..except block.
if Quantity <= 0 then
Raise('Quantity must be positive');
Information about the last error
Inside an except block, these values describe the error being handled:
| Name | Description |
|---|---|
LastExceptionMessage |
The error message. |
LastExceptionClassName |
The class name of the error (exception class). |
LastExceptionLine, LastExceptionCol |
The script position where the error occurred. |
LastExceptionHelpContext |
The help context number of the error. |
An error that is not handled stops the script, and the application receives it with its message and position.
Arrays
Square brackets create an array from a list of values. Arrays are zero-based, and items can be of any type. Nested arrays give multi-dimensional arrays.
var
Primes, Matrix, Item;
begin
Primes := [2, 3, 5, 7];
Item := Primes[1]; // 3
Primes[0] := 1;
Matrix := [['a', 'b'], ['c', 'd']];
Item := Matrix[1, 0]; // 'c'
Item := Matrix[1][0]; // also 'c'
end;
Length(A)is the number of items;Low(A)andHigh(A)are the first and last indexes.VarArrayCreate([0, 9], 12)creates an array of 10 empty items.- Accessing an index out of the bounds is an error.
- See also Array variables.
Strings
Besides the operators and the string routines (see Built-in routines), strings can be indexed to read or change a single character. The first character has index 1:
S := 'Hello';
C := S[1]; // 'H'
S[1] := 'J'; // S is 'Jello'
Working with objects
The application makes objects available to scripts: the controls of a form, data objects, or
any other object of the application. Scripts can also create objects of the classes the
application provides, like TStringList.
Properties and methods
Use a dot to access properties and methods. Methods without parameters can be called with or without parentheses.
Edit1.Text := 'Hello';
Width := Panel1.Width - 10;
Memo1.Lines.Add('New line');
Memo1.Lines.Clear;
Creating and destroying objects
Call the Create method of a class to create an object, and Free to destroy it. To check if
a variable refers to an object, use Assigned(Obj): comparing with nil works only for
variables that were set to nil or to an object, not for variables never assigned.
var
List: TStringList;
begin
List := TStringList.Create;
try
List.Add('one');
finally
List.Free;
end;
end;
Default and indexed properties
Indexed properties take the index in square brackets. When a class has a default indexed property, the property name can be omitted:
S := Memo1.Lines.Strings[0];
S := Memo1.Lines[0]; // the same: Strings is the default property
Enumerations and sets
Enumeration values are used by name, like constants. To set a set property, assign an array of
values. Where a routine expects a set, convert the array with SetOf:
Panel1.Align := alClient;
Label1.Font.Style := [fsBold, fsItalic];
Answer := MessageDlg('Save changes?', mtConfirmation, SetOf([mbYes, mbNo]), 0);
Events
To handle an event of an object, write a procedure with the parameters of the event, and assign the procedure name, as a string, to the event property:
procedure Button1Click(Sender: TObject);
begin
ShowMessage('Clicked');
end;
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
CanClose := Memo1.Lines.Count > 0;
end;
begin
Button1.OnClick := 'Button1Click';
Form1.OnCloseQuery := 'FormCloseQuery';
end;
Declare as var the event parameters that the handler changes, like CanClose above.
Using libraries and other scripts
The uses clause, at the beginning of the script, gives access to libraries: units of the
application (for example Classes, SysUtils, Dialogs or Forms, when the application
provides them) and other scripts.
uses
Classes, Dialogs, MyLibrary;
The names of a library can be used directly, or qualified with the library name:
MyLibrary.Calculate(10). imports loads a library without making its names directly
visible: they must be qualified.
When a script uses another script, the global variables, constants, procedures and functions of the other script are available to it:
// Script "MathLib"
var
Precision;
function RoundTo2(Value);
begin
Result := Round(Value * 100) / 100;
end;
// Main script
uses
MathLib;
begin
Precision := 2;
ShowMessage(FloatToStr(RoundTo2(3.14159)));
end;
The main block of a used script does not run when another script uses it, so its global variables start without a value. Initialize them in a routine of the library that the other scripts call.
The application defines where used scripts are found (another script it manages, or a file). A script used as a library can be written in any of the TMS Scripter languages.
Declaring forms in script
A script can declare a form, designed in a form file (.dfm, or .sfm in the TMS Scripter
IDE), and be used as a class to create and show instances of that form. The $FORM directive
at the beginning of the script gives the form class name and the form file:
{$FORM TCustomerForm, customerform.dfm}
var
CustomerId; // a property of each form instance
procedure btSaveClick(Sender: TObject);
begin
ShowMessage('Saving customer ' + IntToStr(CustomerId) + ': ' + edName.Text);
Close;
end;
procedure Init(AId);
begin
CustomerId := AId;
btSave.OnClick := 'btSaveClick';
end;
- The components of the form file are available by name (
edName,btSave), and so are the properties and methods of the form itself (Caption,Close). - The global variables of the form script are properties of the form, and its procedures and functions are methods. Each instance of the form has its own variable values.
- The form file must not contain event handlers: assign the events in script, as above.
Another script uses the form script and creates instances of the form class:
uses
Forms, CustomerFormScript;
var
F: TCustomerForm;
begin
F := TCustomerForm.Create(Application);
F.Init(42);
F.edName.Text := 'John';
F.ShowModal;
F.Free;
end;
Declaring classes in script
A script can declare a class with the $CLASS directive. The global variables of the script are
the properties of the class, its procedures and functions are the methods, and its main block
runs when an instance is created (use it to initialize the properties).
{$CLASS TCounter}
var
Count;
Step;
procedure Increment;
begin
Count := Count + Step;
end;
begin
Count := 0;
Step := 1;
end;
uses
CounterScript;
var
C: TCounter;
begin
C := TCounter.Create;
C.Step := 5;
C.Increment;
ShowMessage(IntToStr(C.Count)); // 5
C.Free;
end;
Script classes cannot inherit from other classes. Calling Free is optional: objects of
script classes that are not freed are destroyed automatically later.
Calling DLL functions
Scripts can declare and call functions of DLLs (Windows only, when the application allows it). See Calling DLL functions.
Built-in routines
The routines available to every script (strings, conversions, math, date and time, dialogs, files) are listed in Built-in routines.