Preface
Goal: A practical case to collect unique record fields using Free Pascal.
From Delphi to Retirement
Story of my life.
I have been using Pascal since a was a teenager, starting from Turbo Pascal, and then continue learning. For a while I use Pascal for many project, from data acquisition, to multhreaded accounting.
It has been real fun helping other jedi. I know insight most people don’t know. Then I suddenly stop using Delphi in 2007, and decide to move completely to open source.
I refused the idea of using unlicensed stuff. I decide to make myself clean. So I migrate from windows to linux, from photoshop to inkscape, from desktop coder to web developer.
Even with my expertise, it is simply hard to come back to Pascal. I knew about Kylix project, but never even try it. I tried Lazarus many times, but never have time to get into it. All I can do is watch form a far. I completely retired form Pascal.
Ressurection
14 years gap.
Times goes by, after 14 years, I decide to relearn Free Pascal, for nostalgic reason.
I choose free pascal, among other Pascal dialect. So here I am a Pascal veteran, starting from scratch.
Difference with Other Language.
No functional stuff.
It is a little lower level than other language, so the code is longer than usual.
To solve a particular issuse, you can have at least two approach, whether pure free pascal, or the object pascal one.
You can even use visual component library, along with the good Lazarus IDE, But this is beyond our topic.
Reference Reading
Source Examples
You can obtain source examples here:
Common Use Case
Task: Get the unique tag string
Please read overview for more detail.
Prepopulated Data
Songs and Poetry
Prepare the data type, for building the data structure.
{$mode objfpc}{$H+}
type TSong = record
Title : string;
Tags : array of string;
end;
Then, fill the data itself, as below:
const
Songs : array of TSong =
( ( Title : 'Cantaloupe Island';
Tags : ('60s', 'jazz') ),
( Title : 'Let It Be';
Tags : ('60s', 'rock') ),
( Title : 'Knockin'' on Heaven''s Door';
Tags : ('70s', 'rock') ),
( Title : 'Emotion';
Tags : ('70s', 'pop') ),
( Title : 'The River';
Tags : () )
);
Pascal Solution
The Answer
As said, you can use either pure free pascal, or the object pascal one. In object oriented manners, you this could be as long function below:
function TSongList3.GetUniqueTags(): TTags;
var
SL : TStringList;
ASong : PSong;
Tag : ANSIString;
begin
SL := TStringList.Create;
SL.Duplicates := dupIgnore;
SL.Sorted := true;
for ASong in Self do
for Tag in ASong^.Tags do
SL.Add(Tag);
Result := SL.ToStringArray(0, Count-1);
SL.Free;
end;
Enough with introduction, at this point we should go straight to coding.
Environment
Instead of using FP IDE or Lazarus, You can use any IDE, such as geany or ViM.
No need any special setup. Just run and voila..!
But later we are going to learn,
on how to configure fpc.cfg
anyway.
Compiled
Free Pascal is a compiled language. We have to compile the code first, before we run the code.
❯ fpc 01-tags-a.pp
Free Pascal Compiler version 3.2.2 [2022/03/02] for x86_64
Copyright (c) 1993-2021 by Florian Klaempfl and others
Target OS: Linux for x86-64
Compiling 01-tags-a.pp
Linking 01-tags-a
11 lines compiled, 0.7 sec
1: Array of String
Different choice for different situation.
Before building a records,
I begin with simple array
of string
.
But beware of the differences.
Evolution
Pascal originally has static array
,
but now has dynamic array
.
Pascal originally has string
type,
but now has ANSIString
type.
So now the string
type may refer to,
either ANSIString
and ShortString
,
depends on the {$H}
switch.
Now that we can have array of string
,
do not forget that Object Pascal has TSTringList
class.
To make this even more complex, Pascal is more like a low level language, compared to python and friend. There is now way to easily dump the variable. We have to do it ourself.
To make this easy to comprehend. Le’t go straight to example code.
Static Array of Short String
Without any {$H}
switch,
the default is the original short string.
const
Tags : array [0..4] of string =
('rock', 'jazz', 'rock', 'pop', 'pop');
var
I: integer;
begin
for I := 0 to 4 do
Write(Tags[I] + ' ');
WriteLn();
end.
With the result similar as below:
❯ ./01-tags-a
rock jazz rock pop pop
Dynamic Array of ANSI String
Instead of fixed size array, we can use dynamic array.
With ANSI string,
We can utilize Join
function from SysUtils
unit.
This way, we can dump array in pretty format.
uses SysUtils;
const
Tags : array of ANSIString =
('rock', 'jazz', 'rock', 'pop', 'pop');
begin
WriteLn(ANSIString.Join(', ', Tags));
end.
With the result similar as below:
❯ ./01-tags-b
rock, jazz, rock, pop, pop
Local Directives
We can make our life easier by using local directives. The detail for long string directive is in official documentation.
Instead of ANSIString.Join()
,
we can utilize any varibale name such as s.Join()
.
{$mode objfpc}
{$H+}
uses SysUtils;
const
Tags : array of string =
('rock', 'jazz', 'rock', 'pop', 'pop');
var
S: string = '';
begin
WriteLn(S.Join(', ', Tags));
end.
With the result similar as below:
❯ ./01-tags-c
rock, jazz, rock, pop, pop
Split and Join
We can also use Split
function to initialize the array.
uses Classes, SysUtils;
var
S, T : ANSIString;
Tags : array of ANSIString;
begin
S := 'rock:jazz:rock:pop:pop';
Tags := S.Split(':');
WriteLn(T.Join(', ', Tags) + '.');
end.
With the result similar as below:
❯ ./01-tags-d
rock, jazz, rock, pop, pop.
Beware of the unitialized T
variable.
I intentionally use this T
variable,
So you know what the problem is.
❯ fpc 01-tags-d.pp
Compiling 01-tags-d.pp
01-tags-d.pp(9,11) Warning: Variable "T" of a managed type does not seem to be initialized
Linking 01-tags-d
11 lines compiled, 0.5 sec
1 warning(s) issued
You can omit using T
, by using S.Join
,
or even ANSIString
.Join, or if you wish use
{$H+}switch, then use
string.Join
directly.
Manual Dump Function
For more complex data structure, we might need to make our own variable dump. So prepare before things began to be more complex. Consider start from simple array as below:
function DumpArray(Tags: array of string): string;
var
Index, Last: integer;
S: string = '';
begin
Last := length(Tags)-1;
for Index := 0 to Last do
begin
S := S + Tags[Index];
if Index<>Last then
S := S + ', '
else
S := S + '.';
end;
DumpArray := S;
end;
Now we can call the function directly, in main program entry point.
const
Tags : array of string =
('rock', 'jazz', 'rock', 'pop', 'pop');
begin
WriteLn(DumpArray(Tags));
end.
With the result similar as below:
❯ ./01-tags-e
rock, jazz, rock, pop, pop.
Notice the dot at the end of the output.
Object Pascal: TStringList (alt 1)
Instead of pure free pascal,
we can also take advantage of object pascal.
The object to handle this is called TStringList
.
This one below is intentionally,
a pretty bad example of using TStringList
.
Prepare the header: uses, const, and variable definition.
uses Classes, SysUtils;
const
S : ANSIString = 'rock:jazz:rock:pop:pop';
var
S1 : TStrings;
S2, S3 : ANSIString;
SA : TStringArray;
Then program entry point.
begin
SA := S.Split(':');
S1 := TStringList.Create;
S1.AddStrings(SA);
S2 := S1.GetText();
S3 := StringReplace(s2,
sLineBreak, ':',[rfReplaceAll]);
WriteLn(S3);
S1.Free;
end.
With the result similar as below:
❯ ./01-tags-f
rock:jazz:rock:pop:pop:
Object Pascal: TStringList (alt 2)
We are going to use TStringList
in a more proper fashioned,
by using its own method to solve our problem.
Prepare the header: uses, const and variable definition.
uses Classes, SysUtils;
const
S : ANSIString = 'rock:jazz:rock:pop:pop';
var
SL : TStrings;
SA : TStringArray;
Then program entry point.
begin
SL := TStringList.Create;
SL.AddStrings(S.Split(':'));
SA := SL.ToStringArray(0, 4);
WriteLn(AnsiString.Join(', ', SA) + '.');
SL.Free;
end.
With the result similar as below:
❯ ./01-tags-g
rock, jazz, rock, pop, pop.
This is, I believe, easier to be read.
What is Next 🤔?
We are going to move on to record structure topic.
Consider continue reading [ Pascal - Playing with Records - Part Two ].