それ例外でできるよ!

http://kmonos.net/wlog/101.html#_1934090919 の記事に対して、「それ例外でできるよ」とつぶやいたところ、「再帰では最内周で補足されてしまうため使えない」と言い伏せられたため、無理やり解決してみました。
恐らく何言ってんだか何やってんだかわからないと思いますが、私もわかってないです。

with Ada.Exceptions;           use Ada.Exceptions;
with Ada.Text_IO;              use Ada.Text_IO;
with Ada.Unchecked_Conversion;
with System.Standard_Library;
procedure MultiExit is
   e1_name : String := "e1";
   procedure printf (s : String; p : Exception_Id);
   pragma Import (C, printf);
   procedure Test1 (a : Integer; e : Exception_Id) is
      e1_Def : aliased System.Standard_Library.Exception_Data :=
        (Not_Handled_By_Others => False,
         Lang                  => 'A',
         Name_Length           => e1_name'Length,
         Full_Name             => e1_name'Address,
         HTable_Ptr            => null,
         Import_Code           => 0,
         Raise_Hook            => null);
      function C is new Ada.Unchecked_Conversion (
         System.Standard_Library.Exception_Data_Ptr,
         Exception_Id);
   begin
      Put_Line ("begin" & a'Img);
      --  Put_Line (System.Exception_Table.Registered_Exceptions_Count'Img);
      case a is
         when 0 =>
            test1 (a + 1, e);
         when 1 =>
            test1 (a + 1, C (e1_Def'Unchecked_Access));
         when 2 =>
            test1 (a + 1, e);
         when 3 =>
            Raise_Exception (e);
         when others =>
            null;
      end case;
      --  Put_Line (System.Exception_Table.Registered_Exceptions_Count'Img);
      Put_Line ("end" & a'Img);
   exception
      when e : others =>
         if Exception_Identity (e) /= C (e1_Def'Unchecked_Access) then
            raise;
         end if;
         Put_Line ("catched" & a'Img);
   end Test1;
begin
   Test1 (0, Program_Error'Identity);
end MultiExit;
 ./multiexit 
begin 0
begin 1
begin 2
begin 3
catched 1 ※普通に例外を定義すると3になる
end 0

普通のコンパイラではローカルに例外を定義しても、ローカル変数とは異なり関数呼び出しのたびにローカルに例外を特定するための情報が再生成されるわけではなく、例外情報自体はstaticなため、再帰では同じものとして扱われてしまって例外をlongjmpの代わりに使うには不足なわけです。必ず最内周で補足されてしまいます。つまりjmp_bufをローカル変数に配置できるlongjmpは偉い。longjmp>>>(本当にローカルであることの壁)>>>>>例外。
それで例外に無理やりlongjmpのような動作をさせることにしました。
何をやっているかといいますと、例外を識別するためのException_Data構造体をローカル変数として確保します。で、AdaではException_IdはException_Data構造体のアドレスそのもののため、キャストして例外として投げることができます。
受けとる側では、当たり前ですが文法上例外として定義した物以外は受け取れませんので、when e : othersで全部受けてからif文でException_Id(Exception_Data構造体のアドレス)を判定して、他の例外が来ていたら再度投げ直します。インストラクションポインタと例外コードから飛び先をテーブル引きだけで特定するZCXの価値が半減してますが仕方ないです。というよりもZCXを使う以上は例外の情報が静的に.oに埋め込まれますので、例外の個数を動的に増やすことはできず、こうしてif文で判断できる形に持っていくしかないのかも。
クラスを例外として扱う言語では、真・ローカルクラスを実現するためにはVMTをコピーしたりいろいろ面倒そうですので、ebpを例外クラスのフィールドに持たせてしまってif文ではそれを見る、のが簡単かもです。
これで例外でlongjmpの真似ができます。素直にlongjmp使った方が賢いです。ありがとうございました。