Program Structure
Hello, World
puts "Hello, World!" program HelloWorld;
begin
writeln('Hello, World!');
end. Every Pascal program begins with
program name; and ends with end. (a period, not a semicolon). The writeln procedure writes to standard output followed by a newline. Single-quoted strings are the Pascal convention; double-quoted strings are not standard Pascal.Comments
# Single-line comment
count = 10 # inline comment
puts count
=begin
Multi-line
comment block
=end program CommentsDemo;
begin
// Single-line comment (Free Pascal extension)
{ Brace-style comment β the classic Pascal way }
(* Asterisk-paren comment β also traditional *)
writeln('code'); // inline comment
end. Pascal has three comment styles. The
{ } brace style is the most traditional; (* *) is equally standard and predates brace comments; // single-line comments are a Free Pascal extension borrowed from C++. Unlike Ruby, multi-line comments are not delimited by special start/end keywords β any block comment syntax works across multiple lines.Constants
MAX_SIZE = 100
PI = 3.14159
puts MAX_SIZE
puts PI program ConstantsDemo;
const
MaxSize = 100;
Pi = 3.14159;
Greeting = 'Hello';
begin
writeln(MaxSize);
writeln(Pi:0:5);
writeln(Greeting);
end. Pascal uses a
const block before the begin to declare compile-time constants. Unlike Ruby constants (which are uppercase by convention and mutable), Pascal constants are truly immutable β the compiler will reject any assignment to them. The Pi:0:5 notation in writeln formats the real number with 0 minimum width and 5 decimal places.Type Declarations
# Ruby uses duck typing; aliases are informal
Score = Integer # not how Ruby works
# In Ruby, you'd just use Integer directly program TypeDemo;
type
Score = Integer;
Name = string[50];
Grade = 'A'..'F';
var
points: Score;
studentName: Name;
begin
points := 95;
studentName := 'Alice';
writeln(studentName, ': ', points);
end. Pascal's
type block creates named type aliases and new types. string[50] declares a fixed-length string of up to 50 characters. The 'A'..'F' syntax creates a subrange type that limits values to that range. Variable declarations must appear in a var block before begin, not inline β unlike some newer languages that support inline declarations.Declaration Order
# Ruby has no declaration order β define as you go
LIMIT = 10
def double(value)
value * 2
end
result = double(5)
puts result program StructureDemo;
uses SysUtils; { 1. Unit imports }
const
Limit = 10; { 2. Constants }
type
Count = Integer; { 3. Type aliases }
var
result: Count; { 4. Global variables }
function Double(value: Integer): Integer; { 5. Subprograms }
begin
Result := value * 2;
end;
begin { 6. Main program body }
result := Double(5);
writeln(result);
end. Pascal enforces a strict declaration order:
uses β const β type β var β subprogram definitions β main begin...end. block. Every identifier must be declared before it is used. This top-down structure means helper functions are always defined above the code that calls them, which is the opposite of Ruby's method-defined-anywhere approach.Variables & Types
Variable Declaration
# Ruby: dynamic typing, no declaration needed
count = 10
message = "hello"
price = 9.99
active = true program VarDemo;
var
count: Integer;
message: string;
price: Double;
active: Boolean;
begin
count := 10;
message := 'hello';
price := 9.99;
active := True;
writeln(count, ' ', message, ' ', price:0:2, ' ', active);
end. Pascal requires every variable to be declared in a
var block with an explicit type before any code runs. The := operator assigns values. Using a variable before assignment is a bug (the value is undefined, often zero for numbers but not guaranteed). Ruby's dynamic typing is entirely absent β every name has a fixed type for its lifetime.Integer Types
# Ruby has a single Integer type that grows arbitrarily
small = 127
large = 2_147_483_647
very_large = 9_223_372_036_854_775_807
puts small
puts large
puts very_large program IntegerTypes;
var
small: ShortInt; { -128..127 }
medium: Integer; { -2^31..2^31-1 (32-bit) }
large: Int64; { -2^63..2^63-1 }
unsigned: Cardinal; { 0..2^32-1 }
begin
small := 127;
medium := 2147483647;
large := 9223372036854775807;
unsigned := 4294967295;
writeln(small);
writeln(medium);
writeln(large);
writeln(unsigned);
end. Pascal provides fixed-width integer types:
ShortInt (8-bit), SmallInt (16-bit), Integer (32-bit on most platforms), Int64 (64-bit), and unsigned variants like Byte, Word, and Cardinal. Overflow wraps silently without raising an error, unlike Ruby where integers grow to arbitrary size automatically.Float Types
temperature = 36.6
ratio = 1.0 / 3.0
puts temperature
puts ratio.round(4) program FloatTypes;
uses SysUtils;
var
temperature: Single; { 32-bit float }
ratio: Double; { 64-bit float (preferred) }
begin
temperature := 36.6;
ratio := 1.0 / 3.0;
writeln(temperature:0:1);
writeln(Format('%.4f', [ratio]));
end. Double is the standard floating-point type in Pascal (equivalent to Ruby's Float); Single is a 32-bit alternative with less precision. The value:width:decimals syntax in writeln formats floats inline: temperature:0:1 means minimum width 0, 1 decimal place. The Format function from SysUtils provides printf-style formatting for more complex cases.Boolean
active = true
inactive = false
puts active
puts !active
puts (3 > 2)
puts (true && false) program BooleanDemo;
var
active: Boolean;
inactive: Boolean;
begin
active := True;
inactive := False;
writeln(active);
writeln(not active);
writeln(3 > 2);
writeln(True and False);
writeln(True or False);
end. Pascal prints booleans as
TRUE / FALSE (uppercase). The logical operators are words: and, or, not, xor β not &&, ||, !. In standard Pascal, and and or do not short-circuit; Free Pascal adds and then / or else for short-circuit evaluation.Character Type
letter = 'A'
puts letter
puts letter.ord
puts letter.downcase
puts 65.chr program CharDemo;
var
letter: Char;
code: Integer;
begin
letter := 'A';
code := Ord(letter);
writeln(letter);
writeln(code);
writeln(Chr(65));
writeln(Chr(Ord(letter) + 32)); { lowercase via ASCII offset }
end. Pascal's
Char type holds a single character, written with single quotes. Ord(ch) returns the ASCII code (equivalent to Ruby's String#ord), and Chr(n) converts a number back to a character (like Ruby's Integer#chr). Unlike Ruby, a Char is not a one-character string β it is a distinct type with its own operations.String Type
greeting = "Hello"
name = "Alice"
full = greeting + ", " + name + "!"
puts full
puts full.length program StringDemo;
var
greeting: string;
name: string;
full: string;
begin
greeting := 'Hello';
name := 'Alice';
full := greeting + ', ' + name + '!';
writeln(full);
writeln(Length(full));
end. Free Pascal's default
string type is AnsiString β a variable-length, reference-counted string with no length limit (unlike the older string[255] short strings). String concatenation uses +, just like Ruby. Length(s) returns the byte count. Strings are 1-indexed in Pascal: the first character is at position 1, not 0.Type Conversion
number = 42
text = number.to_s
puts text + " items"
puts text.to_i * 2
puts 3.14.to_i
puts Integer("42") program TypeConversion;
uses SysUtils;
var
number: Integer;
price: Double;
text: string;
converted: Integer;
begin
number := 42;
price := 3.14;
text := IntToStr(number);
writeln(text + ' items');
converted := StrToInt('99');
writeln(converted * 2);
writeln(FloatToStr(price));
writeln(Trunc(price)); { float β integer (truncate) }
writeln(Round(price)); { float β integer (round) }
end. Pascal requires explicit type conversion β there is no implicit coercion between numeric and string types.
IntToStr and FloatToStr convert numbers to strings; StrToInt and StrToFloat parse strings (raising an exception if the string is invalid). Trunc truncates a float toward zero; Round rounds to the nearest integer. All string utilities come from SysUtils.String Operations
Concatenation
first = "Hello"
second = "World"
result = first + ", " + second + "!"
puts result
puts "Count: " + 42.to_s program StringConcat;
uses SysUtils;
var
first: string;
second: string;
result: string;
begin
first := 'Hello';
second := 'World';
result := first + ', ' + second + '!';
writeln(result);
writeln('Count: ' + IntToStr(42));
end. Pascal concatenates strings with the
+ operator, identical to Ruby. Unlike Ruby, you cannot mix strings and numbers in concatenation β numbers must be explicitly converted first with IntToStr or FloatToStr. There is no string interpolation syntax; building formatted strings uses concatenation or the Format function.Length
text = "Hello, World!"
puts text.length
puts text.size # same as length
puts text.empty?
puts "".empty? program StringLength;
var
text: string;
begin
text := 'Hello, World!';
writeln(Length(text));
writeln(text = ''); { is it empty? }
writeln(Length('') = 0); { empty check }
end. Length(s) returns the number of bytes in a string (for pure ASCII strings, this equals the character count). Pascal has no built-in empty? method; the idiom is to compare with '' or check Length(s) = 0. Free Pascal's default AnsiString measures length in bytes, not Unicode code points, so multi-byte UTF-8 characters will count as multiple bytes.Substring
text = "Hello, World!"
puts text[0, 5] # "Hello"
puts text[7..] # "World!"
puts text[-6..] # "World!" program SubstringDemo;
var
text: string;
begin
text := 'Hello, World!';
writeln(Copy(text, 1, 5)); { "Hello" β 1-indexed, 5 chars }
writeln(Copy(text, 8, 6)); { "World!" β start at 8, 6 chars }
writeln(Copy(text, 8, MaxInt)); { "World!" β to end of string }
end. Copy(str, startPos, count) extracts a substring. Pascal strings are 1-indexed, so the first character is at position 1 β this is the opposite of Ruby's 0-based indexing. To extract to the end of the string, pass MaxInt as the count. There is no negative-index syntax; to index from the end you must calculate the position as Length(s) - offset + 1.Search
text = "Hello, World!"
puts text.index("World") # 7
puts text.include?("World") # true
puts text.include?("xyz") # false program StringSearch;
var
text: string;
position: Integer;
begin
text := 'Hello, World!';
position := Pos('World', text);
writeln(position); { 8 β 1-indexed }
writeln(position > 0); { TRUE β found }
writeln(Pos('xyz', text)); { 0 β not found }
end. Pos(needle, haystack) returns the 1-based position of the first occurrence, or 0 if not found (unlike Ruby's index, which returns nil on failure). The argument order is reversed from Ruby: the search target comes first, the string to search comes second. A return value of 0 is the standard "not found" sentinel in Pascal.Case Conversion
text = "Hello, World!"
puts text.upcase
puts text.downcase program StringCase;
uses SysUtils;
var
text: string;
begin
text := 'Hello, World!';
writeln(UpperCase(text));
writeln(LowerCase(text));
end. UpperCase and LowerCase from SysUtils convert ASCII strings. They work correctly for the 26 English letters but do not handle non-ASCII characters β accented letters like ΓΌ or Γ± pass through unchanged. For Unicode-aware case conversion, Free Pascal provides WideUpperCase and WideLowerCase in the LazUTF8 unit.Trim Whitespace
text = " hello "
puts text.strip
puts text.lstrip
puts text.rstrip program StringTrim;
uses SysUtils;
var
text: string;
begin
text := ' hello ';
writeln('[' + Trim(text) + ']');
writeln('[' + TrimLeft(text) + ']');
writeln('[' + TrimRight(text) + ']');
end. Trim, TrimLeft, and TrimRight in SysUtils correspond directly to Ruby's strip, lstrip, and rstrip. They remove ASCII whitespace (spaces, tabs, newlines) from the respective ends of the string. Unlike Ruby, there is no in-place variant β these functions always return a new string.String Formatting
name = "Alice"
score = 95
average = 3.14159
puts "#{name} scored #{score}"
puts format("Average: %.2f", average)
puts "%s: %d" % [name, score] program StringFormat;
uses SysUtils;
var
name: string;
score: Integer;
average: Double;
begin
name := 'Alice';
score := 95;
average := 3.14159;
writeln(Format('%s scored %d', [name, score]));
writeln(Format('Average: %.2f', [average]));
writeln(Format('%-10s: %5d', [name, score])); { left-align, right-align }
end. Format from SysUtils uses printf-style format specifiers: %s for strings, %d for integers, %f for floats. The arguments are passed in a square-bracket array. This is functionally equivalent to Ruby's format / sprintf or % operator. There is no string interpolation syntax in Pascal.Arrays
Static Array
scores = [85, 92, 78, 95, 88]
puts scores[0]
puts scores[4]
puts scores.length program StaticArray;
var
scores: array[1..5] of Integer;
begin
scores[1] := 85;
scores[2] := 92;
scores[3] := 78;
scores[4] := 95;
scores[5] := 88;
writeln(scores[1]);
writeln(scores[5]);
writeln(High(scores) - Low(scores) + 1); { element count }
end. Pascal static arrays are declared with explicit bounds:
array[low..high] of type. By convention arrays start at 1, though any integer bounds are valid (e.g. array[0..4] or even array[-2..2]). The size is fixed at compile time and cannot change at runtime. High(arr) and Low(arr) return the upper and lower bounds, making it easy to write generic loops.Array Iteration
numbers = [10, 20, 30, 40, 50]
numbers.each { |number| puts number }
total = numbers.sum
puts "Total: #{total}" program ArrayIteration;
var
numbers: array[1..5] of Integer;
total: Integer;
index: Integer;
begin
numbers[1] := 10; numbers[2] := 20; numbers[3] := 30;
numbers[4] := 40; numbers[5] := 50;
for index := Low(numbers) to High(numbers) do
writeln(numbers[index]);
total := 0;
for index := Low(numbers) to High(numbers) do
total := total + numbers[index];
writeln('Total: ', total);
end. Pascal array iteration uses a
for loop over the index range. Using Low(arr) and High(arr) is the recommended style β it adapts automatically if the array bounds change. There is no each or map equivalent in standard Pascal; all higher-order collection operations must be written as explicit loops.Dynamic Array
items = []
items << "apple"
items << "banana"
items << "cherry"
puts items.length
puts items[1] program DynamicArray;
var
items: array of string;
begin
SetLength(items, 3);
items[0] := 'apple';
items[1] := 'banana';
items[2] := 'cherry';
writeln(Length(items));
writeln(items[1]);
SetLength(items, 5); { grow the array }
items[3] := 'date';
items[4] := 'elderberry';
writeln(Length(items));
end. Dynamic arrays are declared with
array of type (no bounds). SetLength(arr, n) allocates or resizes the array; existing elements are preserved on growth. Dynamic arrays are always 0-indexed, unlike static arrays which default to 1-based bounds. They behave like Ruby arrays in terms of sizing but require explicit calls to SetLength β there is no automatic push.Array Bounds
numbers = [10, 20, 30, 40, 50]
puts numbers.length
puts numbers.first
puts numbers.last program ArrayBounds;
var
numbers: array[1..5] of Integer;
begin
numbers[1] := 10; numbers[2] := 20; numbers[3] := 30;
numbers[4] := 40; numbers[5] := 50;
writeln('Length: ', High(numbers) - Low(numbers) + 1);
writeln('Low: ', Low(numbers));
writeln('High: ', High(numbers));
writeln('First: ', numbers[Low(numbers)]);
writeln('Last: ', numbers[High(numbers)]);
end. Low(arr) and High(arr) return the lower and upper bounds of any array at compile time. For static arrays these are constants; for dynamic arrays they return 0 and Length(arr) - 1 respectively. Always prefer Low/High over hardcoded values β it makes code resilient to bound changes and eliminates off-by-one errors.Multi-Dimensional Array
matrix = Array.new(3) { Array.new(3, 0) }
matrix[0][0] = 1
matrix[1][1] = 5
matrix[2][2] = 9
matrix.each { |row| puts row.inspect } program TwoDArray;
var
matrix: array[1..3, 1..3] of Integer;
row, col: Integer;
begin
for row := 1 to 3 do
for col := 1 to 3 do
matrix[row, col] := 0;
matrix[1, 1] := 1;
matrix[2, 2] := 5;
matrix[3, 3] := 9;
for row := 1 to 3 do begin
for col := 1 to 3 do
write(matrix[row, col], ' ');
writeln;
end;
end. Multi-dimensional arrays in Pascal use comma-separated bounds in a single bracket:
array[1..3, 1..3] of Integer. Elements are indexed with matrix[row, col] (comma-separated, not double-bracket as in some languages). writeln with no arguments outputs a blank line. Pascal does not have a built-in array inspect or print method β displaying 2D arrays always requires nested loops.Control Flow
If / Then / Else
temperature = 25
if temperature > 30
puts "hot"
elsif temperature > 20
puts "warm"
else
puts "cool"
end program IfThen;
var
temperature: Integer;
begin
temperature := 25;
if temperature > 30 then
writeln('hot')
else if temperature > 20 then
writeln('warm')
else
writeln('cool');
end. Pascal's
if syntax uses then after the condition and has no closing end for a single-statement branch. For multiple statements in a branch, wrap them in begin...end. The else if chain is written as two separate keywords (not elsif like Ruby). Critically, there is no semicolon before else β a semicolon there ends the if statement entirely.Multi-Statement Branch
value = 42
if value > 0
puts "positive"
puts "greater than zero"
puts value * 2
end program IfBlock;
var
value: Integer;
begin
value := 42;
if value > 0 then begin
writeln('positive');
writeln('greater than zero');
writeln(value * 2);
end;
end. When a branch needs multiple statements, Pascal wraps them in
begin...end β a compound statement block. This is analogous to Ruby's if...end block, but the delimiter is explicit rather than the entire if construct having its own end. Forgetting begin...end and writing two statements after then is a classic Pascal bug: only the first statement belongs to the branch.Case Statement
day_number = 3
case day_number
when 1 then puts "Monday"
when 2 then puts "Tuesday"
when 3 then puts "Wednesday"
when 4, 5 then puts "Thursday or Friday"
else puts "Weekend"
end program CaseDemo;
var
dayNumber: Integer;
begin
dayNumber := 3;
case dayNumber of
1: writeln('Monday');
2: writeln('Tuesday');
3: writeln('Wednesday');
4, 5: writeln('Thursday or Friday');
else writeln('Weekend');
end;
end. Pascal's
case...of matches on ordinal values (integers, characters, booleans, enumerations). The syntax places the matching value(s) before a colon. Multiple values can share a branch using a comma-separated list (4, 5:). The else clause is optional. Unlike C's switch, Pascal's case never falls through β each branch ends automatically at the next case.Case with Ranges
score = 85
grade = case score
when 90..100 then "A"
when 80..89 then "B"
when 70..79 then "C"
when 60..69 then "D"
else "F"
end
puts grade program CaseRanges;
var
score: Integer;
grade: string;
begin
score := 85;
case score of
90..100: grade := 'A';
80..89: grade := 'B';
70..79: grade := 'C';
60..69: grade := 'D';
else grade := 'F';
end;
writeln(grade);
end. Pascal
case branches can use subrange syntax (low..high) to match a range of values in a single branch. This is syntactically cleaner than a chain of or comparisons. Ruby's case/when supports the same range syntax β the two languages are nearly identical here. Note that case in Pascal is a statement, not an expression; it cannot be assigned directly.Boolean Operators
age = 25
has_permit = true
puts age >= 18 && has_permit
puts age < 18 || !has_permit
puts (age >= 18) == has_permit program BooleanOps;
var
age: Integer;
hasPermit: Boolean;
begin
age := 25;
hasPermit := True;
writeln((age >= 18) and hasPermit);
writeln((age < 18) or (not hasPermit));
writeln((age >= 18) = hasPermit);
writeln((age >= 18) xor (age < 30)); { exclusive or }
end. Pascal uses English keywords for boolean operators:
and, or, not, xor. Both operands are always evaluated (no short-circuit by default). Parentheses around comparison expressions are required because and/or have lower precedence than comparison operators β age >= 18 and flag would parse as age >= (18 and flag), which is wrong.Loops
For Loop (counting up)
(1..5).each do |counter|
puts counter
end
# or
5.times { |index| puts index + 1 } program ForLoop;
var
counter: Integer;
begin
for counter := 1 to 5 do
writeln(counter);
end. Pascal's
for loop uses := start to finish do and always steps by exactly 1. The loop variable is a local copy β assigning to it inside the body has no effect on the loop progression (unlike some languages). The loop variable's value after the loop ends is undefined in standard Pascal. For loops over non-integer steps, use a while loop with explicit increment.For Loop (counting down)
5.downto(1) { |counter| puts counter } program ForDownto;
var
counter: Integer;
begin
for counter := 5 downto 1 do
writeln(counter);
end. Replace
to with downto to count downward by 1 each iteration. Pascal has no equivalent of Ruby's step option for arbitrary step sizes in a for loop; for steps other than Β±1 you must use while with a manual counter. The downto keyword is one of Pascal's most recognizable idioms for Rubyists.While Loop
counter = 1
while counter <= 5
puts counter
counter += 1
end program WhileLoop;
var
counter: Integer;
begin
counter := 1;
while counter <= 5 do begin
writeln(counter);
counter := counter + 1;
end;
end. Pascal's
while...do loop checks the condition before each iteration β identical to Ruby's while. When the loop body has multiple statements, wrap them in begin...end. There is no += compound assignment operator in Pascal; increment must be written as counter := counter + 1 or using the built-in Inc(counter) procedure.Repeat / Until
counter = 1
loop do
puts counter
counter += 1
break if counter > 5
end
# More idiomatic:
counter = 1
begin
puts counter
counter += 1
end while counter <= 5 program RepeatLoop;
var
counter: Integer;
begin
counter := 1;
repeat
writeln(counter);
counter := counter + 1;
until counter > 5;
end. repeat...until is Pascal's do-while loop: the body always runs at least once, and the condition is tested at the end of each iteration. The loop continues until the condition is True (the inverse of C's do...while which repeats while true). The repeat body never needs begin...end because repeat itself acts as the opening delimiter.Break and Continue
(1..10).each do |number|
next if number.even?
break if number > 7
puts number
end program BreakContinue;
var
number: Integer;
begin
for number := 1 to 10 do begin
if number mod 2 = 0 then Continue;
if number > 7 then Break;
writeln(number);
end;
end. Break exits the enclosing loop immediately; Continue skips the rest of the loop body and proceeds to the next iteration. These are identical in behavior to Ruby's break and next. In Pascal, Break and Continue are capitalized by convention (though FPC accepts lowercase). They work inside for, while, and repeat loops.Procedures
Basic Procedure
def greet
puts "Hello from a procedure"
puts "No return value"
end
greet program ProcedureDemo;
procedure Greet;
begin
writeln('Hello from a procedure');
writeln('No return value');
end;
begin
Greet;
end. A Pascal
procedure is a named subprogram that has no return value β analogous to a Ruby method that returns nil. Procedures are declared before the main begin...end. block. The declaration ends with a semicolon after the closing end, while the main program's last end ends with a period. Pascal's explicit distinction between procedures and functions is one of its defining design choices.Value Parameters
def greet_person(name, age)
puts "Hello, #{name}! You are #{age} years old."
end
greet_person("Alice", 30) program ProcParams;
procedure GreetPerson(personName: string; age: Integer);
begin
writeln('Hello, ', personName, '! You are ', age, ' years old.');
end;
begin
GreetPerson('Alice', 30);
end. Procedure parameters are declared with their types separated by semicolons. Parameters of the same type can be grouped:
width, height: Integer. All parameters default to pass by value β the procedure receives a copy and cannot modify the caller's variable. This is the same default as Ruby's method arguments for immutable objects like integers and strings.Var Parameters (Pass by Reference)
# Ruby passes objects by reference to their value;
# to modify a caller's variable, return a new value
def double_value(number)
number * 2 # return new value; caller must reassign
end
count = 5
count = double_value(count)
puts count program VarParams;
procedure DoubleValue(var number: Integer);
begin
number := number * 2;
end;
procedure Swap(var first, second: Integer);
var
temp: Integer;
begin
temp := first;
first := second;
second := temp;
end;
var
count: Integer;
alpha, beta: Integer;
begin
count := 5;
DoubleValue(count);
writeln(count); { 10 β modified in place }
alpha := 1; beta := 2;
Swap(alpha, beta);
writeln(alpha, ' ', beta); { 2 1 }
end. Prefixing a parameter with
var makes it pass-by-reference: the procedure receives the caller's actual variable, not a copy, and any assignment modifies the original. This is Pascal's equivalent of Ruby's swap!(a, b) pattern or passing an array/hash to a method and mutating it. The classic use case is procedures that must "return" multiple values β each output goes into a var parameter.Const Parameters
def display_info(name, score)
# Ruby has no const params; convention enforces it
puts "#{name}: #{score}"
end
display_info("Alice", 95) program ConstParams;
procedure DisplayInfo(const personName: string; const score: Integer);
begin
writeln(personName, ': ', score);
{ personName := 'Bob'; { would cause a compile error } }
end;
begin
DisplayInfo('Alice', 95);
end. Prefixing a parameter with
const tells the compiler the procedure will not modify it. For large strings and records, const can be more efficient than value parameters because the compiler may pass a reference internally while still preventing modification. Any attempt to assign to a const parameter causes a compile-time error β a useful form of documentation that Ruby lacks.Functions
Basic Function
def square(number)
number * number
end
puts square(7) program FunctionDemo;
function Square(number: Integer): Integer;
begin
Result := number * number;
end;
begin
writeln(Square(7));
end. A Pascal
function is declared like a procedure but with a return type after the parameter list. The return value is set by assigning to the special variable Result (the Free Pascal ObjFPC way) or to the function's own name (the classic Pascal way: Square := number * number). Unlike Ruby, where every method returns the last expression automatically, Pascal requires an explicit assignment.Multiple Parameters
def clamp(value, minimum, maximum)
[minimum, [value, maximum].min].max
end
puts clamp(15, 0, 10)
puts clamp(-5, 0, 10)
puts clamp(7, 0, 10) program ClampDemo;
function Clamp(value, minimum, maximum: Integer): Integer;
begin
if value < minimum then
Result := minimum
else if value > maximum then
Result := maximum
else
Result := value;
end;
begin
writeln(Clamp(15, 0, 10));
writeln(Clamp(-5, 0, 10));
writeln(Clamp(7, 0, 10));
end. Parameters of the same type can be grouped with a comma in the declaration:
value, minimum, maximum: Integer. The function body uses if...else if...else chains to set Result. Unlike Ruby, Pascal has no ternary operator (condition ? a : b) β conditional expressions must use if statements. A function must always set Result before it returns; leaving it unset is a bug.Recursive Function
def factorial(number)
return 1 if number <= 1
number * factorial(number - 1)
end
puts factorial(5)
puts factorial(10) program Recursion;
function Factorial(number: Integer): Int64;
begin
if number <= 1 then
Result := 1
else
Result := number * Factorial(number - 1);
end;
begin
writeln(Factorial(5));
writeln(Factorial(10));
end. Recursive functions in Pascal work exactly like in Ruby β the function calls itself with a modified argument until it reaches the base case. Using
Int64 as the return type prevents overflow for factorials beyond 12 (where the result exceeds Integer range). Pascal has no tail-call optimization, so very deep recursion causes a stack overflow just as it does in Ruby.Early Return with Exit
def find_first_positive(numbers)
numbers.each do |number|
return number if number > 0
end
nil
end
puts find_first_positive([-3, -1, 4, -2, 7]) program EarlyReturn;
var
numbers: array[1..5] of Integer;
index: Integer;
function FindFirstPositive(const values: array of Integer): Integer;
var
i: Integer;
begin
Result := -1; { sentinel: not found }
for i := Low(values) to High(values) do begin
if values[i] > 0 then begin
Result := values[i];
Exit; { return immediately }
end;
end;
end;
begin
numbers[1] := -3; numbers[2] := -1; numbers[3] := 4;
numbers[4] := -2; numbers[5] := 7;
writeln(FindFirstPositive(numbers));
end. Exit causes a function or procedure to return immediately, like Ruby's return. In Free Pascal you can also write Exit(value) to set Result and return in one step. An untyped Exit in a function returns whatever Result was last set to. The pattern of setting a sentinel default value for Result at the top and overriding it in the loop is idiomatic Pascal.Function Overloading
# Ruby has no overloading; use optional args or duck typing
def describe(value)
case value
when Integer then "Integer: #{value}"
when String then "String: #{value}"
when Float then "Float: #{value}"
end
end
puts describe(42)
puts describe("hello") program Overloading;
uses SysUtils;
function Describe(value: Integer): string; overload;
begin
Result := 'Integer: ' + IntToStr(value);
end;
function Describe(value: string): string; overload;
begin
Result := 'String: ' + value;
end;
function Describe(value: Double): string; overload;
begin
Result := 'Double: ' + FloatToStr(value);
end;
begin
writeln(Describe(42));
writeln(Describe('hello'));
writeln(Describe(3.14));
end. Free Pascal supports function overloading via the
overload directive: multiple functions may share the same name as long as their parameter types differ. The compiler selects the correct version at compile time based on the argument types. Ruby achieves similar flexibility through duck typing and optional arguments rather than overloading.Records & Types
Record Definition
person = { name: "Alice", age: 30, active: true }
puts person[:name]
puts person[:age]
# Or with Struct:
Person = Struct.new(:name, :age, :active)
alice = Person.new("Alice", 30, true)
puts alice.name program RecordDemo;
type
TPerson = record
name: string;
age: Integer;
active: Boolean;
end;
var
person: TPerson;
begin
person.name := 'Alice';
person.age := 30;
person.active := True;
writeln(person.name);
writeln(person.age);
writeln(person.active);
end. A Pascal
record is a value type that groups related fields β similar to Ruby's Struct but lower-level. Records are declared in the type block. By convention, user-defined type names are prefixed with T (for "type"). Records are stored by value, not by reference: assigning one record to another copies all its fields, unlike Ruby objects which are always references.Array of Records
people = [
{ name: "Alice", score: 95 },
{ name: "Bob", score: 87 },
{ name: "Carol", score: 92 },
]
people.each { |person| puts "#{person[:name]}: #{person[:score]}" } program RecordArray;
type
TStudent = record
name: string;
score: Integer;
end;
var
students: array[1..3] of TStudent;
index: Integer;
begin
students[1].name := 'Alice'; students[1].score := 95;
students[2].name := 'Bob'; students[2].score := 87;
students[3].name := 'Carol'; students[3].score := 92;
for index := 1 to 3 do
writeln(students[index].name, ': ', students[index].score);
end. Arrays of records are a fundamental data structure in Pascal, equivalent to Ruby's array of hashes or array of structs. Each element is accessed by index, and each field by dot notation:
students[2].name. Because records are value types, each array slot contains a complete copy of the record β there are no nil entries and no need to initialize with a constructor.Nested Records
address = { street: "123 Main St", city: "Springfield" }
person = { name: "Alice", address: address }
puts person[:name]
puts person[:address][:city] program NestedRecord;
type
TAddress = record
street: string;
city: string;
end;
TPerson = record
name: string;
address: TAddress;
end;
var
person: TPerson;
begin
person.name := 'Alice';
person.address.street := '123 Main St';
person.address.city := 'Springfield';
writeln(person.name);
writeln(person.address.city);
end. Records can be nested: a field can be of another record type. Access chains fields with dot notation:
person.address.city. Because records are value types, nested records are embedded inline in the parent record's memory β there are no pointer indirections or nil checks. This differs from Ruby, where nested hashes or objects are always separate heap allocations connected by references.With Statement
person = { name: "Alice", age: 30, city: "Springfield" }
# Ruby has no "with" equivalent; just use the variable
puts person[:name]
puts person[:age]
puts person[:city] program WithDemo;
type
TPerson = record
name: string;
age: Integer;
city: string;
end;
var
person: TPerson;
begin
with person do begin
name := 'Alice';
age := 30;
city := 'Springfield';
writeln(name);
writeln(age);
writeln(city);
end;
end. The
with record do statement opens the record's fields as local names inside the block, eliminating repetitive person.fieldName prefixes. It is convenient for long initialization blocks. However, with can cause subtle name-shadowing bugs when a local variable and a record field share a name β the field silently wins. Modern Pascal style avoids with in complex code for this reason.Sets
Set Declaration
require 'set'
vowels = Set.new(['a', 'e', 'i', 'o', 'u'])
puts vowels.include?('a')
puts vowels.include?('b')
puts vowels.size program SetDemo;
type
TLetterSet = set of Char;
var
vowels: TLetterSet;
begin
vowels := ['a', 'e', 'i', 'o', 'u'];
writeln('a' in vowels);
writeln('b' in vowels);
end. Pascal's built-in
set of type creates a bit-set over an ordinal type. The elements are stored as individual bits in a packed integer, making membership testing extremely fast (O(1)). Sets in Pascal are limited to ordinal types with at most 256 possible values: Char, small integers, or enumeration types. Unlike Ruby's Set, Pascal sets cannot hold arbitrary objects or grow beyond their declared element type.Set Operations
require 'set'
primary = Set.new([:red, :yellow, :blue])
secondary = Set.new([:orange, :green, :purple])
warm = Set.new([:red, :orange, :yellow])
cool = Set.new([:blue, :green, :purple])
puts (warm & cool).empty? # intersection
puts (warm | cool).size # union
puts (warm - Set.new([:orange])) # difference program SetOperations;
type
TDaySet = set of (Monday, Tuesday, Wednesday, Thursday, Friday,
Saturday, Sunday);
var
workdays: TDaySet;
weekends: TDaySet;
allDays: TDaySet;
commonDay: TDaySet;
begin
workdays := [Monday, Tuesday, Wednesday, Thursday, Friday];
weekends := [Saturday, Sunday];
allDays := workdays + weekends; { union }
commonDay := workdays * weekends; { intersection β empty }
writeln(Friday in workdays);
writeln(Saturday in workdays);
workdays := workdays - [Friday]; { difference }
writeln(Friday in workdays);
writeln(Monday in allDays);
end. Pascal set operators use arithmetic symbols:
+ for union, - for difference, * for intersection. The in operator tests membership. These map directly to Ruby's |, -, and & operators on Set. Enumeration types (type TDay = (Monday, Tuesday, ...)) are ordinal and work perfectly as set element types.Enumeration Types
# Ruby uses symbols as enumerations
DIRECTIONS = %i[north south east west]
direction = :north
puts direction
puts DIRECTIONS.include?(:north) program EnumDemo;
type
TDirection = (North, South, East, West);
var
direction: TDirection;
directions: set of TDirection;
begin
direction := North;
directions := [North, East];
writeln(Ord(direction)); { 0 β ordinal position }
writeln(direction = North); { TRUE }
writeln(West in directions); { FALSE }
writeln(North in directions); { TRUE }
for direction := North to West do
writeln(Ord(direction));
end. Pascal enumeration types define a sequence of named constants. Each constant has an ordinal value starting at 0 β
Ord(North) is 0, Ord(West) is 3. Enumerations are ordinal types, so they work as set elements and as for loop bounds. Unlike Ruby's symbols (which are just names), Pascal enumerations are type-checked: you cannot assign North to a variable of a different enumeration type.Classes & OOP
Class Definition
class Animal
def initialize(name)
@name = name
end
def speak
puts "#{@name} makes a sound"
end
end
animal = Animal.new("Dog")
animal.speak program ClassDemo;
uses SysUtils;
type
TAnimal = class
private
FName: string;
public
constructor Create(aName: string);
procedure Speak;
end;
constructor TAnimal.Create(aName: string);
begin
inherited Create;
FName := aName;
end;
procedure TAnimal.Speak;
begin
writeln(FName, ' makes a sound');
end;
var
animal: TAnimal;
begin
animal := TAnimal.Create('Dog');
animal.Speak;
animal.Free;
end. Free Pascal classes use Object Pascal (ObjFPC) syntax. The
T prefix for type names is a strong convention. Fields are prefixed with F by convention. constructor Create is the initializer (initialize in Ruby). Objects are heap-allocated and must be explicitly freed with .Free β Pascal has no garbage collector.Properties
class Person
attr_accessor :name
attr_reader :age
def initialize(name, age)
@name = name
@age = age
end
end
person = Person.new("Alice", 30)
puts person.name
person.name = "Bob"
puts person.name program Properties;
uses SysUtils;
type
TPerson = class
private
FName: string;
FAge: Integer;
public
constructor Create(aName: string; aAge: Integer);
property Name: string read FName write FName;
property Age: Integer read FAge; { read-only }
end;
constructor TPerson.Create(aName: string; aAge: Integer);
begin
inherited Create;
FName := aName;
FAge := aAge;
end;
var
person: TPerson;
begin
person := TPerson.Create('Alice', 30);
writeln(person.Name);
person.Name := 'Bob';
writeln(person.Name);
writeln(person.Age);
person.Free;
end. Object Pascal properties are declared with
property Name: Type read FField write FField. The read part names the backing field (or a getter method); the write part names the field or setter method. Omitting write makes the property read-only. Properties look like field access at the call site β person.Name β but can invoke arbitrary getter/setter logic behind the scenes, just like Ruby's attr_accessor.Inheritance
class Animal
def initialize(name)
@name = name
end
def speak = puts "#{@name} makes a sound"
end
class Dog < Animal
def speak = puts "#{@name} barks"
end
dog = Dog.new("Rex")
dog.speak program Inheritance;
uses SysUtils;
type
TAnimal = class
private
FName: string;
public
constructor Create(aName: string);
procedure Speak; virtual;
end;
TDog = class(TAnimal)
public
procedure Speak; override;
end;
constructor TAnimal.Create(aName: string);
begin
inherited Create;
FName := aName;
end;
procedure TAnimal.Speak;
begin
writeln(FName, ' makes a sound');
end;
procedure TDog.Speak;
begin
writeln(FName, ' barks');
end;
var
dog: TDog;
begin
dog := TDog.Create('Rex');
dog.Speak;
dog.Free;
end. A subclass is declared as
TDog = class(TAnimal) β identical to Ruby's class Dog < Animal. Methods that can be overridden must be marked virtual in the parent and override in the child. Without virtual, a method cannot be overridden polymorphically; without override, the child's method shadows the parent's but does not participate in virtual dispatch.Polymorphism
class Shape
def area = 0
end
class Circle < Shape
def initialize(radius) = @radius = radius
def area = Math::PI * @radius ** 2
end
class Rectangle < Shape
def initialize(width, height)
@width, @height = width, height
end
def area = @width * @height
end
shapes = [Circle.new(5), Rectangle.new(4, 6)]
shapes.each { |shape| puts shape.area.round(2) } program Polymorphism;
uses SysUtils;
type
TShape = class
function Area: Double; virtual;
end;
TCircle = class(TShape)
private
FRadius: Double;
public
constructor Create(aRadius: Double);
function Area: Double; override;
end;
TRectangle = class(TShape)
private
FWidth, FHeight: Double;
public
constructor Create(aWidth, aHeight: Double);
function Area: Double; override;
end;
function TShape.Area: Double; begin Result := 0; end;
constructor TCircle.Create(aRadius: Double);
begin inherited Create; FRadius := aRadius; end;
function TCircle.Area: Double;
begin Result := Pi * FRadius * FRadius; end;
constructor TRectangle.Create(aWidth, aHeight: Double);
begin inherited Create; FWidth := aWidth; FHeight := aHeight; end;
function TRectangle.Area: Double;
begin Result := FWidth * FHeight; end;
var
shapes: array[1..2] of TShape;
index: Integer;
begin
shapes[1] := TCircle.Create(5);
shapes[2] := TRectangle.Create(4, 6);
for index := 1 to 2 do
writeln(Format('%.2f', [shapes[index].Area]));
for index := 1 to 2 do
shapes[index].Free;
end. Virtual method dispatch enables polymorphism: an array of
TShape can hold TCircle and TRectangle objects, and calling .Area invokes the correct overridden version at runtime. This is identical in concept to Ruby's duck typing, but enforced via an explicit type hierarchy. All objects stored by base-class reference must be freed after use β Pascal does not track object lifetimes automatically.Error Handling
Try / Except
begin
raise "Something went wrong"
rescue RuntimeError => error
puts "Caught: #{error.message}"
end
puts "After exception" program TryExcept;
uses SysUtils;
begin
try
raise Exception.Create('Something went wrong');
except
on error: Exception do
writeln('Caught: ', error.Message);
end;
writeln('After exception');
end. Pascal's
try...except...end corresponds to Ruby's begin...rescue...end. The on error: Exception do clause catches a specific exception type and names it for use inside the handler β equivalent to Ruby's rescue ExceptionClass => error. Exception.Create(message) is the base exception class analogous to Ruby's RuntimeError.Catching Specific Exceptions
begin
Integer("not a number")
rescue ArgumentError => error
puts "Argument error: #{error.message}"
rescue => error
puts "Other error: #{error.message}"
end program ExceptionTypes;
uses SysUtils;
begin
try
StrToInt('not a number');
except
on error: EConvertError do
writeln('Convert error: ', error.Message);
on error: Exception do
writeln('Other error: ', error.Message);
end;
end. Multiple
on type: Handler clauses in a single except block catch different exception types, tested in order from most specific to most general. EConvertError is raised by StrToInt when the string is not a valid integer. Common exception classes in Free Pascal's SysUtils include EConvertError, EDivByZero, ERangeError, and EInOutError.Try / Finally
def process_data
puts "Opening resource"
begin
puts "Processing"
raise "Error mid-process"
ensure
puts "Closing resource (always runs)"
end
end
process_data rescue nil program TryFinally;
uses SysUtils;
procedure ProcessData;
begin
writeln('Acquiring resource');
try
writeln('Working...');
raise Exception.Create('Something went wrong');
finally
writeln('Resource released'); { always runs, even on exception }
end;
{ exception propagates to caller after finally }
end;
begin
try
ProcessData;
except
on error: Exception do
writeln('Caught by caller: ', error.Message);
end;
end. try...finally guarantees the finally block runs whether or not an exception was raised β the Pascal equivalent of Ruby's ensure. Crucially, try...finally does not catch the exception; after the finally block completes, the exception continues propagating up the call stack. In this example, ProcessData cleans up its resource, then the exception travels to the caller's try...except. Use try...finally for guaranteed cleanup and try...except separately (typically in a caller) for error handling.Custom Exception Classes
class ValidationError < StandardError
def initialize(field, message)
super("#{field}: #{message}")
end
end
begin
raise ValidationError.new("email", "is invalid")
rescue ValidationError => error
puts "Validation failed: #{error.message}"
end program CustomException;
uses SysUtils;
type
EValidationError = class(Exception)
private
FField: string;
public
constructor Create(const aField, aMessage: string);
property Field: string read FField;
end;
constructor EValidationError.Create(const aField, aMessage: string);
begin
FField := aField;
inherited Create(aField + ': ' + aMessage);
end;
begin
try
raise EValidationError.Create('email', 'is invalid');
except
on error: EValidationError do
writeln('Validation failed: ', error.Message);
on error: Exception do
writeln('Other error: ', error.Message);
end;
end. Custom exceptions inherit from
Exception (or any more specific class) and are prefixed with E by convention. The constructor calls inherited Create(message) to initialize the base class message. Custom exception classes can add extra fields for structured error information β here, Field stores which form field failed validation. The pattern mirrors Ruby's custom exception subclasses exactly.Units & Standard Library
Uses Clause
require 'json'
require 'date'
require 'set'
data = JSON.parse('{"key": "value"}')
puts data["key"] program UsesDemo;
uses
SysUtils, { string utilities, exceptions }
Math, { trigonometry, floor, ceil, max, min }
StrUtils; { PosEx, ReverseString, etc. }
var
text: string;
begin
text := 'Hello, World!';
writeln(UpperCase(text));
writeln(Format('Pi is %.4f', [Pi]));
writeln(Max(10, 20));
writeln(Min(10, 20));
end. uses is Pascal's import system β the equivalent of Ruby's require. The System unit is always implicitly included and provides basic I/O, string functions, and math built-ins. SysUtils adds Format, IntToStr, type conversion, exception classes, and file utilities. Math provides trigonometric and advanced math functions. Unlike Ruby gems, Pascal units are part of the standard library and do not require installation.Math Functions
puts Math.sqrt(16)
puts 3.7.floor
puts 3.2.ceil
puts 2 ** 10
puts [3, 1, 4, 1, 5].max
puts [3, 1, 4, 1, 5].min program MathDemo;
uses Math;
var
values: array[1..5] of LongInt;
begin
writeln(Sqrt(16):0:0);
writeln(Floor(3.7));
writeln(Ceil(3.2));
writeln(Round(Power(2, 10)));
values[1] := 3; values[2] := 1; values[3] := 4;
values[4] := 1; values[5] := 5;
writeln(MaxIntValue(values));
writeln(MinIntValue(values));
writeln(Abs(-42));
end. The
Math unit provides Floor, Ceil, Power, Log2, Log10, Ln, Sin, Cos, Tan, ArcTan, and more. Sqrt, Abs, Round, and Trunc live in the built-in System unit and are always available. MaxIntValue and MinIntValue find the extremes of an integer array.Random Numbers
puts rand(10) # 0..9
puts rand(1..6) # 1..6 (dice)
puts rand # 0.0..1.0
items = ["a", "b", "c"]
puts items.sample program RandomDemo;
var
index: Integer;
begin
Randomize; { seed from system time }
writeln(Random(10)); { 0..9 }
writeln(Random(6) + 1); { 1..6 (dice roll) }
writeln(Random:0:4); { 0.0..1.0 }
for index := 1 to 5 do
write(Random(100), ' ');
writeln;
end. Randomize seeds the random number generator from the system clock β call it once at program startup. Without it, the same seed is used every run and the sequence is deterministic. Random(N) returns an integer in 0..N-1; Random with no argument returns a Double in [0.0, 1.0). There is no built-in sample equivalent for arrays; generate a random index with Random(Length(arr)).File I/O
File.write("output.txt", "Hello, file!
")
content = File.read("output.txt")
puts content program FileIO;
var
outputFile: TextFile;
line: string;
begin
AssignFile(outputFile, 'output.txt');
Rewrite(outputFile); { open for writing }
writeln(outputFile, 'Hello, file!');
CloseFile(outputFile);
AssignFile(outputFile, 'output.txt');
Reset(outputFile); { open for reading }
while not Eof(outputFile) do begin
readln(outputFile, line);
writeln(line);
end;
CloseFile(outputFile);
end. Pascal file I/O uses a three-step pattern:
AssignFile (bind variable to path), Rewrite/Reset (open for write/read), then CloseFile when done. writeln(file, text) writes to the file; readln(file, variable) reads a line. The Eof function returns True when all lines have been read. This example cannot run in Compiler Explorer's sandbox environment, which does not allow filesystem access.β Gotchas for Rubyists
Assignment vs Comparison
# Ruby: = assigns, == compares
count = 10
puts count == 10 # true
puts count = 10 # assigns 10, prints 10 program AssignVsCompare;
var
count: Integer;
begin
count := 10; { := assigns }
writeln(count = 10); { = compares β TRUE }
writeln(count = 5); { FALSE }
{ count = 10; } { compile error: cannot use = for assignment }
end. Pascal's most famous quirk:
:= assigns a value, and = tests equality. This is the opposite convention from Ruby (and almost every other language). Writing count = 10 in a statement context is a compile error in Pascal. This design was intentional β Niklaus Wirth chose := to make assignment visually distinct from comparison, eliminating the classic C bug of writing if (x = 5) instead of if (x == 5).Integer vs Float Division
puts 10 / 3 # 3 (integer division)
puts 10.0 / 3 # 3.333... (float division)
puts 10 % 3 # 1 (modulo) program DivisionDemo;
uses SysUtils;
var
result: Double;
begin
writeln(10 div 3); { 3 β integer division }
writeln(10 mod 3); { 1 β modulo }
result := 10 / 3; { / always gives Double }
writeln(Format('%.4f', [result])); { 3.3333 }
end. Pascal divides integer arithmetic from real arithmetic explicitly. The
/ operator always returns a Double, even when both operands are integers. Use div for integer division (truncates toward zero) and mod for the remainder. Ruby's / on two integers returns an integer; Pascal's does not. Assigning 10 / 3 to an Integer variable is a compile error without an explicit cast.Semicolons: When to Omit
# Ruby has no semicolon rules to memorize
if true
puts "yes" # no trailing semicolon
end program SemicolonRules;
var
count: Integer;
begin
count := 1; { semicolon: separates statements }
if count > 0 then
writeln('positive') { NO semicolon before else }
else
writeln('non-positive');
writeln('done'); { last statement before end: semicolon optional }
end. Semicolons in Pascal are statement separators, not terminators β they appear between statements, not after every statement. The most common pitfall: placing a semicolon before
else accidentally ends the if and makes the else an orphan, causing a compile error. Similarly, no semicolon is needed after the last statement before end, before until in a repeat loop, or before end.Case Does Not Fall Through
# Ruby case/when never falls through
value = 2
case value
when 1
puts "one"
when 2
puts "two" # only this runs
when 3
puts "three"
end program CaseNoFallthrough;
var
value: Integer;
begin
value := 2;
case value of
1: writeln('one');
2: writeln('two'); { only this branch runs }
3: writeln('three');
end;
{ No break needed β Pascal case never falls through }
end. Pascal's
case statement never falls through to the next branch β each branch ends automatically after its statement or block. This is the opposite of C and Java, where a missing break causes fall-through. Rubyists accustomed to Ruby's case/when (which also never falls through) will find Pascal's behavior natural. There is no Pascal equivalent of C's intentional fall-through pattern.Array Indexing: 1-Based vs 0-Based
items = ["a", "b", "c"]
puts items[0] # "a" β 0-based
puts items[1] # "b"
puts items[-1] # "c" β negative index from end program ArrayIndexing;
var
staticItems: array[1..3] of string;
dynamicItems: array of string;
begin
staticItems[1] := 'a'; { static arrays are 1-based by convention }
staticItems[2] := 'b';
staticItems[3] := 'c';
writeln(staticItems[1]);
writeln(staticItems[3]);
SetLength(dynamicItems, 3);
dynamicItems[0] := 'x'; { dynamic arrays are always 0-based }
dynamicItems[1] := 'y';
dynamicItems[2] := 'z';
writeln(dynamicItems[0]);
writeln(dynamicItems[2]);
end. Pascal static arrays default to 1-based indexing, but you can declare any bounds you like:
array[0..4] is perfectly valid. Dynamic arrays are always 0-based. This inconsistency surprises Rubyists who expect a single convention. There is no negative-index syntax for accessing from the end β use arr[High(arr)] for the last element. Picking up the wrong convention causes silent one-off errors that pass the compiler check.