Appendix I: Answers to Selected Exercises
This appendix provides solutions to approximately two selected exercises per chapter. For programming exercises, complete Pascal code is given. These answers are intended for self-study verification — try each exercise yourself before consulting the solution.
Part I: Foundations (Chapters 1-8)
Chapter 1: Why Pascal?
Exercise A.1 — The Goldilocks Problem
"Too permissive": Python allows x = "hello" followed by x = 42 with no warning — the variable silently changes type. A beginner may not realize they have overwritten their string with an integer. "Too dangerous": C allows int arr[5]; arr[10] = 99; — writing past the end of an array corrupts memory with no compile-time or guaranteed runtime error. "Too ceremonious": Java requires public static void main(String[] args) before printing "Hello, World!" — a beginner must accept five keywords on faith.
Exercise B.5 — Hello, World! Comparison Table
| Language | Lines | "On faith" concepts | Confusing syntax |
|---|---|---|---|
| Pascal | 4-5 | program, begin/end (self-explanatory) |
None significant |
| Python | 1 | None | None |
| C | 5-6 | #include, int main(void), return 0, \n |
Format specifiers |
| Java | 5-7 | public, static, void, String[] args, System.out.println |
Nearly everything |
| JavaScript | 1 | None | None |
Python and JavaScript are most self-explanatory for this trivial case. However, Pascal's clarity becomes more apparent as programs grow beyond one line.
Chapter 2: Setting Up
Exercise A.2 (representative) — Identifying IDE Regions
The Lazarus IDE consists of: (1) the Source Editor, where you write code; (2) the Form Designer, where you visually place components; (3) the Object Inspector, which shows properties and events of selected components; (4) the Component Palette, a toolbar of available components organized by category; (5) the Messages window, which shows compiler output, errors, and warnings; and (6) the Project Inspector, which lists all files in the project.
Exercise C.1 — Hello World Compilation
program HelloWorld;
begin
WriteLn('Hello, World!');
WriteLn('My name is [Your Name].');
WriteLn('I am learning Pascal!');
end.
Compile with fpc HelloWorld.pas. Run with ./HelloWorld (Linux/macOS) or HelloWorld.exe (Windows). The output is three lines of text.
Exercise C.2 (representative) — Compiler Error Exploration
Remove the period after end and compile:
program BrokenHello;
begin
WriteLn('Hello')
end
Error: Fatal: Unexpected end of file. The period after end is required — it tells the compiler where the program ends. Without it, the compiler reaches the end of the file while still expecting more tokens. Fix: add . after end.
Chapter 3: Variables, Types, and Expressions
Exercise B.1 — Tracing Output
7
3.50
Explanation: x := 7 stores 7 as an integer. y := x / 2 performs real division (the / operator always returns a Real), so y = 3.5. WriteLn(x) prints 7. WriteLn(y:0:2) prints 3.50 (two decimal places).
Exercise C.2 — Temperature Converter
program TempConverter;
const
OFFSET = 32;
NUMERATOR = 5;
DENOMINATOR = 9;
var
fahrenheit, celsius: Real;
begin
fahrenheit := 98.6;
celsius := (fahrenheit - OFFSET) * NUMERATOR / DENOMINATOR;
WriteLn('Fahrenheit: ', fahrenheit:0:1);
WriteLn('Celsius: ', celsius:0:1);
end.
Output:
Fahrenheit: 98.6
Celsius: 37.0
Chapter 4: Input, Output, and Formatting
Exercise C.2 (representative) — Unit Converter
program UnitConverter;
{$mode objfpc}{$H+}
const
KM_PER_MILE = 1.60934;
LBS_PER_KG = 2.20462;
CM_PER_INCH = 2.54;
var
choice: Integer;
value, result: Real;
begin
WriteLn('Unit Converter');
WriteLn('1. Miles to Kilometers');
WriteLn('2. Pounds to Kilograms');
WriteLn('3. Inches to Centimeters');
Write('Choose (1-3): ');
ReadLn(choice);
Write('Enter value: ');
ReadLn(value);
case choice of
1: begin
result := value * KM_PER_MILE;
WriteLn(value:0:2, ' miles = ', result:0:2, ' km');
end;
2: begin
result := value / LBS_PER_KG;
WriteLn(value:0:2, ' lbs = ', result:0:2, ' kg');
end;
3: begin
result := value * CM_PER_INCH;
WriteLn(value:0:2, ' in = ', result:0:2, ' cm');
end;
else
WriteLn('Invalid choice.');
end;
end.
Exercise C.1 (representative) — Formatted Receipt
program Receipt;
const
TAX_RATE = 0.08;
var
item1, item2, subtotal, tax, total: Real;
begin
item1 := 12.99;
item2 := 24.50;
subtotal := item1 + item2;
tax := subtotal * TAX_RATE;
total := subtotal + tax;
WriteLn('=========================');
WriteLn(' RECEIPT ');
WriteLn('=========================');
WriteLn('Widget $', item1:8:2);
WriteLn('Gadget $', item2:8:2);
WriteLn('-------------------------');
WriteLn('Subtotal $', subtotal:8:2);
WriteLn('Tax (8%) $', tax:8:2);
WriteLn('=========================');
WriteLn('TOTAL $', total:8:2);
WriteLn('=========================');
end.
Exercise B.2 (representative) — Format Specifier Prediction
For WriteLn(x:10:3) where x = 3.14159: the output is 3.142 — right-justified in a field of 10 characters with 3 decimal places. The value is rounded (not truncated) to 3 decimal places.
Chapter 5: Making Decisions
Exercise 5.16 — Leap Year
program LeapYear;
var
year: Integer;
isLeap: Boolean;
begin
Write('Enter a year: ');
ReadLn(year);
isLeap := ((year mod 4 = 0) and (year mod 100 <> 0)) or (year mod 400 = 0);
if isLeap then
WriteLn(year, ' is a leap year.')
else
WriteLn(year, ' is not a leap year.');
end.
Test results: 2000 is a leap year (divisible by 400). 1900 is not (divisible by 100 but not 400). 2024 is a leap year (divisible by 4, not by 100). 2025 is not (not divisible by 4). The single Boolean expression captures all three rules.
Exercise 5.1 — Syntax Validity
(a) Valid. (b) Valid. (c) Invalid — the semicolon before else terminates the if statement, making else an unexpected token. The semicolon is a statement separator, not a terminator. (d) Valid — parentheses around each comparison are required because and has higher precedence than > and <. (e) Invalid — without parentheses, Pascal parses this as if x > (5 and y) < 10 due to operator precedence, causing a type mismatch error.
Exercise 5.13 — Simple Calculator
program Calculator;
var
num1, num2, result: Real;
op: Char;
begin
Write('Enter first number: ');
ReadLn(num1);
Write('Enter operator (+, -, *, /): ');
ReadLn(op);
Write('Enter second number: ');
ReadLn(num2);
case op of
'+': result := num1 + num2;
'-': result := num1 - num2;
'*': result := num1 * num2;
'/': begin
if num2 = 0 then
begin
WriteLn('Error: Division by zero.');
Halt(1);
end;
result := num1 / num2;
end;
else
begin
WriteLn('Error: Invalid operator "', op, '".');
Halt(1);
end;
end;
WriteLn(num1:0:2, ' ', op, ' ', num2:0:2, ' = ', result:0:4);
end.
Chapter 6: Loops and Iteration
Exercise C.3 — Input Validation
program InputValidation;
var
score: Integer;
grade: Char;
begin
repeat
Write('Enter a test score (0-100): ');
ReadLn(score);
if (score < 0) or (score > 100) then
WriteLn('Invalid! Score must be between 0 and 100.');
until (score >= 0) and (score <= 100);
if score >= 90 then grade := 'A'
else if score >= 80 then grade := 'B'
else if score >= 70 then grade := 'C'
else if score >= 60 then grade := 'D'
else grade := 'F';
WriteLn('Score: ', score, ' Grade: ', grade);
end.
Note the use of repeat..until for input validation — the prompt is shown at least once, which is exactly what we want.
Exercise C.12 — FizzBuzz
program FizzBuzz;
var
i: Integer;
begin
for i := 1 to 100 do
begin
if (i mod 15 = 0) then
WriteLn('FizzBuzz')
else if (i mod 3 = 0) then
WriteLn('Fizz')
else if (i mod 5 = 0) then
WriteLn('Buzz')
else
WriteLn(i);
end;
end.
Key insight: test mod 15 first (not mod 3 and mod 5 separately), because a number divisible by both 3 and 5 is divisible by 15. If you test mod 3 first, you would print "Fizz" for multiples of 15 and never reach the "FizzBuzz" branch.
Exercise C.5 — Factorial Calculator
program Factorial;
var
n, i: Integer;
fact: Int64;
begin
repeat
Write('Enter a non-negative integer: ');
ReadLn(n);
if n < 0 then
WriteLn('Error: must be non-negative. Try again.');
until n >= 0;
fact := 1;
for i := 2 to n do
fact := fact * i;
WriteLn(n, '! = ', fact);
end.
Note: 0! = 1 by convention. The for loop does not execute when n is 0 or 1, so fact correctly remains 1.
Exercise C.9 — Digit Sum
program DigitSum;
var
n, original, sum: Integer;
begin
Write('Enter a positive integer: ');
ReadLn(n);
original := n;
sum := 0;
while n > 0 do
begin
sum := sum + (n mod 10);
n := n div 10;
end;
WriteLn('The digit sum of ', original, ' is ', sum);
end.
For input 1234: iterations yield remainders 4, 3, 2, 1. Sum = 10.
Chapter 7: Procedures and Functions
Exercise 7.6 — Swap
program SwapDemo;
{ var parameters are essential here: we need to modify
the caller's variables, not copies of them. }
procedure Swap(var A, B: Integer);
var
Temp: Integer;
begin
Temp := A;
A := B;
B := Temp;
end;
var
x, y: Integer;
begin
x := 10;
y := 25;
WriteLn('Before swap: x = ', x, ', y = ', y);
Swap(x, y);
WriteLn('After swap: x = ', x, ', y = ', y);
end.
Output:
Before swap: x = 10, y = 25
After swap: x = 25, y = 10
Exercise 7.9 — Power Function
program PowerDemo;
function IntPower(Base, Exponent: Integer): Int64;
var
i: Integer;
result: Int64;
begin
result := 1;
for i := 1 to Exponent do
result := result * Base;
IntPower := result;
end;
begin
WriteLn('2^10 = ', IntPower(2, 10)); { 1024 }
WriteLn('3^5 = ', IntPower(3, 5)); { 243 }
WriteLn('5^0 = ', IntPower(5, 0)); { 1 }
WriteLn('7^3 = ', IntPower(7, 3)); { 343 }
end.
Chapter 8: Scope, Parameters, and the Call Stack
Exercise A.1 (representative) — Scope Analysis
Given nested procedures where an inner procedure declares a variable with the same name as an outer variable, the inner declaration shadows the outer one. Within the inner procedure, the name refers to the local variable. The outer variable still exists but is inaccessible by that name from within the inner scope. This is why unique, descriptive names are important.
Exercise B.1 (representative) — Parameter Passing Prediction
procedure Mystery(A: Integer; var B: Integer);
begin
A := A + 10;
B := B + 10;
end;
var
x, y: Integer;
begin
x := 5;
y := 5;
Mystery(x, y);
WriteLn(x, ' ', y);
end.
Output: 5 15. Explanation: A is a value parameter (copy), so modifying A inside the procedure does not affect x. B is a var parameter (reference), so modifying B directly modifies y. This is one of the most fundamental distinctions in Pascal.
Exercise C.1 (representative) — Call Stack Trace
program StackTrace;
procedure C;
begin
WriteLn('In C'); { Stack: Main -> A -> B -> C }
end;
procedure B;
begin
WriteLn('Entering B'); { Stack: Main -> A -> B }
C;
WriteLn('Leaving B');
end;
procedure A;
begin
WriteLn('Entering A'); { Stack: Main -> A }
B;
WriteLn('Leaving A');
end;
begin
WriteLn('Starting'); { Stack: Main }
A;
WriteLn('Finished');
end.
Output: Starting, Entering A, Entering B, In C, Leaving B, Leaving A, Finished. Maximum stack depth: 4 frames.
Part II: Data Structures (Chapters 9-15)
Chapter 9: Arrays
Exercise 9.2 — Array Analysis
(a) 365 elements. (b) First index: 1; last index: 365. (c) temps[100]. (d) 365 * 8 = 2,920 bytes.
Exercise C.1 (representative) — Array Reversal
program ReverseArray;
const
SIZE = 10;
type
TIntArray = array[1..SIZE] of Integer;
procedure ReverseInPlace(var A: TIntArray; Count: Integer);
var
i, temp: Integer;
begin
for i := 1 to Count div 2 do
begin
temp := A[i];
A[i] := A[Count - i + 1];
A[Count - i + 1] := temp;
end;
end;
var
data: TIntArray;
i: Integer;
begin
for i := 1 to SIZE do
data[i] := i * 10;
Write('Original: ');
for i := 1 to SIZE do
Write(data[i]:4);
WriteLn;
ReverseInPlace(data, SIZE);
Write('Reversed: ');
for i := 1 to SIZE do
Write(data[i]:4);
WriteLn;
end.
Chapter 10: Strings
Exercise A4 — String Function Results
s[1] = 'P'. s[6] = 'l'. Length(s) = 6. Pos('cal', s) = 4 (the substring 'cal' starts at position 4). Copy(s, 4, 3) = 'cal' (3 characters starting at position 4).
Exercise B1 — Initials Function
program InitialsDemo;
{$mode objfpc}{$H+}
uses SysUtils;
function Initials(const FullName: String): String;
var
i: Integer;
InWord: Boolean;
begin
Result := '';
InWord := False;
for i := 1 to Length(FullName) do
begin
if FullName[i] = ' ' then
InWord := False
else if not InWord then
begin
Result := Result + UpCase(FullName[i]);
InWord := True;
end;
end;
end;
begin
WriteLn(Initials('Ada Byron Lovelace')); { ABL }
WriteLn(Initials('Niklaus Wirth')); { NW }
WriteLn(Initials('Rosa Martinelli')); { RM }
end.
Chapter 11: Records and Variant Records
Exercise 11.1 — Records vs. Parallel Arrays
Records solve the problem of grouping related data of different types under a single name. Parallel arrays require maintaining multiple arrays (one per field) and keeping them synchronized — if you sort one array, you must rearrange all others identically. Records eliminate this by keeping all fields for a single entity together. Two limitations removed: (1) no risk of arrays getting out of sync, and (2) a single variable or parameter represents one complete entity.
Exercise C.1 (representative) — Student Record
program StudentRecordDemo;
{$mode objfpc}{$H+}
uses SysUtils;
type
TStudent = record
FirstName: String[30];
LastName: String[30];
ID: Integer;
GPA: Real;
end;
procedure PrintStudent(const S: TStudent);
begin
WriteLn('Name: ', S.FirstName, ' ', S.LastName);
WriteLn('ID: ', S.ID);
WriteLn('GPA: ', S.GPA:0:2);
WriteLn;
end;
var
students: array[1..3] of TStudent;
begin
students[1].FirstName := 'Rosa';
students[1].LastName := 'Martinelli';
students[1].ID := 1001;
students[1].GPA := 3.85;
students[2].FirstName := 'Tomas';
students[2].LastName := 'Vieira';
students[2].ID := 1002;
students[2].GPA := 3.42;
students[3].FirstName := 'Ada';
students[3].LastName := 'Lovelace';
students[3].ID := 1003;
students[3].GPA := 4.00;
PrintStudent(students[1]);
PrintStudent(students[2]);
PrintStudent(students[3]);
end.
Chapter 12: Sets and Enumerations
Exercise A1 — Enumerated Type
program EnumDemo;
type
TColor = (Red, Orange, Yellow, Green, Blue, Indigo, Violet);
var
c: TColor;
begin
c := Green;
WriteLn('Ordinal value of Green: ', Ord(c)); { 3 }
end.
The ordinal values are automatically assigned starting from 0: Red=0, Orange=1, Yellow=2, Green=3, Blue=4, Indigo=5, Violet=6.
Exercise B2 — Dice Simulation with Subrange Type
program DiceSim;
type
TDiceRoll = 1..6;
var
counts: array[2..12] of Integer;
d1, d2: TDiceRoll;
sum, i, trial: Integer;
begin
for i := 2 to 12 do
counts[i] := 0;
Randomize;
for trial := 1 to 1000 do
begin
d1 := Random(6) + 1;
d2 := Random(6) + 1;
sum := d1 + d2;
Inc(counts[sum]);
end;
WriteLn('Sum Count');
WriteLn('-----------');
for i := 2 to 12 do
WriteLn(i:4, counts[i]:8);
end.
Exercise C.1 (representative) — Character Classification with Sets
program CharClassify;
var
ch: Char;
begin
Write('Enter a character: ');
ReadLn(ch);
if ch in ['A'..'Z'] then
WriteLn(ch, ' is an uppercase letter')
else if ch in ['a'..'z'] then
WriteLn(ch, ' is a lowercase letter')
else if ch in ['0'..'9'] then
WriteLn(ch, ' is a digit')
else if ch in [' ', #9] then
WriteLn('Whitespace character')
else
WriteLn(ch, ' is a symbol/punctuation');
end.
Chapter 13: Files and I/O
Exercise 13.1 — File Opening Modes
Rewrite: creates a new file for writing. If the file exists, its content is destroyed. Reset: opens an existing file. For text files, it opens for reading only. For typed/untyped files, it opens for reading and writing. If the file does not exist, a runtime error occurs. Append: opens an existing text file and positions the pointer at the end, so new data is added after existing content. If the file does not exist, a runtime error occurs.
Exercise C.1 (representative) — Writing and Reading a Text File
program FileDemo;
{$mode objfpc}{$H+}
var
F: TextFile;
line: String;
count: Integer;
begin
{ Write }
AssignFile(F, 'test.txt');
Rewrite(F);
WriteLn(F, 'First line');
WriteLn(F, 'Second line');
WriteLn(F, 'Third line');
CloseFile(F);
{ Read back }
AssignFile(F, 'test.txt');
Reset(F);
count := 0;
while not Eof(F) do
begin
ReadLn(F, line);
Inc(count);
WriteLn('Line ', count, ': ', line);
end;
CloseFile(F);
WriteLn('Total lines: ', count);
end.
Chapter 14: Pointers and Dynamic Memory
Exercise 2 — Pointer vs. Value Copy
(a) After a^ := b^ (Line X): a^ = 20, b^ = 20. The values are the same but a and b still point to different memory locations. This is a value copy. (b) After a := b (Line Y): both a and b point to the same memory location (containing 20). The memory previously pointed to by a is now leaked — no pointer references it, so it can never be freed.
Exercise C.1 (representative) — Dynamic Integer Array
program DynPointerArray;
type
PInteger = ^Integer;
var
p: PInteger;
i: Integer;
begin
for i := 1 to 5 do
begin
New(p);
p^ := i * i;
WriteLn('Value: ', p^, ' at address: ', PtrUInt(p));
Dispose(p);
end;
end.
Chapter 15: Dynamic Data Structures
Exercise A1 — Terminology Definitions
(a) A linked list is a sequence of nodes where each node contains data and a pointer to the next node. (b) The head pointer points to the first node in the list; if the list is empty, it is nil. (c) The tail pointer points to the last node; it enables O(1) insertion at the end. (d) LIFO: Last In, First Out — the most recently added element is removed first (stack behavior). (e) FIFO: First In, First Out — the earliest added element is removed first (queue behavior).
Exercise C.1 (representative) — Linked List Operations
program LinkedListDemo;
type
PNode = ^TNode;
TNode = record
Data: Integer;
Next: PNode;
end;
procedure InsertAtHead(var Head: PNode; Value: Integer);
var
NewNode: PNode;
begin
New(NewNode);
NewNode^.Data := Value;
NewNode^.Next := Head;
Head := NewNode;
end;
procedure PrintList(Head: PNode);
var
Current: PNode;
begin
Current := Head;
while Current <> nil do
begin
Write(Current^.Data);
if Current^.Next <> nil then
Write(' -> ');
Current := Current^.Next;
end;
WriteLn(' -> nil');
end;
procedure FreeList(var Head: PNode);
var
Temp: PNode;
begin
while Head <> nil do
begin
Temp := Head;
Head := Head^.Next;
Dispose(Temp);
end;
end;
var
Head: PNode;
begin
Head := nil;
InsertAtHead(Head, 30);
InsertAtHead(Head, 20);
InsertAtHead(Head, 10);
PrintList(Head); { 10 -> 20 -> 30 -> nil }
FreeList(Head);
end.
Part III: Object Pascal (Chapters 16-21)
Chapter 16: Introduction to OOP
Exercise 16.1 — Class vs. Object
A class is like an architectural blueprint for a house. It defines the structure (how many rooms, where the doors go, what materials to use) but is not itself a house. An object is a specific house built from that blueprint. You can build many houses (objects) from the same blueprint (class), each with its own paint color and furniture (field values), but all sharing the same layout (methods and structure).
Exercise C.1 (representative) — Bank Account Class
program BankAccountDemo;
{$mode objfpc}{$H+}
uses SysUtils;
type
TBankAccount = class
private
FOwner: String;
FBalance: Currency;
public
constructor Create(const AOwner: String; AInitialBalance: Currency);
procedure Deposit(Amount: Currency);
function Withdraw(Amount: Currency): Boolean;
procedure Display;
property Owner: String read FOwner;
property Balance: Currency read FBalance;
end;
constructor TBankAccount.Create(const AOwner: String; AInitialBalance: Currency);
begin
inherited Create;
FOwner := AOwner;
if AInitialBalance >= 0 then
FBalance := AInitialBalance
else
FBalance := 0;
end;
procedure TBankAccount.Deposit(Amount: Currency);
begin
if Amount > 0 then
FBalance := FBalance + Amount;
end;
function TBankAccount.Withdraw(Amount: Currency): Boolean;
begin
if (Amount > 0) and (Amount <= FBalance) then
begin
FBalance := FBalance - Amount;
Result := True;
end
else
Result := False;
end;
procedure TBankAccount.Display;
begin
WriteLn('Owner: ', FOwner);
WriteLn('Balance: $', FBalance:0:2);
end;
var
acct: TBankAccount;
begin
acct := TBankAccount.Create('Rosa Martinelli', 1000.00);
try
acct.Display;
acct.Deposit(250.00);
WriteLn('After deposit of $250:');
acct.Display;
if acct.Withdraw(100.00) then
WriteLn('Withdrawal of $100 successful.')
else
WriteLn('Withdrawal failed.');
acct.Display;
finally
acct.Free;
end;
end.
Chapter 17: Inheritance and Polymorphism
Exercise A1 — The "is-a" Relationship
"Is-a" examples: A guitar is a musical instrument. A golden retriever is a dog. A "has-a" that looks like "is-a": A car is NOT an engine — a car has an engine. Trying to model TCar = class(TEngine) is wrong because a car contains an engine as a component, not as its identity.
Exercise C.1 (representative) — Shape Hierarchy
program ShapeHierarchy;
{$mode objfpc}{$H+}
uses SysUtils, Math;
type
TShape = class
private
FName: String;
public
constructor Create(const AName: String);
function Area: Double; virtual; abstract;
function Perimeter: Double; virtual; abstract;
procedure Display; virtual;
property Name: String read FName;
end;
TCircle = class(TShape)
private
FRadius: Double;
public
constructor Create(ARadius: Double);
function Area: Double; override;
function Perimeter: Double; override;
end;
TRectangle = class(TShape)
private
FWidth, FHeight: Double;
public
constructor Create(AWidth, AHeight: Double);
function Area: Double; override;
function Perimeter: Double; override;
end;
constructor TShape.Create(const AName: String);
begin
inherited Create;
FName := AName;
end;
procedure TShape.Display;
begin
WriteLn(FName, ': Area = ', Area:0:2,
', Perimeter = ', Perimeter:0:2);
end;
constructor TCircle.Create(ARadius: Double);
begin
inherited Create('Circle');
FRadius := ARadius;
end;
function TCircle.Area: Double;
begin
Result := Pi * FRadius * FRadius;
end;
function TCircle.Perimeter: Double;
begin
Result := 2 * Pi * FRadius;
end;
constructor TRectangle.Create(AWidth, AHeight: Double);
begin
inherited Create('Rectangle');
FWidth := AWidth;
FHeight := AHeight;
end;
function TRectangle.Area: Double;
begin
Result := FWidth * FHeight;
end;
function TRectangle.Perimeter: Double;
begin
Result := 2 * (FWidth + FHeight);
end;
var
shapes: array[1..2] of TShape;
i: Integer;
begin
shapes[1] := TCircle.Create(5.0);
shapes[2] := TRectangle.Create(4.0, 6.0);
for i := 1 to 2 do
shapes[i].Display; { Polymorphic dispatch }
for i := 1 to 2 do
shapes[i].Free;
end.
Chapter 18: Interfaces and Abstract Classes
Exercise A1 — "is-a" vs. "can-do"
"Is-a" (inheritance): A TSavingsAccount is a TBankAccount — it has all the properties of a bank account plus additional features like interest. "Can-do" (interface): A TSavingsAccount can be serialized (ISerializable), and a TCustomer can also be serialized. Serialization is a capability that multiple unrelated classes share, not an identity relationship. Interfaces model capabilities; inheritance models identity.
Exercise C.1 (representative) — Interface Implementation
program InterfaceDemo;
{$mode objfpc}{$H+}
uses SysUtils;
type
IPrintable = interface
['{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}']
function ToString: String;
procedure Print;
end;
TStudent = class(TInterfacedObject, IPrintable)
private
FName: String;
FGrade: Integer;
public
constructor Create(const AName: String; AGrade: Integer);
function ToString: String; override;
procedure Print;
end;
constructor TStudent.Create(const AName: String; AGrade: Integer);
begin
inherited Create;
FName := AName;
FGrade := AGrade;
end;
function TStudent.ToString: String;
begin
Result := FName + ' (Grade: ' + IntToStr(FGrade) + ')';
end;
procedure TStudent.Print;
begin
WriteLn(ToString);
end;
procedure PrintItem(const Item: IPrintable);
begin
Item.Print;
end;
var
s: IPrintable;
begin
s := TStudent.Create('Tomas Vieira', 92);
PrintItem(s);
{ No need to free — reference counting handles it }
end.
Chapter 19: Exception Handling
Exercise A1 — try..except vs. try..finally
try..except catches and handles exceptions — you use it when you want to recover from an error or display a message. try..finally guarantees cleanup code runs regardless of whether an exception occurred — you use it for releasing resources. Use both together when you need to both clean up and handle the error: nest try..finally inside try..except, or vice versa.
Exercise C.1 (representative) — Safe File Reading
program SafeFileRead;
{$mode objfpc}{$H+}
uses SysUtils;
procedure ReadFileContents(const FileName: String);
var
F: TextFile;
Line: String;
LineNum: Integer;
begin
if not FileExists(FileName) then
raise EFileNotFoundException.Create('File not found: ' + FileName);
AssignFile(F, FileName);
try
Reset(F);
try
LineNum := 0;
while not Eof(F) do
begin
ReadLn(F, Line);
Inc(LineNum);
WriteLn(LineNum:4, ': ', Line);
end;
finally
CloseFile(F);
end;
except
on E: EInOutError do
WriteLn('I/O error reading file: ', E.Message);
end;
end;
begin
try
ReadFileContents('sample.txt');
except
on E: EFileNotFoundException do
WriteLn(E.Message);
on E: Exception do
WriteLn('Unexpected error: ', E.Message);
end;
end.
Chapter 20: Generics
Exercise A.1 (representative) — Why Generics?
Without generics, you must write separate TIntegerList, TStringList, TStudentList, etc., duplicating the list management logic for every type. Generics let you write TGenericList<T> once, and the compiler generates type-safe, specialized code for each type you use. Benefits: code reuse (write once), type safety (the compiler catches type mismatches), and no casting (no runtime as or type-checking needed).
Exercise A.2 (representative) — generic vs. specialize
In Free Pascal's {$mode objfpc}, you declare a generic type with the generic keyword and instantiate it with specialize:
type
generic TGenericList<T> = class ... end;
TIntList = specialize TGenericList<Integer>;
In {$mode delphi}, the syntax mirrors Delphi:
type
TGenericList<T> = class ... end;
TIntList = TGenericList<Integer>;
Both produce identical compiled code. The objfpc syntax is more explicit; the delphi syntax is more concise.
Exercise C.1 (representative) — Generic Stack
program GenericStackDemo;
{$mode objfpc}{$H+}
type
generic TStack<T> = class
private
FData: array of T;
FCount: Integer;
public
constructor Create;
procedure Push(const Value: T);
function Pop: T;
function Peek: T;
function IsEmpty: Boolean;
property Count: Integer read FCount;
end;
constructor TStack.Create;
begin
inherited Create;
FCount := 0;
SetLength(FData, 0);
end;
procedure TStack.Push(const Value: T);
begin
if FCount >= Length(FData) then
SetLength(FData, FCount + 8);
FData[FCount] := Value;
Inc(FCount);
end;
function TStack.Pop: T;
begin
if FCount = 0 then
raise Exception.Create('Stack underflow');
Dec(FCount);
Result := FData[FCount];
end;
function TStack.Peek: T;
begin
if FCount = 0 then
raise Exception.Create('Stack is empty');
Result := FData[FCount - 1];
end;
function TStack.IsEmpty: Boolean;
begin
Result := FCount = 0;
end;
type
TIntStack = specialize TStack<Integer>;
var
stack: TIntStack;
begin
stack := TIntStack.Create;
try
stack.Push(10);
stack.Push(20);
stack.Push(30);
WriteLn('Top: ', stack.Peek); { 30 }
WriteLn('Popped: ', stack.Pop); { 30 }
WriteLn('Popped: ', stack.Pop); { 20 }
WriteLn('Count: ', stack.Count); { 1 }
finally
stack.Free;
end;
end.
Chapter 21: Advanced Object Pascal
Exercise A.1 (representative) — Operator Overloading Use Case
Operator overloading is appropriate when a user-defined type has a natural mathematical interpretation. A TCurrency type that represents money is a good candidate: a + b should add two currency amounts. A TStudent type is a poor candidate: what would student1 + student2 mean? The rule: overload operators only when the meaning is obvious and unsurprising to anyone reading the code.
Exercise C.1 (representative) — TCurrency with Overloaded Operators
program CurrencyDemo;
{$mode objfpc}{$H+}
uses SysUtils;
type
TCurrency = record
Cents: Int64;
end;
operator + (A, B: TCurrency): TCurrency;
begin
Result.Cents := A.Cents + B.Cents;
end;
operator - (A, B: TCurrency): TCurrency;
begin
Result.Cents := A.Cents - B.Cents;
end;
operator * (A: TCurrency; Factor: Integer): TCurrency;
begin
Result.Cents := A.Cents * Factor;
end;
operator = (A, B: TCurrency): Boolean;
begin
Result := A.Cents = B.Cents;
end;
function CurrToStr(C: TCurrency): String;
begin
Result := Format('$%d.%2.2d', [C.Cents div 100, Abs(C.Cents mod 100)]);
end;
function MakeCurrency(Dollars: Integer; Cents: Integer = 0): TCurrency;
begin
Result.Cents := Dollars * 100 + Cents;
end;
var
price, tax, total: TCurrency;
begin
price := MakeCurrency(29, 99);
tax := MakeCurrency(2, 40);
total := price + tax;
WriteLn('Price: ', CurrToStr(price));
WriteLn('Tax: ', CurrToStr(tax));
WriteLn('Total: ', CurrToStr(total));
end.
Part IV: Algorithms and Problem Solving (Chapters 22-26)
Chapter 22: Recursion
Exercise A.2 — Factorial Stack Trace
Call Factorial(5): N=5, not base case, returns 5 * Factorial(4)
Call Factorial(4): N=4, returns 4 * Factorial(3)
Call Factorial(3): N=3, returns 3 * Factorial(2)
Call Factorial(2): N=2, returns 2 * Factorial(1)
Call Factorial(1): N=1, returns 1 * Factorial(0)
Call Factorial(0): N=0, BASE CASE, returns 1
Factorial(1) = 1 * 1 = 1
Factorial(2) = 2 * 1 = 2
Factorial(3) = 3 * 2 = 6
Factorial(4) = 4 * 6 = 24
Factorial(5) = 5 * 24 = 120
Maximum stack depth: 6 frames.
Exercise C.1 (representative) — Recursive Power Function
program RecursivePower;
function Power(Base, Exp: Integer): Int64;
begin
if Exp = 0 then
Power := 1 { Base case }
else
Power := Base * Power(Base, Exp - 1); { Recursive case }
end;
begin
WriteLn('2^10 = ', Power(2, 10)); { 1024 }
WriteLn('3^4 = ', Power(3, 4)); { 81 }
WriteLn('5^0 = ', Power(5, 0)); { 1 }
end.
Chapter 23: Searching and Sorting
Exercise A.5 — Insertion Sort Trace
Start: [5, 3, 8, 1, 9, 2]
Insert 3: [3, 5, 8, 1, 9, 2] (3 < 5, shift 5 right, insert 3)
Insert 8: [3, 5, 8, 1, 9, 2] (8 >= 5, no move)
Insert 1: [1, 3, 5, 8, 9, 2] (1 < all, shift all right, insert 1)
Insert 9: [1, 3, 5, 8, 9, 2] (9 >= 8, no move)
Insert 2: [1, 2, 3, 5, 8, 9] (2 > 1, shift 3,5,8,9 right, insert 2)
Exercise C.1 (representative) — Binary Search
program BinarySearchDemo;
type
TIntArray = array[1..100] of Integer;
function BinarySearch(const A: TIntArray; Count, Target: Integer): Integer;
var
Lo, Hi, Mid: Integer;
begin
Lo := 1;
Hi := Count;
while Lo <= Hi do
begin
Mid := (Lo + Hi) div 2;
if A[Mid] = Target then
begin
BinarySearch := Mid;
Exit;
end
else if A[Mid] < Target then
Lo := Mid + 1
else
Hi := Mid - 1;
end;
BinarySearch := -1; { Not found }
end;
var
data: TIntArray;
i, pos: Integer;
begin
for i := 1 to 20 do
data[i] := i * 5; { 5, 10, 15, ..., 100 }
pos := BinarySearch(data, 20, 35);
if pos > 0 then
WriteLn('Found 35 at position ', pos) { Position 7 }
else
WriteLn('35 not found');
pos := BinarySearch(data, 20, 42);
if pos > 0 then
WriteLn('Found 42 at position ', pos)
else
WriteLn('42 not found'); { Not found }
end.
Chapter 24: Trees and Graphs
Exercise A.1 (representative) — BST Property
In a binary search tree, for every node N: all values in N's left subtree are less than N's value, and all values in N's right subtree are greater than N's value. This property enables efficient search: at each node, you compare the target with the node's value and recurse into only one subtree, eliminating half the remaining nodes.
Exercise A.2 (representative) — Graph Representation Comparison
Adjacency matrix: uses a 2D array A[V, V] where A[i, j] = 1 if an edge exists. Space: O(V^2). Edge lookup: O(1). Good for dense graphs. Adjacency list: uses an array of linked lists, one per vertex. Space: O(V + E). Edge lookup: O(degree). Good for sparse graphs. For a social network with 1 million users (V = 10^6) and average 100 friends each (E = 5 * 10^7), the adjacency list uses ~50 MB while the matrix would use ~1 TB. Always choose the list for sparse graphs.
Exercise C.1 (representative) — BST Insertion and Inorder Traversal
program BSTDemo;
type
PBSTNode = ^TBSTNode;
TBSTNode = record
Data: Integer;
Left, Right: PBSTNode;
end;
procedure Insert(var Root: PBSTNode; Value: Integer);
begin
if Root = nil then
begin
New(Root);
Root^.Data := Value;
Root^.Left := nil;
Root^.Right := nil;
end
else if Value < Root^.Data then
Insert(Root^.Left, Value)
else if Value > Root^.Data then
Insert(Root^.Right, Value);
{ If Value = Root^.Data, do nothing (no duplicates) }
end;
procedure InorderPrint(Root: PBSTNode);
begin
if Root <> nil then
begin
InorderPrint(Root^.Left);
Write(Root^.Data, ' ');
InorderPrint(Root^.Right);
end;
end;
procedure FreeTree(var Root: PBSTNode);
begin
if Root <> nil then
begin
FreeTree(Root^.Left);
FreeTree(Root^.Right);
Dispose(Root);
Root := nil;
end;
end;
var
Root: PBSTNode;
begin
Root := nil;
Insert(Root, 50);
Insert(Root, 30);
Insert(Root, 70);
Insert(Root, 20);
Insert(Root, 40);
Insert(Root, 60);
Insert(Root, 80);
Write('Inorder traversal: ');
InorderPrint(Root); { 20 30 40 50 60 70 80 }
WriteLn;
FreeTree(Root);
end.
Chapter 25: Algorithm Design Strategies
Exercise A.1 (representative) — Greedy vs. Dynamic Programming
A greedy algorithm makes the locally best choice at each step. For the US coin system (25, 10, 5, 1), greedy works: for 41 cents, choose a quarter (16 remaining), a dime (6 remaining), a nickel (1 remaining), a penny = 4 coins, which is optimal. But for a hypothetical system with coins of 1, 3, 4, greedy fails for 6 cents: greedy picks 4+1+1 = 3 coins, but optimal is 3+3 = 2 coins. Dynamic programming finds the true optimum by solving all subproblems.
Exercise A.3 (representative) — Divide and Conquer Steps
Every divide-and-conquer algorithm follows three steps: (1) Divide the problem into smaller subproblems of the same type. (2) Conquer each subproblem recursively (base case: solve directly when small enough). (3) Combine the subproblem solutions into a solution for the original problem. In merge sort: divide by splitting the array in half, conquer by recursively sorting each half, combine by merging the two sorted halves. In binary search: divide by computing the midpoint, conquer by recursing into the relevant half, combine is trivial (just return the found index).
Exercise C.1 (representative) — Fibonacci with Memoization
program FibMemo;
const
MAX_N = 90;
var
memo: array[0..MAX_N] of Int64;
initialized: array[0..MAX_N] of Boolean;
function Fib(n: Integer): Int64;
begin
if n <= 1 then
Fib := n
else if initialized[n] then
Fib := memo[n]
else
begin
memo[n] := Fib(n - 1) + Fib(n - 2);
initialized[n] := True;
Fib := memo[n];
end;
end;
var
i: Integer;
begin
FillChar(initialized, SizeOf(initialized), 0);
for i := 0 to 20 do
WriteLn('Fib(', i:2, ') = ', Fib(i));
end.
Chapter 26: Complexity Analysis
Exercise A.1 (representative) — Big O Classification
Given code fragments: (1) A single loop from 1 to n: O(n). (2) Nested loops both from 1 to n: O(n^2). (3) A loop that halves n each iteration: O(log n). (4) A loop from 1 to n containing a loop that halves n: O(n log n). (5) Three sequential (non-nested) loops from 1 to n: O(n) — constants and lower-order terms are dropped.
Exercise A.2 (representative) — Best, Worst, and Average Case
For linear search: best case is O(1) — the target is the first element. Worst case is O(n) — the target is the last element or not present. Average case is O(n/2), which simplifies to O(n). For insertion sort: best case is O(n) — the array is already sorted, so the inner loop never executes. Worst case is O(n^2) — the array is sorted in reverse order. Average case is O(n^2). The best case matters in practice: if your data is "almost sorted" (common in real-world datasets), insertion sort can outperform quicksort.
Exercise C.1 (representative) — Empirical Timing
program TimingDemo;
{$mode objfpc}{$H+}
uses SysUtils, DateUtils;
function LinearSearch(const A: array of Integer; Target: Integer): Integer;
var
i: Integer;
begin
for i := Low(A) to High(A) do
if A[i] = Target then
Exit(i);
Result := -1;
end;
var
data: array of Integer;
i, n: Integer;
startTime: TDateTime;
elapsed: Int64;
begin
for n := 10000 to 50000 do
begin
if n mod 10000 <> 0 then Continue;
SetLength(data, n);
for i := 0 to n - 1 do
data[i] := i;
startTime := Now;
for i := 1 to 1000 do
LinearSearch(data, n); { Worst case: not found }
elapsed := MilliSecondsBetween(Now, startTime);
WriteLn('n = ', n:6, ' Time: ', elapsed, ' ms');
end;
end.
Observe that doubling n roughly doubles the time, confirming O(n).
Part V: GUI Programming with Lazarus (Chapters 27-32)
Chapter 27: Introduction to Lazarus
Exercise A.2 — Message Loop
The message loop is the central while loop in a GUI application that retrieves events from the OS message queue and dispatches them to component handlers. If an event handler takes 5 seconds, the loop is blocked for 5 seconds: the window cannot repaint (appears frozen or white), mouse clicks and keystrokes are queued but not processed, and the OS may display a "Not Responding" warning. Solution: move long-running work to a background thread.
Exercise C.1 (representative) — Button Click Counter
Create a new Lazarus application. Place a TButton (Caption: 'Click Me') and a TLabel (Caption: '0') on the form. Add a private field FClickCount: Integer to the form class. In FormCreate, set FClickCount := 0. In Button1Click:
procedure TForm1.Button1Click(Sender: TObject);
begin
Inc(FClickCount);
Label1.Caption := IntToStr(FClickCount);
end;
Chapter 28: Forms, Controls, and Events
Exercise A.1 (representative) — Control Categories
Input controls accept user data: TEdit (single-line text), TSpinEdit (numeric), TComboBox (dropdown selection), TCheckBox (boolean), TRadioButton (mutually exclusive choice). Display controls show information: TLabel (text), TImage (graphics), TProgressBar (progress indicator). Action controls trigger operations: TButton (click), TBitBtn (button with icon), TSpeedButton (toolbar button).
Exercise C.1 (representative) — Temperature Converter GUI
Place a TEdit (EdtFahrenheit), a TButton (BtnConvert, Caption: 'Convert'), and a TLabel (LblResult) on a form. In the button click handler:
procedure TForm1.BtnConvertClick(Sender: TObject);
var
F, C: Double;
begin
try
F := StrToFloat(EdtFahrenheit.Text);
C := (F - 32) * 5 / 9;
LblResult.Caption := Format('%.1f F = %.1f C', [F, C]);
except
on E: EConvertError do
LblResult.Caption := 'Please enter a valid number.';
end;
end;
This demonstrates the basic Lazarus pattern: read from a control, compute, display in another control, and handle errors gracefully.
Chapter 29: Menus, Dialogs, and Application Structure
Exercise C.1 (representative) — File Open/Save
Use TOpenDialog and TSaveDialog components. Set the Filter property to 'Text files|*.txt|All files|*.*'. In the menu handler:
procedure TForm1.MenuFileOpenClick(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
Memo1.Lines.LoadFromFile(OpenDialog1.FileName);
Caption := 'Editor - ' + ExtractFileName(OpenDialog1.FileName);
end;
end;
Chapter 30: Drawing, Graphics, and Custom Controls
Exercise A.1 (representative) — Canvas Coordinate System
The canvas coordinate system has its origin (0, 0) at the top-left corner of the component. The X axis increases to the right and the Y axis increases downward (opposite to the standard mathematical convention). This is universal across virtually all GUI frameworks. To draw a point at the center of a TPaintBox, use PaintBox1.Width div 2 for X and PaintBox1.Height div 2 for Y. All coordinates are in pixels.
Exercise C.1 (representative) — Canvas Drawing
procedure TForm1.PaintBox1Paint(Sender: TObject);
var
i: Integer;
begin
with PaintBox1.Canvas do
begin
Brush.Color := clWhite;
FillRect(0, 0, PaintBox1.Width, PaintBox1.Height);
Pen.Color := clBlue;
Pen.Width := 2;
for i := 0 to 10 do
begin
MoveTo(0, i * 20);
LineTo(PaintBox1.Width, i * 20);
end;
end;
end;
Chapter 31: Database Programming
Exercise A.1 (representative) — SQL Basics
SELECT retrieves data: SELECT Name, GPA FROM Students WHERE GPA > 3.5 ORDER BY GPA DESC;. INSERT adds a row: INSERT INTO Students (Name, GPA) VALUES ('Rosa', 3.85);. UPDATE modifies existing rows: UPDATE Students SET GPA = 3.90 WHERE Name = 'Rosa';. DELETE removes rows: DELETE FROM Students WHERE GPA < 2.0;.
Chapter 32: Deploying Applications
Exercise A.1 (representative) — Static vs. Dynamic Linking
Static linking embeds all library code into the executable, producing a single large file with no dependencies. Dynamic linking keeps libraries as separate DLL/SO files that are loaded at runtime. Static: larger file, simpler deployment, no "DLL hell." Dynamic: smaller file, shared libraries reduce total disk usage, libraries can be updated independently.
Exercise A.2 (representative) — Deployment Checklist
Before distributing a Lazarus application: (1) Compile in Release mode (Project Options > Compiler Options > set optimization to -O2, remove debugging info with -g-). (2) Strip the executable with the strip command to remove debug symbols, which can reduce file size by 50-80%. (3) Test on a clean machine (one without Lazarus or Free Pascal installed) to catch missing dependencies. (4) On Windows, check whether the application needs any DLLs (e.g., SQLite3.dll for database apps). (5) Consider using an installer (Inno Setup on Windows, AppImage on Linux, .app bundle on macOS).
Part VI: Systems and Advanced Topics (Chapters 33-37)
Chapter 33: Units, Packages, and Modular Design
Exercise A.1 — Interface vs. Implementation
The interface section declares what the unit offers (public types, constants, function signatures). The implementation section provides how those declarations work (function bodies, private helpers). Separation benefits: (1) client code depends only on signatures, not on implementation details; (2) changing a function body does not require recompiling dependent units if the signature stays the same; (3) implementation details are hidden, preventing external code from relying on internal structures.
Exercise C.1 (representative) — Creating a Math Utility Unit
{ File: MathUtils.pas }
unit MathUtils;
{$mode objfpc}{$H+}
interface
function GCD(A, B: Integer): Integer;
function LCM(A, B: Integer): Integer;
function IsPrime(N: Integer): Boolean;
function Factorial(N: Integer): Int64;
implementation
function GCD(A, B: Integer): Integer;
begin
A := Abs(A);
B := Abs(B);
while B <> 0 do
begin
A := A mod B;
if A = 0 then
begin
Result := B;
Exit;
end;
B := B mod A;
end;
Result := A;
end;
function LCM(A, B: Integer): Integer;
begin
if (A = 0) or (B = 0) then
Result := 0
else
Result := Abs(A) * (Abs(B) div GCD(A, B));
end;
function IsPrime(N: Integer): Boolean;
var
i: Integer;
begin
if N < 2 then Exit(False);
if N < 4 then Exit(True);
if (N mod 2 = 0) or (N mod 3 = 0) then Exit(False);
i := 5;
while i * i <= N do
begin
if (N mod i = 0) or (N mod (i + 2) = 0) then
Exit(False);
i := i + 6;
end;
Result := True;
end;
function Factorial(N: Integer): Int64;
var
i: Integer;
begin
Result := 1;
for i := 2 to N do
Result := Result * i;
end;
end.
Chapter 34: File Formats and Serialization
Exercise A.2 — Packed Records
Without packed, the compiler inserts padding bytes for alignment (e.g., a Byte field followed by a Word field may have 1 byte of padding). This means the record's in-memory layout does not match the binary file format's byte layout. packed eliminates all padding, ensuring each field immediately follows the previous one. Essential for binary file formats where byte positions must be exact.
Exercise C.1 (representative) — JSON Read/Write
program JsonDemo;
{$mode objfpc}{$H+}
uses
SysUtils, fpjson, jsonparser;
var
Root: TJSONObject;
Expenses: TJSONArray;
Expense: TJSONObject;
JsonStr: String;
begin
{ Build JSON }
Root := TJSONObject.Create;
try
Root.Add('version', 1);
Root.Add('user', 'Rosa Martinelli');
Expenses := TJSONArray.Create;
Root.Add('expenses', Expenses);
Expense := TJSONObject.Create;
Expense.Add('date', '2026-03-15');
Expense.Add('amount', 42.50);
Expense.Add('category', 'Office Supplies');
Expense.Add('description', 'Printer paper');
Expenses.Add(Expense);
Expense := TJSONObject.Create;
Expense.Add('date', '2026-03-16');
Expense.Add('amount', 8.99);
Expense.Add('category', 'Food');
Expense.Add('description', 'Lunch');
Expenses.Add(Expense);
JsonStr := Root.FormatJSON;
WriteLn(JsonStr);
finally
Root.Free; { Frees all children automatically }
end;
end.
Important: when you add a child object to a parent (Expenses.Add(Expense)), the parent takes ownership. Only free the root object — it cascades to all children.
Chapter 35: Networking and Internet Programming
Exercise A.1 (representative) — Client-Server Model
The client initiates a connection and sends requests; the server listens for connections and sends responses. In HTTP: the browser (client) sends a GET request; the web server responds with HTML. In PennyWise/MicroServe: the desktop app (client) sends expense data via a REST API call; the server processes it and returns a confirmation or updated data. The server must be running before the client connects.
Exercise C.1 (representative) — HTTP GET Request
program HttpGetDemo;
{$mode objfpc}{$H+}
uses
SysUtils, fphttpclient;
var
Response: String;
Client: TFPHTTPClient;
begin
Client := TFPHTTPClient.Create(nil);
try
try
Response := Client.Get('https://httpbin.org/get');
WriteLn('Response received:');
WriteLn(Copy(Response, 1, 500)); { Show first 500 chars }
except
on E: Exception do
WriteLn('Error: ', E.Message);
end;
finally
Client.Free;
end;
end.
Chapter 36: Multithreading and Concurrent Programming
Exercise A.1 (representative) — Why Threads?
Threads solve the "frozen GUI" problem: when a long-running operation (file download, database query, complex calculation) runs on the main thread, the message loop is blocked and the application appears unresponsive. By running the operation on a separate thread, the main thread remains free to process UI events. The key rule: never access GUI components directly from a worker thread — use Synchronize or Queue to safely update the UI.
Exercise C.1 (representative) — Simple Worker Thread
type
TCounterThread = class(TThread)
private
FProgress: Integer;
procedure UpdateUI;
protected
procedure Execute; override;
end;
procedure TCounterThread.Execute;
var
i: Integer;
begin
for i := 1 to 100 do
begin
if Terminated then Exit;
Sleep(50); { Simulate work }
FProgress := i;
Synchronize(@UpdateUI);
end;
end;
procedure TCounterThread.UpdateUI;
begin
Form1.ProgressBar1.Position := FProgress;
Form1.LblProgress.Caption := IntToStr(FProgress) + '%';
end;
Note: Synchronize runs UpdateUI on the main thread, making it safe to access ProgressBar1 and LblProgress. Without Synchronize, accessing GUI components from the worker thread causes unpredictable behavior.
Chapter 37: Interfacing with the Operating System
Exercise A.1 (representative) — Cross-Platform Path Handling
Windows uses \ as the path separator; Unix/macOS uses /. Hardcoding separators makes code non-portable. Solution: use PathDelim constant or IncludeTrailingPathDelimiter function from SysUtils. For user-specific paths: GetAppConfigDir(False) returns the appropriate location on each OS (e.g., AppData\Local on Windows, ~/.config on Linux).
Chapter 37: Interfacing with the Operating System
Exercise A.1 (representative) — Cross-Platform Path Handling
Windows uses \ as the path separator; Unix/macOS uses /. Hardcoding separators makes code non-portable. Solution: use PathDelim constant or IncludeTrailingPathDelimiter function from SysUtils. For user-specific paths: GetAppConfigDir(False) returns the appropriate location on each OS (e.g., AppData\Local on Windows, ~/.config on Linux).
Exercise C.1 (representative) — Environment and Directory Information
program SysInfoDemo;
{$mode objfpc}{$H+}
uses
SysUtils;
var
ConfigDir, TempDir: String;
begin
WriteLn('Current directory: ', GetCurrentDir);
WriteLn('Temp directory: ', GetTempDir);
ConfigDir := GetAppConfigDir(False);
WriteLn('Config directory: ', ConfigDir);
WriteLn('Path separator: ', PathDelim);
WriteLn('Line ending: ',
{$IFDEF WINDOWS}'CRLF'{$ELSE}'LF'{$ENDIF});
WriteLn('OS: ',
{$IFDEF WINDOWS}'Windows'
{$ELSE}{$IFDEF DARWIN}'macOS'
{$ELSE}'Linux/Unix'{$ENDIF}{$ENDIF});
if not DirectoryExists(ConfigDir) then
begin
ForceDirectories(ConfigDir);
WriteLn('Created config directory.');
end;
end.
This demonstrates conditional compilation ({$IFDEF}), platform-independent path functions, and directory creation with ForceDirectories (which creates intermediate directories as needed).
Part VII: Capstone and Ecosystem (Chapters 38-40)
Chapter 38: Capstone Project
Exercise C.3 (representative) — Search Feature Design
The search feature should: (1) Add a TEdit and TButton to the toolbar area. (2) On button click, read the search text. (3) Call TTransactionManager.Search(Query) which filters transactions by checking if the query substring appears in the description, category, or formatted amount (case-insensitive). (4) Populate the grid with matching results. (5) Add a "Clear Search" button to restore the full list. The key design decision is whether to filter in memory (fast, simple) or in SQL (scalable to large datasets).
Chapter 38: Capstone Project
Exercise C.3 (representative) — Search Feature Design
The search feature should: (1) Add a TEdit and TButton to the toolbar area. (2) On button click, read the search text. (3) Call TTransactionManager.Search(Query) which filters transactions by checking if the query substring appears in the description, category, or formatted amount (case-insensitive). (4) Populate the grid with matching results. (5) Add a "Clear Search" button to restore the full list. The key design decision is whether to filter in memory (fast, simple) or in SQL (scalable to large datasets).
Exercise C.5 (representative) — Export Functionality
procedure TMainForm.BtnExportCSVClick(Sender: TObject);
var
SaveDlg: TSaveDialog;
F: TextFile;
i: Integer;
Trans: TTransaction;
begin
SaveDlg := TSaveDialog.Create(Self);
try
SaveDlg.Filter := 'CSV files|*.csv|All files|*.*';
SaveDlg.DefaultExt := '.csv';
if SaveDlg.Execute then
begin
AssignFile(F, SaveDlg.FileName);
Rewrite(F);
try
WriteLn(F, 'Date,Amount,Category,Description,Type');
for i := 0 to TransactionManager.Count - 1 do
begin
Trans := TransactionManager.Items[i];
WriteLn(F, Format('%s,%.2f,%s,"%s",%s', [
FormatDateTime('yyyy-mm-dd', Trans.Date),
Trans.Amount,
Trans.Category,
StringReplace(Trans.Description, '"', '""', [rfReplaceAll]),
Trans.TypeName
]));
end;
finally
CloseFile(F);
end;
ShowMessage('Exported ' + IntToStr(TransactionManager.Count) +
' transactions to ' + SaveDlg.FileName);
end;
finally
SaveDlg.Free;
end;
end;
Note the StringReplace call that escapes double quotes within fields — essential for correct CSV output.
Chapter 39: The Pascal Ecosystem
Exercise A.1 (representative) — Package Managers
The Online Package Manager (OPM) in Lazarus provides a GUI for discovering, installing, and updating community packages. It functions similarly to pip (Python) or npm (JavaScript), but is integrated into the IDE rather than being a command-line tool. To use OPM: Package menu > Online Package Manager > search for the package > Install. Packages are downloaded as source and compiled locally.
Chapter 40: Pascal's Legacy and Future
Exercise A.1 (representative) — Language Family Tree
Pascal influenced: Modula-2 (Wirth's successor, adding modules), Oberon (Wirth's minimalist follow-up), Ada (department of defense language with Pascal heritage), Delphi/Object Pascal (OOP extension), and C# (designed by Anders Hejlsberg, who created Turbo Pascal). Features that transferred: strong typing, structured programming, unit/module system, property syntax (Delphi to C#), and component-based development.
Exercise A.2 (representative) — Pascal in 2026
Arguments for Pascal's continued relevance: (1) Native compilation produces fast, standalone executables with no runtime dependencies — increasingly valuable as software bloat grows. (2) Strong typing catches bugs at compile time — as AI-generated code increases, the compiler acts as a safety net. (3) Lazarus provides a genuinely free, cross-platform GUI toolkit — no licensing fees, no vendor lock-in. (4) The language is small and learnable — a student can master the entire language in one semester. (5) Free Pascal is actively maintained with regular releases and a responsive community. Arguments against: smaller library ecosystem than Python/JavaScript, fewer job postings listing Pascal specifically, fewer online learning resources. The counterargument: Pascal teaches thinking that transfers to any language.