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 Ada, command-line arguments are accessed using the Ada.Command_Line package.
Ada provides functions in Ada.Command_Line to access command-line arguments:
| Function | Description |
|---|---|
Argument_Count |
Returns the number of arguments passed (excluding the program name) |
Argument(n) |
Returns the nth argument as a string (1-based indexing) |
Command_Name |
Returns the name of the program |
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Command_Line; use Ada.Command_Line;
procedure Cmd_Args is
begin
Put_Line("Number of arguments: " & Integer'Image(Argument_Count));
Put_Line("Program name: " & Command_Name);
for I in 1 .. Argument_Count loop
Put_Line("Argument(" & Integer'Image(I) & ") = " & Argument(I));
end loop;
end Cmd_Args;
Compile and run with arguments:
gnatmake cmd_args.adb ./cmd_args hello world 123
Argument function uses 1-based indexing. Argument(1) is the first user-provided argument. The program name is accessed separately via Command_Name.
Here's an example that expects a filename as a command-line argument:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Command_Line; use Ada.Command_Line;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Process_Args is
Count : Integer;
begin
-- Check if correct number of arguments provided
if Argument_Count < 1 then
Put_Line("Usage: " & Command_Name & " <filename> [count]");
Set_Exit_Status(Failure);
return;
end if;
Put_Line("Processing file: " & Argument(1));
-- If a second argument is provided, treat it as a number
if Argument_Count >= 2 then
Count := Integer'Value(Argument(2));
Put("Count parameter: ");
Put(Count, Width => 0);
New_Line;
end if;
end Process_Args;
Set_Exit_Status procedure sets the program's exit code. Failure is a predefined constant (typically 1), and Success is 0. Using return in the main procedure exits the program.
Ada provides robust file handling capabilities through the Ada.Text_IO package. File operations follow a specific pattern: declare, open, read/write, close.
The basic approach uses Open, Get_Line, and Close:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Command_Line; use Ada.Command_Line;
procedure Read_File_Demo is
F : File_Type;
Line : String(1..1024);
Last : Natural;
begin
if Argument_Count < 1 then
Put_Line("Usage: " & Command_Name & " <filename>");
Set_Exit_Status(Failure);
return;
end if;
-- Open existing file for reading
Open(File => F,
Mode => In_File,
Name => Argument(1));
-- Read line by line until end of file
while not End_Of_File(F) loop
Get_Line(F, Line, Last);
Put_Line(Line(1..Last));
end loop;
-- Close the file
Close(F);
end Read_File_Demo;
| Procedure/Function | Description |
|---|---|
Open(F, Mode, Name) |
Opens an existing file with specified mode |
Create(F, Mode, Name) |
Creates a new file (overwrites if exists) |
Close(F) |
Closes the file |
Get_Line(F, Str, Last) |
Reads a line; Last indicates actual length read |
Put_Line(F, Str) |
Writes string to file followed by newline |
End_Of_File(F) |
Returns True if at end of file |
Is_Open(F) |
Returns True if file is currently open |
| Mode | Description |
|---|---|
In_File |
Open for reading only |
Out_File |
Open for writing (creates/truncates) |
Append_File |
Open for appending (writes to end) |
Ada uses exceptions for error handling, which is more robust than checking return codes:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Command_Line; use Ada.Command_Line;
procedure Safe_File_Read is
F : File_Type;
Line : String(1..1024);
Last : Natural;
Filename : String(1..256);
Fn_Last : Natural;
begin
if Argument_Count < 1 then
Put_Line("Usage: " & Command_Name & " <filename>");
Set_Exit_Status(Failure);
return;
end if;
-- Copy argument to fixed string
Fn_Last := Argument(1)'Length;
Filename(1..Fn_Last) := Argument(1);
begin
Open(File => F, Mode => In_File, Name => Filename(1..Fn_Last));
exception
when Name_Error =>
Put_Line("Error: File '" & Filename(1..Fn_Last) & "' not found.");
Set_Exit_Status(Failure);
return;
when Use_Error =>
Put_Line("Error: Cannot open file '" & Filename(1..Fn_Last) & "'.");
Set_Exit_Status(Failure);
return;
end;
Put_Line("File contents:");
while not End_Of_File(F) loop
Get_Line(F, Line, Last);
Put_Line(Line(1..Last));
end loop;
Close(F);
end Safe_File_Read;
Name_Error when a file doesn't exist and Use_Error when a file cannot be opened for other reasons (permissions, etc.). Exception handling provides clean, structured error recovery.
The Ada.Directories package provides a way to check if a file exists before opening:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Command_Line; use Ada.Command_Line;
with Ada.Directories; use Ada.Directories;
procedure Check_File_Exists is
F : File_Type;
Line : String(1..1024);
Last : Natural;
begin
if Argument_Count < 1 then
Put_Line("Usage: " & Command_Name & " <filename>");
Set_Exit_Status(Failure);
return;
end if;
-- Check if file exists before opening
if not Exists(Argument(1)) then
Put_Line("Error: File '" & Argument(1) & "' not found.");
Set_Exit_Status(Failure);
return;
end if;
Open(F, In_File, Argument(1));
while not End_Of_File(F) loop
Get_Line(F, Line, Last);
Put_Line(Line(1..Last));
end loop;
Close(F);
end Check_File_Exists;
Tokenization is the process of breaking a string into smaller pieces (tokens) based on delimiters. Ada provides several packages for string manipulation, including Ada.Strings.Fixed and Ada.Strings.Unbounded.
| Type | Description | Package |
|---|---|---|
String |
Fixed-length string (must know size at declaration) | Built-in |
Unbounded_String |
Variable-length string (can grow/shrink) | Ada.Strings.Unbounded |
Bounded_String |
Variable-length up to a maximum | Ada.Strings.Bounded |
| Function | Description | Package |
|---|---|---|
Index(Str, Pattern) |
Returns position of pattern (0 if not found) | Ada.Strings.Fixed |
Str(First..Last) |
Slice: extracts substring | Built-in |
Str'Length |
Returns the length of the string | Built-in attribute |
Trim(Str, Side) |
Removes whitespace from specified side(s) | Ada.Strings.Fixed |
To_Unbounded_String(Str) |
Converts fixed string to unbounded | Ada.Strings.Unbounded |
To_String(UStr) |
Converts unbounded string to fixed | Ada.Strings.Unbounded |
Here's how to split a string using a delimiter with unbounded strings:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings; use Ada.Strings;
procedure Tokenize_Demo is
Sentence : Unbounded_String;
Token : Unbounded_String;
Delim_Pos : Natural;
Token_Count : Natural := 0;
begin
Sentence := To_Unbounded_String("The quick brown fox jumps");
Put_Line("Original: " & To_String(Sentence));
Put_Line("Tokens:");
-- Process tokens while string is not empty
while Length(Sentence) > 0 loop
Delim_Pos := Index(Sentence, " ");
if Delim_Pos > 0 then
-- Extract token before delimiter
Token := Unbounded_Slice(Sentence, 1, Delim_Pos - 1);
-- Remove token and delimiter from string
Delete(Sentence, 1, Delim_Pos);
else
-- No more delimiters - rest of string is last token
Token := Sentence;
Sentence := Null_Unbounded_String;
end if;
-- Trim whitespace
Token := Trim(Token, Both);
if Length(Token) > 0 then
Token_Count := Token_Count + 1;
Put_Line(" Token" & Natural'Image(Token_Count) &
": " & To_String(Token));
end if;
end loop;
Put_Line("Total tokens:" & Natural'Image(Token_Count));
end Tokenize_Demo;
It's helpful to create a function that extracts the next token from a string:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings; use Ada.Strings;
procedure Tokenizer_Function is
-- Extracts and returns the next token, modifying the source string
procedure Get_Next_Token
(Source : in out Unbounded_String;
Delimiter : in Character;
Token : out Unbounded_String)
is
Delim_Pos : Natural;
Delim_Str : constant String := (1 => Delimiter);
begin
Source := Trim(Source, Both);
if Length(Source) = 0 then
Token := Null_Unbounded_String;
return;
end if;
Delim_Pos := Index(Source, Delim_Str);
if Delim_Pos > 0 then
Token := Unbounded_Slice(Source, 1, Delim_Pos - 1);
Delete(Source, 1, Delim_Pos);
else
Token := Source;
Source := Null_Unbounded_String;
end if;
Token := Trim(Token, Both);
end Get_Next_Token;
CSV_Line : Unbounded_String;
Field : Unbounded_String;
begin
CSV_Line := To_Unbounded_String("Alice,25,Computer Science,3.85");
Put_Line("Parsing CSV: " & To_String(CSV_Line));
New_Line;
Get_Next_Token(CSV_Line, ',', Field);
Put_Line("Name: " & To_String(Field));
Get_Next_Token(CSV_Line, ',', Field);
Put_Line("Age: " & To_String(Field));
Get_Next_Token(CSV_Line, ',', Field);
Put_Line("Major: " & To_String(Field));
Get_Next_Token(CSV_Line, ',', Field);
Put_Line("GPA: " & To_String(Field));
end Tokenizer_Function;
Combining file reading with tokenization:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Command_Line; use Ada.Command_Line;
with Ada.Directories; use Ada.Directories;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings; use Ada.Strings;
procedure Tokenize_File is
procedure Get_Next_Token
(Source : in out Unbounded_String;
Delimiter : in Character;
Token : out Unbounded_String)
is
Delim_Pos : Natural;
Delim_Str : constant String := (1 => Delimiter);
begin
Source := Trim(Source, Both);
if Length(Source) = 0 then
Token := Null_Unbounded_String;
return;
end if;
Delim_Pos := Index(Source, Delim_Str);
if Delim_Pos > 0 then
Token := Unbounded_Slice(Source, 1, Delim_Pos - 1);
Delete(Source, 1, Delim_Pos);
else
Token := Source;
Source := Null_Unbounded_String;
end if;
Token := Trim(Token, Both);
end Get_Next_Token;
F : File_Type;
Line_Buffer : String(1..1024);
Last : Natural;
Line : Unbounded_String;
Token : Unbounded_String;
Token_Count : Natural := 0;
begin
if Argument_Count < 1 then
Put_Line("Usage: " & Command_Name & " <filename>");
Set_Exit_Status(Failure);
return;
end if;
if not Exists(Argument(1)) then
Put_Line("Error: File not found.");
Set_Exit_Status(Failure);
return;
end if;
Open(F, In_File, Argument(1));
while not End_Of_File(F) loop
Get_Line(F, Line_Buffer, Last);
Line := To_Unbounded_String(Line_Buffer(1..Last));
-- Tokenize each line on spaces
while Length(Line) > 0 loop
Get_Next_Token(Line, ' ', Token);
if Length(Token) > 0 then
Token_Count := Token_Count + 1;
end if;
end loop;
end loop;
Close(F);
Put_Line("Total tokens in file:" & Natural'Image(Token_Count));
end Tokenize_File;
Trim function helps clean up the string, and checking for empty tokens prevents counting blank entries.
Ada supports both constrained arrays (fixed size, determined at compile time or runtime) and unconstrained array types (flexible bounds). Ada also provides powerful array attributes for safe iteration.
Constrained arrays have bounds that are fixed when declared:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Static_Array_Demo is
-- Array with indices 1 to 5
Numbers : array (1 .. 5) of Integer;
-- Array with indices 0 to 4
Grades : array (0 .. 4) of Float;
-- Named array type with initialization
type Day_Names is array (1 .. 7) of String(1..3);
Days : constant Day_Names := ("Mon", "Tue", "Wed", "Thu",
"Fri", "Sat", "Sun");
begin
-- Assign values
for I in Numbers'Range loop
Numbers(I) := I * 10;
end loop;
-- Access values
Put_Line("Numbers:");
for I in Numbers'Range loop
Put(" Numbers(");
Put(I, Width => 0);
Put(") = ");
Put(Numbers(I), Width => 0);
New_Line;
end loop;
New_Line;
Put_Line("Days of the week:");
for I in Days'Range loop
Put_Line(" " & Days(I));
end loop;
end Static_Array_Demo;
'Range attribute provides the valid index range for safe iteration, and 'First and 'Last give the bounds.
Unconstrained array types allow you to define arrays whose bounds are specified when objects are created:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Unconstrained_Array_Demo is
-- Define unconstrained array type (bounds not yet specified)
type Int_Array is array (Positive range <>) of Integer;
-- Create array with bounds at declaration
Numbers : Int_Array(1..5);
-- Function that returns array of any size
function Create_Sequence(Size : Positive) return Int_Array is
Result : Int_Array(1..Size);
begin
for I in Result'Range loop
Result(I) := I * 10;
end loop;
return Result;
end Create_Sequence;
Seq : constant Int_Array := Create_Sequence(8);
begin
-- Initialize Numbers
for I in Numbers'Range loop
Numbers(I) := I * 10;
end loop;
Put_Line("Numbers array (length" & Integer'Image(Numbers'Length) & "):");
for I in Numbers'Range loop
Put(" Numbers(");
Put(I, Width => 0);
Put(") = ");
Put(Numbers(I), Width => 0);
New_Line;
end loop;
New_Line;
Put_Line("Sequence array (length" & Integer'Image(Seq'Length) & "):");
for I in Seq'Range loop
Put(" Seq(");
Put(I, Width => 0);
Put(") = ");
Put(Seq(I), Width => 0);
New_Line;
end loop;
end Unconstrained_Array_Demo;
| Attribute | Description |
|---|---|
Arr'Length |
Returns the number of elements in the array |
Arr'First |
Returns the first (lowest) valid index |
Arr'Last |
Returns the last (highest) valid index |
Arr'Range |
Returns the range Arr'First..Arr'Last |
'Range, 'First, and 'Last instead of hardcoded bounds. This prevents off-by-one errors and makes code work correctly regardless of array bounds.
For truly dynamic arrays that can grow at runtime, Ada provides the Ada.Containers.Vectors package:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Containers.Vectors;
with Ada.Containers; use Ada.Containers;
procedure Vector_Demo is
-- Instantiate a vector package for integers
package Int_Vectors is new Ada.Containers.Vectors
(Index_Type => Natural,
Element_Type => Integer);
use Int_Vectors;
Numbers : Vector;
begin
-- Start with empty vector
Put_Line("Initial length:" &
Count_Type'Image(Length(Numbers)));
-- Append elements (vector grows automatically)
Append(Numbers, 10);
Append(Numbers, 20);
Append(Numbers, 30);
Append(Numbers, 40);
Append(Numbers, 50);
Put_Line("After appending (length" &
Count_Type'Image(Length(Numbers)) & "):");
for I in Numbers.First_Index .. Numbers.Last_Index loop
Put(" Numbers(");
Put(I, Width => 0);
Put(") = ");
Put(Element(Numbers, I), Width => 0);
New_Line;
end loop;
-- Add more elements
Append(Numbers, 60);
Append(Numbers, 70);
Append(Numbers, 80);
New_Line;
Put_Line("After growing (length" &
Count_Type'Image(Length(Numbers)) & "):");
for E of Numbers loop
Put(" ");
Put(E, Width => 0);
New_Line;
end loop;
end Vector_Demo;
Ada.Containers.Vectors is similar to C++'s std::vector or Java's ArrayList. It automatically manages memory and resizing. The for E of Numbers syntax is Ada 2012's "for of" loop for container iteration.
A complete example using vectors for both strings and numbers:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Containers.Vectors;
with Ada.Containers; use Ada.Containers;
procedure String_Vector_Demo is
-- Vector of unbounded strings
package String_Vectors is new Ada.Containers.Vectors
(Index_Type => Natural,
Element_Type => Unbounded_String);
use String_Vectors;
Words : Vector;
begin
-- Append string elements
Append(Words, To_Unbounded_String("Hello"));
Append(Words, To_Unbounded_String("Dynamic"));
Append(Words, To_Unbounded_String("World"));
Put_Line("Words:");
for I in Words.First_Index .. Words.Last_Index loop
Put_Line(" [" & Natural'Image(I) & "]: " &
To_String(Element(Words, I)));
end loop;
-- Alternative: use "for of" loop
New_Line;
Put_Line("Using for-of loop:");
for W of Words loop
Put_Line(" " & To_String(W));
end loop;
end String_Vector_Demo;
A complete example that reads a file, tokenizes it, and stores tokens in a vector:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Command_Line; use Ada.Command_Line;
with Ada.Directories; use Ada.Directories;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings; use Ada.Strings;
with Ada.Containers.Vectors;
with Ada.Containers; use Ada.Containers;
procedure Tokens_To_Vector is
package String_Vectors is new Ada.Containers.Vectors
(Index_Type => Natural,
Element_Type => Unbounded_String);
use String_Vectors;
procedure Get_Next_Token
(Source : in out Unbounded_String;
Delimiter : in Character;
Token : out Unbounded_String)
is
Delim_Pos : Natural;
Delim_Str : constant String := (1 => Delimiter);
begin
Source := Trim(Source, Both);
if Length(Source) = 0 then
Token := Null_Unbounded_String;
return;
end if;
Delim_Pos := Index(Source, Delim_Str);
if Delim_Pos > 0 then
Token := Unbounded_Slice(Source, 1, Delim_Pos - 1);
Delete(Source, 1, Delim_Pos);
else
Token := Source;
Source := Null_Unbounded_String;
end if;
Token := Trim(Token, Both);
end Get_Next_Token;
Tokens : Vector;
F : File_Type;
Line_Buffer : String(1..1024);
Last : Natural;
Line : Unbounded_String;
Token : Unbounded_String;
begin
if Argument_Count < 1 then
Put_Line("Usage: " & Command_Name & " <filename>");
Set_Exit_Status(Failure);
return;
end if;
if not Exists(Argument(1)) then
Put_Line("Error: File not found.");
Set_Exit_Status(Failure);
return;
end if;
Open(F, In_File, Argument(1));
while not End_Of_File(F) loop
Get_Line(F, Line_Buffer, Last);
Line := To_Unbounded_String(Line_Buffer(1..Last));
Line := Trim(Line, Both);
while Length(Line) > 0 loop
Get_Next_Token(Line, ' ', Token);
if Length(Token) > 0 then
Append(Tokens, Token);
end if;
end loop;
end loop;
Close(F);
-- Display results
Put_Line("=== File Token Analysis ===");
Put_Line("Total tokens:" &
Count_Type'Image(Length(Tokens)));
if not Is_Empty(Tokens) then
Put_Line("First token: " &
To_String(First_Element(Tokens)));
Put_Line("Last token: " &
To_String(Last_Element(Tokens)));
end if;
New_Line;
Put_Line("Tokens longer than 5 characters:");
for I in Tokens.First_Index .. Tokens.Last_Index loop
if Ada.Strings.Unbounded.Length(Element(Tokens, I)) > 5 then
Put_Line(" [" & Natural'Image(I) & "]: " &
To_String(Element(Tokens, I)) &
" (length:" &
Natural'Image(Ada.Strings.Unbounded.Length(
Element(Tokens, I))) & ")");
end if;
end loop;
end Tokens_To_Vector;
Ada provides strong memory safety guarantees. Unlike garbage-collected languages, Ada gives you control over memory management while providing compile-time checks to prevent many common errors.
Ada uses different strategies depending on the type of data:
| Data Type | Memory Management | Notes |
|---|---|---|
| Local variables | Automatic | Allocated on stack, freed when scope ends |
| Unbounded_String | Automatic (managed) | Implementation handles memory |
| Containers (Vectors, etc.) | Automatic (managed) | Freed when container goes out of scope |
| Access types (new) | Manual or Controlled | Must use Unchecked_Deallocation or Controlled types |
| Controlled types | Automatic (destructor) | Finalize called automatically |
Ada's standard containers handle memory automatically:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Containers.Vectors;
with Ada.Containers; use Ada.Containers;
procedure Auto_Memory_Demo is
package String_Vectors is new Ada.Containers.Vectors
(Index_Type => Natural,
Element_Type => Unbounded_String);
use String_Vectors;
procedure Create_Data is
Data : Vector;
begin
-- Memory is allocated automatically
for I in 1 .. 1000 loop
Append(Data, To_Unbounded_String("Item" & Integer'Image(I)));
end loop;
Put_Line("Created" &
Count_Type'Image(Length(Data)) &
" items inside procedure");
-- When this procedure returns, Data goes out of scope
-- Memory is automatically freed (destructor called)
end Create_Data;
begin
Put_Line("=== Automatic Memory Management Demo ===");
for I in 1 .. 5 loop
New_Line;
Put_Line("Iteration" & Integer'Image(I));
Create_Data;
-- Memory from previous call has been freed
end loop;
New_Line;
Put_Line("Program completed - all memory cleaned up automatically");
end Auto_Memory_Demo;
Finalize procedure is called automatically when they go out of scope. This provides deterministic memory management—memory is freed immediately when the scope ends, not at some later time chosen by a garbage collector.
When using access types (pointers) with new, you can manually deallocate memory:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
with Ada.Unchecked_Deallocation;
procedure Manual_Memory_Demo is
type Student is record
Name : String(1..50);
Name_Length : Natural;
Age : Integer;
GPA : Float;
end record;
type Student_Access is access Student;
-- Instantiate deallocation procedure
procedure Free is new Ada.Unchecked_Deallocation
(Object => Student,
Name => Student_Access);
S : Student_Access;
begin
Put_Line("=== Manual Memory Management Demo ===");
-- Allocate memory for the record
S := new Student;
Put_Line("Memory allocated with 'new'");
-- Use the allocated memory
S.Name(1..5) := "Alice";
S.Name_Length := 5;
S.Age := 20;
S.GPA := 3.85;
Put_Line("Student: " & S.Name(1..S.Name_Length));
Put_Line("Age:" & Integer'Image(S.Age));
Put("GPA: ");
Put(S.GPA, Fore => 1, Aft => 2, Exp => 0);
New_Line;
-- IMPORTANT: Free the memory when done
Free(S);
Put_Line("Memory freed with Free()");
-- S is now null (Free sets it to null)
if S = null then
Put_Line("Pointer is now null (safe)");
end if;
end Manual_Memory_Demo;
new to allocate memory, you must instantiate Ada.Unchecked_Deallocation to free it. The word "Unchecked" reminds you that you're taking responsibility for correct deallocation. Forgetting to free memory causes memory leaks.
| Feature | Ada | Garbage-Collected (Java, Python) |
|---|---|---|
| Containers/Collections | Automatic (Controlled types) | Automatic (GC) |
| Strings (Unbounded) | Automatic (managed) | Automatic (GC) |
| Pointer/access allocation | Manual (Unchecked_Deallocation) | Automatic (GC) |
| When memory is freed | Immediately (deterministic) | When GC runs (unpredictable) |
| Memory leaks possible | Yes (if Free forgotten) | Rare (GC prevents most) |
| Dangling pointer protection | Free sets to null | GC prevents |
| Performance overhead | Minimal | GC pauses possible |
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Containers.Vectors;
with Ada.Containers; use Ada.Containers;
with Ada.Unchecked_Deallocation;
procedure Memory_Best_Practices is
-- For dynamic data, prefer containers over raw access types
package Int_Vectors is new Ada.Containers.Vectors
(Index_Type => Natural,
Element_Type => Integer);
use Int_Vectors;
-- If you must use access types, encapsulate allocation/deallocation
type Data_Record is record
Values : Vector;
Name : Unbounded_String;
end record;
type Data_Access is access Data_Record;
procedure Free is new Ada.Unchecked_Deallocation
(Object => Data_Record,
Name => Data_Access);
Data : Data_Access := null; -- Initialize to null
begin
Put_Line("=== Memory Best Practices ===");
begin
-- Allocate memory
Data := new Data_Record;
-- Use the memory
Data.Name := To_Unbounded_String("Test Data");
Append(Data.Values, 100);
Append(Data.Values, 200);
Put_Line("Data name: " & To_String(Data.Name));
Put_Line("First value:" & Integer'Image(First_Element(Data.Values)));
exception
when others =>
-- Clean up on error
if Data /= null then
Free(Data);
end if;
raise; -- Re-raise the exception
end;
-- Normal cleanup
if Data /= null then
Free(Data);
-- Data is now null
end if;
Put_Line("Memory properly cleaned up");
end Memory_Best_Practices;
Vectors, Maps, etc.) over manual memory management with access types. Containers handle memory automatically and are safer. Only use new and Unchecked_Deallocation when you have a specific reason that containers can't satisfy.
This example shows how containers handle memory automatically when processing files:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Command_Line; use Ada.Command_Line;
with Ada.Directories; use Ada.Directories;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings; use Ada.Strings;
with Ada.Containers.Vectors;
with Ada.Containers; use Ada.Containers;
procedure Process_Large_Data is
package String_Vectors is new Ada.Containers.Vectors
(Index_Type => Natural,
Element_Type => Unbounded_String);
use String_Vectors;
procedure Get_Next_Token
(Source : in Out Unbounded_String;
Delimiter : in Character;
Token : out Unbounded_String)
is
Delim_Pos : Natural;
Delim_Str : constant String := (1 => Delimiter);
begin
Source := Trim(Source, Both);
if Length(Source) = 0 then
Token := Null_Unbounded_String;
return;
end if;
Delim_Pos := Index(Source, Delim_Str);
if Delim_Pos > 0 then
Token := Unbounded_Slice(Source, 1, Delim_Pos - 1);
Delete(Source, 1, Delim_Pos);
else
Token := Source;
Source := Null_Unbounded_String;
end if;
Token := Trim(Token, Both);
end Get_Next_Token;
procedure Analyze_File(Filename : String) is
F : File_Type;
Line_Buffer : String(1..4096);
Last : Natural;
Line : Unbounded_String;
Token : Unbounded_String;
Tokens : Vector;
Long_Words : Vector;
begin
Put_Line("Analyzing: " & Filename);
-- Vectors allocated automatically
Open(F, In_File, Filename);
while not End_Of_File(F) loop
Get_Line(F, Line_Buffer, Last);
Line := To_Unbounded_String(Line_Buffer(1..Last));
Line := Trim(Line, Both);
while Length(Line) > 0 loop
Get_Next_Token(Line, ' ', Token);
if Length(Token) > 0 then
Append(Tokens, Token);
if Length(Token) > 6 then
Append(Long_Words, Token);
end if;
end if;
end loop;
end loop;
Close(F);
Put_Line(" Tokens found:" & Count_Type'Image(Length(Tokens)));
Put_Line(" Long words:" & Count_Type'Image(Length(Long_Words)));
-- When this procedure returns:
-- - Tokens vector is finalized -> memory freed
-- - Long_Words vector is finalized -> memory freed
-- - All Unbounded_Strings in vectors are freed
-- No manual cleanup needed!
end Analyze_File;
begin
if Argument_Count < 1 then
Put_Line("Usage: " & Command_Name & " <file1> [file2] [file3] ...");
Set_Exit_Status(Failure);
return;
end if;
Put_Line("=== Multi-File Analyzer ===");
New_Line;
for I in 1 .. Argument_Count loop
if Exists(Argument(I)) then
Analyze_File(Argument(I));
New_Line;
else
Put_Line("Skipping (not found): " & Argument(I));
end if;
end loop;
Put_Line("Done! All temporary memory has been reclaimed.");
end Process_Large_Data;
new.
This comprehensive example combines all the concepts: command-line arguments, file reading, tokenization, containers, and demonstrates Ada's memory management:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Command_Line; use Ada.Command_Line;
with Ada.Directories; use Ada.Directories;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings; use Ada.Strings;
with Ada.Characters.Handling; use Ada.Characters.Handling;
with Ada.Containers.Vectors;
with Ada.Containers; use Ada.Containers;
procedure Word_Frequency_Analyzer is
package String_Vectors is new Ada.Containers.Vectors
(Index_Type => Natural,
Element_Type => Unbounded_String);
use String_Vectors;
package Int_Vectors is new Ada.Containers.Vectors
(Index_Type => Natural,
Element_Type => Integer);
use Int_Vectors;
-- Get next token from string
procedure Get_Next_Token
(Source : in out Unbounded_String;
Delimiter : in Character;
Token : out Unbounded_String)
is
Delim_Pos : Natural;
Delim_Str : constant String := (1 => Delimiter);
begin
Source := Trim(Source, Both);
if Length(Source) = 0 then
Token := Null_Unbounded_String;
return;
end if;
Delim_Pos := Index(Source, Delim_Str);
if Delim_Pos > 0 then
Token := Unbounded_Slice(Source, 1, Delim_Pos - 1);
Delete(Source, 1, Delim_Pos);
else
Token := Source;
Source := Null_Unbounded_String;
end if;
Token := Trim(Token, Both);
end Get_Next_Token;
-- Check if character is punctuation
function Is_Punctuation(C : Character) return Boolean is
begin
return C = '.' or else C = ',' or else C = '!' or else
C = '?' or else C = ';' or else C = ':' or else
C = '"' or else C = ''' or else C = '(' or else
C = ')' or else C = '-';
end Is_Punctuation;
-- Remove punctuation from beginning and end of a word
function Strip_Punctuation(S : Unbounded_String) return Unbounded_String is
Result : Unbounded_String := S;
begin
while Length(Result) > 0
and then Is_Punctuation(Element(Result, 1))
loop
Delete(Result, 1, 1);
end loop;
while Length(Result) > 0
and then Is_Punctuation(Element(Result, Length(Result)))
loop
Delete(Result, Length(Result), Length(Result));
end loop;
return Result;
end Strip_Punctuation;
-- Variables
F : File_Type;
Line_Buffer : String(1..4096);
Last : Natural;
Line : Unbounded_String;
Token : Unbounded_String;
Word : Unbounded_String;
Filename : Unbounded_String;
-- Dynamic containers for storing data
Tokens : String_Vectors.Vector;
Unique_Words : String_Vectors.Vector;
Word_Counts : Int_Vectors.Vector;
Found : Boolean;
Max_Count : Integer := 0;
Most_Common : Unbounded_String;
begin
-- Check command-line arguments
if Argument_Count < 1 then
Put_Line("Word Frequency Analyzer");
Put_Line("Usage: " & Command_Name & " <filename>");
Set_Exit_Status(Failure);
return;
end if;
Filename := To_Unbounded_String(Argument(1));
-- Check if file exists
if not Exists(To_String(Filename)) then
Put_Line("Error: File '" & To_String(Filename) & "' not found.");
Set_Exit_Status(Failure);
return;
end if;
-- Read and tokenize the file
Open(F, In_File, To_String(Filename));
while not End_Of_File(F) loop
Get_Line(F, Line_Buffer, Last);
Line := To_Unbounded_String(To_Lower(Line_Buffer(1..Last)));
Line := Trim(Line, Both);
while Length(Line) > 0 loop
Get_Next_Token(Line, ' ', Token);
if Length(Token) > 0 then
String_Vectors.Append(Tokens, Token);
end if;
end loop;
end loop;
Close(F);
-- Count word frequencies
for I in Tokens.First_Index .. Tokens.Last_Index loop
Word := Strip_Punctuation(String_Vectors.Element(Tokens, I));
if Length(Word) > 0 then
-- Check if word already exists
Found := False;
for J in Unique_Words.First_Index .. Unique_Words.Last_Index loop
if String_Vectors.Element(Unique_Words, J) = Word then
Int_Vectors.Replace_Element
(Word_Counts, J,
Int_Vectors.Element(Word_Counts, J) + 1);
Found := True;
exit;
end if;
end loop;
-- Add new word if not found
if not Found then
String_Vectors.Append(Unique_Words, Word);
Int_Vectors.Append(Word_Counts, 1);
end if;
end if;
end loop;
-- Display results
Put_Line("=== Word Frequency Analysis ===");
Put_Line("File: " & To_String(Filename));
Put_Line("Total tokens:" &
Count_Type'Image(String_Vectors.Length(Tokens)));
Put_Line("Unique words:" &
Count_Type'Image(String_Vectors.Length(Unique_Words)));
New_Line;
-- Display words with count > 1
Put_Line("Words appearing more than once:");
Put_Line("--------------------------------");
for I in Unique_Words.First_Index .. Unique_Words.Last_Index loop
if Int_Vectors.Element(Word_Counts, I) > 1 then
declare
W : constant String :=
To_String(String_Vectors.Element(Unique_Words, I));
C : constant Integer := Int_Vectors.Element(Word_Counts, I);
Pad_Len : constant Natural :=
(if W'Length < 15 then 15 - W'Length else 1);
Padding : constant String(1..Pad_Len) := (others => ' ');
begin
Put_Line(" " & W & Padding & ":" & Integer'Image(C));
end;
end if;
end loop;
-- Find most common word
for I in Unique_Words.First_Index .. Unique_Words.Last_Index loop
if Int_Vectors.Element(Word_Counts, I) > Max_Count then
Max_Count := Int_Vectors.Element(Word_Counts, I);
Most_Common := String_Vectors.Element(Unique_Words, I);
end if;
end loop;
New_Line;
Put_Line("Most common word: '" & To_String(Most_Common) &
"' (" & Integer'Image(Max_Count) & " occurrences)");
-- Memory note: All containers (Tokens, Unique_Words, Word_Counts)
-- will be automatically freed when the program ends because their
-- Finalize procedures are called. No manual cleanup needed!
end Word_Frequency_Analyzer;
| Task | Ada Syntax |
|---|---|
| Get argument count | Argument_Count |
| Get argument n | Argument(n) |
| Get program name | Command_Name |
| Set exit status | Set_Exit_Status(Failure); |
| Declare file variable | F : File_Type; |
| Open file for reading | Open(F, In_File, Filename); |
| Read line from file | Get_Line(F, Buffer, Last); |
| Check end of file | End_Of_File(F) |
| Close file | Close(F); |
| Check file exists | Ada.Directories.Exists(Name) |
| Find substring position | Index(Str, Pattern) |
| Extract substring | Str(First..Last) or Unbounded_Slice |
| Delete from unbounded string | Delete(Str, From, Through); |
| Instantiate vector package | package My_Vec is new Ada.Containers.Vectors(...); |
| Declare vector variable | V : My_Vec.Vector; |
| Append to vector | Append(V, Element); |
| Get vector length | Length(V) |
| Get vector element | Element(V, Index) |
| Array bounds | Arr'First, Arr'Last, Arr'Range |
| Allocate memory | Ptr := new Type; |
| Deallocate memory | Free(Ptr); (after instantiating Unchecked_Deallocation) |
Write an Ada program that processes a CSV (Comma-Separated Values) file containing student grade data. Your program should:
StudentName,Score1,Score2,Score3Example:
Alice,85,92,78
Alice,85,92,78 Bob,90,88,95 Charlie,72,68,75 Diana,95,98,92
Get_Next_Token procedure to parse each CSV line by commaFloat'Value to convert score strings to numbersAppend to store data as you read it(score1 + score2 + score3) / 3.0Save your program as grade_analyzer.adb and compile with:
gnatmake grade_analyzer.adb ./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.adb
-----------------------------------------------------------------------
-- 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 vectors
-- 4. Display individual averages and class statistics
--
-- Author: [Your Name]
-- Date: [Date]
-----------------------------------------------------------------------
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Float_Text_IO; use Ada.Float_Text_IO;
with Ada.Command_Line; use Ada.Command_Line;
with Ada.Directories; use Ada.Directories;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Strings; use Ada.Strings;
with Ada.Containers.Vectors;
with Ada.Containers; use Ada.Containers;
procedure Grade_Analyzer is
-----------------------------------------------------------------------
-- PART 1: Vector Package Instantiations
-----------------------------------------------------------------------
-- IMPORTANT: We need separate vector packages for different types
-- TODO: Complete the vector instantiation for Unbounded_String
-- HINT: Index_Type should be Natural, Element_Type is the data type
package String_Vectors is new Ada.Containers.Vectors
(Index_Type => ______,
Element_Type => ______);
use String_Vectors;
-- TODO: Complete the vector instantiation for Float
package Float_Vectors is new Ada.Containers.Vectors
(Index_Type => ______,
Element_Type => ______);
use Float_Vectors;
-----------------------------------------------------------------------
-- PART 2: Get_Next_Token Procedure
-----------------------------------------------------------------------
-- 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 procedure)
-- Delimiter - the character to split on
-- Token - the extracted token (output parameter)
--
-- HINT: Use Index() to find the delimiter, Unbounded_Slice() to extract,
-- and Delete() to remove the token from source.
procedure Get_Next_Token
(Source : in Out Unbounded_String;
Delimiter : in Character;
Token : out Unbounded_String)
is
Delim_Pos : Natural;
Delim_Str : constant String := (1 => Delimiter);
begin
Source := Trim(Source, Both);
if Length(Source) = 0 then
Token := Null_Unbounded_String;
return;
end if;
-- TODO: Find the position of the delimiter in Source
-- HINT: Use Index(stringToSearch, patternToFind)
Delim_Pos := ______(Source, Delim_Str);
if Delim_Pos > 0 then
-- TODO: Extract token (everything before the delimiter)
-- HINT: Use Unbounded_Slice(source, startPos, endPos)
Token := ______(Source, 1, Delim_Pos - 1);
-- TODO: Remove the token and delimiter from source
-- HINT: Use Delete(stringVar, fromPos, throughPos)
______(Source, 1, Delim_Pos);
else
-- No delimiter found - return entire string
Token := Source;
Source := Null_Unbounded_String;
end if;
Token := Trim(Token, Both);
end Get_Next_Token;
-----------------------------------------------------------------------
-- Variable Declarations
-----------------------------------------------------------------------
-- File handling
F : File_Type;
Line_Buffer : String(1..1024);
Last : Natural;
Line : Unbounded_String;
Filename : Unbounded_String;
-- Vectors for storing student data
Names : String_Vectors.Vector;
Averages : Float_Vectors.Vector;
-- Variables for parsing and calculations
Token : Unbounded_String;
Score1, Score2, Score3 : Float;
Student_Avg, Class_Total : Float := 0.0;
Class_Avg : Float;
Highest_Avg, Lowest_Avg : Float;
Highest_Name : Unbounded_String;
Lowest_Name : Unbounded_String;
-----------------------------------------------------------------------
-- MAIN PROGRAM
-----------------------------------------------------------------------
begin
-----------------------------------------------------------------------
-- PART 3: Command-Line Argument Handling
-----------------------------------------------------------------------
-- TODO: Check if a filename was provided
-- HINT: Use Argument_Count to get number of arguments
if ______ < 1 then
Put_Line("=== Student Grade Analyzer ===");
-- TODO: Display the program name using Command_Name
Put_Line("Usage: " & ______ & " <filename>");
Set_Exit_Status(Failure);
return;
end if;
-- TODO: Get the filename from command-line argument #1
-- HINT: Use Argument(n) and To_Unbounded_String()
Filename := To_Unbounded_String(______(1));
-- TODO: Check if file exists
-- HINT: Use Exists() function from Ada.Directories
if not ______(To_String(Filename)) then
Put_Line("Error: File '" & To_String(Filename) & "' not found.");
Set_Exit_Status(Failure);
return;
end if;
-----------------------------------------------------------------------
-- PART 4: Open File and Read Data
-----------------------------------------------------------------------
-- TODO: Open the file for reading
-- HINT: Use Open(fileVar, mode, filename)
-- Mode for reading is In_File
______(F, ______, To_String(Filename));
-- TODO: Loop while not at end of file
-- HINT: Use "while not End_Of_File(fileVar) loop"
while not ______(F) loop
-- TODO: Read a line from the file
-- HINT: Use Get_Line(fileVar, buffer, lastPos)
______(F, Line_Buffer, Last);
Line := To_Unbounded_String(Line_Buffer(1..Last));
-- Skip empty lines
if Length(Trim(Line, Both)) = 0 then
goto Continue;
end if;
----------------------------------------------------------------
-- PART 5: Parse the CSV Line
----------------------------------------------------------------
-- TODO: Extract student name (first token before comma)
-- HINT: Call Get_Next_Token with Line, ',', and Token
______(Line, ',', Token);
-- Store the name (Token now contains the student name)
declare
Student_Name : constant Unbounded_String := Token;
begin
-- TODO: Extract Score1 - get next token and convert to Float
-- HINT: Use Float'Value(To_String(token)) to convert
Get_Next_Token(Line, ',', Token);
Score1 := ______'Value(To_String(Token));
-- TODO: Extract Score2
Get_Next_Token(Line, ',', Token);
Score2 := ______'______(To_String(Token));
-- TODO: Extract Score3
Get_Next_Token(Line, ',', Token);
Score3 := ______'______(______);
-- TODO: Calculate the student's average
-- Formula: (score1 + score2 + score3) / 3.0
Student_Avg := (______ + ______ + ______) / 3.0;
-- TODO: Append student name to Names vector
-- HINT: Use Append(vectorVar, element) from String_Vectors
String_Vectors.______(Names, Student_Name);
-- TODO: Append student average to Averages vector
Float_Vectors.______(Averages, Student_Avg);
end;
<<Continue>>
null; -- Required: a label must be followed by a statement
end loop;
-- TODO: Close the file
-- HINT: Use Close(fileVar)
______(F);
-----------------------------------------------------------------------
-- PART 6: Calculate Statistics
-----------------------------------------------------------------------
-- Initialize statistics variables
Class_Total := 0.0;
Highest_Avg := 0.0;
Lowest_Avg := 999999.0;
-- TODO: Loop through all students to calculate statistics
-- HINT: Use "for I in Names.First_Index .. Names.Last_Index loop"
for I in Names.______ .. Names.______ loop
-- Add this student's average to class total
Class_Total := Class_Total + Float_Vectors.Element(Averages, I);
-- TODO: Check if this is the highest average so far
-- HINT: Compare Float_Vectors.Element(Averages, I) with Highest_Avg
if Float_Vectors.Element(Averages, I) > ______ then
Highest_Avg := Float_Vectors.Element(Averages, I);
-- TODO: Save the name of the highest-scoring student
Highest_Name := String_Vectors.______(Names, I);
end if;
-- TODO: Check if this is the lowest average so far
if Float_Vectors.Element(Averages, I) < ______ then
Lowest_Avg := Float_Vectors.Element(Averages, I);
-- TODO: Save the name of the lowest-scoring student
Lowest_Name := String_Vectors.______(Names, I);
end if;
end loop;
-- TODO: Calculate class average
-- Formula: classTotal / numberOfStudents
-- HINT: Use Float(Length(Names)) to convert count to Float
Class_Avg := Class_Total / Float(String_Vectors.______(Names));
-----------------------------------------------------------------------
-- PART 7: Display Results
-----------------------------------------------------------------------
Put_Line("=== Student Grade Analyzer ===");
Put_Line("File: " & To_String(Filename));
-- TODO: Display the number of students loaded
-- HINT: Use Count_Type'Image(Length(vectorVar))
Put_Line("Students loaded:" &
Count_Type'Image(String_Vectors.______(Names)));
New_Line;
Put_Line("Student Averages:");
for I in Names.First_Index .. Names.Last_Index loop
declare
Name : constant String :=
To_String(String_Vectors.Element(Names, I));
Avg : constant Float := Float_Vectors.Element(Averages, I);
Pad_Len : constant Natural :=
(if Name'Length < 15 then 15 - Name'Length else 1);
Padding : constant String(1..Pad_Len) := (others => ' ');
begin
Put(" " & Name & Padding & ": ");
Put(Avg, Fore => 1, Aft => 2, Exp => 0);
New_Line;
end;
end loop;
New_Line;
Put_Line("Class Statistics:");
Put(" Class Average : ");
Put(Class_Avg, Fore => 1, Aft => 2, Exp => 0);
New_Line;
Put(" Highest Score : " & To_String(Highest_Name) & " (");
Put(Highest_Avg, Fore => 1, Aft => 2, Exp => 0);
Put_Line(")");
Put(" Lowest Score : " & To_String(Lowest_Name) & " (");
Put(Lowest_Avg, Fore => 1, Aft => 2, Exp => 0);
Put_Line(")");
-----------------------------------------------------------------------
-- MEMORY MANAGEMENT EXPLANATION
-----------------------------------------------------------------------
-- TODO: Complete this section by answering the questions
--
-- 1. Why don't we need to manually free the Names and Averages vectors?
-- YOUR ANSWER: ________________________________________________
--
-- 2. When exactly is the memory for these vectors released?
-- YOUR ANSWER: ________________________________________________
--
-- 3. How is this different from languages like Java or Python?
-- YOUR ANSWER: ________________________________________________
--
-----------------------------------------------------------------------
end Grade_Analyzer;
Use this table to check your work. Each blank corresponds to a specific Ada concept:
| Section | Blank Location | Concept Needed |
|---|---|---|
| Part 1 | Index_Type => ______ |
Natural (standard index type) |
| Part 1 | Element_Type => ______ (strings) |
Unbounded_String |
| Part 1 | Element_Type => ______ (floats) |
Float |
| Part 2 | Delim_Pos := ______ |
Index function |
| Part 2 | Token := ______ |
Unbounded_Slice function |
| Part 2 | ______(Source, 1, Delim_Pos) |
Delete procedure |
| Part 3 | if ______ < 1 |
Argument_Count |
| Part 3 | "Usage: " & ______ |
Command_Name |
| Part 3 | ______(1) |
Argument function |
| Part 3 | if not ______ |
Exists function |
| Part 4 | ______(F, ______, ...) |
Open, In_File |
| Part 4 | while not ______(F) |
End_Of_File |
| Part 4 | ______(F, Line_Buffer, Last) |
Get_Line |
| Part 5 | ______(Line, ',', Token) |
Get_Next_Token |
| Part 5 | ______'Value |
Float attribute |
| Part 5 | Average calculation | Score1, Score2, Score3 |
| Part 5 | String_Vectors.______ |
Append procedure |
| Part 5 | ______(F) |
Close procedure |
| Part 6 | Names.______ |
First_Index, Last_Index |
| Part 6 | > ______ |
Highest_Avg variable |
| Part 6 | String_Vectors.______(Names, I) |
Element function |
| Part 6 | < ______ |
Lowest_Avg variable |
| Part 6 | String_Vectors.______(Names) |
Length function |
| Part 7 | String_Vectors.______(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 |
Length), you may need to qualify: String_Vectors.Length(Names)Close(F) when done readingFloat'Value(str) to convert strings to Float, not IntegerTrim(str, Both) to remove whitespaceTo_String(unbounded) when needed3.0 not 3 to ensure Float division<<Continue>> must be followed by a statement; use null; if needed