Ruby.CodeCompared.To/Pascal

An interactive executable cheatsheet for Rubyists learning Pascal

Ruby 4.0 Free Pascal 3.2.2
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.