Writing a Simple Line Editor
We're finally ready for the next Outside Assignment! This assignment will give you a chance to write a program of greater complexity than the previous assignments. By the time you've completed Outside Assignment 5, you should feel comfortable with Ada. The full set of requirements for the line editor we want you to write are in your printed course notes, starting on page 20. We'll discuss them briefly here. No test driver is supplied, but after you've written the program, we'll give you some tests to perform manually on your line editor. You've completed the assignment when your editor passes all the tests.
A line editor edits only one line of a text file at a time, and is much simpler than a screen editor that lets you move a cursor wherever you want. The line editor you'll write, called Ledit, will take almost no effort to learn how to use. The only commands are LIST and EXIT!
The user begins each line of text with a line number, similar to early versions of Basic. Line numbers must be integers between 1 and 29999. Regardless of the order in which lines are entered, Ledit maintains a linked list of lines in order by number, so that it can List the text in order, or write it to a file.
Line numbers need not be consecutive, and they may be preceded by any number of spaces. For example, if we type
40 -- This is a comment. 20 begin 10 with Ada.Text_IO; use Ada.Text_IO; 30 end Add;and then type LIST, the editor displays
10 with Ada.Text_IO; use Ada.Text_IO; 20 begin 30 end Add; 40 -- This is a comment.
To insert lines, we merely type lines with intermediate line numbers . In our example, if we now type
15 procedure Hello is LISTwe'll see
10 with Ada.Text_IO; use Ada.Text_IO; 15 procedure Hello is 20 begin 30 end Add; 40 -- This is a comment.
To replace a line, we simply retype the line with the same line number as the line to be replaced, and to delete a line, we type only the line number. If we now type
15 procedure Add is 40 LISTwe'll see
10 with Ada.Text_IO; use Ada.Text_IO; 15 procedure Add is 20 begin 30 end Add;
Thus the user can insert, replace, and delete lines by line numbers, without learning any commands! If the user forgets the space after the line number, no harm is done; Ledit takes 20begin the same as 20 begin. However, the user can indent code by adding extra spaces after the line number. The following example has two extra spaces after each line number (three spaces total):
24 Put(2 + 2); 26 New_Line; 18 package My_Int_IO is new Integer_IO(Integer); use My_Int_IO; LIST 10 with Ada.Text_IO; use Ada.Text_IO; 15 procedure Add is 18 package My_Int_IO is new Integer_IO(Integer); use My_Int_IO; 20 begin 24 Put(2 + 2); 26 New_Line; 30 end Add;
When Listing, Ledit always allows exactly five spaces for the line number, so that the text lines up correctly:
LIST 2 This is a sample listing, 20 showing how the text 200 lines up, even when 2000 some line numbers are 20000 longer than others.
When we type EXIT, Ledit writes the output file without the line numbers. The text above would all start in column 1 of the output file.
For Ledit to be useful with files larger than a page, it must be possible to list a range of lines:
LIST 20 - 30
This is only a summary of the requirements for Ledit. Please refer to pages 20-24 of your printed course notes for the actual requirements. As a point of reference, our solution requires about 180 lines of Ada on four pages.
Here are the steps to follow for Outside Assignment 5. They can also be found in your printed course notes on page 25:
- Carefully read the requirements starting on page 20 of your printed course notes. Take your time.
- Write the Ada code, compile, and link. Call the main program Ledit. If you have any questions about what Ledit should do, you can compile and run our solution, which is in L_EDIT.ANS.
- Refer to pages 26-28 of your printed course notes for instructions on testing your line editor. If any tests are failed, go back to step 2.
- When all the tests are passed, you've completed the assignment and will have a chance to compare your solution with ours.
Congratulations on Completing Outside Assignment 5!
If you like, you can compare your solution with ours, which is in L_EDIT.ANS. A listing starts on page 29 of your printed course notes. Note that a single procedure handles adding, deleting, and replacing lines. Replacing is done by first deleting, then adding a line.
Your solution might be very different from ours, but if it passed all the tests, consider it correct.
Early in this course we used the generic package Integer_IO. Let's now learn how to write our own generic packages, procedures, and functions.
-- Our solution to Outside Assignment 5 -- (Written for Ada 83 and Ada 95): with Text_IO; use Text_IO; procedure Ledit is Max_Length : constant := 80; Max_Line_Number : constant := 29_999; type Text is record Len : Integer range 0 .. Max_Length := 0; Val : String(1 .. Max_Length); end record; type Link; type P is access Link; type Link Is record Num : Positive; Line : Text; Next : P; end record; Head : P := new Link; Temp : P; Input_File : File_Type; Output_File : File_Type; Input : Text; Finished : Boolean := False; Line_Num : Natural := 10; function Str(T : in Text) return String is separate; procedure Read_Input_File is separate; procedure Do_Command is separate; begin Put("Input file: "); Get_Line(Input.Val, Input.Len); Read_Input_File; Put("Output file: "); Get_Line(Input.Val, Input.Len); Create(Output_File, Name => Str(Input)); -- Get and process commands. while not Finished loop Put("> "); Get_Line(Input.Val, Input.Len); Do_Command; end loop; -- Write the output file. Temp := Head.Next; -- Skip unused link at start of linked list. while Temp /= null loop Put_Line(Output_File, Str(Temp.Line)); -- Write line of text. Temp := Temp.Next; -- Get next link. end loop; Close(Output_File); end Ledit; separate (Ledit) function Str(T : in Text) return String is begin return T.Val(1 .. T.Len); end Str; separate (Ledit) procedure Read_Input_File is begin -- If the input file exists, display a message and read it in. Open(Input_File, In_File, Str(Input)); Put_Line("File found."); Temp := Head; while not End_Of_File(Input_File) loop Get_Line(Input_File, Input.Val, Input.Len); -- Read a line. Temp.Next := new Link'(Line_Num, Input, null); -- Add to list. Temp := Temp.Next; -- Advance to next link. Line_Num := Line_Num + 10; end loop; Close(Input_File); exception -- If the input file doesn't exist, just display a message. when Name_Error => Put_Line("File not found."); end Read_Input_File; separate (Ledit) procedure Do_Command is procedure Delete_First_Character(T : in out Text) is separate; procedure Get_Leading_Integer(N : out Natural) is separate; procedure Strip_Leading_Spaces_From_Input is separate; procedure Add_Delete_Replace_Line is separate; procedure List is separate; begin Strip_Leading_Spaces_From_Input; if Str(Input) = "exit" or Str(Input) = "EXIT" then Finished := True; elsif Input.Len >= 4 and (Input.Val(1 .. 4) = "list" or Input.Val(1 .. 4) = "LIST") then List; elsif Input.Len > 0 and Input.Val(1) not in '0' .. '9' then Put_Line("Unrecognized command."); elsif Input.Len > 0 then Get_Leading_Integer(Line_Num); if Line_Num not in 1 .. Max_Line_Number then Put_Line("Illegal line number."); else Add_Delete_Replace_Line; end if; end if; exception when Numeric_Error | Constraint_Error => Put_Line("Line number too large."); end Do_Command; separate (Ledit.Do_Command) procedure Add_Delete_Replace_Line is Inp : Text := Input; begin if Inp.Len > 0 and Inp.Val(1) = ' ' then -- Treat "9x" like "9 x". Delete_First_Character(Inp); end if; Temp := Head; -- Find where this number belongs in linked list. while Temp /= null and then Temp.Next /= null and then Temp.Next.Num <= Line_Num loop if Temp.Next.Num = Line_Num then Temp.Next := Temp.Next.Next; -- Delete line. else Temp := Temp.Next; -- Advance to next link in list. end if; end loop; if Input.Len > 0 then -- Add line. Temp.Next := new Link'(Line_Num, Inp, Temp.Next); end if; end Add_Delete_Replace_Line; separate (Ledit.Do_Command) procedure Delete_First_Character(T : in out Text) is begin T.Val(1 .. T.Len - 1) := T.Val(2 .. T.Len); T.Len := T.Len - 1; end Delete_First_Character; separate (Ledit.Do_Command) procedure Get_Leading_Integer(N : out Natural) is Ans: Integer := 0; begin while Input.Len > 0 and Input.Val(1) in '0' .. '9' loop Ans := Ans*10 + Character'Pos(Input.Val(1)) - Character'Pos('0'); Delete_First_Character(Input); end loop; N := Ans; end Get_Leading_Integer; separate (Ledit.Do_Command) procedure Strip_Leading_Spaces_From_Input is begin while Input.Len > 0 and Input.Val(1) = ' ' loop Delete_First_Character(Input); end loop; end Strip_Leading_Spaces_From_Input; separate (Ledit.Do_Command) procedure List is package IIO is new Integer_IO(Integer); use IIO; Start, Finish : Natural; Valid : Boolean := True; begin Input.Len := Input.Len - 4; -- Delete the name of the command. Input.Val(1 .. Input.Len) := Input.Val(5 .. Input.Len + 4); Strip_Leading_Spaces_From_Input; if Input.Len = 0 then -- For "LIST" alone, list all lines. Start := 0; Finish := Max_Line_Number + 1; else Get_Leading_Integer(Start); -- Get number after "LIST". Strip_Leading_Spaces_From_Input; if Input.Len = 0 then -- For "LIST n", list only line n. Finish := Start; elsif Input.Val(1) /= '-' then -- Else "-" must follow n. Valid := False; else Delete_First_Character(Input); -- Delete the "-". Strip_Leading_Spaces_From_Input; Get_Leading_Integer(Finish); -- Get number after "-". Strip_Leading_Spaces_From_Input; if Finish = 0 and Start = 0 then -- "LIST -" isn't valid. Valid := False; elsif Finish = 0 then -- For "LIST n -", list n through end. Finish := Max_Line_Number + 1; end if; Valid := Valid and Input.Len = 0; -- No trailing garbage. end if; end if; if not Valid then Put_Line("Illegal syntax for LIST."); else Temp := Head.Next; -- Skip unused link at start of linked list. while Temp /= null and then Temp.Num <= Finish loop if Temp.Num >= Start then Put(Temp.Num, Width => 5); -- Display line #, width 5. Put_Line(' ' & Str(Temp.Line)); -- Display text of line. end if; Temp := Temp.Next; -- Get next link. end loop; end if; exception when Numeric_Error | Constraint_Error => Put_Line("Line number too large in List."); end List;