--  GHDL Run Time (GRT) - SDF parser.
--  Copyright (C) 2002 - 2016 Tristan Gingold
--
--  This program is free software: you can redistribute it and/or modify
--  it under the terms of the GNU General Public License as published by
--  the Free Software Foundation, either version 2 of the License, or
--  (at your option) any later version.
--
--  This program is distributed in the hope that it will be useful,
--  but WITHOUT ANY WARRANTY; without even the implied warranty of
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--  GNU General Public License for more details.
--
--  You should have received a copy of the GNU General Public License
--  along with this program.  If not, see <gnu.org/licenses>.
--
--  As a special exception, if other files instantiate generics from this
--  unit, or you link this unit with other files to produce an executable,
--  this unit does not by itself cause the resulting executable to be
--  covered by the GNU General Public License. This exception does not
--  however invalidate any other reasons why the executable file might be
--  covered by the GNU Public License.

with Grt.Stdio; use Grt.Stdio;
with Grt.C; use Grt.C;
with Grt.Strings; use Grt.Strings;
with Grt.Errors; use Grt.Errors;
with Ada.Unchecked_Deallocation;
with Grt.Vital_Annotate;

package body Grt.Sdf is
   use ASCII;

   type Sdf_Token_Type is
     (
      Tok_Oparen, -- (
      Tok_Cparen, -- )
      Tok_Qstring,
      Tok_Identifier,
      Tok_Rnumber,
      Tok_Dnumber,
      Tok_Div, -- /
      Tok_Dot, -- .
      Tok_Cln, -- :

      Tok_Error,
      Tok_Eof
     );

   type Sdf_Context_Acc is access Sdf_Context_Type;
   procedure Unchecked_Deallocation is new Ada.Unchecked_Deallocation
     (Name => Sdf_Context_Acc, Object => Sdf_Context_Type);

   Sdf_Context : Sdf_Context_Acc;

   --  Current data read from the file.
   Buf : String_Access (1 .. Buf_Size) := null;

   --  Length of the buffer, including the EOT.
   Buf_Len : Natural;
   Pos : Natural;
   Line_Start : Integer;

   Sdf_Stream : FILEs := NULL_Stream;
   Sdf_Filename : String_Access := null;
   Sdf_Line : Natural;

   function Open_Sdf (Filename : String) return Boolean
   is
      N_Filename : String (1 .. Filename'Length + 1);
      Mode : constant String := "rt" & ASCII.NUL;
   begin
      N_Filename (1 .. Filename'Length) := Filename;
      N_Filename (N_Filename'Last) := ASCII.NUL;
      Sdf_Stream := fopen (N_Filename'Address, Mode'Address);
      if Sdf_Stream = NULL_Stream then
         Error_S ("cannot open SDF file '");
         Diag_C (Filename);
         Error_E ("'");
         return False;
      end if;
      Sdf_Context := new Sdf_Context_Type;

      Sdf_Context.Version := Sdf_Version_Unknown;

      --  Set the timescale to 1 ns.
      Sdf_Context.Timescale := 1000;

      Buf := new String (1 .. Buf_Size);
      Buf_Len := 1;
      Buf (1) := EOT;
      Sdf_Line := 1;
      Sdf_Filename := new String'(Filename);
      Pos := 1;
      Line_Start := 1;
      return True;
   end Open_Sdf;

   procedure Close_Sdf
   is
   begin
      fclose (Sdf_Stream);
      Sdf_Stream := NULL_Stream;
      Unchecked_Deallocation (Sdf_Context);
      Unchecked_Deallocation (Buf);
   end Close_Sdf;

   procedure Read_Sdf
   is
      Res : size_t;
   begin
      Res := fread (Buf (Pos)'Address, 1, size_t (Read_Size), Sdf_Stream);
      Line_Start := Line_Start - Buf_Len + Pos;
      Buf_Len := Pos + Natural (Res);
      Buf (Buf_Len) := EOT;
   end Read_Sdf;


   Ident_Start : Natural;
   Ident_End : Natural;

   procedure Read_Append
   is
      Len : Natural;
   begin
      Len := Pos - Ident_Start;
      if Ident_Start = 1 or Len >= 1024 then
         Error_S ("SDF line ");
         Diag_C (Sdf_Line);
         Error_E (" is too long");
      end if;
      Buf (1 .. Len) := Buf (Ident_Start .. Ident_Start + Len - 1);
      Pos := Len + 1;
      Ident_Start := 1;
      Read_Sdf;
   end Read_Append;

   procedure Error_S_Sdf is
   begin
      Error_S (Sdf_Filename.all);
      Diag_C (':');
      Diag_C (Sdf_Line);
      Diag_C (':');
      Diag_C (Pos - Line_Start);
      Diag_C (": ");
   end Error_S_Sdf;

   procedure Error_Sdf (Msg : String) is
   begin
      Error_S_Sdf;
      Error_E (Msg);
   end Error_Sdf;

   procedure Error_Bad_Character is
   begin
      Error_Sdf ("bad character in SDF file");
   end Error_Bad_Character;

   procedure Scan_Identifier is
   begin
      Ident_Start := Pos;
      loop
         Pos := Pos + 1;
         case Buf (Pos) is
            when 'a' .. 'z'
              | 'A' .. 'Z'
              | '0' .. '9'
              | '_' =>
               null;
            when '\' =>
               Error_Sdf ("escape character not handled");
               Ident_End := Pos - 1;
               return;
            when EOT =>
               --  Continue to read.
               Read_Append;
               Pos := Pos - 1;
            when others =>
               Ident_End := Pos - 1;
               return;
         end case;
      end loop;
   end Scan_Identifier;

   function Ident_Length return Natural is
   begin
      return Ident_End - Ident_Start + 1;
   end Ident_Length;

   function Is_Ident (Str : String) return Boolean
   is
   begin
      if Ident_Length /= Str'Length then
         return False;
      end if;
      return Buf (Ident_Start .. Ident_End) = Str;
   end Is_Ident;

   procedure Scan_Qstring
   is
   begin
      Ident_Start := Pos + 1;
      loop
         Pos := Pos + 1;
         case Buf (Pos) is
            when EOT =>
               --  Continue to read.
               Read_Append;
               Pos := Pos - 1;
            when ASCII.NUL .. Character'Val (3)
              | Character'Val (5) .. Character'Val (31)
              | Character'Val (127) .. Character'Val (255) =>
               Error_Bad_Character;
            when ' '
              | '!'
              | '#' .. '~' =>
               null;
            when '"' => -- "
               Ident_End := Pos - 1;
               Pos := Pos + 1;
               exit;
         end case;
      end loop;
   end Scan_Qstring;

   Scan_Int : Integer;
   Scan_Exp : Integer;

   function Scan_Number return Sdf_Token_Type
   is
      Has_Dot : Boolean;
      Is_Negative : Boolean;
   begin
      Has_Dot := False;
      Is_Negative := False;
      Scan_Int := 0;
      Scan_Exp := 0;
      loop
         case Buf (Pos) is
            when '0' .. '9' =>
               Scan_Int := Scan_Int * 10
                 + Character'Pos (Buf (Pos)) - Character'Pos ('0');
               if Has_Dot then
                  Scan_Exp := Scan_Exp - 1;
               end if;
               Pos := Pos + 1;
            when '.' =>
               if Has_Dot then
                  Error_Bad_Character;
                  return Tok_Error;
               else
                  Has_Dot := True;
               end if;
               Pos := Pos + 1;
            when '-' =>
               if Is_Negative then
                  Error_Bad_Character;
                  return Tok_Error;
               else
                  Is_Negative := True;
               end if;
               Pos := Pos + 1;
            when EOT =>
               if Pos /= Buf_Len then
                  Error_Bad_Character;
                  return Tok_Error;
               end if;
               Pos := 1;
               Read_Sdf;
               exit when Buf_Len = 1;
            when others =>
               exit;
         end case;
      end loop;
      if Is_Negative then
         Scan_Int := -Scan_Int;
      end if;
      if Has_Dot then
         return Tok_Rnumber;
      else
         return Tok_Dnumber;
      end if;
   end Scan_Number;

   procedure Refill_Buf is
   begin
      Buf (1 .. Buf_Len - Pos) := Buf (Pos .. Buf_Len - 1);
      Pos := Buf_Len - Pos + 1;
      Read_Sdf;
      Pos := 1;
   end Refill_Buf;

   procedure Skip_Spaces is
   begin
      --  Fast blanks skipping.
      while Buf (Pos) = ' ' loop
         Pos := Pos + 1;
      end loop;

      loop
         --  Be sure there is at least 1 character.
         if Pos + 1 >= Buf_Len then
            Refill_Buf;
         end if;

         case Buf (Pos) is
            when EOT =>
               if Pos /= Buf_Len then
                  return;
               end if;
               Pos := 1;
               Read_Sdf;
               if Buf_Len = 1 then
                  return;
               end if;
            when LF =>
               Pos := Pos + 1;
               if Buf (Pos) = CR then
                  Pos := Pos + 1;
               end if;
               Line_Start := Pos;
               Sdf_Line := Sdf_Line + 1;
            when CR =>
               Pos := Pos + 1;
               if Buf (Pos) = LF then
                  Pos := Pos + 1;
               end if;
               Line_Start := Pos;
               Sdf_Line := Sdf_Line + 1;
            when ' '
              | HT =>
               Pos := Pos + 1;
            when '/' =>
               if Buf (Pos + 1) = '/' then
                  Pos := Pos + 2;
                  --  Skip line comment.
                  loop
                     exit when Buf (Pos) = CR;
                     exit when Buf (Pos) = LF;
                     exit when Buf (Pos) = EOT;
                     Pos := Pos + 1;
                     if Pos >= Buf_Len then
                        Refill_Buf;
                     end if;
                  end loop;
               else
                  return;
               end if;
            when others =>
               return;
         end case;
      end loop;
   end Skip_Spaces;

   function Get_Token return Sdf_Token_Type is
   begin
      Skip_Spaces;

      --  Be sure there is at least 4 characters.
      if Pos + 4 >= Buf_Len then
         Refill_Buf;
      end if;

      case Buf (Pos) is
         when EOT =>
            if Buf_Len = 1 then
               return Tok_Eof;
            else
               Error_Bad_Character;
               return Tok_Error;
            end if;
         when '"' => -- "
            Scan_Qstring;
            return Tok_Qstring;
         when '/' =>
            --  Skip_Spaces has already handled line comments.
            Pos := Pos + 1;
            return Tok_Div;
         when '.' =>
            Pos := Pos + 1;
            return Tok_Dot;
         when ':' =>
            Pos := Pos + 1;
            return Tok_Cln;
         when '(' =>
            Pos := Pos + 1;
            return Tok_Oparen;
         when ')' =>
            Pos := Pos + 1;
            return Tok_Cparen;
         when 'a' .. 'z'
           | 'A' .. 'Z' =>
            Scan_Identifier;
            return Tok_Identifier;
         when '0' .. '9'
           | '-' =>
            return Scan_Number;
         when others =>
            Error_Bad_Character;
            return Tok_Error;
      end case;
   end Get_Token;

   function Is_White_Space (C : Character) return Boolean is
   begin
      case C is
         when ' '
           | HT
           | CR
           | LF =>
            return True;
         when others =>
            return False;
      end case;
   end Is_White_Space;

   function Get_Edge_Token return Edge_Type is
   begin
      Skip_Spaces;

      --  Be sure there is at least 4 characters.
      if Pos + 4 >= Buf_Len then
         Refill_Buf;
      end if;

      case Buf (Pos) is
         when '0' =>
            if Is_White_Space (Buf (Pos + 2)) then
               if Buf (Pos + 1) = 'z' then
                  Pos := Pos + 2;
                  return Edge_0z;
               elsif Buf (Pos + 1) = '1' then
                  Pos := Pos + 2;
                  return Edge_01;
               end if;
            end if;
         when '1' =>
            if Is_White_Space (Buf (Pos + 2)) then
               if Buf (Pos + 1) = 'z' then
                  Pos := Pos + 2;
                  return Edge_1z;
               elsif Buf (Pos + 1) = '0' then
                  Pos := Pos + 2;
                  return Edge_10;
               end if;
            end if;
         when 'z' =>
            if Is_White_Space (Buf (Pos + 2)) then
               if Buf (Pos + 1) = '0' then
                  Pos := Pos + 2;
                  return Edge_Z0;
               elsif Buf (Pos + 1) = '1' then
                  Pos := Pos + 2;
                  return Edge_Z1;
               end if;
            end if;
         when 'p' =>
            Scan_Identifier;
            if Is_Ident ("posedge") then
               return Edge_Posedge;
            end if;
         when 'n' =>
            Scan_Identifier;
            if Is_Ident ("negedge") then
               return Edge_Negedge;
            end if;
         when others =>
            null;
      end case;
      Error_Sdf ("edge_identifier expected");
      return Edge_Error;
   end Get_Edge_Token;

   procedure Error_Sdf (Tok : Sdf_Token_Type)
   is
   begin
      case Tok is
         when Tok_Qstring =>
            Error_Sdf ("qstring expected");
         when Tok_Oparen =>
            Error_Sdf ("'(' expected");
         when Tok_Identifier =>
            Error_Sdf ("identifier expected");
         when Tok_Cln =>
            Error_Sdf ("':' (colon) expected");
         when others =>
            Error_Sdf ("parse error");
      end case;
   end Error_Sdf;

   function Expect (Tok : Sdf_Token_Type) return Boolean
   is
   begin
      if Get_Token = Tok then
         return True;
      end if;
      Error_Sdf (Tok);
      return False;
   end Expect;

   function Expect_Cp_Op_Ident (Tok : Sdf_Token_Type) return Boolean
   is
   begin
      if Tok /= Tok_Cparen then
         Error_Sdf (Tok_Cparen);
         return False;
      end if;
      if not Expect (Tok_Oparen)
        or else not Expect (Tok_Identifier)
      then
         return False;
      end if;
      return True;
   end Expect_Cp_Op_Ident;

   function Expect_Qstr_Cp_Op_Ident (Str : String) return Boolean
   is
      Tok : Sdf_Token_Type;
   begin
      if not Is_Ident (Str) then
         return True;
      end if;

      Tok := Get_Token;
      if Tok = Tok_Qstring then
         Tok := Get_Token;
      end if;

      return Expect_Cp_Op_Ident (Tok);
   end Expect_Qstr_Cp_Op_Ident;

   procedure Start_Generic_Name (Kind : Timing_Generic_Kind) is
   begin
      Sdf_Context.Kind := Kind;
      Sdf_Context.Port_Num := 0;
      Sdf_Context.Ports (1).L := Invalid_Dnumber;
      Sdf_Context.Ports (2).L := Invalid_Dnumber;
      Sdf_Context.Ports (1).Edge := Edge_None;
      Sdf_Context.Ports (2).Edge := Edge_None;
   end Start_Generic_Name;

   --  Status of a parsing.
   --  ERROR: parse error (syntax is not correct)
   --  ALTERN: alternate construct parsed (ie simple RNUMBER for tc_rvalue).
   --  OPTIONAL: the construct is absent.
   --  FOUND: the construct is present.
   --  SET: the construct is present and a value was extracted from.
   type Parse_Status_Type is
     (
      Status_Error,
      Status_Altern,
      Status_Optional,
      Status_Found,
      Status_Set
     );

   function Num_To_Time return Ghdl_I64
   is
      Res : Ghdl_I64;
   begin
      Res := Ghdl_I64 (Scan_Int) * Ghdl_I64 (Sdf_Context.Timescale);
      while Scan_Exp < 0 loop
         Res := Res / 10;
         Scan_Exp := Scan_Exp + 1;
      end loop;
      return Res;
   end Num_To_Time;

   --  Parse: REXPRESSION? ')'
   procedure Parse_Rexpression
     (Status : out Parse_Status_Type; Val : out Ghdl_I64)
   is
      Tok : Sdf_Token_Type;

      procedure Pr_Rnumber (Mtm : Mtm_Type)
      is
      begin
         if Tok = Tok_Rnumber or Tok = Tok_Dnumber then
            if Mtm = Sdf_Mtm then
               Val := Num_To_Time;
               Status := Status_Set;
            elsif Status /= Status_Set then
               Status := Status_Found;
            end if;
            Tok := Get_Token;
         end if;
      end Pr_Rnumber;

      function Pr_Colon return Boolean
      is
      begin
         if Tok /= Tok_Cln then
            Error_Sdf (Tok_Cln);
            Status := Status_Error;
            return False;
         else
            Tok := Get_Token;
            return True;
         end if;
      end Pr_Colon;

   begin
      Val := 0;
      Tok := Get_Token;
      Status := Status_Error;
      if Tok = Tok_Cparen then
         Status := Status_Optional;
         return;
      end if;

      Pr_Rnumber (Minimum);

      if not Pr_Colon then
         return;
      end if;

      Pr_Rnumber (Typical);

      if not Pr_Colon then
         return;
      end if;

      Pr_Rnumber (Maximum);

      if Status = Status_Error then
         Error_Sdf ("at least one number required in an rexpression");
         return;
      end if;

      if Tok /= Tok_Cparen then
         Error_Sdf (Tok_Cparen);
         Status := Status_Error;
      end if;
   end Parse_Rexpression;

   function Expect_Rexpr_Cp_Op_Ident return Boolean
   is
      Status : Parse_Status_Type;
      Val : Ghdl_I64;
   begin
      Parse_Rexpression (Status, Val);
      if Status = Status_Error then
         return False;
      end if;
      if not Expect (Tok_Oparen)
        or else not Expect (Tok_Identifier)
      then
         Error_Sdf (Tok_Identifier);
         return False;
      end if;
      return True;
   end Expect_Rexpr_Cp_Op_Ident;

   function Parse_Port_Path1 (Tok : Sdf_Token_Type) return Boolean
   is
      Port_Spec : Port_Spec_Type
         renames Sdf_Context.Ports (Sdf_Context.Port_Num);
      Len : Natural;
   begin
      if Tok /= Tok_Identifier then
         Error_Sdf ("port path expected");
         return False;
      end if;
      Len := 0;
      for I in Ident_Start .. Ident_End loop
         Len := Len + 1;
         Port_Spec.Name (Len) := To_Lower (Buf (I));
      end loop;
      Port_Spec.Name_Len := Len;

      --  Parse   [ DNUMBER ]
      --        | [ DNUMBER : DNUMBER ]
      Skip_Spaces;
      if Buf (Pos) = '[' then
         Port_Spec.R := Invalid_Dnumber;
         Pos := Pos + 1;
         if Get_Token /= Tok_Dnumber then
            Error_Sdf (Tok);
         else
            Port_Spec.L := Ghdl_I32 (Scan_Int);
         end if;
         Skip_Spaces;
         if Buf (Pos) = ':' then
            Pos := Pos + 1;
            if Get_Token /= Tok_Dnumber then
               Error_Sdf (Tok);
            else
               Port_Spec.R := Ghdl_I32 (Scan_Int);
            end if;
            Skip_Spaces;
         end if;
         if Buf (Pos) /= ']' then
            Error_Sdf ("']' expected");
         else
            Pos := Pos + 1;
         end if;
      end if;

      return True;
   end Parse_Port_Path1;

   function Parse_Port_Path return Boolean
   is
   begin
      Sdf_Context.Port_Num := Sdf_Context.Port_Num + 1;
      return Parse_Port_Path1 (Get_Token);
   end Parse_Port_Path;

   function Parse_Port_Spec return Boolean
   is
      Tok : Sdf_Token_Type;
      Edge : Edge_Type;
   begin
      Sdf_Context.Port_Num := Sdf_Context.Port_Num + 1;
      Tok := Get_Token;
      if Tok = Tok_Identifier then
         return Parse_Port_Path1 (Tok);
      elsif Tok /= Tok_Oparen then
         Error_Sdf ("port spec expected");
         return False;
      end if;
      Edge := Get_Edge_Token;
      if Edge = Edge_Error then
         return False;
      end if;
      Sdf_Context.Ports (Sdf_Context.Port_Num).Edge := Edge;
      if not Parse_Port_Path1 (Get_Token) then
         return False;
      end if;
      if Get_Token /= Tok_Cparen then
         Error_Sdf (Tok_Cparen);
         return False;
      end if;
      return True;
   end Parse_Port_Spec;

   function Parse_Port_Tchk return Boolean renames Parse_Port_Spec;

   --  tc_rvalue ::= ( RNUMBER )
   --            ||= ( rexpression )
   --  Return status_optional for ( )
   function Parse_Tc_Rvalue return Parse_Status_Type
   is
      Tok : Sdf_Token_Type;
      Res : Parse_Status_Type;
   begin
      --  '('
      if Get_Token /= Tok_Oparen then
         Error_Sdf (Tok_Oparen);
         return Status_Error;
      end if;
      Res := Status_Found;
      Tok := Get_Token;
      if Tok = Tok_Rnumber or Tok = Tok_Dnumber then
         Sdf_Context.Timing (1) := Num_To_Time;
         Tok := Get_Token;
         if Tok = Tok_Cparen then
            --  This is a simple RNUMBER.
            return Status_Altern;
         end if;
         if Sdf_Mtm = Minimum then
            Res := Status_Set;
         end if;
      end if;
      if Tok = Tok_Cparen then
         return Status_Optional;
      end if;
      if Tok /= Tok_Cln then
         Error_Sdf (Tok_Cln);
         return Status_Error;
      end if;
      Tok := Get_Token;
      if Tok = Tok_Rnumber or Tok = Tok_Dnumber then
         if Sdf_Mtm = Typical then
            Sdf_Context.Timing (1) := Num_To_Time;
            Res := Status_Set;
         end if;
         Tok := Get_Token;
      end if;
      if Tok /= Tok_Cln then
         Error_Sdf (Tok_Cln);
         return Status_Error;
      end if;
      Tok := Get_Token;
      if Tok = Tok_Rnumber or Tok = Tok_Dnumber then
         if Sdf_Mtm = Maximum then
            Sdf_Context.Timing (1) := Num_To_Time;
            Res := Status_Set;
         end if;
         Tok := Get_Token;
      end if;
      if Tok /= Tok_Cparen then
         Error_Sdf (Tok_Cparen);
         return Status_Error;
      end if;
      return Res;
   end Parse_Tc_Rvalue;

   function Parse_Simple_Tc_Rvalue return Boolean is
   begin
      Sdf_Context.Timing_Nbr := 0;

      case Parse_Tc_Rvalue is
         when Status_Error
           | Status_Optional =>
            return False;
         when Status_Altern =>
            null;
         when Status_Found =>
            Sdf_Context.Timing_Set (1) := False;
         when Status_Set =>
            Sdf_Context.Timing_Set (1) := True;
      end case;
      return True;
   end Parse_Simple_Tc_Rvalue;

   --  rvalue ::= ( RNUMBER )
   --         ||= rexp_list
   --  Parse: rvalue )
   function Parse_Rvalue return Boolean
   is
      Tok : Sdf_Token_Type;
   begin
      Sdf_Context.Timing_Nbr := 0;
      Sdf_Context.Timing_Set := (others => False);

      case Parse_Tc_Rvalue is
         when Status_Error =>
            return False;
         when Status_Altern =>
            Sdf_Context.Timing_Nbr := 1;
            if Get_Token /= Tok_Cparen then
               Error_Sdf (Tok_Cparen);
            end if;
            return True;
         when Status_Found
           | Status_Optional =>
            null;
         when Status_Set =>
            Sdf_Context.Timing_Set (1) := True;
      end case;

      Sdf_Context.Timing_Nbr := 1;
      loop
         Tok := Get_Token;
         exit when Tok = Tok_Cparen;
         if Tok /= Tok_Oparen then
            Error_Sdf (Tok_Oparen);
            return False;
         end if;

         Sdf_Context.Timing_Nbr := Sdf_Context.Timing_Nbr + 1;
         declare
            Status : Parse_Status_Type;
            Val : Ghdl_I64;
         begin
            Parse_Rexpression (Status, Val);
            case Status is
               when Status_Error
                 | Status_Altern =>
                  return False;
               when Status_Optional
                 | Status_Found =>
                  null;
               when Status_Set =>
                  Sdf_Context.Timing_Set (Sdf_Context.Timing_Nbr) := True;
                  Sdf_Context.Timing (Sdf_Context.Timing_Nbr) := Val;
            end case;
         end;
      end loop;
      if Boolean'(False) then
         --  Do not expand here, since the most used is 01.
         case Sdf_Context.Timing_Nbr is
            when 1 =>
               for I in 2 .. 6 loop
                  Sdf_Context.Timing (I) := Sdf_Context.Timing (1);
                  Sdf_Context.Timing_Set (I) := Sdf_Context.Timing_Set (1);
               end loop;
            when 2 =>
               for I in 3 .. 4 loop
                  Sdf_Context.Timing (I) := Sdf_Context.Timing (1);
                  Sdf_Context.Timing_Set (I) := Sdf_Context.Timing_Set (1);
               end loop;
               for I in 5 .. 6 loop
                  Sdf_Context.Timing (I) := Sdf_Context.Timing (2);
                  Sdf_Context.Timing_Set (I) := Sdf_Context.Timing_Set (2);
               end loop;
            when 3 =>
               for I in 4 .. 6 loop
                  Sdf_Context.Timing (I) := Sdf_Context.Timing (I - 3);
                  Sdf_Context.Timing_Set (I) := Sdf_Context.Timing_Set (I - 3);
               end loop;
            when 6
              | 12 =>
               null;
            when others =>
               Error_Sdf ("bad number of rvalue");
               return False;
         end case;
      end if;
      return True;
   end Parse_Rvalue;

   function Handle_Generic return Boolean
   is
      Name : String (1 .. 1024);
      Len : Natural;

      procedure Start (Str : String) is
      begin
         Name (1 .. Str'Length) := Str;
         Len := Str'Length;
      end Start;

      procedure Add (Str : String)
      is
         Nlen : Natural;
      begin
         Len := Len + 1;
         Name (Len) := '_';
         Nlen := Len + Str'Length;
         Name (Len + 1 .. Nlen) := Str;
         Len := Nlen;
      end Add;

      procedure Add_Edge (Edge : Edge_Type; Force : Boolean) is
      begin
         case Edge is
            when Edge_Posedge =>
               Add ("posedge");
            when Edge_Negedge =>
               Add ("negedge");
            when Edge_01 =>
               Add ("01");
            when Edge_10 =>
               Add ("10");
            when Edge_0z =>
               Add ("0z");
            when Edge_Z1 =>
               Add ("Z1");
            when Edge_1z =>
               Add ("1z");
            when Edge_Z0 =>
               Add ("ZO");
            when Edge_None =>
               if Force then
                  Add ("noedge");
               end if;
            when Edge_Error =>
               Add ("?");
         end case;
      end Add_Edge;

      Ok : Boolean;
   begin
      case Sdf_Context.Kind is
         when Delay_Iopath =>
            Start ("tpd");
         when Delay_Port =>
            Start ("tipd");
         when Timingcheck_Setup =>
            Start ("tsetup");
         when Timingcheck_Hold =>
            Start ("thold");
         when Timingcheck_Setuphold =>
            Start ("tsetup");
         when Timingcheck_Recovery =>
            Start ("trecovery");
         when Timingcheck_Removal =>
            Start ("tremoval");
         when Timingcheck_Skew =>
            Start ("tskew");
         when Timingcheck_Width =>
            Start ("tpw");
         when Timingcheck_Period =>
            Start ("tperiod");
         when Timingcheck_Nochange =>
            Start ("tncsetup");
      end case;
      for I in 1 .. Sdf_Context.Port_Num loop
         Add (Sdf_Context.Ports (I).Name
              (1 .. Sdf_Context.Ports (I).Name_Len));
      end loop;
      if Sdf_Context.Kind in Timing_Generic_Full_Condition then
         Add_Edge (Sdf_Context.Ports (1).Edge, True);
         Add_Edge (Sdf_Context.Ports (2).Edge, False);
      elsif Sdf_Context.Kind in Timing_Generic_Simple_Condition then
         Add_Edge (Sdf_Context.Ports (1).Edge, False);
      end if;
      Vital_Annotate.Sdf_Generic (Sdf_Context.all, Name (1 .. Len), Ok);
      if not Ok then
         Error_S_Sdf;
         Diag_C ("could not annotate generic ");
         Error_E (Name (1 .. Len));
      end if;
      return True;
   end Handle_Generic;

   function Parse_Sdf return Boolean
   is
      Tok : Sdf_Token_Type;
      Ok : Boolean;
   begin
      if Get_Token /= Tok_Oparen
        or else Get_Token /= Tok_Identifier
        or else not Is_Ident ("DELAYFILE")
        or else Get_Token /= Tok_Oparen
        or else Get_Token /= Tok_Identifier
      then
         Error_Sdf ("not an SDF file");
         return False;
      end if;

      if Is_Ident ("SDFVERSION") then
         Tok := Get_Token;
         if Tok = Tok_Qstring then
            Sdf_Context.Version := Sdf_Version_Bad;
            if Ident_Length = 3 and then Buf (Ident_Start + 1) = '.' then
               --  Version has the format '"X.Y"' (without simple quote).
               if Buf (Ident_Start) = '2'
                 and then Buf (Ident_Start + 2) = '1'
               then
                  Sdf_Context.Version := Sdf_2_1;
               end if;
            end if;
            Tok := Get_Token;
         end if;

         if not Expect_Cp_Op_Ident (Tok) then
            return False;
         end if;
      end if;

      if not Expect_Qstr_Cp_Op_Ident ("DESIGN") then
         return False;
      end if;

      if not Expect_Qstr_Cp_Op_Ident ("DATE") then
         return False;
      end if;

      if not Expect_Qstr_Cp_Op_Ident ("VENDOR") then
         return False;
      end if;

      if not Expect_Qstr_Cp_Op_Ident ("PROGRAM") then
         return False;
      end if;

      if not Expect_Qstr_Cp_Op_Ident ("VERSION") then
         return False;
      end if;

      if Is_Ident ("DIVIDER") then
         Tok := Get_Token;
         if Tok = Tok_Div or Tok = Tok_Dot then
            Tok := Get_Token;
         end if;
         if not Expect_Cp_Op_Ident (Tok) then
            return False;
         end if;
      end if;

      if Is_Ident ("VOLTAGE") then
         if not Expect_Rexpr_Cp_Op_Ident then
            return False;
         end if;
      end if;

      if not Expect_Qstr_Cp_Op_Ident ("PROCESS") then
         return False;
      end if;

      if Is_Ident ("TEMPERATURE") then
         if not Expect_Rexpr_Cp_Op_Ident then
            return False;
         end if;
      end if;

      if Is_Ident ("TIMESCALE") then
         Tok := Get_Token;
         if Tok = Tok_Rnumber or Tok = Tok_Dnumber then
            if Scan_Exp = 0 and (Scan_Int = 1
                                 or Scan_Int = 10
                                 or Scan_Int = 100)
            then
               Sdf_Context.Timescale := Scan_Int;
            else
               Error_Sdf ("bad timescale value");
               return False;
            end if;
            Tok := Get_Token;
            if Tok /= Tok_Identifier then
               Error_Sdf (Tok_Identifier);
            end if;
            if Is_Ident ("ps") then
               null;
            elsif Is_Ident ("ns") then
               Sdf_Context.Timescale := Sdf_Context.Timescale * 1000;
            elsif Is_Ident ("us") then
               Sdf_Context.Timescale := Sdf_Context.Timescale * 1000_000;
            else
               Error_Sdf ("bad timescale unit");
               return False;
            end if;
            Tok := Get_Token;
         end if;
         if not Expect_Cp_Op_Ident (Tok) then
            return False;
         end if;
      end if;

      Vital_Annotate.Sdf_Header (Sdf_Context.all);

      --  Parse cell+
      loop
         if not Is_Ident ("CELL") then
            Error_Sdf ("CELL expected");
            return False;
         end if;
         --  Parse celltype
         if Get_Token /= Tok_Oparen
           or else Get_Token /= Tok_Identifier
           or else not Is_Ident ("CELLTYPE")
           or else Get_Token /= Tok_Qstring
         then
            Error_Sdf ("CELLTYPE expected");
            return False;
         end if;
         Sdf_Context.Celltype_Len := Ident_Length;
         if Sdf_Context.Celltype_Len > Sdf_Context.Celltype'Length then
            Error_Sdf ("CELLTYPE qstring is too long");
            return False;
         end if;
         for I in Ident_Start .. Ident_End loop
            Sdf_Context.Celltype (I - Ident_Start + 1) := To_Lower (Buf (I));
         end loop;
         Vital_Annotate.Sdf_Celltype (Sdf_Context.all);
         if Get_Token /= Tok_Cparen
           or else Get_Token /= Tok_Oparen
           or else Get_Token /= Tok_Identifier
           or else not Is_Ident ("INSTANCE")
         then
            Error_Sdf ("INSTANCE expected");
            return False;
         end if;
         --  Parse instance+
         loop
            exit when not Is_Ident ("INSTANCE");
            Tok := Get_Token;
            if Tok /= Tok_Cparen then
               loop
                  if Tok /= Tok_Identifier then
                     Error_Sdf ("instance identifier expected");
                     return False;
                  end if;
                  for I in Ident_Start .. Ident_End loop
                     Buf (I) := To_Lower (Buf (I));
                  end loop;
                  Vital_Annotate.Sdf_Instance
                    (Sdf_Context.all, Buf (Ident_Start .. Ident_End), Ok);
                  if not Ok then
                     Error_Sdf ("cannot find instance");
                     return False;
                  end if;
                  Tok := Get_Token;
                  exit when Tok /= Tok_Dot;
                  Tok := Get_Token;
               end loop;
            end if;
            if Tok /= Tok_Cparen
              or else Get_Token /= Tok_Oparen
              or else Get_Token /= Tok_Identifier
            then
               Error_Sdf ("instance or timing_spec expected");
               return False;
            end if;
         end loop;
         Vital_Annotate.Sdf_Instance_End (Sdf_Context.all, Ok);
         if not Ok then
            Error_Sdf ("bad instance or celltype mistmatch");
            return False;
         end if;

         --  Parse timing_spec+
         loop
            if Is_Ident ("DELAY") then
               --  Parse deltype+
               Tok := Get_Token;
               loop
                  if Tok /= Tok_Oparen
                    or else Get_Token /= Tok_Identifier
                  then
                     Error_Sdf ("deltype expected");
                     return False;
                  end if;
                  if Is_Ident ("PATHPULSE")
                    or else Is_Ident ("GLOBALPATHPULSE")
                  then
                     Error_Sdf ("PATHPULSE and GLOBALPATHPULSE not allowed");
                     return False;
                  end if;
                  if Is_Ident ("ABSOLUTE") then
                     null;
                  elsif Is_Ident ("INCREMENT") then
                     null;
                  else
                     Error_Sdf ("ABSOLUTE or INCREMENT expected");
                     return False;
                  end if;
                  --  Parse absvals+ or incvals+
                  Tok := Get_Token;
                  loop
                     if Tok /= Tok_Oparen
                       or else Get_Token /= Tok_Identifier
                     then
                        Error_Sdf ("absvals or incvals expected");
                        return False;
                     end if;
                     if Is_Ident ("IOPATH") then
                        Start_Generic_Name (Delay_Iopath);
                        if not Parse_Port_Spec
                          or else not Parse_Port_Path
                          or else not Parse_Rvalue
                        then
                           return False;
                        end if;
                     elsif Is_Ident ("PORT") then
                        Start_Generic_Name (Delay_Port);
                        if not Parse_Port_Path
                          or else not Parse_Rvalue
                        then
                           return False;
                        end if;
                     elsif Is_Ident ("COND")
                       or else Is_Ident ("INTERCONNECT")
                       or else Is_Ident ("DEVICE")
                     then
                        Error_Sdf
                          ("COND, INTERCONNECT, or DEVICE not handled");
                        return False;
                     elsif Is_Ident ("NETDELAY") then
                        Error_Sdf ("NETDELAY not allowed in VITAL SDF");
                        return False;
                     else
                        Error_Sdf ("absvals or incvals expected");
                        return False;
                     end if;

                     if not Handle_Generic then
                        return False;
                     end if;

                     Tok := Get_Token;
                     exit when Tok = Tok_Cparen;
                  end loop;
                  Tok := Get_Token;
                  exit when Tok = Tok_Cparen;
               end loop;
            elsif Is_Ident ("TIMINGCHECK") then
               --  parse tc_def+
               Tok := Get_Token;
               loop
                  if Tok /= Tok_Oparen
                    or else Get_Token /= Tok_Identifier
                  then
                     Error_Sdf ("tc_def expected");
                     return False;
                  end if;
                  if Is_Ident ("SETUP") then
                     Start_Generic_Name (Timingcheck_Setup);
                  elsif Is_Ident ("HOLD") then
                     Start_Generic_Name (Timingcheck_Hold);
                  elsif Is_Ident ("SETUPHOLD") then
                     Start_Generic_Name (Timingcheck_Setuphold);
                  elsif Is_Ident ("RECOVERY") then
                     Start_Generic_Name (Timingcheck_Recovery);
                  elsif Is_Ident ("REMOVAL") then
                     Start_Generic_Name (Timingcheck_Removal);
                  elsif Is_Ident ("SKEW") then
                     Start_Generic_Name (Timingcheck_Skew);
                  elsif Is_Ident ("WIDTH") then
                     Start_Generic_Name (Timingcheck_Width);
                  elsif Is_Ident ("PERIOD") then
                     Start_Generic_Name (Timingcheck_Period);
                  elsif Is_Ident ("NOCHANGE") then
                     Start_Generic_Name (Timingcheck_Nochange);
                  elsif Is_Ident ("PATHCONSTRAINT")
                    or else Is_Ident ("SUM")
                    or else Is_Ident ("DIFF")
                    or else Is_Ident ("SKEWCONSTRAINT")
                  then
                     Error_Sdf ("non-VITAL tc_def");
                     return False;
                  else
                     Error_Sdf ("bad tc_def");
                     return False;
                  end if;

                  case Sdf_Context.Kind is
                     when Timingcheck_Setup
                       | Timingcheck_Hold
                       | Timingcheck_Recovery
                       | Timingcheck_Removal
                       | Timingcheck_Skew
                       | Timingcheck_Setuphold
                       | Timingcheck_Nochange =>
                        if not Parse_Port_Tchk
                          or else not Parse_Port_Tchk
                          or else not Parse_Simple_Tc_Rvalue
                        then
                           return False;
                        end if;
                     when Timingcheck_Width
                       | Timingcheck_Period =>
                        if not Parse_Port_Tchk
                          or else not Parse_Simple_Tc_Rvalue
                        then
                           return False;
                        end if;
                     when others =>
                        Internal_Error ("sdf_parse");
                  end case;

                  if not Handle_Generic then
                     return False;
                  end if;

                  case Sdf_Context.Kind is
                     when Timingcheck_Setuphold
                       | Timingcheck_Nochange =>
                        if not Parse_Simple_Tc_Rvalue then
                           return False;
                        end if;
                        Error_Sdf ("setuphold and nochange not yet handled");
                        return False;
                     when others =>
                        null;
                  end case;

                  if Get_Token /= Tok_Cparen then
                     Error_Sdf (Tok_Cparen);
                     return False;
                  end if;
                  Tok := Get_Token;
                  exit when Tok = Tok_Cparen;
               end loop;
            end if;
            Tok := Get_Token;
            exit when Tok = Tok_Cparen;
            if Tok /= Tok_Oparen then
               Error_Sdf (Tok_Oparen);
               return False;
            end if;
            if Get_Token /= Tok_Identifier then
               Error_Sdf (Tok_Identifier);
               return False;
            end if;
         end loop;
         Tok := Get_Token;
         exit when Tok = Tok_Cparen;
         if Tok /= Tok_Oparen
           or else Get_Token /= Tok_Identifier
         then
            Error_Sdf (Tok_Identifier);
         end if;
      end loop;
      if Get_Token /= Tok_Eof then
         Error_Sdf ("EOF expected");
         return False;
      end if;
      return True;
   end Parse_Sdf;

   function Parse_Sdf_File (Filename : String) return Boolean
   is
      Res : Boolean;
   begin
      if not Open_Sdf (Filename) then
         return False;
      end if;
      Res := Parse_Sdf;
      Close_Sdf;
      return Res;
   end Parse_Sdf_File;

end Grt.Sdf;
