emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/wisi 66d7e59 27/35: In ada-mode, wisi: release Ada mode


From: Stefan Monnier
Subject: [elpa] externals/wisi 66d7e59 27/35: In ada-mode, wisi: release Ada mode 6.2.0, wisi 1.2.0.
Date: Sat, 28 Nov 2020 14:47:56 -0500 (EST)

branch: externals/wisi
commit 66d7e59ad20c99dbfbfd17fe9dcd56588c727ce4
Author: Stephen Leake <stephen_leake@stephe-leake.org>
Commit: Stephen Leake <stephen_leake@stephe-leake.org>

    In ada-mode, wisi: release Ada mode 6.2.0, wisi 1.2.0.
---
 NEWS                                               |   45 +-
 README                                             |    2 +-
 emacs_wisi_common_parse.adb                        |  507 ++++++
 emacs_wisi_common_parse.ads                        |  154 ++
 gen_emacs_wisi_lr_parse.adb                        |   39 +
 gen_emacs_wisi_lr_parse.ads                        |   55 +
 gen_emacs_wisi_lr_text_rep_parse.adb               |   44 +
 gen_emacs_wisi_lr_text_rep_parse.ads               |   50 +
 gen_emacs_wisi_packrat_parse.adb                   |  180 ++
 gen_emacs_wisi_packrat_parse.ads                   |   42 +
 gen_run_wisi_libadalang_parse.adb                  |  176 ++
 gen_run_wisi_libadalang_parse.ads                  |   30 +
 gen_run_wisi_lr_parse.adb                          |   37 +
 gen_run_wisi_lr_parse.ads                          |   42 +
 gen_run_wisi_lr_text_rep_parse.adb                 |   44 +
 gen_run_wisi_lr_text_rep_parse.ads                 |   44 +
 gen_run_wisi_packrat_parse.adb                     |  241 +++
 gen_run_wisi_packrat_parse.ads                     |   36 +
 run_wisi_common_parse.adb                          |  343 ++++
 run_wisi_common_parse.ads                          |   73 +
 sal-gen_bounded_definite_queues.adb                |  119 +-
 sal-gen_bounded_definite_queues.ads                |  106 +-
 ...l-gen_bounded_definite_stacks-gen_image_aux.adb |   27 +-
 sal-gen_bounded_definite_stacks-gen_image_aux.ads  |   27 +
 sal-gen_bounded_definite_stacks.adb                |   82 +
 sal-gen_bounded_definite_stacks.ads                |  103 ++
 sal-gen_bounded_definite_vectors-gen_image.adb     |   79 +-
 sal-gen_bounded_definite_vectors-gen_refs.adb      |   35 +
 sal-gen_bounded_definite_vectors-gen_refs.ads      |   54 +
 sal-gen_bounded_definite_vectors-gen_sorted.ads    |   52 -
 sal-gen_bounded_definite_vectors.adb               |  344 ++--
 sal-gen_bounded_definite_vectors.ads               |  299 ++--
 ...unded_definite_vectors_sorted-gen_image_aux.adb |   20 +-
 ...unded_definite_vectors_sorted-gen_image_aux.ads |   23 +
 ...en_bounded_definite_vectors_sorted-gen_refs.adb |   29 +
 ...en_bounded_definite_vectors_sorted-gen_refs.ads |   39 +
 ... => sal-gen_bounded_definite_vectors_sorted.adb |  172 +-
 sal-gen_bounded_definite_vectors_sorted.ads        |   85 +
 sal-gen_definite_doubly_linked_lists.adb           |   55 +-
 sal-gen_definite_doubly_linked_lists.ads           |   43 +-
 sal-gen_definite_doubly_linked_lists_sorted.adb    | 1084 ++++++------
 sal-gen_definite_doubly_linked_lists_sorted.ads    |  372 +++--
 sal-gen_graphs.adb                                 |   21 +-
 sal-gen_indefinite_doubly_linked_lists.adb         |  412 ++---
 sal-gen_indefinite_doubly_linked_lists.ads         |   40 +-
 sal-gen_unbounded_definite_min_heaps_fibonacci.adb |   45 +-
 sal-gen_unbounded_definite_min_heaps_fibonacci.ads |   27 +-
 sal-gen_unbounded_definite_queues.adb              |    4 +-
 sal-gen_unbounded_definite_queues.ads              |    6 +-
 sal-gen_unbounded_definite_red_black_trees.adb     |   34 +-
 sal-gen_unbounded_definite_red_black_trees.ads     |   57 +-
 sal-gen_unbounded_definite_stacks.adb              |   12 +-
 sal-gen_unbounded_definite_stacks.ads              |   28 +-
 sal-gen_unbounded_definite_vectors.adb             |   59 +-
 sal-gen_unbounded_definite_vectors.ads             |  471 +++---
 sal-gen_unbounded_definite_vectors_sorted.adb      |  368 +++++
 sal-gen_unbounded_definite_vectors_sorted.ads      |  170 ++
 sal.adb                                            |    2 +-
 wisi-compile.el                                    |  225 ---
 wisi-elisp-lexer.el                                |  393 -----
 wisi-elisp-parse.el                                | 1721 --------------------
 wisi-parse-common.el                               |  778 ++++-----
 wisi-process-parse.el                              | 1617 +++++++++---------
 wisi-run-indent-test.el                            |  631 +++----
 wisi-tests.el                                      |   30 +-
 wisi.adb                                           |  582 +++----
 wisi.ads                                           |  115 +-
 wisi.el                                            |  151 +-
 wisitoken.gpr => wisi.gpr                          |   47 +-
 wisitoken-bnf-generate.adb                         |   21 +-
 wisitoken-bnf-generate_grammar.adb                 |   14 +-
 wisitoken-bnf-generate_utils.adb                   |  196 +--
 wisitoken-bnf-generate_utils.ads                   |   22 +-
 wisitoken-bnf-output_ada_common.adb                |   74 +-
 wisitoken-bnf-output_ada_emacs.adb                 |  129 +-
 wisitoken-bnf-output_elisp.adb                     |  293 ----
 wisitoken-bnf-output_elisp_common.adb              |   58 +-
 wisitoken-bnf-output_elisp_common.ads              |    7 +-
 wisitoken-bnf.adb                                  |   15 +-
 wisitoken-bnf.ads                                  |   26 +-
 wisitoken-generate-lr-lalr_generate.adb            |    9 +-
 wisitoken-generate-lr-lr1_generate.adb             |    2 +-
 wisitoken-generate-lr.adb                          |  228 +--
 wisitoken-generate-lr.ads                          |    4 +-
 wisitoken-generate-lr1_items.adb                   |    4 +-
 wisitoken-generate.adb                             |    3 +-
 wisitoken-parse-lr-mckenzie_recover-base.adb       |   31 +-
 wisitoken-parse-lr-mckenzie_recover-base.ads       |    3 +-
 wisitoken-parse-lr-mckenzie_recover-explore.adb    |  613 ++++---
 wisitoken-parse-lr-mckenzie_recover-parse.adb      |   75 +-
 wisitoken-parse-lr-mckenzie_recover-parse.ads      |   19 +-
 wisitoken-parse-lr-mckenzie_recover.adb            |  521 +++---
 wisitoken-parse-lr-mckenzie_recover.ads            |   59 +-
 wisitoken-parse-lr-parser.adb                      |   60 +-
 wisitoken-parse-lr-parser.ads                      |   12 +-
 wisitoken-parse-lr-parser_lists.adb                |   36 +-
 wisitoken-parse-lr-parser_lists.ads                |    1 +
 wisitoken-parse-lr-parser_no_recover.adb           |   13 +-
 wisitoken-parse-lr.adb                             |  408 ++---
 wisitoken-parse-lr.ads                             |  136 +-
 wisitoken-parse-packrat-generated.adb              |    6 +-
 wisitoken-parse-packrat-procedural.adb             |    5 +-
 wisitoken-parse.adb                                |    4 +-
 wisitoken-syntax_trees-lr_utils.adb                |  220 +++
 wisitoken-syntax_trees-lr_utils.ads                |   88 +
 wisitoken-syntax_trees.adb                         |   56 +-
 wisitoken-syntax_trees.ads                         |   19 +-
 wisitoken-wisi_ada.adb                             |   22 +-
 wisitoken.ads                                      |   13 +-
 wisitoken_grammar_main.adb                         |  177 +-
 wisitoken_grammar_main.ads                         |    1 +
 wisitoken_grammar_runtime.adb                      |  152 +-
 112 files changed, 9032 insertions(+), 8302 deletions(-)

diff --git a/NEWS b/NEWS
index 28f737c..bb8f8c6 100644
--- a/NEWS
+++ b/NEWS
@@ -1,13 +1,56 @@
 GNU Emacs wisi NEWS -- history of user-visible changes.
 
 Copyright (C) 2019 Free Software Foundation, Inc.
-See the end of the file for license conditions.
 
 Please send wisi bug reports to bug-gnu-emacs@gnu.org, with
 'wisi' in the subject. If possible, use M-x report-emacs-bug.
 
 
 * wisi
+13 Aug 2019
+
+** parser process protocol version 3
+
+** Support for the elisp parser and lexer is deleted; only the Ada
+   process parser is supported.
+
+** New user variable `wisi-indent-context-lines' specifies the minimum
+   number of lines before point to include in a parse for indenting a
+   single line. This gives better results when indenting in a nested
+   'if then else', for example. The default value is 0; you must
+   change it to see an effect.
+
+** Error correction is faster by approximately 30%;
+   %mckenzie_enqueue_limit can be raised accordingly.
+
+** %mckenzie_enqueue_limit is now applied to the total of all parsers
+    in recovery; previously, it was applied to each parser separately.
+    Applying to the total gives a more consistent maximum user wait
+    time for recovery, at the cost of not finding solutions when there
+    are many parsers involved. You may want to increase
+    %mckenzie_enqueue_limit for this as well.
+
+** In the process parser, `wisi-statement-start' now sets 'containing'
+   in all contained caches to the start token, if not set already;
+   previously it only did this if the token was mentioned in the
+   `wisi-statement-start' action. This makes `wisi-containing-action'
+   unnecessary.
+
+** `wisi-containing-action' is deleted.
+
+** An argument of `wisi-motion-action' that is a vector may now
+   provide only one token ID. That token ID is searched for in the
+   containing token region, and the motion token chain starting at the
+   first one found is included in the current right hand side motion
+   token chain.
+
+** The elisp parser and lexer are deleted.
+
+** The process parser supports a new parse command `wisi-refactor',
+   which returns a new message "Edit". It is intended for performing
+   syntax-guided refactoring of code statements.
+
+* wisi 2.1.1
 11 Jul 2019
 
 ** parser process protocol version 3
diff --git a/README b/README
index f7fc4eb..c7176d2 100644
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-Emacs wisi package 2.1.1
+Emacs wisi package 2.2.0
 
 The wisi package provides utilities for using generalized LALR parsers
 (in elisp or external processes) to do indentation, fontification, and
diff --git a/emacs_wisi_common_parse.adb b/emacs_wisi_common_parse.adb
new file mode 100644
index 0000000..f74ce37
--- /dev/null
+++ b/emacs_wisi_common_parse.adb
@@ -0,0 +1,507 @@
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2018 - 2019 Free Software Foundation, Inc.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with Ada.Command_Line;
+with Ada.Directories;
+with Ada.Exceptions;
+with Ada.Strings.Fixed;
+with Ada.Text_IO;
+with GNAT.OS_Lib;
+with GNAT.Traceback.Symbolic;
+with SAL;
+with System.Multiprocessors;
+with System.Storage_Elements;
+with WisiToken.Lexer;
+package body Emacs_Wisi_Common_Parse is
+
+   procedure Usage (Name : in String)
+   is
+      use Ada.Text_IO;
+   begin
+      Put_Line ("usage: " & Name & "[--recover-log <file-name>]");
+      Put_Line ("enters a loop waiting for commands:");
+      Put_Line ("Prompt is '" & Prompt & "'");
+      Put_Line ("commands are case sensitive");
+      Put_Line ("See wisi-process-parse.el *--send-parse, *--send-noop for 
arguments.");
+   end Usage;
+
+   procedure Read_Input (A : System.Address; N : Integer)
+   is
+      use System.Storage_Elements;
+
+      B         : System.Address := A;
+      Remaining : Integer        := N;
+      Read      : Integer;
+   begin
+      --  We use GNAT.OS_Lib because it does not buffer input, so it runs
+      --  under Emacs nicely; GNAT Text_IO does not return text until
+      --  some fairly large buffer is filled.
+      --
+      --  With GNAT GPL 2016, GNAT.OS_Lib.Read does _not_ wait for all N
+      --  bytes or EOF; it returns as soon as it gets some bytes.
+      loop
+         Read := GNAT.OS_Lib.Read (GNAT.OS_Lib.Standin, B, Remaining);
+         if Read = 0 then
+            --  Pipe closed; probably parent Emacs crashed. Force exit.
+            raise SAL.Programmer_Error with "input pipe closed";
+         end if;
+         Remaining := Remaining - Read;
+         exit when Remaining <= 0;
+         B := B + Storage_Offset (Read);
+      end loop;
+   end Read_Input;
+
+   function Get_Command_Length return Integer
+   is
+      Temp : aliased String (1 .. 3) := (others => ' '); -- initialize for 
error message
+   begin
+      Read_Input (Temp'Address, Temp'Length);
+      return Integer'Value (Temp);
+   exception
+   when Constraint_Error =>
+      --  From Integer'Value
+      raise Protocol_Error with "invalid command byte count; '" & Temp & "'";
+   end Get_Command_Length;
+
+   function Get_String
+     (Source : in     String;
+      Last   : in out Integer)
+     return String
+   is
+      use Ada.Strings.Fixed;
+      First : constant Integer := Index
+        (Source  => Source,
+         Pattern => """",
+         From    => Last + 1);
+   begin
+      Last := Index
+        (Source  => Source,
+         Pattern => """",
+         From    => First + 1);
+
+      if First = 0 or Last = 0 then
+         raise Protocol_Error with "no '""' found for string";
+      end if;
+
+      return Source (First + 1 .. Last - 1);
+   end Get_String;
+
+   function Get_Integer
+     (Source : in     String;
+      Last   : in out Integer)
+     return Integer
+   is
+      use Ada.Strings.Fixed;
+      First : constant Integer := Last + 2; -- final char of previous item, 
space
+   begin
+      Last := Index
+        (Source  => Source,
+         Pattern => " ",
+         From    => First);
+
+      if Last = 0 then
+         Last := Source'Last;
+      else
+         Last := Last - 1;
+      end if;
+
+      return Integer'Value (Source (First .. Last));
+   exception
+   when others =>
+      Ada.Text_IO.Put_Line ("bad integer '" & Source (First .. Source'Last) & 
"'");
+      raise;
+   end Get_Integer;
+
+   function Get_Process_Start_Params return Process_Start_Params
+   is
+      use Ada.Command_Line;
+      procedure Put_Usage
+      is
+         use Ada.Text_IO;
+      begin
+         Put_Line (Standard_Error, "process start args:");
+         Put_Line (Standard_Error, "--help : put this help");
+         Put_Line (Standard_Error, "--recover-log <file_name> : log recover 
actions to file");
+      end Put_Usage;
+
+      Next_Arg : Integer := 1;
+   begin
+      return Result : Process_Start_Params do
+         loop
+            exit when Next_Arg > Argument_Count;
+
+            if Next_Arg <= Argument_Count and then Argument (Next_Arg) = 
"--help" then
+               Put_Usage;
+               raise Finish;
+
+            elsif Next_Arg + 1 <= Argument_Count and then Argument (Next_Arg) 
= "--recover-log" then
+               Result.Recover_Log_File_Name := 
Ada.Strings.Unbounded.To_Unbounded_String (Argument (Next_Arg + 1));
+               Next_Arg := Next_Arg + 2;
+            end if;
+         end loop;
+      end return;
+   end Get_Process_Start_Params;
+
+   function Get_Parse_Params (Command_Line : in String; Last : in out Integer) 
return Parse_Params
+   is
+      use WisiToken;
+   begin
+      return Result : Parse_Params do
+         --  We don't use an aggregate, to enforce execution order.
+         --  Match wisi-process-parse.el wisi-process--send-parse
+
+         Result.Post_Parse_Action    := Wisi.Post_Parse_Action_Type'Val 
(Get_Integer (Command_Line, Last));
+         Result.Source_File_Name     := +Get_String (Command_Line, Last);
+         Result.Begin_Byte_Pos       := Get_Integer (Command_Line, Last);
+
+         --  Emacs end is after last char.
+         Result.End_Byte_Pos         := Get_Integer (Command_Line, Last) - 1;
+
+         Result.Goal_Byte_Pos        := Get_Integer (Command_Line, Last);
+         Result.Begin_Char_Pos       := WisiToken.Buffer_Pos (Get_Integer 
(Command_Line, Last));
+         Result.Begin_Line           := WisiToken.Line_Number_Type 
(Get_Integer (Command_Line, Last));
+         Result.End_Line             := WisiToken.Line_Number_Type 
(Get_Integer (Command_Line, Last));
+         Result.Begin_Indent         := Get_Integer (Command_Line, Last);
+         Result.Partial_Parse_Active := 1 = Get_Integer (Command_Line, Last);
+         Result.Debug_Mode           := 1 = Get_Integer (Command_Line, Last);
+         Result.Parse_Verbosity      := Get_Integer (Command_Line, Last);
+         Result.McKenzie_Verbosity   := Get_Integer (Command_Line, Last);
+         Result.Action_Verbosity     := Get_Integer (Command_Line, Last);
+         Result.McKenzie_Disable     := Get_Integer (Command_Line, Last);
+         Result.Task_Count           := Get_Integer (Command_Line, Last);
+         Result.Check_Limit          := Get_Integer (Command_Line, Last);
+         Result.Enqueue_Limit        := Get_Integer (Command_Line, Last);
+         Result.Max_Parallel         := Get_Integer (Command_Line, Last);
+         Result.Byte_Count           := Get_Integer (Command_Line, Last);
+      end return;
+   end Get_Parse_Params;
+
+   function Get_Refactor_Params (Command_Line : in String; Last : in out 
Integer) return Refactor_Params
+   is
+      use WisiToken;
+   begin
+      return Result : Refactor_Params do
+         --  We don't use an aggregate, to enforce execution order.
+         --  Match wisi-process-parse.el wisi-process--send-refactor
+
+         Result.Refactor_Action    := Get_Integer (Command_Line, Last);
+         Result.Source_File_Name   := +Get_String (Command_Line, Last);
+         Result.Parse_Region.First := WisiToken.Buffer_Pos (Get_Integer 
(Command_Line, Last));
+         Result.Parse_Region.Last  := WisiToken.Buffer_Pos (Get_Integer 
(Command_Line, Last) - 1);
+
+         Result.Edit_Begin           := WisiToken.Buffer_Pos (Get_Integer 
(Command_Line, Last));
+         Result.Parse_Begin_Char_Pos := WisiToken.Buffer_Pos (Get_Integer 
(Command_Line, Last));
+         Result.Parse_Begin_Line     := WisiToken.Line_Number_Type 
(Get_Integer (Command_Line, Last));
+         Result.Parse_End_Line       := WisiToken.Line_Number_Type 
(Get_Integer (Command_Line, Last));
+         Result.Debug_Mode           := 1 = Get_Integer (Command_Line, Last);
+         Result.Parse_Verbosity      := Get_Integer (Command_Line, Last);
+         Result.Action_Verbosity     := Get_Integer (Command_Line, Last);
+         Result.Max_Parallel         := Get_Integer (Command_Line, Last);
+         Result.Byte_Count           := Get_Integer (Command_Line, Last);
+      end return;
+   end Get_Refactor_Params;
+
+   procedure Process_Stream
+     (Name                      : in     String;
+      Language_Protocol_Version : in     String;
+      Partial_Parse_Active      : in out Boolean;
+      Params                    : in     Process_Start_Params;
+      Parser                    : in out WisiToken.Parse.LR.Parser.Parser;
+      Parse_Data                : in out Wisi.Parse_Data_Type'Class;
+      Descriptor                : in     WisiToken.Descriptor)
+   is
+      use Ada.Text_IO;
+      use WisiToken; -- "+", "-" Unbounded_string
+
+      procedure Cleanup
+      is begin
+         if Is_Open (Parser.Recover_Log_File) then
+            Close (Parser.Recover_Log_File);
+         end if;
+      end Cleanup;
+
+   begin
+      declare
+         use Ada.Directories;
+         use Ada.Strings.Unbounded;
+      begin
+         if Length (Params.Recover_Log_File_Name) > 0 then
+            Put_Line (";; logging to '" & (-Params.Recover_Log_File_Name) & 
"'");
+            --  to Current_Output, visible from Emacs
+
+            if Exists (-Params.Recover_Log_File_Name) then
+               Open (Parser.Recover_Log_File, Append_File, 
-Params.Recover_Log_File_Name);
+            else
+               Create (Parser.Recover_Log_File, Out_File, 
-Params.Recover_Log_File_Name);
+            end if;
+         end if;
+      end;
+
+      Parser.Trace.Set_Prefix (";; "); -- so debug messages don't confuse 
Emacs.
+
+      Put_Line
+        (Name & " protocol: process version " & Protocol_Version & " language 
version " & Language_Protocol_Version);
+
+      --  Read commands and tokens from standard_input via GNAT.OS_Lib,
+      --  send results to standard_output.
+      loop
+         Put (Prompt); Flush;
+         declare
+            Command_Length : constant Integer := Get_Command_Length;
+            Command_Line   : aliased String (1 .. Command_Length);
+            Last           : Integer;
+
+            function Match (Target : in String) return Boolean
+            is begin
+               Last := Command_Line'First + Target'Length - 1;
+               return Last <= Command_Line'Last and then Command_Line 
(Command_Line'First .. Last) = Target;
+            end Match;
+         begin
+            Read_Input (Command_Line'Address, Command_Length);
+
+            Put_Line (";; " & Command_Line);
+
+            if Match ("parse") then
+               --  Args: see wisi-process-parse.el 
wisi-process-parse--send-parse
+               --  Input: <source text>
+               --  Response:
+               --  [response elisp vector]...
+               --  [elisp error form]...
+               --  prompt
+               declare
+                  Params : constant Parse_Params := Get_Parse_Params 
(Command_Line, Last);
+                  Buffer : Ada.Strings.Unbounded.String_Access;
+
+                  procedure Clean_Up
+                  is
+                     use all type SAL.Base_Peek_Type;
+                  begin
+                     Parser.Lexer.Discard_Rest_Of_Input;
+                     if Parser.Parsers.Count > 0 then
+                        Parse_Data.Put
+                          (Parser.Lexer.Errors,
+                           Parser.Parsers.First.State_Ref.Errors,
+                           Parser.Parsers.First.State_Ref.Tree);
+                     end if;
+                     Ada.Strings.Unbounded.Free (Buffer);
+                  end Clean_Up;
+
+               begin
+                  Trace_Parse    := Params.Parse_Verbosity;
+                  Trace_McKenzie := Params.McKenzie_Verbosity;
+                  Trace_Action   := Params.Action_Verbosity;
+                  Debug_Mode     := Params.Debug_Mode;
+
+                  Partial_Parse_Active := Params.Partial_Parse_Active;
+
+                  if WisiToken.Parse.LR.McKenzie_Defaulted (Parser.Table.all) 
then
+                     --  There is no McKenzie information; don't override that.
+                     null;
+                  elsif Params.McKenzie_Disable = -1 then
+                     --  Use default
+                     Parser.Enable_McKenzie_Recover := True;
+                  else
+                     Parser.Enable_McKenzie_Recover := Params.McKenzie_Disable 
= 0;
+                  end if;
+
+                  Parse_Data.Initialize
+                    (Post_Parse_Action => Params.Post_Parse_Action,
+                     Lexer             => Parser.Lexer,
+                     Descriptor        => Descriptor'Unrestricted_Access,
+                     Base_Terminals    => Parser.Terminals'Unrestricted_Access,
+                     Begin_Line        => Params.Begin_Line,
+                     End_Line          => Params.End_Line,
+                     Begin_Indent      => Params.Begin_Indent,
+                     Params            => Command_Line (Last + 2 .. 
Command_Line'Last));
+
+                  if Params.Task_Count > 0 then
+                     Parser.Table.McKenzie_Param.Task_Count := 
System.Multiprocessors.CPU_Range (Params.Task_Count);
+                  end if;
+                  if Params.Check_Limit > 0 then
+                     Parser.Table.McKenzie_Param.Check_Limit := 
Base_Token_Index (Params.Check_Limit);
+                  end if;
+                  if Params.Enqueue_Limit > 0 then
+                     Parser.Table.McKenzie_Param.Enqueue_Limit := 
Params.Enqueue_Limit;
+                  end if;
+
+                  if Params.Max_Parallel > 0 then
+                     Parser.Max_Parallel := SAL.Base_Peek_Type 
(Params.Max_Parallel);
+                  end if;
+
+                  Buffer := new String (Params.Begin_Byte_Pos .. 
Params.End_Byte_Pos);
+
+                  Read_Input (Buffer (Params.Begin_Byte_Pos)'Address, 
Params.Byte_Count);
+
+                  Parser.Lexer.Reset_With_String_Access
+                    (Buffer, Params.Source_File_Name, Params.Begin_Char_Pos, 
Params.Begin_Line);
+                  begin
+                     Parser.Parse;
+                  exception
+                  when WisiToken.Partial_Parse =>
+                     null;
+                  end;
+                  Parser.Execute_Actions;
+                  Parse_Data.Put (Parser);
+                  Clean_Up;
+
+               exception
+               when Syntax_Error =>
+                  Clean_Up;
+                  Put_Line ("(parse_error)");
+
+               when E : Parse_Error =>
+                  Clean_Up;
+                  Put_Line ("(parse_error """ & 
Ada.Exceptions.Exception_Message (E) & """)");
+
+               when E : Fatal_Error =>
+                  Clean_Up;
+                  Put_Line ("(error """ & Ada.Exceptions.Exception_Message (E) 
& """)");
+               end;
+
+            elsif Match ("refactor") then
+               --  Args: see wisi-process-parse.el 
wisi-process-parse--send-refactor
+               --  Input: <source text>
+               --  Response:
+               --  [edit elisp vector]...
+               --  prompt
+               declare
+                  Params : constant Refactor_Params := Get_Refactor_Params 
(Command_Line, Last);
+                  Buffer : Ada.Strings.Unbounded.String_Access;
+
+                  procedure Clean_Up
+                  is
+                     use all type SAL.Base_Peek_Type;
+                  begin
+                     Parser.Lexer.Discard_Rest_Of_Input;
+                     if Parser.Parsers.Count > 0 then
+                        Parse_Data.Put
+                          (Parser.Lexer.Errors,
+                           Parser.Parsers.First.State_Ref.Errors,
+                           Parser.Parsers.First.State_Ref.Tree);
+                     end if;
+                     Ada.Strings.Unbounded.Free (Buffer);
+                  end Clean_Up;
+
+               begin
+                  Trace_Parse  := Params.Parse_Verbosity;
+                  Trace_Action := Params.Action_Verbosity;
+                  Debug_Mode   := Params.Debug_Mode;
+
+                  Partial_Parse_Active := True;
+
+                  Parse_Data.Initialize
+                    (Post_Parse_Action => Wisi.Navigate, -- mostly ignored
+                     Lexer             => Parser.Lexer,
+                     Descriptor        => Descriptor'Unrestricted_Access,
+                     Base_Terminals    => Parser.Terminals'Unrestricted_Access,
+                     Begin_Line        => Params.Parse_Begin_Line,
+                     End_Line          => Params.Parse_End_Line,
+                     Begin_Indent      => 0,
+                     Params            => "");
+
+                  if Params.Max_Parallel > 0 then
+                     Parser.Max_Parallel := SAL.Base_Peek_Type 
(Params.Max_Parallel);
+                  end if;
+
+                  Buffer := new String (Integer (Params.Parse_Region.First) .. 
Integer (Params.Parse_Region.Last));
+
+                  Read_Input (Buffer (Buffer'First)'Address, 
Params.Byte_Count);
+
+                  Parser.Lexer.Reset_With_String_Access
+                    (Buffer, Params.Source_File_Name, 
Params.Parse_Begin_Char_Pos, Params.Parse_Begin_Line);
+                  begin
+                     Parser.Parse;
+                  exception
+                  when WisiToken.Partial_Parse =>
+                     null;
+                  end;
+                  Parser.Execute_Actions;
+                  Parse_Data.Refactor (Parser.Parsers.First_State_Ref.Tree, 
Params.Refactor_Action, Params.Edit_Begin);
+                  Clean_Up;
+
+               exception
+               when Syntax_Error =>
+                  Clean_Up;
+                  Put_Line ("(parse_error ""refactor " & 
Params.Parse_Region.First'Image &
+                              Params.Parse_Region.Last'Image & ": syntax 
error"")");
+
+               when E : Parse_Error =>
+                  Clean_Up;
+                  Put_Line ("(parse_error ""refactor " & 
Params.Parse_Region.First'Image &
+                              Params.Parse_Region.Last'Image & ": " & 
Ada.Exceptions.Exception_Message (E) & """)");
+
+               when E : others => -- includes Fatal_Error
+                  Clean_Up;
+                  Put_Line ("(error """ & Ada.Exceptions.Exception_Message (E) 
& """)");
+               end;
+
+            elsif Match ("noop") then
+               --  Args: <source byte count>
+               --  Input: <source text>
+               --  Response: prompt
+               declare
+                  Byte_Count  : constant Integer                             
:= Get_Integer (Command_Line, Last);
+                  Buffer      : constant Ada.Strings.Unbounded.String_Access 
:= new String (1 .. Byte_Count);
+                  Token       : Base_Token;
+                  Lexer_Error : Boolean;
+                  pragma Unreferenced (Lexer_Error);
+               begin
+                  Token.ID := Invalid_Token_ID;
+                  Read_Input (Buffer (1)'Address, Byte_Count);
+
+                  Parser.Lexer.Reset_With_String_Access (Buffer, +"");
+                  loop
+                     exit when Token.ID = Parser.Trace.Descriptor.EOI_ID;
+                     Lexer_Error := Parser.Lexer.Find_Next (Token);
+                  end loop;
+               exception
+               when Syntax_Error =>
+                  Parser.Lexer.Discard_Rest_Of_Input;
+               end;
+
+            elsif Match ("quit") then
+               exit;
+
+            else
+               Put_Line ("(error ""bad command: '" & Command_Line & "'"")");
+            end if;
+         exception
+         when E : Protocol_Error =>
+            --  don't exit the loop; allow debugging bad elisp
+            Put_Line ("(error ""protocol error "": " & 
Ada.Exceptions.Exception_Message (E) & """)");
+         end;
+      end loop;
+      Cleanup;
+   exception
+   when Finish =>
+      null;
+
+   when E : others =>
+      Cleanup;
+      Ada.Command_Line.Set_Exit_Status (Ada.Command_Line.Failure);
+      New_Line (2);
+      Put_Line
+        ("(error ""unhandled exception: " & Ada.Exceptions.Exception_Name (E) 
& ": " &
+           Ada.Exceptions.Exception_Message (E) & """)");
+      Put_Line (GNAT.Traceback.Symbolic.Symbolic_Traceback (E));
+
+   end Process_Stream;
+
+end Emacs_Wisi_Common_Parse;
diff --git a/emacs_wisi_common_parse.ads b/emacs_wisi_common_parse.ads
new file mode 100644
index 0000000..1d75de6
--- /dev/null
+++ b/emacs_wisi_common_parse.ads
@@ -0,0 +1,154 @@
+--  Abstract :
+--
+--  Common utilities for Gen_Emacs_Wisi_*_Parse
+--
+--  Copyright (C) 2018 - 2019 Free Software Foundation, Inc.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with Ada.Strings.Unbounded;
+with System;
+with Wisi;
+with WisiToken.Parse.LR.Parser;
+package Emacs_Wisi_Common_Parse is
+
+   Protocol_Version : constant String := "4";
+   --  Protocol_Version defines the data sent between elisp and the
+   --  background process, except for the language-specific parameters,
+   --  which are defined by the Language_Protocol_Version parameter to
+   --  Parse_Stream, below.
+   --
+   --  This value must match wisi-process-parse.el
+   --  wisi-process-parse-protocol-version.
+   --
+   --  See wisi-process-parse.el functions, and this package body, for
+   --  the implementation of the protocol.
+   --
+   --  Only changes once per wisi release. Increment as soon as required,
+   --  record new version in NEWS-wisi.text.
+
+   Prompt : constant String := ";;> ";
+
+   Protocol_Error : exception;
+   Finish         : exception;
+
+   procedure Usage (Name : in String);
+
+   procedure Read_Input (A : System.Address; N : Integer);
+
+   function Get_Command_Length return Integer;
+
+   function Get_String
+     (Source : in     String;
+      Last   : in out Integer)
+     return String;
+
+   function Get_Integer
+     (Source : in     String;
+      Last   : in out Integer)
+     return Integer;
+
+   type Process_Start_Params is record
+      Recover_Log_File_Name : Ada.Strings.Unbounded.Unbounded_String;
+      --  log enabled if non-empty.
+   end record;
+
+   function Get_Process_Start_Params return Process_Start_Params;
+   --  Get from Ada.Command_Line. Handles --help by outputing help,
+   --  raising Finish.
+
+   procedure Process_Stream
+     (Name                      : in     String;
+      Language_Protocol_Version : in     String;
+      Partial_Parse_Active      : in out Boolean;
+      Params                    : in     Process_Start_Params;
+      Parser                    : in out WisiToken.Parse.LR.Parser.Parser;
+      Parse_Data                : in out Wisi.Parse_Data_Type'Class;
+      Descriptor                : in     WisiToken.Descriptor);
+
+   ----------
+   --  Parse command
+
+   type Parse_Params is record
+      Post_Parse_Action : Wisi.Post_Parse_Action_Type;
+      Source_File_Name  : Ada.Strings.Unbounded.Unbounded_String;
+
+      Begin_Byte_Pos : Integer;
+      --  Source file byte position of first char sent; start parse here.
+
+      End_Byte_Pos : Integer;
+      --  Byte position of last char sent.
+
+      Goal_Byte_Pos : Integer;
+      --  Byte position of end of desired parse region; terminate parse at
+      --  or after here.
+
+      Begin_Char_Pos : WisiToken.Buffer_Pos;
+      --  Char position of first char sent.
+
+      Begin_Line : WisiToken.Line_Number_Type;
+      End_Line   : WisiToken.Line_Number_Type;
+      --  Line number of line containing Begin_Byte_Pos, End_Byte_Pos
+
+      Begin_Indent : Integer;
+      --  Indentation of Line_Begin
+
+      Partial_Parse_Active : Boolean;
+      Debug_Mode           : Boolean;
+      Parse_Verbosity      : Integer;
+      McKenzie_Verbosity   : Integer;
+      Action_Verbosity     : Integer;
+      McKenzie_Disable     : Integer;
+      Task_Count           : Integer;
+      Check_Limit          : Integer;
+      Enqueue_Limit        : Integer;
+      Max_Parallel         : Integer;
+      Byte_Count           : Integer;
+      --  Count of bytes of source file sent.
+   end record;
+
+   function Get_Parse_Params (Command_Line : in String; Last : in out Integer) 
return Parse_Params;
+
+   ----------
+   --  Refactor command
+
+   type Refactor_Params is record
+      Refactor_Action  : Positive; -- Language-specific
+      Source_File_Name : Ada.Strings.Unbounded.Unbounded_String;
+
+      Parse_Region : WisiToken.Buffer_Region;
+      --  Source file byte region to parse.
+
+      Edit_Begin : WisiToken.Buffer_Pos;
+      --  Source file byte position at start of expression to refactor.
+
+      Parse_Begin_Char_Pos : WisiToken.Buffer_Pos;
+      --  Char position of first char sent.
+
+      Parse_Begin_Line : WisiToken.Line_Number_Type;
+      Parse_End_Line   : WisiToken.Line_Number_Type;
+      --  Line numbers of lines containing Parse_Begin_Byte_Pos, 
Parse_End_Byte_Pos
+
+      Debug_Mode       : Boolean;
+      Parse_Verbosity  : Integer;
+      Action_Verbosity : Integer;
+      Max_Parallel     : Integer;
+      Byte_Count       : Integer;
+      --  Count of bytes of source file sent.
+   end record;
+
+   function Get_Refactor_Params (Command_Line : in String; Last : in out 
Integer) return Refactor_Params;
+
+end Emacs_Wisi_Common_Parse;
diff --git a/gen_emacs_wisi_lr_parse.adb b/gen_emacs_wisi_lr_parse.adb
new file mode 100644
index 0000000..951a871
--- /dev/null
+++ b/gen_emacs_wisi_lr_parse.adb
@@ -0,0 +1,39 @@
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2014, 2017 - 2019 All Rights Reserved.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with Emacs_Wisi_Common_Parse; use Emacs_Wisi_Common_Parse;
+with WisiToken.Parse.LR.Parser;
+with WisiToken.Text_IO_Trace;
+procedure Gen_Emacs_Wisi_LR_Parse
+is
+   Trace      : aliased WisiToken.Text_IO_Trace.Trace 
(Descriptor'Unrestricted_Access);
+   Parser     : WisiToken.Parse.LR.Parser.Parser;
+   Parse_Data : aliased Parse_Data_Type (Parser.Line_Begin_Token'Access);
+
+   Params : constant Process_Start_Params := Get_Process_Start_Params;
+begin
+   Create_Parser
+     (Parser, Language_Fixes, Language_Matching_Begin_Tokens, 
Language_String_ID_Set,
+      Trace'Unrestricted_Access,
+      Parse_Data'Unchecked_Access);
+
+   Process_Stream (Name, Language_Protocol_Version, Partial_Parse_Active, 
Params, Parser, Parse_Data, Descriptor);
+
+end Gen_Emacs_Wisi_LR_Parse;
diff --git a/gen_emacs_wisi_lr_parse.ads b/gen_emacs_wisi_lr_parse.ads
new file mode 100644
index 0000000..02eac21
--- /dev/null
+++ b/gen_emacs_wisi_lr_parse.ads
@@ -0,0 +1,55 @@
+--  Abstract :
+--
+--  Generic Emacs background process; parse token stream, return
+--  parser actions.
+--
+--  See gen_run_wisi_parse.ads for a standalone version.
+--
+--  References :
+--
+--  [1] On the elisp side, the inter-process protocol is defined in
+--  wisi-process-parse.el, functions wisi-process-parse--send-parse
+--  and wisi-process-parse--execute.
+--
+--  [2] On the Ada side, it is defined here, and in
+--  wisitoken-wisi_runtime.adb
+--
+--  Copyright (C) 2017 - 2019 Free Software Foundation, Inc.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with WisiToken.Parse.LR.Parser;
+with WisiToken.Syntax_Trees;
+with Wisi;
+generic
+   type Parse_Data_Type  is new Wisi.Parse_Data_Type with private;
+
+   Name                           : in String; --  for Usage, error messages.
+   Language_Protocol_Version      : in String; --  Defines language-specific 
parse parameters.
+   Descriptor                     : in WisiToken.Descriptor;
+   Partial_Parse_Active           : in out Boolean;
+   Language_Fixes                 : in 
WisiToken.Parse.LR.Parser.Language_Fixes_Access;
+   Language_Matching_Begin_Tokens : in 
WisiToken.Parse.LR.Parser.Language_Matching_Begin_Tokens_Access;
+   Language_String_ID_Set         : in 
WisiToken.Parse.LR.Parser.Language_String_ID_Set_Access;
+
+   with procedure Create_Parser
+     (Parser                         :    out          
WisiToken.Parse.LR.Parser.Parser;
+      Language_Fixes                 : in              
WisiToken.Parse.LR.Parser.Language_Fixes_Access;
+      Language_Matching_Begin_Tokens : in              
WisiToken.Parse.LR.Parser.Language_Matching_Begin_Tokens_Access;
+      Language_String_ID_Set         : in              
WisiToken.Parse.LR.Parser.Language_String_ID_Set_Access;
+      Trace                          : not null access WisiToken.Trace'Class;
+      User_Data                      : in              
WisiToken.Syntax_Trees.User_Data_Access);
+
+procedure Gen_Emacs_Wisi_LR_Parse;
diff --git a/gen_emacs_wisi_lr_text_rep_parse.adb 
b/gen_emacs_wisi_lr_text_rep_parse.adb
new file mode 100644
index 0000000..f74144c
--- /dev/null
+++ b/gen_emacs_wisi_lr_text_rep_parse.adb
@@ -0,0 +1,44 @@
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2014, 2017 - 2019 All Rights Reserved.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with Ada.Command_Line;
+with Ada.Directories;
+with Emacs_Wisi_Common_Parse; use Emacs_Wisi_Common_Parse;
+
+with WisiToken.Text_IO_Trace;
+procedure Gen_Emacs_Wisi_LR_Text_Rep_Parse
+is
+   use WisiToken; -- "+", "-" Unbounded_string
+
+   Trace      : aliased WisiToken.Text_IO_Trace.Trace 
(Descriptor'Unrestricted_Access);
+   Parser     : WisiToken.Parse.LR.Parser.Parser;
+   Parse_Data : aliased Parse_Data_Type (Parser.Line_Begin_Token'Access);
+
+   Params : constant Process_Start_Params := Get_Process_Start_Params;
+begin
+   Create_Parser
+     (Parser, Language_Fixes, Language_Matching_Begin_Tokens, 
Language_String_ID_Set,
+      Trace'Unrestricted_Access,
+      Parse_Data'Unchecked_Access,
+      Ada.Directories.Containing_Directory (Ada.Command_Line.Command_Name) & 
"/" & Text_Rep_File_Name);
+
+   Process_Stream (Name, Language_Protocol_Version, Partial_Parse_Active, 
Params, Parser, Parse_Data, Descriptor);
+
+end Gen_Emacs_Wisi_LR_Text_Rep_Parse;
diff --git a/gen_emacs_wisi_lr_text_rep_parse.ads 
b/gen_emacs_wisi_lr_text_rep_parse.ads
new file mode 100644
index 0000000..f9ff468
--- /dev/null
+++ b/gen_emacs_wisi_lr_text_rep_parse.ads
@@ -0,0 +1,50 @@
+--  Abstract :
+--
+--  Generic Emacs background process; parse token stream, return
+--  parser actions.
+--
+--  See gen_run_wisi_parse.ads for a standalone version.
+--
+--  References : see gen_emacs_wisi_lr_parse.ads
+--
+--  Copyright (C) 2017, 2018, 2019 Free Software Foundation, Inc.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with WisiToken.Parse.LR.Parser;
+with WisiToken.Syntax_Trees;
+with Wisi;
+generic
+   type Parse_Data_Type  is new Wisi.Parse_Data_Type with private;
+
+   Name                           : in String; --  for Usage, error messages. 
"_wisi_parse" will be appended
+   Language_Protocol_Version      : in String; --  Defines language-specific 
parse parameters.
+   Descriptor                     : in WisiToken.Descriptor;
+   Partial_Parse_Active           : in out Boolean;
+   Language_Fixes                 : in 
WisiToken.Parse.LR.Parser.Language_Fixes_Access;
+   Language_Matching_Begin_Tokens : in 
WisiToken.Parse.LR.Parser.Language_Matching_Begin_Tokens_Access;
+   Language_String_ID_Set         : in 
WisiToken.Parse.LR.Parser.Language_String_ID_Set_Access;
+   Text_Rep_File_Name             : in String;
+
+   with procedure Create_Parser
+     (Parser                         :    out          
WisiToken.Parse.LR.Parser.Parser;
+      Language_Fixes                 : in              
WisiToken.Parse.LR.Parser.Language_Fixes_Access;
+      Language_Matching_Begin_Tokens : in              
WisiToken.Parse.LR.Parser.Language_Matching_Begin_Tokens_Access;
+      Language_String_ID_Set         : in              
WisiToken.Parse.LR.Parser.Language_String_ID_Set_Access;
+      Trace                          : not null access WisiToken.Trace'Class;
+      User_Data                      : in              
WisiToken.Syntax_Trees.User_Data_Access;
+      Text_Rep_File_Name             : in              String);
+
+procedure Gen_Emacs_Wisi_LR_Text_Rep_Parse;
diff --git a/gen_emacs_wisi_packrat_parse.adb b/gen_emacs_wisi_packrat_parse.adb
new file mode 100644
index 0000000..b4e95f6
--- /dev/null
+++ b/gen_emacs_wisi_packrat_parse.adb
@@ -0,0 +1,180 @@
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2018 All Rights Reserved.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with Ada.Command_Line;
+with Ada.Exceptions;
+with Ada.Strings.Fixed;
+with Ada.Strings.Unbounded;
+with Ada.Text_IO; use Ada.Text_IO;
+with Emacs_Wisi_Common_Parse; use Emacs_Wisi_Common_Parse;
+with GNAT.OS_Lib;
+with GNAT.Traceback.Symbolic;
+with System.Storage_Elements;
+with WisiToken.Lexer;
+with WisiToken.Parse.Packrat;
+with WisiToken.Text_IO_Trace;
+procedure Gen_Emacs_Wisi_Parse_Packrat
+is
+   use WisiToken; -- "+", "-" Unbounded_string
+
+   Trace      : aliased WisiToken.Text_IO_Trace.Trace (Descriptor'Access);
+   Parser     : WisiToken.Parse.Packrat.Parser;
+   Parse_Data : aliased Parse_Data_Type (Parser.Line_Begin_Token'Access);
+
+begin
+   Create_Parser (Parser, Trace'Unrestricted_Access, 
Parse_Data'Unchecked_Access);
+
+   declare
+      use Ada.Command_Line;
+   begin
+      case Argument_Count is
+      when 0 =>
+         null;
+
+      when others =>
+         Usage (Name);
+         raise Programmer_Error with "invalid option count: " & Integer'Image 
(Argument_Count);
+      end case;
+   end;
+
+   Put_Line (Name & " " & Version & ", protocol version " & Protocol_Version);
+
+   --  Read commands and tokens from standard_input via GNAT.OS_Lib,
+   --  send results to standard_output.
+   loop
+      Put (Prompt); Flush;
+      declare
+         Command_Length : constant Integer := Get_Command_Length;
+         Command_Line   : aliased String (1 .. Command_Length);
+         Last           : Integer;
+
+         function Match (Target : in String) return Boolean
+         is begin
+            Last := Command_Line'First + Target'Length - 1;
+            return Last <= Command_Line'Last and then Command_Line 
(Command_Line'First .. Last) = Target;
+         end Match;
+      begin
+         Read_Input (Command_Line'Address, Command_Length);
+
+         Put_Line (";; " & Command_Line);
+
+         if Match ("parse") then
+            --  Args: see Usage
+            --  Input: <source text>
+            --  Response:
+            --  [response elisp vector]...
+            --  [elisp error form]...
+            --  prompt
+            declare
+               use Wisi;
+               Cl_Params : constant Command_Line_Params := Get_Cl_Params 
(Command_Line, Last);
+               Buffer    : Ada.Strings.Unbounded.String_Access;
+
+               procedure Clean_Up
+               is begin
+                  Parser.Lexer.Discard_Rest_Of_Input;
+                  Parser.Put_Errors (-Cl_Param.Source_File_Name);
+                  Ada.Strings.Unbounded.Free (Buffer);
+               end Clean_Up;
+
+            begin
+               --  Computing Line_Count in elisp allows parsing in parallel 
with
+               --  sending source text.
+
+               Trace_Parse    := Cl_Params.Parse_Verbosity;
+               Trace_McKenzie := Cl_Params.McKenzie_Verbosity;
+               Trace_Action   := Cl_Params.Action_Verbosity;
+               Debug_Mode     := Cl_Params.Debug_Mode;
+
+               Parse_Data.Initialize
+                 (Post_Parse_Action => Cl_Params.Post_Parse_Action,
+                  Descriptor        => Descriptor'Access,
+                  Source_File_Name  => -Cl_Params.Source_File_Name,
+                  Line_Count        => Cl_Params.Line_Count,
+                  Params            => Command_Line (Last + 2 .. 
Command_Line'Last));
+
+               Buffer := new String (1 .. Cl_Params.Byte_Count);
+               Read_Input (Buffer (1)'Address, Cl_Params.Byte_Count);
+
+               Parser.Lexer.Reset_With_String_Access (Buffer);
+               Parser.Parse;
+               Parser.Execute_Actions;
+               Put (Parse_Data);
+               Clean_Up;
+
+            exception
+            when Syntax_Error =>
+               Clean_Up;
+               Put_Line ("(parse_error)");
+
+            when E : Parse_Error =>
+               Clean_Up;
+               Put_Line ("(parse_error """ & Ada.Exceptions.Exception_Message 
(E) & """)");
+
+            when E : Fatal_Error =>
+               Clean_Up;
+               Put_Line ("(error """ & Ada.Exceptions.Exception_Message (E) & 
""")");
+            end;
+
+         elsif Match ("noop") then
+            --  Args: <source byte count>
+            --  Input: <source text>
+            --  Response: prompt
+            declare
+               Byte_Count  : constant Integer                             := 
Get_Integer (Command_Line, Last);
+               Buffer      : constant Ada.Strings.Unbounded.String_Access := 
new String (1 .. Byte_Count);
+               Token       : Base_Token;
+               Lexer_Error : Boolean;
+               pragma Unreferenced (Lexer_Error);
+            begin
+               Token.ID := Invalid_Token_ID;
+               Read_Input (Buffer (1)'Address, Byte_Count);
+
+               Parser.Lexer.Reset_With_String_Access (Buffer);
+               loop
+                  exit when Token.ID = Parser.Trace.Descriptor.EOF_ID;
+                  Lexer_Error := Parser.Lexer.Find_Next (Token);
+               end loop;
+            exception
+            when Syntax_Error =>
+               Parser.Lexer.Discard_Rest_Of_Input;
+            end;
+
+         elsif Match ("quit") then
+            exit;
+
+         else
+            Put_Line ("(error ""bad command: '" & Command_Line & "'"")");
+         end if;
+      exception
+      when E : Protocol_Error =>
+         --  don't exit the loop; allow debugging bad elisp
+         Put_Line ("(error ""protocol error "": " & 
Ada.Exceptions.Exception_Message (E) & """)");
+      end;
+   end loop;
+exception
+when E : others =>
+   Ada.Command_Line.Set_Exit_Status (Ada.Command_Line.Failure);
+   New_Line (2);
+   Put_Line
+     ("(error ""unhandled exception: " & Ada.Exceptions.Exception_Name (E) & 
": " &
+        Ada.Exceptions.Exception_Message (E) & """)");
+   Put_Line (GNAT.Traceback.Symbolic.Symbolic_Traceback (E));
+end Gen_Emacs_Wisi_Parse_Packrat;
diff --git a/gen_emacs_wisi_packrat_parse.ads b/gen_emacs_wisi_packrat_parse.ads
new file mode 100644
index 0000000..79c69ec
--- /dev/null
+++ b/gen_emacs_wisi_packrat_parse.ads
@@ -0,0 +1,42 @@
+--  Abstract :
+--
+--  Generic Emacs background process; packrat parse token stream,
+--  return parser actions.
+--
+--  See gen_run_wisi_parse_packrat.ads for a standalone version.
+--
+--  References :
+--
+--  See gen_emacs_wisi_parse.ads
+--
+--  Copyright (C) 2018 Free Software Foundation, Inc.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with WisiToken.Parse.Packrat;
+with WisiToken.Syntax_Trees;
+with WisiToken.Wisi_Runtime;
+generic
+   type Parse_Data_Type  is new WisiToken.Wisi_Runtime.Parse_Data_Type with 
private;
+
+   Name       : in String; --  for Usage, error messages. 
"_wisi_parse_packrat" will be appended
+   Descriptor : in WisiToken.Descriptor;
+
+   with procedure Create_Parser
+     (Parser    :    out          WisiToken.Parse.Packrat.Parser;
+      Trace     : not null access WisiToken.Trace'Class;
+      User_Data : in              WisiToken.Syntax_Trees.User_Data_Access);
+
+procedure Gen_Emacs_Wisi_Parse_Packrat;
diff --git a/gen_run_wisi_libadalang_parse.adb 
b/gen_run_wisi_libadalang_parse.adb
new file mode 100644
index 0000000..c154136
--- /dev/null
+++ b/gen_run_wisi_libadalang_parse.adb
@@ -0,0 +1,176 @@
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2018 All Rights Reserved.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with Ada.Command_Line;
+with Ada.Exceptions;
+with Ada.Real_Time;
+with Ada.Strings.Unbounded;
+with Ada.Text_IO; use Ada.Text_IO;
+with GNAT.Traceback.Symbolic;
+with Wisi.Libadalang;
+with WisiToken.Text_IO_Trace;
+procedure Gen_Run_Wisi_Libadalang_Parse
+is
+   use WisiToken; -- Token_ID, "+", "-" Unbounded_string
+
+   Trace      : aliased WisiToken.Text_IO_Trace.Trace 
(Descriptor'Unrestricted_Access);
+   Parser     : Wisi.Libadalang.Parser;
+   Parse_Data : aliased Parse_Data_Type (Parser.Line_Begin_Token'Access);
+
+   procedure Put_Usage
+   is begin
+      Put_Line ("usage: <file_name> <parse_action> [options]");
+      Put_Line ("parse_action: {Navigate | Face | Indent}");
+      Put_Line ("options:");
+      Put_Line ("--verbosity n m : parse, action");
+      Put_Line ("--lang_params <language-specific params>");
+      Put_Line ("--repeat_count n : repeat parse count times, for profiling; 
default 1");
+      New_Line;
+   end Put_Usage;
+
+   Source_File_Name  : Ada.Strings.Unbounded.Unbounded_String;
+   Post_Parse_Action : Wisi.Post_Parse_Action_Type;
+   Lang_Params       : Ada.Strings.Unbounded.Unbounded_String;
+
+   Repeat_Count : Integer := 1;
+   Arg          : Integer;
+   Start        : Ada.Real_Time.Time;
+
+begin
+   declare
+      use Ada.Command_Line;
+   begin
+      if Argument_Count < 1 then
+         Put_Usage;
+         Set_Exit_Status (Failure);
+         return;
+      end if;
+
+      Source_File_Name  := +Ada.Command_Line.Argument (1);
+      Post_Parse_Action := Wisi.Post_Parse_Action_Type'Value 
(Ada.Command_Line.Argument (2));
+      Arg               := 3;
+
+      loop
+         exit when Arg > Argument_Count;
+
+         if Argument (Arg) = "--lang_params" then
+            Lang_Params := +Argument (Arg + 1);
+            Arg := Arg + 2;
+
+         elsif Argument (Arg) = "--repeat_count" then
+            Repeat_Count := Integer'Value (Argument (Arg + 1));
+            Arg := Arg + 2;
+
+         elsif Argument (Arg) = "--verbosity" then
+            WisiToken.Trace_Parse  := Integer'Value (Argument (Arg + 1));
+            WisiToken.Trace_Action := Integer'Value (Argument (Arg + 2));
+            Arg                    := Arg + 3;
+
+         else
+            Put_Line ("unrecognized option: '" & Argument (Arg) & "'");
+            Put_Usage;
+            return;
+         end if;
+      end loop;
+   end;
+
+   Parser.Trace            := Trace'Unrestricted_Access;
+   Parser.Lexer            := new Wisi.Libadalang.Lexer 
(Trace'Unrestricted_Access);
+   Parser.User_Data        := Parse_Data'Unrestricted_Access;
+   Parser.Source_File_Name := Source_File_Name;
+
+   Parser.Tree.Initialize (Shared_Tree => 
Parser.Base_Tree'Unrestricted_Access, Flush => True);
+
+   Parse_Data.Initialize
+     (Post_Parse_Action => Post_Parse_Action,
+      Descriptor        => Descriptor'Unrestricted_Access,
+      Source_File_Name  => -Source_File_Name,
+      Line_Count        => 1, --  FIXME: fix wisi_runtime to not need this!
+      Params            => -Lang_Params);
+
+   if Repeat_Count > 1 then
+      Start := Ada.Real_Time.Clock;
+   end if;
+
+   for I in 1 .. Repeat_Count loop
+      declare
+         procedure Clean_Up
+         is begin
+            if I = 1 then
+               null;
+               --  FIXME: Errors!
+               --  Parse_Data.Put
+               --    (Parser.Lexer.Errors,
+               --     Parser.Parsers.First.State_Ref.Errors,
+               --     Parser.Parsers.First.State_Ref.Tree);
+            end if;
+         end Clean_Up;
+
+      begin
+         Parse_Data.Reset;
+         Parser.Parse;
+         Parser.Execute_Actions;
+
+         if Repeat_Count = 1 then
+            Parse_Data.Put;
+
+            --  FIXME: put errors via parse_data.put
+            if Parser.Any_Errors then
+               Parser.Put_Errors;
+            end if;
+            --  Parse_Data.Put
+            --       (Parser.Lexer.Errors,
+            --        Parser.Parsers.First.State_Ref.Errors,
+            --        Parser.Parsers.First.State_Ref.Tree);
+         end if;
+      exception
+      when WisiToken.Syntax_Error =>
+         Clean_Up;
+         Put_Line ("(parse_error)");
+
+      when E : WisiToken.Parse_Error =>
+         Clean_Up;
+         Put_Line ("(parse_error """ & Ada.Exceptions.Exception_Message (E) & 
""")");
+
+      when E : WisiToken.Fatal_Error =>
+         Clean_Up;
+         Put_Line ("(error """ & Ada.Exceptions.Exception_Message (E) & """)");
+      end;
+   end loop;
+
+   if Repeat_Count > 1 then
+      declare
+         use Ada.Real_Time;
+         Finish : constant Time := Clock;
+      begin
+         Put_Line ("Total time:" & Duration'Image (To_Duration (Finish - 
Start)));
+         Put_Line ("per iteration:" & Duration'Image (To_Duration ((Finish - 
Start) / Repeat_Count)));
+      end;
+   end if;
+
+exception
+when E : others =>
+   Ada.Command_Line.Set_Exit_Status (Ada.Command_Line.Failure);
+   New_Line (2);
+   Put_Line
+     ("(error ""unhandled exception: " & Ada.Exceptions.Exception_Name (E) & 
": " &
+        Ada.Exceptions.Exception_Message (E) & """)");
+   Put_Line (GNAT.Traceback.Symbolic.Symbolic_Traceback (E));
+end Gen_Run_Wisi_Libadalang_Parse;
diff --git a/gen_run_wisi_libadalang_parse.ads 
b/gen_run_wisi_libadalang_parse.ads
new file mode 100644
index 0000000..356c67d
--- /dev/null
+++ b/gen_run_wisi_libadalang_parse.ads
@@ -0,0 +1,30 @@
+--  Abstract :
+--
+--  Run an Emacs libadalang parser as a standalone executable, for debugging.
+--
+--  See gen_emacs_wisi_libadalang_parse.ads for the Emacs background process.
+--
+--  Copyright (C) 2018 Free Software Foundation, Inc.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with Wisi;
+with WisiToken;
+generic
+   type Parse_Data_Type is new Wisi.Parse_Data_Type with private;
+
+   Descriptor : in WisiToken.Descriptor;
+
+procedure Gen_Run_Wisi_Libadalang_Parse;
diff --git a/gen_run_wisi_lr_parse.adb b/gen_run_wisi_lr_parse.adb
new file mode 100644
index 0000000..ee63c77
--- /dev/null
+++ b/gen_run_wisi_lr_parse.adb
@@ -0,0 +1,37 @@
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2017 - 2019 All Rights Reserved.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with Run_Wisi_Common_Parse;
+with WisiToken.Text_IO_Trace;
+procedure Gen_Run_Wisi_LR_Parse
+is
+   Trace      : aliased WisiToken.Text_IO_Trace.Trace 
(Descriptor'Unrestricted_Access);
+   Parser     : WisiToken.Parse.LR.Parser.Parser;
+   Parse_Data : aliased Parse_Data_Type (Parser.Line_Begin_Token'Access);
+begin
+   --  Create parser first so Put_Usage has defaults from Parser.Table,
+   --  and Get_CL_Params can override them.
+   Create_Parser
+     (Parser, Language_Fixes, Language_Matching_Begin_Tokens, 
Language_String_ID_Set,
+      Trace'Unrestricted_Access, Parse_Data'Unchecked_Access);
+
+   Run_Wisi_Common_Parse.Parse_File (Parser, Parse_Data, Descriptor);
+
+end Gen_Run_Wisi_LR_Parse;
diff --git a/gen_run_wisi_lr_parse.ads b/gen_run_wisi_lr_parse.ads
new file mode 100644
index 0000000..34fbd3c
--- /dev/null
+++ b/gen_run_wisi_lr_parse.ads
@@ -0,0 +1,42 @@
+--  Abstract :
+--
+--  Run an Emacs LR parser as a standalone executable, for debugging.
+--
+--  See gen_emacs_wisi_lr_parse.ads for the Emacs background process.
+--
+--  Copyright (C) 2017, 2018, 2019 Free Software Foundation, Inc.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with WisiToken.Parse.LR.Parser;
+with WisiToken.Syntax_Trees;
+with Wisi;
+generic
+   type Parse_Data_Type is new Wisi.Parse_Data_Type with private;
+
+   Descriptor                     : in WisiToken.Descriptor;
+   Language_Fixes                 : in 
WisiToken.Parse.LR.Parser.Language_Fixes_Access;
+   Language_Matching_Begin_Tokens : in 
WisiToken.Parse.LR.Parser.Language_Matching_Begin_Tokens_Access;
+   Language_String_ID_Set         : in 
WisiToken.Parse.LR.Parser.Language_String_ID_Set_Access;
+
+   with procedure Create_Parser
+     (Parser                         :    out          
WisiToken.Parse.LR.Parser.Parser;
+      Language_Fixes                 : in              
WisiToken.Parse.LR.Parser.Language_Fixes_Access;
+      Language_Matching_Begin_Tokens : in              
WisiToken.Parse.LR.Parser.Language_Matching_Begin_Tokens_Access;
+      Language_String_ID_Set         : in              
WisiToken.Parse.LR.Parser.Language_String_ID_Set_Access;
+      Trace                          : not null access WisiToken.Trace'Class;
+      User_Data                      : in              
WisiToken.Syntax_Trees.User_Data_Access);
+
+procedure Gen_Run_Wisi_LR_Parse;
diff --git a/gen_run_wisi_lr_text_rep_parse.adb 
b/gen_run_wisi_lr_text_rep_parse.adb
new file mode 100644
index 0000000..09efefd
--- /dev/null
+++ b/gen_run_wisi_lr_text_rep_parse.adb
@@ -0,0 +1,44 @@
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2017 - 2019 All Rights Reserved.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with Ada.Command_Line;
+with Ada.Directories;
+with Run_Wisi_Common_Parse;
+with WisiToken.Text_IO_Trace;
+procedure Gen_Run_Wisi_LR_Text_Rep_Parse
+is
+   Trace      : aliased WisiToken.Text_IO_Trace.Trace 
(Descriptor'Unrestricted_Access);
+   Parser     : WisiToken.Parse.LR.Parser.Parser;
+   Parse_Data : aliased Parse_Data_Type (Parser.Line_Begin_Token'Access);
+begin
+   --  Create parser first so Put_Usage has defaults from Parser.Table,
+   --  and Get_CL_Params can override them.
+   declare
+      use Ada.Command_Line;
+   begin
+      --  text_rep file is in same directory as exectuable.
+      Create_Parser
+        (Parser, Language_Fixes, Language_Matching_Begin_Tokens, 
Language_String_ID_Set,
+         Trace'Unrestricted_Access, Parse_Data'Unchecked_Access,
+         Ada.Directories.Containing_Directory (Command_Name) & "/" & 
Text_Rep_File_Name);
+
+      Run_Wisi_Common_Parse.Parse_File (Parser, Parse_Data, Descriptor);
+   end;
+end Gen_Run_Wisi_LR_Text_Rep_Parse;
diff --git a/gen_run_wisi_lr_text_rep_parse.ads 
b/gen_run_wisi_lr_text_rep_parse.ads
new file mode 100644
index 0000000..dd45d16
--- /dev/null
+++ b/gen_run_wisi_lr_text_rep_parse.ads
@@ -0,0 +1,44 @@
+--  Abstract :
+--
+--  Run an Emacs LR text_rep parser as a standalone executable, for debugging.
+--
+--  See gen_emacs_wisi_*_parse.ads for the Emacs background process.
+--
+--  Copyright (C) 2018, 2019 Free Software Foundation, Inc.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with WisiToken.Parse.LR.Parser;
+with WisiToken.Syntax_Trees;
+with Wisi;
+generic
+   type Parse_Data_Type is new Wisi.Parse_Data_Type with private;
+
+   Descriptor                     : in WisiToken.Descriptor;
+   Language_Fixes                 : in 
WisiToken.Parse.LR.Parser.Language_Fixes_Access;
+   Language_Matching_Begin_Tokens : in 
WisiToken.Parse.LR.Parser.Language_Matching_Begin_Tokens_Access;
+   Language_String_ID_Set         : in 
WisiToken.Parse.LR.Parser.Language_String_ID_Set_Access;
+   Text_Rep_File_Name             : in String;
+
+   with procedure Create_Parser
+     (Parser                         :    out          
WisiToken.Parse.LR.Parser.Parser;
+      Language_Fixes                 : in              
WisiToken.Parse.LR.Parser.Language_Fixes_Access;
+      Language_Matching_Begin_Tokens : in              
WisiToken.Parse.LR.Parser.Language_Matching_Begin_Tokens_Access;
+      Language_String_ID_Set         : in              
WisiToken.Parse.LR.Parser.Language_String_ID_Set_Access;
+      Trace                          : not null access WisiToken.Trace'Class;
+      User_Data                      : in              
WisiToken.Syntax_Trees.User_Data_Access;
+      Text_Rep_File_Name             : in              String);
+
+procedure Gen_Run_Wisi_LR_Text_Rep_Parse;
diff --git a/gen_run_wisi_packrat_parse.adb b/gen_run_wisi_packrat_parse.adb
new file mode 100644
index 0000000..fb3e900
--- /dev/null
+++ b/gen_run_wisi_packrat_parse.adb
@@ -0,0 +1,241 @@
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2018 All Rights Reserved.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with Ada.Command_Line;
+with Ada.Exceptions;
+with Ada.IO_Exceptions;
+with Ada.Real_Time;
+with Ada.Strings.Unbounded;
+with Ada.Text_IO; use Ada.Text_IO;
+with GNAT.Traceback.Symbolic;
+with WisiToken.Lexer;
+with WisiToken.Text_IO_Trace;
+procedure Gen_Run_Wisi_Parse_Packrat
+is
+   use WisiToken; -- Token_ID, "+", "-" Unbounded_string
+
+   Trace      : aliased WisiToken.Text_IO_Trace.Trace (Descriptor'Access);
+   Parser     : WisiToken.Parse.Packrat.Parser;
+   Parse_Data : aliased Parse_Data_Type (Parser.Line_Begin_Token'Access);
+
+   procedure Put_Usage
+   is begin
+      Put_Line ("usage: " & Name & "_wisi_parse <file_name> <parse_action> 
[options]");
+      Put_Line ("parse_action: {Navigate | Face | Indent}");
+      Put_Line ("options:");
+      Put_Line ("--verbosity n m l:");
+      Put_Line ("   n: parser; m: mckenzie; l: action");
+      Put_Line ("   0 - only report parse errors");
+      Put_Line ("   1 - shows spawn/terminate parallel parsers, error recovery 
enter/exit");
+      Put_Line ("   2 - add each parser cycle, error recovery enqueue/check");
+      Put_Line ("   3 - parse stack in each cycle, error recovery parse 
actions");
+      Put_Line ("   4 - add lexer debug");
+      Put_Line ("--lang_params <language-specific params>");
+      Put_Line ("--lexer_only : only run lexer, for profiling");
+      Put_Line ("--repeat_count n : repeat parse count times, for profiling; 
default 1");
+      Put_Line ("--pause : when repeating, prompt for <enter> after each 
parse; allows seeing memory leaks");
+      New_Line;
+   end Put_Usage;
+
+   Source_File_Name  : Ada.Strings.Unbounded.Unbounded_String;
+   Post_Parse_Action : WisiToken.Wisi_Runtime.Post_Parse_Action_Type;
+
+   Line_Count   : WisiToken.Line_Number_Type := 1;
+   Lexer_Only   : Boolean                    := False;
+   Repeat_Count : Integer                    := 1;
+   Pause        : Boolean                    := False;
+   Arg          : Integer;
+   Lang_Params  : Ada.Strings.Unbounded.Unbounded_String;
+   Start        : Ada.Real_Time.Time;
+begin
+   Create_Parser (Parser, Trace'Unrestricted_Access, 
Parse_Data'Unchecked_Access);
+
+   declare
+      use Ada.Command_Line;
+   begin
+      if Argument_Count < 1 then
+         Put_Usage;
+         Set_Exit_Status (Failure);
+         return;
+      end if;
+
+      Source_File_Name  := +Ada.Command_Line.Argument (1);
+      Post_Parse_Action := WisiToken.Wisi_Runtime.Post_Parse_Action_Type'Value 
(Ada.Command_Line.Argument (2));
+      Arg               := 3;
+
+      loop
+         exit when Arg > Argument_Count;
+
+         if Argument (Arg) = "--verbosity" then
+            WisiToken.Trace_Parse    := Integer'Value (Argument (Arg + 1));
+            WisiToken.Trace_McKenzie := Integer'Value (Argument (Arg + 2));
+            WisiToken.Trace_Action   := Integer'Value (Argument (Arg + 3));
+            Arg                      := Arg + 4;
+
+         elsif Argument (Arg) = "--lang_params" then
+            Lang_Params := +Argument (Arg + 1);
+            Arg := Arg + 2;
+
+         elsif Argument (Arg) = "--lexer_only" then
+            Lexer_Only := True;
+            Arg := Arg + 1;
+
+         elsif Argument (Arg) = "--pause" then
+            Pause := True;
+            Arg := Arg + 1;
+
+         elsif Argument (Arg) = "--repeat_count" then
+            Repeat_Count := Integer'Value (Argument (Arg + 1));
+            Arg := Arg + 2;
+
+         else
+            Put_Line ("unrecognized option: '" & Argument (Arg) & "'");
+            Put_Usage;
+            return;
+         end if;
+      end loop;
+   end;
+
+   --  Do this after setting Trace_Parse so lexer verbosity is set
+   begin
+      Parser.Lexer.Reset_With_File (-Source_File_Name);
+   exception
+   when Ada.IO_Exceptions.Name_Error =>
+      Put_Line (Standard_Error, "'" & (-Source_File_Name) & "' cannot be 
opened");
+      return;
+   end;
+
+   --  See comment in wisi-wisi_runtime.ads for why we still need this.
+   declare
+      Token : Base_Token;
+      Lexer_Error : Boolean;
+      pragma Unreferenced (Lexer_Error);
+   begin
+      loop
+         begin
+            Lexer_Error := Parser.Lexer.Find_Next (Token);
+            exit when Token.ID = Descriptor.EOF_ID;
+         exception
+         when WisiToken.Syntax_Error =>
+            Parser.Lexer.Discard_Rest_Of_Input;
+            Parser.Put_Errors (-Source_File_Name);
+            Put_Line ("(lexer_error)");
+         end;
+      end loop;
+      Line_Count := Token.Line;
+   end;
+
+   if WisiToken.Trace_Action > WisiToken.Outline then
+      Put_Line ("line_count:" & Line_Number_Type'Image (Line_Count));
+   end if;
+
+   Parse_Data.Initialize
+     (Post_Parse_Action => Post_Parse_Action,
+      Descriptor        => Descriptor'Access,
+      Source_File_Name  => -Source_File_Name,
+      Line_Count        => Line_Count,
+      Params            => -Lang_Params);
+
+   if Repeat_Count > 1 then
+      Start := Ada.Real_Time.Clock;
+   end if;
+
+   for I in 1 .. Repeat_Count loop
+      declare
+         procedure Clean_Up
+         is begin
+            Parser.Lexer.Discard_Rest_Of_Input;
+            if Repeat_Count = 1 then
+               Parser.Put_Errors (-Source_File_Name);
+            end if;
+         end Clean_Up;
+
+      begin
+         Parse_Data.Reset;
+         Parser.Lexer.Reset;
+
+         if Lexer_Only then
+            declare
+               Token : Base_Token;
+               Lexer_Error : Boolean;
+               pragma Unreferenced (Lexer_Error);
+            begin
+               Parser.Lexer.Reset;
+               loop
+                  Lexer_Error := Parser.Lexer.Find_Next (Token);
+                  exit when Token.ID = Descriptor.EOF_ID;
+               end loop;
+               --  We don't handle errors here; that was done in the count 
lines loop
+               --  above.
+            end;
+         else
+            Parser.Parse;
+            Parser.Execute_Actions;
+
+            if Repeat_Count = 1 then
+               Parse_Data.Put;
+               Parser.Put_Errors (-Source_File_Name);
+            end if;
+         end if;
+      exception
+      when WisiToken.Syntax_Error =>
+         Clean_Up;
+         Put_Line ("(parse_error)");
+
+      when E : WisiToken.Parse_Error =>
+         Clean_Up;
+         Put_Line ("(parse_error """ & Ada.Exceptions.Exception_Message (E) & 
""")");
+
+      when E : WisiToken.Fatal_Error =>
+         Clean_Up;
+         Put_Line ("(error """ & Ada.Exceptions.Exception_Message (E) & """)");
+      end;
+
+      if Pause then
+         Put_Line ("Enter to continue:");
+         Flush (Standard_Output);
+         declare
+            Junk : constant String := Get_Line;
+            pragma Unreferenced (Junk);
+         begin
+            null;
+         end;
+      end if;
+   end loop;
+
+   if Repeat_Count > 1 then
+      declare
+         use Ada.Real_Time;
+         Finish : constant Time := Clock;
+      begin
+         Put_Line ("Total time:" & Duration'Image (To_Duration (Finish - 
Start)));
+         Put_Line ("per iteration:" & Duration'Image (To_Duration ((Finish - 
Start) / Repeat_Count)));
+      end;
+   end if;
+
+exception
+when E : others =>
+   Ada.Command_Line.Set_Exit_Status (Ada.Command_Line.Failure);
+   New_Line (2);
+   Put_Line
+     ("(error ""unhandled exception: " & Ada.Exceptions.Exception_Name (E) & 
": " &
+        Ada.Exceptions.Exception_Message (E) & """)");
+   Put_Line (GNAT.Traceback.Symbolic.Symbolic_Traceback (E));
+end Gen_Run_Wisi_Parse_Packrat;
diff --git a/gen_run_wisi_packrat_parse.ads b/gen_run_wisi_packrat_parse.ads
new file mode 100644
index 0000000..538da1d
--- /dev/null
+++ b/gen_run_wisi_packrat_parse.ads
@@ -0,0 +1,36 @@
+--  Abstract :
+--
+--  Run an Emacs packrate parser as a standalone executable, for debugging.
+--
+--  See gen_emacs_wisi_parse_packrat.ads for the Emacs background process.
+--
+--  Copyright (C) 2018 Free Software Foundation, Inc.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with WisiToken.Parse.Packrat;
+with WisiToken.Syntax_Trees;
+with WisiToken.Wisi_Runtime;
+generic
+   type Parse_Data_Type is new WisiToken.Wisi_Runtime.Parse_Data_Type with 
private;
+
+   Descriptor : in WisiToken.Descriptor;
+
+   with procedure Create_Parser
+     (Parser    :    out          WisiToken.Parse.Packrat.Parser;
+      Trace     : not null access WisiToken.Trace'Class;
+      User_Data : in              WisiToken.Syntax_Trees.User_Data_Access);
+
+procedure Gen_Run_Wisi_Parse_Packrat;
diff --git a/run_wisi_common_parse.adb b/run_wisi_common_parse.adb
new file mode 100644
index 0000000..e4a45c0
--- /dev/null
+++ b/run_wisi_common_parse.adb
@@ -0,0 +1,343 @@
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2018 - 2019 Free Software Foundation, Inc.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with Ada.Command_Line;
+with Ada.Exceptions;
+with Ada.IO_Exceptions;
+with Ada.Real_Time;
+with Ada.Text_IO;
+with GNAT.Traceback.Symbolic;
+with SAL;
+with System.Multiprocessors;
+package body Run_Wisi_Common_Parse is
+
+   procedure Usage (Parser : in out WisiToken.Parse.LR.Parser.Parser)
+   is
+      use all type WisiToken.Parse.LR.Parse_Table_Ptr;
+      use Ada.Text_IO;
+   begin
+      Put_Line ("usage: parse <parse_action> <file_name> [partial parse 
params] [options]");
+      Put_Line ("   or: refactor <refactor_action> <file_name> <edit_begin> 
[options]");
+      Put_Line ("parse_action: {Navigate | Face | Indent}");
+      Put_Line ("partial parse params: begin_byte_pos end_byte_pos 
goal_byte_pos begin_char_pos begin_line" &
+                  " end_line begin_indent");
+      Put_Line ("options:");
+      Put_Line ("--verbosity n m l: (no 'm' for refactor)");
+      Put_Line ("   n: parser; m: mckenzie; l: action");
+      Put_Line ("   0 - only report parse errors");
+      Put_Line ("   1 - shows spawn/terminate parallel parsers, error recovery 
enter/exit");
+      Put_Line ("   2 - add each parser cycle, error recovery enqueue/check");
+      Put_Line ("   3 - parse stack in each cycle, error recovery parse 
actions");
+      Put_Line ("   4 - add lexer debug");
+      Put_Line ("--check_limit n  : set error recover token check limit" &
+                  (if Parser.Table = null then ""
+                   else "; default" & 
Parser.Table.McKenzie_Param.Check_Limit'Image));
+      Put_Line ("--check_delta n  : set error recover delta check limit" &
+                  (if Parser.Table = null then ""
+                   else "; default" & 
Parser.Table.McKenzie_Param.Check_Delta_Limit'Image));
+      Put_Line ("--enqueue_limit n  : set error recover token enqueue limit" &
+                  (if Parser.Table = null then ""
+                   else "; default" & 
Parser.Table.McKenzie_Param.Enqueue_Limit'Image));
+      Put_Line ("--max_parallel n  : set maximum count of parallel parsers 
(default" &
+                  WisiToken.Parse.LR.Parser.Default_Max_Parallel'Image & ")");
+      Put_Line ("--task_count n : worker tasks in error recovery");
+      Put_Line ("--disable_recover : disable error recovery; default enabled");
+      Put_Line ("--debug_mode : tracebacks from unhandled exceptions; default 
disabled");
+      Put_Line ("--lang_params <language-specific params>");
+      Put_Line ("--repeat_count n : repeat parse count times, for profiling; 
default 1");
+      New_Line;
+   end Usage;
+
+   function Get_CL_Params (Parser : in out WisiToken.Parse.LR.Parser.Parser) 
return Command_Line_Params
+   is
+      use Ada.Command_Line;
+      use WisiToken;
+      Arg     : Integer := 1;
+      Command : Command_Type;
+   begin
+      if Argument_Count < 1 then
+         Usage (Parser);
+         Set_Exit_Status (Failure);
+         raise Finish;
+
+      elsif Argument (Arg) = "--help" then
+         Usage (Parser);
+         raise Finish;
+
+      elsif Argument_Count < 2 then
+         Usage (Parser);
+         Set_Exit_Status (Failure);
+         raise Finish;
+      end if;
+
+      Command := Command_Type'Value (Ada.Command_Line.Argument (1));
+
+      return Result : Command_Line_Params (Command) do
+         Result.Source_File_Name  := +Ada.Command_Line.Argument (3);
+
+         case Command is
+         when Parse =>
+            Result.Post_Parse_Action := Wisi.Post_Parse_Action_Type'Value 
(Ada.Command_Line.Argument (2));
+
+            if Argument_Count >= 4 and then Argument (4)(1) /= '-' then
+               Result.Begin_Byte_Pos := WisiToken.Buffer_Pos'Value (Argument 
(4));
+               Result.End_Byte_Pos   := WisiToken.Buffer_Pos'Value (Argument 
(5)) - 1; -- match emacs region
+               Result.Goal_Byte_Pos  := WisiToken.Buffer_Pos'Value (Argument 
(6));
+               Result.Begin_Char_Pos := WisiToken.Buffer_Pos'Value (Argument 
(7));
+               Result.Begin_Line     := WisiToken.Line_Number_Type'Value 
(Argument (8));
+               Result.End_Line       := WisiToken.Line_Number_Type'Value 
(Argument (9));
+               Result.Begin_Indent   := Integer'Value (Argument (10));
+               Arg                   := 11;
+            else
+               Result.Begin_Byte_Pos := WisiToken.Invalid_Buffer_Pos;
+               Result.End_Byte_Pos   := WisiToken.Invalid_Buffer_Pos;
+               Result.Begin_Char_Pos := WisiToken.Buffer_Pos'First;
+               Result.Begin_Line     := WisiToken.Line_Number_Type'First;
+               Arg                   := 4;
+            end if;
+
+         when Refactor =>
+            Result.Refactor_Action := Integer'Value (Argument (2));
+            Result.Edit_Begin      := WisiToken.Buffer_Pos'Value (Argument 
(4));
+            Arg                    := 5;
+         end case;
+
+         loop
+            exit when Arg > Argument_Count;
+
+            if Argument (Arg) = "--verbosity" then
+               WisiToken.Trace_Parse    := Integer'Value (Argument (Arg + 1));
+               case Command is
+               when Parse =>
+                  WisiToken.Trace_McKenzie := Integer'Value (Argument (Arg + 
2));
+                  WisiToken.Trace_Action   := Integer'Value (Argument (Arg + 
3));
+                  Arg                      := Arg + 4;
+               when Refactor =>
+                  WisiToken.Trace_Action   := Integer'Value (Argument (Arg + 
2));
+                  Arg                      := Arg + 3;
+               end case;
+
+            elsif Argument (Arg) = "--check_limit" then
+               Parser.Table.McKenzie_Param.Check_Limit := Token_Index'Value 
(Argument (Arg + 1));
+               Arg := Arg + 2;
+
+            elsif Argument (Arg) = "--check_delta" then
+               Parser.Table.McKenzie_Param.Check_Delta_Limit := Integer'Value 
(Argument (Arg + 1));
+               Arg := Arg + 2;
+
+            elsif Argument (Arg) = "--debug_mode" then
+               WisiToken.Debug_Mode := True;
+               Arg := Arg + 1;
+
+            elsif Argument (Arg) = "--disable_recover" then
+               Parser.Enable_McKenzie_Recover := False;
+               Arg := Arg + 1;
+
+            elsif Argument (Arg) = "--enqueue_limit" then
+               Parser.Table.McKenzie_Param.Enqueue_Limit := Integer'Value 
(Argument (Arg + 1));
+               Arg := Arg + 2;
+
+            elsif Argument (Arg) = "--lang_params" then
+               Result.Lang_Params := +Argument (Arg + 1);
+               Arg := Arg + 2;
+
+            elsif Argument (Arg) = "--max_parallel" then
+               Parser.Max_Parallel := SAL.Base_Peek_Type'Value (Argument (Arg 
+ 1));
+               Arg := Arg + 2;
+
+            elsif Argument (Arg) = "--repeat_count" then
+               Result.Repeat_Count := Integer'Value (Argument (Arg + 1));
+               Arg := Arg + 2;
+
+            elsif Argument (Arg) = "--task_count" then
+               Parser.Table.McKenzie_Param.Task_Count := 
System.Multiprocessors.CPU_Range'Value (Argument (Arg + 1));
+               Arg := Arg + 2;
+
+            else
+               Ada.Text_IO.Put_Line ("unrecognized option: '" & Argument (Arg) 
& "'");
+               Usage (Parser);
+               Set_Exit_Status (Failure);
+               raise SAL.Parameter_Error;
+            end if;
+         end loop;
+      end return;
+   exception
+   when Finish =>
+      raise;
+
+   when E : others =>
+      Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Name (E) & ": " & 
Ada.Exceptions.Exception_Message (E));
+      Usage (Parser);
+      Set_Exit_Status (Failure);
+      raise SAL.Parameter_Error;
+   end Get_CL_Params;
+
+   procedure Parse_File
+     (Parser     : in out WisiToken.Parse.LR.Parser.Parser;
+      Parse_Data : in out Wisi.Parse_Data_Type'Class;
+      Descriptor : in     WisiToken.Descriptor)
+   is
+      use Ada.Text_IO;
+      use WisiToken;
+
+      Start     : Ada.Real_Time.Time;
+      End_Line  : WisiToken.Line_Number_Type;
+   begin
+      declare
+         Cl_Params : constant Command_Line_Params := Get_CL_Params (Parser);
+      begin
+         begin
+            case Cl_Params.Command is
+            when Parse =>
+               Parser.Lexer.Reset_With_File
+                 (-Cl_Params.Source_File_Name, Cl_Params.Begin_Byte_Pos, 
Cl_Params.End_Byte_Pos,
+                  Cl_Params.Begin_Char_Pos, Cl_Params.Begin_Line);
+            when Refactor =>
+               Parser.Lexer.Reset_With_File (-Cl_Params.Source_File_Name);
+            end case;
+         exception
+         when Ada.IO_Exceptions.Name_Error =>
+            Put_Line (Standard_Error, "'" & (-Cl_Params.Source_File_Name) & "' 
cannot be opened");
+            return;
+         end;
+
+         if Cl_Params.Command = Refactor or else Cl_Params.End_Line = 
Invalid_Line_Number then
+            --  User did not provide; run lexer to get end line.
+            declare
+               Token : Base_Token;
+               Lexer_Error : Boolean;
+               pragma Unreferenced (Lexer_Error);
+            begin
+               loop
+                  Lexer_Error := Parser.Lexer.Find_Next (Token);
+                  exit when Token.ID = Descriptor.EOI_ID;
+               end loop;
+               End_Line := Token.Line;
+            end;
+         else
+            End_Line := Cl_Params.End_Line;
+         end if;
+
+         Parse_Data.Initialize
+           (Post_Parse_Action =>
+              (case Cl_Params.Command is
+               when Parse    => Cl_Params.Post_Parse_Action,
+               when Refactor => Wisi.Navigate),
+            Lexer            => Parser.Lexer,
+            Descriptor       => Descriptor'Unrestricted_Access,
+            Base_Terminals   => Parser.Terminals'Unrestricted_Access,
+            Begin_Line       =>
+              (case Cl_Params.Command is
+               when Parse => Cl_Params.Begin_Line,
+               when Refactor => WisiToken.Line_Number_Type'First),
+            End_Line         => End_Line,
+            Begin_Indent     =>
+              (case Cl_Params.Command is
+               when Parse    => Cl_Params.Begin_Indent,
+               when Refactor => 0),
+            Params            => -Cl_Params.Lang_Params);
+
+         if Cl_Params.Repeat_Count > 1 then
+            Start := Ada.Real_Time.Clock;
+         end if;
+
+         for I in 1 .. Cl_Params.Repeat_Count loop
+            declare
+               procedure Clean_Up
+               is
+                  use all type SAL.Base_Peek_Type;
+               begin
+                  Parser.Lexer.Discard_Rest_Of_Input;
+                  if Cl_Params.Repeat_Count = 1 and Parser.Parsers.Count > 0 
then
+                     Parse_Data.Put
+                       (Parser.Lexer.Errors,
+                        Parser.Parsers.First.State_Ref.Errors,
+                        Parser.Parsers.First.State_Ref.Tree);
+                  end if;
+               end Clean_Up;
+
+            begin
+               Parse_Data.Reset;
+               Parser.Lexer.Reset;
+
+               begin
+                  Parser.Parse;
+               exception
+               when WisiToken.Partial_Parse =>
+                  null;
+               end;
+
+               Parser.Execute_Actions;
+
+               case Cl_Params.Command is
+               when Parse =>
+                  if Cl_Params.Repeat_Count = 1 then
+                     Parse_Data.Put (Parser);
+                     Parse_Data.Put
+                       (Parser.Lexer.Errors,
+                        Parser.Parsers.First.State_Ref.Errors,
+                        Parser.Parsers.First.State_Ref.Tree);
+                  end if;
+
+               when Refactor =>
+                  Parse_Data.Refactor
+                    (Parser.Parsers.First_State_Ref.Tree,
+                     Cl_Params.Refactor_Action, Cl_Params.Edit_Begin);
+               end case;
+            exception
+            when WisiToken.Syntax_Error =>
+               Clean_Up;
+               Put_Line ("(parse_error)");
+
+            when E : WisiToken.Parse_Error =>
+               Clean_Up;
+               Put_Line ("(parse_error """ & Ada.Exceptions.Exception_Message 
(E) & """)");
+
+            when E : others => -- includes Fatal_Error
+               Clean_Up;
+               Put_Line ("(error """ & Ada.Exceptions.Exception_Message (E) & 
""")");
+            end;
+         end loop;
+
+         if Cl_Params.Repeat_Count > 1 then
+            declare
+               use Ada.Real_Time;
+               Finish : constant Time := Clock;
+            begin
+               Put_Line ("Total time:" & Duration'Image (To_Duration (Finish - 
Start)));
+               Put_Line ("per iteration:" & Duration'Image (To_Duration 
((Finish - Start) / Cl_Params.Repeat_Count)));
+            end;
+         end if;
+      end;
+   exception
+   when SAL.Parameter_Error | Finish =>
+      --  From Get_CL_Params; already handled.
+      null;
+
+   when E : others =>
+      Ada.Command_Line.Set_Exit_Status (Ada.Command_Line.Failure);
+      New_Line (2);
+      Put_Line
+        ("(error ""unhandled exception: " & Ada.Exceptions.Exception_Name (E) 
& ": " &
+           Ada.Exceptions.Exception_Message (E) & """)");
+      Put_Line (GNAT.Traceback.Symbolic.Symbolic_Traceback (E));
+   end Parse_File;
+
+end Run_Wisi_Common_Parse;
diff --git a/run_wisi_common_parse.ads b/run_wisi_common_parse.ads
new file mode 100644
index 0000000..c00d007
--- /dev/null
+++ b/run_wisi_common_parse.ads
@@ -0,0 +1,73 @@
+--  Abstract :
+--
+--  Common utilities for Gen_Run_Wisi_*_Parse
+--
+--  Copyright (C) 2018 - 2019 Free Software Foundation, Inc.
+--
+--  This program is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston,
+--  MA 02110-1335, USA.
+
+pragma License (GPL);
+
+with Ada.Strings.Unbounded;
+with Wisi;
+with WisiToken.Parse.LR.Parser;
+package Run_Wisi_Common_Parse is
+
+   Finish : exception;
+
+   procedure Usage (Parser : in out WisiToken.Parse.LR.Parser.Parser);
+   --  Puts parameter description to Current_Output.
+
+   type Command_Type is (Parse, Refactor);
+
+   type Command_Line_Params (Command : Command_Type) is record
+
+      Source_File_Name : Ada.Strings.Unbounded.Unbounded_String;
+      Lang_Params      : Ada.Strings.Unbounded.Unbounded_String;
+      Repeat_Count     : Integer := 1;
+
+      case Command is
+      when Parse =>
+         Post_Parse_Action : Wisi.Post_Parse_Action_Type;
+         Begin_Byte_Pos    : WisiToken.Buffer_Pos       := 
WisiToken.Invalid_Buffer_Pos;
+         End_Byte_Pos      : WisiToken.Buffer_Pos       := 
WisiToken.Invalid_Buffer_Pos;
+         Goal_Byte_Pos     : WisiToken.Buffer_Pos       := 
WisiToken.Invalid_Buffer_Pos;
+         Begin_Char_Pos    : WisiToken.Buffer_Pos       := 
WisiToken.Buffer_Pos'First;
+         Begin_Line        : WisiToken.Line_Number_Type := 
WisiToken.Line_Number_Type'First;
+         End_Line          : WisiToken.Line_Number_Type := 
WisiToken.Invalid_Line_Number;
+         Begin_Indent      : Integer                    := 0;
+
+      when Refactor =>
+         --  We assume the file contains only the one statement/declaration
+         --  that needs refactoring.
+
+         Refactor_Action : Positive;
+         --  Language-specific
+
+         Edit_Begin : WisiToken.Buffer_Pos;
+         --  Source file byte position at start of expression to refactor.
+      end case;
+   end record;
+
+   function Get_CL_Params (Parser : in out WisiToken.Parse.LR.Parser.Parser) 
return Command_Line_Params;
+   --  For any errors, calls Usage, raises SAL.Parameter_Error.
+   --
+   --  Handles --help by outputing help, raising Finish.
+
+   procedure Parse_File
+     (Parser     : in out WisiToken.Parse.LR.Parser.Parser;
+      Parse_Data : in out Wisi.Parse_Data_Type'Class;
+      Descriptor : in     WisiToken.Descriptor);
+   --  Calls Get_CL_Params, reads in file, parses, does post-parse actions.
+
+end Run_Wisi_Common_Parse;
diff --git a/sal-gen_bounded_definite_queues.adb 
b/sal-gen_bounded_definite_queues.adb
index 61e5c12..71b68c5 100644
--- a/sal-gen_bounded_definite_queues.adb
+++ b/sal-gen_bounded_definite_queues.adb
@@ -17,43 +17,20 @@
 
 pragma License (Modified_GPL);
 
-package body SAL.Gen_Bounded_Definite_Queues is
-
-   --  Local subprograms
-
-   function Wrap (Queue : in Queue_Type; I : in Integer) return Integer
-   is begin
-      if I > Queue.Size then
-         return I - Queue.Size;
-      elsif I < 1 then
-         return Queue.Size + I;
-      else
-         return I;
-      end if;
-   end Wrap;
+package body SAL.Gen_Bounded_Definite_Queues
+  with Spark_Mode
+is
+   pragma Suppress (All_Checks);
 
    ----------
    --  Public subprograms
 
-   function Get_Overflow_Handling (Queue : in Queue_Type) return 
Overflow_Action_Type
-   is begin
-      return Queue.Overflow_Handling;
-   end Get_Overflow_Handling;
-
-   procedure Set_Overflow_Handling (Queue : in out Queue_Type; Handling : in 
Overflow_Action_Type)
-   is begin
-      Queue.Overflow_Handling := Handling;
-   end Set_Overflow_Handling;
-
    procedure Clear (Queue : in out Queue_Type) is
    begin
       Queue.Count := 0;
    end Clear;
 
-   function Count (Queue : in Queue_Type) return Natural is
-   begin
-      return Queue.Count;
-   end Count;
+   function Count (Queue : in Queue_Type) return Base_Peek_Type is 
(Queue.Count);
 
    function Is_Empty (Queue : in Queue_Type) return Boolean is
    begin
@@ -65,90 +42,62 @@ package body SAL.Gen_Bounded_Definite_Queues is
       return Queue.Count = Queue.Size;
    end Is_Full;
 
-   function Remove (Queue : in out Queue_Type) return Item_Type
+   procedure Remove (Queue : in out Queue_Type; Item : out Item_Type)
    is begin
-      if Queue.Count = 0 then
-         raise Container_Empty;
-      end if;
+      Item := Queue.Data (Queue.Head);
 
-      return Item : constant Item_Type := Queue.Data (Queue.Head)
-      do
-         Queue.Count := Queue.Count - 1;
+      Queue.Count := Queue.Count - 1;
 
-         if Queue.Count > 0 then
-            Queue.Head := Wrap (Queue, Queue.Head + 1);
-         end if;
+      if Queue.Count > 0 then
+         Queue.Head := Wrap (Queue.Size, Queue.Head + 1);
+      end if;
+   end Remove;
+
+   function Remove (Queue : in out Queue_Type) return Item_Type with
+     Spark_Mode => Off
+   is begin
+      return Item : Item_Type  do
+         Remove (Queue, Item);
       end return;
    end Remove;
 
    procedure Drop (Queue : in out Queue_Type)
    is begin
-      if Queue.Count = 0 then
-         raise Container_Empty;
-      end if;
-
       Queue.Count := Queue.Count - 1;
 
       if Queue.Count > 0 then
-         Queue.Head := Wrap (Queue, Queue.Head + 1);
+         Queue.Head := Wrap (Queue.Size, Queue.Head + 1);
       end if;
    end Drop;
 
-   function Peek (Queue : in Queue_Type; N : Integer := 0) return Item_Type
-   is begin
-      if Queue.Count = 0 then
-         raise Container_Empty;
-      end if;
-
-      return Queue.Data (Wrap (Queue, Queue.Head + N));
-   end Peek;
+   function Peek (Queue : in Queue_Type; N : Peek_Type := 1) return Item_Type
+     is (Queue.Data (Wrap (Queue.Size, Queue.Head + N - 1)));
+   --  Expression function to allow use in Spark proofs of conditions in spec.
 
    procedure Add (Queue : in out Queue_Type; Item : in Item_Type) is
    begin
-      if Queue.Count = Queue.Size then
-         case Queue.Overflow_Handling is
-         when Error =>
-            raise Container_Full;
-         when Overwrite =>
-            Queue.Count := Queue.Count - 1;
-            Queue.Head  := Wrap (Queue, Queue.Head + 1);
-         end case;
-      end if;
-
       if Queue.Count = 0 then
-         Queue.Tail     := 1;
-         Queue.Head     := 1;
-         Queue.Count    := 1;
-         Queue.Data (1) := Item;
+         Queue.Tail  := 1;
+         Queue.Head  := 1;
+         Queue.Count := 1;
       else
-         Queue.Tail              := Wrap (Queue, Queue.Tail + 1);
-         Queue.Data (Queue.Tail) := Item;
-         Queue.Count             := Queue.Count + 1;
+         Queue.Tail  := Wrap (Queue.Size, Queue.Tail + 1);
+         Queue.Count := Queue.Count + 1;
       end if;
+      Queue.Data (Queue.Tail) := Item;
    end Add;
 
    procedure Add_To_Head (Queue : in out Queue_Type; Item : in Item_Type) is
    begin
-      if Queue.Count = Queue.Size then
-         case Queue.Overflow_Handling is
-         when Error =>
-            raise Container_Full;
-         when Overwrite =>
-            Queue.Count := Queue.Count - 1;
-            Queue.Tail  := Wrap (Queue, Queue.Tail + 1);
-         end case;
-      end if;
-
       if Queue.Count = 0 then
-         Queue.Tail     := 1;
-         Queue.Head     := 1;
-         Queue.Count    := 1;
-         Queue.Data (1) := Item;
+         Queue.Tail  := 1;
+         Queue.Head  := 1;
+         Queue.Count := 1;
       else
-         Queue.Head              := Wrap (Queue, Queue.Head - 1);
-         Queue.Data (Queue.Head) := Item;
-         Queue.Count             := Queue.Count + 1;
+         Queue.Head  := Wrap (Queue.Size, Queue.Head - 1);
+         Queue.Count := Queue.Count + 1;
       end if;
+      Queue.Data (Queue.Head) := Item;
    end Add_To_Head;
 
 end SAL.Gen_Bounded_Definite_Queues;
diff --git a/sal-gen_bounded_definite_queues.ads 
b/sal-gen_bounded_definite_queues.ads
index 0b286f8..5614519 100644
--- a/sal-gen_bounded_definite_queues.ads
+++ b/sal-gen_bounded_definite_queues.ads
@@ -2,7 +2,7 @@
 --
 --  A generic queue, allowing definite non-limited item types.
 --
---  Copyright (C) 2004, 2008, 2009, 2011, 2017, 2019 Free Software Foundation 
All Rights Reserved.
+--  Copyright (C) 2004, 2008, 2009, 2011, 2017, 2019 Free Software Foundation  
All Rights Reserved.
 --
 --  This library is free software;  you can redistribute it and/or modify it
 --  under terms of the  GNU General Public License  as published by the Free
@@ -19,81 +19,99 @@ pragma License (Modified_GPL);
 
 generic
    type Item_Type is private;
-package SAL.Gen_Bounded_Definite_Queues is
+package SAL.Gen_Bounded_Definite_Queues
+  with Spark_Mode
+is
    pragma Pure;
+   pragma Suppress (All_Checks); --  Users must check Is_Full before Add, 
Is_Empty before Remove.
 
-   type Queue_Type (Size : Positive) is tagged private;
-   --  Size is maximum number of items in the queue.
-   --  Tagged to allow Object.Method syntax.
+   subtype Size_Type is Peek_Type range 1 .. Peek_Type'Last / 2;
+   --  The upper limit is needed to avoid overflow in Peek.
 
-   function Get_Overflow_Handling (Queue : in Queue_Type) return 
Overflow_Action_Type;
-   procedure Set_Overflow_Handling (Queue : in out Queue_Type; Handling : in 
Overflow_Action_Type);
-   --  See Add for meaning of Overflow_Handling. Default is Error.
+   type Queue_Type (Size : Size_Type) is private;
+   --  Size is maximum number of items in the queue.
 
-   procedure Clear (Queue : in out Queue_Type);
+   procedure Clear (Queue : in out Queue_Type)
+   with Post => Count (Queue) = 0;
    --  Empty Queue of all items.
 
-   function Count (Queue : in Queue_Type) return Natural;
+   function Count (Queue : in Queue_Type) return Base_Peek_Type;
    --  Returns count of items in the Queue
 
-   function Is_Empty (Queue : in Queue_Type) return Boolean;
+   function Is_Empty (Queue : in Queue_Type) return Boolean
+   with Post => Is_Empty'Result = (Count (Queue) = 0);
    --  Returns true if no items are in Queue.
 
-   function Is_Full (Queue : in Queue_Type) return Boolean;
+   function Is_Full (Queue : in Queue_Type) return Boolean
+   with Post => Is_Full'Result = (Count (Queue) = Queue.Size);
    --  Returns true if Queue is full.
 
-   function Remove (Queue : in out Queue_Type) return Item_Type;
+   procedure Remove (Queue : in out Queue_Type; Item : out Item_Type) with
+     Pre  => Count (Queue) > 0,
+     Post => Count (Queue) = Count (Queue)'Old - 1 and Item = Peek (Queue'Old) 
and
+             (for all I in 1 .. Count (Queue) => Peek (Queue'Old, I + 1) = 
Peek (Queue, I));
    --  Remove head item from Queue, return it.
-   --
-   --  Raises Container_Empty if Is_Empty.
+
+   function Remove (Queue : in out Queue_Type) return Item_Type with
+     Spark_Mode => Off;
 
    function Get (Queue : in out Queue_Type) return Item_Type renames Remove;
 
-   procedure Drop (Queue : in out Queue_Type);
+   procedure Drop (Queue : in out Queue_Type) with
+     Pre  => Count (Queue) > 0,
+     Post => Count (Queue) = Count (Queue)'Old - 1 and
+             (for all I in 1 .. Count (Queue) => Peek (Queue'Old, I + 1) = 
Peek (Queue, I));
    --  Remove head item from Queue, discard it.
-   --
-   --  Raises Container_Empty if Is_Empty.
 
-   function Peek (Queue : in Queue_Type; N : Integer := 0) return Item_Type;
-   --  Return a copy of a queue item, without removing it. N = 0 is
+   function Peek (Queue : in Queue_Type; N : Peek_Type := 1) return Item_Type 
with
+     Pre  => Count (Queue) in 1 .. Queue.Size and N in 1 .. Count (Queue);
+   --  Return a copy of a queue item, without removing it. N = 1 is
    --  the queue head.
 
-   procedure Add (Queue : in out Queue_Type; Item : in Item_Type);
+   procedure Add (Queue : in out Queue_Type; Item : in Item_Type) with
+     Pre  => Count (Queue) in 0 .. Queue.Size - 1,
+     Post => Count (Queue) = Count (Queue)'Old + 1 and Peek (Queue, Count 
(Queue)) = Item and
+             (for all I in 1 .. Count (Queue)'Old => Peek (Queue'Old, I) = 
Peek (Queue, I));
    --  Add Item to the tail of Queue.
-   --
-   --  If Queue is full, result depends on Queue.Overflow_Handling:
-   --
-   --  when Overwrite, an implicit Remove is done (and the data
-   --  discarded), then Add is done.
-   --
-   --  when Error, raises Container_Full.
 
    procedure Put (Queue : in out Queue_Type; Item : in Item_Type) renames Add;
 
-   procedure Add_To_Head (Queue : in out Queue_Type; Item : in Item_Type);
+   procedure Add_To_Head (Queue : in out Queue_Type; Item : in Item_Type) with
+     Pre  => Count (Queue) in 0 .. Queue.Size - 1,
+     Post => Count (Queue) = Count (Queue)'Old + 1 and
+             (Peek (Queue) = Item and
+              (for all I in 2 .. Count (Queue) => Peek (Queue'Old, I - 1) = 
Peek (Queue, I)));
    --  Add Item to the head of Queue.
-   --
-   --  If Queue is full, result depends on Queue.Overflow_Handling:
-   --
-   --  when Overwrite, an implicit Remove is done (and the data
-   --  discarded), then Add is done.
-   --
-   --  when Error, raises Container_Full.
 
 private
 
-   type Item_Array_Type is array (Positive range <>) of Item_Type;
-
-   type Queue_Type (Size : Positive) is tagged record
-      Overflow_Handling : Overflow_Action_Type := Error;
+   type Item_Array_Type is array (Peek_Type range <>) of Item_Type;
 
-      Head  : Natural := 0;
-      Tail  : Natural := 0;
-      Count : Natural := 0;
+   type Queue_Type (Size : Size_Type) is
+   record
+      Head  : Peek_Type      := 1;
+      Tail  : Peek_Type      := 1;
+      Count : Base_Peek_Type := 0;
       Data  : Item_Array_Type (1 .. Size);
       --  Add at Tail + 1, remove at Head. Count is current count;
       --  easier to keep track of that than to compute Is_Empty for
       --  each Add and Remove.
-   end record;
+      --
+      --  Empty is indicated by Count = 0; head and tail are arbitrary
+      --  in that case.
+   end record with
+     Type_Invariant =>
+       (Head in 1 .. Size and
+        Tail in 1 .. Size and
+        Count in 0 .. Size) and then
+       (Count = 0 or else Wrap (Size, Head + Count - 1) = Tail);
+
+   function Wrap (Size : in Size_Type; I : in Base_Peek_Type) return Peek_Type
+     is (if I > Size then I - Size
+         elsif I = 0 then Size
+         else I)
+     with
+       Pre  => I in 0 .. 2 * Size - 1,
+       Post => Wrap'Result in 1 .. Size;
 
 end SAL.Gen_Bounded_Definite_Queues;
diff --git a/sal-gen_bounded_definite_vectors-gen_image.adb 
b/sal-gen_bounded_definite_stacks-gen_image_aux.adb
similarity index 59%
copy from sal-gen_bounded_definite_vectors-gen_image.adb
copy to sal-gen_bounded_definite_stacks-gen_image_aux.adb
index e1726d1..7dcd96f 100644
--- a/sal-gen_bounded_definite_vectors-gen_image.adb
+++ b/sal-gen_bounded_definite_stacks-gen_image_aux.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  Copyright (C) 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2019 Free Software Foundation, Inc.
 --
 --  This library is free software;  you can redistribute it and/or modify it
 --  under terms of the  GNU General Public License  as published by the Free
@@ -17,23 +17,26 @@
 
 pragma License (Modified_GPL);
 
-function SAL.Gen_Bounded_Definite_Vectors.Gen_Image (Item : in Vector) return 
String
+with Ada.Strings.Unbounded;
+function SAL.Gen_Bounded_Definite_Stacks.Gen_Image_Aux
+  (Item  : in Stack;
+   Aux   : in Aux_Data;
+   Depth : in SAL.Base_Peek_Type := 0)
+  return String
 is
-   use all type SAL.Base_Peek_Type;
-   use Ada.Strings;
    use Ada.Strings.Unbounded;
-   Result : Unbounded_String := To_Unbounded_String ("(");
-   Last   : Base_Peek_Type   := To_Peek_Index (Item.Last);
+   Result : Unbounded_String        := To_Unbounded_String ("(");
+   Last   : constant Base_Peek_Type :=
+     (if Depth = 0
+      then Item.Top
+      else Base_Peek_Type'Min (Depth, Item.Top));
 begin
-   for I in Item.Elements (1 .. Last) loop
-      Result := Result &
-        ((if Trim
-          then Fixed.Trim (Element_Image (Item.Elements (I)), Left)
-          else Element_Image (Item.Elements (I)));
+   for I in 1 .. Last loop
+      Result := Result & Element_Image (Item.Peek (I), Aux);
       if I /= Last then
          Result := Result & ", ";
       end if;
    end loop;
    Result := Result & ")";
    return To_String (Result);
-end SAL.Gen_Bounded_Definite_Vectors.Gen_Image;
+end SAL.Gen_Bounded_Definite_Stacks.Gen_Image_Aux;
diff --git a/sal-gen_bounded_definite_stacks-gen_image_aux.ads 
b/sal-gen_bounded_definite_stacks-gen_image_aux.ads
new file mode 100644
index 0000000..e960398
--- /dev/null
+++ b/sal-gen_bounded_definite_stacks-gen_image_aux.ads
@@ -0,0 +1,27 @@
+--  Abstract :
+--
+--  Image with auxiliary data for instantiations of parent.
+--
+--  Copyright (C) 2019 Free Software Foundation, Inc.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+generic
+   type Aux_Data (<>) is private;
+   with function Element_Image (Item : in Element_Type; Aux : in Aux_Data) 
return String;
+function SAL.Gen_Bounded_Definite_Stacks.Gen_Image_Aux
+  (Item  : in Stack;
+   Aux   : in Aux_Data;
+   Depth : in SAL.Base_Peek_Type := 0)
+  return String;
diff --git a/sal-gen_bounded_definite_stacks.adb 
b/sal-gen_bounded_definite_stacks.adb
new file mode 100644
index 0000000..fc81daa
--- /dev/null
+++ b/sal-gen_bounded_definite_stacks.adb
@@ -0,0 +1,82 @@
+--  Abstract:
+--
+--  see spec
+--
+--  Copyright (C) 1998, 2003, 2009, 2015, 2017 - 2019 Free Software 
Foundation, Inc.
+--
+--  SAL is free software; you can redistribute it and/or modify it
+--  under terms of the GNU General Public License as published by the
+--  Free Software Foundation; either version 3, or (at your option)
+--  any later version. SAL 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 distributed
+--  with SAL; see file COPYING. If not, write to the Free Software
+--  Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+--  USA.
+--
+--  As a special exception, if other files instantiate generics from
+--  SAL, or you link SAL object files with other files to produce an
+--  executable, that 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.
+
+pragma License (Modified_GPL);
+
+package body SAL.Gen_Bounded_Definite_Stacks
+  with Spark_Mode
+is
+   pragma Suppress (All_Checks);
+
+   procedure Clear (Stack : in out Sgbds.Stack)
+   is begin
+      Stack.Top := 0;
+   end Clear;
+
+   function Depth (Stack : in Sgbds.Stack) return Size_Type
+     is (Stack.Top);
+
+   function Is_Empty (Stack : in Sgbds.Stack) return Boolean
+   is begin
+      return Stack.Top = 0;
+   end Is_Empty;
+
+   function Is_Full (Stack : in Sgbds.Stack) return Boolean
+   is begin
+      return Stack.Top = Stack.Size;
+   end Is_Full;
+
+   function Peek
+     (Stack : in Sgbds.Stack;
+      Index : in Peek_Type := 1)
+     return Element_Type
+     is (Stack.Data (Stack.Top - Index + 1));
+
+   procedure Pop (Stack : in out Sgbds.Stack; Count : in Base_Peek_Type := 1)
+   is begin
+      Stack.Top := Stack.Top - Count;
+   end Pop;
+
+   procedure Pop (Stack : in out Sgbds.Stack; Item : out Element_Type)
+   is begin
+      Item := Stack.Peek (1);
+      Stack.Top := Stack.Top - 1;
+   end Pop;
+
+   function Pop (Stack : in out Sgbds.Stack) return Element_Type with
+     Spark_Mode => Off
+   is begin
+      return Result : Element_Type do
+         Pop (Stack, Result);
+      end return;
+   end Pop;
+
+   procedure Push (Stack : in out Sgbds.Stack; Item : in Element_Type)
+   is begin
+      Stack.Top := Stack.Top + 1;
+      Stack.Data (Stack.Top) := Item;
+   end Push;
+
+end SAL.Gen_Bounded_Definite_Stacks;
diff --git a/sal-gen_bounded_definite_stacks.ads 
b/sal-gen_bounded_definite_stacks.ads
new file mode 100644
index 0000000..f743508
--- /dev/null
+++ b/sal-gen_bounded_definite_stacks.ads
@@ -0,0 +1,103 @@
+--  Abstract:
+--
+--  Bounded stack implementation, with full Spark verification,
+--  optimized for speed.
+--
+--  Copyright (C) 1998-2000, 2002-2003, 2009, 2015, 2017 - 2019 Free Software 
Foundation, Inc.
+--
+--  SAL is free software; you can redistribute it and/or modify it
+--  under terms of the GNU General Public License as published by the
+--  Free Software Foundation; either version 3, or (at your option)
+--  any later version. SAL 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 distributed
+--  with SAL; see file COPYING. If not, write to the Free Software
+--  Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+--  USA.
+--
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+generic
+   type Element_Type is private;
+package SAL.Gen_Bounded_Definite_Stacks
+  with Spark_Mode
+is
+   pragma Pure;
+   --  pragma Suppress (All_Checks); --  Users must check Is_Full before Push, 
Is_Empty before Pop etc.
+
+   package Sgbds renames SAL.Gen_Bounded_Definite_Stacks;
+
+   subtype Size_Type is Base_Peek_Type range 0 .. Base_Peek_Type'Last / 2;
+   --  The upper limit is needed to avoid overflow in Peek.
+   --  Zero included for Depth result.
+
+   type Stack (Size : Size_Type) is tagged private;
+   --  Tagged to allow Object.Method notation.
+
+   --  No Empty_Stack constant, to avoid requiring a Default_Element.
+
+   procedure Clear (Stack : in out Sgbds.Stack)
+   with Post'Class => Depth (Stack) = 0;
+   --  Empty Stack of all items.
+
+   function Depth (Stack : in Sgbds.Stack) return Size_Type;
+   --  Returns current count of items in Stack
+
+   function Is_Empty (Stack : in Sgbds.Stack) return Boolean
+   with Post'Class => Is_Empty'Result = (Depth (Stack) = 0);
+   --  Returns true iff no items are in Stack.
+
+   function Is_Full (Stack : in Sgbds.Stack) return Boolean
+   with Post'Class => Is_Full'Result = (Depth (Stack) = Stack.Size);
+   --  Returns true iff Stack is full.
+
+   function Peek (Stack : in Sgbds.Stack; Index : in Peek_Type := 1) return 
Element_Type
+   with Pre'Class  => Depth (Stack) in 1 .. Stack.Size and Index in 1 .. Depth 
(Stack);
+   --  Return the Index'th item from the top of Stack; the Item is _not_ 
removed.
+   --  Top item has index 1.
+
+   procedure Pop (Stack : in out Sgbds.Stack; Count : in Base_Peek_Type := 1) 
with
+     Pre'Class  => Depth (Stack) in 1 .. Stack.Size and Count in 0 .. Depth 
(Stack),
+     Post'Class => Depth (Stack) = Depth (Stack)'Old - Count and then
+                   (for all I in 1 .. Depth (Stack) => Peek (Stack'Old, I + 
Count) = Peek (Stack, I));
+   --  Remove Count Items from the top of Stack, discard them.
+
+   procedure Pop (Stack : in out Sgbds.Stack; Item : out Element_Type) with
+     Pre'Class  => Depth (Stack) in 1 .. Stack.Size,
+     Post'Class =>
+       Depth (Stack) = Depth (Stack)'Old - 1 and then
+       (Item = Peek (Stack'Old) and
+        (for all I in 1 .. Depth (Stack) => Peek (Stack'Old, I + 1) = Peek 
(Stack, I)));
+   --  Remove one item from the top of Stack, return in Item.
+
+   function Pop (Stack : in out Sgbds.Stack) return Element_Type with
+     Spark_Mode => Off;
+   --  Remove one item from the top of Stack, and return it.
+
+   procedure Push (Stack : in out Sgbds.Stack; Item : in Element_Type) with
+     Pre'Class  => Depth (Stack) in 0 .. Stack.Size - 1,
+     Post'Class =>
+       Depth (Stack) = Depth (Stack)'Old + 1 and then
+       (Item = Peek (Stack) and
+        (for all I in 1 .. Depth (Stack'Old) => Peek (Stack'Old, I) = Peek 
(Stack, I + 1)));
+   --  Add Item to the top of Stack.
+
+private
+
+   type Element_Array is array (Size_Type range <>) of aliased Element_Type;
+
+   type Stack (Size : Size_Type) is tagged record
+      Top  : Base_Peek_Type := Invalid_Peek_Index; -- empty
+      Data : Element_Array (1 .. Size);
+      --  Top of stack is at Data (Top).
+      --  Data (1 .. Top) has been set at some point.
+   end record with
+     Dynamic_Predicate => Top in 0 .. Size;
+
+end SAL.Gen_Bounded_Definite_Stacks;
diff --git a/sal-gen_bounded_definite_vectors-gen_image.adb 
b/sal-gen_bounded_definite_vectors-gen_image.adb
index e1726d1..ae901fe 100644
--- a/sal-gen_bounded_definite_vectors-gen_image.adb
+++ b/sal-gen_bounded_definite_vectors-gen_image.adb
@@ -1,39 +1,40 @@
---  Abstract :
---
---  See spec.
---
---  Copyright (C) 2018 Free Software Foundation, Inc.
---
---  This library is free software;  you can redistribute it and/or modify it
---  under terms of the  GNU General Public License  as published by the Free
---  Software  Foundation;  either version 3,  or (at your  option) any later
---  version. This library is distributed in the hope that it will be useful,
---  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
---  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
---  As a special exception under Section 7 of GPL version 3, you are granted
---  additional permissions described in the GCC Runtime Library Exception,
---  version 3.1, as published by the Free Software Foundation.
-
-pragma License (Modified_GPL);
-
-function SAL.Gen_Bounded_Definite_Vectors.Gen_Image (Item : in Vector) return 
String
-is
-   use all type SAL.Base_Peek_Type;
-   use Ada.Strings;
-   use Ada.Strings.Unbounded;
-   Result : Unbounded_String := To_Unbounded_String ("(");
-   Last   : Base_Peek_Type   := To_Peek_Index (Item.Last);
-begin
-   for I in Item.Elements (1 .. Last) loop
-      Result := Result &
-        ((if Trim
-          then Fixed.Trim (Element_Image (Item.Elements (I)), Left)
-          else Element_Image (Item.Elements (I)));
-      if I /= Last then
-         Result := Result & ", ";
-      end if;
-   end loop;
-   Result := Result & ")";
-   return To_String (Result);
-end SAL.Gen_Bounded_Definite_Vectors.Gen_Image;
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2018 - 2019 Free Software Foundation, Inc.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+with Ada.Strings.Fixed;
+with Ada.Strings.Unbounded;
+function SAL.Gen_Bounded_Definite_Vectors.Gen_Image (Item : in Vector) return 
String
+is
+   use Ada.Strings;
+   use Ada.Strings.Unbounded;
+   Result : Unbounded_String        := To_Unbounded_String ("(");
+   Last   : constant Base_Peek_Type := To_Peek_Index (Item.Last);
+begin
+   for I in 1 .. Last loop
+      Result := Result &
+        (if Trim
+         then Fixed.Trim (Element_Image (Item.Elements (I)), Left)
+         else Element_Image (Item.Elements (I)));
+      if I /= Last then
+         Result := Result & ", ";
+      end if;
+   end loop;
+   Result := Result & ")";
+   return To_String (Result);
+end SAL.Gen_Bounded_Definite_Vectors.Gen_Image;
diff --git a/sal-gen_bounded_definite_vectors-gen_refs.adb 
b/sal-gen_bounded_definite_vectors-gen_refs.adb
new file mode 100644
index 0000000..8f9d741
--- /dev/null
+++ b/sal-gen_bounded_definite_vectors-gen_refs.adb
@@ -0,0 +1,35 @@
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2019 Free Software Foundation, Inc.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+package body SAL.Gen_Bounded_Definite_Vectors.Gen_Refs is
+
+   function Variable_Ref
+     (Container : aliased in out Vector;
+      Index     :         in     Index_Type)
+     return Variable_Reference_Type
+   is begin
+      return (Element => Container.Elements (To_Peek_Index (Index))'Access, 
Dummy => 1);
+   end Variable_Ref;
+
+   function Constant_Ref (Container : aliased in Vector; Index : in 
Index_Type) return Constant_Reference_Type
+   is begin
+      return (Element => Container.Elements (To_Peek_Index (Index))'Access, 
Dummy => 1);
+   end Constant_Ref;
+
+end SAL.Gen_Bounded_Definite_Vectors.Gen_Refs;
diff --git a/sal-gen_bounded_definite_vectors-gen_refs.ads 
b/sal-gen_bounded_definite_vectors-gen_refs.ads
new file mode 100644
index 0000000..c235cbc
--- /dev/null
+++ b/sal-gen_bounded_definite_vectors-gen_refs.ads
@@ -0,0 +1,54 @@
+--  Abstract :
+--
+--  Variable_Reference for parent.
+--
+--  In a child package because it's not Spark, and Spark does not
+--  allow 'Spark_Mode => Off' on type declarations.
+--
+--  Copyright (C) 2019 Free Software Foundation, Inc.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+generic
+package SAL.Gen_Bounded_Definite_Vectors.Gen_Refs
+  with Spark_Mode => Off
+is
+
+   type Variable_Reference_Type (Element : not null access Element_Type) is 
private with
+     Implicit_Dereference => Element;
+
+   function Variable_Ref (Container : aliased in out Vector; Index : in 
Index_Type) return Variable_Reference_Type
+   with Inline,
+     Pre => Index in Index_Type'First .. Last_Index (Container);
+
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is private with
+     Implicit_Dereference => Element;
+
+   function Constant_Ref (Container : aliased in Vector; Index : in 
Index_Type) return Constant_Reference_Type
+   with Inline,
+     Pre => Index in Index_Type'First .. Last_Index (Container);
+
+private
+
+   type Variable_Reference_Type (Element : not null access Element_Type) is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
+
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
+
+end SAL.Gen_Bounded_Definite_Vectors.Gen_Refs;
diff --git a/sal-gen_bounded_definite_vectors-gen_sorted.ads 
b/sal-gen_bounded_definite_vectors-gen_sorted.ads
deleted file mode 100644
index e6e101f..0000000
--- a/sal-gen_bounded_definite_vectors-gen_sorted.ads
+++ /dev/null
@@ -1,52 +0,0 @@
---  Abstract :
---
---  Add sorted behavior to parent.
---
---  Copyright (C) 2018, 2019 Free Software Foundation, Inc.
---
---  This library is free software;  you can redistribute it and/or modify it
---  under terms of the  GNU General Public License  as published by the Free
---  Software  Foundation;  either version 3,  or (at your  option) any later
---  version. This library is distributed in the hope that it will be useful,
---  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
---  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
---  As a special exception under Section 7 of GPL version 3, you are granted
---  additional permissions described in the GCC Runtime Library Exception,
---  version 3.1, as published by the Free Software Foundation.
-
-pragma License (Modified_GPL);
-
-generic
-   with function Element_Compare (Left, Right : in Element_Type) return 
Compare_Result;
-package SAL.Gen_Bounded_Definite_Vectors.Gen_Sorted is
-
-   type Vector is new SAL.Gen_Bounded_Definite_Vectors.Vector with null record;
-
-   overriding procedure Append (Container : in out Vector; New_Item : in 
Element_Type)
-   with Inline => True;
-   --  Raises Programmer_Error
-
-   overriding procedure Prepend (Container : in out Vector; New_Item : in 
Element_Type)
-   with Inline => True;
-   --  Raises Programmer_Error
-
-   overriding
-   procedure Insert
-     (Container : in out Vector;
-      New_Item  : in     Element_Type;
-      Before    : in     Extended_Index)
-   with Inline => True;
-   --  Raises Programmer_Error
-
-   not overriding
-   procedure Insert
-     (Container       : in out Vector;
-      New_Item        : in     Element_Type;
-      Ignore_If_Equal : in     Boolean := False);
-   --  Insert New_Item in sorted position. Items are sorted in increasing
-   --  order according to Element_Compare. New_Item is inserted after
-   --  Equal items, unless Ignore_If_Equal is true, in which case
-   --  New_Item is not inserted.
-
-end SAL.Gen_Bounded_Definite_Vectors.Gen_Sorted;
diff --git a/sal-gen_bounded_definite_vectors.adb 
b/sal-gen_bounded_definite_vectors.adb
index 42f5643..17aac08 100644
--- a/sal-gen_bounded_definite_vectors.adb
+++ b/sal-gen_bounded_definite_vectors.adb
@@ -1,224 +1,120 @@
---  Abstract :
---
---  See spec.
---
---  Copyright (C) 2017, 2018 Free Software Foundation, Inc.
---
---  This library is free software;  you can redistribute it and/or modify it
---  under terms of the  GNU General Public License  as published by the Free
---  Software  Foundation;  either version 3,  or (at your  option) any later
---  version. This library is distributed in the hope that it will be useful,
---  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
---  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
---  As a special exception under Section 7 of GPL version 3, you are granted
---  additional permissions described in the GCC Runtime Library Exception,
---  version 3.1, as published by the Free Software Foundation.
-
-pragma License (Modified_GPL);
-
-package body SAL.Gen_Bounded_Definite_Vectors is
-
-   function Length (Container : in Vector) return Ada.Containers.Count_Type
-   is begin
-      --  We assume the type ranges are sensible, so no exceptions occur
-      --  here.
-      return Ada.Containers.Count_Type (Container.Last - Index_Type'First + 1);
-   end Length;
-
-   function Is_Full (Container : in Vector) return Boolean
-   is begin
-      return To_Peek_Index (Container.Last) = Peek_Type (Capacity);
-   end Is_Full;
-
-   procedure Clear (Container : in out Vector)
-   is begin
-      Container.Last := No_Index;
-   end Clear;
-
-   function Element (Container : Vector; Index : Index_Type) return 
Element_Type
-   is begin
-      return Container.Elements (Peek_Type (Index - Index_Type'First + 1));
-   end Element;
-
-   function Last_Index (Container : Vector) return Extended_Index
-   is begin
-      return Container.Last;
-   end Last_Index;
-
-   procedure Set_Last (Container : in out Vector; Last : in Index_Type)
-   is begin
-      Container.Last := Last;
-   end Set_Last;
-
-   procedure Append (Container : in out Vector; New_Item : in Element_Type)
-   is
-      J : constant Peek_Type := To_Peek_Index (Container.Last + 1);
-   begin
-      if J > Container.Elements'Last then
-         raise Container_Full;
-      end if;
-      Container.Elements (J) := New_Item;
-      Container.Last := Container.Last + 1;
-   end Append;
-
-   procedure Prepend (Container : in out Vector; New_Item : in Element_Type)
-   is
-      J : constant Peek_Type := Peek_Type (Container.Last + 1 - 
Index_Type'First + 1);
-   begin
-      if J > Container.Elements'Last then
-         raise Container_Full;
-      end if;
-
-      Container.Elements (2 .. J) := Container.Elements (1 .. J - 1);
-      Container.Elements (1) := New_Item;
-      Container.Last := Container.Last + 1;
-   end Prepend;
-
-   procedure Insert
-     (Container : in out Vector;
-      New_Item  : in     Element_Type;
-      Before    : in     Extended_Index)
-   is
-      J : constant Peek_Type := To_Peek_Index ((if Before = No_Index then 
Container.Last + 1 else Before));
-      K : constant Base_Peek_Type := To_Peek_Index (Container.Last);
-   begin
-      if K + 1 > Container.Elements'Last then
-         raise Container_Full;
-      end if;
-
-      Container.Elements (J + 1 .. K + 1) := Container.Elements (J .. K);
-      Container.Elements (J) := New_Item;
-      Container.Last := Container.Last + 1;
-   end Insert;
-
-   function "+" (Item : in Element_Type) return Vector
-   is begin
-      return Result : Vector do
-         Result.Append (Item);
-      end return;
-   end "+";
-
-   function "&" (Left : in Vector; Right : in Element_Type) return Vector
-   is begin
-      return Result : Vector := Left do
-         Result.Append (Right);
-      end return;
-   end "&";
-
-   procedure Delete_First (Container : in out Vector; Count : in Index_Type := 
1)
-   is
-      J : constant Peek_Type := Peek_Type (Container.Last - Index_Type'First + 
Count);
-   begin
-      if Count > Container.Last then
-         raise Container_Empty;
-      end if;
-      Container.Elements (1 .. J - 1) := Container.Elements (2 .. J);
-      Container.Last := Container.Last - Count;
-   end Delete_First;
-
-   function Constant_Reference (Container : aliased Vector; Index : in 
Index_Type) return Constant_Reference_Type
-   is
-      J : constant Peek_Type := Peek_Type (Index - Index_Type'First + 1);
-   begin
-      if Index > Container.Last then
-         raise Constraint_Error;
-      end if;
-      return (Element => Container.Elements (J)'Access);
-   end Constant_Reference;
-
-   function Variable_Reference
-     (Container : aliased in out Vector;
-      Index     :         in     Index_Type)
-     return Variable_Reference_Type
-   is
-      J : constant Peek_Type := Peek_Type (Index - Index_Type'First + 1);
-   begin
-      if Index > Container.Last then
-         raise Constraint_Error;
-      end if;
-      return (Element => Container.Elements (J)'Access);
-   end Variable_Reference;
-
-   function Has_Element (Position : Cursor) return Boolean is
-   begin
-      if Position.Container = null then
-         return False;
-      end if;
-
-      return Position.Index <= Position.Container.Last;
-   end Has_Element;
-
-   overriding function First (Object : Iterator) return Cursor
-   is begin
-      if Object.Container.Last = No_Index then
-         return (null, Index_Type'First);
-      else
-         return (Object.Container, Object.Container.First_Index);
-      end if;
-   end First;
-
-   overriding function Last  (Object : Iterator) return Cursor
-   is begin
-      if Object.Container.Last = No_Index then
-         return (null, Index_Type'First);
-      else
-         return (Object.Container, Object.Container.Last_Index);
-      end if;
-   end Last;
-
-   overriding function Next
-     (Object   : Iterator;
-      Position : Cursor) return Cursor
-   is begin
-      if Position.Index = Object.Container.Last then
-         return (null, Index_Type'First);
-      else
-         return (Object.Container, Position.Index + 1);
-      end if;
-   end Next;
-
-   overriding function Previous
-     (Object   : Iterator;
-      Position : Cursor) return Cursor
-   is begin
-      if Position.Index = Index_Type'First then
-         return (null, Index_Type'First);
-      else
-         return (Object.Container, Position.Index - 1);
-      end if;
-   end Previous;
-
-   function Iterate (Container : Vector) return 
Vector_Iterator_Interfaces.Reversible_Iterator'Class
-   is begin
-      return Iterator'
-        (Container => Container'Unrestricted_Access,
-         Index     => No_Index);
-   end Iterate;
-
-   function Constant_Reference (Container : aliased Vector; Position : in 
Cursor) return Constant_Reference_Type
-   is
-      J : constant Peek_Type := Peek_Type (Position.Index - Index_Type'First + 
1);
-   begin
-      return (Element => Container.Elements (J)'Access);
-   end Constant_Reference;
-
-   function Variable_Reference
-     (Container : aliased in out Vector;
-      Position  :         in     Cursor)
-     return Variable_Reference_Type
-   is
-      J : constant Peek_Type := Peek_Type (Position.Index - Index_Type'First + 
1);
-   begin
-      return (Element => Container.Elements (J)'Access);
-   end Variable_Reference;
-
-   ----------
-   --  Spec private functions
-
-   function To_Peek_Index (Index : in Extended_Index) return Base_Peek_Type
-   is begin
-      return Base_Peek_Type (Index - Index_Type'First + 1);
-   end To_Peek_Index;
-
-end SAL.Gen_Bounded_Definite_Vectors;
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2017 - 2019 Free Software Foundation, Inc.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+package body SAL.Gen_Bounded_Definite_Vectors
+  with Spark_Mode
+is
+   pragma Suppress (All_Checks);
+
+   function Length (Container : in Vector) return Ada.Containers.Count_Type
+   is (Ada.Containers.Count_Type (To_Peek_Index (Container.Last)));
+
+   function Is_Full (Container : in Vector) return Boolean
+   is begin
+      return Length (Container) = Capacity;
+   end Is_Full;
+
+   procedure Clear (Container : in out Vector)
+   is begin
+      Container.Last := No_Index;
+   end Clear;
+
+   function Element (Container : Vector; Index : Index_Type) return 
Element_Type
+   is (Container.Elements (Peek_Type (Index - Index_Type'First + 1)));
+
+   procedure Replace_Element
+     (Container : in out Vector;
+      Index     : in     Index_Type;
+      New_Item  : in     Element_Type)
+   is begin
+      Container.Elements (To_Peek_Index (Index)) := New_Item;
+   end Replace_Element;
+
+   function Last_Index (Container : Vector) return Extended_Index
+   is (Container.Last);
+
+   procedure Append (Container : in out Vector; New_Item : in Element_Type)
+   is
+      J : constant Peek_Type := To_Peek_Index (Container.Last + 1);
+   begin
+      Container.Elements (J) := New_Item;
+      Container.Last := Container.Last + 1;
+   end Append;
+
+   procedure Prepend (Container : in out Vector; New_Item : in Element_Type)
+   is
+      J : constant Peek_Type := Peek_Type (Container.Last + 1 - 
Index_Type'First + 1);
+   begin
+      Container.Elements (2 .. J) := Container.Elements (1 .. J - 1);
+      Container.Elements (1) := New_Item;
+      Container.Last := Container.Last + 1;
+   end Prepend;
+
+   procedure Insert
+     (Container : in out Vector;
+      New_Item  : in     Element_Type;
+      Before    : in     Extended_Index)
+   is
+      J : constant Peek_Type := To_Peek_Index ((if Before = No_Index then 
Container.Last + 1 else Before));
+      K : constant Base_Peek_Type := To_Peek_Index (Container.Last);
+   begin
+      Container.Elements (J + 1 .. K + 1) := Container.Elements (J .. K);
+      Container.Elements (J) := New_Item;
+      Container.Last := Container.Last + 1;
+   end Insert;
+
+   function "+" (Item : in Element_Type) return Vector
+   is begin
+      return Result : Vector do
+         Append (Result, Item);
+      end return;
+   end "+";
+
+   function "&" (Left : in Vector; Right : in Element_Type) return Vector
+   is begin
+      --  WORKAROUND: If init Result with ":= Left", GNAT Community 2019
+      --  checks Default_Initial_Condition (which fails when Left is not
+      --  empty)! That is only supposed to be checked when initialized by
+      --  default. Reported to AdaCore as ticket S724-042.
+      return Result : Vector do
+         Result := Left;
+         Append (Result, Right);
+      end return;
+   end "&";
+
+   procedure Delete_First (Container : in out Vector; Count : in 
Ada.Containers.Count_Type := 1)
+   is
+      use Ada.Containers;
+   begin
+      if Count = 0 then
+         return;
+      end if;
+
+      declare
+         New_Last : constant Extended_Index := Extended_Index (Integer 
(Container.Last) - Integer (Count));
+         J        : constant Base_Peek_Type := Base_Peek_Type (Count);
+         K        : constant Peek_Type      := To_Peek_Index (Container.Last);
+      begin
+         --  Delete items 1 .. J, shift remaining down.
+         Container.Elements (1 .. K - J) := Container.Elements (J + 1 .. K);
+         Container.Last := New_Last;
+      end;
+   end Delete_First;
+
+end SAL.Gen_Bounded_Definite_Vectors;
diff --git a/sal-gen_bounded_definite_vectors.ads 
b/sal-gen_bounded_definite_vectors.ads
index 9747704..9e698e2 100644
--- a/sal-gen_bounded_definite_vectors.ads
+++ b/sal-gen_bounded_definite_vectors.ads
@@ -1,154 +1,145 @@
---  Abstract :
---
---  A simple bounded vector of definite items, intended to be faster
---  than Ada.Containers.Bounded_Definite_Vectors.
---
---  Copyright (C) 2017 - 2019 Free Software Foundation, Inc.
---
---  This library is free software;  you can redistribute it and/or modify it
---  under terms of the  GNU General Public License  as published by the Free
---  Software  Foundation;  either version 3,  or (at your  option) any later
---  version. This library is distributed in the hope that it will be useful,
---  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
---  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
---  As a special exception under Section 7 of GPL version 3, you are granted
---  additional permissions described in the GCC Runtime Library Exception,
---  version 3.1, as published by the Free Software Foundation.
-
-pragma License (Modified_GPL);
-
-with Ada.Iterator_Interfaces;
-generic
-   type Index_Type is range <>;
-   type Element_Type is private;
-   Capacity : in Ada.Containers.Count_Type;
-package SAL.Gen_Bounded_Definite_Vectors is
-
-   subtype Extended_Index is Index_Type'Base
-     range Index_Type'First - 1 ..
-           Index_Type'Min (Index_Type'Base'Last - 1, Index_Type'Last) + 1;
-
-   No_Index : constant Extended_Index := Extended_Index'First;
-
-   type Vector is tagged private with
-      Constant_Indexing => Constant_Reference,
-      Variable_Indexing => Variable_Reference,
-      Default_Iterator  => Iterate,
-      Iterator_Element  => Element_Type;
-
-   Empty_Vector : constant Vector;
-
-   function Length (Container : in Vector) return Ada.Containers.Count_Type;
-
-   function Is_Full (Container : in Vector) return Boolean;
-
-   procedure Clear (Container : in out Vector);
-
-   function First_Index (Container : in Vector) return Index_Type is 
(Index_Type'First);
-
-   function Last_Index (Container : in Vector) return Extended_Index;
-   --  No_Index when Container is empty.
-
-   procedure Set_Last (Container : in out Vector; Last : in Index_Type);
-   --  Elements with indices < Last that have not been set are undefined.
-
-   function Element (Container : Vector; Index : Index_Type) return 
Element_Type;
-   --  Index of first element in vector is Index_Type'First.
-
-   procedure Append (Container : in out Vector; New_Item : in Element_Type);
-   --  Raises Container_Full if full (more useful than a precondition failure).
-
-   procedure Prepend (Container : in out Vector; New_Item : in Element_Type);
-   --  Insert New_Item at beginning of Container; current elements slide right.
-
-   procedure Insert
-     (Container : in out Vector;
-      New_Item  : in     Element_Type;
-      Before    : in     Extended_Index);
-   --  Insert New_Item before Before, or after Last_Index if Before is
-   --  No_Index. Current elements at Before and after slide right.
-   --  New_Item then has index Before.
-
-   function "+" (Item : in Element_Type) return Vector;
-   function "&" (Left : in Vector; Right : in Element_Type) return Vector;
-
-   procedure Delete_First (Container : in out Vector; Count : in Index_Type := 
1);
-   --  Remaining elements slide down.
-
-   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is null record
-   with Implicit_Dereference => Element;
-
-   function Constant_Reference (Container : aliased Vector; Index : in 
Index_Type) return Constant_Reference_Type;
-   pragma Inline (Constant_Reference);
-
-   type Variable_Reference_Type (Element : not null access Element_Type) is 
null record
-   with Implicit_Dereference => Element;
-
-   function Variable_Reference
-     (Container : aliased in out Vector;
-      Index     :         in     Index_Type)
-     return Variable_Reference_Type;
-   pragma Inline (Variable_Reference);
-
-   type Cursor is private;
-
-   function Has_Element (Position : Cursor) return Boolean;
-
-   package Vector_Iterator_Interfaces is new Ada.Iterator_Interfaces (Cursor, 
Has_Element);
-
-   function Iterate (Container : Vector) return 
Vector_Iterator_Interfaces.Reversible_Iterator'Class;
-
-   function Constant_Reference (Container : aliased Vector; Position : in 
Cursor) return Constant_Reference_Type;
-   pragma Inline (Constant_Reference);
-
-   function Variable_Reference
-     (Container : aliased in out Vector;
-      Position  :         in     Cursor)
-     return Variable_Reference_Type;
-   pragma Inline (Variable_Reference);
-
-private
-
-   type Array_Type is array (Peek_Type range 1 .. Peek_Type (Capacity)) of 
aliased Element_Type;
-
-   type Vector is tagged
-   record
-      Elements : Array_Type := (others => <>);
-      Last     : Extended_Index := No_Index;
-   end record;
-
-   type Vector_Access is access all Vector;
-   for Vector_Access'Storage_Size use 0;
-
-   type Cursor is record
-      Container : Vector_Access;
-      Index     : Index_Type := Index_Type'First;
-   end record;
-
-   type Iterator is new Vector_Iterator_Interfaces.Reversible_Iterator with
-   record
-      Container : Vector_Access;
-      Index     : Index_Type'Base;
-   end record;
-
-   overriding function First (Object : Iterator) return Cursor;
-   overriding function Last  (Object : Iterator) return Cursor;
-
-   overriding function Next
-     (Object   : Iterator;
-      Position : Cursor) return Cursor;
-
-   overriding function Previous
-     (Object   : Iterator;
-      Position : Cursor) return Cursor;
-
-   Empty_Vector : constant Vector := (others => <>);
-
-   ----------
-   --  For child units
-
-   function To_Peek_Index (Index : in Extended_Index) return Base_Peek_Type
-   with Inline;
-
-end SAL.Gen_Bounded_Definite_Vectors;
+--  Abstract :
+--
+--  A simple bounded vector of definite items, in Spark.
+--
+--  Copyright (C) 2017 - 2019 Free Software Foundation, Inc.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+generic
+   type Index_Type is range <>;
+   type Element_Type is private;
+   Capacity : in Ada.Containers.Count_Type;
+package SAL.Gen_Bounded_Definite_Vectors
+  with Spark_Mode
+is
+   use all type Ada.Containers.Count_Type;
+
+   subtype Extended_Index is Index_Type'Base
+     range Index_Type'First - 1 ..
+           Index_Type'Min (Index_Type'Base'Last - 1, Index_Type'Last) + 1;
+
+   pragma Assert (Capacity <= Ada.Containers.Count_Type (Index_Type'Last - 
Index_Type'First + 1));
+
+   No_Index : constant Extended_Index := Extended_Index'First;
+
+   type Vector is private with
+     Default_Initial_Condition => Length (Vector) = 0;
+
+   function Length (Container : in Vector) return Ada.Containers.Count_Type 
with
+     Post => Length'Result in 0 .. Capacity;
+
+   function Is_Full (Container : in Vector) return Boolean with
+     Post => Is_Full'Result = (Length (Container) = Capacity);
+
+   function Has_Space (Container : in Vector; Item_Count : in 
Ada.Containers.Count_Type) return Boolean
+     is (Length (Container) + Item_Count <= Capacity)
+     with Pre => Item_Count <= Ada.Containers.Count_Type'Last - Length 
(Container);
+
+   procedure Clear (Container : in out Vector) with
+     Post => Length (Container) = 0;
+
+   function First_Index (Container : in Vector) return Index_Type is 
(Index_Type'First) with
+       Depends => (First_Index'Result => null, null => Container);
+
+   function Last_Index (Container : in Vector) return Extended_Index;
+   --  No_Index when Container is empty.
+
+   function Element (Container : in Vector; Index : in Index_Type) return 
Element_Type
+   with Pre => Index <= Last_Index (Container);
+   --  Index of first element in Vector is Index_Type'First.
+
+   procedure Replace_Element
+     (Container : in out Vector;
+      Index     : in     Index_Type;
+      New_Item  : in     Element_Type)
+   with
+     Pre  => Index <= Last_Index (Container),
+     Post => Element (Container, Index) = New_Item;
+   --  Index of first element in Vector is Index_Type'First.
+
+   procedure Append (Container : in out Vector; New_Item : in Element_Type) 
with
+     Pre  => Length (Container) < Capacity,
+     Post => Length (Container) = Length (Container'Old) + 1 and
+             Element (Container, Last_Index (Container)) = New_Item and
+             (for all I in Index_Type'First .. Last_Index (Container) - 1 =>
+                Element (Container'Old, I) = Element (Container, I));
+
+   procedure Prepend (Container : in out Vector; New_Item : in Element_Type) 
with
+     Pre  => Length (Container) < Capacity,
+     Post => Length (Container) = Length (Container'Old) + 1 and then
+             (Element (Container, Index_Type'First) = New_Item and
+              (for all I in Index_Type'First .. Last_Index (Container'Old) =>
+                 Element (Container'Old, I) = Element (Container, I + 1)));
+   --  Insert New_Item at beginning of Container; current elements slide right.
+
+   procedure Insert
+     (Container : in out Vector;
+      New_Item  : in     Element_Type;
+      Before    : in     Extended_Index) with
+     Pre  => Length (Container) < Capacity and Before <= Last_Index 
(Container),
+     Contract_Cases =>
+       (Before = No_Index =>
+          Length (Container) = Length (Container'Old) + 1 and
+          Element (Container, Last_Index (Container)) = New_Item and
+          (for all I in Index_Type'First .. Last_Index (Container) - 1 =>
+             Element (Container'Old, I) = Element (Container, I)),
+        Before /= No_Index =>
+          Length (Container) = Length (Container'Old) + 1 and
+          Element (Container, Before) = New_Item and
+             (for all I in Index_Type'First .. Before - 1 =>
+                Element (Container'Old, I) = Element (Container, I)) and
+             (for all I in Before + 1 .. Last_Index (Container) =>
+                Element (Container'Old, I - 1) = Element (Container, I)));
+   --  Insert New_Item before Before, or after Last_Index if Before is
+   --  No_Index. Current elements at Before and after slide right.
+   --  New_Item then has index Before.
+
+   function "+" (Item : in Element_Type) return Vector with
+     Post => Length ("+"'Result) = 1 and
+             Element ("+"'Result, Index_Type'First) = Item;
+
+   function "&" (Left : in Vector; Right : in Element_Type) return Vector with
+     Pre  => Length (Left) < Capacity,
+     Post => Length ("&"'Result) = Length (Left) + 1 and
+             (for all I in Index_Type'First .. Last_Index (Left) => Element 
(Left, I) = Element ("&"'Result, I)) and
+             Element ("&"'Result, Last_Index ("&"'Result)) = Right;
+
+   procedure Delete_First (Container : in out Vector; Count : in 
Ada.Containers.Count_Type := 1) with
+     Pre  => Length (Container) >= Count,
+     Post => Length (Container) = Length (Container)'Old - Count and then
+             (for all I in Index_Type'First .. Last_Index (Container) =>
+                Element (Container'Old, Index_Type (Integer (I) + Integer 
(Count))) = Element (Container, I));
+   --  Remaining elements slide down.
+
+private
+
+   type Array_Type is array (Peek_Type range 1 .. Peek_Type (Capacity)) of 
aliased Element_Type;
+
+   type Vector is
+   record
+      Elements : Array_Type;
+      Last     : Extended_Index := No_Index;
+   end record with
+     Type_Invariant => To_Peek_Index (Last) <= Elements'Last;
+   pragma Annotate (GNATprove, Intentional, "type ""Vector"" is not fully 
initialized",
+                    "Only items in Elements with index < Last are accessed");
+
+   ----------
+   --  For child units
+
+   function To_Peek_Index (Index : in Extended_Index) return Base_Peek_Type is
+     (Base_Peek_Type (Index - Index_Type'First + 1));
+
+end SAL.Gen_Bounded_Definite_Vectors;
diff --git a/sal-gen_bounded_definite_vectors-gen_image.adb 
b/sal-gen_bounded_definite_vectors_sorted-gen_image_aux.adb
similarity index 59%
copy from sal-gen_bounded_definite_vectors-gen_image.adb
copy to sal-gen_bounded_definite_vectors_sorted-gen_image_aux.adb
index e1726d1..cee9c74 100644
--- a/sal-gen_bounded_definite_vectors-gen_image.adb
+++ b/sal-gen_bounded_definite_vectors_sorted-gen_image_aux.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  Copyright (C) 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2019 Free Software Foundation, Inc.
 --
 --  This library is free software;  you can redistribute it and/or modify it
 --  under terms of the  GNU General Public License  as published by the Free
@@ -17,23 +17,19 @@
 
 pragma License (Modified_GPL);
 
-function SAL.Gen_Bounded_Definite_Vectors.Gen_Image (Item : in Vector) return 
String
+with Ada.Strings.Unbounded;
+function SAL.Gen_Bounded_Definite_Vectors_Sorted.Gen_Image_Aux (Item : in 
Vector; Aux : in Aux_Data) return String
 is
-   use all type SAL.Base_Peek_Type;
-   use Ada.Strings;
    use Ada.Strings.Unbounded;
-   Result : Unbounded_String := To_Unbounded_String ("(");
-   Last   : Base_Peek_Type   := To_Peek_Index (Item.Last);
+   Result : Unbounded_String        := To_Unbounded_String ("(");
+   Last   : constant Base_Peek_Type := Item.Last;
 begin
-   for I in Item.Elements (1 .. Last) loop
-      Result := Result &
-        ((if Trim
-          then Fixed.Trim (Element_Image (Item.Elements (I)), Left)
-          else Element_Image (Item.Elements (I)));
+   for I in 1 .. Last loop
+      Result := Result & Element_Image (Item.Elements (I), Aux);
       if I /= Last then
          Result := Result & ", ";
       end if;
    end loop;
    Result := Result & ")";
    return To_String (Result);
-end SAL.Gen_Bounded_Definite_Vectors.Gen_Image;
+end SAL.Gen_Bounded_Definite_Vectors_Sorted.Gen_Image_Aux;
diff --git a/sal-gen_bounded_definite_vectors_sorted-gen_image_aux.ads 
b/sal-gen_bounded_definite_vectors_sorted-gen_image_aux.ads
new file mode 100644
index 0000000..28c56fd
--- /dev/null
+++ b/sal-gen_bounded_definite_vectors_sorted-gen_image_aux.ads
@@ -0,0 +1,23 @@
+--  Abstract :
+--
+--  Image with auxiliary data for instantiations of parent.
+--
+--  Copyright (C) 2019 Free Software Foundation, Inc.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+generic
+   type Aux_Data (<>) is private;
+   with function Element_Image (Item : in Element_Type; Aux : in Aux_Data) 
return String;
+function SAL.Gen_Bounded_Definite_Vectors_Sorted.Gen_Image_Aux (Item : in 
Vector; Aux : in Aux_Data) return String;
diff --git a/sal-gen_bounded_definite_vectors_sorted-gen_refs.adb 
b/sal-gen_bounded_definite_vectors_sorted-gen_refs.adb
new file mode 100644
index 0000000..fb5d619
--- /dev/null
+++ b/sal-gen_bounded_definite_vectors_sorted-gen_refs.adb
@@ -0,0 +1,29 @@
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2019 Free Software Foundation, Inc.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+package body SAL.Gen_Bounded_Definite_Vectors_Sorted.Gen_Refs is
+
+   function Constant_Ref (Container : aliased Vector; Index : in Peek_Type) 
return Constant_Reference_Type
+   is begin
+      return
+        (Element => Container.Elements (Index)'Access,
+         Dummy   => 1);
+   end Constant_Ref;
+
+end SAL.Gen_Bounded_Definite_Vectors_Sorted.Gen_Refs;
diff --git a/sal-gen_bounded_definite_vectors_sorted-gen_refs.ads 
b/sal-gen_bounded_definite_vectors_sorted-gen_refs.ads
new file mode 100644
index 0000000..17e98c4
--- /dev/null
+++ b/sal-gen_bounded_definite_vectors_sorted-gen_refs.ads
@@ -0,0 +1,39 @@
+--  Abstract :
+--
+--  Constant_Reference for parent.
+--
+--  In a child package because it's not Spark, and Spark does not
+--  allow 'Spark_Mode => Off' on type declarations.
+--
+--  Copyright (C) 2019 Free Software Foundation, Inc.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+generic
+package SAL.Gen_Bounded_Definite_Vectors_Sorted.Gen_Refs is
+
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is private with
+     Implicit_Dereference => Element;
+
+   function Constant_Ref (Container : aliased Vector; Index : in Peek_Type) 
return Constant_Reference_Type with
+     Inline;
+
+private
+
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
+
+end SAL.Gen_Bounded_Definite_Vectors_Sorted.Gen_Refs;
diff --git a/sal-gen_bounded_definite_vectors-gen_sorted.adb 
b/sal-gen_bounded_definite_vectors_sorted.adb
similarity index 56%
rename from sal-gen_bounded_definite_vectors-gen_sorted.adb
rename to sal-gen_bounded_definite_vectors_sorted.adb
index b77f06c..9979f28 100644
--- a/sal-gen_bounded_definite_vectors-gen_sorted.adb
+++ b/sal-gen_bounded_definite_vectors_sorted.adb
@@ -1,90 +1,82 @@
---  Abstract :
---
---  See spec.
---
---  Copyright (C) 2018 - 2019 Free Software Foundation, Inc.
---
---  This library is free software;  you can redistribute it and/or modify it
---  under terms of the  GNU General Public License  as published by the Free
---  Software  Foundation;  either version 3,  or (at your  option) any later
---  version. This library is distributed in the hope that it will be useful,
---  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
---  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
---  As a special exception under Section 7 of GPL version 3, you are granted
---  additional permissions described in the GCC Runtime Library Exception,
---  version 3.1, as published by the Free Software Foundation.
-
-pragma License (Modified_GPL);
-
-package body SAL.Gen_Bounded_Definite_Vectors.Gen_Sorted is
-
-   overriding procedure Append (Container : in out Vector; New_Item : in 
Element_Type)
-   is begin
-      raise Programmer_Error;
-   end Append;
-
-   overriding procedure Prepend (Container : in out Vector; New_Item : in 
Element_Type)
-   is begin
-      raise Programmer_Error;
-   end Prepend;
-
-   overriding
-   procedure Insert
-     (Container : in out Vector;
-      New_Item  : in     Element_Type;
-      Before    : in     Extended_Index)
-   is begin
-      raise Programmer_Error;
-   end Insert;
-
-   procedure Insert
-     (Container       : in out Vector;
-      New_Item        : in     Element_Type;
-      Ignore_If_Equal : in     Boolean := False)
-   is
-      K : constant Base_Peek_Type := To_Peek_Index (Container.Last);
-      J : Base_Peek_Type := K;
-   begin
-      if K + 1 > Container.Elements'Last then
-         raise Container_Full;
-
-      elsif K = 0 then
-         --  Container empty
-         Container.Last := Container.Last + 1;
-         Container.Elements (1) := New_Item;
-         return;
-      end if;
-
-      loop
-         exit when J < 1;
-
-         case Element_Compare (New_Item, Container.Elements (J)) is
-         when Less =>
-            J := J - 1;
-         when Equal =>
-            if Ignore_If_Equal then
-               return;
-            else
-               --  Insert after J
-               exit;
-            end if;
-         when Greater =>
-            --  Insert after J
-            exit;
-         end case;
-      end loop;
-
-      if J = 0 then
-         --  Insert before all
-         Container.Elements (2 .. K + 1) := Container.Elements (1 .. K);
-         Container.Elements (1) := New_Item;
-      else
-         --  Insert after J
-         Container.Elements (J + 2 .. K + 1) := Container.Elements (J + 1 .. 
K);
-         Container.Elements (J + 1) := New_Item;
-      end if;
-      Container.Last := Container.Last + 1;
-   end Insert;
-
-end SAL.Gen_Bounded_Definite_Vectors.Gen_Sorted;
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2018 - 2019 Free Software Foundation, Inc.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+package body SAL.Gen_Bounded_Definite_Vectors_Sorted is
+
+   function Length (Container : in Vector) return Ada.Containers.Count_Type
+     is (Ada.Containers.Count_Type (Container.Last));
+
+   function Is_Full (Container : in Vector) return Boolean
+   is begin
+      return Container.Last = Peek_Type (Capacity);
+   end Is_Full;
+
+   procedure Clear (Container : in out Vector)
+   is begin
+      Container.Last := No_Index;
+   end Clear;
+
+   function Last_Index (Container : in Vector) return Base_Peek_Type
+     is (Container.Last);
+
+   function Element (Container : in Vector; Index : in Peek_Type) return 
Element_Type
+     is (Container.Elements (Index));
+
+   procedure Insert
+     (Container       : in out Vector;
+      New_Item        : in     Element_Type;
+      Ignore_If_Equal : in     Boolean := False)
+   is
+      K : constant Base_Peek_Type := Container.Last;
+      J : Base_Peek_Type := K;
+   begin
+      if K = 0 then
+         --  Container empty
+         Container.Last := 1;
+         Container.Elements (1) := New_Item;
+         return;
+      end if;
+
+      loop
+         pragma Loop_Invariant (J < Container.Elements'Last);
+         pragma Loop_Variant (Decreases => J);
+         exit when J < 1;
+
+         case Element_Compare (New_Item, Container.Elements (J)) is
+         when Less =>
+            J := J - 1;
+         when Equal =>
+            if Ignore_If_Equal then
+               return;
+            else
+               --  Insert after J
+               exit;
+            end if;
+         when Greater =>
+            --  Insert after J
+            exit;
+         end case;
+      end loop;
+
+      Container.Elements (J + 2 .. K + 1) := Container.Elements (J + 1 .. K);
+      Container.Elements (J + 1) := New_Item;
+      Container.Last := Container.Last + 1;
+   end Insert;
+
+end SAL.Gen_Bounded_Definite_Vectors_Sorted;
diff --git a/sal-gen_bounded_definite_vectors_sorted.ads 
b/sal-gen_bounded_definite_vectors_sorted.ads
new file mode 100644
index 0000000..7bc398c
--- /dev/null
+++ b/sal-gen_bounded_definite_vectors_sorted.ads
@@ -0,0 +1,85 @@
+--  Abstract :
+--
+--  A simple bounded sorted vector of definite items, in Spark.
+--
+--  Copyright (C) 2018 - 2019 Free Software Foundation, Inc.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+generic
+   type Element_Type is private;
+   with function Element_Compare (Left, Right : in Element_Type) return 
Compare_Result;
+   Capacity : in Ada.Containers.Count_Type;
+package SAL.Gen_Bounded_Definite_Vectors_Sorted
+  with Spark_Mode
+is
+   use all type Ada.Containers.Count_Type;
+
+   type Vector is private with
+     Default_Initial_Condition => Length (Vector) = 0;
+
+   function Length (Container : in Vector) return Ada.Containers.Count_Type 
with
+     Post => Length'Result in 0 .. Capacity;
+
+   function Is_Full (Container : in Vector) return Boolean with
+     Post => Is_Full'Result = (Length (Container) = Capacity);
+
+   procedure Clear (Container : in out Vector) with
+     Post => Length (Container) = 0;
+
+   function First_Index (Container : in Vector) return Peek_Type
+     is (Peek_Type'First) with
+       Depends => (First_Index'Result => null, null => Container);
+
+   function Last_Index (Container : in Vector) return Base_Peek_Type with
+     Inline;
+
+   function Element (Container : in Vector; Index : in Peek_Type) return 
Element_Type with
+     Pre => Index in First_Index (Container) .. Last_Index (Container);
+
+   function Is_Sorted (Container : in Vector) return Boolean is
+     (for all I in First_Index (Container) .. Last_Index (Container) - 1 =>
+        Element_Compare (Element (Container, I), Element (Container, I + 1)) 
in Less | Equal);
+
+   procedure Insert
+     (Container       : in out Vector;
+      New_Item        : in     Element_Type;
+      Ignore_If_Equal : in     Boolean := False) with
+     Pre  => Length (Container) < Capacity,
+     Post => Is_Sorted (Container) and
+             (Length (Container) = Length (Container'Old) or
+              Length (Container) = Length (Container'Old) + 1);
+   --  Insert New_Item in sorted position. Items are sorted in increasing
+   --  order according to Element_Compare. New_Item is inserted after
+   --  Equal items, unless Ignore_If_Equal is true, in which case
+   --  New_Item is not inserted.
+   --
+   --  The presense of Ignore_If_Equal makes it too difficult to prove
+   --  whether the length did or did not increase.
+
+private
+
+   type Array_Type is array (Peek_Type range 1 .. Peek_Type (Capacity)) of 
aliased Element_Type;
+
+   No_Index : constant Base_Peek_Type := 0;
+
+   type Vector is record
+      Elements : Array_Type;
+      Last     : Base_Peek_Type := No_Index;
+   end record with
+     Type_Invariant => Last <= Elements'Last and Is_Sorted (Vector);
+   pragma Annotate (GNATprove, Intentional, "type ""Vector"" is not fully 
initialized",
+                    "Only items in Elements with index < Last are accessed");
+
+end SAL.Gen_Bounded_Definite_Vectors_Sorted;
diff --git a/sal-gen_definite_doubly_linked_lists.adb 
b/sal-gen_definite_doubly_linked_lists.adb
index e8da73a..f0c5776 100644
--- a/sal-gen_definite_doubly_linked_lists.adb
+++ b/sal-gen_definite_doubly_linked_lists.adb
@@ -2,7 +2,7 @@
 --
 --  see spec
 --
---  Copyright (C) 2017, 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2017 - 2019 Free Software Foundation, Inc.
 --
 --  This library is free software; you can redistribute it and/or
 --  modify it under terms of the GNU General Public License as
@@ -24,6 +24,21 @@ pragma License (Modified_GPL);
 
 package body SAL.Gen_Definite_Doubly_Linked_Lists is
 
+   procedure Delete_Node (Container : in out List; Node : in out Node_Access)
+   is begin
+      if Node.Next = null then
+         Container.Tail := Node.Prev;
+      else
+         Node.Next.Prev := Node.Prev;
+      end if;
+      if Node.Prev = null then
+         Container.Head := Node.Next;
+      else
+         Node.Prev.Next := Node.Next;
+      end if;
+      Free (Node);
+   end Delete_Node;
+
    ---------
    --  Public operations, declaration order.
 
@@ -186,23 +201,21 @@ package body SAL.Gen_Definite_Doubly_Linked_Lists is
    procedure Delete (Container : in out List; Position : in out Cursor)
    is
       use all type Ada.Containers.Count_Type;
-      Node : Node_Access renames Position.Ptr;
    begin
-      if Node.Next = null then
-         Container.Tail := Node.Prev;
-      else
-         Node.Next.Prev := Node.Prev;
-      end if;
-      if Node.Prev = null then
-         Container.Head := Node.Next;
-      else
-         Node.Prev.Next := Node.Next;
-      end if;
-      Free (Node);
+      Delete_Node (Container, Position.Ptr);
       Position        := No_Element;
       Container.Count := Container.Count - 1;
    end Delete;
 
+   procedure Delete_First (Container : in out List)
+   is
+      use all type Ada.Containers.Count_Type;
+      Node : Node_Access := Container.Head;
+   begin
+      Delete_Node (Container, Node);
+      Container.Count := Container.Count - 1;
+   end Delete_First;
+
    procedure Insert
      (Container : in out List;
       Before    : in     Cursor;
@@ -252,25 +265,25 @@ package body SAL.Gen_Definite_Doubly_Linked_Lists is
    is
       pragma Unreferenced (Container);
    begin
-      return (Element => Position.Ptr.all.Element'Access);
+      return (Element => Position.Ptr.all.Element'Access, Dummy => 1);
    end Constant_Reference;
 
    function Constant_Ref (Position : in Cursor) return Constant_Reference_Type
    is begin
-      return (Element => Position.Ptr.all.Element'Access);
+      return (Element => Position.Ptr.all.Element'Access, Dummy => 1);
    end Constant_Ref;
 
-   function Reference (Container : in List; Position : in Cursor) return 
Reference_Type
+   function Variable_Reference (Container : in List; Position : in Cursor) 
return Variable_Reference_Type
    is
       pragma Unreferenced (Container);
    begin
-      return (Element => Position.Ptr.all.Element'Access);
-   end Reference;
+      return (Element => Position.Ptr.all.Element'Access, Dummy => 1);
+   end Variable_Reference;
 
-   function Ref (Position : in Cursor) return Reference_Type
+   function Variable_Ref (Position : in Cursor) return Variable_Reference_Type
    is begin
-      return (Element => Position.Ptr.all.Element'Access);
-   end Ref;
+      return (Element => Position.Ptr.all.Element'Access, Dummy => 1);
+   end Variable_Ref;
 
    function Iterate (Container : aliased in List) return 
Iterator_Interfaces.Reversible_Iterator'Class
    is begin
diff --git a/sal-gen_definite_doubly_linked_lists.ads 
b/sal-gen_definite_doubly_linked_lists.ads
index fda94c4..9648320 100644
--- a/sal-gen_definite_doubly_linked_lists.ads
+++ b/sal-gen_definite_doubly_linked_lists.ads
@@ -34,7 +34,7 @@ package SAL.Gen_Definite_Doubly_Linked_Lists is
    type List is new Ada.Finalization.Controlled with private
    with
       Constant_Indexing => Constant_Reference,
-      Variable_Indexing => Reference,
+      Variable_Indexing => Variable_Reference,
       Default_Iterator  => Iterate,
       Iterator_Element  => Element_Type;
 
@@ -52,6 +52,8 @@ package SAL.Gen_Definite_Doubly_Linked_Lists is
    overriding procedure Finalize (Container : in out List);
    --  Free all items in List.
 
+   procedure Clear (Container : in out List) renames Finalize;
+
    function Length (Container : in List) return Ada.Containers.Count_Type;
 
    procedure Append (Container : in out List; Element : in Element_Type);
@@ -83,6 +85,8 @@ package SAL.Gen_Definite_Doubly_Linked_Lists is
    procedure Delete (Container : in out List; Position : in out Cursor)
    with Pre => Position /= No_Element;
 
+   procedure Delete_First (Container : in out List);
+
    procedure Insert
      (Container : in out List;
       Before    : in     Cursor;
@@ -92,25 +96,24 @@ package SAL.Gen_Definite_Doubly_Linked_Lists is
    function Persistent_Ref (Position : in Cursor) return access Element_Type
    with Pre => Position /= No_Element;
 
-   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is null record
-   with Implicit_Dereference => Element;
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is private with
+     Implicit_Dereference => Element;
 
    function Constant_Reference (Container : in List; Position : in Cursor) 
return Constant_Reference_Type
-   with Pre => Position /= No_Element;
-   pragma Inline (Constant_Reference);
+   with Inline, Pre => Position /= No_Element;
+   --  Not 'Constant_Ref' because that is taken, and it is wrong for 
Constant_Indexing
+
    function Constant_Ref (Position : in Cursor) return Constant_Reference_Type
-   with Pre => Position /= No_Element;
-   pragma Inline (Constant_Ref);
+   with Inline, Pre => Position /= No_Element;
 
-   type Reference_Type (Element : not null access Element_Type) is null record
-   with Implicit_Dereference => Element;
+   type Variable_Reference_Type (Element : not null access Element_Type) is 
private with
+     Implicit_Dereference => Element;
 
-   function Reference (Container : in List; Position : in Cursor) return 
Reference_Type
-   with Pre => Position /= No_Element;
-   pragma Inline (Reference);
-   function Ref (Position : in Cursor) return Reference_Type
-   with Pre => Position /= No_Element;
-   pragma Inline (Ref);
+   function Variable_Reference (Container : in List; Position : in Cursor) 
return Variable_Reference_Type
+   with Inline, Pre => Position /= No_Element;
+
+   function Variable_Ref (Position : in Cursor) return Variable_Reference_Type
+   with Inline, Pre => Position /= No_Element;
 
    package Iterator_Interfaces is new Ada.Iterator_Interfaces (Cursor, 
Has_Element);
 
@@ -140,6 +143,16 @@ private
       Ptr       : Node_Access;
    end record;
 
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
+
+   type Variable_Reference_Type (Element : not null access Element_Type) is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
+
    Empty_List : constant List := (Ada.Finalization.Controlled with null, null, 
0);
 
    No_Element : constant Cursor := (null, null);
diff --git a/sal-gen_definite_doubly_linked_lists_sorted.adb 
b/sal-gen_definite_doubly_linked_lists_sorted.adb
index addac4f..7a4bb11 100644
--- a/sal-gen_definite_doubly_linked_lists_sorted.adb
+++ b/sal-gen_definite_doubly_linked_lists_sorted.adb
@@ -1,542 +1,542 @@
---  Abstract :
---
---  See spec.
---
---  Copyright (C) 2018 Free Software Foundation, Inc.
---
---  This library is free software;  you can redistribute it and/or modify it
---  under terms of the  GNU General Public License  as published by the Free
---  Software  Foundation;  either version 3,  or (at your  option) any later
---  version. This library is distributed in the hope that it will be useful,
---  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
---  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
---  As a special exception under Section 7 of GPL version 3, you are granted
---  additional permissions described in the GCC Runtime Library Exception,
---  version 3.1, as published by the Free Software Foundation.
-
-pragma License (Modified_GPL);
-
-package body SAL.Gen_Definite_Doubly_Linked_Lists_Sorted is
-
-   ----------
-   --  Body subprograms, alphabetical
-
-   procedure Find
-     (Container     : in     List;
-      Element       : in     Element_Type;
-      Found         :    out Node_Access;
-      Found_Compare :    out Compare_Result)
-   is
-      --  Return pointer to first item in Container for which Compare (item,
-      --  element) returns True or Greater. If no such element exists, Found
-      --  is null, Found_Compare is Less.
-      use Ada.Containers;
-   begin
-      if Container.Head = null then
-         Found         := null;
-         Found_Compare := Less;
-         return;
-      end if;
-
-      declare
-         Low_Index  : Count_Type  := 1;
-         High_Index : Count_Type  := Container.Count;
-         Next_Node  : Node_Access := Container.Head;
-         Next_Index : Count_Type  := Low_Index;
-         Old_Index  : Count_Type;
-      begin
-         loop
-            Old_Index  := Next_Index;
-            Next_Index := (Low_Index + High_Index) / 2;
-
-            if Next_Index > Old_Index then
-               for I in Old_Index + 1 .. Next_Index loop
-                  Next_Node := Next_Node.Next;
-               end loop;
-            elsif Next_Index < Old_Index then
-               for I in Next_Index .. Old_Index - 1 loop
-                  Next_Node := Next_Node.Prev;
-               end loop;
-            end if;
-
-            case Element_Compare (Next_Node.Element, Element) is
-            when Less =>
-               if Next_Index = High_Index then
-                  --  no more nodes to check
-                  Found         := null;
-                  Found_Compare := Less;
-                  return;
-               elsif Next_Index = Low_Index then
-                  --  force check of high_index
-                  Low_Index := High_Index;
-               else
-                  Low_Index := Next_Index;
-               end if;
-
-            when Equal =>
-               Found         := Next_Node;
-               Found_Compare := Equal;
-               return;
-
-            when Greater =>
-               if Low_Index = Next_Index then
-                  --  no more nodes to check
-                  Found         := Next_Node;
-                  Found_Compare := Greater;
-                  return;
-               elsif High_Index = Next_Index then
-                  --  Desired result is either high_index or low_index
-                  pragma Assert (Low_Index + 1 = High_Index);
-                  case Element_Compare (Next_Node.Prev.Element, Element) is
-                  when Less =>
-                     Found         := Next_Node;
-                     Found_Compare := Greater;
-                     return;
-                  when Equal =>
-                     Found         := Next_Node.Prev;
-                     Found_Compare := Equal;
-                     return;
-                  when Greater =>
-                     Found         := Next_Node.Prev;
-                     Found_Compare := Greater;
-                     return;
-                  end case;
-               else
-                  High_Index := Next_Index;
-               end if;
-            end case;
-         end loop;
-      end;
-   end Find;
-
-   procedure Insert_Before
-     (Container : in out List;
-      Before    : in     Node_Access;
-      Element   : in     Element_Type)
-   is
-      New_Node : constant Node_Access := new Node_Type'
-        (Element => Element,
-         Prev    => Before.Prev,
-         Next    => Before);
-   begin
-      if Before = Container.Head then
-         Before.Prev    := New_Node;
-         Container.Head := New_Node;
-      else
-         Before.Prev.Next := New_Node;
-         Before.Prev      := New_Node;
-      end if;
-   end Insert_Before;
-
-   procedure Insert_After_Tail
-     (Container : in out List;
-      Element   : in     Element_Type)
-   is
-      New_Node : constant Node_Access := new Node_Type'
-        (Element => Element,
-         Prev    => Container.Tail,
-         Next    => null);
-   begin
-      Container.Tail.Next := New_Node;
-      Container.Tail      := New_Node;
-   end Insert_After_Tail;
-
-   ---------
-   --  Public operations, declaration order.
-
-   overriding
-   procedure Adjust (Container : in out List)
-   is
-      Next_Source : Node_Access := Container.Head;
-      New_Node    : Node_Access;
-   begin
-      if Next_Source = null then
-         return;
-      end if;
-
-      Container.Tail := null;
-
-      loop
-         New_Node := new Node_Type'
-           (Element => Next_Source.Element,
-            Next    => null,
-            Prev    => Container.Tail);
-         if Container.Tail = null then
-            Container.Head := New_Node;
-            Container.Tail := New_Node;
-         else
-            Container.Tail.Next := New_Node;
-            Container.Tail      := New_Node;
-         end if;
-         Next_Source := Next_Source.Next;
-         exit when Next_Source = null;
-      end loop;
-   end Adjust;
-
-   overriding
-   procedure Finalize (Container : in out List)
-   is
-      Next : Node_Access := Container.Head;
-   begin
-      loop
-         exit when Next = null;
-         Next := Container.Head.Next;
-         Free (Container.Head);
-         Container.Head := Next;
-      end loop;
-      Container.Tail := null;
-   end Finalize;
-
-   overriding function "=" (Left, Right : in List) return Boolean
-   is
-      Left_I  : Node_Access := Left.Head;
-      Right_I : Node_Access := Right.Head;
-   begin
-      loop
-         exit when Left_I = null;
-
-         if Right_I = null then
-            return False;
-         elsif Left_I.Element /= Right_I.Element then
-            return False;
-         end if;
-
-         Left_I  := Left_I.Next;
-         Right_I := Right_I.Next;
-      end loop;
-      return Right_I = null;
-   end "=";
-
-   function Length (Container : in List) return Ada.Containers.Count_Type
-   is begin
-      return Container.Count;
-   end Length;
-
-   function To_List (Element : in Element_Type) return List
-   is
-      New_Node : constant Node_Access := new Node_Type'
-        (Element => Element,
-         Prev    => null,
-         Next    => null);
-   begin
-      return Result : constant List :=
-        (Ada.Finalization.Controlled with
-         Head  => New_Node,
-         Tail  => New_Node,
-         Count => 1);
-   end To_List;
-
-   procedure Insert (Container : in out List; Element : in Element_Type)
-   is
-      Node    : Node_Access := Container.Head;
-      Compare : Compare_Result;
-   begin
-      if Node = null then
-         Container := To_List (Element);
-      else
-         Find (Container, Element, Node, Compare);
-
-         Container.Count := Container.Count + 1;
-
-         if Node = null then
-            Insert_After_Tail (Container, Element);
-         else
-            Insert_Before (Container, Node, Element);
-         end if;
-      end if;
-   end Insert;
-
-   function Contains (Container : in List; Element : in Element_Type) return 
Boolean
-   is
-      Node    : Node_Access := Container.Head;
-      Compare : Compare_Result;
-   begin
-      Find (Container, Element, Node, Compare);
-      return Compare = Equal;
-   end Contains;
-
-   procedure Merge
-     (Target : in out List;
-      Source : in     List;
-      Added  :    out Boolean)
-   is
-      Target_I : Node_Access := Target.Head;
-      Source_I : Node_Access := Source.Head;
-   begin
-      if Target_I = null then
-         if Source_I = null then
-            Added := False;
-         else
-            Target.Head  := Source.Head;
-            Target.Tail  := Source.Tail;
-            Target.Count := Source.Count;
-            Adjust (Target);
-
-            Added := True;
-         end if;
-
-      elsif Source_I = null then
-         Added := False;
-
-      else
-         Added := False;
-         loop
-            exit when Source_I = null;
-
-            if Target_I = null then
-               Added := True;
-               Target.Count := Target.Count + 1;
-               Insert_After_Tail (Target, Source_I.Element);
-               Source_I := Source_I.Next;
-
-            else
-               case Element_Compare (Target_I.Element, Source_I.Element) is
-               when Greater =>
-                  Added := True;
-                  Target.Count := Target.Count + 1;
-                  Insert_Before (Target, Target_I, Source_I.Element);
-                  Source_I := Source_I.Next;
-
-               when Equal =>
-                  Target_I := Target_I.Next;
-                  Source_I := Source_I.Next;
-
-               when Less =>
-                  Target_I := Target_I.Next;
-               end case;
-            end if;
-         end loop;
-      end if;
-   end Merge;
-
-   procedure Merge
-     (Target  : in out List;
-      Source  : in     List;
-      Added   :    out Boolean;
-      Exclude : in     Element_Type)
-   is
-      Target_I : Node_Access := Target.Head;
-      Source_I : Node_Access := Source.Head;
-   begin
-      Added := False;
-
-      if Target_I = null then
-         if Source_I = null then
-            return;
-         else
-            loop
-               if Source_I = null then
-                  return;
-               end if;
-               exit when Source_I.Element /= Exclude;
-               Source_I := Source_I.Next;
-            end loop;
-
-            Added    := True;
-            Target   := To_List (Source_I.Element);
-            Source_I := Source_I.Next;
-         end if;
-      end if;
-
-      loop
-         exit when Source_I = null;
-
-         if Source_I.Element = Exclude then
-            Source_I := Source_I.Next;
-
-         elsif Target_I = null then
-            Added := True;
-            Target.Count := Target.Count + 1;
-            Insert_After_Tail (Target, Source_I.Element);
-            Source_I := Source_I.Next;
-
-         else
-            case Element_Compare (Target_I.Element, Source_I.Element) is
-            when Greater =>
-               Added := True;
-               Target.Count := Target.Count + 1;
-               Insert_Before (Target, Target_I, Source_I.Element);
-               Source_I := Source_I.Next;
-
-            when Equal =>
-               Target_I := Target_I.Next;
-               Source_I := Source_I.Next;
-
-            when Less =>
-               Target_I := Target_I.Next;
-            end case;
-         end if;
-      end loop;
-   end Merge;
-
-   function Has_Element (Position : in Cursor) return Boolean
-   is begin
-      return Position.Ptr /= null;
-   end Has_Element;
-
-   function First (Container : in List) return Cursor
-   is begin
-      if Container.Head = null then
-         return No_Element;
-      else
-         return (Container'Unrestricted_Access, Container.Head);
-      end if;
-   end First;
-
-   function Last (Container : in List) return Cursor
-   is begin
-      if Container.Tail = null then
-         return No_Element;
-      else
-         return (Container'Unrestricted_Access, Container.Tail);
-      end if;
-   end Last;
-
-   function Find (Container : in List; Element : in Element_Type) return Cursor
-   is
-      Node    : Node_Access;
-      Compare : Compare_Result;
-   begin
-      Find (Container, Element, Node, Compare);
-
-      if Node = null then
-         return No_Element;
-      elsif Compare = Equal then
-         return (Container'Unrestricted_Access, Node);
-      else
-         return No_Element;
-      end if;
-   end Find;
-
-   procedure Next (Position : in out Cursor)
-   is begin
-      if Position.Ptr /= null then
-         if Position.Ptr.Next = null then
-            Position := No_Element;
-         else
-            Position.Ptr := Position.Ptr.Next;
-         end if;
-      end if;
-   end Next;
-
-   function Next (Position : in Cursor) return Cursor
-   is begin
-      if Position.Ptr = null then
-         return Position;
-      else
-         if Position.Ptr.Next = null then
-            return No_Element;
-         else
-            return (Position.Container, Position.Ptr.Next);
-         end if;
-      end if;
-   end Next;
-
-   function Previous (Position : in Cursor) return Cursor
-   is begin
-      if Position.Ptr = null then
-         return Position;
-      else
-         if Position.Ptr.Prev = null then
-            return No_Element;
-         else
-            return (Position.Container, Position.Ptr.Prev);
-         end if;
-      end if;
-   end Previous;
-
-   function Element (Position : in Cursor) return Element_Type
-   is begin
-      return Position.Ptr.Element;
-   end Element;
-
-   procedure Delete (Container : in out List; Position : in out Cursor)
-   is
-      Node : Node_Access renames Position.Ptr;
-   begin
-      if Node.Next = null then
-         Container.Tail := Node.Prev;
-      else
-         Node.Next.Prev := Node.Prev;
-      end if;
-      if Node.Prev = null then
-         Container.Head := Node.Next;
-      else
-         Node.Prev.Next := Node.Next;
-      end if;
-      Free (Node);
-      Position        := No_Element;
-      Container.Count := Container.Count - 1;
-   end Delete;
-
-   function Pop (Container : in out List) return Element_Type
-   is
-      Node : Node_Access := Container.Head;
-   begin
-      return Result : constant Element_Type := Container.Head.Element do
-         Container.Head := Node.Next;
-         if Node.Next = null then
-            Container.Tail := null;
-         else
-            Node.Next.Prev := null;
-         end if;
-         Free (Node);
-         Container.Count := Container.Count - 1;
-      end return;
-   end Pop;
-
-   function Constant_Reference (Container : in List; Position : in Cursor) 
return Constant_Reference_Type
-   is
-      pragma Unreferenced (Container);
-   begin
-      return (Element => Position.Ptr.all.Element'Access);
-   end Constant_Reference;
-
-   function Constant_Ref (Position : in Cursor) return Constant_Reference_Type
-   is begin
-      return (Element => Position.Ptr.all.Element'Access);
-   end Constant_Ref;
-
-   function Reference (Container : in List; Position : in Cursor) return 
Reference_Type
-   is
-      pragma Unreferenced (Container);
-   begin
-      return (Element => Position.Ptr.all.Element'Access);
-   end Reference;
-
-   function Ref (Position : in Cursor) return Reference_Type
-   is begin
-      return (Element => Position.Ptr.all.Element'Access);
-   end Ref;
-
-   function Iterate (Container : aliased in List) return 
Iterator_Interfaces.Reversible_Iterator'Class
-   is begin
-      return Iterator'(Container => Container'Unrestricted_Access);
-   end Iterate;
-
-   overriding function First (Object : Iterator) return Cursor
-   is begin
-      return First (Object.Container.all);
-   end First;
-
-   overriding function Last  (Object : Iterator) return Cursor
-   is begin
-      return Last (Object.Container.all);
-   end Last;
-
-   overriding function Next (Object : in Iterator; Position : in Cursor) 
return Cursor
-   is
-      pragma Unreferenced (Object);
-   begin
-      return Next (Position);
-   end Next;
-
-   overriding function Previous (Object : in Iterator; Position : in Cursor) 
return Cursor
-   is
-      pragma Unreferenced (Object);
-   begin
-      return Previous (Position);
-   end Previous;
-
-end SAL.Gen_Definite_Doubly_Linked_Lists_Sorted;
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2018, 2019 Free Software Foundation, Inc.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+package body SAL.Gen_Definite_Doubly_Linked_Lists_Sorted is
+
+   ----------
+   --  Body subprograms, alphabetical
+
+   procedure Find
+     (Container     : in     List;
+      Element       : in     Element_Type;
+      Found         :    out Node_Access;
+      Found_Compare :    out Compare_Result)
+   is
+      --  Return pointer to first item in Container for which Compare (item,
+      --  element) returns True or Greater. If no such element exists, Found
+      --  is null, Found_Compare is Less.
+      use Ada.Containers;
+   begin
+      if Container.Head = null then
+         Found         := null;
+         Found_Compare := Less;
+         return;
+      end if;
+
+      declare
+         Low_Index  : Count_Type  := 1;
+         High_Index : Count_Type  := Container.Count;
+         Next_Node  : Node_Access := Container.Head;
+         Next_Index : Count_Type  := Low_Index;
+         Old_Index  : Count_Type;
+      begin
+         loop
+            Old_Index  := Next_Index;
+            Next_Index := (Low_Index + High_Index) / 2;
+
+            if Next_Index > Old_Index then
+               for I in Old_Index + 1 .. Next_Index loop
+                  Next_Node := Next_Node.Next;
+               end loop;
+            elsif Next_Index < Old_Index then
+               for I in Next_Index .. Old_Index - 1 loop
+                  Next_Node := Next_Node.Prev;
+               end loop;
+            end if;
+
+            case Element_Compare (Next_Node.Element, Element) is
+            when Less =>
+               if Next_Index = High_Index then
+                  --  no more nodes to check
+                  Found         := null;
+                  Found_Compare := Less;
+                  return;
+               elsif Next_Index = Low_Index then
+                  --  force check of high_index
+                  Low_Index := High_Index;
+               else
+                  Low_Index := Next_Index;
+               end if;
+
+            when Equal =>
+               Found         := Next_Node;
+               Found_Compare := Equal;
+               return;
+
+            when Greater =>
+               if Low_Index = Next_Index then
+                  --  no more nodes to check
+                  Found         := Next_Node;
+                  Found_Compare := Greater;
+                  return;
+               elsif High_Index = Next_Index then
+                  --  Desired result is either high_index or low_index
+                  pragma Assert (Low_Index + 1 = High_Index);
+                  case Element_Compare (Next_Node.Prev.Element, Element) is
+                  when Less =>
+                     Found         := Next_Node;
+                     Found_Compare := Greater;
+                     return;
+                  when Equal =>
+                     Found         := Next_Node.Prev;
+                     Found_Compare := Equal;
+                     return;
+                  when Greater =>
+                     Found         := Next_Node.Prev;
+                     Found_Compare := Greater;
+                     return;
+                  end case;
+               else
+                  High_Index := Next_Index;
+               end if;
+            end case;
+         end loop;
+      end;
+   end Find;
+
+   procedure Insert_Before
+     (Container : in out List;
+      Before    : in     Node_Access;
+      Element   : in     Element_Type)
+   is
+      New_Node : constant Node_Access := new Node_Type'
+        (Element => Element,
+         Prev    => Before.Prev,
+         Next    => Before);
+   begin
+      if Before = Container.Head then
+         Before.Prev    := New_Node;
+         Container.Head := New_Node;
+      else
+         Before.Prev.Next := New_Node;
+         Before.Prev      := New_Node;
+      end if;
+   end Insert_Before;
+
+   procedure Insert_After_Tail
+     (Container : in out List;
+      Element   : in     Element_Type)
+   is
+      New_Node : constant Node_Access := new Node_Type'
+        (Element => Element,
+         Prev    => Container.Tail,
+         Next    => null);
+   begin
+      Container.Tail.Next := New_Node;
+      Container.Tail      := New_Node;
+   end Insert_After_Tail;
+
+   ---------
+   --  Public operations, declaration order.
+
+   overriding
+   procedure Adjust (Container : in out List)
+   is
+      Next_Source : Node_Access := Container.Head;
+      New_Node    : Node_Access;
+   begin
+      if Next_Source = null then
+         return;
+      end if;
+
+      Container.Tail := null;
+
+      loop
+         New_Node := new Node_Type'
+           (Element => Next_Source.Element,
+            Next    => null,
+            Prev    => Container.Tail);
+         if Container.Tail = null then
+            Container.Head := New_Node;
+            Container.Tail := New_Node;
+         else
+            Container.Tail.Next := New_Node;
+            Container.Tail      := New_Node;
+         end if;
+         Next_Source := Next_Source.Next;
+         exit when Next_Source = null;
+      end loop;
+   end Adjust;
+
+   overriding
+   procedure Finalize (Container : in out List)
+   is
+      Next : Node_Access := Container.Head;
+   begin
+      loop
+         exit when Next = null;
+         Next := Container.Head.Next;
+         Free (Container.Head);
+         Container.Head := Next;
+      end loop;
+      Container.Tail := null;
+   end Finalize;
+
+   overriding function "=" (Left, Right : in List) return Boolean
+   is
+      Left_I  : Node_Access := Left.Head;
+      Right_I : Node_Access := Right.Head;
+   begin
+      loop
+         exit when Left_I = null;
+
+         if Right_I = null then
+            return False;
+         elsif Left_I.Element /= Right_I.Element then
+            return False;
+         end if;
+
+         Left_I  := Left_I.Next;
+         Right_I := Right_I.Next;
+      end loop;
+      return Right_I = null;
+   end "=";
+
+   function Length (Container : in List) return Ada.Containers.Count_Type
+   is begin
+      return Container.Count;
+   end Length;
+
+   function To_List (Element : in Element_Type) return List
+   is
+      New_Node : constant Node_Access := new Node_Type'
+        (Element => Element,
+         Prev    => null,
+         Next    => null);
+   begin
+      return Result : constant List :=
+        (Ada.Finalization.Controlled with
+         Head  => New_Node,
+         Tail  => New_Node,
+         Count => 1);
+   end To_List;
+
+   procedure Insert (Container : in out List; Element : in Element_Type)
+   is
+      Node    : Node_Access := Container.Head;
+      Compare : Compare_Result;
+   begin
+      if Node = null then
+         Container := To_List (Element);
+      else
+         Find (Container, Element, Node, Compare);
+
+         Container.Count := Container.Count + 1;
+
+         if Node = null then
+            Insert_After_Tail (Container, Element);
+         else
+            Insert_Before (Container, Node, Element);
+         end if;
+      end if;
+   end Insert;
+
+   function Contains (Container : in List; Element : in Element_Type) return 
Boolean
+   is
+      Node    : Node_Access := Container.Head;
+      Compare : Compare_Result;
+   begin
+      Find (Container, Element, Node, Compare);
+      return Compare = Equal;
+   end Contains;
+
+   procedure Merge
+     (Target : in out List;
+      Source : in     List;
+      Added  :    out Boolean)
+   is
+      Target_I : Node_Access := Target.Head;
+      Source_I : Node_Access := Source.Head;
+   begin
+      if Target_I = null then
+         if Source_I = null then
+            Added := False;
+         else
+            Target.Head  := Source.Head;
+            Target.Tail  := Source.Tail;
+            Target.Count := Source.Count;
+            Adjust (Target);
+
+            Added := True;
+         end if;
+
+      elsif Source_I = null then
+         Added := False;
+
+      else
+         Added := False;
+         loop
+            exit when Source_I = null;
+
+            if Target_I = null then
+               Added := True;
+               Target.Count := Target.Count + 1;
+               Insert_After_Tail (Target, Source_I.Element);
+               Source_I := Source_I.Next;
+
+            else
+               case Element_Compare (Target_I.Element, Source_I.Element) is
+               when Greater =>
+                  Added := True;
+                  Target.Count := Target.Count + 1;
+                  Insert_Before (Target, Target_I, Source_I.Element);
+                  Source_I := Source_I.Next;
+
+               when Equal =>
+                  Target_I := Target_I.Next;
+                  Source_I := Source_I.Next;
+
+               when Less =>
+                  Target_I := Target_I.Next;
+               end case;
+            end if;
+         end loop;
+      end if;
+   end Merge;
+
+   procedure Merge
+     (Target  : in out List;
+      Source  : in     List;
+      Added   :    out Boolean;
+      Exclude : in     Element_Type)
+   is
+      Target_I : Node_Access := Target.Head;
+      Source_I : Node_Access := Source.Head;
+   begin
+      Added := False;
+
+      if Target_I = null then
+         if Source_I = null then
+            return;
+         else
+            loop
+               if Source_I = null then
+                  return;
+               end if;
+               exit when Source_I.Element /= Exclude;
+               Source_I := Source_I.Next;
+            end loop;
+
+            Added    := True;
+            Target   := To_List (Source_I.Element);
+            Source_I := Source_I.Next;
+         end if;
+      end if;
+
+      loop
+         exit when Source_I = null;
+
+         if Source_I.Element = Exclude then
+            Source_I := Source_I.Next;
+
+         elsif Target_I = null then
+            Added := True;
+            Target.Count := Target.Count + 1;
+            Insert_After_Tail (Target, Source_I.Element);
+            Source_I := Source_I.Next;
+
+         else
+            case Element_Compare (Target_I.Element, Source_I.Element) is
+            when Greater =>
+               Added := True;
+               Target.Count := Target.Count + 1;
+               Insert_Before (Target, Target_I, Source_I.Element);
+               Source_I := Source_I.Next;
+
+            when Equal =>
+               Target_I := Target_I.Next;
+               Source_I := Source_I.Next;
+
+            when Less =>
+               Target_I := Target_I.Next;
+            end case;
+         end if;
+      end loop;
+   end Merge;
+
+   function Has_Element (Position : in Cursor) return Boolean
+   is begin
+      return Position.Ptr /= null;
+   end Has_Element;
+
+   function First (Container : in List) return Cursor
+   is begin
+      if Container.Head = null then
+         return No_Element;
+      else
+         return (Container'Unrestricted_Access, Container.Head);
+      end if;
+   end First;
+
+   function Last (Container : in List) return Cursor
+   is begin
+      if Container.Tail = null then
+         return No_Element;
+      else
+         return (Container'Unrestricted_Access, Container.Tail);
+      end if;
+   end Last;
+
+   function Find (Container : in List; Element : in Element_Type) return Cursor
+   is
+      Node    : Node_Access;
+      Compare : Compare_Result;
+   begin
+      Find (Container, Element, Node, Compare);
+
+      if Node = null then
+         return No_Element;
+      elsif Compare = Equal then
+         return (Container'Unrestricted_Access, Node);
+      else
+         return No_Element;
+      end if;
+   end Find;
+
+   procedure Next (Position : in out Cursor)
+   is begin
+      if Position.Ptr /= null then
+         if Position.Ptr.Next = null then
+            Position := No_Element;
+         else
+            Position.Ptr := Position.Ptr.Next;
+         end if;
+      end if;
+   end Next;
+
+   function Next (Position : in Cursor) return Cursor
+   is begin
+      if Position.Ptr = null then
+         return Position;
+      else
+         if Position.Ptr.Next = null then
+            return No_Element;
+         else
+            return (Position.Container, Position.Ptr.Next);
+         end if;
+      end if;
+   end Next;
+
+   function Previous (Position : in Cursor) return Cursor
+   is begin
+      if Position.Ptr = null then
+         return Position;
+      else
+         if Position.Ptr.Prev = null then
+            return No_Element;
+         else
+            return (Position.Container, Position.Ptr.Prev);
+         end if;
+      end if;
+   end Previous;
+
+   function Element (Position : in Cursor) return Element_Type
+   is begin
+      return Position.Ptr.Element;
+   end Element;
+
+   procedure Delete (Container : in out List; Position : in out Cursor)
+   is
+      Node : Node_Access renames Position.Ptr;
+   begin
+      if Node.Next = null then
+         Container.Tail := Node.Prev;
+      else
+         Node.Next.Prev := Node.Prev;
+      end if;
+      if Node.Prev = null then
+         Container.Head := Node.Next;
+      else
+         Node.Prev.Next := Node.Next;
+      end if;
+      Free (Node);
+      Position        := No_Element;
+      Container.Count := Container.Count - 1;
+   end Delete;
+
+   function Pop (Container : in out List) return Element_Type
+   is
+      Node : Node_Access := Container.Head;
+   begin
+      return Result : constant Element_Type := Container.Head.Element do
+         Container.Head := Node.Next;
+         if Node.Next = null then
+            Container.Tail := null;
+         else
+            Node.Next.Prev := null;
+         end if;
+         Free (Node);
+         Container.Count := Container.Count - 1;
+      end return;
+   end Pop;
+
+   function Constant_Reference (Container : in List; Position : in Cursor) 
return Constant_Reference_Type
+   is
+      pragma Unreferenced (Container);
+   begin
+      return (Element => Position.Ptr.all.Element'Access, Dummy => 1);
+   end Constant_Reference;
+
+   function Constant_Ref (Position : in Cursor) return Constant_Reference_Type
+   is begin
+      return (Element => Position.Ptr.all.Element'Access, Dummy => 1);
+   end Constant_Ref;
+
+   function Variable_Reference (Container : in List; Position : in Cursor) 
return Variable_Reference_Type
+   is
+      pragma Unreferenced (Container);
+   begin
+      return (Element => Position.Ptr.all.Element'Access, Dummy => 1);
+   end Variable_Reference;
+
+   function Variable_Ref (Position : in Cursor) return Variable_Reference_Type
+   is begin
+      return (Element => Position.Ptr.all.Element'Access, Dummy => 1);
+   end Variable_Ref;
+
+   function Iterate (Container : aliased in List) return 
Iterator_Interfaces.Reversible_Iterator'Class
+   is begin
+      return Iterator'(Container => Container'Unrestricted_Access);
+   end Iterate;
+
+   overriding function First (Object : Iterator) return Cursor
+   is begin
+      return First (Object.Container.all);
+   end First;
+
+   overriding function Last  (Object : Iterator) return Cursor
+   is begin
+      return Last (Object.Container.all);
+   end Last;
+
+   overriding function Next (Object : in Iterator; Position : in Cursor) 
return Cursor
+   is
+      pragma Unreferenced (Object);
+   begin
+      return Next (Position);
+   end Next;
+
+   overriding function Previous (Object : in Iterator; Position : in Cursor) 
return Cursor
+   is
+      pragma Unreferenced (Object);
+   begin
+      return Previous (Position);
+   end Previous;
+
+end SAL.Gen_Definite_Doubly_Linked_Lists_Sorted;
diff --git a/sal-gen_definite_doubly_linked_lists_sorted.ads 
b/sal-gen_definite_doubly_linked_lists_sorted.ads
index d57748b..bbac90f 100644
--- a/sal-gen_definite_doubly_linked_lists_sorted.ads
+++ b/sal-gen_definite_doubly_linked_lists_sorted.ads
@@ -1,181 +1,191 @@
---  Abstract :
---
---  A generic sorted doubly linked list with definite elements.
---
---  Copyright (C) 2018 - 2019 Free Software Foundation, Inc.
---
---  This library is free software;  you can redistribute it and/or modify it
---  under terms of the  GNU General Public License  as published by the Free
---  Software  Foundation;  either version 3,  or (at your  option) any later
---  version. This library is distributed in the hope that it will be useful,
---  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
---  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
---  As a special exception under Section 7 of GPL version 3, you are granted
---  additional permissions described in the GCC Runtime Library Exception,
---  version 3.1, as published by the Free Software Foundation.
-
-pragma License (Modified_GPL);
-
-with Ada.Finalization;
-with Ada.Iterator_Interfaces;
-with Ada.Unchecked_Deallocation;
-generic
-   type Element_Type is private;
-   with function Element_Compare (Left, Right : in Element_Type) return 
Compare_Result;
-package SAL.Gen_Definite_Doubly_Linked_Lists_Sorted is
-   use all type Ada.Containers.Count_Type;
-
-   type List is new Ada.Finalization.Controlled with private
-   with
-      Constant_Indexing => Constant_Reference,
-      Variable_Indexing => Reference,
-      Default_Iterator  => Iterate,
-      Iterator_Element  => Element_Type;
-
-   --  If user uses Variable_Indexing, they must not change the sort
-   --  order of the elements.
-
-   type List_Access is access all List;
-   for List_Access'Storage_Size use 0;
-
-   Empty_List : constant List;
-
-   overriding procedure Adjust (Container : in out List);
-   --  Deep copy.
-
-   overriding procedure Finalize (Container : in out List);
-   --  Free all items in List.
-
-   procedure Clear (Container : in out List) renames Finalize;
-
-   overriding function "=" (Left, Right : in List) return Boolean;
-   --  True if contents are the same.
-
-   function Length (Container : in List) return Ada.Containers.Count_Type;
-
-   function To_List (Element : in Element_Type) return List;
-
-   procedure Insert (Container : in out List; Element : in Element_Type);
-   --  Insert Element before first item for which Element_Order (item,
-   --  element) returns True.
-
-   function Contains (Container : in List; Element : in Element_Type) return 
Boolean;
-
-   procedure Merge
-     (Target : in out List;
-      Source : in     List;
-      Added  :    out Boolean);
-   --  Add all elements of Source to Target, if they are not already
-   --  present.
-   --
-   --  Added is True if any element was not already present.
-
-   procedure Merge
-     (Target  : in out List;
-      Source  : in     List;
-      Added   :    out Boolean;
-      Exclude : in     Element_Type);
-   --  Add all elements of Source to Target, if they are not already
-   --  present, and are not equal to Exclude.
-   --
-   --  Added is True if any element was not already present.
-
-   type Cursor is private;
-
-   No_Element : constant Cursor;
-
-   function Has_Element (Position : in Cursor) return Boolean;
-
-   function First (Container : in List) return Cursor;
-   function Last (Container : in List) return Cursor;
-
-   function Find (Container : in List; Element : in Element_Type) return 
Cursor;
-   --  No_Element if Element not found.
-
-   procedure Next (Position : in out Cursor)
-   with Pre => Position /= No_Element;
-
-   function Next (Position : in Cursor) return Cursor
-   with Pre => Position /= No_Element;
-   function Previous (Position : in Cursor) return Cursor
-   with Pre => Position /= No_Element;
-
-   function Element (Position : in Cursor) return Element_Type
-   with Pre => Position /= No_Element;
-
-   procedure Delete (Container : in out List; Position : in out Cursor)
-   with Pre => Position /= No_Element;
-
-   function Pop (Container : in out List) return Element_Type
-   with Pre => Container.Length > 0;
-   --  Return Container.First, delete it from Container.
-
-   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is null record
-   with Implicit_Dereference => Element;
-
-   function Constant_Reference (Container : in List; Position : in Cursor) 
return Constant_Reference_Type;
-   pragma Inline (Constant_Reference);
-   function Constant_Ref (Position : in Cursor) return Constant_Reference_Type;
-   pragma Inline (Constant_Ref);
-
-   type Reference_Type (Element : not null access Element_Type) is null record
-   with Implicit_Dereference => Element;
-
-   function Reference (Container : in List; Position : in Cursor) return 
Reference_Type
-   with Pre => Position /= No_Element;
-   pragma Inline (Reference);
-   function Ref (Position : in Cursor) return Reference_Type
-   with Pre => Position /= No_Element;
-   pragma Inline (Ref);
-   --  User must not change the element in a way that affects the sort order.
-
-   package Iterator_Interfaces is new Ada.Iterator_Interfaces (Cursor, 
Has_Element);
-
-   function Iterate (Container : aliased in List) return 
Iterator_Interfaces.Reversible_Iterator'Class;
-
-private
-   type Node_Type;
-
-   type Node_Access is access Node_Type;
-
-   type Node_Type is record
-      Element : aliased Element_Type;
-      Prev    : Node_Access;
-      Next    : Node_Access;
-   end record;
-
-   procedure Free is new Ada.Unchecked_Deallocation (Node_Type, Node_Access);
-
-   type List is new Ada.Finalization.Controlled with record
-      Head  : Node_Access               := null;
-      Tail  : Node_Access               := null;
-      Count : Ada.Containers.Count_Type := 0;
-   end record;
-
-   type Cursor is record
-      Container : List_Access;
-      Ptr       : Node_Access;
-   end record;
-
-   Empty_List : constant List := (Ada.Finalization.Controlled with null, null, 
0);
-
-   No_Element : constant Cursor := (null, null);
-
-   type Iterator is new Iterator_Interfaces.Reversible_Iterator with
-   record
-      Container : List_Access;
-   end record;
-
-   overriding function First (Object : Iterator) return Cursor;
-   overriding function Last  (Object : Iterator) return Cursor;
-
-   overriding function Next
-     (Object   : Iterator;
-      Position : Cursor) return Cursor;
-
-   overriding function Previous
-     (Object   : Iterator;
-      Position : Cursor) return Cursor;
-
-end SAL.Gen_Definite_Doubly_Linked_Lists_Sorted;
+--  Abstract :
+--
+--  A generic sorted doubly linked list with definite elements.
+--
+--  Copyright (C) 2018 - 2019 Free Software Foundation, Inc.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+with Ada.Finalization;
+with Ada.Iterator_Interfaces;
+with Ada.Unchecked_Deallocation;
+generic
+   type Element_Type is private;
+   with function Element_Compare (Left, Right : in Element_Type) return 
Compare_Result;
+package SAL.Gen_Definite_Doubly_Linked_Lists_Sorted is
+   use all type Ada.Containers.Count_Type;
+
+   type List is new Ada.Finalization.Controlled with private
+   with
+      Constant_Indexing => Constant_Reference,
+      Variable_Indexing => Variable_Reference,
+      Default_Iterator  => Iterate,
+      Iterator_Element  => Element_Type;
+
+   --  If user uses Variable_Indexing, they must not change the sort
+   --  order of the elements.
+
+   type List_Access is access all List;
+   for List_Access'Storage_Size use 0;
+
+   Empty_List : constant List;
+
+   overriding procedure Adjust (Container : in out List);
+   --  Deep copy.
+
+   overriding procedure Finalize (Container : in out List);
+   --  Free all items in List.
+
+   procedure Clear (Container : in out List) renames Finalize;
+
+   overriding function "=" (Left, Right : in List) return Boolean;
+   --  True if contents are the same.
+
+   function Length (Container : in List) return Ada.Containers.Count_Type;
+
+   function To_List (Element : in Element_Type) return List;
+
+   procedure Insert (Container : in out List; Element : in Element_Type);
+   --  Insert Element before first item for which Element_Order (item,
+   --  element) returns True.
+
+   function Contains (Container : in List; Element : in Element_Type) return 
Boolean;
+
+   procedure Merge
+     (Target : in out List;
+      Source : in     List;
+      Added  :    out Boolean);
+   --  Add all elements of Source to Target, if they are not already
+   --  present.
+   --
+   --  Added is True if any element was not already present.
+
+   procedure Merge
+     (Target  : in out List;
+      Source  : in     List;
+      Added   :    out Boolean;
+      Exclude : in     Element_Type);
+   --  Add all elements of Source to Target, if they are not already
+   --  present, and are not equal to Exclude.
+   --
+   --  Added is True if any element was not already present.
+
+   type Cursor is private;
+
+   No_Element : constant Cursor;
+
+   function Has_Element (Position : in Cursor) return Boolean;
+
+   function First (Container : in List) return Cursor;
+   function Last (Container : in List) return Cursor;
+
+   function Find (Container : in List; Element : in Element_Type) return 
Cursor;
+   --  No_Element if Element not found.
+
+   procedure Next (Position : in out Cursor)
+   with Pre => Position /= No_Element;
+
+   function Next (Position : in Cursor) return Cursor
+   with Pre => Position /= No_Element;
+   function Previous (Position : in Cursor) return Cursor
+   with Pre => Position /= No_Element;
+
+   function Element (Position : in Cursor) return Element_Type
+   with Pre => Position /= No_Element;
+
+   procedure Delete (Container : in out List; Position : in out Cursor)
+   with Pre => Position /= No_Element;
+
+   function Pop (Container : in out List) return Element_Type
+   with Pre => Container.Length > 0;
+   --  Return Container.First, delete it from Container.
+
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is private with
+     Implicit_Dereference => Element;
+
+   function Constant_Reference (Container : in List; Position : in Cursor) 
return Constant_Reference_Type with
+     Inline, Pre => Position /= No_Element;
+
+   function Constant_Ref (Position : in Cursor) return Constant_Reference_Type 
with
+     Inline, Pre => Position /= No_Element;
+
+   type Variable_Reference_Type (Element : not null access Element_Type) is 
private with
+     Implicit_Dereference => Element;
+
+   function Variable_Reference (Container : in List; Position : in Cursor) 
return Variable_Reference_Type
+   with Inline, Pre => Position /= No_Element;
+
+   function Variable_Ref (Position : in Cursor) return Variable_Reference_Type
+   with Inline, Pre => Position /= No_Element;
+   --  User must not change the element in a way that affects the sort order.
+
+   package Iterator_Interfaces is new Ada.Iterator_Interfaces (Cursor, 
Has_Element);
+
+   function Iterate (Container : aliased in List) return 
Iterator_Interfaces.Reversible_Iterator'Class;
+
+private
+   type Node_Type;
+
+   type Node_Access is access Node_Type;
+
+   type Node_Type is record
+      Element : aliased Element_Type;
+      Prev    : Node_Access;
+      Next    : Node_Access;
+   end record;
+
+   procedure Free is new Ada.Unchecked_Deallocation (Node_Type, Node_Access);
+
+   type List is new Ada.Finalization.Controlled with record
+      Head  : Node_Access               := null;
+      Tail  : Node_Access               := null;
+      Count : Ada.Containers.Count_Type := 0;
+   end record;
+
+   type Cursor is record
+      Container : List_Access;
+      Ptr       : Node_Access;
+   end record;
+
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
+
+   type Variable_Reference_Type (Element : not null access Element_Type) is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
+
+   Empty_List : constant List := (Ada.Finalization.Controlled with null, null, 
0);
+
+   No_Element : constant Cursor := (null, null);
+
+   type Iterator is new Iterator_Interfaces.Reversible_Iterator with
+   record
+      Container : List_Access;
+   end record;
+
+   overriding function First (Object : Iterator) return Cursor;
+   overriding function Last  (Object : Iterator) return Cursor;
+
+   overriding function Next
+     (Object   : Iterator;
+      Position : Cursor) return Cursor;
+
+   overriding function Previous
+     (Object   : Iterator;
+      Position : Cursor) return Cursor;
+
+end SAL.Gen_Definite_Doubly_Linked_Lists_Sorted;
diff --git a/sal-gen_graphs.adb b/sal-gen_graphs.adb
index 284b26c..ef0fcc3 100644
--- a/sal-gen_graphs.adb
+++ b/sal-gen_graphs.adb
@@ -55,10 +55,9 @@ package body SAL.Gen_Graphs is
             Graph.Vertices.Set_First_Last (Vertex, Vertex);
          else
             if Vertex < Graph.Vertices.First_Index then
-               Graph.Vertices.Set_First (Vertex);
-            end if;
-            if Vertex > Graph.Vertices.Last_Index then
-               Graph.Vertices.Set_Last (Vertex);
+               Graph.Vertices.Set_First_Last (Vertex, 
Graph.Vertices.Last_Index);
+            elsif Vertex > Graph.Vertices.Last_Index then
+               Graph.Vertices.Set_First_Last (Graph.Vertices.First_Index, 
Vertex);
             end if;
          end if;
       end Update_First_Last;
@@ -158,8 +157,10 @@ package body SAL.Gen_Graphs is
       To    : in     Edge_Data)
      return Path_Arrays.Vector
    is
-      Vertex_Queue  : Vertex_Queues.Queue_Type
-        (Size => Integer (Graph.Vertices.Last_Index - 
Graph.Vertices.First_Index + 1));
+      use Vertex_Queues;
+
+      Vertex_Queue  : Queue_Type
+        (Size => Peek_Type (Graph.Vertices.Last_Index - 
Graph.Vertices.First_Index + 1));
 
       type Colors is (White, Gray, Black);
 
@@ -227,11 +228,11 @@ package body SAL.Gen_Graphs is
          end if;
       end loop;
 
-      Vertex_Queue.Put (From);
+      Put (Vertex_Queue, From);
 
-      while not Vertex_Queue.Is_Empty loop
+      while not Is_Empty (Vertex_Queue) loop
          declare
-            U_Index : constant Vertex_Index := Vertex_Queue.Get;
+            U_Index : constant Vertex_Index := Get (Vertex_Queue);
             U       : Aux_Node renames Aux (U_Index);
          begin
             Edges :
@@ -253,7 +254,7 @@ package body SAL.Gen_Graphs is
                         Result_List.Append (Build_Path (V_Index, Result_Edge));
                      end if;
 
-                     Vertex_Queue.Put (V_Index);
+                     Put (Vertex_Queue, V_Index);
                   end if;
                end;
             end loop Edges;
diff --git a/sal-gen_indefinite_doubly_linked_lists.adb 
b/sal-gen_indefinite_doubly_linked_lists.adb
index 071c319..ced09c2 100644
--- a/sal-gen_indefinite_doubly_linked_lists.adb
+++ b/sal-gen_indefinite_doubly_linked_lists.adb
@@ -1,201 +1,211 @@
---  Abstract :
---
---  see spec
---
---  Copyright (C) 2018 Free Software Foundation, Inc.
---
---  This library is free software; you can redistribute it and/or
---  modify it under terms of the GNU General Public License as
---  published by the Free Software Foundation; either version 3, or (at
---  your option) any later version. This library 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
---  distributed with this program; see file COPYING. If not, write to
---  the Free Software Foundation, 59 Temple Place - Suite 330, Boston,
---  MA 02111-1307, USA.
---
---  As a special exception under Section 7 of GPL version 3, you are granted
---  additional permissions described in the GCC Runtime Library Exception,
---  version 3.1, as published by the Free Software Foundation.
-
-pragma License (Modified_GPL);
-
-package body SAL.Gen_Indefinite_Doubly_Linked_Lists is
-
-   ---------
-   --  Public operations, declaration order.
-
-   overriding
-   procedure Adjust (Container : in out List)
-   is
-      Source   : Node_Access := Container.Head;
-      New_Node : Node_Access;
-   begin
-      if Source = null then
-         return;
-      end if;
-
-      Container.Tail := null;
-
-      loop
-         New_Node := new Node_Type'
-           (Element => new Element_Type'(Source.Element.all),
-            Next    => null,
-            Prev    => Container.Tail);
-         if Container.Tail = null then
-            Container.Head := New_Node;
-            Container.Tail := New_Node;
-         else
-            Container.Tail.Next := New_Node;
-            Container.Tail      := New_Node;
-         end if;
-         Source := Source.Next;
-         exit when Source = null;
-      end loop;
-   end Adjust;
-
-   overriding
-   procedure Finalize (Container : in out List)
-   is
-      Next : Node_Access := Container.Head;
-   begin
-      loop
-         exit when Next = null;
-         Next := Container.Head.Next;
-         Free (Container.Head.Element);
-         Free (Container.Head);
-         Container.Head := Next;
-      end loop;
-      Container.Tail := null;
-   end Finalize;
-
-   function Length (Container : in List) return SAL.Base_Peek_Type
-   is begin
-      return Container.Count;
-   end Length;
-
-   procedure Append (Container : in out List; Element : in Element_Type)
-   is
-      New_Node : constant Node_Access := new Node_Type'
-        (Element => new Element_Type'(Element),
-         Prev    => Container.Tail,
-         Next    => null);
-   begin
-      if Container.Tail = null then
-         Container.Head := New_Node;
-         Container.Tail := New_Node;
-      else
-         Container.Tail.Next := New_Node;
-         Container.Tail      := New_Node;
-      end if;
-      Container.Count := Container.Count + 1;
-   end Append;
-
-   procedure Prepend (Container : in out List; Element : in Element_Type)
-   is
-      New_Node : constant Node_Access := new Node_Type'
-        (Element => new Element_Type'(Element),
-         Prev    => null,
-         Next    => Container.Head);
-   begin
-      if Container.Tail = null then
-         Container.Head := New_Node;
-         Container.Tail := New_Node;
-      else
-         Container.Head.Prev := New_Node;
-         Container.Head      := New_Node;
-      end if;
-      Container.Count := Container.Count + 1;
-   end Prepend;
-
-   function Has_Element (Position : in Cursor) return Boolean
-   is begin
-      return Position.Ptr /= null;
-   end Has_Element;
-
-   function First (Container : in List) return Cursor
-   is begin
-      if Container.Head = null then
-         return No_Element;
-      else
-         return (Container'Unrestricted_Access, Container.Head);
-      end if;
-   end First;
-
-   procedure Next (Position : in out Cursor)
-   is begin
-      if Position.Ptr /= null then
-         if Position.Ptr.Next = null then
-            Position := No_Element;
-         else
-            Position.Ptr := Position.Ptr.Next;
-         end if;
-      end if;
-   end Next;
-
-   function Next (Position : in Cursor) return Cursor
-   is begin
-      if Position.Ptr = null then
-         return Position;
-      else
-         if Position.Ptr.Next = null then
-            return No_Element;
-         else
-            return (Position.Container, Position.Ptr.Next);
-         end if;
-      end if;
-   end Next;
-
-   function Element (Position : in Cursor) return Element_Type
-   is begin
-      return Position.Ptr.Element.all;
-   end Element;
-
-   procedure Delete (Container : in out List; Position : in out Cursor)
-   is
-      Node : Node_Access renames Position.Ptr;
-   begin
-      if Node.Next = null then
-         Container.Tail := Node.Prev;
-      else
-         Node.Next.Prev := Node.Prev;
-      end if;
-      if Node.Prev = null then
-         Container.Head := Node.Next;
-      else
-         Node.Prev.Next := Node.Next;
-      end if;
-      Free (Node.Element);
-      Free (Node);
-      Position        := No_Element;
-      Container.Count := Container.Count - 1;
-   end Delete;
-
-   function Persistent_Ref (Position : in Cursor) return access Element_Type
-   is begin
-      return Position.Ptr.Element;
-   end Persistent_Ref;
-
-   function Constant_Reference (Position : in Cursor) return 
Constant_Reference_Type
-   is begin
-      return (Element => Position.Ptr.all.Element);
-   end Constant_Reference;
-
-   function Constant_Ref (Container : in List'Class; Position : in Peek_Type) 
return Constant_Reference_Type
-   is
-      Ptr : Node_Access := Container.Head;
-   begin
-      for I in 2 .. Position loop
-         Ptr := Ptr.Next;
-      end loop;
-      return (Element => Ptr.all.Element);
-   end Constant_Ref;
-
-   function Reference (Position : in Cursor) return Reference_Type
-   is begin
-      return (Element => Position.Ptr.all.Element);
-   end Reference;
-
-end SAL.Gen_Indefinite_Doubly_Linked_Lists;
+--  Abstract :
+--
+--  see spec
+--
+--  Copyright (C) 2018, 2019 Free Software Foundation, Inc.
+--
+--  This library is free software; you can redistribute it and/or
+--  modify it under terms of the GNU General Public License as
+--  published by the Free Software Foundation; either version 3, or (at
+--  your option) any later version. This library 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
+--  distributed with this program; see file COPYING. If not, write to
+--  the Free Software Foundation, 59 Temple Place - Suite 330, Boston,
+--  MA 02111-1307, USA.
+--
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+package body SAL.Gen_Indefinite_Doubly_Linked_Lists is
+
+   ---------
+   --  Public operations, declaration order.
+
+   overriding
+   procedure Adjust (Container : in out List)
+   is
+      Source   : Node_Access := Container.Head;
+      New_Node : Node_Access;
+   begin
+      if Source = null then
+         return;
+      end if;
+
+      Container.Tail := null;
+
+      loop
+         New_Node := new Node_Type'
+           (Element => new Element_Type'(Source.Element.all),
+            Next    => null,
+            Prev    => Container.Tail);
+         if Container.Tail = null then
+            Container.Head := New_Node;
+            Container.Tail := New_Node;
+         else
+            Container.Tail.Next := New_Node;
+            Container.Tail      := New_Node;
+         end if;
+         Source := Source.Next;
+         exit when Source = null;
+      end loop;
+   end Adjust;
+
+   overriding
+   procedure Finalize (Container : in out List)
+   is
+      Next : Node_Access := Container.Head;
+   begin
+      loop
+         exit when Next = null;
+         Next := Container.Head.Next;
+         Free (Container.Head.Element);
+         Free (Container.Head);
+         Container.Head := Next;
+      end loop;
+      Container.Tail := null;
+   end Finalize;
+
+   function Length (Container : in List) return SAL.Base_Peek_Type
+   is begin
+      return Container.Count;
+   end Length;
+
+   procedure Append (Container : in out List; Element : in Element_Type)
+   is
+      New_Node : constant Node_Access := new Node_Type'
+        (Element => new Element_Type'(Element),
+         Prev    => Container.Tail,
+         Next    => null);
+   begin
+      if Container.Tail = null then
+         Container.Head := New_Node;
+         Container.Tail := New_Node;
+      else
+         Container.Tail.Next := New_Node;
+         Container.Tail      := New_Node;
+      end if;
+      Container.Count := Container.Count + 1;
+   end Append;
+
+   procedure Prepend (Container : in out List; Element : in Element_Type)
+   is
+      New_Node : constant Node_Access := new Node_Type'
+        (Element => new Element_Type'(Element),
+         Prev    => null,
+         Next    => Container.Head);
+   begin
+      if Container.Tail = null then
+         Container.Head := New_Node;
+         Container.Tail := New_Node;
+      else
+         Container.Head.Prev := New_Node;
+         Container.Head      := New_Node;
+      end if;
+      Container.Count := Container.Count + 1;
+   end Prepend;
+
+   function Has_Element (Position : in Cursor) return Boolean
+   is begin
+      return Position.Ptr /= null;
+   end Has_Element;
+
+   function First (Container : in List) return Cursor
+   is begin
+      if Container.Head = null then
+         return No_Element;
+      else
+         return (Container'Unrestricted_Access, Container.Head);
+      end if;
+   end First;
+
+   procedure Next (Position : in out Cursor)
+   is begin
+      if Position.Ptr /= null then
+         if Position.Ptr.Next = null then
+            Position := No_Element;
+         else
+            Position.Ptr := Position.Ptr.Next;
+         end if;
+      end if;
+   end Next;
+
+   function Next (Position : in Cursor) return Cursor
+   is begin
+      if Position.Ptr = null then
+         return Position;
+      else
+         if Position.Ptr.Next = null then
+            return No_Element;
+         else
+            return (Position.Container, Position.Ptr.Next);
+         end if;
+      end if;
+   end Next;
+
+   function Element (Position : in Cursor) return Element_Type
+   is begin
+      return Position.Ptr.Element.all;
+   end Element;
+
+   procedure Delete (Container : in out List; Position : in out Cursor)
+   is
+      Node : Node_Access renames Position.Ptr;
+   begin
+      if Node.Next = null then
+         Container.Tail := Node.Prev;
+      else
+         Node.Next.Prev := Node.Prev;
+      end if;
+      if Node.Prev = null then
+         Container.Head := Node.Next;
+      else
+         Node.Prev.Next := Node.Next;
+      end if;
+      Free (Node.Element);
+      Free (Node);
+      Position        := No_Element;
+      Container.Count := Container.Count - 1;
+   end Delete;
+
+   function Persistent_Ref (Position : in Cursor) return access Element_Type
+   is begin
+      return Position.Ptr.Element;
+   end Persistent_Ref;
+
+   function Constant_Ref (Position : in Cursor) return Constant_Reference_Type
+   is begin
+      return (Element => Position.Ptr.all.Element, Dummy => 1);
+   end Constant_Ref;
+
+   function Constant_Reference (Container : in List; Position : in Peek_Type) 
return Constant_Reference_Type
+   is
+      Ptr : Node_Access := Container.Head;
+   begin
+      for I in 2 .. Position loop
+         Ptr := Ptr.Next;
+      end loop;
+      return (Element => Ptr.all.Element, Dummy => 1);
+   end Constant_Reference;
+
+   function Variable_Reference (Container : in List; Position : in Peek_Type) 
return Variable_Reference_Type
+   is
+      Ptr : Node_Access := Container.Head;
+   begin
+      for I in 2 .. Position loop
+         Ptr := Ptr.Next;
+      end loop;
+      return (Element => Ptr.all.Element, Dummy => 1);
+   end Variable_Reference;
+
+   function Variable_Ref (Position : in Cursor) return Variable_Reference_Type
+   is begin
+      return (Element => Position.Ptr.all.Element, Dummy => 1);
+   end Variable_Ref;
+
+end SAL.Gen_Indefinite_Doubly_Linked_Lists;
diff --git a/sal-gen_indefinite_doubly_linked_lists.ads 
b/sal-gen_indefinite_doubly_linked_lists.ads
index 0ebfc54..609e26c 100644
--- a/sal-gen_indefinite_doubly_linked_lists.ads
+++ b/sal-gen_indefinite_doubly_linked_lists.ads
@@ -29,7 +29,9 @@ generic
    type Element_Type (<>) is private;
 package SAL.Gen_Indefinite_Doubly_Linked_Lists is
 
-   type List is new Ada.Finalization.Controlled with private;
+   type List is new Ada.Finalization.Controlled with private with
+     Constant_Indexing => Constant_Reference,
+     Variable_Indexing => Variable_Reference;
 
    Empty_List : constant List;
 
@@ -66,23 +68,23 @@ package SAL.Gen_Indefinite_Doubly_Linked_Lists is
    function Persistent_Ref (Position : in Cursor) return access Element_Type
    with Pre => Has_Element (Position);
 
-   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is null record
-   with Implicit_Dereference => Element;
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is private with
+     Implicit_Dereference => Element;
 
-   function Constant_Reference (Position : in Cursor) return 
Constant_Reference_Type
-   with Pre => Has_Element (Position);
-   pragma Inline (Constant_Reference);
+   function Constant_Ref (Position : in Cursor) return Constant_Reference_Type
+   with Inline, Pre => Has_Element (Position);
 
-   function Constant_Ref (Container : in List'Class; Position : in Peek_Type) 
return Constant_Reference_Type
-   with Pre => Position <= Container.Length;
-   pragma Inline (Constant_Ref);
+   function Constant_Reference (Container : in List; Position : in Peek_Type) 
return Constant_Reference_Type
+   with Inline, Pre => Position <= Container.Length;
 
-   type Reference_Type (Element : not null access Element_Type) is null record
-   with Implicit_Dereference => Element;
+   type Variable_Reference_Type (Element : not null access Element_Type) is 
private with
+     Implicit_Dereference => Element;
 
-   function Reference (Position : in Cursor) return Reference_Type
-   with Pre => Has_Element (Position);
-   pragma Inline (Reference);
+   function Variable_Reference (Container : in List; Position : in Peek_Type) 
return Variable_Reference_Type
+   with Inline, Pre => Position <= Container.Length;
+
+   function Variable_Ref (Position : in Cursor) return Variable_Reference_Type
+   with Inline, Pre => Has_Element (Position);
 
 private
    type Node_Type;
@@ -110,6 +112,16 @@ private
       Ptr       : Node_Access;
    end record;
 
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
+
+   type Variable_Reference_Type (Element : not null access Element_Type) is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
+
    Empty_List : constant List := (Ada.Finalization.Controlled with null, null, 
0);
 
    No_Element : constant Cursor := (null, null);
diff --git a/sal-gen_unbounded_definite_min_heaps_fibonacci.adb 
b/sal-gen_unbounded_definite_min_heaps_fibonacci.adb
index c58a9d4..c88a0cb 100644
--- a/sal-gen_unbounded_definite_min_heaps_fibonacci.adb
+++ b/sal-gen_unbounded_definite_min_heaps_fibonacci.adb
@@ -35,27 +35,6 @@ package body SAL.Gen_Unbounded_Definite_Min_Heaps_Fibonacci 
is
    ----------
    --  local subprogram bodies, alphabetical order
 
-   function Add (Heap : in out Heap_Type; Item : in Element_Type) return 
Node_Access
-   is
-      X : constant Node_Access := new Node'(Item, null, null, null, null, 0, 
False);
-   begin
-      --  [1] 19.2 FIB-HEAP-INSERT
-      if Heap.Min = null then
-         Heap.Min       := X;
-         Heap.Min.Left  := Heap.Min;
-         Heap.Min.Right := Heap.Min;
-      else
-         Insert_Into_Root_List (Heap, X);
-
-         if Key (Item) < Key (Heap.Min.Element) then
-            Heap.Min := X;
-         end if;
-      end if;
-      Heap.Count := Heap.Count + 1;
-
-      return X;
-   end Add;
-
    procedure Consolidate (Heap : in out Heap_Type)
    is
       --  [1] 19.4 max degree of Fibonacci heap
@@ -319,22 +298,26 @@ package body 
SAL.Gen_Unbounded_Definite_Min_Heaps_Fibonacci is
 
    procedure Add (Heap : in out Heap_Type; Item : in Element_Type)
    is
-      X : constant Node_Access := Add (Heap, Item);
-      pragma Unreferenced (X);
+      X : constant Node_Access := new Node'(Item, null, null, null, null, 0, 
False);
    begin
-      null;
-   end Add;
+      --  [1] 19.2 FIB-HEAP-INSERT
+      if Heap.Min = null then
+         Heap.Min       := X;
+         Heap.Min.Left  := Heap.Min;
+         Heap.Min.Right := Heap.Min;
+      else
+         Insert_Into_Root_List (Heap, X);
 
-   function Add (Heap : in out Heap_Type; Item : in Element_Type) return 
Element_Access
-   is
-      X : constant Node_Access := Add (Heap, Item);
-   begin
-      return X.all.Element'Access;
+         if Key (Item) < Key (Heap.Min.Element) then
+            Heap.Min := X;
+         end if;
+      end if;
+      Heap.Count := Heap.Count + 1;
    end Add;
 
    function Peek (Heap : in Heap_Type) return Constant_Reference_Type
    is begin
-      return (Element => Heap.Min.all.Element'Access);
+      return (Element => Heap.Min.all.Element'Access, Dummy => 1);
    end Peek;
 
    procedure Process (Heap : in Heap_Type; Process_Element : access procedure 
(Element : in Element_Type))
diff --git a/sal-gen_unbounded_definite_min_heaps_fibonacci.ads 
b/sal-gen_unbounded_definite_min_heaps_fibonacci.ads
index 6f978e7..aab9fcf 100644
--- a/sal-gen_unbounded_definite_min_heaps_fibonacci.ads
+++ b/sal-gen_unbounded_definite_min_heaps_fibonacci.ads
@@ -25,7 +25,6 @@ pragma License (Modified_GPL);
 with Ada.Finalization;
 generic
    type Element_Type is private;
-   type Element_Access is access all Element_Type;
    type Key_Type is private;
    with function Key (Item : in Element_Type) return Key_Type;
    with procedure Set_Key (Item : in out Element_Type; Key : in Key_Type);
@@ -68,23 +67,8 @@ package SAL.Gen_Unbounded_Definite_Min_Heaps_Fibonacci is
 
    procedure Insert (Heap : in out Heap_Type; Item : in Element_Type) renames 
Add;
 
-   function Add (Heap : in out Heap_Type; Item : in Element_Type) return 
Element_Access;
-   --  Add Item to Heap, return a pointer to it. This avoids extra
-   --  copying of Item.
-   --
-   --  Result is valid at least until next Get.
-
-   --  Despite being called a "mergeable heap" in [1], there is no
-   --  algorithm for merging two Fibonacci heaps. And the naive method of
-   --  simply splicing the root lists apparently breaks the consolidate
-   --  algorithm; it assumes there can only be one tree of each degree >
-   --  0.
-
-   --  procedure Increase_Key (Heap : in out Heap_Type; index : in index_type; 
Item : in Element_Type);
-   --  IMPROVEME: implement. need Index (heap, Key), or Add return index.
-
-   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is null record
-   with Implicit_Dereference => Element;
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is private with
+     Implicit_Dereference => Element;
 
    function Peek (Heap : in Heap_Type) return Constant_Reference_Type;
    --  Return a constant reference to the min element.
@@ -117,8 +101,11 @@ private
       Min   : Node_Access;
       Count : Base_Peek_Type;
    end record;
-   type Heap_Access_Constant is access constant Heap_Type;
-   for Heap_Access_Constant'Storage_Size use 0;
+
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
 
    Empty_Heap : constant Heap_Type := (Ada.Finalization.Controlled with Min => 
null, Count => 0);
 
diff --git a/sal-gen_unbounded_definite_queues.adb 
b/sal-gen_unbounded_definite_queues.adb
index f0290fc..af53823 100644
--- a/sal-gen_unbounded_definite_queues.adb
+++ b/sal-gen_unbounded_definite_queues.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  Copyright (C) 2017, 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2017 - 2019 Free Software Foundation, Inc.
 --
 --  This library is free software;  you can redistribute it and/or modify it
 --  under terms of the  GNU General Public License  as published by the Free
@@ -81,7 +81,7 @@ package body SAL.Gen_Unbounded_Definite_Queues is
          Next (I);
       end loop;
 
-      return (Element => Element_Lists.Reference (Queue.Data, I).Element, 
Dummy => 1);
+      return (Element => Element_Lists.Variable_Reference (Queue.Data, 
I).Element, Dummy => 1);
    end Variable_Peek;
 
    procedure Add (Queue : in out Pkg.Queue; Item : in Element_Type)
diff --git a/sal-gen_unbounded_definite_queues.ads 
b/sal-gen_unbounded_definite_queues.ads
index 8e7163b..237800d 100644
--- a/sal-gen_unbounded_definite_queues.ads
+++ b/sal-gen_unbounded_definite_queues.ads
@@ -17,7 +17,7 @@
 
 pragma License (Modified_GPL);
 
-with Ada.Containers.Doubly_Linked_Lists;
+with SAL.Gen_Definite_Doubly_Linked_Lists;
 generic
    type Element_Type is private;
 package SAL.Gen_Unbounded_Definite_Queues is
@@ -37,7 +37,7 @@ package SAL.Gen_Unbounded_Definite_Queues is
    function Length (Queue : in Pkg.Queue) return Base_Peek_Type renames Count;
 
    function Is_Empty (Queue : in Pkg.Queue) return Boolean;
-   --  Return true if no items are in Pkg.Queue.
+   --  Return true if no items are in Queue.
 
    function Is_Full (Queue : in Pkg.Queue) return Boolean is (False);
    --  Return true if Queue is full.
@@ -85,7 +85,7 @@ package SAL.Gen_Unbounded_Definite_Queues is
 
 private
 
-   package Element_Lists is new Ada.Containers.Doubly_Linked_Lists 
(Element_Type);
+   package Element_Lists is new SAL.Gen_Definite_Doubly_Linked_Lists 
(Element_Type);
 
    --  We don't provide cursors or write access to queue elements, so we
    --  don't need any tampering checks.
diff --git a/sal-gen_unbounded_definite_red_black_trees.adb 
b/sal-gen_unbounded_definite_red_black_trees.adb
index 9099f06..adea0d4 100644
--- a/sal-gen_unbounded_definite_red_black_trees.adb
+++ b/sal-gen_unbounded_definite_red_black_trees.adb
@@ -2,7 +2,7 @@
 --
 --  Generic unbounded red-black tree with definite elements.
 --
---  Copyright (C) 2017, 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2017 - 2019 Free Software Foundation, Inc.
 --
 --  This library is free software;  you can redistribute it and/or modify it
 --  under terms of the  GNU General Public License  as published by the Free
@@ -298,53 +298,53 @@ package body SAL.Gen_Unbounded_Definite_Red_Black_Trees is
       return Cursor.Node /= null;
    end Has_Element;
 
-   function Constant_Ref
+   function Constant_Reference
      (Container : aliased in Tree;
       Position  :         in Cursor)
-     return Constant_Ref_Type
+     return Constant_Reference_Type
    is
       pragma Unreferenced (Container);
    begin
-      return (Element => Position.Node.all.Element'Access);
-   end Constant_Ref;
+      return (Element => Position.Node.all.Element'Access, Dummy => 1);
+   end Constant_Reference;
 
-   function Constant_Ref
+   function Constant_Reference
      (Container : aliased in Tree;
       Key       :         in Key_Type)
-     return Constant_Ref_Type
+     return Constant_Reference_Type
    is
       Node : constant Node_Access := Find (Container.Root, Key, Container.Nil);
    begin
       if Node = null then
          raise Not_Found;
       else
-         return (Element => Node.all.Element'Access);
+         return (Element => Node.all.Element'Access, Dummy => 1);
       end if;
-   end Constant_Ref;
+   end Constant_Reference;
 
-   function Variable_Ref
+   function Variable_Reference
      (Container : aliased in Tree;
       Position  :         in Cursor)
-     return Variable_Ref_Type
+     return Variable_Reference_Type
    is
       pragma Unreferenced (Container);
    begin
-      return (Element => Position.Node.all.Element'Access);
-   end Variable_Ref;
+      return (Element => Position.Node.all.Element'Access, Dummy => 1);
+   end Variable_Reference;
 
-   function Variable_Ref
+   function Variable_Reference
      (Container : aliased in Tree;
       Key       :         in Key_Type)
-     return Variable_Ref_Type
+     return Variable_Reference_Type
    is
       Node : constant Node_Access := Find (Container.Root, Key, Container.Nil);
    begin
       if Node = null then
          raise Not_Found;
       else
-         return (Element => Node.all.Element'Access);
+         return (Element => Node.all.Element'Access, Dummy => 1);
       end if;
-   end Variable_Ref;
+   end Variable_Reference;
 
    function Iterate (Tree : in Pkg.Tree'Class) return Iterator
    is begin
diff --git a/sal-gen_unbounded_definite_red_black_trees.ads 
b/sal-gen_unbounded_definite_red_black_trees.ads
index 83c9c88..b51e4b9 100644
--- a/sal-gen_unbounded_definite_red_black_trees.ads
+++ b/sal-gen_unbounded_definite_red_black_trees.ads
@@ -37,8 +37,8 @@ package SAL.Gen_Unbounded_Definite_Red_Black_Trees is
 
    type Tree is new Ada.Finalization.Limited_Controlled with private
    with
-     Constant_Indexing => Constant_Ref,
-     Variable_Indexing => Variable_Ref,
+     Constant_Indexing => Constant_Reference,
+     Variable_Indexing => Variable_Reference,
      Default_Iterator  => Iterate,
      Iterator_Element  => Element_Type;
 
@@ -58,34 +58,35 @@ package SAL.Gen_Unbounded_Definite_Red_Black_Trees is
 
    function Has_Element (Cursor : in Pkg.Cursor) return Boolean;
 
-   type Constant_Ref_Type (Element : not null access constant Element_Type) is 
null record
-   with Implicit_Dereference => Element;
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is private with
+     Implicit_Dereference => Element;
 
-   function Constant_Ref
+   function Constant_Reference
      (Container : aliased in Tree;
       Position  :         in Cursor)
-     return Constant_Ref_Type;
+     return Constant_Reference_Type with
+     Inline;
 
-   function Constant_Ref
+   function Constant_Reference
      (Container : aliased in Tree;
       Key       :         in Key_Type)
-     return Constant_Ref_Type;
-   pragma Inline (Constant_Ref);
+     return Constant_Reference_Type with
+     Inline;
 
-   type Variable_Ref_Type (Element : not null access Element_Type) is null 
record
-   with Implicit_Dereference => Element;
+   type Variable_Reference_Type (Element : not null access Element_Type) is 
private with
+     Implicit_Dereference => Element;
 
-   function Variable_Ref
+   function Variable_Reference
      (Container : aliased in Tree;
       Position  :         in Cursor)
-     return Variable_Ref_Type;
-   pragma Inline (Variable_Ref);
+     return Variable_Reference_Type with
+     Inline;
 
-   function Variable_Ref
+   function Variable_Reference
      (Container : aliased in Tree;
       Key       :         in Key_Type)
-     return Variable_Ref_Type;
-   pragma Inline (Variable_Ref);
+     return Variable_Reference_Type with
+     Inline;
    --  Raises Not_Found if Key not found in Container.
 
    package Iterators is new Ada.Iterator_Interfaces (Cursor, Has_Element);
@@ -116,11 +117,13 @@ package SAL.Gen_Unbounded_Definite_Red_Black_Trees is
       Direction   : in Known_Direction_Type;
       First, Last : in Key_Type)
      return Cursor;
-   --  Find first element with Key greater than or equal to First, and
-   --  less than or equal to Last. If Direction is Ascending, start
-   --  search at First; if Descending, at Last.
+   --  Find first element with key in range First .. Last. If Direction
+   --  is Ascending, start at First, otherwise start at Last.
    --
-   --  Has_Element is False if there is no such Key.
+   --  Has_Element (result) is False if there is no such element.
+   --
+   --  The Iterator does not remember First, Last; the user must check
+   --  those for any element that Next or Previous returns.
 
    function Count (Tree : in Pkg.Tree) return Ada.Containers.Count_Type;
 
@@ -131,7 +134,7 @@ package SAL.Gen_Unbounded_Definite_Red_Black_Trees is
    --  Result points to newly inserted element.
 
    procedure Delete (Tree : in out Pkg.Tree; Position : in out Cursor);
-   --  Delete element at Position; set Position to point to no element.
+   --  Delete element at Position, set Position to No_Element.
 private
 
    type Node;
@@ -169,6 +172,16 @@ private
       Right_Done : Boolean := True;
    end record;
 
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
+
+   type Variable_Reference_Type (Element : not null access Element_Type) is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
+
    No_Element : constant Cursor :=
      (Node       => null,
       Direction  => Unknown,
diff --git a/sal-gen_unbounded_definite_stacks.adb 
b/sal-gen_unbounded_definite_stacks.adb
index 0a54eab..2ead18f 100644
--- a/sal-gen_unbounded_definite_stacks.adb
+++ b/sal-gen_unbounded_definite_stacks.adb
@@ -165,25 +165,25 @@ package body SAL.Gen_Unbounded_Definite_Stacks is
       Stack.Data (Depth - Index + 1) := Element;
    end Set;
 
-   function Constant_Ref
+   function Constant_Reference
      (Container : aliased in Stack'Class;
       Position  :         in Peek_Type)
-     return Constant_Ref_Type
+     return Constant_Reference_Type
    is begin
       return
         (Element => Container.Data (Container.Top - Position + 1)'Access,
          Dummy => 1);
-   end Constant_Ref;
+   end Constant_Reference;
 
-   function Constant_Ref
+   function Constant_Reference
      (Container : aliased in Stack'Class;
       Position  :         in Cursor)
-     return Constant_Ref_Type
+     return Constant_Reference_Type
    is begin
       return
         (Element => Container.Data (Container.Top - Position.Ptr + 1)'Access,
          Dummy => 1);
-   end Constant_Ref;
+   end Constant_Reference;
 
    function Has_Element (Position : in Cursor) return Boolean
    is begin
diff --git a/sal-gen_unbounded_definite_stacks.ads 
b/sal-gen_unbounded_definite_stacks.ads
index cb54a24..26d0466 100644
--- a/sal-gen_unbounded_definite_stacks.ads
+++ b/sal-gen_unbounded_definite_stacks.ads
@@ -33,7 +33,7 @@ package SAL.Gen_Unbounded_Definite_Stacks is
 
    type Stack is new Ada.Finalization.Controlled with private
    with
-     Constant_Indexing => Constant_Ref,
+     Constant_Indexing => Constant_Reference,
      Default_Iterator  => Iterate,
      Iterator_Element  => Element_Type;
 
@@ -56,7 +56,7 @@ package SAL.Gen_Unbounded_Definite_Stacks is
    function Peek
      (Stack : in Sguds.Stack;
       Index : in Peek_Type := 1)
-     return Element_Type;
+     return Element_Type with Inline;
    --  Return the Index'th item from the top of Stack; the Item is _not_ 
removed.
    --  Top item has index 1.
    --
@@ -106,25 +106,20 @@ package SAL.Gen_Unbounded_Definite_Stacks is
    --
    --  Useful when creating a stack from pre-existing data.
 
-   type Constant_Ref_Type (Element : not null access constant Element_Type) is
-   record
-      Dummy : Integer := raise Program_Error with "uninitialized reference";
-   end record
-   with Implicit_Dereference => Element;
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is private with
+     Implicit_Dereference => Element;
 
-   function Constant_Ref
+   function Constant_Reference
      (Container : aliased in Stack'Class;
       Position  :         in Peek_Type)
-     return Constant_Ref_Type;
-   pragma Inline (Constant_Ref);
+     return Constant_Reference_Type with Inline;
 
    type Cursor is private;
 
-   function Constant_Ref
+   function Constant_Reference
      (Container : aliased in Stack'Class;
       Position  :         in Cursor)
-     return Constant_Ref_Type;
-   pragma Inline (Constant_Ref);
+     return Constant_Reference_Type with Inline;
 
    function Has_Element (Position : in Cursor) return Boolean;
 
@@ -143,11 +138,16 @@ private
       Data : Element_Array_Access;
 
       --  Top of stack is at Data (Top).
-      --  Data (1 .. Last_Index) has been set at some point.
+      --  Data (1 .. Top) has been set at some point.
    end record;
 
    type Stack_Access is access all Stack;
 
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
+
    Empty_Stack : constant Stack := (Ada.Finalization.Controlled with 
Invalid_Peek_Index, null);
 
    type Cursor is record
diff --git a/sal-gen_unbounded_definite_vectors.adb 
b/sal-gen_unbounded_definite_vectors.adb
index b5019bb..be77f9c 100644
--- a/sal-gen_unbounded_definite_vectors.adb
+++ b/sal-gen_unbounded_definite_vectors.adb
@@ -112,6 +112,26 @@ package body SAL.Gen_Unbounded_Definite_Vectors is
       end if;
    end Capacity;
 
+   procedure Set_Capacity
+     (Container : in out Vector;
+      First     : in     Index_Type;
+      Last      : in     Extended_Index)
+   is
+      First_Peek : constant Peek_Type := To_Peek_Type (First);
+      Last_Peek  : constant Peek_Type := To_Peek_Type (Last);
+   begin
+      if Container.Elements = null then
+         Container.Elements := new Array_Type (First_Peek .. Last_Peek);
+      else
+         if First_Peek < Container.Elements'First then
+            Grow (Container.Elements, First_Peek);
+         end if;
+         if Last_Peek < Container.Elements'Last then
+            Grow (Container.Elements, Last_Peek);
+         end if;
+      end if;
+   end Set_Capacity;
+
    function Element (Container : Vector; Index : Index_Type) return 
Element_Type
    is begin
       return Container.Elements (To_Peek_Type (Index));
@@ -124,7 +144,7 @@ package body SAL.Gen_Unbounded_Definite_Vectors is
 
    function First_Index (Container : Vector) return Extended_Index
    is begin
-      if Container.Elements = null then
+      if Container.First = No_Index then
          return No_Index + 1;
       else
          return Container.First;
@@ -133,11 +153,7 @@ package body SAL.Gen_Unbounded_Definite_Vectors is
 
    function Last_Index (Container : Vector) return Extended_Index
    is begin
-      if Container.Elements = null then
-         return No_Index;
-      else
-         return Container.Last;
-      end if;
+      return Container.Last;
    end Last_Index;
 
    procedure Append (Container : in out Vector; New_Item : in Element_Type)
@@ -388,19 +404,6 @@ package body SAL.Gen_Unbounded_Definite_Vectors is
       Set_Last (Container, Last);
    end Set_First_Last;
 
-   procedure Set_Length (Container : in out Vector; Length : in 
Ada.Containers.Count_Type)
-   is
-      use all type Ada.Containers.Count_Type;
-   begin
-      if Container.First = No_Index then
-         Container.First := Index_Type'First;
-         Container.Last  := Container.First - 1;
-      end if;
-      if Length > 0 then
-         Container.Set_Last (Index_Type (Length) + Container.First - 1);
-      end if;
-   end Set_Length;
-
    procedure Delete (Container : in out Vector; Index : in Index_Type)
    is
       J : constant Peek_Type := To_Peek_Type (Index);
@@ -438,10 +441,8 @@ package body SAL.Gen_Unbounded_Definite_Vectors is
    end Element;
 
    function First (Container : aliased in Vector) return Cursor
-   is
-      use all type Ada.Containers.Count_Type;
-   begin
-      if Container.Length = 0 then
+   is begin
+      if Container.First = No_Index then
          return No_Element;
       else
          return (Container'Access, To_Peek_Type (Container.First));
@@ -517,7 +518,7 @@ package body SAL.Gen_Unbounded_Definite_Vectors is
    is
       J : constant Peek_Type := To_Peek_Type (Index);
    begin
-      return (Element => Container.Elements (J)'Access);
+      return (Element => Container.Elements (J)'Access, Dummy => 1);
    end Constant_Ref;
 
    function Variable_Ref
@@ -527,7 +528,7 @@ package body SAL.Gen_Unbounded_Definite_Vectors is
    is
       J : constant Peek_Type := To_Peek_Type (Index);
    begin
-      return (Element => Container.Elements (J)'Access);
+      return (Element => Container.Elements (J)'Access, Dummy => 1);
    end Variable_Ref;
 
    overriding function First (Object : Iterator) return Cursor
@@ -535,7 +536,7 @@ package body SAL.Gen_Unbounded_Definite_Vectors is
       if Object.Container.Elements = null then
          return (null, Invalid_Peek_Index);
       else
-         return (Object.Container, To_Peek_Type 
(Object.Container.First_Index));
+         return (Object.Container, To_Peek_Type (Object.Container.First));
       end if;
    end First;
 
@@ -544,7 +545,7 @@ package body SAL.Gen_Unbounded_Definite_Vectors is
       if Object.Container.Elements = null then
          return (null, Invalid_Peek_Index);
       else
-         return (Object.Container, To_Peek_Type (Object.Container.Last_Index));
+         return (Object.Container, To_Peek_Type (Object.Container.Last));
       end if;
    end Last;
 
@@ -573,7 +574,7 @@ package body SAL.Gen_Unbounded_Definite_Vectors is
 
    function Constant_Ref (Container : aliased Vector; Position : in Cursor) 
return Constant_Reference_Type
    is begin
-      return (Element => Container.Elements (Position.Index)'Access);
+      return (Element => Container.Elements (Position.Index)'Access, Dummy => 
1);
    end Constant_Ref;
 
    function Variable_Ref
@@ -581,7 +582,7 @@ package body SAL.Gen_Unbounded_Definite_Vectors is
       Position  :         in Cursor)
      return Variable_Reference_Type
    is begin
-      return (Element => Container.Elements (Position.Index)'Access);
+      return (Element => Container.Elements (Position.Index)'Access, Dummy => 
1);
    end Variable_Ref;
 
 end SAL.Gen_Unbounded_Definite_Vectors;
diff --git a/sal-gen_unbounded_definite_vectors.ads 
b/sal-gen_unbounded_definite_vectors.ads
index 8023794..76cca13 100644
--- a/sal-gen_unbounded_definite_vectors.ads
+++ b/sal-gen_unbounded_definite_vectors.ads
@@ -1,230 +1,241 @@
---  Abstract :
---
---  A simple unbounded vector of definite items, intended to be faster
---  than Ada.Containers.Vectors.
---
---  Prepend is as fast (in amortized time) as Append.
---
---  It provides no checking of cursor tampering; higher level code
---  must ensure that.
---
---  Copyright (C) 2018 - 2019 Free Software Foundation, Inc.
---
---  This library is free software;  you can redistribute it and/or modify it
---  under terms of the  GNU General Public License  as published by the Free
---  Software  Foundation;  either version 3,  or (at your  option) any later
---  version. This library is distributed in the hope that it will be useful,
---  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
---  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
---  As a special exception under Section 7 of GPL version 3, you are granted
---  additional permissions described in the GCC Runtime Library Exception,
---  version 3.1, as published by the Free Software Foundation.
-
-pragma License (Modified_GPL);
-
-with Ada.Finalization;
-with Ada.Iterator_Interfaces;
-with Ada.Unchecked_Deallocation;
-generic
-   type Index_Type is range <>;
-   type Element_Type is private;
-   Default_Element : in Element_Type;
-package SAL.Gen_Unbounded_Definite_Vectors is
-
-   subtype Extended_Index is Index_Type'Base
-     range Index_Type'First - 1 ..
-           Index_Type'Min (Index_Type'Base'Last - 1, Index_Type'Last) + 1;
-
-   No_Index : constant Extended_Index := Extended_Index'First;
-
-   type Vector is new Ada.Finalization.Controlled with private with
-      Constant_Indexing => Constant_Ref,
-      Variable_Indexing => Variable_Ref,
-      Default_Iterator  => Iterate,
-      Iterator_Element  => Element_Type;
-
-   Empty_Vector : constant Vector;
-
-   overriding procedure Finalize (Container : in out Vector);
-   overriding procedure Adjust (Container : in out Vector);
-
-   overriding function "=" (Left, Right : in Vector) return Boolean is
-     (raise Programmer_Error);
-   --  Use Gen_Comparable child.
-
-   function Length (Container : in Vector) return Ada.Containers.Count_Type;
-   function Capacity (Container : in Vector) return Ada.Containers.Count_Type;
-
-   procedure Clear (Container : in out Vector)
-   renames Finalize;
-
-   function First_Index (Container : Vector) return Extended_Index;
-   --  No_Index + 1 when Container is empty, so "for I in C.First_Index
-   --  .. C.Last_Index loop" works.
-
-   function Last_Index (Container : Vector) return Extended_Index;
-   --  No_Index when Container is empty.
-
-   function Element (Container : Vector; Index : Index_Type) return 
Element_Type
-   with Pre => Index >= Container.First_Index and Index <= 
Container.Last_Index;
-
-   procedure Replace_Element (Container : Vector; Index : Index_Type; New_Item 
: in Element_Type);
-
-   procedure Append (Container : in out Vector; New_Item : in Element_Type);
-   --  Insert New_Item at end of Container.
-   --
-   --  Raises Constraint_Error if index of new item would be greater than
-   --  Index_Type'Last.
-
-   procedure Append (Container : in out Vector; New_Items : in Vector);
-   --  Insert all elements of New_Items at end of Container.
-
-   procedure Prepend (Container : in out Vector; New_Item : in Element_Type);
-   --  Insert New_Item at beginning of Container.
-   --
-   --  Raises Constraint_Error if index of new item would be less than
-   --  Index_Type'First.
-
-   procedure Prepend
-     (Target       : in out Vector;
-      Source       : in     Vector;
-      Source_First : in     Index_Type;
-      Source_Last  : in     Index_Type);
-   --  Copy Source (Source_First .. Source_Last) to Target, before
-   --  Target.First_Index.
-
-   procedure Insert
-     (Container : in out Vector;
-      Element   : in     Element_Type;
-      Before    : in     Index_Type);
-   --  Existing elements at Before and after are slid to higher indices.
-
-   procedure Merge
-     (Target : in out Vector;
-      Source : in out Vector);
-   --  Copy all elements from Source to Target, to the same index range,
-   --  deleting them from Source, and overwriting overlapping ranges.
-
-   function To_Vector (Item : in Element_Type; Count : in 
Ada.Containers.Count_Type := 1) return Vector;
-
-   function "+" (Element : in Element_Type) return Vector;
-
-   function "&" (Left, Right : in Element_Type) return Vector;
-   function "&" (Left : in Vector; Right : in Element_Type) return Vector;
-
-   procedure Set_First (Container : in out Vector; First : in Index_Type);
-   procedure Set_Last (Container : in out Vector; Last : in Extended_Index);
-   procedure Set_First_Last
-     (Container : in out Vector;
-      First     : in     Index_Type;
-      Last      : in     Extended_Index);
-   --  Default First is Index_Type'First.
-   --  Elements with First <= index <= Last that have not been set have
-   --  Default_Element value.
-
-   procedure Set_Length (Container : in out Vector; Length : in 
Ada.Containers.Count_Type);
-   --  Set Last so Container.Length returns Length. New elements have
-   --  Default_Element value.
-
-   procedure Delete (Container : in out Vector; Index : in Index_Type);
-   --  Replace Index element contents with default. If Index =
-   --  Container.Last_Index, Container.Last_Index is decremented.
-
-   function Contains (Container : in Vector; Element : in Element_Type) return 
Boolean;
-   --  Return True if Element is in Container, False if not.
-
-   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is null record
-   with Implicit_Dereference => Element;
-
-   function Constant_Ref (Container : aliased in Vector; Index : in 
Index_Type) return Constant_Reference_Type
-   with Pre => Index >= Container.First_Index and Index <= 
Container.Last_Index;
-   pragma Inline (Constant_Ref);
-
-   type Variable_Reference_Type (Element : not null access Element_Type) is 
null record
-   with Implicit_Dereference => Element;
-
-   function Variable_Ref (Container : aliased in Vector; Index : in 
Index_Type) return Variable_Reference_Type
-   with Pre => Index >= Container.First_Index and Index <= 
Container.Last_Index;
-   pragma Inline (Variable_Ref);
-
-   type Cursor is private;
-
-   No_Element : constant Cursor;
-
-   function Has_Element (Position : Cursor) return Boolean;
-   function Element (Position : Cursor) return Element_Type
-   with Pre => Position /= No_Element;
-   function First (Container : aliased in Vector) return Cursor;
-   function Next (Position : in Cursor) return Cursor;
-   procedure Next (Position : in out Cursor);
-   function Prev (Position : in Cursor) return Cursor;
-   procedure Prev (Position : in out Cursor);
-
-   function To_Cursor
-     (Container : aliased in Vector;
-      Index     :         in Extended_Index)
-     return Cursor;
-
-   function To_Index (Position : in Cursor) return Extended_Index;
-
-   package Iterator_Interfaces is new Ada.Iterator_Interfaces (Cursor, 
Has_Element);
-
-   function Iterate (Container : aliased in Vector) return 
Iterator_Interfaces.Reversible_Iterator'Class;
-
-   function Constant_Ref (Container : aliased in Vector; Position : in Cursor) 
return Constant_Reference_Type
-   with Pre => Has_Element (Position);
-   pragma Inline (Constant_Ref);
-
-   function Variable_Ref (Container : aliased in Vector; Position  : in 
Cursor) return Variable_Reference_Type
-   with Pre => Has_Element (Position);
-   pragma Inline (Variable_Ref);
-
-private
-
-   type Array_Type is array (SAL.Peek_Type range <>) of aliased Element_Type;
-   type Array_Access is access Array_Type;
-   procedure Free is new Ada.Unchecked_Deallocation (Array_Type, Array_Access);
-
-   type Vector is new Ada.Finalization.Controlled with
-   record
-      Elements : Array_Access;
-      First    : Extended_Index := No_Index;
-      Last     : Extended_Index := No_Index;
-   end record;
-
-   type Vector_Access is access constant Vector;
-   for Vector_Access'Storage_Size use 0;
-
-   type Cursor is record
-      Container : Vector_Access  := null;
-      Index     : Base_Peek_Type := Invalid_Peek_Index;
-   end record;
-
-   type Iterator is new Iterator_Interfaces.Reversible_Iterator with
-   record
-      Container : Vector_Access;
-   end record;
-
-   overriding function First (Object : Iterator) return Cursor;
-   overriding function Last  (Object : Iterator) return Cursor;
-
-   overriding function Next
-     (Object   : Iterator;
-      Position : Cursor) return Cursor;
-
-   overriding function Previous
-     (Object   : Iterator;
-      Position : Cursor) return Cursor;
-
-   Empty_Vector : constant Vector := (Ada.Finalization.Controlled with others 
=> <>);
-
-   No_Element : constant Cursor := (others => <>);
-
-   ----------
-   --  Visible for child package
-
-   function To_Peek_Type (Item : in Extended_Index) return Base_Peek_Type with 
Inline;
-   function To_Index_Type (Item : in Base_Peek_Type) return Extended_Index;
-
-end SAL.Gen_Unbounded_Definite_Vectors;
+--  Abstract :
+--
+--  A simple unbounded vector of definite items, intended to be faster
+--  than Ada.Containers.Vectors.
+--
+--  Prepend is as fast (in amortized time) as Append.
+--
+--  It provides no checking of cursor tampering; higher level code
+--  must ensure that.
+--
+--  Copyright (C) 2018 - 2019 Free Software Foundation, Inc.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+with Ada.Finalization;
+with Ada.Iterator_Interfaces;
+with Ada.Unchecked_Deallocation;
+generic
+   type Index_Type is range <>;
+   type Element_Type is private;
+   Default_Element : in Element_Type;
+package SAL.Gen_Unbounded_Definite_Vectors is
+
+   subtype Extended_Index is Index_Type'Base
+     range Index_Type'First - 1 ..
+           Index_Type'Min (Index_Type'Base'Last - 1, Index_Type'Last) + 1;
+
+   No_Index : constant Extended_Index := Extended_Index'First;
+
+   type Vector is new Ada.Finalization.Controlled with private with
+      Constant_Indexing => Constant_Ref,
+      Variable_Indexing => Variable_Ref,
+      Default_Iterator  => Iterate,
+      Iterator_Element  => Element_Type;
+
+   Empty_Vector : constant Vector;
+
+   overriding procedure Finalize (Container : in out Vector);
+   overriding procedure Adjust (Container : in out Vector);
+
+   overriding function "=" (Left, Right : in Vector) return Boolean is
+     (raise Programmer_Error);
+   --  Use Gen_Comparable child.
+
+   function Length (Container : in Vector) return Ada.Containers.Count_Type;
+   function Capacity (Container : in Vector) return Ada.Containers.Count_Type;
+
+   procedure Set_Capacity
+     (Container : in out Vector;
+      First     : in     Index_Type;
+      Last      : in     Extended_Index);
+   --  Allocates memory, but does not change Container.First, Container.Last.
+
+   procedure Clear (Container : in out Vector)
+   renames Finalize;
+
+   function First_Index (Container : Vector) return Extended_Index;
+   --  No_Index + 1 when Container is empty, so "for I in C.First_Index
+   --  .. C.Last_Index loop" works.
+
+   function Last_Index (Container : Vector) return Extended_Index;
+   --  No_Index when Container is empty.
+
+   function Element (Container : Vector; Index : Index_Type) return 
Element_Type
+   with Pre => Index >= Container.First_Index and Index <= 
Container.Last_Index;
+
+   procedure Replace_Element (Container : Vector; Index : Index_Type; New_Item 
: in Element_Type);
+
+   procedure Append (Container : in out Vector; New_Item : in Element_Type);
+   --  Insert New_Item at end of Container.
+   --
+   --  Raises Constraint_Error if index of new item would be greater than
+   --  Index_Type'Last.
+
+   procedure Append (Container : in out Vector; New_Items : in Vector);
+   --  Insert all elements of New_Items at end of Container.
+
+   procedure Prepend (Container : in out Vector; New_Item : in Element_Type);
+   --  Insert New_Item at beginning of Container.
+   --
+   --  Raises Constraint_Error if index of new item would be less than
+   --  Index_Type'First.
+
+   procedure Prepend
+     (Target       : in out Vector;
+      Source       : in     Vector;
+      Source_First : in     Index_Type;
+      Source_Last  : in     Index_Type);
+   --  Copy Source (Source_First .. Source_Last) to Target, before
+   --  Target.First_Index.
+
+   procedure Insert
+     (Container : in out Vector;
+      Element   : in     Element_Type;
+      Before    : in     Index_Type);
+   --  Existing elements at Before and after are slid to higher indices.
+
+   procedure Merge
+     (Target : in out Vector;
+      Source : in out Vector);
+   --  Copy all elements from Source to Target, to the same index range,
+   --  deleting them from Source, and overwriting overlapping ranges.
+
+   function To_Vector (Item : in Element_Type; Count : in 
Ada.Containers.Count_Type := 1) return Vector;
+
+   function "+" (Element : in Element_Type) return Vector;
+
+   function "&" (Left, Right : in Element_Type) return Vector;
+   function "&" (Left : in Vector; Right : in Element_Type) return Vector;
+
+   procedure Set_First_Last
+     (Container : in out Vector;
+      First     : in     Index_Type;
+      Last      : in     Extended_Index);
+   --  Elements in First .. Last that have not been set have
+   --  Default_Element value.
+
+   procedure Delete (Container : in out Vector; Index : in Index_Type);
+   --  Replace Index element contents with default. If Index =
+   --  Container.Last_Index, Container.Last_Index is decremented.
+
+   function Contains (Container : in Vector; Element : in Element_Type) return 
Boolean;
+   --  Return True if Element is in Container, False if not.
+
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is private with
+     Implicit_Dereference => Element;
+
+   function Constant_Ref (Container : aliased in Vector; Index : in 
Index_Type) return Constant_Reference_Type
+   with Inline, Pre => Index in Container.First_Index .. Container.Last_Index;
+
+   type Variable_Reference_Type (Element : not null access Element_Type) is 
private with
+     Implicit_Dereference => Element;
+
+   function Variable_Ref (Container : aliased in Vector; Index : in 
Index_Type) return Variable_Reference_Type
+   with Inline, Pre => Index in Container.First_Index .. Container.Last_Index;
+
+   type Cursor is private;
+
+   No_Element : constant Cursor;
+
+   function Has_Element (Position : Cursor) return Boolean;
+   function Element (Position : Cursor) return Element_Type
+   with Pre => Position /= No_Element;
+   function First (Container : aliased in Vector) return Cursor;
+   function Next (Position : in Cursor) return Cursor;
+   procedure Next (Position : in out Cursor);
+   function Prev (Position : in Cursor) return Cursor;
+   procedure Prev (Position : in out Cursor);
+
+   function To_Cursor
+     (Container : aliased in Vector;
+      Index     :         in Extended_Index)
+     return Cursor;
+
+   function To_Index (Position : in Cursor) return Extended_Index;
+
+   package Iterator_Interfaces is new Ada.Iterator_Interfaces (Cursor, 
Has_Element);
+
+   function Iterate (Container : aliased in Vector) return 
Iterator_Interfaces.Reversible_Iterator'Class;
+
+   function Constant_Ref (Container : aliased in Vector; Position : in Cursor) 
return Constant_Reference_Type
+   with Inline, Pre => Has_Element (Position);
+
+   function Variable_Ref (Container : aliased in Vector; Position  : in 
Cursor) return Variable_Reference_Type
+   with Pre => Has_Element (Position);
+   pragma Inline (Variable_Ref);
+
+private
+
+   type Array_Type is array (SAL.Peek_Type range <>) of aliased Element_Type;
+   type Array_Access is access Array_Type;
+   procedure Free is new Ada.Unchecked_Deallocation (Array_Type, Array_Access);
+
+   type Vector is new Ada.Finalization.Controlled with
+   record
+      Elements : Array_Access;
+      --  Elements may be non-null with First = No_Index, after
+      --  Set_Capacity. If First /= No_Index and Last >= First, Elements /=
+      --  null.
+      First    : Extended_Index := No_Index;
+      Last     : Extended_Index := No_Index;
+   end record;
+
+   type Vector_Access is access constant Vector;
+   for Vector_Access'Storage_Size use 0;
+
+   type Cursor is record
+      Container : Vector_Access  := null;
+      Index     : Base_Peek_Type := Invalid_Peek_Index;
+   end record;
+
+   type Iterator is new Iterator_Interfaces.Reversible_Iterator with
+   record
+      Container : Vector_Access;
+   end record;
+
+   overriding function First (Object : Iterator) return Cursor;
+   overriding function Last  (Object : Iterator) return Cursor;
+
+   overriding function Next
+     (Object   : Iterator;
+      Position : Cursor) return Cursor;
+
+   overriding function Previous
+     (Object   : Iterator;
+      Position : Cursor) return Cursor;
+
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
+
+   type Variable_Reference_Type (Element : not null access Element_Type) is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
+
+   Empty_Vector : constant Vector := (Ada.Finalization.Controlled with others 
=> <>);
+
+   No_Element : constant Cursor := (others => <>);
+
+   ----------
+   --  Visible for child package
+
+   function To_Peek_Type (Item : in Extended_Index) return Base_Peek_Type with 
Inline;
+   function To_Index_Type (Item : in Base_Peek_Type) return Extended_Index;
+
+   procedure Grow (Elements : in out Array_Access; Index : in Base_Peek_Type);
+
+end SAL.Gen_Unbounded_Definite_Vectors;
diff --git a/sal-gen_unbounded_definite_vectors_sorted.adb 
b/sal-gen_unbounded_definite_vectors_sorted.adb
new file mode 100644
index 0000000..6d836c5
--- /dev/null
+++ b/sal-gen_unbounded_definite_vectors_sorted.adb
@@ -0,0 +1,368 @@
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2019 Free Software Foundation, Inc.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+package body SAL.Gen_Unbounded_Definite_Vectors_Sorted is
+
+   ----------
+   --  Body subprograms, arbitrary order
+
+   procedure Grow (Elements : in out Array_Access; Index : in Base_Peek_Type)
+   is
+      --  Reallocate Elements so Elements (Index) is a valid element.
+
+      Old_First  : constant Peek_Type := Elements'First;
+      Old_Last   : constant Peek_Type := Elements'Last;
+      New_First  : Peek_Type          := Old_First;
+      New_Last   : Peek_Type          := Old_Last;
+      New_Length : Peek_Type          := Elements'Length;
+
+      New_Array : Array_Access;
+   begin
+      loop
+         exit when New_First <= Index;
+         New_Length := New_Length * 2;
+         New_First  := Peek_Type'Max (Peek_Type'First, Old_Last - New_Length + 
1);
+      end loop;
+      loop
+         exit when New_Last >= Index;
+         New_Length := New_Length * 2;
+         New_Last   := Peek_Type'Min (Peek_Type'Last, New_First + New_Length - 
1);
+      end loop;
+
+      New_Array := new Array_Type (New_First .. New_Last);
+
+      --  We'd like to use this:
+      --
+      --  New_Array (New_First .. Old_First - 1) := (others => <>);
+      --
+      --  but that can overflow the stack, since the aggregate is allocated
+      --  on the stack.
+
+      for I in New_First .. Old_First - 1 loop
+         New_Array (I .. I) := (others => <>);
+      end loop;
+
+      New_Array (Old_First .. Old_Last) := Elements.all;
+
+      for I in Old_Last + 1 .. New_Last loop
+         New_Array (I .. I)   := (others => <>);
+      end loop;
+
+      Free (Elements);
+      Elements := New_Array;
+   end Grow;
+
+   procedure Find
+     (Container : in     Vector;
+      Key       : in     Key_Type;
+      Found     :    out Boolean;
+      At_After  :    out Base_Peek_Type)
+   with Pre => Container.Last /= No_Index
+   is
+      --  If Found is True, item is at At_After. If False, item should be
+      --  inserted after At_After.
+      Low  : Base_Peek_Type := Peek_Type'First - 1;
+      High : Base_Peek_Type := Container.Last + 1;
+      I    : Base_Peek_Type := Low + High / 2;
+   begin
+      loop
+         case Key_Compare (Key, To_Key (Container.Elements (I))) is
+         when Less =>
+            High := I;
+            if I = Low then
+               Found    := False;
+               At_After := I;
+               return;
+
+            elsif I - 1 = Low then
+               Found    := False;
+               At_After := I - 1;
+               return;
+
+            else
+               I := I - (I - Low) / 2;
+            end if;
+
+         when Equal =>
+            Found    := True;
+            At_After := I;
+            return;
+
+         when Greater =>
+            Low := I;
+            if I = High then
+               Found    := False;
+               At_After := I - 1;
+               return;
+
+            elsif I + 1 = High then
+               Found    := False;
+               At_After := I;
+               return;
+
+            else
+               I := I + (High - I) / 2;
+            end if;
+         end case;
+      end loop;
+   end Find;
+
+   ----------
+   --  Public subprograms
+
+   overriding procedure Finalize (Container : in out Vector)
+   is begin
+      Free (Container.Elements);
+      Container.Last := No_Index;
+   end Finalize;
+
+   overriding procedure Adjust (Container : in out Vector)
+   is begin
+      if Container.Elements /= null then
+         Container.Elements := new Array_Type'(Container.Elements.all);
+      end if;
+   end Adjust;
+
+   function Length (Container : in Vector) return Ada.Containers.Count_Type
+   is begin
+      --  We assume the type ranges are sensible, so no exceptions occur
+      --  here.
+      if Container.Elements = null then
+         return 0;
+      else
+         return Ada.Containers.Count_Type (Container.Last - 
Container.Elements'First + 1);
+      end if;
+   end Length;
+
+   function Capacity (Container : in Vector) return Ada.Containers.Count_Type
+   is begin
+      if Container.Elements = null then
+         return 0;
+      else
+         return Ada.Containers.Count_Type (Container.Elements'Length);
+      end if;
+   end Capacity;
+
+   procedure Set_Capacity
+     (Container : in out Vector;
+      Length    : in     Ada.Containers.Count_Type)
+   is
+      use all type Ada.Containers.Count_Type;
+      First_Peek : constant Peek_Type      := Peek_Type'First;
+      Last_Peek  : constant Base_Peek_Type := Base_Peek_Type (Length);
+   begin
+      if Length = 0 then
+         return;
+      elsif Container.Elements = null then
+         Container.Elements := new Array_Type (First_Peek .. Last_Peek);
+      else
+         if First_Peek < Container.Elements'First then
+            Grow (Container.Elements, First_Peek);
+         end if;
+         if Last_Peek < Container.Elements'Last then
+            Grow (Container.Elements, Last_Peek);
+         end if;
+      end if;
+   end Set_Capacity;
+
+   function Has_Element (Position : Cursor) return Boolean is
+   begin
+      return Position.Index /= Invalid_Peek_Index;
+   end Has_Element;
+
+   function "&" (Left, Right : in Element_Type) return Vector
+   is begin
+      return Result : Vector do
+         Result.Insert (Left);
+         Result.Insert (Right);
+      end return;
+   end "&";
+
+   function "&" (Left : in Vector; Right : in Element_Type) return Vector
+   is begin
+      return Result : Vector := Left do
+         Result.Insert (Right);
+      end return;
+   end "&";
+
+   function Contains (Container : in Vector; Key : in Key_Type) return Boolean
+   is
+      Found : Boolean;
+      I     : Base_Peek_Type;
+   begin
+      if Container.Last = No_Index then
+         return False;
+      end if;
+      Find (Container, Key, Found, I);
+      return Found;
+   end Contains;
+
+   procedure Insert
+     (Container : in out Vector;
+      New_Item  : in     Element_Type)
+   is
+      New_Key : constant Key_Type       := To_Key (New_Item);
+      J       : constant Peek_Type      := Peek_Type'First;
+      K       : constant Base_Peek_Type := Container.Last;
+      I       : Base_Peek_Type          := K;
+   begin
+      if Container.Last = No_Index then
+         Container.Last := Peek_Type'First;
+         I              := Container.Last;
+
+         if Container.Elements = null then
+            Container.Elements := new Array_Type (I .. I);
+            --  else Set_Capacity called.
+         end if;
+         Container.Elements (I) := New_Item;
+         return;
+
+      else
+         Container.Last := Container.Last + 1;
+      end if;
+
+      pragma Assert (Container.Elements /= null);
+
+      if I + 1 > Container.Elements'Last then
+         Grow (Container.Elements, I + 1);
+      end if;
+
+      loop
+         exit when I < J;
+
+         case Key_Compare (New_Key, To_Key (Container.Elements (I))) is
+         when Less =>
+            --  Linear search is simple, we assume insert is used far less 
often
+            --  than Find. And this is optimal when inserting in Key order.
+            I :=  I - 1;
+         when Equal =>
+            --  Insert after I
+            exit;
+         when Greater =>
+            --  Insert after I
+            exit;
+         end case;
+      end loop;
+
+      if I < J then
+         --  Insert before all
+         Container.Elements (J + 1 .. K + 1) := Container.Elements (J .. K);
+         Container.Elements (J) := New_Item;
+      else
+         --  Insert after I
+         Container.Elements (I + 2 .. K + 1) := Container.Elements (I + 1 .. 
K);
+         Container.Elements (I + 1) := New_Item;
+      end if;
+   end Insert;
+
+   function Find
+     (Container : aliased in Vector;
+      Key       :         in Key_Type)
+     return Find_Reference_Type
+   is
+      Found : Boolean;
+      I     : Base_Peek_Type;
+   begin
+      if Container.Last = No_Index then
+         return (Element => null, Dummy => 1);
+      end if;
+      Find (Container, Key, Found, I);
+      if Found then
+         return (Element => Container.Elements (I)'Access, Dummy => 1);
+      else
+         return (Element => null, Dummy => 1);
+      end if;
+   end Find;
+
+   function Find_Constant
+     (Container : aliased in Vector;
+      Key       :         in Key_Type)
+     return Find_Reference_Constant_Type
+   is
+      Found : Boolean;
+      I     : Base_Peek_Type;
+   begin
+      if Container.Last = No_Index then
+         return (Element => null, Dummy => 1);
+      end if;
+      Find (Container, Key, Found, I);
+      if Found then
+         return (Element => Container.Elements (I)'Access, Dummy => 1);
+      else
+         return (Element => null, Dummy => 1);
+      end if;
+   end Find_Constant;
+
+   overriding function First (Object : Iterator) return Cursor
+   is begin
+      if Object.Container.Elements = null then
+         return (null, Invalid_Peek_Index);
+      else
+         return (Object.Container, Peek_Type'First);
+      end if;
+   end First;
+
+   overriding function Last  (Object : Iterator) return Cursor
+   is begin
+      if Object.Container.Elements = null then
+         return (null, Invalid_Peek_Index);
+      else
+         return (Object.Container, Object.Container.Last);
+      end if;
+   end Last;
+
+   overriding function Next (Object : in Iterator; Position : in Cursor) 
return Cursor
+   is begin
+      if Position.Index = Object.Container.Last then
+         return (null, Invalid_Peek_Index);
+      else
+         return (Object.Container, Position.Index + 1);
+      end if;
+   end Next;
+
+   overriding function Previous (Object : in Iterator; Position : in Cursor) 
return Cursor
+   is begin
+      if Position.Index = Peek_Type'First then
+         return (null, Invalid_Peek_Index);
+      else
+         return (Object.Container, Position.Index - 1);
+      end if;
+   end Previous;
+
+   function Iterate (Container : aliased in Vector) return 
Iterator_Interfaces.Reversible_Iterator'Class
+   is begin
+      return Iterator'(Container => Container'Unrestricted_Access);
+   end Iterate;
+
+   function Constant_Ref (Container : aliased Vector; Position : in Cursor) 
return Constant_Reference_Type
+   is begin
+      return (Element => Container.Elements (Position.Index)'Access, Dummy => 
1);
+   end Constant_Ref;
+
+   function Last_Index (Container : aliased Vector) return Base_Peek_Type
+   is begin
+      return Container.Last;
+   end Last_Index;
+
+   function Constant_Ref (Container : aliased Vector; Index : in Peek_Type) 
return Constant_Reference_Type
+   is begin
+      return (Element => Container.Elements (Index)'Access, Dummy => 1);
+   end Constant_Ref;
+
+end SAL.Gen_Unbounded_Definite_Vectors_Sorted;
diff --git a/sal-gen_unbounded_definite_vectors_sorted.ads 
b/sal-gen_unbounded_definite_vectors_sorted.ads
new file mode 100644
index 0000000..c197ec4
--- /dev/null
+++ b/sal-gen_unbounded_definite_vectors_sorted.ads
@@ -0,0 +1,170 @@
+--  Abstract :
+--
+--  A simple unbounded sorted vector of definite items.
+--
+--  Copyright (C) 2019 Free Software Foundation, Inc.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+with Ada.Finalization;
+with Ada.Iterator_Interfaces;
+with Ada.Unchecked_Deallocation;
+generic
+   type Element_Type is private;
+   type Key_Type is private;
+   with function To_Key (Item : in Element_Type) return Key_Type;
+   with function Key_Compare (Left, Right : in Key_Type) return Compare_Result;
+package SAL.Gen_Unbounded_Definite_Vectors_Sorted is
+
+   type Vector is new Ada.Finalization.Controlled with private with
+      Constant_Indexing => Constant_Ref,
+      Default_Iterator  => Iterate,
+      Iterator_Element  => Element_Type;
+
+   Empty_Vector : constant Vector;
+
+   overriding procedure Finalize (Container : in out Vector);
+   overriding procedure Adjust (Container : in out Vector);
+
+   procedure Clear (Container : in out Vector)
+   renames Finalize;
+
+   function Length (Container : in Vector) return Ada.Containers.Count_Type;
+   function Capacity (Container : in Vector) return Ada.Containers.Count_Type;
+
+   procedure Set_Capacity
+     (Container : in out Vector;
+      Length    : in     Ada.Containers.Count_Type);
+   --  Allocates uninitialized memory; does not change Container.First,
+   --  Container.Last.
+
+   function "&" (Left, Right : in Element_Type) return Vector;
+   function "&" (Left : in Vector; Right : in Element_Type) return Vector;
+
+   function Contains (Container : in Vector; Key : in Key_Type) return Boolean;
+
+   procedure Insert
+     (Container : in out Vector;
+      New_Item  : in     Element_Type);
+   --  Insert New_Item in sorted position. Items are sorted in increasing
+   --  order according to Element_Compare.
+   --
+   --  Raises Duplicate_Key if To_Key (New_Item) is already in Container.
+
+   type Find_Reference_Type (Element : access Element_Type) is private with
+     Implicit_Dereference => Element;
+
+   function Find
+     (Container : aliased in Vector;
+      Key       :         in Key_Type)
+     return Find_Reference_Type;
+   --  Result.Element is null if Key not in Container. User must not modify 
Key.
+
+   type Find_Reference_Constant_Type (Element : access constant Element_Type) 
is private with
+     Implicit_Dereference => Element;
+
+   function Find_Constant
+     (Container : aliased in Vector;
+      Key       : in Key_Type)
+     return Find_Reference_Constant_Type;
+   --  Result.Element is null if Key not in Container.
+
+   type Cursor is private;
+
+   No_Element : constant Cursor;
+
+   function Has_Element (Position : Cursor) return Boolean;
+
+   package Iterator_Interfaces is new Ada.Iterator_Interfaces (Cursor, 
Has_Element);
+
+   function Iterate (Container : aliased in Vector) return 
Iterator_Interfaces.Reversible_Iterator'Class;
+
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is private with
+     Implicit_Dereference => Element;
+
+   function Constant_Ref (Container : aliased Vector; Position : in Cursor) 
return Constant_Reference_Type
+   with Inline;
+
+   function First_Index (Container : aliased Vector) return Peek_Type is 
(Peek_Type'First);
+   function Last_Index (Container : aliased Vector) return Base_Peek_Type
+   with Inline;
+   function Constant_Ref (Container : aliased Vector; Index : in Peek_Type) 
return Constant_Reference_Type
+   with Inline;
+
+private
+
+   type Array_Type is array (SAL.Peek_Type range <>) of aliased Element_Type;
+   type Array_Access is access Array_Type;
+   procedure Free is new Ada.Unchecked_Deallocation (Array_Type, Array_Access);
+
+   No_Index : constant Base_Peek_Type := 0;
+
+   type Vector is new Ada.Finalization.Controlled with
+   record
+      Elements : Array_Access;
+      --  Elements may be non-null with First = No_Index, after
+      --  Set_Capacity. If First /= No_Index and Last >= First, Elements /=
+      --  null.
+      Last     : Base_Peek_Type := No_Index;
+   end record;
+
+   type Vector_Access is access constant Vector;
+   for Vector_Access'Storage_Size use 0;
+
+   type Cursor is record
+      Container : Vector_Access  := null;
+      Index     : Base_Peek_Type := No_Index;
+   end record;
+
+   type Iterator is new Iterator_Interfaces.Reversible_Iterator with
+   record
+      Container : Vector_Access;
+   end record;
+
+   overriding function First (Object : Iterator) return Cursor;
+   overriding function Last  (Object : Iterator) return Cursor;
+
+   overriding function Next
+     (Object   : Iterator;
+      Position : Cursor) return Cursor;
+
+   overriding function Previous
+     (Object   : Iterator;
+      Position : Cursor) return Cursor;
+
+   type Find_Reference_Type (Element : access Element_Type) is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
+
+   type Find_Reference_Constant_Type (Element : access constant Element_Type) 
is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
+
+   type Constant_Reference_Type (Element : not null access constant 
Element_Type) is
+   record
+      Dummy : Integer := raise Program_Error with "uninitialized reference";
+   end record;
+
+   Empty_Vector : constant Vector := (Ada.Finalization.Controlled with others 
=> <>);
+
+   No_Element : constant Cursor := (others => <>);
+
+   ----------
+   --  Visible for child package
+
+   procedure Grow (Elements : in out Array_Access; Index : in Base_Peek_Type);
+
+end SAL.Gen_Unbounded_Definite_Vectors_Sorted;
diff --git a/sal.adb b/sal.adb
index ac3b037..e3f5d52 100644
--- a/sal.adb
+++ b/sal.adb
@@ -26,7 +26,7 @@ package body SAL is
 
    function Version return String is
    begin
-      return "SAL 3.2";
+      return "SAL 3.3";
    end Version;
 
 end SAL;
diff --git a/wisi-compile.el b/wisi-compile.el
deleted file mode 100644
index 9788938..0000000
--- a/wisi-compile.el
+++ /dev/null
@@ -1,225 +0,0 @@
-;; wisi-compile.el --- Grammar compiler for the wisi parser, integrating Wisi 
OpenToken output.  -*- lexical-binding:t -*-
-;;
-;; Copyright (C) 2012-2017 Free Software Foundation, Inc.
-;;
-;; Author: Stephen Leake <stephen_leake@member.fsf.org>
-;;
-;; This file is part of GNU Emacs.
-;;
-;; GNU Emacs 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 3 of the License, or
-;; (at your option) any later version.
-;;
-;; GNU Emacs 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
-;;
-
-;;; Commentary:
-;;
-
-;;;; History: first experimental version Jan 2013
-;;
-;;;; Context
-;;
-;; Semantic (info "(semantic)Top") provides an LALR(1) parser
-;; wisent-parse.  The grammar used is defined by the functions
-;; semantic-grammar-create-package, which reads a bison-like source
-;; file and produces corresponding elisp source, and
-;; wisent-compile-grammar, which generates a parser table.
-;;
-;; However, the algorithm used in wisent-compile-grammar cannot cope
-;; with the grammar for the Ada language, because it is not
-;; LALR(1).  So we provide a generalized LALR parser, which spawns
-;; parallel LALR parsers at each conflict.  Instead of also rewriting
-;; the entire semantic grammar compiler, we use the OpenToken LALR
-;; parser generator, which is easier to modify (it is written in Ada,
-;; not Lisp).
-;;
-;; The Ada function Wisi.Generate reads the bison-like input and
-;; produces corresponding elisp source code, similar to that
-;; produced by semantic-grammar-create-package.
-;;
-;; wisi-compile-grammar (provided here) generates the automaton
-;; structure required by wisi-parse
-;;
-;;;;
-
-(defun wisi-compose-action (value symbol-obarray nonterms)
-  (let* ((nonterm (car value))
-       (index   (cdr value))
-       (symbol (intern-soft (format "%s:%d" nonterm index) symbol-obarray))
-       (rhs (car (nth index (cdr (assoc nonterm nonterms))))))
-    (list nonterm symbol (length rhs))
-    ))
-
-(defun wisi-replace-actions (action symbol-obarray nonterms)
-  "Replace semantic action symbol names in ACTION with list as defined in 
`wisi-compile-grammar'.
-ACTION is the alist for one state from the grammar, with the form:
-  ((default . error) ITEM ... )
-ITEM is one of:
-reduction  (TOKEN . (NONTERM . INDEX)) where NONTERM . INDEX gives the action 
symbol name.
-shift (TOKEN . STATE)
-shift/reduce conflict (STATE (NONTERM . INDEX))
-reduce/shift conflict ((NONTERM . INDEX) (NONTERM . INDEX))
-
-SYMBOL-OBARRAY contains the action symbols.
-NONTERMS is from the grammar.
-Return the new action alist."
-  ;; result is list of (nonterm index action-symbol token-count)
-  (let (result item)
-    (while action
-     (setq item (pop action))
-     (cond
-      ((or
-       (memq (cdr item) '(error accept))
-       (numberp (cdr item))) ;; shift
-       (push item result))
-
-      ((listp (cdr item))
-       (let ((value (cdr item)))
-        (cond
-         ((symbolp (car value))
-          ;; reduction
-          (push (cons (car item)
-                      (wisi-compose-action value symbol-obarray nonterms))
-                result))
-
-         ((integerp (car value))
-          ;; shift/reduce conflict
-          (push (cons (car item)
-                      (list (car value)
-                            (wisi-compose-action (cadr value) symbol-obarray 
nonterms)))
-                result))
-
-         (t ;; reduce/reduce conflict
-          (push (cons (car item)
-                      (list (wisi-compose-action (car value) symbol-obarray 
nonterms)
-                            (wisi-compose-action (cadr value) symbol-obarray 
nonterms)))
-                result))
-         )))
-
-      (t
-       (error "unexpected '%s'; expected 'error, 'accept, numberp, stringp, 
listp" (cdr item)))
-      ));; while/cond
-
-   (reverse result)))
-
-(defun wisi-semantic-action (form nonterm iactn symbol-obarray)
-  "Define an Elisp semantic action function for a production, interned in 
SYMBOL-OBARRAY.
-FORM is the body of the semantic action.
-NONTERM is the nonterminal left hand side.
-IACTN is the index of the production in the NTERM rule.
-
-The semantic action function accepts two arguments;
-- wisi-nterm  : the nonterminal
-- wisi-tokens : the list of tokens to be reduced.
-
-It returns nil; it is called for the semantic side-effects only."
-  ;; based on comp.el wisent-semantic-action
-  (let* ((name (format "%s:%d" nonterm iactn))
-        (action-symbol (intern name symbol-obarray)))
-
-    (fset action-symbol
-         `(lambda (wisi-nterm wisi-tokens)
-            ,form
-            nil))
-    (byte-compile action-symbol)))
-
-(defun wisi-compile-grammar (grammar)
-  "Compile the LALR(1) GRAMMAR; return the automaton for wisi-parse.
-GRAMMAR is a list NONTERMS ACTIONS GOTOS, where:
-
-NONTERMS is a list of productions; each production is a
-list (nonterm (tokens semantic-action) ...) where `semantic-action' is
-any lisp form. The set of (tokens semantic-action) are the right hand
-sides; nonterm is the left hand side.
-
-ACTIONS is an array indexed by parser state, of alists indexed by
-terminal tokens. The value of each item in the alists is one of:
-
-`error'
-
-`accept'
-
-integer - shift; gives new state
-
- (nonterm . index) - reduce by nonterm production index.
-
- (integer (nonterm . index)) - a shift/reduce conflict
- ((nonterm . index) (nonterm . index)) - a reduce/reduce conflict
-
-The first item in the alist must have the key `default' (not a
-terminal token); it is used when no other item matches the
-current token.
-
-GOTOS is an array indexed by parser state, of alists giving the
-new state after a reduce for each nonterminal legal in that
-state.
-
-The automaton is an array [parser-actions gotos symbol-obarray]:
-
-- parser-actions is a copy of the input ACTIONS, with semantic
-actions replaced by a list (nonterm action-symbol token-count),
-where:
-
--- nonterm is a symbol from NONTERMS, and is the non-terminal to
-reduce to
-
--- token-count is the number of tokens in the reduction,
-
--- action-symbol is nil if there is no semantic action, or a
-symbol interned in symbol-obarray
-
-- gotos is a copy of GOTOS.
-
-- symbol-obarray is an obarray containing functions that
-implement the semantic action for each nonterminal; the function
-names have the format nonterm:index."
-  ;; We store named symbols for semantic actions, not just lambda
-  ;; functions, so we have a name for debug trace.
-
-  (let ((defs (nth 0 grammar))
-       (symbol-obarray (make-vector 13 0));; for parse actions
-        (byte-compile-warnings '(not free-vars)) ;; for "wisi-test-success" in 
test/wisi/*
-       def nonterm rhs-list rule
-       semantic-action index)
-
-    (while defs
-      (setq def      (car defs)
-            defs     (cdr defs)
-            nonterm  (car def)
-            rhs-list (cdr def)
-            index    0)
-      (while rhs-list
-        (setq rule            (car rhs-list)
-              rhs-list        (cdr rhs-list)
-              semantic-action (cadr rule))
-
-       (when semantic-action
-         (wisi-semantic-action semantic-action nonterm index symbol-obarray))
-
-       (setq index (1+ index))
-       ))
-
-    ;; replace semantic actions in ACTIONS with symbols from symbol-obarray
-    (let ((nactions (length (nth 1 grammar)))
-         (actions (nth 1 grammar))
-         (i 0))
-      (while (< i nactions)
-       (aset actions i
-             (wisi-replace-actions (aref actions i) symbol-obarray (nth 0 
grammar)))
-       (setq i (1+ i)))
-      (vector
-       actions
-       (nth 2 grammar)
-       symbol-obarray)
-      )))
-
-(provide 'wisi-compile)
-;;; wisi-compile.el ends here
diff --git a/wisi-elisp-lexer.el b/wisi-elisp-lexer.el
deleted file mode 100644
index b0d1e3c..0000000
--- a/wisi-elisp-lexer.el
+++ /dev/null
@@ -1,393 +0,0 @@
-;;; wisi-elisp-lexer.el --- A lexer for wisi, implemented in elisp -*- 
lexical-binding:t -*-
-;;
-;; Copyright (C) 2017, 2018  Free Software Foundation, Inc.
-;;
-;; Author: Stephen Leake <stephen_leake@stephe-leake.org>
-;;
-;; This file is part of GNU Emacs.
-;;
-;; GNU Emacs 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 3 of the License, or
-;; (at your option) any later version.
-;;
-;; GNU Emacs 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
-;;
-
-;;; Commentary:
-
-;;;; History: see NEWS-wisi.text
-
-(require 'cl-lib)
-(require 'semantic/lex)
-(require 'wisi-parse-common)
-
-(cl-defstruct wisi-elisp-lexer
-  id-alist ;; alist mapping strings to token ids; used by repair error
-  keyword-table ;; obarray holding keyword tokens
-  punctuation-table ;; obarray holding punctuation tokens
-  punctuation-table-max-length ;; max string length in punctuation-table
-  string-double-term ;; non-nil if strings delimited by double quotes
-  string-quote-escape-doubled ;; Non-nil if a string delimiter is escaped by 
doubling it
-  string-quote-escape
-  ;; Cons (delim . character) where `character' escapes quotes in strings 
delimited by `delim'.
-  string-single-term ;; non-nil if strings delimited by single quotes
-  symbol-term ;; symbol for a terminal symbol token
-  number-term ;; symbol for a terminal number literal token
-  number-p ;; function that determines if argument is a number literal
-  line-begin ;; vector of beginning-of-line positions in buffer
-  last-line ;; index into line-begin of line containing last lexed token
-  )
-
-(defun wisi-elisp-lexer-reset (line-count lexer)
-  "Reset lexer to start a new parse. LINE-COUNT is the count of lines in the 
current buffer."
-  (setf (wisi-elisp-lexer-line-begin lexer) (wisi--set-line-begin line-count))
-  (setf (wisi-elisp-lexer-last-line lexer) nil))
-
-(defvar-local wisi--lexer nil
-  "A `wisi-elisp-lexer' struct defining the lexer for the current buffer.")
-
-(defun wisi-elisp-lexer--safe-intern (name obtable)
-  (let ((var (intern-soft name obtable)))
-    (and (boundp var) (symbol-value var))))
-
-(cl-defun wisi-make-elisp-lexer (&key token-table-raw keyword-table-raw 
string-quote-escape-doubled string-quote-escape)
-  "Return a ‘wisi-elisp-lexer’ object."
-  (let* ((token-table (semantic-lex-make-type-table token-table-raw))
-        (keyword-table (semantic-lex-make-keyword-table keyword-table-raw))
-        (left-paren (cadr (wisi-elisp-lexer--safe-intern "left-paren" 
token-table)))
-        (right-paren (cadr (wisi-elisp-lexer--safe-intern "right-paren" 
token-table)))
-        (punctuation-table (wisi-elisp-lexer--safe-intern "punctuation" 
token-table))
-        (punct-max-length 0)
-        (number (cadr (wisi-elisp-lexer--safe-intern "number" token-table)))
-        (symbol (cadr (wisi-elisp-lexer--safe-intern "symbol" token-table)))
-        (string-double (cadr (wisi-elisp-lexer--safe-intern "string-double" 
token-table)))
-        (string-single (cadr (wisi-elisp-lexer--safe-intern "string-single" 
token-table)))
-        id-alist
-        fail)
-    (dolist (item punctuation-table)
-      ;; check that all chars used in punctuation tokens have punctuation 
syntax
-      (mapc (lambda (char)
-             (when (not (= ?. (char-syntax char)))
-               (setq fail t)
-               (message "in %s, %c does not have punctuation syntax"
-                        (car item) char)))
-           (cdr item))
-
-      ;; accumulate max length
-      (when (< punct-max-length (length (cdr item)))
-       (setq punct-max-length (length (cdr item))))
-
-      ;; build id-alist
-      (push item id-alist)
-      )
-
-    (when fail
-      (error "aborting due to punctuation errors"))
-
-    (when number
-      (push (cons (nth 0 number) "1234") id-alist)
-      (when (nth 2 number)
-       (require (nth 2 number)))) ;; for number-p
-
-    (when left-paren
-      (push left-paren id-alist)
-      (set (intern (cdr left-paren) keyword-table) (car left-paren)))
-    (when right-paren
-      (push right-paren id-alist)
-      (set (intern (cdr right-paren) keyword-table) (car right-paren)))
-
-    (when symbol
-      (push (cons (car symbol) "a_bogus_identifier") id-alist))
-
-    (when string-double
-      (push (cons (car string-double) "\"\"") id-alist))
-
-    (when string-single
-      (push (cons (car string-single) "''") id-alist))
-
-    (dolist (item keyword-table-raw)
-      (push (cons (cdr item) (car item)) id-alist))
-
-    (make-wisi-elisp-lexer
-     :id-alist id-alist
-     :keyword-table keyword-table
-     :punctuation-table punctuation-table
-     :punctuation-table-max-length punct-max-length
-     :string-double-term (car string-double)
-     :string-quote-escape-doubled string-quote-escape-doubled
-     :string-quote-escape string-quote-escape
-     :string-single-term (car string-single)
-     :symbol-term (car symbol)
-     :number-term (nth 0 number)
-     :number-p (nth 1 number)
-     )
-    ))
-
-(defun wisi-number-p (token-text)
-  ;; Not ’wisi-elisp-lexer-number-p’, because this can appear in grammar files.
-  "Return t if TOKEN-TEXT plus text after point matches the
-syntax for a real literal; otherwise nil.  Point is after
-TOKEN-TEXT; move point to just past token."
-  ;; Typical literals:
-  ;; 1234
-  ;; 1234.5678
-  ;; _not_ including non-decimal base, or underscores (see ada-wisi-number-p)
-  ;;
-  ;; Starts with a simple integer
-  (when (string-match "^[0-9]+$" token-text)
-    (when (looking-at "\\.[0-9]+")
-      ;; real number
-      (goto-char (match-end 0))
-      (when (looking-at  "[Ee][+-][0-9]+")
-        ;; exponent
-        (goto-char (match-end 0))))
-
-    t
-    ))
-
-(defun wisi-forward-token ()
-  ;; Not ’wisi-elisp-lexer-forward-token’, for backward compatibility
-  "Move point forward across one token, then skip whitespace and comments.
-Return the corresponding token as a `wisi-tok'.
-If at whitespace or comment, throw an error.
-If at end of buffer, return `wisi-eoi-term'."
-  (let ((start (point))
-       ;; (info "(elisp)Syntax Table Internals" "*info elisp syntax*")
-       end
-       (syntax (syntax-class (syntax-after (point))))
-       (first nil)
-       (comment-line nil)
-       (comment-end nil)
-       token-id token-text line)
-    (cond
-     ((eobp)
-      (setq token-id wisi-eoi-term))
-
-     ((eq syntax 1)
-      ;; punctuation. Find the longest matching string in 
wisi-punctuation-table
-      (forward-char 1)
-      (let ((next-point (point))
-           temp-text temp-id done)
-       (while (not done)
-         (setq temp-text (buffer-substring-no-properties start (point)))
-         (setq temp-id (car (rassoc temp-text 
(wisi-elisp-lexer-punctuation-table wisi--lexer))))
-         (when temp-id
-           (setq token-id temp-id
-                 next-point (point)))
-         (if (or
-              (eobp)
-              (= (- (point) start) 
(wisi-elisp-lexer-punctuation-table-max-length wisi--lexer)))
-             (setq done t)
-           (forward-char 1))
-         )
-       (goto-char next-point)))
-
-     ((memq syntax '(4 5)) ;; open, close parenthesis
-      (forward-char 1)
-      (setq token-text (buffer-substring-no-properties start (point)))
-      (setq token-id (symbol-value (intern-soft token-text 
(wisi-elisp-lexer-keyword-table wisi--lexer)))))
-
-     ((eq syntax 7)
-      ;; string quote, either single or double. we assume point is
-      ;; before the start quote, not the end quote
-      (let ((delim (char-after (point)))
-           (forward-sexp-function nil))
-       (condition-case err
-           (progn
-             (forward-sexp)
-
-             ;; point is now after the end quote; check for an escaped quote
-             (while (or
-                     (and (wisi-elisp-lexer-string-quote-escape-doubled 
wisi--lexer)
-                          (eq (char-after (point)) delim))
-                     (and (eq delim (car (wisi-elisp-lexer-string-quote-escape 
wisi--lexer)))
-                          (eq (char-before (1- (point))) (cdr 
(wisi-elisp-lexer-string-quote-escape wisi--lexer)))))
-               (forward-sexp))
-             (setq token-id (if (= delim ?\")
-                                (wisi-elisp-lexer-string-double-term 
wisi--lexer)
-                              (wisi-elisp-lexer-string-single-term 
wisi--lexer))))
-         (scan-error
-          ;; Something screwed up; we should not get here if
-          ;; syntax-propertize works properly.
-          (signal 'wisi-parse-error (format "wisi-forward-token: forward-sexp 
failed %s" err))
-          ))))
-
-     ((memq syntax '(2 3 6)) ;; word, symbol, expression prefix (includes 
numbers)
-      (skip-syntax-forward "w_'")
-      (setq token-text (buffer-substring-no-properties start (point)))
-      (setq token-id
-           (or (symbol-value (intern-soft (downcase token-text) 
(wisi-elisp-lexer-keyword-table wisi--lexer)))
-               (and (functionp (wisi-elisp-lexer-number-p wisi--lexer))
-                    (funcall (wisi-elisp-lexer-number-p wisi--lexer) 
token-text)
-                    (setq token-text (buffer-substring-no-properties start 
(point)))
-                    (wisi-elisp-lexer-number-term wisi--lexer))
-               (wisi-elisp-lexer-symbol-term wisi--lexer)))
-      )
-
-     (t
-      (signal 'wisi-parse-error (format "wisi-forward-token: unsupported 
syntax %s" syntax)))
-
-     );; cond
-
-    (unless token-id
-      (signal 'wisi-parse-error
-             (wisi-error-msg "unrecognized token '%s'" 
(buffer-substring-no-properties start (point)))))
-
-    (setq end (point))
-
-    (forward-comment (point-max))
-
-    (when (and (not (eq token-id wisi-eoi-term))
-              (eq wisi--parse-action 'indent))
-      ;; parsing for indent; track line numbers
-
-      (if (wisi-elisp-lexer-last-line wisi--lexer)
-         (progn
-           (setq line (wisi-elisp-lexer-last-line wisi--lexer))
-           (when (>= start (aref (wisi-elisp-lexer-line-begin wisi--lexer) 
line))
-             ;; first token on next non-blank line
-             (setq line (1+ line))
-             (setq first t))
-           ;; else other token on line
-           )
-
-       ;; First token on first non-comment line
-       (setq line (line-number-at-pos start))
-       (setq first t)
-       )
-      (setf (wisi-elisp-lexer-last-line wisi--lexer) line)
-
-      ;; set comment-line, comment-end
-      (when (and (< (1+ (wisi-elisp-lexer-last-line wisi--lexer)) (length 
(wisi-elisp-lexer-line-begin wisi--lexer)))
-                (>= (point) (aref (wisi-elisp-lexer-line-begin wisi--lexer)
-                                (1+ (wisi-elisp-lexer-last-line 
wisi--lexer)))))
-       (setq comment-line (1+ (wisi-elisp-lexer-last-line wisi--lexer)))
-       (setf (wisi-elisp-lexer-last-line wisi--lexer) comment-line)
-       (setq comment-end (line-end-position 0)))
-
-      ;; count blank or comment lines following token
-      (when comment-end
-       (while (and (< (1+ (wisi-elisp-lexer-last-line wisi--lexer)) (length 
(wisi-elisp-lexer-line-begin wisi--lexer)))
-                   (>= comment-end (aref (wisi-elisp-lexer-line-begin 
wisi--lexer) (wisi-elisp-lexer-last-line wisi--lexer))))
-         (setf (wisi-elisp-lexer-last-line wisi--lexer) (1+ 
(wisi-elisp-lexer-last-line wisi--lexer))))
-
-      ))
-
-    (make-wisi-tok
-     :token token-id
-     :region (cons start end)
-     :line line
-     :first first
-     :comment-end comment-end
-     :comment-line comment-line)
-    ))
-
-(defun wisi-backward-token ()
-  ;; Not ’wisi-elisp-lexer-backward-token’, for backward compatibility
-  "Move point backward across one token, skipping whitespace and comments.
-Does _not_ handle numbers with wisi-number-p; just sees
-lower-level syntax.  Return a `wisi-tok' - same structure as
-wisi-forward-token, but only sets token-id and region."
-  (forward-comment (- (point)))
-  ;; skips leading whitespace, comment, trailing whitespace.
-
-  ;; (info "(elisp)Syntax Table Internals" "*info elisp syntax*")
-  (let ((end (point))
-       (syntax (syntax-class (syntax-after (1- (point)))))
-       token-id token-text)
-    (cond
-     ((bobp) nil)
-
-     ((eq syntax 1)
-      ;; punctuation. Find the longest matching string in 
wisi-lex-punctuation-table
-      (backward-char 1)
-      (let ((next-point (point))
-           temp-text temp-id done)
-       (while (not done)
-         (setq temp-text (buffer-substring-no-properties (point) end))
-         (when (setq temp-id (car (rassoc temp-text 
(wisi-elisp-lexer-punctuation-table wisi--lexer))))
-           (setq token-id temp-id)
-           (setq next-point (point)))
-         (if (or
-              (bobp)
-              (= (- end (point)) 
(wisi-elisp-lexer-punctuation-table-max-length wisi--lexer)))
-             (setq done t)
-           (backward-char 1))
-         )
-       (goto-char next-point))
-      )
-
-     ((memq syntax '(4 5)) ;; open, close parenthesis
-      (backward-char 1)
-      (setq token-id
-           (symbol-value
-            (intern-soft (buffer-substring-no-properties (point) end)
-                         (wisi-elisp-lexer-keyword-table wisi--lexer)))))
-
-     ((eq syntax 7)
-      ;; a string quote. we assume we are after the end quote, not the start 
quote
-      (let ((delim (char-after (1- (point))))
-           (forward-sexp-function nil))
-       (forward-sexp -1)
-       (setq token-id (if (= delim ?\")
-                          (wisi-elisp-lexer-string-double-term wisi--lexer)
-                        (wisi-elisp-lexer-string-single-term wisi--lexer)))
-       ))
-
-     (t ;; assuming word or symbol syntax
-      (if (zerop (skip-syntax-backward "."))
-         (skip-syntax-backward "w_'"))
-      (setq token-text (buffer-substring-no-properties (point) end))
-      (setq token-id
-           (or (symbol-value (intern-soft (downcase token-text) 
(wisi-elisp-lexer-keyword-table wisi--lexer)))
-               (and (functionp (wisi-elisp-lexer-number-p wisi--lexer))
-                    (funcall (wisi-elisp-lexer-number-p wisi--lexer) 
token-text)
-                    (setq token-text (buffer-substring-no-properties (point) 
end))
-                    (wisi-elisp-lexer-number-term wisi--lexer))
-               (wisi-elisp-lexer-symbol-term wisi--lexer))))
-     )
-
-    (make-wisi-tok
-     :token token-id
-     :region (cons (point) end))
-    ))
-
-;;;; Debugging
-
-(defun wisi-lex-buffer (&optional parse-action)
-  ;; for timing the lexer
-  (interactive)
-  (when (< emacs-major-version 25) (syntax-propertize (point-max)))
-
-  (let* ((wisi--parse-action (or parse-action 'indent))
-        (line-count (1+ (count-lines (point-min) (point-max))))
-        )
-
-    (cl-case wisi--parse-action
-      (indent
-       (setf (wisi-elisp-lexer-last-line wisi--lexer) nil)
-       (setf (wisi-elisp-lexer-line-begin wisi--lexer) (wisi--set-line-begin 
line-count)))
-      (t nil))
-
-    (goto-char (point-min))
-    (while (forward-comment 1))
-    (while (not (eq wisi-eoi-term (wisi-tok-token (wisi-forward-token)))))
-    ))
-
-(defun wisi-show-token ()
-  "Move forward across one keyword, show token."
-  (interactive)
-  (let* ((wisi--parse-action nil)
-        (token (wisi-forward-token)))
-    (message "%s" token)))
-
-
-(provide 'wisi-elisp-lexer)
-;;; end of file
diff --git a/wisi-elisp-parse.el b/wisi-elisp-parse.el
deleted file mode 100644
index 2c93a37..0000000
--- a/wisi-elisp-parse.el
+++ /dev/null
@@ -1,1721 +0,0 @@
-;; wisi-elisp-parse.el --- Wisi parser  -*- lexical-binding:t -*-
-
-;; Copyright (C) 2013-2015, 2017 - 2019  Free Software Foundation, Inc.
-
-;; This file is part of GNU Emacs.
-
-;; GNU Emacs 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 3 of the License, or
-;; (at your option) any later version.
-
-;; GNU Emacs 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
-
-
-;;; Commentary:
-
-;; An extended LALR parser, that handles shift/reduce and
-;; reduce/reduce conflicts by spawning parallel parsers to follow each
-;; path.
-
-;;; Code:
-
-(require 'cl-lib)
-(require 'wisi-elisp-lexer)
-(require 'wisi-parse-common)
-
-(defvar wisi-elisp-parse-max-parallel-current (cons 0 0)
-  "Cons (count . point); Maximum number of parallel parsers used in most 
recent parse,
-point at which that max was spawned.")
-
-(defvar wisi-debug-identical 0
-  "Debug terminating identical parsers.
-0 - keep lower-numbered parser.
-1 - keep higher-numbered parser.
-2 - error.")
-
-(cl-defstruct (wisi-elisp-parser-state
-           (:copier nil))
-  label ;; integer identifying parser for debug
-
-  active
-  ;; 'shift  - need new token
-  ;; 'reduce - need reduce
-  ;; 'accept - parsing completed
-  ;; 'error  - failed, error not reported yet
-  ;; nil     - terminated
-  ;;
-  ;; 'pending-shift, 'pending-reduce - newly created parser
-
-  stack
-  ;; Each stack item takes two slots: wisi-tok, state
-
-  sp ;; stack pointer
-
-  pending
-  ;; list of (action-symbol stack-fragment)
-  )
-
-(cl-defstruct (wisi-elisp-parser (:include wisi-parser))
-  actions
-  gotos
-  next-token
-  )
-
-;;;###autoload
-(defun wisi-make-elisp-parser (automaton next-token)
-  "Return ‘wisi-parser’ object.
-
-- AUTOMATON is the parse table generated by `wisi-compile-grammar'.
-
-- NEXT-TOKEN is a function with no argument called by the parser to
-  obtain the next token from the current buffer after point, as a
-  ’wisi-tok’ object (normally ‘wisi-forward-token’)."
-  (make-wisi-elisp-parser
-   :actions (aref automaton 0)
-   :gotos (aref automaton 1)
-   :next-token next-token))
-
-(cl-defmethod wisi-parse-kill ((_parser wisi-elisp-parser))
-  nil)
-
-(defvar wisi-elisp-parse--indent nil
-  ;; not buffer-local; only let-bound in wisi-parse-current (elisp)
-  "A vector of indentation for all lines in buffer.
-Each element can be one of:
-- nil : no indent set yet
-
-- integer : indent
-
-- list ('anchor (start-id ...) indent)  :
-  indent for current line, base indent for following 'anchored
-  lines. Start-id is list of ids anchored at this line. For parens
-  and other uses.
-
-- list ('anchored id delta) :
-  indent = delta + 'anchor id line indent; for lines indented
-  relative to anchor.
-
-- list ('anchor (start-id ...) ('anchored id delta))
-  for nested anchors.")
-
-(cl-defmethod wisi-parse-current ((parser wisi-elisp-parser) _begin _send-end 
_parse-end)
-  "Parse entire current buffer.
-BEGIN, END are ignored"
-
-  (let ((actions (wisi-elisp-parser-actions parser))
-       (gotos   (wisi-elisp-parser-gotos parser))
-       (parser-states ;; vector of parallel parser states
-        (vector
-         (make-wisi-elisp-parser-state
-          :label 0
-          :active  'shift
-          :stack   (make-vector wisi-parse-max-stack-size nil)
-          :sp      0
-          :pending nil)))
-       (active-parser-count 1)
-       active-parser-count-prev
-       (active 'shift)
-       (token nil)
-       some-pending
-       wisi-elisp-parse--indent)
-
-    (cl-case wisi--parse-action
-      (indent
-       (let ((line-count (1+ (count-lines (point-min) (point-max)))))
-        (setq wisi-elisp-parse--indent (make-vector line-count nil))
-        (wisi-elisp-lexer-reset line-count wisi--lexer)))
-
-      (navigate
-       (setq wisi-end-caches nil))
-
-      (t nil))
-
-    (setf (wisi-parser-lexer-errors parser) nil)
-    (setf (wisi-parser-parse-errors parser) nil)
-
-    ;; We assume the lexer relies on syntax properties
-    (when (< emacs-major-version 25) (syntax-propertize (point-max)))
-
-    (goto-char (point-min))
-    (forward-comment (point-max))
-    (aset (wisi-elisp-parser-state-stack (aref parser-states 0)) 0 0)
-
-    (setq token (funcall (wisi-elisp-parser-next-token parser)))
-    (setq wisi-elisp-parse-max-parallel-current (cons 0 0))
-
-    (while (not (eq active 'accept))
-      (setq active-parser-count-prev active-parser-count)
-      (setq some-pending nil)
-      (dotimes (parser-index (length parser-states))
-       (when (eq active (wisi-elisp-parser-state-active (aref parser-states 
parser-index)))
-         (let* ((parser-state (aref parser-states parser-index))
-                (result (wisi-elisp-parse-1 token parser-state (> 
active-parser-count 1) actions gotos)))
-           (when result
-             ;; spawn a new parser
-             (when (= active-parser-count wisi-parse-max-parallel)
-               (let* ((state (aref (wisi-elisp-parser-state-stack parser-state)
-                                   (wisi-elisp-parser-state-sp parser-state)))
-                      (msg (wisi-error-msg (concat "too many parallel parsers 
required in grammar state %d;"
-                                                   " simplify grammar, or 
increase `wisi-elisp-parse-max-parallel'")
-                                           state)))
-                 (push (make-wisi--parse-error :pos (point) :message msg) 
(wisi-parser-parse-errors parser))
-                 (signal 'wisi-parse-error msg)))
-
-             (let ((j (wisi-elisp-parse-free-parser parser-states)))
-               (cond
-                ((= j -1)
-                 ;; Add to parser-states; the new parser won't be executed
-                 ;; again in this parser-index loop.
-                 (setq parser-states (vconcat parser-states (vector nil)))
-                 (setq j (1- (length parser-states))))
-                ((< j parser-index)
-                 ;; The new parser won't be executed again in this
-                 ;; parser-index loop; nothing to do.
-                 )
-                (t
-                 ;; Don't let the new parser execute again in this
-                 ;; parser-index loop.
-                 (setq some-pending t)
-                 (setf (wisi-elisp-parser-state-active result)
-                       (cl-case (wisi-elisp-parser-state-active result)
-                         (shift 'pending-shift)
-                         (reduce 'pending-reduce)
-                        )))
-                 )
-               (setq active-parser-count (1+ active-parser-count))
-               (when (> active-parser-count (car 
wisi-elisp-parse-max-parallel-current))
-                 (setq wisi-elisp-parse-max-parallel-current (cons 
active-parser-count (point))))
-               (setf (wisi-elisp-parser-state-label result) j)
-               (aset parser-states j result))
-             (when (> wisi-debug 1)
-                (message "spawn parser (%d active)" active-parser-count)))
-
-           (when (eq 'error (wisi-elisp-parser-state-active parser-state))
-             (setq active-parser-count (1- active-parser-count))
-             (when (> wisi-debug 1)
-                (message "terminate parser (%d active)" active-parser-count))
-             (cl-case active-parser-count
-               (0
-                (cond
-                 ((= active-parser-count-prev 1)
-                  ;; We were not in a parallel parse; abandon parsing, report 
the error.
-                  (let* ((state (aref (wisi-elisp-parser-state-stack 
parser-state)
-                                      (wisi-elisp-parser-state-sp 
parser-state)))
-                         (msg (wisi-error-msg "syntax error in grammar state 
%d; unexpected %s, expecting one of %s"
-                                              state
-                                              (wisi-token-text token)
-                                              (mapcar 'car (aref actions 
state)))))
-                    (push (make-wisi--parse-error :pos (point) :message msg) 
(wisi-parser-parse-errors parser))
-                    (signal 'wisi-parse-error msg)))
-                 (t
-                  ;; Report errors from all parsers that failed on this token.
-                  (let ((msg))
-                    (dotimes (_ (length parser-states))
-                      (let* ((parser-state (aref parser-states parser-index))
-                             (state (aref (wisi-elisp-parser-state-stack 
parser-state)
-                                          (wisi-elisp-parser-state-sp 
parser-state))))
-                        (when (eq 'error (wisi-elisp-parser-state-active 
parser-state))
-                          (setq msg
-                                (concat msg
-                                        (when msg "\n")
-                                        (wisi-error-msg
-                                         "syntax error in grammar state %d; 
unexpected %s, expecting one of %s"
-                                         state
-                                         (wisi-token-text token)
-                                         (mapcar 'car (aref actions state)))))
-                          )))
-                    (push (make-wisi--parse-error :pos (point) :message msg) 
(wisi-parser-parse-errors parser))
-                    (signal 'wisi-parse-error msg)))
-                 ))
-
-               (1
-                (setf (wisi-elisp-parser-state-active parser-state) nil); 
Don't save error for later.
-                (wisi-elisp-parse-execute-pending (aref parser-states 
(wisi-elisp-parse-active-parser parser-states))))
-
-               (t
-                ;; We were in a parallel parse, and this parser
-                ;; failed; mark it inactive, don't save error for
-                ;; later.
-                (setf (wisi-elisp-parser-state-active parser-state) nil)
-                )))
-           )));; end dotimes
-
-      (when some-pending
-       ;; Change pending-* parsers to *.
-       (dotimes (parser-index (length parser-states))
-         (cond
-          ((eq (wisi-elisp-parser-state-active (aref parser-states 
parser-index)) 'pending-shift)
-           (setf (wisi-elisp-parser-state-active (aref parser-states 
parser-index)) 'shift))
-          ((eq (wisi-elisp-parser-state-active (aref parser-states 
parser-index)) 'pending-reduce)
-           (setf (wisi-elisp-parser-state-active (aref parser-states 
parser-index)) 'reduce))
-          )))
-
-      (setq active (wisi-elisp-parsers-active parser-states 
active-parser-count))
-      (when (eq active 'shift)
-       (when (> active-parser-count 1)
-         (setq active-parser-count (wisi-elisp-parse-elim-identical parser 
parser-states active-parser-count)))
-
-       (setq token (funcall (wisi-elisp-parser-next-token parser))))
-    )
-    (when (> active-parser-count 1)
-      (error "ambiguous parse result"))
-
-    (cl-case wisi--parse-action
-      (indent
-       (wisi-elisp-parse--indent-leading-comments)
-       (wisi-elisp-parse--resolve-anchors))
-
-      (t nil))
-
-    ;; Return region parsed.
-    (cons (point-min) (point))
-    ))
-
-(defun wisi-elisp-parsers-active-index (parser-states)
-  ;; only called when active-parser-count = 1
-  (let ((result nil)
-       (i 0))
-    (while (and (not result)
-               (< i (length parser-states)))
-      (when (wisi-elisp-parser-state-active (aref parser-states i))
-       (setq result i))
-      (setq i (1+ i)))
-    result))
-
-(defun wisi-elisp-parsers-active (parser-states active-count)
-  "Return the type of parser cycle to execute.
-PARSER-STATES[*].active is the last action a parser took. If it
-was `shift', that parser used the input token, and should not be
-executed again until another input token is available, after all
-parsers have shifted the current token or terminated.
-
-Returns one of:
-
-`accept' : all PARSER-STATES have active set to nil or `accept' -
-done parsing
-
-`shift' : all PARSER-STATES have active set to nil, `accept', or
-`shift' - get a new token, execute `shift' parsers.
-
-`reduce' : some PARSER-STATES have active set to `reduce' - no new
-token, execute `reduce' parsers."
-  (let ((result nil)
-       (i 0)
-       (shift-count 0)
-       (accept-count 0)
-       active)
-    (while (and (not result)
-               (< i (length parser-states)))
-      (setq active (wisi-elisp-parser-state-active (aref parser-states i)))
-      (cond
-       ((eq active 'shift) (setq shift-count (1+ shift-count)))
-       ((eq active 'reduce) (setq result 'reduce))
-       ((eq active 'accept) (setq accept-count (1+ accept-count)))
-       )
-      (setq i (1+ i)))
-
-    (cond
-     (result )
-     ((= accept-count active-count)
-      'accept)
-     ((= (+ shift-count accept-count) active-count)
-      'shift)
-     (t
-      ;; all parsers in error state; should not get here
-      (error "all parsers in error state; programmer error"))
-     )))
-
-(defun wisi-elisp-parse-free-parser (parser-states)
-  "Return index to a non-active parser in PARSER-STATES, -1 if there is none."
-  (let ((result nil)
-       (i 0))
-    (while (and (not result)
-               (< i (length parser-states)))
-      (when (not (wisi-elisp-parser-state-active (aref parser-states i)))
-       (setq result i))
-      (setq i (1+ i)))
-    (if result result -1)))
-
-(defun wisi-elisp-parse-active-parser (parser-states)
-  "Return index to the first active parser in PARSER-STATES."
-  (let ((result nil)
-       (i 0))
-    (while (and (not result)
-               (< i (length parser-states)))
-      (when (wisi-elisp-parser-state-active (aref parser-states i))
-       (setq result i))
-      (setq i (1+ i)))
-    (unless result
-      (error "no active parsers"))
-    result))
-
-(defun wisi-elisp-parse-elim-identical (parser parser-states 
active-parser-count)
-  "Check for parsers in PARSER-STATES that have reached identical states 
eliminate one.
-Return new ACTIVE-PARSER-COUNT. Assumes all parsers have active
-nil, `shift', or `accept'."
-  ;; parser-states passed by reference; active-parser-count by copy
-  ;; see test/ada_mode-slices.adb for example
-  (dotimes (parser-i (1- (length parser-states)))
-    (when (wisi-elisp-parser-state-active (aref parser-states parser-i))
-      (dotimes (parser-j (- (length parser-states) parser-i 1))
-       (when (wisi-elisp-parser-state-active (aref parser-states (+ parser-i 
parser-j 1)))
-         (when (eq (wisi-elisp-parser-state-sp (aref parser-states parser-i))
-                    (wisi-elisp-parser-state-sp (aref parser-states (+ 
parser-i parser-j 1))))
-           (let ((compare t)
-                 exec)
-             (dotimes (stack-i (wisi-elisp-parser-state-sp (aref parser-states 
parser-i)))
-               (setq
-                compare
-                (and compare ;; bypass expensive 'arefs' after first stack 
item compare fail
-                     (equal (aref (wisi-elisp-parser-state-stack (aref 
parser-states parser-i)) stack-i)
-                            (aref (wisi-elisp-parser-state-stack (aref 
parser-states (+ parser-i parser-j 1)))
-                                  stack-i)))))
-             (when compare
-               ;; parser stacks are identical
-               (setq active-parser-count (1- active-parser-count))
-               (when (> wisi-debug 1)
-                 (message "terminate identical parser %d (%d active)"
-                          (+ parser-i parser-j 1) active-parser-count)
-                 (let ((state-i (aref parser-states parser-i))
-                       (state-j (aref parser-states (+ parser-i parser-j 1))))
-                   (message "%d actions:" (wisi-elisp-parser-state-label 
state-i))
-                   (mapc #'wisi-elisp-parse-debug-put-action 
(wisi-elisp-parser-state-pending state-i))
-
-                   (message "%d actions:" (wisi-elisp-parser-state-label 
state-j))
-                   (mapc #'wisi-elisp-parse-debug-put-action 
(wisi-elisp-parser-state-pending state-j))
-                   ))
-               (cl-ecase wisi-debug-identical
-                 (0
-                  (setq exec parser-i)
-                  (setf (wisi-elisp-parser-state-active (aref parser-states (+ 
parser-i parser-j 1))) nil))
-
-                 (1
-                  (setq exec (+ parser-i parser-j 1))
-                  (setf (wisi-elisp-parser-state-active (aref parser-states 
parser-i)) nil))
-
-                 (2
-                  (let ((msg "identical parser stacks"))
-                    (push (make-wisi--parse-error :pos (point) :message msg) 
(wisi-parser-parse-errors parser))
-                    (signal 'wisi-parse-error msg)))
-                 )
-               (when (= active-parser-count 1)
-                 ;; The actions for the two parsers are not
-                 ;; identical, but most of the time either is good
-                 ;; enough for indentation and navigation, so we just
-                 ;; do the actions for the one that is not
-                 ;; terminating. Some times, a significant action is
-                 ;; lost. In that case, turn on
-                 ;; ‘wisi-debug-identical’ to investigate fixing it.
-                 (wisi-elisp-parse-execute-pending (aref parser-states exec)))
-               ))))
-       )))
-  active-parser-count)
-
-(defun wisi-elisp-parse-exec-action (func nonterm tokens)
-  "Execute action if TOKENS not null."
-  ;; `tokens' is null when all tokens in a grammar statement are
-  ;; optional and not present.
-  (unless wisi-action-disable
-    (if (< 0 (length tokens))
-       (when wisi--parse-action
-         (funcall func nonterm tokens))
-
-      (when (> wisi-debug 1)
-       (message "... action skipped; no tokens"))
-      )))
-
-(defvar wisi-elisp-parser-state nil
-  "Let-bound in `wisi-elisp-parse-reduce', used in `wisi-parse-find-token'.")
-
-(defun wisi-elisp-parse-debug-put-action (action)
-  ;; Action is (semantic-function nonterm [tokens])
-  (message "%s [%s]"
-          (nth 0 action)
-          (mapcar #'wisi-tok-debug-image (nth 2 action))))
-
-(defun wisi-elisp-parse-execute-pending (parser-state)
-  (let ((wisi-elisp-parser-state parser-state);; reference, for 
wisi-parse-find-token
-       (pending (wisi-elisp-parser-state-pending parser-state)))
-
-    (when (> wisi-debug 1)
-      (message "%d: pending actions:" (wisi-elisp-parser-state-label 
parser-state)))
-
-    (while pending
-      (when (> wisi-debug 1) (wisi-elisp-parse-debug-put-action (car pending)))
-
-      (let ((func-args (pop pending)))
-       (wisi-elisp-parse-exec-action (nth 0 func-args) (nth 1 func-args) 
(cl-caddr func-args)))
-      )
-    (setf (wisi-elisp-parser-state-pending parser-state) nil)
-    ))
-
-(defmacro wisi-elisp-parse-action (i al)
-  "Return the parser action.
-I is a token item number and AL is the list of (item . action)
-available at current state.  The first element of AL contains the
-default action for this state."
-  `(cdr (or (assq ,i ,al) (car ,al))))
-
-(defun wisi-elisp-parse-1 (token parser-state pendingp actions gotos)
-  "Perform one shift or reduce on PARSER-STATE.
-If PENDINGP, push actions onto PARSER-STATE.pending; otherwise execute them.
-See `wisi-elisp-parse' for full details.
-Return nil or new parser (a wisi-elisp-parser-state struct)."
-  (let* ((state (aref (wisi-elisp-parser-state-stack parser-state)
-               (wisi-elisp-parser-state-sp parser-state)))
-        (parse-action (wisi-elisp-parse-action (wisi-tok-token token) (aref 
actions state)))
-        new-parser-state)
-
-    (when (> wisi-debug 1)
-      ;; output trace info
-      (if (> wisi-debug 2)
-         (progn
-           ;; put top 10 stack items
-           (let* ((count (min 20 (wisi-elisp-parser-state-sp parser-state)))
-                  (msg (make-vector (+ 1 count) nil)))
-             (dotimes (i count)
-               (aset msg (- count i)
-                     (aref (wisi-elisp-parser-state-stack parser-state)
-                           (- (wisi-elisp-parser-state-sp parser-state) i)))
-               )
-             (message "%d: %s: %d: %s"
-                      (wisi-elisp-parser-state-label parser-state)
-                      (wisi-elisp-parser-state-active parser-state)
-                      (wisi-elisp-parser-state-sp parser-state)
-                      msg))
-           (message "   %d: %s: %s" state (wisi-tok-debug-image token) 
parse-action))
-       (message "%d: %d: %s: %s" (wisi-elisp-parser-state-label parser-state) 
state token parse-action)))
-
-    (when (and (listp parse-action)
-              (not (symbolp (car parse-action))))
-      ;; Conflict; spawn a new parser.
-      (setq new-parser-state
-           (make-wisi-elisp-parser-state
-            :active  nil
-            :stack   (vconcat (wisi-elisp-parser-state-stack parser-state))
-            :sp      (wisi-elisp-parser-state-sp parser-state)
-            :pending (wisi-elisp-parser-state-pending parser-state)))
-
-      (wisi-elisp-parse-2 (cadr parse-action) token new-parser-state t gotos)
-      (setq pendingp t)
-      (setq parse-action (car parse-action))
-      );; when
-
-    ;; current parser
-    (wisi-elisp-parse-2 parse-action token parser-state pendingp gotos)
-
-    new-parser-state))
-
-(defun wisi-elisp-parse-2 (action token parser-state pendingp gotos)
-  "Execute parser ACTION (must not be a conflict).
-Return nil."
-  (cond
-   ((eq action 'accept)
-    (setf (wisi-elisp-parser-state-active parser-state) 'accept))
-
-   ((eq action 'error)
-    (setf (wisi-elisp-parser-state-active parser-state) 'error))
-
-   ((natnump action)
-    ;; Shift token and new state (= action) onto stack
-    (let ((stack (wisi-elisp-parser-state-stack parser-state)); reference
-         (sp (wisi-elisp-parser-state-sp parser-state))); copy
-      (setq sp (+ sp 2))
-      (aset stack (1- sp) token)
-      (aset stack sp action)
-      (setf (wisi-elisp-parser-state-sp parser-state) sp))
-    (setf (wisi-elisp-parser-state-active parser-state) 'shift))
-
-   (t
-    (wisi-elisp-parse-reduce action parser-state pendingp gotos)
-    (setf (wisi-elisp-parser-state-active parser-state) 'reduce))
-   ))
-
-(defun wisi-elisp-parse-first-last (stack i j)
-  "Return a pair (FIRST . LAST), indices for the first and last
-non-empty tokens for a nonterminal; or nil if all tokens are
-empty. STACK is the parser stack.  I and J are the indices in
-STACK of the first and last tokens of the nonterminal."
-  (let ((start (car (wisi-tok-region (aref stack i))))
-        (end   (cdr (wisi-tok-region (aref stack j)))))
-    (while (and (or (not start) (not end))
-               (/= i j))
-      (cond
-       ((not start)
-       ;; item i is an empty production
-       (setq start (car (wisi-tok-region (aref stack (setq i (+ i 2)))))))
-
-       ((not end)
-       ;; item j is an empty production
-       (setq end (cdr (wisi-tok-region (aref stack (setq j (- j 2)))))))
-
-       (t (setq i j))))
-
-    (when (and start end)
-      (cons i j))
-    ))
-
-(cl-defmethod wisi-parse-find-token ((_parser wisi-elisp-parser) token-symbol)
-  "Find token with TOKEN-SYMBOL on current parser stack, return token struct.
-For use in grammar actions."
-  ;; Called from wisi-parse-exec-action in wisi-parse-reduce
-  (let* ((stack (wisi-elisp-parser-state-stack wisi-elisp-parser-state))
-        (sp (1- (wisi-elisp-parser-state-sp wisi-elisp-parser-state)))
-        (tok (aref stack sp)))
-    (while (and (> sp 0)
-               (not (eq token-symbol (wisi-tok-token tok))))
-      (setq sp (- sp 2))
-      (setq tok (aref stack sp)))
-    (if (= sp 0)
-       (error "token %s not found on parse stack" token-symbol)
-      tok)
-    ))
-
-(cl-defmethod wisi-parse-stack-peek ((_parser wisi-elisp-parser) n)
-  ;; IMPROVEME: store stack in parser
-  (let* ((stack (wisi-elisp-parser-state-stack wisi-elisp-parser-state))
-        (sp (1- (wisi-elisp-parser-state-sp wisi-elisp-parser-state)))
-        (i (- sp (* 2 n))))
-    (when (> i 0)
-      (aref stack i))))
-
-(defun wisi-elisp-parse-reduce (action parser-state pendingp gotos)
-  "Reduce PARSER-STATE.stack, and execute or pend ACTION."
-  (let* ((wisi-elisp-parser-state parser-state);; reference, for 
wisi-parse-find-token
-        (stack (wisi-elisp-parser-state-stack parser-state)); reference
-        (sp (wisi-elisp-parser-state-sp parser-state)); copy
-        (token-count (nth 2 action))
-        (nonterm (nth 0 action))
-        (first-last (when (> token-count 0)
-                      (wisi-elisp-parse-first-last stack (- sp (* 2 (1- 
token-count)) 1) (1- sp))))
-        (nonterm-region (when first-last
-                          (cons
-                           (car (wisi-tok-region (aref stack (car 
first-last))))
-                           (cdr (wisi-tok-region (aref stack (cdr 
first-last)))))))
-        (post-reduce-state (aref stack (- sp (* 2 token-count))))
-        (new-state (cdr (assoc nonterm (aref gotos post-reduce-state))))
-        (tokens (make-vector token-count nil))
-        line first comment-line comment-end)
-
-    (when (not new-state)
-      (error "no goto for %s %d" nonterm post-reduce-state))
-
-    (dotimes (i token-count) ;;  i = 0 .. (1- token-count); last token = 0, 
first token = (1- token-count)
-      (let ((tok (aref stack (- sp (* 2 i) 1))))
-       (when (nth 1 action)
-         ;; don't need wisi-tokens for a null user action
-         (aset tokens (- token-count i 1) tok))
-
-       (when (eq wisi--parse-action 'indent)
-         (setq line (or (wisi-tok-line tok) line))
-         (cond
-          ((numberp (wisi-tok-first tok))
-           (setq first (wisi-tok-first tok)))
-
-          ((wisi-tok-first tok)
-           (setq first (wisi-tok-line tok)))
-
-          ((and (not (= i 0))
-                      (wisi-tok-comment-line tok))
-           ;; comment lines following last token are not included in nonterm
-           ;; test/ada_mode-nominal.ads Object_Access_Type_5a
-           ;; test/ada_mode-parens.adb
-           (setq first (wisi-tok-comment-line tok)))
-          )
-         (when (and (= i 0)
-                    (wisi-tok-comment-line tok))
-           (setq comment-line (wisi-tok-comment-line tok))
-           (setq comment-end (wisi-tok-comment-end tok)))
-       )))
-
-    (setq sp (+ 2 (- sp (* 2 token-count))))
-    (aset stack (1- sp)
-         (make-wisi-tok
-          :token nonterm
-          :region nonterm-region
-          :nonterminal t
-          :line line
-          :first first
-          :comment-line comment-line
-          :comment-end comment-end))
-    (aset stack sp new-state)
-    (setf (wisi-elisp-parser-state-sp parser-state) sp)
-
-    (when (nth 1 action)
-      ;; nothing to do for a null user action
-      (if pendingp
-         (if (wisi-elisp-parser-state-pending parser-state)
-             (setf (wisi-elisp-parser-state-pending parser-state)
-                   (append (wisi-elisp-parser-state-pending parser-state)
-                           (list (list (nth 1 action) nonterm tokens))))
-           (setf (wisi-elisp-parser-state-pending parser-state)
-                 (list (list (nth 1 action) nonterm tokens))))
-
-       ;; Not pending.
-       (wisi-elisp-parse-exec-action (nth 1 action) nonterm tokens)
-       ))
-    ))
-
-;;;; navigate grammar actions
-
-(defun wisi-elisp-parse--set-end (start-mark end-mark)
-  "Set END-MARK on all caches in `wisi-end-caches' in range START-MARK 
END-MARK,
-delete from `wisi-end-caches'."
-  (let ((i 0)
-       pos cache)
-    (while (< i (length wisi-end-caches))
-      (setq pos (nth i wisi-end-caches))
-      (setq cache (wisi-get-cache pos))
-
-      (if (and (>= pos start-mark)
-              (<  pos end-mark))
-         (progn
-           (setf (wisi-cache-end cache) end-mark)
-           (setq wisi-end-caches (delq pos wisi-end-caches)))
-
-       ;; else not in range
-       (setq i (1+ i)))
-      )))
-
-(defvar wisi-tokens nil
-  ;; Not wisi-elisp-parse--tokens for ease in debugging actions, and
-  ;; to match lots of doc strings.
-  "Array of ‘wisi-tok’ structures for the right hand side of the current 
production.
-Let-bound in parser semantic actions.")
-
-(defvar wisi-nterm nil
-  ;; Not wisi-elisp-parse--nterm for ease in debugging actions
-  "The token id for the left hand side of the current production.
-Let-bound in parser semantic actions.")
-
-(defun wisi-statement-action (pairs)
-  ;; Not wisi-elisp-parse--statement-action to match existing grammar files
-  "Cache navigation information in text properties of tokens.
-Intended as a grammar non-terminal action.
-
-PAIRS is a vector of the form [TOKEN-NUMBER CLASS TOKEN-NUMBER
-CLASS ...] where TOKEN-NUMBER is the (1 indexed) token number in
-the production, CLASS is the wisi class of that token. Use in a
-grammar action as:
-  (wisi-statement-action [1 statement-start 7 statement-end])"
-  (when (eq wisi--parse-action 'navigate)
-    (save-excursion
-      (let ((first-item t)
-           first-keyword-mark
-           (override-start nil)
-           (i 0))
-       (while (< i (length pairs))
-         (let* ((number (1- (aref pairs i)))
-                (region (wisi-tok-region (aref wisi-tokens number)))
-                (token (wisi-tok-token (aref wisi-tokens number)))
-                (class (aref pairs (setq i (1+ i))))
-                (mark (when region (copy-marker (car region) t)))
-                cache)
-
-           (setq i (1+ i))
-
-           (unless
-               (cond
-                ((fboundp 'seq-contains)  ;; emacs < 27
-                   (seq-contains wisi-class-list class))
-                ((fboundp 'seq-contains-p) ;; emacs >= 27
-                 (seq-contains-p wisi-class-list class)))
-             (error "%s not in wisi-class-list" class))
-
-           (if region
-               (progn
-                 (if (setq cache (wisi-get-cache (car region)))
-                     ;; We are processing a previously set non-terminal; ie 
simple_statement in
-                     ;;
-                     ;; statement : label_opt simple_statement
-                     ;;
-                     ;; override nonterm, class, containing
-                     (progn
-                       (setf (wisi-cache-class cache) (or override-start 
class))
-                       (setf (wisi-cache-nonterm cache) wisi-nterm)
-                       (setf (wisi-cache-containing cache) first-keyword-mark)
-                       (if wisi-end-caches
-                           (push (car region) wisi-end-caches)
-                         (setq wisi-end-caches (list (car region)))
-                         ))
-
-                   ;; else create new cache
-                   (with-silent-modifications
-                     (put-text-property
-                      (car region)
-                      (1+ (car region))
-                      'wisi-cache
-                      (wisi-cache-create
-                       :nonterm    wisi-nterm
-                       :token      token
-                       :last       (- (cdr region) (car region))
-                       :class      (or override-start class)
-                       :containing first-keyword-mark)
-                      ))
-                   (if wisi-end-caches
-                       (push (car region) wisi-end-caches)
-                     (setq wisi-end-caches (list (car region)))
-                     ))
-
-                 (when first-item
-                   (setq first-item nil)
-                   (when (or override-start
-                             (eq class 'statement-start))
-                     (setq override-start nil)
-                     (setq first-keyword-mark mark)))
-
-                 (when (eq class 'statement-end)
-                   (wisi-elisp-parse--set-end first-keyword-mark (copy-marker 
(car region) t)))
-                 )
-
-             ;; region is nil when a production is empty; if the first
-             ;; token is a start, override the class on the next token.
-             (when (and first-item
-                        (eq class 'statement-start))
-               (setq override-start class)))
-           ))
-       ))))
-
-(defun wisi-name-action (name)
-  ;; Not wisi-elisp-parse--name-action to simplify grammar files
-  "NAME is a token number; mark that token with the 'wisi-name text property.
-Intended as a grammar action."
-  (when (eq wisi--parse-action 'navigate)
-    (let ((region (wisi-tok-region (aref wisi-tokens (1- name)))))
-      (when region
-       ;; region can be null on an optional or virtual token
-       (with-silent-modifications
-         (put-text-property (car region) (cdr region) 'wisi-name t))
-       ))))
-
-(defun wisi-containing-action (containing-token contained-token)
-  ;; Not wisi-elisp-parse--containing-action to match existing grammar files
-  "Set containing marks in all tokens in CONTAINED-TOKEN
-with null containing mark to marker pointing to CONTAINING-TOKEN.
-If CONTAINING-TOKEN is empty, the next token number is used."
-  (when (eq wisi--parse-action 'navigate)
-    (let* ((containing-tok (aref wisi-tokens (1- containing-token)))
-          (containing-region (wisi-tok-region containing-tok))
-          (contained-tok (aref wisi-tokens (1- contained-token)))
-          (contained-region (wisi-tok-region contained-tok)))
-
-      (unless containing-region
-       (signal 'wisi-parse-error
-               (wisi-error-msg
-                "wisi-containing-action: containing-region '%s' is empty. 
grammar error; bad action"
-                (wisi-tok-token containing-tok))))
-
-      (unless (or (not contained-region) ;; contained-token is empty
-                 (wisi-get-cache (car containing-region)))
-       (signal 'wisi-parse-error
-               (wisi-error-msg
-                "wisi-containing-action: containing-token '%s' has no cache. 
grammar error; missing action"
-                (wisi-token-text (aref wisi-tokens (1- containing-token))))))
-
-      (when contained-region
-         ;; nil when empty production, may not contain any caches
-         (save-excursion
-           (goto-char (cdr contained-region))
-           (let ((cache (wisi-backward-cache))
-                 (mark (copy-marker (car containing-region) t)))
-             (while cache
-
-               ;; skip blocks that are already marked
-               (while (and (>= (point) (car contained-region))
-                           (markerp (wisi-cache-containing cache)))
-                 (goto-char (wisi-cache-containing cache))
-                 (setq cache (wisi-get-cache (point))))
-
-               (if (or (and (= (car containing-region) (car contained-region))
-                            (<= (point) (car contained-region)))
-                       (< (point) (car contained-region)))
-                   ;; done
-                   (setq cache nil)
-
-                 ;; else set mark, loop
-                 (setf (wisi-cache-containing cache) mark)
-                 (setq cache (wisi-backward-cache)))
-               ))))
-      )))
-
-(defun wisi-elisp-parse--match-token (cache tokens start)
-  "Return t if CACHE has id from TOKENS and is at START or has containing 
equal to START.
-point must be at cache token start.
-TOKENS is a vector [number token_id token_id ...].
-number is ignored."
-  (let ((i 1)
-       (done nil)
-       (result nil)
-       token)
-    (when (or (= start (point))
-             (and (wisi-cache-containing cache)
-                  (= start (wisi-cache-containing cache))))
-      (while (and (not done)
-                 (< i (length tokens)))
-       (setq token (aref tokens i))
-       (if (eq token (wisi-cache-token cache))
-           (setq result t
-                 done t)
-         (setq i (1+ i)))
-       ))
-    result))
-
-(defun wisi-motion-action (token-numbers)
-  ;; Not wisi-elisp-parse--motion-action to match existing grammar files
-  "Set prev/next marks in all tokens given by TOKEN-NUMBERS.
-TOKEN-NUMBERS is a vector with each element one of:
-
-number: the token number; mark that token
-
-vector [number token_id]:
-vector [number token_id token_id ...]:
-   mark all tokens in number nonterminal matching token_id with nil prev/next."
-  (when (eq wisi--parse-action 'navigate)
-    (save-excursion
-      (let (prev-keyword-mark
-           prev-cache
-           token
-           start
-           cache
-           mark
-           (i 0))
-       (while (< i (length token-numbers))
-         (let ((token-number (aref token-numbers i))
-               region)
-           (setq i (1+ i))
-           (cond
-            ((numberp token-number)
-             (setq token (aref wisi-tokens (1- token-number)))
-             (setq region (wisi-tok-region token))
-             (when region
-               (unless start (setq start (car region)))
-               (setq cache (wisi-get-cache (car region)))
-               (unless cache (error "no cache on token %d; add to 
statement-action" token-number))
-               (setq mark (copy-marker (car region) t))
-
-               (if prev-keyword-mark
-                   (progn
-                     (setf (wisi-cache-prev cache) prev-keyword-mark)
-                     (setf (wisi-cache-next prev-cache) mark)
-                     (setq prev-keyword-mark mark)
-                     (setq prev-cache cache))
-
-                 ;; else first token; save as prev
-                 (setq prev-keyword-mark mark)
-                 (setq prev-cache cache))
-               ))
-
-            ((vectorp token-number)
-             ;; token-number may contain 1 or more token_ids
-             ;; the corresponding region may be empty
-             ;; there may not have been a prev keyword
-             (setq region (wisi-tok-region (aref wisi-tokens (1- (aref 
token-number 0)))))
-             (when region ;; not an empty token
-               ;; We must search for all targets at the same time, to
-               ;; get the motion order right.
-               (unless start (setq start (car region)))
-               (goto-char (car region))
-               (setq cache (wisi-get-cache (point)))
-               (unless cache (error "no cache at %d; add to statement-action" 
(car region)))
-               (while (< (point) (cdr region))
-                 (when (wisi-elisp-parse--match-token cache token-number start)
-                   (setq mark (copy-marker (point) t))
-
-                   (if prev-keyword-mark
-                       ;; Don't include this token if prev/next
-                       ;; already set by a lower level statement,
-                       ;; such as a nested if/then/elsif/end if.
-                       (when (and (null (wisi-cache-prev cache))
-                                  (null (wisi-cache-next prev-cache)))
-                         (setf (wisi-cache-prev cache) prev-keyword-mark)
-                         (setf (wisi-cache-next prev-cache) mark)
-                         (setq prev-keyword-mark mark)
-                         (setq prev-cache cache))
-
-                     ;; else first token; save as prev
-                     (setq prev-keyword-mark mark)
-                     (setq prev-cache cache)))
-
-                 (setq cache (wisi-forward-cache))
-                 )))
-
-            (t
-             (error "unexpected token-number %s" token-number))
-            )
-
-           ))
-       ))))
-
-;;;; Face grammar actions
-
-(defun wisi-elisp-parse--face-put-cache (region class)
-  "Put a ’wisi-face’ cache with class CLASS on REGION."
-  (when (> wisi-debug 1)
-    (message "face: put cache %s:%s" region class))
-  (with-silent-modifications
-    (put-text-property
-     (car region)
-     (1+ (car region))
-     'wisi-face
-     (wisi-cache-create
-      :last (- (cdr region) (car region))
-      :class class)
-     )))
-
-(defun wisi-face-mark-action (pairs)
-  ;; Not wisi-elisp-parse--face-mark-action to match existing grammar files
-  "PAIRS is a vector of TOKEN CLASS pairs; mark TOKEN (token number)
-as having face CLASS (prefix or suffix).
-Intended as a grammar action."
-  (when (eq wisi--parse-action 'face)
-    (let ((i 0))
-      (while (< i (length pairs))
-       (let ((region (wisi-tok-region (aref wisi-tokens (1- (aref pairs i)))))
-             (class (aref pairs (setq i (1+ i)))))
-         (setq i (1+ i))
-         (when region
-           ;; region can be null on an optional or virtual token
-           (let ((cache (get-text-property (car region) 'wisi-face)))
-             (if cache
-                 ;; previously marked; extend this cache, delete any others
-                 (progn
-                   (with-silent-modifications
-                     (remove-text-properties (+ (car region) (wisi-cache-last 
cache)) (cdr region) '(wisi-face nil)))
-                   (setf (wisi-cache-class cache) class)
-                   (setf (wisi-cache-last cache) (- (cdr region) (car 
region))))
-
-               ;; else not previously marked
-               (wisi-elisp-parse--face-put-cache region class)))
-           ))
-       ))))
-
-(defun wisi-face-remove-action (tokens)
-  ;; Not wisi-elisp-parse--face-remove-action to match existing grammar files
-  "Remove face caches and faces in TOKENS.
-Intended as a grammar action.
-
-TOKENS is a vector of token numbers."
-  (when (eq wisi--parse-action 'face)
-    (let ((i 0))
-      (while (< i (length tokens))
-       (let* ((number (1- (aref tokens i)))
-              (region (wisi-tok-region (aref wisi-tokens number)))
-              face-cache)
-
-         (setq i (1+ i))
-
-         (when region
-           (let ((pos (car region)))
-             (while (< pos (cdr region))
-               (when (setq face-cache (get-text-property pos 'wisi-face))
-                 (when (> wisi-debug 1)
-                   (message "face: remove face %s" (cons pos (+ pos 
(wisi-cache-last face-cache)))))
-                 (with-silent-modifications
-                   (remove-text-properties
-                    pos (+ pos (wisi-cache-last face-cache))
-                    (list
-                     'wisi-face nil
-                     'font-lock-face nil
-                     'fontified t))))
-               (setq pos (next-single-property-change
-                          (+ pos (or (and face-cache
-                                          (wisi-cache-last face-cache))
-                                     0))
-                          'wisi-face nil (cdr region)))
-               )))
-         )))))
-
-(defun wisi-elisp-parse--face-action-1 (face region)
-  "Apply FACE to REGION."
-  (when region
-    (when (> wisi-debug 1)
-      (message "face: add face %s:%s" region face))
-    (with-silent-modifications
-      (add-text-properties
-       (car region) (cdr region)
-       (list
-       'font-lock-face face
-       'fontified t)))
-    ))
-
-(defun wisi-face-apply-action (triples)
-  ;; Not wisi-elisp-parse--face-apply-action to match existing grammar files
-  "Set face information in `wisi-face' text properties of tokens.
-Intended as a grammar non-terminal action.
-
-TRIPLES is a vector of the form [TOKEN-NUMBER PREFIX-FACE SUFFIX-FACE ...]
-
-In the first ’wisi-face’ cache in each token region, apply
-PREFIX-FACE to class PREFIX, SUFFIX-FACE to class SUFFIX, or
-SUFFIX-FACE to all of the token region if there is no ’wisi-face’
-cache."
-  (when (eq wisi--parse-action 'face)
-    (let (number prefix-face suffix-face (i 0))
-      (while (< i (length triples))
-       (setq number (aref triples i))
-       (setq prefix-face (aref triples (setq i (1+ i))))
-       (setq suffix-face (aref triples (setq i (1+ i))))
-       (cond
-        ((integerp number)
-         (let* ((token-region (wisi-tok-region (aref wisi-tokens (1- number))))
-                (pos (car token-region))
-                (j 0)
-                (some-cache nil)
-                cache)
-           (when token-region
-             ;; region can be null for an optional or virtual token
-             (while (< j 2)
-               (setq cache (get-text-property pos 'wisi-face))
-               (cond
-                ((and (not some-cache)
-                      (null cache))
-                 ;; cache is null when applying a face to a token
-                 ;; directly, without first calling
-                 ;; wisi-face-mark-action. Or when there is a
-                 ;; previously applied face in a lower level token,
-                 ;; such as a numeric literal.
-                 (wisi-elisp-parse--face-action-1 suffix-face token-region))
-
-                ((and cache
-                      (eq 'prefix (wisi-cache-class cache)))
-                 (setq some-cache t)
-                 (wisi-elisp-parse--face-action-1 prefix-face 
(wisi-cache-region cache pos)))
-
-                ((and cache
-                      (eq 'suffix (wisi-cache-class cache)))
-                 (setq some-cache t)
-                 (wisi-elisp-parse--face-action-1 suffix-face 
(wisi-cache-region cache pos)))
-
-                (t
-                 ;; don’t apply a face
-                 nil)
-                )
-
-               (setq j (1+ j))
-               (if suffix-face
-                   (setq pos (next-single-property-change (+ 2 pos) 'wisi-face 
nil (cdr token-region)))
-                 (setq j 2))
-               ))))
-
-        (t
-         ;; catch conversion errors from previous grammar syntax
-         (error "wisi-face-apply-action with non-integer token number"))
-        )
-       (setq i (1+ i))
-       ))))
-
-(defun wisi-face-apply-list-action (triples)
-  ;; Not wisi-elisp-parse--face-apply-list-action to match existing grammar 
files
-  "Similar to ’wisi-face-apply-action’, but applies faces to all
-tokens with a `wisi-face' cache in the wisi-tokens[token-number]
-region, and does not apply a face if there are no such caches."
-  (when (eq wisi--parse-action 'face)
-    (let (number token-region face-region prefix-face suffix-face cache (i 0) 
pos)
-      (while (< i (length triples))
-       (setq number (aref triples i))
-       (setq prefix-face (aref triples (setq i (1+ i))))
-       (setq suffix-face (aref triples (setq i (1+ i))))
-       (cond
-        ((integerp number)
-         (setq token-region (wisi-tok-region (aref wisi-tokens (1- number))))
-         (when token-region
-           ;; region can be null for an optional token
-           (setq pos (car token-region))
-           (while (and pos
-                       (< pos (cdr token-region)))
-             (setq cache (get-text-property pos 'wisi-face))
-             (setq face-region (wisi-cache-region cache pos))
-             (cond
-              ((or (null (wisi-cache-class cache))
-                   (eq 'prefix (wisi-cache-class cache)))
-               (wisi-elisp-parse--face-action-1 prefix-face face-region))
-              ((eq 'suffix (wisi-cache-class cache))
-               (wisi-elisp-parse--face-action-1 suffix-face face-region))
-
-              (t
-               (error "wisi-face-apply-list-action: face cache class is not 
prefix or suffix")))
-
-             (setq pos (next-single-property-change (1+ pos) 'wisi-face nil 
(cdr token-region)))
-             )))
-        (t
-         ;; catch conversion errors from previous grammar syntax
-         (error "wisi-face-apply-list-action with non-integer token number"))
-        )
-       (setq i (1+ i))
-       ))))
-
-;;;; indent grammar actions
-
-(defvar wisi-elisp-parse-indent-hanging-function nil
-  "Language-specific implementation of `wisi-hanging', `wisi-hanging%'.
-A function taking args TOK DELTA1 DELTA2 OPTION NO-ACCUMULATE,
-and returning an indent.
-TOK is a `wisi-tok' struct for the token being indented.
-DELTA1, DELTA2 are the indents of the first and following lines
-within the nonterminal.  OPTION is non-nil if action is `wisi-hanging%'.
-point is at start of TOK, and may be moved.")
-(make-variable-buffer-local 'wisi-elisp-parse-indent-hanging-function)
-
-(defvar wisi-token-index nil
-  ;; Not wisi-elisp-parse--token-index for backward compatibility
-  "Index of current token in `wisi-tokens'.
-Let-bound in `wisi-indent-action', for grammar actions.")
-
-(defvar wisi-indent-comment nil
-  ;; Not wisi-elisp-parse--indent-comment for backward compatibility
-  "Non-nil if computing indent for comment.
-Let-bound in `wisi-indent-action', for grammar actions.")
-
-(defun wisi-elisp-parse--apply-int (i delta)
-  "Add DELTA (an integer) to the indent at index I."
-  (let ((indent (aref wisi-elisp-parse--indent i))) ;; reference if list
-
-    (cond
-     ((null indent)
-      (aset wisi-elisp-parse--indent i delta))
-
-     ((integerp indent)
-      (aset wisi-elisp-parse--indent i (+ delta indent)))
-
-     ((listp indent)
-      (cond
-       ((eq 'anchor (car indent))
-       (cond
-        ((null (nth 2 indent))
-         (setf (nth 2 indent) delta))
-
-        ((integerp (nth 2 indent))
-         (setf (nth 2 indent) (+ delta (nth 2 indent))))
-
-        ;; else anchored; not affected by this delta
-        ))
-
-       ((eq 'anchored (car indent))
-       ;; not affected by this delta
-       )))
-
-     (t
-      (error "wisi-elisp-parse--apply-int: invalid form : %s" indent))
-     )))
-
-(defun wisi-elisp-parse--apply-anchored (delta i)
-  "Apply DELTA (an anchored indent) to indent I."
-  ;; delta is from wisi-anchored; ('anchored 1 delta no-accumulate)
-  (let ((indent (aref wisi-elisp-parse--indent i))
-       (accumulate (not (nth 3 delta))))
-
-    (when delta
-      (cond
-       ((null indent)
-       (aset wisi-elisp-parse--indent i (seq-take delta 3)))
-
-       ((integerp indent)
-       (when accumulate
-         (let ((temp (seq-take delta 3)))
-           (setf (nth 2 temp) (+ indent (nth 2 temp)))
-           (aset wisi-elisp-parse--indent i temp))))
-
-       ((and (listp indent)
-            (eq 'anchor (car indent))
-            (or (null (nth 2 indent))
-                (integerp (nth 2 indent))))
-       (when (or (null (nth 2 indent))
-                 accumulate)
-         (let ((temp (seq-take delta 3)))
-           (cond
-            ((null (nth 2 indent))
-             (setf (nth 2 indent) temp))
-
-            (t
-             (setf (nth 2 temp) (+ (nth 2 indent) (nth 2 temp)))
-             (setf (nth 2 indent) temp))))
-         ))
-       ))))
-
-(defun wisi-elisp-parse--indent-null-p (indent)
-  (or (null indent)
-      (and (eq 'anchor (nth 0 indent))
-          (null (nth 2 indent)))))
-
-(defun wisi-elisp-parse--indent-token-1 (line end delta)
-  "Apply indent DELTA to all lines from LINE (a line number) thru END (a 
buffer position)."
-  (let ((i (1- line));; index to wisi-elisp-lexer-line-begin, 
wisi-elisp-parse--indent
-       (paren-first (when (and (listp delta)
-                               (eq 'hanging (car delta)))
-                      (nth 2 delta))))
-
-    (while (<= (aref (wisi-elisp-lexer-line-begin wisi--lexer) i) end)
-      (if
-         (and ;; no check for called from wisi--indent-comment;
-              ;; comments within tokens are indented by
-              ;; wisi--indent-token
-              wisi-indent-comment-col-0
-              (= 11 (syntax-class (syntax-after (aref 
(wisi-elisp-lexer-line-begin wisi--lexer) i)))))
-         (wisi-elisp-parse--apply-int i 0)
-       (cond
-        ((integerp delta)
-         (wisi-elisp-parse--apply-int i delta))
-
-        ((listp delta)
-         (cond
-          ((eq 'anchored (car delta))
-           (wisi-elisp-parse--apply-anchored delta i))
-
-          ((eq 'hanging (car delta))
-           ;; from wisi-hanging; delta is ('hanging first-line nest delta1 
delta2 no-accumulate)
-           ;; delta1, delta2 may be anchored
-           (when (or (not (nth 5 delta))
-                     (wisi-elisp-parse--indent-null-p (aref 
wisi-elisp-parse--indent i)))
-             (if (= i (1- (nth 1 delta)))
-                 ;; apply delta1
-                 (let ((delta1 (nth 3 delta)))
-                   (cond
-                    ((integerp delta1)
-                     (wisi-elisp-parse--apply-int i delta1))
-
-                    (t ;; anchored
-                     (wisi-elisp-parse--apply-anchored delta1 i))
-                    ))
-
-               ;; don't apply hanging indent in nested parens.
-               ;; test/ada_mode-parens.adb
-               ;; No_Conditional_Set : constant Ada.Strings.Maps.Character_Set 
:=
-               ;;   Ada.Strings.Maps."or"
-               ;;     (Ada.Strings.Maps.To_Set (' '),
-               (when (= paren-first
-                        (nth 0 (save-excursion (syntax-ppss (aref 
(wisi-elisp-lexer-line-begin wisi--lexer) i)))))
-                 (let ((delta2 (nth 4 delta)))
-                   (cond
-                    ((integerp delta2)
-                     (wisi-elisp-parse--apply-int i delta2))
-
-                    (t ;; anchored
-                     (wisi-elisp-parse--apply-anchored delta2 i))
-                    )))
-               )))
-
-          (t
-           (error "wisi-elisp-parse--indent-token-1: invalid delta: %s" delta))
-          )) ;; listp delta
-
-        (t
-         (error "wisi-elisp-parse--indent-token-1: invalid delta: %s" delta))
-        ))
-      (setq i (1+ i))
-      )))
-
-(defun wisi-elisp-parse--indent-token (tok token-delta)
-  "Add TOKEN-DELTA to all indents in TOK region,"
-  (let ((line (if (wisi-tok-nonterminal tok)
-                 (wisi-tok-first tok)
-               (when (wisi-tok-first tok) (wisi-tok-line tok))))
-       (end (cdr (wisi-tok-region tok))))
-    (when (and line end token-delta)
-      (wisi-elisp-parse--indent-token-1 line end token-delta))))
-
-(defun wisi-elisp-parse--indent-comment (tok comment-delta)
-  "Add COMMENT-DELTA to all indents in comment region following TOK."
-  (let ((line (wisi-tok-comment-line tok))
-       (end (wisi-tok-comment-end tok)))
-    (when (and line end comment-delta)
-      (wisi-elisp-parse--indent-token-1 line end comment-delta))))
-
-(defun wisi-elisp-parse--anchored-1 (tok offset &optional no-accumulate)
-  "Return offset of TOK relative to current indentation + OFFSET.
-For use in grammar indent actions."
-  (when (wisi-tok-region tok)
-    ;; region can be nil when token is inserted by error recovery
-    (let ((pos (car (wisi-tok-region tok)))
-         delta)
-
-      (goto-char pos)
-      (setq delta (+ offset (- (current-column) (current-indentation))))
-      (wisi-elisp-parse--anchored-2
-       (wisi-tok-line tok) ;; anchor-line
-       (if wisi-indent-comment
-          (wisi-tok-comment-end (aref wisi-tokens wisi-token-index))
-        (cdr (wisi-tok-region (aref wisi-tokens wisi-token-index))));; end
-       delta
-       no-accumulate)
-      )))
-
-(defun wisi-elisp-parse--max-anchor (begin-line end)
-  (let ((i (1- begin-line))
-       (max-i (length (wisi-elisp-lexer-line-begin wisi--lexer)))
-       (result 0))
-    (while (and (< i max-i)
-               (<= (aref (wisi-elisp-lexer-line-begin wisi--lexer) i) end))
-      (let ((indent (aref wisi-elisp-parse--indent i)))
-       (when (and indent (listp indent))
-         (cond
-          ((eq 'anchor (car indent))
-           (setq result (max result (car (nth 1 indent))))
-           (when (and (nth 2 indent) (listp (nth 2 indent)))
-             (setq result (max result (nth 1 (nth 2 indent))))
-             ))
-          (t ;; anchored
-           (setq result (max result (nth 1 indent))))
-          )))
-      (setq i (1+ i)))
-    result
-    ))
-
-(defun wisi-elisp-parse--anchored-2 (anchor-line end delta no-accumulate)
-  "Set ANCHOR-LINE as anchor, increment anchors thru END, return anchored 
delta."
-  ;; Typically, we use anchored to indent relative to a token buried in a line:
-  ;;
-  ;; test/ada_mode-parens.adb
-  ;; Local_2 : Integer := (1 + 2 +
-  ;;                         3);
-  ;; line starting with '3' is anchored to '('
-  ;;
-  ;; If the anchor is a nonterminal, and the first token in the anchor
-  ;; is also first on a line, we don't need anchored to compute the
-  ;; delta:
-  ;;
-  ;; test/ada_mode-parens.adb
-  ;; Local_5 : Integer :=
-  ;;   (1 + 2 +
-  ;;      3);
-  ;; delta for line starting with '3' can just be '3'.
-  ;;
-  ;; However, in some places we need anchored to prevent later
-  ;; deltas from accumulating:
-  ;;
-  ;; test/ada_mode-parens.adb
-  ;; No_Conditional_Set : constant Ada.Strings.Maps.Character_Set :=
-  ;;   Ada.Strings.Maps."or"
-  ;;     (Ada.Strings.Maps.To_Set (' '),
-  ;;
-  ;; here the function call actual parameter part is indented first
-  ;; by 'name' and later by 'expression'; we use anchored to keep the
-  ;; 'name' delta and ignore the later delta.
-  ;;
-  ;; So we apply anchored whether the anchor token is first or not.
-
-  (let* ((i (1- anchor-line))
-        (indent (aref wisi-elisp-parse--indent i)) ;; reference if list
-        (anchor-id (1+ (wisi-elisp-parse--max-anchor anchor-line end))))
-
-    ;; Set anchor
-    (cond
-     ((or
-       (null indent)
-       (integerp indent))
-      (aset wisi-elisp-parse--indent i (list 'anchor (list anchor-id) indent)))
-
-     ((and (listp indent)
-          (eq 'anchor (car indent)))
-      (push anchor-id (nth 1 indent)))
-
-     ((and (listp indent)
-          (eq 'anchored (car indent)))
-      (aset wisi-elisp-parse--indent i (list 'anchor (list anchor-id) 
(copy-sequence indent))))
-
-     (t
-      (error "wisi-anchored-delta: invalid form in indent: %s" indent)))
-
-    (list 'anchored anchor-id delta no-accumulate)
-    ))
-
-(defun wisi-anchored (token-number offset &optional no-accumulate)
-  ;; Not wisi-elisp-parse--anchored to match existing grammar files
-  "Return offset of token TOKEN-NUMBER in `wisi-tokens'.relative to current 
indentation + OFFSET.
-For use in grammar indent actions."
-  (wisi-elisp-parse--anchored-1 (aref wisi-tokens (1- token-number)) offset 
no-accumulate))
-
-(defun wisi-anchored* (token-number offset)
-  ;; Not wisi-elisp-parse--anchored* to match existing grammar files
-  "If TOKEN-NUMBER token in `wisi-tokens' is first on a line,
-call ’wisi-anchored OFFSET’. Otherwise return 0.
-For use in grammar indent actions."
-  (if (wisi-tok-first (aref wisi-tokens (1- token-number)))
-      (wisi-anchored token-number offset)
-    0))
-
-(defun wisi-anchored*- (token-number offset)
-  ;; Not wisi-elisp-parse--anchored*- to match existing grammar files
-  "If existing indent is zero, and TOKEN-NUMBER token in `wisi-tokens' is 
first on a line,
-call ’wisi-anchored OFFSET’. Otherwise return 0.
-For use in grammar indent actions."
-  (if (wisi-tok-first (aref wisi-tokens (1- token-number)))
-      (wisi-anchored token-number offset t)
-    0))
-
-(defun wisi-elisp-parse--paren-in-anchor-line (anchor-tok offset)
-  "If there is an opening paren containing ANCHOR-TOK in the same line as 
ANCHOR-TOK,
-return OFFSET plus the delta from the line indent to the paren
-position. Otherwise return OFFSET."
-  (let* ((tok-syntax (syntax-ppss (car (wisi-tok-region anchor-tok))))
-        (paren-pos (nth 1 tok-syntax))
-        (anchor-line (wisi-tok-line anchor-tok)))
-
-    (when (and paren-pos ;; in paren
-             (< paren-pos (aref (wisi-elisp-lexer-line-begin wisi--lexer) (1- 
anchor-line))))
-      ;; paren not in anchor line
-      (setq paren-pos nil))
-
-    (if paren-pos
-       (progn
-         (goto-char paren-pos)
-         (+ 1 (- (current-column) (current-indentation)) offset))
-      offset)
-    ))
-
-(defun wisi-anchored% (token-number offset &optional no-accumulate)
-  ;; Not wisi-elisp-parse--anchored% to match existing grammar files
-  "Return either an anchor for the current token at OFFSET from an enclosing 
paren on
-the line containing TOKEN-NUMBER, or OFFSET.
-For use in grammar indent actions."
-  (let* ((indent-tok (aref wisi-tokens wisi-token-index))
-        ;; indent-tok is a nonterminal; this function makes no sense for 
terminals
-        (anchor-tok (aref wisi-tokens (1- token-number))))
-
-    (wisi-elisp-parse--anchored-2
-     (wisi-tok-line anchor-tok)
-
-     (if wisi-indent-comment
-        (wisi-tok-comment-end indent-tok)
-       (cdr (wisi-tok-region indent-tok))) ;; end
-
-     (wisi-elisp-parse--paren-in-anchor-line anchor-tok offset)
-     no-accumulate)
-    ))
-
-(defun wisi-anchored%- (token-number offset)
-  ;; Not wisi-elisp-parse--anchored%- to match existing grammar files
-  "If existing indent is zero, anchor the current token at OFFSET
-from the first token on the line containing TOKEN-NUMBER in `wisi-tokens'.
-Return the delta.
-For use in grammar indent actions."
-  (wisi-anchored% token-number offset t))
-
-(defun wisi-elisp-parse--hanging-1 (delta1 delta2 option no-accumulate)
-  "If OPTION is nil, implement `wisi-hanging'; otherwise `wisi-hanging%'."
-  (let ((tok (aref wisi-tokens wisi-token-index)))
-    ;; tok is a nonterminal; this function makes no sense for terminals
-    ;; syntax-ppss moves point to start of tok
-
-    (cond
-     ((functionp wisi-elisp-parse-indent-hanging-function)
-      (funcall wisi-elisp-parse-indent-hanging-function tok delta1 delta2 
option no-accumulate))
-
-     (t
-      (let ((tok-syntax (syntax-ppss (car (wisi-tok-region tok))))
-           (first-tok-first-on-line
-            ;; first token in tok is first on line
-            (and (numberp (wisi-tok-first tok))
-                 (= (wisi-tok-line tok) (wisi-tok-first tok)))))
-       (list 'hanging
-             (wisi-tok-line tok) ;; first line of token
-             (nth 0 tok-syntax) ;; paren nest level at tok
-             delta1
-             (if (or (not option) first-tok-first-on-line)
-                 delta2
-               delta1)
-             no-accumulate))
-      ))
-    ))
-
-(defun wisi-hanging (delta1 delta2)
-  ;; Not wisi-elisp-parse--hanging to match existing grammar files
-  "Use DETLA1 for first line, DELTA2 for following lines.
-For use in grammar indent actions."
-  (wisi-elisp-parse--hanging-1 delta1 delta2 nil nil))
-
-(defun wisi-hanging% (delta1 delta2)
-  ;; Not wisi-elisp-parse--hanging% to match existing grammar files
-  "If first token is first in line, use DETLA1 for first line, DELTA2 for 
following lines.
-Otherwise use DELTA1 for all lines.
-For use in grammar indent actions."
-  (wisi-elisp-parse--hanging-1 delta1 delta2 t nil))
-
-(defun wisi-hanging%- (delta1 delta2)
-  ;; Not wisi-elisp-parse--hanging%- to match existing grammar files
-  "If existing indent is non-zero, do nothing.
-Else if first token is first in line, use DETLA1 for first line,
-DELTA2 for following lines.  Otherwise use DELTA1 for all lines.
-For use in grammar indent actions."
-  (wisi-elisp-parse--hanging-1 delta1 delta2 t t))
-
-(defun wisi-elisp-parse--indent-offset (token offset)
-  "Return offset from beginning of first token on line containing TOKEN,
-   to beginning of TOKEN, plus OFFSET."
-  (save-excursion
-    (goto-char (aref (wisi-elisp-lexer-line-begin wisi--lexer) (1- 
(wisi-tok-line token))))
-    (back-to-indentation)
-    (+ offset (- (car (wisi-tok-region token)) (point)))
-    ))
-
-(defun wisi-elisp-parse--indent-compute-delta (delta tok)
-  "Return evaluation of DELTA."
-  (cond
-   ((integerp delta)
-    delta)
-
-   ((symbolp delta)
-    (symbol-value delta))
-
-   ((vectorp delta)
-    ;; [token comment]
-    ;; if wisi-indent-comment, we are indenting the comments of the
-    ;; previous token; they should align with the 'token' delta.
-    (wisi-elisp-parse--indent-compute-delta (aref delta 0) tok))
-
-   (t ;; form
-    (cond
-     ((eq 'anchored (car delta))
-      delta)
-
-     (t
-      (save-excursion
-       (goto-char (car (wisi-tok-region tok)))
-       (eval delta)))))
-   ))
-
-(defun wisi-indent-action (deltas)
-  ;; Not wisi-elisp-parse--indent-action to match existing grammar files
-  "Accumulate `wisi--indents' from DELTAS.
-DELTAS is a vector; each element can be:
-- an integer
-- a symbol
-- a lisp form
-- a vector.
-
-The first three are evaluated to give an integer delta. A vector must
-have two elements, giving the code and following comment
-deltas. Otherwise the comment delta is the following delta in
-DELTAS."
-  (when (eq wisi--parse-action 'indent)
-    (dotimes (wisi-token-index (length wisi-tokens))
-      (let* ((tok (aref wisi-tokens wisi-token-index))
-            (token-delta (aref deltas wisi-token-index))
-            (comment-delta
-             (cond
-              ((vectorp token-delta)
-               (aref token-delta 1))
-
-              ((< wisi-token-index (1- (length wisi-tokens)))
-               (aref deltas (1+ wisi-token-index)))
-              )))
-       (when (wisi-tok-region tok)
-         ;; region is null when optional nonterminal is empty
-         (let ((wisi-indent-comment nil))
-           (setq token-delta
-                 (when (and token-delta
-                            (wisi-tok-first tok))
-                   (wisi-elisp-parse--indent-compute-delta token-delta tok)))
-
-           (when token-delta
-             (wisi-elisp-parse--indent-token tok token-delta))
-
-           (setq wisi-indent-comment t)
-           (setq comment-delta
-                 (when (and comment-delta
-                            (wisi-tok-comment-line tok))
-                   (wisi-elisp-parse--indent-compute-delta comment-delta tok)))
-
-           (when comment-delta
-             (wisi-elisp-parse--indent-comment tok comment-delta))
-           )
-         )))))
-
-(defun wisi-indent-action* (n deltas)
-  ;; Not wisi-elisp-parse--indent-action* to match existing grammar files
-  "If any of the first N tokens in `wisi-tokens' is first on a line,
-call `wisi-indent-action' with DELTAS.  Otherwise do nothing."
-  (when (eq wisi--parse-action 'indent)
-    (let ((done nil)
-         (i 0)
-         tok)
-      (while (and (not done)
-                 (< i n))
-       (setq tok (aref wisi-tokens i))
-       (setq i (1+ i))
-       (when (and (wisi-tok-region tok)
-                  (wisi-tok-first tok))
-         (setq done t)
-         (wisi-indent-action deltas))
-       ))))
-
-;;;; non-grammar indent functions
-
-(defconst wisi-elisp-parse--max-anchor-depth 20) ;; IMRPOVEME: can compute in 
actions
-
-(defun wisi-elisp-parse--indent-leading-comments ()
-  "Set `wisi-elisp-parse--indent to 0 for comment lines before first token in 
buffer.
-Leave point at first token (or eob)."
-  (save-excursion
-    (goto-char (point-min))
-    (forward-comment (point-max))
-    (let ((end (point))
-         (i 0)
-         (max-i (length wisi-elisp-parse--indent)))
-      (while (and (< i max-i)
-                 (< (aref (wisi-elisp-lexer-line-begin wisi--lexer) i) end))
-       (aset wisi-elisp-parse--indent i 0)
-       (setq i (1+ i)))
-      )))
-
-(defun wisi-elisp-parse--resolve-anchors ()
-  (let ((anchor-indent (make-vector wisi-elisp-parse--max-anchor-depth 0))
-       pos)
-
-    (dotimes (i (length wisi-elisp-parse--indent))
-      (let ((indent (aref wisi-elisp-parse--indent i)))
-
-       (cond
-        ((or (null indent)
-             (integerp indent)))
-
-        ((listp indent)
-         (let ((anchor-ids (nth 1 indent))
-               (indent2 (nth 2 indent)))
-           (cond
-            ((eq 'anchor (car indent))
-             (cond
-              ((null indent2))
-
-              ((integerp indent2)
-               (dotimes (i (length anchor-ids))
-                 (aset anchor-indent (nth i anchor-ids) indent2))
-               (setq indent indent2))
-
-              ((listp indent2) ;; 'anchored
-               (setq indent (+ (aref anchor-indent (nth 1 indent2)) (nth 2 
indent2)))
-
-               (dotimes (i (length anchor-ids))
-                 (aset anchor-indent (nth i anchor-ids) indent)))
-
-              (t
-               (error "wisi-indent-region: invalid form in wisi-ind-indent %s" 
indent))
-              ));; 'anchor
-
-            ((eq 'anchored (car indent))
-             (setq indent (+ (aref anchor-indent (nth 1 indent)) indent2)))
-
-            (t
-             (error "wisi-indent-region: invalid form in wisi-ind-indent %s" 
indent))
-            )));; listp indent
-
-        (t
-         (error "wisi-indent-region: invalid form in wisi-ind-indent %s" 
indent))
-        );; cond indent
-
-       (when (> i 0)
-         (setq pos (aref (wisi-elisp-lexer-line-begin wisi--lexer) i))
-         (with-silent-modifications
-           (put-text-property (1- pos) pos 'wisi-indent indent)))
-       )) ;; dotimes lines
-
-    ))
-
-(provide 'wisi-elisp-parse)
-;;; wisi-elisp-parse.el ends here
diff --git a/wisi-parse-common.el b/wisi-parse-common.el
index 895e899..dd9d862 100644
--- a/wisi-parse-common.el
+++ b/wisi-parse-common.el
@@ -1,374 +1,404 @@
-;;; wisi-parse-common.el --- declarations used by wisi-parse.el, 
wisi-ada-parse.el, and wisi.el
-;;
-;; Copyright (C) 2014, 2015, 2017 - 2019  Free Software Foundation, Inc.
-;;
-;; Author: Stephen Leake <stephen_leake@member.fsf.org>
-;;
-;; This file is part of GNU Emacs.
-;;
-;; GNU Emacs 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 3 of the License, or
-;; (at your option) any later version.
-;;
-;; GNU Emacs 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
-
-;;; Code:
-
-(cl-defstruct (wisi--lexer-error)
-  pos ;; position (integer) in buffer where error was detected.
-  message  ;; string error message
-  inserted ;; char inserted after pos.
-  )
-
-(cl-defstruct (wisi--parse-error-repair)
-  pos ;; position (integer) in buffer where insert/delete is done.
-  inserted ;; list of token IDs that were inserted before pos
-  deleted  ;; list of token IDs that were deleted after pos
-  )
-
-(cl-defstruct (wisi--parse-error)
-  ;; Includes information derived from compiler error recovery to edit
-  ;; text to fix one error. Used by ’wisi-repair-error’ to edit buffer.
-  pos      ;; position (integer or marker) in buffer where error was detected.
-  message  ;; string error message
-  repair   ;; list of wisi--parse-error-repair.
-  )
-
-(cl-defstruct wisi-parser
-  ;; Separate lists for lexer and parse errors, because lexer errors
-  ;; must be repaired first, before parse errors can be repaired. And
-  ;; they have different structures.
-  lexer-errors
-  ;; list of wisi--lexer-errors from last parse.  Can be more than one if
-  ;; lexer supports error recovery.
-  parse-errors
-  ;; List of wisi--parse-errors from last parse. Can be more than one if
-  ;; parser supports error recovery.
-)
-
-(cl-defgeneric wisi-parse-format-language-options ((parser wisi-parser))
-  "Return a string to be sent to the parser, containing settings
-for the language-specific parser options."
-  ;; not needed for the elisp parser, which can see the options directly.
-  )
-
-(cl-defgeneric wisi-parse-expand-region ((parser wisi-parser) begin end)
-  "Return a cons SEND-BEGIN . SEND-END that is an expansion of
-region BEGIN END that starts and ends at points the parser can
-handle gracefully."
-  (cons begin end))
-
-(defun wisi-search-backward-skip (regexp skip-p)
-  "Search backward for REGEXP. If SKIP-P returns non-nil, search again.
-SKIP-P is a function taking no parameters.
-Return nil if no match found before bob."
-  (let ((maybe-found-p (search-backward-regexp regexp nil t)))
-    (while (and maybe-found-p
-               (funcall skip-p)
-               (setq maybe-found-p (search-backward-regexp regexp nil t))))
-    maybe-found-p))
-
-(defun wisi-search-forward-skip (regexp skip-p)
-  "Search forward for REGEXP. If SKIP-P returns non-nil, search again.
-SKIP-P is a function taking no parameters.
-Return nil if no match found before eob."
-  (let ((maybe-found-p (search-forward-regexp regexp nil t)))
-    (while (and maybe-found-p
-               (funcall skip-p)
-               (setq maybe-found-p (search-forward-regexp regexp nil t))))
-    maybe-found-p))
-
-(defun wisi-show-expanded-region ()
-  "For debugging. Expand currently selected region."
-  (interactive)
-  (let ((region (wisi-parse-expand-region wisi--parser (region-beginning) 
(region-end))))
-    (message "pre (%d . %d) post %s" (region-beginning) (region-end) region)
-    (set-mark (car region))
-    (goto-char (cdr region))
-    ))
-
-(cl-defgeneric wisi-parse-adjust-indent ((parser wisi-parser) indent _repair)
-  "Adjust INDENT for REPAIR (a wisi--parse-error-repair struct). Return new 
indent."
-  indent)
-
-(cl-defgeneric wisi-parse-current ((parser wisi-parser) begin send-end 
parse-end)
-  "Parse current buffer starting at BEGIN, continuing at least thru PARSE-END.
-If using an external parser, send it BEGIN thru SEND-END.")
-
-(cl-defgeneric wisi-parse-kill ((parser wisi-parser))
-  "Kill any external process associated with parser.")
-
-(cl-defgeneric wisi-parse-find-token ((parser wisi-parser) token-symbol)
-  "Find token with TOKEN-SYMBOL on current parser stack, return token struct.
-For use in grammar actions.")
-
-(cl-defgeneric wisi-parse-stack-peek ((parser wisi-parser) n)
-  "Return the Nth token on the parse stack.
-For use in grammar actions.")
-
-(cl-defstruct
-  (wisi-cache
-   (:constructor wisi-cache-create)
-   (:copier nil))
-  nonterm;; nonterminal from parse
-
-  token
-  ;; terminal symbol from wisi-keyword-table or
-  ;; wisi-punctuation-table, or lower-level nonterminal from parse
-
-  last ;; pos of last char in token, relative to first (0 indexed)
-
-  class ;; one of wisi-class-list
-
-  containing
-  ;; Marker at the start of the containing statement for this token.
-  ;; nil for outermost containing.
-
-  prev ;; marker at previous motion token in statement; nil if none
-  next ;; marker at next motion token in statement; nil if none
-  end  ;; marker at token at end of current statement
-  )
-
-(defun wisi-get-cache (pos)
-  "Return `wisi-cache' struct from the `wisi-cache' text property at POS."
-  (get-text-property pos 'wisi-cache))
-
-(defun wisi-backward-cache ()
-  "Move point backward to the beginning of the first token preceding point 
that has a cache.
-Returns cache, or nil if at beginning of buffer."
-  ;; If point is not near cache, p-s-p-c will return pos just after
-  ;; cache, so 1- is the beginning of cache.
-  ;;
-  ;; If point is just after end of cache, p-s-p-c will return pos at
-  ;; start of cache.
-  ;;
-  ;; So we test for the property before subtracting 1.
-  (let ((pos (previous-single-property-change (point) 'wisi-cache))
-       cache)
-    (cond
-     ((null pos)
-      (goto-char (point-min))
-      nil)
-
-     ((setq cache (get-text-property pos 'wisi-cache))
-      (goto-char pos)
-      cache)
-
-     (t
-      (setq pos (1- pos))
-      (setq cache (get-text-property pos 'wisi-cache))
-      (goto-char pos)
-      cache)
-     )))
-
-(defun wisi-forward-cache ()
-  "Move point forward to the beginning of the first token after point that has 
a cache.
-Returns cache, or nil if at end of buffer."
-  (let (cache pos)
-    (when (get-text-property (point) 'wisi-cache)
-      ;; on a cache; get past it
-      (goto-char (1+ (point))))
-
-    (setq cache (get-text-property (point) 'wisi-cache))
-    (if cache
-       nil
-
-      (setq pos (next-single-property-change (point) 'wisi-cache))
-      (if pos
-         (progn
-           (goto-char pos)
-           (setq cache (get-text-property pos 'wisi-cache)))
-       ;; at eob
-       (goto-char (point-max))
-       (setq cache nil))
-      )
-    cache
-    ))
-
-(defun wisi-cache-region (cache &optional start)
-  "Return region designated by START (default point) to cache last."
-  (unless start (setq start (point)))
-  (cons start (+ start (wisi-cache-last cache))))
-
-(defvar wisi-debug 0
-  "wisi debug mode:
-0 : normal - ignore parse errors, for indenting new code
-1 : report parse errors (for running tests)
-2 : show parse states, position point at parse errors
-3 : also show top 10 items of parser stack.")
-
-;; The following parameters are easily changeable for debugging.
-(defvar wisi-action-disable nil
-  "If non-nil, disable all elisp actions during parsing.
-Allows timing parse separate from actions.")
-
-(defvar-local wisi-trace-mckenzie 0
-  "McKenzie trace level; 0 for none")
-
-(defvar-local wisi-trace-action 0
-  "Parse action trace level; 0 for none")
-
-(defvar-local wisi-mckenzie-disable nil
-  "If non-nil, disable McKenzie error recovery. Otherwise, use parser 
default.")
-
-(defcustom wisi-mckenzie-task-count nil
-  "If integer, sets McKenzie error recovery task count.
-Higher value (up to system processor limit) runs error recovery
-faster, but may encounter race conditions.  Using only one task
-makes error recovery repeatable; useful for tests.  If nil, uses
-value from grammar file."
-  :type 'integer
-  :group 'wisi
-  :safe 'integerp)
-(make-variable-buffer-local 'wisi-mckenzie-task-count)
-
-(defcustom wisi-mckenzie-check-limit nil
-  "If integer, sets McKenzie error recovery algorithm token check limit.
-This sets the number of tokens past the error point that must be
-parsed successfully for a solution to be deemed successful.
-Higher value gives better solutions, but may fail if there are
-two errors close together.  If nil, uses value from grammar
-file."
-  :type 'integer
-  :group 'wisi
-  :safe 'integerp)
-(make-variable-buffer-local 'wisi-mckenzie-check-limit)
-
-(defcustom wisi-mckenzie-enqueue-limit nil
-  "If integer, sets McKenzie error recovery algorithm enqueue limit.
-This sets the maximum number of solutions that will be considered.
-Higher value has more recover power, but will be slower to fail.
-If nil, uses value from grammar file."
-  :type 'integer
-  :group 'wisi
-  :safe 'integerp)
-(make-variable-buffer-local 'wisi-mckenzie-enqueue-limit)
-
-(defcustom wisi-parse-max-parallel 15
-  "Maximum number of parallel parsers during regular parsing.
-Parallel parsers are used to resolve redundancy in the grammar.
-If a file needs more than this, it's probably an indication that
-the grammar is excessively redundant."
-  :type 'integer
-  :group 'wisi
-  :safe 'integerp)
-
-(defvar wisi-parse-max-stack-size 500
-  "Maximum parse stack size.
-Larger stack size allows more deeply nested constructs.")
-;; end of easily changeable parameters
-
-(defvar wisi--parse-action nil
-  ;; not buffer-local; only let-bound in wisi-indent-region, 
wisi-validate-cache
-  "Reason current parse is begin run; one of
-{indent, face, navigate}.")
-
-(defvar-local wisi-indent-comment-col-0 nil
-  "If non-nil, comments currently starting in column 0 are left in column 0.
-Otherwise, they are indented with previous comments or code.
-Normally set from a language-specific option.")
-
-(defvar-local wisi-end-caches nil
-  "List of buffer positions of caches in current statement that need 
wisi-cache-end set.")
-
-(defconst wisi-eoi-term 'Wisi_EOI
-  ;; must match FastToken wisi-output_elisp.adb EOI_Name, which must
-  ;; be part of a valid Ada identifer.
-  "End Of Input token.")
-
-(defconst wisi-class-list
-  [motion ;; motion-action
-   statement-end
-   statement-override
-   statement-start
-   misc ;; other stuff
-   ]
-  "array of valid token classes; checked in wisi-statement-action, used in 
wisi-process-parse.")
-
-(defun wisi-error-msg (message &rest args)
-  (let ((line (line-number-at-pos))
-       (col (- (point) (line-beginning-position))))
-    (format
-     "%s:%d:%d: %s"
-       (buffer-name) ;; buffer-file-name is sometimes nil here!?
-       line col
-       (apply 'format message args))))
-
-(defvar wisi-parse-error nil)
-(put 'wisi-parse-error
-     'error-conditions
-     '(error wisi-parse-error))
-(put 'wisi-parse-error
-     'error-message
-     "wisi parse error")
-
-(cl-defstruct wisi-tok
-  token  ;; symbol from a token table ;; IMPROVEME: rename to ’id’?
-  region ;; cons giving buffer region containing token text
-
-  nonterminal ;; t if a nonterminal
-
-  line ;; Line number at start of token. Nil for empty nonterminals
-
-  first
-  ;; For terminals, t if token is the first token on a line.
-  ;;
-  ;; For nonterminals, line number of first contained line (not
-  ;; including trailing comments) that needs indenting; it is a
-  ;; comment, or begins with a contained token.
-  ;;
-  ;; Otherwise nil.
-
-  ;; The following are non-nil if token (terminal or non-terminal) is
-  ;; followed by blank or comment lines
-  comment-line ;; first blank or comment line following token
-  comment-end ;; position at end of blank or comment lines
-  )
-
-(defun wisi-token-text (token)
-  "Return buffer text from token range."
-  (let ((region (wisi-tok-region token)))
-    (and region
-       (buffer-substring-no-properties (car region) (cdr region)))))
-
-(defun wisi-and-regions (left right)
-  "Return region enclosing both LEFT and RIGHT."
-  (if left
-      (if right
-         (cons (min (car left) (car right))
-               (max (cdr left) (cdr right)))
-       left)
-    right))
-
-(defun wisi--set-line-begin (line-count)
-  "Return a vector of line-beginning positions, with length LINE-COUNT."
-  (let ((result (make-vector line-count 0)))
-    (save-excursion
-      (goto-char (point-min))
-
-      (dotimes (i line-count)
-       (aset result i (point))
-       (forward-line 1)))
-    result))
-
-;;;; debugging
-(defun wisi-tok-debug-image (tok)
-  "Return id and region from TOK, as string."
-  (cond
-   ((wisi-tok-region tok)
-    (format "(%s %d . %d)"
-           (wisi-tok-token tok)
-           (car (wisi-tok-region tok))
-           (cdr (wisi-tok-region tok))))
-   (t
-    (format "(%s)" (wisi-tok-token tok)))
-   ))
-
-(provide 'wisi-parse-common)
+;;; wisi-parse-common.el --- declarations used by wisi-parse.el, 
wisi-ada-parse.el, and wisi.el
+;;
+;; Copyright (C) 2014, 2015, 2017 - 2019  Free Software Foundation, Inc.
+;;
+;; Author: Stephen Leake <stephen_leake@member.fsf.org>
+;;
+;; This file is part of GNU Emacs.
+;;
+;; GNU Emacs 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 3 of the License, or
+;; (at your option) any later version.
+;;
+;; GNU Emacs 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(defcustom wisi-partial-parse-threshold 100001
+  "Minimum size that will be parsed by each call to the parser.
+A parse is always requested at a point (or on a region); the
+point is first expanded to a start point before the region and an
+end point after the region, that the parser can gracefully
+handle. If the final region covers the entire buffer, a complete
+parse is done. Indent assumes the start point of the parse region
+is properly indented. Most navigate parses ignore this setting
+and parse the whole buffer."
+  :type 'integer
+  :group 'wisi
+  :safe 'integerp)
+(make-variable-buffer-local 'wisi-partial-parse-threshold)
+
+(cl-defstruct (wisi--lexer-error)
+  pos ;; position (integer) in buffer where error was detected.
+  message  ;; string error message
+  inserted ;; char inserted after pos.
+  )
+
+(cl-defstruct (wisi--parse-error-repair)
+  pos ;; position (integer) in buffer where insert/delete is done.
+  inserted ;; list of token IDs that were inserted before pos
+  deleted  ;; list of token IDs that were deleted after pos
+  deleted-region ;; buffer (cons FIRST LAST) region deleted
+  )
+
+(cl-defstruct (wisi--parse-error)
+  ;; Includes information derived from compiler error recovery to edit
+  ;; text to fix one error. Used by ’wisi-repair-error’ to edit buffer.
+  pos      ;; position (integer or marker) in buffer where error was detected.
+  message  ;; string error message
+  repair   ;; list of wisi--parse-error-repair.
+  )
+
+(cl-defstruct wisi-parser
+  ;; Separate lists for lexer and parse errors, because lexer errors
+  ;; must be repaired first, before parse errors can be repaired. And
+  ;; they have different structures.
+  lexer-errors
+  ;; list of wisi--lexer-errors from last parse.  Can be more than one if
+  ;; lexer supports error recovery.
+  parse-errors
+  ;; List of wisi--parse-errors from last parse. Can be more than one if
+  ;; parser supports error recovery.
+
+  repair-image
+  ;; alist of (TOKEN-ID . STRING); used by repair error
+)
+
+(cl-defgeneric wisi-parse-format-language-options ((parser wisi-parser))
+  "Return a string to be sent to the parser, containing settings
+for the language-specific parser options."
+  ;; not needed for the elisp parser, which can see the options directly.
+  )
+
+(cl-defgeneric wisi-parse-expand-region ((parser wisi-parser) begin end)
+  "Return a cons SEND-BEGIN . SEND-END that is an expansion of
+region BEGIN END that starts and ends at points the parser can
+handle gracefully."
+  (cons begin end))
+
+(defvar-local wisi--parser nil
+  "The current wisi parser; a ‘wisi-parser’ object.")
+
+(defun wisi-read-parse-action ()
+  "Read a parse action symbol from the minibuffer."
+  (intern-soft (completing-read "parse action (indent): " '(face navigate 
indent) nil t nil nil 'indent)))
+
+(defun wisi-search-backward-skip (regexp skip-p)
+  "Search backward for REGEXP. If SKIP-P returns non-nil, search again.
+SKIP-P is a function taking no parameters.
+Return nil if no match found before bob."
+  (let ((maybe-found-p (search-backward-regexp regexp nil t)))
+    (while (and maybe-found-p
+               (funcall skip-p)
+               (setq maybe-found-p (search-backward-regexp regexp nil t))))
+    maybe-found-p))
+
+(defun wisi-search-forward-skip (regexp skip-p)
+  "Search forward for REGEXP. If SKIP-P returns non-nil, search again.
+SKIP-P is a function taking no parameters.
+Return nil if no match found before eob."
+  (let ((maybe-found-p (search-forward-regexp regexp nil t)))
+    (while (and maybe-found-p
+               (funcall skip-p)
+               (setq maybe-found-p (search-forward-regexp regexp nil t))))
+    maybe-found-p))
+
+(defun wisi-show-expanded-region ()
+  "For debugging. Expand currently selected region."
+  (interactive)
+  (let ((region (wisi-parse-expand-region wisi--parser (region-beginning) 
(region-end))))
+    (message "pre (%d . %d) post %s" (region-beginning) (region-end) region)
+    (set-mark (car region))
+    (goto-char (cdr region))
+    ))
+
+(cl-defgeneric wisi-parse-adjust-indent ((parser wisi-parser) indent _repair)
+  "Adjust INDENT for REPAIR (a wisi--parse-error-repair struct). Return new 
indent."
+  indent)
+
+(cl-defgeneric wisi-parse-current ((parser wisi-parser) begin send-end 
parse-end)
+  "Parse current buffer starting at BEGIN, continuing at least thru PARSE-END.
+If using an external parser, send it BEGIN thru SEND-END.")
+
+(cl-defgeneric wisi-refactor ((parser wisi-parser) refactor-action parse-begin 
parse-end edit-begin)
+  "Send parser command to perform REFACTOR-ACTION on region PARSE-BEGIN 
PARSE-END at point EDIT_BEGIN.
+The parse region is not expanded first; it must be the statement
+or declaration containing EDIT_BEGIN.")
+
+(cl-defgeneric wisi-parse-kill ((parser wisi-parser))
+  "Kill any external process associated with parser.")
+
+(cl-defgeneric wisi-parse-find-token ((parser wisi-parser) token-symbol)
+  "Find token with TOKEN-SYMBOL on current parser stack, return token struct.
+For use in grammar actions.")
+
+(cl-defgeneric wisi-parse-stack-peek ((parser wisi-parser) n)
+  "Return the Nth token on the parse stack.
+For use in grammar actions.")
+
+(cl-defstruct
+  (wisi-cache
+   (:constructor wisi-cache-create)
+   (:copier nil))
+  nonterm;; nonterminal from parse
+
+  token
+  ;; terminal symbol from wisi-keyword-table or
+  ;; wisi-punctuation-table, or lower-level nonterminal from parse
+
+  last ;; pos of last char in token, relative to first (0 indexed)
+
+  class ;; one of wisi-class-list
+
+  containing
+  ;; Marker at the start of the containing statement for this token.
+  ;; nil for outermost containing.
+
+  prev ;; marker at previous motion token in statement; nil if none
+  next ;; marker at next motion token in statement; nil if none
+  end  ;; marker at token at end of current statement
+  )
+
+(defun wisi-get-cache (pos)
+  "Return `wisi-cache' struct from the `wisi-cache' text property at POS."
+  (get-text-property pos 'wisi-cache))
+
+(defun wisi-backward-cache ()
+  "Move point backward to the beginning of the first token preceding point 
that has a cache.
+Returns cache, or nil if at beginning of buffer."
+  ;; If point is not near cache, p-s-p-c will return pos just after
+  ;; cache, so 1- is the beginning of cache.
+  ;;
+  ;; If point is just after end of cache, p-s-p-c will return pos at
+  ;; start of cache.
+  ;;
+  ;; So we test for the property before subtracting 1.
+  (let ((pos (previous-single-property-change (point) 'wisi-cache))
+       cache)
+    (cond
+     ((null pos)
+      (goto-char (point-min))
+      nil)
+
+     ((setq cache (get-text-property pos 'wisi-cache))
+      (goto-char pos)
+      cache)
+
+     (t
+      (setq pos (1- pos))
+      (setq cache (get-text-property pos 'wisi-cache))
+      (goto-char pos)
+      cache)
+     )))
+
+(defun wisi-forward-cache ()
+  "Move point forward to the beginning of the first token after point that has 
a cache.
+Returns cache, or nil if at end of buffer."
+  (let (cache pos)
+    (when (get-text-property (point) 'wisi-cache)
+      ;; on a cache; get past it
+      (goto-char (1+ (point))))
+
+    (setq cache (get-text-property (point) 'wisi-cache))
+    (if cache
+       nil
+
+      (setq pos (next-single-property-change (point) 'wisi-cache))
+      (if pos
+         (progn
+           (goto-char pos)
+           (setq cache (get-text-property pos 'wisi-cache)))
+       ;; at eob
+       (goto-char (point-max))
+       (setq cache nil))
+      )
+    cache
+    ))
+
+(defun wisi-cache-region (cache &optional start)
+  "Return region designated by START (default point) to cache last."
+  (unless start (setq start (point)))
+  (cons start (+ start (wisi-cache-last cache))))
+
+(defvar wisi-debug 0
+  "wisi debug mode:
+0 : normal - ignore parse errors, for indenting new code
+1 : report parse errors (for running tests)
+2 : show parse states, position point at parse errors
+3 : also show top 10 items of parser stack.")
+
+;; The following parameters are easily changeable for debugging.
+(defvar wisi-action-disable nil
+  "If non-nil, disable all elisp actions during parsing.
+Allows timing parse separate from actions.")
+
+(defvar-local wisi-trace-mckenzie 0
+  "McKenzie trace level; 0 for none")
+
+(defvar-local wisi-trace-action 0
+  "Parse action trace level; 0 for none")
+
+(defvar-local wisi-mckenzie-disable nil
+  "If non-nil, disable McKenzie error recovery. Otherwise, use parser 
default.")
+
+(defcustom wisi-mckenzie-task-count nil
+  "If integer, sets McKenzie error recovery task count.
+Higher value (up to system processor limit) runs error recovery
+faster, but may encounter race conditions.  Using only one task
+makes error recovery repeatable; useful for tests.  If nil, uses
+value from grammar file."
+  :type 'integer
+  :group 'wisi
+  :safe 'integerp)
+(make-variable-buffer-local 'wisi-mckenzie-task-count)
+
+(defcustom wisi-mckenzie-check-limit nil
+  "If integer, sets McKenzie error recovery algorithm token check limit.
+This sets the number of tokens past the error point that must be
+parsed successfully for a solution to be deemed successful.
+Higher value gives better solutions, but may fail if there are
+two errors close together.  If nil, uses value from grammar
+file."
+  :type 'integer
+  :group 'wisi
+  :safe 'integerp)
+(make-variable-buffer-local 'wisi-mckenzie-check-limit)
+
+(defcustom wisi-mckenzie-enqueue-limit nil
+  "If integer, sets McKenzie error recovery algorithm enqueue limit.
+This sets the maximum number of solutions that will be considered.
+Higher value has more recover power, but will be slower to fail.
+If nil, uses value from grammar file."
+  :type 'integer
+  :group 'wisi
+  :safe 'integerp)
+(make-variable-buffer-local 'wisi-mckenzie-enqueue-limit)
+
+(defcustom wisi-parse-max-parallel 15
+  "Maximum number of parallel parsers during regular parsing.
+Parallel parsers are used to resolve redundancy in the grammar.
+If a file needs more than this, it's probably an indication that
+the grammar is excessively redundant."
+  :type 'integer
+  :group 'wisi
+  :safe 'integerp)
+
+(defvar wisi-parse-max-stack-size 500
+  "Maximum parse stack size.
+Larger stack size allows more deeply nested constructs.")
+;; end of easily changeable parameters
+
+(defvar wisi--parse-action nil
+  ;; not buffer-local; only let-bound in wisi-indent-region, 
wisi-validate-cache
+  "Reason current parse is begin run; one of
+{indent, face, navigate}.")
+
+(defvar-local wisi-indent-comment-col-0 nil
+  "If non-nil, comments currently starting in column 0 are left in column 0.
+Otherwise, they are indented with previous comments or code.
+Normally set from a language-specific option.")
+
+(defvar-local wisi-end-caches nil
+  "List of buffer positions of caches in current statement that need 
wisi-cache-end set.")
+
+(defconst wisi-eoi-term 'Wisi_EOI
+  ;; must match FastToken wisi-output_elisp.adb EOI_Name, which must
+  ;; be part of a valid Ada identifer.
+  "End Of Input token.")
+
+(defconst wisi-class-list
+  [motion ;; motion-action
+   statement-end
+   statement-override
+   statement-start
+   misc ;; other stuff
+   ]
+  "array of valid token classes; checked in wisi-statement-action, used in 
wisi-process-parse.")
+
+(defun wisi-error-msg (message &rest args)
+  (let ((line (line-number-at-pos))
+       (col (- (point) (line-beginning-position))))
+    (format
+     "%s:%d:%d: %s"
+       (buffer-name) ;; buffer-file-name is sometimes nil here!?
+       line col
+       (apply 'format message args))))
+
+(defvar wisi-parse-error nil)
+(put 'wisi-parse-error
+     'error-conditions
+     '(error wisi-parse-error))
+(put 'wisi-parse-error
+     'error-message
+     "wisi parse error")
+
+(cl-defstruct wisi-tok
+  token  ;; symbol from a token table ;; IMPROVEME: rename to ’id’?
+  region ;; cons giving buffer region containing token text
+
+  nonterminal ;; t if a nonterminal
+
+  line ;; Line number at start of token. Nil for empty nonterminals
+
+  first
+  ;; For terminals, t if token is the first token on a line.
+  ;;
+  ;; For nonterminals, line number of first contained line (not
+  ;; including trailing comments) that needs indenting; it is a
+  ;; comment, or begins with a contained token.
+  ;;
+  ;; Otherwise nil.
+
+  ;; The following are non-nil if token (terminal or non-terminal) is
+  ;; followed by blank or comment lines
+  comment-line ;; first blank or comment line following token
+  comment-end ;; position at end of blank or comment lines
+  )
+
+(defun wisi-token-text (token)
+  "Return buffer text from token range."
+  (let ((region (wisi-tok-region token)))
+    (and region
+       (buffer-substring-no-properties (car region) (cdr region)))))
+
+(defun wisi-and-regions (left right)
+  "Return region enclosing both LEFT and RIGHT."
+  (if left
+      (if right
+         (cons (min (car left) (car right))
+               (max (cdr left) (cdr right)))
+       left)
+    right))
+
+(defun wisi--set-line-begin (line-count)
+  "Return a vector of line-beginning positions, with length LINE-COUNT."
+  (let ((result (make-vector line-count 0)))
+    (save-excursion
+      (goto-char (point-min))
+
+      (dotimes (i line-count)
+       (aset result i (point))
+       (forward-line 1)))
+    result))
+
+;;;; debugging
+(defun wisi-tok-debug-image (tok)
+  "Return id and region from TOK, as string."
+  (cond
+   ((wisi-tok-region tok)
+    (format "(%s %d . %d)"
+           (wisi-tok-token tok)
+           (car (wisi-tok-region tok))
+           (cdr (wisi-tok-region tok))))
+   (t
+    (format "(%s)" (wisi-tok-token tok)))
+   ))
+
+(provide 'wisi-parse-common)
diff --git a/wisi-process-parse.el b/wisi-process-parse.el
index d941781..5fb025d 100644
--- a/wisi-process-parse.el
+++ b/wisi-process-parse.el
@@ -1,782 +1,835 @@
-;;; wisi-process-parse.el --- interface to external parse program
-;;
-;; Copyright (C) 2014, 2017 - 2019 Free Software Foundation, Inc.
-;;
-;; Author: Stephen Leake <stephen_leake@member.fsf.org>
-;;
-;; This file is part of GNU Emacs.
-;;
-;; GNU Emacs 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 3 of the License, or
-;; (at your option) any later version.
-;;
-;; GNU Emacs 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
-
-(require 'cl-lib)
-(require 'wisi-parse-common)
-
-(defgroup wisi nil
-  "Options for Wisi package."
-  :group 'programming)
-
-(defcustom wisi-process-time-out 5.0
-  "Time out waiting for parser response. An error occurs if there
-  is no response from the parser after waiting this amount (in
-  seconds)."
-  :type 'float
-  :safe 'floatp)
-(make-variable-buffer-local 'wisi-process-time-out)
-
-(defconst wisi-process-parse-protocol-version "3"
-  "Defines data exchanged between this package and the background process.
-Must match emacs_wisi_common_parse.ads Protocol_Version.")
-
-(defconst wisi-process-parse-prompt "^;;> "
-  "Regexp matching executable prompt; indicates previous command is complete.")
-
-(defconst wisi-process-parse-quit-cmd "004quit\n"
-  "Command to external process telling it to quit.")
-
-;;;;; sessions
-
-;; The executable builds internal parser structures on startup,
-;; then runs a loop, waiting for parse requests.
-;;
-;; We only need one process per language; there is no persistent state
-;; in the process between parses, and processes are too heavy-weight
-;; to have one per buffer. We use a global alist of parser objects to
-;; find the right one for the current buffer.
-
-(cl-defstruct (wisi-process--parser (:include wisi-parser))
-  (label nil)             ;; string uniquely identifying parser
-  language-protocol-version ;; string identifying language-specific params
-  (exec-file nil)        ;; absolute file name of executable
-  (exec-opts nil)         ;; list of process start options for executable
-  (token-table nil)       ;; vector of token symbols, indexed by integer
-  (face-table nil)       ;; vector of face symbols, indexed by integer
-  (busy nil)              ;; t while parser is active
-  (process nil)          ;; running *_wisi_parse executable
-  (buffer nil)                   ;; receives output of executable
-  line-begin              ;; vector of beginning-of-line positions in buffer
-  (total-wait-time 0.0)   ;; total time during last parse spent waiting for 
subprocess output.
-  (response-count 0)      ;; responses received from subprocess during last 
parse; for profiling.
-  end-pos                 ;; last character position parsed
-  language-action-table   ;; array of function pointers, each taking an sexp 
sent by the process
-  )
-
-(defvar wisi-process--alist nil
-  "Alist mapping string label to ‘wisi-process--session’ struct")
-
-;;;###autoload
-(defun wisi-process-parse-get (parser)
-  "Return a ‘wisi-process--parser’ object matching PARSER label.
-If label found in ‘wisi-process--alist’, return that.
-Otherwise add PARSER to ‘wisi-process--alist’, return it."
-  (or (cdr (assoc (wisi-process--parser-label parser) wisi-process--alist))
-      (let ((exec-file (locate-file (wisi-process--parser-exec-file parser) 
exec-path '("" ".exe"))))
-
-       (unless exec-file
-         (error "%s not found on `exec-path'" (wisi-process--parser-exec-file 
parser)))
-
-       (push (cons (wisi-process--parser-label parser) parser) 
wisi-process--alist)
-
-       parser
-     )))
-
-(defun wisi-process-parse-set-exec (label exec-file)
-  "Change the EXEC-FILE for parsers with LABEL."
-  (let ((parser (cdr (assoc label wisi-process--alist))))
-    (when parser
-      (wisi-parse-kill parser)
-      (setf (wisi-process--parser-exec-file parser) exec-file))))
-
-(defun wisi-process-parse--check-version (parser)
-  "Verify protocol version reported by process."
-  ;; The process has just started; the first non-comment line in the
-  ;; process buffer contains the process and language protocol versions.
-  (with-current-buffer (wisi-process--parser-buffer parser)
-    (goto-char (point-min))
-    (search-forward-regexp "protocol: process version \\([0-9]+\\) language 
version \\([0-9]+\\)")
-    (unless (and (match-string 1)
-                (string-equal (match-string 1) 
wisi-process-parse-protocol-version)
-                (match-string 2)
-                (string-equal (match-string 2) 
(wisi-process--parser-language-protocol-version parser)))
-      (wisi-parse-kill parser)
-      (error "%s parser process protocol version mismatch: elisp %s %s, 
process %s %s"
-            (wisi-process--parser-label parser)
-            wisi-process-parse-protocol-version 
(wisi-process--parser-language-protocol-version parser)
-            (match-string 1) (match-string 2)))
-    ))
-
-(defun wisi-process-parse--require-process (parser)
-  "Start the process for PARSER if not already started."
-  (unless (process-live-p (wisi-process--parser-process parser))
-    (let ((process-connection-type nil) ;; use a pipe, not a pty; avoid 
line-by-line reads
-         (process-name (format " *%s_wisi_parse*" (wisi-process--parser-label 
parser))))
-
-      (unless (buffer-live-p (wisi-process--parser-buffer parser))
-       ;; User may have killed buffer to kill parser.
-       (setf (wisi-process--parser-buffer parser)
-             (get-buffer-create process-name)))
-
-      (with-current-buffer (wisi-process--parser-buffer parser)
-       (erase-buffer)); delete any previous messages, prompt
-
-      (setf (wisi-process--parser-process parser)
-           (make-process
-            :name process-name
-            :buffer (wisi-process--parser-buffer parser)
-            :command (append (list (wisi-process--parser-exec-file parser))
-                             (wisi-process--parser-exec-opts parser))))
-
-      (set-process-query-on-exit-flag (wisi-process--parser-process parser) 
nil)
-      (setf (wisi-process--parser-busy parser) nil)
-
-      (wisi-process-parse--wait parser)
-      (wisi-process-parse--check-version parser)
-      )))
-
-(defun wisi-process-parse--wait (parser)
-  "Wait for the current command to complete."
-  (let ((process (wisi-process--parser-process parser))
-       (search-start (point-min))
-       (wait-count 0)
-       (found nil))
-
-    (with-current-buffer (wisi-process--parser-buffer parser)
-      (while (and (process-live-p process)
-                 (progn
-                   ;; process output is inserted before point, so move back 
over it to search it
-                   (goto-char search-start)
-                   (not (setq found (re-search-forward 
wisi-process-parse-prompt (point-max) t)))))
-       (setq search-start (point));; don't search same text again
-       (setq wait-count (1+ wait-count))
-       (accept-process-output process 0.1))
-
-      (unless found
-       (wisi-process-parse-show-buffer parser)
-       (error "%s process died" (wisi-process--parser-exec-file parser)))
-      )))
-
-(defun wisi-process-parse-show-buffer (parser)
-  "Show PARSER buffer."
-  (if (buffer-live-p (wisi-process--parser-buffer parser))
-      (pop-to-buffer (wisi-process--parser-buffer parser))
-    (error "wisi-process-parse process not active")))
-
-(defun wisi-process-parse--send-parse (parser begin send-end parse-end)
-  "Send a parse command to PARSER external process, followed by
-the content of the current buffer from BEGIN thru SEND-END.  Does
-not wait for command to complete. PARSE-END is end of desired
-parse region."
-  ;; Must match "parse" command arguments read by
-  ;; emacs_wisi_common_parse,adb Get_Parse_Params.
-  (let* ((cmd (format "parse %d \"%s\" %d %d %d %d %d %d %d %d %d %d %d %d %d 
%d %d %d %d %d %s"
-                     (cl-ecase wisi--parse-action
-                       (navigate 0)
-                       (face 1)
-                       (indent 2))
-                     (if (buffer-file-name) (file-name-nondirectory 
(buffer-file-name)) "")
-                     (position-bytes begin)
-                     (position-bytes send-end)
-                     (position-bytes parse-end)
-                     begin ;; char_pos
-                     (line-number-at-pos begin)
-                     (line-number-at-pos send-end)
-                     (save-excursion (goto-char begin) (back-to-indentation) 
(current-column));; indent-begin
-                     (if (or (and (= begin (point-min)) (= parse-end 
(point-max)))
-                             (< (point-max) wisi-partial-parse-threshold))
-                         0 1) ;; partial parse active
-                     (if (> wisi-debug 0) 1 0) ;; debug-mode
-                     (1- wisi-debug) ;; trace_parse
-                     wisi-trace-mckenzie
-                     wisi-trace-action
-                     (if wisi-mckenzie-disable 1 0)
-                     (or wisi-mckenzie-task-count -1)
-                     (or wisi-mckenzie-check-limit -1)
-                     (or wisi-mckenzie-enqueue-limit -1)
-                     (or wisi-parse-max-parallel -1)
-                     (- (position-bytes send-end) (position-bytes begin)) ;; 
send-end is after last byte
-                     (wisi-parse-format-language-options parser)
-                     ))
-        (msg (format "%03d%s" (length cmd) cmd))
-        (process (wisi-process--parser-process parser)))
-    (with-current-buffer (wisi-process--parser-buffer parser)
-      (erase-buffer))
-
-    (process-send-string process msg)
-    (process-send-string process (buffer-substring-no-properties begin 
send-end))
-
-    ;; We don’t wait for the send to complete; the external process
-    ;; may start parsing and send an error message.
-    ))
-
-(defun wisi-process-parse--send-noop (parser)
-  "Send a noop command to PARSER external process, followed by
-the content of the current buffer.  Does not wait for command to
-complete."
-  (let* ((cmd (format "noop %d" (1- (position-bytes (point-max)))))
-        (msg (format "%03d%s" (length cmd) cmd))
-        (process (wisi-process--parser-process parser)))
-    (with-current-buffer (wisi-process--parser-buffer parser)
-      (erase-buffer))
-
-    (process-send-string process msg)
-    (process-send-string process (buffer-substring-no-properties (point-min) 
(point-max)))
-    ))
-
-(defun wisi-process-parse--marker-or-nil (item)
-  (if (= -1 item) nil (copy-marker item t)))
-
-(defun wisi-process-parse--Navigate_Cache (parser sexp)
-  ;; sexp is [Navigate_Cache pos statement_id id length class containing_pos 
prev_pos next_pos end_pos]
-  ;; see ‘wisi-process-parse--execute’
-  (let ((pos (aref sexp 1)))
-    (with-silent-modifications
-      (put-text-property
-       pos
-       (1+ pos)
-       'wisi-cache
-       (wisi-cache-create
-       :nonterm    (aref (wisi-process--parser-token-table parser) (aref sexp 
2))
-       :token      (aref (wisi-process--parser-token-table parser) (aref sexp 
3))
-       :last       (aref sexp 4)
-       :class      (aref wisi-class-list (aref sexp 5))
-       :containing (wisi-process-parse--marker-or-nil (aref sexp 6))
-       :prev       (wisi-process-parse--marker-or-nil (aref sexp 7))
-       :next       (wisi-process-parse--marker-or-nil (aref sexp 8))
-       :end        (wisi-process-parse--marker-or-nil (aref sexp 9))
-       )))
-    ))
-
-(defun wisi-process-parse--Name_Property (parser sexp)
-  ;; sexp is [Name_Property first-pos last-pos]
-  ;; see ‘wisi-process-parse--execute’
-  ;; implements wisi-name-action
-  (with-silent-modifications
-    (put-text-property (aref sexp 1) (1+ (aref sexp 2)) 'wisi-name t)))
-
-(defun wisi-process-parse--Face_Property (parser sexp)
-  ;; sexp is [Face_Property first-pos last-pos face-index]
-  ;; see ‘wisi-process-parse--execute’
-  ;; implements wisi--face-action-1
-  (with-silent-modifications
-    (add-text-properties
-     (aref sexp 1)
-     (1+ (aref sexp 2))
-     (list 'font-lock-face (aref (wisi-process--parser-face-table parser) 
(aref sexp 3))
-          'fontified t)
-     )))
-
-(defun wisi-process-parse--Indent (parser sexp)
-  ;; sexp is [Indent line-number indent]
-  ;; see ‘wisi-process-parse--execute’
-  (let ((pos (aref (wisi-process--parser-line-begin parser) (1- (aref sexp 
1)))))
-    (with-silent-modifications
-      (when (< (point-min) pos)
-       (put-text-property
-        (1- pos)
-        pos
-        'wisi-indent
-        (aref sexp 2)))
-      )))
-
-(defun wisi-process-parse--Lexer_Error (parser sexp)
-  ;; sexp is [Lexer_Error char-position <message> <repair-char>]
-  ;; see ‘wisi-process-parse--execute’
-  (let ((pos (aref sexp 1))
-       err)
-
-    (goto-char pos);; for current-column
-
-    (setq err
-         (make-wisi--lexer-error
-          :pos (copy-marker pos)
-          :message
-          (format "%s:%d:%d: %s"
-                  (if (buffer-file-name) (file-name-nondirectory 
(buffer-file-name)) "")
-                  ;; file-name can be nil during vc-resolve-conflict
-                  (line-number-at-pos pos)
-                  (current-column)
-                  (aref sexp 2))
-          :inserted (when (= 4 (length sexp)) (aref sexp 3))))
-
-    (push err (wisi-parser-lexer-errors parser))
-    ))
-
-(defun wisi-process-parse--Parser_Error (parser sexp)
-  ;; sexp is [Parser_Error char-position <string>]
-  ;; see ‘wisi-process-parse--execute’
-  (let ((pos (aref sexp 1))
-       err)
-
-    (goto-char pos);; for current-column
-
-    (setq err
-         (make-wisi--parse-error
-          :pos (copy-marker pos)
-          :message
-          (format "%s:%d:%d: %s"
-                  (if (buffer-file-name) (file-name-nondirectory 
(buffer-file-name)) "")
-                  ;; file-name can be nil during vc-resolve-conflict
-                  (line-number-at-pos pos)
-                  (1+ (current-column))
-                  (aref sexp 2))))
-
-    (push err (wisi-parser-parse-errors parser))
-    ))
-
-(defun wisi-process-parse--Check_Error (parser sexp)
-  ;; sexp is [Check_Error code name-1-pos name-2-pos <string>]
-  ;; see ‘wisi-process-parse--execute’
-  (let* ((name-1-pos (aref sexp 2))
-       (name-1-col (1+ (progn (goto-char name-1-pos)(current-column)))) ;; 
gnat columns are 1 + emacs columns
-       (name-2-pos (aref sexp 3))
-       (name-2-col (1+ (progn (goto-char name-2-pos)(current-column))))
-       (file-name (if (buffer-file-name) (file-name-nondirectory 
(buffer-file-name)) ""))
-       ;; file-name can be nil during vc-resolve-conflict
-       (err (make-wisi--parse-error
-             :pos (copy-marker name-1-pos)
-             :message
-             (format "%s:%d:%d: %s %s:%d:%d"
-                     file-name (line-number-at-pos name-1-pos) name-1-col
-                     (aref sexp 4)
-                     file-name (line-number-at-pos name-2-pos) name-2-col)))
-       )
-
-    (push err (wisi-parser-parse-errors parser))
-    ))
-
-(defun wisi-process-parse--Recover (parser sexp)
-  ;; sexp is [Recover [pos [inserted] [deleted]]...]
-  ;; see ‘wisi-process-parse--execute’
-  ;; convert to list of wisi--parse-error-repair, add to last error
-  (let* ((token-table (wisi-process--parser-token-table parser))
-        (last-error (car (wisi-parser-parse-errors parser))))
-    (unless (= 1 (length sexp))
-      (cl-do ((i 1 (1+ i))) ((= i (length sexp)))
-       (push
-        (make-wisi--parse-error-repair
-         :pos (aref (aref sexp i) 0)
-         :inserted (mapcar (lambda (id) (aref token-table id)) (aref (aref 
sexp i) 1))
-         :deleted  (mapcar (lambda (id) (aref token-table id)) (aref (aref 
sexp i) 2)))
-        (wisi--parse-error-repair last-error)))
-      )))
-
-(defun wisi-process-parse--End (parser sexp)
-  ;; sexp is [End pos]
-  ;; see ‘wisi-process-parse--execute’
-  (setf (wisi-process--parser-end-pos parser) (aref sexp 1)))
-
-(defun wisi-process-parse--Language (parser sexp)
-  ;; sexp is [Language language-action ...]
-  (funcall (aref (wisi-process--parser-language-action-table parser) (aref 
sexp 1)) sexp))
-
-(defun wisi-process-parse--execute (parser sexp)
-  "Execute encoded SEXP sent from external process."
-  ;; sexp is [action arg ...]; an encoded instruction that we need to execute
-  ;;
-  ;; Actions:
-  ;;
-  ;; [Navigate_Cache pos statement_id id length class containing_pos prev_pos 
next_pos end_pos]
-  ;;    Set a wisi-cache text-property.
-  ;;    *pos          : integer buffer position; -1 if nil (not set)
-  ;;    *id           : integer index into parser-token-table
-  ;;    length        : integer character count
-  ;;    class         : integer index into wisi-class-list
-  ;;
-  ;; [Name_Property first-pos last-pos]
-  ;;
-  ;; [Face_Property first-pos last-pos face-index]
-  ;;    Set a font-lock-face text-property
-  ;;    face-index: integer index into parser-elisp-face-table
-  ;;
-  ;; [Indent line-number indent]
-  ;;    Set an indent text property
-  ;;
-  ;; [Lexer_Error char-position <message> <repair-char>]
-  ;;    The lexer detected an error at char-position.
-  ;;
-  ;;    If <repair-char> is not ASCII NUL, it was inserted immediately
-  ;;    after char-position to fix the error.
-  ;;
-  ;; [Parser_Error char-position <message>]
-  ;;    The parser detected a syntax error; save information for later
-  ;;    reporting.
-  ;;
-  ;;    If error recovery is successful, there can be more than one
-  ;;    error reported during a parse.
-  ;;
-  ;; [Check_Error code name-1-pos name-2-pos <string>]
-  ;;    The parser detected a semantic check error; save information
-  ;;    for later reporting.
-  ;;
-  ;;    If error recovery is successful, there can be more than one
-  ;;    error reported during a parse.
-  ;;
-  ;; [Recover [pos [inserted] [deleted]]...]
-  ;;    The parser finished a successful error recovery.
-  ;;
-  ;;    pos: Buffer position
-  ;;
-  ;;    inserted: Virtual tokens (terminal or non-terminal) inserted before 
pos.
-  ;;
-  ;;    deleted: Tokens deleted after pos.
-  ;;
-  ;;    Args are token ids; index into parser-token-table. Save the information
-  ;;    for later use by ’wisi-repair-error’.
-  ;;
-  ;; [Language ...]
-  ;;    Dispatch to a language-specific action, via
-  ;;    `wisi-process--parser-language-action-table'.
-  ;;
-  ;;
-  ;; Numeric action codes are given in the case expression below
-
-  (cl-ecase (aref sexp 0)
-    (1  (wisi-process-parse--Navigate_Cache parser sexp))
-    (2  (wisi-process-parse--Face_Property parser sexp))
-    (3  (wisi-process-parse--Indent parser sexp))
-    (4  (wisi-process-parse--Lexer_Error parser sexp))
-    (5  (wisi-process-parse--Parser_Error parser sexp))
-    (6  (wisi-process-parse--Check_Error parser sexp))
-    (7  (wisi-process-parse--Recover parser sexp))
-    (8  (wisi-process-parse--End parser sexp))
-    (9  (wisi-process-parse--Name_Property parser sexp))
-    (10 (wisi-process-parse--Language parser sexp))
-    ))
-
-;;;;; main
-
-(cl-defmethod wisi-parse-kill ((parser wisi-process--parser))
-  (when (process-live-p (wisi-process--parser-process parser))
-    ;; We used to send a quit command first, to be nice. But there's
-    ;; no timeout on that, so it would hang when the process
-    ;; executable is not reading command input.
-    (when (process-live-p (wisi-process--parser-process parser))
-      (kill-process (wisi-process--parser-process parser)))
-    )
-  (setf (wisi-process--parser-busy parser) nil))
-
-(defvar wisi--lexer nil) ;; wisi-elisp-lexer.el
-(declare-function wisi-elisp-lexer-reset "wisi-elisp-lexer")
-
-(cl-defmethod wisi-parse-current ((parser wisi-process--parser) begin send-end 
parse-end)
-  "Run the external parser on the current buffer, from BEGIN to at least 
PARSE-END.
-Send BEGIN thru SEND-END to external parser."
-  (wisi-process-parse--require-process parser)
-
-  ;; font-lock can trigger a face parse while navigate or indent parse
-  ;; is active, due to ‘accept-process-output’ below. font-lock must
-  ;; not hang (it is called from an idle timer), so don’t
-  ;; wait. Signaling an error tells font-lock to try again later.
-  ;;
-  ;; If the parser is left busy due to some error, that is a bug. In
-  ;; order to detect such bugs, and avoid weird errors from
-  ;; wisi-indent-region, we signal an error here.
-  (if (wisi-process--parser-busy parser)
-      (progn
-       (setf (wisi-parser-parse-errors parser)
-             (list
-              (make-wisi--parse-error
-               :pos 0
-               :message (format "%s:%d:%d: parser busy (try 
’wisi-kill-parser’)"
-                                (if (buffer-file-name) (file-name-nondirectory 
(buffer-file-name)) "") 1 1))
-              ))
-       (error "%s parse abandoned; parser busy - use partial parse?" 
wisi--parse-action)
-       )
-
-    ;; It is not possible for a background elisp function (ie
-    ;; font-lock) to interrupt this code between checking and setting
-    ;; parser-busy; background elisp can only run when we call
-    ;; accept-process-output below.
-    (setf (wisi-process--parser-busy parser) t)
-
-    (condition-case-unless-debug err
-       (let* ((source-buffer (current-buffer))
-              (response-buffer (wisi-process--parser-buffer parser))
-              (process (wisi-process--parser-process parser))
-              (w32-pipe-read-delay 0) ;; fastest subprocess read
-              response
-              response-end
-              (response-count 0)
-              (sexp-start (point-min))
-              (need-more nil) ;; point-max if need more, to check for new input
-              (done nil)
-              start-wait-time)
-
-         (setf (wisi-process--parser-total-wait-time parser) 0.0)
-
-         (setf (wisi-parser-lexer-errors parser) nil)
-         (setf (wisi-parser-parse-errors parser) nil)
-
-         (let ((total-line-count (1+ (count-lines (point-max) (point-min)))))
-           (setf (wisi-process--parser-line-begin parser) 
(wisi--set-line-begin total-line-count))
-           (wisi-process-parse--send-parse parser begin send-end parse-end)
-
-           ;; We reset the elisp lexer, because post-parse actions may use it.
-           (when wisi--lexer
-             (wisi-elisp-lexer-reset total-line-count wisi--lexer))
-           )
-
-         (set-buffer response-buffer)
-
-         ;; process responses until prompt received
-         (while (not done)
-
-           ;; process all complete responses currently in buffer
-           (while (and (not need-more)
-                       (not done))
-
-             (goto-char sexp-start)
-
-             (cond
-              ((eobp)
-               (setq need-more (point-max)))
-
-              ((looking-at wisi-process-parse-prompt)
-               (setq done t))
-
-              ((or (looking-at "\\[") ;; encoded action
-                   (looking-at "(")) ;; error or other elisp expression to eval
-               (condition-case nil
-                   (setq response-end (scan-sexps (point) 1))
-                 (error
-                  ;; incomplete response
-                  (setq need-more (point-max))
-                  nil))
-
-               (unless need-more
-                 (setq response-count (1+ response-count))
-                 (setq response (car (read-from-string 
(buffer-substring-no-properties (point) response-end))))
-                 (goto-char response-end)
-                 (forward-line 1)
-                 (setq sexp-start (point))
-
-                 (set-buffer source-buffer) ;; for put-text-property in actions
-                 (cond
-                  ((listp response)
-                   ;; error of some sort
-                   (cond
-                    ((equal '(parse_error) response)
-                     ;; Parser detected a syntax error, and recovery failed, 
so signal it.
-
-                     (when (> wisi-debug 0)
-                       ;; Save a copy of parser output; may be overwritten by 
subsequent parse face attempts.
-                       (set-buffer response-buffer)
-                       (let ((content (buffer-substring-no-properties 
(point-min) (point-max)))
-                             (buf-name (concat (buffer-name) "-save-error")))
-                         (set-buffer (get-buffer-create buf-name))
-                         (insert content)))
-
-                     (if (wisi-parser-parse-errors parser)
-                         (signal 'wisi-parse-error
-                                 (wisi--parse-error-message (car 
(wisi-parser-parse-errors parser))))
-
-                       ;; can have no errors when testing a new parser
-                       (push
-                        (make-wisi--parse-error :pos 0 :message "parser failed 
with no message")
-                        (wisi-parser-parse-errors parser))
-                       (signal 'wisi-parse-error "parser failed with no 
message")))
-
-                    ((equal 'parse_error (car response))
-                     ;; Parser detected some other error non-fatal error, so 
signal it.
-                     (push
-                        (make-wisi--parse-error :pos 0 :message (cadr 
response))
-                        (wisi-parser-parse-errors parser))
-                     (signal 'wisi-parse-error (cdr response)))
-
-                    ((and (eq 'error (car response))
-                          (string-prefix-p "bad command:" (cadr response)))
-                     ;; Parser dropped bytes, is treating buffer
-                     ;; content bytes as commands. Kill the process
-                     ;; to kill the pipes; there is no other way to
-                     ;; flush them.
-                     (kill-process (wisi-process--parser-process parser))
-                     (signal 'wisi-parse-error "parser lost sync; killed"))
-
-                    (t
-                     ;; Some other error
-                     (condition-case-unless-debug err
-                         (eval response)
-                       (error
-                        (push (make-wisi--parse-error :pos (point) :message 
(cadr err)) (wisi-parser-parse-errors parser))
-                        (signal (car err) (cdr err)))))
-                    ))
-
-                  ((arrayp response)
-                   ;; encoded action
-                   (condition-case-unless-debug err
-                       (wisi-process-parse--execute parser response)
-                     (wisi-parse-error
-                      (push (make-wisi--parse-error :pos (point) :message 
(cadr err)) (wisi-parser-parse-errors parser))
-                      (signal (car err) (cdr err)))
-
-                     (error ;; ie from [C:\Windows\system32\KERNEL32.DLL], or 
bug in action code above.
-                      (set-buffer response-buffer)
-                      (let ((content (buffer-substring-no-properties 
(point-min) (point-max)))
-                            (buf-name (concat (buffer-name) "-save-error")))
-                        (set-buffer (get-buffer-create buf-name))
-                        (insert content)
-                        (insert (format "%s" err))
-                        (error "parser failed; error messages in %s" 
buf-name)))
-                     ))
-                  )
-
-                 (set-buffer response-buffer)
-                 ))
-
-              (t
-               ;; debug output
-               (forward-line 1)
-               (setq sexp-start (point)))
-              )
-             )
-
-           (unless done
-             ;; end of response buffer
-             (unless (process-live-p process)
-               (set-buffer response-buffer)
-               (let ((content (buffer-substring-no-properties (point-min) 
(point-max)))
-                     (buf-name (concat (buffer-name) "-save-error")))
-                 (set-buffer (get-buffer-create buf-name))
-                 (insert content)
-                 (error "parser failed; error messages in %s" buf-name)))
-
-             (setq start-wait-time (float-time))
-
-             ;; If we specify no time-out here, we get messages about
-             ;; "blocking call with quit inhibited", when this is
-             ;; called by font-lock from the display engine.
-             ;;
-             ;; Specifying just-this-one t prevents C-q from
-             ;; interrupting this?
-             (accept-process-output
-              process
-              wisi-process-time-out
-              nil ;; milliseconds
-              nil)  ;; just-this-one
-
-             (setf (wisi-process--parser-total-wait-time parser)
-                   (+ (wisi-process--parser-total-wait-time parser)
-                      (- (float-time) start-wait-time)))
-
-             (when (and (= (point-max) need-more)
-                        (> (wisi-process--parser-total-wait-time parser) 
wisi-process-time-out))
-               (error "wisi-process-parse not getting more text (or bad syntax 
in process output)"))
-
-             (setq need-more nil))
-           );; while not done
-
-         ;; got command prompt
-         (unless (process-live-p process)
-           (wisi-process-parse-show-buffer parser)
-           (error "wisi-process-parse process died"))
-
-         (setf (wisi-process--parser-response-count parser) response-count)
-
-         (setf (wisi-process--parser-busy parser) nil)
-         (set-buffer source-buffer)
-         ;; If we get here, the parse succeeded (possibly with error
-         ;; recovery); move point to end of parsed region.
-         (goto-char (wisi-process--parser-end-pos parser))
-         (cons begin (point))
-         )
-
-      (wisi-parse-error
-       (setf (wisi-process--parser-busy parser) nil)
-       (signal (car err) (cdr err)))
-
-      (error
-       (setf (wisi-process--parser-busy parser) nil)
-       (signal (car err) (cdr err))
-       ))))
-
-(defvar wisi--parser nil) ;; wisi.el
-
-(defun wisi-process-send-tokens-noop ()
-  "Run lexer, send tokens to subprocess; otherwise no operation.
-For use with ’wisi-time’."
-  (wisi-process-parse--require-process wisi--parser)
-  (if (wisi-process--parser-busy wisi--parser)
-      (error "%s parser busy" wisi--parse-action)
-
-    ;; not busy
-    (let* ((source-buffer (current-buffer))
-          (action-buffer (wisi-process--parser-buffer wisi--parser))
-          (process (wisi-process--parser-process wisi--parser))
-          (sexp-start (point-min))
-          (need-more nil)
-          (done nil))
-
-      (setf (wisi-process--parser-busy wisi--parser) t)
-      (wisi-process-parse--send-noop wisi--parser)
-
-      (set-buffer action-buffer)
-      (while (and (process-live-p process)
-                 (not done))
-       (goto-char sexp-start)
-       (cond
-        ((eobp)
-         (setq need-more t))
-
-        ((looking-at wisi-process-parse-prompt)
-         (setq done t))
-
-        (t
-         (forward-line 1)
-         (setq sexp-start (point)))
-        )
-
-       (unless done
-         ;; end of response buffer
-         (unless (process-live-p process)
-           (wisi-process-parse-show-buffer wisi--parser)
-           (error "wisi-process-parse process died"))
-
-         (accept-process-output process 1.0 nil nil)
-         (setq need-more nil))
-       )
-      (set-buffer source-buffer)
-      (setf (wisi-process--parser-busy wisi--parser) nil)
-      )))
-
-;;;;; debugging
-(defun wisi-process-parse-ids-to-enum (token-table &rest int-ids)
-  "Translate INT-IDS from process integer token ids to elisp enumeral ids.
-Returns reversed sequence."
-  (let ((enum-ids nil))
-    (cl-dolist (i int-ids)
-      (push (aref token-table i) enum-ids))
-    enum-ids))
-
-(defun wisi-process-parse-show-args ()
-  "Show the partial parse command-line args for run_ada_[lalr | lr1]_parse for 
current region.
-Also add it to the kill ring."
-  (interactive)
-  (let* ((begin (region-beginning))
-        (end   (region-end))
-        (parse-action (wisi-read-parse-action))
-        (msg
-         (format "%s %s %d %d %d %d %d %d %d"
-                 (file-name-nondirectory (buffer-file-name))
-                 parse-action
-                 (position-bytes begin)
-                 (position-bytes end)
-                 (position-bytes end)
-                 begin ;; char_pos
-                 (line-number-at-pos begin)
-                 (line-number-at-pos end)
-                 (save-excursion (goto-char begin) (back-to-indentation) 
(current-column));; indent-begin
-                 )))
-    (kill-new msg)
-    (message msg)))
-
-(provide 'wisi-process-parse)
+;;; wisi-process-parse.el --- interface to external parse program
+;;
+;; Copyright (C) 2014, 2017 - 2019 Free Software Foundation, Inc.
+;;
+;; Author: Stephen Leake <stephen_leake@member.fsf.org>
+;;
+;; This file is part of GNU Emacs.
+;;
+;; GNU Emacs 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 3 of the License, or
+;; (at your option) any later version.
+;;
+;; GNU Emacs 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+(require 'cl-lib)
+(require 'wisi-parse-common)
+
+(defgroup wisi nil
+  "Options for Wisi package."
+  :group 'programming)
+
+(defcustom wisi-process-time-out 5.0
+  "Time out waiting for parser response. An error occurs if there
+  is no response from the parser after waiting this amount (in
+  seconds)."
+  :type 'float
+  :safe 'floatp)
+(make-variable-buffer-local 'wisi-process-time-out)
+
+(defconst wisi-process-parse-protocol-version "4"
+  "Defines data exchanged between this package and the background process.
+Must match emacs_wisi_common_parse.ads Protocol_Version.")
+
+(defconst wisi-process-parse-prompt "^;;> "
+  "Regexp matching executable prompt; indicates previous command is complete.")
+
+(defconst wisi-process-parse-quit-cmd "004quit\n"
+  "Command to external process telling it to quit.")
+
+;;;;; sessions
+
+;; The executable builds internal parser structures on startup,
+;; then runs a loop, waiting for parse requests.
+;;
+;; We only need one process per language; there is no persistent state
+;; in the process between parses, and processes are too heavy-weight
+;; to have one per buffer. We use a global alist of parser objects to
+;; find the right one for the current buffer.
+
+(cl-defstruct (wisi-process--parser (:include wisi-parser))
+  (label nil)             ;; string uniquely identifying parser
+  language-protocol-version ;; string identifying language-specific params
+  (exec-file nil)        ;; absolute file name of executable
+  (exec-opts nil)         ;; list of process start options for executable
+  (token-table nil)       ;; vector of token symbols, indexed by integer
+  (face-table nil)       ;; vector of face symbols, indexed by integer
+  (busy nil)              ;; t while parser is active
+  (process nil)          ;; running *_wisi_parse executable
+  (buffer nil)                   ;; receives output of executable
+  line-begin              ;; vector of beginning-of-line positions in buffer
+  (total-wait-time 0.0)   ;; total time during last parse spent waiting for 
subprocess output.
+  (response-count 0)      ;; responses received from subprocess during last 
parse; for profiling.
+  end-pos                 ;; last character position parsed
+  language-action-table   ;; array of function pointers, each taking an sexp 
sent by the process
+  )
+
+(defvar wisi-process--alist nil
+  "Alist mapping string label to ‘wisi-process--session’ struct")
+
+;;;###autoload
+(defun wisi-process-parse-get (parser)
+  "Return a ‘wisi-process--parser’ object matching PARSER label.
+If label found in ‘wisi-process--alist’, return that.
+Otherwise add PARSER to ‘wisi-process--alist’, return it."
+  (or (cdr (assoc (wisi-process--parser-label parser) wisi-process--alist))
+      (let ((exec-file (locate-file (wisi-process--parser-exec-file parser) 
exec-path '("" ".exe"))))
+
+       (unless exec-file
+         (error "%s not found on `exec-path'" (wisi-process--parser-exec-file 
parser)))
+
+       (push (cons (wisi-process--parser-label parser) parser) 
wisi-process--alist)
+
+       parser
+     )))
+
+(defun wisi-process-parse-set-exec (label exec-file)
+  "Change the EXEC-FILE for parsers with LABEL."
+  (let ((parser (cdr (assoc label wisi-process--alist))))
+    (when parser
+      (wisi-parse-kill parser)
+      (setf (wisi-process--parser-exec-file parser) exec-file))))
+
+(defun wisi-process-parse--check-version (parser)
+  "Verify protocol version reported by process."
+  ;; The process has just started; the first non-comment line in the
+  ;; process buffer contains the process and language protocol versions.
+  (with-current-buffer (wisi-process--parser-buffer parser)
+    (goto-char (point-min))
+    (search-forward-regexp "protocol: process version \\([0-9]+\\) language 
version \\([0-9]+\\)")
+    (unless (and (match-string 1)
+                (string-equal (match-string 1) 
wisi-process-parse-protocol-version)
+                (match-string 2)
+                (string-equal (match-string 2) 
(wisi-process--parser-language-protocol-version parser)))
+      (wisi-parse-kill parser)
+      (error "%s parser process protocol version mismatch: elisp %s %s, 
process %s %s"
+            (wisi-process--parser-label parser)
+            wisi-process-parse-protocol-version 
(wisi-process--parser-language-protocol-version parser)
+            (match-string 1) (match-string 2)))
+    ))
+
+(defun wisi-process-parse--require-process (parser)
+  "Start the process for PARSER if not already started."
+  (unless (process-live-p (wisi-process--parser-process parser))
+    (let ((process-connection-type nil) ;; use a pipe, not a pty; avoid 
line-by-line reads
+         (process-name (format " *%s_wisi_parse*" (wisi-process--parser-label 
parser))))
+
+      (unless (buffer-live-p (wisi-process--parser-buffer parser))
+       ;; User may have killed buffer to kill parser.
+       (setf (wisi-process--parser-buffer parser)
+             (get-buffer-create process-name)))
+
+      (with-current-buffer (wisi-process--parser-buffer parser)
+       (erase-buffer)); delete any previous messages, prompt
+
+      (setf (wisi-process--parser-process parser)
+           (make-process
+            :name process-name
+            :buffer (wisi-process--parser-buffer parser)
+            :command (append (list (wisi-process--parser-exec-file parser))
+                             (wisi-process--parser-exec-opts parser))))
+
+      (set-process-query-on-exit-flag (wisi-process--parser-process parser) 
nil)
+      (setf (wisi-process--parser-busy parser) nil)
+
+      (wisi-process-parse--wait parser)
+      (wisi-process-parse--check-version parser)
+      )))
+
+(defun wisi-process-parse--wait (parser)
+  "Wait for the current command to complete."
+  (let ((process (wisi-process--parser-process parser))
+       (search-start (point-min))
+       (wait-count 0)
+       (found nil))
+
+    (with-current-buffer (wisi-process--parser-buffer parser)
+      (while (and (process-live-p process)
+                 (progn
+                   ;; process output is inserted before point, so move back 
over it to search it
+                   (goto-char search-start)
+                   (not (setq found (re-search-forward 
wisi-process-parse-prompt (point-max) t)))))
+       (setq search-start (point));; don't search same text again
+       (setq wait-count (1+ wait-count))
+       (accept-process-output process 0.1))
+
+      (unless found
+       (wisi-process-parse-show-buffer parser)
+       (error "%s process died" (wisi-process--parser-exec-file parser)))
+      )))
+
+(defun wisi-process-parse-show-buffer (parser)
+  "Show PARSER buffer."
+  (if (buffer-live-p (wisi-process--parser-buffer parser))
+      (pop-to-buffer (wisi-process--parser-buffer parser))
+    (error "wisi-process-parse process not active")))
+
+(defun wisi-process-parse--send-parse (parser begin send-end parse-end)
+  "Send a parse command to PARSER external process, followed by
+the content of the current buffer from BEGIN thru SEND-END.  Does
+not wait for command to complete. PARSE-END is end of desired
+parse region."
+  ;; Must match "parse" command arguments read by
+  ;; emacs_wisi_common_parse.adb Get_Parse_Params.
+  (let* ((cmd (format "parse %d \"%s\" %d %d %d %d %d %d %d %d %d %d %d %d %d 
%d %d %d %d %d %s"
+                     (cl-ecase wisi--parse-action
+                       (navigate 0)
+                       (face 1)
+                       (indent 2))
+                     (if (buffer-file-name) (file-name-nondirectory 
(buffer-file-name)) "")
+                     (position-bytes begin)
+                     (position-bytes send-end)
+                     (position-bytes parse-end)
+                     begin ;; char_pos
+                     (line-number-at-pos begin)
+                     (line-number-at-pos send-end)
+                     (save-excursion (goto-char begin) (back-to-indentation) 
(current-column));; indent-begin
+                     (if (or (and (= begin (point-min)) (= parse-end 
(point-max)))
+                             (< (point-max) wisi-partial-parse-threshold))
+                         0 1) ;; partial parse active
+                     (if (> wisi-debug 0) 1 0) ;; debug-mode
+                     (1- wisi-debug) ;; trace_parse
+                     wisi-trace-mckenzie
+                     wisi-trace-action
+                     (if wisi-mckenzie-disable 1 0)
+                     (or wisi-mckenzie-task-count -1)
+                     (or wisi-mckenzie-check-limit -1)
+                     (or wisi-mckenzie-enqueue-limit -1)
+                     (or wisi-parse-max-parallel -1)
+                     (- (position-bytes send-end) (position-bytes begin)) ;; 
send-end is after last byte
+                     (wisi-parse-format-language-options parser)
+                     ))
+        (msg (format "%03d%s" (length cmd) cmd))
+        (process (wisi-process--parser-process parser)))
+    (with-current-buffer (wisi-process--parser-buffer parser)
+      (erase-buffer))
+
+    (process-send-string process msg)
+    (process-send-string process (buffer-substring-no-properties begin 
send-end))
+
+    ;; We don’t wait for the send to complete; the external process
+    ;; may start parsing and send an error message.
+    ))
+
+(defun wisi-process-parse--send-refactor (parser refactor-action parse-begin 
parse-end edit-begin)
+  "Send a refactor command to PARSER external process, followed
+by the content of the current buffer from PARSE-BEGIN thru
+PARSE-END, wait for command to complete. PARSER will respond with
+one or more Edit messages."
+  ;; Must match "refactor" command arguments read by
+  ;; emacs_wisi_common_parse.adb Get_Refactor_Params.
+  (let* ((cmd (format "refactor %d \"%s\" %d %d %d %d %d %d %d %d %d %d %d"
+                     refactor-action
+                     (if (buffer-file-name) (file-name-nondirectory 
(buffer-file-name)) "")
+                     (position-bytes parse-begin)
+                     (position-bytes parse-end)
+                     (position-bytes edit-begin)
+                     parse-begin ;; char_pos
+                     (line-number-at-pos parse-begin)
+                     (line-number-at-pos parse-end)
+                     (if (> wisi-debug 0) 1 0) ;; debug-mode
+                     (1- wisi-debug) ;; trace_parse
+                     wisi-trace-action
+                     (or wisi-parse-max-parallel -1)
+                     (- (position-bytes parse-end) (position-bytes 
parse-begin)) ;; parse-end is after last byte
+                     ))
+        (msg (format "%03d%s" (length cmd) cmd))
+        (process (wisi-process--parser-process parser)))
+    (with-current-buffer (wisi-process--parser-buffer parser)
+      (erase-buffer))
+
+    (process-send-string process msg)
+    (process-send-string process (buffer-substring-no-properties parse-begin 
parse-end))
+    (wisi-process-parse--wait parser)
+    ))
+
+(defun wisi-process-parse--send-noop (parser)
+  "Send a noop command to PARSER external process, followed by
+the content of the current buffer.  Does not wait for command to
+complete."
+  (let* ((cmd (format "noop %d" (1- (position-bytes (point-max)))))
+        (msg (format "%03d%s" (length cmd) cmd))
+        (process (wisi-process--parser-process parser)))
+    (with-current-buffer (wisi-process--parser-buffer parser)
+      (erase-buffer))
+
+    (process-send-string process msg)
+    (process-send-string process (buffer-substring-no-properties (point-min) 
(point-max)))
+    ))
+
+(defun wisi-process-parse--marker-or-nil (item)
+  (if (= -1 item) nil (copy-marker item t)))
+
+(defun wisi-process-parse--Navigate_Cache (parser sexp)
+  ;; sexp is [Navigate_Cache pos statement_id id length class containing_pos 
prev_pos next_pos end_pos]
+  ;; see ‘wisi-process-parse--execute’
+  (let ((pos (aref sexp 1)))
+    (with-silent-modifications
+      (put-text-property
+       pos
+       (1+ pos)
+       'wisi-cache
+       (wisi-cache-create
+       :nonterm    (aref (wisi-process--parser-token-table parser) (aref sexp 
2))
+       :token      (aref (wisi-process--parser-token-table parser) (aref sexp 
3))
+       :last       (aref sexp 4)
+       :class      (aref wisi-class-list (aref sexp 5))
+       :containing (wisi-process-parse--marker-or-nil (aref sexp 6))
+       :prev       (wisi-process-parse--marker-or-nil (aref sexp 7))
+       :next       (wisi-process-parse--marker-or-nil (aref sexp 8))
+       :end        (wisi-process-parse--marker-or-nil (aref sexp 9))
+       )))
+    ))
+
+(defun wisi-process-parse--Name_Property (parser sexp)
+  ;; sexp is [Name_Property first-pos last-pos]
+  ;; see ‘wisi-process-parse--execute’
+  ;; implements wisi-name-action
+  (with-silent-modifications
+    (put-text-property (aref sexp 1) (1+ (aref sexp 2)) 'wisi-name t)))
+
+(defun wisi-process-parse--Face_Property (parser sexp)
+  ;; sexp is [Face_Property first-pos last-pos face-index]
+  ;; see ‘wisi-process-parse--execute’
+  ;; implements wisi--face-action-1
+  (with-silent-modifications
+    (add-text-properties
+     (aref sexp 1)
+     (1+ (aref sexp 2))
+     (list 'font-lock-face (aref (wisi-process--parser-face-table parser) 
(aref sexp 3))
+          'fontified t)
+     )))
+
+(defun wisi-process-parse--Indent (parser sexp)
+  ;; sexp is [Indent line-number indent]
+  ;; see ‘wisi-process-parse--execute’
+  (let ((pos (aref (wisi-process--parser-line-begin parser) (1- (aref sexp 
1)))))
+    (with-silent-modifications
+      (when (< (point-min) pos)
+       (put-text-property
+        (1- pos)
+        pos
+        'wisi-indent
+        (aref sexp 2)))
+      )))
+
+(defun wisi-process-parse--Lexer_Error (parser sexp)
+  ;; sexp is [Lexer_Error char-position <message> <repair-char>]
+  ;; see ‘wisi-process-parse--execute’
+  (let ((pos (aref sexp 1))
+       err)
+
+    (goto-char pos);; for current-column
+
+    (setq err
+         (make-wisi--lexer-error
+          :pos (copy-marker pos)
+          :message
+          (format "%s:%d:%d: %s"
+                  (if (buffer-file-name) (file-name-nondirectory 
(buffer-file-name)) "")
+                  ;; file-name can be nil during vc-resolve-conflict
+                  (line-number-at-pos pos)
+                  (current-column)
+                  (aref sexp 2))
+          :inserted (when (= 4 (length sexp)) (aref sexp 3))))
+
+    (push err (wisi-parser-lexer-errors parser))
+    ))
+
+(defun wisi-process-parse--Parser_Error (parser sexp)
+  ;; sexp is [Parser_Error char-position <string>]
+  ;; see ‘wisi-process-parse--execute’
+  (let ((pos (aref sexp 1))
+       err)
+
+    (goto-char pos);; for current-column
+
+    (setq err
+         (make-wisi--parse-error
+          :pos (copy-marker pos)
+          :message
+          (format "%s:%d:%d: %s"
+                  (if (buffer-file-name) (file-name-nondirectory 
(buffer-file-name)) "")
+                  ;; file-name can be nil during vc-resolve-conflict
+                  (line-number-at-pos pos)
+                  (1+ (current-column))
+                  (aref sexp 2))))
+
+    (push err (wisi-parser-parse-errors parser))
+    ))
+
+(defun wisi-process-parse--Check_Error (parser sexp)
+  ;; sexp is [Check_Error code name-1-pos name-2-pos <string>]
+  ;; see ‘wisi-process-parse--execute’
+  (let* ((name-1-pos (aref sexp 2))
+       (name-1-col (1+ (progn (goto-char name-1-pos)(current-column)))) ;; 
gnat columns are 1 + emacs columns
+       (name-2-pos (aref sexp 3))
+       (name-2-col (1+ (progn (goto-char name-2-pos)(current-column))))
+       (file-name (if (buffer-file-name) (file-name-nondirectory 
(buffer-file-name)) ""))
+       ;; file-name can be nil during vc-resolve-conflict
+       (err (make-wisi--parse-error
+             :pos (copy-marker name-1-pos)
+             :message
+             (format "%s:%d:%d: %s %s:%d:%d"
+                     file-name (line-number-at-pos name-1-pos) name-1-col
+                     (aref sexp 4)
+                     file-name (line-number-at-pos name-2-pos) name-2-col)))
+       )
+
+    (push err (wisi-parser-parse-errors parser))
+    ))
+
+(defun wisi-process-parse--Recover (parser sexp)
+  ;; sexp is [Recover [pos [inserted] [deleted] deleted-region]...]
+  ;; see ‘wisi-process-parse--execute’
+  ;; convert to list of wisi--parse-error-repair, add to last error
+  (let* ((token-table (wisi-process--parser-token-table parser))
+        (last-error (car (wisi-parser-parse-errors parser))))
+    (unless (= 1 (length sexp))
+      (cl-do ((i 1 (1+ i))) ((= i (length sexp)))
+       (push
+        (make-wisi--parse-error-repair
+         :pos (aref (aref sexp i) 0)
+         :inserted (mapcar (lambda (id) (aref token-table id)) (aref (aref 
sexp i) 1))
+         :deleted  (mapcar (lambda (id) (aref token-table id)) (aref (aref 
sexp i) 2))
+         :deleted-region (aref (aref sexp i) 3))
+        (wisi--parse-error-repair last-error)))
+      )))
+
+(defun wisi-process-parse--End (parser sexp)
+  ;; sexp is [End pos]
+  ;; see ‘wisi-process-parse--execute’
+  (setf (wisi-process--parser-end-pos parser) (aref sexp 1)))
+
+(defun wisi-process-parse--Edit (parser sexp)
+  ;; sexp is [Edit begin end text]
+  (delete-region (aref sexp 1) (1+ (aref sexp 2)))
+  (goto-char (aref sexp 1))
+  (insert (aref sexp 3)))
+
+(defun wisi-process-parse--Language (parser sexp)
+  ;; sexp is [Language language-action ...]
+  (funcall (aref (wisi-process--parser-language-action-table parser) (aref 
sexp 1)) sexp))
+
+(defun wisi-process-parse--execute (parser sexp)
+  "Execute encoded SEXP sent from external process."
+  ;; sexp is [action arg ...]; an encoded instruction that we need to execute
+  ;;
+  ;; Actions:
+  ;;
+  ;; [Navigate_Cache pos statement_id id length class containing_pos prev_pos 
next_pos end_pos]
+  ;;    Set a wisi-cache text-property.
+  ;;    *pos          : integer buffer position; -1 if nil (not set)
+  ;;    *id           : integer index into parser-token-table
+  ;;    length        : integer character count
+  ;;    class         : integer index into wisi-class-list
+  ;;
+  ;; [Name_Property first-pos last-pos]
+  ;;
+  ;; [Face_Property first-pos last-pos face-index]
+  ;;    Set a font-lock-face text-property
+  ;;    face-index: integer index into parser-elisp-face-table
+  ;;
+  ;; [Indent line-number indent]
+  ;;    Set an indent text property
+  ;;
+  ;; [Lexer_Error char-position <message> <repair-char>]
+  ;;    The lexer detected an error at char-position.
+  ;;
+  ;;    If <repair-char> is not ASCII NUL, it was inserted immediately
+  ;;    after char-position to fix the error.
+  ;;
+  ;; [Parser_Error char-position <message>]
+  ;;    The parser detected a syntax error; save information for later
+  ;;    reporting.
+  ;;
+  ;;    If error recovery is successful, there can be more than one
+  ;;    error reported during a parse.
+  ;;
+  ;; [Check_Error code name-1-pos name-2-pos <string>]
+  ;;    The parser detected a semantic check error; save information
+  ;;    for later reporting.
+  ;;
+  ;;    If error recovery is successful, there can be more than one
+  ;;    error reported during a parse.
+  ;;
+  ;; [Recover [pos [inserted] [deleted] deleted-region]...]
+  ;;    The parser finished a successful error recovery.
+  ;;
+  ;;    pos: Buffer position
+  ;;
+  ;;    inserted: Virtual tokens (terminal or non-terminal) inserted
+  ;;    before pos.
+  ;;
+  ;;    deleted: Tokens deleted after pos.
+  ;;
+  ;;    deleted-region: source buffer region containing deleted tokens
+  ;;
+  ;;    Args are token ids; index into parser-token-table. Save the
+  ;;    information for later use by ’wisi-repair-error’.
+  ;;
+  ;; [Edit begin end text]
+  ;;    Replace region BEGIN . END with TEXT; normally the result of a
+  ;;    refactor command.
+  ;;
+  ;; [Language ...]
+  ;;    Dispatch to a language-specific action, via
+  ;;    `wisi-process--parser-language-action-table'.
+  ;;
+  ;;
+  ;; Numeric action codes are given in the case expression below
+
+  (cl-ecase (aref sexp 0)
+    (1  (wisi-process-parse--Navigate_Cache parser sexp))
+    (2  (wisi-process-parse--Face_Property parser sexp))
+    (3  (wisi-process-parse--Indent parser sexp))
+    (4  (wisi-process-parse--Lexer_Error parser sexp))
+    (5  (wisi-process-parse--Parser_Error parser sexp))
+    (6  (wisi-process-parse--Check_Error parser sexp))
+    (7  (wisi-process-parse--Recover parser sexp))
+    (8  (wisi-process-parse--End parser sexp))
+    (9  (wisi-process-parse--Name_Property parser sexp))
+    (10 (wisi-process-parse--Edit parser sexp))
+    (11 (wisi-process-parse--Language parser sexp))
+    ))
+
+;;;;; main
+
+(cl-defmethod wisi-parse-kill ((parser wisi-process--parser))
+  (when (process-live-p (wisi-process--parser-process parser))
+    ;; We used to send a quit command first, to be nice. But there's
+    ;; no timeout on that, so it would hang when the process
+    ;; executable is not reading command input.
+    (when (process-live-p (wisi-process--parser-process parser))
+      (kill-process (wisi-process--parser-process parser)))
+    )
+  (setf (wisi-process--parser-busy parser) nil))
+
+(defvar wisi--lexer nil) ;; wisi-elisp-lexer.el
+(declare-function wisi-elisp-lexer-reset "wisi-elisp-lexer")
+
+(defun wisi-process-parse--prepare (parser)
+  (wisi-process-parse--require-process parser)
+
+  ;; font-lock can trigger a face parse while navigate or indent parse
+  ;; is active, due to ‘accept-process-output’ below. Signaling an
+  ;; error tells font-lock to try again later.
+  (if (wisi-process--parser-busy parser)
+      (progn
+       (setf (wisi-parser-parse-errors parser)
+             (list
+              (make-wisi--parse-error
+               :pos 0
+               :message (format "%s:%d:%d: parser busy (try 
’wisi-kill-parser’)"
+                                (if (buffer-file-name) (file-name-nondirectory 
(buffer-file-name)) "") 1 1))
+              ))
+       (error "%s parse abandoned; parser busy - use partial parse?" 
wisi--parse-action)
+       )
+
+    ;; It is not possible for a background elisp function (ie
+    ;; font-lock) to interrupt this code between checking and setting
+    ;; parser-busy; background elisp can only run when we call
+    ;; accept-process-output below.
+    (setf (wisi-process--parser-busy parser) t)
+
+    (setf (wisi-process--parser-total-wait-time parser) 0.0)
+    (setf (wisi-parser-lexer-errors parser) nil)
+    (setf (wisi-parser-parse-errors parser) nil)
+    ))
+
+(defun wisi-process-parse--handle-messages (parser)
+  (condition-case-unless-debug err
+      (let* ((source-buffer (current-buffer))
+            (response-buffer (wisi-process--parser-buffer parser))
+            (process (wisi-process--parser-process parser))
+            (w32-pipe-read-delay 0) ;; fastest subprocess read
+            response
+            response-end
+            (response-count 0)
+            sexp-start
+            (need-more nil) ;; point-max if need more, to check for new input
+            (done nil)
+            start-wait-time)
+
+       (set-buffer response-buffer)
+       (setq sexp-start (point-min))
+
+       ;; process responses until prompt received
+       (while (not done)
+
+         ;; process all complete responses currently in buffer
+         (while (and (not need-more)
+                     (not done))
+
+           (goto-char sexp-start)
+
+           (cond
+            ((eobp)
+             (setq need-more (point-max)))
+
+            ((looking-at wisi-process-parse-prompt)
+             (setq done t))
+
+            ((or (looking-at "\\[") ;; encoded action
+                 (looking-at "(")) ;; error or other elisp expression to eval
+             (condition-case nil
+                 (setq response-end (scan-sexps (point) 1))
+               (error
+                ;; incomplete response
+                (setq need-more (point-max))
+                nil))
+
+             (unless need-more
+               (setq response-count (1+ response-count))
+               (setq response (car (read-from-string 
(buffer-substring-no-properties (point) response-end))))
+               (goto-char response-end)
+               (forward-line 1)
+               (setq sexp-start (point))
+
+               (set-buffer source-buffer) ;; for put-text-property in actions
+               (cond
+                ((listp response)
+                 ;; error of some sort
+                 (cond
+                  ((equal '(parse_error) response)
+                   ;; Parser detected a syntax error, and recovery failed, so 
signal it.
+
+                   (when (> wisi-debug 0)
+                     ;; Save a copy of parser output; may be overwritten by 
subsequent parse face attempts.
+                     (set-buffer response-buffer)
+                     (let ((content (buffer-substring-no-properties 
(point-min) (point-max)))
+                           (buf-name (concat (buffer-name) "-save-error")))
+                       (set-buffer (get-buffer-create buf-name))
+                       (insert content)))
+
+                   (if (wisi-parser-parse-errors parser)
+                       (signal 'wisi-parse-error
+                               (wisi--parse-error-message (car 
(wisi-parser-parse-errors parser))))
+
+                     ;; can have no errors when testing a new parser
+                     (push
+                      (make-wisi--parse-error :pos 0 :message "parser failed 
with no message")
+                      (wisi-parser-parse-errors parser))
+                     (signal 'wisi-parse-error "parser failed with no 
message")))
+
+                  ((equal 'parse_error (car response))
+                   ;; Parser detected some other error non-fatal error, so 
signal it.
+                   (push
+                    (make-wisi--parse-error :pos 0 :message (cadr response))
+                    (wisi-parser-parse-errors parser))
+                   (signal 'wisi-parse-error (cdr response)))
+
+                  ((and (eq 'error (car response))
+                        (string-prefix-p "bad command:" (cadr response)))
+                   ;; Parser dropped bytes, is treating buffer
+                   ;; content bytes as commands. Kill the process
+                   ;; to kill the pipes; there is no other way to
+                   ;; flush them.
+                   (kill-process (wisi-process--parser-process parser))
+                   (signal 'wisi-parse-error "parser lost sync; killed"))
+
+                  (t
+                   ;; Some other error
+                   (condition-case-unless-debug err
+                       (eval response)
+                     (error
+                      (push (make-wisi--parse-error :pos (point) :message 
(cadr err)) (wisi-parser-parse-errors parser))
+                      (signal (car err) (cdr err)))))
+                  ))
+
+                ((arrayp response)
+                 ;; encoded action
+                 (condition-case-unless-debug err
+                     (wisi-process-parse--execute parser response)
+                   (wisi-parse-error
+                    (push (make-wisi--parse-error :pos (point) :message (cadr 
err)) (wisi-parser-parse-errors parser))
+                    (signal (car err) (cdr err)))
+
+                   (error ;; ie from [C:\Windows\system32\KERNEL32.DLL], or 
bug in action code above.
+                    (set-buffer response-buffer)
+                    (let ((content (buffer-substring-no-properties (point-min) 
(point-max)))
+                          (buf-name (concat (buffer-name) "-save-error")))
+                      (set-buffer (get-buffer-create buf-name))
+                      (insert content)
+                      (insert (format "%s" err))
+                      (error "parser failed; error messages in %s" buf-name)))
+                   ))
+                )
+
+               (set-buffer response-buffer)
+               ))
+
+            (t
+             ;; debug output
+             (forward-line 1)
+             (setq sexp-start (point)))
+            )
+             )
+
+           (unless done
+             ;; end of response buffer
+             (unless (process-live-p process)
+               (set-buffer response-buffer)
+               (let ((content (buffer-substring-no-properties (point-min) 
(point-max)))
+                     (buf-name (concat (buffer-name) "-save-error")))
+                 (set-buffer (get-buffer-create buf-name))
+                 (insert content)
+                 (error "parser failed; error messages in %s" buf-name)))
+
+             (setq start-wait-time (float-time))
+
+             ;; If we specify no time-out here, we get messages about
+             ;; "blocking call with quit inhibited", when this is
+             ;; called by font-lock from the display engine.
+             ;;
+             ;; Specifying just-this-one t prevents C-q from
+             ;; interrupting this?
+             (accept-process-output
+              process
+              wisi-process-time-out
+              nil ;; milliseconds
+              nil)  ;; just-this-one
+
+             (setf (wisi-process--parser-total-wait-time parser)
+                   (+ (wisi-process--parser-total-wait-time parser)
+                      (- (float-time) start-wait-time)))
+
+             (when (and (= (point-max) need-more)
+                        (> (wisi-process--parser-total-wait-time parser) 
wisi-process-time-out))
+               (error "wisi-process-parse not getting more text (or bad syntax 
in process output)"))
+
+             (setq need-more nil))
+           );; while not done
+
+         ;; got command prompt
+         (unless (process-live-p process)
+           (wisi-process-parse-show-buffer parser)
+           (error "wisi-process-parse process died"))
+
+         (setf (wisi-process--parser-response-count parser) response-count)
+
+         (setf (wisi-process--parser-busy parser) nil)
+         (set-buffer source-buffer)
+         ;; If we get here, the parse succeeded (possibly with error
+         ;; recovery); move point to end of parsed region.
+         (goto-char (wisi-process--parser-end-pos parser))
+         )
+
+      (wisi-parse-error
+       (setf (wisi-process--parser-busy parser) nil)
+       (signal (car err) (cdr err)))
+
+      (error
+       (setf (wisi-process--parser-busy parser) nil)
+       (signal (car err) (cdr err))
+       )))
+
+(cl-defmethod wisi-parse-current ((parser wisi-process--parser) begin send-end 
parse-end)
+  (wisi-process-parse--prepare parser)
+  (let ((total-line-count (1+ (count-lines (point-max) (point-min)))))
+    (setf (wisi-process--parser-line-begin parser) (wisi--set-line-begin 
total-line-count))
+    (wisi-process-parse--send-parse parser begin send-end parse-end)
+
+    ;; We reset the elisp lexer, because post-parse actions may use it.
+    (when wisi--lexer
+      (wisi-elisp-lexer-reset total-line-count wisi--lexer))
+    )
+  (wisi-process-parse--handle-messages parser)
+  (cons begin (point))
+  )
+
+(cl-defmethod wisi-refactor ((parser wisi-process--parser) refactor-action 
parse-begin parse-end edit-begin)
+  (save-excursion
+    (wisi-process-parse--prepare parser)
+    (wisi-process-parse--send-refactor parser refactor-action parse-begin 
parse-end edit-begin)
+    (wisi-process-parse--handle-messages parser))
+  )
+
+(defvar wisi--parser nil) ;; wisi.el
+
+(defun wisi-process-send-tokens-noop ()
+  "Run lexer, send tokens to subprocess; otherwise no operation.
+For use with ’wisi-time’."
+  (wisi-process-parse--require-process wisi--parser)
+  (if (wisi-process--parser-busy wisi--parser)
+      (error "%s parser busy" wisi--parse-action)
+
+    ;; not busy
+    (let* ((source-buffer (current-buffer))
+          (action-buffer (wisi-process--parser-buffer wisi--parser))
+          (process (wisi-process--parser-process wisi--parser))
+          (sexp-start (point-min))
+          (need-more nil)
+          (done nil))
+
+      (setf (wisi-process--parser-busy wisi--parser) t)
+      (wisi-process-parse--send-noop wisi--parser)
+
+      (set-buffer action-buffer)
+      (while (and (process-live-p process)
+                 (not done))
+       (goto-char sexp-start)
+       (cond
+        ((eobp)
+         (setq need-more t))
+
+        ((looking-at wisi-process-parse-prompt)
+         (setq done t))
+
+        (t
+         (forward-line 1)
+         (setq sexp-start (point)))
+        )
+
+       (unless done
+         ;; end of response buffer
+         (unless (process-live-p process)
+           (wisi-process-parse-show-buffer wisi--parser)
+           (error "wisi-process-parse process died"))
+
+         (accept-process-output process 1.0 nil nil)
+         (setq need-more nil))
+       )
+      (set-buffer source-buffer)
+      (setf (wisi-process--parser-busy wisi--parser) nil)
+      )))
+
+;;;;; debugging
+(defun wisi-process-parse-ids-to-enum (token-table &rest int-ids)
+  "Translate INT-IDS from process integer token ids to elisp enumeral ids.
+Returns reversed sequence."
+  (let ((enum-ids nil))
+    (cl-dolist (i int-ids)
+      (push (aref token-table i) enum-ids))
+    enum-ids))
+
+(defun wisi-process-parse-show-args ()
+  "Show the partial parse command-line args for run_ada_[lalr | lr1]_parse for 
current region.
+Also add it to the kill ring."
+  (interactive)
+  (let* ((begin (region-beginning))
+        (end   (region-end))
+        (parse-action (wisi-read-parse-action))
+        (msg
+         (format "%s %s %d %d %d %d %d %d %d"
+                 (file-name-nondirectory (buffer-file-name))
+                 parse-action
+                 (position-bytes begin)
+                 (position-bytes end)
+                 (position-bytes end)
+                 begin ;; char_pos
+                 (line-number-at-pos begin)
+                 (line-number-at-pos end)
+                 (save-excursion (goto-char begin) (back-to-indentation) 
(current-column));; indent-begin
+                 )))
+    (kill-new msg)
+    (message msg)))
+
+(provide 'wisi-process-parse)
diff --git a/wisi-run-indent-test.el b/wisi-run-indent-test.el
index ce5b355..fcf0b33 100644
--- a/wisi-run-indent-test.el
+++ b/wisi-run-indent-test.el
@@ -1,300 +1,331 @@
-;;; wisi-run-indent-test.el --- utils for automating indentation and casing 
tests
-;;
-;; Copyright (C) 2018 - 2019  Free Software Foundation, Inc.
-;;
-;; This file is part of GNU Emacs.
-;;
-;; GNU Emacs 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 3 of the License, or
-;; (at your option) any later version.
-;;
-;; GNU Emacs 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
-
-(require 'wisi-tests)
-
-;; user can set these to t in an EMACSCMD
-(defvar skip-cmds nil)
-(defvar skip-reindent-test nil)
-(defvar skip-recase-test nil)
-(defvar skip-write nil)
-
-(defun test-in-comment-p ()
-  (nth 4 (syntax-ppss)))
-
-(defun test-face (token face)
-  "Test if all of TOKEN in next code line has FACE.
-FACE may be a list."
-  (save-excursion
-    (when (test-in-comment-p)
-      (beginning-of-line); forward-comment doesn't move if inside a comment!
-      (forward-comment (point-max)))
-    (condition-case err
-       (search-forward token (line-end-position 5))
-      (error
-       (error "can't find '%s'" token)))
-
-    (save-match-data
-      (wisi-validate-cache (line-beginning-position 0) (line-end-position 5) 
nil 'face))
-
-    ;; We don't use face-at-point, because it doesn't respect
-    ;; font-lock-face set by the parser! And we want to check for
-    ;; conflicts between font-lock-keywords and the parser.
-
-    ;; font-lock-keywords sets 'face property, parser sets 'font-lock-face.
-
-    ;; In emacs < 27, if we use (get-text-property (point) 'face), we
-    ;; also get 'font-lock-face, but not vice-versa. So we have to use
-    ;; text-properties-at to check for both.
-    (let* ((token (match-string 0))
-          (props (text-properties-at (match-beginning 0)))
-          key
-          token-face)
-
-      (cond
-       ((plist-get props 'font-lock-face)
-       (setq key 'font-lock-face)
-       (setq token-face (plist-get props 'font-lock-face)))
-
-       ((plist-get props 'face)
-       (setq key 'face)
-       (setq token-face (plist-get props 'face)))
-       )
-
-      (when (and (memq 'font-lock-face props)
-                (memq 'face props))
-       (describe-text-properties (match-beginning 0))
-       (error "mixed font-lock-keyword and parser faces for '%s'" token))
-
-      (unless (not (text-property-not-all 0 (length token) key token-face 
token))
-       (error "mixed faces, expecting %s for '%s'" face token))
-
-      (unless (or (and (listp face)
-                      (memq token-face face))
-                 (eq token-face face))
-       (error "found face %s, expecting %s for '%s'" token-face face token))
-    )))
-
-(defun test-face-1 (search token face)
-  "Move to end of comment, search for SEARCH, call `test-face'."
-  (save-excursion
-    (when (test-in-comment-p)
-      (beginning-of-line); forward-comment doesn't move if inside a comment!
-      (forward-comment (point-max)))
-    (search-forward search)
-    (test-face token face)
-    ))
-
-(defun test-cache-class (token class)
-  "Test if TOKEN in next code line has wisi-cache with class CLASS."
-  (save-excursion
-    (wisi-validate-cache (line-beginning-position 0) (line-end-position 3) nil 
'navigate)
-    (beginning-of-line); forward-comment doesn't move if inside a comment!
-    (forward-comment (point-max))
-    (condition-case err
-       (search-forward token (line-end-position 5))
-      (error
-       (error "can't find '%s'" token)))
-
-    (let ((cache (get-text-property (match-beginning 0) 'wisi-cache)))
-
-      (unless cache (error "no cache"))
-      (unless (eq (wisi-cache-class cache) class)
-       (error "expecting class %s, found '%s'" class (wisi-cache-class cache)))
-    )))
-
-(defun test-cache-containing (containing contained)
-  "Test if CONTAINING in next code line has wisi-cache with that contains 
CONTAINED."
-  (save-excursion
-    (wisi-validate-cache (line-beginning-position 0) (line-end-position 3) nil 
'navigate)
-    (beginning-of-line)
-    (forward-comment (point-max))
-    (let (containing-pos contained-cache)
-      (condition-case err
-         (search-forward containing (line-end-position 5))
-       (error
-        (error "can't find '%s'" containing)))
-      (setq containing-pos (match-beginning 0))
-
-      (condition-case err
-         (search-forward contained (line-end-position 5))
-       (error
-        (error "can't find '%s'" contained)))
-      (setq contained-cache (get-text-property (match-beginning 0) 
'wisi-cache))
-
-      (unless contained-cache (error "no cache on %s" contained))
-      (unless (= containing-pos (wisi-cache-containing contained-cache))
-       (error "expecting %d, got %d" containing-pos (wisi-cache-containing 
contained-cache)))
-    )))
-
-(defun run-test-here ()
-  "Run an indentation and casing test on the current buffer."
-  (interactive)
-  (setq indent-tabs-mode nil)
-  (setq jit-lock-context-time 0.0);; for test-face
-
-  (let ((error-count 0)
-       (test-buffer (current-buffer))
-       cmd-line
-       last-result last-cmd expected-result)
-    ;; Look for EMACS* comments in the file:
-    ;;
-    ;; EMACSCMD: <form>
-    ;;    Executes the lisp form inside a save-excursion, saves the result as 
a lisp object.
-    ;;
-    ;; EMACSRESULT: <form>
-    ;;    point is moved to end of line, <form> is evaluated inside
-    ;;    save-excursion and compared (using `equal') with the result
-    ;;    of the previous EMACSCMD, and the test fails if they don't
-    ;;    match.
-    ;;
-    ;; EMACS_SKIP_UNLESS: <form>
-    ;;   skip entire test if form evals nil
-    ;;
-    ;; EMACSDEBUG: <form>
-    ;;    Eval form, display result. Also used for setting breakpoint.
-
-    (goto-char (point-min))
-    (while (and (not skip-cmds)
-               (re-search-forward (concat comment-start "EMACS\\([^:]+\\):") 
nil t))
-      (cond
-       ((string= (match-string 1) "CMD")
-       (looking-at ".*$")
-       (save-excursion
-         (setq cmd-line (line-number-at-pos)
-               last-cmd (match-string 0)
-               last-result
-               (condition-case-unless-debug err
-                   (eval (car (read-from-string last-cmd)))
-                 (error
-                    (setq error-count (1+ error-count))
-                    (message "%s:%d: command: %s"
-                             (buffer-file-name) cmd-line last-cmd)
-                    (message "%s:%d: %s: %s"
-                             (buffer-file-name)
-                             (line-number-at-pos)
-                             (car err)
-                             (cdr err))))
-               )
-         ;; save-excursion does not preserve mapping of buffer to
-         ;; window, but some tests depend on that. For example,
-         ;; execute-kbd-macro doesn’t work properly if current buffer
-         ;; is not visible..
-         (pop-to-buffer test-buffer)))
-
-       ((string= (match-string 1) "RESULT")
-       (looking-at ".*$")
-       (setq expected-result (save-excursion (end-of-line 1) (eval (car 
(read-from-string (match-string 0))))))
-       (unless (equal expected-result last-result)
-         (when debug-on-error (debug))
-         (setq error-count (1+ error-count))
-         (message
-          (concat
-           (format "error: %s:%d:\n" (buffer-file-name) (line-number-at-pos))
-           (format "Result of '%s' does not match.\nGot    '%s',\nexpect '%s'"
-                   last-cmd
-                   last-result
-                   expected-result)
-           ))))
-
-       ((string= (match-string 1) "_SKIP_UNLESS")
-       (looking-at ".*$")
-       (unless (eval (car (read-from-string (match-string 0))))
-         (setq skip-cmds t)
-         (setq skip-reindent-test t)
-         (setq skip-recase-test t)
-         ;; We don’t set ‘skip-write’ t here, so the *.diff Make target 
succeeds.
-         ))
-
-       ((string= (match-string 1) "DEBUG")
-       (looking-at ".*$")
-       (message "DEBUG: %s:%d %s"
-                (current-buffer)
-                (line-number-at-pos)
-                (save-excursion
-                 (eval (car (read-from-string (match-string 0)))))))
-
-       (t
-       (setq error-count (1+ error-count))
-       (error (concat "Unexpected EMACS test command " (match-string 1))))))
-
-    (when (> error-count 0)
-      (error
-       "%s:%d: aborting due to previous errors (%d)"
-       (buffer-file-name) (line-number-at-pos (point)) error-count))
-    )
-
-  (when (not skip-reindent-test)
-    ;; Reindent the buffer
-    (message "indenting")
-
-    ;; first unindent; if the indentation rules do nothing, the test
-    ;; would pass, otherwise!  Only unindent by 1 column, so comments
-    ;; not currently in column 0 are still not in column 0, in case
-    ;; the mode supports a special case for comments in column 0.
-    (indent-rigidly (point-min) (point-max) -1)
-
-    ;; indent-region uses save-excursion, so we can't goto an error location
-    (indent-region (point-min) (point-max))
-
-    ;; Cleanup the buffer; indenting often leaves trailing whitespace;
-    ;; files must be saved without any.
-    (delete-trailing-whitespace)
-    )
-  )
-
-(defun run-test (file-name)
-  "Run an indentation and casing test on FILE-NAME."
-  (interactive "f")
-  ;; we'd like to run emacs from a makefile as:
-  ;;
-  ;; emacs -Q --batch -l runtest.el -f run-test-here <filename>
-  ;;
-  ;; However, the function specified with -f is run _before_
-  ;; <filename> is visited. So we try this instead:
-  ;;
-  ;; emacs -Q --batch -l runtest.el --eval '(run-test "<filename>")'
-  ;;
-  ;; And then we discover that processes spawned with start-process
-  ;; don't run when emacs is in --batch mode. So we try this:
-  ;;
-  ;; emacs -Q -l runtest.el --eval '(progn (run-test 
"<filename>")(kill-emacs))'
-  ;;
-  ;; Then we have problems with font lock defaulting to jit-lock; that
-  ;; screws up font-lock tests because the test runs before jit-lock
-  ;; does. This forces default font-lock, which fontifies the whole
-  ;; buffer when (font-lock-fontify-buffer) is called, which tests
-  ;; that rely on font-lock do explicitly.
-  (setq font-lock-support-mode nil)
-
-  (let ((dir default-directory))
-    (find-file file-name) ;; sets default-directory
-
-    (when (eq major-mode 'fundamental-mode)
-      ;; Running a grammar in test/wisi
-      (add-to-list 'load-path (expand-file-name "."))
-      (wisi-tests-setup (file-name-sans-extension (file-name-nondirectory 
file-name))))
-
-    (run-test-here)
-
-    (unless skip-write
-      ;; Write the result file; makefile will diff.
-      (when skip-reindent-test
-       ;; user sets skip-reindent-test when testing interactive editing
-       ;; commands, so the diff would fail. Revert to the original file,
-       ;; save a copy of that.
-       (revert-buffer t t))
-
-      (delete-trailing-whitespace)
-      (write-file (concat dir (file-name-nondirectory file-name) ".tmp")) )
-    )
-  )
-
-(provide 'wisi-run-indent-test)
-;; end of file
+;;; wisi-run-indent-test.el --- utils for automating indentation and casing 
tests
+;;
+;; Copyright (C) 2018 - 2019  Free Software Foundation, Inc.
+;;
+;; This file is part of GNU Emacs.
+;;
+;; GNU Emacs 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 3 of the License, or
+;; (at your option) any later version.
+;;
+;; GNU Emacs 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+(require 'wisi-tests)
+
+;; user can set these to t in an EMACSCMD
+(defvar skip-cmds nil)
+(defvar skip-reindent-test nil)
+(defvar skip-recase-test nil)
+(defvar skip-write nil)
+
+(defun test-in-comment-p ()
+  (nth 4 (syntax-ppss)))
+
+(defun test-face (token face)
+  "Test if all of TOKEN in next code line has FACE.
+FACE may be a list."
+  (save-excursion
+    (when (test-in-comment-p)
+      (beginning-of-line); forward-comment doesn't move if inside a comment!
+      (forward-comment (point-max)))
+    (condition-case err
+       (search-forward token (line-end-position 5))
+      (error
+       (error "can't find '%s'" token)))
+
+    (save-match-data
+      (wisi-validate-cache (line-beginning-position 0) (line-end-position 5) 
nil 'face))
+
+    ;; We don't use face-at-point, because it doesn't respect
+    ;; font-lock-face set by the parser! And we want to check for
+    ;; conflicts between font-lock-keywords and the parser.
+
+    ;; font-lock-keywords sets 'face property, parser sets 'font-lock-face.
+
+    ;; In emacs < 27, if we use (get-text-property (point) 'face), we
+    ;; also get 'font-lock-face, but not vice-versa. So we have to use
+    ;; text-properties-at to check for both.
+    (let* ((token (match-string 0))
+          (props (text-properties-at (match-beginning 0)))
+          key
+          token-face)
+
+      (cond
+       ((plist-get props 'font-lock-face)
+       (setq key 'font-lock-face)
+       (setq token-face (plist-get props 'font-lock-face)))
+
+       ((plist-get props 'face)
+       (setq key 'face)
+       (setq token-face (plist-get props 'face)))
+       )
+
+      (when (and (memq 'font-lock-face props)
+                (memq 'face props))
+       (describe-text-properties (match-beginning 0))
+       (error "mixed font-lock-keyword and parser faces for '%s'" token))
+
+      (unless (not (text-property-not-all 0 (length token) key token-face 
token))
+       (error "mixed faces, expecting %s for '%s'" face token))
+
+      (unless (or (and (listp face)
+                      (memq token-face face))
+                 (eq token-face face))
+       (error "found face %s, expecting %s for '%s'" token-face face token))
+    )))
+
+(defun test-face-1 (search token face)
+  "Move to end of comment, search for SEARCH, call `test-face'."
+  (save-excursion
+    (when (test-in-comment-p)
+      (beginning-of-line); forward-comment doesn't move if inside a comment!
+      (forward-comment (point-max)))
+    (search-forward search)
+    (test-face token face)
+    ))
+
+(defun test-cache-class (token class)
+  "Test if TOKEN in next code line has wisi-cache with class CLASS."
+  (save-excursion
+    (wisi-validate-cache (line-beginning-position 0) (line-end-position 3) nil 
'navigate)
+    (beginning-of-line); forward-comment doesn't move if inside a comment!
+    (forward-comment (point-max))
+    (condition-case err
+       (search-forward token (line-end-position 5))
+      (error
+       (error "can't find '%s'" token)))
+
+    (let ((cache (get-text-property (match-beginning 0) 'wisi-cache)))
+
+      (unless cache (error "no cache"))
+      (unless (eq (wisi-cache-class cache) class)
+       (error "expecting class %s, found '%s'" class (wisi-cache-class cache)))
+    )))
+
+(defun test-cache-containing (containing contained)
+  "Test if CONTAINING in next code line has wisi-cache with that contains 
CONTAINED."
+  (save-excursion
+    (wisi-validate-cache (line-beginning-position 0) (line-end-position 3) nil 
'navigate)
+    (beginning-of-line)
+    (forward-comment (point-max))
+    (let (containing-pos contained-cache)
+      (search-forward containing (line-end-position 5))
+      (setq containing-pos (match-beginning 0))
+
+      (search-forward contained (line-end-position 5))
+      (setq contained-cache (get-text-property (match-beginning 0) 
'wisi-cache))
+
+      (unless contained-cache (error "no cache on %s" contained))
+      (unless (= containing-pos (wisi-cache-containing contained-cache))
+       (error "expecting %d, got %d" containing-pos (wisi-cache-containing 
contained-cache)))
+    )))
+
+(defvar test-refactor-markers nil
+  "Stores positions altered by `test-refactor-1' for `test-refactor-2'.
+Each item is a list (ACTION PARSE-BEGIN PARSE-END EDIT-BEGIN)")
+
+(defun test-refactor-1 (action inverse-action search-string refactor-string)
+  (beginning-of-line)
+  (forward-comment (point-max)) ;; forward-comment does not work from inside 
comment
+  (search-forward search-string (line-end-position 7))
+  (wisi-validate-cache (line-end-position -7) (line-end-position 7) t 
'navigate)
+  (search-forward refactor-string (line-end-position 7))
+  (let* ((edit-begin (match-beginning 0))
+        (cache (wisi-goto-statement-start))
+        (parse-begin (point))
+        (parse-end (wisi-cache-end cache)))
+    (setq parse-end (+ parse-end (wisi-cache-last (wisi-get-cache 
(wisi-cache-end cache)))))
+    (push (list
+          inverse-action
+          (copy-marker parse-begin nil)
+          (copy-marker parse-end nil)
+          (copy-marker edit-begin nil))
+         test-refactor-markers)
+    (wisi-refactor wisi--parser action parse-begin parse-end edit-begin)
+    ))
+
+(defun test-refactor-inverse ()
+  "Reverse refactors done by recent set of `test-refactor-1'."
+  (save-excursion
+    (condition-case-unless-debug nil
+       (dolist (item test-refactor-markers)
+         (wisi-refactor wisi--parser
+                        (nth 0 item)
+                        (marker-position (nth 1 item))
+                        (marker-position (nth 2 item))
+                        (marker-position (nth 3 item))))
+      (error nil))
+    (setq test-refactor-markers nil)))
+
+(defun run-test-here ()
+  "Run an indentation and casing test on the current buffer."
+  (interactive)
+  (setq indent-tabs-mode nil)
+  (setq jit-lock-context-time 0.0);; for test-face
+
+  (let ((error-count 0)
+       (test-buffer (current-buffer))
+       cmd-line
+       last-result last-cmd expected-result)
+    ;; Look for EMACS* comments in the file:
+    ;;
+    ;; EMACSCMD: <form>
+    ;;    Executes the lisp form inside a save-excursion, saves the result as 
a lisp object.
+    ;;
+    ;; EMACSRESULT: <form>
+    ;;    point is moved to end of line, <form> is evaluated inside
+    ;;    save-excursion and compared (using `equal') with the result
+    ;;    of the previous EMACSCMD, and the test fails if they don't
+    ;;    match.
+    ;;
+    ;; EMACS_SKIP_UNLESS: <form>
+    ;;   skip entire test if form evals nil
+    ;;
+    ;; EMACSDEBUG: <form>
+    ;;    Eval form, display result. Also used for setting breakpoint.
+
+    (goto-char (point-min))
+    (while (and (not skip-cmds)
+               (re-search-forward (concat comment-start "EMACS\\([^:]+\\):") 
nil t))
+      (cond
+       ((string= (match-string 1) "CMD")
+       (looking-at ".*$")
+       (save-excursion
+         (setq cmd-line (line-number-at-pos)
+               last-cmd (match-string 0)
+               last-result
+               (condition-case-unless-debug err
+                   (eval (car (read-from-string last-cmd)))
+                 (error
+                    (setq error-count (1+ error-count))
+                    (message "%s:%d: command: %s"
+                             (buffer-file-name) cmd-line last-cmd)
+                    (message "%s:%d: %s: %s"
+                             (buffer-file-name)
+                             (line-number-at-pos)
+                             (car err)
+                             (cdr err))))
+               )
+         ;; save-excursion does not preserve mapping of buffer to
+         ;; window, but some tests depend on that. For example,
+         ;; execute-kbd-macro doesn’t work properly if current buffer
+         ;; is not visible..
+         (pop-to-buffer test-buffer)))
+
+       ((string= (match-string 1) "RESULT")
+       (looking-at ".*$")
+       (setq expected-result (save-excursion (end-of-line 1) (eval (car 
(read-from-string (match-string 0))))))
+       (unless (equal expected-result last-result)
+         (when debug-on-error (debug))
+         (setq error-count (1+ error-count))
+         (message
+          (concat
+           (format "error: %s:%d:\n" (buffer-file-name) (line-number-at-pos))
+           (format "Result of '%s' does not match.\nGot    '%s',\nexpect '%s'"
+                   last-cmd
+                   last-result
+                   expected-result)
+           ))))
+
+       ((string= (match-string 1) "_SKIP_UNLESS")
+       (looking-at ".*$")
+       (unless (eval (car (read-from-string (match-string 0))))
+         (setq skip-cmds t)
+         (setq skip-reindent-test t)
+         (setq skip-recase-test t)
+         ;; We don’t set ‘skip-write’ t here, so the *.diff Make target 
succeeds.
+         ))
+
+       ((string= (match-string 1) "DEBUG")
+       (looking-at ".*$")
+       (message "DEBUG: %s:%d %s"
+                (current-buffer)
+                (line-number-at-pos)
+                (save-excursion
+                 (eval (car (read-from-string (match-string 0)))))))
+
+       (t
+       (setq error-count (1+ error-count))
+       (error (concat "Unexpected EMACS test command " (match-string 1))))))
+
+    (when (> error-count 0)
+      (error
+       "%s:%d: aborting due to previous errors (%d)"
+       (buffer-file-name) (line-number-at-pos (point)) error-count))
+    )
+
+  (when (not skip-reindent-test)
+    ;; Reindent the buffer
+    (message "indenting")
+
+    ;; first unindent; if the indentation rules do nothing, the test
+    ;; would pass, otherwise!  Only unindent by 1 column, so comments
+    ;; not currently in column 0 are still not in column 0, in case
+    ;; the mode supports a special case for comments in column 0.
+    (indent-rigidly (point-min) (point-max) -1)
+
+    ;; indent-region uses save-excursion, so we can't goto an error location
+    (indent-region (point-min) (point-max))
+
+    ;; Cleanup the buffer; indenting often leaves trailing whitespace;
+    ;; files must be saved without any.
+    (delete-trailing-whitespace)
+    )
+  )
+
+(defun run-test (file-name)
+  "Run an indentation and casing test on FILE-NAME."
+  (interactive "f")
+  ;; we'd like to run emacs from a makefile as:
+  ;;
+  ;; emacs -Q --batch -l runtest.el -f run-test-here <filename>
+  ;;
+  ;; However, the function specified with -f is run _before_
+  ;; <filename> is visited. So we try this instead:
+  ;;
+  ;; emacs -Q --batch -l runtest.el --eval '(run-test "<filename>")'
+  ;;
+  ;; And then we discover that processes spawned with start-process
+  ;; don't run when emacs is in --batch mode. So we try this:
+  ;;
+  ;; emacs -Q -l runtest.el --eval '(progn (run-test 
"<filename>")(kill-emacs))'
+  ;;
+  ;; Then we have problems with font lock defaulting to jit-lock; that
+  ;; screws up font-lock tests because the test runs before jit-lock
+  ;; does. This forces default font-lock, which fontifies the whole
+  ;; buffer when (font-lock-fontify-buffer) is called, which tests
+  ;; that rely on font-lock do explicitly.
+  (setq font-lock-support-mode nil)
+
+  (let ((dir default-directory))
+    (find-file file-name) ;; sets default-directory
+
+    (when (eq major-mode 'fundamental-mode)
+      ;; Running a grammar in test/wisi
+      (add-to-list 'load-path (expand-file-name "."))
+      (wisi-tests-setup (file-name-sans-extension (file-name-nondirectory 
file-name))))
+
+    (run-test-here)
+
+    (unless skip-write
+      ;; Write the result file; makefile will diff.
+      (when skip-reindent-test
+       ;; user sets skip-reindent-test when testing interactive editing
+       ;; commands, so the diff would fail. Revert to the original file,
+       ;; save a copy of that.
+       (revert-buffer t t))
+
+      (delete-trailing-whitespace)
+      (write-file (concat dir (file-name-nondirectory file-name) ".tmp")) )
+    )
+  )
+
+(provide 'wisi-run-indent-test)
+;; end of file
diff --git a/wisi-tests.el b/wisi-tests.el
index d1468ef..b730cf1 100644
--- a/wisi-tests.el
+++ b/wisi-tests.el
@@ -24,7 +24,7 @@
 (require 'cl-lib)
 (require 'wisi)
 
-(defvar wisi-test-parser 'elisp
+(defvar wisi-test-parser 'process
   "Set to ’process to test external process parser.")
 
 (defvar test-syntax-table
@@ -81,27 +81,6 @@
   (syntax-ppss-flush-cache (point-min));; force re-evaluate with hook.
 
   (cl-ecase wisi-test-parser
-    (elisp
-     (require 'wisi-elisp-parse)
-     (let* ((grammar-file-root (concat grammar-name "-lalr-elisp"))
-           (grammar-file-name (concat grammar-file-root ".el"))
-           (grammar-file-abs (locate-file grammar-file-name load-path)))
-       (unless grammar-file-abs
-        (error "can’t find ’%s’ on ’%s’" grammar-file-name load-path))
-       (require (intern grammar-file-root)))
-
-     (wisi-setup
-      :indent-calculate nil
-      :post-indent-fail nil
-      :parser (wisi-make-elisp-parser
-              (symbol-value (intern-soft (concat grammar-name 
"-lalr-elisp-parse-table")))
-              `wisi-forward-token)
-      :lexer (wisi-make-elisp-lexer
-             :token-table-raw (symbol-value (intern-soft (concat grammar-name 
"-lalr-elisp-token-table-raw")))
-             :keyword-table-raw (symbol-value (intern-soft (concat 
grammar-name "-lalr-elisp-keyword-table-raw")))
-             :string-quote-escape-doubled nil
-             :string-quote-escape nil)))
-
     (process
      (require 'wisi-process-parse)
      (require (intern (concat grammar-name "-process"))) ;; generated by 
wisi-generate
@@ -118,12 +97,7 @@
        :exec-file (concat grammar-name "_wisi_parse.exe")
        :face-table (symbol-value (intern-soft (concat grammar-name 
"-process-face-table")))
        :token-table (symbol-value (intern-soft (concat grammar-name 
"-process-token-table")))
-       ))
-      :lexer (wisi-make-elisp-lexer
-             :token-table-raw (symbol-value (intern-soft (concat grammar-name 
"-lalr-elisp-token-table-raw")))
-             :keyword-table-raw (symbol-value (intern-soft (concat 
grammar-name "-lalr-elisp-keyword-table-raw")))
-             :string-quote-escape-doubled nil
-             :string-quote-escape nil))
+       )))
      (setq wisi-mckenzie-disable nil)
      )
     )
diff --git a/wisi.adb b/wisi.adb
index b65bbee..1ba7a4f 100644
--- a/wisi.adb
+++ b/wisi.adb
@@ -19,23 +19,13 @@ pragma License (Modified_GPL);
 
 with Ada.Exceptions;
 with Ada.Strings.Bounded;
+with Ada.Strings.Unbounded;
 with Ada.Text_IO;
 with SAL;
 with WisiToken.Semantic_Checks;
 package body Wisi is
    use WisiToken;
 
-   Navigate_Cache_Code  : constant String := "1";
-   Face_Property_Code   : constant String := "2";
-   Indent_Code          : constant String := "3";
-   Lexer_Error_Code     : constant String := "4";
-   Parser_Error_Code    : constant String := "5";
-   Check_Error_Code     : constant String := "6";
-   Recover_Code         : constant String := "7 ";
-   End_Code             : constant String := "8";
-   Name_Property_Code   : constant String := "9";
-   Language_Action_Code : constant String := "10 ";
-
    Chars_Per_Int : constant Integer := Integer'Width;
 
    ----------
@@ -380,114 +370,133 @@ package body Wisi is
       Descriptor                    : in WisiToken.Descriptor;
       Embedded_Quote_Escape_Doubled : in Boolean)
    is
-      use Ada.Containers;
       use Ada.Strings.Unbounded;
       use Parse.LR;
+      use Parse.LR.Config_Op_Arrays, Parse.LR.Config_Op_Array_Refs;
+
+      --  Output is a sequence of edit regions; each is:
+      --  [edit-pos [inserted token-ids] [deleted token-ids] deleted-region]
+
+      type State_Label is
+        (None,     -- not started yet
+         Inserted, -- edit-pos, some insert ids appended
+         Deleted); -- some delete ids appended
+
+      State : State_Label := None;
+      --  State of the current edit region.
+
+      Line           : Unbounded_String := To_Unbounded_String ("[");
+      Deleted_Region : Buffer_Region    := Null_Buffer_Region;
+      Last_Deleted : Config_Op (Delete) := (Delete, Invalid_Token_ID, 
Invalid_Token_Index);
+
+      procedure Start_Edit_Region (Op : in Insert_Delete_Op)
+      is begin
+         Append (Line, "[");
+         Append (Line, Buffer_Pos'Image (Terminals 
(WisiToken.Parse.LR.Token_Index (Op)).Char_Region.First));
+         Append (Line, "[");
+      end Start_Edit_Region;
 
-      Line    : Unbounded_String := To_Unbounded_String ("[");
-      Last_Op : Config_Op        := (Fast_Forward, WisiToken.Token_Index'Last);
+      function Deleted_Region_Image return String
+      is begin
+         return "(" & Deleted_Region.First'Image & " . " & Buffer_Pos'Image 
(Deleted_Region.Last + 1) & ")";
+      end Deleted_Region_Image;
 
+      procedure Terminate_Edit_Region
+      is begin
+         case State is
+         when None =>
+            null;
+         when Inserted =>
+            Append (Line, "][]" & Deleted_Region_Image & "]");
+         when Deleted =>
+            Append (Line, "]" & Deleted_Region_Image & "]");
+         end case;
+         Deleted_Region := Null_Buffer_Region;
+      end Terminate_Edit_Region;
    begin
       if Trace_Action > Detail then
          Ada.Text_IO.Put_Line (";; " & Parse.LR.Image (Item.Ops, Descriptor));
       end if;
 
       Append (Line, Recover_Code);
-      if Item.Ops.Length = 0 then
-         Append (Line, "]");
+      for I in First_Index (Item.Ops) .. Last_Index (Item.Ops) loop
+         declare
+            Op : Config_Op renames Constant_Ref (Item.Ops, I);
+         begin
+            case Op.Op is
+            when Fast_Forward =>
+               Terminate_Edit_Region;
+               State := None;
 
-      else
-         for I in Item.Ops.First_Index .. Item.Ops.Last_Index loop
-            declare
-               Op : Config_Op renames Item.Ops (I);
-            begin
-               case Op.Op is
-               when Fast_Forward =>
-                  if Last_Op.Op in Insert then
-                     Append (Line, "][]]");
-                  elsif Last_Op.Op in Delete then
-                     Append (Line, "]]");
-                  end if;
+            when Undo_Reduce | Push_Back =>
+               null;
 
-                  Last_Op := Op;
+            when Insert =>
+               case State is
+               when None =>
+                  Start_Edit_Region (Op);
 
-               when Undo_Reduce | Push_Back =>
+               when Inserted =>
                   null;
 
-               when Insert =>
-                  if Last_Op.Op = Fast_Forward then
-                     Append (Line, "[");
-                     Append (Line, Buffer_Pos'Image (Terminals 
(Op.Ins_Token_Index).Char_Region.First));
-                     Append (Line, "[");
-
-                  elsif Last_Op.Op = Delete then
-                     Append (Line, "]][");
-                     Append (Line, Buffer_Pos'Image (Terminals 
(Op.Ins_Token_Index).Char_Region.First));
-                     Append (Line, "[");
+               when Deleted =>
+                  Terminate_Edit_Region;
+                  Start_Edit_Region (Op);
 
-                  else
-                     --  Last_Op.Op = Insert
-                     null;
-                  end if;
-                  Append (Line, Token_ID'Image (Op.Ins_ID));
-
-                  Last_Op := Op;
+               end case;
+               Append (Line, Token_ID'Image (Op.Ins_ID));
+               State := Inserted;
 
-               when Delete =>
-                  declare
-                     Skip : Boolean := False;
-                  begin
-                     if Last_Op.Op = Fast_Forward then
-                        Append (Line, "[");
-                        Append (Line, Buffer_Pos'Image (Terminals 
(Op.Del_Token_Index).Char_Region.First));
-                        Append (Line, "[][");
-
-                     elsif Last_Op.Op = Insert then
-                        Append (Line, "][");
-
-                     elsif Last_Op.Op = Delete then
-                        if Embedded_Quote_Escape_Doubled and then
-                          ((Last_Op.Del_ID = Descriptor.String_1_ID and 
Op.Del_ID = Descriptor.String_1_ID) or
-                             (Last_Op.Del_ID = Descriptor.String_2_ID and 
Op.Del_ID = Descriptor.String_2_ID))
-                        then
-                           declare
-                              Tok_1 : Augmented_Token renames Terminals 
(Last_Op.Del_Token_Index);
-                              Tok_2 : Augmented_Token renames Terminals 
(Op.Del_Token_Index);
-                           begin
-                              if Tok_1.Char_Region.Last + 1 = 
Tok_2.Char_Region.First then
-                                 --  Buffer text was '"""', lexer repair 
changed it to '""""'. The
-                                 --  repaired text looks like a single string 
with an embedded quote.
-                                 --  But here, it is two STRING_LITERAL 
tokens. Don't send the second
-                                 --  delete to elisp. See 
test/ada_mode-recover_string_quote_1.adb
-                                 Skip := True;
-                              end if;
-                           end;
-                        end if;
+            when Delete =>
+               Deleted_Region := Deleted_Region and Terminals 
(Op.Del_Token_Index).Char_Region;
+               declare
+                  Skip : Boolean := False;
+               begin
+                  case State is
+                  when None =>
+                     Start_Edit_Region (Op);
+                     Append (Line, "][");
 
-                     end if;
+                  when Inserted =>
+                     Append (Line, "][");
 
-                     if not Skip then
-                        Append (Line, Token_ID'Image (Op.Del_ID));
+                  when Deleted =>
+                     if Embedded_Quote_Escape_Doubled and then
+                       ((Last_Deleted.Del_ID = Descriptor.String_1_ID and 
Op.Del_ID = Descriptor.String_1_ID) or
+                          (Last_Deleted.Del_ID = Descriptor.String_2_ID and 
Op.Del_ID = Descriptor.String_2_ID))
+                     then
+                        declare
+                           Tok_1 : Augmented_Token renames Terminals 
(Last_Deleted.Del_Token_Index);
+                           Tok_2 : Augmented_Token renames Terminals 
(Op.Del_Token_Index);
+                        begin
+                           if Tok_1.Char_Region.Last + 1 = 
Tok_2.Char_Region.First then
+                              --  Buffer text was '"""', lexer repair changed 
it to '""""'. The
+                              --  repaired text looks like a single string 
with an embedded quote.
+                              --  But here, it is two STRING_LITERAL tokens. 
Don't send the second
+                              --  delete to elisp. See 
test/ada_mode-recover_string_quote_1.adb
+                              Skip := True;
+                           end if;
+                        end;
                      end if;
-                  end;
-                  Last_Op := Op;
-               end case;
-            end;
-         end loop;
-
-         case Last_Op.Op is
-         when Fast_Forward =>
-            Append (Line, "]");
+                  end case;
+                  State := Deleted;
 
-         when Undo_Reduce | Push_Back =>
-            null;
+                  if not Skip then
+                     Append (Line, Token_ID'Image (Op.Del_ID));
+                  end if;
+               end;
+               Last_Deleted := Op;
+            end case;
+         end;
+      end loop;
 
-         when Insert =>
-            Append (Line, "][]]]");
-         when Delete =>
-            Append (Line, "]]]");
-         end case;
-      end if;
+      case State is
+      when None =>
+         null;
+      when Inserted | Deleted =>
+         Terminate_Edit_Region;
+      end case;
+      Append (Line, "]");
       Ada.Text_IO.Put_Line (To_String (Line));
    end Put;
 
@@ -587,8 +596,9 @@ package body Wisi is
 
    procedure Initialize
      (Data              : in out Parse_Data_Type;
+      Lexer             : in     WisiToken.Lexer.Handle;
       Descriptor        : access constant WisiToken.Descriptor;
-      Source_File_Name  : in     String;
+      Base_Terminals    : in     Base_Token_Array_Access;
       Post_Parse_Action : in     Post_Parse_Action_Type;
       Begin_Line        : in     Line_Number_Type;
       End_Line          : in     Line_Number_Type;
@@ -606,8 +616,9 @@ package body Wisi is
         (First   => Begin_Line,
          Last    => End_Line + 1);
 
+      Data.Lexer             := Lexer;
       Data.Descriptor        := Descriptor;
-      Data.Source_File_Name  := +Source_File_Name;
+      Data.Base_Terminals    := Base_Terminals;
       Data.Post_Parse_Action := Post_Parse_Action;
 
       case Post_Parse_Action is
@@ -658,7 +669,7 @@ package body Wisi is
 
    function Source_File_Name (Data : in Parse_Data_Type) return String
    is begin
-      return -Data.Source_File_Name;
+      return Data.Lexer.File_Name;
    end Source_File_Name;
 
    function Post_Parse_Action (Data : in Parse_Data_Type) return 
Post_Parse_Action_Type
@@ -942,33 +953,30 @@ package body Wisi is
       Tokens  : in     Syntax_Trees.Valid_Node_Index_Array;
       Params  : in     Statement_Param_Array)
    is
-      First_Item         : Boolean        := True;
-      Start_Set          : Boolean        := False;
-      Override_Start_Set : Boolean        := False;
-      Containing_Pos     : Nil_Buffer_Pos := Nil; --  wisi first-keyword-pos
+      Nonterm_Tok        : constant Aug_Token_Ref := Get_Aug_Token (Data, 
Tree, Nonterm);
+      First_Item         : Boolean                := True;
+      Start_Set          : Boolean                := False;
+      Override_Start_Set : Boolean                := False;
+      Containing_Pos     : Nil_Buffer_Pos         := Nil;
    begin
       for Pair of Params loop
          if not (Pair.Index in Tokens'Range) then
-            declare
-               Nonterm_Tok : constant Aug_Token_Ref := Get_Aug_Token (Data, 
Tree, Nonterm);
-            begin
-               raise Fatal_Error with Error_Message
-                 (File_Name => -Data.Source_File_Name,
-                  Line      => Nonterm_Tok.Line,
-                  Column    => Nonterm_Tok.Column,
-                  Message   => "wisi-statement-action: " & Trimmed_Image 
(Tree.Production_ID (Nonterm)) &
-                    " token index" & SAL.Peek_Type'Image (Pair.Index) &
-                    " not in tokens range (" & SAL.Peek_Type'Image 
(Tokens'First) & " .." &
-                    SAL.Peek_Type'Image (Tokens'Last) & "); bad grammar 
action.");
-            end;
+            raise Fatal_Error with Error_Message
+              (File_Name => Data.Lexer.File_Name,
+               Line      => Nonterm_Tok.Line,
+               Column    => Nonterm_Tok.Column,
+               Message   => "wisi-statement-action: " & Trimmed_Image 
(Tree.Production_ID (Nonterm)) &
+                 " token index" & SAL.Peek_Type'Image (Pair.Index) &
+                 " not in tokens range (" & SAL.Peek_Type'Image (Tokens'First) 
& " .." &
+                 SAL.Peek_Type'Image (Tokens'Last) & "); bad grammar action.");
 
          elsif Tree.Byte_Region (Tokens (Pair.Index)) /= Null_Buffer_Region 
then
             declare
                use all type WisiToken.Syntax_Trees.Node_Label;
                Token  : constant Aug_Token_Ref :=
-                  (if Pair.Class = Statement_End and then
+                 (if Pair.Class = Statement_End and then
                     Tree.Label (Tokens (Pair.Index)) = 
WisiToken.Syntax_Trees.Nonterm
-                  then Data.Terminals.Variable_Ref (Tree.Max_Terminal_Index 
(Tokens (Pair.Index)))
+                  then To_Aug_Token_Ref (Data.Terminals 
(Tree.Max_Terminal_Index (Tokens (Pair.Index))))
                   else Get_Aug_Token (Data, Tree, Tokens (Pair.Index)));
 
                Cache_Pos : constant Buffer_Pos         := 
Token.Char_Region.First;
@@ -1014,6 +1022,28 @@ package body Wisi is
                   if Override_Start_Set or Pair.Class = Statement_Start then
                      Override_Start_Set := False;
                      Containing_Pos     := (True, Token.Char_Region.First);
+
+                     --  Set containing on all contained caches
+                     declare
+                        use Navigate_Cache_Trees;
+                        Iterator : constant Navigate_Cache_Trees.Iterator := 
Data.Navigate_Caches.Iterate;
+                        Cursor   : Navigate_Cache_Trees.Cursor            := 
Find_In_Range
+                          (Iterator, Ascending, Nonterm_Tok.Char_Region.First 
+ 1, -- don't set containing on start
+                           Nonterm_Tok.Char_Region.Last);
+                     begin
+                        loop
+                           exit when not Has_Element (Cursor);
+                           declare
+                              Cache : Navigate_Cache_Type renames 
Data.Navigate_Caches (Cursor);
+                           begin
+                              if not Cache.Containing_Pos.Set then
+                                 Cache.Containing_Pos := Containing_Pos;
+                              end if;
+                              exit when Nonterm_Tok.Char_Region.Last < 
Cache.Pos + 1;
+                           end;
+                           Cursor := Iterator.Next (Cursor);
+                        end loop;
+                     end;
                   end if;
                end if;
 
@@ -1045,7 +1075,7 @@ package body Wisi is
             Token : constant Aug_Token_Ref := Get_Aug_Token (Data, Tree, 
Tokens (Tokens'First));
          begin
             raise Fatal_Error with Error_Message
-              (File_Name => -Data.Source_File_Name,
+              (File_Name => Data.Lexer.File_Name,
                Line      => Token.Line,
                Column    => Token.Column,
                Message   => "wisi-name-action: " & Trimmed_Image 
(Tree.Production_ID (Nonterm)) & " name (" &
@@ -1069,7 +1099,7 @@ package body Wisi is
             return;
          elsif Has_Element (Cursor) then
             raise Fatal_Error with Error_Message
-              (File_Name => -Data.Source_File_Name,
+              (File_Name => Data.Lexer.File_Name,
                Line      => Name_Token.Line,
                Column    => Name_Token.Column,
                Message   => "wisi-name-action: name set twice.");
@@ -1079,112 +1109,6 @@ package body Wisi is
       end;
    end Name_Action;
 
-   procedure Containing_Action
-     (Data       : in out Parse_Data_Type;
-      Tree       : in     Syntax_Trees.Tree;
-      Nonterm    : in     Syntax_Trees.Valid_Node_Index;
-      Tokens     : in     Syntax_Trees.Valid_Node_Index_Array;
-      Containing : in     Positive_Index_Type;
-      Contained  : in     Positive_Index_Type)
-   is
-      use all type Syntax_Trees.Node_Label;
-      pragma Unreferenced (Nonterm);
-
-      --  [2] wisi-containing-action.
-      --
-      --  Compute as much as possible with virtual tokens; see
-      --  test/format_paramlist.adb
-   begin
-      if Tree.Label (Tokens (Containing)) = Syntax_Trees.Virtual_Terminal or
-        Tree.Label (Tokens (Contained)) = Syntax_Trees.Virtual_Terminal
-      then
-         return;
-      end if;
-
-      declare
-         use Navigate_Cache_Trees;
-         Containing_Tok    : constant Aug_Token_Ref := Get_Aug_Token (Data, 
Tree, Tokens (Containing));
-         Containing_Region : Buffer_Region renames Containing_Tok.Char_Region;
-         Contained_Tok     : constant Aug_Token_Ref := Get_Aug_Token (Data, 
Tree, Tokens (Contained));
-         Contained_Region  : Buffer_Region renames Contained_Tok.Char_Region;
-         Iterator          : constant Navigate_Cache_Trees.Iterator := 
Data.Navigate_Caches.Iterate;
-         Cursor            : Navigate_Cache_Trees.Cursor;
-         Mark              : constant Buffer_Pos                    := 
Containing_Region.First;
-      begin
-         if Containing_Region = Null_Buffer_Region then
-            if Tree.Is_Virtual (Tokens (Containing)) then
-               return;
-            else
-               raise Fatal_Error with Error_Message
-                 (File_Name => -Data.Source_File_Name,
-                  Line      => Containing_Tok.Line,
-                  Column    => Containing_Tok.Column,
-                  Message   => "wisi-containing-action: containing-region " &
-                    Image (Containing_Tok.ID, Data.Descriptor.all) &
-                    " is empty. grammar error; bad action.");
-            end if;
-         end if;
-
-         if not Data.Navigate_Caches.Present (Containing_Region.First) then
-            raise Fatal_Error with Error_Message
-              (File_Name => -Data.Source_File_Name,
-               Line      => Containing_Tok.Line,
-               Column    => Containing_Tok.Column,
-               Message   => "wisi-containing-action: containing token " &
-                 Image (Containing_Tok.ID, Data.Descriptor.all) &
-                 " has no cache. grammar error; missing action.");
-         end if;
-
-         if Contained_Tok.Char_Region /= Null_Buffer_Region then
-            --  Contained region is nil in an empty production.
-            Cursor := Previous (Iterator, Contained_Tok.Char_Region.Last);
-
-            while Has_Element (Cursor) loop
-               declare
-                  Cache : Navigate_Cache_Type renames Variable_Ref 
(Data.Navigate_Caches, Cursor).Element.all;
-               begin
-
-                  exit when Cache.Pos < Contained_Region.First or
-                    (Containing_Region.First = Contained_Region.First and
-                       Cache.Pos <= Contained_Region.First);
-
-                  --  Skip blocks that are already marked.
-
-                  if Cache.Containing_Pos.Set then
-                     Cursor := Find (Iterator, Cache.Containing_Pos.Item, 
Direction => Descending);
-                  else
-                     Cache.Containing_Pos := (True, Mark);
-                     Cursor := Previous (Iterator, Cursor);
-                  end if;
-
-               end;
-            end loop;
-         end if;
-      end;
-   end Containing_Action;
-
-   function "+" (Item : in Token_ID) return Token_ID_Lists.List
-   is begin
-      return Result : Token_ID_Lists.List do
-         Result.Append (Item);
-      end return;
-   end "+";
-
-   function "&" (List : in Token_ID_Lists.List; Item : in Token_ID) return 
Token_ID_Lists.List
-   is begin
-      return Result : Token_ID_Lists.List := List do
-         Result.Append (Item);
-      end return;
-   end "&";
-
-   function "&" (Left, Right : in Token_ID) return Token_ID_Lists.List
-   is begin
-      return Result : Token_ID_Lists.List do
-         Result.Append (Left);
-         Result.Append (Right);
-      end return;
-   end "&";
-
    procedure Motion_Action
      (Data    : in out Parse_Data_Type;
       Tree    : in     Syntax_Trees.Tree;
@@ -1194,50 +1118,64 @@ package body Wisi is
    is
       --  [2] wisi-motion-action
       use Navigate_Cache_Trees;
-      use all type Ada.Containers.Count_Type;
-
-      Start             : Nil_Buffer_Pos := (Set => False);
-      Prev_Keyword_Mark : Nil_Buffer_Pos := (Set => False);
-      Iter              : constant Iterator := Data.Navigate_Caches.Iterate;
-      Prev_Cache_Cur    : Cursor;
-      Cache_Cur         : Cursor;
-      Point             : Buffer_Pos;
-
-      function Match (IDs : in Token_ID_Lists.List) return Boolean
-      is
-         Cache : Navigate_Cache_Type renames Constant_Ref 
(Data.Navigate_Caches, Cache_Cur).Element.all;
-      begin
-         --  [2] wisi-elisp-parse--match-token
-         if (Start.Set and then Point = Start.Item) or else
-           Cache.Containing_Pos = Start
-         then
-            for ID of IDs loop
-               if ID = Cache.ID then
-                  return True;
-               end if;
-            end loop;
-         end if;
-         return False;
-      end Match;
 
+      Start          : Nil_Buffer_Pos    := (Set => False);
+      Iter           : constant Iterator := Data.Navigate_Caches.Iterate;
+      Prev_Cache_Cur : Cursor;
+      Cache_Cur      : Cursor;
    begin
+      if WisiToken.Trace_Action > Outline then
+         Ada.Text_IO.Put_Line
+           ("Motion_Action " & Image (Tree.ID (Nonterm), Data.Descriptor.all) 
& " " &
+              Image (Tree.Byte_Region (Nonterm)));
+      end if;
       for Param of Params loop
          if Tree.Byte_Region (Tokens (Param.Index)) /= Null_Buffer_Region then
             declare
+               use all type WisiToken.Syntax_Trees.Node_Label;
                Token  : constant Aug_Token_Ref := Get_Aug_Token (Data, Tree, 
Tokens (Param.Index));
                Region : constant Buffer_Region := Token.Char_Region;
+               Skip   : Boolean                := False;
             begin
                if not Start.Set then
                   Start := (True, Region.First);
                end if;
 
-               Cache_Cur := Find (Iter, Region.First, Direction => Ascending);
-               if not Has_Element (Cache_Cur) then
-                  if Tree.Is_Virtual (Tokens (Param.Index)) then
-                     return;
+               case Tree.Label (Tokens (Param.Index)) is
+               when Shared_Terminal =>
+                  Cache_Cur := Find (Iter, Region.First);
+               when Virtual_Terminal | Virtual_Identifier =>
+                  return;
+
+               when Syntax_Trees.Nonterm =>
+                  if Param.ID = Invalid_Token_ID then
+                     Cache_Cur := Find (Iter, Region.First);
+
                   else
+                     Skip      := True;
+                     Cache_Cur := Find_In_Range (Iter, Ascending, 
Region.First, Region.Last);
+                     loop
+                        exit when not Has_Element (Cache_Cur);
+                        if Data.Navigate_Caches (Cache_Cur).Pos > Region.Last 
then
+                           Cache_Cur := No_Element;
+                           exit;
+
+                        elsif Data.Navigate_Caches (Cache_Cur).ID = Param.ID 
and
+                          not Data.Navigate_Caches (Cache_Cur).Prev_Pos.Set
+                        then
+                           Skip := False;
+                           exit;
+                        end if;
+
+                        Cache_Cur := Next (Iter, Cache_Cur);
+                     end loop;
+                  end if;
+               end case;
+
+               if not Skip then
+                  if not Has_Element (Cache_Cur) then
                      raise Fatal_Error with Error_Message
-                       (File_Name => -Data.Source_File_Name,
+                       (File_Name => Data.Lexer.File_Name,
                         Line      => Token.Line,
                         Column    => Token.Column,
                         Message   => "wisi-motion-action: token " &
@@ -1245,42 +1183,38 @@ package body Wisi is
                           " has no cache; add to statement-action for " &
                           Trimmed_Image (Tree.Production_ID (Nonterm)) & ".");
                   end if;
-               end if;
-
-               if Param.IDs.Length = 0 then
-                  if Prev_Keyword_Mark.Set then
-                     Variable_Ref (Data.Navigate_Caches, 
Cache_Cur).Element.Prev_Pos      := Prev_Keyword_Mark;
-                     Variable_Ref (Data.Navigate_Caches, 
Prev_Cache_Cur).Element.Next_Pos := (True, Region.First);
-                  end if;
 
-                  Prev_Keyword_Mark := (True, Region.First);
-                  Prev_Cache_Cur    := Cache_Cur;
+                  if Has_Element (Prev_Cache_Cur) then
+                     declare
+                        Cache      : Navigate_Cache_Type renames 
Data.Navigate_Caches (Cache_Cur);
+                        Prev_Cache : Navigate_Cache_Type renames 
Data.Navigate_Caches (Prev_Cache_Cur);
+                     begin
+                        if not Cache.Prev_Pos.Set then
+                           Cache.Prev_Pos := (True, Prev_Cache.Pos);
+                           if WisiToken.Trace_Action > Detail then
+                              Ada.Text_IO.Put_Line ("   " & Cache.Pos'Image & 
" prev to " & Cache.Prev_Pos.Item'Image);
+                           end if;
+                        end if;
 
-               else
-                  Point := Region.First;
-                  loop
-                     exit when Point >= Region.Last;
-                     if Match (Param.IDs) then
-                        if Prev_Keyword_Mark.Set then
-                           if not Constant_Ref (Data.Navigate_Caches, 
Cache_Cur).Element.Prev_Pos.Set and
-                             not Constant_Ref (Data.Navigate_Caches, 
Prev_Cache_Cur).Element.Next_Pos.Set
-                           then
-                              Variable_Ref (Data.Navigate_Caches, 
Cache_Cur).Element.Prev_Pos      := Prev_Keyword_Mark;
-                              Variable_Ref (Data.Navigate_Caches, 
Prev_Cache_Cur).Element.Next_Pos := (True, Point);
-                              Prev_Keyword_Mark := (True, Point);
-                              Prev_Cache_Cur    := Cache_Cur;
+                        if not Prev_Cache.Next_Pos.Set then
+                           Prev_Cache.Next_Pos := (True, Cache.Pos);
+                           if WisiToken.Trace_Action > Detail then
+                              Ada.Text_IO.Put_Line
+                                ("   " & Prev_Cache.Pos'Image & " next to " & 
Prev_Cache.Next_Pos.Item'Image);
                            end if;
-                        else
-                           Prev_Keyword_Mark := (True, Point);
-                           Prev_Cache_Cur    := Cache_Cur;
                         end if;
-                     end if;
+                     end;
+                  end if;
+
+                  loop
+                     --  Set Prev_Cache_Cur to last motion cache in nonterm 
chain
+                     exit when not Data.Navigate_Caches 
(Cache_Cur).Next_Pos.Set;
 
-                     Cache_Cur := Next (Iter, Cache_Cur);
-                     exit when Cache_Cur = No_Element;
+                     Cache_Cur := Find (Iter, Data.Navigate_Caches 
(Cache_Cur).Next_Pos.Item);
+                     pragma Assert (Has_Element (Cache_Cur)); --  otherwise 
there's a bug in this subprogram.
 
-                     Point := Constant_Ref (Data.Navigate_Caches, 
Cache_Cur).Element.Pos;
                   end loop;
+                  Prev_Cache_Cur := Cache_Cur;
                end if;
             end;
          end if;
@@ -1296,7 +1230,6 @@ package body Wisi is
    is
       pragma Unreferenced (Nonterm);
 
-      --  [2] wisi-face-apply-action
       use Face_Cache_Trees;
 
       Iter       : constant Iterator := Data.Face_Caches.Iterate;
@@ -1311,7 +1244,7 @@ package body Wisi is
                Cache_Cur := Find (Iter, Token.Char_Region.First, Direction => 
Ascending);
                if Has_Element (Cache_Cur) then
                   declare
-                     Cache : Face_Cache_Type renames Variable_Ref 
(Data.Face_Caches, Cache_Cur).Element.all;
+                     Cache : Face_Cache_Type renames Data.Face_Caches 
(Cache_Cur);
                   begin
                      case Cache.Class is
                      when Prefix =>
@@ -1321,8 +1254,7 @@ package body Wisi is
                         Suffix_Cur := Next (Iter, Cache_Cur);
                         if Has_Element (Suffix_Cur) then
                            declare
-                              Suf_Cache : Face_Cache_Type renames Variable_Ref
-                                (Data.Face_Caches, Suffix_Cur).Element.all;
+                              Suf_Cache : Face_Cache_Type renames 
Data.Face_Caches (Suffix_Cur);
                            begin
                               if Suffix = Suf_Cache.Class and
                                 Inside (Suf_Cache.Region.First, 
Token.Char_Region)
@@ -1367,9 +1299,9 @@ package body Wisi is
                Cache_Cur := Find_In_Range (Iter, Ascending, 
Token.Char_Region.First, Token.Char_Region.Last);
                loop
                   exit when not Has_Element (Cache_Cur) or else
-                    Constant_Ref (Data.Face_Caches, 
Cache_Cur).Element.Region.First > Token.Char_Region.Last;
+                    Data.Face_Caches (Cache_Cur).Region.First > 
Token.Char_Region.Last;
                   declare
-                     Cache : Face_Cache_Type renames Variable_Ref 
(Data.Face_Caches, Cache_Cur).Element.all;
+                     Cache : Face_Cache_Type renames Data.Face_Caches 
(Cache_Cur);
                   begin
                      case Cache.Class is
                      when Prefix =>
@@ -1395,7 +1327,6 @@ package body Wisi is
    is
       pragma Unreferenced (Nonterm);
 
-      --  [2] wisi-face-apply-action
       use Face_Cache_Trees;
 
       Iter      : constant Iterator := Data.Face_Caches.Iterate;
@@ -1409,14 +1340,14 @@ package body Wisi is
                Cache_Cur := Find (Iter, Token.Char_Region.First, Direction => 
Ascending);
                if Has_Element (Cache_Cur) then
                   declare
-                     Cache : Face_Cache_Type renames Variable_Ref 
(Data.Face_Caches, Cache_Cur).Element.all;
+                     Cache : Face_Cache_Type renames Data.Face_Caches 
(Cache_Cur);
                      Other_Cur : Cursor := Find_In_Range
                        (Iter, Ascending, Cache.Region.Last + 1, 
Token.Char_Region.Last);
                      Temp : Cursor;
                   begin
                      loop
                         exit when not Has_Element (Other_Cur) or else
-                          Constant_Ref (Data.Face_Caches, 
Other_Cur).Element.Region.First > Token.Char_Region.Last;
+                          Data.Face_Caches (Other_Cur).Region.First > 
Token.Char_Region.Last;
                         Temp := Other_Cur;
                         Other_Cur := Next (Iter, Other_Cur);
                         Delete (Data.Face_Caches, Temp);
@@ -1457,7 +1388,7 @@ package body Wisi is
                Cache_Cur := Find_In_Range (Iter, Ascending, 
Token.Char_Region.First, Token.Char_Region.Last);
                loop
                   exit when not Has_Element (Cache_Cur) or else
-                    Constant_Ref (Data.Face_Caches, 
Cache_Cur).Element.Region.First > Token.Char_Region.Last;
+                    Data.Face_Caches (Cache_Cur).Region.First > 
Token.Char_Region.Last;
                   Temp := Cache_Cur;
                   Cache_Cur := Next (Iter, Cache_Cur);
                   Delete (Data.Face_Caches, Temp);
@@ -1614,7 +1545,6 @@ package body Wisi is
    is
       Indenting_Token : constant Aug_Token_Ref := Get_Aug_Token (Data, Tree, 
Tree_Indenting);
    begin
-      --  [2] wisi-elisp-parse--hanging-1
       if Indenting_Comment then
          return Indent_Compute_Delta
            (Data, Tree, Tokens, (Simple, Delta_1), Tree_Indenting, 
Indenting_Comment);
@@ -1822,7 +1752,6 @@ package body Wisi is
       Put (Lexer_Errors);
 
       for Item of Parse_Errors loop
-         --  We don't include parser id here; not very useful.
          case Item.Label is
          when Parse.LR.Action =>
             Put_Line
@@ -1857,7 +1786,7 @@ package body Wisi is
    is
       use Ada.Text_IO;
    begin
-      Put_Line ("(error """ & Error_Message (-Data.Source_File_Name, 
Line_Number, 0, Message) & """)");
+      Put_Line ("(error """ & Error_Message (Data.Lexer.File_Name, 
Line_Number, 0, Message) & """)");
    end Put_Error;
 
    ----------
@@ -1934,12 +1863,46 @@ package body Wisi is
    begin
       return
         (case Tree.Label (Tree_Index) is
-         when Shared_Terminal => Data.Terminals.Variable_Ref (Tree.Terminal 
(Tree_Index)),
+         when Shared_Terminal => To_Aug_Token_Ref (Data.Terminals 
(Tree.Terminal (Tree_Index))),
          when Virtual_Terminal => raise SAL.Programmer_Error with 
"wisi_runtime.get_aug_token virtual terminal",
          when Virtual_Identifier => raise SAL.Programmer_Error with 
"wisi_runtime.get_aug_token virtual identifier",
-         when Nonterm => (Element => Augmented_Token_Access (Tree.Augmented 
(Tree_Index))));
+         when Nonterm => To_Aug_Token_Ref (Tree.Augmented (Tree_Index)));
    end Get_Aug_Token;
 
+   function Get_Text
+     (Data       : in Parse_Data_Type;
+      Tree       : in WisiToken.Syntax_Trees.Tree;
+      Tree_Index : in WisiToken.Syntax_Trees.Valid_Node_Index)
+     return String
+   is
+      use all type Syntax_Trees.Node_Label;
+   begin
+      case Tree.Label (Tree_Index) is
+      when Shared_Terminal | Nonterm =>
+         return Data.Lexer.Buffer_Text (Tree.Byte_Region (Tree_Index));
+
+      when Virtual_Terminal | Virtual_Identifier =>
+         raise SAL.Programmer_Error;
+
+      end case;
+   end Get_Text;
+
+   function Elisp_Escape_Quotes (Item : in String) return String
+   is
+      Result : String (Item'First .. Item'First + Item'Length * 2);
+      Last   : Integer := Item'First - 1;
+   begin
+      for I in Item'Range loop
+         if Item (I) = '"' then
+            Last := Last + 1;
+            Result (Last) := '\';
+         end if;
+         Last := Last + 1;
+         Result (Last) := Item (I);
+      end loop;
+      return Result (Result'First .. Last);
+   end Elisp_Escape_Quotes;
+
    overriding
    function Image
      (Item       : in Augmented_Token;
@@ -1960,25 +1923,6 @@ package body Wisi is
       end if;
    end Image;
 
-   function Image
-     (Item       : in Augmented_Token_Access_Array;
-      Descriptor : in WisiToken.Descriptor)
-     return String
-   is
-      use all type SAL.Base_Peek_Type;
-      use Ada.Strings.Unbounded;
-      Result : Unbounded_String := +"(";
-   begin
-      for I in Item'Range loop
-         Result := Result & Image (Item (I).all, Descriptor);
-         if I /= Item'Last then
-            Result := Result & ", ";
-         end if;
-      end loop;
-      Result := Result & ")";
-      return -Result;
-   end Image;
-
    function Indent_Anchored_2
      (Data        : in out Parse_Data_Type;
       Anchor_Line : in     Line_Number_Type;
diff --git a/wisi.ads b/wisi.ads
index 818a7cc..d240f0a 100644
--- a/wisi.ads
+++ b/wisi.ads
@@ -4,9 +4,9 @@
 --
 --  References
 --
---  [1] wisi.el - defines parse action functions.
+--  [1] wisi-parse-common.el - defines common stuff.
 --
---  [2] wisi-elisp-parse.el - defines parse action functions.
+--  [2] wisi.texi - defines parse action functions.
 --
 --  [3] wisi-process-parse.el - defines elisp/process API
 --
@@ -27,8 +27,6 @@ pragma License (Modified_GPL);
 
 with Ada.Containers.Doubly_Linked_Lists;
 with Ada.Containers.Vectors;
-with Ada.Strings.Unbounded;
-with Ada.Unchecked_Deallocation;
 with SAL.Gen_Unbounded_Definite_Red_Black_Trees;
 with SAL.Gen_Unbounded_Definite_Vectors;
 with WisiToken.Parse.LR;
@@ -45,8 +43,9 @@ package Wisi is
 
    procedure Initialize
      (Data              : in out Parse_Data_Type;
+      Lexer             : in     WisiToken.Lexer.Handle;
       Descriptor        : access constant WisiToken.Descriptor;
-      Source_File_Name  : in     String;
+      Base_Terminals    : in     WisiToken.Base_Token_Array_Access;
       Post_Parse_Action : in     Post_Parse_Action_Type;
       Begin_Line        : in     WisiToken.Line_Number_Type;
       End_Line          : in     WisiToken.Line_Number_Type;
@@ -95,6 +94,7 @@ package Wisi is
       Nonterm : in     WisiToken.Syntax_Trees.Valid_Node_Index;
       Tokens  : in     WisiToken.Syntax_Trees.Valid_Node_Index_Array;
       Params  : in     Statement_Param_Array);
+   --  Implements [2] wisi-statement-action.
 
    procedure Name_Action
      (Data    : in out Parse_Data_Type;
@@ -102,31 +102,27 @@ package Wisi is
       Nonterm : in     WisiToken.Syntax_Trees.Valid_Node_Index;
       Tokens  : in     WisiToken.Syntax_Trees.Valid_Node_Index_Array;
       Name    : in     WisiToken.Positive_Index_Type);
+   --  Implements [2] wisi-name-action.
 
-   procedure Containing_Action
-     (Data       : in out Parse_Data_Type;
-      Tree       : in     WisiToken.Syntax_Trees.Tree;
-      Nonterm    : in     WisiToken.Syntax_Trees.Valid_Node_Index;
-      Tokens     : in     WisiToken.Syntax_Trees.Valid_Node_Index_Array;
-      Containing : in     WisiToken.Positive_Index_Type;
-      Contained  : in     WisiToken.Positive_Index_Type);
-
-   package Token_ID_Lists is new Ada.Containers.Doubly_Linked_Lists 
(WisiToken.Token_ID, WisiToken."=");
-
-   Empty_IDs : constant Token_ID_Lists.List := Token_ID_Lists.Empty_List;
-
-   function "+" (Item : in WisiToken.Token_ID) return Token_ID_Lists.List;
-   function "&" (List : in Token_ID_Lists.List; Item : in WisiToken.Token_ID) 
return Token_ID_Lists.List;
-   function "&" (Left, Right : in WisiToken.Token_ID) return 
Token_ID_Lists.List;
-
-   type Index_IDs is record
+   type Index_ID is record
       Index : WisiToken.Positive_Index_Type; -- into Tokens
-      IDs   : Token_ID_Lists.List;
+      ID    : WisiToken.Token_ID;
+      --  If ID is not Invalid_Token_ID, it is the first token in the
+      --  nonterm that Index points to that should have a navigate cache for
+      --  Motion_Action to link to; an error is reported by Motion_Action if
+      --  it does not.
+      --
+      --  If ID is Invalid_Token_ID, and the token at Index is a
+      --  nonterminal, the first token in that nonterminal must have a
+      --  navigate cache; an error is reported by Motion_Action if not.
    end record;
 
-   package Index_IDs_Vectors is new Ada.Containers.Vectors 
(Ada.Containers.Count_Type, Index_IDs);
+   package Index_ID_Vectors is new Ada.Containers.Vectors 
(Ada.Containers.Count_Type, Index_ID);
+
+   subtype Motion_Param_Array is Index_ID_Vectors.Vector;
 
-   subtype Motion_Param_Array is Index_IDs_Vectors.Vector;
+   Invalid_Token_ID : WisiToken.Token_ID := WisiToken.Invalid_Token_ID;
+   --  So Create_Parser can just use "Invalid_Token_ID".
 
    procedure Motion_Action
      (Data    : in out Parse_Data_Type;
@@ -161,7 +157,6 @@ package Wisi is
    --  Implements [2] wisi-face-apply-list-action.
 
    type Face_Class_Type is (Prefix, Suffix);
-   --  Matches wisi-cache-class values set in [1] wisi-face-apply-action.
 
    type Index_Face_Class is record
       Index : WisiToken.Positive_Index_Type; -- into Tokens
@@ -320,10 +315,19 @@ package Wisi is
       Option            : in     Boolean;
       Accumulate        : in     Boolean)
      return Delta_Type;
-   --  [2] wisi-elisp-parse--hanging-1
+   --  Implements [2] wisi-hanging, wisi-hanging%, wisi-hanging%-.
    --
-   --  Language specific child packages override this to implement
-   --  wisi-elisp-parse-indent-hanging-function.
+   --  Language specific child packages may override this to implement
+   --  language-specific cases.
+
+   ----------
+   --  Other
+
+   procedure Refactor
+     (Data       : in out Parse_Data_Type;
+      Tree       : in     WisiToken.Syntax_Trees.Tree;
+      Action     : in     Positive;
+      Edit_Begin : in     WisiToken.Buffer_Pos) is null;
 
    type Arg_Index_Array is array (Positive range <>) of 
WisiToken.Positive_Index_Type;
 
@@ -411,6 +415,13 @@ private
       --  nonterminals, empty.
 
    end record;
+   type Augmented_Token_Access is access all Augmented_Token;
+   type Augmented_Token_Access_Constant is access constant Augmented_Token;
+   type Aug_Token_Ref (Element : access constant Augmented_Token) is null 
record with
+     Implicit_Dereference => Element;
+
+   function To_Aug_Token_Ref (Item : in WisiToken.Base_Token_Class_Access) 
return Aug_Token_Ref
+     is (Element => Augmented_Token_Access_Constant (Item));
 
    overriding
    function Image
@@ -429,21 +440,13 @@ private
      return WisiToken.Line_Number_Type;
    --  Return first and last line in Token's region.
 
-   type Augmented_Token_Access is access all Augmented_Token;
-   procedure Free is new Ada.Unchecked_Deallocation (Augmented_Token, 
Augmented_Token_Access);
-
-   type Augmented_Token_Access_Array is array (WisiToken.Positive_Index_Type 
range <>) of Augmented_Token_Access;
-   --  1 indexed to match token numbers in grammar actions.
-
-   function Image
-     (Item       : in Augmented_Token_Access_Array;
-      Descriptor : in WisiToken.Descriptor)
-     return String;
-
    package Augmented_Token_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
      (WisiToken.Token_Index, Augmented_Token, Default_Element => (others => 
<>));
    --  Index matches Base_Token_Arrays.
 
+   function To_Aug_Token_Ref (Item : in 
Augmented_Token_Arrays.Constant_Reference_Type) return Aug_Token_Ref
+     is (Element => 
Augmented_Token_Access_Constant'(Item.Element.all'Unchecked_Access));
+
    package Line_Paren_Vectors is new SAL.Gen_Unbounded_Definite_Vectors
      (WisiToken.Line_Number_Type, Integer, Default_Element => Integer'Last);
    package Line_Begin_Pos_Vectors is new SAL.Gen_Unbounded_Definite_Vectors
@@ -498,7 +501,7 @@ private
 
    type Face_Cache_Type is record
       Region : WisiToken.Buffer_Region;
-      Class  : Face_Class_Type; -- wisi-cache-class; one of {'prefix | 'suffix}
+      Class  : Face_Class_Type;
       Face   : Nil_Integer;     -- not set, or index into *-process-faces-names
    end record;
 
@@ -579,8 +582,9 @@ private
 
       --  Data for post-parse actions
 
+      Lexer             : WisiToken.Lexer.Handle;
       Descriptor        : access constant WisiToken.Descriptor;
-      Source_File_Name  : Ada.Strings.Unbounded.Unbounded_String;
+      Base_Terminals    : WisiToken.Base_Token_Array_Access;
       Post_Parse_Action : Post_Parse_Action_Type;
       Navigate_Caches   : Navigate_Cache_Trees.Tree;  -- Set by Navigate.
       Name_Caches       : Name_Cache_Trees.Tree;      -- Set by Navigate.
@@ -659,8 +663,6 @@ private
    ----------
    --  Utilities for language-specific child packages
 
-   subtype Aug_Token_Ref is Augmented_Token_Arrays.Variable_Reference_Type;
-
    function Current_Indent_Offset
      (Data         : in Parse_Data_Type;
       Anchor_Token : in Augmented_Token'Class;
@@ -684,6 +686,17 @@ private
       Tree_Index : in WisiToken.Syntax_Trees.Valid_Node_Index)
      return Aug_Token_Ref;
 
+   function Get_Text
+     (Data       : in Parse_Data_Type;
+      Tree       : in WisiToken.Syntax_Trees.Tree;
+      Tree_Index : in WisiToken.Syntax_Trees.Valid_Node_Index)
+     return String;
+   --  Return text contained by Tree_Index token in source file
+   --  (lexer.buffer).
+
+   function Elisp_Escape_Quotes (Item : in String) return String;
+   --  Prefix any '"' in Item with '\' for elisp.
+
    function Indent_Anchored_2
      (Data        : in out Parse_Data_Type;
       Anchor_Line : in     WisiToken.Line_Number_Type;
@@ -710,4 +723,18 @@ private
    --  [2] wisi-elisp-parse--indent-token-1. Sets Data.Indents, so caller
    --  may not be in a renames for a Data.Indents element.
 
+   --  Visible for language-specific children. Must match list in
+   --  wisi-process-parse.el wisi-process-parse--execute.
+   Navigate_Cache_Code  : constant String := "1";
+   Face_Property_Code   : constant String := "2";
+   Indent_Code          : constant String := "3";
+   Lexer_Error_Code     : constant String := "4";
+   Parser_Error_Code    : constant String := "5";
+   Check_Error_Code     : constant String := "6";
+   Recover_Code         : constant String := "7 ";
+   End_Code             : constant String := "8";
+   Name_Property_Code   : constant String := "9";
+   Edit_Action_Code     : constant String := "10";
+   Language_Action_Code : constant String := "11 ";
+
 end Wisi;
diff --git a/wisi.el b/wisi.el
index 5ea8d5a..3500f91 100644
--- a/wisi.el
+++ b/wisi.el
@@ -7,8 +7,8 @@
 ;; Keywords: parser
 ;;  indentation
 ;;  navigation
-;; Version: 2.1.1
-;; package-requires: ((cl-lib "1.0") (emacs "25.0") (seq "2.20"))
+;; Version: 2.2.0
+;; package-requires: ((emacs "25.0") (seq "2.20"))
 ;; URL: http://stephe-leake.org/ada/wisitoken.html
 ;;
 ;; This file is part of GNU Emacs.
@@ -98,22 +98,10 @@
 ;;   than we need. Finally, the semantic parser does not support error
 ;;   correction, and thus fails in most editing situations.
 ;;
-;; We use wisitoken wisi-generate to compile BNF to Elisp source, and
-;; wisi-compile-grammar to compile that to the parser table. See
-;; ada-mode info for more information on the developer tools used for
-;; ada-mode and wisi.
+;; We use the WisiToken tool wisi-bnf-generate to compile BNF or EBNF
+;; to Ada source, See ada-mode.info and wisi.info for more information
+;; on the developer tools used for ada-mode and wisi.
 ;;
-;; Alternately, to gain speed and error handling, we use wisi-generate
-;; to generate Ada source, and run that in an external process. That
-;; supports error correction while parsing.
-;;
-;;;; syntax-propertize
-;;
-;; `wisi-forward-token' relies on syntax properties, so
-;; `syntax-propertize' must be called on the text to be lexed before
-;; wisi-forward-token is called. Emacs >= 25 calls syntax-propertize
-;; transparently in the low-level lexer functions.
-
 ;;; Code:
 
 (require 'cl-lib)
@@ -121,7 +109,6 @@
 (require 'seq)
 (require 'semantic/lex)
 (require 'wisi-parse-common)
-(require 'wisi-elisp-lexer)
 (require 'wisi-fringe)
 (require 'xref)
 
@@ -132,19 +119,13 @@
   :safe 'integerp)
 (make-variable-buffer-local 'wisi-size-threshold)
 
-(defcustom wisi-partial-parse-threshold 100001
-  "Minimum size that will be parsed by each call to the parser.
-A parse is always requested at a point (or on a region); the
-point is first expanded to a start point before the region and an
-end point after the region, that the parser can gracefully
-handle. If the final region covers the entire buffer, a complete
-parse is done. Indent assumes the start point of the parse region
-is properly indented. Most navigate parses ignore this setting
-and parse the whole buffer."
+(defcustom wisi-indent-context-lines 0
+  "Minimum number of lines before point to include in a parse for indent.
+Increasing this will give better results when in the middle of a
+deeply nested statement, but worse in some situations."
   :type 'integer
   :group 'wisi
   :safe 'integerp)
-(make-variable-buffer-local 'wisi-partial-parse-threshold)
 
 (defvar wisi-inhibit-parse nil
   "When non-nil, don't run the parser.
@@ -175,14 +156,14 @@ Useful when debugging parser or parser actions."
 ;;;; token info cache
 
 (defvar-local wisi-parse-failed nil
-  "Non-nil when a recent parse has failed - cleared when parse succeeds.")
+  "Non-nil when last parse failed - cleared when parse succeeds.")
 
 (defvar-local wisi--parse-try
   (list
    (cons 'face t)
    (cons 'navigate t)
    (cons 'indent t))
-  "Non-nil when parse is needed - cleared when parse succeeds.")
+  "Non-nil when parse is needed because text has changed - cleared when parse 
succeeds.")
 
 (defun wisi-parse-try (&optional parse-action)
   (cdr (assoc (or parse-action wisi--parse-action) wisi--parse-try)))
@@ -190,6 +171,19 @@ Useful when debugging parser or parser actions."
 (defun wisi-set-parse-try (value &optional parse-action)
   (setcdr (assoc (or parse-action wisi--parse-action) wisi--parse-try) value))
 
+(defvar-local wisi--last-parse-region
+  (list
+   (cons 'face nil)
+   (cons 'navigate nil)
+   (cons 'indent nil))
+  "Last region on which parse was requested.")
+
+(defun wisi-last-parse-region (&optional parse-action)
+  (cdr (assoc (or parse-action wisi--parse-action) wisi--last-parse-region)))
+
+(defun wisi-set-last-parse-region (begin end parse-action)
+  (setcdr (assoc parse-action wisi--last-parse-region) (cons begin end)))
+
 (defvar-local wisi--cached-regions
   (list
    (cons 'face nil)
@@ -366,6 +360,9 @@ Truncate any region that overlaps POS."
   (wisi-set-parse-try t 'indent)
   (wisi-set-parse-try t 'face)
   (wisi-set-parse-try t 'navigate)
+  (wisi-set-last-parse-region (point-min) (point-min) 'indent)
+  (wisi-set-last-parse-region (point-min) (point-min) 'face)
+  (wisi-set-last-parse-region (point-min) (point-min) 'navigate)
   (wisi-fringe-clean))
 
 ;; wisi--change-* keep track of buffer modifications.
@@ -390,11 +387,8 @@ Set by `wisi-before-change', used and reset by 
`wisi--post-change'.")
   "Non-nil when `wisi-indent-region' is actively indenting.
 Used to ignore whitespace changes in before/after change hooks.")
 
-(defvar-local wisi--parser nil
-  "Choice of wisi parser implementation; a ‘wisi-parser’ object.")
-
 (defvar-local wisi--last-parse-action nil
-  "Last value of `wisi--parse-action' when `wisi-validate-cache' was run.")
+  "Value of `wisi--parse-action' when `wisi-validate-cache' was last run.")
 
 (defun wisi-before-change (begin end)
   "For `before-change-functions'."
@@ -707,6 +701,7 @@ Usefull if the parser appears to be hung."
        (message msg))
 
       (setq wisi--last-parse-action wisi--parse-action)
+      (wisi-set-last-parse-region begin parse-end wisi--parse-action)
 
       (unless (eq wisi--parse-action 'face)
        (when (buffer-live-p wisi-error-buffer)
@@ -792,24 +787,31 @@ Usefull if the parser appears to be hung."
       (let ((wisi--parse-action parse-action))
        (wisi--check-change)
 
-       ;; Now we can rely on wisi-cache-covers-region
-
-       (if (and (or (not wisi-parse-failed)
-                    (wisi-parse-try))
-               (not (wisi-cache-covers-region begin end)))
-           (progn
-             ;; Don't keep retrying failed parse until text changes again.
-             (wisi-set-parse-try nil)
-             (wisi--run-parse begin end))
-
+       ;; Now we can rely on wisi-cache-covers-region.
+       ;;
+       ;; If the last parse failed but was partial, and we are trying
+       ;; a different region, it may succeed. Otherwise, don't keep
+       ;; retrying a failed parse until the text changes again.
+       (cond
+        ((and (not wisi-parse-failed)
+              (wisi-cache-covers-region begin end))
          (when (> wisi-debug 0)
-           (message "parse %s skipped: parse-failed %s parse-try %s 
cache-covers-region %s %s.%s"
+           (message "parse %s skipped: cache-covers-region %s %s.%s"
                     parse-action
-                    wisi-parse-failed
-                    (wisi-parse-try)
                     (wisi-cache-covers-region begin end)
                     begin end)))
 
+        ((and wisi-parse-failed
+              (equal (cons begin end) (wisi-last-parse-region parse-action))
+              (not (wisi-parse-try parse-action)))
+         (when (> wisi-debug 0)
+           (message "parse %s skipped: parse-failed" parse-action)))
+
+        (t
+         (progn
+           (wisi-set-parse-try nil)
+           (wisi--run-parse begin end))))
+
        ;; We want this error even if we did not try to parse; it means
        ;; the parse results are not valid.
        (when (and error-on-fail wisi-parse-failed)
@@ -851,29 +853,6 @@ If LIMIT (a buffer position) is reached, throw an error."
        (error "cache with class %s not found" class)))
     cache))
 
-(defun wisi-forward-find-token (token limit &optional noerror)
-  "Search forward for TOKEN.
-If point is at a matching token, return that token.  TOKEN may be
-a list; stop on any member of the list.  Return `wisi-tok'
-struct, or if LIMIT (a buffer position) is reached, then if
-NOERROR is nil, throw an error, if non-nil, return nil."
-  (let ((token-list (cond
-                    ((listp token) token)
-                    (t (list token))))
-       (tok (wisi-forward-token))
-       (done nil))
-    (while (not (or done
-                   (memq (wisi-tok-token tok) token-list)))
-      (setq tok (wisi-forward-token))
-      (when (or (>= (point) limit)
-               (eobp))
-       (goto-char limit)
-       (setq tok nil)
-       (if noerror
-           (setq done t)
-         (error "token %s not found" token))))
-    tok))
-
 (defun wisi-forward-find-cache-token (ids limit)
   "Search forward for a cache with token in IDS (a list of token ids).
 Return cache, or nil if at LIMIT or end of buffer."
@@ -1201,7 +1180,6 @@ If INDENT-BLANK-LINES is non-nil, also indent blank lines 
(for use as
                   (wisi-parse-try 'indent)))
 
       (wisi-set-parse-try nil)
-
       (wisi--run-parse begin end)
 
       ;; If there were errors corrected, the indentation is
@@ -1270,7 +1248,7 @@ If INDENT-BLANK-LINES is non-nil, also indent blank lines 
(for use as
     (when (>= (point) savep)
       (setq to-indent t))
 
-    (wisi-indent-region (line-beginning-position) (line-end-position) t)
+    (wisi-indent-region (line-beginning-position (- 
wisi-indent-context-lines)) (1+ (line-end-position)) t)
 
     (goto-char savep)
     (when to-indent (back-to-indentation))
@@ -1278,24 +1256,18 @@ If INDENT-BLANK-LINES is non-nil, also indent blank 
lines (for use as
 
 (defun wisi-repair-error-1 (data)
   "Repair error reported in DATA (a ’wisi--parse-error’ or 
’wisi--lexer-error’)"
-  (let ((wisi--parse-action 'navigate) ;; tell wisi-forward-token not to 
compute indent stuff.
-       tok-2)
+  (let ((wisi--parse-action 'navigate))
     (cond
      ((wisi--lexer-error-p data)
       (goto-char (1+ (wisi--lexer-error-pos data)))
       (insert (wisi--lexer-error-inserted data)))
      ((wisi--parse-error-p data)
       (dolist (repair (wisi--parse-error-repair data))
-       (goto-char (wisi--parse-error-repair-pos repair))
-       (dolist (tok-1 (wisi--parse-error-repair-deleted repair))
-         (setq tok-2 (wisi-forward-token))
-         (if (eq tok-1 (wisi-tok-token tok-2))
-             (delete-region (car (wisi-tok-region tok-2)) (cdr 
(wisi-tok-region tok-2)))
-           (error "mismatched tokens: %d: parser %s, buffer %s %s"
-                  (point) tok-1 (wisi-tok-token tok-2) (wisi-tok-region 
tok-2))))
-
+       (when (< 0 (length (wisi--parse-error-repair-deleted repair)))
+         (delete-region (car (wisi--parse-error-repair-deleted-region repair))
+                        (cdr (wisi--parse-error-repair-deleted-region 
repair))))
        (dolist (id (wisi--parse-error-repair-inserted repair))
-         (insert (cdr (assoc id (wisi-elisp-lexer-id-alist wisi--lexer))))
+         (insert (cdr (assoc id (wisi-parser-repair-image wisi--parser))))
          (insert " "))
        ))
      )))
@@ -1467,10 +1439,6 @@ If non-nil, only repair errors in BEG END region."
   (define-key global-map "\M-j" 'wisi-show-cache)
   )
 
-(defun wisi-read-parse-action ()
-  "Read a parse action symbol from the minibuffer."
-  (intern-soft (completing-read "parse action (indent): " '(face navigate 
indent) nil t nil nil 'indent)))
-
 (defun wisi-parse-buffer (&optional parse-action begin end)
   (interactive)
   (unless parse-action
@@ -1587,13 +1555,12 @@ If non-nil, only repair errors in BEG END region."
 
 ;;;;; setup
 
-(cl-defun wisi-setup (&key indent-calculate post-indent-fail parser lexer)
+(cl-defun wisi-setup (&key indent-calculate post-indent-fail parser)
   "Set up a buffer for parsing files with wisi."
   (when wisi--parser
     (wisi-kill-parser))
 
   (setq wisi--parser parser)
-  (setq wisi--lexer lexer)
   (setq wisi--cached-regions
        (list
         (cons 'face nil)
@@ -1606,6 +1573,12 @@ If non-nil, only repair errors in BEG END region."
         (cons 'navigate t)
         (cons 'indent t)))
 
+  (setq wisi--last-parse-region
+       (list
+        (cons 'face nil)
+        (cons 'navigate nil)
+        (cons 'indent nil)))
+
   ;; file local variables may have added opentoken, gnatprep
   (setq wisi-indent-calculate-functions (append 
wisi-indent-calculate-functions indent-calculate))
   (set (make-local-variable 'indent-line-function) #'wisi-indent-line)
diff --git a/wisitoken.gpr b/wisi.gpr
similarity index 57%
rename from wisitoken.gpr
rename to wisi.gpr
index 2b0064d..8ca2dd2 100644
--- a/wisitoken.gpr
+++ b/wisi.gpr
@@ -1,8 +1,8 @@
 --  Abstract :
 --
---  Provide SAL and WisiToken to other ELPA packages
+--  Make installed ELPA package wisi Ada code available for other projects.
 --
---  Copyright (C) 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2017, 2019 Free Software Foundation, Inc.
 --
 --  This program is free software; you can redistribute it and/or
 --  modify it under terms of the GNU General Public License as
@@ -18,12 +18,10 @@
 
 with "gnatcoll";
 with "standard_common";
-project WisiToken is
+project Wisi is
 
    for Source_Dirs use (".");
 
-   for Languages use ("Ada", "C");
-
    case Standard_Common.Profile is
    when "On" =>
       for Object_Dir use "obj_pro";
@@ -34,39 +32,28 @@ project WisiToken is
       for Exec_Dir use ".";
    end case;
 
+   for Languages use ("Ada");
+
    package Compiler is
+
       case Standard_Common.Build is
       when "Debug" =>
          for Default_Switches ("Ada") use
-           Standard_Common.Compiler.Debug_Switches &
-           Standard_Common.Compiler.Style_Checks;
+           Standard_Common.Compiler.Common_Switches &
+           Standard_Common.Compiler.Style_Checks &
+           Standard_Common.Compiler.Debug_Switches;
+
+         for Default_Switches ("C") use 
Standard_Common.Compiler.Debug_Switches_C;
 
       when "Normal" =>
          for Default_Switches ("Ada") use
-           Standard_Common.Compiler.Release_Switches &
-           Standard_Common.Compiler.Style_Checks;
-      end case;
-
-   end Compiler;
-
-   package Builder is
-      for Switches ("Ada") use Standard_Common.Builder'Default_Switches 
("Ada");
-
-      --  We use ".exe" extension even on non-Windows, to simplify the 
makefiles.
-      for Executable_Suffix use ".exe";
+           Standard_Common.Compiler.Common_Switches &
+           Standard_Common.Compiler.Style_Checks &
+           Standard_Common.Compiler.Release_Switches;
 
-      case Standard_Common.Profile is
-      when "On" =>
-         for Global_Compilation_Switches ("Ada") use ("-pg");
-
-      when "Off" =>
-         null;
+         for Default_Switches ("C") use 
Standard_Common.Compiler.Release_Switches_C;
       end case;
 
-   end Builder;
-
-   package Binder is
-      for Default_Switches ("Ada") use Standard_Common.Binder'Default_Switches 
("Ada");
-   end Binder;
+   end Compiler;
 
-end WisiToken;
+end Wisi;
diff --git a/wisitoken-bnf-generate.adb b/wisitoken-bnf-generate.adb
index ca98c75..72a0221 100644
--- a/wisitoken-bnf-generate.adb
+++ b/wisitoken-bnf-generate.adb
@@ -1,6 +1,6 @@
 --  Abstract :
 --
---  Parser for Wisi grammar files, producing Ada or Elisp source
+--  Parser for Wisi grammar files, producing Ada source
 --  files for a parser.
 --
 --  Copyright (C) 2012 - 2015, 2017 - 2019 Free Software Foundation, Inc.
@@ -32,7 +32,6 @@ with WisiToken.BNF.Generate_Utils;
 with WisiToken.BNF.Output_Ada;
 with WisiToken.BNF.Output_Ada_Common;
 with WisiToken.BNF.Output_Ada_Emacs;
-with WisiToken.BNF.Output_Elisp;
 with WisiToken.BNF.Output_Elisp_Common;
 with WisiToken.Generate.LR.LALR_Generate;
 with WisiToken.Generate.LR.LR1_Generate;
@@ -53,8 +52,8 @@ is
    begin
       --  verbosity meaning is actually determined by output choice;
       --  they should be consistent with this description.
-      Put_Line (Standard_Error, "version 1.2.0");
-      Put_Line (Standard_Error, "wisi-generate [options] {wisi grammar file}");
+      Put_Line (Standard_Error, "version 1.3.0");
+      Put_Line (Standard_Error, "wisitoken-bnf-generate [options] {wisi 
grammar file}");
       Put_Line (Standard_Error, "Generate source code implementing a parser 
for the grammar.");
       New_Line (Standard_Error);
       Put_Line (Standard_Error, "The following grammar file directives control 
parser generation:");
@@ -131,7 +130,6 @@ is
 
    Trace          : aliased WisiToken.Text_IO_Trace.Trace 
(Wisitoken_Grammar_Actions.Descriptor'Access);
    Input_Data     : aliased WisiToken_Grammar_Runtime.User_Data_Type;
-   Elisp_Tokens   : WisiToken.BNF.Tokens;
    Grammar_Parser : WisiToken.Parse.LR.Parser_No_Recover.Parser;
 
    Do_Time : Boolean := False;
@@ -421,12 +419,6 @@ begin
                when re2c_Lexer =>
                   WisiToken.BNF.Output_Ada_Common.Create_re2c
                     (Input_Data, Tuple, Generate_Data, -Output_File_Name_Root);
-                  if Tuple.Out_Lang = Ada_Emacs_Lang and 
Elisp_Tokens.Keywords.Is_Empty then
-                     --  elisp code needs keywords for font-lock.
-                     Elisp_Tokens.Keywords := Input_Data.Tokens.Keywords;
-                  end if;
-               when Elisp_Lexer =>
-                  Elisp_Tokens := Input_Data.Tokens;
                when others =>
                   null;
                end case;
@@ -474,7 +466,6 @@ begin
 
                Generate_Data.Parser_State_Count :=
                  Generate_Data.LR_Parse_Table.State_Last - 
Generate_Data.LR_Parse_Table.State_First + 1;
-               WisiToken.BNF.Generate_Utils.Count_Actions (Generate_Data);
                WisiToken.BNF.Generate_Utils.Put_Stats (Input_Data, 
Generate_Data);
 
             when LR1 =>
@@ -502,7 +493,6 @@ begin
 
                Generate_Data.Parser_State_Count :=
                  Generate_Data.LR_Parse_Table.State_Last - 
Generate_Data.LR_Parse_Table.State_First + 1;
-               WisiToken.BNF.Generate_Utils.Count_Actions (Generate_Data);
                WisiToken.BNF.Generate_Utils.Put_Stats (Input_Data, 
Generate_Data);
 
             when Packrat_Generate_Algorithm =>
@@ -558,12 +548,9 @@ begin
 
                when Ada_Emacs_Lang =>
                   WisiToken.BNF.Output_Ada_Emacs
-                    (Input_Data, Elisp_Tokens, -Output_File_Name_Root, 
Generate_Data, Packrat_Data, Tuple,
+                    (Input_Data, -Output_File_Name_Root, Generate_Data, 
Packrat_Data, Tuple,
                      Test_Main, Multiple_Tuples, -Language_Name);
 
-               when Elisp_Lang =>
-                  WisiToken.BNF.Output_Elisp (Input_Data, 
-Output_File_Name_Root, Generate_Data, Packrat_Data, Tuple);
-
                end case;
                if WisiToken.Generate.Error then
                   raise WisiToken.Grammar_Error with "errors: aborting";
diff --git a/wisitoken-bnf-generate_grammar.adb 
b/wisitoken-bnf-generate_grammar.adb
index 165f00f..cd165c4 100644
--- a/wisitoken-bnf-generate_grammar.adb
+++ b/wisitoken-bnf-generate_grammar.adb
@@ -2,7 +2,7 @@
 --
 --  Output Ada source code to recreate Grammar.
 --
---  Copyright (C) 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2018 - 2019 Free Software Foundation, Inc.
 --
 --  This library is free software;  you can redistribute it and/or modify it
 --  under terms of the  GNU General Public License  as published by the Free
@@ -31,8 +31,9 @@ is
    Text : Unbounded_String;
    Need_Comma : Boolean := False;
 begin
-   Indent_Line ("Grammar.Set_First (" & Trimmed_Image (Grammar.First_Index) & 
");");
-   Indent_Line ("Grammar.Set_Last (" & Trimmed_Image (Grammar.Last_Index) & 
");");
+   Indent_Line
+     ("Grammar.Set_First_Last (" & Trimmed_Image (Grammar.First_Index) & ", " &
+        Trimmed_Image (Grammar.Last_Index) & ");");
 
    for Prod of Grammar loop
       Indent_Line ("declare");
@@ -40,8 +41,7 @@ begin
       Indent_Line ("begin");
       Indent := Indent + 3;
       Indent_Line ("Prod.LHS := " & Trimmed_Image (Prod.LHS) & ";");
-      Indent_Line ("Prod.RHSs.Set_First (0);");
-      Indent_Line ("Prod.RHSs.Set_Last (" & Trimmed_Image 
(Prod.RHSs.Last_Index) & ");");
+      Indent_Line ("Prod.RHSs.Set_First_Last (0, " & Trimmed_Image 
(Prod.RHSs.Last_Index) & ");");
       for RHS_Index in Prod.RHSs.First_Index .. Prod.RHSs.Last_Index loop
          declare
             RHS : Right_Hand_Side renames Prod.RHSs (RHS_Index);
@@ -51,8 +51,8 @@ begin
             Indent_Line ("begin");
             Indent := Indent + 3;
             if RHS.Tokens.Length > 0 then
-               Indent_Line ("RHS.Tokens.Set_First (1);");
-               Indent_Line ("RHS.Tokens.Set_Last (" & Trimmed_Image (Prod.RHSs 
(RHS_Index).Tokens.Last_Index) & ");");
+               Indent_Line
+                 ("RHS.Tokens.Set_First_Last (1, " & Trimmed_Image (Prod.RHSs 
(RHS_Index).Tokens.Last_Index) & ");");
 
                if RHS.Tokens.Length = 1 then
                   Indent_Line ("To_Vector ((1 => " & Trimmed_Image (RHS.Tokens 
(1)) & "), RHS.Tokens);");
diff --git a/wisitoken-bnf-generate_utils.adb b/wisitoken-bnf-generate_utils.adb
index 0f84547..65f64e6 100644
--- a/wisitoken-bnf-generate_utils.adb
+++ b/wisitoken-bnf-generate_utils.adb
@@ -50,34 +50,13 @@ package body WisiToken.BNF.Generate_Utils is
       --   This function is used to compute Descriptor.Image
       case Cursor.Kind is
       when Non_Grammar_Kind =>
-         declare
-            Kind_Ref : constant 
WisiToken.BNF.Token_Lists.Constant_Reference_Type :=
-              WisiToken.BNF.Token_Lists.Constant_Reference 
(Cursor.Data.Tokens.Non_Grammar, Cursor.Token_Kind);
-
-            Item_Ref : constant String_Pair_Lists.Constant_Reference_Type :=
-              String_Pair_Lists.Constant_Reference (Kind_Ref.Element.Tokens, 
Cursor.Token_Item);
-         begin
-            return -Item_Ref.Element.Name;
-         end;
+         return -Cursor.Data.Tokens.Non_Grammar (Cursor.Token_Kind).Tokens 
(Cursor.Token_Item).Name;
 
       when Terminals_Keywords =>
-         declare
-            Keyword_Ref : constant String_Pair_Lists.Constant_Reference_Type :=
-              String_Pair_Lists.Constant_Reference 
(Cursor.Data.Tokens.Keywords, Cursor.Keyword);
-         begin
-            return -Keyword_Ref.Element.Name;
-         end;
+         return -Cursor.Data.Tokens.Keywords (Cursor.Keyword).Name;
 
       when Terminals_Others =>
-         declare
-            Kind_Ref : constant 
WisiToken.BNF.Token_Lists.Constant_Reference_Type :=
-              WisiToken.BNF.Token_Lists.Constant_Reference 
(Cursor.Data.Tokens.Tokens, Cursor.Token_Kind);
-
-            Item_Ref : constant String_Pair_Lists.Constant_Reference_Type :=
-              String_Pair_Lists.Constant_Reference (Kind_Ref.Element.Tokens, 
Cursor.Token_Item);
-         begin
-            return -Item_Ref.Element.Name;
-         end;
+         return -Cursor.Data.Tokens.Tokens (Cursor.Token_Kind).Tokens 
(Cursor.Token_Item).Name;
 
       when EOI =>
          return EOI_Name;
@@ -86,12 +65,7 @@ package body WisiToken.BNF.Generate_Utils is
          return WisiToken_Accept_Name;
 
       when Nonterminal =>
-         declare
-            Rule_Ref : constant Rule_Lists.Constant_Reference_Type := 
Rule_Lists.Constant_Reference
-              (Cursor.Data.Tokens.Rules, Cursor.Nonterminal);
-         begin
-            return -Rule_Ref.Element.Left_Hand_Side;
-         end;
+         return -Cursor.Data.Tokens.Rules (Cursor.Nonterminal).Left_Hand_Side;
 
       when Done =>
          raise SAL.Programmer_Error with "token cursor is done";
@@ -106,10 +80,8 @@ package body WisiToken.BNF.Generate_Utils is
       use WisiToken.Wisi_Ada;
       Descriptor : WisiToken.Descriptor renames Data.Descriptor.all;
    begin
-      Data.Grammar.Set_First (Descriptor.First_Nonterminal);
-      Data.Grammar.Set_Last (Descriptor.Last_Nonterminal);
-      Data.Source_Line_Map.Set_First (Descriptor.First_Nonterminal);
-      Data.Source_Line_Map.Set_Last (Descriptor.Last_Nonterminal);
+      Data.Grammar.Set_First_Last (Descriptor.First_Nonterminal, 
Descriptor.Last_Nonterminal);
+      Data.Source_Line_Map.Set_First_Last (Descriptor.First_Nonterminal, 
Descriptor.Last_Nonterminal);
 
       Data.Action_Names := new Names_Array_Array (Descriptor.First_Nonterminal 
.. Descriptor.Last_Nonterminal);
       Data.Check_Names  := new Names_Array_Array (Descriptor.First_Nonterminal 
.. Descriptor.Last_Nonterminal);
@@ -121,8 +93,7 @@ package body WisiToken.BNF.Generate_Utils is
              (Find_Token_ID (Data, Start_Token) & Descriptor.EOI_ID + 
WisiToken.Syntax_Trees.Null_Action);
 
          Data.Source_Line_Map (Descriptor.Accept_ID).Line := 
Line_Number_Type'First;
-         Data.Source_Line_Map (Descriptor.Accept_ID).RHS_Map.Set_First (0);
-         Data.Source_Line_Map (Descriptor.Accept_ID).RHS_Map.Set_Last (0);
+         Data.Source_Line_Map (Descriptor.Accept_ID).RHS_Map.Set_First_Last 
(0, 0);
          Data.Source_Line_Map (Descriptor.Accept_ID).RHS_Map (0) := 
Line_Number_Type'First;
       exception
       when Not_Found =>
@@ -144,12 +115,10 @@ package body WisiToken.BNF.Generate_Utils is
          begin
             LHS := Find_Token_ID (Data, -Rule.Left_Hand_Side);
 
-            RHSs.Set_First (RHS_Index);
-            RHSs.Set_Last (Natural (Rule.Right_Hand_Sides.Length) - 1);
+            RHSs.Set_First_Last (RHS_Index, Natural 
(Rule.Right_Hand_Sides.Length) - 1);
 
             Data.Source_Line_Map (LHS).Line := Rule.Source_Line;
-            Data.Source_Line_Map (LHS).RHS_Map.Set_First (RHSs.First_Index);
-            Data.Source_Line_Map (LHS).RHS_Map.Set_Last (RHSs.Last_Index);
+            Data.Source_Line_Map (LHS).RHS_Map.Set_First_Last 
(RHSs.First_Index, RHSs.Last_Index);
 
             for Right_Hand_Side of Rule.Right_Hand_Sides loop
                declare
@@ -159,8 +128,7 @@ package body WisiToken.BNF.Generate_Utils is
                   I      : Integer := 1;
                begin
                   if Right_Hand_Side.Tokens.Length > 0 then
-                     Tokens.Set_First (I);
-                     Tokens.Set_Last (Integer (Right_Hand_Side.Tokens.Length));
+                     Tokens.Set_First_Last (I, Integer 
(Right_Hand_Side.Tokens.Length));
                      for Token of Right_Hand_Side.Tokens loop
                         Tokens (I) := Find_Token_ID (Data, -Token.Identifier);
                         I := I + 1;
@@ -296,34 +264,14 @@ package body WisiToken.BNF.Generate_Utils is
    is begin
       case Cursor.Kind is
       when Non_Grammar_Kind =>
-         declare
-            Token_Ref : constant 
WisiToken.BNF.Token_Lists.Constant_Reference_Type :=
-              WisiToken.BNF.Token_Lists.Constant_Reference 
(Container.Data.Tokens.Non_Grammar, Cursor.Token_Kind);
-
-            Item_Ref : constant String_Pair_Lists.Constant_Reference_Type :=
-              String_Pair_Lists.Constant_Reference (Token_Ref.Element.Tokens, 
Cursor.Token_Item);
-         begin
-            return (Element => Item_Ref.Element.all.Name'Access);
-         end;
+         return
+           (Element => Container.Data.Tokens.Non_Grammar 
(Cursor.Token_Kind).Tokens (Cursor.Token_Item).Name'Access);
 
       when Terminals_Keywords =>
-         declare
-            Keyword_Ref : constant String_Pair_Lists.Constant_Reference_Type :=
-              String_Pair_Lists.Constant_Reference 
(Container.Data.Tokens.Keywords, Cursor.Keyword);
-         begin
-            return (Element => Keyword_Ref.Element.all.Name'Access);
-         end;
+         return (Element => Container.Data.Tokens.Keywords 
(Cursor.Keyword).Name'Access);
 
       when Terminals_Others =>
-         declare
-            Token_Ref : constant 
WisiToken.BNF.Token_Lists.Constant_Reference_Type :=
-              WisiToken.BNF.Token_Lists.Constant_Reference 
(Container.Data.Tokens.Tokens, Cursor.Token_Kind);
-
-            Item_Ref : constant String_Pair_Lists.Constant_Reference_Type :=
-              String_Pair_Lists.Constant_Reference (Token_Ref.Element.Tokens, 
Cursor.Token_Item);
-         begin
-            return (Element => Item_Ref.Element.all.Name'Access);
-         end;
+         return (Element => Container.Data.Tokens.Tokens 
(Cursor.Token_Kind).Tokens (Cursor.Token_Item).Name'Access);
 
       when EOI =>
          return (Element => Aliased_EOI_Name'Access);
@@ -332,12 +280,7 @@ package body WisiToken.BNF.Generate_Utils is
          return (Element => Aliased_WisiToken_Accept_Name'Access);
 
       when Nonterminal =>
-         declare
-            Rule_Ref : constant Rule_Lists.Constant_Reference_Type := 
Rule_Lists.Constant_Reference
-              (Container.Data.Tokens.Rules, Cursor.Nonterminal);
-         begin
-            return (Element => Rule_Ref.Element.all.Left_Hand_Side'Access);
-         end;
+         return (Element => Container.Data.Tokens.Rules 
(Cursor.Nonterminal).Left_Hand_Side'Access);
 
       when Done =>
          raise SAL.Programmer_Error with "token cursor is done";
@@ -391,7 +334,7 @@ package body WisiToken.BNF.Generate_Utils is
             Kind        => Terminals_Keywords,
             ID          => Cursor.Data.Descriptor.First_Terminal,
             Token_Kind  => WisiToken.BNF.Token_Lists.No_Element,
-            Token_Item  => String_Pair_Lists.No_Element,
+            Token_Item  => String_Triple_Lists.No_Element,
             Keyword     => Cursor.Data.Tokens.Keywords.First,
             Nonterminal => Rule_Lists.No_Element);
 
@@ -404,13 +347,13 @@ package body WisiToken.BNF.Generate_Utils is
             Kind        => Terminals_Others,
             ID          => Cursor.ID,
             Token_Kind  => Cursor.Data.Tokens.Tokens.First,
-            Token_Item  => String_Pair_Lists.No_Element,
+            Token_Item  => String_Triple_Lists.No_Element,
             Keyword     => String_Pair_Lists.No_Element,
             Nonterminal => Rule_Lists.No_Element);
 
          if WisiToken.BNF.Token_Lists.Has_Element (Cursor.Token_Kind) then
             Cursor.Token_Item := Cursor.Data.Tokens.Tokens 
(Cursor.Token_Kind).Tokens.First;
-            return WisiToken.BNF.String_Pair_Lists.Has_Element 
(Cursor.Token_Item);
+            return WisiToken.BNF.String_Triple_Lists.Has_Element 
(Cursor.Token_Item);
          else
             return False;
          end if;
@@ -422,7 +365,7 @@ package body WisiToken.BNF.Generate_Utils is
             Kind        => EOI,
             ID          => Cursor.ID,
             Token_Kind  => WisiToken.BNF.Token_Lists.No_Element,
-            Token_Item  => String_Pair_Lists.No_Element,
+            Token_Item  => String_Triple_Lists.No_Element,
             Keyword     => String_Pair_Lists.No_Element,
             Nonterminal => Rule_Lists.No_Element);
 
@@ -436,7 +379,7 @@ package body WisiToken.BNF.Generate_Utils is
                   Kind        => WisiToken_Accept,
                   ID          => Cursor.ID,
                   Token_Kind  => WisiToken.BNF.Token_Lists.No_Element,
-                  Token_Item  => String_Pair_Lists.No_Element,
+                  Token_Item  => String_Triple_Lists.No_Element,
                   Keyword     => String_Pair_Lists.No_Element,
                   Nonterminal => Rule_Lists.No_Element);
             else
@@ -455,7 +398,7 @@ package body WisiToken.BNF.Generate_Utils is
             Kind        => Nonterminal,
             ID          => Cursor.ID,
             Token_Kind  => WisiToken.BNF.Token_Lists.No_Element,
-            Token_Item  => String_Pair_Lists.No_Element,
+            Token_Item  => String_Triple_Lists.No_Element,
             Keyword     => String_Pair_Lists.No_Element,
             Nonterminal => Cursor.Data.Tokens.Rules.First);
 
@@ -482,14 +425,14 @@ package body WisiToken.BNF.Generate_Utils is
          Kind        => Non_Grammar_Kind,
          ID          => Token_ID'First,
          Token_Kind  => Data.Tokens.Non_Grammar.First,
-         Token_Item  => String_Pair_Lists.No_Element,
+         Token_Item  => String_Triple_Lists.No_Element,
          Keyword     => String_Pair_Lists.No_Element,
          Nonterminal => Rule_Lists.No_Element);
    begin
       if Non_Grammar then
          if WisiToken.BNF.Token_Lists.Has_Element (Cursor.Token_Kind) then
             Cursor.Token_Item := Cursor.Data.Tokens.Non_Grammar 
(Cursor.Token_Kind).Tokens.First;
-            if WisiToken.BNF.String_Pair_Lists.Has_Element (Cursor.Token_Item) 
then
+            if WisiToken.BNF.String_Triple_Lists.Has_Element 
(Cursor.Token_Item) then
                return Cursor;
             end if;
          end if;
@@ -508,15 +451,15 @@ package body WisiToken.BNF.Generate_Utils is
 
       case Cursor.Kind is
       when Non_Grammar_Kind =>
-         String_Pair_Lists.Next (Cursor.Token_Item);
-         if String_Pair_Lists.Has_Element (Cursor.Token_Item) then
+         String_Triple_Lists.Next (Cursor.Token_Item);
+         if String_Triple_Lists.Has_Element (Cursor.Token_Item) then
             return;
          else
             WisiToken.BNF.Token_Lists.Next (Cursor.Token_Kind);
 
             if WisiToken.BNF.Token_Lists.Has_Element (Cursor.Token_Kind) then
                Cursor.Token_Item := Cursor.Data.Tokens.Non_Grammar 
(Cursor.Token_Kind).Tokens.First;
-               if String_Pair_Lists.Has_Element (Cursor.Token_Item) then
+               if String_Triple_Lists.Has_Element (Cursor.Token_Item) then
                   return;
                end if;
             end if;
@@ -541,14 +484,14 @@ package body WisiToken.BNF.Generate_Utils is
          return;
 
       when Terminals_Others =>
-         WisiToken.BNF.String_Pair_Lists.Next (Cursor.Token_Item);
-         if WisiToken.BNF.String_Pair_Lists.Has_Element (Cursor.Token_Item) 
then
+         WisiToken.BNF.String_Triple_Lists.Next (Cursor.Token_Item);
+         if WisiToken.BNF.String_Triple_Lists.Has_Element (Cursor.Token_Item) 
then
             return;
          else
             WisiToken.BNF.Token_Lists.Next (Cursor.Token_Kind);
             if WisiToken.BNF.Token_Lists.Has_Element (Cursor.Token_Kind) then
                Cursor.Token_Item := Cursor.Data.Tokens.Tokens 
(Cursor.Token_Kind).Tokens.First;
-               if WisiToken.BNF.String_Pair_Lists.Has_Element 
(Cursor.Token_Item) then
+               if WisiToken.BNF.String_Triple_Lists.Has_Element 
(Cursor.Token_Item) then
                   return;
                end if;
             end if;
@@ -608,13 +551,13 @@ package body WisiToken.BNF.Generate_Utils is
    is begin
       case Cursor.Kind is
       when Non_Grammar_Kind =>
-         return -Token_Lists.Constant_Reference 
(Cursor.Data.Tokens.Non_Grammar, Cursor.Token_Kind).Kind;
+         return -Cursor.Data.Tokens.Non_Grammar (Cursor.Token_Kind).Kind;
 
       when Terminals_Keywords =>
          return "keyword";
 
       when Terminals_Others =>
-         return -Token_Lists.Constant_Reference (Cursor.Data.Tokens.Tokens, 
Cursor.Token_Kind).Kind;
+         return -Cursor.Data.Tokens.Tokens (Cursor.Token_Kind).Kind;
 
       when EOI =>
          return "EOI";
@@ -623,7 +566,7 @@ package body WisiToken.BNF.Generate_Utils is
          return "accept";
 
       when Nonterminal =>
-            return "nonterminal";
+         return "nonterminal";
 
       when Done =>
          raise SAL.Programmer_Error with "token cursor is done";
@@ -634,34 +577,13 @@ package body WisiToken.BNF.Generate_Utils is
    is begin
       case Cursor.Kind is
       when Non_Grammar_Kind =>
-         declare
-            Token_Ref : constant 
WisiToken.BNF.Token_Lists.Constant_Reference_Type :=
-              WisiToken.BNF.Token_Lists.Constant_Reference 
(Cursor.Data.Tokens.Non_Grammar, Cursor.Token_Kind);
-
-            Item_Ref : constant String_Pair_Lists.Constant_Reference_Type :=
-              String_Pair_Lists.Constant_Reference (Token_Ref.Element.Tokens, 
Cursor.Token_Item);
-         begin
-            return -Item_Ref.Element.Value;
-         end;
+         return -Cursor.Data.Tokens.Non_Grammar (Cursor.Token_Kind).Tokens 
(Cursor.Token_Item).Value;
 
       when Terminals_Keywords =>
-         declare
-            Keyword_Ref : constant String_Pair_Lists.Constant_Reference_Type :=
-              String_Pair_Lists.Constant_Reference 
(Cursor.Data.Tokens.Keywords, Cursor.Keyword);
-         begin
-            return -Keyword_Ref.Element.Value;
-         end;
+         return -Cursor.Data.Tokens.Keywords (Cursor.Keyword).Value;
 
       when Terminals_Others =>
-         declare
-            Token_Ref : constant 
WisiToken.BNF.Token_Lists.Constant_Reference_Type :=
-              WisiToken.BNF.Token_Lists.Constant_Reference 
(Cursor.Data.Tokens.Tokens, Cursor.Token_Kind);
-
-            Item_Ref : constant String_Pair_Lists.Constant_Reference_Type :=
-              String_Pair_Lists.Constant_Reference (Token_Ref.Element.Tokens, 
Cursor.Token_Item);
-         begin
-            return -Item_Ref.Element.Value;
-         end;
+         return -Cursor.Data.Tokens.Tokens (Cursor.Token_Kind).Tokens 
(Cursor.Token_Item).Value;
 
       when EOI | WisiToken_Accept | Nonterminal =>
             return "";
@@ -671,6 +593,26 @@ package body WisiToken.BNF.Generate_Utils is
       end case;
    end Value;
 
+   function Repair_Image (Cursor : in Token_Cursor) return String
+   is begin
+      case Cursor.Kind is
+      when Non_Grammar_Kind =>
+         return -Cursor.Data.Tokens.Non_Grammar (Cursor.Token_Kind).Tokens 
(Cursor.Token_Item).Repair_Image;
+
+      when Terminals_Keywords =>
+         return "";
+
+      when Terminals_Others =>
+         return -Cursor.Data.Tokens.Tokens (Cursor.Token_Kind).Tokens 
(Cursor.Token_Item).Repair_Image;
+
+      when EOI | WisiToken_Accept | Nonterminal =>
+            return "";
+
+      when Done =>
+         raise SAL.Programmer_Error with "token cursor is done";
+      end case;
+   end Repair_Image;
+
    function To_Conflicts
      (Data             : aliased in out Generate_Data;
       Conflicts        :         in     WisiToken.BNF.Conflict_Lists.List;
@@ -760,15 +702,6 @@ package body WisiToken.BNF.Generate_Utils is
       return Result;
    end To_McKenzie_Param;
 
-   procedure Count_Actions (Data : in out Generate_Utils.Generate_Data)
-   is begin
-      Data.Table_Actions_Count := 0;
-      for State_Index in Data.LR_Parse_Table.States'Range loop
-         Data.Table_Actions_Count := Data.Table_Actions_Count +
-           Actions_Length (Data.LR_Parse_Table.States (State_Index)) + 1;
-      end loop;
-   end Count_Actions;
-
    procedure Put_Stats
      (Input_Data    : in WisiToken_Grammar_Runtime.User_Data_Type;
       Generate_Data : in Generate_Utils.Generate_Data)
@@ -780,24 +713,7 @@ package body WisiToken.BNF.Generate_Utils is
         (Integer'Image (Input_Data.Rule_Count) & " rules," &
            Integer'Image (Input_Data.Action_Count) & " user actions," &
            Integer'Image (Input_Data.Check_Count) & " checks," &
-           WisiToken.State_Index'Image (Generate_Data.Parser_State_Count) & " 
states," &
-           Integer'Image (Generate_Data.Table_Actions_Count) & " parse 
actions");
+           WisiToken.State_Index'Image (Generate_Data.Parser_State_Count) & " 
states");
    end Put_Stats;
 
-   function Actions_Length (State : in Parse.LR.Parse_State) return Integer
-   is
-      use all type WisiToken.Parse.LR.Action_Node_Ptr;
-      Node : Parse.LR.Action_Node_Ptr := State.Action_List;
-   begin
-      return Result : Integer := 0
-      do
-         loop
-            exit when Node = null;
-            Result := Result + 1;
-            Node := Node.Next;
-            exit when Node.Next = null; -- don't count Error
-         end loop;
-      end return;
-   end Actions_Length;
-
 end WisiToken.BNF.Generate_Utils;
diff --git a/wisitoken-bnf-generate_utils.ads b/wisitoken-bnf-generate_utils.ads
index bcf599f..0cd7508 100644
--- a/wisitoken-bnf-generate_utils.ads
+++ b/wisitoken-bnf-generate_utils.ads
@@ -49,11 +49,10 @@ package WisiToken.BNF.Generate_Utils is
       --  The following fields are LR specific; so far, it's not worth
       --  splitting them out.
 
-      Ignore_Conflicts    : Boolean                       := False;
-      Conflicts           : WisiToken.Generate.LR.Conflict_Lists.List;
-      LR_Parse_Table      : WisiToken.Parse.LR.Parse_Table_Ptr;
-      Table_Actions_Count : Integer                       := -1; -- parse, not 
user, actions
-      Parser_State_Count  : WisiToken.Unknown_State_Index := 0;
+      Ignore_Conflicts   : Boolean                       := False;
+      Conflicts          : WisiToken.Generate.LR.Conflict_Lists.List;
+      LR_Parse_Table     : WisiToken.Parse.LR.Parse_Table_Ptr;
+      Parser_State_Count : WisiToken.Unknown_State_Index := 0;
    end record;
 
    function Initialize
@@ -132,6 +131,12 @@ package WisiToken.BNF.Generate_Utils is
    --  Tokens  : Tokens (i).Tokens (j).Value
    --  Rules   : empty string (they have no Value)
 
+   function Repair_Image (Cursor : in Token_Cursor) return String;
+   --  Return the token repair image from the .wy file:
+   --  Keywords: empty string
+   --  Tokens  : Tokens (i).Tokens (j).Repair_Image
+   --  Rules   : empty string
+
    function To_Conflicts
      (Data             : aliased in out Generate_Data;
       Conflicts        :         in     WisiToken.BNF.Conflict_Lists.List;
@@ -149,15 +154,10 @@ package WisiToken.BNF.Generate_Utils is
       Item :         in McKenzie_Recover_Param_Type)
      return WisiToken.Parse.LR.McKenzie_Param_Type;
 
-   procedure Count_Actions (Data : in out Generate_Utils.Generate_Data);
-
    procedure Put_Stats
      (Input_Data    : in WisiToken_Grammar_Runtime.User_Data_Type;
       Generate_Data : in Generate_Utils.Generate_Data);
 
-   function Actions_Length (State : in Parse.LR.Parse_State) return Integer;
-   --  Not including Error.
-
 private
 
    type Token_Cursor_Kind is
@@ -168,7 +168,7 @@ private
       Kind        : Token_Cursor_Kind;
       ID          : Token_ID;
       Token_Kind  : WisiToken.BNF.Token_Lists.Cursor; -- Non_Grammar or 
Tokens, depending on Kind
-      Token_Item  : String_Pair_Lists.Cursor;
+      Token_Item  : String_Triple_Lists.Cursor;
       Keyword     : String_Pair_Lists.Cursor;
       Nonterminal : Rule_Lists.Cursor;
    end record;
diff --git a/wisitoken-bnf-output_ada_common.adb 
b/wisitoken-bnf-output_ada_common.adb
index b3d7dea..97ec9ed 100644
--- a/wisitoken-bnf-output_ada_common.adb
+++ b/wisitoken-bnf-output_ada_common.adb
@@ -33,13 +33,12 @@ package body WisiToken.BNF.Output_Ada_Common is
    function Duplicate_Reduce (State : in Parse.LR.Parse_State) return Boolean
    is
       use Parse.LR;
-      Node        : Action_Node_Ptr       := State.Action_List;
-      Action_Node : Parse_Action_Node_Ptr := Node.Action;
+      Action_Node : Parse_Action_Node_Ptr;
       First       : Boolean               := True;
       Action      : Reduce_Action_Rec;
    begin
-      loop
-         Action_Node := Node.Action;
+      for Node of State.Action_List loop
+         Action_Node := Node.Actions;
          if Action_Node.Next /= null then
             --  conflict
             return False;
@@ -55,32 +54,27 @@ package body WisiToken.BNF.Output_Ada_Common is
                return False;
             end if;
          end if;
-         Node := Node.Next;
-         exit when Node.Next = null; --  Last entry is Error.
       end loop;
       return True;
    end Duplicate_Reduce;
 
    function Symbols_Image (State : in Parse.LR.Parse_State) return String
    is
+      use all type Ada.Containers.Count_Type;
       use Ada.Strings.Unbounded;
-      use Parse.LR;
 
       Result     : Unbounded_String;
-      Need_Comma : Boolean          := False;
-      Node       : Action_Node_Ptr  := State.Action_List;
+      Need_Comma : Boolean := False;
    begin
-      if Generate_Utils.Actions_Length (State) = 1 then
-         return "(1 => " & Token_ID'Image (Node.Symbol) & ")";
+      if State.Action_List.Length = 1 then
+         return "(1 => " & Token_ID'Image (State.Action_List (1).Symbol) & ")";
       else
          Result := +"(";
-         loop
+         for Node of State.Action_List loop
             Result := Result &
               (if Need_Comma then ", " else "") &
               Trimmed_Image (Node.Symbol);
             Need_Comma := True;
-            Node := Node.Next;
-            exit when Node.Next = null; -- last is Error
          end loop;
          Result := Result & ")";
          return -Result;
@@ -267,6 +261,7 @@ package body WisiToken.BNF.Output_Ada_Common is
                            
"WisiToken.Parse.LR.Parser.Language_String_ID_Set_Access;");
          else
             Indent_Line ("  (Parser                       :    out 
WisiToken.Parse.LR.Parser_No_Recover.Parser;");
+            Indent_Line ("   --  no error recovery");
          end if;
          Indent_Line ("   Trace                        : not null access 
WisiToken.Trace'Class;");
          Indent_Start ("   User_Data                    : in     
WisiToken.Syntax_Trees.User_Data_Access");
@@ -555,11 +550,15 @@ package body WisiToken.BNF.Output_Ada_Common is
          declare
             use Ada.Containers;
             Base_Indent : constant Ada.Text_IO.Count := Indent;
-            Node        : Action_Node_Ptr := Table.States 
(State_Index).Action_List;
          begin
+            Indent_Line
+              ("Table.States (" & Trimmed_Image (State_Index) & 
").Action_List.Set_Capacity (" &
+                 Trimmed_Image (Table.States (State_Index).Action_List.Length) 
& ");");
+
             if Duplicate_Reduce (Table.States (State_Index)) then
                declare
-                  Action : constant Reduce_Action_Rec := Node.Action.Item;
+                  Node   : Action_Node renames Table.States 
(State_Index).Action_List (1);
+                  Action : constant Reduce_Action_Rec := Node.Actions.Item;
                begin
                   Set_Col (Indent);
                   Line := +"Add_Action (Table.States (" & Trimmed_Image 
(State_Index) & "), " &
@@ -587,11 +586,10 @@ package body WisiToken.BNF.Output_Ada_Common is
                end;
 
             else
-               loop
-                  exit when Node = null;
+               for Node of Table.States (State_Index).Action_List loop
                   Set_Col (Indent);
                   declare
-                     Action_Node : Parse_Action_Node_Ptr := Node.Action;
+                     Action_Node : Parse_Action_Node_Ptr := Node.Actions;
                   begin
                      case Action_Node.Item.Verb is
                      when Shift =>
@@ -632,7 +630,7 @@ package body WisiToken.BNF.Output_Ada_Common is
                         Append (");");
 
                      when Parse.LR.Error =>
-                        Line := +"Add_Error (Table.States (" & Trimmed_Image 
(State_Index) & "));";
+                        raise SAL.Programmer_Error;
                      end case;
                      Indent_Wrap (-Line);
                      Line_Count := Line_Count + 1;
@@ -675,24 +673,22 @@ package body WisiToken.BNF.Output_Ada_Common is
                      end loop;
                   end;
                   Indent := Base_Indent;
-                  Node   := Node.Next;
                end loop;
             end if;
          end Actions;
 
+         if Table.States (State_Index).Goto_List.Length > 0 then
+            Indent_Line
+              ("Table.States (" & Trimmed_Image (State_Index) & 
").Goto_List.Set_Capacity (" &
+                 Trimmed_Image (Table.States (State_Index).Goto_List.Length) & 
");");
+         end if;
          Gotos :
-         declare
-            Node : Goto_Node_Ptr := Table.States (State_Index).Goto_List;
-         begin
-            loop
-               exit when Node = null;
-               Set_Col (Indent);
-               Put ("Add_Goto (Table.States (" & Trimmed_Image (State_Index) & 
"), ");
-               Put_Line (Trimmed_Image (Symbol (Node)) & ", " & Trimmed_Image 
(State (Node)) & ");");
-               Line_Count := Line_Count + 1;
-               Node := Next (Node);
-            end loop;
-         end Gotos;
+         for Node of Table.States (State_Index).Goto_List loop
+            Set_Col (Indent);
+            Put ("Add_Goto (Table.States (" & Trimmed_Image (State_Index) & 
"), ");
+            Put_Line (Trimmed_Image (Node.Symbol) & ", " & Trimmed_Image 
(Node.State) & ");");
+            Line_Count := Line_Count + 1;
+         end loop Gotos;
 
          if Input_Data.Language_Params.Error_Recover then
             if Table.States (State_Index).Kernel.Length > 0 then
@@ -741,6 +737,7 @@ package body WisiToken.BNF.Output_Ada_Common is
       for Subr in 1 .. Subr_Count loop
          Indent_Line ("Subr_" & Trimmed_Image (Subr) & ";");
       end loop;
+      Indent_Line ("Table.Error_Action := new Parse_Action_Node'((Verb => 
Error), null);");
       Indent := Indent - 3;
       Indent_Line ("end;");
    end Create_LR_Parser_Table;
@@ -1217,14 +1214,9 @@ package body WisiToken.BNF.Output_Ada_Common is
                Indent_Line ("    continue; }");
 
             elsif Kind (I) = "delimited-text" then
-               --  Val contains the start and end strings, separated by space
-               declare
-                  Start_Last : constant Integer := Index (Val, " ");
-               begin
-                  Indent_Line
-                    (Val (1 .. Start_Last - 1) & " {*id = " & 
WisiToken.Token_ID'Image (ID (I)) &
-                       "; skip_to(lexer, " & Val (Start_Last + 1 .. Val'Last) 
& "); continue;}");
-               end;
+               Indent_Line
+                    (Val & " {*id = " & WisiToken.Token_ID'Image (ID (I)) &
+                       "; skip_to(lexer, " & Repair_Image (I) & "); 
continue;}");
 
             elsif 0 /= Index (Source => Val, Pattern => "/") then
                Indent_Line (Val & " {*id = " & WisiToken.Token_ID'Image (ID 
(I)) & "; continue;}");
diff --git a/wisitoken-bnf-output_ada_emacs.adb 
b/wisitoken-bnf-output_ada_emacs.adb
index ae109fa..2de6e0c 100644
--- a/wisitoken-bnf-output_ada_emacs.adb
+++ b/wisitoken-bnf-output_ada_emacs.adb
@@ -40,7 +40,6 @@ with WisiToken.Generate.Packrat;
 with WisiToken_Grammar_Runtime;
 procedure WisiToken.BNF.Output_Ada_Emacs
   (Input_Data            :         in WisiToken_Grammar_Runtime.User_Data_Type;
-   Elisp_Tokens          :         in WisiToken.BNF.Tokens;
    Output_File_Name_Root :         in String;
    Generate_Data         : aliased in 
WisiToken.BNF.Generate_Utils.Generate_Data;
    Packrat_Data          :         in WisiToken.Generate.Packrat.Data;
@@ -277,28 +276,10 @@ is
             when others =>  "(" & (-Result) & "))");
       end Statement_Params;
 
-      function Containing_Params (Params : in String) return String
-      is
-         --  Input looks like: 1 2)
-         First        : constant Integer := Index_Non_Blank (Params);
-         Second       : constant Integer := Index (Params, Blank_Set, First);
-         First_Label  : constant String  := Params (First .. Second - 1);
-         Second_Label : constant String  := Params (Second + 1 .. Params'Last 
- 1);
-      begin
-         if (0 = Index (First_Label, Numeric, Outside) or else Label_Used 
(First_Label)) and
-           (0 = Index (Second_Label, Numeric, Outside) or else Label_Used 
(Second_Label))
-         then
-            Nonterm_Needed := True;
-            return " (Parse_Data, Tree, Nonterm, Tokens, " & First_Label & ", 
" & Second_Label & ")";
-         else
-            return "";
-         end if;
-      end Containing_Params;
-
       function Motion_Params (Params : in String) return String
       is
-         --  Input looks like: [1 [2 EXCEPTION WHEN] 3 ...]
-         --  Result: (..., Motion_Param_Array'((1, Empty_IDs) & (2, (3 & 8)) & 
(3, Empty_IDs))
+         --  Input looks like: [1 [2 EXCEPTION] 3 ...]
+         --  Result: (..., Motion_Param_Array'((1, Invalid_Token_ID) & (2, 3) 
& (3, Invalid_Token_ID))
          use Generate_Utils;
          use Ada.Strings.Maps;
 
@@ -307,56 +288,61 @@ is
          Last   : Integer          := Index_Non_Blank (Params); -- skip [
          First  : Integer;
          Vector : Boolean;
-         Result : Unbounded_String := +" (Parse_Data, Tree, Nonterm, Tokens, 
(";
+         Result : Unbounded_String;
 
          Index_First  : Integer;
          Index_Last   : Integer;
-         IDs          : Unbounded_String;
-         IDs_Count    : Integer;
-         Need_Comma_1 : Boolean := False;
-         Need_Comma_2 : Boolean := False;
+         ID           : Unbounded_String;
+         Need_Comma : Boolean := False;
+         Count      : Integer          := 0;
       begin
          loop
+            if not (Last in Params'First .. Params'Last) then
+               Put_Error
+                 (Error_Message
+                    (Input_Data.Grammar_Lexer.File_Name, RHS.Source_Line,
+                     "Missing ']' or ')'"));
+               exit;
+            end if;
             Last := Index_Non_Blank (Params, Integer'Min (Params'Last, Last + 
1));
 
             exit when Params (Last) = ']' or Params (Last) = ')';
 
             Vector := Params (Last) = '[';
             if Vector then
-               Index_First  := Last + 1;
-               Last         := Index (Params, Delim, Index_First);
-               Index_Last   := Last - 1;
-               IDs_Count    := 0;
-               IDs          := Null_Unbounded_String;
-               Need_Comma_2 := False;
-               loop
-                  exit when Params (Last) = ']';
-                  First     := Last + 1;
-                  Last      := Index (Params, Delim, First);
-                  IDs_Count := IDs_Count + 1;
-                  begin
-                     IDs := IDs & (if Need_Comma_2 then " & " else "") &
-                       Trimmed_Image (Find_Token_ID (Generate_Data, Params 
(First .. Last - 1)));
-                     Need_Comma_2 := True;
-                  exception
-                  when E : Not_Found =>
-                     Put_Error
-                       (Error_Message
-                          (Input_Data.Grammar_Lexer.File_Name, RHS.Source_Line,
-                           Ada.Exceptions.Exception_Message (E)));
-                  end;
-               end loop;
+               Index_First := Last + 1;
+               Last        := Index (Params, Delim, Index_First);
+               Index_Last  := Last - 1;
+               First       := Last + 1;
+               Last        := Index (Params, Delim, First);
+               begin
+                  ID := +Trimmed_Image (Find_Token_ID (Generate_Data, Params 
(First .. Last - 1)));
+               exception
+               when E : Not_Found =>
+                  Put_Error
+                    (Error_Message
+                       (Input_Data.Grammar_Lexer.File_Name, RHS.Source_Line,
+                        Ada.Exceptions.Exception_Message (E)));
+               end;
 
                declare
                   Label : constant String := Params (Index_First .. 
Index_Last);
                begin
                   if 0 = Index (Label, Numeric, Outside) or else Label_Used 
(Label) then
-                     Nonterm_Needed := True;
-                     Result := Result & (if Need_Comma_1 then " & " else "") & 
"(" &
-                       Label & ", " &
-                       (if IDs_Count = 1 then "+" else "") & IDs & ")";
+                     Result := Result & (if Need_Comma then " & " else "") & 
"(" &
+                       Label & ", " & ID & ")";
+                     Need_Comma := True;
+                     Count  := Count + 1;
                   end if;
                end;
+               if Params (Last) /= ']' then
+                  Put_Error
+                    (Error_Message
+                       (Input_Data.Grammar_Lexer.File_Name, RHS.Source_Line,
+                        "too many token IDs in motion action"));
+                  return -Result & "))";
+               end if;
+
             else
                First  := Index_Non_Blank (Params, Last);
                Last   := Index (Params, Delim, First);
@@ -364,14 +350,20 @@ is
                   Label : constant String := Params (First .. Last - 1);
                begin
                   if 0 = Index (Label, Numeric, Outside) or else Label_Used 
(Label) then
-                     Nonterm_Needed := True;
-                     Result := Result & (if Need_Comma_1 then " & " else "") & 
"(" & Label & ", Empty_IDs)";
+                     Result := Result & (if Need_Comma then " & " else "") & 
"(" & Label & ", Invalid_Token_ID)";
+                     Need_Comma := True;
+                     Count  := Count + 1;
                   end if;
                end;
             end if;
-            Need_Comma_1 := True;
          end loop;
-         return -(Result & "))");
+         if Count <= 1 then
+            --  No point in calling Motion_Action with only one param.
+            return "";
+         else
+            Nonterm_Needed := True;
+            return " (Parse_Data, Tree, Nonterm, Tokens, (" & (-Result) & "))";
+         end if;
       end Motion_Params;
 
       function Face_Apply_Params (Params : in String) return String
@@ -1073,15 +1065,6 @@ is
                end if;
             end;
 
-         elsif Elisp_Name = "wisi-containing-action" then
-            declare
-               Params : constant String := Containing_Params (Line (Last + 1 
.. Line'Last));
-            begin
-               if Params'Length > 0 then
-                  Navigate_Lines.Append (Elisp_Name_To_Ada (Elisp_Name, False, 
Trim => 5) & Params & ";");
-               end if;
-            end;
-
          elsif Elisp_Name = "wisi-motion-action" then
             declare
                Params : constant String := Motion_Params (Line (Last + 1 .. 
Line'Last));
@@ -1644,17 +1627,9 @@ is
       Output_Elisp_Common.Indent_Name_Table
         (Output_File_Name_Root, "process-face-table", Input_Data.Tokens.Faces);
 
-      --  We need the elisp lexer for some operations
-      if Elisp_Tokens.Keywords.Length > 0 then
-         New_Line;
-         Output_Elisp_Common.Indent_Keyword_Table
-           (Output_File_Name_Root, "elisp", Elisp_Tokens.Keywords, 
Ada.Strings.Unbounded.To_String'Access);
-      end if;
-      if Elisp_Tokens.Tokens.Length > 0 then
-         New_Line;
-         Output_Elisp_Common.Indent_Token_Table
-           (Output_File_Name_Root, "elisp", Elisp_Tokens.Tokens, 
Ada.Strings.Unbounded.To_String'Access);
-      end if;
+      --  We need -repair-image for wisi-repair-error
+      New_Line;
+      Output_Elisp_Common.Indent_Repair_Image (Output_File_Name_Root, 
"process", Input_Data.Tokens);
 
       New_Line;
       Put_Line ("(provide '" & Output_File_Name_Root & "-process)");
diff --git a/wisitoken-bnf-output_elisp.adb b/wisitoken-bnf-output_elisp.adb
deleted file mode 100644
index ff52449..0000000
--- a/wisitoken-bnf-output_elisp.adb
+++ /dev/null
@@ -1,293 +0,0 @@
---  Abstract :
---
---  Output Elisp code implementing the grammar defined by the parameters.
---
---  Copyright (C) 2012 - 2015, 2017 - 2019 Free Software Foundation, Inc.
---
---  The WisiToken package is free software; you can redistribute it
---  and/or modify it under terms of the GNU General Public License as
---  published by the Free Software Foundation; either version 3, or
---  (at your option) any later version. This library 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.
---
---  As a special exception under Section 7 of GPL version 3, you are granted
---  additional permissions described in the GCC Runtime Library Exception,
---  version 3.1, as published by the Free Software Foundation.
-
-pragma License (Modified_GPL);
-
-with Ada.Text_IO; use Ada.Text_IO;
-with WisiToken.BNF.Generate_Utils;
-with WisiToken.BNF.Output_Elisp_Common;
-with WisiToken.Generate.Packrat;
-with WisiToken.Parse.LR;
-with WisiToken_Grammar_Runtime;
-procedure WisiToken.BNF.Output_Elisp
-  (Input_Data    :         in WisiToken_Grammar_Runtime.User_Data_Type;
-   Elisp_Package :         in String;
-   Generate_Data : aliased in WisiToken.BNF.Generate_Utils.Generate_Data;
-   Packrat_Data  :         in WisiToken.Generate.Packrat.Data;
-   Tuple         :         in Generate_Tuple)
-is
-   pragma Unreferenced (Packrat_Data);
-
-   procedure Action_Table (Table : in WisiToken.Parse.LR.Parse_Table; 
Descriptor : in WisiToken.Descriptor)
-   is
-      use WisiToken.Parse.LR;
-   begin
-      Put ("     [");
-      for State in Table.States'Range loop
-         if State = Table.States'First then
-            Put ("(");
-         else
-            Put ("      (");
-         end if;
-
-         Put ("(default . error)");
-
-         declare
-            Action : Action_Node_Ptr := Table.States (State).Action_List;
-         begin
-            loop
-               declare
-                  Parse_Action_Node : Parse_Action_Node_Ptr := Action.Action;
-                  Conflict          : constant Boolean      := 
Parse_Action_Node.Next /= null;
-               begin
-                  Put (" (" & Image (Action.Symbol, Descriptor) & " . ");
-
-                  if Conflict then
-                     Put ("(");
-                  end if;
-
-                  loop
-                     declare
-                        Parse_Action : Parse_Action_Rec renames 
Parse_Action_Node.Item;
-                     begin
-                        case Parse_Action.Verb is
-                        when Accept_It =>
-                           Put ("accept");
-
-                        when Error =>
-                           Put ("error");
-
-                        when Reduce =>
-                           Put
-                             ("(" & Image (Parse_Action.Production.LHS, 
Descriptor) & " ." &
-                                Integer'Image (Parse_Action.Production.RHS) & 
")");
-
-                        when Shift =>
-                           Put (State_Index'Image (Parse_Action.State));
-
-                        end case;
-
-                        if Parse_Action_Node.Next = null then
-                           if Conflict then
-                              Put (")");
-                           end if;
-                           Put (")");
-                           exit;
-                        else
-                           Put (" ");
-                           Parse_Action_Node := Parse_Action_Node.Next;
-                        end if;
-                     end;
-                  end loop;
-               end;
-
-               Action := Action.Next;
-
-               if Action.Next = null then
-                  if Action.Action.Item.Verb /= Error then
-                     raise SAL.Programmer_Error with "state" &
-                       State_Index'Image (State) & ": default action is not 
error";
-                  end if;
-                  --  let default handle it
-                  Action := null;
-               end if;
-
-               if Action = null then
-                  if State = Table.States'Last then
-                     Put (")");
-                  else
-                     Put_Line (")");
-                  end if;
-                  exit;
-               end if;
-            end loop;
-         end;
-      end loop;
-      Put_Line ("]");
-   end Action_Table;
-
-   procedure Goto_Table (Table : in WisiToken.Parse.LR.Parse_Table; Descriptor 
: in WisiToken.Descriptor)
-   is
-      use WisiToken.Parse.LR;
-
-      subtype Nonterminals is Token_ID range Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal;
-
-      function Count_Nonterminals (List : in Goto_Node_Ptr) return Integer
-      is
-         Item  : Goto_Node_Ptr := List;
-         Count : Integer       := 0;
-      begin
-         while Item /= null loop
-            if Symbol (Item) in Nonterminals then
-               Count := Count + 1;
-            end if;
-            Item := Next (Item);
-         end loop;
-         return Count;
-      end Count_Nonterminals;
-
-   begin
-      Put ("     [");
-      for State in Table.States'Range loop
-         declare
-            Nonterminal_Count : constant Integer := Count_Nonterminals 
(Table.States (State).Goto_List);
-            Gotos             : Goto_Node_Ptr    := Table.States 
(State).Goto_List;
-         begin
-            if Nonterminal_Count = 0 then
-               if State = Table.States'First then
-                  Put_Line ("nil");
-               else
-                  if State = Table.States'Last then
-                     Put ("      nil");
-                  else
-                     Put_Line ("      nil");
-                  end if;
-               end if;
-            else
-               if State = Table.States'First then
-                  Put ("(");
-               else
-                  Put ("      (");
-               end if;
-               loop
-                  if Symbol (Gotos) in Nonterminals then
-                     Put ("(" & Image (Symbol (Gotos), Descriptor) & " ." &
-                            State_Index'Image (Parse.LR.State (Gotos)) & ")");
-                  end if;
-                  Gotos := Next (Gotos);
-                  exit when Gotos = null;
-               end loop;
-               if State = Table.States'Last then
-                  Put (")");
-               else
-                  Put_Line (")");
-               end if;
-            end if;
-         end;
-      end loop;
-      Put ("]");
-   end Goto_Table;
-
-   procedure Output
-     (Elisp_Package : in String;
-      Tokens        : in WisiToken.BNF.Tokens;
-      Parser        : in WisiToken.Parse.LR.Parse_Table_Ptr;
-      Descriptor    : in WisiToken.Descriptor)
-   is
-      use Ada.Strings.Unbounded;
-      use Ada.Containers; -- count_type
-
-      Rule_Length : constant Count_Type := Tokens.Rules.Length;
-      Rule_Count  : Count_Type := 1;
-
-      RHS_Length : Count_Type;
-      RHS_Count  : Count_Type;
-   begin
-      Put_Line ("(defconst " & Elisp_Package & "-elisp-parse-table");
-      Put_Line ("   (wisi-compile-grammar");
-
-      --  nonterminal productions
-      Put ("   '((");
-      for Rule of Tokens.Rules loop
-         if Rule_Count = 1 then
-            Put ("(");
-         else
-            Put ("      (");
-         end if;
-         Put_Line (-Rule.Left_Hand_Side);
-
-         RHS_Length := Rule.Right_Hand_Sides.Length;
-         RHS_Count  := 1;
-         for RHS of Rule.Right_Hand_Sides loop
-            Put ("       ((");
-            for Token of RHS.Tokens loop
-               Put (-Token.Identifier & " ");
-            end loop;
-            if Length (RHS.Action) = 0 then
-               Put (")");
-            else
-               Put_Line (")");
-               Put ("        " & (-RHS.Action));
-            end if;
-
-            if RHS_Count = RHS_Length then
-               Put (")");
-            else
-               Put_Line (")");
-            end if;
-            RHS_Count := RHS_Count + 1;
-         end loop;
-         if Rule_Count = Rule_Length then
-            Put (")");
-         else
-            Put_Line (")");
-         end if;
-         Rule_Count := Rule_Count + 1;
-      end loop;
-      Put_Line (")");
-
-      Action_Table (Parser.all, Descriptor);
-      Goto_Table (Parser.all, Descriptor);
-      Put_Line ("))");
-
-      Put_Line ("  ""Parser table."")");
-   end Output;
-
-   procedure Create_Elisp (Algorithm : in LR_Generate_Algorithm)
-   is
-      use Ada.Strings.Unbounded;
-      File            : File_Type;
-      Elisp_Package_1 : constant String :=
-        (case Algorithm is
-         when LALR => Elisp_Package & "-lalr",
-         when LR1  => Elisp_Package & "-lr1");
-   begin
-      Create (File, Out_File, Elisp_Package_1 & "-elisp.el");
-      Set_Output (File);
-
-      Put_Line (";;; " & Elisp_Package_1 & "-elisp.el --- Generated parser 
support file  -*- lexical-binding:t -*-");
-      Put_Command_Line (Elisp_Comment & "  ", Use_Tuple => True, Tuple => 
Tuple);
-      Put_Raw_Code (Elisp_Comment, Input_Data.Raw_Code (Copyright_License));
-      Put_Raw_Code (Elisp_Comment, Input_Data.Raw_Code (Actions_Spec_Context));
-      New_Line;
-
-      Put_Line ("(require 'wisi)");
-      Put_Line ("(require 'wisi-compile)");
-      Put_Line ("(require 'wisi-elisp-parse)");
-      New_Line;
-      Output_Elisp_Common.Indent_Keyword_Table
-        (Elisp_Package_1, "elisp", Input_Data.Tokens.Keywords, 
To_String'Access);
-      New_Line;
-      Output_Elisp_Common.Indent_Token_Table (Elisp_Package_1, "elisp", 
Input_Data.Tokens.Tokens, To_String'Access);
-      New_Line;
-      Output (Elisp_Package_1, Input_Data.Tokens, 
Generate_Data.LR_Parse_Table, Generate_Data.Descriptor.all);
-      New_Line;
-      Put_Line ("(provide '" & Elisp_Package_1 & "-elisp)");
-      Put_Line (";; end of file");
-      Close (File);
-
-      Set_Output (Standard_Output);
-   end Create_Elisp;
-
-begin
-   Create_Elisp (Tuple.Gen_Alg);
-
-   if WisiToken.Trace_Generate > 0 then
-      WisiToken.BNF.Generate_Utils.Put_Stats (Input_Data, Generate_Data);
-   end if;
-end WisiToken.BNF.Output_Elisp;
diff --git a/wisitoken-bnf-output_elisp_common.adb 
b/wisitoken-bnf-output_elisp_common.adb
index a0b7158..2260955 100644
--- a/wisitoken-bnf-output_elisp_common.adb
+++ b/wisitoken-bnf-output_elisp_common.adb
@@ -106,8 +106,6 @@ package body WisiToken.BNF.Output_Elisp_Common is
       Indent_Line ("  '(");
       Indent := Indent + 3;
       for Kind of Tokens loop
-         --  We don't use All_Tokens.Iterate here, because we need the
-         --  Kind/token nested list structure, and the order is not important.
          if not (-Kind.Kind = "line_comment" or -Kind.Kind = "whitespace") then
             Indent_Line ("(""" & (-Kind.Kind) & """");
             Indent := Indent + 1;
@@ -155,4 +153,60 @@ package body WisiToken.BNF.Output_Elisp_Common is
       Indent := Indent - 3;
    end Indent_Name_Table;
 
+   procedure Indent_Repair_Image
+     (Output_File_Root : in String;
+      Label            : in String;
+      Tokens           : in WisiToken.BNF.Tokens)
+   is
+      use all type Ada.Text_IO.Count;
+      use Ada.Strings.Unbounded;
+      use WisiToken.Generate;
+
+      function re2c_To_Elisp (Item : in String) return String
+      is
+         Result : String (1 .. Item'Length * 2);
+         Last : Integer := Result'First - 1;
+      begin
+         --  Convert re2c case-insensitive string '...' to elisp string "...",
+         --  with '"' escaped.
+         if Item (Item'First) /= ''' then
+            return Item;
+         end if;
+
+         for C of Item loop
+            if C = ''' then
+               Result (Last + 1) := '"';
+               Last := Last + 1;
+            elsif C = '"' then
+               Result (Last + 1) := '\';
+               Result (Last + 2) := '"';
+               Last := Last + 2;
+            else
+               Result (Last + 1) := C;
+               Last := Last + 1;
+            end if;
+         end loop;
+         return Result (1 .. Last);
+      end re2c_To_Elisp;
+
+   begin
+      Indent_Line ("(defconst " & Output_File_Root & "-" & Label & 
"-repair-image");
+      Indent_Line ("  '(");
+      Indent := Indent + 3;
+      for Pair of Tokens.Keywords loop
+         Indent_Line ("(" & (-Pair.Name) & " . " & (-Pair.Value) & ")");
+      end loop;
+      for Kind of Tokens.Tokens loop
+         for Token of Kind.Tokens loop
+            if Length (Token.Repair_Image) > 0 then
+               Indent_Line ("(" & (-Token.Name) & " . " & re2c_To_Elisp 
(-Token.Repair_Image) & ")");
+            else
+               Indent_Line ("(" & (-Token.Name) & " . " & (-Token.Value) & 
")");
+            end if;
+         end loop;
+      end loop;
+      Indent_Line ("))");
+      Indent := Indent - 3;
+   end Indent_Repair_Image;
+
 end WisiToken.BNF.Output_Elisp_Common;
diff --git a/wisitoken-bnf-output_elisp_common.ads 
b/wisitoken-bnf-output_elisp_common.ads
index 040bd5e..03a655e 100644
--- a/wisitoken-bnf-output_elisp_common.ads
+++ b/wisitoken-bnf-output_elisp_common.ads
@@ -2,7 +2,7 @@
 --
 --  Subprograms common to Output_Elisp and Output_Ada_Emacs
 --
---  Copyright (C) 2012, 2013, 2015, 2017, 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2012, 2013, 2015, 2017, 2018, 2019 Free Software Foundation, 
Inc.
 --
 --  The WisiToken package is free software; you can redistribute it
 --  and/or modify it under terms of the GNU General Public License as
@@ -46,4 +46,9 @@ package WisiToken.BNF.Output_Elisp_Common is
       Label            : in     String;
       Names            : in     String_Lists.List);
 
+   procedure Indent_Repair_Image
+     (Output_File_Root : in String;
+      Label            : in String;
+      Tokens           : in WisiToken.BNF.Tokens);
+
 end WisiToken.BNF.Output_Elisp_Common;
diff --git a/wisitoken-bnf.adb b/wisitoken-bnf.adb
index dc37d56..29e4f60 100644
--- a/wisitoken-bnf.adb
+++ b/wisitoken-bnf.adb
@@ -204,25 +204,26 @@ package body WisiToken.BNF is
    end Count;
 
    procedure Add_Token
-     (Tokens : in out Token_Lists.List;
-      Kind   : in     String;
-      Name   : in     String;
-      Value  : in     String)
+     (Tokens       : in out Token_Lists.List;
+      Kind         : in     String;
+      Name         : in     String;
+      Value        : in     String;
+      Repair_Image : in     String := "")
    is
       use type Ada.Strings.Unbounded.Unbounded_String;
    begin
       for Token_Kind of Tokens loop
          if Token_Kind.Kind = Kind then
-            Token_Kind.Tokens.Append ((+Name, +Value));
+            Token_Kind.Tokens.Append ((+Name, +Value, +Repair_Image));
             return;
          end if;
       end loop;
 
       --  Kind not found; add it
       declare
-         Temp : String_Pair_Lists.List;
+         Temp : String_Triple_Lists.List;
       begin
-         Temp.Append ((+Name, +Value));
+         Temp.Append ((+Name, +Value, +Repair_Image));
          Tokens.Append ((+Kind, Temp));
       end;
    end Add_Token;
diff --git a/wisitoken-bnf.ads b/wisitoken-bnf.ads
index 3532de7..d586c4a 100644
--- a/wisitoken-bnf.ads
+++ b/wisitoken-bnf.ads
@@ -64,15 +64,14 @@ package WisiToken.BNF is
    type Generate_Algorithm_Set is array (Generate_Algorithm) of Boolean;
    type Generate_Algorithm_Set_Access is access Generate_Algorithm_Set;
 
-   type Output_Language is (Ada_Lang, Ada_Emacs_Lang, Elisp_Lang);
+   type Output_Language is (Ada_Lang, Ada_Emacs_Lang);
    subtype Ada_Output_Language is Output_Language range Ada_Lang .. 
Ada_Emacs_Lang;
    --  _Lang to avoid colliding with the standard package Ada and
    --  WisiToken packages named *.Ada. In the grammar file, they
    --  are named by (case insensitive):
    Output_Language_Image : constant array (Output_Language) of 
String_Access_Constant :=
      (Ada_Lang       => new String'("Ada"),
-      Ada_Emacs_Lang => new String'("Ada_Emacs"),
-      Elisp_Lang     => new String'("elisp"));
+      Ada_Emacs_Lang => new String'("Ada_Emacs"));
 
    function To_Output_Language (Item : in String) return Output_Language;
    --  Raises User_Error for invalid Item
@@ -188,6 +187,14 @@ package WisiToken.BNF is
    function Is_Present (List : in String_Pair_Lists.List; Name : in String) 
return Boolean;
    function Value (List : in String_Pair_Lists.List; Name : in String) return 
String;
 
+   type String_Triple_Type is record
+      Name         : aliased Ada.Strings.Unbounded.Unbounded_String;
+      Value        : Ada.Strings.Unbounded.Unbounded_String;
+      Repair_Image : Ada.Strings.Unbounded.Unbounded_String;
+   end record;
+
+   package String_Triple_Lists is new Ada.Containers.Doubly_Linked_Lists 
(String_Triple_Type);
+
    type Elisp_Action_Type is record
       --  Elisp name is the key
       Action_Label : Ada.Strings.Unbounded.Unbounded_String;
@@ -229,7 +236,7 @@ package WisiToken.BNF is
 
    type Token_Kind_Type is record
       Kind   : Ada.Strings.Unbounded.Unbounded_String;
-      Tokens : String_Pair_Lists.List;
+      Tokens : String_Triple_Lists.List;
    end record;
 
    package Token_Lists is new Ada.Containers.Doubly_Linked_Lists 
(Token_Kind_Type);
@@ -238,11 +245,12 @@ package WisiToken.BNF is
    --  Count of all leaves.
 
    procedure Add_Token
-     (Tokens : in out Token_Lists.List;
-      Kind   : in     String;
-      Name   : in     String;
-      Value  : in     String);
-   --  Add Name, Value to Kind list in Tokens.
+     (Tokens       : in out Token_Lists.List;
+      Kind         : in     String;
+      Name         : in     String;
+      Value        : in     String;
+      Repair_Image : in     String := "");
+   --  Add Name, Value, Repair_Image to Kind list in Tokens.
 
    function Is_In (Tokens : in Token_Lists.List; Kind : in String) return 
Boolean;
    function Is_In
diff --git a/wisitoken-generate-lr-lalr_generate.adb 
b/wisitoken-generate-lr-lalr_generate.adb
index f55c822..3631f64 100644
--- a/wisitoken-generate-lr-lalr_generate.adb
+++ b/wisitoken-generate-lr-lalr_generate.adb
@@ -176,7 +176,7 @@ package body WisiToken.Generate.LR.LALR_Generate is
 
       Found_State : Unknown_State_Index;
    begin
-      Kernels.Set_First (First_State_Index);
+      Kernels.Set_First_Last (First_State_Index, First_State_Index - 1);
 
       Add (New_Item_Set, Kernels, Kernel_Tree, Descriptor, Include_Lookaheads 
=> False);
 
@@ -298,7 +298,7 @@ package body WisiToken.Generate.LR.LALR_Generate is
          Propagations.Append ((From_Cur, To_List (To_Item)));
 
       elsif not Has_Element (To_Match) then
-         Ref (From_Match).To.Append (To_Item);
+         Variable_Ref (From_Match).To.Append (To_Item);
 
       else
          raise SAL.Programmer_Error with "Add_Propagation: unexpected case";
@@ -360,7 +360,7 @@ package body WisiToken.Generate.LR.LALR_Generate is
                Ada.Text_IO.Put_Line ("  spontaneous: " & Lookahead_Image 
(Closure_Item.Lookaheads.all, Descriptor));
             end if;
 
-            LR1_Items.Include (Ref (To_Item), Closure_Item.Lookaheads.all, 
Descriptor);
+            LR1_Items.Include (Variable_Ref (To_Item), 
Closure_Item.Lookaheads.all, Descriptor);
          end if;
       end;
    end Generate_Lookahead_Info;
@@ -382,7 +382,8 @@ package body WisiToken.Generate.LR.LALR_Generate is
          More_To_Check := False;
          for Mapping of List loop
             for Copy of Mapping.To loop
-               LR1_Items.Include (Ref (Copy), Constant_Ref 
(Mapping.From).Lookaheads.all, Added_One, Descriptor);
+               LR1_Items.Include
+                 (Variable_Ref (Copy), Constant_Ref 
(Mapping.From).Lookaheads.all, Added_One, Descriptor);
 
                More_To_Check := More_To_Check or Added_One;
             end loop;
diff --git a/wisitoken-generate-lr-lr1_generate.adb 
b/wisitoken-generate-lr-lr1_generate.adb
index 0f177d9..d148c67 100644
--- a/wisitoken-generate-lr-lr1_generate.adb
+++ b/wisitoken-generate-lr-lr1_generate.adb
@@ -100,7 +100,7 @@ package body WisiToken.Generate.LR.LR1_Generate is
       Found_State  : Unknown_State_Index;
 
    begin
-      C.Set_First (First_State_Index);
+      C.Set_First_Last (First_State_Index, First_State_Index - 1);
 
       Add (New_Item_Set, C, C_Tree, Descriptor, Include_Lookaheads => True);
 
diff --git a/wisitoken-generate-lr.adb b/wisitoken-generate-lr.adb
index b814885..54ddfd6 100644
--- a/wisitoken-generate-lr.adb
+++ b/wisitoken-generate-lr.adb
@@ -254,7 +254,7 @@ package body WisiToken.Generate.LR is
    procedure Add_Action
      (Symbol               : in     Token_ID;
       Action               : in     Parse_Action_Rec;
-      Action_List          : in out Action_Node_Ptr;
+      Action_List          : in out Action_Arrays.Vector;
       Closure              : in     LR1_Items.Item_Set;
       Grammar              : in     WisiToken.Productions.Prod_Arrays.Vector;
       Has_Empty_Production : in     Token_ID_Set;
@@ -263,7 +263,7 @@ package body WisiToken.Generate.LR is
       Conflicts            : in out Conflict_Lists.List;
       Descriptor           : in     WisiToken.Descriptor)
    is
-      Matching_Action : constant Action_Node_Ptr := Find (Symbol, Action_List);
+      Matching_Action : constant Action_Arrays.Find_Reference_Type := 
Action_List.Find (Symbol);
    begin
       if Trace_Generate > Detail then
          Ada.Text_IO.Put (Image (Symbol, Descriptor) & " => ");
@@ -271,8 +271,8 @@ package body WisiToken.Generate.LR is
          Ada.Text_IO.New_Line;
       end if;
 
-      if Matching_Action /= null then
-         if Is_In (Action, Matching_Action.Action) then
+      if Matching_Action.Element /= null then
+         if Is_In (Action, Matching_Action.Actions) then
             --  Action is already in the list.
             if Trace_Generate > Detail then
                Ada.Text_IO.Put_Line (" - already present");
@@ -287,10 +287,10 @@ package body WisiToken.Generate.LR is
                --  list of conflicting actions, so we keep it the first item 
in the
                --  list; no order in the rest of the list.
                Action_A : constant Parse_Action_Rec :=
-                 (if Action.Verb in Shift | Accept_It then Action else 
Matching_Action.Action.Item);
+                 (if Action.Verb in Shift | Accept_It then Action else 
Matching_Action.Actions.Item);
 
                Action_B : constant Parse_Action_Rec :=
-                 (if Action.Verb in Shift | Accept_It then 
Matching_Action.Action.Item else Action);
+                 (if Action.Verb in Shift | Accept_It then 
Matching_Action.Actions.Item else Action);
 
                New_Conflict : constant Conflict :=
                  (Action_A    => Action_A.Verb,
@@ -362,9 +362,9 @@ package body WisiToken.Generate.LR is
                end if;
 
                if Action.Verb = Shift then
-                  Matching_Action.Action := new Parse_Action_Node'(Action, 
Matching_Action.Action);
+                  Matching_Action.Actions := new Parse_Action_Node'(Action, 
Matching_Action.Actions);
                else
-                  Matching_Action.Action.Next := new 
Parse_Action_Node'(Action, Matching_Action.Action.Next);
+                  Matching_Action.Actions.Next := new 
Parse_Action_Node'(Action, Matching_Action.Actions.Next);
                end if;
             end;
          end if;
@@ -442,51 +442,9 @@ package body WisiToken.Generate.LR is
          end if;
       end loop;
 
-      --  Place a default error action at the end of every state.
-      --  (it should always have at least one action already).
-      declare
-         --  The default action, when nothing else matches an input
-         Default_Action : constant Action_Node :=
-           --  The symbol here is actually irrelevant; it is the
-           --  position as the last on a state's action list that makes
-           --  it the default.
-           (Symbol => Invalid_Token_ID,
-            Action => new Parse_Action_Node'(Parse_Action_Rec'(Verb => 
WisiToken.Parse.LR.Error), null),
-            Next   => null);
-
-         Last_Action : Action_Node_Ptr := Table.States (State).Action_List;
-      begin
-         if Last_Action = null then
-            --  This happens if the first production in the grammar is
-            --  not the start symbol production.
-            --
-            --  It also happens when the start symbol production does
-            --  not have an explicit EOF, or when there is more than
-            --  one production that has the start symbol on the left
-            --  hand side.
-            --
-            --  It also happens when the grammar is bad, for example:
-            --
-            --  declarations <= declarations & declaration
-            --
-            --  without 'declarations <= declaration'.
-            --
-            --  We continue generating the grammar, in order to help the user
-            --  debug this issue.
-            WisiToken.Generate.Error := True;
-
-            Ada.Text_IO.Put_Line
-              (Ada.Text_IO.Current_Error, "Error: state" & State_Index'Image 
(State) &
-                 " has no actions; bad grammar, or " &
-                 "first production in grammar must be the only start symbol 
production, " &
-                 "and it must must have an explicit EOF.");
-         else
-            while Last_Action.Next /= null loop
-               Last_Action := Last_Action.Next;
-            end loop;
-            Last_Action.Next := new Action_Node'(Default_Action);
-         end if;
-      end;
+      --  We don't place a default error action at the end of every state;
+      --  Parse.LR.Action_For returns Table.Error_Action when Symbol is not 
found.
+      Table.Error_Action := new Parse_Action_Node'((Verb => 
WisiToken.Parse.LR.Error), null);
 
       for Item of Closure.Goto_List loop
          if Item.Symbol in Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal then
@@ -497,7 +455,7 @@ package body WisiToken.Generate.LR is
 
    procedure Add_Lookahead_Actions
      (Item                 : in     LR1_Items.Item;
-      Action_List          : in out Action_Node_Ptr;
+      Action_List          : in out Action_Arrays.Vector;
       Grammar              : in     WisiToken.Productions.Prod_Arrays.Vector;
       Has_Empty_Production : in     Token_ID_Set;
       First_Nonterm_Set    : in     Token_Array_Token_Set;
@@ -854,25 +812,21 @@ package body WisiToken.Generate.LR is
       Working_Set : LR1_Items.Item_Lists.List := Kernel.Set;
       Recursive   : Boolean := False;
 
-      function Find_Action (List : in Action_Node_Ptr; ID : in Token_ID) 
return Minimal_Action
-      is
-         Node : Action_Node_Ptr := List;
-      begin
-         loop
+      function Find_Action (List : in Action_Arrays.Vector; ID : in Token_ID) 
return Minimal_Action
+      is begin
+         for Node of List loop
             if Node.Symbol = ID then
-               case Node.Action.Item.Verb is
+               case Node.Actions.Item.Verb is
                when Shift =>
-                  return (Shift, ID, Node.Action.Item.State);
+                  return (Shift, ID, Node.Actions.Item.State);
                when Reduce =>
                   --  Item.Dot is a nonterm that starts with a nullable 
nonterm; reduce
                   --  to that first.
-                  return (Reduce, Node.Action.Item.Production.LHS, 0);
+                  return (Reduce, Node.Actions.Item.Production.LHS, 0);
                when Accept_It | WisiToken.Parse.LR.Error =>
                   raise SAL.Programmer_Error;
                end case;
             end if;
-            Node := Node.Next;
-            exit when Node = null;
          end loop;
          raise SAL.Programmer_Error;
       end Find_Action;
@@ -1219,6 +1173,7 @@ package body WisiToken.Generate.LR is
       Action_Names : in Names_Array_Array;
       Check_Names  : in Names_Array_Array)
    is
+      use all type SAL.Base_Peek_Type;
       use Ada.Containers;
       use Ada.Text_IO;
       File : File_Type;
@@ -1238,75 +1193,67 @@ package body WisiToken.Generate.LR is
       New_Line (File);
 
       for State of Table.States loop
-         declare
-            Node_I : Action_Node_Ptr := State.Action_List;
-         begin
-            loop
-               exit when Node_I = null;
-               --  Action first, so we know if Symbol is present (not when 
Error)
-               declare
-                  Node_J     : Parse_Action_Node_Ptr := Node_I.Action;
-                  Put_Symbol : Boolean               := True;
-               begin
-                  loop
-                     Put (File, Parse_Action_Verbs'Image (Node_J.Item.Verb));
+         Put (File, Trimmed_Image (State.Action_List.Length) & ' ');
+         for I in State.Action_List.First_Index .. 
State.Action_List.Last_Index loop
+            --  Action first, for historical reasons
+            declare
+               Node_I : Action_Node renames State.Action_List (I);
+               Node_J : Parse_Action_Node_Ptr := Node_I.Actions;
+            begin
+               loop
+                  Put (File, Parse_Action_Verbs'Image (Node_J.Item.Verb));
 
-                     case Node_J.Item.Verb is
-                     when Shift =>
-                        Put (File, State_Index'Image (Node_J.Item.State));
+                  case Node_J.Item.Verb is
+                  when Shift =>
+                     Put (File, State_Index'Image (Node_J.Item.State));
 
-                     when Reduce | Accept_It =>
-                        Put (File, Token_ID'Image (Node_J.Item.Production.LHS) 
&
-                               Integer'Image (Node_J.Item.Production.RHS));
+                  when Reduce | Accept_It =>
+                     Put (File, Token_ID'Image (Node_J.Item.Production.LHS) &
+                            Integer'Image (Node_J.Item.Production.RHS));
 
-                        if Action_Names (Node_J.Item.Production.LHS) /= null 
and then
-                          Action_Names 
(Node_J.Item.Production.LHS)(Node_J.Item.Production.RHS) /= null
-                        then
-                           Put (File, " true");
-                        else
-                           Put (File, " false");
-                        end if;
-                        if Check_Names (Node_J.Item.Production.LHS) /= null 
and then
-                          Check_Names 
(Node_J.Item.Production.LHS)(Node_J.Item.Production.RHS) /= null
-                        then
-                           Put (File, " true");
-                        else
-                           Put (File, " false");
-                        end if;
+                     if Action_Names (Node_J.Item.Production.LHS) /= null and 
then
+                       Action_Names 
(Node_J.Item.Production.LHS)(Node_J.Item.Production.RHS) /= null
+                     then
+                        Put (File, " true");
+                     else
+                        Put (File, " false");
+                     end if;
+                     if Check_Names (Node_J.Item.Production.LHS) /= null and 
then
+                       Check_Names 
(Node_J.Item.Production.LHS)(Node_J.Item.Production.RHS) /= null
+                     then
+                        Put (File, " true");
+                     else
+                        Put (File, " false");
+                     end if;
 
-                        Put (File, Ada.Containers.Count_Type'Image 
(Node_J.Item.Token_Count));
+                     Put (File, Ada.Containers.Count_Type'Image 
(Node_J.Item.Token_Count));
 
-                     when Parse.LR.Error =>
-                        --  Error action terminates the action list
-                        Put_Symbol := False;
-                     end case;
+                  when Parse.LR.Error =>
+                     raise SAL.Programmer_Error;
+                  end case;
 
-                     Node_J := Node_J.Next;
-                     exit when Node_J = null;
-                     Put (File, ' ');
-                  end loop;
-                  Put (File, ';');
-                  if Put_Symbol then
-                     Put (File, Token_ID'Image (Node_I.Symbol));
-                  end if;
-               end;
+                  Node_J := Node_J.Next;
+                  exit when Node_J = null;
+                  Put (File, ' ');
+               end loop;
+               Put (File, ';');
+               Put (File, Token_ID'Image (Node_I.Symbol));
+            end;
+            if I = State.Action_List.Last_Index then
+               Put_Line (File, ";");
+            else
                New_Line (File);
+            end if;
+         end loop;
 
-               Node_I := Node_I.Next;
+         if State.Goto_List.Length > 0 then
+            Put (File, Trimmed_Image (State.Goto_List.Length));
+            for Node of State.Goto_List loop
+               Put (File, Node.Symbol'Image & Node.State'Image);
             end loop;
-         end;
-
-         declare
-            Node_I : Goto_Node_Ptr := State.Goto_List;
-         begin
-            loop
-               exit when Node_I = null;
-               Put (File, Token_ID'Image (Symbol (Node_I)) & State_Index'Image 
(Parse.LR.State (Node_I)));
-               Node_I := Next (Node_I);
-            end loop;
-            Put (File, ';');
-            New_Line (File);
-         end;
+         end if;
+         Put (File, ';');
+         New_Line (File);
 
          if State.Kernel.Length = 0 then
             --  Not set for state 0
@@ -1455,34 +1402,27 @@ package body WisiToken.Generate.LR is
       use all type Ada.Containers.Count_Type;
       use Ada.Text_IO;
       use Ada.Strings.Fixed;
-      Action_Ptr : Action_Node_Ptr := State.Action_List;
-      Goto_Ptr   : Goto_Node_Ptr   := State.Goto_List;
    begin
-      while Action_Ptr /= null loop
-         Put ("   ");
-         if Action_Ptr.Next = null then
-            Put ("default" & (Descriptor.Image_Width - 7) * ' ' & " => ");
-
-         elsif Action_Ptr.Action.Item.Verb /= Parse.LR.Error then
-            Put (Image (Action_Ptr.Symbol, Descriptor) &
-                   (Descriptor.Image_Width - Image (Action_Ptr.Symbol, 
Descriptor)'Length) * ' '
-                   & " => ");
-         end if;
-         Put (Descriptor, Action_Ptr.Action);
+      for Action of State.Action_List loop
+         Put ("   " & Image (Action.Symbol, Descriptor) &
+                (Descriptor.Image_Width - Image (Action.Symbol, 
Descriptor)'Length) * ' '
+                & " => ");
+         Put (Descriptor, Action.Actions);
          New_Line;
-         Action_Ptr := Action_Ptr.Next;
       end loop;
 
-      if Goto_Ptr /= null then
+      --  The error line is redundant, but we keep it to match existing good 
parse tables.
+      Put_Line ("   default" & (Descriptor.Image_Width - 7) * ' ' & " => 
ERROR");
+
+      if State.Goto_List.Length > 0 then
          New_Line;
       end if;
 
-      while Goto_Ptr /= null loop
+      for Item of State.Goto_List loop
          Put_Line
-           ("   " & Image (Symbol (Goto_Ptr), Descriptor) &
-              (Descriptor.Image_Width - Image (Symbol (Goto_Ptr), 
Descriptor)'Length) * ' ' &
-              " goto state" & State_Index'Image (Parse.LR.State (Goto_Ptr)));
-         Goto_Ptr := Next (Goto_Ptr);
+           ("   " & Image (Item.Symbol, Descriptor) &
+              (Descriptor.Image_Width - Image (Item.Symbol, 
Descriptor)'Length) * ' ' &
+              " goto state" & Item.State'Image);
       end loop;
 
       New_Line;
diff --git a/wisitoken-generate-lr.ads b/wisitoken-generate-lr.ads
index 297689b..1bb9e2b 100644
--- a/wisitoken-generate-lr.ads
+++ b/wisitoken-generate-lr.ads
@@ -63,7 +63,7 @@ package WisiToken.Generate.LR is
    procedure Add_Action
      (Symbol               : in     Token_ID;
       Action               : in     Parse_Action_Rec;
-      Action_List          : in out Action_Node_Ptr;
+      Action_List          : in out Action_Arrays.Vector;
       Closure              : in     LR1_Items.Item_Set;
       Grammar              : in     WisiToken.Productions.Prod_Arrays.Vector;
       Has_Empty_Production : in     Token_ID_Set;
@@ -89,7 +89,7 @@ package WisiToken.Generate.LR is
 
    procedure Add_Lookahead_Actions
      (Item                 : in     LR1_Items.Item;
-      Action_List          : in out Action_Node_Ptr;
+      Action_List          : in out Action_Arrays.Vector;
       Grammar              : in     WisiToken.Productions.Prod_Arrays.Vector;
       Has_Empty_Production : in     Token_ID_Set;
       First_Nonterm_Set    : in     Token_Array_Token_Set;
diff --git a/wisitoken-generate-lr1_items.adb b/wisitoken-generate-lr1_items.adb
index 86cd8d9..f394b67 100644
--- a/wisitoken-generate-lr1_items.adb
+++ b/wisitoken-generate-lr1_items.adb
@@ -71,7 +71,7 @@ package body WisiToken.Generate.LR1_Items is
 
          Modified := True;
       else
-         Include (Ref (Found), Lookaheads, Modified);
+         Include (Variable_Ref (Found), Lookaheads, Modified);
       end if;
 
       return Modified;
@@ -306,7 +306,7 @@ package body WisiToken.Generate.LR1_Items is
       if Found_Tree = Item_Set_Trees.No_Element then
          return Unknown_State;
       else
-         return Item_Set_Tree.Constant_Ref (Found_Tree).State;
+         return Item_Set_Tree (Found_Tree).State;
       end if;
    end Find;
 
diff --git a/wisitoken-generate.adb b/wisitoken-generate.adb
index 769a581..eae0ca3 100644
--- a/wisitoken-generate.adb
+++ b/wisitoken-generate.adb
@@ -268,8 +268,7 @@ package body WisiToken.Generate is
       subtype Terminal is Token_ID range Descriptor.First_Terminal .. 
Descriptor.Last_Terminal;
    begin
       return Result : Token_Sequence_Arrays.Vector do
-         Result.Set_First (First'First (1));
-         Result.Set_Last (First'Last (1));
+         Result.Set_First_Last (First'First (1), First'Last (1));
 
          for I in First'Range (1) loop
             declare
diff --git a/wisitoken-parse-lr-mckenzie_recover-base.adb 
b/wisitoken-parse-lr-mckenzie_recover-base.adb
index ae60955..d8b03f3 100644
--- a/wisitoken-parse-lr-mckenzie_recover-base.adb
+++ b/wisitoken-parse-lr-mckenzie_recover-base.adb
@@ -24,11 +24,11 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Base is
      (Parsers                 : not null access Parser_Lists.List;
       Parser_Status           : in              Parser_Status_Array;
       Min_Success_Check_Count : in              Natural;
+      Total_Enqueue_Count     : in              Natural;
       Check_Delta_Limit       : in              Natural;
       Enqueue_Limit           : in              Natural)
      return Boolean
    is
-      use all type SAL.Base_Peek_Type;
       Done_Count : SAL.Base_Peek_Type := 0;
    begin
       --  Return True if all parsers are done, or if any parser has a config
@@ -41,9 +41,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Base is
                   --  fail; another parser succeeded, this one taking too long.
                   Done_Count := Done_Count + 1;
 
-               elsif P_Status.Parser_State.Recover.Enqueue_Count +
-                 P_Status.Parser_State.Recover.Config_Full_Count >= 
Enqueue_Limit
-               then
+               elsif Total_Enqueue_Count + 
P_Status.Parser_State.Recover.Config_Full_Count >= Enqueue_Limit then
                   --  fail
                   Done_Count := Done_Count + 1;
                end if;
@@ -90,7 +88,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Base is
         (Parsers   : not null access Parser_Lists.List;
          Terminals : not null access constant Base_Token_Arrays.Vector)
       is
-         use all type SAL.Base_Peek_Type;
          Index : SAL.Peek_Type := 1;
       begin
          Supervisor.Parsers      := Parsers;
@@ -98,6 +95,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Base is
          All_Parsers_Done        := False;
          Success_Counter         := 0;
          Min_Success_Check_Count := Natural'Last;
+         Total_Enqueue_Count     := 0;
          Fatal_Called            := False;
          Result                  := Recover_Status'First;
          Error_ID                := Ada.Exceptions.Null_Id;
@@ -133,10 +131,9 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Base is
         (Parser_Index : out SAL.Base_Peek_Type;
          Config       : out Configuration;
          Status       : out Config_Status)
-        when (Fatal_Called or All_Parsers_Done) or else
-          Get_Barrier (Parsers, Parser_Status, Min_Success_Check_Count, 
Check_Delta_Limit, Enqueue_Limit)
+        when (Fatal_Called or All_Parsers_Done) or else Get_Barrier
+          (Parsers, Parser_Status, Min_Success_Check_Count, 
Total_Enqueue_Count, Check_Delta_Limit, Enqueue_Limit)
       is
-         use all type SAL.Base_Peek_Type;
          Done_Count     : SAL.Base_Peek_Type := 0;
          Min_Cost       : Integer            := Integer'Last;
          Min_Cost_Index : SAL.Base_Peek_Type;
@@ -190,13 +187,11 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Base is
 
                         Done_Count := Done_Count + 1;
 
-                     elsif P_Status.Parser_State.Recover.Enqueue_Count +
-                       P_Status.Parser_State.Recover.Config_Full_Count >= 
Enqueue_Limit
-                     then
+                     elsif Total_Enqueue_Count + 
P_Status.Parser_State.Recover.Config_Full_Count >= Enqueue_Limit then
                         if Trace_McKenzie > Outline then
                            Put_Line
                              (Trace.all,
-                              P_Status.Parser_State.Label, "fail; enqueue 
limit (" &
+                              P_Status.Parser_State.Label, "fail; total 
enqueue limit (" &
                                 Enqueue_Limit'Image & " cost" &
                                 
P_Status.Parser_State.Recover.Config_Heap.Min_Key'Image & ")",
                               Task_ID => False);
@@ -274,7 +269,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Base is
          Config       : in     Configuration;
          Configs      : in out Config_Heaps.Heap_Type)
       is
-         use all type SAL.Base_Peek_Type;
          Data : McKenzie_Data renames Parser_Status 
(Parser_Index).Parser_State.Recover;
       begin
          Put (Parser_Index, Configs); --  Decrements Active_Worker_Count.
@@ -331,7 +325,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Base is
 
       procedure Put (Parser_Index : in SAL.Peek_Type; Configs : in out 
Config_Heaps.Heap_Type)
       is
-         use all type SAL.Base_Peek_Type;
          Configs_Count : constant SAL.Base_Peek_Type := Configs.Count; -- 
Before it is emptied, for Trace.
 
          P_Status : Base.Parser_Status renames Parser_Status (Parser_Index);
@@ -339,13 +332,14 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Base is
       begin
          P_Status.Active_Workers := P_Status.Active_Workers - 1;
 
+         Total_Enqueue_Count := Total_Enqueue_Count + Integer (Configs_Count);
+         Data.Enqueue_Count  := Data.Enqueue_Count + Integer (Configs_Count);
          loop
             exit when Configs.Count = 0;
 
             --  [1] has a check for duplicate configs here; that only happens 
with
             --  higher costs, which take too long for our application.
             Data.Config_Heap.Add (Configs.Remove);
-            Data.Enqueue_Count := Data.Enqueue_Count + 1;
          end loop;
 
          if Trace_McKenzie > Detail then
@@ -353,7 +347,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Base is
               (Trace.all, P_Status.Parser_State.Label,
                "enqueue:" & SAL.Base_Peek_Type'Image (Configs_Count) &
                  "/" & SAL.Base_Peek_Type'Image (Data.Config_Heap.Count) &
-                 "/" & Trimmed_Image (Data.Enqueue_Count) &
+                 "/" & Trimmed_Image (Total_Enqueue_Count) &
                  "/" & Trimmed_Image (Data.Check_Count) &
                  ", min cost:" &
                  (if Data.Config_Heap.Count > 0
@@ -363,14 +357,15 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Base is
          end if;
       end Put;
 
-      procedure Config_Full (Parser_Index : in SAL.Peek_Type)
+      procedure Config_Full (Prefix : in String; Parser_Index : in 
SAL.Peek_Type)
       is
          P_Status : Base.Parser_Status renames Parser_Status (Parser_Index);
          Data : McKenzie_Data renames P_Status.Parser_State.Recover;
       begin
          Data.Config_Full_Count := Data.Config_Full_Count + 1;
          if Trace_McKenzie > Outline then
-            Put_Line (Trace.all, Label (Parser_Index), "config.ops is full; " 
& Data.Config_Full_Count'Image);
+            Put_Line (Trace.all, Label (Parser_Index), Prefix & ": config.ops 
is full; " &
+                        Data.Config_Full_Count'Image);
          end if;
       end Config_Full;
 
diff --git a/wisitoken-parse-lr-mckenzie_recover-base.ads 
b/wisitoken-parse-lr-mckenzie_recover-base.ads
index cde4bd5..f67df90 100644
--- a/wisitoken-parse-lr-mckenzie_recover-base.ads
+++ b/wisitoken-parse-lr-mckenzie_recover-base.ads
@@ -123,7 +123,7 @@ private package WisiToken.Parse.LR.McKenzie_Recover.Base is
       --
       --  Decrements active worker count.
 
-      procedure Config_Full (Parser_Index : in SAL.Peek_Type);
+      procedure Config_Full (Prefix : in String; Parser_Index : in 
SAL.Peek_Type);
       --  Report that a config.ops was full when trying to add another op.
       --  This is counted towards the enqueue limit.
 
@@ -149,6 +149,7 @@ private package WisiToken.Parse.LR.McKenzie_Recover.Base is
       All_Parsers_Done        : Boolean;
       Success_Counter         : Natural;
       Min_Success_Check_Count : Natural;
+      Total_Enqueue_Count     : Natural;
       Fatal_Called            : Boolean;
       Result                  : Recover_Status;
       Error_ID                : Ada.Exceptions.Exception_Id;
diff --git a/wisitoken-parse-lr-mckenzie_recover-explore.adb 
b/wisitoken-parse-lr-mckenzie_recover-explore.adb
index 272eadf..f749cfb 100644
--- a/wisitoken-parse-lr-mckenzie_recover-explore.adb
+++ b/wisitoken-parse-lr-mckenzie_recover-explore.adb
@@ -18,6 +18,7 @@
 pragma License (Modified_GPL);
 
 with Ada.Exceptions;
+with SAL.Gen_Bounded_Definite_Queues;
 with WisiToken.Parse.LR.McKenzie_Recover.Parse;
 with WisiToken.Parse.LR.Parser;
 package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
@@ -34,19 +35,19 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
       Cost_Delta        : in              Integer;
       Strategy          : in              Strategies)
    is
+      use Config_Op_Arrays;
       McKenzie_Param : McKenzie_Param_Type renames Shared.Table.McKenzie_Param;
 
       Op : constant Config_Op := (Insert, ID, Config.Current_Shared_Token, 
State, Config.Stack.Depth);
    begin
       Config.Strategy_Counts (Strategy) := Config.Strategy_Counts (Strategy) + 
1;
 
-      begin
-         Config.Ops.Append (Op);
-      exception
-      when SAL.Container_Full =>
-         Super.Config_Full (Parser_Index);
+      if Is_Full (Config.Ops) then
+         Super.Config_Full ("do_shift ops", Parser_Index);
          raise Bad_Config;
-      end;
+      else
+         Append (Config.Ops, Op);
+      end if;
 
       if Cost_Delta = 0 then
          Config.Cost := Config.Cost + McKenzie_Param.Insert (ID);
@@ -64,7 +65,12 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
       Config.Error_Token.ID := Invalid_Token_ID;
       Config.Check_Status   := (Label => WisiToken.Semantic_Checks.Ok);
 
-      Config.Stack.Push ((State, Syntax_Trees.Invalid_Node_Index, (ID, Virtual 
=> True, others => <>)));
+      if Config.Stack.Is_Full then
+         Super.Config_Full ("do_shift stack", Parser_Index);
+         raise Bad_Config;
+      else
+         Config.Stack.Push ((State, Syntax_Trees.Invalid_Node_Index, (ID, 
Virtual => True, others => <>)));
+      end if;
       if Trace_McKenzie > Detail then
          Base.Put
            ((if Label'Length > 0 then Label & ": " else "") & "insert " & 
Image (ID, Super.Trace.Descriptor.all),
@@ -84,7 +90,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
       Action            : in              Reduce_Action_Rec;
       Do_Language_Fixes : in              Boolean := True)
    is
-      use all type SAL.Base_Peek_Type;
       use all type Semantic_Checks.Check_Status_Label;
       use all type WisiToken.Parse.LR.Parser.Language_Fixes_Access;
 
@@ -114,16 +119,20 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
          end if;
 
          --  Finish the reduce; ignore the check fail.
-         Config.Stack.Pop (SAL.Base_Peek_Type (Config.Check_Token_Count));
+         if Config.Stack.Depth < SAL.Base_Peek_Type (Config.Check_Token_Count) 
then
+            raise SAL.Programmer_Error;
+         else
+            Config.Stack.Pop (SAL.Base_Peek_Type (Config.Check_Token_Count));
+         end if;
          Config.Error_Token.ID := Invalid_Token_ID;
          Config.Check_Status   := (Label => Ok);
       end case;
 
-      if Config.Stack.Depth = 0 or else Config.Stack (1).State = Unknown_State 
then
+      if Config.Stack.Depth = 0 or else Config.Stack.Peek.State = 
Unknown_State then
          raise Bad_Config;
       end if;
 
-      New_State := Goto_For (Table, Config.Stack (1).State, 
Action.Production.LHS);
+      New_State := Goto_For (Table, Config.Stack.Peek.State, 
Action.Production.LHS);
 
       if New_State = Unknown_State then
          if Trace_McKenzie > Extra then
@@ -143,7 +152,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
               ": state" & State_Index'Image (Prev_State) & " reduce" &
               Ada.Containers.Count_Type'Image (Action.Token_Count) & " to " &
               Image (Action.Production.LHS, Descriptor) & ", goto" &
-              State_Index'Image (New_State) & " via" & State_Index'Image 
(Config.Stack (2).State));
+              State_Index'Image (New_State) & " via" & State_Index'Image 
(Config.Stack.Peek (2).State));
       end if;
    end Do_Reduce_1;
 
@@ -166,7 +175,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
 
       Orig_Config : Configuration;
       Table       : Parse_Table renames Shared.Table.all;
-      Next_Action : Parse_Action_Node_Ptr := Action_For (Table, Config.Stack 
(1).State, Inserted_ID);
+      Next_Action : Parse_Action_Node_Ptr := Action_For (Table, 
Config.Stack.Peek.State, Inserted_ID);
    begin
       if Next_Action.Next /= null then
          Orig_Config := Config;
@@ -241,10 +250,11 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
       --  If there are conflicts, all are parsed; if more than one succeed,
       --  all are enqueued in Local_Config_Heap, and this returns Abandon.
 
-      use all type SAL.Base_Peek_Type;
+      use Parse.Parse_Item_Arrays;
+      use Config_Op_Arrays;
       use all type Ada.Containers.Count_Type;
 
-      Parse_Items : Parse.Parse_Item_Arrays.Vector;
+      Parse_Items : aliased Parse.Parse_Item_Arrays.Vector;
 
       Dummy : Boolean := Parse.Parse
         (Super, Shared, Parser_Index, Parse_Items, Config,
@@ -254,15 +264,20 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
    begin
       --  This solution is from Language_Fixes; any cost increase is done 
there.
 
-      if Parse_Items.Length = 1 then
+      if Length (Parse_Items) = 1 then
          declare
-            Item : Parse.Parse_Item renames Parse_Items (1);
+            Item : Parse.Parse_Item renames 
Parse.Parse_Item_Array_Refs.Constant_Ref (Parse_Items, 1);
          begin
             if Item.Parsed and Item.Config.Current_Insert_Delete = 
No_Insert_Delete then
                --  Item.Config.Error_Token.ID, Check_Status are correct.
                Config := Item.Config;
 
-               Config.Ops.Append ((Fast_Forward, Config.Current_Shared_Token));
+               if Is_Full (Config.Ops) then
+                  Super.Config_Full ("fast_forward 1", Parser_Index);
+                  return Abandon;
+               else
+                  Append (Config.Ops, (Fast_Forward, 
Config.Current_Shared_Token));
+               end if;
                Config.Minimal_Complete_State := None;
                Config.Matching_Begin_Done    := False;
                return Continue;
@@ -271,26 +286,32 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
             end if;
          end;
       else
-         for Item of Parse_Items loop
-            if Item.Parsed and Item.Config.Current_Insert_Delete = 
No_Insert_Delete then
-               Item.Config.Ops.Append ((Fast_Forward, 
Item.Config.Current_Shared_Token));
-               Item.Config.Minimal_Complete_State := None;
-               Item.Config.Matching_Begin_Done    := False;
-               Local_Config_Heap.Add (Item.Config);
+         for I in First_Index (Parse_Items) .. Last_Index (Parse_Items) loop
+            declare
+               Item : Parse.Parse_Item renames 
Parse.Parse_Item_Array_Refs.Variable_Ref (Parse_Items, I);
+            begin
+               if Item.Parsed and Item.Config.Current_Insert_Delete = 
No_Insert_Delete then
+                  if Is_Full (Config.Ops) then
+                     Super.Config_Full ("fast_forward 2", Parser_Index);
+                     return Abandon;
+                  else
+                     Append (Item.Config.Ops, (Fast_Forward, 
Item.Config.Current_Shared_Token));
+                  end if;
+                  Item.Config.Minimal_Complete_State := None;
+                  Item.Config.Matching_Begin_Done    := False;
+                  Local_Config_Heap.Add (Item.Config);
 
-               if Trace_McKenzie > Detail then
-                  Base.Put ("fast forward enqueue", Super, Shared, 
Parser_Index, Item.Config);
+                  if Trace_McKenzie > Detail then
+                     Base.Put ("fast forward enqueue", Super, Shared, 
Parser_Index, Item.Config);
+                  end if;
                end if;
-            end if;
+            end;
          end loop;
          return Abandon;
       end if;
    exception
    when Bad_Config =>
       return Abandon;
-   when SAL.Container_Full =>
-      Super.Config_Full (Parser_Index);
-      return Abandon;
    end Fast_Forward;
 
    function Check
@@ -301,25 +322,31 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
       Local_Config_Heap : in out          Config_Heaps.Heap_Type)
      return Check_Status
    is
+      use Config_Op_Arrays, Config_Op_Array_Refs;
+      use Parse.Parse_Item_Arrays;
       use all type Semantic_Checks.Check_Status_Label;
 
       McKenzie_Param : McKenzie_Param_Type renames Shared.Table.McKenzie_Param;
 
-      Parse_Items : Parse.Parse_Item_Arrays.Vector;
+      Parse_Items : aliased Parse.Parse_Item_Arrays.Vector;
       Result      : Check_Status := Continue;
 
-      function Max_Push_Back_Token_Index (Ops : in Config_Op_Arrays.Vector) 
return WisiToken.Base_Token_Index
+      function Max_Push_Back_Token_Index (Ops : aliased in 
Config_Op_Arrays.Vector) return WisiToken.Base_Token_Index
       is
          Result : WisiToken.Base_Token_Index := 
WisiToken.Base_Token_Index'First;
       begin
          --  For Ops since last Fast_Forward, return maximum Token_Index in a
          --  Push_Back. If there are no such ops, return a value that will be
          --  less than the current token index.
-         for Op of reverse Ops loop
-            exit when Op.Op = Fast_Forward;
-            if Op.Op = Push_Back and then Op.PB_Token_Index > Result then
-               Result := Op.PB_Token_Index;
-            end if;
+         for I in reverse First_Index (Ops) .. Last_Index (Ops) loop
+            declare
+               Op : Config_Op renames Constant_Ref (Ops, I);
+            begin
+               exit when Op.Op = Fast_Forward;
+               if Op.Op = Push_Back and then Op.PB_Token_Index > Result then
+                  Result := Op.PB_Token_Index;
+               end if;
+            end;
          end loop;
          return Result;
       end Max_Push_Back_Token_Index;
@@ -339,22 +366,22 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
       --  Set Config.error to reflect failure, if it is at current token, so
       --  Use_Minimal_Complete_Actions can see it.
       declare
-         Item          : Parse.Parse_Item renames Parse_Items 
(Parse_Items.First_Index);
-         Parsed_Config : Configuration renames Item.Config;
+         Item : Parse.Parse_Item renames 
Parse.Parse_Item_Array_Refs.Constant_Ref
+           (Parse_Items, First_Index (Parse_Items));
       begin
-         if Parsed_Config.Check_Status.Label /= Ok then
-            Config.Check_Status := Parsed_Config.Check_Status;
-            Config.Error_Token  := Parsed_Config.Error_Token;
+         if Item.Config.Check_Status.Label /= Ok then
+            Config.Check_Status := Item.Config.Check_Status;
+            Config.Error_Token  := Item.Config.Error_Token;
 
             --  Explore cannot fix a check fail; only Language_Fixes can. The
             --  "ignore error" case is handled immediately on return from
             --  Language_Fixes in Process_One, below.
             Result := Abandon;
 
-         elsif Parsed_Config.Error_Token.ID /= Invalid_Token_ID then
+         elsif Item.Config.Error_Token.ID /= Invalid_Token_ID then
 
             if Item.Shift_Count = 0 then
-               Config.Error_Token  := Parsed_Config.Error_Token;
+               Config.Error_Token  := Item.Config.Error_Token;
                Config.Check_Status := (Label => Ok);
             else
                --  Error is not at current token, but Explore might find 
something
@@ -370,39 +397,41 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
       --  All Parse_Items either failed or were not parsed; if they failed
       --  and made progress, enqueue them so Language_Fixes can try to fix
       --  them.
-      for Item of Parse_Items loop
-         if Item.Config.Error_Token.ID /= Invalid_Token_ID and then
-           Item.Shift_Count > 0 and then
-           Max_Push_Back_Token_Index (Item.Config.Ops) < 
Item.Config.Current_Shared_Token - 1
-         then
-            --  Some progress was made; explore at the new error point. It is
-            --  likely that there is only one actual error point, and this 
moves
-            --  away from it, so we give it a cost.
-            begin
-               Item.Config.Minimal_Complete_State := None;
-               Item.Config.Matching_Begin_Done    := False;
-               if Item.Config.Ops (Item.Config.Ops.Last_Index).Op = 
Fast_Forward then
-
-                  Item.Config.Cost := Item.Config.Cost + 
McKenzie_Param.Fast_Forward;
-
-                  Item.Config.Ops (Item.Config.Ops.Last_Index).FF_Token_Index 
:=
-                    Item.Config.Current_Shared_Token;
-
-               else
-                  Item.Config.Cost := Item.Config.Cost + 
McKenzie_Param.Fast_Forward;
-
-                  Item.Config.Ops.Append ((Fast_Forward, 
Item.Config.Current_Shared_Token));
+      for I in First_Index (Parse_Items) .. Last_Index (Parse_Items) loop
+         declare
+            Item : Parse.Parse_Item renames 
Parse.Parse_Item_Array_Refs.Variable_Ref (Parse_Items, I);
+         begin
+            if Item.Config.Error_Token.ID /= Invalid_Token_ID and then
+              Item.Shift_Count > 0 and then
+              Max_Push_Back_Token_Index (Item.Config.Ops) < 
Item.Config.Current_Shared_Token - 1
+            then
+               --  Some progress was made; explore at the new error point. It 
is
+               --  likely that there is only one actual error point, and this 
moves
+               --  away from it, so we give it a cost.
+               begin
+                  Item.Config.Minimal_Complete_State := None;
+                  Item.Config.Matching_Begin_Done    := False;
+                  if Constant_Ref (Item.Config.Ops, Last_Index 
(Item.Config.Ops)).Op = Fast_Forward then
+                     Item.Config.Cost := Item.Config.Cost + 
McKenzie_Param.Fast_Forward;
+                     Variable_Ref (Item.Config.Ops, Last_Index 
(Item.Config.Ops)).FF_Token_Index :=
+                       Item.Config.Current_Shared_Token;
+                  else
+                     Item.Config.Cost := Item.Config.Cost + 
McKenzie_Param.Fast_Forward;
+
+                     if Is_Full (Item.Config.Ops) then
+                        Super.Config_Full ("check 1", Parser_Index);
+                        raise Bad_Config;
+                     else
+                        Append (Item.Config.Ops, (Fast_Forward, 
Item.Config.Current_Shared_Token));
+                     end if;
+                  end if;
+               end;
+               Local_Config_Heap.Add (Item.Config);
+               if Trace_McKenzie > Detail then
+                  Base.Put ("new error point ", Super, Shared, Parser_Index, 
Item.Config);
                end if;
-            exception
-            when SAL.Container_Full =>
-               Super.Config_Full (Parser_Index);
-               raise Bad_Config;
-            end;
-            Local_Config_Heap.Add (Item.Config);
-            if Trace_McKenzie > Detail then
-               Base.Put ("new error point ", Super, Shared, Parser_Index, 
Item.Config);
             end if;
-         end if;
+         end;
       end loop;
 
       if Trace_McKenzie > Extra then
@@ -465,19 +494,23 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
 
          --  loop only exits via returns above
       end loop;
+   exception
+   when Bad_Config =>
+      --  From Do_Reduce_1
+      return False;
    end Check_Reduce_To_Start;
 
    procedure Try_Push_Back
      (Super             : not null access Base.Supervisor;
       Shared            : not null access Base.Shared;
       Parser_Index      : in              SAL.Base_Peek_Type;
-      Config            : in out          Configuration;
+      Config            : in              Configuration;
       Local_Config_Heap : in out          Config_Heaps.Heap_Type)
    is
       Trace          : WisiToken.Trace'Class renames Super.Trace.all;
       McKenzie_Param : McKenzie_Param_Type renames Shared.Table.McKenzie_Param;
 
-      Token : constant Recover_Token := Config.Stack (1).Token;
+      Token : constant Recover_Token := Config.Stack.Peek.Token;
    begin
       --  Try pushing back the stack top, to allow insert and other
       --  operations at that point.
@@ -491,6 +524,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
          --  in trying to redo it.
 
          declare
+            use Config_Op_Arrays;
             New_Config : Configuration := Config;
          begin
             New_Config.Error_Token.ID := Invalid_Token_ID;
@@ -498,14 +532,19 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
 
             New_Config.Stack.Pop;
 
-            if Token.Min_Terminal_Index = Invalid_Token_Index then
-               --  Token is empty; Config.current_shared_token does not 
change, no
-               --  cost increase.
-               New_Config.Ops.Append ((Push_Back, Token.ID, 
New_Config.Current_Shared_Token));
+            if Is_Full (New_Config.Ops) then
+               Super.Config_Full ("push_back 1", Parser_Index);
+               raise Bad_Config;
             else
-               New_Config.Cost := New_Config.Cost + McKenzie_Param.Push_Back 
(Token.ID);
-               New_Config.Ops.Append ((Push_Back, Token.ID, 
Token.Min_Terminal_Index));
-               New_Config.Current_Shared_Token := Token.Min_Terminal_Index;
+               if Token.Min_Terminal_Index = Invalid_Token_Index then
+                  --  Token is empty; Config.current_shared_token does not 
change, no
+                  --  cost increase.
+                  Append (New_Config.Ops, (Push_Back, Token.ID, 
New_Config.Current_Shared_Token));
+               else
+                  New_Config.Cost := New_Config.Cost + 
McKenzie_Param.Push_Back (Token.ID);
+                  Append (New_Config.Ops, (Push_Back, Token.ID, 
Token.Min_Terminal_Index));
+                  New_Config.Current_Shared_Token := Token.Min_Terminal_Index;
+               end if;
             end if;
             New_Config.Strategy_Counts (Explore_Table) := 
New_Config.Strategy_Counts (Explore_Table) + 1;
 
@@ -517,25 +556,22 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
             end if;
          end;
       end if;
-   exception
-   when SAL.Container_Full =>
-      Super.Config_Full (Parser_Index);
    end Try_Push_Back;
 
    function Just_Pushed_Back_Or_Deleted (Config : in Configuration; ID : in 
Token_ID) return Boolean
    is
+      use Config_Op_Arrays, Config_Op_Array_Refs;
       use all type Ada.Containers.Count_Type;
    begin
-      if Config.Ops.Length = 0 then
+      if Length (Config.Ops) = 0 then
          return False;
       else
          declare
-            Last_Op : Config_Op renames Config.Ops (Config.Ops.Last_Index);
+            Last_Op : Config_Op renames Constant_Ref (Config.Ops, Last_Index 
(Config.Ops));
          begin
-            return Last_Op = (Push_Back, ID, Config.Current_Shared_Token) or
-              Last_Op = (Push_Back, ID, Config.Current_Shared_Token - 1) or
-            Last_Op = (Delete, ID, Config.Current_Shared_Token) or
-              Last_Op = (Delete, ID, Config.Current_Shared_Token - 1);
+            return
+              (Last_Op.Op = Push_Back and then Last_Op.PB_ID = ID) or
+              (Last_Op.Op = Delete and then Last_Op.Del_ID = ID);
          end;
       end if;
    end Just_Pushed_Back_Or_Deleted;
@@ -544,19 +580,20 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
      (Super             : not null access Base.Supervisor;
       Shared            : not null access Base.Shared;
       Parser_Index      : in              SAL.Base_Peek_Type;
-      Config            : in out          Configuration;
+      Config            : in              Configuration;
       Local_Config_Heap : in out          Config_Heaps.Heap_Type)
    is
       Trace : WisiToken.Trace'Class renames Super.Trace.all;
       McKenzie_Param : McKenzie_Param_Type renames Shared.Table.McKenzie_Param;
 
-      Token : constant Recover_Token := Config.Stack (1).Token;
+      Token : constant Recover_Token := Config.Stack.Peek.Token;
    begin
       --  Try expanding the nonterm on the stack top, to allow pushing_back
       --  its components, or insert and other operations at that point.
 
       if Undo_Reduce_Valid (Config.Stack, Super.Parser_State 
(Parser_Index).Tree) then
          declare
+            use Config_Op_Arrays;
             New_Config  : Configuration := Config;
             Token_Count : Ada.Containers.Count_Type;
          begin
@@ -570,8 +607,12 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
                New_Config.Cost := New_Config.Cost + McKenzie_Param.Undo_Reduce 
(Token.ID);
             end if;
 
-            New_Config.Ops.Append ((Undo_Reduce, Token.ID, Token_Count));
-
+            if Is_Full (New_Config.Ops) then
+               Super.Config_Full ("undo_reduce 1", Parser_Index);
+               raise Bad_Config;
+            else
+               Append (New_Config.Ops, (Undo_Reduce, Token.ID, Token_Count));
+            end if;
             New_Config.Strategy_Counts (Explore_Table) := 
New_Config.Strategy_Counts (Explore_Table) + 1;
 
             Local_Config_Heap.Add (New_Config);
@@ -582,9 +623,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
             end if;
          end;
       end if;
-   exception
-   when SAL.Container_Full =>
-      Super.Config_Full (Parser_Index);
    end Try_Undo_Reduce;
 
    procedure Insert_From_Action_List
@@ -606,8 +644,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
       --  conflicts or semantic check fails encountered, they create other
       --  configs to enqueue.
 
-      I : Action_List_Iterator := First (Table.States 
(Config.Stack.Peek.State));
-
       Current_Token : constant Token_ID := Current_Token_ID_Peek
         (Shared.Terminals.all, Config.Current_Shared_Token, 
Config.Insert_Delete, Config.Current_Insert_Delete);
 
@@ -616,92 +652,93 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
       --  Most of the time, all the reductions in a state are the same. So
       --  we cache the first result. This includes one reduction; if an
       --  associated semantic check failed, this does not include the fixes.
-   begin
-
-      loop
-         exit when I.Is_Done;
 
-         declare
-            ID     : constant Token_ID := I.Symbol;
-            Action : Parse_Action_Rec renames I.Action;
-         begin
-            if ID /= EOF_ID and then --  can't insert eof
-              ID /= Invalid_Token_ID -- invalid when Verb = Error
-            then
-               if Just_Pushed_Back_Or_Deleted (Config, ID) then
-                  if Trace_McKenzie > Extra then
-                     Put_Line
-                       (Super.Trace.all, Super.Label (Parser_Index), "Insert: 
abandon " & Image (ID, Descriptor) &
-                          ": undo push_back");
-                  end if;
-               elsif ID = Current_Token then
-                  --  This needed because we allow explore when the error is 
not at the
-                  --  explore point; it prevents inserting useless tokens (ie
-                  --  'identifier ;' in ada_lite).
-                  if Trace_McKenzie > Extra then
-                     Put_Line
-                       (Super.Trace.all, Super.Label (Parser_Index), "Insert: 
abandon " & Image (ID, Descriptor) &
-                          ": current token");
-                  end if;
+      I : Parse_Action_Node_Ptr;
+   begin
+      for Node of Table.States (Config.Stack.Peek.State).Action_List loop
+         I := Node.Actions;
+         loop
+            exit when I = null;
+            declare
+               ID     : constant Token_ID := Node.Symbol;
+               Action : Parse_Action_Rec renames I.Item;
+            begin
+               if ID /= EOF_ID and then -- can't insert eof
+                 ID /= Invalid_Token_ID -- invalid when Verb = Error
+               then
+                  if Just_Pushed_Back_Or_Deleted (Config, ID) then
+                     if Trace_McKenzie > Extra then
+                        Put_Line
+                          (Super.Trace.all, Super.Label (Parser_Index), 
"Insert: abandon " & Image (ID, Descriptor) &
+                             ": undo push_back");
+                     end if;
+                  elsif ID = Current_Token then
+                     --  This needed because we allow explore when the error 
is not at the
+                     --  explore point; it prevents inserting useless tokens 
(ie
+                     --  'identifier ;' in ada_lite).
+                     if Trace_McKenzie > Extra then
+                        Put_Line
+                          (Super.Trace.all, Super.Label (Parser_Index), 
"Insert: abandon " & Image (ID, Descriptor) &
+                             ": current token");
+                     end if;
 
-               elsif (for some Minimal of Minimal_Insert => ID = Minimal) then
-                  --  Was inserted by Insert_Minimal_Complete_Actions
-                  null;
+                  elsif (for some Minimal of Minimal_Insert => ID = Minimal) 
then
+                     --  Was inserted by Insert_Minimal_Complete_Actions
+                     null;
 
-               else
-                  case Action.Verb is
-                  when Shift =>
-                     declare
-                        New_Config : Configuration := Config;
-                     begin
-                        New_Config.Error_Token.ID := Invalid_Token_ID;
-                        New_Config.Check_Status   := (Label => 
WisiToken.Semantic_Checks.Ok);
-
-                        Do_Shift
-                          ("Insert", Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, Action.State, ID,
-                           Cost_Delta => 0,
-                           Strategy   => Explore_Table);
-                     end;
-
-                  when Reduce =>
-                     if not Equal (Action, Cached_Action) then
+                  else
+                     case Action.Verb is
+                     when Shift =>
                         declare
                            New_Config : Configuration := Config;
                         begin
-                           New_Config.Error_Token.ID := Invalid_Token_ID;
-                           New_Config.Check_Status   := (Label => 
WisiToken.Semantic_Checks.Ok);
-
-                           Do_Reduce_1 ("Insert", Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, Action);
-                           Cached_Config := New_Config;
-                           Cached_Action := Action;
-
-                           Do_Reduce_2
-                             ("Insert", Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, ID,
+                           Do_Shift
+                             ("Insert", Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, Action.State, ID,
                               Cost_Delta => 0,
                               Strategy   => Explore_Table);
                         end;
 
-                     else
-                        declare
-                           New_Config : Configuration := Cached_Config;
-                        begin
-                           Do_Reduce_2
-                             ("Insert", Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, ID,
-                              Cost_Delta => 0,
-                              Strategy   => Explore_Table);
-                        end;
-                     end if;
+                     when Reduce =>
+                        if not Equal (Action, Cached_Action) then
+                           declare
+                              New_Config : Configuration := Config;
+                           begin
+                              New_Config.Error_Token.ID := Invalid_Token_ID;
+                              New_Config.Check_Status   := (Label => 
WisiToken.Semantic_Checks.Ok);
+
+                              Do_Reduce_1
+                                ("Insert", Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, Action);
+                              Cached_Config := New_Config;
+                              Cached_Action := Action;
+
+                              Do_Reduce_2
+                                ("Insert", Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, ID,
+                                 Cost_Delta => 0,
+                                 Strategy   => Explore_Table);
+                           end;
+
+                        else
+                           declare
+                              New_Config : Configuration := Cached_Config;
+                           begin
+                              Do_Reduce_2
+                                ("Insert", Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, ID,
+                                 Cost_Delta => 0,
+                                 Strategy   => Explore_Table);
+                           end;
+                        end if;
 
-                  when Accept_It =>
-                     raise SAL.Programmer_Error with "found test case for 
Process_One Accept_It";
+                     when Accept_It =>
+                        raise SAL.Programmer_Error with "found test case for 
Process_One Accept_It";
 
-                  when Error =>
-                     null;
-                  end case;
+                     when Error =>
+                        null;
+                     end case;
+                  end if;
                end if;
-            end if;
-         end;
-         I.Next;
+            end;
+            I := I.Next;
+         end loop;
       end loop;
    end Insert_From_Action_List;
 
@@ -714,7 +751,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
      return Token_ID_Arrays.Vector
       --  Return tokens inserted (empty if none).
    is
-      use all type SAL.Base_Peek_Type;
       use Ada.Containers;
 
       Table         : Parse_Table renames Shared.Table.all;
@@ -727,32 +763,42 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
          Config : Configuration;
       end record;
 
-      package Item_Queues is new SAL.Gen_Unbounded_Definite_Queues (Work_Item);
+      package Item_Queues is new SAL.Gen_Bounded_Definite_Queues (Work_Item);
+      use Item_Queues;
 
-      Work : Item_Queues.Queue;
+      Work : Queue_Type (10);
+      --  The required queue size depends on the number of multiple-item
+      --  Minimal_Complete_Actions encountered. That is limited by compound
+      --  statement nesting, and by the frequency of such actions.
 
       function To_Reduce_Action (Action : in Minimal_Action) return 
Reduce_Action_Rec
         is (Reduce, (Action.Nonterm, 0), null, null, Action.Token_Count);
 
       procedure Minimal_Do_Shift (Action : in Minimal_Action; Config : in out 
Configuration)
-      is begin
+      is
+         use Config_Op_Arrays, Config_Op_Array_Refs;
+      begin
          --  Check for a cycle. We compare stack depth as well as state, so
          --  nested compound statements don't look like a cycle; see
          --  test_mckenzie_recover Push_Back_1. We don't check for cycles in
          --  Insert_From_Action_List because we assume cost eliminates cycles
          --  there; Minimal_Complete_Delta is usually negative, so cost does
          --  not necessarily increase here.
-         for Op of reverse Config.Ops loop
-            if Op.Op = Insert and then
-              (Op.Ins_ID = Action.ID and Op.State = Action.State and 
Op.Stack_Depth = Config.Stack.Depth)
-            then
-               if Trace_McKenzie > Extra then
-                  Put_Line
-                    (Super.Trace.all, Super.Label (Parser_Index), 
"Minimal_Complete_Actions: abandon " &
-                       Image (Action.ID, Descriptor) & Action.State'Image & ": 
cycle");
+         for I in reverse First_Index (Config.Ops) .. Last_Index (Config.Ops) 
loop
+            declare
+               Op : Config_Op renames Constant_Ref (Config.Ops, I);
+            begin
+               if Op.Op = Insert and then
+                 (Op.Ins_ID = Action.ID and Op.State = Action.State and 
Op.Stack_Depth = Config.Stack.Depth)
+               then
+                  if Trace_McKenzie > Extra then
+                     Put_Line
+                       (Super.Trace.all, Super.Label (Parser_Index), 
"Minimal_Complete_Actions: abandon " &
+                          Image (Action.ID, Descriptor) & Action.State'Image & 
": cycle");
+                  end if;
+                  return;
                end if;
-               return;
-            end if;
+            end;
          end loop;
 
          --  We don't check Action.ID = Current_Token; the error is at the
@@ -804,7 +850,12 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
             return;
          elsif Actions.Length = 1 then
             if (not Reduce_Only) or Actions (Actions.First_Index).Verb = 
Reduce then
-               Work.Add ((Actions (Actions.First_Index), Config));
+               if Is_Full (Work) then
+                  Super.Config_Full ("Minimal_Complete_Actions 1", 
Parser_Index);
+                  raise Bad_Config;
+               else
+                  Add (Work, (Actions (Actions.First_Index), Config));
+               end if;
             end if;
             return;
          end if;
@@ -848,7 +899,12 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
 
          for I in Length'Range loop
             if (Use_Recursive and Item_Not_Recursive (I)) or ((not 
Use_Recursive) and Length (I) = Min_Length) then
-               Work.Add ((Actions (I), Config));
+               if Is_Full (Work) then
+                  Super.Config_Full ("Minimal_Complete_Actions 2", 
Parser_Index);
+                  raise Bad_Config;
+               else
+                  Add (Work, (Actions (I), Config));
+               end if;
             elsif Trace_McKenzie > Extra then
                Put_Line
                  (Super.Trace.all, Super.Label (Parser_Index), 
"Minimal_Complete_Actions: drop " &
@@ -878,10 +934,10 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
          Orig_Config, Reduce_Only => False);
 
       loop
-         exit when Work.Is_Empty;
+         exit when Is_Empty (Work);
 
          declare
-            Item : Work_Item := Work.Get;
+            Item : Work_Item := Get (Work);
          begin
             if Trace_McKenzie > Extra then
                Put_Line
@@ -976,7 +1032,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
          return;
       end if;
 
-      --  Set up for Parse
       declare
          New_Config  : Configuration := Config;
       begin
@@ -985,41 +1040,46 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
          end loop;
 
          declare
-            use all type SAL.Base_Peek_Type;
-            Parse_Items : Parse.Parse_Item_Arrays.Vector;
+            use Parse.Parse_Item_Arrays;
+            Parse_Items : aliased Parse.Parse_Item_Arrays.Vector;
             Dummy : constant Boolean :=  Parse.Parse
               (Super, Shared, Parser_Index, Parse_Items, New_Config,
                Shared_Token_Goal => Invalid_Token_Index,
                All_Conflicts     => True,
                Trace_Prefix      => "parse Matching_Begin");
          begin
-            for Item of Parse_Items loop
-               if Item.Parsed and Item.Config.Current_Insert_Delete = 
No_Insert_Delete then
-                  Item.Config.Matching_Begin_Done := True;
-                  Item.Config.Cost := Item.Config.Cost + 
Table.McKenzie_Param.Matching_Begin;
-                  Item.Config.Strategy_Counts (Matching_Begin) := 
Item.Config.Strategy_Counts (Matching_Begin) + 1;
-                  Item.Config.Error_Token.ID := Invalid_Token_ID;
-                  Item.Config.Check_Status := (Label => 
WisiToken.Semantic_Checks.Ok);
-
-                  if Trace_McKenzie > Detail then
-                     Base.Put
-                       ("Matching_Begin: insert " & Image 
(Matching_Begin_Tokens, Descriptor),
-                        Super, Shared, Parser_Index, Item.Config);
-                  end if;
-                  Local_Config_Heap.Add (Item.Config);
-               else
-                  if Trace_McKenzie > Detail then
-                     Base.Put
-                     ("Matching_Begin: abandon " & Image 
(Matching_Begin_Tokens, Descriptor) & ": parse fail",
-                        Super, Shared, Parser_Index, Item.Config);
+            for I in First_Index (Parse_Items) .. Last_Index (Parse_Items) loop
+               declare
+                  Item : Parse.Parse_Item renames 
Parse.Parse_Item_Array_Refs.Variable_Ref (Parse_Items, I);
+               begin
+                  if Item.Parsed and Item.Config.Current_Insert_Delete = 
No_Insert_Delete then
+                     Item.Config.Matching_Begin_Done := True;
+                     Item.Config.Cost := Item.Config.Cost + 
Table.McKenzie_Param.Matching_Begin;
+                     Item.Config.Strategy_Counts (Matching_Begin) := 
Item.Config.Strategy_Counts (Matching_Begin) + 1;
+                     Item.Config.Error_Token.ID := Invalid_Token_ID;
+                     Item.Config.Check_Status := (Label => 
WisiToken.Semantic_Checks.Ok);
+
+                     if Trace_McKenzie > Detail then
+                        Base.Put
+                          ("Matching_Begin: insert " & Image 
(Matching_Begin_Tokens, Descriptor),
+                           Super, Shared, Parser_Index, Item.Config);
+                     end if;
+                     Local_Config_Heap.Add (Item.Config);
+                  else
+                     if Trace_McKenzie > Detail then
+                        Base.Put
+                          ("Matching_Begin: abandon " & Image 
(Matching_Begin_Tokens, Descriptor) & ": parse fail",
+                           Super, Shared, Parser_Index, Item.Config);
+                     end if;
                   end if;
-               end if;
+               end;
             end loop;
          end;
       end;
    exception
    when SAL.Container_Full =>
-      Super.Config_Full (Parser_Index);
+      --  From config_ops_sorted
+      Super.Config_Full ("Minimal_Complete_Actions 3", Parser_Index);
    end Insert_Matching_Begin;
 
    procedure Try_Insert_Terminal
@@ -1083,6 +1143,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
       Config            : in out          Configuration;
       Local_Config_Heap : in out          Config_Heaps.Heap_Type)
    is
+      use Config_Op_Arrays;
       use all type Parser.Language_String_ID_Set_Access;
 
       Descriptor  : WisiToken.Descriptor renames Shared.Trace.Descriptor.all;
@@ -1116,22 +1177,29 @@ package body 
WisiToken.Parse.LR.McKenzie_Recover.Explore is
       end String_ID_Set;
 
       procedure String_Literal_In_Stack
-        (New_Config        : in out Configuration;
+        (Label             : in     String;
+         New_Config        : in out Configuration;
          Matching          : in     SAL.Peek_Type;
          String_Literal_ID : in     Token_ID)
       is
+         use Parse.Parse_Item_Arrays;
+
          Saved_Shared_Token : constant WisiToken.Token_Index := 
New_Config.Current_Shared_Token;
 
          Tok         : Recover_Token;
          J           : WisiToken.Token_Index;
-         Parse_Items : Parse.Parse_Item_Arrays.Vector;
+         Parse_Items : aliased Parse.Parse_Item_Arrays.Vector;
       begin
          --  Matching is the index of a token on New_Config.Stack containing a 
string
          --  literal. Push back thru that token, then delete all tokens after
          --  the string literal to Saved_Shared_Token.
+         if not Has_Space (New_Config.Ops, Ada.Containers.Count_Type 
(Matching)) then
+            Super.Config_Full ("insert quote 1 " & Label, Parser_Index);
+            raise Bad_Config;
+         end if;
          for I in 1 .. Matching loop
             Tok := New_Config.Stack.Pop.Token;
-            New_Config.Ops.Append ((Push_Back, Tok.ID, 
Tok.Min_Terminal_Index));
+            Append (New_Config.Ops, (Push_Back, Tok.ID, 
Tok.Min_Terminal_Index));
          end loop;
 
          New_Config.Current_Shared_Token := Tok.Min_Terminal_Index;
@@ -1148,12 +1216,12 @@ package body 
WisiToken.Parse.LR.McKenzie_Recover.Explore is
               (Super, Shared, Parser_Index, Parse_Items, New_Config,
                Shared_Token_Goal => J,
                All_Conflicts     => False,
-               Trace_Prefix      => "insert quote parse pushback")
+               Trace_Prefix      => "insert quote parse pushback " & Label)
             then
                --  The non-deleted tokens parsed without error. We don't care 
if any
                --  conflicts were encountered; we are not using the parse 
result.
-               New_Config := Parse_Items (1).Config;
-               New_Config.Ops.Append ((Fast_Forward, 
New_Config.Current_Shared_Token));
+               New_Config := Parse.Parse_Item_Array_Refs.Constant_Ref 
(Parse_Items, 1).Config;
+               Append (New_Config.Ops, (Fast_Forward, 
New_Config.Current_Shared_Token));
             else
                raise SAL.Programmer_Error;
             end if;
@@ -1161,11 +1229,16 @@ package body 
WisiToken.Parse.LR.McKenzie_Recover.Explore is
          when Bad_Config =>
             raise SAL.Programmer_Error;
          end;
-         J := New_Config.Current_Shared_Token; -- parse result
-         loop
-            exit when J = Saved_Shared_Token;
-            New_Config.Ops.Append ((Delete, Shared.Terminals.all (J).ID, J));
-            J := J + 1;
+
+         if not Has_Space
+           (New_Config.Ops, Ada.Containers.Count_Type (Saved_Shared_Token - 1 
- New_Config.Current_Shared_Token))
+         then
+            Super.Config_Full ("insert quote 2 " & Label, Parser_Index);
+            raise Bad_Config;
+         end if;
+
+         for J in New_Config.Current_Shared_Token .. Saved_Shared_Token - 1 
loop
+            Append (New_Config.Ops, (Delete, Shared.Terminals.all (J).ID, J));
          end loop;
 
          New_Config.Current_Shared_Token := Saved_Shared_Token;
@@ -1186,13 +1259,18 @@ package body 
WisiToken.Parse.LR.McKenzie_Recover.Explore is
          --  This is a guess, so we give it a nominal cost
          New_Config.Cost := New_Config.Cost + 1;
 
+         if not Has_Space (New_Config.Ops, Ada.Containers.Count_Type (Last - 
First)) then
+            Super.Config_Full ("insert quote 3 " & Label, Parser_Index);
+            raise Bad_Config;
+         end if;
+
          for I in First .. Last loop
-            New_Config.Ops.Append ((Delete, Shared.Terminals.all (I).ID, I));
+            Append (New_Config.Ops, (Delete, Shared.Terminals.all (I).ID, I));
          end loop;
          New_Config.Current_Shared_Token := Last + 1;
 
          --  Let explore do insert after these deletes.
-         New_Config.Ops.Append ((Fast_Forward, 
New_Config.Current_Shared_Token));
+         Append (New_Config.Ops, (Fast_Forward, 
New_Config.Current_Shared_Token));
 
          if New_Config.Resume_Token_Goal - Check_Limit < 
New_Config.Current_Shared_Token then
             New_Config.Resume_Token_Goal := New_Config.Current_Shared_Token + 
Check_Limit;
@@ -1206,12 +1284,8 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
          New_Config.Strategy_Counts (String_Quote) := 
New_Config.Strategy_Counts (String_Quote) + 1;
 
          if Trace_McKenzie > Detail then
-            Base.Put ("insert missing quote " & Label & " ", Super, Shared, 
Parser_Index, New_Config);
+            Base.Put ("insert quote " & Label & " ", Super, Shared, 
Parser_Index, New_Config);
          end if;
-      exception
-      when SAL.Container_Full =>
-         Super.Config_Full (Parser_Index);
-         raise Bad_Config;
       end Finish;
 
    begin
@@ -1263,7 +1337,12 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
             loop
                Token := New_Config.Stack.Pop.Token;
                if Token.Byte_Region /= Null_Buffer_Region then
-                  New_Config.Ops.Append ((Push_Back, Token.ID, 
Token.Min_Terminal_Index));
+                  if Is_Full (New_Config.Ops) then
+                     Super.Config_Full ("insert quote 4 a", Parser_Index);
+                     raise Bad_Config;
+                  else
+                     Append (New_Config.Ops, (Push_Back, Token.ID, 
Token.Min_Terminal_Index));
+                  end if;
                   exit;
                end if;
             end loop;
@@ -1289,7 +1368,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
          --  stack.
 
          declare
-            use all type SAL.Base_Peek_Type;
             Matching : SAL.Peek_Type := 1;
          begin
             Find_Descendant_ID
@@ -1301,7 +1379,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
                --  happens in a high cost non critical config.
                if Trace_McKenzie > Detail then
                   Put_Line
-                    (Super.Trace.all, Super.Label (Parser_Index), "abandon 
missing quote b; string literal in virtual");
+                    (Super.Trace.all, Super.Label (Parser_Index), "insert 
quote b abandon; string literal in virtual");
                end if;
                return;
             end if;
@@ -1309,7 +1387,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
             declare
                New_Config : Configuration := Config;
             begin
-               String_Literal_In_Stack (New_Config, Matching, 
Lexer_Error_Token.ID);
+               String_Literal_In_Stack ("b", New_Config, Matching, 
Lexer_Error_Token.ID);
 
                Finish
                  ("b", New_Config, Config.Current_Shared_Token, 
Shared.Line_Begin_Token.all (Current_Line + 1) - 1);
@@ -1342,7 +1420,12 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
             loop
                Token := New_Config.Stack.Pop.Token;
                if Token.Byte_Region /= Null_Buffer_Region then
-                  New_Config.Ops.Append ((Push_Back, Token.ID, 
Token.Min_Terminal_Index));
+                  if Is_Full (New_Config.Ops) then
+                     Super.Config_Full ("insert quote 5 d", Parser_Index);
+                     raise Bad_Config;
+                  else
+                     Append (New_Config.Ops, (Push_Back, Token.ID, 
Token.Min_Terminal_Index));
+                  end if;
                   exit;
                end if;
             end loop;
@@ -1362,7 +1445,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
          --  on the stack containing the string literal that should be extended
          --  to the found quote. See test_mckenzie_recover.adb String_Quote_1.
          declare
-            use all type SAL.Base_Peek_Type;
             Matching : SAL.Peek_Type := 1;
          begin
             --  Lexer_Error_Token is a string literal; find a matching one.
@@ -1377,7 +1459,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
                declare
                   New_Config : Configuration := Config;
                begin
-                  String_Literal_In_Stack (New_Config, Matching, 
Lexer_Error_Token.ID);
+                  String_Literal_In_Stack ("e", New_Config, Matching, 
Lexer_Error_Token.ID);
 
                   Finish ("e", New_Config, Config.Current_Shared_Token, 
Lexer_Error_Token_Index);
                   Local_Config_Heap.Add (New_Config);
@@ -1386,9 +1468,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
          end;
       end if;
    exception
-   when SAL.Container_Full =>
-      Super.Config_Full (Parser_Index);
-
    when Bad_Config =>
       null;
    end Try_Insert_Quote;
@@ -1397,11 +1476,12 @@ package body 
WisiToken.Parse.LR.McKenzie_Recover.Explore is
      (Super             : not null access Base.Supervisor;
       Shared            : not null access Base.Shared;
       Parser_Index      : in              SAL.Base_Peek_Type;
-      Config            : in out          Configuration;
+      Config            : in              Configuration;
       Local_Config_Heap : in out          Config_Heaps.Heap_Type)
    is
       --  Try deleting (= skipping) the current shared input token.
 
+      use Config_Op_Arrays, Config_Op_Array_Refs;
       use all type Ada.Containers.Count_Type;
       Trace       : WisiToken.Trace'Class renames Super.Trace.all;
       EOF_ID      : Token_ID renames Trace.Descriptor.EOI_ID;
@@ -1413,20 +1493,25 @@ package body 
WisiToken.Parse.LR.McKenzie_Recover.Explore is
    begin
       if ID /= EOF_ID and then
          --  can't delete EOF
-         (Config.Ops.Length = 0 or else
+         (Length (Config.Ops) = 0 or else
            --  Don't delete an ID we just inserted; waste of time
-           (not Equal (Config.Ops (Config.Ops.Last_Index), (Insert, ID, 
Config.Current_Shared_Token, 1, 0))))
+           (not Equal (Constant_Ref (Config.Ops, Last_Index (Config.Ops)),
+                       (Insert, ID, Config.Current_Shared_Token, 1, 0))))
       then
          declare
             New_Config : Configuration := Config;
 
             function Matching_Push_Back return Boolean
             is begin
-               for Op of reverse New_Config.Ops loop
-                  exit when not (Op.Op in Undo_Reduce | Push_Back | Delete);
-                  if Op = (Push_Back, ID, New_Config.Current_Shared_Token) then
-                     return True;
-                  end if;
+               for I in reverse First_Index (New_Config.Ops) .. Last_Index 
(New_Config.Ops) loop
+                  declare
+                     Op : Config_Op renames Config_Op_Array_Refs.Variable_Ref 
(New_Config.Ops, I).Element.all;
+                  begin
+                     exit when not (Op.Op in Undo_Reduce | Push_Back | Delete);
+                     if Op = (Push_Back, ID, New_Config.Current_Shared_Token) 
then
+                        return True;
+                     end if;
+                  end;
                end loop;
                return False;
             end Matching_Push_Back;
@@ -1443,7 +1528,12 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
                New_Config.Cost := Natural'Max (Natural'First, New_Config.Cost 
- McKenzie_Param.Push_Back (ID));
             end if;
 
-            New_Config.Ops.Append ((Delete, ID, Config.Current_Shared_Token));
+            if Is_Full (New_Config.Ops) then
+               Super.Config_Full ("delete", Parser_Index);
+               raise Bad_Config;
+            else
+               Append (New_Config.Ops, (Delete, ID, 
Config.Current_Shared_Token));
+            end if;
             New_Config.Current_Shared_Token := New_Config.Current_Shared_Token 
+ 1;
             loop
                exit when not Super.Parser_State 
(Parser_Index).Prev_Deleted.Contains (New_Config.Current_Shared_Token);
@@ -1462,9 +1552,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
             end if;
          end;
       end if;
-   exception
-   when SAL.Container_Full =>
-      Super.Config_Full (Parser_Index);
    end Try_Delete_Input;
 
    procedure Process_One
@@ -1477,7 +1564,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
 
       use all type Base.Config_Status;
       use all type Parser.Language_Fixes_Access;
-      use all type SAL.Base_Peek_Type;
       use all type Semantic_Checks.Check_Status_Label;
 
       Trace      : WisiToken.Trace'Class renames Super.Trace.all;
@@ -1561,7 +1647,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
                   --  finish reduce.
                   Config.Stack.Pop (SAL.Base_Peek_Type 
(Config.Check_Token_Count));
 
-                  New_State := Goto_For (Table, Config.Stack (1).State, 
Config.Error_Token.ID);
+                  New_State := Goto_For (Table, Config.Stack.Peek.State, 
Config.Error_Token.ID);
 
                   if New_State = Unknown_State then
                      if Config.Stack.Depth = 1 then
@@ -1572,7 +1658,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
                      else
                         raise SAL.Programmer_Error with
                           "process_one found test case for new_state = 
Unknown; old state " &
-                          Trimmed_Image (Config.Stack (1).State) & " nonterm " 
& Image
+                          Trimmed_Image (Config.Stack.Peek.State) & " nonterm 
" & Image
                             (Config.Error_Token.ID, Trace.Descriptor.all);
                      end if;
                   end if;
@@ -1621,6 +1707,8 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
       --
       --  All possible permutations will be explored.
 
+      pragma Assert (Config.Stack.Depth > 0);
+
       Try_Insert_Terminal (Super, Shared, Parser_Index, Config, 
Local_Config_Heap);
 
       if None_Since_FF (Config.Ops, Delete) and then
@@ -1652,9 +1740,12 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
 
       Super.Put (Parser_Index, Local_Config_Heap);
    exception
-   when E : others =>
+   when Bad_Config =>
       --  Just abandon this config; tell Super we are done.
       Super.Put (Parser_Index, Local_Config_Heap);
+
+   when E : others =>
+      Super.Put (Parser_Index, Local_Config_Heap);
       if Debug_Mode then
          raise;
       elsif Trace_McKenzie > Outline then
diff --git a/wisitoken-parse-lr-mckenzie_recover-parse.adb 
b/wisitoken-parse-lr-mckenzie_recover-parse.adb
index 963cf1a..5db933e 100644
--- a/wisitoken-parse-lr-mckenzie_recover-parse.adb
+++ b/wisitoken-parse-lr-mckenzie_recover-parse.adb
@@ -26,8 +26,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Parse is
       Nonterm         :    out Recover_Token;
       Default_Virtual : in     Boolean)
    is
-      use all type SAL.Base_Peek_Type;
-
       Min_Terminal_Index_Set : Boolean := False;
    begin
       Nonterm :=
@@ -36,7 +34,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Parse is
          others  => <>);
 
       for I in Tokens'Range loop
-         Tokens (I) := Stack (Tokens'Last - I + 1).Token;
+         Tokens (I) := Stack.Peek (Tokens'Last - I + 1).Token;
       end loop;
 
       for T of Tokens loop
@@ -67,13 +65,13 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Parse is
       Default_Virtual : in              Boolean)
      return Semantic_Checks.Check_Status
    is
-      use all type SAL.Base_Peek_Type;
       use all type Semantic_Checks.Semantic_Check;
       use all type Semantic_Checks.Check_Status_Label;
 
       Last   : constant SAL.Base_Peek_Type := SAL.Base_Peek_Type 
(Action.Token_Count);
       Tokens : Recover_Token_Array (1 .. Last);
    begin
+      pragma Assert (Stack.Depth > Last);
       Compute_Nonterm (Action.Production.LHS, Stack, Tokens, Nonterm, 
Default_Virtual);
 
       if Action.Check = null then
@@ -92,13 +90,13 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Parse is
    end Reduce_Stack;
 
    function Parse_One_Item
-     (Super             : not null access Base.Supervisor;
-      Shared            : not null access Base.Shared;
-      Parser_Index      : in              SAL.Peek_Type;
-      Parse_Items       : in out          Parse_Item_Arrays.Vector;
-      Parse_Item_Index  : in              Positive;
-      Shared_Token_Goal : in              Base_Token_Index;
-      Trace_Prefix      : in              String)
+     (Super             :         not null access Base.Supervisor;
+      Shared            :         not null access Base.Shared;
+      Parser_Index      :         in              SAL.Peek_Type;
+      Parse_Items       : aliased in out          Parse_Item_Arrays.Vector;
+      Parse_Item_Index  :         in              Positive;
+      Shared_Token_Goal :         in              Base_Token_Index;
+      Trace_Prefix      :         in              String)
      return Boolean
    is
       --  Perform parse actions on Parse_Items (Parse_Item_Index), until one
@@ -111,15 +109,16 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Parse is
       --  If any actions have conflicts, append the conflict configs and 
actions to
       --  Parse_Items.
 
+      use Parse_Item_Arrays;
+      use Sorted_Insert_Delete_Arrays;
       use all type Ada.Containers.Count_Type;
-      use all type SAL.Base_Peek_Type;
       use all type Semantic_Checks.Check_Status_Label;
 
       Trace      : WisiToken.Trace'Class renames Super.Trace.all;
       Descriptor : WisiToken.Descriptor renames Super.Trace.Descriptor.all;
       Table      : Parse_Table renames Shared.Table.all;
 
-      Item   : Parse_Item renames Parse_Items (Parse_Item_Index);
+      Item   : Parse_Item renames Parse_Item_Array_Refs.Variable_Ref 
(Parse_Items, Parse_Item_Index).Element.all;
       Config : Configuration renames Item.Config;
       Action : Parse_Action_Node_Ptr renames Item.Action;
 
@@ -142,7 +141,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Parse is
          if Trace_McKenzie > Extra then
             if Config.Current_Insert_Delete /= No_Insert_Delete then
                Put_Line (Trace, Super.Label (Parser_Index), Trace_Prefix & ": 
Insert_Delete: " &
-                           Image (Insert_Delete_Arrays.Vector 
(Config.Insert_Delete), Trace.Descriptor.all));
+                           Image (Config.Insert_Delete, Trace.Descriptor.all));
             end if;
          end if;
 
@@ -156,14 +155,14 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Parse is
       Item.Parsed := True;
 
       if Action = null then
-         Action := Action_For (Table, Config.Stack (1).State, 
Current_Token.ID);
+         Action := Action_For (Table, Config.Stack.Peek.State, 
Current_Token.ID);
       end if;
 
       loop
          Conflict := Action.Next;
          loop
             exit when Conflict = null;
-            if Parse_Items.Is_Full then
+            if Is_Full (Parse_Items) then
                if Trace_McKenzie > Outline then
                   Put_Line (Trace, Super.Label (Parser_Index), Trace_Prefix & 
": too many conflicts; abandoning");
                end if;
@@ -180,7 +179,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Parse is
                           Image (Conflict.Item, Descriptor));
                   end if;
 
-                  Parse_Items.Append ((New_Config, Conflict, Parsed => False, 
Shift_Count => Item.Shift_Count));
+                  Append (Parse_Items, (New_Config, Conflict, Parsed => False, 
Shift_Count => Item.Shift_Count));
                end;
             end if;
             Conflict := Conflict.Next;
@@ -193,7 +192,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Parse is
                  ":" & Image (Current_Token, Descriptor) &
                  " : " & Image (Action.Item, Descriptor) &
                  (if Action.Item.Verb = Reduce
-                  then " via" & Config.Stack (SAL.Peek_Type 
(Action.Item.Token_Count + 1)).State'Image
+                  then " via" & Config.Stack.Peek (SAL.Peek_Type 
(Action.Item.Token_Count + 1)).State'Image
                   else ""));
          end if;
 
@@ -269,10 +268,10 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Parse is
          exit when not Success or
            Action.Item.Verb = Accept_It or
            (if Shared_Token_Goal = Invalid_Token_Index
-            then Config.Insert_Delete.Length = 0
+            then Length (Config.Insert_Delete) = 0
             else Config.Current_Shared_Token > Shared_Token_Goal);
 
-         Action := Action_For (Table, Config.Stack (1).State, 
Current_Token.ID);
+         Action := Action_For (Table, Config.Stack.Peek.State, 
Current_Token.ID);
       end loop;
 
       Config.Current_Shared_Token := Restore_Terminals_Current;
@@ -281,35 +280,41 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Parse is
    end Parse_One_Item;
 
    function Parse
-     (Super             : not null access Base.Supervisor;
-      Shared            : not null access Base.Shared;
-      Parser_Index      : in              SAL.Peek_Type;
-      Parse_Items       :    out          Parse_Item_Arrays.Vector;
-      Config            : in              Configuration;
-      Shared_Token_Goal : in              Base_Token_Index;
-      All_Conflicts     : in              Boolean;
-      Trace_Prefix      : in              String)
+     (Super             :         not null access Base.Supervisor;
+      Shared            :         not null access Base.Shared;
+      Parser_Index      :         in              SAL.Peek_Type;
+      Parse_Items       : aliased    out          Parse_Item_Arrays.Vector;
+      Config            :         in              Configuration;
+      Shared_Token_Goal :         in              Base_Token_Index;
+      All_Conflicts     :         in              Boolean;
+      Trace_Prefix      :         in              String)
      return Boolean
    is
+      use Parse_Item_Arrays;
       Trace : WisiToken.Trace'Class renames Super.Trace.all;
 
       Last_Parsed : Natural;
       Success     : Boolean;
    begin
-      Parse_Items.Clear;
-      Parse_Items.Append ((Config, Action => null, Parsed => False, 
Shift_Count => 0));
+      Clear (Parse_Items);
+      Append (Parse_Items, (Config, Action => null, Parsed => False, 
Shift_Count => 0));
 
       --  Clear any errors; so they reflect the parse result.
-      Parse_Items (Parse_Items.First_Index).Config.Error_Token.ID := 
Invalid_Token_ID;
-      Parse_Items (Parse_Items.First_Index).Config.Check_Status   := (Label => 
Semantic_Checks.Ok);
-
-      Last_Parsed := Parse_Items.First_Index;
+      declare
+         Config : Configuration renames Parse_Item_Array_Refs.Variable_Ref
+           (Parse_Items, First_Index (Parse_Items)).Config;
+      begin
+         Config.Error_Token.ID := Invalid_Token_ID;
+         Config.Check_Status   := (Label => Semantic_Checks.Ok);
+      end;
+
+      Last_Parsed := First_Index (Parse_Items);
       loop
          --  Loop over initial config and any conflicts.
          Success := Parse_One_Item
            (Super, Shared, Parser_Index, Parse_Items, Last_Parsed, 
Shared_Token_Goal, Trace_Prefix);
 
-         exit when Parse_Items.Last_Index = Last_Parsed;
+         exit when Last_Index (Parse_Items) = Last_Parsed;
 
          exit when Success and not All_Conflicts;
 
diff --git a/wisitoken-parse-lr-mckenzie_recover-parse.ads 
b/wisitoken-parse-lr-mckenzie_recover-parse.ads
index e5e4d36..abd8946 100644
--- a/wisitoken-parse-lr-mckenzie_recover-parse.ads
+++ b/wisitoken-parse-lr-mckenzie_recover-parse.ads
@@ -17,6 +17,7 @@
 
 pragma License (Modified_GPL);
 
+with SAL.Gen_Bounded_Definite_Vectors.Gen_Refs;
 with WisiToken.Parse.LR.McKenzie_Recover.Base;
 private package WisiToken.Parse.LR.McKenzie_Recover.Parse is
 
@@ -53,15 +54,17 @@ private package WisiToken.Parse.LR.McKenzie_Recover.Parse is
    package Parse_Item_Arrays is new SAL.Gen_Bounded_Definite_Vectors 
(Positive, Parse_Item, Capacity => 10);
    --  Parse_Item_Arrays.Capacity sets maximum conflicts in one call to Parse
 
+   package Parse_Item_Array_Refs is new Parse_Item_Arrays.Gen_Refs;
+
    function Parse
-     (Super             : not null access Base.Supervisor;
-      Shared            : not null access Base.Shared;
-      Parser_Index      : in              SAL.Peek_Type;
-      Parse_Items       :    out          Parse_Item_Arrays.Vector;
-      Config            : in              Configuration;
-      Shared_Token_Goal : in              Base_Token_Index;
-      All_Conflicts     : in              Boolean;
-      Trace_Prefix      : in              String)
+     (Super             :         not null access Base.Supervisor;
+      Shared            :         not null access Base.Shared;
+      Parser_Index      :         in              SAL.Peek_Type;
+      Parse_Items       : aliased    out          Parse_Item_Arrays.Vector;
+      Config            :         in              Configuration;
+      Shared_Token_Goal :         in              Base_Token_Index;
+      All_Conflicts     :         in              Boolean;
+      Trace_Prefix      :         in              String)
      return Boolean;
    --  Attempt to parse Config and any conflict configs. If not
    --  All_Conflicts, return when Config.Insert_Delete is all processed,
diff --git a/wisitoken-parse-lr-mckenzie_recover.adb 
b/wisitoken-parse-lr-mckenzie_recover.adb
index 49c2106..c53f0ef 100644
--- a/wisitoken-parse-lr-mckenzie_recover.adb
+++ b/wisitoken-parse-lr-mckenzie_recover.adb
@@ -91,25 +91,25 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
    --  problem, and would mean a task that terminates due to an exception
    --  is never restarted.
 
-   function To_Recover
-     (Parser_Stack : in Parser_Lists.Parser_Stacks.Stack;
-      Tree         : in Syntax_Trees.Tree)
-     return Recover_Stacks.Stack
+   procedure To_Recover
+     (Parser_Stack : in     Parser_Lists.Parser_Stacks.Stack;
+      Tree         : in     Syntax_Trees.Tree;
+      Stack        : in out Recover_Stacks.Stack)
    is
-      use all type SAL.Base_Peek_Type;
-      Result : Recover_Stacks.Stack;
-      Depth  : constant SAL.Peek_Type := Parser_Stack.Depth;
+      Depth : constant SAL.Peek_Type := Parser_Stack.Depth;
    begin
-      Result.Set_Depth (Depth);
-      for I in 1 .. Depth loop
+      pragma Assert (Stack.Depth = 0);
+      if Stack.Size < Depth then
+         raise SAL.Programmer_Error with "recover stack needs more space;" & 
Depth'Image;
+      end if;
+      for I in reverse 1 .. Depth loop
          declare
             Item  : Parser_Lists.Parser_Stack_Item renames Parser_Stack (I);
             Token : constant Recover_Token := (if I = Depth then (others => 
<>) else Tree.Recover_Token (Item.Token));
          begin
-            Result.Set (I, Depth, (Item.State, Item.Token, Token));
+            Stack.Push ((Item.State, Item.Token, Token));
          end;
       end loop;
-      return Result;
    end To_Recover;
 
    procedure Recover_Init
@@ -119,7 +119,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       use all type WisiToken.Parse.LR.Parser.Language_Fixes_Access;
 
       Trace  : WisiToken.Trace'Class renames Shared_Parser.Trace.all;
-      Config : constant Configuration_Access := 
Parser_State.Recover.Config_Heap.Add (Configuration'(others => <>));
+      Config : Configuration;
       Error  : Parse_Error renames Parser_State.Errors 
(Parser_State.Errors.Last);
    begin
       Parser_State.Recover.Enqueue_Count := Parser_State.Recover.Enqueue_Count 
+ 1;
@@ -148,7 +148,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       --  Additional initialization of Parser_State.Recover is done in
       --  Supervisor.Initialize.
 
-      Config.Stack := To_Recover (Parser_State.Stack, Parser_State.Tree);
+      To_Recover (Parser_State.Stack, Parser_State.Tree, Config.Stack);
 
       --  Parser_State.Recover_Insert_Delete must be empty (else we would not 
get
       --  here). Therefore Parser_State current token is in
@@ -160,7 +160,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       when Action =>
          Config.Error_Token := Parser_State.Tree.Recover_Token 
(Error.Error_Token);
          if Trace_McKenzie > Detail then
-            Put ("enqueue", Trace, Parser_State.Label, 
Shared_Parser.Terminals, Config.all,
+            Put ("enqueue", Trace, Parser_State.Label, 
Shared_Parser.Terminals, Config,
                  Task_ID => False);
          end if;
 
@@ -168,7 +168,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
          if Shared_Parser.Language_Fixes = null then
             --  The only fix is to ignore the error.
             if Trace_McKenzie > Detail then
-               Put ("enqueue", Trace, Parser_State.Label, 
Shared_Parser.Terminals, Config.all,
+               Put ("enqueue", Trace, Parser_State.Label, 
Shared_Parser.Terminals, Config,
                     Task_ID => False);
             end if;
 
@@ -179,15 +179,15 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
             --  solution; see McKenzie_Recover.Explore Process_One.
 
             Config.Check_Status      := Error.Check_Status;
-            Config.Error_Token       := Config.Stack (1).Token;
+            Config.Error_Token       := Config.Stack.Peek.Token;
             Config.Check_Token_Count := Undo_Reduce (Config.Stack, 
Parser_State.Tree);
 
-            Config.Ops.Append ((Undo_Reduce, Config.Error_Token.ID, 
Config.Check_Token_Count));
+            Config_Op_Arrays.Append (Config.Ops, (Undo_Reduce, 
Config.Error_Token.ID, Config.Check_Token_Count));
 
             if Trace_McKenzie > Detail then
                Put ("undo_reduce " & Image
                       (Config.Error_Token.ID, Trace.Descriptor.all), Trace, 
Parser_State.Label,
-                    Shared_Parser.Terminals, Config.all, Task_ID => False);
+                    Shared_Parser.Terminals, Config, Task_ID => False);
             end if;
          end if;
 
@@ -196,12 +196,13 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
          --  recovery.
          raise SAL.Programmer_Error;
       end case;
+
+      Parser_State.Recover.Config_Heap.Add (Config);
    end Recover_Init;
 
    function Recover (Shared_Parser : in out LR.Parser.Parser) return 
Recover_Status
    is
       use all type Parser.Post_Recover_Access;
-      use all type SAL.Base_Peek_Type;
       Trace : WisiToken.Trace'Class renames Shared_Parser.Trace.all;
 
       Parsers : Parser_Lists.List renames Shared_Parser.Parsers;
@@ -302,6 +303,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       --  the same check here is premature optimization.
       declare
          use Parser_Lists;
+
          Cur         : Cursor             := Parsers.First;
          Solutions   : SAL.Base_Peek_Type := 0;
          Spawn_Limit : SAL.Base_Peek_Type := Shared_Parser.Max_Parallel; -- 
per parser
@@ -357,6 +359,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
                      Trace.Put_Line
                        (Integer'Image (Cur.Label) &
                           ": fail, enqueue" & Integer'Image 
(Data.Enqueue_Count) &
+                          (if Data.Config_Full_Count > 0 then ", config_full" 
& Data.Config_Full_Count'Image else "") &
                           ", check " & Integer'Image (Data.Check_Count) &
                           ", max shared_token " & WisiToken.Token_Index'Image 
(Shared_Parser.Terminals.Last_Index));
                   end if;
@@ -381,6 +384,8 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
                --  Can't have active 'renames State_Ref' when terminate a 
parser
                declare
                   use Parser_Lists;
+                  use Config_Op_Arrays, Config_Op_Array_Refs;
+                  use Sorted_Insert_Delete_Arrays;
 
                   Parser_State : Parser_Lists.Parser_State renames 
Current_Parser.State_Ref;
 
@@ -396,7 +401,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
                   Shared_Token_Changed  : Boolean := False;
                   Current_Token_Virtual : Boolean := False;
 
-                  Sorted_Insert_Delete : Sorted_Insert_Delete_Arrays.Vector;
+                  Sorted_Insert_Delete : aliased 
Sorted_Insert_Delete_Arrays.Vector;
 
                   procedure Apply_Prev_Token
                   is begin
@@ -467,163 +472,187 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
                   --  also avoids enlarging a non-flushed branched tree, which 
saves
                   --  time and space.
 
-                  for Op of Result.Ops loop
-                     case Op.Op is
-                     when Fast_Forward =>
-                        if Op.FF_Token_Index < Min_Op_Token_Index then
-                           Min_Op_Token_Index := Op.FF_Token_Index;
-                        end if;
+                  for I in First_Index (Result.Ops) .. Last_Index (Result.Ops) 
loop
+                     declare
+                        Op : Config_Op renames Constant_Ref (Result.Ops, I);
+                     begin
+                        case Op.Op is
+                        when Fast_Forward =>
+                           if Op.FF_Token_Index < Min_Op_Token_Index then
+                              Min_Op_Token_Index := Op.FF_Token_Index;
+                           end if;
 
-                     when Undo_Reduce =>
-                        null;
+                        when Undo_Reduce =>
+                           null;
 
-                     when Push_Back =>
-                        if Op.PB_Token_Index /= Invalid_Token_Index then
-                           if Op.PB_Token_Index < Min_Op_Token_Index then
-                              Min_Op_Token_Index := Op.PB_Token_Index;
-                           end if;
-                           if Op.PB_Token_Index < Min_Push_Back_Token_Index 
then
-                              Min_Push_Back_Token_Index := Op.PB_Token_Index;
+                        when Push_Back =>
+                           if Op.PB_Token_Index /= Invalid_Token_Index then
+                              if Op.PB_Token_Index < Min_Op_Token_Index then
+                                 Min_Op_Token_Index := Op.PB_Token_Index;
+                              end if;
+                              if Op.PB_Token_Index < Min_Push_Back_Token_Index 
then
+                                 Min_Push_Back_Token_Index := 
Op.PB_Token_Index;
+                              end if;
                            end if;
-                        end if;
 
-                     when Insert =>
-                        if Op.Ins_Token_Index /= Invalid_Token_Index then
-                           if Op.Ins_Token_Index < Min_Op_Token_Index then
-                              Min_Op_Token_Index := Op.Ins_Token_Index;
-                           end if;
-                           if Op.Ins_Token_Index < Min_Push_Back_Token_Index 
then
-                              Min_Push_Back_Token_Index := Op.Ins_Token_Index;
+                        when Insert =>
+                           if Op.Ins_Token_Index /= Invalid_Token_Index then
+                              if Op.Ins_Token_Index < Min_Op_Token_Index then
+                                 Min_Op_Token_Index := Op.Ins_Token_Index;
+                              end if;
+                              if Op.Ins_Token_Index < 
Min_Push_Back_Token_Index then
+                                 Min_Push_Back_Token_Index := 
Op.Ins_Token_Index;
+                              end if;
                            end if;
-                        end if;
 
-                     when Delete =>
-                        if Op.Del_Token_Index /= Invalid_Token_Index then
-                           if Op.Del_Token_Index < Min_Op_Token_Index then
-                              Min_Op_Token_Index := Op.Del_Token_Index;
-                           end if;
-                           if Op.Del_Token_Index < Min_Push_Back_Token_Index 
then
-                              Min_Push_Back_Token_Index := Op.Del_Token_Index;
+                        when Delete =>
+                           if Op.Del_Token_Index /= Invalid_Token_Index then
+                              if Op.Del_Token_Index < Min_Op_Token_Index then
+                                 Min_Op_Token_Index := Op.Del_Token_Index;
+                              end if;
+                              if Op.Del_Token_Index < 
Min_Push_Back_Token_Index then
+                                 Min_Push_Back_Token_Index := 
Op.Del_Token_Index;
+                              end if;
                            end if;
-                        end if;
 
-                     end case;
+                        end case;
+                     end;
                   end loop;
 
-                  for Op of Result.Ops loop
-                     case Op.Op is
-                     when Fast_Forward =>
-                        Stack_Matches_Ops := False;
-
-                     when Undo_Reduce =>
-                        if not Stack_Matches_Ops then
-                           if Trace_McKenzie > Outline then
-                              Put_Line
-                                (Trace, Parser_State.Label, "Undo_Reduce after 
insert or fast_forward",
-                                 Task_ID => False);
-                           end if;
-                           raise Bad_Config;
-                        end if;
+                  for I in First_Index (Result.Ops) .. Last_Index (Result.Ops) 
loop
+                     declare
+                        Op : Config_Op renames Constant_Ref (Result.Ops, I);
+                     begin
+                        case Op.Op is
+                        when Fast_Forward =>
+                           Stack_Matches_Ops := False;
 
-                        declare
-                           Item : constant Parser_Lists.Parser_Stack_Item := 
Parser_State.Stack.Pop;
-                        begin
-                           case Tree.Label (Item.Token) is
-                           when Syntax_Trees.Shared_Terminal |
-                             Syntax_Trees.Virtual_Identifier |
-                             Syntax_Trees.Virtual_Terminal =>
+                        when Undo_Reduce =>
+                           if not Stack_Matches_Ops then
                               if Trace_McKenzie > Outline then
                                  Put_Line
-                                   (Trace, Parser_State.Label, "expecting 
nonterminal, found " &
-                                      Image (Tree.ID (Item.Token), 
Trace.Descriptor.all),
+                                   (Trace, Parser_State.Label, "Undo_Reduce 
after insert or fast_forward",
                                     Task_ID => False);
                               end if;
                               raise Bad_Config;
-
-                           when Syntax_Trees.Nonterm =>
-                              for C of Tree.Children (Item.Token) loop
-                                 Parser_State.Stack.Push ((Tree.State (C), C));
-                              end loop;
-                           end case;
-                        end;
-
-                     when Push_Back =>
-                        if Stack_Matches_Ops then
-                           Parser_State.Stack.Pop;
-                           if Op.PB_Token_Index /= Invalid_Token_Index then
-                              Parser_State.Shared_Token := Op.PB_Token_Index;
-                              Shared_Token_Changed      := True;
                            end if;
 
-                        elsif Op.PB_Token_Index = Min_Op_Token_Index then
-                           loop
-                              --  Multiple push_backs can have the same 
Op.PB_Token_Index, so we may
-                              --  already be at the target.
-                              exit when Parser_State.Shared_Token <= 
Op.PB_Token_Index and
-                                (Parser_State.Stack.Depth = 1 or else
-                                   Tree.Min_Terminal_Index (Parser_State.Stack 
(1).Token) /= Invalid_Token_Index);
-                              --  also push back empty tokens.
-
-                              declare
-                                 Item : constant 
Parser_Lists.Parser_Stack_Item := Parser_State.Stack.Pop;
-
-                                 Min_Index : constant Base_Token_Index :=
-                                   Parser_State.Tree.Min_Terminal_Index 
(Item.Token);
-                              begin
-                                 if Min_Index /= Invalid_Token_Index then
-                                    Shared_Token_Changed := True;
-                                    Parser_State.Shared_Token := Min_Index;
+                           declare
+                              Item : constant Parser_Lists.Parser_Stack_Item 
:= Parser_State.Stack.Pop;
+                           begin
+                              case Tree.Label (Item.Token) is
+                              when Syntax_Trees.Shared_Terminal |
+                                Syntax_Trees.Virtual_Identifier |
+                                Syntax_Trees.Virtual_Terminal =>
+                                 if Trace_McKenzie > Outline then
+                                    Put_Line
+                                      (Trace, Parser_State.Label, "expecting 
nonterminal, found " &
+                                         Image (Tree.ID (Item.Token), 
Trace.Descriptor.all),
+                                       Task_ID => False);
                                  end if;
-                              end;
-                           end loop;
-                           pragma Assert (Parser_State.Shared_Token = 
Op.PB_Token_Index);
-                        end if;
+                                 raise Bad_Config;
+
+                              when Syntax_Trees.Nonterm =>
+                                 for C of Tree.Children (Item.Token) loop
+                                    Parser_State.Stack.Push ((Tree.State (C), 
C));
+                                 end loop;
+                              end case;
+                           end;
+
+                        when Push_Back =>
+                           if Stack_Matches_Ops then
+                              Parser_State.Stack.Pop;
+                              if Op.PB_Token_Index /= Invalid_Token_Index then
+                                 Parser_State.Shared_Token := 
Op.PB_Token_Index;
+                                 Shared_Token_Changed      := True;
+                              end if;
 
-                     when Insert =>
-                        if Stack_Matches_Ops and Op.Ins_Token_Index = 
Parser_State.Shared_Token then
-                           --  This is the first Insert. Even if a later 
Push_Back supercedes it,
-                           --  we record Stack_Matches_Ops false here.
-                           Stack_Matches_Ops := False;
+                           elsif Op.PB_Token_Index = Min_Op_Token_Index then
+                              loop
+                                 --  Multiple push_backs can have the same 
Op.PB_Token_Index, so we may
+                                 --  already be at the target.
+                                 exit when Parser_State.Shared_Token <= 
Op.PB_Token_Index and
+                                   (Parser_State.Stack.Depth = 1 or else
+                                      Tree.Min_Terminal_Index 
(Parser_State.Stack (1).Token) /= Invalid_Token_Index);
+                                 --  also push back empty tokens.
+
+                                 declare
+                                    Item : constant 
Parser_Lists.Parser_Stack_Item := Parser_State.Stack.Pop;
+
+                                    Min_Index : constant Base_Token_Index :=
+                                      Parser_State.Tree.Min_Terminal_Index 
(Item.Token);
+                                 begin
+                                    if Min_Index /= Invalid_Token_Index then
+                                       Shared_Token_Changed := True;
+                                       Parser_State.Shared_Token := Min_Index;
+                                    end if;
+                                 end;
+                              end loop;
+                              pragma Assert (Parser_State.Shared_Token = 
Op.PB_Token_Index);
+                           end if;
 
-                           if Op.Ins_Token_Index <= Min_Push_Back_Token_Index 
then
-                              Parser_State.Current_Token := 
Parser_State.Tree.Add_Terminal (Op.Ins_ID);
-                              Current_Token_Virtual      := True;
+                        when Insert =>
+                           if Stack_Matches_Ops and Op.Ins_Token_Index = 
Parser_State.Shared_Token then
+                              --  This is the first Insert. Even if a later 
Push_Back supercedes it,
+                              --  we record Stack_Matches_Ops false here.
+                              Stack_Matches_Ops := False;
+
+                              if Op.Ins_Token_Index <= 
Min_Push_Back_Token_Index then
+                                 Parser_State.Current_Token := 
Parser_State.Tree.Add_Terminal (Op.Ins_ID);
+                                 Current_Token_Virtual      := True;
+                              else
+                                 if Is_Full (Sorted_Insert_Delete) then
+                                    raise Bad_Config;
+                                 else
+                                    Insert (Sorted_Insert_Delete, Op);
+                                 end if;
+                              end if;
                            else
-                              Sorted_Insert_Delete.Insert (Op);
+                              if Is_Full (Sorted_Insert_Delete) then
+                                 raise Bad_Config;
+                              else
+                                 Insert (Sorted_Insert_Delete, Op);
+                              end if;
                            end if;
-                        else
-                           Sorted_Insert_Delete.Insert (Op);
-                        end if;
 
-                     when Delete =>
-                        if Stack_Matches_Ops and Op.Del_Token_Index = 
Parser_State.Shared_Token then
-                           --  We can apply multiple deletes.
-                           Parser_State.Shared_Token := Op.Del_Token_Index + 1;
-                           Apply_Prev_Token;
-                           Shared_Token_Changed      := True;
-                        else
-                           Sorted_Insert_Delete.Insert (Op);
-                        end if;
-                     end case;
+                        when Delete =>
+                           if Stack_Matches_Ops and Op.Del_Token_Index = 
Parser_State.Shared_Token then
+                              --  We can apply multiple deletes.
+                              Parser_State.Shared_Token := Op.Del_Token_Index 
+ 1;
+                              Apply_Prev_Token;
+                              Shared_Token_Changed      := True;
+                           else
+                              if Is_Full (Sorted_Insert_Delete) then
+                                 raise Bad_Config;
+                              else
+                                 Insert (Sorted_Insert_Delete, Op);
+                              end if;
+                           end if;
+                        end case;
+                     end;
                   end loop;
 
                   --  We may not have processed the current Insert or Delete 
above, if
                   --  they are after a fast_forward.
-                  for Op of Sorted_Insert_Delete loop
-                     if Token_Index (Op) = Parser_State.Shared_Token and not 
Current_Token_Virtual then
-                        case Insert_Delete_Op_Label'(Op.Op) is
-                        when Insert =>
-                           Parser_State.Current_Token := 
Parser_State.Tree.Add_Terminal (ID (Op));
-                           Current_Token_Virtual      := True;
+                  for I in First_Index (Sorted_Insert_Delete) .. Last_Index 
(Sorted_Insert_Delete) loop
+                     declare
+                        Op : Insert_Delete_Op renames 
Insert_Delete_Array_Refs.Constant_Ref (Sorted_Insert_Delete, I);
+                     begin
+                        if Token_Index (Op) = Parser_State.Shared_Token and 
not Current_Token_Virtual then
+                           case Insert_Delete_Op_Label'(Op.Op) is
+                           when Insert =>
+                              Parser_State.Current_Token := 
Parser_State.Tree.Add_Terminal (ID (Op));
+                              Current_Token_Virtual      := True;
 
-                        when Delete =>
-                           Parser_State.Shared_Token := Op.Del_Token_Index + 1;
-                           Apply_Prev_Token;
-                           Shared_Token_Changed      := True;
-                        end case;
-                     else
-                        Parser_State.Recover_Insert_Delete.Put (Op);
-                     end if;
+                           when Delete =>
+                              Parser_State.Shared_Token := Op.Del_Token_Index 
+ 1;
+                              Apply_Prev_Token;
+                              Shared_Token_Changed      := True;
+                           end case;
+                        else
+                           Parser_State.Recover_Insert_Delete.Put (Op);
+                        end if;
+                     end;
                   end loop;
 
                   --  If not Shared_Token_Changed, Shared_Token is the error 
token,
@@ -686,7 +715,10 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       return Super.Recover_Result;
 
    exception
-   when others =>
+   when E : others =>
+      if Debug_Mode then
+         Trace.Put (Ada.Exceptions.Exception_Name (E) & ": " & 
Ada.Exceptions.Exception_Message (E));
+      end if;
       return Fail_Programmer_Error;
    end Recover;
 
@@ -700,22 +732,23 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
    end Check;
 
    function Current_Token
-     (Terminals                 : in     Base_Token_Arrays.Vector;
-      Terminals_Current         : in out WisiToken.Base_Token_Index;
-      Restore_Terminals_Current :    out WisiToken.Base_Token_Index;
-      Insert_Delete             : in out Sorted_Insert_Delete_Arrays.Vector;
-      Current_Insert_Delete     : in out SAL.Base_Peek_Type;
-      Prev_Deleted              : in     Recover_Token_Index_Arrays.Vector)
+     (Terminals                 :         in     Base_Token_Arrays.Vector;
+      Terminals_Current         :         in out WisiToken.Base_Token_Index;
+      Restore_Terminals_Current :            out WisiToken.Base_Token_Index;
+      Insert_Delete             : aliased in out 
Sorted_Insert_Delete_Arrays.Vector;
+      Current_Insert_Delete     :         in out SAL.Base_Peek_Type;
+      Prev_Deleted              :         in     
Recover_Token_Index_Arrays.Vector)
      return Base_Token
    is
-      use all type SAL.Base_Peek_Type;
+      use Sorted_Insert_Delete_Arrays;
+      use Insert_Delete_Array_Refs;
 
       procedure Inc_I_D
       is begin
          Current_Insert_Delete := Current_Insert_Delete + 1;
-         if Current_Insert_Delete > Insert_Delete.Last_Index then
+         if Current_Insert_Delete > Last_Index (Insert_Delete) then
             Current_Insert_Delete := No_Insert_Delete;
-            Insert_Delete.Clear;
+            Clear (Insert_Delete);
          end if;
       end Inc_I_D;
 
@@ -730,9 +763,9 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
             Restore_Terminals_Current := Terminals_Current;
             return Terminals (Terminals_Current);
 
-         elsif Token_Index (Insert_Delete (Current_Insert_Delete)) = 
Terminals_Current then
+         elsif Token_Index (Constant_Ref (Insert_Delete, 
Current_Insert_Delete)) = Terminals_Current then
             declare
-               Op : Insert_Delete_Op renames Insert_Delete 
(Current_Insert_Delete);
+               Op : Insert_Delete_Op renames Constant_Ref (Insert_Delete, 
Current_Insert_Delete);
             begin
                case Insert_Delete_Op_Label (Op.Op) is
                when Insert =>
@@ -759,13 +792,14 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
    end Current_Token;
 
    function Current_Token_ID_Peek
-     (Terminals             : in     Base_Token_Arrays.Vector;
-      Terminals_Current     : in     Base_Token_Index;
-      Insert_Delete         : in     Sorted_Insert_Delete_Arrays.Vector;
-      Current_Insert_Delete : in     SAL.Base_Peek_Type)
+     (Terminals             :         in Base_Token_Arrays.Vector;
+      Terminals_Current     :         in Base_Token_Index;
+      Insert_Delete         : aliased in Sorted_Insert_Delete_Arrays.Vector;
+      Current_Insert_Delete :         in SAL.Base_Peek_Type)
      return Token_ID
    is
-      use all type SAL.Base_Peek_Type;
+      use Insert_Delete_Array_Refs;
+
       Result : Token_ID;
    begin
       if Terminals_Current = Base_Token_Index'First then
@@ -780,9 +814,9 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       if Current_Insert_Delete = No_Insert_Delete then
          null;
 
-      elsif Token_Index (Insert_Delete (Current_Insert_Delete)) = 
Terminals_Current then
+      elsif Token_Index (Constant_Ref (Insert_Delete, Current_Insert_Delete)) 
= Terminals_Current then
          declare
-            Op : Insert_Delete_Op renames Insert_Delete 
(Current_Insert_Delete);
+            Op : Insert_Delete_Op renames Constant_Ref (Insert_Delete, 
Current_Insert_Delete);
          begin
             case Insert_Delete_Op_Label (Op.Op) is
             when Insert =>
@@ -798,14 +832,13 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
    end Current_Token_ID_Peek;
 
    procedure Current_Token_ID_Peek_3
-     (Terminals             : in     Base_Token_Arrays.Vector;
-      Terminals_Current     : in     Base_Token_Index;
-      Insert_Delete         : in     Sorted_Insert_Delete_Arrays.Vector;
-      Current_Insert_Delete : in     SAL.Base_Peek_Type;
-      Prev_Deleted          : in     Recover_Token_Index_Arrays.Vector;
-      Tokens                :    out Token_ID_Array_1_3)
+     (Terminals             :         in     Base_Token_Arrays.Vector;
+      Terminals_Current     :         in     Base_Token_Index;
+      Insert_Delete         : aliased in     
Sorted_Insert_Delete_Arrays.Vector;
+      Current_Insert_Delete :         in     SAL.Base_Peek_Type;
+      Prev_Deleted          :         in     Recover_Token_Index_Arrays.Vector;
+      Tokens                :            out Token_ID_Array_1_3)
    is
-      use all type SAL.Base_Peek_Type;
       Terminals_Next : WisiToken.Token_Index := Terminals_Current + 1;
    begin
       if Terminals_Current = Base_Token_Index'First then
@@ -841,13 +874,14 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       else
          for I in Tokens'Range loop
             declare
+               use Sorted_Insert_Delete_Arrays, Insert_Delete_Array_Refs;
                J : constant SAL.Base_Peek_Type := Current_Insert_Delete + 
SAL.Peek_Type (I) - 1;
             begin
-               if (J >= Insert_Delete.First_Index and J <= 
Insert_Delete.Last_Index) and then
-                 Token_Index (Insert_Delete (J)) = Terminals_Current
+               if (J in First_Index (Insert_Delete) .. Last_Index 
(Insert_Delete)) and then
+                 Token_Index (Constant_Ref (Insert_Delete, J)) = 
Terminals_Current
                then
                   declare
-                     Op : Insert_Delete_Op renames Insert_Delete (J);
+                     Op : Insert_Delete_Op renames Constant_Ref 
(Insert_Delete, J);
                   begin
                      case Insert_Delete_Op_Label (Op.Op) is
                      when Insert =>
@@ -869,46 +903,48 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       Config    : in out Configuration;
       ID        : in     Token_ID)
    is
+      use Config_Op_Arrays;
+      use Sorted_Insert_Delete_Arrays;
       Op : constant Config_Op := (Delete, ID, Config.Current_Shared_Token);
    begin
       Check (Terminals (Config.Current_Shared_Token).ID, ID);
-      Config.Ops.Append (Op);
-      Config.Insert_Delete.Insert (Op);
+      if Is_Full (Config.Ops) or Is_Full (Config.Insert_Delete) then
+         raise Bad_Config;
+      end if;
+      Append (Config.Ops, Op);
+      Insert (Config.Insert_Delete, Op);
       Config.Current_Insert_Delete := 1;
-   exception
-   when SAL.Container_Full =>
-      raise Bad_Config;
    end Delete_Check;
 
    procedure Delete_Check
      (Terminals : in     Base_Token_Arrays.Vector;
       Config    : in out Configuration;
-      Index     : in out     WisiToken.Token_Index;
+      Index     : in out WisiToken.Token_Index;
       ID        : in     Token_ID)
    is
+      use Config_Op_Arrays;
+      use Sorted_Insert_Delete_Arrays;
       Op : constant Config_Op := (Delete, ID, Index);
    begin
       Check (Terminals (Index).ID, ID);
-      Config.Ops.Append (Op);
-      Config.Insert_Delete.Insert (Op);
+      if Is_Full (Config.Ops) or Is_Full (Config.Insert_Delete) then
+         raise Bad_Config;
+      end if;
+      Append (Config.Ops, Op);
+      Insert (Config.Insert_Delete, Op);
       Config.Current_Insert_Delete := 1;
       Index := Index + 1;
-   exception
-   when SAL.Container_Full =>
-      raise Bad_Config;
    end Delete_Check;
 
    procedure Find_ID
      (Config         : in     Configuration;
       ID             : in     Token_ID;
       Matching_Index : in out SAL.Peek_Type)
-   is
-      use all type SAL.Peek_Type;
-   begin
+   is begin
       loop
          exit when Matching_Index = Config.Stack.Depth; -- Depth has 
Invalid_Token_ID
          declare
-            Stack_ID : Token_ID renames Config.Stack (Matching_Index).Token.ID;
+            Stack_ID : Token_ID renames Config.Stack.Peek 
(Matching_Index).Token.ID;
          begin
             exit when Stack_ID = ID;
          end;
@@ -920,13 +956,11 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
      (Config         : in     Configuration;
       IDs            : in     Token_ID_Set;
       Matching_Index : in out SAL.Peek_Type)
-   is
-      use all type SAL.Peek_Type;
-   begin
+   is begin
       loop
-         exit when Matching_Index = Config.Stack.Depth; -- Depth has 
Invalid_Token_ID
+         exit when Matching_Index >= Config.Stack.Depth; -- Depth has 
Invalid_Token_ID
          declare
-            ID : Token_ID renames Config.Stack (Matching_Index).Token.ID;
+            ID : Token_ID renames Config.Stack.Peek (Matching_Index).Token.ID;
          begin
             exit when ID in IDs'First .. IDs'Last and then IDs (ID);
          end;
@@ -942,14 +976,13 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       Matching_Index : in out SAL.Peek_Type)
    is
       use Syntax_Trees;
-      use all type SAL.Peek_Type;
    begin
       loop
-         exit when Matching_Index = Config.Stack.Depth; -- Depth has 
Invalid_Token_ID
-         exit when Config.Stack (Matching_Index).Token.ID in ID_Set'Range and 
then
-           (ID_Set (Config.Stack (Matching_Index).Token.ID) and
-              (Config.Stack (Matching_Index).Tree_Index /= Invalid_Node_Index 
and then
-                 Tree.Find_Descendant (Config.Stack 
(Matching_Index).Tree_Index, ID) /= Invalid_Node_Index));
+         exit when Matching_Index >= Config.Stack.Depth; -- Depth has 
Invalid_Token_ID
+         exit when Config.Stack.Peek (Matching_Index).Token.ID in ID_Set'Range 
and then
+           (ID_Set (Config.Stack.Peek (Matching_Index).Token.ID) and
+              (Config.Stack.Peek (Matching_Index).Tree_Index /= 
Invalid_Node_Index and then
+                 Tree.Find_Descendant (Config.Stack.Peek 
(Matching_Index).Tree_Index, ID) /= Invalid_Node_Index));
 
          Matching_Index := Matching_Index + 1;
       end loop;
@@ -963,13 +996,12 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       Case_Insensitive    : in     Boolean)
    is
       use Ada.Characters.Handling;
-      use all type SAL.Peek_Type;
       Match_Name : constant String := (if Case_Insensitive then To_Lower 
(Name) else Name);
    begin
       loop
-         exit when Matching_Name_Index = Config.Stack.Depth; -- Depth has 
Invalid_Token_ID
+         exit when Matching_Name_Index >= Config.Stack.Depth; -- Depth has 
Invalid_Token_ID
          declare
-            Token       : Recover_Token renames Config.Stack 
(Matching_Name_Index).Token;
+            Token       : Recover_Token renames Config.Stack.Peek 
(Matching_Name_Index).Token;
             Name_Region : constant Buffer_Region :=
               (if Token.Name = Null_Buffer_Region
                then Token.Byte_Region
@@ -996,18 +1028,17 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       Case_Insensitive    : in     Boolean)
    is
       use Ada.Characters.Handling;
-      use all type SAL.Peek_Type;
       Match_Name : constant String := (if Case_Insensitive then To_Lower 
(Name) else Name);
    begin
       Other_Count := 0;
 
       loop
-         exit when Matching_Name_Index = Config.Stack.Depth; -- Depth has 
Invalid_Token_ID
+         exit when Matching_Name_Index >= Config.Stack.Depth; -- Depth has 
Invalid_Token_ID
          declare
-            Token       : Recover_Token renames Config.Stack 
(Matching_Name_Index).Token;
+            Token       : Recover_Token renames Config.Stack.Peek 
(Matching_Name_Index).Token;
             Name_Region : constant Buffer_Region :=
               (if Token.Name = Null_Buffer_Region
-               then Token.Byte_Region -- FIXME: why not only Token.name?
+               then Token.Byte_Region
                else Token.Name);
          begin
             exit when Name_Region /= Null_Buffer_Region and then
@@ -1027,14 +1058,16 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
 
    procedure Insert (Config : in out Configuration; ID : in Token_ID)
    is
+      use Config_Op_Arrays;
+      use Sorted_Insert_Delete_Arrays;
       Op : constant Config_Op := (Insert, ID, Config.Current_Shared_Token, 
Unknown_State, 0);
    begin
-      Config.Ops.Append (Op);
-      Config.Insert_Delete.Insert (Op);
+      if Is_Full (Config.Ops) or Is_Full (Config.Insert_Delete) then
+         raise Bad_Config;
+      end if;
+      Append (Config.Ops, Op);
+      Insert (Config.Insert_Delete, Op);
       Config.Current_Insert_Delete := 1;
-   exception
-   when SAL.Container_Full =>
-      raise Bad_Config;
    end Insert;
 
    procedure Insert (Config : in out Configuration; IDs : in Token_ID_Array)
@@ -1045,15 +1078,15 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
    end Insert;
 
    function Next_Token
-     (Terminals                 : in     Base_Token_Arrays.Vector;
-      Terminals_Current         : in out Base_Token_Index;
-      Restore_Terminals_Current : in out WisiToken.Base_Token_Index;
-      Insert_Delete             : in out Sorted_Insert_Delete_Arrays.Vector;
-      Current_Insert_Delete     : in out SAL.Base_Peek_Type;
-      Prev_Deleted              : in     Recover_Token_Index_Arrays.Vector)
+     (Terminals                 :         in     Base_Token_Arrays.Vector;
+      Terminals_Current         :         in out Base_Token_Index;
+      Restore_Terminals_Current :         in out WisiToken.Base_Token_Index;
+      Insert_Delete             : aliased in out 
Sorted_Insert_Delete_Arrays.Vector;
+      Current_Insert_Delete     :         in out SAL.Base_Peek_Type;
+      Prev_Deleted              :         in     
Recover_Token_Index_Arrays.Vector)
      return Base_Token
    is
-      use all type SAL.Base_Peek_Type;
+      use Sorted_Insert_Delete_Arrays, Insert_Delete_Array_Refs;
 
       function Next_Terminal return Base_Token
       is begin
@@ -1069,18 +1102,18 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
 
    begin
       loop
-         if Insert_Delete.Last_Index > 0 and then Current_Insert_Delete = 
Insert_Delete.Last_Index then
-            Current_Insert_Delete     := No_Insert_Delete;
-            Insert_Delete.Clear;
+         if Last_Index (Insert_Delete) > 0 and then Current_Insert_Delete = 
Last_Index (Insert_Delete) then
+            Current_Insert_Delete := No_Insert_Delete;
+            Clear (Insert_Delete);
             return Next_Terminal;
 
          elsif Current_Insert_Delete = No_Insert_Delete then
             return Next_Terminal;
 
-         elsif Token_Index (Insert_Delete (Current_Insert_Delete + 1)) = 
Terminals_Current + 1 then
+         elsif Token_Index (Constant_Ref (Insert_Delete, Current_Insert_Delete 
+ 1)) = Terminals_Current + 1 then
             Current_Insert_Delete := Current_Insert_Delete + 1;
             declare
-               Op : constant Insert_Delete_Op := Insert_Delete 
(Current_Insert_Delete);
+               Op : Insert_Delete_Op renames Constant_Ref (Insert_Delete, 
Current_Insert_Delete);
             begin
                case Insert_Delete_Op_Label'(Op.Op) is
                when Insert =>
@@ -1100,6 +1133,9 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
 
    procedure Push_Back (Config : in out Configuration)
    is
+      use Config_Op_Arrays, Config_Op_Array_Refs;
+      use Sorted_Insert_Delete_Arrays;
+
       Item        : constant Recover_Stack_Item := Config.Stack.Pop;
       Token_Index : constant Base_Token_Index   := 
Item.Token.Min_Terminal_Index;
 
@@ -1115,22 +1151,26 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
    begin
       if Token_Index /= Invalid_Token_Index then
          Config.Current_Shared_Token := Token_Index;
-         for I in Config.Ops.First_Index .. Config.Ops.Last_Index loop
-            if Compare (Token_Index, Config.Ops (I)) then
-               Config.Insert_Delete.Insert (Config.Ops (I), Ignore_If_Equal => 
True);
+         for I in First_Index (Config.Ops) .. Last_Index (Config.Ops) loop
+            if Compare (Token_Index, Constant_Ref (Config.Ops, I)) then
+               if Is_Full (Config.Insert_Delete) then
+                  raise Bad_Config;
+               end if;
+               Insert (Config.Insert_Delete, Constant_Ref (Config.Ops, I), 
Ignore_If_Equal => True);
             end if;
          end loop;
       end if;
 
-      Config.Ops.Append ((Push_Back, Item.Token.ID, 
Config.Current_Shared_Token));
-   exception
-   when SAL.Container_Full =>
-      raise Bad_Config;
+      if Is_Full (Config.Ops) then
+         raise Bad_Config;
+      end if;
+      Append (Config.Ops, (Push_Back, Item.Token.ID, 
Config.Current_Shared_Token));
    end Push_Back;
 
    procedure Push_Back_Check (Config : in out Configuration; Expected_ID : in 
Token_ID)
    is begin
-      Check (Config.Stack (1).Token.ID, Expected_ID);
+      pragma Assert (Config.Stack.Depth > 1);
+      Check (Config.Stack.Peek (1).Token.ID, Expected_ID);
       Push_Back (Config);
    end Push_Back_Check;
 
@@ -1154,8 +1194,8 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
 
       --  Build a string, call trace.put_line once, so output from multiple
       --  tasks is not interleaved (mostly).
+      use Insert_Delete_Array_Refs;
       use all type Ada.Strings.Unbounded.Unbounded_String;
-      use all type SAL.Base_Peek_Type;
       use all type WisiToken.Semantic_Checks.Check_Status_Label;
 
       Descriptor : WisiToken.Descriptor renames Trace.Descriptor.all;
@@ -1186,7 +1226,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
          Result := Result & "|" & Image (Config.Current_Shared_Token, 
Terminals, Descriptor) & "|";
       else
          Result := Result & "/" & Trimmed_Image (Config.Current_Insert_Delete) 
& ":" &
-           Image (Config.Insert_Delete (Config.Current_Insert_Delete), 
Descriptor) & "/";
+           Image (Constant_Ref (Config.Insert_Delete, 
Config.Current_Insert_Delete), Descriptor) & "/";
       end if;
 
       Result := Result & Image (Config.Ops, Descriptor);
@@ -1212,7 +1252,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       Tree  : in     Syntax_Trees.Tree)
      return Ada.Containers.Count_Type
    is
-      Nonterm_Item : constant Recover_Stack_Item := Stack.Pop;
+      Nonterm_Item : constant Recover_Stack_Item := Recover_Stacks.Pop (Stack);
    begin
       if Nonterm_Item.Token.Byte_Region = Null_Buffer_Region then
          return 0;
@@ -1232,8 +1272,9 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       Tree     : in     Syntax_Trees.Tree;
       Expected : in     Token_ID)
    is begin
-      Check (Config.Stack (1).Token.ID, Expected);
-      Config.Ops.Append ((Undo_Reduce, Expected, Undo_Reduce (Config.Stack, 
Tree)));
+      pragma Assert (Config.Stack.Depth > 1);
+      Check (Config.Stack.Peek (1).Token.ID, Expected);
+      Config_Op_Arrays.Append (Config.Ops, (Undo_Reduce, Expected, Undo_Reduce 
(Config.Stack, Tree)));
    exception
    when SAL.Container_Full =>
       raise Bad_Config;
diff --git a/wisitoken-parse-lr-mckenzie_recover.ads 
b/wisitoken-parse-lr-mckenzie_recover.ads
index b143866..9b84a03 100644
--- a/wisitoken-parse-lr-mckenzie_recover.ads
+++ b/wisitoken-parse-lr-mckenzie_recover.ads
@@ -62,12 +62,12 @@ private
    --  Implemented using 'pragma Assert'.
 
    function Current_Token
-     (Terminals                 : in     Base_Token_Arrays.Vector;
-      Terminals_Current         : in out Base_Token_Index;
-      Restore_Terminals_Current :    out WisiToken.Base_Token_Index;
-      Insert_Delete             : in out Sorted_Insert_Delete_Arrays.Vector;
-      Current_Insert_Delete     : in out SAL.Base_Peek_Type;
-      Prev_Deleted              : in     Recover_Token_Index_Arrays.Vector)
+     (Terminals                 :         in     Base_Token_Arrays.Vector;
+      Terminals_Current         :         in out Base_Token_Index;
+      Restore_Terminals_Current :            out WisiToken.Base_Token_Index;
+      Insert_Delete             : aliased in out 
Sorted_Insert_Delete_Arrays.Vector;
+      Current_Insert_Delete     :         in out SAL.Base_Peek_Type;
+      Prev_Deleted              :         in     
Recover_Token_Index_Arrays.Vector)
      return Base_Token;
    --  Return the current token, from either Terminals or Insert_Delete;
    --  set up for Next_Token.
@@ -75,21 +75,21 @@ private
    --  See Next_Token for more info.
 
    function Current_Token_ID_Peek
-     (Terminals             : in     Base_Token_Arrays.Vector;
-      Terminals_Current     : in     Base_Token_Index;
-      Insert_Delete         : in     Sorted_Insert_Delete_Arrays.Vector;
-      Current_Insert_Delete : in     SAL.Base_Peek_Type)
+     (Terminals             :         in Base_Token_Arrays.Vector;
+      Terminals_Current     :         in Base_Token_Index;
+      Insert_Delete         : aliased in Sorted_Insert_Delete_Arrays.Vector;
+      Current_Insert_Delete :         in SAL.Base_Peek_Type)
      return Token_ID;
    --  Return the current token from either Terminals or
    --  Insert_Delete, without setting up for Next_Token.
 
    procedure Current_Token_ID_Peek_3
-     (Terminals             : in     Base_Token_Arrays.Vector;
-      Terminals_Current     : in     Base_Token_Index;
-      Insert_Delete         : in     Sorted_Insert_Delete_Arrays.Vector;
-      Current_Insert_Delete : in     SAL.Base_Peek_Type;
-      Prev_Deleted          : in     Recover_Token_Index_Arrays.Vector;
-      Tokens                :    out Token_ID_Array_1_3);
+     (Terminals             :         in     Base_Token_Arrays.Vector;
+      Terminals_Current     :         in     Base_Token_Index;
+      Insert_Delete         : aliased in     
Sorted_Insert_Delete_Arrays.Vector;
+      Current_Insert_Delete :         in     SAL.Base_Peek_Type;
+      Prev_Deleted          :         in     Recover_Token_Index_Arrays.Vector;
+      Tokens                :            out Token_ID_Array_1_3);
    --  Return the current token (in Tokens (1)) from either Terminals or
    --  Insert_Delete, without setting up for Next_Token. Return the two
    --  following tokens in Tokens (2 .. 3).
@@ -177,12 +177,12 @@ private
    --  Call Insert for each item in IDs.
 
    function Next_Token
-     (Terminals                 : in     Base_Token_Arrays.Vector;
-      Terminals_Current         : in out Base_Token_Index;
-      Restore_Terminals_Current : in out WisiToken.Base_Token_Index;
-      Insert_Delete             : in out Sorted_Insert_Delete_Arrays.Vector;
-      Current_Insert_Delete     : in out SAL.Base_Peek_Type;
-      Prev_Deleted              : in     Recover_Token_Index_Arrays.Vector)
+     (Terminals                 :         in     Base_Token_Arrays.Vector;
+      Terminals_Current         :         in out Base_Token_Index;
+      Restore_Terminals_Current :         in out WisiToken.Base_Token_Index;
+      Insert_Delete             : aliased in out 
Sorted_Insert_Delete_Arrays.Vector;
+      Current_Insert_Delete     :         in out SAL.Base_Peek_Type;
+      Prev_Deleted              :         in     
Recover_Token_Index_Arrays.Vector)
      return Base_Token;
    --  Return the next token, from either Terminals or Insert_Delete;
    --  update Terminals_Current or Current_Insert_Delete.
@@ -234,14 +234,15 @@ private
    --  Put message to Trace, with parser and task info.
 
    function Undo_Reduce_Valid
-     (Stack : in out Recover_Stacks.Stack;
-      Tree  : in     Syntax_Trees.Tree)
+     (Stack : in Recover_Stacks.Stack;
+      Tree  : in Syntax_Trees.Tree)
      return Boolean
-     is ((Stack.Peek.Tree_Index /= WisiToken.Syntax_Trees.Invalid_Node_Index 
and then
-            Tree.Is_Nonterm (Stack.Peek.Tree_Index)) or
-           (Stack.Peek.Tree_Index = WisiToken.Syntax_Trees.Invalid_Node_Index 
and
-              (not Stack.Peek.Token.Virtual and
-                 Stack.Peek.Token.Byte_Region = Null_Buffer_Region)));
+     is (Stack.Depth > 1 and then
+           ((Stack.Peek.Tree_Index /= 
WisiToken.Syntax_Trees.Invalid_Node_Index and then
+               Tree.Is_Nonterm (Stack.Peek.Tree_Index)) or
+              (Stack.Peek.Tree_Index = 
WisiToken.Syntax_Trees.Invalid_Node_Index and
+                 (not Stack.Peek.Token.Virtual and
+                    Stack.Peek.Token.Byte_Region = Null_Buffer_Region))));
    --  Undo_Reduce needs to know what tokens the nonterm contains, to
    --  push them on the stack. Thus we need either a valid Tree index, or
    --  an empty nonterm. If Token.Virtual, we can't trust
diff --git a/wisitoken-parse-lr-parser.adb b/wisitoken-parse-lr-parser.adb
index 986675d..70fab20 100644
--- a/wisitoken-parse-lr-parser.adb
+++ b/wisitoken-parse-lr-parser.adb
@@ -45,7 +45,6 @@ package body WisiToken.Parse.LR.Parser is
       --  error recovery to take better advantage of them. One recovery
       --  strategy is to fix things so the semantic check passes.
 
-      use all type SAL.Base_Peek_Type;
       use all type Semantic_Checks.Check_Status_Label;
       use all type Semantic_Checks.Semantic_Check;
 
@@ -139,8 +138,6 @@ package body WisiToken.Parse.LR.Parser is
 
       when Reduce =>
          declare
-            use all type SAL.Base_Peek_Type;
-
             New_State : constant Unknown_State_Index := Goto_For
               (Table => Shared_Parser.Table.all,
                State => Parser_State.Stack (SAL.Base_Peek_Type 
(Action.Token_Count) + 1).State,
@@ -226,9 +223,7 @@ package body WisiToken.Parse.LR.Parser is
    procedure Do_Deletes
      (Shared_Parser : in out LR.Parser.Parser;
       Parser_State  : in out Parser_Lists.Parser_State)
-   is
-      use all type SAL.Base_Peek_Type;
-   begin
+   is begin
       if Trace_Parse > Extra then
          Shared_Parser.Trace.Put_Line
            (Integer'Image (Parser_State.Label) & ": shared_token:" &
@@ -285,8 +280,6 @@ package body WisiToken.Parse.LR.Parser is
       Verb          :    out All_Parse_Action_Verbs;
       Zombie_Count  :    out SAL.Base_Peek_Type)
    is
-      use all type SAL.Base_Peek_Type;
-
       Shift_Count   : SAL.Base_Peek_Type := 0;
       Accept_Count  : SAL.Base_Peek_Type := 0;
       Error_Count   : SAL.Base_Peek_Type := 0;
@@ -383,12 +376,7 @@ package body WisiToken.Parse.LR.Parser is
       Parser.Language_String_ID_Set         := Language_String_ID_Set;
       Parser.User_Data                      := User_Data;
 
-      --  We can't use Table.McKenzie_Param /= Default_McKenzie_Param here,
-      --  because the discriminants are different.
-      Parser.Enable_McKenzie_Recover :=
-        Table.McKenzie_Param.Check_Limit /= Default_McKenzie_Param.Check_Limit 
or
-          Table.McKenzie_Param.Check_Delta_Limit /= 
Default_McKenzie_Param.Check_Delta_Limit or
-          Table.McKenzie_Param.Enqueue_Limit /= 
Default_McKenzie_Param.Enqueue_Limit;
+      Parser.Enable_McKenzie_Recover := not McKenzie_Defaulted (Table.all);
 
       Parser.Max_Parallel         := Max_Parallel;
       Parser.Terminate_Same_State := Terminate_Same_State;
@@ -403,7 +391,6 @@ package body WisiToken.Parse.LR.Parser is
       use all type Ada.Strings.Unbounded.Unbounded_String;
       use all type Syntax_Trees.User_Data_Access;
       use all type Ada.Containers.Count_Type;
-      use all type SAL.Base_Peek_Type;
 
       Trace : WisiToken.Trace'Class renames Shared_Parser.Trace.all;
 
@@ -676,6 +663,7 @@ package body WisiToken.Parse.LR.Parser is
                Recover_Result : McKenzie_Recover.Recover_Status := 
McKenzie_Recover.Recover_Status'First;
 
                Pre_Recover_Parser_Count : constant SAL.Base_Peek_Type := 
Shared_Parser.Parsers.Count;
+               Start : Ada.Calendar.Time;
             begin
                --  Recover algorithms expect current token at
                --  Parsers(*).Current_Token, will set
@@ -686,10 +674,17 @@ package body WisiToken.Parse.LR.Parser is
                if Shared_Parser.Enable_McKenzie_Recover then
                   if Debug_Mode then
                      Trace.Put_Clock ("pre-recover" & 
Shared_Parser.Parsers.Count'Img & " active");
+                     Start := Ada.Calendar.Clock;
                   end if;
                   Recover_Result := McKenzie_Recover.Recover (Shared_Parser);
                   if Debug_Mode then
-                     Trace.Put_Clock ("post-recover" & 
Shared_Parser.Parsers.Count'Img & " active");
+                     declare
+                        use Ada.Calendar;
+                        Recover_Duration : constant Duration := Clock - Start;
+                     begin
+                        Trace.Put_Clock
+                          ("post-recover" & Shared_Parser.Parsers.Count'Img & 
" active," & Recover_Duration'Image);
+                     end;
                   end if;
 
                   if Trace_Parse > Outline then
@@ -732,7 +727,7 @@ package body WisiToken.Parse.LR.Parser is
                      end;
                   end if;
                else
-                  if Trace_Parse > Outline then
+                  if Trace_Parse > Outline or Trace_McKenzie > Outline then
                      Trace.Put_Line ("recover disabled");
                   end if;
                end if;
@@ -786,7 +781,10 @@ package body WisiToken.Parse.LR.Parser is
                          First_Terminal => Trace.Descriptor.First_Terminal,
                          Last_Terminal  => Trace.Descriptor.Last_Terminal,
                          Recover        => <>,
-                         Msg            => +"recover: fail " & 
McKenzie_Recover.Recover_Status'Image (Recover_Result)));
+                         Msg            =>
+                           (if Shared_Parser.Enable_McKenzie_Recover
+                            then +"recover: fail " & 
McKenzie_Recover.Recover_Status'Image (Recover_Result)
+                            else +"recover disabled")));
                   end loop;
                   raise WisiToken.Syntax_Error;
                end if;
@@ -970,7 +968,7 @@ package body WisiToken.Parse.LR.Parser is
       end if;
 
       if Debug_Mode then
-         Trace.Put_Clock ("finish");
+         Trace.Put_Clock ("finish parse");
       end if;
 
       --  We don't raise Syntax_Error for lexer errors, since they are all
@@ -1007,9 +1005,7 @@ package body WisiToken.Parse.LR.Parser is
    end Parse;
 
    overriding function Tree (Shared_Parser : in Parser) return 
Syntax_Trees.Tree
-   is
-      use all type SAL.Base_Peek_Type;
-   begin
+   is begin
       if Shared_Parser.Parsers.Count > 1 then
          raise WisiToken.Parse_Error with "ambigous parse";
       else
@@ -1020,7 +1016,6 @@ package body WisiToken.Parse.LR.Parser is
    overriding
    procedure Execute_Actions (Parser : in out LR.Parser.Parser)
    is
-      use all type SAL.Base_Peek_Type;
       use all type Syntax_Trees.User_Data_Access;
       use all type WisiToken.Syntax_Trees.Semantic_Action;
 
@@ -1065,26 +1060,29 @@ package body WisiToken.Parse.LR.Parser is
          end if;
 
          declare
-            Parser_State : Parser_Lists.Parser_State renames 
Parser.Parsers.First_State_Ref.Element.all;
+            use Config_Op_Arrays, Config_Op_Array_Refs;
+            Parser_State : Parser_Lists.Parser_State renames 
Parser.Parsers.First_State_Ref;
          begin
             if Trace_Action > Outline then
                Parser.Trace.Put_Line
                  (Integer'Image (Parser_State.Label) & ": root node: " & 
Parser_State.Tree.Image
-                 (Parser_State.Tree.Root, Descriptor));
+                    (Parser_State.Tree.Root, Descriptor));
             end if;
 
-            if (for some Err of Parser_State.Errors => Any (Err.Recover.Ops, 
Delete)) then
-               for Err of Parser_State.Errors loop
-                  for Op of Err.Recover.Ops loop
+            for Err of Parser_State.Errors loop
+               for I in First_Index (Err.Recover.Ops) .. Last_Index 
(Err.Recover.Ops) loop
+                  declare
+                     Op : Config_Op renames Constant_Ref (Err.Recover.Ops, I);
+                  begin
                      case Op.Op is
                      when Delete =>
                         Parser.User_Data.Delete_Token (Op.Del_Token_Index);
                      when others =>
                         null;
                      end case;
-                  end loop;
+                  end;
                end loop;
-            end if;
+            end loop;
 
             Parser.User_Data.Initialize_Actions (Parser_State.Tree);
             Parser_State.Tree.Process_Tree (Process_Node'Access);
@@ -1094,7 +1092,6 @@ package body WisiToken.Parse.LR.Parser is
 
    overriding function Any_Errors (Parser : in LR.Parser.Parser) return Boolean
    is
-      use all type SAL.Base_Peek_Type;
       use all type Ada.Containers.Count_Type;
       Parser_State : Parser_Lists.Parser_State renames 
Parser.Parsers.First_Constant_State_Ref;
    begin
@@ -1104,7 +1101,6 @@ package body WisiToken.Parse.LR.Parser is
 
    overriding procedure Put_Errors (Parser : in LR.Parser.Parser)
    is
-      use all type SAL.Base_Peek_Type;
       use Ada.Text_IO;
 
       Parser_State : Parser_Lists.Parser_State renames 
Parser.Parsers.First_Constant_State_Ref;
diff --git a/wisitoken-parse-lr-parser.ads b/wisitoken-parse-lr-parser.ads
index a88e3af..e84d65d 100644
--- a/wisitoken-parse-lr-parser.ads
+++ b/wisitoken-parse-lr-parser.ads
@@ -80,10 +80,10 @@ package WisiToken.Parse.LR.Parser is
    type Post_Recover_Access is access procedure;
 
    type Parser is new WisiToken.Parse.Base_Parser with record
-      Table                                 : Parse_Table_Ptr;
-      Language_Fixes                        : Language_Fixes_Access;
+      Table                          : Parse_Table_Ptr;
+      Language_Fixes                 : Language_Fixes_Access;
       Language_Matching_Begin_Tokens : Language_Matching_Begin_Tokens_Access;
-      Language_String_ID_Set                : Language_String_ID_Set_Access;
+      Language_String_ID_Set         : Language_String_ID_Set_Access;
 
       String_Quote_Checked : Line_Number_Type := Invalid_Line_Number;
       --  Max line checked for missing string quote.
@@ -143,9 +143,9 @@ package WisiToken.Parse.LR.Parser is
    --  raise Parse_Error for an ambiguous parse.
 
    overriding procedure Execute_Actions (Parser : in out LR.Parser.Parser);
-   --  Call User_Data.Reduce on all nonterms in the syntax tree, then
-   --  User_Data.Delete_Token on any tokens deleted by error recovery,
-   --  then the grammar semantic actions.
+   --  Call User_Data.Delete_Token on any tokens deleted by error
+   --  recovery, then User_Data.Reduce and the grammar semantic actions
+   --  on all nonterms in the syntax tree.
 
    overriding function Any_Errors (Parser : in LR.Parser.Parser) return 
Boolean;
    --  Return True if any errors where encountered, recovered or not.
diff --git a/wisitoken-parse-lr-parser_lists.adb 
b/wisitoken-parse-lr-parser_lists.adb
index e2a0918..480e86f 100644
--- a/wisitoken-parse-lr-parser_lists.adb
+++ b/wisitoken-parse-lr-parser_lists.adb
@@ -27,7 +27,6 @@ package body WisiToken.Parse.LR.Parser_Lists is
       Depth      : in SAL.Base_Peek_Type := 0)
      return String
    is
-      use all type SAL.Base_Peek_Type;
       use Ada.Strings.Unbounded;
 
       Last : constant SAL.Base_Peek_Type :=
@@ -104,14 +103,14 @@ package body WisiToken.Parse.LR.Parser_Lists is
 
    function Label (Cursor : in Parser_Lists.Cursor) return Natural
    is begin
-      return Parser_State_Lists.Constant_Reference (Cursor.Ptr).Label;
+      return Parser_State_Lists.Constant_Ref (Cursor.Ptr).Label;
    end Label;
 
    function Total_Recover_Cost (Cursor : in Parser_Lists.Cursor) return Integer
    is
       Result : Integer := 0;
    begin
-      for Error of Parser_State_Lists.Constant_Reference (Cursor.Ptr).Errors 
loop
+      for Error of Parser_State_Lists.Constant_Ref (Cursor.Ptr).Errors loop
          Result := Error.Recover.Cost;
       end loop;
       return Result;
@@ -120,12 +119,13 @@ package body WisiToken.Parse.LR.Parser_Lists is
    function Max_Recover_Ops_Length (Cursor : in Parser_Lists.Cursor) return 
Ada.Containers.Count_Type
    is
       use Ada.Containers;
+      use Config_Op_Arrays;
       Result : Count_Type := 0;
-      Errors : Parse_Error_Lists.List renames 
Parser_State_Lists.Constant_Reference (Cursor.Ptr).Errors;
+      Errors : Parse_Error_Lists.List renames Parser_State_Lists.Constant_Ref 
(Cursor.Ptr).Errors;
    begin
       for Error of Errors loop
-         if Error.Recover.Ops.Length > Result then
-            Result := Error.Recover.Ops.Length;
+         if Length (Error.Recover.Ops) > Result then
+            Result := Length (Error.Recover.Ops);
          end if;
       end loop;
       return Result;
@@ -134,7 +134,7 @@ package body WisiToken.Parse.LR.Parser_Lists is
    function Min_Recover_Cost (Cursor : in Parser_Lists.Cursor) return Integer
    is
       Result : Integer := Integer'Last;
-      Errors : Parse_Error_Lists.List renames 
Parser_State_Lists.Constant_Reference (Cursor.Ptr).Errors;
+      Errors : Parse_Error_Lists.List renames Parser_State_Lists.Constant_Ref 
(Cursor.Ptr).Errors;
    begin
       for Error of Errors loop
          if Error.Recover.Cost < Result then
@@ -146,12 +146,12 @@ package body WisiToken.Parse.LR.Parser_Lists is
 
    procedure Set_Verb (Cursor : in Parser_Lists.Cursor; Verb : in 
All_Parse_Action_Verbs)
    is begin
-      Parser_State_Lists.Reference (Cursor.Ptr).Verb := Verb;
+      Parser_State_Lists.Variable_Ref (Cursor.Ptr).Verb := Verb;
    end Set_Verb;
 
    function Verb (Cursor : in Parser_Lists.Cursor) return 
All_Parse_Action_Verbs
    is begin
-      return Parser_State_Lists.Constant_Reference (Cursor.Ptr).Verb;
+      return Parser_State_Lists.Constant_Ref (Cursor.Ptr).Verb;
    end Verb;
 
    procedure Terminate_Parser
@@ -161,8 +161,7 @@ package body WisiToken.Parse.LR.Parser_Lists is
       Trace     : in out WisiToken.Trace'Class;
       Terminals : in     Base_Token_Arrays.Vector)
    is
-      use all type SAL.Base_Peek_Type;
-      State : Parser_State renames Parser_State_Lists.Constant_Reference 
(Current.Ptr).Element.all;
+      State : Parser_State renames Parser_State_Lists.Constant_Ref 
(Current.Ptr).Element.all;
    begin
       if Trace_Parse > Outline then
          Trace.Put_Line
@@ -186,7 +185,6 @@ package body WisiToken.Parse.LR.Parser_Lists is
       Trace     : in out WisiToken.Trace'Class;
       Terminals : in     Base_Token_Arrays.Vector)
    is
-      use all type SAL.Base_Peek_Type;
       use all type Ada.Containers.Count_Type;
 
       function Compare
@@ -268,22 +266,22 @@ package body WisiToken.Parse.LR.Parser_Lists is
 
    function State_Ref (Position : in Cursor) return State_Reference
    is begin
-      return (Element => Parser_State_Lists.Constant_Reference 
(Position.Ptr).Element);
+      return (Element => Parser_State_Lists.Constant_Ref 
(Position.Ptr).Element);
    end State_Ref;
 
    function First_State_Ref (List : in Parser_Lists.List'Class) return 
State_Reference
    is begin
-      return (Element => Parser_State_Lists.Constant_Reference 
(List.Elements.First).Element);
+      return (Element => Parser_State_Lists.Constant_Ref 
(List.Elements.First).Element);
    end First_State_Ref;
 
    function First_Constant_State_Ref (List : in Parser_Lists.List'Class) 
return Constant_State_Reference
    is begin
-      return (Element => Parser_State_Lists.Constant_Reference 
(List.Elements.First).Element);
+      return (Element => Parser_State_Lists.Constant_Ref 
(List.Elements.First).Element);
    end First_Constant_State_Ref;
 
    procedure Put_Top_10 (Trace : in out WisiToken.Trace'Class; Cursor : in 
Parser_Lists.Cursor)
    is
-      Parser_State : Parser_Lists.Parser_State renames 
Parser_State_Lists.Constant_Reference (Cursor.Ptr);
+      Parser_State : Parser_Lists.Parser_State renames 
Parser_State_Lists.Constant_Ref (Cursor.Ptr);
    begin
       Trace.Put (Natural'Image (Parser_State.Label) & " stack: ");
       Trace.Put_Line (Image (Parser_State.Stack, Trace.Descriptor.all, 
Parser_State.Tree, Depth => 10));
@@ -297,7 +295,7 @@ package body WisiToken.Parse.LR.Parser_Lists is
    begin
       List.Parser_Label := List.Parser_Label + 1;
       declare
-         Item : Parser_State renames Parser_State_Lists.Reference 
(Cursor.Ptr).Element.all;
+         Item : Parser_State renames Parser_State_Lists.Variable_Ref 
(Cursor.Ptr);
          --  We can't do 'Prepend' in the scope of this 'renames';
          --  that would be tampering with cursors.
       begin
@@ -353,7 +351,7 @@ package body WisiToken.Parse.LR.Parser_Lists is
    is
       pragma Unreferenced (Container);
    begin
-      return (Element => Parser_State_Lists.Constant_Reference 
(Position.Ptr).Element);
+      return (Element => Parser_State_Lists.Constant_Ref 
(Position.Ptr).Element);
    end Constant_Reference;
 
    function Reference
@@ -363,7 +361,7 @@ package body WisiToken.Parse.LR.Parser_Lists is
    is
       pragma Unreferenced (Container);
    begin
-      return (Element => Parser_State_Lists.Reference (Position.Ptr).Element);
+      return (Element => Parser_State_Lists.Variable_Ref 
(Position.Ptr).Element);
    end Reference;
 
    function Persistent_State_Ref (Position : in Parser_Node_Access) return 
State_Access
diff --git a/wisitoken-parse-lr-parser_lists.ads 
b/wisitoken-parse-lr-parser_lists.ads
index a5af546..19aa370 100644
--- a/wisitoken-parse-lr-parser_lists.ads
+++ b/wisitoken-parse-lr-parser_lists.ads
@@ -22,6 +22,7 @@ pragma License (Modified_GPL);
 
 with Ada.Iterator_Interfaces;
 with SAL.Gen_Indefinite_Doubly_Linked_Lists;
+with SAL.Gen_Unbounded_Definite_Stacks;
 with WisiToken.Syntax_Trees;
 package WisiToken.Parse.LR.Parser_Lists is
 
diff --git a/wisitoken-parse-lr-parser_no_recover.adb 
b/wisitoken-parse-lr-parser_no_recover.adb
index 67371f1..7943aab 100644
--- a/wisitoken-parse-lr-parser_no_recover.adb
+++ b/wisitoken-parse-lr-parser_no_recover.adb
@@ -36,8 +36,6 @@ package body WisiToken.Parse.LR.Parser_No_Recover is
       Nonterm        :    out WisiToken.Syntax_Trees.Valid_Node_Index;
       Trace          : in out WisiToken.Trace'Class)
    is
-      use all type SAL.Base_Peek_Type;
-
       Parser_State  : Parser_Lists.Parser_State renames 
Current_Parser.State_Ref.Element.all;
       Children_Tree : Syntax_Trees.Valid_Node_Index_Array (1 .. 
SAL.Base_Peek_Type (Action.Token_Count));
       --  for Set_Children.
@@ -83,8 +81,6 @@ package body WisiToken.Parse.LR.Parser_No_Recover is
          Current_Parser.Set_Verb (Reduce);
 
          declare
-            use all type SAL.Base_Peek_Type;
-
             New_State : constant Unknown_State_Index := Goto_For
               (Table => Shared_Parser.Table.all,
                State => Parser_State.Stack (SAL.Base_Peek_Type 
(Action.Token_Count) + 1).State,
@@ -159,8 +155,6 @@ package body WisiToken.Parse.LR.Parser_No_Recover is
      (Shared_Parser : in out Parser;
       Verb          :    out All_Parse_Action_Verbs)
    is
-      use all type SAL.Base_Peek_Type;
-
       Shift_Count  : SAL.Base_Peek_Type := 0;
       Accept_Count : SAL.Base_Peek_Type := 0;
       Error_Count  : SAL.Base_Peek_Type := 0;
@@ -236,7 +230,6 @@ package body WisiToken.Parse.LR.Parser_No_Recover is
    overriding procedure Parse (Shared_Parser : aliased in out Parser)
    is
       use all type Syntax_Trees.User_Data_Access;
-      use all type SAL.Base_Peek_Type;
 
       Trace : WisiToken.Trace'Class renames Shared_Parser.Trace.all;
 
@@ -436,7 +429,6 @@ package body WisiToken.Parse.LR.Parser_No_Recover is
 
    overriding procedure Execute_Actions (Parser : in out 
LR.Parser_No_Recover.Parser)
    is
-      use all type SAL.Base_Peek_Type;
       use all type Syntax_Trees.User_Data_Access;
 
       procedure Process_Node
@@ -489,9 +481,7 @@ package body WisiToken.Parse.LR.Parser_No_Recover is
    end Execute_Actions;
 
    overriding function Tree (Parser : in LR.Parser_No_Recover.Parser) return 
Syntax_Trees.Tree
-   is
-      use all type SAL.Base_Peek_Type;
-   begin
+   is begin
       if Parser.Parsers.Count > 1 then
          raise WisiToken.Parse_Error with "ambigous parse";
       else
@@ -501,7 +491,6 @@ package body WisiToken.Parse.LR.Parser_No_Recover is
 
    overriding function Any_Errors (Parser : in LR.Parser_No_Recover.Parser) 
return Boolean
    is
-      use all type SAL.Base_Peek_Type;
       use all type Ada.Containers.Count_Type;
       Parser_State : Parser_Lists.Parser_State renames 
Parser.Parsers.First_Constant_State_Ref;
    begin
diff --git a/wisitoken-parse-lr.adb b/wisitoken-parse-lr.adb
index 8900d4c..0ec3aa9 100644
--- a/wisitoken-parse-lr.adb
+++ b/wisitoken-parse-lr.adb
@@ -109,68 +109,41 @@ package body WisiToken.Parse.LR is
       return False;
    end Is_In;
 
-   function Find
-     (Symbol      : in Token_ID;
-      Action_List : in Action_Node_Ptr)
-     return Action_Node_Ptr
-   is
-      Action_Node : Action_Node_Ptr := Action_List;
-   begin
-      while Action_Node /= null loop
-         if Action_Node.Symbol = Symbol then
-            return Action_Node;
-         end if;
-         Action_Node := Action_Node.Next;
-      end loop;
-
-      return null;
-   end Find;
+   function Compare (Left, Right : in Token_ID) return SAL.Compare_Result
+   is begin
+      if Left < Right then
+         return SAL.Less;
+      elsif Left = Right then
+         return SAL.Equal;
+      else
+         return SAL.Greater;
+      end if;
+   end Compare;
 
    procedure Add
-     (List   : in out Action_Node_Ptr;
+     (List   : in out Action_Arrays.Vector;
       Symbol : in     Token_ID;
       Action : in     Parse_Action_Rec)
-   is
-      New_Item : constant Action_Node_Ptr := new Action_Node'(Symbol, new 
Parse_Action_Node'(Action, null), null);
-      I        : Action_Node_Ptr          := List;
-   begin
-      if I = null then
-         List := New_Item;
-      else
-         if List.Symbol > Symbol then
-            New_Item.Next := List;
-            List          := New_Item;
-         else
-            if List.Next = null then
-               List.Next := New_Item;
-            else
-               I := List;
+   is begin
+      declare
+         Node : constant Action_Arrays.Find_Reference_Type := List.Find 
(Symbol);
+      begin
+         if Node.Element /= null then
+            declare
+               I : Parse_Action_Node_Ptr := Node.Element.Actions;
+            begin
                loop
-                  exit when I.Next = null or else I.Next.Symbol > Symbol;
+                  exit when I.Next = null;
                   I := I.Next;
                end loop;
-               New_Item.Next := I.Next;
-               I.Next        := New_Item;
-            end if;
+               I.Next := new Parse_Action_Node'(Action, null);
+               return;
+            end;
          end if;
-      end if;
+      end;
+      List.Insert ((Symbol, new Parse_Action_Node'(Action, null)));
    end Add;
 
-   function Symbol (List : in Goto_Node_Ptr) return Token_ID
-   is begin
-      return List.Symbol;
-   end Symbol;
-
-   function State (List : in Goto_Node_Ptr) return State_Index
-   is begin
-      return List.State;
-   end State;
-
-   function Next (List : in Goto_Node_Ptr) return Goto_Node_Ptr
-   is begin
-      return List.Next;
-   end Next;
-
    function To_Vector (Item : in Kernel_Info_Array) return 
Kernel_Info_Arrays.Vector
    is begin
       return Result : Kernel_Info_Arrays.Vector do
@@ -219,70 +192,12 @@ package body WisiToken.Parse.LR is
       end return;
    end To_Vector;
 
-   function First (State : in Parse_State) return Action_List_Iterator
-   is begin
-      return Iter : Action_List_Iterator := (Node => State.Action_List, Item 
=> null) do
-         loop
-            exit when Iter.Node = null;
-            Iter.Item := Iter.Node.Action;
-            exit when Iter.Item /= null;
-            Iter.Node := Iter.Node.Next;
-         end loop;
-      end return;
-   end First;
-
-   function Is_Done (Iter : in Action_List_Iterator) return Boolean
-   is begin
-      return Iter.Node = null;
-   end Is_Done;
-
-   procedure Next (Iter : in out Action_List_Iterator)
-   is begin
-      if Iter.Node = null then
-         return;
-      end if;
-
-      if Iter.Item.Next = null then
-         loop
-            Iter.Node := Iter.Node.Next;
-            exit when Iter.Node = null;
-            Iter.Item := Iter.Node.Action;
-            exit when Iter.Item /= null;
-         end loop;
-      else
-         Iter.Item := Iter.Item.Next; -- a conflict
-      end if;
-   end Next;
-
-   function Symbol (Iter : in Action_List_Iterator) return Token_ID
-   is begin
-      return Iter.Node.Symbol;
-   end Symbol;
-
-   function Action (Iter : in Action_List_Iterator) return Parse_Action_Rec
-   is begin
-      return Iter.Item.Item;
-   end Action;
-
    procedure Add_Action
-     (State       : in out LR.Parse_State;
-      Symbol      : in     Token_ID;
-      State_Index : in     WisiToken.State_Index)
-   is
-      Action   : constant Parse_Action_Rec := (Shift, State_Index);
-      New_Node : constant Action_Node_Ptr  := new Action_Node'(Symbol, new 
Parse_Action_Node'(Action, null), null);
-      Node     : Action_Node_Ptr;
-   begin
-      if State.Action_List = null then
-         State.Action_List := New_Node;
-      else
-         Node := State.Action_List;
-         loop
-            exit when Node.Next = null;
-            Node := Node.Next;
-         end loop;
-         Node.Next := New_Node;
-      end if;
+     (State        : in out LR.Parse_State;
+      Symbol       : in     Token_ID;
+      State_Index  : in     WisiToken.State_Index)
+   is begin
+      Add (State.Action_List, Symbol, (Shift, State_Index));
    end Add_Action;
 
    procedure Add_Action
@@ -294,29 +209,13 @@ package body WisiToken.Parse.LR is
       Semantic_Action : in     WisiToken.Syntax_Trees.Semantic_Action;
       Semantic_Check  : in     Semantic_Checks.Semantic_Check)
    is
-      Action   : Parse_Action_Rec;
-      New_Node : Action_Node_Ptr;
-      Node     : Action_Node_Ptr;
+      Action : constant Parse_Action_Rec :=
+        (case Verb is
+         when Reduce    => (Reduce, Production, Semantic_Action, 
Semantic_Check, RHS_Token_Count),
+         when Accept_It => (Accept_It, Production, Semantic_Action, 
Semantic_Check, RHS_Token_Count),
+         when others    => raise SAL.Programmer_Error);
    begin
-      case Verb is
-      when Reduce =>
-         Action := (Reduce, Production, Semantic_Action, Semantic_Check, 
RHS_Token_Count);
-      when Accept_It =>
-         Action := (Accept_It, Production, Semantic_Action, Semantic_Check, 
RHS_Token_Count);
-      when others =>
-         null;
-      end case;
-      New_Node := new Action_Node'(Symbol, new Parse_Action_Node'(Action, 
null), null);
-      if State.Action_List = null then
-         State.Action_List := New_Node;
-      else
-         Node := State.Action_List;
-         loop
-            exit when Node.Next = null;
-            Node := Node.Next;
-         end loop;
-         Node.Next := New_Node;
-      end if;
+      Add (State.Action_List, Symbol, Action);
    end Add_Action;
 
    procedure Add_Action
@@ -327,14 +226,13 @@ package body WisiToken.Parse.LR is
       Semantic_Action : in     WisiToken.Syntax_Trees.Semantic_Action;
       Semantic_Check  : in     WisiToken.Semantic_Checks.Semantic_Check)
    is begin
-      --  We assume Duplicate_Reduce is True for this state; no
-      --  conflicts, all the same action.
+      --  We assume WisiToken.BNF.Output_Ada_Common.Duplicate_Reduce is True
+      --  for this state; no conflicts, all the same action.
       for Symbol of Symbols loop
          Add_Action
            (State, Symbol, Reduce, Production, RHS_Token_Count,
             Semantic_Action, Semantic_Check);
       end loop;
-      Add_Error (State);
    end Add_Action;
 
    procedure Add_Conflict
@@ -347,7 +245,10 @@ package body WisiToken.Parse.LR is
    is
       Conflict : constant Parse_Action_Rec :=
         (Reduce, Reduce_Production, Semantic_Action, Semantic_Check, 
RHS_Token_Count);
-      Node : Parse_Action_Node_Ptr := Find (Symbol, State.Action_List).Action;
+
+      Ref : constant Action_Arrays.Find_Reference_Constant_Type := 
State.Action_List.Find_Constant (Symbol);
+
+      Node : Parse_Action_Node_Ptr := Ref.Actions;
    begin
       loop
          exit when Node.Next = null;
@@ -356,50 +257,12 @@ package body WisiToken.Parse.LR is
       Node.Next := new Parse_Action_Node'(Conflict, null);
    end Add_Conflict;
 
-   procedure Add_Error (State  : in out LR.Parse_State)
-   is
-      Action : constant Parse_Action_Rec := (Verb => Error);
-      Node   : Action_Node_Ptr           := State.Action_List;
-   begin
-      if Node = null then
-         raise SAL.Programmer_Error with "adding an error action to a parse 
table state before other actions.";
-      end if;
-      loop
-         exit when Node.Next = null;
-         Node := Node.Next;
-      end loop;
-      Node.Next := new Action_Node'(Invalid_Token_ID, new 
Parse_Action_Node'(Action, null), null);
-   end Add_Error;
-
    procedure Add_Goto
      (State      : in out LR.Parse_State;
       Symbol     : in     Token_ID;
       To_State   : in     State_Index)
-   is
-      List     : Goto_Node_Ptr renames State.Goto_List;
-      New_Item : constant Goto_Node_Ptr := new Goto_Node'(Symbol, To_State, 
null);
-      I        : Goto_Node_Ptr := List;
-   begin
-      if I = null then
-         List := New_Item;
-      else
-         if List.Symbol > Symbol then
-            New_Item.Next := List;
-            List          := New_Item;
-         else
-            if List.Next = null then
-               List.Next := New_Item;
-            else
-               I := List;
-               loop
-                  exit when I.Next = null or List.Symbol > Symbol;
-                  I := I.Next;
-               end loop;
-               New_Item.Next := I.Next;
-               I.Next        := New_Item;
-            end if;
-         end if;
-      end if;
+   is begin
+      State.Goto_List.Insert ((Symbol, To_State));
    end Add_Goto;
 
    function Goto_For
@@ -408,103 +271,60 @@ package body WisiToken.Parse.LR is
       ID    : in Token_ID)
      return Unknown_State_Index
    is
-      Goto_Node : constant Goto_Node_Ptr := Goto_For (Table, State, ID);
+      Ref : constant Goto_Arrays.Find_Reference_Constant_Type := Table.States 
(State).Goto_List.Find_Constant (ID);
    begin
-      if Goto_Node = null then
+      if Ref.Element = null then
          --  We can only get here during error recovery.
          return Unknown_State;
       else
-         return Goto_Node.State;
+         return Ref.State;
       end if;
    end Goto_For;
 
-   function Goto_For
-     (Table : in Parse_Table;
-      State : in State_Index;
-      ID    : in Token_ID)
-     return Goto_Node_Ptr
-   is
-      Goto_Node : Goto_Node_Ptr := Table.States (State).Goto_List;
-   begin
-      while Goto_Node /= null and then Goto_Node.Symbol /= ID loop
-         Goto_Node := Goto_Node.Next;
-      end loop;
-
-      return Goto_Node;
-   end Goto_For;
-
    function Action_For
      (Table : in Parse_Table;
       State : in State_Index;
       ID    : in Token_ID)
      return Parse_Action_Node_Ptr
    is
-      Action_Node : Action_Node_Ptr := Table.States (State).Action_List;
+      Ref : constant Action_Arrays.Find_Reference_Constant_Type := 
Table.States (State).Action_List.Find_Constant (ID);
    begin
-      if Action_Node = null then
-         raise SAL.Programmer_Error with "no actions for state" & 
Unknown_State_Index'Image (State);
+      if Ref.Element = null then
+         return Table.Error_Action;
       end if;
 
-      while Action_Node.Next /= null and Action_Node.Symbol /= ID loop
-         Action_Node := Action_Node.Next;
-      end loop;
-
-      return Action_Node.Action;
+      return Ref.Actions;
    end Action_For;
 
    function Expecting (Table : in Parse_Table; State : in State_Index) return 
Token_ID_Set
    is
-      Result : Token_ID_Set    := (Table.First_Terminal .. Table.Last_Terminal 
=> False);
-      Action : Action_Node_Ptr := Table.States (State).Action_List;
+      Result : Token_ID_Set := (Table.First_Terminal .. Table.Last_Terminal => 
False);
    begin
-      loop
-         --  Last action is error; don't include it.
-         exit when Action.Next = null;
-
+      for Action of Table.States (State).Action_List loop
          Result (Action.Symbol) := True;
-         Action := Action.Next;
       end loop;
       return Result;
    end Expecting;
 
    procedure Free_Table (Table : in out Parse_Table_Ptr)
    is
-
       procedure Free is new Ada.Unchecked_Deallocation (Parse_Table, 
Parse_Table_Ptr);
-      Action            : Action_Node_Ptr;
-      Temp_Action       : Action_Node_Ptr;
       Parse_Action      : Parse_Action_Node_Ptr;
       Temp_Parse_Action : Parse_Action_Node_Ptr;
-      Got               : Goto_Node_Ptr;
-      Temp_Got          : Goto_Node_Ptr;
    begin
       if Table = null then
          return;
       end if;
 
       for State of Table.States loop
-         Action := State.Action_List;
-         loop
-            exit when Action = null;
-            Parse_Action := Action.Action;
+         for Action of State.Action_List loop
+            Parse_Action := Action.Actions;
             loop
                exit when Parse_Action = null;
                Temp_Parse_Action := Parse_Action;
                Parse_Action := Parse_Action.Next;
                Free (Temp_Parse_Action);
             end loop;
-
-            Temp_Action := Action;
-            Action := Action.Next;
-            Free (Temp_Action);
-         end loop;
-
-         Got := State.Goto_List;
-         loop
-            exit when Got = null;
-            Temp_Got := Got;
-            Got := Got.Next;
-            Free (Temp_Got);
          end loop;
       end loop;
 
@@ -650,17 +470,18 @@ package body WisiToken.Parse.LR is
 
          for State of Table.States loop
             declare
-               Node_I       : Action_Node_Ptr := new Action_Node;
-               Actions_Done : Boolean         := False;
+               Actions_Done : Boolean := False;
             begin
-               State.Action_List := Node_I;
+               State.Action_List.Set_Capacity (Next_Count_Type);
+
                loop
                   declare
+                     Node_I      : Action_Node;
                      Node_J      : Parse_Action_Node_Ptr := new 
Parse_Action_Node;
-                     Action_Done : Boolean := False;
+                     Action_Done : Boolean               := False;
                      Verb        : Parse_Action_Verbs;
                   begin
-                     Node_I.Action := Node_J;
+                     Node_I.Actions := Node_J;
                      loop
                         Verb := Next_Parse_Action_Verbs;
                         Node_J.Item :=
@@ -692,14 +513,16 @@ package body WisiToken.Parse.LR is
                            Node_J.Item.Token_Count := Next_Count_Type;
 
                         when Error =>
-                           Actions_Done := True;
+                           raise SAL.Programmer_Error;
                         end case;
 
                         if Check_Semicolon then
                            Action_Done := True;
 
-                           if not Actions_Done then
-                              Node_I.Symbol := Next_Token_ID;
+                           Node_I.Symbol := Next_Token_ID;
+
+                           if Check_Semicolon then
+                              Actions_Done := True;
                            end if;
                         end if;
 
@@ -710,11 +533,10 @@ package body WisiToken.Parse.LR is
                      end loop;
 
                      Check_New_Line;
+                     State.Action_List.Insert (Node_I);
                   end;
 
                   exit when Actions_Done;
-                  Node_I.Next := new Action_Node;
-                  Node_I      := Node_I.Next;
                end loop;
             end;
 
@@ -722,36 +544,34 @@ package body WisiToken.Parse.LR is
                --  No Gotos
                null;
             else
+               State.Goto_List.Set_Capacity (Next_Count_Type);
                declare
-                  Node_I : Goto_Node_Ptr := new Goto_Node;
+                  Node : Goto_Node;
                begin
-                  State.Goto_List  := Node_I;
                   loop
-                     Node_I.Symbol := Next_Token_ID;
-                     Node_I.State  := Next_State_Index;
+                     Node.Symbol := Next_Token_ID;
+                     Node.State  := Next_State_Index;
+                     State.Goto_List.Insert (Node);
                      exit when Check_Semicolon;
-                     Node_I.Next   := new Goto_Node;
-                     Node_I        := Node_I.Next;
                   end loop;
                end;
             end if;
             Check_New_Line;
 
             declare
-               First : constant Integer := Next_Integer;
-               Last  : constant Integer := Next_Integer;
+               First : constant Count_Type := Next_Count_Type;
+               Last  : constant Integer    := Next_Integer;
             begin
                if Last = -1 then
                   --  State.Kernel not set for state 0
                   null;
                else
-                  State.Kernel.Set_First (Count_Type (First));
-                  State.Kernel.Set_Last (Count_Type (Last));
+                  State.Kernel.Set_First_Last (First, Count_Type (Last));
 
                   for I in State.Kernel.First_Index .. State.Kernel.Last_Index 
loop
                      State.Kernel (I).LHS := Next_Token_ID;
                      State.Kernel (I).Before_Dot := Next_Token_ID;
-                     State.Kernel (I).Length_After_Dot := Count_Type 
(Next_Integer);
+                     State.Kernel (I).Length_After_Dot := Next_Count_Type;
                   end loop;
                end if;
             end;
@@ -761,8 +581,12 @@ package body WisiToken.Parse.LR is
                --  No minimal action
                null;
             else
-               State.Minimal_Complete_Actions.Set_First (Count_Type 
(Next_Integer));
-               State.Minimal_Complete_Actions.Set_Last (Count_Type 
(Next_Integer));
+               declare
+                  First : constant Count_Type := Next_Count_Type;
+                  Last  : constant Count_Type := Next_Count_Type;
+               begin
+                  State.Minimal_Complete_Actions.Set_First_Last (First, Last);
+               end;
                for I in State.Minimal_Complete_Actions.First_Index .. 
State.Minimal_Complete_Actions.Last_Index loop
                   declare
                      Verb         : constant Minimal_Verbs := 
Next_Parse_Action_Verbs;
@@ -788,6 +612,9 @@ package body WisiToken.Parse.LR is
 
             exit when Check_EOI;
          end loop;
+
+         Table.Error_Action := new Parse_Action_Node'((Verb => Error), null);
+
          return Table;
       end;
    exception
@@ -830,40 +657,79 @@ package body WisiToken.Parse.LR is
         Left.Ins_Token_Index = Right.Ins_Token_Index;
    end Equal;
 
-   function None_Since_FF (Ops : in Config_Op_Arrays.Vector; Op : in 
Config_Op_Label) return Boolean
-   is begin
-      for O of reverse Ops loop
-         exit when O.Op = Fast_Forward;
-         if O.Op = Op then
+   function None (Ops : aliased in Config_Op_Arrays.Vector; Op : in 
Config_Op_Label) return Boolean
+   is
+      use Config_Op_Arrays, Config_Op_Array_Refs;
+   begin
+      for I in First_Index (Ops) .. Last_Index (Ops) loop
+         if Constant_Ref (Ops, I).Op /= Op then
             return False;
          end if;
       end loop;
       return True;
+   end None;
+
+   function None_Since_FF (Ops : aliased in Config_Op_Arrays.Vector; Op : in 
Config_Op_Label) return Boolean
+   is
+      use Config_Op_Arrays, Config_Op_Array_Refs;
+   begin
+      for I in reverse First_Index (Ops) .. Last_Index (Ops) loop
+         declare
+            O : Config_Op renames Constant_Ref (Ops, I);
+         begin
+            exit when O.Op = Fast_Forward;
+            if O.Op = Op then
+               return False;
+            end if;
+         end;
+      end loop;
+      return True;
    end None_Since_FF;
 
-   function Only_Since_FF (Ops : in Config_Op_Arrays.Vector; Op : in 
Config_Op_Label) return Boolean
+   function Only_Since_FF (Ops : aliased in Config_Op_Arrays.Vector; Op : in 
Config_Op_Label) return Boolean
    is
+      use Config_Op_Arrays, Config_Op_Array_Refs;
       use all type Ada.Containers.Count_Type;
    begin
-      if Ops.Length = 0 or else Ops (Ops.Last_Index).Op /= Op then
+      if Length (Ops) = 0 or else Constant_Ref (Ops, Last_Index (Ops)).Op /= 
Op then
          return False;
       else
-         for O of reverse Ops loop
-            exit when O.Op = Fast_Forward;
-            if O.Op /= Op then
-               return False;
-            end if;
+         for I in reverse First_Index (Ops) .. Last_Index (Ops) loop
+            declare
+               O : Config_Op renames Constant_Ref (Ops, I);
+            begin
+               exit when O.Op = Fast_Forward;
+               if O.Op /= Op then
+                  return False;
+               end if;
+            end;
          end loop;
          return True;
       end if;
    end Only_Since_FF;
 
+   function Any (Ops : aliased in Config_Op_Arrays.Vector; Op : in 
Config_Op_Label) return Boolean
+   is
+      use Config_Op_Arrays, Config_Op_Array_Refs;
+   begin
+      for I in First_Index (Ops) .. Last_Index (Ops) loop
+         declare
+            O : Config_Op renames Constant_Ref (Ops, I);
+         begin
+            if O.Op = Op then
+               return True;
+            end if;
+         end;
+      end loop;
+      return False;
+   end Any;
+
    function Valid_Tree_Indices (Stack : in Recover_Stacks.Stack; Depth : in 
SAL.Base_Peek_Type) return Boolean
    is
       use all type WisiToken.Syntax_Trees.Node_Index;
    begin
       for I in 1 .. Depth loop
-         if Stack (I).Tree_Index = Syntax_Trees.Invalid_Node_Index then
+         if Stack.Peek (I).Tree_Index = Syntax_Trees.Invalid_Node_Index then
             return False;
          end if;
       end loop;
diff --git a/wisitoken-parse-lr.ads b/wisitoken-parse-lr.ads
index 91b55c8..76a02d5 100644
--- a/wisitoken-parse-lr.ads
+++ b/wisitoken-parse-lr.ads
@@ -37,15 +37,19 @@ pragma License (Modified_GPL);
 with Ada.Containers.Indefinite_Doubly_Linked_Lists;
 with Ada.Unchecked_Deallocation;
 with SAL.Gen_Array_Image;
+with SAL.Gen_Bounded_Definite_Stacks.Gen_Image_Aux;
 with SAL.Gen_Bounded_Definite_Vectors.Gen_Image_Aux;
-with SAL.Gen_Bounded_Definite_Vectors.Gen_Sorted;
+with SAL.Gen_Bounded_Definite_Vectors.Gen_Refs;
+with SAL.Gen_Bounded_Definite_Vectors_Sorted.Gen_Image_Aux;
+with SAL.Gen_Bounded_Definite_Vectors_Sorted.Gen_Refs;
 with SAL.Gen_Unbounded_Definite_Min_Heaps_Fibonacci;
 with SAL.Gen_Unbounded_Definite_Queues.Gen_Image_Aux;
-with SAL.Gen_Unbounded_Definite_Stacks.Gen_Image_Aux;
+with SAL.Gen_Unbounded_Definite_Vectors_Sorted;
 with System.Multiprocessors;
 with WisiToken.Semantic_Checks;
 with WisiToken.Syntax_Trees;
 package WisiToken.Parse.LR is
+   use all type SAL.Base_Peek_Type;
 
    type All_Parse_Action_Verbs is (Pause, Shift, Reduce, Accept_It, Error);
    subtype Parse_Action_Verbs is All_Parse_Action_Verbs range Shift .. Error;
@@ -54,7 +58,7 @@ package WisiToken.Parse.LR is
    --  to re-sync on the same input terminal.
 
    subtype Token_ID_Array_1_3 is Token_ID_Array (1 .. 3);
-   --  For Use_Minimal_Complete_Actions in McDenzie_Recover.
+   --  For Language_Matching_Begin_Tokens.
 
    type Parse_Action_Rec (Verb : Parse_Action_Verbs := Shift) is record
       case Verb is
@@ -99,33 +103,33 @@ package WisiToken.Parse.LR is
    function Is_In (Item : in Parse_Action_Rec; List : in 
Parse_Action_Node_Ptr) return Boolean;
    --  True if Item is Equal to any element of List.
 
-   type Action_Node;
-   type Action_Node_Ptr is access Action_Node;
-
    type Action_Node is record
-      Symbol : Token_ID := Invalid_Token_ID; -- ignored if Action is Error
-      Action : Parse_Action_Node_Ptr;
-      Next   : Action_Node_Ptr;
+      Symbol  : Token_ID := Invalid_Token_ID; -- ignored if Action is Error
+      Actions : Parse_Action_Node_Ptr;
    end record;
-   procedure Free is new Ada.Unchecked_Deallocation (Action_Node, 
Action_Node_Ptr);
 
-   function Find
-     (Symbol      : in Token_ID;
-      Action_List : in Action_Node_Ptr)
-     return Action_Node_Ptr;
+   function To_Key (Item : in Action_Node) return Token_ID is (Item.Symbol);
+
+   function Compare (Left, Right : in Token_ID) return SAL.Compare_Result;
+
+   package Action_Arrays is new SAL.Gen_Unbounded_Definite_Vectors_Sorted
+     (Action_Node, Token_ID, To_Key, Compare);
 
    procedure Add
-     (List   : in out Action_Node_Ptr;
+     (List   : in out Action_Arrays.Vector;
       Symbol : in     Token_ID;
       Action : in     Parse_Action_Rec);
    --  Add action to List, sorted on ascending Symbol.
 
-   type Goto_Node is private;
-   type Goto_Node_Ptr is access Goto_Node;
+   type Goto_Node is record
+      Symbol : Token_ID;
+      State  : State_Index;
+   end record;
+
+   function To_Key (Item : in Goto_Node) return Token_ID is (Item.Symbol);
 
-   function Symbol (List : in Goto_Node_Ptr) return Token_ID;
-   function State (List : in Goto_Node_Ptr) return State_Index;
-   function Next (List : in Goto_Node_Ptr) return Goto_Node_Ptr;
+   package Goto_Arrays is  new SAL.Gen_Unbounded_Definite_Vectors_Sorted
+     (Goto_Node, Token_ID, To_Key, Compare);
 
    type Kernel_Info is record
       LHS              : Token_ID                  := Token_ID'First;
@@ -172,8 +176,8 @@ package WisiToken.Parse.LR is
    function Strict_Image is new Minimal_Action_Arrays.Gen_Image (Strict_Image);
 
    type Parse_State is record
-      Action_List : Action_Node_Ptr;
-      Goto_List   : Goto_Node_Ptr;
+      Action_List : Action_Arrays.Vector;
+      Goto_List   : Goto_Arrays.Vector;
 
       --  The following are used in error recovery.
       Kernel : Kernel_Info_Arrays.Vector;
@@ -188,17 +192,6 @@ package WisiToken.Parse.LR is
 
    type Parse_State_Array is array (State_Index range <>) of Parse_State;
 
-   type Action_List_Iterator is tagged private;
-   --  Iterates over all shift/reduce actions for a state, including
-   --  conflicts.
-
-   function First (State : in Parse_State) return Action_List_Iterator;
-   function Is_Done (Iter : in Action_List_Iterator) return Boolean;
-   procedure Next (Iter : in out Action_List_Iterator);
-
-   function Symbol (Iter : in Action_List_Iterator) return Token_ID;
-   function Action (Iter : in Action_List_Iterator) return Parse_Action_Rec;
-
    procedure Add_Action
      (State       : in out Parse_State;
       Symbol      : in     Token_ID;
@@ -234,9 +227,6 @@ package WisiToken.Parse.LR is
       Semantic_Check    : in     WisiToken.Semantic_Checks.Semantic_Check);
    --  Add a Reduce conflict to State.
 
-   procedure Add_Error (State  : in out Parse_State);
-   --  Add an Error action to State, at tail of action list.
-
    procedure Add_Goto
      (State    : in out Parse_State;
       Symbol   : in     Token_ID;
@@ -306,6 +296,7 @@ package WisiToken.Parse.LR is
      is tagged
    record
       States         : Parse_State_Array (State_First .. State_Last);
+      Error_Action   : Parse_Action_Node_Ptr;
       McKenzie_Param : McKenzie_Param_Type (First_Terminal, Last_Terminal, 
First_Nonterminal, Last_Nonterminal);
    end record;
 
@@ -314,11 +305,6 @@ package WisiToken.Parse.LR is
       State : in State_Index;
       ID    : in Token_ID)
      return Unknown_State_Index;
-   function Goto_For
-     (Table : in Parse_Table;
-      State : in State_Index;
-      ID    : in Token_ID)
-     return Goto_Node_Ptr;
    --  Return next state after reducing stack by nonterminal ID;
    --  Unknown_State if none (only possible during error recovery).
    --  Second form allows retrieving Production.
@@ -327,11 +313,19 @@ package WisiToken.Parse.LR is
      (Table : in Parse_Table;
       State : in State_Index;
       ID    : in Token_ID)
-     return Parse_Action_Node_Ptr;
+     return Parse_Action_Node_Ptr
+   with Post => Action_For'Result /= null;
    --  Return the action for State, terminal ID.
 
    function Expecting (Table : in Parse_Table; State : in State_Index) return 
Token_ID_Set;
 
+   function McKenzie_Defaulted (Table : in Parse_Table) return Boolean is
+     --  We can't use Table.McKenzie_Param = Default_McKenzie_Param here,
+     --  because the discriminants are different.
+     (Table.McKenzie_Param.Check_Limit = Default_McKenzie_Param.Check_Limit and
+        Table.McKenzie_Param.Check_Delta_Limit = 
Default_McKenzie_Param.Check_Delta_Limit and
+        Table.McKenzie_Param.Enqueue_Limit = 
Default_McKenzie_Param.Enqueue_Limit);
+
    type Parse_Table_Ptr is access Parse_Table;
    procedure Free_Table (Table : in out Parse_Table_Ptr);
 
@@ -372,8 +366,8 @@ package WisiToken.Parse.LR is
       Tokens     : in Fast_Token_ID_Arrays.Vector;
       Descriptor : in WisiToken.Descriptor)
      return String
-     is (SAL.Peek_Type'Image (Index) & ":" & SAL.Peek_Type'Image 
(Tokens.Last_Index) & ":" &
-           Image (Tokens (Index), Descriptor));
+     is (SAL.Peek_Type'Image (Index) & ":" & SAL.Peek_Type'Image 
(Fast_Token_ID_Arrays.Last_Index (Tokens)) & ":" &
+           Image (Fast_Token_ID_Arrays.Element (Tokens, Index), Descriptor));
 
    type Config_Op_Label is (Fast_Forward, Undo_Reduce, Push_Back, Insert, 
Delete);
    subtype Insert_Delete_Op_Label is Config_Op_Label range Insert .. Delete;
@@ -470,6 +464,8 @@ package WisiToken.Parse.LR is
    --  config does hit that limit, it is abandoned; some other config is
    --  likely to be cheaper.
 
+   package Config_Op_Array_Refs is new Config_Op_Arrays.Gen_Refs;
+
    function Config_Op_Image (Item : in Config_Op; Descriptor : in 
WisiToken.Descriptor) return String
      is ("(" & Config_Op_Label'Image (Item.Op) & ", " &
            (case Item.Op is
@@ -495,28 +491,26 @@ package WisiToken.Parse.LR is
    function Image (Item : in Config_Op_Arrays.Vector; Descriptor : in 
WisiToken.Descriptor) return String
      renames Config_Op_Array_Image;
 
-   function None (Ops : in Config_Op_Arrays.Vector; Op : in Config_Op_Label) 
return Boolean
-     is (for all O of Ops => O.Op /= Op);
+   function None (Ops : aliased in Config_Op_Arrays.Vector; Op : in 
Config_Op_Label) return Boolean;
    --  True if Ops contains no Op.
 
-   function None_Since_FF (Ops : in Config_Op_Arrays.Vector; Op : in 
Config_Op_Label) return Boolean;
+   function None_Since_FF (Ops : aliased in Config_Op_Arrays.Vector; Op : in 
Config_Op_Label) return Boolean;
    --  True if Ops contains no Op after the last Fast_Forward (or ops.first, if
    --  no Fast_Forward).
 
-   function Only_Since_FF (Ops : in Config_Op_Arrays.Vector; Op : in 
Config_Op_Label) return Boolean;
+   function Only_Since_FF (Ops : aliased in Config_Op_Arrays.Vector; Op : in 
Config_Op_Label) return Boolean;
    --  True if Ops contains only Op (at least one) after the last Fast_Forward 
(or ops.first, if
    --  no Fast_Forward).
 
-   function Any (Ops : in Config_Op_Arrays.Vector; Op : in Config_Op_Label) 
return Boolean
-     is (for some O of Ops => O.Op = Op);
+   function Any (Ops : aliased in Config_Op_Arrays.Vector; Op : in 
Config_Op_Label) return Boolean;
    --  True if Ops contains at least one Op.
 
-   package Insert_Delete_Arrays is new SAL.Gen_Bounded_Definite_Vectors
-     (Positive_Index_Type, Insert_Delete_Op, Capacity => 80);
+   package Sorted_Insert_Delete_Arrays is new 
SAL.Gen_Bounded_Definite_Vectors_Sorted
+     (Insert_Delete_Op, Compare, Capacity => 80);
 
-   package Sorted_Insert_Delete_Arrays is new Insert_Delete_Arrays.Gen_Sorted 
(Compare);
+   package Insert_Delete_Array_Refs is new 
Sorted_Insert_Delete_Arrays.Gen_Refs;
 
-   function Image is new Insert_Delete_Arrays.Gen_Image_Aux 
(WisiToken.Descriptor, Image);
+   function Image is new Sorted_Insert_Delete_Arrays.Gen_Image_Aux 
(WisiToken.Descriptor, Image);
 
    type Recover_Stack_Item is record
       State : Unknown_State_Index;
@@ -530,7 +524,7 @@ package WisiToken.Parse.LR is
       --  during recover.
    end record;
 
-   package Recover_Stacks is new SAL.Gen_Unbounded_Definite_Stacks 
(Recover_Stack_Item);
+   package Recover_Stacks is new SAL.Gen_Bounded_Definite_Stacks 
(Recover_Stack_Item);
 
    function Image (Item : in Recover_Stack_Item; Descriptor : in 
WisiToken.Descriptor) return String
      is ((if Item.State = Unknown_State then " " else Trimmed_Image 
(Item.State)) & " : " &
@@ -546,7 +540,8 @@ package WisiToken.Parse.LR is
      return String
      renames Recover_Stack_Image;
 
-   function Valid_Tree_Indices (Stack : in Recover_Stacks.Stack; Depth : in 
SAL.Base_Peek_Type) return Boolean;
+   function Valid_Tree_Indices (Stack : in Recover_Stacks.Stack; Depth : in 
SAL.Base_Peek_Type) return Boolean with
+     Pre => Stack.Depth >= Depth;
    --  Return True if Stack top Depth items have valid Tree_Indices,
    --  which is true if they were copied from the parser stack, and not
    --  pushed by recover.
@@ -559,9 +554,15 @@ package WisiToken.Parse.LR is
    type Minimal_Complete_State is (None, Active, Done);
 
    type Configuration is record
-      Stack : Recover_Stacks.Stack;
+      Stack : Recover_Stacks.Stack (70);
       --  Initially built from the parser stack, then the stack after the
       --  Ops below have been performed.
+      --
+      --  Required size is determined by source code structure nesting;
+      --  larger size slows down recover due to memory cache thrashing and
+      --  allocation.
+      --
+      --  Emacs Ada mode wisi.adb needs > 50
 
       Resume_Token_Goal : WisiToken.Token_Index := WisiToken.Token_Index'Last;
       --  A successful solution shifts this token. Per-config because it
@@ -575,7 +576,7 @@ package WisiToken.Parse.LR is
       String_Quote_Checked : Line_Number_Type := Invalid_Line_Number;
       --  Max line checked for missing string quote.
 
-      Insert_Delete : Sorted_Insert_Delete_Arrays.Vector;
+      Insert_Delete : aliased Sorted_Insert_Delete_Arrays.Vector;
       --  Edits to the input stream that are not yet parsed; contains only
       --  Insert and Delete ops, in token_index order.
 
@@ -599,7 +600,7 @@ package WisiToken.Parse.LR is
       --  in explore when adding an op, or in language_fixes when adding a
       --  fix).
 
-      Ops : Config_Op_Arrays.Vector;
+      Ops : aliased Config_Op_Arrays.Vector;
       --  Record of operations applied to this Config, in application order.
       --  Insert and Delete ops that are not yet parsed are reflected in
       --  Insert_Delete, in token_index order.
@@ -612,8 +613,6 @@ package WisiToken.Parse.LR is
       Minimal_Complete_State : LR.Minimal_Complete_State := None;
       Matching_Begin_Done    : Boolean                   := False;
    end record;
-   type Configuration_Access is access all Configuration;
-   for Configuration_Access'Storage_Size use 0;
 
    function Key (A : in Configuration) return Integer is (A.Cost);
 
@@ -621,7 +620,6 @@ package WisiToken.Parse.LR is
 
    package Config_Heaps is new SAL.Gen_Unbounded_Definite_Min_Heaps_Fibonacci
      (Element_Type   => Configuration,
-      Element_Access => Configuration_Access,
       Key_Type       => Integer,
       Key            => Key,
       Set_Key        => Set_Key);
@@ -666,18 +664,4 @@ package WisiToken.Parse.LR is
 
    package Parse_Error_Lists is new 
Ada.Containers.Indefinite_Doubly_Linked_Lists (Parse_Error);
 
-private
-
-   type Goto_Node is record
-      Symbol     : Token_ID;
-      State      : State_Index;
-      Next       : Goto_Node_Ptr;
-   end record;
-   procedure Free is new Ada.Unchecked_Deallocation (Goto_Node, Goto_Node_Ptr);
-
-   type Action_List_Iterator is tagged record
-      Node : Action_Node_Ptr;
-      Item : Parse_Action_Node_Ptr;
-   end record;
-
 end WisiToken.Parse.LR;
diff --git a/wisitoken-parse-packrat-generated.adb 
b/wisitoken-parse-packrat-generated.adb
index 0c909e5..f5f4b16 100644
--- a/wisitoken-parse-packrat-generated.adb
+++ b/wisitoken-parse-packrat-generated.adb
@@ -35,13 +35,11 @@ package body WisiToken.Parse.Packrat.Generated is
       Parser.Base_Tree.Clear;
       Parser.Tree.Initialize (Parser.Base_Tree'Unchecked_Access, Flush => 
True);
       Parser.Lex_All;
-      Parser.Derivs.Set_First (Descriptor.First_Nonterminal);
-      Parser.Derivs.Set_Last (Descriptor.Last_Nonterminal);
+      Parser.Derivs.Set_First_Last (Descriptor.First_Nonterminal, 
Descriptor.Last_Nonterminal);
 
       for Nonterm in Descriptor.First_Nonterminal .. 
Parser.Trace.Descriptor.Last_Nonterminal loop
          Parser.Derivs (Nonterm).Clear;
-         Parser.Derivs (Nonterm).Set_First (Parser.Terminals.First_Index);
-         Parser.Derivs (Nonterm).Set_Last (Parser.Terminals.Last_Index);
+         Parser.Derivs (Nonterm).Set_First_Last (Parser.Terminals.First_Index, 
Parser.Terminals.Last_Index);
       end loop;
 
       for Token_Index in Parser.Terminals.First_Index .. 
Parser.Terminals.Last_Index loop
diff --git a/wisitoken-parse-packrat-procedural.adb 
b/wisitoken-parse-packrat-procedural.adb
index 1f00e48..97f71ff 100644
--- a/wisitoken-parse-packrat-procedural.adb
+++ b/wisitoken-parse-packrat-procedural.adb
@@ -224,15 +224,12 @@ package body WisiToken.Parse.Packrat.Procedural is
 
       for Nonterm in Descriptor.First_Nonterminal .. 
Parser.Trace.Descriptor.Last_Nonterminal loop
          Parser.Derivs (Nonterm).Clear;
-         Parser.Derivs (Nonterm).Set_First (Parser.Terminals.First_Index);
-
+         Parser.Derivs (Nonterm).Set_First_Last (Parser.Terminals.First_Index, 
Parser.Terminals.Last_Index + 1);
          --  There might be an empty nonterm after the last token
-         Parser.Derivs (Nonterm).Set_Last (Parser.Terminals.Last_Index + 1);
       end loop;
 
       for Token_Index in Parser.Terminals.First_Index .. 
Parser.Terminals.Last_Index loop
          Junk := Parser.Tree.Add_Terminal (Token_Index, Parser.Terminals);
-         --  FIXME: move this into Lex_All, delete Terminals, just use 
Syntax_Tree
       end loop;
 
       Result := Apply_Rule (Parser, Parser.Start_ID, 
Parser.Terminals.First_Index - 1);
diff --git a/wisitoken-parse.adb b/wisitoken-parse.adb
index 09280d9..ee7b1e8 100644
--- a/wisitoken-parse.adb
+++ b/wisitoken-parse.adb
@@ -38,12 +38,12 @@ package body WisiToken.Parse is
          if Token.Line /= Invalid_Line_Number then
             --  Some lexers don't support line numbers.
             if Parser.Lexer.First then
-               Parser.Line_Begin_Token.Set_Length (Ada.Containers.Count_Type 
(Token.Line));
+               Parser.Line_Begin_Token.Set_First_Last (Line_Number_Type'First, 
Token.Line);
                Parser.Line_Begin_Token (Token.Line) := 
Parser.Terminals.Last_Index +
                  (if Token.ID >= Parser.Trace.Descriptor.First_Terminal then 1 
else 0);
 
             elsif Token.ID = Parser.Trace.Descriptor.EOI_ID then
-               Parser.Line_Begin_Token.Set_Length (Ada.Containers.Count_Type 
(Token.Line + 1));
+               Parser.Line_Begin_Token.Set_First_Last (Line_Number_Type'First, 
Token.Line + 1);
                Parser.Line_Begin_Token (Token.Line + 1) := 
Parser.Terminals.Last_Index + 1;
             end if;
          end if;
diff --git a/wisitoken-syntax_trees-lr_utils.adb 
b/wisitoken-syntax_trees-lr_utils.adb
new file mode 100644
index 0000000..a456d82
--- /dev/null
+++ b/wisitoken-syntax_trees-lr_utils.adb
@@ -0,0 +1,220 @@
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2019 Stephen Leake All Rights Reserved.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+package body WisiToken.Syntax_Trees.LR_Utils is
+
+   procedure Raise_Programmer_Error
+     (Label      : in String;
+      Descriptor : in WisiToken.Descriptor;
+      Lexer      : in WisiToken.Lexer.Handle;
+      Tree       : in WisiToken.Syntax_Trees.Tree;
+      Terminals  : in WisiToken.Base_Token_Arrays.Vector;
+      Node       : in WisiToken.Syntax_Trees.Node_Index)
+   is
+      Terminal_Index : constant Base_Token_Index := Tree.Min_Terminal_Index 
(Node);
+   begin
+      raise SAL.Programmer_Error with Error_Message
+        (Lexer.File_Name,
+         (if Terminal_Index = Invalid_Token_Index then 1 else Terminals 
(Terminal_Index).Line), 0,
+         Label & Node'Image & ":" & Tree.Image (Node, Descriptor, 
Include_Children => True));
+   end Raise_Programmer_Error;
+
+   function Has_Element (Cursor : in LR_Utils.Cursor) return Boolean is 
(Cursor.Node /= Invalid_Node_Index);
+
+   function Node (Cursor : in LR_Utils.Cursor) return Node_Index is 
(Cursor.Node);
+
+   overriding function First (Iter : Iterator) return Cursor
+   is begin
+      return Result : Cursor do
+         Result.Node := Iter.Root;
+         loop
+            declare
+               Children : constant Valid_Node_Index_Array := 
Iter.Tree.Children (Result.Node);
+            begin
+               if Iter.Tree.ID (Children (1)) = Iter.List_ID then
+                  Result.Node := Children (1);
+               elsif Iter.Tree.ID (Children (1)) = Iter.Element_ID then
+                  Result.Node := Children (1);
+                  exit;
+               else
+                  Raise_Programmer_Error
+                    ("first_list_element", Iter.Descriptor.all, Iter.Lexer, 
Iter.Tree, Iter.Terminals.all, Result.Node);
+               end if;
+            end;
+         end loop;
+      end return;
+   end First;
+
+   overriding function Last  (Iter : Iterator) return Cursor
+   is
+      --  Tree is one of:
+      --
+      --  case a: single element list
+      --  element_list : root
+      --  | element: Last
+      --
+      --  case c: no next
+      --  element_list: root
+      --  | element_list
+      --  | | element:
+      --  | element: Last
+      Children : constant Valid_Node_Index_Array := Iter.Tree.Children 
(Iter.Root);
+   begin
+      return (Node => Children (Children'Last));
+   end Last;
+
+   overriding function Next (Iter : Iterator; Position : Cursor) return Cursor
+   is begin
+      if Position.Node = Invalid_Node_Index then
+         return Position;
+      else
+         return Result : Cursor do
+            declare
+               use all type SAL.Base_Peek_Type;
+               --  Tree is one of:
+               --
+               --  case a: first element, no next
+               --  rhs
+               --  | rhs_item_list
+               --  | | rhs_item: Element
+               --  | action
+               --
+               --  case b: first element, next
+               --  rhs_item_list
+               --  | rhs_item_list
+               --  | | rhs_item: Element
+               --  | rhs_item: next element
+               --
+               --  case c: non-first element, no next
+               --  rhs
+               --  | rhs_item_list : Grand_Parent
+               --  | | rhs_item_list
+               --  | | | rhs_item:
+               --  | | rhs_item: Element
+               --  | action : Aunt
+               --
+               --  case d: non-first element, next
+               --  rhs_item_list
+               --  | rhs_item_list : Grand_Parent
+               --  | | rhs_item_list
+               --  | | | rhs_item:
+               --  | | rhs_item: Element
+               --  | rhs_item: next element : Aunt
+
+               Grand_Parent    : constant Valid_Node_Index       := 
Iter.Tree.Parent (Position.Node, 2);
+               Aunts           : constant Valid_Node_Index_Array := 
Iter.Tree.Children (Grand_Parent);
+               Last_List_Child : SAL.Base_Peek_Type              := 
Aunts'First - 1;
+            begin
+               if Iter.Tree.ID (Grand_Parent) /= Iter.List_ID then
+                  --  No next
+                  Result.Node := Invalid_Node_Index;
+               else
+                  for I in Aunts'Range loop
+                     if Iter.Tree.ID (Aunts (I)) in Iter.List_ID | 
Iter.Element_ID then
+                        Last_List_Child := I;
+                     end if;
+                  end loop;
+
+                  if Last_List_Child = 1 then
+                     --  No next
+                     Result.Node := Invalid_Node_Index;
+                  else
+                     Result.Node := Aunts (Last_List_Child);
+                  end if;
+               end if;
+            end;
+         end return;
+      end if;
+   end Next;
+
+   overriding function Previous (Iter   : Iterator; Position : Cursor) return 
Cursor
+   is begin
+      if Position.Node = Invalid_Node_Index then
+         return Position;
+      else
+         return Result : Cursor do
+            --  Tree is one of:
+            --
+            --  case a: first element, no prev
+            --  ?
+            --  | rhs_item_list
+            --  | | rhs_item: Element
+            --
+            --  case b: second element
+            --  ?
+            --  | rhs_item_list
+            --  | | rhs_item: prev item
+            --  | rhs_item: Element
+            --
+            --  case c: nth element
+            --  ?
+            --  | rhs_item_list
+            --  | | rhs_item_list
+            --  | | | rhs_item:
+            --  | | rhs_item: prev element
+            --  | rhs_item: Element
+            declare
+               Parent : constant Valid_Node_Index := Iter.Tree.Parent 
(Position.Node);
+            begin
+               if Position.Node = Iter.Tree.Child (Parent, 1) then
+                  --  No prev
+                  Result.Node := Invalid_Node_Index;
+
+               else
+                  declare
+                     Prev_Children : constant Valid_Node_Index_Array := 
Iter.Tree.Children
+                       (Iter.Tree.Child (Parent, 1));
+                  begin
+                     Result.Node := Prev_Children (Prev_Children'Last);
+                  end;
+               end if;
+            end;
+         end return;
+      end if;
+   end Previous;
+
+   function Iterate
+     (Tree         : in WisiToken.Syntax_Trees.Tree;
+      Terminals    : in WisiToken.Base_Token_Array_Access;
+      Lexer        : in WisiToken.Lexer.Handle;
+      Descriptor   : in WisiToken.Descriptor_Access_Constant;
+      Root         : in Valid_Node_Index;
+      Element_ID   : in WisiToken.Token_ID;
+      Separator_ID : in WisiToken.Token_ID := WisiToken.Invalid_Token_ID)
+     return Iterator_Interfaces.Reversible_Iterator'Class
+   is begin
+      return Iterator'
+        (Iterator_Interfaces.Reversible_Iterator with
+         Tree, Terminals, Lexer, Descriptor, Root,
+         List_ID      => Tree.ID (Root),
+         Element_ID   => Element_ID,
+         Separator_ID => Separator_ID);
+   end Iterate;
+
+   function Count (Iter : Iterator) return Ada.Containers.Count_Type
+   is
+      use Ada.Containers;
+      Result : Count_Type := 0;
+   begin
+      for Item in Iter loop
+         Result := Result + 1;
+      end loop;
+      return Result;
+   end Count;
+
+end WisiToken.Syntax_Trees.LR_Utils;
diff --git a/wisitoken-syntax_trees-lr_utils.ads 
b/wisitoken-syntax_trees-lr_utils.ads
new file mode 100644
index 0000000..9033f49
--- /dev/null
+++ b/wisitoken-syntax_trees-lr_utils.ads
@@ -0,0 +1,88 @@
+--  Abstract :
+--
+--  Utilities for navigating syntax trees produced by an LR parser.
+--
+--  Copyright (C) 2019 Stephen Leake All Rights Reserved.
+--
+--  This library is free software;  you can redistribute it and/or modify it
+--  under terms of the  GNU General Public License  as published by the Free
+--  Software  Foundation;  either version 3,  or (at your  option) any later
+--  version. This library is distributed in the hope that it will be useful,
+--  but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
+--  TABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+--  As a special exception under Section 7 of GPL version 3, you are granted
+--  additional permissions described in the GCC Runtime Library Exception,
+--  version 3.1, as published by the Free Software Foundation.
+
+pragma License (Modified_GPL);
+
+with Ada.Iterator_Interfaces;
+package WisiToken.Syntax_Trees.LR_Utils is
+
+   procedure Raise_Programmer_Error
+     (Label      : in String;
+      Descriptor : in WisiToken.Descriptor;
+      Lexer      : in WisiToken.Lexer.Handle;
+      Tree       : in WisiToken.Syntax_Trees.Tree;
+      Terminals  : in WisiToken.Base_Token_Arrays.Vector;
+      Node       : in WisiToken.Syntax_Trees.Node_Index);
+   pragma No_Return (Raise_Programmer_Error);
+
+   ----------
+   --  List functions
+   --
+   --  A list has one of the following grammar forms:
+   --
+   --  list : list element | element ;
+   --  list : element | list element ;
+   --
+   --  list : list separator element | element ;
+   --  list : element | list separator element ;
+
+   type Cursor is private;
+   function Has_Element (Cursor : in LR_Utils.Cursor) return Boolean;
+
+   function Node (Cursor : in LR_Utils.Cursor) return Node_Index;
+
+   package Iterator_Interfaces is new Ada.Iterator_Interfaces (Cursor, 
Has_Element);
+
+   type Iterator is new Iterator_Interfaces.Reversible_Iterator with private;
+
+   overriding function First (Iter : Iterator) return Cursor;
+   overriding function Last  (Iter : Iterator) return Cursor;
+
+   overriding function Next (Iter : Iterator; Position : Cursor) return Cursor;
+
+   overriding function Previous (Iter : Iterator; Position : Cursor) return 
Cursor;
+
+   function Iterate
+     (Tree         : in WisiToken.Syntax_Trees.Tree;
+      Terminals    : in WisiToken.Base_Token_Array_Access;
+      Lexer        : in WisiToken.Lexer.Handle;
+      Descriptor   : in WisiToken.Descriptor_Access_Constant;
+      Root         : in Valid_Node_Index;
+      Element_ID   : in WisiToken.Token_ID;
+      Separator_ID : in WisiToken.Token_ID := WisiToken.Invalid_Token_ID)
+     return Iterator_Interfaces.Reversible_Iterator'Class;
+
+   function Count (Iter : Iterator) return Ada.Containers.Count_Type;
+
+private
+
+   type Cursor is record
+      Node : Node_Index;
+   end record;
+
+   type Iterator is new Iterator_Interfaces.Reversible_Iterator with record
+      Tree         : WisiToken.Syntax_Trees.Tree;
+      Terminals    : WisiToken.Base_Token_Array_Access;
+      Lexer        : WisiToken.Lexer.Handle;
+      Descriptor   : WisiToken.Descriptor_Access_Constant;
+      Root         : Valid_Node_Index;
+      List_ID      : WisiToken.Token_ID;
+      Element_ID   : WisiToken.Token_ID;
+      Separator_ID : WisiToken.Token_ID;
+   end record;
+
+end WisiToken.Syntax_Trees.LR_Utils;
diff --git a/wisitoken-syntax_trees.adb b/wisitoken-syntax_trees.adb
index e8ac152..abcc4d8 100644
--- a/wisitoken-syntax_trees.adb
+++ b/wisitoken-syntax_trees.adb
@@ -229,7 +229,10 @@ package body WisiToken.Syntax_Trees is
    is
       function Compute (N : in Syntax_Trees.Node) return Node_Index
       is begin
-         if Child_Index in N.Children.First_Index .. N.Children.Last_Index then
+         if N.Label /= Nonterm then
+            return Invalid_Node_Index;
+
+         elsif Child_Index in N.Children.First_Index .. N.Children.Last_Index 
then
             return N.Children (Child_Index);
          else
             return Invalid_Node_Index;
@@ -244,9 +247,7 @@ package body WisiToken.Syntax_Trees is
    end Child;
 
    function Children (N : in Syntax_Trees.Node) return Valid_Node_Index_Array
-   is
-      use all type Ada.Containers.Count_Type;
-   begin
+   is begin
       if N.Children.Length = 0 then
          return (1 .. 0 => <>);
       else
@@ -355,7 +356,7 @@ package body WisiToken.Syntax_Trees is
                      end loop;
 
                      if Last_Index = SAL.Base_Peek_Type'Last then
-                        New_Children.Set_Length (Children'Length);
+                        New_Children.Set_First_Last (Children'First, 
Children'Last);
                         for I in Children'Range loop
                            New_Children (I) := Copy_Node (Tree, Children (I), 
Parent);
                         end loop;
@@ -488,6 +489,7 @@ package body WisiToken.Syntax_Trees is
          end if;
          Tree.Branched_Nodes.Finalize;
          Tree.Last_Shared_Node := Invalid_Node_Index;
+         Tree.Shared_Tree := null;
       end if;
    end Finalize;
 
@@ -592,7 +594,31 @@ package body WisiToken.Syntax_Trees is
          end if;
       end Process;
 
-      Junk : constant Boolean := Process_Tree (Tree, Node, After, 
Process'Access);
+      Junk : constant Boolean := Process_Tree (Tree, Node, Before, 
Process'Access);
+      pragma Unreferenced (Junk);
+   begin
+      return Found;
+   end Find_Descendant;
+
+   function Find_Descendant
+     (Tree      : in     Syntax_Trees.Tree;
+      Node      : in     Valid_Node_Index;
+      Predicate : access function (Tree : in Syntax_Trees.Tree; Node : in 
Valid_Node_Index) return Boolean)
+     return Node_Index
+   is
+      Found : Node_Index := Invalid_Node_Index;
+
+      function Process (Tree : in Syntax_Trees.Tree; Node : in 
Valid_Node_Index) return Boolean
+      is begin
+         if Predicate (Tree, Node) then
+            Found := Node;
+            return False;
+         else
+            return True;
+         end if;
+      end Process;
+
+      Junk : constant Boolean := Process_Tree (Tree, Node, Before, 
Process'Access);
       pragma Unreferenced (Junk);
    begin
       return Found;
@@ -871,16 +897,12 @@ package body WisiToken.Syntax_Trees is
    end First_Terminal_ID;
 
    function Has_Branched_Nodes (Tree : in Syntax_Trees.Tree) return Boolean
-   is
-      use all type Ada.Containers.Count_Type;
-   begin
+   is begin
       return Tree.Branched_Nodes.Length > 0;
    end Has_Branched_Nodes;
 
    function Has_Children (Tree : in Syntax_Trees.Tree; Node : in 
Valid_Node_Index) return Boolean
-   is
-      use all type Ada.Containers.Count_Type;
-   begin
+   is begin
       if Node <= Tree.Last_Shared_Node then
          return Tree.Shared_Tree.Nodes (Node).Children.Length > 0;
       else
@@ -1438,7 +1460,7 @@ package body WisiToken.Syntax_Trees is
 
       Min_Terminal_Index_Set : Boolean := False;
    begin
-      N.Children.Set_Length (Children'Length);
+      N.Children.Set_First_Last (Children'First, Children'Last);
       for I in Children'Range loop
          N.Children (J) := Children (I);
          declare
@@ -1516,7 +1538,7 @@ package body WisiToken.Syntax_Trees is
       Parent_Node.RHS_Index := New_ID.RHS;
       Parent_Node.Action    := null;
 
-      Parent_Node.Children.Set_Length (Children'Length);
+      Parent_Node.Children.Set_First_Last (Children'First, Children'Last);
       for I in Children'Range loop
          --  We don't update Min/Max_terminal_index; we assume Set_Children is
          --  only called after parsing is done, so they are no longer needed.
@@ -1544,8 +1566,10 @@ package body WisiToken.Syntax_Trees is
 
    procedure Set_Flush_False (Tree : in out Syntax_Trees.Tree)
    is begin
-      Tree.Flush := False;
-      Tree.Branched_Nodes.Set_First (Tree.Last_Shared_Node + 1);
+      if Tree.Flush then
+         Tree.Flush := False;
+         Tree.Branched_Nodes.Set_First_Last (Tree.Last_Shared_Node + 1, 
Tree.Last_Shared_Node);
+      end if;
    end Set_Flush_False;
 
    procedure Set_Name_Region
diff --git a/wisitoken-syntax_trees.ads b/wisitoken-syntax_trees.ads
index dcca9b4..85fd2c5 100644
--- a/wisitoken-syntax_trees.ads
+++ b/wisitoken-syntax_trees.ads
@@ -205,7 +205,7 @@ package WisiToken.Syntax_Trees is
    procedure Set_Children
      (Tree     : in out Syntax_Trees.Tree;
       Node     : in     Valid_Node_Index;
-      New_ID : in WisiToken.Production_ID;
+      New_ID   : in     WisiToken.Production_ID;
       Children : in     Valid_Node_Index_Array)
    with
      Pre => Tree.Flushed and
@@ -370,6 +370,14 @@ package WisiToken.Syntax_Trees is
    --  Return the descendant of Node (may be Node) whose ID is ID, or
    --  Invalid_Node_Index if none match.
 
+   function Find_Descendant
+     (Tree      : in     Syntax_Trees.Tree;
+      Node      : in     Valid_Node_Index;
+      Predicate : access function (Tree : in Syntax_Trees.Tree; Node : in 
Valid_Node_Index) return Boolean)
+     return Node_Index;
+   --  Return the descendant of Node (may be Node) for which Predicate
+   --  returns True, or Invalid_Node_Index if none do.
+
    function Find_Min_Terminal_Index
      (Tree  : in Syntax_Trees.Tree;
       Index : in Token_Index)
@@ -467,6 +475,7 @@ package WisiToken.Syntax_Trees is
    --  Text_IO.Current_Output, for debugging.
 
 private
+   use all type Ada.Containers.Count_Type;
 
    type Node (Label : Node_Label := Virtual_Terminal) is
    --  Label has a default to allow changing the label during tree editing.
@@ -562,6 +571,12 @@ private
 
       Root : Node_Index := Invalid_Node_Index;
    end record with
-     Type_Invariant => (if Tree.Flush then not Tree.Has_Branched_Nodes);
+     Type_Invariant =>
+       (Shared_Tree = null or else
+        (if Tree.Flush
+         then Last_Shared_Node = Shared_Tree.Nodes.Last_Index and
+            Branched_Nodes.Length = 0
+         else Last_Shared_Node <= Shared_Tree.Nodes.Last_Index and
+            Last_Shared_Node < Branched_Nodes.First_Index));
 
 end WisiToken.Syntax_Trees;
diff --git a/wisitoken-wisi_ada.adb b/wisitoken-wisi_ada.adb
index 4a098c8..34dad7c 100644
--- a/wisitoken-wisi_ada.adb
+++ b/wisitoken-wisi_ada.adb
@@ -2,7 +2,7 @@
 --
 --  see spec
 --
---  Copyright (C) 2013, 2014, 2015, 2017, 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2013, 2014, 2015, 2017 - 2019 Free Software Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -85,8 +85,7 @@ package body WisiToken.Wisi_Ada is
    function Only (Subject : in Instance) return Prod_Arrays.Vector
    is begin
       return Result : Prod_Arrays.Vector do
-         Result.Set_First (Subject.LHS);
-         Result.Set_Last (Subject.LHS);
+         Result.Set_First_Last (Subject.LHS, Subject.LHS);
          Result (Subject.LHS) := Subject;
       end return;
    end Only;
@@ -96,7 +95,7 @@ package body WisiToken.Wisi_Ada is
       Index : Integer := Left.RHSs.Last_Index + 1;
    begin
       return Result : Instance := Left do
-         Result.RHSs.Set_Last (Left.RHSs.Last_Index + Integer 
(Right.RHSs.Length));
+         Result.RHSs.Set_First_Last (Result.RHSs.First_Index, 
Left.RHSs.Last_Index + Integer (Right.RHSs.Length));
          for RHS of Right.RHSs loop
             Result.RHSs (Index) := RHS;
             Index := Index + 1;
@@ -107,8 +106,7 @@ package body WisiToken.Wisi_Ada is
    function "and" (Left : in Instance; Right : in Instance) return 
Prod_Arrays.Vector
    is begin
       return Result : Prod_Arrays.Vector do
-         Result.Set_First (Token_ID'Min (Left.LHS, Right.LHS));
-         Result.Set_Last (Token_ID'Max (Left.LHS, Right.LHS));
+         Result.Set_First_Last (Token_ID'Min (Left.LHS, Right.LHS), 
Token_ID'Max (Left.LHS, Right.LHS));
          if Left.LHS = Right.LHS then
             Result (Left.LHS) := Merge (Left, Right);
          else
@@ -122,9 +120,9 @@ package body WisiToken.Wisi_Ada is
    is begin
       return Result : Prod_Arrays.Vector := Left do
          if Right.LHS < Result.First_Index then
-            Result.Set_First (Right.LHS);
+            Result.Set_First_Last (Right.LHS, Result.Last_Index);
          elsif Right.LHS > Result.Last_Index then
-            Result.Set_Last (Right.LHS);
+            Result.Set_First_Last (Result.First_Index, Right.LHS);
          end if;
 
          if Result (Right.LHS).LHS = Invalid_Token_ID then
@@ -139,14 +137,14 @@ package body WisiToken.Wisi_Ada is
    is begin
       return Result : Prod_Arrays.Vector := Left do
          if Right.First_Index < Result.First_Index then
-            Result.Set_First (Right.First_Index);
+            Result.Set_First_Last (Right.First_Index, Result.Last_Index);
          elsif Right.First_Index > Result.Last_Index then
-            Result.Set_Last (Right.First_Index);
+            Result.Set_First_Last (Result.First_Index, Right.First_Index);
          end if;
          if Right.Last_Index < Result.First_Index then
-            Result.Set_First (Right.Last_Index);
+            Result.Set_First_Last (Right.Last_Index, Result.Last_Index);
          elsif Right.Last_Index > Result.Last_Index then
-            Result.Set_Last (Right.Last_Index);
+            Result.Set_First_Last (Result.First_Index, Right.Last_Index);
          end if;
 
          for P of Right loop
diff --git a/wisitoken.ads b/wisitoken.ads
index d4652b8..891fb26 100644
--- a/wisitoken.ads
+++ b/wisitoken.ads
@@ -310,6 +310,7 @@ package WisiToken is
    end record;
 
    type Base_Token_Class_Access is access all Base_Token'Class;
+   type Base_Token_Class_Access_Constant is access constant Base_Token'Class;
 
    function Image
      (Item       : in Base_Token;
@@ -352,10 +353,11 @@ package WisiToken is
 
    type Recover_Token is record
       --  Maintaining a syntax tree during recover is too slow, so we store
-      --  enough information in the recover stack to perform semantic_checks
-      --  and to apply the solution to the main parser state. We make
-      --  thousands of copies of the parse stack during recover, so
-      --  minimizing size is critical.
+      --  enough information in the recover stack to perform
+      --  Semantic_Checks, Language_Fixes, and Push_Back operations. and to
+      --  apply the solution to the main parser state. We make thousands of
+      --  copies of the parse stack during recover, so minimizing size and
+      --  compute time for this is critical.
       ID : Token_ID := Invalid_Token_ID;
 
       Byte_Region : Buffer_Region := Null_Buffer_Region;
@@ -372,8 +374,7 @@ package WisiToken is
 
       Virtual : Boolean := True;
       --  For terminals, True if inserted by recover. For nonterminals, True
-      --  if any contained token has Virtual = True. Used by Semantic_Checks
-      --  and push_back.
+      --  if any contained token has Virtual = True.
    end record;
 
    function Image
diff --git a/wisitoken_grammar_main.adb b/wisitoken_grammar_main.adb
index 7a2bb22..981e60c 100644
--- a/wisitoken_grammar_main.adb
+++ b/wisitoken_grammar_main.adb
@@ -49,13 +49,15 @@ package body Wisitoken_Grammar_Main is
       declare
          procedure Subr_1
          is begin
+            Table.States (0).Action_List.Set_Capacity (2);
             Add_Action (Table.States (0), 23, 1);
             Add_Action (Table.States (0), 33, 2);
-            Add_Error (Table.States (0));
+            Table.States (0).Goto_List.Set_Capacity (4);
             Add_Goto (Table.States (0), 38, 3);
             Add_Goto (Table.States (0), 43, 4);
             Add_Goto (Table.States (0), 55, 5);
             Add_Goto (Table.States (0), 56, 6);
+            Table.States (1).Action_List.Set_Capacity (7);
             Add_Action (Table.States (1), 3, 7);
             Add_Action (Table.States (1), 4, 8);
             Add_Action (Table.States (1), 5, 9);
@@ -63,33 +65,40 @@ package body Wisitoken_Grammar_Main is
             Add_Action (Table.States (1), 7, 11);
             Add_Action (Table.States (1), 8, 12);
             Add_Action (Table.States (1), 33, 13);
-            Add_Error (Table.States (1));
+            Table.States (1).Goto_List.Set_Capacity (1);
             Add_Goto (Table.States (1), 39, 14);
+            Table.States (2).Action_List.Set_Capacity (2);
             Add_Action (Table.States (2), 13, 15);
             Add_Action (Table.States (2), 14, 16);
-            Add_Error (Table.States (2));
+            Table.States (3).Action_List.Set_Capacity (3);
             Add_Action (Table.States (3), (23, 33, 36), (55, 0), 1, null, 
null);
+            Table.States (4).Action_List.Set_Capacity (3);
             Add_Action (Table.States (4), (23, 33, 36), (55, 1), 1, null, 
null);
+            Table.States (5).Action_List.Set_Capacity (3);
             Add_Action (Table.States (5), (23, 33, 36), (56, 0), 1, null, 
null);
+            Table.States (6).Action_List.Set_Capacity (3);
             Add_Action (Table.States (6), 23, 1);
             Add_Action (Table.States (6), 33, 2);
             Add_Action (Table.States (6), 36, Accept_It, (37, 0), 1, null, 
null);
-            Add_Error (Table.States (6));
+            Table.States (6).Goto_List.Set_Capacity (3);
             Add_Goto (Table.States (6), 38, 3);
             Add_Goto (Table.States (6), 43, 4);
             Add_Goto (Table.States (6), 55, 17);
+            Table.States (7).Action_List.Set_Capacity (1);
             Add_Action (Table.States (7), 33, 18);
-            Add_Error (Table.States (7));
+            Table.States (7).Goto_List.Set_Capacity (1);
             Add_Goto (Table.States (7), 40, 19);
+            Table.States (8).Action_List.Set_Capacity (1);
             Add_Action (Table.States (8), 5, 20);
-            Add_Error (Table.States (8));
+            Table.States (9).Action_List.Set_Capacity (1);
             Add_Action (Table.States (9), 33, 21);
-            Add_Error (Table.States (9));
+            Table.States (10).Action_List.Set_Capacity (1);
             Add_Action (Table.States (10), (1 =>  33), (39, 0), 1, null, null);
+            Table.States (11).Action_List.Set_Capacity (1);
             Add_Action (Table.States (11), 21, 22);
-            Add_Error (Table.States (11));
+            Table.States (12).Action_List.Set_Capacity (1);
             Add_Action (Table.States (12), 21, 23);
-            Add_Error (Table.States (12));
+            Table.States (13).Action_List.Set_Capacity (13);
             Add_Action (Table.States (13), 8, 24);
             Add_Action (Table.States (13), 10, 25);
             Add_Action (Table.States (13), 15, 26);
@@ -104,11 +113,12 @@ package body Wisitoken_Grammar_Main is
             Add_Action (Table.States (13), 34, 33);
             Add_Action (Table.States (13), 35, 34);
             Add_Action (Table.States (13), 36, Reduce, (38, 3), 2, 
declaration_3'Access, null);
-            Add_Error (Table.States (13));
+            Table.States (13).Goto_List.Set_Capacity (2);
             Add_Goto (Table.States (13), 41, 35);
             Add_Goto (Table.States (13), 42, 36);
+            Table.States (14).Action_List.Set_Capacity (1);
             Add_Action (Table.States (14), 33, 37);
-            Add_Error (Table.States (14));
+            Table.States (15).Action_List.Set_Capacity (10);
             Add_Action (Table.States (15), 12, Reduce, (46, 0), 0, null, null);
             Add_Action (Table.States (15), 18, 38);
             Add_Action (Table.States (15), 19, 39);
@@ -120,7 +130,7 @@ package body Wisitoken_Grammar_Main is
             Add_Conflict (Table.States (15), 33, (46, 0), 0, null, null);
             Add_Action (Table.States (15), 35, 43);
             Add_Action (Table.States (15), 36, Reduce, (46, 0), 0, null, null);
-            Add_Error (Table.States (15));
+            Table.States (15).Goto_List.Set_Capacity (9);
             Add_Goto (Table.States (15), 45, 44);
             Add_Goto (Table.States (15), 46, 45);
             Add_Goto (Table.States (15), 47, 46);
@@ -130,6 +140,7 @@ package body Wisitoken_Grammar_Main is
             Add_Goto (Table.States (15), 51, 50);
             Add_Goto (Table.States (15), 52, 51);
             Add_Goto (Table.States (15), 53, 52);
+            Table.States (16).Action_List.Set_Capacity (10);
             Add_Action (Table.States (16), 12, Reduce, (46, 0), 0, null, null);
             Add_Action (Table.States (16), 18, 38);
             Add_Action (Table.States (16), 19, 39);
@@ -141,7 +152,7 @@ package body Wisitoken_Grammar_Main is
             Add_Conflict (Table.States (16), 33, (46, 0), 0, null, null);
             Add_Action (Table.States (16), 35, 43);
             Add_Action (Table.States (16), 36, Reduce, (46, 0), 0, null, null);
-            Add_Error (Table.States (16));
+            Table.States (16).Goto_List.Set_Capacity (9);
             Add_Goto (Table.States (16), 45, 53);
             Add_Goto (Table.States (16), 46, 45);
             Add_Goto (Table.States (16), 47, 46);
@@ -151,40 +162,55 @@ package body Wisitoken_Grammar_Main is
             Add_Goto (Table.States (16), 51, 50);
             Add_Goto (Table.States (16), 52, 51);
             Add_Goto (Table.States (16), 53, 52);
+            Table.States (17).Action_List.Set_Capacity (3);
             Add_Action (Table.States (17), (23, 33, 36), (56, 1), 2, null, 
null);
+            Table.States (18).Action_List.Set_Capacity (2);
             Add_Action (Table.States (18), (9, 33), (40, 0), 1, null, null);
+            Table.States (19).Action_List.Set_Capacity (2);
             Add_Action (Table.States (19), 9, 54);
             Add_Action (Table.States (19), 33, 55);
-            Add_Error (Table.States (19));
+            Table.States (20).Action_List.Set_Capacity (3);
             Add_Action (Table.States (20), (23, 33, 36), (38, 5), 3, 
declaration_5'Access, null);
+            Table.States (21).Action_List.Set_Capacity (1);
             Add_Action (Table.States (21), 16, 56);
-            Add_Error (Table.States (21));
+            Table.States (22).Action_List.Set_Capacity (1);
             Add_Action (Table.States (22), 33, 57);
-            Add_Error (Table.States (22));
+            Table.States (23).Action_List.Set_Capacity (1);
             Add_Action (Table.States (23), 33, 58);
-            Add_Error (Table.States (23));
+            Table.States (24).Action_List.Set_Capacity (13);
             Add_Action (Table.States (24), (8, 10, 15, 16, 20, 23, 28, 30, 32, 
33, 34, 35, 36), (42, 10), 1, null,
             null);
+            Table.States (25).Action_List.Set_Capacity (13);
             Add_Action (Table.States (25), (8, 10, 15, 16, 20, 23, 28, 30, 32, 
33, 34, 35, 36), (42, 5), 1, null,
             null);
+            Table.States (26).Action_List.Set_Capacity (13);
             Add_Action (Table.States (26), (8, 10, 15, 16, 20, 23, 28, 30, 32, 
33, 34, 35, 36), (42, 0), 1, null,
             null);
+            Table.States (27).Action_List.Set_Capacity (13);
             Add_Action (Table.States (27), (8, 10, 15, 16, 20, 23, 28, 30, 32, 
33, 34, 35, 36), (42, 2), 1, null,
             null);
+            Table.States (28).Action_List.Set_Capacity (13);
             Add_Action (Table.States (28), (8, 10, 15, 16, 20, 23, 28, 30, 32, 
33, 34, 35, 36), (42, 3), 1, null,
             null);
+            Table.States (29).Action_List.Set_Capacity (13);
             Add_Action (Table.States (29), (8, 10, 15, 16, 20, 23, 28, 30, 32, 
33, 34, 35, 36), (42, 6), 1, null,
             null);
+            Table.States (30).Action_List.Set_Capacity (13);
             Add_Action (Table.States (30), (8, 10, 15, 16, 20, 23, 28, 30, 32, 
33, 34, 35, 36), (42, 7), 1, null,
             null);
+            Table.States (31).Action_List.Set_Capacity (13);
             Add_Action (Table.States (31), (8, 10, 15, 16, 20, 23, 28, 30, 32, 
33, 34, 35, 36), (42, 4), 1, null,
             null);
+            Table.States (32).Action_List.Set_Capacity (13);
             Add_Action (Table.States (32), (8, 10, 15, 16, 20, 23, 28, 30, 32, 
33, 34, 35, 36), (42, 1), 1, null,
             null);
+            Table.States (33).Action_List.Set_Capacity (13);
             Add_Action (Table.States (33), (8, 10, 15, 16, 20, 23, 28, 30, 32, 
33, 34, 35, 36), (42, 8), 1, null,
             null);
+            Table.States (34).Action_List.Set_Capacity (13);
             Add_Action (Table.States (34), (8, 10, 15, 16, 20, 23, 28, 30, 32, 
33, 34, 35, 36), (42, 9), 1, null,
             null);
+            Table.States (35).Action_List.Set_Capacity (13);
             Add_Action (Table.States (35), 8, 24);
             Add_Action (Table.States (35), 10, 25);
             Add_Action (Table.States (35), 15, 26);
@@ -199,10 +225,12 @@ package body Wisitoken_Grammar_Main is
             Add_Action (Table.States (35), 34, 33);
             Add_Action (Table.States (35), 35, 34);
             Add_Action (Table.States (35), 36, Reduce, (38, 2), 3, 
declaration_2'Access, null);
-            Add_Error (Table.States (35));
+            Table.States (35).Goto_List.Set_Capacity (1);
             Add_Goto (Table.States (35), 42, 59);
+            Table.States (36).Action_List.Set_Capacity (13);
             Add_Action (Table.States (36), (8, 10, 15, 16, 20, 23, 28, 30, 32, 
33, 34, 35, 36), (41, 0), 1, null,
             null);
+            Table.States (37).Action_List.Set_Capacity (11);
             Add_Action (Table.States (37), 8, 24);
             Add_Action (Table.States (37), 10, 25);
             Add_Action (Table.States (37), 15, 26);
@@ -214,16 +242,17 @@ package body Wisitoken_Grammar_Main is
             Add_Action (Table.States (37), 33, 32);
             Add_Action (Table.States (37), 34, 33);
             Add_Action (Table.States (37), 35, 34);
-            Add_Error (Table.States (37));
+            Table.States (37).Goto_List.Set_Capacity (2);
             Add_Goto (Table.States (37), 41, 60);
             Add_Goto (Table.States (37), 42, 36);
+            Table.States (38).Action_List.Set_Capacity (6);
             Add_Action (Table.States (38), 18, 38);
             Add_Action (Table.States (38), 19, 39);
             Add_Action (Table.States (38), 20, 40);
             Add_Action (Table.States (38), 21, 41);
             Add_Action (Table.States (38), 33, 42);
             Add_Action (Table.States (38), 35, 43);
-            Add_Error (Table.States (38));
+            Table.States (38).Goto_List.Set_Capacity (8);
             Add_Goto (Table.States (38), 47, 46);
             Add_Goto (Table.States (38), 48, 47);
             Add_Goto (Table.States (38), 49, 61);
@@ -232,13 +261,14 @@ package body Wisitoken_Grammar_Main is
             Add_Goto (Table.States (38), 52, 51);
             Add_Goto (Table.States (38), 53, 52);
             Add_Goto (Table.States (38), 54, 62);
+            Table.States (39).Action_List.Set_Capacity (6);
             Add_Action (Table.States (39), 18, 38);
             Add_Action (Table.States (39), 19, 39);
             Add_Action (Table.States (39), 20, 40);
             Add_Action (Table.States (39), 21, 41);
             Add_Action (Table.States (39), 33, 42);
             Add_Action (Table.States (39), 35, 43);
-            Add_Error (Table.States (39));
+            Table.States (39).Goto_List.Set_Capacity (8);
             Add_Goto (Table.States (39), 47, 46);
             Add_Goto (Table.States (39), 48, 47);
             Add_Goto (Table.States (39), 49, 61);
@@ -247,13 +277,14 @@ package body Wisitoken_Grammar_Main is
             Add_Goto (Table.States (39), 52, 51);
             Add_Goto (Table.States (39), 53, 52);
             Add_Goto (Table.States (39), 54, 63);
+            Table.States (40).Action_List.Set_Capacity (6);
             Add_Action (Table.States (40), 18, 38);
             Add_Action (Table.States (40), 19, 39);
             Add_Action (Table.States (40), 20, 40);
             Add_Action (Table.States (40), 21, 41);
             Add_Action (Table.States (40), 33, 42);
             Add_Action (Table.States (40), 35, 43);
-            Add_Error (Table.States (40));
+            Table.States (40).Goto_List.Set_Capacity (8);
             Add_Goto (Table.States (40), 47, 46);
             Add_Goto (Table.States (40), 48, 47);
             Add_Goto (Table.States (40), 49, 61);
@@ -262,8 +293,9 @@ package body Wisitoken_Grammar_Main is
             Add_Goto (Table.States (40), 52, 51);
             Add_Goto (Table.States (40), 53, 52);
             Add_Goto (Table.States (40), 54, 64);
+            Table.States (41).Action_List.Set_Capacity (1);
             Add_Action (Table.States (41), 33, 65);
-            Add_Error (Table.States (41));
+            Table.States (42).Action_List.Set_Capacity (18);
             Add_Action (Table.States (42), 11, Reduce, (50, 0), 1, null, null);
             Add_Action (Table.States (42), 12, Reduce, (50, 0), 1, null, null);
             Add_Action (Table.States (42), 16, 66);
@@ -282,7 +314,7 @@ package body Wisitoken_Grammar_Main is
             Add_Action (Table.States (42), 33, Reduce, (50, 0), 1, null, null);
             Add_Action (Table.States (42), 35, Reduce, (50, 0), 1, null, null);
             Add_Action (Table.States (42), 36, Reduce, (50, 0), 1, null, null);
-            Add_Error (Table.States (42));
+            Table.States (43).Action_List.Set_Capacity (15);
             Add_Action (Table.States (43), 11, Reduce, (50, 1), 1, 
rhs_item_1'Access, null);
             Add_Action (Table.States (43), 12, Reduce, (50, 1), 1, 
rhs_item_1'Access, null);
             Add_Action (Table.States (43), 18, Reduce, (50, 1), 1, 
rhs_item_1'Access, null);
@@ -298,20 +330,24 @@ package body Wisitoken_Grammar_Main is
             Add_Action (Table.States (43), 33, Reduce, (50, 1), 1, 
rhs_item_1'Access, null);
             Add_Action (Table.States (43), 35, Reduce, (50, 1), 1, 
rhs_item_1'Access, null);
             Add_Action (Table.States (43), 36, Reduce, (50, 1), 1, 
rhs_item_1'Access, null);
-            Add_Error (Table.States (43));
+            Table.States (44).Action_List.Set_Capacity (5);
             Add_Action (Table.States (44), 12, 71);
             Add_Action (Table.States (44), 23, 72);
             Add_Conflict (Table.States (44), 23, (44, 1), 0, null, null);
             Add_Action (Table.States (44), 29, 73);
             Add_Action (Table.States (44), 33, Reduce, (44, 1), 0, null, null);
             Add_Action (Table.States (44), 36, Reduce, (44, 1), 0, null, null);
-            Add_Error (Table.States (44));
+            Table.States (44).Goto_List.Set_Capacity (1);
             Add_Goto (Table.States (44), 44, 74);
+            Table.States (45).Action_List.Set_Capacity (5);
             Add_Action (Table.States (45), (12, 23, 29, 33, 36), (45, 0), 1, 
null, null);
+            Table.States (46).Action_List.Set_Capacity (14);
             Add_Action (Table.States (46), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (50, 2), 1,
             rhs_item_2'Access, null);
+            Table.States (47).Action_List.Set_Capacity (14);
             Add_Action (Table.States (47), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (49, 0), 1, null,
             null);
+            Table.States (48).Action_List.Set_Capacity (11);
             Add_Action (Table.States (48), 11, 75);
             Add_Action (Table.States (48), 12, Reduce, (46, 1), 1, null, null);
             Add_Action (Table.States (48), 18, 38);
@@ -324,39 +360,48 @@ package body Wisitoken_Grammar_Main is
             Add_Conflict (Table.States (48), 33, (46, 1), 1, null, null);
             Add_Action (Table.States (48), 35, 43);
             Add_Action (Table.States (48), 36, Reduce, (46, 1), 1, null, null);
-            Add_Error (Table.States (48));
+            Table.States (48).Goto_List.Set_Capacity (6);
             Add_Goto (Table.States (48), 47, 46);
             Add_Goto (Table.States (48), 48, 76);
             Add_Goto (Table.States (48), 50, 49);
             Add_Goto (Table.States (48), 51, 50);
             Add_Goto (Table.States (48), 52, 51);
             Add_Goto (Table.States (48), 53, 52);
+            Table.States (49).Action_List.Set_Capacity (14);
             Add_Action (Table.States (49), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (48, 0), 1, null,
             null);
+            Table.States (50).Action_List.Set_Capacity (14);
             Add_Action (Table.States (50), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (50, 5), 1,
             rhs_item_5'Access, null);
+            Table.States (51).Action_List.Set_Capacity (14);
             Add_Action (Table.States (51), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (50, 3), 1,
             rhs_item_3'Access, null);
+            Table.States (52).Action_List.Set_Capacity (14);
             Add_Action (Table.States (52), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (50, 4), 1,
             rhs_item_4'Access, null);
+            Table.States (53).Action_List.Set_Capacity (5);
             Add_Action (Table.States (53), 12, 71);
             Add_Action (Table.States (53), 23, 72);
             Add_Conflict (Table.States (53), 23, (44, 1), 0, null, null);
             Add_Action (Table.States (53), 29, 73);
             Add_Action (Table.States (53), 33, Reduce, (44, 1), 0, null, null);
             Add_Action (Table.States (53), 36, Reduce, (44, 1), 0, null, null);
-            Add_Error (Table.States (53));
+            Table.States (53).Goto_List.Set_Capacity (1);
             Add_Goto (Table.States (53), 44, 77);
+            Table.States (54).Action_List.Set_Capacity (3);
             Add_Action (Table.States (54), (23, 33, 36), (38, 1), 4, 
declaration_1'Access, null);
+            Table.States (55).Action_List.Set_Capacity (2);
             Add_Action (Table.States (55), (9, 33), (40, 1), 2, null, null);
+            Table.States (56).Action_List.Set_Capacity (1);
             Add_Action (Table.States (56), 33, 78);
-            Add_Error (Table.States (56));
+            Table.States (57).Action_List.Set_Capacity (1);
             Add_Action (Table.States (57), 17, 79);
-            Add_Error (Table.States (57));
+            Table.States (58).Action_List.Set_Capacity (1);
             Add_Action (Table.States (58), 17, 80);
-            Add_Error (Table.States (58));
+            Table.States (59).Action_List.Set_Capacity (13);
             Add_Action (Table.States (59), (8, 10, 15, 16, 20, 23, 28, 30, 32, 
33, 34, 35, 36), (41, 1), 2, null,
             null);
+            Table.States (60).Action_List.Set_Capacity (13);
             Add_Action (Table.States (60), 8, 24);
             Add_Action (Table.States (60), 10, 25);
             Add_Action (Table.States (60), 15, 26);
@@ -371,8 +416,9 @@ package body Wisitoken_Grammar_Main is
             Add_Action (Table.States (60), 34, 33);
             Add_Action (Table.States (60), 35, 34);
             Add_Action (Table.States (60), 36, Reduce, (38, 0), 4, 
declaration_0'Access, null);
-            Add_Error (Table.States (60));
+            Table.States (60).Goto_List.Set_Capacity (1);
             Add_Goto (Table.States (60), 42, 59);
+            Table.States (61).Action_List.Set_Capacity (10);
             Add_Action (Table.States (61), 12, Reduce, (54, 0), 1, null, null);
             Add_Action (Table.States (61), 18, 38);
             Add_Action (Table.States (61), 19, 39);
@@ -383,44 +429,50 @@ package body Wisitoken_Grammar_Main is
             Add_Action (Table.States (61), 28, Reduce, (54, 0), 1, null, null);
             Add_Action (Table.States (61), 33, 42);
             Add_Action (Table.States (61), 35, 43);
-            Add_Error (Table.States (61));
+            Table.States (61).Goto_List.Set_Capacity (6);
             Add_Goto (Table.States (61), 47, 46);
             Add_Goto (Table.States (61), 48, 76);
             Add_Goto (Table.States (61), 50, 49);
             Add_Goto (Table.States (61), 51, 50);
             Add_Goto (Table.States (61), 52, 51);
             Add_Goto (Table.States (61), 53, 52);
+            Table.States (62).Action_List.Set_Capacity (2);
             Add_Action (Table.States (62), 12, 81);
             Add_Action (Table.States (62), 26, 82);
-            Add_Error (Table.States (62));
+            Table.States (63).Action_List.Set_Capacity (2);
             Add_Action (Table.States (63), 12, 81);
             Add_Action (Table.States (63), 27, 83);
-            Add_Error (Table.States (63));
+            Table.States (64).Action_List.Set_Capacity (2);
             Add_Action (Table.States (64), 12, 81);
             Add_Action (Table.States (64), 28, 84);
-            Add_Error (Table.States (64));
+            Table.States (65).Action_List.Set_Capacity (1);
             Add_Action (Table.States (65), 16, 85);
-            Add_Error (Table.States (65));
+            Table.States (66).Action_List.Set_Capacity (6);
             Add_Action (Table.States (66), 18, 38);
             Add_Action (Table.States (66), 19, 39);
             Add_Action (Table.States (66), 20, 40);
             Add_Action (Table.States (66), 21, 41);
             Add_Action (Table.States (66), 33, 86);
             Add_Action (Table.States (66), 35, 43);
-            Add_Error (Table.States (66));
+            Table.States (66).Goto_List.Set_Capacity (5);
             Add_Goto (Table.States (66), 47, 46);
             Add_Goto (Table.States (66), 50, 87);
             Add_Goto (Table.States (66), 51, 50);
             Add_Goto (Table.States (66), 52, 51);
             Add_Goto (Table.States (66), 53, 52);
+            Table.States (67).Action_List.Set_Capacity (14);
             Add_Action (Table.States (67), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (53, 4), 2, null,
             null);
+            Table.States (68).Action_List.Set_Capacity (14);
             Add_Action (Table.States (68), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (52, 2), 2, null,
             null);
+            Table.States (69).Action_List.Set_Capacity (14);
             Add_Action (Table.States (69), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (53, 5), 2, null,
             null);
+            Table.States (70).Action_List.Set_Capacity (14);
             Add_Action (Table.States (70), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (52, 3), 2,
             rhs_optional_item_3'Access, null);
+            Table.States (71).Action_List.Set_Capacity (10);
             Add_Action (Table.States (71), 12, Reduce, (46, 0), 0, null, null);
             Add_Action (Table.States (71), 18, 38);
             Add_Action (Table.States (71), 19, 39);
@@ -432,7 +484,7 @@ package body Wisitoken_Grammar_Main is
             Add_Conflict (Table.States (71), 33, (46, 0), 0, null, null);
             Add_Action (Table.States (71), 35, 43);
             Add_Action (Table.States (71), 36, Reduce, (46, 0), 0, null, null);
-            Add_Error (Table.States (71));
+            Table.States (71).Goto_List.Set_Capacity (8);
             Add_Goto (Table.States (71), 46, 88);
             Add_Goto (Table.States (71), 47, 46);
             Add_Goto (Table.States (71), 48, 47);
@@ -441,31 +493,39 @@ package body Wisitoken_Grammar_Main is
             Add_Goto (Table.States (71), 51, 50);
             Add_Goto (Table.States (71), 52, 51);
             Add_Goto (Table.States (71), 53, 52);
+            Table.States (72).Action_List.Set_Capacity (2);
             Add_Action (Table.States (72), 4, 89);
             Add_Action (Table.States (72), 5, 90);
-            Add_Error (Table.States (72));
+            Table.States (73).Action_List.Set_Capacity (3);
             Add_Action (Table.States (73), (23, 33, 36), (44, 0), 1, null, 
null);
+            Table.States (74).Action_List.Set_Capacity (3);
             Add_Action (Table.States (74), (23, 33, 36), (43, 0), 4, 
nonterminal_0'Access, null);
+            Table.States (75).Action_List.Set_Capacity (6);
             Add_Action (Table.States (75), 11, 91);
             Add_Action (Table.States (75), 12, Reduce, (46, 2), 2, null, null);
             Add_Action (Table.States (75), 23, Reduce, (46, 2), 2, null, null);
             Add_Action (Table.States (75), 29, Reduce, (46, 2), 2, null, null);
             Add_Action (Table.States (75), 33, Reduce, (46, 2), 2, null, null);
             Add_Action (Table.States (75), 36, Reduce, (46, 2), 2, null, null);
-            Add_Error (Table.States (75));
+            Table.States (76).Action_List.Set_Capacity (14);
             Add_Action (Table.States (76), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (49, 1), 2, null,
             null);
+            Table.States (77).Action_List.Set_Capacity (3);
             Add_Action (Table.States (77), (23, 33, 36), (43, 1), 4, 
nonterminal_1'Access, null);
+            Table.States (78).Action_List.Set_Capacity (3);
             Add_Action (Table.States (78), (23, 33, 36), (38, 4), 5, 
declaration_4'Access, null);
+            Table.States (79).Action_List.Set_Capacity (1);
             Add_Action (Table.States (79), (1 =>  33), (39, 1), 4, null, null);
+            Table.States (80).Action_List.Set_Capacity (1);
             Add_Action (Table.States (80), (1 =>  33), (39, 2), 4, null, null);
+            Table.States (81).Action_List.Set_Capacity (6);
             Add_Action (Table.States (81), 18, 38);
             Add_Action (Table.States (81), 19, 39);
             Add_Action (Table.States (81), 20, 40);
             Add_Action (Table.States (81), 21, 41);
             Add_Action (Table.States (81), 33, 42);
             Add_Action (Table.States (81), 35, 43);
-            Add_Error (Table.States (81));
+            Table.States (81).Goto_List.Set_Capacity (7);
             Add_Goto (Table.States (81), 47, 46);
             Add_Goto (Table.States (81), 48, 47);
             Add_Goto (Table.States (81), 49, 92);
@@ -473,6 +533,7 @@ package body Wisitoken_Grammar_Main is
             Add_Goto (Table.States (81), 51, 50);
             Add_Goto (Table.States (81), 52, 51);
             Add_Goto (Table.States (81), 53, 52);
+            Table.States (82).Action_List.Set_Capacity (15);
             Add_Action (Table.States (82), 11, Reduce, (53, 0), 3, null, null);
             Add_Action (Table.States (82), 12, Reduce, (53, 0), 3, null, null);
             Add_Action (Table.States (82), 18, Reduce, (53, 0), 3, null, null);
@@ -488,9 +549,10 @@ package body Wisitoken_Grammar_Main is
             Add_Action (Table.States (82), 33, Reduce, (53, 0), 3, null, null);
             Add_Action (Table.States (82), 35, Reduce, (53, 0), 3, null, null);
             Add_Action (Table.States (82), 36, Reduce, (53, 0), 3, null, null);
-            Add_Error (Table.States (82));
+            Table.States (83).Action_List.Set_Capacity (14);
             Add_Action (Table.States (83), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (52, 0), 3, null,
             null);
+            Table.States (84).Action_List.Set_Capacity (17);
             Add_Action (Table.States (84), 11, Reduce, (51, 0), 3, null, null);
             Add_Action (Table.States (84), 12, Reduce, (51, 0), 3, null, null);
             Add_Action (Table.States (84), 18, Reduce, (51, 0), 3, null, null);
@@ -508,9 +570,9 @@ package body Wisitoken_Grammar_Main is
             Add_Action (Table.States (84), 33, Reduce, (51, 0), 3, null, null);
             Add_Action (Table.States (84), 35, Reduce, (51, 0), 3, null, null);
             Add_Action (Table.States (84), 36, Reduce, (51, 0), 3, null, null);
-            Add_Error (Table.States (84));
+            Table.States (85).Action_List.Set_Capacity (1);
             Add_Action (Table.States (85), 33, 97);
-            Add_Error (Table.States (85));
+            Table.States (86).Action_List.Set_Capacity (17);
             Add_Action (Table.States (86), 11, Reduce, (50, 0), 1, null, null);
             Add_Action (Table.States (86), 12, Reduce, (50, 0), 1, null, null);
             Add_Action (Table.States (86), 18, Reduce, (50, 0), 1, null, null);
@@ -528,15 +590,18 @@ package body Wisitoken_Grammar_Main is
             Add_Action (Table.States (86), 33, Reduce, (50, 0), 1, null, null);
             Add_Action (Table.States (86), 35, Reduce, (50, 0), 1, null, null);
             Add_Action (Table.States (86), 36, Reduce, (50, 0), 1, null, null);
-            Add_Error (Table.States (86));
+            Table.States (87).Action_List.Set_Capacity (14);
             Add_Action (Table.States (87), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (48, 1), 3, null,
             null);
+            Table.States (88).Action_List.Set_Capacity (5);
             Add_Action (Table.States (88), (12, 23, 29, 33, 36), (45, 1), 3, 
null, null);
+            Table.States (89).Action_List.Set_Capacity (1);
             Add_Action (Table.States (89), 5, 98);
-            Add_Error (Table.States (89));
+            Table.States (90).Action_List.Set_Capacity (1);
             Add_Action (Table.States (90), 33, 99);
-            Add_Error (Table.States (90));
+            Table.States (91).Action_List.Set_Capacity (5);
             Add_Action (Table.States (91), (12, 23, 29, 33, 36), (46, 3), 3, 
null, null);
+            Table.States (92).Action_List.Set_Capacity (10);
             Add_Action (Table.States (92), 12, Reduce, (54, 1), 3, null, null);
             Add_Action (Table.States (92), 18, 38);
             Add_Action (Table.States (92), 19, 39);
@@ -547,34 +612,42 @@ package body Wisitoken_Grammar_Main is
             Add_Action (Table.States (92), 28, Reduce, (54, 1), 3, null, null);
             Add_Action (Table.States (92), 33, 42);
             Add_Action (Table.States (92), 35, 43);
-            Add_Error (Table.States (92));
+            Table.States (92).Goto_List.Set_Capacity (6);
             Add_Goto (Table.States (92), 47, 46);
             Add_Goto (Table.States (92), 48, 76);
             Add_Goto (Table.States (92), 50, 49);
             Add_Goto (Table.States (92), 51, 50);
             Add_Goto (Table.States (92), 52, 51);
             Add_Goto (Table.States (92), 53, 52);
+            Table.States (93).Action_List.Set_Capacity (14);
             Add_Action (Table.States (93), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (53, 1), 4, null,
             null);
+            Table.States (94).Action_List.Set_Capacity (14);
             Add_Action (Table.States (94), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (53, 2), 4, null,
             null);
+            Table.States (95).Action_List.Set_Capacity (14);
             Add_Action (Table.States (95), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (52, 1), 4, null,
             null);
+            Table.States (96).Action_List.Set_Capacity (14);
             Add_Action (Table.States (96), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (53, 3), 4, null,
             null);
+            Table.States (97).Action_List.Set_Capacity (1);
             Add_Action (Table.States (97), 17, 100);
-            Add_Error (Table.States (97));
+            Table.States (98).Action_List.Set_Capacity (5);
             Add_Action (Table.States (98), (12, 23, 29, 33, 36), (45, 3), 4, 
null, null);
+            Table.States (99).Action_List.Set_Capacity (1);
             Add_Action (Table.States (99), 16, 101);
-            Add_Error (Table.States (99));
+            Table.States (100).Action_List.Set_Capacity (14);
             Add_Action (Table.States (100), (11, 12, 18, 19, 20, 21, 23, 26, 
27, 28, 29, 33, 35, 36), (47, 0), 5, null,
             null);
+            Table.States (101).Action_List.Set_Capacity (1);
             Add_Action (Table.States (101), 33, 102);
-            Add_Error (Table.States (101));
+            Table.States (102).Action_List.Set_Capacity (5);
             Add_Action (Table.States (102), (12, 23, 29, 33, 36), (45, 2), 6, 
null, null);
          end Subr_1;
       begin
          Subr_1;
+         Table.Error_Action := new Parse_Action_Node'((Verb => Error), null);
       end;
 
       WisiToken.Parse.LR.Parser_No_Recover.New_Parser
diff --git a/wisitoken_grammar_main.ads b/wisitoken_grammar_main.ads
index e07ed68..35a5e9e 100644
--- a/wisitoken_grammar_main.ads
+++ b/wisitoken_grammar_main.ads
@@ -27,6 +27,7 @@ package Wisitoken_Grammar_Main is
 
    procedure Create_Parser
      (Parser                       :    out 
WisiToken.Parse.LR.Parser_No_Recover.Parser;
+      --  no error recovery
       Trace                        : not null access WisiToken.Trace'Class;
       User_Data                    : in     
WisiToken.Syntax_Trees.User_Data_Access);
 
diff --git a/wisitoken_grammar_runtime.adb b/wisitoken_grammar_runtime.adb
index 4841ada..346eadb 100644
--- a/wisitoken_grammar_runtime.adb
+++ b/wisitoken_grammar_runtime.adb
@@ -26,6 +26,7 @@ with SAL.Generic_Decimal_Image;
 with System.Assertions;
 with WisiToken.Generate;   use WisiToken.Generate;
 with Wisitoken_Grammar_Actions; use Wisitoken_Grammar_Actions;
+with WisiToken.Syntax_Trees.LR_Utils;
 package body WisiToken_Grammar_Runtime is
 
    use WisiToken;
@@ -35,17 +36,19 @@ package body WisiToken_Grammar_Runtime is
 
    procedure Raise_Programmer_Error
      (Label : in String;
+      Data  : in User_Data_Type;
       Tree  : in WisiToken.Syntax_Trees.Tree;
       Node  : in WisiToken.Syntax_Trees.Node_Index);
    pragma No_Return (Raise_Programmer_Error);
 
    procedure Raise_Programmer_Error
      (Label : in String;
+      Data  : in User_Data_Type;
       Tree  : in WisiToken.Syntax_Trees.Tree;
       Node  : in WisiToken.Syntax_Trees.Node_Index)
    is begin
-      raise SAL.Programmer_Error with Label & 
WisiToken.Syntax_Trees.Node_Index'Image (Node) &
-        ":" & Tree.Image (Node, Wisitoken_Grammar_Actions.Descriptor, 
Include_Children => True);
+      WisiToken.Syntax_Trees.LR_Utils.Raise_Programmer_Error
+        (Label, Wisitoken_Grammar_Actions.Descriptor, Data.Grammar_Lexer, 
Tree, Data.Terminals.all, Node);
    end Raise_Programmer_Error;
 
    function Get_Line
@@ -213,7 +216,7 @@ package body WisiToken_Grammar_Runtime is
                   end;
 
                when others =>
-                  Raise_Programmer_Error ("Get_RHS; unimplimented token", 
Tree, I);
+                  Raise_Programmer_Error ("Get_RHS; unimplimented token", 
Data, Tree, I);
                end case;
             end loop;
 
@@ -241,7 +244,7 @@ package body WisiToken_Grammar_Runtime is
       declare
          use Ada.Exceptions;
       begin
-         Raise_Programmer_Error ("Get_RHS: " & Exception_Name (E) & ": " & 
Exception_Message (E), Tree, Token);
+         Raise_Programmer_Error ("Get_RHS: " & Exception_Name (E) & ": " & 
Exception_Message (E), Data, Tree, Token);
       end;
    end Get_RHS;
 
@@ -282,7 +285,7 @@ package body WisiToken_Grammar_Runtime is
          Data.Ignore_Lines := False;
 
       when others =>
-         Raise_Programmer_Error ("Get_Right_Hand_Sides", Tree, Token);
+         Raise_Programmer_Error ("Get_Right_Hand_Sides", Data, Tree, Token);
       end case;
    end Get_Right_Hand_Sides;
 
@@ -357,7 +360,7 @@ package body WisiToken_Grammar_Runtime is
          if Data.Terminals.Length = 0 then
             Data.Non_Grammar (0).Append (Token);
          else
-            Data.Non_Grammar.Set_Last (Data.Terminals.Last_Index);
+            Data.Non_Grammar.Set_First_Last (0, Data.Terminals.Last_Index);
             Data.Non_Grammar (Data.Terminals.Last_Index).Append (Token);
          end if;
       end if;
@@ -495,17 +498,21 @@ package body WisiToken_Grammar_Runtime is
       when Syntax_Trees.Nonterm =>
          --  must be token_keyword_non_grammar
          declare
-            Children   : Syntax_Trees.Valid_Node_Index_Array renames 
Tree.Children (Tokens (2));
-            Child_1_ID : constant Token_Enum_ID := To_Token_Enum (Tree.ID 
(Children (1)));
+            Children_2 : constant Syntax_Trees.Valid_Node_Index_Array := 
Tree.Children (Tokens (2));
+            Child_1_ID : constant Token_Enum_ID := To_Token_Enum (Tree.ID 
(Children_2 (1)));
          begin
             case Child_1_ID is
             when Wisitoken_Grammar_Actions.TOKEN_ID =>
-
-               WisiToken.BNF.Add_Token
-                 (Data.Tokens.Tokens,
-                  Kind  => Get_Text (Data, Tree, Children (3)),
-                  Name  => Get_Text (Data, Tree, Tokens (3)),
-                  Value => Get_Text (Data, Tree, Tokens (4)));
+               declare
+                  Children_4 : constant Syntax_Trees.Valid_Node_Index_Array := 
Tree.Children (Tokens (4));
+               begin
+                  WisiToken.BNF.Add_Token
+                    (Data.Tokens.Tokens,
+                     Kind         => Get_Text (Data, Tree, Children_2 (3)),
+                     Name         => Get_Text (Data, Tree, Tokens (3)),
+                     Value        => Get_Text (Data, Tree, Children_4 (1)),
+                     Repair_Image => (if Children_4'Length = 1 then "" else 
Get_Text (Data, Tree, Children_4 (2))));
+               end;
 
             when KEYWORD_ID =>
 
@@ -517,7 +524,7 @@ package body WisiToken_Grammar_Runtime is
 
                WisiToken.BNF.Add_Token
                  (Data.Tokens.Non_Grammar,
-                  Kind  => Get_Text (Data, Tree, Children (3)),
+                  Kind  => Get_Text (Data, Tree, Children_2 (3)),
                   Name  => Get_Text (Data, Tree, Tokens (3)),
                   Value => Get_Text (Data, Tree, Tokens (4)));
 
@@ -825,7 +832,7 @@ package body WisiToken_Grammar_Runtime is
               (Data.Grammar_Lexer.File_Name, 1, 1, "duplicate virtual nonterm 
'" & LHS_String & "'");
 
          when others =>
-            Raise_Programmer_Error ("Add_Nonterminal", Tree, LHS_Node);
+            Raise_Programmer_Error ("Add_Nonterminal", Data, Tree, LHS_Node);
          end case;
       else
          Data.Label_Count := Data.Label_Count + Labels.Length;
@@ -862,7 +869,7 @@ package body WisiToken_Grammar_Runtime is
             end;
          end if;
       when Other =>
-         Raise_Programmer_Error ("untranslated EBNF node", Tree, Tree.Parent 
(Tokens (Token)));
+         Raise_Programmer_Error ("untranslated EBNF node", Data, Tree, 
Tree.Parent (Tokens (Token)));
       end case;
    end Check_EBNF;
 
@@ -967,7 +974,7 @@ package body WisiToken_Grammar_Runtime is
                   Node := Children (1);
                   exit;
                else
-                  Raise_Programmer_Error ("first_list_element", Tree, Node);
+                  Raise_Programmer_Error ("first_list_element", Data, Tree, 
Node);
                end if;
             end;
          end loop;
@@ -1151,11 +1158,16 @@ package body WisiToken_Grammar_Runtime is
       end Append_Element;
 
       procedure Insert_Optional_RHS (B : in Valid_Node_Index)
+      with Pre => Tree.ID (B) in +rhs_multiple_item_ID | +rhs_optional_item_ID 
| +IDENTIFIER_ID
       is
          --  B is an optional item in an rhs_item_list :
          --  | a b? c
+         --  | a b* c
+         --
+         --  or B is a virtual identifier naming the new nonterm replacing the
+         --  original.
          --
-         --  Insert a second rhs_item_list without B
+         --  where a, c can be empty. Insert a second rhs_item_list without B.
          --
          --  The containing elment may be rhs or rhs_alternative_list
 
@@ -1205,7 +1217,7 @@ package body WisiToken_Grammar_Runtime is
 
             when others =>
                Raise_Programmer_Error
-                 ("translate_ebnf_to_bnf insert_optional_rhs unimplemented 
RHS", Tree, Container);
+                 ("translate_ebnf_to_bnf insert_optional_rhs unimplemented 
RHS", Data, Tree, Container);
             end case;
          end Add_Actions;
       begin
@@ -1241,7 +1253,12 @@ package body WisiToken_Grammar_Runtime is
                New_RHS_AC :=
                  (if Tree.ID (Container) = +rhs_ID
                   then Tree.Add_Nonterm ((+rhs_ID, 0), (1 .. 0 => 
Invalid_Node_Index))
-                  else Tree.Add_Nonterm ((+rhs_item_list_ID, 0), (1 .. 0 => 
Invalid_Node_Index)));
+                  else
+                     --  rhs_alternative_list_ID
+                     --  The grammar does not allow an empty alternative in an
+                     --  rhs_alterntive_list; this will be fixed when it is 
converted to an
+                     --  rhs_list.
+                     Tree.Add_Nonterm ((+rhs_item_list_ID, 0), (1 .. 0 => 
Invalid_Node_Index)));
             else
                --  c is empty
                New_RHS_AC :=
@@ -1308,6 +1325,7 @@ package body WisiToken_Grammar_Runtime is
       Compilation_Unit_List_Tail : constant Valid_Node_Index := Tree.Child 
(Tree.Root, 1);
 
       procedure Add_Compilation_Unit (Unit : in Valid_Node_Index; Prepend : in 
Boolean := False)
+      with Pre => Tree.ID (Unit) in +declaration_ID | +nonterminal_ID
       is
          Comp_Unit : constant Valid_Node_Index := Tree.Add_Nonterm
            ((+compilation_unit_ID, (if Tree.ID (Unit) = +declaration_ID then 0 
else 1)),
@@ -1337,6 +1355,7 @@ package body WisiToken_Grammar_Runtime is
       end To_RHS_List;
 
       function Convert_RHS_Alternative (Content : in Valid_Node_Index) return 
Valid_Node_Index
+      with Pre => Tree.ID (Content) = +rhs_alternative_list_ID
       is
          --  Convert rhs_alternative_list rooted at Content to an rhs_list
          Node : Valid_Node_Index := Content;
@@ -1359,14 +1378,29 @@ package body WisiToken_Grammar_Runtime is
             --  | rhs: new
             --  | | rhs_item_list: keep Node,Child (3)
 
-            Tree.Set_Children
-              (Node,
-               (+rhs_list_ID, 1),
-               (1 => Tree.Child (Node, 1),
-                2 => Tree.Child (Node, 2),
-                3 => Tree.Add_Nonterm
-                  ((+rhs_ID, 1),
-                   (1 => Tree.Child (Node, 3)))));
+            if Tree.Is_Empty (Tree.Child (Node, 3)) then
+               --  Convert empty rhs_item_list to empty rhs
+               Tree.Set_Children
+                 (Tree.Child (Node, 3),
+                  (+rhs_ID, 0),
+                  (1 .. 0 => Invalid_Node_Index));
+
+               Tree.Set_Children
+                 (Node,
+                  (+rhs_list_ID, 1),
+                  (1 => Tree.Child (Node, 1),
+                   2 => Tree.Child (Node, 2),
+                   3 => Tree.Child (Node, 3)));
+            else
+               Tree.Set_Children
+                 (Node,
+                  (+rhs_list_ID, 1),
+                  (1 => Tree.Child (Node, 1),
+                   2 => Tree.Child (Node, 2),
+                   3 => Tree.Add_Nonterm
+                     ((+rhs_ID, 1),
+                      (1 => Tree.Child (Node, 3)))));
+            end if;
 
             Clear_EBNF_Node (Node);
             Node := Tree.Child (Node, 1);
@@ -1393,7 +1427,7 @@ package body WisiToken_Grammar_Runtime is
       procedure New_Nonterminal
         (New_Identifier : in Identifier_Index;
          Content        : in Valid_Node_Index)
-         with Pre => To_Token_Enum (Tree.ID (Content)) in 
rhs_alternative_list_ID | rhs_element_ID
+      with Pre => To_Token_Enum (Tree.ID (Content)) in rhs_alternative_list_ID 
| rhs_element_ID
       is
          --  Convert subtree rooted at Content to an rhs_list contained by a 
new nonterminal
          --  named New_Identifier.
@@ -1404,8 +1438,7 @@ package body WisiToken_Grammar_Runtime is
               (case To_Token_Enum (Tree.ID (Content)) is
                when rhs_element_ID          => To_RHS_List (Content),
                when rhs_alternative_list_ID => Convert_RHS_Alternative 
(Content),
-               when others => raise SAL.Programmer_Error with "new_nonterminal 
unimplemented content" &
-                 Tree.Image (Content, Wisitoken_Grammar_Actions.Descriptor)),
+               when others => raise SAL.Programmer_Error),
             Child_4   => Tree.Add_Nonterm
               ((+semicolon_opt_ID, 0),
                (1     => Tree.Add_Terminal (+SEMICOLON_ID))));
@@ -1418,6 +1451,8 @@ package body WisiToken_Grammar_Runtime is
          RHS_Element_1 : in Valid_Node_Index;
          RHS_Element_3 : in Valid_Node_Index;
          Byte_Region   : in Buffer_Region)
+      with Pre => Tree.ID (RHS_Element_1) = +rhs_element_ID and
+                  Tree.ID (RHS_Element_3) = +rhs_element_ID
       is
          --  nonterminal: foo_list
          --  | IDENTIFIER: "foo_list" List_Nonterm
@@ -1528,7 +1563,7 @@ package body WisiToken_Grammar_Runtime is
 
          when rhs_alternative_list_ID =>
             --  All handled by New_Nonterminal*
-            raise SAL.Not_Implemented with Tree.Image (Node, 
Wisitoken_Grammar_Actions.Descriptor);
+            raise SAL.Programmer_Error;
 
          when rhs_attribute_ID =>
             --  Just delete it
@@ -1559,7 +1594,7 @@ package body WisiToken_Grammar_Runtime is
             begin
                if Tree.RHS_Index (RHS_Item_List) /= 0 then
                   --  Not first
-                  Raise_Programmer_Error ("translate_ebnf_to_bnf 
rhs_attribute_id unimplemented", Tree, Node);
+                  Raise_Programmer_Error ("translate_ebnf_to_bnf 
rhs_attribute_id unimplemented", Data, Tree, Node);
                end if;
 
                Tree.Set_Children
@@ -1611,7 +1646,7 @@ package body WisiToken_Grammar_Runtime is
                            when Virtual_Identifier =>
                               New_Ident := Tree.Identifier (Name_Node);
                            when others =>
-                              Raise_Programmer_Error ("process_node 
rhs_group_item", Tree, Name_Node);
+                              Raise_Programmer_Error ("process_node 
rhs_group_item", Data, Tree, Name_Node);
                            end case;
 
                            exit;
@@ -1643,9 +1678,10 @@ package body WisiToken_Grammar_Runtime is
             --  | a b+ c
             --  | a b* c
             --
-            --  Replace it with a new canonical list nonterminal:
+            --  where a and/or c can be empty. Replace it with a new canonical
+            --  list nonterminal:
             --
-            --  nonterminal_nnn
+            --  nonterminal_nnn_list
             --  : b
             --  | nonterminal_nnn_list b
             --
@@ -1693,7 +1729,7 @@ package body WisiToken_Grammar_Runtime is
                   --
                   --  The tokens may have labels.
                   --
-                  --  Handling these cases specially this eliminates a 
conflict between
+                  --  Handling these cases specially eliminates a conflict 
between
                   --  reducing to enumConstants and reducing to the introduced 
nonterm
                   --  list.
                   --
@@ -1720,6 +1756,7 @@ package body WisiToken_Grammar_Runtime is
                   then
                      return;
                   end if;
+                  if Invalid_Node_Index /= Prev_List_Element (Element_1, 
+rhs_item_list_ID) then return; end if;
                   if Invalid_Node_Index /= Next_List_Element (RHS_Element, 
+rhs_item_list_ID) then return; end if;
                   if Invalid_Node_Index /= Next_List_Element (RHS_2, 
+rhs_list_ID) or
                     Invalid_Node_Index /= Prev_List_Element (RHS_2, 
+rhs_list_ID)
@@ -1893,7 +1930,7 @@ package body WisiToken_Grammar_Runtime is
                            when others =>
                               Raise_Programmer_Error
                                 ("unimplemented Find_List_Nonterminal_2 case 
'" & Element_Content & "'",
-                                 Tree, Name_Node);
+                                 Data, Tree, Name_Node);
                            end case;
 
                            --  list nonterm is the next nonterminal
@@ -1969,7 +2006,7 @@ package body WisiToken_Grammar_Runtime is
                                     when others =>
                                        Raise_Programmer_Error
                                          ("unimplemented 
Find_List_Nonterminal_1 case '" & Element_Content & "'",
-                                          Tree, Name_Node);
+                                          Data, Tree, Name_Node);
                                     end case;
 
                                     exit;
@@ -2031,7 +2068,7 @@ package body WisiToken_Grammar_Runtime is
                   end if;
 
                when others =>
-                  Raise_Programmer_Error ("translate_ebnf_to_bnf 
rhs_multiple_item unimplmented", Tree, Node);
+                  Raise_Programmer_Error ("translate_ebnf_to_bnf 
rhs_multiple_item unimplmented", Data, Tree, Node);
                end case;
 
                if Allow_Empty then
@@ -2060,7 +2097,8 @@ package body WisiToken_Grammar_Runtime is
             exception
             when E : System.Assertions.Assert_Failure =>
                Raise_Programmer_Error
-                 ("translate_ebnf_to_bnf multiple_item assert: " & 
Ada.Exceptions.Exception_Message (E), Tree, Node);
+                 ("translate_ebnf_to_bnf multiple_item assert: " & 
Ada.Exceptions.Exception_Message (E),
+                  Data, Tree, Node);
             end;
 
          when rhs_optional_item_ID =>
@@ -2137,7 +2175,7 @@ package body WisiToken_Grammar_Runtime is
                            when Shared_Terminal =>
                               Name_Terminal := Tree.Min_Terminal_Index 
(Name_Identifier_Node);
                            when others =>
-                              Raise_Programmer_Error ("unhandled rhs_optional 
case ", Tree, Name_Identifier_Node);
+                              Raise_Programmer_Error ("unhandled rhs_optional 
case ", Data, Tree, Name_Identifier_Node);
                            end case;
                         end;
                      end if;
@@ -2161,7 +2199,8 @@ package body WisiToken_Grammar_Runtime is
                                     Name_Ident := Tree.Identifier 
(Name_Identifier_Node);
                                  when others =>
                                     Raise_Programmer_Error
-                                      ("unhandled rhs_optional case '" & 
New_Text & "'", Tree, Name_Identifier_Node);
+                                      ("unhandled rhs_optional case '" & 
New_Text & "'",
+                                       Data, Tree, Name_Identifier_Node);
                                  end case;
                                  exit;
                               end if;
@@ -2295,7 +2334,7 @@ package body WisiToken_Grammar_Runtime is
                   Tree.Set_Children (Tree.Parent (Node), (+rhs_item_ID, 1), (1 
=> Tree.Child (Node, 1)));
 
                when others =>
-                  Raise_Programmer_Error ("translate_ebnf_to_bnf 
rhs_optional_item unimplmented", Tree, Node);
+                  Raise_Programmer_Error ("translate_ebnf_to_bnf 
rhs_optional_item unimplmented", Data, Tree, Node);
                end case;
 
                Clear_EBNF_Node (Node);
@@ -2374,7 +2413,7 @@ package body WisiToken_Grammar_Runtime is
                         (1 => Tree.Add_Identifier (+IDENTIFIER_ID, Name_Ident, 
Tree.Byte_Region (Node))));
 
                   when others =>
-                     Raise_Programmer_Error ("translate_ebnf_to_bnf 
string_literal_2 unimplemented", Tree, Node);
+                     Raise_Programmer_Error ("translate_ebnf_to_bnf 
string_literal_2 unimplemented", Data, Tree, Node);
                   end case;
                end;
 
@@ -2409,7 +2448,7 @@ package body WisiToken_Grammar_Runtime is
             end;
 
          when others =>
-            Raise_Programmer_Error ("unimplemented EBNF node", Tree, Node);
+            Raise_Programmer_Error ("unimplemented EBNF node", Data, Tree, 
Node);
          end case;
       exception
       when SAL.Programmer_Error =>
@@ -2418,7 +2457,7 @@ package body WisiToken_Grammar_Runtime is
          Raise_Programmer_Error
            ("unhandled exception " & Ada.Exceptions.Exception_Name (E) & ": " &
               Ada.Exceptions.Exception_Message (E),
-            Tree, Node);
+            Data, Tree, Node);
       end Process_Node;
 
    begin
@@ -2452,10 +2491,6 @@ package body WisiToken_Grammar_Runtime is
             Ada.Text_IO.Put_Line (Base_Identifier_Index'Image (I) & " " & 
(-Data.Tokens.Virtual_Identifiers (I)));
          end loop;
       end if;
-   exception
-   when E : SAL.Not_Implemented =>
-      Ada.Text_IO.Put_Line
-        (Ada.Text_IO.Standard_Error, "Translate_EBNF_To_BNF not implemented: " 
& Ada.Exceptions.Exception_Message (E));
    end Translate_EBNF_To_BNF;
 
    procedure Print_Source
@@ -2562,7 +2597,7 @@ package body WisiToken_Grammar_Runtime is
             use Ada.Exceptions;
          begin
             Raise_Programmer_Error
-              ("Put_RHS_Element: " & Exception_Name (E) & ": " & 
Exception_Message (E), Tree, Node);
+              ("Put_RHS_Element: " & Exception_Name (E) & ": " & 
Exception_Message (E), Data, Tree, Node);
          end;
       end Put_RHS_Element;
 
@@ -2587,7 +2622,7 @@ package body WisiToken_Grammar_Runtime is
             use Ada.Exceptions;
          begin
             Raise_Programmer_Error
-              ("Put_RHS_Item_List: " & Exception_Name (E) & ": " & 
Exception_Message (E), Tree, Node);
+              ("Put_RHS_Item_List: " & Exception_Name (E) & ": " & 
Exception_Message (E), Data, Tree, Node);
          end;
       end Put_RHS_Item_List;
 
@@ -2634,7 +2669,7 @@ package body WisiToken_Grammar_Runtime is
             end if;
 
          when others =>
-            Raise_Programmer_Error ("Put_RHS", Tree, Node);
+            Raise_Programmer_Error ("Put_RHS", Data, Tree, Node);
          end case;
       exception
       when SAL.Programmer_Error =>
@@ -2644,7 +2679,7 @@ package body WisiToken_Grammar_Runtime is
          declare
             use Ada.Exceptions;
          begin
-            Raise_Programmer_Error ("Put_RHS: " & Exception_Name (E) & ": " & 
Exception_Message (E), Tree, Node);
+            Raise_Programmer_Error ("Put_RHS: " & Exception_Name (E) & ": " & 
Exception_Message (E), Data, Tree, Node);
          end;
       end Put_RHS;
 
@@ -2673,7 +2708,7 @@ package body WisiToken_Grammar_Runtime is
             Put_Comments (Node);
 
          when others =>
-            Raise_Programmer_Error ("Put_RHS_List", Tree, Node);
+            Raise_Programmer_Error ("Put_RHS_List", Data, Tree, Node);
          end case;
       exception
       when SAL.Programmer_Error =>
@@ -2683,7 +2718,8 @@ package body WisiToken_Grammar_Runtime is
          declare
             use Ada.Exceptions;
          begin
-            Raise_Programmer_Error ("Put_RHS_List: " & Exception_Name (E) & ": 
" & Exception_Message (E), Tree, Node);
+            Raise_Programmer_Error
+              ("Put_RHS_List: " & Exception_Name (E) & ": " & 
Exception_Message (E), Data, Tree, Node);
          end;
       end Put_RHS_List;
 



reply via email to

[Prev in Thread] Current Thread [Next in Thread]