Table of Contents

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:

  1. A program header: program MyScript; (ignored).
  2. A uses clause listing the libraries and other scripts the script uses (see Using libraries and other scripts).
  3. Global declarations: var and const sections.
  4. Procedures and functions.
  5. 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;
  • Null and Unassigned.

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' + 2 is 3.
  • A string that does not contain a number cannot be used in arithmetic: 'Total: ' + 5 is 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' = 10 is True.
  • Arithmetic with Null gives Null.

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;
  • to counts up and downto counts down, by one.
  • step sets another increment for to loops. It must be a number (negative or floating-point numbers are allowed): for I := 10 to 0 step -2 do. downto does not accept step.
  • 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) and High(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.