After completing this exercise, you will be able to:
Command-line arguments allow users to pass information to a program when it is executed. In Pascal, command-line arguments are accessed using the built-in ParamCount function and ParamStr function.
Pascal provides two functions to access command-line arguments:
| Function | Description |
|---|---|
ParamCount |
Returns the number of arguments passed (excluding the program name) |
ParamStr(n) |
Returns the nth argument as a string (0 = program name) |
program CmdArgs;
var
i: integer;
begin
writeln('Number of arguments: ', ParamCount);
for i := 0 to ParamCount do
writeln('ParamStr(', i, ') = ', ParamStr(i));
end.
Compile and run with arguments:
fpc cmdargs.pas
./cmdargs hello world 123
ParamStr(0) is always the program name itself. User-provided arguments start at ParamStr(1). Unlike C/C++'s argc, ParamCount does not include the program name in its count.
Here's an example that expects a filename as a command-line argument:
program ProcessArgs;
uses
SysUtils; { For StrToInt }
var
filename: string;
count: integer;
begin
{ Check if correct number of arguments provided }
if ParamCount < 1 then
begin
writeln('Usage: ', ParamStr(0), ' <filename> [count]');
Halt(1); { Exit with error code }
end;
filename := ParamStr(1);
writeln('Processing file: ', filename);
{ If a second argument is provided, treat it as a number }
if ParamCount >= 2 then
begin
count := StrToInt(ParamStr(2));
writeln('Count parameter: ', count);
end;
end.
Halt procedure terminates the program immediately. You can pass an exit code as an argument (0 typically means success, non-zero indicates an error).
Pascal provides robust file handling capabilities through the TextFile type (for text files) and related procedures. File operations follow a specific pattern: assign, open, read/write, close.
The basic approach uses Assign, Reset, and Close:
program ReadFileDemo;
var
f: TextFile;
line: string;
begin
if ParamCount < 1 then
begin
writeln('Usage: ', ParamStr(0), ' <filename>');
Halt(1);
end;
{ Associate file variable with filename }
Assign(f, ParamStr(1));
{ Open file for reading }
Reset(f);
{ Read line by line until end of file }
while not Eof(f) do
begin
ReadLn(f, line);
writeln(line);
end;
{ Close the file }
Close(f);
end.
| Procedure | Description |
|---|---|
Assign(f, name) |
Associates a file variable with a filename |
Reset(f) |
Opens an existing file for reading |
Rewrite(f) |
Creates a new file for writing (overwrites if exists) |
Append(f) |
Opens a file for appending (writes to end) |
ReadLn(f, var) |
Reads a line from file into variable |
WriteLn(f, data) |
Writes data to file followed by newline |
Eof(f) |
Returns true if at end of file |
Close(f) |
Closes the file |
It's good practice to handle potential file errors using the {$I} directive and IOResult:
program SafeFileRead;
var
f: TextFile;
line: string;
filename: string;
begin
if ParamCount < 1 then
begin
writeln('Usage: ', ParamStr(0), ' <filename>');
Halt(1);
end;
filename := ParamStr(1);
Assign(f, filename);
{ Disable automatic I/O error checking }
{$I-}
Reset(f);
{$I+}
{ Check if file opened successfully }
if IOResult <> 0 then
begin
writeln('Error: File ''', filename, ''' not found.');
Halt(1);
end;
writeln('File contents:');
while not Eof(f) do
begin
ReadLn(f, line);
writeln(line);
end;
Close(f);
end.
{$I-} directive disables automatic I/O checking, allowing you to handle errors manually with IOResult. Always re-enable it with {$I+} after checking. If IOResult returns 0, the operation succeeded.
The SysUtils unit provides a cleaner way to check if a file exists:
program CheckFileExists;
uses
SysUtils;
var
f: TextFile;
line: string;
filename: string;
begin
if ParamCount < 1 then
begin
writeln('Usage: ', ParamStr(0), ' <filename>');
Halt(1);
end;
filename := ParamStr(1);
{ Check if file exists before opening }
if not FileExists(filename) then
begin
writeln('Error: File ''', filename, ''' not found.');
Halt(1);
end;
Assign(f, filename);
Reset(f);
while not Eof(f) do
begin
ReadLn(f, line);
writeln(line);
end;
Close(f);
end.
Tokenization is the process of breaking a string into smaller pieces (tokens) based on delimiters. Pascal provides several ways to accomplish this using string manipulation functions.
| Function | Description | Example |
|---|---|---|
Pos(substr, str) |
Returns position of substring (1-based), or 0 if not found | Pos(',', 'a,b,c') → 2 |
Copy(str, start, len) |
Returns a substring starting at position start | Copy('hello', 2, 3) → 'ell' |
Length(str) |
Returns the length of the string | Length('hello') → 5 |
Delete(str, start, len) |
Removes characters from string (modifies in place) | Delete(s, 1, 3) |
Trim(str) |
Removes leading and trailing whitespace (SysUtils) | Trim(' hi ') → 'hi' |
Here's how to split a string using a delimiter:
program TokenizeDemo;
var
sentence: string;
token: string;
delimPos: integer;
tokenCount: integer;
begin
sentence := 'The quick brown fox jumps';
tokenCount := 0;
writeln('Original: ', sentence);
writeln('Tokens:');
{ Process tokens while string is not empty }
while Length(sentence) > 0 do
begin
delimPos := Pos(' ', sentence);
if delimPos > 0 then
begin
{ Extract token before delimiter }
token := Copy(sentence, 1, delimPos - 1);
{ Remove token and delimiter from string }
Delete(sentence, 1, delimPos);
end
else
begin
{ No more delimiters - rest of string is last token }
token := sentence;
sentence := '';
end;
Inc(tokenCount);
writeln(' Token ', tokenCount, ': ', token);
end;
writeln('Total tokens: ', tokenCount);
end.
It's helpful to create a function that extracts the next token from a string:
program TokenizerFunction;
uses
SysUtils;
{ Extracts and returns the next token, modifying the source string }
function GetNextToken(var source: string; delimiter: char): string;
var
delimPos: integer;
begin
source := Trim(source); { Remove leading/trailing whitespace }
if Length(source) = 0 then
begin
GetNextToken := '';
exit;
end;
delimPos := Pos(delimiter, source);
if delimPos > 0 then
begin
GetNextToken := Copy(source, 1, delimPos - 1);
Delete(source, 1, delimPos);
end
else
begin
GetNextToken := source;
source := '';
end;
end;
var
csvLine: string;
field: string;
begin
csvLine := 'Alice,25,Computer Science,3.85';
writeln('Parsing CSV: ', csvLine);
writeln;
field := GetNextToken(csvLine, ',');
writeln('Name: ', field);
field := GetNextToken(csvLine, ',');
writeln('Age: ', field);
field := GetNextToken(csvLine, ',');
writeln('Major: ', field);
field := GetNextToken(csvLine, ',');
writeln('GPA: ', field);
end.
Combining file reading with tokenization:
program TokenizeFile;
uses
SysUtils;
function GetNextToken(var source: string; delimiter: char): string;
var
delimPos: integer;
begin
source := Trim(source);
if Length(source) = 0 then
begin
GetNextToken := '';
exit;
end;
delimPos := Pos(delimiter, source);
if delimPos > 0 then
begin
GetNextToken := Copy(source, 1, delimPos - 1);
Delete(source, 1, delimPos);
end
else
begin
GetNextToken := source;
source := '';
end;
end;
var
f: TextFile;
line: string;
token: string;
tokenCount: integer;
begin
if ParamCount < 1 then
begin
writeln('Usage: ', ParamStr(0), ' <filename>');
Halt(1);
end;
if not FileExists(ParamStr(1)) then
begin
writeln('Error: File not found.');
Halt(1);
end;
tokenCount := 0;
Assign(f, ParamStr(1));
Reset(f);
while not Eof(f) do
begin
ReadLn(f, line);
{ Tokenize each line on spaces }
while Length(line) > 0 do
begin
token := GetNextToken(line, ' ');
if Length(token) > 0 then
Inc(tokenCount);
end;
end;
Close(f);
writeln('Total tokens in file: ', tokenCount);
end.
Trim function helps clean up the string, and checking for empty tokens prevents counting blank entries.
Pascal supports both static arrays (fixed size, determined at compile time) and dynamic arrays (size can change at runtime). Understanding both is essential for effective Pascal programming.
Static arrays have a fixed size that must be known at compile time:
program StaticArrayDemo;
var
{ Array with indices 1 to 5 }
numbers: array[1..5] of integer;
{ Array with indices 0 to 4 (more common in modern usage) }
grades: array[0..4] of real;
{ Array initialized at declaration }
days: array[1..7] of string = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun');
i: integer;
begin
{ Assign values }
for i := 1 to 5 do
numbers[i] := i * 10;
{ Access values }
writeln('Numbers:');
for i := 1 to 5 do
writeln(' numbers[', i, '] = ', numbers[i]);
writeln;
writeln('Days of the week:');
for i := 1 to 7 do
writeln(' ', days[i]);
end.
array[1..100], array['A'..'Z'], or even arrays indexed by enumerated types!
Dynamic arrays can change size at runtime using SetLength:
program DynamicArrayDemo;
var
{ Dynamic array - no size specified }
numbers: array of integer;
words: array of string;
i: integer;
begin
{ Set initial size }
SetLength(numbers, 5);
{ Dynamic arrays are always 0-indexed }
for i := 0 to 4 do
numbers[i] := (i + 1) * 10;
writeln('Initial array (length ', Length(numbers), '):');
for i := 0 to High(numbers) do
writeln(' numbers[', i, '] = ', numbers[i]);
{ Grow the array }
SetLength(numbers, 8);
numbers[5] := 60;
numbers[6] := 70;
numbers[7] := 80;
writeln;
writeln('After growing (length ', Length(numbers), '):');
for i := 0 to High(numbers) do
writeln(' numbers[', i, '] = ', numbers[i]);
{ String array example }
SetLength(words, 3);
words[0] := 'Hello';
words[1] := 'Dynamic';
words[2] := 'World';
writeln;
writeln('Words:');
for i := 0 to High(words) do
writeln(' ', words[i]);
end.
| Function | Description |
|---|---|
Length(arr) |
Returns the number of elements in the array |
High(arr) |
Returns the highest valid index (Length - 1 for dynamic arrays) |
Low(arr) |
Returns the lowest valid index (always 0 for dynamic arrays) |
SetLength(arr, n) |
Sets the size of a dynamic array (preserves existing elements) |
High(arr) instead of Length(arr) - 1 for cleaner code.
Since Pascal doesn't have a built-in append operator like C++'s push_back, you need to resize the array manually. Important: When passing dynamic arrays to procedures that need to resize them, you must define a type alias:
program AppendToArray;
type
{ Define type aliases for dynamic arrays }
TIntArray = array of integer;
var
numbers: TIntArray;
i: integer;
{ Procedure to append a value to a dynamic array }
procedure AppendInt(var arr: TIntArray; value: integer);
begin
SetLength(arr, Length(arr) + 1);
arr[High(arr)] := value;
end;
begin
{ Start with empty array }
SetLength(numbers, 0);
{ Append elements one at a time }
AppendInt(numbers, 10);
AppendInt(numbers, 20);
AppendInt(numbers, 30);
AppendInt(numbers, 40);
AppendInt(numbers, 50);
writeln('Array after appending (length ', Length(numbers), '):');
for i := 0 to High(numbers) do
writeln(' [', i, '] = ', numbers[i]);
end.
array of integer in a procedure parameter declaration creates an "open array parameter," which cannot be resized with SetLength. To pass dynamic arrays that need resizing, you must declare a named type (like TIntArray = array of integer) and use that type in both the variable declaration and procedure parameter.
A complete example that reads a file, tokenizes it, and stores tokens in a dynamic array:
program TokensToArray;
uses
SysUtils;
type
TStringArray = array of string;
var
tokens: TStringArray;
f: TextFile;
line, token: string;
delimPos: integer;
i: integer;
procedure AppendToken(var arr: TStringArray; value: string);
begin
SetLength(arr, Length(arr) + 1);
arr[High(arr)] := value;
end;
begin
if ParamCount < 1 then
begin
writeln('Usage: ', ParamStr(0), ' <filename>');
Halt(1);
end;
if not FileExists(ParamStr(1)) then
begin
writeln('Error: File not found.');
Halt(1);
end;
{ Initialize empty array }
SetLength(tokens, 0);
Assign(f, ParamStr(1));
Reset(f);
while not Eof(f) do
begin
ReadLn(f, line);
line := Trim(line);
while Length(line) > 0 do
begin
delimPos := Pos(' ', line);
if delimPos > 0 then
begin
token := Copy(line, 1, delimPos - 1);
Delete(line, 1, delimPos);
line := Trim(line);
end
else
begin
token := line;
line := '';
end;
if Length(token) > 0 then
AppendToken(tokens, token);
end;
end;
Close(f);
{ Display results }
writeln('=== File Token Analysis ===');
writeln('Total tokens: ', Length(tokens));
if Length(tokens) > 0 then
begin
writeln('First token: ', tokens[0]);
writeln('Last token: ', tokens[High(tokens)]);
end;
writeln;
writeln('Tokens longer than 5 characters:');
for i := 0 to High(tokens) do
begin
if Length(tokens[i]) > 5 then
writeln(' [', i, ']: ', tokens[i], ' (length: ', Length(tokens[i]), ')');
end;
end.
Unlike languages with garbage collection (like Java or Python), Pascal requires more awareness of memory management. Understanding how Pascal handles memory is crucial for writing correct and efficient programs.
Pascal uses different strategies depending on the type of data:
| Data Type | Memory Management | Notes |
|---|---|---|
| Static variables | Automatic | Allocated on stack, freed when scope ends |
| Strings | Automatic (reference counted) | FPC handles memory automatically |
| Dynamic arrays | Automatic (reference counted) | Freed when no longer referenced |
| Pointers (New/GetMem) | Manual | Must call Dispose/FreeMem |
| Objects/Classes | Manual | Must call Free or destructor |
Free Pascal uses reference counting for dynamic arrays and strings. When the last reference to an array goes away, the memory is automatically freed:
program RefCountDemo;
type
TIntArray = array of integer;
TStringArray = array of string;
procedure CreateArrays;
var
arr1: TIntArray;
arr2: TStringArray;
begin
{ Memory is allocated automatically }
SetLength(arr1, 1000);
SetLength(arr2, 100);
writeln('Arrays created inside procedure');
writeln('arr1 length: ', Length(arr1));
writeln('arr2 length: ', Length(arr2));
{ When this procedure returns, arr1 and arr2 go out of scope }
{ Reference count drops to 0, memory is automatically freed }
end;
var
i: integer;
begin
writeln('=== Reference Counting Demo ===');
for i := 1 to 5 do
begin
writeln;
writeln('Iteration ', i);
CreateArrays;
{ Memory from previous call has been freed }
end;
writeln;
writeln('Program completed - all memory cleaned up automatically');
end.
When using pointers directly, you must manually allocate and free memory:
program ManualMemoryDemo;
type
PStudent = ^TStudent;
TStudent = record
name: string;
age: integer;
gpa: real;
end;
var
student: PStudent;
begin
writeln('=== Manual Memory Management Demo ===');
{ Allocate memory for the record }
New(student);
writeln('Memory allocated with New()');
{ Use the allocated memory }
student^.name := 'Alice';
student^.age := 20;
student^.gpa := 3.85;
writeln('Student: ', student^.name);
writeln('Age: ', student^.age);
writeln('GPA: ', student^.gpa:0:2);
{ IMPORTANT: Free the memory when done }
Dispose(student);
writeln('Memory freed with Dispose()');
{ Failing to call Dispose() would cause a memory leak! }
end.
New() to allocate memory, you must call Dispose() to free it. Forgetting to free memory causes memory leaks — your program will consume more and more memory over time.
| Feature | Pascal (FPC) | Garbage-Collected (Java, Python) |
|---|---|---|
| Dynamic arrays | Automatic (reference counted) | Automatic (GC) |
| Strings | Automatic (reference counted) | Automatic (GC) |
| Pointer/object allocation | Manual (New/Dispose) | Automatic (GC) |
| When memory is freed | Immediately when ref count = 0 | When GC runs (unpredictable) |
| Memory leaks possible | Yes (if Dispose forgotten) | Rare (GC prevents most) |
| Performance overhead | Minimal | GC pauses possible |
program MemoryBestPractices;
uses
SysUtils;
type
TIntArray = array of integer;
PData = ^TData;
TData = record
values: TIntArray;
name: string;
end;
var
data: PData;
begin
data := nil; { Initialize pointer to nil }
try
{ Allocate memory }
New(data);
{ Use the memory }
data^.name := 'Test Data';
SetLength(data^.values, 5);
data^.values[0] := 100;
writeln('Data name: ', data^.name);
writeln('First value: ', data^.values[0]);
finally
{ Always free memory, even if an error occurred }
if data <> nil then
begin
{ Dynamic array inside record is freed automatically }
Dispose(data);
data := nil; { Set to nil after freeing }
end;
end;
writeln('Memory properly cleaned up');
end.
try...finally blocks to ensure memory is freed even if an error occurs. Always set pointers to nil after freeing to avoid "dangling pointer" bugs where you accidentally use freed memory.
This example shows how dynamic arrays handle memory automatically when processing files:
program ProcessLargeData;
uses
SysUtils;
type
TStringArray = array of string;
procedure AnalyzeFile(filename: string);
var
f: TextFile;
line: string;
tokens: TStringArray;
longWords: TStringArray;
delimPos: integer;
token: string;
begin
writeln('Analyzing: ', filename);
{ Initialize dynamic arrays - memory allocated automatically }
SetLength(tokens, 0);
SetLength(longWords, 0);
Assign(f, filename);
Reset(f);
while not Eof(f) do
begin
ReadLn(f, line);
line := Trim(line);
while Length(line) > 0 do
begin
delimPos := Pos(' ', line);
if delimPos > 0 then
begin
token := Copy(line, 1, delimPos - 1);
Delete(line, 1, delimPos);
line := Trim(line);
end
else
begin
token := line;
line := '';
end;
if Length(token) > 0 then
begin
SetLength(tokens, Length(tokens) + 1);
tokens[High(tokens)] := token;
if Length(token) > 6 then
begin
SetLength(longWords, Length(longWords) + 1);
longWords[High(longWords)] := token;
end;
end;
end;
end;
Close(f);
writeln(' Tokens found: ', Length(tokens));
writeln(' Long words: ', Length(longWords));
{ When this procedure returns:
- tokens array reference count drops to 0 -> memory freed
- longWords array reference count drops to 0 -> memory freed
- All strings in arrays are also freed (reference counted)
No manual cleanup needed! }
end;
var
i: integer;
begin
if ParamCount < 1 then
begin
writeln('Usage: ', ParamStr(0), ' <file1> [file2] [file3] ...');
Halt(1);
end;
writeln('=== Multi-File Analyzer ===');
writeln;
for i := 1 to ParamCount do
begin
if FileExists(ParamStr(i)) then
begin
AnalyzeFile(ParamStr(i));
writeln;
end
else
writeln('Skipping (not found): ', ParamStr(i));
end;
writeln('Done! All temporary memory has been reclaimed.');
end.
New/GetMem or when working with objects/classes.
This comprehensive example combines all the concepts: command-line arguments, file reading, tokenization, arrays, and demonstrates Pascal's memory management:
program WordFrequencyAnalyzer;
uses
SysUtils;
type
TStringArray = array of string;
TIntArray = array of integer;
var
f: TextFile;
line, token, word: string;
filename: string;
delimPos: integer;
{ Dynamic arrays for storing data }
tokens: TStringArray;
uniqueWords: TStringArray;
wordCounts: TIntArray;
i, j: integer;
found: boolean;
maxCount: integer;
mostCommon: string;
{ Remove punctuation from beginning and end of a word }
function StripPunctuation(s: string): string;
const
Punctuation = '.,!?;:"''()-';
begin
while (Length(s) > 0) and (Pos(s[1], Punctuation) > 0) do
Delete(s, 1, 1);
while (Length(s) > 0) and (Pos(s[Length(s)], Punctuation) > 0) do
Delete(s, Length(s), 1);
StripPunctuation := s;
end;
begin
{ Check command-line arguments }
if ParamCount < 1 then
begin
writeln('Word Frequency Analyzer');
writeln('Usage: ', ParamStr(0), ' <filename>');
Halt(1);
end;
filename := ParamStr(1);
{ Check if file exists }
if not FileExists(filename) then
begin
writeln('Error: File ''', filename, ''' not found.');
Halt(1);
end;
{ Initialize dynamic arrays }
SetLength(tokens, 0);
SetLength(uniqueWords, 0);
SetLength(wordCounts, 0);
{ Read and tokenize the file }
Assign(f, filename);
Reset(f);
while not Eof(f) do
begin
ReadLn(f, line);
line := LowerCase(Trim(line));
while Length(line) > 0 do
begin
delimPos := Pos(' ', line);
if delimPos > 0 then
begin
token := Copy(line, 1, delimPos - 1);
Delete(line, 1, delimPos);
line := Trim(line);
end
else
begin
token := line;
line := '';
end;
if Length(token) > 0 then
begin
SetLength(tokens, Length(tokens) + 1);
tokens[High(tokens)] := token;
end;
end;
end;
Close(f);
{ Count word frequencies }
for i := 0 to High(tokens) do
begin
word := StripPunctuation(tokens[i]);
if Length(word) = 0 then
continue;
{ Check if word already exists }
found := false;
for j := 0 to High(uniqueWords) do
begin
if uniqueWords[j] = word then
begin
Inc(wordCounts[j]);
found := true;
break;
end;
end;
{ Add new word if not found }
if not found then
begin
SetLength(uniqueWords, Length(uniqueWords) + 1);
SetLength(wordCounts, Length(wordCounts) + 1);
uniqueWords[High(uniqueWords)] := word;
wordCounts[High(wordCounts)] := 1;
end;
end;
{ Display results }
writeln('=== Word Frequency Analysis ===');
writeln('File: ', filename);
writeln('Total tokens: ', Length(tokens));
writeln('Unique words: ', Length(uniqueWords));
writeln;
{ Display words with count > 1 }
writeln('Words appearing more than once:');
writeln('--------------------------------');
for i := 0 to High(uniqueWords) do
begin
if wordCounts[i] > 1 then
writeln(' ', uniqueWords[i]:15, ' : ', wordCounts[i]);
end;
{ Find most common word }
maxCount := 0;
mostCommon := '';
for i := 0 to High(uniqueWords) do
begin
if wordCounts[i] > maxCount then
begin
maxCount := wordCounts[i];
mostCommon := uniqueWords[i];
end;
end;
writeln;
writeln('Most common word: ''', mostCommon, ''' (', maxCount, ' occurrences)');
{ Memory note: All dynamic arrays (tokens, uniqueWords, wordCounts)
will be automatically freed when the program ends because their
reference counts drop to 0. No manual cleanup needed! }
end.
| Task | Pascal Syntax |
|---|---|
| Get argument count | ParamCount |
| Get argument n | ParamStr(n) |
| Exit program with code | Halt(exitCode); |
| Associate file variable | Assign(f, filename); |
| Open file for reading | Reset(f); |
| Read line from file | ReadLn(f, line); |
| Check end of file | Eof(f) |
| Close file | Close(f); |
| Check file exists | FileExists(filename) |
| Find substring position | Pos(substr, str) |
| Extract substring | Copy(str, start, length) |
| Delete from string | Delete(str, start, length); |
| Declare dynamic array type | type TMyArray = array of Type; |
| Declare dynamic array variable | var arr: TMyArray; |
| Set array size | SetLength(arr, n); |
| Get array length | Length(arr) |
| Get highest index | High(arr) |
| Allocate pointer memory | New(ptr); |
| Free pointer memory | Dispose(ptr); |
Write a Pascal program that processes a CSV (Comma-Separated Values) file containing student grade data. Your program should:
StudentName,Score1,Score2,Score3Alice,85,92,78
Alice,85,92,78
Bob,90,88,95
Charlie,72,68,75
Diana,95,98,92
GetNextToken function to parse each CSV line by commaStrToFloat from SysUtils to convert score strings to numbersTStringArray = array of string)SetLength to store data as you read it(score1 + score2 + score3) / 3Save your program as grade_analyzer.pas and compile with:
fpc grade_analyzer.pas
./grade_analyzer grades.csv
Use the following template to get started. Fill in each blank marked with ________ with the appropriate code. Read the comments carefully for hints!
grade_analyzer.pasgrades.csv fileprogram GradeAnalyzer;
{
CSV Grade Analyzer - Student Starter Code
=========================================
Complete the sections marked with TODO to finish this program.
This program will:
1. Accept a CSV filename as a command-line argument
2. Read and parse the CSV file (Name,Score1,Score2,Score3)
3. Store student names and calculated averages in arrays
4. Display individual averages and class statistics
Author: [Your Name]
Date: [Date]
}
uses
SysUtils; { For StrToFloat, FileExists, Trim }
type
{ IMPORTANT: Define type aliases for dynamic arrays so they can be
passed to procedures and resized with SetLength }
TStringArray = array of string;
TRealArray = array of real;
var
{ File handling }
f: TextFile;
line: string;
filename: string;
{ Dynamic arrays for storing student data }
names: TStringArray;
averages: TRealArray;
{ Variables for parsing and calculations }
token: string;
score1, score2, score3: real;
studentAvg, classTotal, classAvg: real;
highestAvg, lowestAvg: real;
highestName, lowestName: string;
i: integer;
{
===========================================================================
Function: GetNextToken
===========================================================================
Extracts the next token from a string based on a delimiter.
Modifies the source string by removing the extracted token.
Parameters:
source - the string to extract from (modified by this function)
delimiter - the character to split on
Returns:
The extracted token (text before the delimiter)
HINT: Use Pos() to find the delimiter, Copy() to extract,
and Delete() to remove the token from source.
}
function GetNextToken(var source: string; delimiter: char): string;
var
delimPos: integer;
begin
source := Trim(source);
if Length(source) = 0 then
begin
GetNextToken := '';
exit;
end;
{ TODO: Find the position of the delimiter in source
Use: Pos(whatToFind, whereToLook) }
delimPos := ________(delimiter, ________);
if delimPos > 0 then
begin
{ TODO: Extract token (everything before the delimiter)
Use: Copy(sourceString, startPosition, length) }
GetNextToken := ________(source, 1, delimPos - 1);
{ TODO: Remove the token and delimiter from source
Use: Delete(stringVar, startPosition, count) }
________(source, 1, delimPos);
end
else
begin
{ No delimiter found - return entire string }
GetNextToken := source;
source := '';
end;
end;
{
===========================================================================
Procedure: AppendStudent
===========================================================================
Adds a student's name and average to the dynamic arrays.
HINT: Use SetLength to grow the arrays by 1,
then assign to the new last element using High().
NOTE: We use TStringArray and TRealArray types here (not "array of string")
so that SetLength can resize the arrays.
}
procedure AppendStudent(var nameArr: TStringArray;
var avgArr: TRealArray;
studentName: string;
studentAvg: real);
begin
{ TODO: Grow the name array by 1
Use: SetLength(arrayVar, newSize) }
________(nameArr, Length(nameArr) + 1);
{ TODO: Grow the average array by 1 }
________(avgArr, ________(avgArr) + 1);
{ TODO: Add the student name at the end of nameArr
Use: High(arrayVar) to get the last index }
nameArr[________(nameArr)] := studentName;
{ TODO: Add the student average at the end of avgArr }
avgArr[________(avgArr)] := studentAvg;
end;
{ =========================================================================== }
{ ===== MAIN PROGRAM ======================================================== }
{ =========================================================================== }
begin
{ ================================================ }
{ PART 1: Command-Line Argument Handling }
{ ================================================ }
{ TODO: Check if a filename was provided
HINT: Use ParamCount to get number of arguments }
if ________ < 1 then
begin
writeln('=== Student Grade Analyzer ===');
{ TODO: Display the program name using ParamStr(0) }
writeln('Usage: ', ________(0), ' <filename>');
Halt(1);
end;
{ TODO: Get the filename from command-line argument #1 }
filename := ________(1);
{ TODO: Check if file exists
HINT: Use FileExists() function from SysUtils }
if not ________(filename) then
begin
writeln('Error: File ''', filename, ''' not found.');
Halt(1);
end;
{ ================================================ }
{ PART 2: Initialize Arrays and Open File }
{ ================================================ }
{ TODO: Initialize dynamic arrays to empty (length 0)
Use: SetLength(arrayVar, 0) }
________(names, 0);
________(averages, 0);
{ TODO: Associate the file variable with the filename
Use: Assign(fileVar, filename) }
________(f, filename);
{ TODO: Open the file for reading
Use: Reset(fileVar) }
________(f);
{ ================================================ }
{ PART 3: Read and Parse Each Line }
{ ================================================ }
{ TODO: Loop while not at end of file
Use: while not Eof(fileVar) do }
while not ________(f) do
begin
{ TODO: Read a line from the file into 'line' variable
Use: ReadLn(fileVar, stringVar) }
________(f, line);
{ Skip empty lines }
if Length(Trim(line)) = 0 then
continue;
{ TODO: Extract student name (first token before comma)
Use the GetNextToken function you completed above }
token := ________(line, ',');
{ TODO: Extract score1 - get next token and convert to real
Use: StrToFloat(stringValue) to convert string to real }
score1 := StrToFloat(________(line, ','));
{ TODO: Extract score2 }
score2 := ________(GetNextToken(line, ','));
{ TODO: Extract score3 }
score3 := ________(GetNextToken(line, ','));
{ TODO: Calculate the student's average
Formula: (score1 + score2 + score3) / 3 }
studentAvg := (________ + ________ + ________) / 3;
{ TODO: Add student to arrays using AppendStudent procedure }
________(names, averages, token, studentAvg);
end;
{ TODO: Close the file
Use: Close(fileVar) }
________(f);
{ ================================================ }
{ PART 4: Calculate Statistics }
{ ================================================ }
{ Initialize statistics variables }
classTotal := 0;
highestAvg := 0;
lowestAvg := 999999;
highestName := '';
lowestName := '';
{ TODO: Loop through all students to calculate statistics
Use: for i := 0 to High(arrayVar) do }
for i := 0 to ________(names) do
begin
{ Add this student's average to class total }
classTotal := classTotal + averages[i];
{ TODO: Check if this is the highest average so far }
if averages[i] > ________ then
begin
highestAvg := averages[i];
{ TODO: Save the name of the highest-scoring student }
highestName := ________[i];
end;
{ TODO: Check if this is the lowest average so far }
if averages[i] < ________ then
begin
{ TODO: Save the lowest average }
lowestAvg := ________[i];
lowestName := names[i];
end;
end;
{ TODO: Calculate class average
Formula: classTotal / numberOfStudents
Use: Length(arrayVar) to get number of students }
classAvg := classTotal / ________(names);
{ ================================================ }
{ PART 5: Display Results }
{ ================================================ }
writeln('=== Student Grade Analyzer ===');
writeln('File: ', filename);
{ TODO: Display the number of students loaded }
writeln('Students loaded: ', ________(names));
writeln;
writeln('Student Averages:');
for i := 0 to High(names) do
writeln(' ', names[i]:15, ': ', averages[i]:0:2);
writeln;
writeln('Class Statistics:');
writeln(' Class Average : ', classAvg:0:2);
writeln(' Highest Score : ', highestName, ' (', highestAvg:0:2, ')');
writeln(' Lowest Score : ', lowestName, ' (', lowestAvg:0:2, ')');
{
===========================================================================
MEMORY MANAGEMENT EXPLANATION (TODO: Complete this section)
===========================================================================
TODO: Answer these questions in your own words:
1. Why don't we need to manually free the 'names' and 'averages' arrays?
YOUR ANSWER: ___________________________________________________
________________________________________________________________
2. When exactly is the memory for these arrays released?
YOUR ANSWER: ___________________________________________________
________________________________________________________________
3. How is this different from languages like Java or Python?
YOUR ANSWER: ___________________________________________________
________________________________________________________________
}
end.
Use this table to check your work. Each blank corresponds to a specific Pascal concept:
| Section | Blank Location | Concept Needed |
|---|---|---|
| GetNextToken | delimPos := ________(delimiter, ________); |
Pos function and source string |
| GetNextToken | GetNextToken := ________(source, 1, delimPos - 1); |
Copy function |
| GetNextToken | ________(source, 1, delimPos); |
Delete procedure |
| AppendStudent | ________(nameArr, Length(nameArr) + 1); |
SetLength procedure |
| AppendStudent | ________(avgArr) + 1 |
Length function |
| AppendStudent | nameArr[________(nameArr)] |
High function |
| Part 1 | if ________ < 1 |
ParamCount |
| Part 1 | ________(0) and ________(1) |
ParamStr function |
| Part 1 | if not ________(filename) |
FileExists function |
| Part 2 | ________(names, 0) |
SetLength procedure |
| Part 2 | ________(f, filename) |
Assign procedure |
| Part 2 | ________(f) |
Reset procedure |
| Part 3 | while not ________(f) |
Eof function |
| Part 3 | ________(f, line) |
ReadLn procedure |
| Part 3 | token := ________(line, ',') |
GetNextToken function |
| Part 3 | ________(GetNextToken(...)) |
StrToFloat function |
| Part 3 | (________ + ________ + ________) / 3 |
score1, score2, score3 variables |
| Part 3 | ________(names, averages, token, studentAvg) |
AppendStudent procedure |
| Part 3 | ________(f) (at end) |
Close procedure |
| Part 4 | for i := 0 to ________(names) |
High function |
| Part 4 | if averages[i] > ________ |
highestAvg variable |
| Part 4 | highestName := ________[i] |
names array |
| Part 4 | if averages[i] < ________ |
lowestAvg variable |
| Part 4 | lowestAvg := ________[i] |
averages array |
| Part 4 | classTotal / ________(names) |
Length function |
| Part 5 | 'Students loaded: ', ________(names) |
Length function |
Create a file named grades.csv with this content:
Alice,85,92,78
Bob,90,88,95
Charlie,72,68,75
Diana,95,98,92
Then test with these scenarios:
| Test | Command | Expected Result |
|---|---|---|
| No arguments | ./grade_analyzer |
Usage message displayed |
| Missing file | ./grade_analyzer nofile.csv |
Error message about file not found |
| Valid file | ./grade_analyzer grades.csv |
All statistics displayed correctly |
| Student | Average |
|---|---|
| Alice | 85.00 |
| Bob | 91.00 |
| Charlie | 71.67 |
| Diana | 95.00 |
| Class Average | 85.67 |
TStringArray, TRealArray) when passing dynamic arrays to procedures that resize themClose(f) when done readingHigh(arr) not Length(arr) as loop upper boundStrToFloat for real numbers, not StrToIntTrim() to remove spaces from tokens