1
votes

I'm trying to write a lexer in Ada and have run into an issue.

procedure main is
    ...

    type lexer is tagged record
        input : ada.text_io.file_type;
        index : integer;
    end record;
    ...

    my_lexer : lexer;
    input_file_name : bounded_string;
    input_file : ada.text_io.file_type;
    next_token : token;
 begin
    input_file_name := get_input_file;

    ada.text_io.open(
        file => input_file,
        mode => ada.text_io.in_file,
        name => to_string(input_file_name)
    );

    my_lexer := (input => input_file, index => 0);
    next_token := my_lexer.get_next_token;
    ada.text_io.put_line(to_string(next_token.text));
end main;

On the line my_lexer := (input => input_file); I get the following error:

main.adb:22:17: nonlimited tagged type cannot have limited components

I understand this to be an issue since ada.text_io.in_file is a limited type, but if I were to just remove this from the record, and instead pass it as an argument to get_next_token,

    ...
    type lexer is tagged record
        index : integer;
    end record;

    function get_next_token(this: lexer'class; input_file: ada.text_io.file_type) return token;
    ...

Then this would make the state of the lexer invalid if you were to switch the input_file between calls of get_next_token thus breaking it.

Is there a way in Ada to create something like this like you would in C?

struct lexer {
    FILE *input;
    int index;
};
4
You could try to make the lexer type tagged limited instead of tagged (i.e. non-limited).DeeDee
@DeeDee I've tried this, but run into left hand of assignment must not be limited type meaning I couldn't initialize my_lexer right?jacob
@DeeDee I managed to fix it, by creating an access type of ada.text_io.file_type and making input_file an aliased ada.text_io.file_type so I could take a pointer of input_file.jacob

4 Answers

3
votes

One approach, allowing the lexer type to be tagged limited is to create the limited object in-place, so that its components (specifically the limited file_type component) aren't created elsewhere (legal) and then assigned (copied, illegal). In this case, the file component of lexer is passed to the Open call, which updates it in place.

procedure lex2 is

    type lexer is tagged limited record
        input : ada.text_io.file_type;
        index : integer;
    end record;
    
    input_file_name : unbounded_string;
    my_lexer : lexer;
    -- next_token : token;
 begin
    input_file_name := get_input_file;
    my_lexer.index  := 0;
    ada.text_io.open( file => my_lexer.input,
                      mode => ada.text_io.in_file,
                      name => to_string(input_file_name));

    -- next_token := my_lexer.get_next_token;
    -- ada.text_io.put_line(to_string(next_token.text));
end lex2;
3
votes

Usually when you run into problems like this there are two solutions

  1. Define a new type that is an access to the limited type and use it in your record, or
  2. Make your type limited. This is maybe (in my opinion) the cleaner solution since you do not have to mess with allocation, release, and stuff. (Personally, I try to avoid access types as much as possible). Of course, making it limited will prevent you to assign it, but maybe this is not a major problem in the case of a lexer.
2
votes

What I'd do is hide all these details and create an abstraction:

package Lexing is
   type Info is tagged limited private;

   function Is_Open (State : in Info) return Boolean;
   -- Description of subprogram here

   procedure Open (State : in out Info; Name : in String) with
      Pre  => not State_Is_Open,
      Post => State.Is_Open;
   -- Description of subprogram here

   procedure Close (State : in out Info) with
      Pre  => State.Is_Open,
      Post => not State.Is_Open;
   -- Description of subprogram here

   type Token_Info is tagged private;

   function Next (State : in out Info) return Token_Info with
      Pre => State.Is_Open;
   -- Description of subprogram here

   function Text (Token : in Token_Info) return String;
   -- Description of subprogram here

   ...
private -- Lexing
   ...
end Lexing;

Obviously the fun bits are left as an exercise for the reader.

1
votes

I'm not sure if this is the best solution, but this is what I've come up with:

 procedure main is
    type file_access is access all ada.text_io.file_type;
    
    ...

    type lexer is tagged record
        input : file_access;
        index : integer;
    end record;

    ...

    my_lexer : lexer;
    input_file_name : bounded_string;
    input_file : aliased ada.text_io.file_type;
    next_token : token;
 begin
    input_file_name := get_input_file;

    ada.text_io.open(
        file => input_file,
        mode => ada.text_io.in_file,
        name => to_string(input_file_name)
    );

    my_lexer := (input => input_file'access, index => 0);
    next_token := my_lexer.get_next_token;
    ada.text_io.put_line(to_string(next_token.text));
 end main;

Seems a bit convoluted, but I believe it's a way to ensure an access of input_file lives for as long as the procedure.