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.