-- Our solution to Outside Assignment 5:
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, 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, print 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 pointer to next link.
      LINE_NUM := LINE_NUM + 10;
   end loop;
   CLOSE(INPUT_FILE);
exception    -- If the input file doesn't exist, just print 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);  -- Print line number, width 5.
            PUT_LINE(' ' & STR(TEMP.LINE));     -- Print 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;
