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

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

[elpa] externals/wisi 2636b79 25/35: Release ada-mode 6.1.0, wisi 2.1.0


From: Stefan Monnier
Subject: [elpa] externals/wisi 2636b79 25/35: Release ada-mode 6.1.0, wisi 2.1.0
Date: Sat, 28 Nov 2020 14:47:55 -0500 (EST)

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

    Release ada-mode 6.1.0, wisi 2.1.0
---
 NEWS                                               |   13 +
 README                                             |    4 +-
 build-wisitoken-bnf-generate.sh                    |    0
 sal-gen_array_image.adb                            |   34 +
 sal-gen_array_image.ads                            |   24 +
 sal-gen_bounded_definite_vectors.ads               |    6 +-
 sal-gen_definite_doubly_linked_lists.ads           |    6 +-
 sal-gen_definite_doubly_linked_lists_sorted.ads    |    8 +-
 sal-gen_indefinite_doubly_linked_lists.ads         |    5 +-
 sal-gen_unbounded_definite_min_heaps_fibonacci.adb |   33 +-
 sal-gen_unbounded_definite_min_heaps_fibonacci.ads |   13 +-
 sal-gen_unbounded_definite_queues.ads              |    4 +-
 sal-gen_unbounded_definite_red_black_trees.ads     |    5 +-
 sal-gen_unbounded_definite_stacks.ads              |    3 +-
 sal-gen_unbounded_definite_vectors.adb             |   27 +-
 sal-gen_unbounded_definite_vectors.ads             |   23 +-
 sal.adb                                            |    4 +-
 standard_common.gpr                                |   29 +-
 wisi-elisp-parse.el                                |   46 +-
 wisi-fringe.el                                     |   10 +-
 wisi-parse-common.el                               |  694 +++---
 wisi-process-parse.el                              |  228 +-
 wisi.adb                                           |  404 +++-
 wisi.ads                                           |   56 +-
 wisi.el                                            |  581 +++--
 wisitoken-bnf-generate.adb                         |    4 +-
 wisitoken-bnf-generate_utils.adb                   |   14 +-
 wisitoken-bnf-generate_utils.ads                   |    2 +-
 wisitoken-bnf-output_ada.adb                       |   13 +-
 wisitoken-bnf-output_ada_common.adb                |   62 +-
 wisitoken-bnf-output_ada_emacs.adb                 |  156 +-
 wisitoken-bnf.ads                                  |   15 +-
 wisitoken-gen_token_enum.ads                       |    6 +-
 wisitoken-generate-lr-lalr_generate.adb            |   21 +-
 wisitoken-generate-lr-lalr_generate.ads            |   19 +-
 wisitoken-generate-lr-lr1_generate.adb             |   28 +-
 wisitoken-generate-lr-lr1_generate.ads             |   11 +-
 wisitoken-generate-lr.adb                          | 2397 ++++++++++----------
 wisitoken-generate-lr.ads                          |   59 +-
 wisitoken-generate-lr1_items.adb                   |   30 +-
 wisitoken-generate-lr1_items.ads                   |   17 +-
 wisitoken-generate.adb                             |   63 +-
 wisitoken-generate.ads                             |   17 +-
 wisitoken-lexer-re2c.adb                           |  146 +-
 wisitoken-lexer-re2c.ads                           |   35 +-
 wisitoken-lexer-regexp.adb                         |   48 +-
 wisitoken-lexer-regexp.ads                         |   34 +-
 wisitoken-lexer.adb                                |    7 +-
 wisitoken-lexer.ads                                |   52 +-
 wisitoken-parse-lr-mckenzie_recover-base.ads       |    5 +-
 wisitoken-parse-lr-mckenzie_recover-explore.adb    |  457 ++--
 wisitoken-parse-lr-mckenzie_recover-parse.adb      |   12 +-
 wisitoken-parse-lr-mckenzie_recover.adb            |  291 ++-
 wisitoken-parse-lr-mckenzie_recover.ads            |   25 +-
 wisitoken-parse-lr-parser.adb                      |  387 ++--
 wisitoken-parse-lr-parser.ads                      |   27 +-
 wisitoken-parse-lr-parser_lists.adb                |   31 +-
 wisitoken-parse-lr-parser_lists.ads                |   29 +-
 wisitoken-parse-lr-parser_no_recover.adb           | 1002 ++++----
 wisitoken-parse-lr-parser_no_recover.ads           |    4 +-
 wisitoken-parse-lr.adb                             |  246 +-
 wisitoken-parse-lr.ads                             |   56 +-
 wisitoken-parse-packrat-generated.adb              |    9 +-
 wisitoken-parse-packrat-generated.ads              |    9 +-
 wisitoken-parse-packrat-procedural.adb             |    7 +-
 wisitoken-parse-packrat-procedural.ads             |    7 +-
 wisitoken-parse.adb                                |    6 +-
 wisitoken-parse.ads                                |    7 +-
 wisitoken-parse_table-mode.el                      |   96 +
 wisitoken-productions.ads                          |   16 +-
 wisitoken-semantic_checks.adb                      |   19 +-
 wisitoken-semantic_checks.ads                      |   18 +-
 wisitoken-syntax_trees.adb                         |   89 +-
 wisitoken-syntax_trees.ads                         |   36 +-
 wisitoken-text_io_trace.adb                        |   55 +-
 wisitoken-text_io_trace.ads                        |   13 +-
 wisitoken-user_guide.info                          |   16 +-
 wisitoken.adb                                      |    4 +-
 wisitoken.ads                                      |  881 +++----
 wisitoken_grammar_actions.ads                      |    2 +-
 wisitoken_grammar_main.adb                         |   64 +-
 wisitoken_grammar_re2c.c                           |  143 +-
 wisitoken_grammar_runtime.adb                      |   11 +-
 wisitoken_grammar_runtime.ads                      |    1 -
 84 files changed, 5494 insertions(+), 4103 deletions(-)

diff --git a/NEWS b/NEWS
index a594bea..22fe4e4 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,19 @@ 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 2.1.0
+21 Mar 2019
+
+** Add support for partial parsing; useful in very large files. Files
+   larger than wisi-partial-parse-threshold (default 100_001) will be
+   parsed partially.
+
+** Miscellaneous speed-ups in the Ada code; 'pragma Inline', better use of Ada 
tasks.
+
+** Better error correction in the Ada process parser; insert minimimal
+  tokens to complete a statement/declaration before or after the error
+  point.
+
 * wisi 2.0.1
 8 Dec 2018
 
diff --git a/README b/README
index 0435747..db1af26 100644
--- a/README
+++ b/README
@@ -1,7 +1,9 @@
-Emacs wisi package 2.0.0
+Emacs wisi package 2.1.0
 
 The wisi package provides utilities for using generalized LALR parsers
 (in elisp or external processes) to do indentation, fontification, and
 navigation. See ada-mode for an example of its use.
 
+It also provides wisitoken-parse_table-mode, for navigating the
+diagnostic parse tables output by wisitoken-bnf-generate.
 
diff --git a/build-wisitoken-bnf-generate.sh b/build-wisitoken-bnf-generate.sh
old mode 100644
new mode 100755
diff --git a/sal-gen_array_image.adb b/sal-gen_array_image.adb
new file mode 100644
index 0000000..7f9097d
--- /dev/null
+++ b/sal-gen_array_image.adb
@@ -0,0 +1,34 @@
+--  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);
+
+with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
+function SAL.Gen_Array_Image (Item : in Array_Type) return String
+is
+   Result : Unbounded_String := To_Unbounded_String ("(");
+begin
+   for I in Item'Range loop
+      Result := Result & Element_Image (Item (I));
+      if I = Item'Last then
+         Result := Result & ")";
+      else
+         Result := Result & ", ";
+      end if;
+   end loop;
+   return To_String (Result);
+end SAL.Gen_Array_Image;
diff --git a/sal-gen_array_image.ads b/sal-gen_array_image.ads
new file mode 100644
index 0000000..53bd25f
--- /dev/null
+++ b/sal-gen_array_image.ads
@@ -0,0 +1,24 @@
+--  Abstract :
+--
+--  Image for normal Ada array types
+--
+--  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 Index_Type is (<>);
+   type Element_Type is private;
+   type Array_Type is array (Index_Type) of Element_Type;
+   with function Element_Image (Item : in Element_Type) return String;
+function SAL.Gen_Array_Image (Item : in Array_Type) return String;
diff --git a/sal-gen_bounded_definite_vectors.ads 
b/sal-gen_bounded_definite_vectors.ads
index 04a9ff1..9747704 100644
--- a/sal-gen_bounded_definite_vectors.ads
+++ b/sal-gen_bounded_definite_vectors.ads
@@ -3,7 +3,7 @@
 --  A simple bounded vector of definite items, intended to be faster
 --  than Ada.Containers.Bounded_Definite_Vectors.
 --
---  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
@@ -80,6 +80,7 @@ package SAL.Gen_Bounded_Definite_Vectors is
    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;
@@ -88,6 +89,7 @@ package SAL.Gen_Bounded_Definite_Vectors is
      (Container : aliased in out Vector;
       Index     :         in     Index_Type)
      return Variable_Reference_Type;
+   pragma Inline (Variable_Reference);
 
    type Cursor is private;
 
@@ -98,11 +100,13 @@ package SAL.Gen_Bounded_Definite_Vectors is
    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
 
diff --git a/sal-gen_definite_doubly_linked_lists.ads 
b/sal-gen_definite_doubly_linked_lists.ads
index 04a10f1..fda94c4 100644
--- a/sal-gen_definite_doubly_linked_lists.ads
+++ b/sal-gen_definite_doubly_linked_lists.ads
@@ -3,7 +3,7 @@
 --  A generic doubly linked list with definite elements, allowing
 --  permanent references to 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
@@ -97,16 +97,20 @@ package SAL.Gen_Definite_Doubly_Linked_Lists is
 
    function Constant_Reference (Container : in List; Position : in Cursor) 
return Constant_Reference_Type
    with Pre => Position /= No_Element;
+   pragma Inline (Constant_Reference);
    function Constant_Ref (Position : in Cursor) return Constant_Reference_Type
    with Pre => Position /= No_Element;
+   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);
 
    package Iterator_Interfaces is new Ada.Iterator_Interfaces (Cursor, 
Has_Element);
 
diff --git a/sal-gen_definite_doubly_linked_lists_sorted.ads 
b/sal-gen_definite_doubly_linked_lists_sorted.ads
index b3d5431..d57748b 100644
--- a/sal-gen_definite_doubly_linked_lists_sorted.ads
+++ b/sal-gen_definite_doubly_linked_lists_sorted.ads
@@ -2,7 +2,7 @@
 --
 --  A generic sorted doubly linked list with definite elements.
 --
---  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
@@ -47,6 +47,8 @@ package SAL.Gen_Definite_Doubly_Linked_Lists_Sorted is
    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.
 
@@ -113,15 +115,19 @@ package SAL.Gen_Definite_Doubly_Linked_Lists_Sorted is
    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);
diff --git a/sal-gen_indefinite_doubly_linked_lists.ads 
b/sal-gen_indefinite_doubly_linked_lists.ads
index 5898bc7..0ebfc54 100644
--- a/sal-gen_indefinite_doubly_linked_lists.ads
+++ b/sal-gen_indefinite_doubly_linked_lists.ads
@@ -3,7 +3,7 @@
 --  A generic doubly linked list with indefinite elements, allowing
 --  permanent references to elements.
 --
---  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
@@ -71,15 +71,18 @@ package SAL.Gen_Indefinite_Doubly_Linked_Lists is
 
    function Constant_Reference (Position : in Cursor) return 
Constant_Reference_Type
    with Pre => Has_Element (Position);
+   pragma Inline (Constant_Reference);
 
    function Constant_Ref (Container : in List'Class; Position : in Peek_Type) 
return Constant_Reference_Type
    with Pre => Position <= Container.Length;
+   pragma Inline (Constant_Ref);
 
    type Reference_Type (Element : not null access Element_Type) is null record
    with Implicit_Dereference => Element;
 
    function Reference (Position : in Cursor) return Reference_Type
    with Pre => Has_Element (Position);
+   pragma Inline (Reference);
 
 private
    type Node_Type;
diff --git a/sal-gen_unbounded_definite_min_heaps_fibonacci.adb 
b/sal-gen_unbounded_definite_min_heaps_fibonacci.adb
index e35219e..c58a9d4 100644
--- a/sal-gen_unbounded_definite_min_heaps_fibonacci.adb
+++ b/sal-gen_unbounded_definite_min_heaps_fibonacci.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
@@ -337,4 +337,35 @@ package body 
SAL.Gen_Unbounded_Definite_Min_Heaps_Fibonacci is
       return (Element => Heap.Min.all.Element'Access);
    end Peek;
 
+   procedure Process (Heap : in Heap_Type; Process_Element : access procedure 
(Element : in Element_Type))
+   is
+      type Cursor is record
+         --  Every node is in a circular list. List_Origin is the node where we
+         --  entered the list, so we know when we are done.
+         Node        : Node_Access;
+         List_Origin : Node_Access;
+      end record;
+
+      Cur : Cursor := (Heap.Min, Heap.Min);
+
+      procedure Process_Node (Cur : in out Cursor)
+      is
+         Next_Cur : Cursor;
+      begin
+         loop
+            if Cur.Node.Child /= null then
+               Next_Cur := (Cur.Node.Child, Cur.Node.Child);
+               Process_Node (Next_Cur);
+            end if;
+            Process_Element (Cur.Node.Element);
+            Cur.Node := Cur.Node.Right;
+            exit when Cur.Node = Cur.List_Origin;
+         end loop;
+      end Process_Node;
+   begin
+      if Cur.Node /= null then
+         Process_Node (Cur);
+      end if;
+   end Process;
+
 end SAL.Gen_Unbounded_Definite_Min_Heaps_Fibonacci;
diff --git a/sal-gen_unbounded_definite_min_heaps_fibonacci.ads 
b/sal-gen_unbounded_definite_min_heaps_fibonacci.ads
index 09b4e39..6f978e7 100644
--- a/sal-gen_unbounded_definite_min_heaps_fibonacci.ads
+++ b/sal-gen_unbounded_definite_min_heaps_fibonacci.ads
@@ -7,7 +7,7 @@
 --  [1] Introduction to Algorithms, Third Edition. Thomas H. Cormen,
 --  Charles E. Leiserson, Ronald L. Rivest, Clifford Stein. Chapter 19.
 --
---  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
@@ -88,6 +88,15 @@ package SAL.Gen_Unbounded_Definite_Min_Heaps_Fibonacci is
 
    function Peek (Heap : in Heap_Type) return Constant_Reference_Type;
    --  Return a constant reference to the min element.
+   pragma Inline (Peek);
+
+   --  We don't provide a Cursor/Iterator interface; to complex to
+   --  implement. So far, we only need a read-only forward iterator,
+   --  which Process provides.
+
+   procedure Process (Heap : in Heap_Type; Process_Element : access procedure 
(Element : in Element_Type));
+   --  Call Process_Element with each Element in Heap. Min is first; rest are 
in
+   --  arbitrary order.
 
 private
 
@@ -108,6 +117,8 @@ 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;
 
    Empty_Heap : constant Heap_Type := (Ada.Finalization.Controlled with Min => 
null, Count => 0);
 
diff --git a/sal-gen_unbounded_definite_queues.ads 
b/sal-gen_unbounded_definite_queues.ads
index 0e21be7..8e7163b 100644
--- a/sal-gen_unbounded_definite_queues.ads
+++ b/sal-gen_unbounded_definite_queues.ads
@@ -2,7 +2,7 @@
 --
 --  An unbounded queue of definite non-limited 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
@@ -59,6 +59,7 @@ package SAL.Gen_Unbounded_Definite_Queues is
       Implicit_Dereference => Element;
 
    function Peek (Queue : in Pkg.Queue; N : Peek_Type := 1) return 
Constant_Reference_Type;
+   pragma Inline (Peek);
    --  Return a constant reference to a queue item. N = 1 is the queue
    --  head.
    --
@@ -68,6 +69,7 @@ package SAL.Gen_Unbounded_Definite_Queues is
    with Implicit_Dereference => Element;
 
    function Variable_Peek (Queue : in out Pkg.Queue; N : Peek_Type := 1) 
return Variable_Reference_Type;
+   pragma Inline (Variable_Peek);
    --  Return a variable reference to a queue item. N = 1 is the queue
    --  head.
    --
diff --git a/sal-gen_unbounded_definite_red_black_trees.ads 
b/sal-gen_unbounded_definite_red_black_trees.ads
index e9513c9..83c9c88 100644
--- a/sal-gen_unbounded_definite_red_black_trees.ads
+++ b/sal-gen_unbounded_definite_red_black_trees.ads
@@ -8,7 +8,7 @@
 --  [1] Introduction to Algorithms, Thomas H. Cormen, Charles E.
 --  Leiserson, Ronald L. Rivest, Clifford Stein.
 --
---  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
@@ -70,6 +70,7 @@ package SAL.Gen_Unbounded_Definite_Red_Black_Trees is
      (Container : aliased in Tree;
       Key       :         in Key_Type)
      return Constant_Ref_Type;
+   pragma Inline (Constant_Ref);
 
    type Variable_Ref_Type (Element : not null access Element_Type) is null 
record
    with Implicit_Dereference => Element;
@@ -78,11 +79,13 @@ package SAL.Gen_Unbounded_Definite_Red_Black_Trees is
      (Container : aliased in Tree;
       Position  :         in Cursor)
      return Variable_Ref_Type;
+   pragma Inline (Variable_Ref);
 
    function Variable_Ref
      (Container : aliased in Tree;
       Key       :         in Key_Type)
      return Variable_Ref_Type;
+   pragma Inline (Variable_Ref);
    --  Raises Not_Found if Key not found in Container.
 
    package Iterators is new Ada.Iterator_Interfaces (Cursor, Has_Element);
diff --git a/sal-gen_unbounded_definite_stacks.ads 
b/sal-gen_unbounded_definite_stacks.ads
index b0cb65c..954be19 100644
--- a/sal-gen_unbounded_definite_stacks.ads
+++ b/sal-gen_unbounded_definite_stacks.ads
@@ -2,7 +2,7 @@
 --
 --  Stack implementation.
 --
---  Copyright (C) 1998-2000, 2002-2003, 2009, 2015, 2017, 2018 Free Software 
Foundation, Inc.
+--  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
@@ -113,6 +113,7 @@ package SAL.Gen_Unbounded_Definite_Stacks is
      (Container : aliased in Stack'Class;
       Position  :         in Peek_Type)
      return Constant_Ref_Type;
+   pragma Inline (Constant_Ref);
 
 private
 
diff --git a/sal-gen_unbounded_definite_vectors.adb 
b/sal-gen_unbounded_definite_vectors.adb
index 74f3497..49d59a6 100644
--- a/sal-gen_unbounded_definite_vectors.adb
+++ b/sal-gen_unbounded_definite_vectors.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  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
@@ -352,7 +352,7 @@ package body SAL.Gen_Unbounded_Definite_Vectors is
 
       if Container.Last >= First then
          if Container.Elements = null then
-            Container.Elements := new Array_Type'(J .. To_Peek_Type 
(Container.Last) => <>);
+            Container.Elements := new Array_Type'(J .. To_Peek_Type 
(Container.Last) => Default_Element);
 
          elsif Container.Elements'First > J then
             Grow (Container.Elements, J);
@@ -371,7 +371,7 @@ package body SAL.Gen_Unbounded_Definite_Vectors is
 
       if Last >= Container.First then
          if Container.Elements = null then
-            Container.Elements := new Array_Type'(To_Peek_Type 
(Container.First) .. J => <>);
+            Container.Elements := new Array_Type'(To_Peek_Type 
(Container.First) .. J => Default_Element);
 
          elsif Container.Elements'Last < J then
             Grow (Container.Elements, J);
@@ -379,7 +379,10 @@ package body SAL.Gen_Unbounded_Definite_Vectors is
       end if;
    end Set_Last;
 
-   procedure Set_First_Last (Container : in out Vector; First : in Index_Type; 
Last : in Extended_Index)
+   procedure Set_First_Last
+     (Container : in out Vector;
+      First     : in     Index_Type;
+      Last      : in     Extended_Index)
    is begin
       Set_First (Container, First);
       Set_Last (Container, Last);
@@ -398,22 +401,6 @@ package body SAL.Gen_Unbounded_Definite_Vectors is
       end if;
    end Set_Length;
 
-   procedure Set_Length
-     (Container : in out Vector;
-      Length    : in     Ada.Containers.Count_Type;
-      Default   : in     Element_Type)
-   is
-      Old_First : constant Extended_Index := Container.First;
-      Old_Last  : constant Extended_Index := Container.Last;
-   begin
-      Set_Length (Container, Length);
-      if Old_First = No_Index then
-         Container.Elements.all := (others => Default);
-      else
-         Container.Elements (To_Peek_Type (Old_Last + 1) .. To_Peek_Type 
(Container.Last)) := (others => Default);
-      end if;
-   end Set_Length;
-
    procedure Delete (Container : in out Vector; Index : in Index_Type)
    is
       J : constant Peek_Type := To_Peek_Type (Index);
diff --git a/sal-gen_unbounded_definite_vectors.ads 
b/sal-gen_unbounded_definite_vectors.ads
index 6ad1684..ae472d8 100644
--- a/sal-gen_unbounded_definite_vectors.ads
+++ b/sal-gen_unbounded_definite_vectors.ads
@@ -8,7 +8,7 @@
 --  It provides no checking of cursor tampering; higher level code
 --  must ensure that.
 --
---  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
@@ -29,6 +29,7 @@ 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
@@ -114,21 +115,17 @@ package SAL.Gen_Unbounded_Definite_Vectors is
 
    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);
+   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
-   --  Element_Type default value.
+   --  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
-   --  Element_Type default value.
-
-   procedure Set_Length
-     (Container : in out Vector;
-      Length    : in     Ada.Containers.Count_Type;
-      Default   : in     Element_Type);
-   --  Set Last so Container.Length returns Length. New elements have
-   --  Default value.
+   --  Default_Element value.
 
    procedure Delete (Container : in out Vector; Index : in Index_Type);
    --  Replace Index element contents with default. If Index =
@@ -142,12 +139,14 @@ package SAL.Gen_Unbounded_Definite_Vectors is
 
    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;
 
@@ -173,9 +172,11 @@ package SAL.Gen_Unbounded_Definite_Vectors is
 
    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
 
diff --git a/sal.adb b/sal.adb
index 13a5276..6a2dc71 100644
--- a/sal.adb
+++ b/sal.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  Copyright (C) 1997 - 2004, 2006, 2009 Free Software Foundation, Inc.
+--  Copyright (C) 1997 - 2004, 2006, 2009, 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
@@ -26,7 +26,7 @@ package body SAL is
 
    function Version return String is
    begin
-      return "SAL 3.0";
+      return "SAL 3.1";
    end Version;
 
 end SAL;
diff --git a/standard_common.gpr b/standard_common.gpr
index b51f8ed..2ba758a 100644
--- a/standard_common.gpr
+++ b/standard_common.gpr
@@ -2,7 +2,7 @@
 --
 --  Standard settings for all of Stephe's Ada projects.
 --
---  Copyright (C) 2018 Free Software Foundation, Inc.
+--  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
@@ -27,6 +27,15 @@ project Standard_Common is
 
    type Mem_Check_Type is ("On", "Off");
    Mem_Check : Profile_Type := External ("Standard_Common_Mem_Check", "Off");
+   --  Note that Mem_Check requires Build_Type = Debug to be useful
+
+   -- In main project file, add:
+   -- case Standard_Common.Mem_Check is
+   -- when "On" =>
+   --    for Runtime ("Ada") use "adalib_debug";
+   -- when "Off" =>
+   --    null;
+   -- end case;
 
    package Compiler is
       -- Switches for gcc
@@ -41,10 +50,11 @@ project Standard_Common is
          "-fstack-check",
          "-g",
          "-gnat2012",
-         "-gnatfoqQ",
-         "-gnatw.d",
-         "-gnatwaBCeJL",
-         "-gnatyO"
+         "-gnatfqQ", -- f = all error messages, qQ = process semantics, 
generate .ali if syntax errors
+         "-gnatw.d", -- warnings about tags
+         "-gnatwaBCeJL", -- wa = most warnings, wB = no warn on bad fixed 
values, wC = no warn on conditionals
+                         -- we = error on warning, wJ = no warn on 
obsolescent, wL = no warn on elaboration
+         "-gnatyO" -- warn on overriding
         );
 
       --  -gnatVa causes some inline procedures to be non-inlineable;
@@ -55,7 +65,7 @@ project Standard_Common is
          "-gnata",  -- assertions, pre/post-conditions
          "-gnatVa", -- validity checks
          "-gnateE", -- extra info in exceptions
-         "-gnatwaP"
+         "-gnatwaP" -- no warn on Inline
         );
 
       --  -O3 is measurably faster than -O2 for wisitoken generate
@@ -92,6 +102,13 @@ project Standard_Common is
    package Builder is
       --  Switches for gnatmake
       for Default_Switches ("Ada") use ("-C");
+
+      case Profile is
+      when "On" =>
+         for Global_Compilation_Switches ("Ada") use ("-pg");
+      when "Off" =>
+         null;
+      end case;
    end Builder;
 
    --  In project files, normally use this:
diff --git a/wisi-elisp-parse.el b/wisi-elisp-parse.el
index 83ddba2..e757ac8 100644
--- a/wisi-elisp-parse.el
+++ b/wisi-elisp-parse.el
@@ -1,6 +1,6 @@
 ;; wisi-elisp-parse.el --- Wisi parser  -*- lexical-binding:t -*-
 
-;; Copyright (C) 2013-2015, 2017 - 2018  Free Software Foundation, Inc.
+;; Copyright (C) 2013-2015, 2017 - 2019  Free Software Foundation, Inc.
 
 ;; This file is part of GNU Emacs.
 
@@ -85,7 +85,7 @@ point at which that max was spawned.")
 (cl-defmethod wisi-parse-kill ((_parser wisi-elisp-parser))
   nil)
 
-(defvar wisi-elisp-parse--indent
+(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:
@@ -103,25 +103,26 @@ Each element can be one of:
 - list ('anchor (start-id ...) ('anchored id delta))
   for nested anchors.")
 
-(cl-defmethod wisi-parse-current ((parser wisi-elisp-parser))
-  "Parse current buffer from beginning."
-
-  (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-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
@@ -270,6 +271,9 @@ Each element can be one of:
        (wisi-elisp-parse--resolve-anchors))
 
       (t nil))
+
+    ;; Return region parsed.
+    (cons (point-min) (point))
     ))
 
 (defun wisi-elisp-parsers-active-index (parser-states)
diff --git a/wisi-fringe.el b/wisi-fringe.el
index 7f1c10b..82f4c6a 100644
--- a/wisi-fringe.el
+++ b/wisi-fringe.el
@@ -1,6 +1,6 @@
 ;;; wisi-fringe.el --- show approximate error locations in the fringe
 ;;
-;; Copyright (C) 2018  Free Software Foundation, Inc.
+;; Copyright (C) 2018 - 2019  Free Software Foundation, Inc.
 ;;
 ;; This file is part of GNU Emacs.
 ;;
@@ -115,12 +115,16 @@ in the window."
         (rem (- error-line (floor (/ line scale)))))
     (cons (+ window-line-first line) (lsh 1 (min 5 (floor (* rem (* 6 
scale))))))))
 
+(defun wisi-fringe-clean ()
+  "Remove all wisi-fringe marks."
+  (remove-overlays (point-min) (point-max) 'wisi-fringe t))
+
 (defun wisi-fringe-display-errors (positions)
-  "Display a bar in the right fringe for each buffer position in POSITIONS.
+  "Display markers in the left and right fringe for each buffer position in 
POSITIONS.
 The buffer containing POSITIONS must be current, and the window
 displaying that buffer must be current."
   ;; FIXME: recompute fringe display on scroll!
-  (remove-overlays (point-min) (point-max) 'wisi-fringe t)
+  (wisi-fringe-clean)
   (let (scaled-posns
        (buffer-lines (line-number-at-pos (point-max)))
        (window-lines (window-height))
diff --git a/wisi-parse-common.el b/wisi-parse-common.el
index 3aa2c92..bb559a6 100644
--- a/wisi-parse-common.el
+++ b/wisi-parse-common.el
@@ -1,341 +1,353 @@
-;;; wisi-parse-common.el --- declarations used by wisi-parse.el, 
wisi-ada-parse.el, and wisi.el
-;;
-;; Copyright (C) 2014, 2015, 2017, 2018  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-current ((parser wisi-parser))
-  "Parse current buffer.")
-
-(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 only for first token in buffer
-
-  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-cost-limit nil
-  "If integer, sets McKenzie error recovery algorithm cost limit.
-Higher value has more recover power, but takes longer.  If nil,
-uses value from grammar file."
-  :type 'integer
-  :group 'wisi
-  :safe 'integerp)
-(make-variable-buffer-local 'wisi-mckenzie-cost-limit)
-
-(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)
-
-(defvar 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.")
-
-(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
-   name ;; for which-function
-   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:
+
+(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 at a valid parse start point,
+contains END, and ends at a point the parser can handle
+gracefully."
+  (cons begin end))
+
+(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 only for first token in buffer
+
+  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-cost-limit nil
+  "If integer, sets McKenzie error recovery algorithm cost limit.
+Higher value has more recover power, but takes longer.  If nil,
+uses value from grammar file."
+  :type 'integer
+  :group 'wisi
+  :safe 'integerp)
+(make-variable-buffer-local 'wisi-mckenzie-cost-limit)
+
+(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)
+
+(defvar 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.")
+
+(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
+   name ;; for which-function
+   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 cfc98eb..154b75f 100644
--- a/wisi-process-parse.el
+++ b/wisi-process-parse.el
@@ -1,6 +1,6 @@
 ;;; wisi-process-parse.el --- interface to external parse program
 ;;
-;; Copyright (C) 2014, 2017, 2018 Free Software Foundation, Inc.
+;; Copyright (C) 2014, 2017 - 2019 Free Software Foundation, Inc.
 ;;
 ;; Author: Stephen Leake <stephen_leake@member.fsf.org>
 ;;
@@ -22,6 +22,18 @@
 (require 'cl-lib)
 (require 'wisi-parse-common)
 
+(defgroup wisi nil
+  "Options for Wisi package."
+  :group 'programming)
+
+(defcustom wisi-process-time-out 1.0
+  "Time out waiting for parser response. An error occurs if there
+  is no response from the parser after waiting this amount 5
+  times."
+  :type 'float
+  :safe 'floatp)
+(make-variable-buffer-local 'wisi-process-time-out)
+
 (defconst wisi-process-parse-prompt "^;;> "
   "Regexp matching executable prompt; indicates previous command is complete.")
 
@@ -52,23 +64,12 @@
   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
   )
 
 (defvar wisi-process--alist nil
   "Alist mapping string label to ‘wisi-process--session’ struct")
 
-(defgroup wisi nil
-  "Options for Wisi package."
-  :group 'programming)
-
-(defcustom wisi-process-time-out 1.0
-  "Time out waiting for parser response. An error occurs if there
-  is no response from the parser after waiting this amount 5
-  times."
-  :type 'float
-  :safe 'floatp)
-(make-variable-buffer-local 'wisi-process-time-out)
-
 ;;;###autoload
 (defun wisi-process-parse-get (parser)
   "Return a ‘wisi-process--parser’ object matching PARSER label.
@@ -164,18 +165,29 @@ Otherwise add PARSER to ‘wisi-process--alist’, return it."
       (pop-to-buffer (wisi-process--parser-buffer parser))
     (error "wisi-process-parse process not active")))
 
-(defun wisi-process-parse--send-parse (parser line-count)
+(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.  Does not wait for command to
-complete."
-  ;; Must match "parse" command arguments in gen_emacs_wisi_parse.adb
-  (let* ((cmd (format "parse %d \"%s\" %d %d %d %d %d %d %d %d %d %d %d %s"
+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)) "")
-                     line-count
+                     (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
@@ -185,7 +197,7 @@ complete."
                      (if wisi-mckenzie-cost-limit wisi-mckenzie-cost-limit -1)
                      (if wisi-mckenzie-check-limit wisi-mckenzie-check-limit 
-1)
                      (if wisi-mckenzie-enqueue-limit 
wisi-mckenzie-enqueue-limit -1)
-                     (1- (position-bytes (point-max)))
+                     (- (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))
@@ -196,7 +208,7 @@ complete."
       (erase-buffer))
 
     (process-send-string process msg)
-    (process-send-string process (buffer-substring-no-properties (point-min) 
(point-max)))
+    (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.
@@ -259,12 +271,13 @@ complete."
   ;; see ‘wisi-process-parse--execute’
   (let ((pos (aref (wisi-process--parser-line-begin parser) (1- (aref sexp 
1)))))
     (with-silent-modifications
-      (put-text-property
-       (1- pos)
-       pos
-       'wisi-indent
-       (aref sexp 2)))
-    ))
+      (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>]
@@ -348,6 +361,11 @@ complete."
         (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--execute (parser sexp)
   "Execute encoded SEXP sent from external process."
   ;; sexp is [action arg ...]; an encoded instruction that we need to execute
@@ -411,14 +429,16 @@ complete."
     (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))
     ))
 
 ;;;;; main
 
 (cl-defmethod wisi-parse-kill ((parser wisi-process--parser))
   (when (process-live-p (wisi-process--parser-process parser))
-    (process-send-string (wisi-process--parser-process parser) 
wisi-process-parse-quit-cmd)
-    (sit-for 1.0)
+    ;; 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)))
     )
@@ -427,8 +447,9 @@ complete."
 (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))
-  "Run the external parser on the current buffer."
+(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
@@ -476,13 +497,13 @@ complete."
          (setf (wisi-parser-lexer-errors parser) nil)
          (setf (wisi-parser-parse-errors parser) nil)
 
-         (let ((line-count (1+ (count-lines (point-min) (point-max)))))
-           (setf (wisi-process--parser-line-begin parser) 
(wisi--set-line-begin line-count))
-           (wisi-process-parse--send-parse parser line-count)
+         (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 line-count wisi--lexer))
+             (wisi-elisp-lexer-reset total-line-count wisi--lexer))
            )
 
          (set-buffer response-buffer)
@@ -520,49 +541,70 @@ complete."
                  (setq sexp-start (point))
 
                  (set-buffer source-buffer) ;; for put-text-property in actions
-                 (if (listp response)
-                     ;; error of some sort
-                     (cond
-                      ((equal '(parse_error) response)
-                       ;; Parser detected a syntax error, and recovery failed, 
so signal it.
-                       (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.
-                       (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)))))
-                      )
-
-                   ;; else encoded action
+                 (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.
+                     (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)))))
+                      (signal (car err) (cdr err)))
+
+                     (error ;; ie from [C:\Windows\system32\KERNEL32.DLL]
+                      (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)))
+                     ))
+                  )
 
                  (set-buffer response-buffer)
                  ))
@@ -577,8 +619,12 @@ complete."
            (unless done
              ;; end of response buffer
              (unless (process-live-p process)
-               (wisi-process-parse-show-buffer parser)
-               (error "wisi-process-parse process died"))
+               (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 wait-count (1+ wait-count))
              (setq start-wait-time (float-time))
@@ -600,7 +646,7 @@ complete."
                       (- (float-time) start-wait-time)))
 
              (when (and (= (point-max) need-more)
-                      (> wait-count 5))
+                        (> wait-count 5))
                (error "wisi-process-parse not getting more text (or bad syntax 
in process output)"))
 
              (setq need-more nil))
@@ -616,9 +662,9 @@ complete."
          (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 buffer as the elisp
-         ;; parser does.
-         (goto-char (point-max))
+         ;; recovery); move point to end of parsed region.
+         (goto-char (wisi-process--parser-end-pos parser))
+         (cons begin (point))
          )
 
       (wisi-parse-error
@@ -688,4 +734,26 @@ Returns reversed sequence."
       (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.adb b/wisi.adb
index 0c00735..4031df0 100644
--- a/wisi.adb
+++ b/wisi.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
@@ -18,6 +18,7 @@
 pragma License (Modified_GPL);
 
 with Ada.Strings.Bounded;
+with Ada.Text_IO;
 with WisiToken.Semantic_Checks;
 package body Wisi is
    use WisiToken;
@@ -29,6 +30,7 @@ package body Wisi is
    Parser_Error_Code   : constant String := "5";
    Check_Error_Code    : constant String := "6";
    Recover_Code        : constant String := "7 ";
+   End_Code            : constant String := "8";
 
    Chars_Per_Int : constant Integer := Integer'Width;
 
@@ -236,8 +238,8 @@ package body Wisi is
       Offset       : in     Integer)
      return Integer
    is
-      Left_Paren_ID  : WisiToken.Token_ID renames 
Data.Descriptor.Left_Paren_ID;
-      Right_Paren_ID : WisiToken.Token_ID renames 
Data.Descriptor.Right_Paren_ID;
+      Left_Paren_ID  : Token_ID renames Data.Descriptor.Left_Paren_ID;
+      Right_Paren_ID : Token_ID renames Data.Descriptor.Right_Paren_ID;
 
       I              : Base_Token_Index := Anchor_Token.First_Terminals_Index;
       Paren_Count    : Integer          := 0;
@@ -294,8 +296,8 @@ package body Wisi is
    begin
       Append (Line, Navigate_Cache_Code);
       Append (Line, Buffer_Pos'Image (Cache.Pos));
-      Append (Line, WisiToken.Token_ID'Image (Cache.Statement_ID));
-      Append (Line, WisiToken.Token_ID'Image (Cache.ID));
+      Append (Line, Token_ID'Image (Cache.Statement_ID));
+      Append (Line, Token_ID'Image (Cache.ID));
       Append (Line, Integer'Image (Cache.Length));
       Append (Line, Integer'Image (Navigate_Class_Type'Pos (Cache.Class)));
       Append (Cache.Containing_Pos);
@@ -330,8 +332,8 @@ package body Wisi is
       --  grammar indent rules or the algorithms in this package.
       case Item.Label is
       when Not_Set =>
-         Ada.Text_IO.Put_Line
-           ('[' & Indent_Code & Trimmed_Image (Integer (Line_Number)) & " 0]");
+         --  Especially with partial parse, we have no idea what this indent 
should be.
+         null;
 
       when Int =>
          declare
@@ -354,14 +356,14 @@ package body Wisi is
    is
       use Ada.Containers;
       use Ada.Strings.Unbounded;
-      use WisiToken.Parse.LR;
+      use Parse.LR;
 
       Line    : Unbounded_String := To_Unbounded_String ("[");
       Last_Op : Config_Op        := (Fast_Forward, Token_Index'Last);
 
    begin
       if Trace_Action > Detail then
-         Ada.Text_IO.Put_Line (Parse.LR.Image (Item.Ops, Descriptor));
+         Ada.Text_IO.Put_Line (";; " & Parse.LR.Image (Item.Ops, Descriptor));
       end if;
 
       Append (Line, Recover_Code);
@@ -465,47 +467,52 @@ package body Wisi is
 
    procedure Resolve_Anchors (Data : in out Parse_Data_Type)
    is
+      Begin_Indent  : Integer renames Data.Begin_Indent;
       Anchor_Indent : array (First_Anchor_ID .. Data.Max_Anchor_ID) of Integer;
    begin
       if Trace_Action > Outline then
          Ada.Text_IO.New_Line;
+         Ada.Text_IO.Put_Line (";; Begin_Indent: " & Integer'Image 
(Data.Begin_Indent));
          for I in Data.Indents.First_Index .. Data.Indents.Last_Index loop
             Ada.Text_IO.Put_Line (Line_Number_Type'Image (I) & ", " & Image 
(Data.Indents (I)));
          end loop;
       end if;
 
-      if Data.Max_Anchor_ID >= First_Anchor_ID then
-         for I in Data.Indents.First_Index .. Data.Indents.Last_Index loop
-            declare
-               Indent : constant Indent_Type := Data.Indents (I);
-            begin
-               case Indent.Label is
-               when Not_Set | Int =>
-                  null;
+      for I in Data.Indents.First_Index .. Data.Indents.Last_Index loop
+         declare
+            Indent : constant Indent_Type := Data.Indents (I);
+         begin
+            case Indent.Label is
+            when Not_Set =>
+               --  Indent not computed, therefore not output.
+               null;
 
-               when Anchor =>
-                  for I of Indent.Anchor_IDs loop
-                     Anchor_Indent (I) := Indent.Anchor_Indent;
-                  end loop;
-                  Data.Indents.Replace_Element (I, (Int, 
Indent.Anchor_Indent));
+            when Int =>
+               Data.Indents.Replace_Element (I, (Int, Indent.Int_Indent + 
Begin_Indent));
 
-               when Anchored =>
-                  Data.Indents.Replace_Element (I, (Int, Anchor_Indent 
(Indent.Anchored_ID) + Indent.Anchored_Delta));
+            when Anchor =>
+               for I of Indent.Anchor_IDs loop
+                  Anchor_Indent (I) := Indent.Anchor_Indent + Begin_Indent;
+               end loop;
+               Data.Indents.Replace_Element (I, (Int, Indent.Anchor_Indent + 
Begin_Indent));
 
-               when Anchor_Anchored =>
-                  declare
-                     Temp : constant Integer :=
-                       Anchor_Indent (Indent.Anchor_Anchored_ID) + 
Indent.Anchor_Anchored_Delta;
-                  begin
-                     for I of Indent.Anchor_Anchored_IDs loop
-                        Anchor_Indent (I) := Temp;
-                     end loop;
-                     Data.Indents.Replace_Element (I, (Int, Temp));
-                  end;
-               end case;
-            end;
-         end loop;
-      end if;
+            when Anchored =>
+               Data.Indents.Replace_Element
+                 (I, (Int, Anchor_Indent (Indent.Anchored_ID) + 
Indent.Anchored_Delta));
+
+            when Anchor_Anchored =>
+               declare
+                  Temp : constant Integer :=
+                    Anchor_Indent (Indent.Anchor_Anchored_ID) + 
Indent.Anchor_Anchored_Delta;
+               begin
+                  for I of Indent.Anchor_Anchored_IDs loop
+                     Anchor_Indent (I) := Temp;
+                  end loop;
+                  Data.Indents.Replace_Element (I, (Int, Temp));
+               end;
+            end case;
+         end;
+      end loop;
    end Resolve_Anchors;
 
    procedure Set_End
@@ -550,14 +557,21 @@ package body Wisi is
       Descriptor        : access constant WisiToken.Descriptor;
       Source_File_Name  : in     String;
       Post_Parse_Action : in     Post_Parse_Action_Type;
-      Line_Count        : in     Line_Number_Type;
+      Begin_Line        : in     Line_Number_Type;
+      End_Line          : in     Line_Number_Type;
+      Begin_Indent      : in     Integer;
       Params            : in     String)
    is
       pragma Unreferenced (Params);
    begin
+      Data.Line_Begin_Pos.Set_First_Last
+        (First   => Begin_Line,
+         Last    => End_Line);
+
       --  + 1 for data on line following last line; see Lexer_To_Augmented.
-      Data.Line_Begin_Pos.Set_Length (Ada.Containers.Count_Type (Line_Count + 
1), Default => Invalid_Buffer_Pos);
-      Data.Line_Paren_State.Set_Length (Ada.Containers.Count_Type (Line_Count 
+ 1));
+      Data.Line_Paren_State.Set_First_Last
+        (First   => Begin_Line,
+         Last    => End_Line + 1);
 
       Data.Descriptor        := Descriptor;
       Data.Source_File_Name  := +Source_File_Name;
@@ -567,7 +581,11 @@ package body Wisi is
       when Navigate | Face =>
          null;
       when Indent =>
-         Data.Indents.Set_Length (Ada.Containers.Count_Type (Line_Count));
+         Data.Indents.Set_First_Last
+           (First   => Begin_Line,
+            Last    => End_Line);
+
+         Data.Begin_Indent := Begin_Indent;
       end case;
 
       Data.Reset;
@@ -618,6 +636,25 @@ package body Wisi is
    begin
       if Lexer.First then
          Data.Line_Begin_Pos (Token.Line) := Token.Char_Region.First;
+
+         if Token.Line > Data.Line_Begin_Pos.First_Index and then
+           Data.Line_Begin_Pos (Token.Line - 1) = Invalid_Buffer_Pos
+         then
+            --  Previous token contains multiple lines; ie %code in 
wisitoken_grammar.wy
+            declare
+               First_Unset_Line : Line_Number_Type;
+            begin
+               for Line in reverse Data.Line_Begin_Pos.First_Index .. 
Token.Line - 1 loop
+                  if Data.Line_Begin_Pos (Line) /= Invalid_Buffer_Pos then
+                     First_Unset_Line := Line;
+                     exit;
+                  end if;
+               end loop;
+               for Line in First_Unset_Line .. Token.Line - 1 loop
+                  Data.Line_Begin_Pos (Line) := Data.Line_Begin_Pos 
(First_Unset_Line); -- good enough
+               end loop;
+            end;
+         end if;
       end if;
 
       if Token.ID < Data.Descriptor.First_Terminal then
@@ -628,7 +665,7 @@ package body Wisi is
          end if;
 
          if Data.Terminals.Length = 0 then
-            Data.Leading_Non_Grammar.Append (Token);
+            Data.Leading_Non_Grammar.Append ((Token with Lexer.First));
          else
             declare
                Containing_Token : Augmented_Token renames Data.Terminals 
(Data.Terminals.Last_Index);
@@ -640,15 +677,13 @@ package body Wisi is
                       (Containing_Token.Non_Grammar.Last_Index).ID = 
Data.Descriptor.New_Line_ID);
             begin
                if Lexer.First and (Token.ID = Data.Descriptor.Comment_ID or 
Trailing_Blank) then
-                  Containing_Token.First := True;
-
                   if Containing_Token.First_Trailing_Comment_Line = 
Invalid_Line_Number then
                      Containing_Token.First_Trailing_Comment_Line := 
Token.Line;
                   end if;
                   Containing_Token.Last_Trailing_Comment_Line  := Token.Line;
                end if;
 
-               Containing_Token.Non_Grammar.Append ((Token.ID, Token.Line, 
Token.Column, Lexer.First));
+               Containing_Token.Non_Grammar.Append ((Token with Lexer.First));
             end;
          end if;
 
@@ -686,41 +721,98 @@ package body Wisi is
 
    overriding
    procedure Delete_Token
-     (Data        : in out Parse_Data_Type;
-      Token_Index : in     WisiToken.Token_Index)
+     (Data                : in out Parse_Data_Type;
+      Deleted_Token_Index : in     WisiToken.Token_Index)
    is
       use all type Ada.Containers.Count_Type;
-      Deleted_Token    : Augmented_Token renames Data.Terminals (Token_Index);
-      Prev_Token_Index : WisiToken.Base_Token_Index := Token_Index - 1;
+      Deleted_Token    : Augmented_Token renames Data.Terminals 
(Deleted_Token_Index);
+      Prev_Token_Index : Base_Token_Index := Deleted_Token_Index - 1;
+      Next_Token_Index : Base_Token_Index := Deleted_Token_Index + 1;
    begin
-      pragma Assert (Deleted_Token.Deleted = False);
-      Deleted_Token.Deleted := True;
-      if Deleted_Token.Non_Grammar.Length = 0 then
+      if Deleted_Token.Deleted then
+         --  This can happen if error recovery screws up.
+         if WisiToken.Trace_Action > WisiToken.Detail then
+            Ada.Text_IO.Put_Line (";; delete token again; ignored " & Image 
(Deleted_Token, Data.Descriptor.all));
+         end if;
          return;
       end if;
+      if WisiToken.Trace_Action > WisiToken.Detail then
+         Ada.Text_IO.Put_Line (";; delete token " & Image (Deleted_Token, 
Data.Descriptor.all));
+      end if;
+
+      Deleted_Token.Deleted := True;
+
+      if Deleted_Token.Non_Grammar.Length > 0 then
+         --  Move Non_Grammar to previous non-deleted token
+
+         loop
+            exit when Prev_Token_Index = Base_Token_Index'First;
+            exit when Data.Terminals (Prev_Token_Index).Deleted = False;
+            Prev_Token_Index := Prev_Token_Index - 1;
+         end loop;
 
-      loop
          if Prev_Token_Index = Base_Token_Index'First then
-            return;
+            Deleted_Token.Non_Grammar 
(Deleted_Token.Non_Grammar.First_Index).First := Deleted_Token.First;
+            Data.Leading_Non_Grammar.Append (Deleted_Token.Non_Grammar);
+         else
+            declare
+               Prev_Token : Augmented_Token renames Data.Terminals 
(Prev_Token_Index);
+            begin
+               Prev_Token.Non_Grammar.Append (Deleted_Token.Non_Grammar);
+
+               if Deleted_Token.First_Trailing_Comment_Line /= 
Invalid_Line_Number then
+                  if Prev_Token.First_Trailing_Comment_Line = 
Invalid_Line_Number then
+                     Prev_Token.First_Trailing_Comment_Line := 
Deleted_Token.First_Trailing_Comment_Line;
+                  end if;
+                  Prev_Token.Last_Trailing_Comment_Line  := 
Deleted_Token.Last_Trailing_Comment_Line;
+               end if;
+            end;
          end if;
-         exit when Data.Terminals (Prev_Token_Index).Deleted = False;
-         Prev_Token_Index := Prev_Token_Index - 1;
+      end if;
+
+      --  Data.Terminals.Last_Index is Wisi_EOI; it is never deleted
+      loop
+         exit when Data.Terminals (Next_Token_Index).Deleted = False;
+         Next_Token_Index := Next_Token_Index + 1;
+         exit when Next_Token_Index = Data.Terminals.Last_Index;
       end loop;
-      declare
-         Prev_Token : Augmented_Token renames Data.Terminals 
(Prev_Token_Index);
-      begin
-         Prev_Token.Non_Grammar.Append (Deleted_Token.Non_Grammar);
-         if Deleted_Token.First_Trailing_Comment_Line /= Invalid_Line_Number 
then
-            if Prev_Token.First_Trailing_Comment_Line = Invalid_Line_Number 
then
-               if Deleted_Token.First then
-                  Prev_Token.First_Trailing_Comment_Line := 
Deleted_Token.First_Indent_Line;
+
+      if Deleted_Token.First and
+        (Next_Token_Index = Data.Terminals.Last_Index or else
+           Data.Terminals (Next_Token_Index).Line > Deleted_Token.Line)
+      then
+         --  Deleted_Token.Line is now blank; add to previous token non
+         --  grammar.
+         if Prev_Token_Index > Base_Token_Index'First then
+            declare
+               Prev_Token : Augmented_Token renames Data.Terminals 
(Prev_Token_Index);
+            begin
+               if Prev_Token.First_Trailing_Comment_Line = Invalid_Line_Number 
then
+                  Prev_Token.First_Trailing_Comment_Line := Deleted_Token.Line;
+                  Prev_Token.Last_Trailing_Comment_Line  := Deleted_Token.Line;
                else
-                  Prev_Token.First_Trailing_Comment_Line := 
Deleted_Token.First_Trailing_Comment_Line;
+                  if Prev_Token.First_Trailing_Comment_Line > 
Deleted_Token.Line then
+                     Prev_Token.First_Trailing_Comment_Line := 
Deleted_Token.Line;
+                  end if;
+                  if Prev_Token.Last_Trailing_Comment_Line < 
Deleted_Token.Line then
+                     Prev_Token.Last_Trailing_Comment_Line := 
Deleted_Token.Line;
+                  end if;
                end if;
-            end if;
-            Prev_Token.Last_Trailing_Comment_Line  := 
Deleted_Token.Last_Trailing_Comment_Line;
+            end;
          end if;
-      end;
+      end if;
+
+      if Deleted_Token.First and Next_Token_Index < Data.Terminals.Last_Index 
then
+         if not Data.Terminals (Next_Token_Index).First then
+            declare
+               Next_Token : Augmented_Token renames Data.Terminals 
(Next_Token_Index);
+            begin
+               Next_Token.First             := True;
+               Next_Token.First_Indent_Line := Deleted_Token.First_Indent_Line;
+               Next_Token.Last_Indent_Line  := Deleted_Token.Last_Indent_Line;
+            end;
+         end if;
+      end if;
    end Delete_Token;
 
    overriding
@@ -759,19 +851,17 @@ package body Wisi is
 
                   Aug_Nonterm.First := Aug_Nonterm.First or Aug_Token.First;
 
-                  if Aug_Token.First then
-                     if Aug_Token.First_Indent_Line /= Invalid_Line_Number then
-                        Aug_Nonterm.First_Indent_Line := 
Aug_Token.First_Indent_Line;
-                     elsif Trailing_Comment_Done and 
Aug_Token.First_Trailing_Comment_Line /= Invalid_Line_Number then
-                        Aug_Nonterm.First_Indent_Line := 
Aug_Token.First_Trailing_Comment_Line;
-                     end if;
+                  if Aug_Token.First_Indent_Line /= Invalid_Line_Number then
+                     Aug_Nonterm.First_Indent_Line := 
Aug_Token.First_Indent_Line;
+                  elsif Trailing_Comment_Done and 
Aug_Token.First_Trailing_Comment_Line /= Invalid_Line_Number then
+                     Aug_Nonterm.First_Indent_Line := 
Aug_Token.First_Trailing_Comment_Line;
+                  end if;
 
-                     if Aug_Nonterm.Last_Indent_Line = Invalid_Line_Number then
-                        if Trailing_Comment_Done and 
Aug_Token.Last_Trailing_Comment_Line /= Invalid_Line_Number then
-                           Aug_Nonterm.Last_Indent_Line := 
Aug_Token.Last_Trailing_Comment_Line;
-                        elsif Aug_Token.Last_Indent_Line /= 
Invalid_Line_Number then
-                           Aug_Nonterm.Last_Indent_Line := 
Aug_Token.Last_Indent_Line;
-                        end if;
+                  if Aug_Nonterm.Last_Indent_Line = Invalid_Line_Number then
+                     if Trailing_Comment_Done and 
Aug_Token.Last_Trailing_Comment_Line /= Invalid_Line_Number then
+                        Aug_Nonterm.Last_Indent_Line := 
Aug_Token.Last_Trailing_Comment_Line;
+                     elsif Aug_Token.Last_Indent_Line /= Invalid_Line_Number 
then
+                        Aug_Nonterm.Last_Indent_Line := 
Aug_Token.Last_Indent_Line;
                      end if;
                   end if;
 
@@ -876,7 +966,7 @@ package body Wisi is
       Containing : in     Positive_Index_Type;
       Contained  : in     Positive_Index_Type)
    is
-      use all type WisiToken.Syntax_Trees.Node_Label;
+      use all type Syntax_Trees.Node_Label;
       pragma Unreferenced (Nonterm);
 
       --  [2] wisi-containing-action.
@@ -952,21 +1042,21 @@ package body Wisi is
       end;
    end Containing_Action;
 
-   function "+" (Item : in WisiToken.Token_ID) return Token_ID_Lists.List
+   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 WisiToken.Token_ID) 
return Token_ID_Lists.List
+   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 WisiToken.Token_ID) return 
Token_ID_Lists.List
+   function "&" (Left, Right : in Token_ID) return Token_ID_Lists.List
    is begin
       return Result : Token_ID_Lists.List do
          Result.Append (Left);
@@ -1284,7 +1374,7 @@ package body Wisi is
       return "(" & Simple_Indent_Param_Label'Image (Item.Label) &
         (case Item.Label is
          when Int => Integer'Image (Item.Int_Delta),
-         when Anchored_Label => WisiToken.Positive_Index_Type'Image 
(Item.Anchored_Index) & "," &
+         when Anchored_Label => Positive_Index_Type'Image 
(Item.Anchored_Index) & "," &
               Integer'Image (Item.Anchored_Delta),
          when Language => "<language_function>") & ")";
    end Image;
@@ -1314,35 +1404,33 @@ package body Wisi is
       Params  : in     Indent_Param_Array)
    is begin
       if Trace_Action > Outline then
-         Ada.Text_IO.Put_Line ("indent_action_0: " & Tree.Image (Nonterm, 
Data.Descriptor.all));
+         Ada.Text_IO.Put_Line (";; indent_action_0: " & Tree.Image (Nonterm, 
Data.Descriptor.all));
       end if;
 
       --  [2] wisi-indent-action
       for I in Tokens'Range loop
          if Tree.Byte_Region (Tokens (I)) /= Null_Buffer_Region then
             declare
+               use all type WisiToken.Syntax_Trees.Node_Index;
                use all type SAL.Base_Peek_Type;
-               Tree_Token : constant Syntax_Trees.Valid_Node_Index := Tokens 
(I);
-
-               Token             : constant Aug_Token_Ref := Get_Aug_Token 
(Data, Tree, Tree_Token);
+               Tree_Token        : constant Syntax_Trees.Valid_Node_Index := 
Tokens (I);
+               Token             : constant Aug_Token_Ref                 := 
Get_Aug_Token (Data, Tree, Tree_Token);
                Pair              : Indent_Pair renames Params (I);
                Code_Delta        : Delta_Type;
                Comment_Param     : Indent_Param;
-               Comment_Param_Set : Boolean                := False;
+               Comment_Param_Set : Boolean                                := 
False;
                Comment_Delta     : Delta_Type;
             begin
                if Trace_Action > Detail then
                   Ada.Text_IO.Put_Line
-                    ("indent_action_0 a: " & Tree.Image (Tree_Token, 
Data.Descriptor.all) & ": " & Image (Pair));
+                    (";; indent_action_0 a: " & Tree.Image (Tree_Token, 
Data.Descriptor.all) & ": " & Image (Pair));
                end if;
 
                if Token.First_Indent_Line /= Invalid_Line_Number then
                   Code_Delta := Indent_Compute_Delta
                     (Data, Tree, Tokens, Pair.Code_Delta, Tree_Token, 
Indenting_Comment => False);
 
-                  if Code_Delta /= Null_Delta then
-                     Indent_Token_1 (Data, Token, Code_Delta, 
Indenting_Comment => False);
-                  end if;
+                  Indent_Token_1 (Data, Token, Code_Delta, Indenting_Comment 
=> False);
                end if;
 
                if Token.First_Trailing_Comment_Line /= Invalid_Line_Number then
@@ -1353,15 +1441,14 @@ package body Wisi is
                   elsif I < Tokens'Last then
                      Comment_Param     := Params (I + 1).Code_Delta;
                      Comment_Param_Set := True;
+
                   end if;
 
                   if Comment_Param_Set then
                      Comment_Delta := Indent_Compute_Delta
                        (Data, Tree, Tokens, Comment_Param, Tree_Token, 
Indenting_Comment => True);
 
-                     if Comment_Delta /= Null_Delta then
-                        Indent_Token_1 (Data, Token, Comment_Delta, 
Indenting_Comment => True);
-                     end if;
+                     Indent_Token_1 (Data, Token, Comment_Delta, 
Indenting_Comment => True);
                   end if;
                end if;
             end;
@@ -1377,7 +1464,7 @@ package body Wisi is
       N       : in     Positive_Index_Type;
       Params  : in     Indent_Param_Array)
    is
-      use all type WisiToken.Syntax_Trees.Node_Label;
+      use all type Syntax_Trees.Node_Label;
    begin
       --  [2] wisi-indent-action*
       for I in Tokens'First .. N loop
@@ -1426,8 +1513,62 @@ package body Wisi is
       end if;
    end Indent_Hanging_1;
 
-   procedure Put (Data : in out Parse_Data_Type)
-   is begin
+   procedure Put (Data : in out Parse_Data_Type; Parser : in 
Parse.Base_Parser'Class)
+   is
+      use all type Ada.Containers.Count_Type;
+
+      Last_Term : constant Base_Token_Index := Parser.Tree.Max_Terminal_Index 
(Parser.Tree.Root);
+
+      function Get_Last_Char_Pos return Buffer_Pos
+      is begin
+         if Parser.Terminals.Length = 0 then
+            --  All comments, or empty
+            if Data.Leading_Non_Grammar.Length > 0 then
+               return Data.Leading_Non_Grammar 
(Data.Leading_Non_Grammar.Last_Index).Char_Region.Last;
+            else
+               return Buffer_Pos'First;
+            end if;
+         else
+            if Last_Term = Invalid_Token_Index then
+               --  All grammar tokens inserted by recover
+               if Data.Leading_Non_Grammar.Length > 0 then
+                  return Data.Leading_Non_Grammar 
(Data.Leading_Non_Grammar.Last_Index).Char_Region.Last;
+               else
+                  return Buffer_Pos'First;
+               end if;
+            else
+               if Data.Terminals (Last_Term).Non_Grammar.Length > 0 then
+                  return Data.Terminals (Last_Term).Non_Grammar
+                    (Data.Terminals 
(Last_Term).Non_Grammar.Last_Index).Char_Region.Last;
+               else
+                  return Parser.Terminals (Last_Term).Char_Region.Last;
+               end if;
+            end if;
+         end if;
+      end Get_Last_Char_Pos;
+
+      Last_Char_Pos : constant Buffer_Pos := Get_Last_Char_Pos;
+
+      function Get_Last_Line return Line_Number_Type
+      is begin
+         for I in Data.Line_Begin_Pos.First_Index .. 
Data.Line_Begin_Pos.Last_Index loop
+            if Data.Line_Begin_Pos (I) > Last_Char_Pos then
+               return I - 1;
+            end if;
+         end loop;
+         return Data.Line_Begin_Pos.Last_Index;
+      end Get_Last_Line;
+
+   begin
+      if Trace_Action > Outline then
+         Ada.Text_IO.Put_Line
+           (";; last_char_pos:" & Buffer_Pos'Image (Last_Char_Pos + 1) &
+              " last_line:" & Line_Number_Type'Image (Get_Last_Line));
+      end if;
+
+      --  +1 to match Emacs region
+      Ada.Text_IO.Put_Line ('[' & End_Code & Buffer_Pos'Image (Last_Char_Pos + 
1) & ']');
+
       case Data.Post_Parse_Action is
       when Navigate =>
          for Cache of Data.Navigate_Caches loop
@@ -1440,18 +1581,46 @@ package body Wisi is
          end loop;
 
       when Indent =>
-         --  We don't need "Indent_Leading_Comments"; they are indented to 0,
-         --  which is the default.
 
          Resolve_Anchors (Data);
 
-         --  Can't set indent for first line
-         for I in Data.Indents.First_Index + 1 .. Data.Indents.Last_Index loop
+         if Trace_Action > Outline then
+            Ada.Text_IO.Put_Line (";; indent leading non_grammar");
+         end if;
+         for Token of Data.Leading_Non_Grammar loop
+            if Token.First then
+               Put (Token.Line, (Int, Data.Begin_Indent));
+            end if;
+         end loop;
+
+         --  It may be that not all lines in Data.Indents were parsed.
+         if Trace_Action > Outline then
+            Ada.Text_IO.Put_Line (";; indent grammar");
+         end if;
+         for I in Data.Indents.First_Index .. Get_Last_Line loop
             Put (I, Data.Indents (I));
          end loop;
       end case;
    end Put;
 
+   procedure Put (Lexer_Errors : in Lexer.Error_Lists.List)
+   is begin
+      for Item of Lexer_Errors loop
+         Ada.Text_IO.Put_Line
+           ('[' & Lexer_Error_Code & Buffer_Pos'Image (Item.Char_Pos) &
+              " ""lexer error" &
+              (if Item.Recover_Char (1) = ASCII.NUL
+               then """"
+               elsif Item.Recover_Char (1) = '"'
+               then """ ?\"""
+               else """ ?" & Item.Recover_Char (1)) &
+              "]");
+         if Item.Recover_Char (2) /= ASCII.NUL then
+            raise SAL.Programmer_Error with "lexer error with non-ascii or 
multiple repair char";
+         end if;
+      end loop;
+   end Put;
+
    procedure Put
      (Data         : in Parse_Data_Type;
       Lexer_Errors : in Lexer.Error_Lists.List;
@@ -1508,20 +1677,7 @@ package body Wisi is
       end Safe_Pos;
 
    begin
-      for Item of Lexer_Errors loop
-         Put_Line
-           ('[' & Lexer_Error_Code & Buffer_Pos'Image (Item.Char_Pos) &
-              " ""lexer error" &
-              (if Item.Recover_Char (1) = ASCII.NUL
-               then """"
-               elsif Item.Recover_Char (1) = '"'
-               then """ ?\"""
-               else """ ?" & Item.Recover_Char (1)) &
-              "]");
-         if Item.Recover_Char (2) /= ASCII.NUL then
-            raise SAL.Programmer_Error with "lexer error with non-ascii or 
multiple repair char";
-         end if;
-      end loop;
+      Put (Lexer_Errors);
 
       for Item of Parse_Errors loop
          --  We don't include parser id here; not very useful.
@@ -1645,7 +1801,7 @@ package body Wisi is
       Descriptor : in WisiToken.Descriptor)
      return String
    is
-      ID_Image : constant String := WisiToken.Image (Item.ID, Descriptor);
+      ID_Image : constant String := Image (Item.ID, Descriptor);
    begin
       if Item.Line /= Invalid_Line_Number and Trace_Action <= Detail then
          return "(" & ID_Image &
@@ -1839,7 +1995,7 @@ package body Wisi is
    begin
       if Trace_Action > Detail then
          Ada.Text_IO.Put_Line
-           ("indent_token_1: " & Indenting_Token.Image (Data.Descriptor.all) & 
" " & Image (Delta_Indent) &
+           (";; indent_token_1: " & Indenting_Token.Image 
(Data.Descriptor.all) & " " & Image (Delta_Indent) &
               Line_Number_Type'Image (First_Line) & " .." & 
Line_Number_Type'Image (Last_Line) &
               (if Indenting_Comment then " comment" else ""));
       end if;
@@ -1850,11 +2006,11 @@ package body Wisi is
                use all type Ada.Text_IO.Count;
                Indent : Boolean := True;
             begin
-               if Data.Line_Begin_Token.all (Line) /= 
Augmented_Token_Arrays.No_Index then
+               if Data.Line_Begin_Token.all (Line - 1) /= 
Augmented_Token_Arrays.No_Index then
                   for Tok of Data.Terminals (Data.Line_Begin_Token.all (Line - 
1)).Non_Grammar loop
                      if Tok.Line = Line and then
                        Tok.ID = Data.Descriptor.Comment_ID and then
-                       Tok.Col = 0
+                       Tok.Column = 0
                      then
                         Indent := False;
                         exit;
@@ -1864,6 +2020,8 @@ package body Wisi is
 
                if Indent then
                   Indent_Line (Data, Line, Delta_Indent);
+               else
+                  Indent_Line (Data, Line, Null_Delta);
                end if;
             end;
          else
diff --git a/wisi.ads b/wisi.ads
index 4203930..d3f1214 100644
--- a/wisi.ads
+++ b/wisi.ads
@@ -10,7 +10,7 @@
 --
 --  [3] wisi-process-parse.el - defines elisp/process API
 --
---  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
@@ -28,7 +28,6 @@ pragma License (Modified_GPL);
 with Ada.Containers.Doubly_Linked_Lists;
 with Ada.Containers.Vectors;
 with Ada.Strings.Unbounded;
-with Ada.Text_IO;
 with Ada.Unchecked_Deallocation;
 with SAL.Gen_Unbounded_Definite_Red_Black_Trees;
 with SAL.Gen_Unbounded_Definite_Vectors;
@@ -49,16 +48,12 @@ package Wisi is
       Descriptor        : access constant WisiToken.Descriptor;
       Source_File_Name  : in     String;
       Post_Parse_Action : in     Post_Parse_Action_Type;
-      Line_Count        : in     WisiToken.Line_Number_Type;
+      Begin_Line        : in     WisiToken.Line_Number_Type;
+      End_Line          : in     WisiToken.Line_Number_Type;
+      Begin_Indent      : in     Integer;
       Params            : in     String);
-   --  Line_Count only used for Indent. Params contains language-specific
-   --  indent parameter values.
-   --
-   --  It is possible to do without the Line_Count parameter, and grow
-   --  the various vectors dynamically. However, doing that caused
-   --  intermittent problems with too many lines; the Ada code saw more
-   --  lines than the elisp code did. Using the elisp line count is more
-   --  reliable.
+   --  Begin_Line, Begin_Indent, Line_Count only used for Indent. Params
+   --  contains language-specific indent parameter values.
 
    overriding procedure Reset (Data : in out Parse_Data_Type);
    --  Reset for a new parse, with data from previous Initialize.
@@ -74,8 +69,8 @@ package Wisi is
 
    overriding
    procedure Delete_Token
-     (Data        : in out Parse_Data_Type;
-      Token_Index : in     WisiToken.Token_Index);
+     (Data                : in out Parse_Data_Type;
+      Deleted_Token_Index : in     WisiToken.Token_Index);
 
    overriding
    procedure Reduce
@@ -318,11 +313,12 @@ package Wisi is
    --  Language specific child packages override this to implement
    --  wisi-elisp-parse-indent-hanging-function.
 
-   procedure Put (Data : in out Parse_Data_Type);
-   --  Perform post-parse actions, then put result to
+   procedure Put (Data : in out Parse_Data_Type; Parser : in 
WisiToken.Parse.Base_Parser'Class);
+   --  Perform additional post-parse actions, then put result to
    --  Ada.Text_IO.Current_Output, as encoded responses as defined in [3]
    --  wisi-process-parse--execute.
 
+   procedure Put (Lexer_Errors : in WisiToken.Lexer.Error_Lists.List);
    procedure Put
      (Data         : in Parse_Data_Type;
       Lexer_Errors : in WisiToken.Lexer.Error_Lists.List;
@@ -337,16 +333,12 @@ package Wisi is
 
 private
 
-   type Non_Grammar_Token is record
-      ID    : WisiToken.Token_ID         := WisiToken.Invalid_Token_ID;
-      Line  : WisiToken.Line_Number_Type := WisiToken.Invalid_Line_Number;
-      Col   : Ada.Text_IO.Count          := Ada.Text_IO.Count'Last;
-      First : Boolean                    := False;
-      --  Column is needed to detect comments in column 0.
+   type Non_Grammar_Token is new WisiToken.Base_Token with record
+      First : Boolean := False;
    end record;
 
    package Non_Grammar_Token_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
-     (WisiToken.Token_Index, Non_Grammar_Token);
+     (WisiToken.Token_Index, Non_Grammar_Token, Default_Element => (others => 
<>));
 
    type Augmented_Token is new WisiToken.Base_Token with record
       --  Most fields are set by Lexer_To_Augmented at parse time; others
@@ -359,11 +351,9 @@ private
       --  The following fields are only needed for indent.
 
       First : Boolean := False;
-      --  For a terminal, True if the token is not empty and it is first on
-      --  a line, or if it contains trailing blank or comment lines.
+      --  For a terminal, True if the token is first on a line.
       --
-      --  For a nonterminal, True if some contained token's First is True,
-      --  including trailing comments and blank lines.
+      --  For a nonterminal, True if some contained token's First is True.
 
       Paren_State : Integer := 0;
       --  Parenthesis nesting count, before token.
@@ -431,12 +421,14 @@ private
       Descriptor : in WisiToken.Descriptor)
      return String;
 
-   package Augmented_Token_Arrays is new SAL.Gen_Unbounded_Definite_Vectors 
(WisiToken.Token_Index, Augmented_Token);
+   package Augmented_Token_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
+     (WisiToken.Token_Index, Augmented_Token, Default_Element => (others => 
<>));
    --  Index matches Base_Token_Arrays.
 
-   package Line_Paren_Vectors is new SAL.Gen_Unbounded_Definite_Vectors 
(WisiToken.Line_Number_Type, Integer);
+   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
-     (WisiToken.Line_Number_Type, WisiToken.Buffer_Pos);
+     (WisiToken.Line_Number_Type, WisiToken.Buffer_Pos, Default_Element => 
WisiToken.Invalid_Buffer_Pos);
 
    type Nil_Buffer_Pos (Set : Boolean := False) is record
       case Set is
@@ -520,7 +512,8 @@ private
    end record;
    First_Anchor_ID : constant Positive := Positive'First;
 
-   package Indent_Vectors is new Ada.Containers.Vectors 
(WisiToken.Line_Number_Type, Indent_Type);
+   package Indent_Vectors is new SAL.Gen_Unbounded_Definite_Vectors
+     (WisiToken.Line_Number_Type, Indent_Type, Default_Element => (others => 
<>));
    package Navigate_Cursor_Lists is new Ada.Containers.Doubly_Linked_Lists
      (Navigate_Cache_Trees.Cursor, Navigate_Cache_Trees."=");
 
@@ -535,7 +528,7 @@ private
       --  immediately following non-grammar tokens. Does not contain
       --  nonterminal or virtual tokens.
 
-      Leading_Non_Grammar : WisiToken.Base_Token_Arrays.Vector;
+      Leading_Non_Grammar : Non_Grammar_Token_Arrays.Vector;
       --  non-grammar tokens before first grammar token.
 
       Line_Begin_Pos : Line_Begin_Pos_Vectors.Vector;
@@ -558,6 +551,7 @@ private
       End_Positions     : Navigate_Cursor_Lists.List; -- Dynamic data for 
Navigate.
       Face_Caches       : Face_Cache_Trees.Tree;      -- Set by Face.
       Indents           : Indent_Vectors.Vector;      -- Set by Indent.
+      Begin_Indent      : Integer;                    -- Indentation of line 
at start of parse.
 
       --  Copied from language-specific parameters
       Indent_Comment_Col_0 : Boolean := False;
diff --git a/wisi.el b/wisi.el
index 4a6b7ad..3aa522c 100644
--- a/wisi.el
+++ b/wisi.el
@@ -1,15 +1,15 @@
 ;;; wisi.el --- Utilities for implementing an indentation/navigation engine 
using a generalized LALR parser -*- lexical-binding:t -*-
 ;;
-;; Copyright (C) 2012 - 2018  Free Software Foundation, Inc.
+;; Copyright (C) 2012 - 2019  Free Software Foundation, Inc.
 ;;
 ;; Author: Stephen Leake <stephen_leake@stephe-leake.org>
 ;; Maintainer: Stephen Leake <stephen_leake@stephe-leake.org>
 ;; Keywords: parser
 ;;  indentation
 ;;  navigation
-;; Version: 2.0.1
+;; Version: 2.1.0
 ;; package-requires: ((cl-lib "1.0") (emacs "25.0") (seq "2.20"))
-;; URL: http://www.nongnu.org/ada-mode/wisi/wisi.html
+;; URL: http://stephe-leake.org/ada/wisitoken.html
 ;;
 ;; This file is part of GNU Emacs.
 ;;
@@ -47,6 +47,9 @@
 ;; at different times (user indent, font-lock, user navigate), so only
 ;; the relevant parser actions are run.
 ;;
+;; Parsing can be noticeably slow in large files, so sometimes we do a
+;; partial parse, and keep a list of parsed regions.
+;;
 ;; Since we have a cache (the text properties), we need to consider
 ;; when to invalidate it.  Ideally, we invalidate only when a change
 ;; to the buffer would change the result of a parse that crosses that
@@ -56,16 +59,23 @@
 ;; indentation (Python).  Adding comments does not change a parse,
 ;; unless code is commented out.
 ;;
-;; For font-lock and navigate, keeping track of the point after which
-;; caches have been deleted is sufficent (see `wisi-cache-max').
+;; For navigate, we expect fully accurate results, and can tolerate
+;; one initial delay, so we always parse the entire file.
+;;
+;; For font-lock, we only parse the portion of the file requested by
+;; font-lock, so we keep a list of regions, and edit that list when
+;; the buffer is changed..
 ;;
-;; For indenting, we cache the indent for each line in a text property
-;; on the newline char preceding the line. `wisi-indent-region' sets
-;; the cache on all the lines computed (normally the whole buffer),
-;; but performs the indent only on the lines in the indent
+;; For indenting, we expect fast results, and can tolerate some
+;; inaccuracy until the editing is done, so we allow partial parse. We
+;; cache the indent for each line in a text property on the newline
+;; char preceding the line. `wisi-indent-region' sets the cache on all
+;; the lines computed (part of the buffer in large files), but
+;; performs the indent only on the lines in the indent
 ;; region. Subsequent calls to `wisi-indent-region' apply the cached
 ;; indents. Non-whitespace edits to the buffer invalidate the indent
-;; caches in the edited region and after.
+;; caches in the edited region and after. Since we can do partial
+;; parse, we keep a list of parsed regions.
 ;;
 ;; See `wisi--post-change' for the details of what we check for
 ;; invalidating.
@@ -85,7 +95,8 @@
 ;;   The Ada grammar as given in the Ada language reference manual is
 ;;   not LALR(1). So we use a generalized parser. In addition, the
 ;;   semantic lexer is more complex, and gives different information
-;;   than we need.
+;;   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
@@ -120,13 +131,23 @@
 (require 'wisi-elisp-lexer)
 (require 'wisi-fringe)
 
-(defcustom wisi-size-threshold 100000
+(defcustom wisi-size-threshold most-positive-fixnum
   "Max size (in characters) for using wisi parser results for anything."
   :type 'integer
   :group 'wisi
   :safe 'integerp)
 (make-variable-buffer-local 'wisi-size-threshold)
 
+(defcustom wisi-partial-parse-threshold 100001
+  "Min size (in characters) for using partial wisi parser.
+The partial parser finds a nearby start point, and parses a small
+portion of the file containing the region to parse. For indent,
+it assumes the start point is properly indented."
+  :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.
 Language code can set this non-nil when syntax is known to be
@@ -145,6 +166,14 @@ Useful when debugging parser or parser actions."
 (defvar wisi-error-buffer nil
   "Buffer for displaying syntax errors.")
 
+(defun wisi-safe-marker-pos (pos)
+  "Return an integer buffer position from POS, an integer or marker"
+  (cond
+   ((markerp pos)
+    (marker-position pos))
+
+   (t pos)))
+
 ;;;; token info cache
 
 (defvar-local wisi-parse-failed nil
@@ -163,100 +192,183 @@ 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--cache-max
+(defvar-local wisi--cached-regions
   (list
    (cons 'face nil)
    (cons 'navigate nil)
    (cons 'indent nil))
-  "Alist of maximimum position in buffer where parser text properties are 
valid.")
+  "Alist of lists of regions in buffer where parser text properties are valid.
+Regions in a list are in random order.")
+
+(defun wisi--contained-region (begin end region)
+  "Non-nil if BEGIN END (buffer positions) is contained in REGION (a cons of 
positions)."
+  ;; We assume begin < end
+  (and (<= (car region) begin)
+       (<= end (cdr region))))
+
+(defun wisi--contained-pos (pos region)
+  "Non-nil if POS (a buffer position) is contained in REGION (a cons of 
positions)."
+  (and (<= (car region) pos)
+       (<= pos (cdr region))))
+
+(defun wisi-cache-covers-region (begin end &optional parse-action)
+  "Non-nil if BEGIN END is contained in a parsed region."
+  (let ((region-list (cdr (assoc (or parse-action wisi--parse-action) 
wisi--cached-regions)))
+       region)
+    (while (and region-list
+               (not (wisi--contained-region begin end (car region-list))))
+      (pop region-list))
+
+    (when region-list
+      ;; return a nice value for verbosity in wisi-validate-cache
+      (setq region (car region-list))
+      (cons (marker-position (car region)) (marker-position (cdr region))))))
+
+(defun wisi-cache-covers-pos (parse-action pos)
+  "Non-nil if POS is contained in a PARSE-ACTION parsed region."
+  (let ((region-list (cdr (assoc parse-action wisi--cached-regions))))
+    (while (and region-list
+               (not (wisi--contained-pos pos (car region-list))))
+      (pop region-list))
+
+    (when region-list
+      t)))
+
+(defun wisi-cache-contains-pos (parse-action pos)
+  "Non-nil if POS is at or before the end of any PARSE-ACTION parsed region."
+  (let ((region-list (cdr (assoc parse-action wisi--cached-regions)))
+       result)
+    (while (and (not result) region-list)
+      (when (<= pos (cdr (car region-list)))
+       (setq result t))
+      (pop region-list))
+
+    result))
+
+(defun wisi-cache-set-region (region)
+  "Set the cached region list for `wisi--parse-action' to REGION."
+  (setcdr (assoc wisi--parse-action wisi--cached-regions)
+         (list (cons (copy-marker (car region))
+                     (copy-marker (cdr region))))))
+
+(defun wisi-cache-add-region (region)
+  "Add REGION to the cached region list for `wisi--parse-action'."
+  (push (cons (copy-marker (car region))
+             (copy-marker (cdr region)))
+       (cdr (assoc wisi--parse-action wisi--cached-regions))))
+
+(defun wisi-cache-delete-regions-after (parse-action pos)
+  "Delete any PARSE-ACTION parsed region at or after POS.
+Truncate any region that overlaps POS."
+  (let ((region-list (cdr (assoc parse-action wisi--cached-regions)))
+       result)
+    (while (and (not result) region-list)
+      (cond
+       ((and (> pos (car (car region-list)))
+            (<= pos (cdr (car region-list))))
+       ;; region contains POS; keep truncated
+       (push (cons (car (car region-list)) (copy-marker pos)) result))
 
-(defun wisi-cache-max (&optional parse-action)
-  ;; Don't need 'wisi-set-cache-max; (move-marker (wisi-cache-max) foo) works
-  (let ((mark (cdr (assoc (or parse-action wisi--parse-action) 
wisi--cache-max))))
-    (unless (marker-position mark)
-      ;; Sometimes marker gets set to <marker in no buffer>; not clear how.
-      (move-marker mark (point-min)))
-    mark))
+       ((> pos (car (car region-list)))
+       ;; region is entirely before POS; keep
+       (push (car region-list) result))
+
+       ;; else region is entirely after POS; delete
+       )
+
+      (pop region-list))
+    (setcdr (assoc parse-action wisi--cached-regions) result)
+    ))
 
 (defun wisi--delete-face-cache (after)
   (with-silent-modifications
-    (remove-text-properties after (point-max) '(wisi-face nil 'font-lock-face 
nil))
-    ))
+    (remove-text-properties after (point-max) '(font-lock-face nil))
+    )
+  (wisi-cache-delete-regions-after 'face after))
 
 (defun wisi--delete-navigate-cache (after)
   (with-silent-modifications
     ;; This text property is 'wisi-cache', not 'wisi-navigate', for
     ;; historical reasons.
     (remove-text-properties after (point-max) '(wisi-cache nil))
-    ))
+    )
+  (wisi-cache-delete-regions-after 'navigate after))
 
 (defun wisi--delete-indent-cache (after)
   (with-silent-modifications
     (remove-text-properties after (point-max) '(wisi-indent nil))
-    ))
+    )
+  (wisi-cache-delete-regions-after 'indent after))
 
 (defun wisi-invalidate-cache (action after)
   "Invalidate ACTION caches for the current buffer from AFTER to end of 
buffer."
-  (when (< after (wisi-cache-max action))
+  (when (wisi-cache-contains-pos action after)
     (when (> wisi-debug 0) (message "wisi-invalidate-cache %s:%s:%d" action 
(current-buffer) after))
     (cond
      ((eq 'face action)
       (wisi--delete-face-cache after))
 
      ((eq 'navigate action)
-      ;; We goto statement start to ensure that motion within nested
-      ;; structures is properly done (ie prev/next on ’elsif’ is not
-      ;; set by wisi-motion-action if already set by a lower level
-      ;; statement). We don’t do it for ’face or ’indent, because that
-      ;; might require a parse, and they don’t care about nested
-      ;; structures.
-      (save-excursion
-       (goto-char after)
-
-       ;; This is copied from ‘wisi-goto-statement-start’; we can’t
-       ;; call that because it would call ‘wisi-validate-cache’,
-       ;; which would call ‘wisi-invalidate-cache’; infinite loop.
-       ;; If this needed a navigate parse to succeed, we would not
-       ;; get here.
-       (let ((cache (or (wisi-get-cache (point))
-                        (wisi-backward-cache))))
-         (cond
-          ((null cache)
-           ;; at bob
-           nil)
-
-          ((eq 'statement-end (wisi-cache-class cache))
-           ;; If the change did affect part of a structure statement,
-           ;; this is a lower level statement. Otherwise, we are
-           ;; invalidating more than necessary; not a problem.
-           (wisi-goto-start cache)
-           (setq cache (wisi-backward-cache))
-           (when cache ;; else bob
-             (wisi-goto-start cache)))
-
-          (t
-           (wisi-goto-start cache))
-          ))
+      (when (wisi-cache-covers-pos 'navigate after)
+       ;; We goto statement start to ensure that motion within nested
+       ;; structures is properly done (ie prev/next on ’elsif’ is not
+       ;; set by wisi-motion-action if already set by a lower level
+       ;; statement). We don’t do it for ’face or ’indent, because that
+       ;; might require a parse, and they don’t care about nested
+       ;; structures.
+       (save-excursion
+         (goto-char after)
+
+         ;; This is copied from ‘wisi-goto-statement-start’; we can’t
+         ;; call that because it would call ‘wisi-validate-cache’,
+         ;; which would call ‘wisi-invalidate-cache’; infinite loop.
+         ;; If this needed a navigate parse to succeed, we would not
+         ;; get here.
+         (let ((cache (or (wisi-get-cache (point))
+                          (wisi-backward-cache))))
+           (cond
+            ((null cache)
+             ;; at bob
+             nil)
+
+            ((eq 'statement-end (wisi-cache-class cache))
+             ;; If the change did affect part of a structure statement,
+             ;; this is a lower level statement. Otherwise, we are
+             ;; invalidating more than necessary; not a problem.
+             (wisi-goto-start cache)
+             (setq cache (wisi-backward-cache))
+             (when cache ;; else bob
+               (wisi-goto-start cache)))
+
+            (t
+             (wisi-goto-start cache))
+            ))
 
-       (setq after (point)))
+         (setq after (point))))
       (wisi--delete-navigate-cache after))
 
      ((eq 'indent action)
-      ;; indent cache is stored on newline before line being indented.
+      ;; The indent cache is stored on newline before line being
+      ;; indented. We delete that, because changing text on a line can
+      ;; change the indent of that line.
       (setq after
            (save-excursion
              (goto-char after)
              (line-beginning-position)))
       (wisi--delete-indent-cache (max 1 (1- after))))
      )
-    (move-marker (wisi-cache-max action) after)
     ))
 
 (defun wisi-reset-parser ()
-  "For ’ada-reset-parser’."
-  (wisi-invalidate-cache 'indent 0)
-  (wisi-invalidate-cache 'face 0)
-  (wisi-invalidate-cache 'navigate 0))
+  "Force a parse."
+  (interactive)
+  (wisi-invalidate-cache 'indent (point-min))
+  (wisi-invalidate-cache 'face (point-min))
+  (wisi-invalidate-cache 'navigate (point-min))
+  (wisi-set-parse-try t 'indent)
+  (wisi-set-parse-try t 'face)
+  (wisi-set-parse-try t 'navigate)
+  (wisi-fringe-clean))
 
 ;; wisi--change-* keep track of buffer modifications.
 ;; If wisi--change-end comes before wisi--change-beg, it means there were
@@ -323,21 +435,35 @@ Used to ignore whitespace changes in before/after change 
hooks.")
   "For `after-change-functions'"
   ;; begin . end is range of text being inserted (empty if equal);
   ;; length is the size of the deleted text.
-  ;;
-  ;; This change might be changing to/from a keyword; trigger
-  ;; font-lock. See test/ada_mode-interactive_common.adb Obj_1.
-  (unless wisi-indenting-p
+
+  ;; Remove caches on inserted text, which could have caches from
+  ;; anywhere, and are in any case invalid.
+
+  ;; If the insertion changes a word that has wisi fontification,
+  ;; remove fontification from the entire word, so it is all
+  ;; refontified consistently.
+
+  (let (word-begin word-end)
     (save-excursion
-      (let (word-end)
-       (goto-char end)
-       (skip-syntax-forward "w_")
-       (setq word-end (point))
-       (goto-char begin)
-       (skip-syntax-backward "w_")
+      (goto-char end)
+      (skip-syntax-forward "w_")
+      (setq word-end (point))
+      (goto-char begin)
+      (skip-syntax-backward "w_")
+      (setq word-begin (point)))
+    (if (get-text-property word-begin 'font-lock-face)
        (with-silent-modifications
-         (remove-text-properties (point) word-end '(font-lock-face nil 
fontified nil)))
-       )
-      )))
+         (remove-text-properties
+          word-begin word-end
+          '(font-lock-face nil wisi-cache nil wisi-indent nil fontified nil)))
+
+      ;; No point in removing
+      ;; 'fontified here; that's already handled by jit-lock.
+      (with-silent-modifications
+       (remove-text-properties
+        begin end
+        '(font-lock-face nil wisi-cache nil wisi-indent nil))))
+    ))
 
 (defun wisi--post-change (begin end)
   "Update wisi text properties for changes in region BEG END."
@@ -346,14 +472,6 @@ Used to ignore whitespace changes in before/after change 
hooks.")
   ;; see comments above on syntax-propertize
   (when (< emacs-major-version 25) (syntax-propertize end))
 
-  ;; Remove caches on inserted text, which could have caches from
-  ;; before the failed parse (or another buffer), and are in any case
-  ;; invalid. No point in removing 'fontified; that's handled by
-  ;; jit-lock.
-
-  (with-silent-modifications
-    (remove-text-properties begin end '(wisi-cache nil font-lock-face nil)))
-
   (save-excursion
     (let ((need-invalidate t)
          (done nil)
@@ -547,19 +665,31 @@ Usefull if the parser appears to be hung."
   ;; also force re-parse
   (dolist (parse-action '(face navigate indent))
     (wisi-set-parse-try t parse-action)
-    (move-marker (wisi-cache-max parse-action) (point-max));; force delete 
caches
     (wisi-invalidate-cache parse-action (point-min)))
   )
 
-(defun wisi--run-parse ()
-  "Run the parser."
-  (unless (buffer-narrowed-p)
-    (let ((msg (when (> wisi-debug 0)
-                (format "wisi: parsing %s %s:%d ..."
-                        wisi--parse-action
-                        (buffer-name)
-                        (line-number-at-pos (point))))))
-      (when (> wisi-debug 0)
+(defun wisi-partial-parse-p (begin end)
+  (and (wisi-process--parser-p wisi--parser)
+       (not (and (= begin (point-min))
+                (= end (point-max))))
+       (>= (point-max) wisi-partial-parse-threshold)))
+
+(defun wisi--run-parse (begin parse-end)
+  "Run the parser, on at least region BEGIN PARSE-END."
+  (unless (or (buffer-narrowed-p)
+             (= (point-min) (point-max))) ;; some parsers can’t handle an 
empty buffer.
+    (let* ((partial-parse-p (wisi-partial-parse-p begin parse-end))
+          (msg (when (> wisi-debug 0)
+                 (format "wisi: %sparsing %s %s:%d %d %d ..."
+                         (if partial-parse-p "partial " "")
+                         wisi--parse-action
+                         (buffer-name)
+                         begin
+                         (if (markerp parse-end) (marker-position parse-end) 
parse-end)
+                         (line-number-at-pos begin))))
+          (parsed-region nil))
+
+      (when msg
        (message msg))
 
       (setq wisi--last-parse-action wisi--parse-action)
@@ -573,19 +703,25 @@ Usefull if the parser appears to be hung."
 
       (condition-case-unless-debug err
          (save-excursion
-           (wisi-parse-current wisi--parser)
-           (setq wisi-parse-failed nil)
-           (move-marker (wisi-cache-max) (point))
-           )
+           (if partial-parse-p
+             (let ((send-region (wisi-parse-expand-region wisi--parser begin 
parse-end)))
+               (setq parsed-region (wisi-parse-current wisi--parser (car 
send-region) (cdr send-region) parse-end))
+               (wisi-cache-add-region parsed-region))
+
+             ;; parse full buffer
+             (setq parsed-region (cons (point-min) (point-max)))
+             (wisi-cache-set-region (wisi-parse-current wisi--parser 
(point-min) (point-max) (point-max))))
+
+           (when (> wisi-debug 0) (message "... parsed %s" parsed-region))
+           (setq wisi-parse-failed nil))
        (wisi-parse-error
         (cl-ecase wisi--parse-action
           (face
-           ;; caches set by failed parse are ok
-           (wisi--delete-face-cache (wisi-cache-max)))
+           ;; caches set by failed elisp parse are ok
+           (wisi--delete-face-cache (cdr parsed-region)))
 
           (navigate
-           ;; parse partially resets caches before and after wisi-cache-max
-           (move-marker (wisi-cache-max) (point-min))
+           ;; elisp parse partially resets caches
            (wisi--delete-navigate-cache (point-min)))
 
           (indent
@@ -600,12 +736,13 @@ Usefull if the parser appears to be hung."
         (signal (car err) (cdr err)))
        )
 
-      (wisi-fringe-display-errors
-       (append
-       (seq-map (lambda (err) (wisi--lexer-error-pos err)) 
(wisi-parser-lexer-errors wisi--parser))
-       (seq-map (lambda (err) (wisi--parse-error-pos err)) 
(wisi-parser-parse-errors wisi--parser))))
+      (unless partial-parse-p
+       (wisi-fringe-display-errors
+        (append
+         (seq-map (lambda (err) (wisi--lexer-error-pos err)) 
(wisi-parser-lexer-errors wisi--parser))
+         (seq-map (lambda (err) (wisi--parse-error-pos err)) 
(wisi-parser-parse-errors wisi--parser)))))
 
-      (when (> wisi-debug 0)
+      (when (> wisi-debug 1)
        (if (or (wisi-parser-lexer-errors wisi--parser)
                (wisi-parser-parse-errors wisi--parser))
            (progn
@@ -632,23 +769,30 @@ Usefull if the parser appears to be hung."
     (move-marker wisi--change-end (point-min))
     ))
 
-(defun wisi-validate-cache (pos error-on-fail parse-action)
-  "Ensure cached data for PARSE-ACTION is valid at least up to POS in current 
buffer."
-  (if (< (point-max) wisi-size-threshold)
+(defun wisi-validate-cache (begin end error-on-fail parse-action)
+  "Ensure cached data for PARSE-ACTION is valid in region BEGIN END in current 
buffer."
+  (if (and (not wisi-inhibit-parse)
+          (< (point-max) wisi-size-threshold))
       (let ((wisi--parse-action parse-action))
        (wisi--check-change)
 
-       ;; Now we can rely on wisi-cache-max.
-
-       ;; If wisi-cache-max = pos, then there is no cache at pos; need parse
-       (when (and (not wisi-inhibit-parse)
-                  (wisi-parse-try)
-                  (<= (wisi-cache-max) pos))
+       ;; Now we can rely on wisi-cache-covers-region
 
-         ;; Don't keep retrying failed parse until text changes again.
-         (wisi-set-parse-try nil)
-
-         (wisi--run-parse))
+       (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))
+
+         (when (> wisi-debug 0)
+           (message "parse %s skipped: parse-failed %s parse-try %s 
cache-covers-region %s %s.%s"
+                    parse-action
+                    wisi-parse-failed
+                    (wisi-parse-try)
+                    (wisi-cache-covers-region begin end)
+                    begin end)))
 
        ;; We want this error even if we did not try to parse; it means
        ;; the parse results are not valid.
@@ -656,11 +800,14 @@ Usefull if the parser appears to be hung."
          (error "parse %s failed" parse-action))
        )
     (when (> wisi-debug 0)
-      (message "parse skipped due to ‘wisi-size-threshold’"))))
+      (message "parse %s skipped inihibit-parse %s wisi-size-threshold %d"
+              parse-action
+              wisi-inhibit-parse
+              wisi-size-threshold))))
 
-(defun wisi-fontify-region (_begin end)
+(defun wisi-fontify-region (begin end)
   "For `jit-lock-functions'."
-  (wisi-validate-cache end nil 'face))
+  (wisi-validate-cache begin end nil 'face))
 
 (defun wisi-get-containing-cache (cache)
   "Return cache from (wisi-cache-containing CACHE)."
@@ -746,7 +893,7 @@ If LIMIT (a buffer position) is reached, throw an error."
 cache. Otherwise move to cache-next, or cache-end, or next cache
 if both nil.  Return cache found."
   (unless (eobp)
-    (wisi-validate-cache (point-max) t 'navigate) ;; ensure there is a next 
cache to move to
+    (wisi-validate-cache (point-min) (point-max) t 'navigate)
     (let ((cache (wisi-get-cache (point))))
       (if (and cache
               (not (eq (wisi-cache-class cache) 'statement-end)))
@@ -763,7 +910,7 @@ if both nil.  Return cache found."
 (defun wisi-backward-statement-keyword ()
   "If not at a cached token, move backward to prev
 cache. Otherwise move to cache-prev, or prev cache if nil."
-  (wisi-validate-cache (point) t 'navigate)
+  (wisi-validate-cache (point-min) (point-max) t 'navigate)
   (let ((cache (wisi-get-cache (point)))
        prev)
     (when cache
@@ -849,14 +996,14 @@ Return start cache."
   "Move point to token at start of statement point is in or after.
 Return start cache."
   (interactive)
-  (wisi-validate-cache (point) t 'navigate)
+  (wisi-validate-cache (point-min) (point-max) t 'navigate)
   (wisi-goto-start (or (wisi-get-cache (point))
                       (wisi-backward-cache))))
 
 (defun wisi-goto-statement-end ()
   "Move point to token at end of statement point is in or before."
   (interactive)
-  (wisi-validate-cache (point) t 'navigate)
+  (wisi-validate-cache (point-min) (point-max) t 'navigate)
   (let ((cache (or (wisi-get-cache (point))
                   (wisi-forward-cache))))
     (when (wisi-cache-end cache)
@@ -914,7 +1061,7 @@ the comment on the previous line."
 
 (defun wisi-indent-statement ()
   "Indent region given by `wisi-goto-start', `wisi-cache-end'."
-  (wisi-validate-cache (point) t 'navigate)
+  (wisi-validate-cache (point-min) (point-max) t 'navigate)
 
   (save-excursion
     (let ((cache (or (wisi-get-cache (point))
@@ -965,8 +1112,43 @@ Called with BEGIN END.")
        ;; single line in buffer; terminate loop
        (goto-char (point-max))))))
 
-(defun wisi-indent-region (begin end)
-  "For `indent-region-function', using the wisi indentation engine."
+(defun wisi-list-memq (a b)
+  "Return non-nil if any member of A is a memq of B."
+  (let ((temp (copy-sequence a))
+       result)
+    (while (and (not result)
+               temp)
+      (when (memq (pop temp) b)
+       (setq result t)))
+    result))
+
+(defun wisi--get-cached-indent (begin end)
+  "Return cached indent for point (must be bol), after correcting
+for parse errors. BEGIN, END is the parsed region."
+  (let ((indent (get-text-property (1- (point)) 'wisi-indent)))
+    (unless indent
+      (error "nil indent for line %d" (line-number-at-pos (point))))
+    (when (and (wisi-partial-parse-p begin end)
+              (< 0 (length (wisi-parser-parse-errors wisi--parser))))
+      (dolist (err (wisi-parser-parse-errors wisi--parser))
+       (dolist (repair (wisi--parse-error-repair err))
+         ;; point is at bol; error pos may be at first token on same line.
+         (save-excursion
+           (back-to-indentation)
+           (when (>= (point) (wisi--parse-error-repair-pos repair))
+             (setq indent (max 0 (wisi-parse-adjust-indent wisi--parser indent 
repair))))
+           ))))
+    indent))
+
+(defun wisi-indent-region (begin end &optional indent-blank-lines)
+  "For `indent-region-function', using the wisi indentation engine.
+If INDENT-BLANK-LINES is non-nil, also indent blank lines (for use as
+`indent-line-function')."
+  (when (< 0 wisi-debug)
+    (message "wisi-indent-region %d %d"
+            (wisi-safe-marker-pos begin)
+            (wisi-safe-marker-pos end)))
+
   (let ((wisi--parse-action 'indent)
        (parse-required nil)
        (end-mark (copy-marker end))
@@ -974,14 +1156,15 @@ Called with BEGIN END.")
 
     (wisi--check-change)
 
-    ;; Always indent the line containing BEGIN.
+    ;; BEGIN is inclusive; END is exclusive.
     (save-excursion
       (goto-char begin)
       (setq begin (line-beginning-position))
 
       (when (bobp) (forward-line))
       (while (and (not parse-required)
-                 (<= (point) end)
+                 (or (and (= begin end) (= (point) end))
+                     (< (point) end))
                  (not (eobp)))
        (unless (get-text-property (1- (point)) 'wisi-indent)
          (setq parse-required t))
@@ -989,18 +1172,23 @@ Called with BEGIN END.")
       )
 
     ;; A parse either succeeds and sets the indent cache on all
-    ;; lines in the buffer, or fails and leaves valid caches
+    ;; lines in the parsed region, or fails and leaves valid caches
     ;; untouched.
     (when (and parse-required
-              (wisi-parse-try))
+              (or (not wisi-parse-failed)
+                  (wisi-parse-try 'indent)))
 
       (wisi-set-parse-try nil)
-      (wisi--run-parse)
+
+      (wisi--run-parse begin end)
 
       ;; If there were errors corrected, the indentation is
-      ;; potentially ambiguous; see test/ada_mode-interactive_2.adb
-      (setq wisi-indent-failed (< 0 (+ (length (wisi-parser-lexer-errors 
wisi--parser))
-                                      (length (wisi-parser-parse-errors 
wisi--parser)))))
+      ;; potentially ambiguous; see
+      ;; test/ada_mode-interactive_2.adb. Or it was a partial parse,
+      ;; where errors producing bad indent are pretty much expected.
+      (unless (wisi-partial-parse-p begin end)
+       (setq wisi-indent-failed (< 0 (+ (length (wisi-parser-lexer-errors 
wisi--parser))
+                                        (length (wisi-parser-parse-errors 
wisi--parser))))))
       )
 
     (if wisi-parse-failed
@@ -1008,6 +1196,8 @@ Called with BEGIN END.")
          ;; primary indent failed
          (setq wisi-indent-failed t)
          (when (functionp wisi-indent-region-fallback)
+           (when (< 0 wisi-debug)
+             (message "wisi-indent-region fallback"))
            (funcall wisi-indent-region-fallback begin end)))
 
       (save-excursion
@@ -1015,22 +1205,28 @@ Called with BEGIN END.")
        (goto-char begin)
        (let ((wisi-indenting-p t))
          (while (and (not (eobp))
-                     (<= (point) end-mark)) ;; end-mark can be at the start of 
an empty line
-           (indent-line-to (if (bobp) 0 (get-text-property (1- (point)) 
'wisi-indent)))
-           (forward-line 1)))
-
-       ;; Run wisi-indent-calculate-functions
-       (when wisi-indent-calculate-functions
-         (goto-char begin)
-         (while (and (not (eobp))
-                     (< (point) end-mark))
-           (back-to-indentation)
-           (let ((indent
-                  (run-hook-with-args-until-success 
'wisi-indent-calculate-functions)))
-             (when indent
-               (indent-line-to indent)))
-
-           (forward-line 1)))
+                     (or (and (= begin end) (= (point) end))
+                         (< (point) end-mark))) ;; end-mark is exclusive
+           (when (or indent-blank-lines (not (eolp)))
+             ;; ’indent-region’ doesn’t indent an empty line; ’indent-line’ 
does
+             (let ((indent (if (bobp) 0 (wisi--get-cached-indent begin end))))
+                   (indent-line-to indent))
+             )
+           (forward-line 1))
+
+         ;; Run wisi-indent-calculate-functions
+         (when wisi-indent-calculate-functions
+           (goto-char begin)
+           (while (and (not (eobp))
+                       (< (point) end-mark))
+             (back-to-indentation)
+             (let ((indent
+                    (run-hook-with-args-until-success 
'wisi-indent-calculate-functions)))
+               (when indent
+                 (indent-line-to indent)))
+
+             (forward-line 1)))
+         )
 
        (when
            (and prev-indent-failed
@@ -1038,6 +1234,8 @@ Called with BEGIN END.")
          ;; Previous parse failed or indent was potentially
          ;; ambiguous, this one is not.
          (goto-char end-mark)
+         (when (< 0 wisi-debug)
+           (message "wisi-indent-region post-parse-fail-hook"))
          (run-hooks 'wisi-post-indent-fail-hook))
        ))
     ))
@@ -1050,7 +1248,7 @@ Called with BEGIN END.")
     (when (>= (point) savep)
       (setq to-indent t))
 
-    (wisi-indent-region (line-beginning-position) (line-end-position))
+    (wisi-indent-region (line-beginning-position) (line-end-position) t)
 
     (goto-char savep)
     (when to-indent (back-to-indentation))
@@ -1093,7 +1291,6 @@ Called with BEGIN END.")
       (if (buffer-live-p wisi-error-buffer)
          (let ((err
                 (with-current-buffer wisi-error-buffer
-                  ;; FIXME: ensure at beginning of error message line.
                   (get-text-property (point) 'wisi-error-data))))
            (wisi-repair-error-1 err))
        (error "no current error found")
@@ -1122,6 +1319,24 @@ If non-nil, only repair errors in BEG END region."
 
 ;;;; debugging
 
+(defun wisi-show-region (string)
+  (interactive "Mregion: ")
+  (when (not (= ?\( (aref string 0)))
+    (setq string (concat "(" string ")")))
+
+  (let ((region (read string)))
+    (cond
+     ((consp (cdr region))
+      ;; region is a list; (begin end)
+      (set-mark  (nth 0 region))
+      (goto-char (nth 1 region)))
+
+     ((consp region)
+      ;; region is a cons; (begin . end)
+      (set-mark  (car region))
+      (goto-char (cdr region)))
+     )))
+
 (defun wisi-debug-keys ()
   "Add debug key definitions to `global-map'."
   (interactive)
@@ -1130,29 +1345,41 @@ If non-nil, only repair errors in BEG END region."
   (define-key global-map "\M-j" 'wisi-show-cache)
   )
 
-(defun wisi-parse-buffer (&optional parse-action)
+(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 (setq parse-action 'indent))
+  (unless parse-action
+    (setq parse-action (wisi-read-parse-action)))
+  (if (use-region-p)
+      (progn
+       (setq begin (region-beginning))
+       (setq end   (region-end)))
+
+    (unless begin (setq begin (point-min)))
+    (unless end (setq end (point-max))))
+
   (wisi-set-parse-try t parse-action)
-  (move-marker (wisi-cache-max parse-action) (point-max));; force delete caches
-  (wisi-invalidate-cache parse-action (point-min))
+  (wisi-invalidate-cache parse-action begin)
 
   (cl-ecase parse-action
     (face
      (with-silent-modifications
        (remove-text-properties
-       (point-min) (point-max)
+       begin end
        (list
         'font-lock-face nil
         'fontified nil)))
-     (wisi-validate-cache (point-max) t parse-action)
+     (wisi-validate-cache begin end t parse-action)
      (when (fboundp 'font-lock-ensure) (font-lock-ensure))) ;; emacs < 25
 
     (navigate
-     (wisi-validate-cache (point-max) t parse-action))
+     (wisi-validate-cache begin end t parse-action))
 
     (indent
-     (wisi-indent-region (point-min) (point-max)))
+     (wisi-indent-region begin end))
     ))
 
 (defun wisi-time (func count &optional report-wait-time)
@@ -1197,7 +1424,6 @@ If non-nil, only repair errors in BEG END region."
     (wisi-time
      (lambda ()
        (wisi-set-parse-try t 'indent)
-       (move-marker (wisi-cache-max 'indent) (point-max));; force delete caches
        (wisi-invalidate-cache 'indent (point-min))
        (wisi-indent-line)
        (when (wisi-process--parser-p wisi--parser)
@@ -1208,7 +1434,6 @@ If non-nil, only repair errors in BEG END region."
 
 (defun wisi-time-indent-middle-line-warm-cache (count)
   (wisi-set-parse-try t 'indent)
-  (move-marker (wisi-cache-max 'indent) (point-max));; force delete caches
   (wisi-invalidate-cache 'indent (point-min))
   (goto-char (point-min))
   (forward-line (/ (count-lines (point-min) (point-max)) 2))
@@ -1221,11 +1446,10 @@ If non-nil, only repair errors in BEG END region."
   (message "%s" (get-text-property (1- (line-beginning-position)) 
'wisi-indent)))
 
 (defun wisi-show-cache ()
-  "Show navigation and face caches, and applied faces, at point."
+  "Show navigation cache, and applied faces, at point."
   (interactive)
-  (message "%s:%s:%s:%s"
+  (message "%s:%s:%s"
           (wisi-get-cache (point))
-          (get-text-property (point) 'wisi-face)
           (get-text-property (point) 'face)
           (get-text-property (point) 'font-lock-face)
           ))
@@ -1238,10 +1462,6 @@ If non-nil, only repair errors in BEG END region."
       (message "previous %s" (wisi-backward-cache)))
     ))
 
-(defun wisi-show-cache-max (action)
-  (push-mark)
-  (goto-char (wisi-cache-max action)))
-
 ;;;;; setup
 
 (cl-defun wisi-setup (&key indent-calculate post-indent-fail parser lexer)
@@ -1251,12 +1471,11 @@ If non-nil, only repair errors in BEG END region."
 
   (setq wisi--parser parser)
   (setq wisi--lexer lexer)
-
-  (setq wisi--cache-max
+  (setq wisi--cached-regions
        (list
-        (cons 'face (copy-marker (point-min)))
-        (cons 'navigate (copy-marker (point-min)))
-        (cons 'indent (copy-marker (point-min)))))
+        (cons 'face nil)
+        (cons 'navigate nil)
+        (cons 'indent nil)))
 
   (setq wisi--parse-try
        (list
diff --git a/wisitoken-bnf-generate.adb b/wisitoken-bnf-generate.adb
index 8d2da44..d74b4a5 100644
--- a/wisitoken-bnf-generate.adb
+++ b/wisitoken-bnf-generate.adb
@@ -3,7 +3,7 @@
 --  Parser for Wisi grammar files, producing Ada or Elisp source
 --  files for a parser.
 --
---  Copyright (C) 2012 - 2015, 2017, 2018 Free Software Foundation, Inc.
+--  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
@@ -54,7 +54,7 @@ is
    begin
       --  verbosity meaning is actually determined by output choice;
       --  they should be consistent with this description.
-      Put_Line (Standard_Error, "version 1.0.1");
+      Put_Line (Standard_Error, "version 1.1.0");
       Put_Line (Standard_Error, "wisi-generate [options] {wisi grammar file}");
       Put_Line (Standard_Error, "Generate source code implementing a parser 
for the grammar.");
       New_Line (Standard_Error);
diff --git a/wisitoken-bnf-generate_utils.adb b/wisitoken-bnf-generate_utils.adb
index 01bc419..8e52ca4 100644
--- a/wisitoken-bnf-generate_utils.adb
+++ b/wisitoken-bnf-generate_utils.adb
@@ -118,7 +118,7 @@ package body WisiToken.BNF.Generate_Utils is
       begin
          Data.Grammar (Descriptor.Accept_ID) :=
            Descriptor.Accept_ID <= Only
-             (Find_Token_ID (Data, Start_Token) & Descriptor.EOF_ID + 
WisiToken.Syntax_Trees.Null_Action);
+             (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);
@@ -213,7 +213,7 @@ package body WisiToken.BNF.Generate_Utils is
 
    function Initialize (Input_Data : aliased in 
WisiToken_Grammar_Runtime.User_Data_Type) return Generate_Data
    is
-      EOF_ID : constant Token_ID := Token_ID
+      EOI_ID : constant Token_ID := Token_ID
         (Count (Input_Data.Tokens.Non_Grammar) + Count 
(Input_Data.Tokens.Tokens)) + Token_ID
           (Input_Data.Tokens.Keywords.Length) + Token_ID'First;
    begin
@@ -225,11 +225,11 @@ package body WisiToken.BNF.Generate_Utils is
               (if Count (Input_Data.Tokens.Non_Grammar) > 0
                then Token_ID (Count (Input_Data.Tokens.Non_Grammar)) + 
Token_ID'First
                else Token_ID'First),
-            Last_Terminal     => EOF_ID,
-            EOF_ID            => EOF_ID,
-            Accept_ID         => EOF_ID + 1,
-            First_Nonterminal => EOF_ID + 1,
-            Last_Nonterminal  => EOF_ID + 1 + Token_ID 
(Input_Data.Tokens.Rules.Length)),
+            Last_Terminal     => EOI_ID,
+            EOI_ID            => EOI_ID,
+            Accept_ID         => EOI_ID + 1,
+            First_Nonterminal => EOI_ID + 1,
+            Last_Nonterminal  => EOI_ID + 1 + Token_ID 
(Input_Data.Tokens.Rules.Length)),
 
          others => <>)
       do
diff --git a/wisitoken-bnf-generate_utils.ads b/wisitoken-bnf-generate_utils.ads
index 9e2ff96..7720e21 100644
--- a/wisitoken-bnf-generate_utils.ads
+++ b/wisitoken-bnf-generate_utils.ads
@@ -27,7 +27,7 @@ with WisiToken_Grammar_Runtime;
 package WisiToken.BNF.Generate_Utils is
 
    EOI_Name : constant String := "Wisi_EOI";
-   --  EOI_Name is used for EOF_ID token; it must match Emacs ada-mode
+   --  EOI_Name is used for Descriptor.EOI_ID token; it must match Emacs 
ada-mode
    --  wisi.el wisi-eoi-term. It must be a valid Ada identifier when
    --  "_ID" is appended.
 
diff --git a/wisitoken-bnf-output_ada.adb b/wisitoken-bnf-output_ada.adb
index 0d99340..d27c602 100644
--- a/wisitoken-bnf-output_ada.adb
+++ b/wisitoken-bnf-output_ada.adb
@@ -4,7 +4,7 @@
 --  parameters, and a parser for that grammar. The grammar parser
 --  actions must be Ada.
 --
---  Copyright (C) 2017, 2018 Free Software Foundation, Inc.
+--  Copyright (C) 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
@@ -191,11 +191,13 @@ is
                      Unref_Lexer   : constant Boolean := 0 = Index (Line, 
"Lexer");
                      Unref_Nonterm : constant Boolean := 0 = Index (Line, 
"Nonterm");
                      Unref_Tokens  : constant Boolean := 0 = Index (Line, 
"Tokens");
+                     Unref_Recover : constant Boolean := 0 = Index (Line, 
"Recover_Active");
                   begin
                      Indent_Line ("function " & Name);
-                     Indent_Line (" (Lexer   : access constant 
WisiToken.Lexer.Instance'Class;");
-                     Indent_Line ("  Nonterm : in out 
WisiToken.Recover_Token;");
-                     Indent_Line ("  Tokens  : in     
WisiToken.Recover_Token_Array)");
+                     Indent_Line (" (Lexer          : access constant 
WisiToken.Lexer.Instance'Class;");
+                     Indent_Line ("  Nonterm        : in out 
WisiToken.Recover_Token;");
+                     Indent_Line ("  Tokens         : in     
WisiToken.Recover_Token_Array;");
+                     Indent_Line ("  Recover_Active : in     Boolean)");
                      Indent_Line (" return 
WisiToken.Semantic_Checks.Check_Status");
                      Indent_Line ("is");
 
@@ -208,6 +210,9 @@ is
                      if Unref_Tokens then
                         Indent_Line ("   pragma Unreferenced (Tokens);");
                      end if;
+                     if Unref_Recover then
+                        Indent_Line ("   pragma Unreferenced 
(Recover_Active);");
+                     end if;
 
                      Indent_Line ("begin");
                      Indent := Indent + 3;
diff --git a/wisitoken-bnf-output_ada_common.adb 
b/wisitoken-bnf-output_ada_common.adb
index de9db8c..cbd36b7 100644
--- a/wisitoken-bnf-output_ada_common.adb
+++ b/wisitoken-bnf-output_ada_common.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
@@ -138,7 +138,7 @@ package body WisiToken.BNF.Output_Ada_Common is
       Indent_Line ("Last_Terminal                 =>" & 
WisiToken.Token_ID'Image (Descriptor.Last_Terminal) & ",");
       Indent_Line ("First_Nonterminal             =>" & 
WisiToken.Token_ID'Image (Descriptor.First_Nonterminal) & ",");
       Indent_Line ("Last_Nonterminal              =>" & 
WisiToken.Token_ID'Image (Descriptor.Last_Nonterminal) & ",");
-      Indent_Line ("EOF_ID                        =>" & 
WisiToken.Token_ID'Image (Descriptor.EOF_ID) & ",");
+      Indent_Line ("EOI_ID                        =>" & 
WisiToken.Token_ID'Image (Descriptor.EOI_ID) & ",");
       Indent_Line ("Accept_ID                     =>" & 
WisiToken.Token_ID'Image (Descriptor.Accept_ID) & ",");
       Indent_Line ("Case_Insensitive              => " & Image 
(Input_Data.Language_Params.Case_Insensitive) & ",");
       Indent_Line ("New_Line_ID                   =>" & 
WisiToken.Token_ID'Image (Descriptor.New_Line_ID) & ",");
@@ -232,9 +232,10 @@ package body WisiToken.BNF.Output_Ada_Common is
             for Name of Name_List.all loop
                if Name /= null then
                   Indent_Line ("function " & Name.all);
-                  Indent_Line (" (Lexer   : access constant 
WisiToken.Lexer.Instance'Class;");
-                  Indent_Line ("  Nonterm : in out WisiToken.Recover_Token;");
-                  Indent_Line ("  Tokens  : in     
WisiToken.Recover_Token_Array)");
+                  Indent_Line (" (Lexer          : access constant 
WisiToken.Lexer.Instance'Class;");
+                  Indent_Line ("  Nonterm        : in out 
WisiToken.Recover_Token;");
+                  Indent_Line ("  Tokens         : in     
WisiToken.Recover_Token_Array;");
+                  Indent_Line ("  Recover_Active : in     Boolean)");
                   Indent_Line (" return 
WisiToken.Semantic_Checks.Check_Status;");
                end if;
             end loop;
@@ -535,7 +536,7 @@ package body WisiToken.BNF.Output_Ada_Common is
      (Input_Data    : in WisiToken_Grammar_Runtime.User_Data_Type;
       Generate_Data : in WisiToken.BNF.Generate_Utils.Generate_Data)
    is
-      use all type Ada.Containers.Count_Type;
+      use all type WisiToken.Parse.LR.All_Parse_Action_Verbs;
       use Ada.Strings.Unbounded;
 
       Table            : WisiToken.Parse.LR.Parse_Table_Ptr renames 
Generate_Data.LR_Parse_Table;
@@ -711,10 +712,12 @@ package body WisiToken.BNF.Output_Ada_Common is
             end loop;
          end Gotos;
 
-         if Table.States (State_Index).Minimal_Complete_Actions.Length > 0 then
+         if Input_Data.Language_Params.Error_Recover and
+           Table.States (State_Index).Minimal_Complete_Action.Verb /= 
Parse.LR.Pause
+         then
             Indent_Wrap
-              ("Set_Minimal_Action (Table.States (" & Trimmed_Image 
(State_Index) & ").Minimal_Complete_Actions, " &
-                 WisiToken.Parse.LR.Image (Table.States 
(State_Index).Minimal_Complete_Actions, Strict => True) & ");");
+              ("Table.States (" & Trimmed_Image (State_Index) & 
").Minimal_Complete_Action := " &
+                 WisiToken.Parse.LR.Strict_Image (Table.States 
(State_Index).Minimal_Complete_Action) & ";");
          end if;
 
          if Line_Count > Lines_Per_Subr then
@@ -834,7 +837,7 @@ package body WisiToken.BNF.Output_Ada_Common is
       case Common_Data.Interface_Kind is
       when Process =>
          Indent_Line ("   Trace,");
-         Indent_Line ("   Lexer.New_Lexer (Trace),");
+         Indent_Line ("   Lexer.New_Lexer (Trace.Descriptor),");
          Indent_Line ("   Table,");
          if Input_Data.Language_Params.Error_Recover then
             Indent_Line ("   Language_Fixes,");
@@ -876,7 +879,7 @@ package body WisiToken.BNF.Output_Ada_Common is
          Indent_Line ("return Parser : 
WisiToken.Parse.Packrat.Generated.Parser do");
          Indent := Indent + 3;
          Indent_Line ("Parser.Trace := Trace;");
-         Indent_Line ("Parser.Lexer := Lexer.New_Lexer (Trace);");
+         Indent_Line ("Parser.Lexer := Lexer.New_Lexer (Trace.Descriptor);");
          Indent_Line ("Parser.User_Data := User_Data;");
          Indent_Line ("Parser.Parse_WisiToken_Accept := 
Parse_wisitoken_accept_1'Access;");
          Indent := Indent - 3;
@@ -921,7 +924,7 @@ package body WisiToken.BNF.Output_Ada_Common is
          Indent_Line ("return WisiToken.Parse.Packrat.Procedural.Create");
          Indent_Line
            ("  (Grammar, Direct_Left_Recursive, " & Trimmed_Image 
(Generate_Data.Descriptor.Accept_ID) &
-              ", Trace, Lexer.New_Lexer (Trace), User_Data);");
+              ", Trace, Lexer.New_Lexer (Trace.Descriptor), User_Data);");
       end case;
       Indent := Indent - 3;
       Indent_Line ("end Create_Parser;");
@@ -983,7 +986,7 @@ package body WisiToken.BNF.Output_Ada_Common is
       Indent_Line ("int            line_token_start; // line at start of 
current token");
       Indent_Line ("unsigned char* marker;           // saved cursor");
       Indent_Line ("size_t         marker_pos;       // saved character 
position");
-      Indent_Line ("size_t         marker_line;      // saved line ");
+      Indent_Line ("size_t         marker_line;      // saved line");
       Indent_Line ("unsigned char* context;          // saved cursor");
       Indent_Line ("size_t         context_pos;      // saved character 
position");
       Indent_Line ("int            context_line;     // saved line");
@@ -1012,13 +1015,16 @@ package body WisiToken.BNF.Output_Ada_Common is
       Indent_Line ("   (unsigned char* input, size_t length, int verbosity)");
       Indent_Line ("{");
       Indent := Indent + 3;
-      Indent_Line ("wisi_lexer* result  = malloc (sizeof (wisi_lexer));");
-      Indent_Line ("result->buffer      = input;");
-      Indent_Line ("result->buffer_last = input + length - 1;");
-      Indent_Line ("result->cursor      = input;");
-      Indent_Line ("result->char_pos    = 1;");
-      Indent_Line ("result->line        = (*result->cursor == 0x0A) ? 2 : 1;");
-      Indent_Line ("result->verbosity   = verbosity;");
+      Indent_Line ("wisi_lexer* result        = malloc (sizeof 
(wisi_lexer));");
+      Indent_Line ("result->buffer            = input;");
+      Indent_Line ("result->buffer_last       = input + length - 1;");
+      Indent_Line ("result->cursor            = input;");
+      Indent_Line ("result->byte_token_start  = input;");
+      Indent_Line ("result->char_pos          = 1;");
+      Indent_Line ("result->char_token_start  = 1;");
+      Indent_Line ("result->line              = (*result->cursor == 0x0A) ? 2 
: 1;");
+      Indent_Line ("result->line_token_start  = result->line;");
+      Indent_Line ("result->verbosity         = verbosity;");
       Indent_Line ("return result;");
       Indent := Indent - 3;
       Indent_Line ("}");
@@ -1077,10 +1083,13 @@ package body WisiToken.BNF.Output_Ada_Common is
       Indent_Line ("static void skip(wisi_lexer* lexer)");
       Indent_Line ("{");
       Indent := Indent + 3;
-      Indent_Line ("if (lexer->cursor <= lexer->buffer_last) 
++lexer->cursor;");
       Indent_Line ("if (lexer->cursor <= lexer->buffer_last)");
+      Indent_Line ("{");
+      Indent_Line ("   ++lexer->cursor;");
       Indent_Line ("   if (DO_COUNT) ++lexer->char_pos;");
-      Indent_Line ("if (*lexer->cursor == 0x0A) ++lexer->line;");
+      Indent_Line ("   if (lexer->cursor <= lexer->buffer_last)");
+      Indent_Line ("      if (*lexer->cursor == 0x0A) ++lexer->line;");
+      Indent_Line ("}");
       Indent := Indent - 3;
       Indent_Line ("}");
       Indent_Start ("#define YYSKIP() skip(lexer)");
@@ -1144,7 +1153,7 @@ package body WisiToken.BNF.Output_Ada_Common is
       Indent_Line ("if (lexer->cursor > lexer->buffer_last)");
       Indent_Line ("{");
       Indent := Indent + 3;
-      Indent_Line ("*id            =" & WisiToken.Token_ID'Image 
(Generate_Data.Descriptor.EOF_ID) & ";");
+      Indent_Line ("*id            =" & WisiToken.Token_ID'Image 
(Generate_Data.Descriptor.EOI_ID) & ";");
       Indent_Line ("*byte_position = lexer->buffer_last - lexer->buffer + 1;");
       Indent_Line ("*byte_length   = 0;");
       Indent_Line ("*char_position = lexer->char_token_start;");
@@ -1156,10 +1165,7 @@ package body WisiToken.BNF.Output_Ada_Common is
       New_Line;
 
       Indent_Line ("lexer->byte_token_start = lexer->cursor;");
-      Indent_Line ("if (DO_COUNT)");
-      Indent_Line ("   lexer->char_token_start = lexer->char_pos;");
-      Indent_Line ("else");
-      Indent_Line ("   lexer->char_token_start = lexer->char_pos + 1;");
+      Indent_Line ("lexer->char_token_start = lexer->char_pos;");
       Indent_Line ("if (*lexer->cursor == 0x0A)");
       Indent_Line ("   lexer->line_token_start = lexer->line-1;");
       Indent_Line ("else");
@@ -1239,7 +1245,7 @@ package body WisiToken.BNF.Output_Ada_Common is
       end loop;
       New_Line;
 
-      --  Default action
+      --  Default action.
       Indent_Line ("* {status = ERROR_unrecognized_character; continue;}");
 
       Put_Line ("*/");
diff --git a/wisitoken-bnf-output_ada_emacs.adb 
b/wisitoken-bnf-output_ada_emacs.adb
index 30da8c4..a16d289 100644
--- a/wisitoken-bnf-output_ada_emacs.adb
+++ b/wisitoken-bnf-output_ada_emacs.adb
@@ -12,7 +12,7 @@
 --  If run in an Emacs dynamically loaded module, the parser actions
 --  call the elisp actions directly.
 --
---  Copyright (C) 2012 - 2015, 2017, 2018 Free Software Foundation, Inc.
+--  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
@@ -178,7 +178,7 @@ is
       Navigate_Lines     : String_Lists.List;
       Face_Line          : Unbounded_String;
       Indent_Action_Line : Unbounded_String;
-      Check_Lines        : String_Lists.List;
+      Check_Line         : Unbounded_String;
 
       function Statement_Params (Params : in String) return String
       is
@@ -320,7 +320,7 @@ is
             First  := Index_Non_Blank (Params, Last + 1);
             Last   := Index (Params, Delim, First);
             Result := Result & ',' & Integer'Image
-              (Find_Elisp_ID (Input_Data.User_Names.Faces, Params (First .. 
Last - 1)));
+              (Find_Elisp_ID (Input_Data.Tokens.Faces, Params (First .. Last - 
1)));
 
             if Params (Last) = ']' then
                Put_Error
@@ -332,7 +332,7 @@ is
             First  := Index_Non_Blank (Params, Last + 1);
             Last   := Index (Params, Delim, First);
             Result := Result & ',' &
-              Integer'Image (Find_Elisp_ID (Input_Data.User_Names.Faces, 
Params (First .. Last - 1))) & ")";
+              Integer'Image (Find_Elisp_ID (Input_Data.Tokens.Faces, Params 
(First .. Last - 1))) & ")";
 
             Need_Comma := True;
          end loop;
@@ -573,9 +573,9 @@ is
                   Last := Last + 1; -- get past ')'
                   return -Args;
 
-               elsif Is_Present (Input_Data.User_Names.Indents, 
-Function_Name) then
+               elsif Is_Present (Input_Data.Tokens.Indents, -Function_Name) 
then
                   --  Language-specific function call
-                  Function_Name := +Value (Input_Data.User_Names.Indents, 
-Function_Name);
+                  Function_Name := +Value (Input_Data.Tokens.Indents, 
-Function_Name);
                   Arg_Count     := 0;
                   loop
                      exit when Params (Last) = ')';
@@ -735,7 +735,35 @@ is
       procedure Translate_Line (Line : in String)
       is
          Last       : constant Integer := Index (Line, Blank_Set);
-         Elisp_Name : constant String  := Line (Line'First + 1 .. Last - 1);
+         Elisp_Name : constant String  := Line (Line'First + 1 .. (if Last = 0 
then Line'Last else Last) - 1);
+
+         procedure Assert_Face_Empty
+         is begin
+            if Length (Face_Line) > 0 then
+               Put_Error
+                 (Error_Message
+                    (Input_Data.Grammar_Lexer.File_Name, RHS.Source_Line, 
"multiple face actions"));
+            end if;
+         end Assert_Face_Empty;
+
+         procedure Assert_Indent_Empty
+         is begin
+            if Length (Indent_Action_Line) > 0 then
+               Put_Error
+                 (Error_Message
+                    (Input_Data.Grammar_Lexer.File_Name, RHS.Source_Line, 
"multiple indent actions"));
+            end if;
+         end Assert_Indent_Empty;
+
+         procedure Assert_Check_Empty
+         is begin
+            if Length (Check_Line) > 0 then
+               Put_Error
+                 (Error_Message
+                    (Input_Data.Grammar_Lexer.File_Name, RHS.Source_Line, 
"multiple check actions"));
+            end if;
+         end Assert_Check_Empty;
+
       begin
          --  wisi action/check functions, in same order as typically used in
          --  .wy files; Navigate, Face, Indent, Check.
@@ -755,101 +783,58 @@ is
                  Motion_Params (Line (Last + 1 .. Line'Last)) & ";");
 
          elsif Elisp_Name = "wisi-face-apply-action" then
-            if Length (Face_Line) = 0 then
+            Assert_Face_Empty;
                Face_Line := +Elisp_Name_To_Ada (Elisp_Name, False, Trim => 5) &
                  Face_Apply_Params (Line (Last + 1 .. Line'Last)) & ";";
-            else
-               Put_Error
-                 (Error_Message
-                    (Input_Data.Grammar_Lexer.File_Name, RHS.Source_Line, 
"multiple face actions"));
-            end if;
 
          elsif Elisp_Name = "wisi-face-apply-list-action" then
-            if Length (Face_Line) = 0 then
+            Assert_Face_Empty;
                Face_Line := +Elisp_Name_To_Ada (Elisp_Name, False, Trim => 5) &
                  Face_Apply_Params (Line (Last + 1 .. Line'Last)) & ";";
-            else
-               Put_Error
-                 (Error_Message
-                    (Input_Data.Grammar_Lexer.File_Name, RHS.Source_Line, 
"multiple face actions"));
-            end if;
 
          elsif Elisp_Name = "wisi-face-mark-action" then
-            if Length (Face_Line) = 0 then
+            Assert_Face_Empty;
                Face_Line := +Elisp_Name_To_Ada (Elisp_Name, False, Trim => 5) &
                  Face_Mark_Params (Line (Last + 1 .. Line'Last)) & ";";
-            else
-               Put_Error
-                 (Error_Message
-                    (Input_Data.Grammar_Lexer.File_Name, RHS.Source_Line, 
"multiple face actions"));
-            end if;
 
          elsif Elisp_Name = "wisi-face-remove-action" then
-            if Length (Face_Line) = 0 then
+            Assert_Face_Empty;
                Face_Line := +Elisp_Name_To_Ada (Elisp_Name, False, Trim => 5) &
                  Face_Remove_Params (Line (Last + 1 .. Line'Last)) & ";";
-            else
-               Put_Error
-                 (Error_Message
-                    (Input_Data.Grammar_Lexer.File_Name, RHS.Source_Line, 
"multiple face actions"));
-            end if;
 
          elsif Elisp_Name = "wisi-indent-action" then
-            if Length (Indent_Action_Line) = 0 then
+            Assert_Indent_Empty;
                Indent_Action_Line := +"Indent_Action_0" &
                  Indent_Params (Line (Last + 1 .. Line'Last)) & ";";
-            else
-               Put_Error
-                 (Error_Message
-                    (Input_Data.Grammar_Lexer.File_Name, RHS.Source_Line, 
"multiple indent actions"));
-            end if;
 
          elsif Elisp_Name = "wisi-indent-action*" then
-            if Length (Indent_Action_Line) = 0 then
+            Assert_Indent_Empty;
                declare
                   Temp : constant Integer := Index (Line, Blank_Set, Last + 1);
                begin
                   Indent_Action_Line := +"Indent_Action_1" &
                     Indent_Params (Line (Temp + 1 .. Line'Last), Line (Last + 
1 .. Temp - 1) & ", ") & ";";
                end;
-            else
-               Put_Error
-                 (Error_Message
-                    (Input_Data.Grammar_Lexer.File_Name, RHS.Source_Line, 
"multiple indent actions"));
-            end if;
 
          elsif Elisp_Name = "wisi-propagate-name" then
-            if not Check then
-               Put_Error
-                 (Error_Message
-                    (Input_Data.Grammar_Lexer.File_Name, RHS.Source_Line, 
Elisp_Name & " used in action"));
-               return;
-            end if;
-            Check_Lines.Append
-              ("return " & Elisp_Name_To_Ada (Elisp_Name, False, Trim => 5) &
-                 " (Nonterm, Tokens, " & Line (Last + 1 .. Line'Last) & ";");
+            Assert_Check_Empty;
+            Check_Line := +"return " & Elisp_Name_To_Ada (Elisp_Name, False, 
Trim => 5) &
+              " (Nonterm, Tokens, " & Line (Last + 1 .. Line'Last) & ";";
 
          elsif Elisp_Name = "wisi-merge-names" then
-            if not Check then
-               Put_Error
-                 (Error_Message
-                    (Input_Data.Grammar_Lexer.File_Name, RHS.Source_Line, 
Elisp_Name & " used in action"));
-               return;
-            end if;
-            Check_Lines.Append
-              ("return " & Elisp_Name_To_Ada (Elisp_Name, False, Trim => 5) &
-                 Merge_Names_Params (Line (Last + 1 .. Line'Last)) & ";");
+            Assert_Check_Empty;
+            Check_Line := +"return " & Elisp_Name_To_Ada (Elisp_Name, False, 
Trim => 5) &
+              Merge_Names_Params (Line (Last + 1 .. Line'Last)) & ";";
 
          elsif Elisp_Name = "wisi-match-names" then
-            if not Check then
-               Put_Error
-                 (Error_Message
-                    (Input_Data.Grammar_Lexer.File_Name, RHS.Source_Line, 
Elisp_Name & " used in action"));
-               return;
-            end if;
-            Check_Lines.Append
-              ("return " & Elisp_Name_To_Ada (Elisp_Name, False, Trim => 5) &
-                 Match_Names_Params (Line (Last + 1 .. Line'Last)) & ";");
+            Assert_Check_Empty;
+            Check_Line := +"return " & Elisp_Name_To_Ada (Elisp_Name, False, 
Trim => 5) &
+              Match_Names_Params (Line (Last + 1 .. Line'Last)) & ";";
+
+         elsif Elisp_Name = "wisi-terminate-partial-parse" then
+            Assert_Check_Empty;
+            Check_Line := +"return Terminate_Partial_Parse 
(Partial_Parse_Active, Partial_Parse_Byte_Goal, " &
+              "Recover_Active, Nonterm);";
 
          else
             Put_Error
@@ -874,16 +859,18 @@ is
       if Check then
          --  in a check
          Indent_Line ("function " & Name);
-         Indent_Line (" (Lexer   : access constant 
WisiToken.Lexer.Instance'Class;");
-         Indent_Line ("  Nonterm : in out WisiToken.Recover_Token;");
-         Indent_Line ("  Tokens  : in     WisiToken.Recover_Token_Array)");
+         Indent_Line (" (Lexer          : access constant 
WisiToken.Lexer.Instance'Class;");
+         Indent_Line ("  Nonterm        : in out WisiToken.Recover_Token;");
+         Indent_Line ("  Tokens         : in     
WisiToken.Recover_Token_Array;");
+         Indent_Line ("  Recover_Active : in     Boolean)");
          Indent_Line (" return WisiToken.Semantic_Checks.Check_Status");
          declare
-            --  Tokens is always referenced.
-            Unref_Lexer   : constant Boolean := (for all Line of Check_Lines 
=> 0 = Index (Line, "Lexer"));
-            Unref_Nonterm : constant Boolean := (for all Line of Check_Lines 
=> 0 = Index (Line, "Nonterm"));
+            Unref_Lexer   : constant Boolean := 0 = Index (Check_Line, 
"Lexer");
+            Unref_Nonterm : constant Boolean := 0 = Index (Check_Line, 
"Nonterm");
+            Unref_Tokens  : constant Boolean := 0 = Index (Check_Line, 
"Tokens");
+            Unref_Recover : constant Boolean := 0 = Index (Check_Line, 
"Recover_Active");
          begin
-            if Unref_Lexer or Unref_Nonterm then
+            if Unref_Lexer or Unref_Nonterm or Unref_Tokens or Unref_Recover 
then
                Indent_Line ("is");
                if Unref_Lexer then
                   Indent_Line ("   pragma Unreferenced (Lexer);");
@@ -891,15 +878,20 @@ is
                if Unref_Nonterm then
                   Indent_Line ("   pragma Unreferenced (Nonterm);");
                end if;
+               if Unref_Tokens then
+                  Indent_Line ("   pragma Unreferenced (Tokens);");
+               end if;
+               if Unref_Recover then
+                  Indent_Line ("   pragma Unreferenced (Recover_Active);");
+               end if;
+
                Indent_Line ("begin");
             else
                Indent_Line ("is begin");
             end if;
          end;
          Indent := Indent + 3;
-         for Line of Check_Lines loop
-            Indent_Line (Line);
-         end loop;
+         Indent_Line (-Check_Line);
       else
          --  In an action
          Indent_Line ("procedure " & Name);
@@ -1179,7 +1171,7 @@ is
          Indent_Line ("      Elisp_Numbers (I) := Env.make_fixnum (Env, 
emacs_module_h.int64_t (I));");
          Indent_Line ("   end loop;");
          Indent_Line ("   for I in Lexer_Elisp_Symbols'Range loop");
-         Indent_Line ("      Lexer_Elisp_Symbols (I) := Intern_Soft (Env, 
Lexers.User_Names (I).all);");
+         Indent_Line ("      Lexer_Elisp_Symbols (I) := Intern_Soft (Env, 
Lexers.Tokens (I).all);");
          Indent_Line ("   end loop;");
          Indent_Line ("   Parser := Create_Parser (Env, 
Lexer_Elisp_Symbols);");
          Indent_Line ("   return 0;");
@@ -1238,7 +1230,7 @@ is
       New_Line;
 
       Output_Elisp_Common.Indent_Name_Table
-        (Output_File_Name_Root, "process-face-table", 
Input_Data.User_Names.Faces);
+        (Output_File_Name_Root, "process-face-table", Input_Data.Tokens.Faces);
 
       Put_Line ("(provide '" & Output_File_Name_Root & "-process)");
       Set_Output (Standard_Output);
diff --git a/wisitoken-bnf.ads b/wisitoken-bnf.ads
index cd40975..3d46c45 100644
--- a/wisitoken-bnf.ads
+++ b/wisitoken-bnf.ads
@@ -13,7 +13,7 @@
 --  [1] https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form
 --  [2] http://www.nongnu.org/ada-mode/wisi/wisi-user_guide.html, (info 
"(wisi-user_guide)Top")
 --
---  Copyright (C) 2012 - 2015, 2017, 2018 Free Software Foundation, Inc.
+--  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
@@ -257,18 +257,13 @@ package WisiToken.BNF is
       --  Rules included here because they define the nonterminal tokens, as
       --  well as the productions.
 
-      re2c_Regexps : String_Pair_Lists.List;
-      --  From %re2c_regexp; included here because they are used in defining
-      --  the Tokens.
-   end record;
-
-   type User_Names is record
-      --  Specified in grammar file declarations, used in other declarations
+      --  The following are specified in grammar file declarations and used in 
other declarations
       --  or actions. Faces, Indents only used if .wy action language is
       --  elisp and output language is not elisp.
 
-      Faces   : String_Lists.List;      -- %elisp_face
-      Indents : String_Pair_Lists.List; -- %elisp_indent
+      re2c_Regexps : String_Pair_Lists.List; -- %re2c_regexp
+      Faces        : String_Lists.List;      -- %elisp_face
+      Indents      : String_Pair_Lists.List; -- %elisp_indent
    end record;
 
    function "+" (Item : in String) return 
Ada.Strings.Unbounded.Unbounded_String
diff --git a/wisitoken-gen_token_enum.ads b/wisitoken-gen_token_enum.ads
index cfd508b..36fca14 100644
--- a/wisitoken-gen_token_enum.ads
+++ b/wisitoken-gen_token_enum.ads
@@ -2,7 +2,7 @@
 --
 --  Support for an enumerated token type
 --
---  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
@@ -47,7 +47,7 @@ package WisiToken.Gen_Token_Enum is
       Last_Terminal                 => +Last_Terminal,
       First_Nonterminal             => +First_Nonterminal,
       Last_Nonterminal              => +Last_Nonterminal,
-      EOF_ID                        => +EOF_ID,
+      EOI_ID                        => +EOF_ID,
       Accept_ID                     => +Accept_ID,
       Case_Insensitive              => Case_Insensitive,
       New_Line_ID                   => Invalid_Token_ID,
@@ -67,7 +67,7 @@ package WisiToken.Gen_Token_Enum is
       Last_Terminal                 => +Last_Terminal,
       First_Nonterminal             => +First_Nonterminal,
       Last_Nonterminal              => +Last_Nonterminal,
-      EOF_ID                        => +EOF_ID,
+      EOI_ID                        => +EOF_ID,
       Accept_ID                     => +Accept_ID,
       Case_Insensitive              => Case_Insensitive,
       New_Line_ID                   => Invalid_Token_ID,
diff --git a/wisitoken-generate-lr-lalr_generate.adb 
b/wisitoken-generate-lr-lalr_generate.adb
index 0558f74..2a1f403 100644
--- a/wisitoken-generate-lr-lalr_generate.adb
+++ b/wisitoken-generate-lr-lalr_generate.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  Copyright (C) 2002 - 2005, 2008 - 2015, 2017, 2018 Free Software 
Foundation, Inc.
+--  Copyright (C) 2002 - 2005, 2008 - 2015, 2017 - 2019 Free Software 
Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -98,7 +98,7 @@ package body WisiToken.Generate.LR.LALR_Generate is
 
             --  If Symbol = EOF_Token, this is the start symbol accept
             --  production; don't need a kernel with dot after EOF.
-            if (Dot_ID = Symbol and Symbol /= Descriptor.EOF_ID) and then
+            if (Dot_ID = Symbol and Symbol /= Descriptor.EOI_ID) and then
               not Has_Element (Find (Item.Prod, Next (Item.Dot), Goto_Set))
             then
                Goto_Set.Set.Insert
@@ -494,10 +494,11 @@ package body WisiToken.Generate.LR.LALR_Generate is
 
       Has_Empty_Production : constant Token_ID_Set := 
WisiToken.Generate.Has_Empty_Production (Grammar);
 
-      Minimal_Terminal_First : constant Token_Array_Token_ID :=
-        WisiToken.Generate.LR.Minimal_Terminal_First (Grammar, Descriptor);
+      Minimal_Terminal_Sequences : constant Minimal_Sequence_Array :=
+        Compute_Minimal_Terminal_Sequences (Descriptor, Grammar);
 
-      Ancestors : constant Token_Array_Token_Set := 
WisiToken.Generate.Ancestors (Grammar, Descriptor);
+      Minimal_Terminal_First : constant Token_Array_Token_ID :=
+        Compute_Minimal_Terminal_First (Descriptor, 
Minimal_Terminal_Sequences);
 
       First_Nonterm_Set : constant Token_Array_Token_Set := 
WisiToken.Generate.First
         (Grammar, Has_Empty_Production, Descriptor.First_Terminal);
@@ -558,16 +559,20 @@ package body WisiToken.Generate.LR.LALR_Generate is
         (Kernels, Grammar, Has_Empty_Production, First_Nonterm_Set, 
First_Terminal_Sequence, Unknown_Conflicts,
          Table.all, Descriptor);
 
-      --  Set Table.States.Productions, Minimal_Terminal_First for 
McKenzie_Recover
+      --  Set Table.States.Productions, Minimal_Complete_Actions for 
McKenzie_Recover
       for State in Table.States'Range loop
          Table.States (State).Productions := LR1_Items.Productions (Kernels 
(State));
+         if Trace_Generate > Extra then
+            Ada.Text_IO.Put_Line ("Set_Minimal_Complete_Actions:" & 
State_Index'Image (State));
+         end if;
          WisiToken.Generate.LR.Set_Minimal_Complete_Actions
-           (Table.States (State), Kernels (State), Minimal_Terminal_First, 
Ancestors, Descriptor, Grammar);
+           (Table.States (State), Kernels (State), Descriptor, Grammar, 
Minimal_Terminal_Sequences,
+            Minimal_Terminal_First);
       end loop;
 
       if Put_Parse_Table then
          WisiToken.Generate.LR.Put_Parse_Table
-           (Table, "LALR", Grammar, Kernels, Ancestors, Unknown_Conflicts, 
Descriptor);
+           (Table, "LALR", Grammar, Kernels, Unknown_Conflicts, Descriptor);
       end if;
 
       Delete_Known (Unknown_Conflicts, Known_Conflicts_Edit);
diff --git a/wisitoken-generate-lr-lalr_generate.ads 
b/wisitoken-generate-lr-lalr_generate.ads
index 80cc165..b4a109f 100644
--- a/wisitoken-generate-lr-lalr_generate.ads
+++ b/wisitoken-generate-lr-lalr_generate.ads
@@ -2,7 +2,7 @@
 --
 --  Generalized LALR parse table generator.
 --
---  Copyright (C) 2002 - 2003, 2009 - 2010, 2013 - 2015, 2017, 2018 Free 
Software Foundation, Inc.
+--  Copyright (C) 2002 - 2003, 2009 - 2010, 2013 - 2015, 2017 - 2019 Free 
Software Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -63,4 +63,21 @@ package WisiToken.Generate.LR.LALR_Generate is
       Descriptor        : in WisiToken.Descriptor)
      return LR1_Items.Item_Set_List;
 
+   procedure Fill_In_Lookaheads
+     (Grammar                 : in     
WisiToken.Productions.Prod_Arrays.Vector;
+      Has_Empty_Production    : in     Token_ID_Set;
+      First_Terminal_Sequence : in     Token_Sequence_Arrays.Vector;
+      Kernels                 : in out LR1_Items.Item_Set_List;
+      Descriptor              : in     WisiToken.Descriptor);
+
+   procedure Add_Actions
+     (Kernels                 : in     LR1_Items.Item_Set_List;
+      Grammar                 : in     
WisiToken.Productions.Prod_Arrays.Vector;
+      Has_Empty_Production    : in     Token_ID_Set;
+      First_Nonterm_Set       : in     Token_Array_Token_Set;
+      First_Terminal_Sequence : in     Token_Sequence_Arrays.Vector;
+      Conflicts               :    out Conflict_Lists.List;
+      Table                   : in out Parse_Table;
+      Descriptor              : in     WisiToken.Descriptor);
+
 end WisiToken.Generate.LR.LALR_Generate;
diff --git a/wisitoken-generate-lr-lr1_generate.adb 
b/wisitoken-generate-lr-lr1_generate.adb
index 53507ba..62c20c1 100644
--- a/wisitoken-generate-lr-lr1_generate.adb
+++ b/wisitoken-generate-lr-lr1_generate.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  Copyright (C) 2017, 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2017 - 2019 Free Software Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -43,10 +43,10 @@ package body WisiToken.Generate.LR.LR1_Generate is
       for Item of Set.Set loop
          if Item.Dot /= No_Element then
             if Element (Item.Dot) = Symbol and
-              --  We don't need a state with dot after EOF in the
-              --  accept production. EOF should only appear in the
+              --  We don't need a state with dot after EOI in the
+              --  accept production. EOI should only appear in the
               --  accept production.
-              Symbol /= Descriptor.EOF_ID
+              Symbol /= Descriptor.EOI_ID
             then
                Goto_Set.Set.Insert ((Item.Prod, Next (Item.Dot), new 
Token_ID_Set'(Item.Lookaheads.all)));
             end if;
@@ -91,7 +91,7 @@ package body WisiToken.Generate.LR.LR1_Generate is
         ((Set            => Item_Lists.To_List
             ((Prod       => (Grammar.First_Index, 0),
               Dot        => Grammar (Grammar.First_Index).RHSs 
(0).Tokens.First,
-              Lookaheads => new Token_ID_Set'(To_Lookahead (Descriptor.EOF_ID, 
Descriptor)))),
+              Lookaheads => new Token_ID_Set'(To_Lookahead (Descriptor.EOI_ID, 
Descriptor)))),
           Goto_List      => <>,
           Dot_IDs        => <>,
           State          => First_State_Index),
@@ -207,10 +207,11 @@ package body WisiToken.Generate.LR.LR1_Generate is
 
       Has_Empty_Production : constant Token_ID_Set := 
WisiToken.Generate.Has_Empty_Production (Grammar);
 
-      Minimal_Terminal_First : constant Token_Array_Token_ID :=
-        WisiToken.Generate.LR.Minimal_Terminal_First (Grammar, Descriptor);
+      Minimal_Terminal_Sequences : constant Minimal_Sequence_Array :=
+        Compute_Minimal_Terminal_Sequences (Descriptor, Grammar);
 
-      Ancestors : constant Token_Array_Token_Set := 
WisiToken.Generate.Ancestors (Grammar, Descriptor);
+      Minimal_Terminal_First : constant Token_Array_Token_ID :=
+        Compute_Minimal_Terminal_First (Descriptor, 
Minimal_Terminal_Sequences);
 
       First_Nonterm_Set : constant Token_Array_Token_Set := 
WisiToken.Generate.First
         (Grammar, Has_Empty_Production, Descriptor.First_Terminal);
@@ -261,19 +262,24 @@ package body WisiToken.Generate.LR.LR1_Generate is
       Add_Actions
         (Item_Sets, Grammar, Has_Empty_Production, First_Nonterm_Set, 
Unknown_Conflicts, Table.all, Descriptor);
 
-      --  Set Table.States.Productions, Minimal_Terminal_First for 
McKenzie_Recover
+      --  Set Table.States.Productions, Minimal_Complete_Actions for 
McKenzie_Recover
       for State in Table.States'Range loop
          Table.States (State).Productions := LR1_Items.Productions
            (LR1_Items.Filter (Item_Sets (State), Grammar, Descriptor, 
LR1_Items.In_Kernel'Access));
+
+         if Trace_Generate > Detail then
+            Ada.Text_IO.Put_Line ("Set_Minimal_Complete_Actions:" & 
State_Index'Image (State));
+         end if;
+
          WisiToken.Generate.LR.Set_Minimal_Complete_Actions
            (Table.States (State),
             LR1_Items.Filter (Item_Sets (State), Grammar, Descriptor, 
LR1_Items.In_Kernel'Access),
-            Minimal_Terminal_First, Ancestors, Descriptor, Grammar);
+            Descriptor, Grammar, Minimal_Terminal_Sequences, 
Minimal_Terminal_First);
       end loop;
 
       if Put_Parse_Table then
          WisiToken.Generate.LR.Put_Parse_Table
-           (Table, "LR1", Grammar, Item_Sets, Ancestors, Unknown_Conflicts, 
Descriptor);
+           (Table, "LR1", Grammar, Item_Sets, Unknown_Conflicts, Descriptor);
       end if;
 
       if Trace_Generate > Outline then
diff --git a/wisitoken-generate-lr-lr1_generate.ads 
b/wisitoken-generate-lr-lr1_generate.ads
index 7a3c6f6..92992cb 100644
--- a/wisitoken-generate-lr-lr1_generate.ads
+++ b/wisitoken-generate-lr-lr1_generate.ads
@@ -7,7 +7,7 @@
 --  [dragon] "Compilers Principles, Techniques, and Tools" by Aho,
 --  Sethi, and Ullman (aka: "The [Red] Dragon Book").
 --
---  Copyright (C) 2017, 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2017 - 2019 Free Software Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -73,4 +73,13 @@ package WisiToken.Generate.LR.LR1_Generate is
      return LR1_Items.Item_Set_List;
    --  [dragon] algorithm 4.9 pg 231; figure 4.38 pg 232; procedure "items"
 
+   procedure Add_Actions
+     (Item_Sets            : in     LR1_Items.Item_Set_List;
+      Grammar              : in     WisiToken.Productions.Prod_Arrays.Vector;
+      Has_Empty_Production : in     Token_ID_Set;
+      First_Nonterm_Set    : in     Token_Array_Token_Set;
+      Conflicts            :    out Conflict_Lists.List;
+      Table                : in out Parse_Table;
+      Descriptor           : in     WisiToken.Descriptor);
+
 end WisiToken.Generate.LR.LR1_Generate;
diff --git a/wisitoken-generate-lr.adb b/wisitoken-generate-lr.adb
index c124327..f909fde 100644
--- a/wisitoken-generate-lr.adb
+++ b/wisitoken-generate-lr.adb
@@ -1,1141 +1,1256 @@
---  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 (GPL);
-
-with Ada.Strings.Fixed;
-with Ada.Text_IO;
-with System.Multiprocessors;
-with WisiToken.Generate;
-package body WisiToken.Generate.LR is
-
-   ----------
-   --  Body subprograms, alphabetical
-
-   function Count_Reduce (List : in Parse.LR.Minimal_Action_Lists.List) return 
Integer
-   is
-      Count : Integer := 0;
-   begin
-      for Item of List loop
-         if Item.Verb = Reduce then
-            Count := Count + 1;
-         end if;
-      end loop;
-      return Count;
-   end Count_Reduce;
-
-   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;
-
-   procedure Terminal_Sequence
-     (Grammar       : in     WisiToken.Productions.Prod_Arrays.Vector;
-      Descriptor    : in     WisiToken.Descriptor;
-      All_Sequences : in out Token_Sequence_Arrays.Vector;
-      All_Set       : in out Token_ID_Set;
-      Recursing     : in out Token_ID_Set;
-      Nonterm       : in     Token_ID)
-   is
-      use Ada.Containers;
-      Prod : Productions.Instance renames Grammar (Nonterm);
-
-      Temp              : Token_Sequence_Arrays.Vector;
-      Min_Length        : Count_Type := Count_Type'Last;
-      Skipped_Recursive : Boolean    := False;
-   begin
-      --  We get here because All_Sequences (Nonterm) has not been comptued
-      --  yet. Attempt to compute All_Sequences (Nonterm); if successful, set
-      --  All_Set (Nonterm) True.
-
-      --  First fill Temp with terminals from each production for Nonterm.
-      for L in Prod.RHSs.First_Index .. Prod.RHSs.Last_Index loop
-
-         if Prod.RHSs (L).Tokens.Length = 0 then
-            All_Set (Nonterm) := True;
-
-            if Trace_Generate > Detail then
-               Ada.Text_IO.Put_Line (Image (Nonterm, Descriptor) & " => ()");
-            end if;
-
-            return;
-         end if;
-
-         if Prod.RHSs (L).Tokens (1) = Nonterm then
-            --  The first RHS token = LHS; a recursive list. This will never be
-            --  the shortest production, so just skip it.
-            null;
-
-         else
-            declare
-               Sequence : Token_ID_Arrays.Vector;
-            begin
-               for ID of Prod.RHSs (L).Tokens loop
-                  if ID in Descriptor.First_Terminal .. 
Descriptor.Last_Terminal then
-                     Sequence.Append (ID);
-
-                  else
-                     if not All_Set (ID) then
-                        if Recursing (ID) then
-                           --  This nonterm is mutually recursive with some 
other. This
-                           --  production will never be the shortest unless 
it's the only one,
-                           --  so skip it.
-                           if Trace_Generate > Detail then
-                              Ada.Text_IO.Put_Line (Image (ID, Descriptor) & " 
mutual recurse skipped");
-                           end if;
-                           Skipped_Recursive := True;
-                           goto Skip;
-                        else
-                           Recursing (ID) := True;
-                           if Trace_Generate > Detail then
-                              Ada.Text_IO.Put_Line (Image (ID, Descriptor) & " 
recurse");
-                           end if;
-                           Terminal_Sequence (Grammar, Descriptor, 
All_Sequences, All_Set, Recursing, ID);
-                           Recursing (ID) := False;
-
-                           if not All_Set (ID) then
-                              --  abandoned because of recursion
-                              Skipped_Recursive := True;
-                              goto Skip;
-                           end if;
-                        end if;
-                     end if;
-                     Sequence.Append (All_Sequences (ID));
-                  end if;
-               end loop;
-
-               if Trace_Generate > Detail then
-                  Ada.Text_IO.Put_Line (Image (Nonterm, Descriptor) & " -> " & 
Image (Sequence, Descriptor));
-               end if;
-               Temp.Append (Sequence);
-            end;
-         end if;
-
-         <<Skip>>
-         null;
-      end loop;
-
-      --  Now find the minimum length.
-      if Temp.Length = 0 and Skipped_Recursive then
-         --  better luck next time.
-         return;
-      end if;
-
-      for S of Temp loop
-         if S.Length <= Min_Length then
-            Min_Length := S.Length;
-
-            All_Sequences (Nonterm) := S;
-         end if;
-      end loop;
-
-      if Trace_Generate > Detail then
-         Ada.Text_IO.Put_Line (Image (Nonterm, Descriptor) & " ==> " & Image 
(All_Sequences (Nonterm), Descriptor));
-      end if;
-
-      All_Set (Nonterm) := True;
-   end Terminal_Sequence;
-
-   ----------
-   --  Public subprograms, declaration order
-
-   procedure Put
-     (Item       : in Conflict_Lists.List;
-      File       : in Ada.Text_IO.File_Type;
-      Descriptor : in WisiToken.Descriptor)
-   is begin
-      for Conflict of Item loop
-         Ada.Text_IO.Put_Line (File, Image (Conflict, Descriptor));
-      end loop;
-   end Put;
-
-   procedure Add_Action
-     (Symbol               : in     Token_ID;
-      Action               : in     Parse_Action_Rec;
-      Action_List          : in out Action_Node_Ptr;
-      Closure              : in     LR1_Items.Item_Set;
-      Grammar              : in     WisiToken.Productions.Prod_Arrays.Vector;
-      Has_Empty_Production : in     Token_ID_Set;
-      First_Nonterm_Set    : in     Token_Array_Token_Set;
-      Conflicts            : in out Conflict_Lists.List;
-      Descriptor           : in     WisiToken.Descriptor)
-   is
-      Matching_Action : constant Action_Node_Ptr := Find (Symbol, Action_List);
-   begin
-      if Trace_Generate > Outline then
-         Ada.Text_IO.Put (Image (Symbol, Descriptor) & " => ");
-         Put (Descriptor, Action);
-         Ada.Text_IO.New_Line;
-      end if;
-
-      if Matching_Action /= null then
-         if Equal (Matching_Action.Action.Item, Action) then
-            --  Matching_Action is identical to Action, so there is no
-            --  conflict; just don't add it again.
-            if Trace_Generate > Outline then
-               Ada.Text_IO.Put_Line (" - already present");
-            end if;
-            return;
-         else
-            --  There is a conflict. Report it and add it, so the
-            --  generalized parser can follow both paths
-            declare
-               --  Enforce canonical Shift/Reduce or Accept/Reduce
-               --  order, to simplify searching and code generation.
-               Action_A : constant Parse_Action_Rec :=
-                 (if Action.Verb in Shift | Accept_It then Action else 
Matching_Action.Action.Item);
-
-               Action_B : constant Parse_Action_Rec :=
-                 (if Action.Verb in Shift | Accept_It then 
Matching_Action.Action.Item else Action);
-
-               New_Conflict : constant Conflict :=
-                 (Action_A    => Action_A.Verb,
-                  Action_B    => Action_B.Verb,
-                  LHS_A       => Find
-                    (Closure, Action_A, Symbol, Grammar, Has_Empty_Production, 
First_Nonterm_Set, Descriptor),
-                  LHS_B       => Find
-                    (Closure, Action_B, Symbol, Grammar, Has_Empty_Production, 
First_Nonterm_Set, Descriptor),
-                  State_Index => Closure.State,
-                  On          => Symbol);
-            begin
-               if not Is_Present (New_Conflict, Conflicts) then
-                  --  The same conflict may occur in a different
-                  --  item set. Only add it to conflicts once.
-                  Conflicts.Append (New_Conflict);
-
-                  if Trace_Generate > Outline then
-                     Ada.Text_IO.Put_Line (" - conflict added: " & Image 
(New_Conflict, Descriptor));
-                  end if;
-               else
-                  if Trace_Generate > Outline then
-                     Ada.Text_IO.Put_Line (" - conflict duplicate: " & Image 
(New_Conflict, Descriptor));
-                  end if;
-               end if;
-
-               --  More than two actions can occur; see triple_conflict.wy. We 
make
-               --  that an error, since the grammar will be better off without 
them.
-               --  But keep going; the full parse table output will be needed 
to fix
-               --  the excess conflict.
-               if Matching_Action.Action.Next /= null then
-                  if Matching_Action.Action.Item = Action or 
Matching_Action.Action.Next.Item = Action then
-                     if Trace_Generate > Outline then
-                        Ada.Text_IO.Put_Line (" - conflict duplicate");
-                     end if;
-                  else
-                     WisiToken.Generate.Put_Error
-                       ("More than two actions on " & Image (Symbol, 
Descriptor) &
-                          " in state" & State_Index'Image (Closure.State));
-                  end if;
-               end if;
-
-               if Action.Verb = Shift then
-                  Matching_Action.Action := new Parse_Action_Node'(Action, 
Matching_Action.Action);
-               else
-                  Matching_Action.Action.Next := new 
Parse_Action_Node'(Action, Matching_Action.Action.Next);
-               end if;
-            end;
-         end if;
-      else
-         WisiToken.Parse.LR.Add (Action_List, Symbol, Action);
-      end if;
-   end Add_Action;
-
-   procedure Add_Actions
-     (Closure              : in     LR1_Items.Item_Set;
-      Table                : in out Parse_Table;
-      Grammar              : in     WisiToken.Productions.Prod_Arrays.Vector;
-      Has_Empty_Production : in     Token_ID_Set;
-      First_Nonterm_Set    : in     Token_Array_Token_Set;
-      Conflicts            : in out Conflict_Lists.List;
-      Descriptor           : in     WisiToken.Descriptor)
-   is
-      use WisiToken.Token_ID_Arrays;
-
-      State : constant State_Index := Closure.State;
-   begin
-      if Trace_Generate > Outline then
-         Ada.Text_IO.Put_Line ("adding actions for state" & State_Index'Image 
(State));
-      end if;
-
-      for Item of Closure.Set loop
-         if Item.Dot = No_Element then
-            --  Pointer is at the end of the production; add a reduce action.
-
-            Add_Lookahead_Actions
-              (Item, Table.States (State).Action_List, Grammar, 
Has_Empty_Production, First_Nonterm_Set,
-               Conflicts, Closure, Descriptor);
-
-         elsif Element (Item.Dot) in Descriptor.First_Terminal .. 
Descriptor.Last_Terminal then
-            --  Dot is before a terminal token.
-            declare
-               use all type Ada.Containers.Count_Type;
-
-               Dot_ID : constant Token_ID := Element (Item.Dot);
-               --  ID of token after Item.Dot
-
-               Goto_State : constant Unknown_State_Index := 
LR1_Items.Goto_State (Closure, Dot_ID);
-            begin
-               if Dot_ID = Descriptor.EOF_ID then
-                  --  This is the start symbol production with dot before EOF.
-                  declare
-                     P_ID : constant Production_ID := Item.Prod;
-                     RHS  : Productions.Right_Hand_Side renames Grammar 
(P_ID.LHS).RHSs (P_ID.RHS);
-                  begin
-                     Add_Action
-                       (Dot_ID,
-                        (Accept_It, P_ID, RHS.Action, RHS.Check, 
RHS.Tokens.Length - 1),
-                        --  EOF is not pushed on stack in parser, because the 
action for EOF
-                        --  is Accept, not Shift.
-                        Table.States (State).Action_List, Closure,
-                        Grammar, Has_Empty_Production, First_Nonterm_Set, 
Conflicts, Descriptor);
-                  end;
-               else
-                  if Goto_State /= Unknown_State then
-                     Add_Action
-                       (Dot_ID,
-                        (Shift, Goto_State),
-                        Table.States (State).Action_List,
-                        Closure, Grammar, Has_Empty_Production, 
First_Nonterm_Set, Conflicts, Descriptor);
-                  end if;
-               end if;
-            end;
-         else
-            --  Dot is before a non-terminal token; no action.
-            if Trace_Generate > Outline then
-               Ada.Text_IO.Put_Line (Image (Element (Item.Dot), Descriptor) & 
" => no action");
-            end if;
-         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;
-
-      for Item of Closure.Goto_List loop
-         if Item.Symbol in Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal then
-            Add_Goto (Table.States (State), Item.Symbol, Item.State); -- note 
list is already sorted.
-         end if;
-      end loop;
-   end Add_Actions;
-
-   procedure Add_Lookahead_Actions
-     (Item                 : in     LR1_Items.Item;
-      Action_List          : in out Action_Node_Ptr;
-      Grammar              : in     WisiToken.Productions.Prod_Arrays.Vector;
-      Has_Empty_Production : in     Token_ID_Set;
-      First_Nonterm_Set    : in     Token_Array_Token_Set;
-      Conflicts            : in out Conflict_Lists.List;
-      Closure              : in     LR1_Items.Item_Set;
-      Descriptor           : in     WisiToken.Descriptor)
-   is
-      Prod   : Productions.Instance renames Grammar (Item.Prod.LHS);
-      RHS    : Productions.Right_Hand_Side renames Prod.RHSs (Item.Prod.RHS);
-      Action : constant Parse_Action_Rec := (Reduce, Item.Prod, RHS.Action, 
RHS.Check, RHS.Tokens.Length);
-   begin
-      if Trace_Generate > Outline then
-         Ada.Text_IO.Put_Line ("processing lookaheads");
-      end if;
-
-      --  We ignore propagate lookaheads here.
-      for Lookahead in Item.Lookaheads'Range loop
-         if Item.Lookaheads (Lookahead) then
-            if Lookahead = Descriptor.First_Nonterminal then
-               null;
-            else
-               Add_Action
-                 (Lookahead, Action, Action_List, Closure, Grammar,
-                  Has_Empty_Production, First_Nonterm_Set, Conflicts, 
Descriptor);
-            end if;
-         end if;
-      end loop;
-   end Add_Lookahead_Actions;
-
-   procedure Delete_Known
-     (Conflicts       : in out Conflict_Lists.List;
-      Known_Conflicts : in out Conflict_Lists.List)
-   is
-      --  Delete all elements in Conflicts that match an element in
-      --  Known_Conflicts. There can be more than one Conflict that
-      --  match one Known_Conflict.
-      use Conflict_Lists;
-      Known      : Cursor  := Known_Conflicts.First;
-      Next_Known : Cursor;
-   begin
-      loop
-         exit when Known = No_Element;
-         Next_Known := Next (Known);
-         declare
-            I      : Cursor  := Conflicts.First;
-            Next_I : Cursor;
-            Used   : Boolean := False;
-         begin
-            loop
-               exit when I = No_Element;
-               Next_I := Next (I);
-               if Match (Element (Known), Conflicts.Constant_Reference (I)) 
then
-                  Delete (Conflicts, I);
-                  Used := True;
-               end if;
-               I := Next_I;
-            end loop;
-
-            if Used then
-               Delete (Known_Conflicts, Known);
-            end if;
-         end;
-         Known := Next_Known;
-      end loop;
-   end Delete_Known;
-
-   function Find
-     (Closure              : in LR1_Items.Item_Set;
-      Action               : in Parse_Action_Rec;
-      Lookahead            : in Token_ID;
-      Grammar              : in WisiToken.Productions.Prod_Arrays.Vector;
-      Has_Empty_Production : in Token_ID_Set;
-      First                : in Token_Array_Token_Set;
-      Descriptor           : in WisiToken.Descriptor)
-     return Token_ID
-   is
-      use WisiToken.Token_ID_Arrays;
-
-      ID_I : Cursor;
-   begin
-      case Action.Verb is
-      when Reduce | Accept_It =>
-         --  If the nonterm produced by the reduce is the LHS of the state
-         --  production, use it.
-         for Item of Closure.Set loop
-            if LR1_Items.In_Kernel (Grammar, Descriptor, Item) and
-              Action.Production.LHS = Item.Prod.LHS
-            then
-               return Item.Prod.LHS;
-            end if;
-         end loop;
-
-         --  The reduce nonterm is after Dot in a state production; find which
-         --  one, use that.
-         for Item of Closure.Set loop
-            if LR1_Items.In_Kernel (Grammar, Descriptor, Item) then
-               ID_I := Item.Dot;
-               loop
-                  if ID_I = No_Element then
-                     if Item.Lookaheads (Lookahead) then
-                        return Item.Prod.LHS;
-                     end if;
-                  else
-                     declare
-                        Dot_ID : Token_ID renames Element (ID_I);
-                     begin
-                        if Dot_ID = Lookahead or
-                          (Dot_ID in Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal and then
-                             First (Dot_ID, Lookahead))
-                        then
-                           return Item.Prod.LHS;
-                        end if;
-                        exit when Dot_ID in Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal and then
-                          not Has_Empty_Production (Dot_ID);
-                     end;
-                  end if;
-
-                  exit when ID_I = No_Element;
-                  Next (ID_I);
-               end loop;
-            end if;
-         end loop;
-
-      when Shift =>
-
-         for Item of Closure.Set loop
-            --  Lookahead (the token shifted) is starting a nonterm in a state
-            --  production; it is in First of that nonterm.
-            if LR1_Items.In_Kernel (Grammar, Descriptor, Item) then
-               ID_I := Item.Dot;
-               loop
-                  exit when ID_I = No_Element;
-                  declare
-                     Dot_ID : Token_ID renames Element (ID_I);
-                  begin
-                     if Dot_ID = Lookahead or
-                       (Dot_ID in Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal and then
-                          First (Dot_ID, Lookahead))
-                     then
-                        return Item.Prod.LHS;
-                     end if;
-
-                     exit when Dot_ID in Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal and then
-                       not Has_Empty_Production (Dot_ID);
-                  end;
-
-                  Next (ID_I);
-               end loop;
-            end if;
-         end loop;
-
-      when WisiToken.Parse.LR.Error =>
-         raise SAL.Programmer_Error;
-      end case;
-
-      Ada.Text_IO.Put_Line
-        ("item for " & Image (Action, Descriptor) & " on " & Image (Lookahead, 
Descriptor) & " not found in");
-      LR1_Items.Put (Grammar, Descriptor, Closure, Kernel_Only => True);
-      raise SAL.Programmer_Error;
-   end Find;
-
-   function Image (Item : in Conflict; Descriptor : in WisiToken.Descriptor) 
return String
-   is begin
-      return
-        ("%conflict " &
-           Conflict_Parse_Actions'Image (Item.Action_A) & "/" &
-           Conflict_Parse_Actions'Image (Item.Action_B) & " in state " &
-           Image (Item.LHS_A, Descriptor) & ", " &
-           Image (Item.LHS_B, Descriptor) &
-           " on token " & Image (Item.On, Descriptor) &
-           " (" & State_Index'Image (Item.State_Index) & ")"); -- state number 
last for easier delete
-   end Image;
-
-   function Is_Present (Item : in Conflict; Conflicts : in 
Conflict_Lists.List) return Boolean
-   is
-      use Conflict_Lists;
-      I : Cursor := Conflicts.First;
-   begin
-      loop
-         exit when I = No_Element;
-         if Match (Item, Conflicts.Constant_Reference (I)) then
-            return True;
-         end if;
-         I := Next (I);
-      end loop;
-      return False;
-   end Is_Present;
-
-   function Match (Known : in Conflict; Item : in 
Conflict_Lists.Constant_Reference_Type) return Boolean
-   is begin
-      --  Ignore State_Index. Actions are in canonical order; enforced
-      --  in Add_Action above. For reduce/reduce, LHS_A, LHS_B are not
-      --  in canonical order.
-      return
-        Known.Action_A = Item.Action_A and
-        Known.Action_B = Item.Action_B and
-        ((Known.LHS_A = Item.LHS_A and Known.LHS_B = Item.LHS_B) or
-           (Known.LHS_B = Item.LHS_A and Known.LHS_A = Item.LHS_B)) and
-        Known.On = Item.On;
-   end Match;
-
-   procedure Compute_Minimal_Terminal_Sequences
-     (Grammar    : in     WisiToken.Productions.Prod_Arrays.Vector;
-      Descriptor : in     WisiToken.Descriptor;
-      Result     : in out Token_Sequence_Arrays.Vector)
-   is
-      --  Result (ID).Length = 0 is a valid sequence (ie the nonterminal can
-      --  be empty), so we use an auxilliary array to track whether Result
-      --  (ID) has been computed.
-      --
-      --  We also need to detect mutual recursion, and incomplete grammars.
-
-      All_Set   : Token_ID_Set := (Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal => False);
-      Recursing : Token_ID_Set := (Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal => False);
-
-      Last_Count : Integer := 0;
-      This_Count : Integer;
-   begin
-      Result.Set_First (Descriptor.First_Nonterminal);
-      Result.Set_Last (Descriptor.Last_Nonterminal);
-
-      loop
-         exit when (for all B of All_Set => B);
-         for P of Grammar loop
-            if not All_Set (P.LHS) then
-               Terminal_Sequence (Grammar, Descriptor, Result, All_Set, 
Recursing, P.LHS);
-            end if;
-         end loop;
-         This_Count := Count (All_Set);
-         if This_Count = Last_Count then
-            raise Grammar_Error with "nonterminals have no minimum terminal 
sequence: " &
-              Image (All_Set, Descriptor, Inverted => True);
-         end if;
-         Last_Count := This_Count;
-      end loop;
-   end Compute_Minimal_Terminal_Sequences;
-
-   function Minimal_Terminal_First
-     (Grammar    : in     WisiToken.Productions.Prod_Arrays.Vector;
-      Descriptor : in     WisiToken.Descriptor)
-     return Token_Array_Token_ID
-   is
-      use all type Ada.Containers.Count_Type;
-      Minimal_Terminal_Sequences : Token_Sequence_Arrays.Vector;
-   begin
-      Compute_Minimal_Terminal_Sequences (Grammar, Descriptor, 
Minimal_Terminal_Sequences);
-
-      return Result : Token_Array_Token_ID (Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal) do
-         for ID in Result'Range loop
-            if Minimal_Terminal_Sequences (ID).Length = 0 then
-               Result (ID) := Invalid_Token_ID;
-            else
-               Result (ID) := Minimal_Terminal_Sequences 
(ID)(Minimal_Terminal_Sequences (ID).First);
-            end if;
-         end loop;
-      end return;
-   end Minimal_Terminal_First;
-
-   procedure Set_Minimal_Complete_Actions
-     (State                  : in out Parse_State;
-      Kernel                 : in     LR1_Items.Item_Set;
-      Minimal_Terminal_First : in     Token_Array_Token_ID;
-      Ancestors              : in     Token_Array_Token_Set;
-      Descriptor             : in     WisiToken.Descriptor;
-      Grammar                : in     WisiToken.Productions.Prod_Arrays.Vector)
-   is
-      use all type Ada.Containers.Count_Type;
-      use LR1_Items.Item_Lists;
-      use Token_ID_Arrays;
-
-      subtype Terminals is Token_ID range Descriptor.First_Terminal .. 
Descriptor.Last_Terminal;
-
-      Del  : LR1_Items.Item_Lists.Cursor;
-
-      procedure Delete_Same_Ancestor (List : in out LR1_Items.Item_Lists.List; 
Cur : in LR1_Items.Item_Lists.Cursor)
-      is
-         Cur_LHS : constant Token_ID := Element (Cur).Prod.LHS;
-
-         J : LR1_Items.Item_Lists.Cursor := List.First;
-      begin
-         loop
-            exit when not Has_Element (J);
-            if J = Cur then
-               Next (J);
-            else
-               declare
-                  Item : LR1_Items.Item renames Constant_Ref (J);
-               begin
-                  if Cur_LHS = Item.Prod.LHS or else Ancestors (Cur_LHS, 
Item.Prod.LHS) then
-                     Del := J;
-                     Next (J);
-                     List.Delete (Del);
-                  else
-                     Next (J);
-                  end if;
-               end;
-            end if;
-         end loop;
-      end Delete_Same_Ancestor;
-
-      procedure Append_No_Dup (Item : in Minimal_Action)
-      is begin
-         if not State.Minimal_Complete_Actions.Contains (Item) then
-            State.Minimal_Complete_Actions.Insert (Item);
-         end if;
-      end Append_No_Dup;
-
-      function Find_Action (List : in Action_Node_Ptr; ID : in Token_ID) 
return Minimal_Action
-      is
-         Node : Action_Node_Ptr := List;
-      begin
-         loop
-            if Node.Symbol = ID then
-               case Node.Action.Item.Verb is
-               when Shift =>
-                  return (Shift, ID, Node.Action.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);
-               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;
-
-      Working_Set : LR1_Items.Item_Lists.List := Kernel.Set;
-      I           : LR1_Items.Item_Lists.Cursor;
-
-   begin
-      --  First find items to delete.
-      --
-      --  This algorithm will return an empty Minimal_Complete_Actions in
-      --  the top level accept state.
-
-      I := Working_Set.First;
-      loop
-         exit when not Has_Element (I);
-         declare
-            Item : LR1_Items.Item renames Constant_Ref (I);
-            Prod : WisiToken.Productions.Instance renames Grammar 
(Item.Prod.LHS);
-         begin
-            if not Has_Element (Item.Dot) then
-               --  Completing this item also completes items that share an 
ancestor.
-               Delete_Same_Ancestor (Working_Set, I);
-               Next (I);
-
-            elsif To_Index (Item.Dot) = 2 and then
-              Prod.RHSs (Item.Prod.RHS).Tokens (1) = Item.Prod.LHS
-            then
-               --  Item is left-recursive; it can't be minimal.
-               Del := I;
-               Next (I);
-               Working_Set.Delete (Del);
-            else
-               Next (I);
-            end if;
-         end;
-      end loop;
-
-      for Item of Working_Set loop
-         if not Has_Element (Item.Dot) then
-            --  Item has no next terminal. Include a reduce action; the
-            --  Minimal_Terminal_First for the resulting state will be used.
-            Append_No_Dup
-              ((Reduce, Item.Prod.LHS,
-                Token_Count => Grammar (Item.Prod.LHS).RHSs 
(Item.Prod.RHS).Tokens.Length));
-         else
-            declare
-               ID : constant Token_ID := Element (Item.Dot);
-            begin
-               if ID /= Descriptor.EOF_ID then
-
-                  if ID in Terminals then
-                     Append_No_Dup (Find_Action (State.Action_List, ID));
-
-                  else
-                     if Minimal_Terminal_First (ID) = Invalid_Token_ID then
-                        --  Item.Dot is a nullable nonterm, include a reduce 
of the null
-                        --  nonterm, rather than a shift of the following 
terminal; recover
-                        --  must do the reduce first.
-                        Append_No_Dup ((Reduce, ID, Token_Count => 0));
-
-                     else
-                        Append_No_Dup (Find_Action (State.Action_List, 
Minimal_Terminal_First (ID)));
-                     end if;
-                  end if;
-               end if;
-            end;
-         end if;
-      end loop;
-   end Set_Minimal_Complete_Actions;
-
-   ----------
-   --  Parse table output
-
-   procedure Put_Text_Rep
-     (Table        : in Parse_Table;
-      File_Name    : in String;
-      Action_Names : in Names_Array_Array;
-      Check_Names  : in Names_Array_Array)
-   is
-      use Ada.Text_IO;
-      File : File_Type;
-   begin
-      --  Only space, semicolon, newline delimit object values. Bounds of
-      --  arrays output before each array, unless known from discriminants.
-      --  End of lists indicated by semicolon. Action, Check subprograms are
-      --  represented by True if present, False if not; look up the actual
-      --  address Table.Productions.
-
-      Create (File, Out_File, File_Name);
-
-      --  First the discriminants
-      Put (File,
-           Trimmed_Image (Table.State_First) & State_Index'Image 
(Table.State_Last) &
-             Token_ID'Image (Table.First_Terminal) & Token_ID'Image 
(Table.Last_Terminal) &
-             Token_ID'Image (Table.First_Nonterminal) & Token_ID'Image 
(Table.Last_Nonterminal));
-      New_Line (File);
-
-      for State of Table.States loop
-         Put (File, Integer'Image (State.Productions.First_Index));
-         Put (File, Integer'Image (State.Productions.Last_Index));
-         for Prod of State.Productions loop
-            Put (File, Token_ID'Image (Prod.LHS) & Integer'Image (Prod.RHS));
-         end loop;
-         New_Line (File);
-
-         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));
-
-                     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));
-
-                        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));
-
-                     when Parse.LR.Error =>
-                        --  Error action terminates the action list
-                        Put_Symbol := False;
-                     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;
-               New_Line (File);
-
-               Node_I := Node_I.Next;
-            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;
-
-         for Action of State.Minimal_Complete_Actions loop
-            Put (File, ' ' & Minimal_Verbs'Image (Action.Verb));
-            case Action.Verb is
-            when Shift =>
-               Put (File, Token_ID'Image (Action.ID) & State_Index'Image 
(Action.State));
-            when Reduce =>
-               Put (File, Token_ID'Image (Action.Nonterm) & 
Ada.Containers.Count_Type'Image (Action.Token_Count));
-            end case;
-         end loop;
-         Put (File, ';');
-         New_Line (File);
-      end loop;
-      Close (File);
-   end Put_Text_Rep;
-
-   procedure Put (Item : in Parse_Action_Rec; Descriptor : in 
WisiToken.Descriptor)
-   is
-      use Ada.Containers;
-      use Ada.Text_IO;
-   begin
-      case Item.Verb is
-      when Shift =>
-         Put ("shift and goto state" & State_Index'Image (Item.State));
-
-      when Reduce =>
-         Put
-           ("reduce" & Count_Type'Image (Item.Token_Count) & " tokens to " &
-              Image (Item.Production.LHS, Descriptor));
-      when Accept_It =>
-         Put ("accept it");
-      when Parse.LR.Error =>
-         Put ("ERROR");
-      end case;
-   end Put;
-
-   procedure Put (Item : in McKenzie_Param_Type; Descriptor : in 
WisiToken.Descriptor)
-   is
-      use Ada.Text_IO;
-   begin
-      Put_Line ("(Insert =>");
-      for I in Item.Insert'Range loop
-         Put (" " & Padded_Image (I, Descriptor) & " =>" & Natural'Image 
(Item.Insert (I)));
-         if I = Item.Insert'Last then
-            Put_Line (")");
-         else
-            Put_Line (",");
-         end if;
-      end loop;
-      Put_Line ("(Delete =>");
-      for I in Item.Delete'Range loop
-         Put (" " & Padded_Image (I, Descriptor) & " =>" & Natural'Image 
(Item.Delete (I)));
-         if I = Item.Delete'Last then
-            Put_Line (")");
-         else
-            Put_Line (",");
-         end if;
-      end loop;
-      Put_Line ("(Push_Back =>");
-      for I in Item.Delete'Range loop
-         Put (" " & Padded_Image (I, Descriptor) & " =>" & Natural'Image 
(Item.Delete (I)));
-         if I = Item.Delete'Last then
-            Put_Line (")");
-         else
-            Put_Line (",");
-         end if;
-      end loop;
-      Put_Line ("Ignore_Check_Fail =>" & Integer'Image 
(Item.Ignore_Check_Fail));
-      Put_Line ("Task_Count        =>" & 
System.Multiprocessors.CPU_Range'Image (Item.Task_Count));
-      Put_Line ("Cost_Limit        =>" & Integer'Image (Item.Cost_Limit));
-      Put_Line ("Check_Limit       =>" & Token_Index'Image (Item.Check_Limit));
-      Put_Line ("Check_Delta_Limit =>" & Integer'Image 
(Item.Check_Delta_Limit));
-      Put_Line ("Enqueue_Limit     =>" & Integer'Image (Item.Enqueue_Limit));
-   end Put;
-
-   procedure Put (Descriptor : in WisiToken.Descriptor; Item : in 
Parse_Action_Rec)
-   is
-      use Ada.Containers;
-      use Ada.Text_IO;
-   begin
-      case Item.Verb is
-      when Shift =>
-         Put ("shift and goto state" & State_Index'Image (Item.State));
-      when Reduce =>
-         Put
-           ("reduce" & Count_Type'Image (Item.Token_Count) & " tokens to " &
-              Image (Item.Production.LHS, Descriptor));
-         Put (" " & Trimmed_Image (Item.Production));
-      when Accept_It =>
-         Put ("accept it");
-         Put (" " & Trimmed_Image (Item.Production));
-      when Parse.LR.Error =>
-         Put ("ERROR");
-      end case;
-   end Put;
-
-   procedure Put (Descriptor : in WisiToken.Descriptor; Action : in 
Parse_Action_Node_Ptr)
-   is
-      use Ada.Text_IO;
-      Ptr    : Parse_Action_Node_Ptr   := Action;
-      Column : constant Positive_Count := Col;
-   begin
-      loop
-         Put (Descriptor, Ptr.Item);
-         Ptr := Ptr.Next;
-         exit when Ptr = null;
-         Put_Line (",");
-         Set_Col (Column);
-      end loop;
-   end Put;
-
-   procedure Put (Descriptor : in WisiToken.Descriptor; State : in Parse_State)
-   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;
-      Need_Comma : Boolean := False;
-   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);
-         New_Line;
-         Action_Ptr := Action_Ptr.Next;
-      end loop;
-
-      if Goto_Ptr /= null then
-         New_Line;
-      end if;
-
-      while Goto_Ptr /= null 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);
-      end loop;
-
-      if State.Minimal_Complete_Actions.Length > 0 then
-         New_Line;
-         Put ("   Minimal_Complete_Actions => (");
-         for Action of State.Minimal_Complete_Actions loop
-            if Need_Comma then
-               Put (", ");
-            else
-               Need_Comma := True;
-            end if;
-            case Action.Verb is
-            when Shift =>
-               Put (Image (Action.ID, Descriptor));
-            when Reduce =>
-               Put (Image (Action.Nonterm, Descriptor));
-            end case;
-         end loop;
-         Put_Line (")");
-      end if;
-   end Put;
-
-   procedure Put_Parse_Table
-     (Table      : in Parse_Table_Ptr;
-      Title      : in String;
-      Grammar    : in WisiToken.Productions.Prod_Arrays.Vector;
-      Kernels    : in LR1_Items.Item_Set_List;
-      Ancestors  : in Token_Array_Token_Set;
-      Conflicts  : in Conflict_Lists.List;
-      Descriptor : in WisiToken.Descriptor)
-   is
-      use all type Ada.Containers.Count_Type;
-      use Ada.Text_IO;
-      Minimal_Complete_Multiple_Reduce : State_Index_Arrays.Vector;
-   begin
-      Put_Line ("Tokens:");
-      WisiToken.Put_Tokens (Descriptor);
-
-      New_Line;
-      Put_Line ("Productions:");
-      WisiToken.Productions.Put (Grammar, Descriptor);
-
-      if Table.McKenzie_Param.Cost_Limit /= Default_McKenzie_Param.Cost_Limit 
or
-          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
-      then
-         New_Line;
-         Put_Line ("McKenzie:");
-         Put (Table.McKenzie_Param, Descriptor);
-      end if;
-
-      New_Line;
-      Put_Line ("Ancestors:");
-      for ID in Ancestors'Range (1) loop
-         if Any (Ancestors, ID) then
-            Put_Line (Image (ID, Descriptor) & " => " & Image (Slice 
(Ancestors, ID), Descriptor));
-         end if;
-      end loop;
-
-      New_Line;
-      Put_Line (Title & " Parse Table:");
-
-      for State_Index in Table.States'Range loop
-         LR1_Items.Put (Grammar, Descriptor, Kernels (State_Index), 
Kernel_Only => True, Show_Lookaheads => True);
-         New_Line;
-         Put (Descriptor, Table.States (State_Index));
-
-         if Count_Reduce (Table.States (State_Index).Minimal_Complete_Actions) 
> 1 then
-            Minimal_Complete_Multiple_Reduce.Append (State_Index);
-         end if;
-
-         if State_Index /= Table.States'Last then
-            New_Line;
-         end if;
-      end loop;
-
-      if Minimal_Complete_Multiple_Reduce.Length + Conflicts.Length > 0 then
-         New_Line;
-      end if;
-
-      if Minimal_Complete_Multiple_Reduce.Length > 0 then
-         Indent_Wrap
-           ("States with multiple reduce in Minimal_Complete_Action: " & Image 
(Minimal_Complete_Multiple_Reduce));
-      end if;
-
-      if Conflicts.Length > 0 then
-         declare
-            use Ada.Strings.Unbounded;
-            Last_State : Unknown_State_Index := Unknown_State;
-            Line : Unbounded_String := +"States with conflicts:";
-         begin
-            for Conflict of Conflicts loop
-               if Conflict.State_Index /= Last_State then
-                  Append (Line, State_Index'Image (Conflict.State_Index));
-                  Last_State := Conflict.State_Index;
-               end if;
-            end loop;
-            Indent_Wrap (-Line);
-         end;
-      end if;
-   end Put_Parse_Table;
-
-end WisiToken.Generate.LR;
+--  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 (GPL);
+
+with Ada.Strings.Fixed;
+with Ada.Text_IO;
+with System.Multiprocessors;
+with WisiToken.Generate;
+package body WisiToken.Generate.LR is
+
+   package RHS_Set is new SAL.Gen_Unbounded_Definite_Vectors (Natural, 
Boolean, Default_Element => False);
+   type Token_ID_RHS_Set is array (Token_ID range <>) of RHS_Set.Vector;
+
+   type Token_ID_Array_Positive is array (Token_ID range <>) of Positive;
+
+   ----------
+   --  Body subprograms, alphabetical
+
+   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 Min
+     (Item    : in RHS_Sequence_Arrays.Vector;
+      RHS_Set : in LR.RHS_Set.Vector)
+     return Integer
+   is
+      use all type Ada.Containers.Count_Type;
+      Min_Length : Ada.Containers.Count_Type := Ada.Containers.Count_Type'Last;
+      Min_RHS    : Natural                   := Natural'Last;
+   begin
+      for RHS in Item.First_Index .. Item.Last_Index loop
+         if RHS_Set (RHS) and then Min_Length > Item (RHS).Sequence.Length then
+               Min_Length := Item (RHS).Sequence.Length;
+               Min_RHS    := RHS;
+         end if;
+      end loop;
+      if Min_RHS = Natural'Last then
+         raise SAL.Programmer_Error with "nonterm has no minimum terminal 
sequence";
+      else
+         return Min_RHS;
+      end if;
+   end Min;
+
+   procedure Terminal_Sequence
+     (Grammar         : in     WisiToken.Productions.Prod_Arrays.Vector;
+      Descriptor      : in     WisiToken.Descriptor;
+      All_Sequences   : in out Minimal_Sequence_Array;
+      All_Set         : in out Token_ID_Set;
+      RHS_Set         : in out Token_ID_RHS_Set;
+      Recursing       : in out Token_ID_Set;
+      Recursing_Index : in out Token_ID_Array_Positive;
+      Nonterm         : in     Token_ID)
+   is
+      use Ada.Containers;
+      use Token_ID_Arrays;
+
+      subtype Terminals is Token_ID range Descriptor.First_Terminal .. 
Descriptor.Last_Terminal;
+
+      Prod : Productions.Instance renames Grammar (Nonterm);
+
+      Skipped_Recursive : Boolean := False;
+   begin
+      --  We get here because All_Sequences (Nonterm) has not been comptued
+      --  yet. Attempt to compute All_Sequences (Nonterm); it may not
+      --  succeed due to recursion. If successful, set All_Set (Nonterm)
+      --  True.
+      --
+      --  In a useful grammar, all direct and indirect recursive nonterms
+      --  have a non-recursive minimal terminal sequence; finding it will
+      --  break the recursion, allowing this algorithm to complete. This is
+      --  checked in Compute_Minimal_Terminal_Sequences.
+
+      --  Fill All_Sequences (Nonterm) with terminals from each production
+      --  for Nonterm. We set partial results so recursion can be resolved.
+      if All_Sequences (Nonterm).Length = 0 then
+         All_Sequences (Nonterm).Set_First_Last (Prod.RHSs.First_Index, 
Prod.RHSs.Last_Index); -- defaults empty
+         RHS_Set (Nonterm).Set_First_Last (Prod.RHSs.First_Index, 
Prod.RHSs.Last_Index); -- defaults False
+      end if;
+
+      for RHS in Prod.RHSs.First_Index .. Prod.RHSs.Last_Index loop
+         if not RHS_Set (Nonterm)(RHS) then
+            if Prod.RHSs (RHS).Tokens.Length = 0 then
+               RHS_Set (Nonterm)(RHS) := True;
+               if Trace_Generate > Extra then
+                  Ada.Text_IO.Put_Line (Trimmed_Image (Production_ID'(Nonterm, 
RHS)) & " => () empty");
+               end if;
+
+            else
+               for I in Prod.RHSs (RHS).Tokens.First_Index .. Prod.RHSs 
(RHS).Tokens.Last_Index loop
+                  Recursing_Index (Nonterm) := I;
+                  declare
+                     ID : Token_ID renames Prod.RHSs (RHS).Tokens (I);
+                  begin
+                     if ID in Terminals then
+                        All_Sequences (Nonterm) (RHS).Sequence.Append (ID);
+
+                     else
+                        if not All_Set (ID) then
+                           --  Need to compute some RHSs of ID
+
+                           if ID = Nonterm or Recursing (ID) then
+                              --  Nonterm is mutually recursive with itself or 
some other.
+                              All_Sequences (Nonterm)(RHS).Left_Recursive := I 
= Positive'First and
+                                (ID = Nonterm or Recursing_Index (ID) = 
Positive'First);
+
+                              if (for some RHS of RHS_Set (ID) => RHS) then
+                                 --  There is a minimal sequence for ID; use it
+                                 null;
+                              else
+                                 if Trace_Generate > Extra then
+                                    Ada.Text_IO.Put_Line
+                                      (Trimmed_Image (Production_ID'(Nonterm, 
RHS)) & "." & Trimmed_Image (I) & " => " &
+                                         (if ID = Nonterm
+                                          then "direct recursive"
+                                          else "indirect recursive " & Image 
(ID, Descriptor)));
+                                 end if;
+
+                                 All_Sequences (Nonterm)(RHS).Left_Recursive 
:= False;
+                                 All_Sequences (Nonterm)(RHS).Sequence.Clear;
+                                 goto Skip;
+                              end if;
+                           else
+                              Recursing (ID) := True;
+                              if Trace_Generate > Extra then
+                                 Ada.Text_IO.Put_Line (Trimmed_Image (ID) & " 
" & Image (ID, Descriptor) & " compute");
+                              end if;
+                              Terminal_Sequence
+                                (Grammar, Descriptor, All_Sequences, All_Set, 
RHS_Set, Recursing, Recursing_Index, ID);
+                              Recursing (ID) := False;
+
+                              if All_Set (ID) or else (for some RHS of RHS_Set 
(ID) => RHS) then
+                                 --  Found a minimal sequence for ID; use it
+                                 null;
+                              else
+                                 All_Sequences (Nonterm)(RHS).Sequence.Clear;
+                                 goto Skip;
+                              end if;
+                           end if;
+                        end if;
+                        declare
+                           Min_RHS : constant Integer := Min (All_Sequences 
(ID), RHS_Set (ID));
+                        begin
+                           All_Sequences (Nonterm)(RHS).Sequence.Append 
(All_Sequences (ID)(Min_RHS).Sequence);
+                        end;
+                     end if;
+                  end;
+               end loop;
+               RHS_Set (Nonterm)(RHS) := True;
+               if Trace_Generate > Extra then
+                  Ada.Text_IO.Put_Line
+                    (Trimmed_Image (Production_ID'(Nonterm, RHS)) & " => " &
+                       Image (All_Sequences (Nonterm)(RHS), Descriptor));
+               end if;
+            end if;
+         end if;
+         <<Skip>>
+         Skipped_Recursive := True;
+      end loop;
+
+      if Skipped_Recursive then
+         if (for some RHS of RHS_Set (Nonterm) => not RHS) then
+            --  Some RHSs are have unresolved recursion; we will
+            --  eventually try again when the recursion is resolved.
+            if Trace_Generate > Extra then
+               Ada.Text_IO.Put_Line
+                 (Trimmed_Image (Nonterm) & " " & Image (Nonterm, Descriptor) 
& " skipped some recursive");
+            end if;
+            return;
+         end if;
+      end if;
+
+      All_Set (Nonterm) := True;
+
+      if Trace_Generate > Extra then
+         Ada.Text_IO.Put_Line
+           (Trimmed_Image (Nonterm) & " " & Image (Nonterm, Descriptor) & " 
==> " &
+              Image (All_Sequences (Nonterm), Descriptor));
+      end if;
+   end Terminal_Sequence;
+
+   ----------
+   --  Public subprograms, declaration order
+
+   procedure Put
+     (Item       : in Conflict_Lists.List;
+      File       : in Ada.Text_IO.File_Type;
+      Descriptor : in WisiToken.Descriptor)
+   is begin
+      for Conflict of Item loop
+         Ada.Text_IO.Put_Line (File, Image (Conflict, Descriptor));
+      end loop;
+   end Put;
+
+   procedure Add_Action
+     (Symbol               : in     Token_ID;
+      Action               : in     Parse_Action_Rec;
+      Action_List          : in out Action_Node_Ptr;
+      Closure              : in     LR1_Items.Item_Set;
+      Grammar              : in     WisiToken.Productions.Prod_Arrays.Vector;
+      Has_Empty_Production : in     Token_ID_Set;
+      First_Nonterm_Set    : in     Token_Array_Token_Set;
+      Conflicts            : in out Conflict_Lists.List;
+      Descriptor           : in     WisiToken.Descriptor)
+   is
+      Matching_Action : constant Action_Node_Ptr := Find (Symbol, Action_List);
+   begin
+      if Trace_Generate > Outline then
+         Ada.Text_IO.Put (Image (Symbol, Descriptor) & " => ");
+         Put (Descriptor, Action);
+         Ada.Text_IO.New_Line;
+      end if;
+
+      if Matching_Action /= null then
+         if Equal (Matching_Action.Action.Item, Action) then
+            --  Matching_Action is identical to Action, so there is no
+            --  conflict; just don't add it again.
+            if Trace_Generate > Outline then
+               Ada.Text_IO.Put_Line (" - already present");
+            end if;
+            return;
+         else
+            --  There is a conflict. Report it and add it, so the
+            --  generalized parser can follow both paths
+            declare
+               --  Enforce canonical Shift/Reduce or Accept/Reduce
+               --  order, to simplify searching and code generation.
+               Action_A : constant Parse_Action_Rec :=
+                 (if Action.Verb in Shift | Accept_It then Action else 
Matching_Action.Action.Item);
+
+               Action_B : constant Parse_Action_Rec :=
+                 (if Action.Verb in Shift | Accept_It then 
Matching_Action.Action.Item else Action);
+
+               New_Conflict : constant Conflict :=
+                 (Action_A    => Action_A.Verb,
+                  Action_B    => Action_B.Verb,
+                  LHS_A       => Find
+                    (Closure, Action_A, Symbol, Grammar, Has_Empty_Production, 
First_Nonterm_Set, Descriptor),
+                  LHS_B       => Find
+                    (Closure, Action_B, Symbol, Grammar, Has_Empty_Production, 
First_Nonterm_Set, Descriptor),
+                  State_Index => Closure.State,
+                  On          => Symbol);
+            begin
+               if not Is_Present (New_Conflict, Conflicts) then
+                  --  The same conflict may occur in a different
+                  --  item set. Only add it to conflicts once.
+                  Conflicts.Append (New_Conflict);
+
+                  if Trace_Generate > Outline then
+                     Ada.Text_IO.Put_Line (" - conflict added: " & Image 
(New_Conflict, Descriptor));
+                  end if;
+               else
+                  if Trace_Generate > Outline then
+                     Ada.Text_IO.Put_Line (" - conflict duplicate: " & Image 
(New_Conflict, Descriptor));
+                  end if;
+               end if;
+
+               --  More than two actions can occur; see triple_conflict.wy. We 
make
+               --  that an error, since the grammar will be better off without 
them.
+               --  But keep going; the full parse table output will be needed 
to fix
+               --  the excess conflict.
+               if Matching_Action.Action.Next /= null then
+                  if Matching_Action.Action.Item = Action or 
Matching_Action.Action.Next.Item = Action then
+                     if Trace_Generate > Outline then
+                        Ada.Text_IO.Put_Line (" - conflict duplicate");
+                     end if;
+                  else
+                     WisiToken.Generate.Put_Error
+                       ("More than two actions on " & Image (Symbol, 
Descriptor) &
+                          " in state" & State_Index'Image (Closure.State));
+                  end if;
+               end if;
+
+               if Action.Verb = Shift then
+                  Matching_Action.Action := new Parse_Action_Node'(Action, 
Matching_Action.Action);
+               else
+                  Matching_Action.Action.Next := new 
Parse_Action_Node'(Action, Matching_Action.Action.Next);
+               end if;
+            end;
+         end if;
+      else
+         WisiToken.Parse.LR.Add (Action_List, Symbol, Action);
+      end if;
+   end Add_Action;
+
+   procedure Add_Actions
+     (Closure              : in     LR1_Items.Item_Set;
+      Table                : in out Parse_Table;
+      Grammar              : in     WisiToken.Productions.Prod_Arrays.Vector;
+      Has_Empty_Production : in     Token_ID_Set;
+      First_Nonterm_Set    : in     Token_Array_Token_Set;
+      Conflicts            : in out Conflict_Lists.List;
+      Descriptor           : in     WisiToken.Descriptor)
+   is
+      use WisiToken.Token_ID_Arrays;
+
+      State : constant State_Index := Closure.State;
+   begin
+      if Trace_Generate > Outline then
+         Ada.Text_IO.Put_Line ("adding actions for state" & State_Index'Image 
(State));
+      end if;
+
+      for Item of Closure.Set loop
+         if Item.Dot = No_Element then
+            --  Pointer is at the end of the production; add a reduce action.
+
+            Add_Lookahead_Actions
+              (Item, Table.States (State).Action_List, Grammar, 
Has_Empty_Production, First_Nonterm_Set,
+               Conflicts, Closure, Descriptor);
+
+         elsif Element (Item.Dot) in Descriptor.First_Terminal .. 
Descriptor.Last_Terminal then
+            --  Dot is before a terminal token.
+            declare
+               use all type Ada.Containers.Count_Type;
+
+               Dot_ID : constant Token_ID := Element (Item.Dot);
+               --  ID of token after Item.Dot
+
+               Goto_State : constant Unknown_State_Index := 
LR1_Items.Goto_State (Closure, Dot_ID);
+            begin
+               if Dot_ID = Descriptor.EOI_ID then
+                  --  This is the start symbol production with dot before EOF.
+                  declare
+                     P_ID : constant Production_ID := Item.Prod;
+                     RHS  : Productions.Right_Hand_Side renames Grammar 
(P_ID.LHS).RHSs (P_ID.RHS);
+                  begin
+                     Add_Action
+                       (Dot_ID,
+                        (Accept_It, P_ID, RHS.Action, RHS.Check, 
RHS.Tokens.Length - 1),
+                        --  EOF is not pushed on stack in parser, because the 
action for EOF
+                        --  is Accept, not Shift.
+                        Table.States (State).Action_List, Closure,
+                        Grammar, Has_Empty_Production, First_Nonterm_Set, 
Conflicts, Descriptor);
+                  end;
+               else
+                  if Goto_State /= Unknown_State then
+                     Add_Action
+                       (Dot_ID,
+                        (Shift, Goto_State),
+                        Table.States (State).Action_List,
+                        Closure, Grammar, Has_Empty_Production, 
First_Nonterm_Set, Conflicts, Descriptor);
+                  end if;
+               end if;
+            end;
+         else
+            --  Dot is before a non-terminal token; no action.
+            if Trace_Generate > Outline then
+               Ada.Text_IO.Put_Line (Image (Element (Item.Dot), Descriptor) & 
" => no action");
+            end if;
+         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;
+
+      for Item of Closure.Goto_List loop
+         if Item.Symbol in Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal then
+            Add_Goto (Table.States (State), Item.Symbol, Item.State); -- note 
list is already sorted.
+         end if;
+      end loop;
+   end Add_Actions;
+
+   procedure Add_Lookahead_Actions
+     (Item                 : in     LR1_Items.Item;
+      Action_List          : in out Action_Node_Ptr;
+      Grammar              : in     WisiToken.Productions.Prod_Arrays.Vector;
+      Has_Empty_Production : in     Token_ID_Set;
+      First_Nonterm_Set    : in     Token_Array_Token_Set;
+      Conflicts            : in out Conflict_Lists.List;
+      Closure              : in     LR1_Items.Item_Set;
+      Descriptor           : in     WisiToken.Descriptor)
+   is
+      Prod   : Productions.Instance renames Grammar (Item.Prod.LHS);
+      RHS    : Productions.Right_Hand_Side renames Prod.RHSs (Item.Prod.RHS);
+      Action : constant Parse_Action_Rec := (Reduce, Item.Prod, RHS.Action, 
RHS.Check, RHS.Tokens.Length);
+   begin
+      if Trace_Generate > Outline then
+         Ada.Text_IO.Put_Line ("processing lookaheads");
+      end if;
+
+      --  We ignore propagate lookaheads here.
+      for Lookahead in Item.Lookaheads'Range loop
+         if Item.Lookaheads (Lookahead) then
+            if Lookahead = Descriptor.First_Nonterminal then
+               null;
+            else
+               Add_Action
+                 (Lookahead, Action, Action_List, Closure, Grammar,
+                  Has_Empty_Production, First_Nonterm_Set, Conflicts, 
Descriptor);
+            end if;
+         end if;
+      end loop;
+   end Add_Lookahead_Actions;
+
+   procedure Delete_Known
+     (Conflicts       : in out Conflict_Lists.List;
+      Known_Conflicts : in out Conflict_Lists.List)
+   is
+      --  Delete all elements in Conflicts that match an element in
+      --  Known_Conflicts. There can be more than one Conflict that
+      --  match one Known_Conflict.
+      use Conflict_Lists;
+      Known      : Cursor  := Known_Conflicts.First;
+      Next_Known : Cursor;
+   begin
+      loop
+         exit when Known = No_Element;
+         Next_Known := Next (Known);
+         declare
+            I      : Cursor  := Conflicts.First;
+            Next_I : Cursor;
+            Used   : Boolean := False;
+         begin
+            loop
+               exit when I = No_Element;
+               Next_I := Next (I);
+               if Match (Element (Known), Conflicts.Constant_Reference (I)) 
then
+                  Delete (Conflicts, I);
+                  Used := True;
+               end if;
+               I := Next_I;
+            end loop;
+
+            if Used then
+               Delete (Known_Conflicts, Known);
+            end if;
+         end;
+         Known := Next_Known;
+      end loop;
+   end Delete_Known;
+
+   function Find
+     (Closure              : in LR1_Items.Item_Set;
+      Action               : in Parse_Action_Rec;
+      Lookahead            : in Token_ID;
+      Grammar              : in WisiToken.Productions.Prod_Arrays.Vector;
+      Has_Empty_Production : in Token_ID_Set;
+      First                : in Token_Array_Token_Set;
+      Descriptor           : in WisiToken.Descriptor)
+     return Token_ID
+   is
+      use WisiToken.Token_ID_Arrays;
+
+      ID_I : Cursor;
+   begin
+      case Action.Verb is
+      when Reduce | Accept_It =>
+         --  If the nonterm produced by the reduce is the LHS of the state
+         --  production, use it.
+         for Item of Closure.Set loop
+            if LR1_Items.In_Kernel (Grammar, Descriptor, Item) and
+              Action.Production.LHS = Item.Prod.LHS
+            then
+               return Item.Prod.LHS;
+            end if;
+         end loop;
+
+         --  The reduce nonterm is after Dot in a state production; find which
+         --  one, use that.
+         for Item of Closure.Set loop
+            if LR1_Items.In_Kernel (Grammar, Descriptor, Item) then
+               ID_I := Item.Dot;
+               loop
+                  if ID_I = No_Element then
+                     if Item.Lookaheads (Lookahead) then
+                        return Item.Prod.LHS;
+                     end if;
+                  else
+                     declare
+                        Dot_ID : Token_ID renames Element (ID_I);
+                     begin
+                        if Dot_ID = Lookahead or
+                          (Dot_ID in Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal and then
+                             First (Dot_ID, Lookahead))
+                        then
+                           return Item.Prod.LHS;
+                        end if;
+                        exit when Dot_ID in Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal and then
+                          not Has_Empty_Production (Dot_ID);
+                     end;
+                  end if;
+
+                  exit when ID_I = No_Element;
+                  Next (ID_I);
+               end loop;
+            end if;
+         end loop;
+
+      when Shift =>
+
+         for Item of Closure.Set loop
+            --  Lookahead (the token shifted) is starting a nonterm in a state
+            --  production; it is in First of that nonterm.
+            if LR1_Items.In_Kernel (Grammar, Descriptor, Item) then
+               ID_I := Item.Dot;
+               loop
+                  exit when ID_I = No_Element;
+                  declare
+                     Dot_ID : Token_ID renames Element (ID_I);
+                  begin
+                     if Dot_ID = Lookahead or
+                       (Dot_ID in Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal and then
+                          First (Dot_ID, Lookahead))
+                     then
+                        return Item.Prod.LHS;
+                     end if;
+
+                     exit when Dot_ID in Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal and then
+                       not Has_Empty_Production (Dot_ID);
+                  end;
+
+                  Next (ID_I);
+               end loop;
+            end if;
+         end loop;
+
+      when WisiToken.Parse.LR.Error =>
+         raise SAL.Programmer_Error;
+      end case;
+
+      Ada.Text_IO.Put_Line
+        ("item for " & Image (Action, Descriptor) & " on " & Image (Lookahead, 
Descriptor) & " not found in");
+      LR1_Items.Put (Grammar, Descriptor, Closure, Kernel_Only => True);
+      raise SAL.Programmer_Error;
+   end Find;
+
+   function Image (Item : in Conflict; Descriptor : in WisiToken.Descriptor) 
return String
+   is begin
+      return
+        ("%conflict " &
+           Conflict_Parse_Actions'Image (Item.Action_A) & "/" &
+           Conflict_Parse_Actions'Image (Item.Action_B) & " in state " &
+           Image (Item.LHS_A, Descriptor) & ", " &
+           Image (Item.LHS_B, Descriptor) &
+           " on token " & Image (Item.On, Descriptor) &
+           " (" & State_Index'Image (Item.State_Index) & ")"); -- state number 
last for easier delete
+   end Image;
+
+   function Is_Present (Item : in Conflict; Conflicts : in 
Conflict_Lists.List) return Boolean
+   is
+      use Conflict_Lists;
+      I : Cursor := Conflicts.First;
+   begin
+      loop
+         exit when I = No_Element;
+         if Match (Item, Conflicts.Constant_Reference (I)) then
+            return True;
+         end if;
+         I := Next (I);
+      end loop;
+      return False;
+   end Is_Present;
+
+   function Match (Known : in Conflict; Item : in 
Conflict_Lists.Constant_Reference_Type) return Boolean
+   is begin
+      --  Ignore State_Index. Actions are in canonical order; enforced
+      --  in Add_Action above. For reduce/reduce, LHS_A, LHS_B are not
+      --  in canonical order.
+      return
+        Known.Action_A = Item.Action_A and
+        Known.Action_B = Item.Action_B and
+        ((Known.LHS_A = Item.LHS_A and Known.LHS_B = Item.LHS_B) or
+           (Known.LHS_B = Item.LHS_A and Known.LHS_A = Item.LHS_B)) and
+        Known.On = Item.On;
+   end Match;
+
+   function Image (Item : in RHS_Sequence; Descriptor : in 
WisiToken.Descriptor) return String
+   is begin
+      return "(" & Boolean'Image (Item.Left_Recursive) & ", " & Image 
(Item.Sequence, Descriptor) & ")";
+   end Image;
+
+   function Min (Item : in RHS_Sequence_Arrays.Vector) return RHS_Sequence
+   is
+      use all type Ada.Containers.Count_Type;
+      Min_Length : Ada.Containers.Count_Type := Ada.Containers.Count_Type'Last;
+      Min_RHS    : Natural                   := Natural'Last;
+   begin
+      --  This version assumes all RHS are computed.
+      for RHS in Item.First_Index .. Item.Last_Index loop
+         if Min_Length > Item (RHS).Sequence.Length then
+            Min_Length := Item (RHS).Sequence.Length;
+            Min_RHS    := RHS;
+         end if;
+      end loop;
+      if Min_RHS = Natural'Last then
+         raise Grammar_Error with "nonterm has no minimum terminal sequence";
+      else
+         return Item (Min_RHS);
+      end if;
+   end Min;
+
+   function Compute_Minimal_Terminal_Sequences
+     (Descriptor : in     WisiToken.Descriptor;
+      Grammar    : in     WisiToken.Productions.Prod_Arrays.Vector)
+     return Minimal_Sequence_Array
+   is
+      --  Result (ID).Length = 0 is a valid sequence (ie the nonterminal can
+      --  be empty), so we use an auxilliary array to track whether Result
+      --  (ID) has been computed.
+      --
+      --  We also need to detect mutual recursion, and incomplete grammars.
+
+      All_Set   : Token_ID_Set := (Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal  => False);
+      Recursing : Token_ID_Set := (Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal  => False);
+
+      Recursing_Index : Token_ID_Array_Positive :=
+        (Descriptor.First_Nonterminal .. Descriptor.Last_Nonterminal  => 
Positive'Last);
+
+      RHS_Set : Token_ID_RHS_Set :=
+        (Descriptor.First_Nonterminal .. Descriptor.Last_Nonterminal => 
LR.RHS_Set.Empty_Vector);
+
+      Last_Count : Integer := 0;
+      This_Count : Integer;
+   begin
+      return Result : Minimal_Sequence_Array (Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal) do
+         loop
+            exit when (for all B of All_Set => B);
+            for P of Grammar loop
+               if not All_Set (P.LHS) then
+                  Terminal_Sequence (Grammar, Descriptor, Result, All_Set, 
RHS_Set, Recursing, Recursing_Index, P.LHS);
+               end if;
+            end loop;
+            This_Count := Count (All_Set);
+            if This_Count = Last_Count then
+               Ada.Text_IO.Put_Line (Ada.Text_IO.Standard_Error, Image 
(All_Set, Descriptor, Inverted => True));
+               raise Grammar_Error with "recursion not resolved";
+            end if;
+            Last_Count := This_Count;
+         end loop;
+      end return;
+   end Compute_Minimal_Terminal_Sequences;
+
+   function Compute_Minimal_Terminal_First
+     (Descriptor                 : in WisiToken.Descriptor;
+      Minimal_Terminal_Sequences : in Minimal_Sequence_Array)
+     return Token_Array_Token_ID
+   is
+      use all type Ada.Containers.Count_Type;
+      use Token_ID_Arrays;
+   begin
+      return Result : Token_Array_Token_ID (Descriptor.First_Nonterminal .. 
Descriptor.Last_Nonterminal) do
+         for ID in Result'Range loop
+            declare
+               Min_Seq : Token_ID_Arrays.Vector renames Min 
(Minimal_Terminal_Sequences (ID)).Sequence;
+            begin
+               if Min_Seq.Length = 0 then
+                  Result (ID) := Invalid_Token_ID;
+               else
+                  Result (ID) := Element (Min_Seq.First);
+               end if;
+            end;
+         end loop;
+      end return;
+   end Compute_Minimal_Terminal_First;
+
+   procedure Set_Minimal_Complete_Actions
+     (State                      : in out Parse_State;
+      Kernel                     : in     LR1_Items.Item_Set;
+      Descriptor                 : in     WisiToken.Descriptor;
+      Grammar                    : in     
WisiToken.Productions.Prod_Arrays.Vector;
+      Minimal_Terminal_Sequences : in     Minimal_Sequence_Array;
+      Minimal_Terminal_First     : in     Token_Array_Token_ID)
+   is
+      use all type Ada.Containers.Count_Type;
+      use LR1_Items.Item_Lists;
+      use Token_ID_Arrays;
+
+      subtype Terminals is Token_ID range Descriptor.First_Terminal .. 
Descriptor.Last_Terminal;
+
+      Working_Set : LR1_Items.Item_Lists.List := Kernel.Set;
+      Del         : LR1_Items.Item_Lists.Cursor;
+
+      function Find_Action (List : in Action_Node_Ptr; ID : in Token_ID) 
return Minimal_Action
+      is
+         Node : Action_Node_Ptr := List;
+      begin
+         loop
+            if Node.Symbol = ID then
+               case Node.Action.Item.Verb is
+               when Shift =>
+                  return (Shift, ID, Node.Action.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);
+               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;
+
+      procedure Delete_Non_Minimal
+      is
+         Min_Length : Ada.Containers.Count_Type := 
Ada.Containers.Count_Type'Last;
+         I          : LR1_Items.Item_Lists.Cursor;
+         Min_I      : LR1_Items.Item_Lists.Cursor;
+      begin
+         --  The absolute minimal production for an LHS may not be in this 
state.
+         --  For example, for an Ada aggregate, the minimal terminal sequence 
is:
+         --  aggregate <= LEFT_PAREN RIGHT_PAREN
+         --  but one state has:
+         --  aggregate <= LEFT_PAREN expression_opt WITH ^ NULL RECORD 
RIGHT_PAREN
+         --  aggregate <= LEFT_PAREN expression_opt WITH ^ association_list 
RIGHT_PAREN
+         --
+         --  Find the minimum of the productions that are present
+
+         I := Working_Set.First;
+         loop
+            exit when not Has_Element (I);
+            declare
+               Prod : constant WisiToken.Production_ID := Constant_Ref 
(I).Prod;
+            begin
+               --  IMPROVEME: If Dot is near the end of a production, this is 
not the
+               --  best metric; the best metric is count of remaining tokens to
+               --  insert. But changing to that won't make much difference; it 
only
+               --  matters when Dot is not near the beginning, but then we 
don't have
+               --  multiple productions (they all must have same prefix).
+               --
+               --  We must eliminate direct left recursion; otherwise it can 
appear
+               --  to have a minimum length. For example, consider ada_lite 
state
+               --  149:
+               --
+               --  57.0:actual_parameter_part <= LEFT_PAREN association_list ^ 
RIGHT_PAREN
+               --  61.0:association_list <= association_list ^ COMMA 
association_opt
+               --
+               --  Both have length 2, but the association_list requires a
+               --  RIGHT_PAREN eventually.
+               --
+               --  We also have to eliminate indirect left recursion; consider 
ada_lite state 60:
+               --
+               --   94.0:function_specification <= FUNCTION name ^ 
parameter_and_result_profile
+               --  103.0:name <= name ^ LEFT_PAREN range_list
+               --  103.1:name <= name ^ actual_parameter_part
+               --  123.0:selected_component <= name ^ DOT IDENTIFIER
+               --
+               --  'selected' component has length 3, which two others also 
have, but
+               --  it requires more eventually.
+               --
+               --  An item production is left recursive only if Dot is after 
the
+               --  first token (ie, before the second token); consider 
conflict_name
+               --  state 5:
+               --
+               --   8.0:attribute_reference <= name TICK ^ 
attribute_designator, RIGHT_PAREN/TICK/Wisi_EOI
+               --  11.0:qualified_expression <= name TICK ^ aggregate, 
RIGHT_PAREN/TICK/Wisi_EOI
+               --
+               --  Both are indirect left recursive with "name", but both are 
past
+               --  the recursion, and can be completed.
+
+               if Min_Length > Minimal_Terminal_Sequences 
(Prod.LHS)(Prod.RHS).Sequence.Length and
+                 not (Minimal_Terminal_Sequences 
(Prod.LHS)(Prod.RHS).Left_Recursive and then
+                        (Has_Element (Constant_Ref (I).Dot) and then
+                           Constant_Ref (I).Dot = To_Cursor (Grammar 
(Prod.LHS).RHSs (Prod.RHS).Tokens, 2)))
+               then
+                  Min_Length := Minimal_Terminal_Sequences 
(Prod.LHS)(Prod.RHS).Sequence.Length;
+                  Min_I      := I;
+               end if;
+            end;
+            Next (I);
+         end loop;
+
+         if not Has_Element (Min_I) then
+            Working_Set.Clear;
+         else
+            I := Working_Set.First;
+            loop
+               exit when not Has_Element (I);
+               declare
+                  Prod : constant WisiToken.Production_ID := Constant_Ref 
(I).Prod;
+               begin
+                  if I /= Min_I then
+                     if Trace_Generate > Extra then
+                        Ada.Text_IO.Put_Line ("delete " & Image (Prod) & " not 
minimal");
+                     end if;
+                     Del := I;
+                     Next (I);
+                     Working_Set.Delete (Del);
+                  else
+                     Next (I);
+                  end if;
+               end;
+            end loop;
+         end if;
+      end Delete_Non_Minimal;
+
+   begin
+      --  The actions computed here are used in the error recovery
+      --  algorithm, to decide what terminals to insert in the input stream
+      --  in order to correct an error. The strategy is to complete a high
+      --  level production (ie declaration or statement) as quickly as
+      --  possible, because the next real token is known to be the start of
+      --  a high level production, or the end of a containing block-style
+      --  production.
+      --
+      --  The actions are empty in a state that includes the accept
+      --  production, or where the only not left recursive productions can
+      --  be empty. That tells the error recovery algorithm to stop using
+      --  the minimal complete actions strategy.
+
+      if (for some Item of Working_Set =>
+            Item.Prod.LHS = Descriptor.Accept_ID and
+            (Has_Element (Item.Dot) and then Element (Item.Dot) = 
Descriptor.EOI_ID))
+      then
+         --  No actions
+         return;
+      end if;
+
+      if Working_Set.Length > 1 then
+         --  There are multiple productions in this state, all equally valid;
+         --  the choice is determined by what input error recovery inserts.
+         Delete_Non_Minimal;
+      end if;
+
+      if Trace_Generate > Extra then
+         Ada.Text_IO.Put_Line ("after deletions:");
+         LR1_Items.Put (Grammar, Descriptor, Working_Set, Show_Lookaheads => 
False);
+      end if;
+
+      --  Find the actions for the remaining productions.
+
+      for Item of Working_Set loop
+         if not Has_Element (Item.Dot) then
+            --  Item has no next terminal. Include a reduce action; the
+            --  Minimal_Terminal_First for the resulting state will be used.
+            if Trace_Generate > Extra then
+               Ada.Text_IO.Put_Line ("reduce " & Image (Item.Prod));
+            end if;
+            State.Minimal_Complete_Action :=
+              (Reduce, Item.Prod.LHS,
+                Token_Count => Grammar (Item.Prod.LHS).RHSs 
(Item.Prod.RHS).Tokens.Length);
+         else
+            declare
+               ID : constant Token_ID := Element (Item.Dot);
+            begin
+               if ID /= Descriptor.EOI_ID then
+
+                  if ID in Terminals then
+                     State.Minimal_Complete_Action := Find_Action 
(State.Action_List, ID);
+
+                  else
+                     if Minimal_Terminal_First (ID) = Invalid_Token_ID then
+                        --  Item.Dot is a nullable nonterm, include a reduce 
of the null
+                        --  nonterm, rather than a shift of the following 
terminal; recover
+                        --  must do the reduce first.
+                        State.Minimal_Complete_Action := (Reduce, ID, 
Token_Count => 0);
+
+                     else
+                        State.Minimal_Complete_Action := Find_Action 
(State.Action_List, Minimal_Terminal_First (ID));
+                     end if;
+                  end if;
+               end if;
+            end;
+         end if;
+      end loop;
+   end Set_Minimal_Complete_Actions;
+
+   ----------
+   --  Parse table output
+
+   procedure Put_Text_Rep
+     (Table        : in Parse_Table;
+      File_Name    : in String;
+      Action_Names : in Names_Array_Array;
+      Check_Names  : in Names_Array_Array)
+   is
+      use Ada.Text_IO;
+      File : File_Type;
+   begin
+      --  Only space, semicolon, newline delimit object values. Bounds of
+      --  arrays output before each array, unless known from discriminants.
+      --  End of lists indicated by semicolon. Action, Check subprograms are
+      --  represented by True if present, False if not; look up the actual
+      --  address Table.Productions.
+
+      Create (File, Out_File, File_Name);
+
+      --  First the discriminants
+      Put (File,
+           Trimmed_Image (Table.State_First) & State_Index'Image 
(Table.State_Last) &
+             Token_ID'Image (Table.First_Terminal) & Token_ID'Image 
(Table.Last_Terminal) &
+             Token_ID'Image (Table.First_Nonterminal) & Token_ID'Image 
(Table.Last_Nonterminal));
+      New_Line (File);
+
+      for State of Table.States loop
+         Put (File, Integer'Image (State.Productions.First_Index));
+         Put (File, Integer'Image (State.Productions.Last_Index));
+         for Prod of State.Productions loop
+            Put (File, Token_ID'Image (Prod.LHS) & Integer'Image (Prod.RHS));
+         end loop;
+         New_Line (File);
+
+         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));
+
+                     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));
+
+                        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));
+
+                     when Parse.LR.Error =>
+                        --  Error action terminates the action list
+                        Put_Symbol := False;
+                     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;
+               New_Line (File);
+
+               Node_I := Node_I.Next;
+            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;
+
+         declare
+            Action  : Minimal_Action renames State.Minimal_Complete_Action;
+         begin
+            case State.Minimal_Complete_Action.Verb is
+            when Pause =>
+               null;
+            when Shift =>
+               Put (File, Minimal_Verbs'Image (Action.Verb));
+               Put (File, Token_ID'Image (Action.ID) & State_Index'Image 
(Action.State));
+            when Reduce =>
+               Put (File, Minimal_Verbs'Image (Action.Verb));
+               Put (File, Token_ID'Image (Action.Nonterm) & 
Ada.Containers.Count_Type'Image (Action.Token_Count));
+            end case;
+         end;
+         Put (File, ';');
+         New_Line (File);
+      end loop;
+      Close (File);
+   end Put_Text_Rep;
+
+   procedure Put (Item : in Parse_Action_Rec; Descriptor : in 
WisiToken.Descriptor)
+   is
+      use Ada.Containers;
+      use Ada.Text_IO;
+   begin
+      case Item.Verb is
+      when Shift =>
+         Put ("shift and goto state" & State_Index'Image (Item.State));
+
+      when Reduce =>
+         Put
+           ("reduce" & Count_Type'Image (Item.Token_Count) & " tokens to " &
+              Image (Item.Production.LHS, Descriptor));
+      when Accept_It =>
+         Put ("accept it");
+      when Parse.LR.Error =>
+         Put ("ERROR");
+      end case;
+   end Put;
+
+   procedure Put (Item : in McKenzie_Param_Type; Descriptor : in 
WisiToken.Descriptor)
+   is
+      use Ada.Text_IO;
+   begin
+      Put_Line ("(Insert =>");
+      for I in Item.Insert'Range loop
+         Put (" " & Padded_Image (I, Descriptor) & " =>" & Natural'Image 
(Item.Insert (I)));
+         if I = Item.Insert'Last then
+            Put_Line (")");
+         else
+            Put_Line (",");
+         end if;
+      end loop;
+      Put_Line ("(Delete =>");
+      for I in Item.Delete'Range loop
+         Put (" " & Padded_Image (I, Descriptor) & " =>" & Natural'Image 
(Item.Delete (I)));
+         if I = Item.Delete'Last then
+            Put_Line (")");
+         else
+            Put_Line (",");
+         end if;
+      end loop;
+      Put_Line ("(Push_Back =>");
+      for I in Item.Delete'Range loop
+         Put (" " & Padded_Image (I, Descriptor) & " =>" & Natural'Image 
(Item.Delete (I)));
+         if I = Item.Delete'Last then
+            Put_Line (")");
+         else
+            Put_Line (",");
+         end if;
+      end loop;
+      Put_Line ("Ignore_Check_Fail =>" & Integer'Image 
(Item.Ignore_Check_Fail));
+      Put_Line ("Task_Count        =>" & 
System.Multiprocessors.CPU_Range'Image (Item.Task_Count));
+      Put_Line ("Cost_Limit        =>" & Integer'Image (Item.Cost_Limit));
+      Put_Line ("Check_Limit       =>" & Token_Index'Image (Item.Check_Limit));
+      Put_Line ("Check_Delta_Limit =>" & Integer'Image 
(Item.Check_Delta_Limit));
+      Put_Line ("Enqueue_Limit     =>" & Integer'Image (Item.Enqueue_Limit));
+   end Put;
+
+   procedure Put (Descriptor : in WisiToken.Descriptor; Item : in 
Parse_Action_Rec)
+   is
+      use Ada.Containers;
+      use Ada.Text_IO;
+   begin
+      case Item.Verb is
+      when Shift =>
+         Put ("shift and goto state" & State_Index'Image (Item.State));
+      when Reduce =>
+         Put
+           ("reduce" & Count_Type'Image (Item.Token_Count) & " tokens to " &
+              Image (Item.Production.LHS, Descriptor));
+         Put (" " & Trimmed_Image (Item.Production));
+      when Accept_It =>
+         Put ("accept it");
+         Put (" " & Trimmed_Image (Item.Production));
+      when Parse.LR.Error =>
+         Put ("ERROR");
+      end case;
+   end Put;
+
+   procedure Put (Descriptor : in WisiToken.Descriptor; Action : in 
Parse_Action_Node_Ptr)
+   is
+      use Ada.Text_IO;
+      Ptr    : Parse_Action_Node_Ptr   := Action;
+      Column : constant Positive_Count := Col;
+   begin
+      loop
+         Put (Descriptor, Ptr.Item);
+         Ptr := Ptr.Next;
+         exit when Ptr = null;
+         Put_Line (",");
+         Set_Col (Column);
+      end loop;
+   end Put;
+
+   procedure Put (Descriptor : in WisiToken.Descriptor; State : in Parse_State)
+   is
+      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);
+         New_Line;
+         Action_Ptr := Action_Ptr.Next;
+      end loop;
+
+      if Goto_Ptr /= null then
+         New_Line;
+      end if;
+
+      while Goto_Ptr /= null 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);
+      end loop;
+
+      New_Line;
+      Put ("   Minimal_Complete_Action => (");
+      case State.Minimal_Complete_Action.Verb is
+      when Pause =>
+         null;
+      when Shift =>
+         Put (Image (State.Minimal_Complete_Action.ID, Descriptor));
+      when Reduce =>
+         Put (Image (State.Minimal_Complete_Action.Nonterm, Descriptor));
+      end case;
+      Put_Line (")");
+   end Put;
+
+   procedure Put_Parse_Table
+     (Table      : in Parse_Table_Ptr;
+      Title      : in String;
+      Grammar    : in WisiToken.Productions.Prod_Arrays.Vector;
+      Kernels    : in LR1_Items.Item_Set_List;
+      Conflicts  : in Conflict_Lists.List;
+      Descriptor : in WisiToken.Descriptor)
+   is
+      use all type Ada.Containers.Count_Type;
+      use Ada.Text_IO;
+   begin
+      Put_Line ("Tokens:");
+      WisiToken.Put_Tokens (Descriptor);
+
+      New_Line;
+      Put_Line ("Productions:");
+      WisiToken.Productions.Put (Grammar, Descriptor);
+
+      if Table.McKenzie_Param.Cost_Limit /= Default_McKenzie_Param.Cost_Limit 
or
+          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
+      then
+         New_Line;
+         Put_Line ("McKenzie:");
+         Put (Table.McKenzie_Param, Descriptor);
+      end if;
+
+      New_Line;
+      Put_Line (Title & " Parse Table:");
+
+      for State_Index in Table.States'Range loop
+         LR1_Items.Put (Grammar, Descriptor, Kernels (State_Index), 
Kernel_Only => True, Show_Lookaheads => True);
+         New_Line;
+         Put (Descriptor, Table.States (State_Index));
+
+         if State_Index /= Table.States'Last then
+            New_Line;
+         end if;
+      end loop;
+
+      if Conflicts.Length > 0 then
+         declare
+            use Ada.Strings.Unbounded;
+            Last_State : Unknown_State_Index := Unknown_State;
+            Line : Unbounded_String := +"States with conflicts:";
+         begin
+            for Conflict of Conflicts loop
+               if Conflict.State_Index /= Last_State then
+                  Append (Line, State_Index'Image (Conflict.State_Index));
+                  Last_State := Conflict.State_Index;
+               end if;
+            end loop;
+            Indent_Wrap (-Line);
+         end;
+      end if;
+   end Put_Parse_Table;
+
+end WisiToken.Generate.LR;
diff --git a/wisitoken-generate-lr.ads b/wisitoken-generate-lr.ads
index 4a57ff8..9cb22f2 100644
--- a/wisitoken-generate-lr.ads
+++ b/wisitoken-generate-lr.ads
@@ -2,7 +2,7 @@
 --
 --  Common utilities for LR parser table generators.
 --
---  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
@@ -117,33 +117,59 @@ package WisiToken.Generate.LR is
 
    function Match (Known : in Conflict; Item : in 
Conflict_Lists.Constant_Reference_Type) return Boolean;
 
-   procedure Compute_Minimal_Terminal_Sequences
-     (Grammar    : in     WisiToken.Productions.Prod_Arrays.Vector;
-      Descriptor : in     WisiToken.Descriptor;
-      Result     : in out Token_Sequence_Arrays.Vector);
+   type RHS_Sequence is
+   record
+      Left_Recursive : Boolean := False;
+      --  Direct or indirect; see comment in
+      --  Set_Minimal_Complete_Actions.Delete_Non_Minimal.
+
+      Sequence : Token_ID_Arrays.Vector;
+   end record;
+
+   package RHS_Sequence_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
+     (Natural, RHS_Sequence, Default_Element => (others => <>));
+
+   function Image (Item : in RHS_Sequence; Descriptor : in 
WisiToken.Descriptor) return String;
+   --  Positional Ada aggregate syntax.
+
+   function Image is new RHS_Sequence_Arrays.Gen_Image_Aux (Descriptor, Image);
+
+   function Min (Item : in RHS_Sequence_Arrays.Vector) return RHS_Sequence;
+   --  Return element of Item with minimum length;
+
+   type Minimal_Sequence_Array is array (Token_ID range <>) of 
RHS_Sequence_Arrays.Vector;
+
+   function Compute_Minimal_Terminal_Sequences
+     (Descriptor : in     WisiToken.Descriptor;
+      Grammar    : in     WisiToken.Productions.Prod_Arrays.Vector)
+     return Minimal_Sequence_Array;
    --  For each production in Grammar, compute the minimal sequence of
    --  terminals that will complete it. Result is an empty sequence if
-   --  the production may be empty.
+   --  the production may be empty, or Invalid_Token_ID if it is
+   --  recursive.
 
-   function Minimal_Terminal_First
-     (Grammar    : in     WisiToken.Productions.Prod_Arrays.Vector;
-      Descriptor : in     WisiToken.Descriptor)
+   function Compute_Minimal_Terminal_First
+     (Descriptor                 : in WisiToken.Descriptor;
+      Minimal_Terminal_Sequences : in Minimal_Sequence_Array)
       return Token_Array_Token_ID;
    --  For each nonterminal in Grammar, return the first of the minimal
    --  sequence of terminals that will complete it; Invalid_Token_ID if
    --  the minimal sequence is empty.
 
    procedure Set_Minimal_Complete_Actions
-     (State                  : in out Parse_State;
-      Kernel                 : in     LR1_Items.Item_Set;
-      Minimal_Terminal_First : in     Token_Array_Token_ID;
-      Ancestors              : in     Token_Array_Token_Set;
-      Descriptor             : in     WisiToken.Descriptor;
-      Grammar                : in     
WisiToken.Productions.Prod_Arrays.Vector);
-   --  Set State.Minimal_Terminal_First to the set of terminals that will
+     (State                      : in out Parse_State;
+      Kernel                     : in     LR1_Items.Item_Set;
+      Descriptor                 : in     WisiToken.Descriptor;
+      Grammar                    : in     
WisiToken.Productions.Prod_Arrays.Vector;
+      Minimal_Terminal_Sequences : in     Minimal_Sequence_Array;
+      Minimal_Terminal_First     : in     Token_Array_Token_ID);
+   --  Set State.Minimal_Complete_Actions to the set of actions that will
    --  most quickly complete the productions in Kernel (which must be for
    --  State). Useful in error correction when we know the next actual
    --  terminal is a block ending or statement start.
+   --
+   --  The Minimal_Complete_Actions will be empty in a state where there
+   --  is nothing useful to do.
 
    ----------
    --  Parse table output
@@ -169,7 +195,6 @@ package WisiToken.Generate.LR is
       Title      : in String;
       Grammar    : in WisiToken.Productions.Prod_Arrays.Vector;
       Kernels    : in LR1_Items.Item_Set_List;
-      Ancestors  : in Token_Array_Token_Set;
       Conflicts  : in Conflict_Lists.List;
       Descriptor : in WisiToken.Descriptor);
 
diff --git a/wisitoken-generate-lr1_items.adb b/wisitoken-generate-lr1_items.adb
index 05ad564..b6d14ca 100644
--- a/wisitoken-generate-lr1_items.adb
+++ b/wisitoken-generate-lr1_items.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  Copyright (C) 2002, 2003, 2008, 2009, 2012 - 2015, 2017, 2018 Free 
Software Foundation, Inc.
+--  Copyright (C) 2002, 2003, 2008, 2009, 2012 - 2015, 2017 - 2019 Free 
Software Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -43,7 +43,7 @@ package body WisiToken.Generate.LR1_Items is
    begin
       for Item of Set loop
          if Item.Dot /= Token_ID_Arrays.No_Element then
-            if Element (Item.Dot) /= Descriptor.EOF_ID then
+            if Element (Item.Dot) /= Descriptor.EOI_ID then
                IDs (Element (Item.Dot)) := True;
             end if;
          end if;
@@ -536,6 +536,23 @@ package body WisiToken.Generate.LR1_Items is
    procedure Put
      (Grammar         : in WisiToken.Productions.Prod_Arrays.Vector;
       Descriptor      : in WisiToken.Descriptor;
+      Item            : in Item_Lists.List;
+      Show_Lookaheads : in Boolean := True;
+      Kernel_Only     : in Boolean := False)
+   is begin
+      for It of Item loop
+         if not Kernel_Only or else
+           In_Kernel (Grammar, Descriptor, It)
+         then
+            Ada.Text_IO.Put_Line
+              ("  " & Image (Grammar, Descriptor, It, Show_Lookaheads => 
Show_Lookaheads));
+         end if;
+      end loop;
+   end Put;
+
+   procedure Put
+     (Grammar         : in WisiToken.Productions.Prod_Arrays.Vector;
+      Descriptor      : in WisiToken.Descriptor;
       Item            : in Item_Set;
       Show_Lookaheads : in Boolean := True;
       Kernel_Only     : in Boolean := False;
@@ -547,14 +564,7 @@ package body WisiToken.Generate.LR1_Items is
          Put_Line ("State" & Unknown_State_Index'Image (Item.State) & ":");
       end if;
 
-      for It of Item.Set loop
-         if not Kernel_Only or else
-           In_Kernel (Grammar, Descriptor, It)
-         then
-            Put_Line
-              ("  " & Image (Grammar, Descriptor, It, Show_Lookaheads => 
Show_Lookaheads));
-         end if;
-      end loop;
+      Put (Grammar, Descriptor, Item.Set, Show_Lookaheads, Kernel_Only);
 
       if Show_Goto_List then
          Put (Descriptor, Item.Goto_List);
diff --git a/wisitoken-generate-lr1_items.ads b/wisitoken-generate-lr1_items.ads
index d776e4a..3ee55d8 100644
--- a/wisitoken-generate-lr1_items.ads
+++ b/wisitoken-generate-lr1_items.ads
@@ -2,7 +2,7 @@
 --
 --  Types and operatorion for LR(1) items.
 --
---  Copyright (C) 2003, 2008, 2013-2015, 2017, 2018 Free Software Foundation, 
Inc.
+--  Copyright (C) 2003, 2008, 2013 - 2015, 2017 - 2019 Free Software 
Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -216,12 +216,12 @@ package WisiToken.Generate.LR1_Items is
    --  Not combined with non-Lookaheads version for speed; this is called
    --  a lot.
 
-   package Item_Set_Arrays is new SAL.Gen_Unbounded_Definite_Vectors 
(State_Index, Item_Set);
+   package Item_Set_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
+     (State_Index, Item_Set, Default_Element => (others => <>));
    subtype Item_Set_List is Item_Set_Arrays.Vector;
 
-   package State_Index_Arrays is new SAL.Gen_Unbounded_Definite_Vectors 
(Positive, State_Index);
-
-   package Int_Arrays is new SAL.Gen_Unbounded_Definite_Vectors (Positive, 
Interfaces.Integer_16);
+   package Int_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
+     (Positive, Interfaces.Integer_16, Default_Element => 
Interfaces.Integer_16'Last);
    function Compare_Integer_16 (Left, Right : in Interfaces.Integer_16) return 
SAL.Compare_Result is
      (if Left > Right then SAL.Greater
       elsif Left < Right then SAL.Less
@@ -313,6 +313,13 @@ package WisiToken.Generate.LR1_Items is
    procedure Put
      (Grammar         : in WisiToken.Productions.Prod_Arrays.Vector;
       Descriptor      : in WisiToken.Descriptor;
+      Item            : in Item_Lists.List;
+      Show_Lookaheads : in Boolean := True;
+      Kernel_Only     : in Boolean := False);
+
+   procedure Put
+     (Grammar         : in WisiToken.Productions.Prod_Arrays.Vector;
+      Descriptor      : in WisiToken.Descriptor;
       Item            : in Item_Set;
       Show_Lookaheads : in Boolean := True;
       Kernel_Only     : in Boolean := False;
diff --git a/wisitoken-generate.adb b/wisitoken-generate.adb
index 81ae9da..9796b87 100644
--- a/wisitoken-generate.adb
+++ b/wisitoken-generate.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  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
@@ -358,67 +358,6 @@ package body WisiToken.Generate is
       return Result;
    end Follow;
 
-   function Ancestors
-     (Grammar    : in WisiToken.Productions.Prod_Arrays.Vector;
-      Descriptor : in WisiToken.Descriptor)
-     return Token_Array_Token_Set
-   is
-      use all type Ada.Containers.Count_Type;
-
-      Done : Boolean := False;
-   begin
-      return All_Ancestors : Token_Array_Token_Set
-          (Descriptor.First_Terminal .. Grammar.Last_Index,
-           Grammar.First_Index .. Grammar.Last_Index) :=
-             (others => (others => False))
-      do
-         loop
-            exit when Done;
-            Done := True;
-            for Prod of Grammar loop
-               for R of Prod.RHSs loop
-                  if R.Tokens.Length = 1 then
-                     declare
-                        ID : constant Token_ID := R.Tokens (1);
-                     begin
-                        if not All_Ancestors (ID, Prod.LHS) then
-                           Done := False;
-                        end if;
-                        All_Ancestors (ID, Prod.LHS) := True;
-                        for J in All_Ancestors'Range (2) loop
-                           if All_Ancestors (Prod.LHS, J) then
-                              if not All_Ancestors (ID, J) then
-                                 Done := False;
-                                 All_Ancestors (ID, J) := True;
-                              end if;
-                           end if;
-                        end loop;
-                     end;
-                  end if;
-               end loop;
-            end loop;
-         end loop;
-      end return;
-   end Ancestors;
-
-   function Descendants
-     (Ancestors : in Token_Array_Token_Set)
-     return Token_Sequence_Arrays.Vector
-   is
-      subtype Nonterminals is Token_ID range Ancestors'First (2) .. 
Ancestors'Last (2);
-   begin
-      return Result : Token_Sequence_Arrays.Vector do
-         Result.Set_First_Last (Ancestors'First (2), Ancestors'Last (2));
-         for I in Ancestors'Range (1) loop
-            for J in Ancestors'Range (2) loop
-               if I in Nonterminals and Ancestors (I, J) then
-                  Result (J).Append (I);
-               end if;
-            end loop;
-         end loop;
-      end return;
-   end Descendants;
-
    ----------
    --  Indented text output
 
diff --git a/wisitoken-generate.ads b/wisitoken-generate.ads
index 28636d4..01340eb 100644
--- a/wisitoken-generate.ads
+++ b/wisitoken-generate.ads
@@ -12,7 +12,7 @@
 --
 --  See wisitoken.ads
 --
---  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
@@ -93,21 +93,6 @@ package WisiToken.Generate is
    --  tokens that can follow it. Implements algorithm FOLLOW from
    --  [dragon] pg 189.
 
-   function Ancestors
-     (Grammar    : in WisiToken.Productions.Prod_Arrays.Vector;
-      Descriptor : in WisiToken.Descriptor)
-     return Token_Array_Token_Set;
-   --  For each terminal and nonterm, record the nonterms it reduces to
-   --  via one token reductions, recursively. In other words, if there is
-   --  a production J <= I, then Ancestors (I, J) is True.
-
-   function Descendants
-     (Ancestors : in Token_Array_Token_Set)
-     return Token_Sequence_Arrays.Vector;
-   --  Inverse of Ancestors, excluding terminals. If there is a
-   --  production J <= I and I is a nonterminal, then I is present in
-   --  Descendants (J).
-
    ----------
    --  Indented text output. Mostly used for code generation in wisi,
    --  also used in outputing the parse_table and other debug stuff.
diff --git a/wisitoken-lexer-re2c.adb b/wisitoken-lexer-re2c.adb
index 5eae70d..10bbd16 100644
--- a/wisitoken-lexer-re2c.adb
+++ b/wisitoken-lexer-re2c.adb
@@ -2,7 +2,7 @@
 --
 --  see spec.
 --
---  Copyright (C) 2017, 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2017 - 2019 Free Software Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -47,22 +47,29 @@ package body WisiToken.Lexer.re2c is
    type Instance_Access is access Instance; --  silence compiler warning
 
    function New_Lexer
-     (Trace  : not null access WisiToken.Trace'Class)
+     (Descriptor  : not null access constant WisiToken.Descriptor)
      return Handle
    is begin
-      return Handle (Instance_Access'(new Instance (Trace)));
+      return Handle (Instance_Access'(new Instance (Descriptor)));
    end New_Lexer;
 
-   overriding procedure Reset_With_String (Lexer : in out Instance; Input : in 
String)
+   overriding procedure Reset_With_String
+     (Lexer      : in out Instance;
+      Input      : in     String;
+      Begin_Char : in     Buffer_Pos       := Buffer_Pos'First;
+      Begin_Line : in     Line_Number_Type := Line_Number_Type'First)
    is begin
       Finalize (Lexer);
 
       --  We assume Input is in UTF-8 encoding
       Lexer.Source :=
-        (Label       => String_Label,
-         File_Name   => +"",
-         Buffer      => new String'(Input),
-         User_Buffer => False);
+        (Label                     => String_Label,
+         File_Name                 => +"",
+         Buffer_Nominal_First_Byte => Base_Buffer_Pos (Input'First),
+         Buffer_Nominal_First_Char => Begin_Char,
+         Line_Nominal_First        => Begin_Line,
+         Buffer                    => new String'(Input),
+         User_Buffer               => False);
 
       Lexer.Lexer := New_Lexer
         (Buffer    => Lexer.Source.Buffer.all'Address,
@@ -73,9 +80,11 @@ package body WisiToken.Lexer.re2c is
    end Reset_With_String;
 
    overriding procedure Reset_With_String_Access
-     (Lexer     : in out Instance;
-      Input     : in     Ada.Strings.Unbounded.String_Access;
-      File_Name : in     Ada.Strings.Unbounded.Unbounded_String)
+     (Lexer      : in out Instance;
+      Input      : access String;
+      File_Name  : in     Ada.Strings.Unbounded.Unbounded_String;
+      Begin_Char : in     Buffer_Pos       := Buffer_Pos'First;
+      Begin_Line : in     Line_Number_Type := Line_Number_Type'First)
    is begin
       Finalize (Lexer);
 
@@ -85,8 +94,11 @@ package body WisiToken.Lexer.re2c is
          File_Name   =>
            +(if Ada.Strings.Unbounded.Length (File_Name) = 0 then ""
              else Ada.Directories.Simple_Name (-File_Name)),
-         Buffer      => Input,
-         User_Buffer => True);
+         Buffer_Nominal_First_Byte => Base_Buffer_Pos (Input'First),
+         Buffer_Nominal_First_Char => Begin_Char,
+         Line_Nominal_First        => Begin_Line,
+         Buffer                    => Input,
+         User_Buffer               => True);
 
       Lexer.Lexer := New_Lexer
         (Buffer    => Lexer.Source.Buffer.all'Address,
@@ -96,27 +108,49 @@ package body WisiToken.Lexer.re2c is
       Reset (Lexer);
    end Reset_With_String_Access;
 
-   overriding procedure Reset_With_File (Lexer : in out Instance; File_Name : 
in String)
+   overriding procedure Reset_With_File
+     (Lexer          : in out Instance;
+      File_Name      : in     String;
+      Begin_Byte_Pos : in     Buffer_Pos       := Invalid_Buffer_Pos;
+      End_Byte_Pos   : in     Buffer_Pos       := Invalid_Buffer_Pos;
+      Begin_Char     : in     Buffer_Pos       := Buffer_Pos'First;
+      Begin_Line     : in     Line_Number_Type := Line_Number_Type'First)
    is
       use GNATCOLL.Mmap;
+      Length : Buffer_Pos;
    begin
       Finalize (Lexer);
 
       --  We assume the file is in UTF-8 encoding
       Lexer.Source :=
-        (File_Label, +Ada.Directories.Simple_Name (File_Name), Open_Read 
(File_Name), Invalid_Mapped_Region, 1);
+        (File_Label, +Ada.Directories.Simple_Name (File_Name),
+         Buffer_Nominal_First_Byte => Buffer_Pos'First, -- overwritten below,
+         Buffer_Nominal_First_Char => Begin_Char,
+         Line_Nominal_First        => Line_Number_Type'First, -- overwritten 
below
+         File                      => Open_Read (File_Name),
+         Region                    => Invalid_Mapped_Region,
+         Buffer_Last               => 1);
+
+      if Begin_Byte_Pos = Invalid_Buffer_Pos then
+         Lexer.Source.Region := Read (Lexer.Source.File);
+         Length              := Buffer_Pos (Last (Lexer.Source.Region));
+      else
+         Length := End_Byte_Pos - Begin_Byte_Pos + 1;
+
+         Lexer.Source.Buffer_Nominal_First_Byte := Begin_Byte_Pos;
+         Lexer.Source.Line_Nominal_First        := Begin_Line;
+
+         Lexer.Source.Region := Read
+           (Lexer.Source.File,
+            Offset => File_Size (Begin_Byte_Pos - 1), -- Offset is 0 indexed, 
Begin_Byte_Pos is 1 indexed
+            Length => File_Size (Length));
+      end if;
 
-      Lexer.Source.Region      := Read (Lexer.Source.File);
       Lexer.Source.Buffer_Last := Last (Lexer.Source.Region);
 
-      if Integer (Length (Lexer.Source.File)) /= Lexer.Source.Buffer_Last then
-         raise SAL.Programmer_Error with "not all of file is mapped; file 
length" &
-           File_Size'Image (Length (Lexer.Source.File)) & " mapped:" & 
Integer'Image (Lexer.Source.Buffer_Last);
-      end if;
-
       Lexer.Lexer := New_Lexer
         (Buffer    => Data (Lexer.Source.Region).all'Address,
-         Length    => Interfaces.C.size_t (Last (Lexer.Source.Region)),
+         Length    => Interfaces.C.size_t (Length),
          Verbosity => Interfaces.C.int (if Trace_Parse > 3 then Trace_Parse - 
3 else 0));
 
       Reset (Lexer);
@@ -129,9 +163,10 @@ package body WisiToken.Lexer.re2c is
       Lexer.Char_Line_Start := 1;
       Lexer.ID :=
         --  First token is assumed to be first on a line.
-        (if Lexer.Trace.Descriptor.New_Line_ID = Invalid_Token_ID
+        (if Lexer.Descriptor.New_Line_ID = Invalid_Token_ID
          then Invalid_Token_ID
-         else Lexer.Trace.Descriptor.New_Line_ID);
+         else Lexer.Descriptor.New_Line_ID);
+      Lexer.Prev_ID := Invalid_Token_ID;
    end Reset;
 
    overriding function Find_Next
@@ -147,20 +182,33 @@ package body WisiToken.Lexer.re2c is
            (ID => Lexer.ID,
 
             Byte_Region =>
-              (Buffer_Pos (Lexer.Byte_Position),
-               Base_Buffer_Pos (Lexer.Byte_Position + Lexer.Byte_Length - 1)),
-
-            Line => Lexer.Line,
+              (if Lexer.ID = Lexer.Descriptor.EOI_ID and then 
Lexer.Byte_Position = Integer (Base_Buffer_Pos'First)
+               then
+                  --  EOF in empty buffer
+                 (Lexer.Source.Buffer_Nominal_First_Byte,
+                  Lexer.Source.Buffer_Nominal_First_Byte - 1)
+               else
+                 (Base_Buffer_Pos (Lexer.Byte_Position) + 
Lexer.Source.Buffer_Nominal_First_Byte - Buffer_Pos'First,
+                  Base_Buffer_Pos (Lexer.Byte_Position + Lexer.Byte_Length - 
1) +
+                    Lexer.Source.Buffer_Nominal_First_Byte - 
Buffer_Pos'First)),
+
+            Line => Lexer.Line + Lexer.Source.Line_Nominal_First - 
Line_Number_Type'First,
 
             Column =>
-              (if Lexer.ID = Lexer.Trace.Descriptor.New_Line_ID or
-                 Lexer.ID = Lexer.Trace.Descriptor.EOF_ID
+              (if Lexer.ID = Lexer.Descriptor.New_Line_ID or
+                 Lexer.ID = Lexer.Descriptor.EOI_ID
                then 0
                else Ada.Text_IO.Count (Lexer.Char_Position - 
Lexer.Char_Line_Start)),
 
             Char_Region =>
-              (Buffer_Pos (Lexer.Char_Position),
-               Base_Buffer_Pos (Lexer.Char_Position + Lexer.Char_Length - 1)));
+              (if Lexer.ID = Lexer.Descriptor.EOI_ID and then 
Lexer.Byte_Position = Integer (Base_Buffer_Pos'First)
+               then
+                  --  EOF in empty buffer
+                 (Lexer.Source.Buffer_Nominal_First_Byte,
+                  Lexer.Source.Buffer_Nominal_First_Byte - 1)
+               else
+                 (To_Char_Pos (Lexer.Source, Lexer.Char_Position),
+                  To_Char_Pos (Lexer.Source, Lexer.Char_Position + 
Lexer.Char_Length - 1))));
       end Build_Token;
 
    begin
@@ -177,7 +225,7 @@ package body WisiToken.Lexer.re2c is
          begin
             case Status is
             when 0 =>
-               if Lexer.ID = Lexer.Trace.Descriptor.New_Line_ID then
+               if Lexer.ID = Lexer.Descriptor.New_Line_ID then
                   Lexer.Char_Line_Start := Lexer.Char_Position + 1;
                end if;
 
@@ -191,13 +239,20 @@ package body WisiToken.Lexer.re2c is
                declare
                   Buffer : constant GNATCOLL.Mmap.Str_Access := 
WisiToken.Lexer.Buffer (Lexer.Source);
                begin
+                  if Trace_Parse > Lexer_Debug then
+                     --  We don't have a visible Trace object here.
+                     Ada.Text_IO.Put_Line ("lexer error char " & Buffer 
(Lexer.Byte_Position));
+                  end if;
+
                   if Buffer (Lexer.Byte_Position) = ''' then
                      --  Lexer has read to next new-line (or eof), then 
backtracked to next
                      --  char after '.
                      Lexer.Errors.Append
-                       ((Buffer_Pos (Lexer.Char_Position), 
Invalid_Token_Index, (1 => ''', others => ASCII.NUL)));
+                       ((To_Char_Pos (Lexer.Source, Lexer.Char_Position),
+                         Invalid_Token_Index,
+                         (1 => ''', others => ASCII.NUL)));
 
-                     Lexer.ID := Lexer.Trace.Descriptor.String_1_ID;
+                     Lexer.ID := Lexer.Descriptor.String_1_ID;
                      Build_Token;
                      return True;
 
@@ -205,16 +260,18 @@ package body WisiToken.Lexer.re2c is
                      --  Lexer has read to next new-line (or eof), then 
backtracked to next
                      --  char after ".
                      Lexer.Errors.Append
-                       ((Buffer_Pos (Lexer.Char_Position), 
Invalid_Token_Index, (1 => '"', others => ASCII.NUL)));
+                       ((To_Char_Pos (Lexer.Source, Lexer.Char_Position),
+                         Invalid_Token_Index,
+                         (1 => '"', others => ASCII.NUL)));
 
-                     Lexer.ID := Lexer.Trace.Descriptor.String_2_ID;
+                     Lexer.ID := Lexer.Descriptor.String_2_ID;
                      Build_Token;
                      return True;
 
                   else
                      --  Just skip the character; call Next_Token again.
                      Lexer.Errors.Append
-                       ((Buffer_Pos (Lexer.Char_Position), 
Invalid_Token_Index, (others => ASCII.NUL)));
+                       ((To_Char_Pos (Lexer.Source, Lexer.Char_Position), 
Invalid_Token_Index, (others => ASCII.NUL)));
                   end if;
                end;
 
@@ -227,13 +284,18 @@ package body WisiToken.Lexer.re2c is
 
    overriding function First (Lexer : in Instance) return Boolean
    is begin
-      return Lexer.Trace.Descriptor.New_Line_ID /= Invalid_Token_ID and then
-           Lexer.Prev_ID = Lexer.Trace.Descriptor.New_Line_ID;
+      return Lexer.Descriptor.New_Line_ID /= Invalid_Token_ID and then
+           Lexer.Prev_ID = Lexer.Descriptor.New_Line_ID;
    end First;
 
    overriding function Buffer_Text (Lexer : in Instance; Byte_Bounds : in 
Buffer_Region) return String
-   is begin
-      return String (Buffer (Lexer.Source) (Integer (Byte_Bounds.First) .. 
Integer (Byte_Bounds.Last)));
+   is
+      First : constant Integer := Integer
+        (Byte_Bounds.First - Lexer.Source.Buffer_Nominal_First_Byte + 
Buffer_Pos'First);
+      Last  : constant Integer := Integer
+        (Byte_Bounds.Last - Lexer.Source.Buffer_Nominal_First_Byte + 
Buffer_Pos'First);
+   begin
+      return String (Buffer (Lexer.Source) (First .. Last));
    end Buffer_Text;
 
    overriding function File_Name (Lexer : in Instance) return String
diff --git a/wisitoken-lexer-re2c.ads b/wisitoken-lexer-re2c.ads
index 0472908..b58c4ea 100644
--- a/wisitoken-lexer-re2c.ads
+++ b/wisitoken-lexer-re2c.ads
@@ -6,7 +6,7 @@
 --
 --  [1] http://re2c.org/
 --
---  Copyright (C) 2017, 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2017 - 2019 Free Software Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -39,6 +39,9 @@ generic
      return System.Address;
    --  Create the re2c lexer object, passing it the full text to process.
    --  Length is buffer length in 8 bit bytes.
+   --
+   --  The C lexer does not know about Buffer_Nominal_First,
+   --  Line_Nominal_First; its buffer positions and lines start at 1.
 
    with procedure Free_Lexer (Lexer : in out System.Address);
    --  Destruct the re2c lexer object
@@ -75,20 +78,32 @@ package WisiToken.Lexer.re2c is
    overriding procedure Finalize (Object : in out Instance);
 
    function New_Lexer
-     (Trace  : not null access WisiToken.Trace'Class)
+     (Descriptor  : not null access constant WisiToken.Descriptor)
      return WisiToken.Lexer.Handle;
    --  If the tokens do not include a reporting New_Line token, set
    --  New_Line_ID to Invalid_Token_ID.
 
-   overriding procedure Reset_With_String (Lexer : in out Instance; Input : in 
String);
+   overriding procedure Reset_With_String
+     (Lexer      : in out Instance;
+      Input      : in     String;
+      Begin_Char : in     Buffer_Pos       := Buffer_Pos'First;
+      Begin_Line : in     Line_Number_Type := Line_Number_Type'First);
    --  Copies Input to internal buffer.
 
    overriding procedure Reset_With_String_Access
-     (Lexer     : in out Instance;
-      Input     : in     Ada.Strings.Unbounded.String_Access;
-      File_Name : in     Ada.Strings.Unbounded.Unbounded_String);
-
-   overriding procedure Reset_With_File (Lexer : in out Instance; File_Name : 
in String);
+     (Lexer      : in out Instance;
+      Input      : access String;
+      File_Name  : in     Ada.Strings.Unbounded.Unbounded_String;
+      Begin_Char : in     Buffer_Pos       := Buffer_Pos'First;
+      Begin_Line : in     Line_Number_Type := Line_Number_Type'First);
+
+   overriding procedure Reset_With_File
+     (Lexer          : in out Instance;
+      File_Name      : in     String;
+      Begin_Byte_Pos : in     Buffer_Pos       := Invalid_Buffer_Pos;
+      End_Byte_Pos   : in     Buffer_Pos       := Invalid_Buffer_Pos;
+      Begin_Char     : in     Buffer_Pos       := Buffer_Pos'First;
+      Begin_Line     : in     Line_Number_Type := Line_Number_Type'First);
    --  Uses memory mapped file; no copies.
 
    overriding procedure Discard_Rest_Of_Input (Lexer : in out Instance) is 
null;
@@ -114,7 +129,7 @@ private
       Lexer         : System.Address := System.Null_Address;
       Source        : WisiToken.Lexer.Source;
       ID            : Token_ID; --  Last token read by find_next
-      Byte_Position : Natural; -- We don't use Buffer_Pos here, because 
Source.Buffer is indexed by Integer
+      Byte_Position : Natural;  --  We don't use Buffer_Pos here, because 
Source.Buffer is indexed by Integer
       Byte_Length   : Natural;
       Char_Position : Natural;
       Char_Length   : Natural;
@@ -122,7 +137,7 @@ private
       --  start of Managed.Buffer, 1 indexed.
 
       Line            : Line_Number_Type; -- after last (or current) New_Line 
token
-      Char_Line_Start : Natural;          -- Character position after last 
New_Line token
+      Char_Line_Start : Natural;          -- Character position after last 
New_Line token, lexer origin.
       Prev_ID         : Token_ID;         -- previous token_id
    end record;
 
diff --git a/wisitoken-lexer-regexp.adb b/wisitoken-lexer-regexp.adb
index 8eb2242..1790b74 100644
--- a/wisitoken-lexer-regexp.adb
+++ b/wisitoken-lexer-regexp.adb
@@ -2,7 +2,7 @@
 --
 --  See spec
 --
---  Copyright (C) 2015, 2017, 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2015, 2017 - 2019 Free Software Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -50,7 +50,7 @@ package body WisiToken.Lexer.Regexp is
       --  We only support Reset_With_String.
 
       if Current_Char > Lexer.Source.Buffer'Last then
-         Lexer.ID := Lexer.Trace.Descriptor.EOF_ID;
+         Lexer.ID := Lexer.Descriptor.EOI_ID;
          Lexer.Lexeme_Head := Lexer.Buffer_Head;
          Lexer.Lexeme_Tail := Lexer.Buffer_Head - 1;
          return True;
@@ -116,7 +116,7 @@ package body WisiToken.Lexer.Regexp is
          return True;
 
       elsif Current_Char = Lexer.Source.Buffer'Last then
-         Lexer.ID := Lexer.Trace.Descriptor.EOF_ID;
+         Lexer.ID := Lexer.Descriptor.EOI_ID;
          Lexer.Buffer_Head := Lexer.Buffer_Head + 1;
          return True;
 
@@ -141,11 +141,11 @@ package body WisiToken.Lexer.Regexp is
    type Instance_Access is access Instance; --  silence compiler warning
 
    function New_Lexer
-     (Trace  : not null access WisiToken.Trace'Class;
-      Syntax : in              WisiToken.Lexer.Regexp.Syntax)
+     (Descriptor : not null access constant WisiToken.Descriptor;
+      Syntax     : in              WisiToken.Lexer.Regexp.Syntax)
      return WisiToken.Lexer.Handle
    is
-      New_Lexer : constant Instance_Access := new Instance (Trace, 
Syntax'Last);
+      New_Lexer : constant Instance_Access := new Instance (Descriptor, 
Syntax'Last);
    begin
       New_Lexer.Syntax := Syntax;
 
@@ -157,38 +157,56 @@ package body WisiToken.Lexer.Regexp is
       Finalize (Object.Source);
    end Finalize;
 
-   overriding procedure Reset_With_String (Lexer : in out Instance; Input : in 
String)
+   overriding procedure Reset_With_String
+     (Lexer      : in out Instance;
+      Input      : in     String;
+      Begin_Char : in     Buffer_Pos       := Buffer_Pos'First;
+      Begin_Line : in     Line_Number_Type := Line_Number_Type'First)
    is begin
       Finalize (Lexer);
 
       Lexer.Source :=
-        (Label       => String_Label,
-         File_Name   => +"",
-         Buffer      => new String'(Input),
-         User_Buffer => False);
+        (Label                     => String_Label,
+         File_Name                 => +"",
+         Buffer_Nominal_First_Byte => Base_Buffer_Pos (Input'First),
+         Buffer_Nominal_First_Char => Begin_Char,
+         Line_Nominal_First        => Begin_Line,
+         Buffer                    => new String'(Input),
+         User_Buffer               => False);
 
       Reset (Lexer);
    end Reset_With_String;
 
    overriding procedure Reset_With_String_Access
      (Lexer     : in out Instance;
-      Input     : in     Ada.Strings.Unbounded.String_Access;
-      File_Name : in     Ada.Strings.Unbounded.Unbounded_String)
+      Input     : access String;
+      File_Name : in     Ada.Strings.Unbounded.Unbounded_String;
+      Begin_Char : in     Buffer_Pos       := Buffer_Pos'First;
+      Begin_Line : in     Line_Number_Type := Line_Number_Type'First)
    is begin
       Finalize (Lexer);
 
       Lexer.Source :=
         (Label       => String_Label,
          File_Name   => File_Name,
+         Buffer_Nominal_First_Byte => Base_Buffer_Pos (Input'First),
+         Buffer_Nominal_First_Char => Begin_Char,
+         Line_Nominal_First        => Begin_Line,
          Buffer      => Input,
          User_Buffer => True);
 
       Reset (Lexer);
    end Reset_With_String_Access;
 
-   overriding procedure Reset_With_File (Lexer : in out Instance; File_Name : 
in String)
+   overriding procedure Reset_With_File
+     (Lexer          : in out Instance;
+      File_Name      : in     String;
+      Begin_Byte_Pos : in     Buffer_Pos       := Invalid_Buffer_Pos;
+      End_Byte_Pos   : in     Buffer_Pos       := Invalid_Buffer_Pos;
+      Begin_Char     : in     Buffer_Pos       := Buffer_Pos'First;
+      Begin_Line     : in     Line_Number_Type := Line_Number_Type'First)
    is
-      pragma Unreferenced (File_Name);
+      pragma Unreferenced (File_Name, Begin_Byte_Pos, End_Byte_Pos, 
Begin_Char, Begin_Line);
    begin
       Finalize (Lexer);
 
diff --git a/wisitoken-lexer-regexp.ads b/wisitoken-lexer-regexp.ads
index 8a94560..3a3d5f4 100644
--- a/wisitoken-lexer-regexp.ads
+++ b/wisitoken-lexer-regexp.ads
@@ -6,7 +6,7 @@
 --  used in most of the WisiToken unit tests. Since it uses regexp, it
 --  is easy to convert to an Aflex lexer.
 --
---  Copyright (C) 2015, 2017, 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2015, 2017 - 2019 Free Software Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -50,22 +50,34 @@ package WisiToken.Lexer.Regexp is
    type Syntax is array (Token_ID range <>) of Syntax_Item;
 
    type Instance
-     (Trace         : not null access WisiToken.Trace'Class;
+     (Descriptor    : not null access constant WisiToken.Descriptor;
       Last_Terminal : Token_ID)
      is new WisiToken.Lexer.Instance with private;
 
    function New_Lexer
-     (Trace  : not null access WisiToken.Trace'Class;
-      Syntax : in              WisiToken.Lexer.Regexp.Syntax)
+     (Descriptor : not null access constant WisiToken.Descriptor;
+      Syntax     : in              WisiToken.Lexer.Regexp.Syntax)
      return WisiToken.Lexer.Handle;
 
    overriding procedure Finalize (Object : in out Instance);
-   overriding procedure Reset_With_String (Lexer : in out Instance; Input : in 
String);
+   overriding procedure Reset_With_String
+     (Lexer      : in out Instance;
+      Input      : in     String;
+      Begin_Char : in     Buffer_Pos       := Buffer_Pos'First;
+      Begin_Line : in     Line_Number_Type := Line_Number_Type'First);
    overriding procedure Reset_With_String_Access
-     (Lexer     : in out Instance;
-      Input     : in     Ada.Strings.Unbounded.String_Access;
-      File_Name : in     Ada.Strings.Unbounded.Unbounded_String);
-   overriding procedure Reset_With_File (Lexer : in out Instance; File_Name : 
in String);
+     (Lexer      : in out Instance;
+      Input      : access String;
+      File_Name  : in     Ada.Strings.Unbounded.Unbounded_String;
+      Begin_Char : in     Buffer_Pos       := Buffer_Pos'First;
+      Begin_Line : in     Line_Number_Type := Line_Number_Type'First);
+   overriding procedure Reset_With_File
+     (Lexer          : in out Instance;
+      File_Name      : in     String;
+      Begin_Byte_Pos : in     Buffer_Pos       := Invalid_Buffer_Pos;
+      End_Byte_Pos   : in     Buffer_Pos       := Invalid_Buffer_Pos;
+      Begin_Char     : in     Buffer_Pos       := Buffer_Pos'First;
+      Begin_Line     : in     Line_Number_Type := Line_Number_Type'First);
    overriding procedure Reset (Lexer : in out Instance);
 
    overriding procedure Discard_Rest_Of_Input (Lexer : in out Instance) is 
null;
@@ -87,9 +99,9 @@ private
    procedure Free is new Ada.Unchecked_Deallocation (String, String_Access);
 
    type Instance
-     (Trace         : not null access WisiToken.Trace'Class;
+     (Descriptor    : not null access constant WisiToken.Descriptor;
       Last_Terminal : Token_ID)
-     is new WisiToken.Lexer.Instance (Trace => Trace) with
+     is new WisiToken.Lexer.Instance (Descriptor => Descriptor) with
    record
       ID          : Token_ID; --  last token read by find_next
       Syntax      : WisiToken.Lexer.Regexp.Syntax (Token_ID'First .. 
Last_Terminal);
diff --git a/wisitoken-lexer.adb b/wisitoken-lexer.adb
index 8ceff80..f73d517 100644
--- a/wisitoken-lexer.adb
+++ b/wisitoken-lexer.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
@@ -53,4 +53,9 @@ package body WisiToken.Lexer is
       return -Source.File_Name;
    end File_Name;
 
+   function To_Char_Pos (Source : in Lexer.Source; Lexer_Char_Pos : in 
Integer) return Base_Buffer_Pos
+   is begin
+      return Base_Buffer_Pos (Lexer_Char_Pos) + 
Source.Buffer_Nominal_First_Char - Buffer_Pos'First;
+   end To_Char_Pos;
+
 end WisiToken.Lexer;
diff --git a/wisitoken-lexer.ads b/wisitoken-lexer.ads
index 0e9c6a1..7be10ed 100644
--- a/wisitoken-lexer.ads
+++ b/wisitoken-lexer.ads
@@ -2,7 +2,7 @@
 --
 --  An abstract lexer interface.
 --
---  Copyright (C) 2014 - 2015, 2017, 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2014 - 2015, 2017 - 2019 Free Software Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -51,7 +51,7 @@ package WisiToken.Lexer is
 
    package Error_Lists is new Ada.Containers.Doubly_Linked_Lists (Error);
 
-   type Instance (Trace  : not null access WisiToken.Trace'Class)
+   type Instance (Descriptor  : not null access constant WisiToken.Descriptor)
    is abstract new Ada.Finalization.Limited_Controlled with record
       Errors : Error_Lists.List;
    end record;
@@ -60,19 +60,35 @@ package WisiToken.Lexer is
 
    type Handle is access all Class;
 
-   procedure Reset_With_String (Lexer : in out Instance; Input : in String) is 
abstract;
+   procedure Reset_With_String
+     (Lexer      : in out Instance;
+      Input      : in     String;
+      Begin_Char : in     Buffer_Pos       := Buffer_Pos'First;
+      Begin_Line : in     Line_Number_Type := Line_Number_Type'First)
+     is abstract;
    --  Reset Lexer to start a new parse, reading from Input.
 
    procedure Reset_With_String_Access
-     (Lexer     : in out Instance;
-      Input     : in     Ada.Strings.Unbounded.String_Access;
-      File_Name : in     Ada.Strings.Unbounded.Unbounded_String)
+     (Lexer      : in out Instance;
+      Input      : access String;
+      File_Name  : in     Ada.Strings.Unbounded.Unbounded_String;
+      Begin_Char : in     Buffer_Pos       := Buffer_Pos'First;
+      Begin_Line : in     Line_Number_Type := Line_Number_Type'First)
      is abstract;
-   --  Reset Lexer to start a new parse, reading from Input.
-   --  File_Name is used for error messages.
-
-   procedure Reset_With_File (Lexer : in out Instance; File_Name : in String) 
is abstract;
-   --  Reset Lexer to start a new parse, reading from File_Name.
+   --  Reset Lexer to start a new parse, reading from Input. File_Name is
+   --  used for error messages.
+
+   procedure Reset_With_File
+     (Lexer      : in out Instance;
+      File_Name  : in     String;
+      Begin_Pos  : in     Buffer_Pos       := Invalid_Buffer_Pos;
+      End_Pos    : in     Buffer_Pos       := Invalid_Buffer_Pos;
+      Begin_Char : in     Buffer_Pos       := Buffer_Pos'First;
+      Begin_Line : in     Line_Number_Type := Line_Number_Type'First)
+     is abstract;
+   --  Reset Lexer to start a new parse, reading from File_Name. If
+   --  Begin_Pos, End_Pos /= Invalid_Buffer_Pos, only parse that portion
+   --  of the file.
    --
    --  Raises Ada.IO_Exceptions.Name_Error if File_Name cannot be opened.
 
@@ -117,7 +133,7 @@ package WisiToken.Lexer is
    --  If the underlying text feeder does not support the notion of
    --  'line', returns Invalid_Line_Number.
    --
-   --  Token.Col is the column number of the start of the token, 1
+   --  Token.Column is the column number of the start of the token, 1
    --  indexed. If the underlying text feeder does not support the notion
    --  of 'line', returns byte position in internal buffer.
 
@@ -132,19 +148,28 @@ private
       File_Name : Ada.Strings.Unbounded.Unbounded_String;
       --  Not saved in Mapped_File, may be empty for String_Label
 
+      Buffer_Nominal_First_Byte : Buffer_Pos;
+      Buffer_Nominal_First_Char : Buffer_Pos;
+      Line_Nominal_First        : Line_Number_Type;
+
       case Label is
       when String_Label =>
-         Buffer      : Ada.Strings.Unbounded.String_Access;
+         Buffer      : access String;
          User_Buffer : Boolean := False;
          --  If User_Buffer is True, user provided buffer and will deallocate
          --  it. Otherwise we must deallocate it.
 
+         --  Buffer_Nominal_First, Line_Nominal_First are 1.
       when File_Label =>
 
          --  The input is memory mapped from the following, which must be 
closed:
          File        : GNATCOLL.Mmap.Mapped_File;
          Region      : GNATCOLL.Mmap.Mapped_Region;
          Buffer_Last : Positive;
+         --  Region always has first character at offset 0.
+
+         --  Buffer_Nominal_First is Begin_Pos. Line_Nominal_First is
+         --  Begin_Line.
       end case;
    end record;
 
@@ -157,5 +182,6 @@ private
    --  Source.Buffer_Last. Indexing is reliable.
 
    function File_Name (Source : in Lexer.Source) return String;
+   function To_Char_Pos (Source : in Lexer.Source; Lexer_Char_Pos : in 
Integer) return Base_Buffer_Pos;
 
 end WisiToken.Lexer;
diff --git a/wisitoken-parse-lr-mckenzie_recover-base.ads 
b/wisitoken-parse-lr-mckenzie_recover-base.ads
index e7592d4..c5d78ea 100644
--- a/wisitoken-parse-lr-mckenzie_recover-base.ads
+++ b/wisitoken-parse-lr-mckenzie_recover-base.ads
@@ -2,7 +2,7 @@
 --
 --  Base utilities for McKenzie_Recover
 --
---  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
@@ -18,6 +18,7 @@
 pragma License (Modified_GPL);
 
 with Ada.Exceptions;
+with WisiToken.Parse.LR.Parser;
 with WisiToken.Parse.LR.Parser_Lists;
 private package WisiToken.Parse.LR.McKenzie_Recover.Base is
 
@@ -165,7 +166,7 @@ private package WisiToken.Parse.LR.McKenzie_Recover.Base is
    --  There is only one object of this type, declared in Recover. It
    --  provides appropriate access to Shared_Parser components.
    --
-   --  Since all the accesible objects are read-only (except Trace),
+   --  Since all the accessible objects are read-only (except Trace),
    --  there are no protected operations, and this is not a protected
    --  type.
 
diff --git a/wisitoken-parse-lr-mckenzie_recover-explore.adb 
b/wisitoken-parse-lr-mckenzie_recover-explore.adb
index f47ed68..56bd3fe 100644
--- a/wisitoken-parse-lr-mckenzie_recover-explore.adb
+++ b/wisitoken-parse-lr-mckenzie_recover-explore.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  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
@@ -18,6 +18,7 @@
 pragma License (Modified_GPL);
 
 with WisiToken.Parse.LR.McKenzie_Recover.Parse;
+with WisiToken.Parse.LR.Parser;
 package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
 
    procedure Do_Shift
@@ -28,13 +29,16 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
       Config            : in out          Configuration;
       State             : in              State_Index;
       ID                : in              Token_ID;
-      Cost_Delta        : in              Integer)
+      Cost_Delta        : in              Integer;
+      Strategy          : in              Strategies)
    is
       use all type SAL.Base_Peek_Type;
       McKenzie_Param : McKenzie_Param_Type renames Shared.Table.McKenzie_Param;
 
       Op : constant Config_Op := (Insert, ID, Config.Current_Shared_Token);
    begin
+      Config.Strategy_Counts (Strategy) := Config.Strategy_Counts (Strategy) + 
1;
+
       begin
          if Config.Current_Ops = No_Insert_Delete then
             Config.Ops.Append (Op);
@@ -60,6 +64,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
       end if;
 
       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 Trace_McKenzie > Detail then
@@ -69,25 +74,26 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
       Local_Config_Heap.Add (Config);
    end Do_Shift;
 
-   function Do_Reduce_1
-     (Super             : not null access Base.Supervisor;
+   procedure Do_Reduce_1
+     (Label             : in              String;
+      Super             : not null access Base.Supervisor;
       Shared            : not null access Base.Shared;
       Parser_Index      : in              SAL.Peek_Type;
       Local_Config_Heap : in out          Config_Heaps.Heap_Type;
       Config            : in out          Configuration;
-      Action            : in              Reduce_Action_Rec)
-     return Non_Success_Status
+      Action            : in              Reduce_Action_Rec;
+      Do_Language_Fixes : in              Boolean := True)
    is
       use all type SAL.Base_Peek_Type;
-      --  Perform Action on Config, setting Config.Check_Status. If that is
-      --  not Ok, call Language_Fixes (which may enqueue configs),
-      --  return Abandon. Otherwise return Continue.
       use all type Semantic_Checks.Check_Status_Label;
       use all type WisiToken.Parse.LR.Parser.Language_Fixes_Access;
 
-      Table     : Parse_Table renames Shared.Table.all;
-      Nonterm   : Recover_Token;
-      New_State : Unknown_State_Index;
+      Prev_State : constant Unknown_State_Index := Config.Stack.Peek.State;
+
+      Descriptor : WisiToken.Descriptor renames Super.Trace.Descriptor.all;
+      Table      : Parse_Table renames Shared.Table.all;
+      Nonterm    : Recover_Token;
+      New_State  : Unknown_State_Index;
    begin
       Config.Check_Status := Parse.Reduce_Stack (Shared, Config.Stack, Action, 
Nonterm, Default_Virtual => True);
       case Config.Check_Status.Label is
@@ -98,11 +104,13 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
          Config.Error_Token       := Nonterm;
          Config.Check_Token_Count := Action.Token_Count;
 
-         if Shared.Language_Fixes /= null then
-            Shared.Language_Fixes
-              (Super.Trace.all, Shared.Lexer, Super.Label (Parser_Index), 
Shared.Table.all, Shared.Terminals.all,
-               Super.Parser_State (Parser_Index).Tree, Local_Config_Heap,
-               Config);
+         if Do_Language_Fixes then
+            if Shared.Language_Fixes /= null then
+               Shared.Language_Fixes
+                 (Super.Trace.all, Shared.Lexer, Super.Label (Parser_Index), 
Shared.Table.all, Shared.Terminals.all,
+                  Super.Parser_State (Parser_Index).Tree, Local_Config_Heap,
+                  Config);
+            end if;
          end if;
 
          --  Finish the reduce; ignore the check fail.
@@ -122,29 +130,36 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
       end if;
 
       Config.Stack.Push ((New_State, Syntax_Trees.Invalid_Node_Index, 
Nonterm));
-      return Continue;
+
+      if Trace_McKenzie > Extra and Label'Length > 0 then
+         Put_Line
+           (Super.Trace.all, Super.Label (Parser_Index), Label &
+              ": state" & State_Index'Image (Prev_State) & " reduce to " &
+              Image (Action.Production.LHS, Descriptor) & ", goto" &
+              State_Index'Image (New_State));
+      end if;
    end Do_Reduce_1;
 
    procedure Do_Reduce_2
-     (Super             : not null access Base.Supervisor;
+     (Label             : in              String;
+      Super             : not null access Base.Supervisor;
       Shared            : not null access Base.Shared;
       Parser_Index      : in              SAL.Peek_Type;
       Local_Config_Heap : in out          Config_Heaps.Heap_Type;
       Config            : in out          Configuration;
       Inserted_ID       : in              Token_ID;
-      Cost_Delta        : in              Integer)
+      Cost_Delta        : in              Integer;
+      Strategy          : in              Strategies)
    is
-      --  Perform reduce actions until shift Inserted_Token; if all succeed,
+      --  Perform reduce actions until shift Inserted_ID; if all succeed,
       --  add the final configuration to the heap. If a conflict is
       --  encountered, process the other action the same way. If a semantic
       --  check fails, enqueue possible solutions. For parse table error
       --  actions, or exception Bad_Config, just return.
 
       Table       : Parse_Table renames Shared.Table.all;
-      Next_Action : Parse_Action_Node_Ptr;
+      Next_Action : constant Parse_Action_Node_Ptr := Action_For (Table, 
Config.Stack (1).State, Inserted_ID);
    begin
-      Next_Action := Action_For (Table, Config.Stack (1).State, Inserted_ID);
-
       if Next_Action.Next /= null then
          --  There is a conflict; create a new config to shift or reduce.
          declare
@@ -154,15 +169,13 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
             case Action.Verb is
             when Shift =>
                Do_Shift
-                 (Super, Shared, Parser_Index, Local_Config_Heap, New_Config, 
Action.State, Inserted_ID, Cost_Delta);
+                 (Super, Shared, Parser_Index, Local_Config_Heap, New_Config, 
Action.State, Inserted_ID,
+                  Cost_Delta, Strategy);
 
             when Reduce =>
-               case Do_Reduce_1 (Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, Action) is
-               when Abandon =>
-                  null;
-               when Continue =>
-                  Do_Reduce_2 (Super, Shared, Parser_Index, Local_Config_Heap, 
New_Config, Inserted_ID, Cost_Delta);
-               end case;
+               Do_Reduce_1 (Label, Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, Action);
+               Do_Reduce_2 (Label, Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, Inserted_ID,
+                            Cost_Delta, Strategy);
 
             when Accept_It =>
                raise SAL.Programmer_Error with "found test case for Do_Reduce 
Accept_It conflict";
@@ -178,15 +191,12 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
       case Next_Action.Item.Verb is
       when Shift =>
          Do_Shift
-           (Super, Shared, Parser_Index, Local_Config_Heap, Config, 
Next_Action.Item.State, Inserted_ID, Cost_Delta);
+           (Super, Shared, Parser_Index, Local_Config_Heap, Config, 
Next_Action.Item.State, Inserted_ID,
+            Cost_Delta, Strategy);
 
       when Reduce =>
-         case Do_Reduce_1 (Super, Shared, Parser_Index, Local_Config_Heap, 
Config, Next_Action.Item) is
-         when Abandon =>
-            null;
-         when Continue =>
-            Do_Reduce_2 (Super, Shared, Parser_Index, Local_Config_Heap, 
Config, Inserted_ID, Cost_Delta);
-         end case;
+         Do_Reduce_1 (Label, Super, Shared, Parser_Index, Local_Config_Heap, 
Config, Next_Action.Item);
+         Do_Reduce_2 (Label, Super, Shared, Parser_Index, Local_Config_Heap, 
Config, Inserted_ID, Cost_Delta, Strategy);
 
       when Accept_It =>
          raise SAL.Programmer_Error with "found test case for Do_Reduce 
Accept_It";
@@ -416,6 +426,46 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
       return Abandon;
    end Check;
 
+   function Check_Reduce_To_Start
+     (Super        : not null access Base.Supervisor;
+      Shared       : not null access Base.Shared;
+      Parser_Index : in              SAL.Base_Peek_Type;
+      Orig_Config  : in              Configuration)
+     return Boolean
+      --  Returns True if Config reduces to the start nonterm.
+   is
+      Table : Parse_Table renames Shared.Table.all;
+
+      function To_Reduce_Action (Item : in Minimal_Action) return 
Reduce_Action_Rec
+      is begin
+         return (Reduce, (Item.Nonterm, 0), null, null, Item.Token_Count);
+      end To_Reduce_Action;
+
+      Local_Config_Heap : Config_Heaps.Heap_Type; -- never used, because 
Do_Language_Fixes is False.
+
+      Config : Configuration  := Orig_Config;
+      Action : Minimal_Action := Table.States 
(Config.Stack.Peek.State).Minimal_Complete_Action;
+   begin
+      loop
+         case Action.Verb is
+         when Pause =>
+            return True;
+
+         when Shift =>
+            return False;
+
+         when Reduce =>
+            Do_Reduce_1
+              ("", Super, Shared, Parser_Index, Local_Config_Heap, Config,
+               To_Reduce_Action (Action),
+               Do_Language_Fixes => False);
+
+            Action := Table.States 
(Config.Stack.Peek.State).Minimal_Complete_Action;
+         end case;
+         --  loop only exits via returns above
+      end loop;
+   end Check_Reduce_To_Start;
+
    procedure Try_Push_Back
      (Super             : not null access Base.Supervisor;
       Shared            : not null access Base.Shared;
@@ -482,7 +532,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
       use all type Ada.Containers.Count_Type;
 
       Table  : Parse_Table renames Shared.Table.all;
-      EOF_ID : Token_ID renames Super.Trace.Descriptor.EOF_ID;
+      EOF_ID : Token_ID renames Super.Trace.Descriptor.EOI_ID;
 
       --  Find terminal insertions from the current state's action_list to try.
       --
@@ -495,7 +545,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
 
       Cached_Config : Configuration;
       Cached_Action : Reduce_Action_Rec;
-      Cached_Status : Non_Success_Status;
       --  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.
@@ -509,8 +558,10 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
          begin
             if ID /= EOF_ID and then --  can't insert eof
               ID /= Invalid_Token_ID and then -- invalid when Verb = Error
-              (Config.Ops.Length = 0 or else -- don't insert an id we just 
pushed back; we know that failed.
-                 Config.Ops (Config.Ops.Last_Index) /= (Push_Back, ID, 
Config.Current_Shared_Token))
+              (Config.Ops.Length = 0 or else
+                 --  Don't insert an ID we just pushed back or deleted; we 
know that failed.
+                 (Config.Ops (Config.Ops.Last_Index) /= (Push_Back, ID, 
Config.Current_Shared_Token) and
+                    Config.Ops (Config.Ops.Last_Index) /= (Delete, ID, 
Config.Current_Shared_Token)))
             then
                case Action.Verb is
                when Shift =>
@@ -521,7 +572,9 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
                      New_Config.Check_Status   := (Label => 
WisiToken.Semantic_Checks.Ok);
 
                      Do_Shift
-                       (Super, Shared, Parser_Index, Local_Config_Heap, 
New_Config, Action.State, ID, Cost_Delta => 0);
+                       (Super, Shared, Parser_Index, Local_Config_Heap, 
New_Config, Action.State, ID,
+                        Cost_Delta => 0,
+                        Strategy   => Explore_Table);
                   end;
 
                when Reduce =>
@@ -532,26 +585,25 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
                         New_Config.Error_Token.ID := Invalid_Token_ID;
                         New_Config.Check_Status   := (Label => 
WisiToken.Semantic_Checks.Ok);
 
-                        Cached_Status := Do_Reduce_1
-                          (Super, Shared, Parser_Index, Local_Config_Heap, 
New_Config, Action);
+                        Do_Reduce_1 ("Insert", Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, Action);
                         Cached_Config := New_Config;
                         Cached_Action := Action;
 
-                        if Cached_Status = Continue then
-                           Do_Reduce_2
-                             (Super, Shared, Parser_Index, Local_Config_Heap, 
New_Config, ID, Cost_Delta => 0);
-                        end if;
+                        Do_Reduce_2
+                          ("Insert", Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, ID,
+                           Cost_Delta => 0,
+                           Strategy   => Explore_Table);
                      end;
 
                   else
-                     if Cached_Status = Continue then
-                        declare
-                           New_Config : Configuration := Cached_Config;
-                        begin
-                           Do_Reduce_2
-                             (Super, Shared, Parser_Index, Local_Config_Heap, 
New_Config, ID, Cost_Delta => 0);
-                        end;
-                     end if;
+                     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 =>
@@ -566,132 +618,171 @@ package body 
WisiToken.Parse.LR.McKenzie_Recover.Explore is
       end loop;
    end Insert_From_Action_List;
 
-   procedure Insert_Minimal_Complete_Actions
+   function Insert_Minimal_Complete_Actions
      (Super             : not null access Base.Supervisor;
       Shared            : not null access Base.Shared;
       Parser_Index      : in              SAL.Base_Peek_Type;
       Orig_Config       : in              Configuration;
       Local_Config_Heap : in out          Config_Heaps.Heap_Type)
+     return Integer
+      --  Returns count of terminals inserted; 0 if Orig_Config reduces to
+      --  the start nonterm.
    is
-      use all type SAL.Base_Peek_Type;
+      use all type Ada.Containers.Count_Type;
 
-      Table      : Parse_Table renames Shared.Table.all;
-      Descriptor : WisiToken.Descriptor renames Super.Trace.Descriptor.all;
+      Table        : Parse_Table renames Shared.Table.all;
+      Descriptor   : WisiToken.Descriptor renames Super.Trace.Descriptor.all;
+      Insert_Count : Integer := 0;
 
       Cost_Delta : constant Integer := -1;
 
-      type Work_Type is record
-         Config : Configuration;
-         Complete_Actions : Minimal_Action_Lists.List;
-      end record;
-
-      package Work_Queues is new SAL.Gen_Unbounded_Definite_Queues (Work_Type);
-
-      Work : Work_Queues.Queue;
-
-      function Reduce_Only (Item : in Minimal_Action_Lists.List) return 
Minimal_Action_Lists.List
-      is begin
-         return Result : Minimal_Action_Lists.List do
-            for Action of Item loop
-               if Action.Verb = Reduce then
-                  Result.Insert (Action);
-               end if;
-            end loop;
-         end return;
-      end Reduce_Only;
+      New_Config      : Configuration  := Orig_Config;
+      Complete_Action : Minimal_Action := Table.States 
(New_Config.Stack.Peek.State).Minimal_Complete_Action;
 
       function To_Reduce_Action (Item : in Minimal_Action) return 
Reduce_Action_Rec
       is begin
          return (Reduce, (Item.Nonterm, 0), null, null, Item.Token_Count);
       end To_Reduce_Action;
 
+      procedure Minimal_Do_Shift
+      is begin
+         if New_Config.Ops.Length > 0 and then
+           --  Don't insert an ID we just pushed back or deleted; we know that 
failed.
+           (New_Config.Ops (New_Config.Ops.Last_Index) =
+              (Push_Back, Complete_Action.ID, New_Config.Current_Shared_Token) 
or
+              New_Config.Ops (New_Config.Ops.Last_Index) =
+              (Delete, Complete_Action.ID, New_Config.Current_Shared_Token))
+         then
+            if Trace_McKenzie > Extra then
+               Put_Line
+                 (Super.Trace.all, Super.Label (Parser_Index), 
"Minimal_Complete_Actions abandoned " &
+                    Image (Complete_Action.ID, Descriptor));
+            end if;
+            pragma Assert (Insert_Count = 0);
+         else
+            if Trace_McKenzie > Extra then
+               Put_Line
+                 (Super.Trace.all, Super.Label (Parser_Index), 
"Minimal_Complete_Actions shift " &
+                    Image (Complete_Action.ID, Descriptor));
+            end if;
+            New_Config.Check_Status := (Label => WisiToken.Semantic_Checks.Ok);
+            Insert_Count            := Insert_Count + 1;
+
+            Do_Shift
+              (Super, Shared, Parser_Index, Local_Config_Heap, New_Config, 
Complete_Action.State, Complete_Action.ID,
+               Cost_Delta,
+               Strategy   => Minimal_Complete);
+         end if;
+      end Minimal_Do_Shift;
+
    begin
-      Work.Put ((Orig_Config, Table.States 
(Orig_Config.Stack.Peek.State).Minimal_Complete_Actions));
-      loop
-         exit when Work.Length = 0;
+      case Complete_Action.Verb is
+      when Pause =>
+         return 0;
+
+      when Reduce =>
+         --  Do a reduce, look at resulting state. Keep reducing until we can't
+         --  anymore.
          declare
-            Item : constant Work_Type := Work.Get;
+            Reduce_Action : Reduce_Action_Rec := To_Reduce_Action 
(Complete_Action);
          begin
-            for Action of Item.Complete_Actions loop
-               case Action.Verb is
-               when Reduce =>
-                  --  Do a reduce, look at resulting state. Keep reducing 
until we can't
-                  --  anymore (ignoring possible shifts along the way; we are 
looking
-                  --  for the _minimal_ terminals to insert).
-                  declare
-                     use all type Ada.Containers.Count_Type;
-                     New_Config    : Configuration     := Item.Config;
-                     Reduce_Action : Reduce_Action_Rec := To_Reduce_Action 
(Action);
+            loop
+               Do_Reduce_1
+                 ("Minimal_Complete_Actions", Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, Reduce_Action,
+                  Do_Language_Fixes => False);
 
-                     Temp_Actions : Minimal_Action_Lists.List;
-                  begin
-                     loop
-                        case Do_Reduce_1 (Super, Shared, Parser_Index, 
Local_Config_Heap, New_Config, Reduce_Action) is
-                        when Abandon =>
-                           goto Abandon_Reduce;
+               Complete_Action := Table.States 
(New_Config.Stack.Peek.State).Minimal_Complete_Action;
 
-                        when Continue =>
-                           if Trace_McKenzie > Extra then
-                              Put_Line
-                                (Super.Trace.all, Super.Label (Parser_Index), 
"Minimal_Complete_Actions reduce to" &
-                                   State_Index'Image 
(New_Config.Stack.Peek.State) & ", " &
-                                   Image (Reduce_Action.Production.LHS, 
Descriptor));
-                           end if;
+               case Complete_Action.Verb is
+               when Pause =>
+                  return 0;
+               when Shift =>
+                  Minimal_Do_Shift;
+                  return Insert_Count;
+               when Reduce =>
+                  null;
+               end case;
 
-                           Temp_Actions := Reduce_Only
-                             (Table.States 
(New_Config.Stack.Peek.State).Minimal_Complete_Actions);
+               Reduce_Action := To_Reduce_Action
+                 (Table.States 
(New_Config.Stack.Peek.State).Minimal_Complete_Action);
+            end loop;
+         end;
 
-                           exit when Temp_Actions.Length = 0;
+      when Shift =>
+         Minimal_Do_Shift;
+      end case;
+      return Insert_Count;
+   end Insert_Minimal_Complete_Actions;
 
-                           Reduce_Action := To_Reduce_Action 
(Temp_Actions.Pop);
+   procedure Insert_Matching_Begin
+     (Super                : not null access Base.Supervisor;
+      Shared               : not null access Base.Shared;
+      Parser_Index         : in              SAL.Base_Peek_Type;
+      Config               : in              Configuration;
+      Local_Config_Heap    : in out          Config_Heaps.Heap_Type;
+      Matching_Begin_Token : in              Token_ID)
+   is
+      Descriptor : WisiToken.Descriptor renames Super.Trace.Descriptor.all;
+      New_Config : Configuration                  := Config;
 
-                           if Temp_Actions.Length > 0 then
-                              if Trace_McKenzie > Extra then
-                                 Put_Line
-                                   (Super.Trace.all, Super.Label 
(Parser_Index),
-                                    "Minimal_Complete_Actions add work item");
-                              end if;
-                              Work.Put ((New_Config, Temp_Actions));
-                           end if;
-                        end case;
-                     end loop;
+      Action     : constant Parse_Action_Node_Ptr := Action_For
+        (Shared.Table.all, New_Config.Stack (1).State, Matching_Begin_Token);
+   begin
+      if Trace_McKenzie > Extra then
+         Put_Line
+           (Super.Trace.all, Super.Label (Parser_Index), "Matching_Begin 
insert " &
+              Image (Matching_Begin_Token, Descriptor));
+      end if;
 
-                     Insert_Minimal_Complete_Actions (Super, Shared, 
Parser_Index, New_Config, Local_Config_Heap);
+      case Action.Item.Verb is
+      when Shift =>
+         Do_Shift
+           (Super, Shared, Parser_Index, Local_Config_Heap, New_Config, 
Action.Item.State,
+            Matching_Begin_Token,
+            Cost_Delta => -1,
+            Strategy   => Matching_Begin);
 
-                     <<Abandon_Reduce>>
-                  end;
+      when Reduce =>
+         Do_Reduce_1
+           ("Matching_Begin", Super, Shared, Parser_Index, Local_Config_Heap, 
New_Config, Action.Item,
+            Do_Language_Fixes => False);
 
-               when Shift =>
-                  if Trace_McKenzie > Extra then
-                     Put_Line
-                       (Super.Trace.all, Super.Label (Parser_Index), 
"Minimal_Complete_Actions shift " &
-                          Image (Action.ID, Descriptor));
-                  end if;
-                  declare
-                     New_Config : Configuration := Item.Config;
-                  begin
-                     New_Config.Check_Status := (Label => 
WisiToken.Semantic_Checks.Ok);
+         Do_Reduce_2
+           ("Matching_Begin", Super, Shared, Parser_Index, Local_Config_Heap, 
New_Config, Matching_Begin_Token,
+            Cost_Delta => -1,
+            Strategy   => Matching_Begin);
 
-                     Do_Shift
-                       (Super, Shared, Parser_Index, Local_Config_Heap, 
New_Config, Action.State, Action.ID,
-                        Cost_Delta);
-                  end;
-               end case;
-            end loop;
-         end;
-      end loop;
-   end Insert_Minimal_Complete_Actions;
+      when Error | Accept_It =>
+         if Trace_McKenzie > Extra then
+            Put_Line
+              (Super.Trace.all, Super.Label (Parser_Index), "Matching_Begin 
abandoned " &
+                 Image (Matching_Begin_Token, Descriptor));
+         end if;
+      end case;
+   end Insert_Matching_Begin;
 
    procedure Try_Insert_Terminal
-     (Super                      : not null access Base.Supervisor;
-      Shared                     : not null access Base.Shared;
-      Parser_Index               : in              SAL.Base_Peek_Type;
-      Config                     : in              Configuration;
-      Local_Config_Heap          : in out          Config_Heaps.Heap_Type;
-      Use_Minimal_Complete_Actions : in              Boolean)
+     (Super                        : not null access Base.Supervisor;
+      Shared                       : not null access Base.Shared;
+      Parser_Index                 : in              SAL.Base_Peek_Type;
+      Config                       : in              Configuration;
+      Local_Config_Heap            : in out          Config_Heaps.Heap_Type;
+      Use_Minimal_Complete_Actions : in              Boolean;
+      Matching_Begin_Token         : in              Token_ID)
    is begin
       if Use_Minimal_Complete_Actions then
-         Insert_Minimal_Complete_Actions (Super, Shared, Parser_Index, Config, 
Local_Config_Heap);
+         if 0 = Insert_Minimal_Complete_Actions (Super, Shared, Parser_Index, 
Config, Local_Config_Heap) then
+            if Matching_Begin_Token /= Invalid_Token_ID then
+               Insert_Matching_Begin (Super, Shared, Parser_Index, Config, 
Local_Config_Heap, Matching_Begin_Token);
+            else
+               Insert_From_Action_List (Super, Shared, Parser_Index, Config, 
Local_Config_Heap);
+            end if;
+         else
+            --  We _do not_ do Insert_From_Action_list when
+            --  Insert_Minimal_Complete inserted something; let it try again 
next
+            --  cycle.
+            null;
+         end if;
       else
          Insert_From_Action_List (Super, Shared, Parser_Index, Config, 
Local_Config_Heap);
       end if;
@@ -714,7 +805,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
       Local_Config_Heap : in out          Config_Heaps.Heap_Type)
    is
       use all type Parser.Language_String_ID_Set_Access;
-      use all type Lexer.Error_Lists.Cursor;
 
       Descriptor  : WisiToken.Descriptor renames Shared.Trace.Descriptor.all;
       Check_Limit : Token_Index renames 
Shared.Table.McKenzie_Param.Check_Limit;
@@ -724,10 +814,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
       Lexer_Error_Token       : Base_Token;
 
       function Recovered_Lexer_Error (Line : in Line_Number_Type) return 
Base_Token_Index
-      is
-         use WisiToken.Lexer;
-         use WisiToken.Lexer.Error_Lists;
-      begin
+      is begin
          --  We are assuming the list of lexer errors is short, so binary
          --  search would not be significantly faster.
          for Err of reverse Shared.Lexer.Errors loop
@@ -842,6 +929,8 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore is
             end if;
          end if;
 
+         Config.Strategy_Counts (String_Quote) := Config.Strategy_Counts 
(String_Quote) + 1;
+
          if Trace_McKenzie > Detail then
             Base.Put ("insert missing quote " & Label & " ", Super, Shared, 
Parser_Index, New_Config);
          end if;
@@ -1034,7 +1123,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
    is
       --  Try deleting (= skipping) the current shared input token.
       Trace       : WisiToken.Trace'Class renames Super.Trace.all;
-      EOF_ID      : Token_ID renames Trace.Descriptor.EOF_ID;
+      EOF_ID      : Token_ID renames Trace.Descriptor.EOI_ID;
       Check_Limit : Token_Index renames 
Shared.Table.McKenzie_Param.Check_Limit;
 
       McKenzie_Param : McKenzie_Param_Type renames Shared.Table.McKenzie_Param;
@@ -1060,6 +1149,10 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
 
             New_Config.Ops.Append ((Delete, ID, Config.Current_Shared_Token));
             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);
+               New_Config.Current_Shared_Token := 
New_Config.Current_Shared_Token + 1;
+            end loop;
 
             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;
@@ -1092,6 +1185,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
       use all type Parser.Language_Fixes_Access;
       use all type SAL.Base_Peek_Type;
       use all type Semantic_Checks.Check_Status_Label;
+      use all type 
WisiToken.Parse.LR.Parser.Language_Use_Minimal_Complete_Actions_Access;
 
       Trace      : WisiToken.Trace'Class renames Super.Trace.all;
       Descriptor : WisiToken.Descriptor renames Super.Trace.Descriptor.all;
@@ -1104,24 +1198,13 @@ package body 
WisiToken.Parse.LR.McKenzie_Recover.Explore is
       --  We collect all the variants to enqueue, then deliver them all at
       --  once to Super, to minimizes task interactions.
 
-      Use_Minimal_Complete_Actions : Boolean := False;
+      Use_Minimal_Complete_Actions : Boolean  := False;
+      Matching_Begin_Token         : Token_ID := Invalid_Token_ID;
 
       function Allow_Insert_Terminal (Config : in Configuration) return Boolean
-      is
-         use all type Ada.Containers.Count_Type;
-         use all type 
WisiToken.Parse.LR.Parser.Language_Use_Minimal_Complete_Actions_Access;
-      begin
-         if Shared.Language_Use_Minimal_Complete_Actions = null then
-            return None_Since_FF (Config.Ops, Delete);
-         end if;
-
-         Use_Minimal_Complete_Actions := 
Shared.Language_Use_Minimal_Complete_Actions
-           (Current_Token_ID_Peek
-              (Shared.Terminals.all, Config.Current_Shared_Token, 
Config.Insert_Delete, Config.Current_Insert_Delete),
-            Config);
-
+      is begin
          if Use_Minimal_Complete_Actions then
-            if Table.States 
(Config.Stack.Peek.State).Minimal_Complete_Actions.Length = 0 then
+            if Table.States 
(Config.Stack.Peek.State).Minimal_Complete_Action.Verb = Pause then
                --  This happens when there is an extra token after an 
acceptable
                --  grammar statement. There is no production to complete, so 
try
                --  other things.
@@ -1180,7 +1263,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Explore 
is
             if Config.Check_Status.Label = Ok then
                --  Parse table Error action.
                --
-               --  We don't clear Config.Error_Token here, because Try_Insert 
calls
+               --  We don't clear Config.Error_Token here, because 
Try_Insert_Terminal calls
                --  Language_Use_Minimal_Complete_Actions, which needs it. We 
only clear it
                --  when a parse results in no error (or a different error), or 
a
                --  push_back moves the Current_Token.
@@ -1262,15 +1345,37 @@ package body 
WisiToken.Parse.LR.McKenzie_Recover.Explore is
       --
       --  All possible permutations will be explored.
 
-      if None_Since_FF (Config.Ops, Delete) and
-        None_Since_FF (Config.Ops, Insert) and
-        Config.Stack.Depth > 1 -- can't delete the first state
+      if Shared.Language_Use_Minimal_Complete_Actions = null then
+         Use_Minimal_Complete_Actions := False;
+         Matching_Begin_Token         := Invalid_Token_ID;
+      else
+         declare
+            Current_Token : Token_ID;
+            Next_Token : Token_ID;
+         begin
+            Current_Token_ID_Peek_2
+              (Shared.Terminals.all, Config.Current_Shared_Token, 
Config.Insert_Delete, Config.Current_Insert_Delete,
+               Super.Parser_State (Parser_Index).Prev_Deleted, Current_Token, 
Next_Token);
+
+            Shared.Language_Use_Minimal_Complete_Actions
+              (Current_Token, Next_Token, Config,
+               Use_Complete         => Use_Minimal_Complete_Actions,
+               Matching_Begin_Token => Matching_Begin_Token);
+         end;
+      end if;
+
+      if None_Since_FF (Config.Ops, Delete) and then
+        None_Since_FF (Config.Ops, Insert) and then
+        Config.Stack.Depth > 1 and then -- can't delete the first state
+        (not (Use_Minimal_Complete_Actions and then Check_Reduce_To_Start 
(Super, Shared, Parser_Index, Config)))
+        --  If Config reduces to the start nonterm, there's no point in 
push_back.
       then
          Try_Push_Back (Super, Shared, Parser_Index, Config, 
Local_Config_Heap);
       end if;
 
       if Allow_Insert_Terminal (Config) then
-         Try_Insert_Terminal (Super, Shared, Parser_Index, Config, 
Local_Config_Heap, Use_Minimal_Complete_Actions);
+         Try_Insert_Terminal
+           (Super, Shared, Parser_Index, Config, Local_Config_Heap, 
Use_Minimal_Complete_Actions, Matching_Begin_Token);
       end if;
 
       if Config.Current_Insert_Delete = No_Insert_Delete then
diff --git a/wisitoken-parse-lr-mckenzie_recover-parse.adb 
b/wisitoken-parse-lr-mckenzie_recover-parse.adb
index c9a7475..52ee7f1 100644
--- a/wisitoken-parse-lr-mckenzie_recover-parse.adb
+++ b/wisitoken-parse-lr-mckenzie_recover-parse.adb
@@ -2,7 +2,7 @@
 --
 --  See spec
 --
---  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
@@ -82,7 +82,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Parse is
          return (Label => Ok);
       else
          return Status : constant Semantic_Checks.Check_Status :=
-           Action.Check (Shared.Lexer, Nonterm, Tokens)
+           Action.Check (Shared.Lexer, Nonterm, Tokens, Recover_Active => True)
          do
             if Status.Label = Ok then
                Stack.Pop (SAL.Base_Peek_Type (Action.Token_Count));
@@ -129,7 +129,8 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Parse is
          Terminals_Current         => Config.Current_Shared_Token,
          Restore_Terminals_Current => Restore_Terminals_Current,
          Insert_Delete             => Config.Insert_Delete,
-         Current_Insert_Delete     => Config.Current_Insert_Delete);
+         Current_Insert_Delete     => Config.Current_Insert_Delete,
+         Prev_Deleted              => Super.Parser_State 
(Parser_Index).Prev_Deleted);
 
       New_State : Unknown_State_Index;
       Success   : Boolean := True;
@@ -171,7 +172,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Parse is
             Put_Line
               (Trace, Super.Label (Parser_Index), Trace_Prefix & ":" & 
State_Index'Image (Config.Stack.Peek.State) &
                  " :" & Token_Index'Image (Config.Current_Shared_Token) &
-                 " : " & Image (Current_Token, Descriptor) &
+                 ":" & Image (Current_Token, Descriptor) &
                  " : " & Image (Action.Item, Descriptor));
          end if;
 
@@ -196,7 +197,8 @@ package body WisiToken.Parse.LR.McKenzie_Recover.Parse is
                Terminals_Current         => Config.Current_Shared_Token,
                Restore_Terminals_Current => Restore_Terminals_Current,
                Insert_Delete             => Config.Insert_Delete,
-               Current_Insert_Delete     => Config.Current_Insert_Delete);
+               Current_Insert_Delete     => Config.Current_Insert_Delete,
+               Prev_Deleted              => Super.Parser_State 
(Parser_Index).Prev_Deleted);
 
          when Reduce =>
             declare
diff --git a/wisitoken-parse-lr-mckenzie_recover.adb 
b/wisitoken-parse-lr-mckenzie_recover.adb
index c92e2ed..31c6285 100644
--- a/wisitoken-parse-lr-mckenzie_recover.adb
+++ b/wisitoken-parse-lr-mckenzie_recover.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
@@ -20,44 +20,79 @@ pragma License (Modified_GPL);
 with Ada.Characters.Handling;
 with Ada.Exceptions;
 with Ada.Task_Identification;
+with Ada.Unchecked_Deallocation;
+with GNAT.Traceback.Symbolic;
 with System.Multiprocessors;
 with WisiToken.Parse.LR.McKenzie_Recover.Base;
 with WisiToken.Parse.LR.McKenzie_Recover.Explore;
 with WisiToken.Parse.LR.Parser_Lists;
 package body WisiToken.Parse.LR.McKenzie_Recover is
+   use all type System.Multiprocessors.CPU_Range;
 
-   task type Worker_Task
-     (Super  : not null access Base.Supervisor;
-      Shared : not null access Base.Shared)
-   is
-      entry Start;
-      --  Start getting parser/configs to check from Config_Store.
+   type Supervisor_Access is access all Base.Supervisor;
+   type Shared_Access is access all Base.Shared;
 
-      entry Done;
-      --  Available when task is ready to terminate; after this rendezvous,
-      --  task discriminants may be freed.
+   task type Worker_Task is
+      entry Start
+        (Super  : in Supervisor_Access;
+         Shared : in Shared_Access);
+      --  Start getting parser/configs to check from Config_Store. Stop when
+      --  Super reports All_Done;
 
+      entry Done;
+      --  Available after Super has reported All_Done.
    end Worker_Task;
 
+   type Worker_Access is access Worker_Task;
+   procedure Free is new Ada.Unchecked_Deallocation (Worker_Task, 
Worker_Access);
+
    task body Worker_Task
    is
       use all type Base.Config_Status;
-      Status : Base.Config_Status;
-   begin
-      accept Start;
+      Super  : Supervisor_Access;
+      Shared : Shared_Access;
 
+      Status : Base.Config_Status := Valid;
+   begin
       loop
-         Explore.Process_One (Super, Shared, Status);
+         select
+            accept Start
+              (Super  : in Supervisor_Access;
+               Shared : in Shared_Access)
+
+            do
+               Worker_Task.Super  := Super;
+               Worker_Task.Shared := Shared;
+            end Start;
+         or
+            terminate;
+         end select;
 
-         exit when Status = All_Done;
+         loop
+            Explore.Process_One (Super, Shared, Status);
+            exit when Status = All_Done;
+         end loop;
+
+         accept Done;
+
+         Super  := null;
+         Shared := null;
       end loop;
 
-      accept Done;
    exception
    when E : others =>
       Super.Fatal (E);
+      if Debug_Mode then
+         Shared.Trace.Put_Line (GNAT.Traceback.Symbolic.Symbolic_Traceback 
(E));
+      end if;
    end Worker_Task;
 
+   Worker_Tasks : array (1 .. System.Multiprocessors.CPU_Range'Max (1, 
System.Multiprocessors.Number_Of_CPUs - 1)) of
+     Worker_Access;
+   --  Declaring an array of tasks directly causes a circular elaboration
+   --  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)
@@ -165,7 +200,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
    is
       use all type Parser.Post_Recover_Access;
       use all type SAL.Base_Peek_Type;
-      use all type System.Multiprocessors.CPU_Range;
       Trace : WisiToken.Trace'Class renames Shared_Parser.Trace.all;
 
       Parsers : Parser_Lists.List renames Shared_Parser.Parsers;
@@ -191,21 +225,10 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
 
       Task_Count : constant System.Multiprocessors.CPU_Range :=
         (if Shared_Parser.Table.McKenzie_Param.Task_Count = 0
-         then System.Multiprocessors.CPU_Range'Max (1, 
System.Multiprocessors.Number_Of_CPUs - 1)
+         then Worker_Tasks'Last
          --  Keep one CPU free for this main task, and the user.
          else Shared_Parser.Table.McKenzie_Param.Task_Count);
 
-      Worker_Tasks : array (1 .. Task_Count) of Worker_Task (Super'Access, 
Shared'Access);
-
-      procedure Cleanup
-      is begin
-         for I in Worker_Tasks'Range loop
-            if Worker_Tasks (I)'Callable then
-               abort Worker_Tasks (I);
-            end if;
-         end loop;
-      end Cleanup;
-
    begin
       if Trace_McKenzie > Outline then
          Trace.New_Line;
@@ -223,29 +246,45 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
          Trace.Put_Line (System.Multiprocessors.CPU_Range'Image 
(Worker_Tasks'Last) & " parallel tasks");
       end if;
 
-      for I in Worker_Tasks'Range loop
-         Worker_Tasks (I).Start;
+      for I in Worker_Tasks'First .. Task_Count loop
+         if Worker_Tasks (I) = null then
+            Worker_Tasks (I) := new Worker_Task;
+            if Debug_Mode then
+               Trace.Put_Line ("new Worker_Task" & 
System.Multiprocessors.CPU_Range'Image (I));
+            end if;
+
+         elsif Worker_Tasks (I)'Terminated then
+            Free (Worker_Tasks (I));
+            Worker_Tasks (I) := new Worker_Task;
+            if Debug_Mode then
+               Trace.Put_Line ("recreated Worker_Task" & 
System.Multiprocessors.CPU_Range'Image (I));
+            end if;
+         end if;
+
+         Worker_Tasks (I).Start (Super'Unchecked_Access, 
Shared'Unchecked_Access);
       end loop;
 
       declare
          use Ada.Exceptions;
-         ID : Exception_Id;
+         ID      : Exception_Id;
          Message : Ada.Strings.Unbounded.Unbounded_String;
       begin
          Super.Done (ID, Message); -- Wait for all parsers to fail or succeed
+
+         --  Ensure all worker tasks stop getting configs before proceeding;
+         --  otherwise local variables disappear while the task is still trying
+         --  to access them.
+         for I in Worker_Tasks'First .. Task_Count loop
+            if not Worker_Tasks (I)'Terminated then
+               Worker_Tasks (I).Done;
+            end if;
+         end loop;
+
          if ID /= Null_Id then
             Raise_Exception (ID, -Message);
          end if;
       end;
 
-      --  Ensure all tasks terminate before proceeding; otherwise local
-      --  variables disappear while task is still trying to access them.
-      for I in Worker_Tasks'Range loop
-         if Worker_Tasks (I)'Callable then
-            Worker_Tasks (I).Done;
-         end if;
-      end loop;
-
       --  Adjust parser state for each successful recovery.
       --
       --  One option here would be to keep only the parser with the least
@@ -354,7 +393,20 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
                   Current_Token_Virtual : Boolean := False;
 
                   Sorted_Insert_Delete : Sorted_Insert_Delete_Arrays.Vector;
+
+                  procedure Apply_Prev_Token
+                  is begin
+                     loop
+                        exit when not Parser_State.Prev_Deleted.Contains 
(Parser_State.Shared_Token);
+                        Parser_State.Shared_Token := Parser_State.Shared_Token 
+ 1;
+                     end loop;
+                  end Apply_Prev_Token;
+
                begin
+                  --  The verb will be reset by the main parser; just indicate 
the
+                  --  parser recovered from the error.
+                  Parser_State.Set_Verb (Shift);
+
                   Parser_State.Errors (Parser_State.Errors.Last).Recover := 
Result;
 
                   Parser_State.Resume_Token_Goal := Result.Resume_Token_Goal;
@@ -393,6 +445,10 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
                   --  a Push_Back. See test_mckenzie_recover.adb 
Out_Of_Order_Ops for an
                   --  example.
                   --
+                  --  Push_Back can also go back past a previous error 
recovery; we must
+                  --  apply Parser_State.Prev_Deleted here as well, when 
computing
+                  --  Shared_Token.
+                  --
                   --  So first we go thru Ops to find the earliest Push_Back. 
Then we
                   --  apply ops that are before that point, up to the first 
Insert or
                   --  Fast_Forward. After that, we enqueue Insert and Delete 
ops on
@@ -510,6 +566,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
                         if Stack_Matches_Ops and Op.Token_Index = 
Parser_State.Shared_Token then
                            --  We can apply multiple deletes.
                            Parser_State.Shared_Token := Op.Token_Index + 1;
+                           Apply_Prev_Token;
                            Shared_Token_Changed      := True;
                         else
                            Sorted_Insert_Delete.Insert (Op);
@@ -528,6 +585,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
 
                         when Delete =>
                            Parser_State.Shared_Token := Op.Token_Index + 1;
+                           Apply_Prev_Token;
                            Shared_Token_Changed      := True;
                         end case;
                      else
@@ -536,46 +594,25 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
                   end loop;
 
                   --  If not Shared_Token_Changed, Shared_Token is the error 
token,
-                  --  which is the next token to read. If 
Shared_Token_Changed, we
-                  --  have set Shared_Token consistent with that; it is the 
next token to
-                  --  read.
+                  --  which is the next token to read. If 
Shared_Token_Changed, we have
+                  --  set Shared_Token consistent with that; it is the next 
token to
+                  --  read. If Current_Token_Virtual, then after all the 
virtual tokens
+                  --  are inserted, the main parser would normally increment
+                  --  Parser_State.Shared_Token to get the next token, but we 
don't want
+                  --  that now. We could set Shared_Token to 1 less, but this 
way the
+                  --  debug messages all show the expected Shared_Terminal.
+
+                  Parser_State.Inc_Shared_Token := not Current_Token_Virtual;
+
+                  --  The main parser always sets Current_Token to be the 
syntax tree
+                  --  node containing Shared_Token; ensure that is true here 
(virtual
+                  --  tokens where handled above).
 
                   if (not Current_Token_Virtual) and Shared_Token_Changed then
                      Parser_State.Current_Token := 
Parser_State.Tree.Add_Terminal
                        (Parser_State.Shared_Token, Shared_Parser.Terminals);
                   end if;
 
-                  --  Parser_State.Verb is the action that produced the 
current stack
-                  --  top. Parser_State.Inc_Shared_Token determines how to get 
the next
-                  --  token from Shared_Parser.Terminals.
-                  --
-                  --  If the stack top or Current_Token is virtual, then after 
all
-                  --  virtuals are inserted, the main parser would normally 
increment
-                  --  Parser_State.Shared_Token to get the next token. 
However, we have
-                  --  set Shared_Token to the next token, so we don't want it 
to
-                  --  increment. We could set Shared_Token to 1 less, but this 
way the
-                  --  debug messages all show the expected Shared_Terminal.
-
-                  if Parser_State.Stack (1).Token = 
Syntax_Trees.Invalid_Node_Index then
-                     --  a virtual token from a previous recover
-                     Parser_State.Set_Verb (Shift_Recover);
-                     Parser_State.Inc_Shared_Token := False;
-                  else
-                     case Tree.Label (Parser_State.Stack (1).Token) is
-                     when Syntax_Trees.Shared_Terminal =>
-                        Parser_State.Set_Verb (Shift_Recover);
-                        Parser_State.Inc_Shared_Token := not 
Current_Token_Virtual;
-
-                     when Syntax_Trees.Virtual_Terminal =>
-                        Parser_State.Set_Verb (Shift_Recover);
-                        Parser_State.Inc_Shared_Token := False;
-
-                     when Syntax_Trees.Nonterm =>
-                        Parser_State.Set_Verb (Reduce);
-                        Parser_State.Inc_Shared_Token := not 
Current_Token_Virtual;
-                     end case;
-                  end if;
-
                   if Trace_McKenzie > Extra then
                      Put_Line (Trace, Parser_State.Label, "after Ops 
applied:", Task_ID => False);
                      Put_Line
@@ -595,12 +632,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
                        (Trace, Parser_State.Label, "inc_shared_token " & 
Boolean'Image (Parser_State.Inc_Shared_Token) &
                           " parser verb " & All_Parse_Action_Verbs'Image 
(Parser_State.Verb),
                         Task_ID => False);
-
-                  elsif Trace_McKenzie > Outline then
-                     Put_Line
-                       (Trace, Parser_State.Label, "inc_shared_token " & 
Boolean'Image (Parser_State.Inc_Shared_Token) &
-                          " parser verb " & All_Parse_Action_Verbs'Image 
(Parser_State.Verb),
-                        Task_ID => False);
                   end if;
                end;
             exception
@@ -609,7 +640,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
                   --  Oops. just give up
                   return Fail_Programmer_Error;
                end if;
-               Parsers.Terminate_Parser (Current_Parser, "bad config in 
recover", Trace);
+               Parsers.Terminate_Parser (Current_Parser, "bad config in 
recover", Trace, Shared_Parser.Terminals);
             end;
          end if;
          Current_Parser.Next;
@@ -623,7 +654,6 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
 
    exception
    when others =>
-      Cleanup;
       return Fail_Programmer_Error;
    end Recover;
 
@@ -641,7 +671,8 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       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)
+      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;
@@ -656,7 +687,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       end Inc_I_D;
 
    begin
-      if Terminals_Current = Base_Token_Index'First then
+      if Terminals_Current = Invalid_Token_Index then
          --  Happens with really bad syntax; see test_mckenzie_recover.adb 
Error_4.
          raise Bad_Config;
       end if;
@@ -679,7 +710,11 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
                   return (ID => Op.ID, others => <>);
 
                when Delete =>
-                  Terminals_Current         := Terminals_Current + 1;
+                  Terminals_Current    := Terminals_Current + 1;
+                  loop
+                     exit when not Prev_Deleted.Contains (Terminals_Current);
+                     Terminals_Current := Terminals_Current + 1;
+                  end loop;
                   Restore_Terminals_Current := Terminals_Current;
                   Inc_I_D;
                end case;
@@ -690,22 +725,35 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       end loop;
    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)
-     return Token_ID
+   procedure Current_Token_ID_Peek_2
+     (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;
+      Current_Token         :    out Token_ID;
+      Next_Token            :    out Token_ID)
    is
       use all type SAL.Base_Peek_Type;
+      Terminals_Next : Token_Index := Terminals_Current + 1;
    begin
       if Terminals_Current = Base_Token_Index'First then
          --  Happens with really bad syntax; see test_mckenzie_recover.adb 
Error_4.
          raise Bad_Config;
       end if;
 
+      loop
+         exit when not Prev_Deleted.Contains (Terminals_Next);
+         Terminals_Next := Terminals_Next + 1;
+      end loop;
+      if Terminals_Next <= Terminals.Last_Index then
+         Next_Token := Terminals (Terminals_Next).ID;
+      else
+         Next_Token := Invalid_Token_ID;
+      end if;
+
       if Current_Insert_Delete = No_Insert_Delete then
-         return Terminals (Terminals_Current).ID;
+         Current_Token := Terminals (Terminals_Current).ID;
 
       elsif Insert_Delete (Current_Insert_Delete).Token_Index = 
Terminals_Current then
          declare
@@ -713,7 +761,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
          begin
             case Insert_Delete_Op_Label (Op.Op) is
             when Insert =>
-               return Op.ID;
+               Current_Token := Op.ID;
 
             when Delete =>
                --  This should have been handled in Check
@@ -721,9 +769,27 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
             end case;
          end;
       else
-         return Terminals (Terminals_Current).ID;
+         Current_Token := Terminals (Terminals_Current).ID;
+      end if;
+
+      if Current_Insert_Delete = Insert_Delete.Last_Index then
+         null;
+
+      elsif Insert_Delete (Current_Insert_Delete + 1).Token_Index = 
Terminals_Current then
+         declare
+            Op : Insert_Delete_Op renames Insert_Delete (Current_Insert_Delete 
+ 1);
+         begin
+            case Insert_Delete_Op_Label (Op.Op) is
+            when Insert =>
+               Next_Token := Op.ID;
+
+            when Delete =>
+               --  This should have been handled in Check
+               raise SAL.Programmer_Error;
+            end case;
+         end;
       end if;
-   end Current_Token_ID_Peek;
+   end Current_Token_ID_Peek_2;
 
    procedure Delete (Config : in out Configuration; ID : in Token_ID)
    is
@@ -785,9 +851,10 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
    begin
       loop
          exit when Matching_Index = Config.Stack.Depth; -- Depth has 
Invalid_Token_ID
-         exit when 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 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));
 
          Matching_Index := Matching_Index + 1;
       end loop;
@@ -887,23 +954,33 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
       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)
+      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;
+
+      function Next_Terminal return Base_Token
+      is begin
+         Terminals_Current    := Terminals_Current + 1;
+         loop
+            exit when not Prev_Deleted.Contains (Terminals_Current);
+            Terminals_Current := Terminals_Current + 1;
+         end loop;
+
+         Restore_Terminals_Current := Terminals_Current;
+         return Terminals (Terminals_Current);
+      end Next_Terminal;
+
    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;
-            Terminals_Current         := Terminals_Current + 1;
-            Restore_Terminals_Current := Terminals_Current;
-            return Terminals (Terminals_Current);
+            return Next_Terminal;
 
          elsif Current_Insert_Delete = No_Insert_Delete then
-            Terminals_Current         := Terminals_Current + 1;
-            Restore_Terminals_Current := Terminals_Current;
-            return Terminals (Terminals_Current);
+            return Next_Terminal;
 
          elsif Insert_Delete (Current_Insert_Delete + 1).Token_Index = 
Terminals_Current + 1 then
             Current_Insert_Delete := Current_Insert_Delete + 1;
@@ -921,9 +998,7 @@ package body WisiToken.Parse.LR.McKenzie_Recover is
             end;
 
          else
-            Terminals_Current         := Terminals_Current + 1;
-            Restore_Terminals_Current := Terminals_Current;
-            return Terminals (Terminals_Current);
+            return Next_Terminal;
          end if;
       end loop;
    end Next_Token;
diff --git a/wisitoken-parse-lr-mckenzie_recover.ads 
b/wisitoken-parse-lr-mckenzie_recover.ads
index f92897b..eba872a 100644
--- a/wisitoken-parse-lr-mckenzie_recover.ads
+++ b/wisitoken-parse-lr-mckenzie_recover.ads
@@ -11,7 +11,7 @@
 --  [Grune 2008] Parsing Techniques, A Practical Guide, Second
 --  Edition. Dick Grune, Ceriel J.H. Jacobs.
 --
---  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
@@ -66,19 +66,22 @@ private
       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)
+      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.
    --
    --  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)
-     return Token_ID;
+   procedure Current_Token_ID_Peek_2
+     (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;
+      Current_Token         :    out Token_ID;
+      Next_Token            :    out Token_ID);
    --  Return the current token, from either Terminals or Insert_Delete,
    --  without setting up for Next_Token.
 
@@ -149,7 +152,8 @@ private
       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)
+      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.
@@ -164,6 +168,9 @@ private
    --  Insert_Delete contains only Insert and Delete ops, in token_index
    --  order. Those ops are applied when Terminals_Current =
    --  op.token_index.
+   --
+   --  Prev_Deleted contains tokens deleted in previous recover
+   --  operations; those are skipped.
 
    procedure Push_Back (Config : in out Configuration);
    --  Pop the top Config.Stack item, set Config.Current_Shared_Token to
diff --git a/wisitoken-parse-lr-parser.adb b/wisitoken-parse-lr-parser.adb
index 9f8aa66..09df9ed 100644
--- a/wisitoken-parse-lr-parser.adb
+++ b/wisitoken-parse-lr-parser.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  Copyright (C) 2002 - 2005, 2008 - 2015, 2017, 2018 Free Software 
Foundation, Inc.
+--  Copyright (C) 2002 - 2005, 2008 - 2015, 2017 - 2019 Free Software 
Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -29,6 +29,7 @@ pragma License (Modified_GPL);
 
 with Ada.Calendar.Formatting;
 with Ada.Exceptions;
+with GNAT.Traceback.Symbolic;
 with WisiToken.Parse.LR.McKenzie_Recover;
 package body WisiToken.Parse.LR.Parser is
 
@@ -69,11 +70,12 @@ package body WisiToken.Parse.LR.Parser is
 
       else
          declare
-            Nonterm_Token  : Recover_Token := Parser_State.Tree.Recover_Token 
(Nonterm);
+            Nonterm_Token  : Recover_Token                := 
Parser_State.Tree.Recover_Token (Nonterm);
             Children_Token : constant Recover_Token_Array := 
Parser_State.Tree.Recover_Token_Array (Children_Tree);
-            Status : constant Semantic_Checks.Check_Status := Action.Check
-              (Lexer, Nonterm_Token, Children_Token);
+            Status         : Semantic_Checks.Check_Status;
          begin
+            Status := Action.Check (Lexer, Nonterm_Token, Children_Token, 
Recover_Active => False);
+
             Parser_State.Tree.Set_Name_Region (Nonterm, Nonterm_Token.Name);
 
             if Trace_Parse > Detail then
@@ -99,6 +101,11 @@ package body WisiToken.Parse.LR.Parser is
                   return Status.Label;
                end if;
             end case;
+         exception
+         when Partial_Parse =>
+            --  From Action.Check
+            Parser_State.Tree.Set_Root (Nonterm);
+            raise;
          end;
       end if;
    end Reduce_Stack_1;
@@ -180,7 +187,7 @@ package body WisiToken.Parse.LR.Parser is
 
          declare
             Expecting : constant Token_ID_Set := LR.Expecting
-              (Shared_Parser.Table.all, 
Current_Parser.State_Ref.Stack.Peek.State);
+              (Shared_Parser.Table.all, Parser_State.Stack.Peek.State);
          begin
             Parser_State.Errors.Append
               ((Label          => LR.Action,
@@ -193,7 +200,8 @@ package body WisiToken.Parse.LR.Parser is
             if Trace_Parse > Outline then
                Put
                  (Trace,
-                  Integer'Image (Current_Parser.Label) & ": expecting: " &
+                  Integer'Image (Current_Parser.Label) & ":" &
+                    Unknown_State_Index'Image (Parser_State.Stack.Peek.State) 
& ": expecting: " &
                     Image (Expecting, Trace.Descriptor.all));
                Trace.New_Line;
             end if;
@@ -209,7 +217,9 @@ package body WisiToken.Parse.LR.Parser is
    begin
       if Trace_Parse > Extra then
          Shared_Parser.Trace.Put_Line
-           (Integer'Image (Parser_State.Label) & ": recover_insert_delete: " &
+           (Integer'Image (Parser_State.Label) & ": shared_token:" & 
Token_Index'Image (Parser_State.Shared_Token) &
+              " inc_shared_token: " & Boolean'Image 
(Parser_State.Inc_Shared_Token) &
+              " recover_insert_delete: " &
               Image (Parser_State.Recover_Insert_Delete, 
Shared_Parser.Trace.Descriptor.all));
       end if;
 
@@ -221,8 +231,19 @@ package body WisiToken.Parse.LR.Parser is
             then Parser_State.Shared_Token + 1
             else Parser_State.Shared_Token)
          then
-            Parser_State.Shared_Token := Parser_State.Shared_Token + 1;
+            Parser_State.Shared_Token     := Parser_State.Shared_Token + 1;
+            --  We don't reset Inc_Shared_Token here; only after the next 
token is
+            --  actually used.
+            Parser_State.Prev_Deleted.Append 
(Parser_State.Recover_Insert_Delete.Peek.Token_Index);
             Parser_State.Recover_Insert_Delete.Drop;
+
+         elsif Parser_State.Prev_Deleted.Contains
+           ((if Parser_State.Inc_Shared_Token
+             then Parser_State.Shared_Token + 1
+             else Parser_State.Shared_Token))
+         then
+            Parser_State.Shared_Token := Parser_State.Shared_Token + 1;
+
          else
             exit;
          end if;
@@ -236,9 +257,6 @@ package body WisiToken.Parse.LR.Parser is
    --  Shift : some Parsers.Verb return Shift, all with the same current
    --  token in Shared_Parser.Terminals.
    --
-   --  Shift_Recover : some Parsers.Verb return Shift, with current
-   --  tokens virtual (inserted by error recovery).
-   --
    --  Pause : Resume is active, and this parser has reached Resume_Goal,
    --  so it is waiting for the others to catch up.
    --
@@ -254,36 +272,20 @@ package body WisiToken.Parse.LR.Parser is
    is
       use all type SAL.Base_Peek_Type;
 
-      Shift_Count         : SAL.Base_Peek_Type := 0;
-      Shift_Recover_Count : SAL.Base_Peek_Type := 0;
-      Shift_Virtual_Count : SAL.Base_Peek_Type := 0;
-      Accept_Count        : SAL.Base_Peek_Type := 0;
-      Error_Count         : SAL.Base_Peek_Type := 0;
-      Resume_Active       : Boolean            := False;
+      Shift_Count   : SAL.Base_Peek_Type := 0;
+      Accept_Count  : SAL.Base_Peek_Type := 0;
+      Error_Count   : SAL.Base_Peek_Type := 0;
+      Resume_Active : Boolean            := False;
    begin
       Zombie_Count := 0;
 
       for Parser_State of Shared_Parser.Parsers loop
          case Parser_State.Verb is
-         when Pause | Shift_Recover | Shift =>
+         when Pause | Shift =>
             Do_Deletes (Shared_Parser, Parser_State);
 
-            if Parser_State.Recover_Insert_Delete.Length > 0 and then
-              Parser_State.Recover_Insert_Delete.Peek.Op = Insert and then
-              Parser_State.Recover_Insert_Delete.Peek.Token_Index =
-              (if Parser_State.Inc_Shared_Token
-               then Parser_State.Shared_Token + 1
-               else Parser_State.Shared_Token)
-            then
-               --  Shifting a virtual token.
-               Shift_Virtual_Count := Shift_Virtual_Count + 1;
-               Shift_Recover_Count := Shift_Recover_Count + 1;
-               Parser_State.Set_Verb (Shift_Recover);
-
-            else
-               Shift_Count := Shift_Count + 1;
-               Parser_State.Set_Verb (Shift);
-            end if;
+            Shift_Count := Shift_Count + 1;
+            Parser_State.Set_Verb (Shift);
 
             if Parser_State.Resume_Active then
                if Parser_State.Resume_Token_Goal <= Parser_State.Shared_Token 
then
@@ -320,9 +322,6 @@ package body WisiToken.Parse.LR.Parser is
       elsif Shared_Parser.Parsers.Count = Error_Count + Zombie_Count then
          Verb := Error;
 
-      elsif Shift_Recover_Count > 0 then
-         Verb := Shift_Recover;
-
       elsif Shift_Count > 0 then
          Verb := Shift;
 
@@ -332,7 +331,7 @@ package body WisiToken.Parse.LR.Parser is
 
       if Resume_Active then
          for Parser_State of Shared_Parser.Parsers loop
-            if Parser_State.Verb in Shift | Shift_Recover and not 
Parser_State.Resume_Active then
+            if Parser_State.Verb = Shift and not Parser_State.Resume_Active 
then
                Parser_State.Set_Verb (Pause);
             end if;
          end loop;
@@ -397,10 +396,11 @@ package body WisiToken.Parse.LR.Parser is
 
       Trace : WisiToken.Trace'Class renames Shared_Parser.Trace.all;
 
-      Current_Verb   : All_Parse_Action_Verbs;
-      Current_Parser : Parser_Lists.Cursor;
-      Action         : Parse_Action_Node_Ptr;
-      Zombie_Count   : SAL.Base_Peek_Type;
+      Current_Verb    : All_Parse_Action_Verbs;
+      Error_Recovered : Boolean := False;
+      Current_Parser  : Parser_Lists.Cursor;
+      Action          : Parse_Action_Node_Ptr;
+      Zombie_Count    : SAL.Base_Peek_Type;
 
       procedure Check_Error (Check_Parser : in out Parser_Lists.Cursor)
       is begin
@@ -439,7 +439,8 @@ package body WisiToken.Parse.LR.Parser is
                   --  programmer error.
                   if Check_Parser.State_Ref.Conflict_During_Resume then
                      Shared_Parser.Parsers.Terminate_Parser
-                       (Check_Parser, "error in conflict during resume", 
Shared_Parser.Trace.all);
+                       (Check_Parser, "error in conflict during resume", 
Shared_Parser.Trace.all,
+                        Shared_Parser.Terminals);
                   else
                      raise SAL.Programmer_Error with "error during resume";
                   end if;
@@ -451,6 +452,10 @@ package body WisiToken.Parse.LR.Parser is
       end Check_Error;
 
    begin
+      if Debug_Mode then
+         Trace.Put_Clock ("start");
+      end if;
+
       if Shared_Parser.User_Data /= null then
          Shared_Parser.User_Data.Reset;
       end if;
@@ -496,34 +501,6 @@ package body WisiToken.Parse.LR.Parser is
                   end if;
 
                elsif Parser_State.Verb = Shift then
-                  if Parser_State.Inc_Shared_Token then
-                     --  Inc_Shared_Token is only set False by 
McKenzie_Recover; see there
-                     --  for when/why.
-                     Parser_State.Shared_Token := Parser_State.Shared_Token + 
1;
-                  else
-                     Parser_State.Inc_Shared_Token := True;
-                  end if;
-
-                  Parser_State.Current_Token := Parser_State.Tree.Add_Terminal
-                    (Parser_State.Shared_Token, Shared_Parser.Terminals);
-
-                  if Trace_Parse > Extra then
-                     Trace.Put_Line
-                       (Integer'Image (Parser_State.Label) & ": current_token" 
& Parser_State.Tree.Image
-                          (Parser_State.Current_Token, Trace.Descriptor.all));
-                  end if;
-               end if;
-            end loop;
-
-         when Shift_Recover =>
-            --  Same as Shift, except input a token inserted by error 
recovery, or
-            --  input from Shared_Parser.Terminals during error recovery.
-
-            for Parser_State of Shared_Parser.Parsers loop
-               --  We don't check for Verb = Error; during recovery, errors 
cause
-               --  parsers to terminate immediately.
-
-               if Parser_State.Verb = Shift_Recover then
                   if Parser_State.Recover_Insert_Delete.Length > 0 and then
                     Parser_State.Recover_Insert_Delete.Peek.Op = Insert and 
then
                     Parser_State.Recover_Insert_Delete.Peek.Token_Index =
@@ -534,19 +511,14 @@ package body WisiToken.Parse.LR.Parser is
                      Parser_State.Current_Token := 
Parser_State.Tree.Add_Terminal
                        (Parser_State.Recover_Insert_Delete.Get.ID);
 
-                     if Trace_Parse > Extra then
-                        Trace.Put_Line
-                          (Integer'Image (Parser_State.Label) & ": 
current_token" & Parser_State.Tree.Image
-                             (Parser_State.Current_Token, 
Trace.Descriptor.all));
-                     end if;
-
                   elsif (if Parser_State.Inc_Shared_Token
                          then Parser_State.Shared_Token + 1
                          else Parser_State.Shared_Token) <= 
Shared_Parser.Terminals.Last_Index
                   then
                      if Parser_State.Inc_Shared_Token then
-                        --  Inc_Shared_Token is only set False by 
McKenzie_Recover, see there
-                        --  for when/why.
+                        --  Inc_Shared_Token is only set False by 
McKenzie_Recover; see there
+                        --  for when/why. Don't increment past wisi_eoi 
(happens when input
+                        --  buffer is empty; test_mckenzie_recover.adb 
Empty_Comments).
                         Parser_State.Shared_Token := Parser_State.Shared_Token 
+ 1;
                      else
                         Parser_State.Inc_Shared_Token := True;
@@ -555,15 +527,12 @@ package body WisiToken.Parse.LR.Parser is
                      Parser_State.Current_Token := 
Parser_State.Tree.Add_Terminal
                        (Parser_State.Shared_Token, Shared_Parser.Terminals);
 
-                     if Trace_Parse > Extra then
-                        Trace.Put_Line
-                          (Integer'Image (Parser_State.Label) & ": 
current_token" & Parser_State.Tree.Image
-                             (Parser_State.Current_Token, 
Trace.Descriptor.all));
-                     end if;
+                  end if;
 
-                  else
-                     --  Set_Verb set the wrong verb.
-                     raise SAL.Programmer_Error;
+                  if Trace_Parse > Extra then
+                     Trace.Put_Line
+                       (Integer'Image (Parser_State.Label) & ": current_token" 
& Parser_State.Tree.Image
+                          (Parser_State.Current_Token, Trace.Descriptor.all));
                   end if;
                end if;
             end loop;
@@ -593,7 +562,8 @@ package body WisiToken.Parse.LR.Parser is
                      else
                         Temp := Current_Parser;
                         Current_Parser.Next;
-                        Shared_Parser.Parsers.Terminate_Parser (Temp, 
"zombie", Shared_Parser.Trace.all);
+                        Shared_Parser.Parsers.Terminate_Parser
+                          (Temp, "zombie", Shared_Parser.Trace.all, 
Shared_Parser.Terminals);
                      end if;
                      exit when Current_Parser.Is_Done;
                   end loop;
@@ -619,7 +589,8 @@ package body WisiToken.Parse.LR.Parser is
                         else
                            Temp := Current_Parser;
                            Current_Parser.Next;
-                           Shared_Parser.Parsers.Terminate_Parser (Temp, 
"zombie", Shared_Parser.Trace.all);
+                           Shared_Parser.Parsers.Terminate_Parser
+                             (Temp, "zombie", Shared_Parser.Trace.all, 
Shared_Parser.Terminals);
                         end if;
                         exit when Current_Parser.Is_Done;
                      end loop;
@@ -650,15 +621,12 @@ package body WisiToken.Parse.LR.Parser is
                            else
                               Temp := Current_Parser;
                               Current_Parser.Next;
-                              Shared_Parser.Parsers.Terminate_Parser (Temp, 
"errors", Shared_Parser.Trace.all);
+                              Shared_Parser.Parsers.Terminate_Parser
+                                (Temp, "errors", Shared_Parser.Trace.all, 
Shared_Parser.Terminals);
                            end if;
                            exit when Current_Parser.Is_Done;
                         end loop;
 
-                        if Trace_Parse > Outline then
-                           Trace.Put_Line ("ambiguous with error");
-                        end if;
-
                         exit Main_Loop;
 
                      else
@@ -684,7 +652,10 @@ package body WisiToken.Parse.LR.Parser is
             --  All parsers errored; attempt recovery
             declare
                use all type McKenzie_Recover.Recover_Status;
+
                Recover_Result : McKenzie_Recover.Recover_Status := 
McKenzie_Recover.Recover_Status'First;
+
+               Pre_Recover_Parser_Count : constant SAL.Base_Peek_Type := 
Shared_Parser.Parsers.Count;
             begin
                --  Recover algorithms expect current token at
                --  Parsers(*).Current_Token, will set
@@ -696,7 +667,13 @@ package body WisiToken.Parse.LR.Parser is
                   if Trace_Parse > Outline then
                      Trace.Put_Line ("recover");
                   end if;
+                  if Debug_Mode then
+                     Trace.Put_Clock ("pre-recover" & 
Shared_Parser.Parsers.Count'Img & " active");
+                  end if;
                   Recover_Result := McKenzie_Recover.Recover (Shared_Parser);
+                  if Debug_Mode then
+                     Trace.Put_Clock ("post-recover" & 
Shared_Parser.Parsers.Count'Img & " active");
+                  end if;
 
                   if Trace_Parse > Outline then
                      if Recover_Result = Success  then
@@ -712,84 +689,75 @@ package body WisiToken.Parse.LR.Parser is
                   if Ada.Text_IO.Is_Open (Shared_Parser.Recover_Log_File) then
                      declare
                         use Ada.Text_IO;
+                        Strategy_Counts : LR.Strategy_Counts := (others => 0);
                      begin
                         Put
                           (Shared_Parser.Recover_Log_File,
                            Ada.Calendar.Formatting.Image (Ada.Calendar.Clock) 
& " " &
                              McKenzie_Recover.Recover_Status'Image 
(Recover_Result) & " " &
-                             SAL.Base_Peek_Type'Image 
(Shared_Parser.Parsers.Count) & " '" &
+                             SAL.Base_Peek_Type'Image 
(Pre_Recover_Parser_Count) & " '" &
                              Shared_Parser.Lexer.File_Name & "'");
 
+                        Put (Shared_Parser.Recover_Log_File, '(');
                         for Parser of Shared_Parser.Parsers loop
+                           Accumulate (Parser.Recover, Strategy_Counts);
                            Put
                              (Shared_Parser.Recover_Log_File,
                               Integer'Image (Parser.Recover.Enqueue_Count) &
                                 Integer'Image (Parser.Recover.Check_Count) & " 
" &
                                 Boolean'Image (Parser.Recover.Success));
                         end loop;
+                        Put (Shared_Parser.Recover_Log_File, ')');
+
+                        Put (Shared_Parser.Recover_Log_File, Image 
(Strategy_Counts));
                         New_Line (Shared_Parser.Recover_Log_File);
                         Flush (Shared_Parser.Recover_Log_File);
                      end;
                   end if;
                else
                   if Trace_Parse > Outline then
-                     Trace.Put_Line ("recover disabled or not defined");
+                     Trace.Put_Line ("recover disabled");
                   end if;
                end if;
 
                if Recover_Result = Success then
-                  declare
-                     Shift_Recover_Count : Integer := 0;
-                  begin
-                     for Parser_State of Shared_Parser.Parsers loop
-                        Parser_State.Resume_Active          := True;
-                        Parser_State.Conflict_During_Resume := False;
+                  for Parser_State of Shared_Parser.Parsers loop
+                     Parser_State.Resume_Active          := True;
+                     Parser_State.Conflict_During_Resume := False;
 
-                        if Trace_Parse > Outline then
-                           Trace.Put_Line
-                             (Integer'Image (Parser_State.Label) & ": 
Current_Token " &
-                                Parser_State.Tree.Image 
(Parser_State.Current_Token, Trace.Descriptor.all) &
-                                " Shared_Token " & Image
-                                  (Parser_State.Shared_Token, 
Shared_Parser.Terminals, Trace.Descriptor.all));
-
-                           if Trace_Parse > Detail then
-                              Shared_Parser.Trace.Put_Line
-                                (Integer'Image (Parser_State.Label) & ": 
resume_active: True, token goal" &
-                                   Token_Index'Image 
(Parser_State.Resume_Token_Goal));
-                           end if;
+                     if Trace_Parse > Outline then
+                        Trace.Put_Line
+                          (Integer'Image (Parser_State.Label) & ": 
Current_Token " &
+                             Parser_State.Tree.Image 
(Parser_State.Current_Token, Trace.Descriptor.all) &
+                             " Shared_Token " & Image
+                               (Parser_State.Shared_Token, 
Shared_Parser.Terminals, Trace.Descriptor.all));
+
+                        if Trace_Parse > Detail then
+                           Shared_Parser.Trace.Put_Line
+                             (Integer'Image (Parser_State.Label) & ": 
resume_active: True, token goal" &
+                                Token_Index'Image 
(Parser_State.Resume_Token_Goal));
                         end if;
+                     end if;
 
-                        case Parser_State.Verb is
-                        when Shift_Recover =>
-                           Shift_Recover_Count := Shift_Recover_Count + 1;
-
-                           Parser_State.Zombie_Token_Count := 0;
-
-                        when Reduce =>
-                           Current_Verb := Reduce;
-
-                           Parser_State.Zombie_Token_Count := 0;
+                     Parser_State.Zombie_Token_Count := 0;
 
-                        when Error =>
-                           --  Force this parser to be terminated.
-                           if Shared_Parser.Enable_McKenzie_Recover then
-                              Parser_State.Zombie_Token_Count := 
Shared_Parser.Table.McKenzie_Param.Check_Limit + 1;
-                           end if;
+                     case Parser_State.Verb is
+                     when Reduce =>
+                        null;
 
-                        when Shift =>
-                           if Current_Verb /= Reduce then
-                              Current_Verb := Shift;
-                           end if;
+                     when Error =>
+                        --  Force this parser to be terminated.
+                        if Shared_Parser.Enable_McKenzie_Recover then
+                           Parser_State.Zombie_Token_Count := 
Shared_Parser.Table.McKenzie_Param.Check_Limit + 1;
+                        end if;
 
-                        when Pause | Accept_It =>
-                           raise SAL.Programmer_Error;
-                        end case;
-                     end loop;
+                     when Shift =>
+                        null;
 
-                     if Shift_Recover_Count > 0 then
-                        Current_Verb := Shift_Recover;
-                     end if;
-                  end;
+                     when Pause | Accept_It =>
+                        raise SAL.Programmer_Error;
+                     end case;
+                  end loop;
 
                else
                   --  Terminate with error. Parser_State has all the required 
info on
@@ -805,6 +773,12 @@ package body WisiToken.Parse.LR.Parser is
                   end loop;
                   raise WisiToken.Syntax_Error;
                end if;
+
+               --  Immediately execute Do_Action for Current_Token, since it 
changed
+               --  in error recovery; this sets Parser.Verb. This replaces the
+               --  execution of Do_Action that resulted in Error.
+               Error_Recovered := True;
+
             end;
          end case;
 
@@ -816,11 +790,16 @@ package body WisiToken.Parse.LR.Parser is
          loop
             exit Action_Loop when Current_Parser.Is_Done;
 
-            if Shared_Parser.Terminate_Same_State and
-              Current_Verb in Shift | Shift_Recover and
+            --  We don't check duplicate state during resume, because the 
tokens
+            --  inserted/deleted by error recover may cause initially duplicate
+            --  states to diverge.
+            if not Current_Parser.State_Ref.Resume_Active and
+              Shared_Parser.Terminate_Same_State and
+              Current_Verb = Shift and
               (for all Parser of Shared_Parser.Parsers => 
Parser.Recover_Insert_Delete.Count = 0)
             then
-               Shared_Parser.Parsers.Duplicate_State (Current_Parser, 
Shared_Parser.Trace.all);
+               Shared_Parser.Parsers.Duplicate_State
+                 (Current_Parser, Shared_Parser.Trace.all, 
Shared_Parser.Terminals);
                --  If Duplicate_State terminated Current_Parser, 
Current_Parser now
                --  points to the next parser. Otherwise it is unchanged.
             end if;
@@ -828,10 +807,14 @@ package body WisiToken.Parse.LR.Parser is
             exit Action_Loop when Current_Parser.Is_Done;
 
             if Trace_Parse > Extra then
-               Trace.Put_Line
-                 ("current_verb: " & Parse_Action_Verbs'Image (Current_Verb) &
-                    "," & Integer'Image (Current_Parser.Label) &
-                    ".verb: " & Parse_Action_Verbs'Image 
(Current_Parser.Verb));
+               if Error_Recovered then
+                  Trace.Put_Line (Integer'Image (Current_Parser.Label) & 
".error_recovered");
+               else
+                  Trace.Put_Line
+                    ("current_verb: " & Parse_Action_Verbs'Image 
(Current_Verb) &
+                       "," & Integer'Image (Current_Parser.Label) &
+                       ".verb: " & Parse_Action_Verbs'Image 
(Current_Parser.Verb));
+               end if;
             end if;
 
             --  Each branch of the following 'if' calls either 
Current_Parser.Free
@@ -850,10 +833,12 @@ package body WisiToken.Parse.LR.Parser is
 
                   Current_Parser.Next;
                else
-                  Shared_Parser.Parsers.Terminate_Parser (Current_Parser, 
"zombie", Shared_Parser.Trace.all);
+                  Shared_Parser.Parsers.Terminate_Parser
+                    (Current_Parser, "zombie", Shared_Parser.Trace.all, 
Shared_Parser.Terminals);
                end if;
 
-            elsif Current_Parser.Verb = Current_Verb then
+            elsif Current_Parser.Verb = Current_Verb or Error_Recovered then
+
                if Trace_Parse > Extra then
                   Parser_Lists.Put_Top_10 (Trace, Current_Parser);
                end if;
@@ -895,11 +880,13 @@ package body WisiToken.Parse.LR.Parser is
                            if Max_Parser = Current_Parser then
                               Current_Parser.Next;
                               Shared_Parser.Parsers.Terminate_Parser
-                                (Current_Parser, "too many parsers; max error 
repair cost", Trace);
+                                (Current_Parser, "too many parsers; max error 
repair cost", Trace,
+                                 Shared_Parser.Terminals);
                               exit Action_Loop;
                            else
                               Shared_Parser.Parsers.Terminate_Parser
-                                (Max_Parser, "too many parsers; max error 
repair cost", Trace);
+                                (Max_Parser, "too many parsers; max error 
repair cost", Trace,
+                                 Shared_Parser.Terminals);
                            end if;
                         end if;
                      end;
@@ -920,10 +907,16 @@ package body WisiToken.Parse.LR.Parser is
 
                   else
                      if Trace_Parse > Outline then
-                        Trace.Put_Line
-                          (Integer'Image (Current_Parser.Label) & ": spawn" &
-                             Integer'Image (Shared_Parser.Parsers.Last_Label + 
1) & ", (" &
-                             Trimmed_Image (1 + Integer 
(Shared_Parser.Parsers.Count)) & " active)");
+                        declare
+                           Parser_State : Parser_Lists.Parser_State renames 
Current_Parser.State_Ref;
+                        begin
+                           Trace.Put_Line
+                             (Integer'Image (Current_Parser.Label) & ": " &
+                                Trimmed_Image (Parser_State.Stack.Peek.State) 
& ": " &
+                                Parser_State.Tree.Image 
(Parser_State.Current_Token, Trace.Descriptor.all) & " : " &
+                                "spawn" & Integer'Image 
(Shared_Parser.Parsers.Last_Label + 1) & ", (" &
+                                Trimmed_Image (1 + Integer 
(Shared_Parser.Parsers.Count)) & " active)");
+                        end;
                      end if;
 
                      Shared_Parser.Parsers.Prepend_Copy (Current_Parser);
@@ -947,37 +940,62 @@ package body WisiToken.Parse.LR.Parser is
                Current_Parser.Next;
             end if;
          end loop Action_Loop;
+         Error_Recovered := False;
       end loop Main_Loop;
+      if Debug_Mode then
+         Trace.Put_Clock ("finish");
+      end if;
 
       --  We don't raise Syntax_Error for lexer errors, since they are all
       --  recovered, either by inserting a quote, or by ignoring the
       --  character.
    exception
-   when Syntax_Error | WisiToken.Parse_Error =>
+   when Syntax_Error | WisiToken.Parse_Error | Partial_Parse =>
+      if Debug_Mode then
+         Trace.Put_Clock ("finish - error");
+      end if;
       raise;
 
    when E : others =>
       declare
          Msg : constant String := Ada.Exceptions.Exception_Name (E) & ": " & 
Ada.Exceptions.Exception_Message (E);
       begin
-         --  Emacs displays errors in the *syntax-errors* buffer
-         Shared_Parser.Parsers.First_State_Ref.Errors.Append
-           ((Label          => LR.Message,
-             First_Terminal => Trace.Descriptor.First_Terminal,
-             Last_Terminal  => Trace.Descriptor.Last_Terminal,
-             Recover        => <>,
-             Msg            => +Msg));
+         if Shared_Parser.Parsers.Count > 0 then
+            --  Emacs displays errors in the *syntax-errors* buffer
+            Shared_Parser.Parsers.First_State_Ref.Errors.Append
+              ((Label          => LR.Message,
+                First_Terminal => Trace.Descriptor.First_Terminal,
+                Last_Terminal  => Trace.Descriptor.Last_Terminal,
+                Recover        => <>,
+                Msg            => +Msg));
+         end if;
+
+         if Debug_Mode then
+            Ada.Text_IO.Put_Line (GNAT.Traceback.Symbolic.Symbolic_Traceback 
(E));
+         end if;
 
          --  Emacs displays the exception message in the echo area; easy to 
miss
          raise WisiToken.Parse_Error with Msg;
       end;
    end Parse;
 
+   overriding function Tree (Shared_Parser : in Parser) return 
Syntax_Trees.Tree
+   is
+      use all type SAL.Base_Peek_Type;
+   begin
+      if Shared_Parser.Parsers.Count > 1 then
+         raise WisiToken.Parse_Error with "ambigous parse";
+      else
+         return Shared_Parser.Parsers.First_State_Ref.Tree;
+      end if;
+   end Tree;
+
    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;
 
       Descriptor : WisiToken.Descriptor renames Parser.Trace.Descriptor.all;
 
@@ -992,11 +1010,9 @@ package body WisiToken.Parse.LR.Parser is
          end if;
 
          declare
-            use all type Syntax_Trees.Semantic_Action;
             Tree_Children : constant Syntax_Trees.Valid_Node_Index_Array := 
Tree.Children (Node);
          begin
             Parser.User_Data.Reduce (Tree, Node, Tree_Children);
-
             if Tree.Action (Node) /= null then
                Tree.Action (Node) (Parser.User_Data.all, Tree, Node, 
Tree_Children);
             end if;
@@ -1005,33 +1021,34 @@ package body WisiToken.Parse.LR.Parser is
 
    begin
       if Parser.User_Data /= null then
-         if (for some Par of Parser.Parsers =>
-               (for some Err of Par.Errors => Any (Err.Recover.Ops, Delete)))
-         then
-            if Parser.Parsers.Count > 1 then
-               raise Syntax_Error with "ambiguous parse with deleted tokens; 
can't execute actions";
-            end if;
-            for Err of Parser.Parsers.First_State_Ref.Errors loop
-               for Op of Err.Recover.Ops loop
-                  case Op.Op is
-                  when Delete =>
-                     Parser.User_Data.Delete_Token (Op.Token_Index);
-                  when others =>
-                     null;
-                  end case;
-               end loop;
-            end loop;
+         if Parser.Parsers.Count > 1 then
+            raise Syntax_Error with "ambiguous parse; can't execute actions";
          end if;
 
-         for Parser_State of Parser.Parsers loop
+         declare
+            Parser_State : Parser_Lists.Parser_State renames 
Parser.Parsers.First_State_Ref.Element.all;
+         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
+                     case Op.Op is
+                     when Delete =>
+                        Parser.User_Data.Delete_Token (Op.Token_Index);
+                     when others =>
+                        null;
+                     end case;
+                  end loop;
+               end loop;
             end if;
 
             Parser_State.Tree.Process_Tree (Process_Node'Access);
-         end loop;
+         end;
       end if;
    end Execute_Actions;
 
diff --git a/wisitoken-parse-lr-parser.ads b/wisitoken-parse-lr-parser.ads
index f5b7405..3324165 100644
--- a/wisitoken-parse-lr-parser.ads
+++ b/wisitoken-parse-lr-parser.ads
@@ -5,7 +5,7 @@
 --  In a child package of Parser.LR partly for historical reasons,
 --  partly to allow McKenzie_Recover to be in a sibling package.
 --
---  Copyright (C) 2002, 2003, 2009, 2010, 2013-2015, 2017, 2018 Free Software 
Foundation, Inc.
+--  Copyright (C) 2002, 2003, 2009, 2010, 2013-2015, 2017 - 2019 Free Software 
Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -53,14 +53,19 @@ package WisiToken.Parse.LR.Parser is
    --  For an Error action, Config.Error_Token gives the terminal that
    --  caused the error.
 
-   type Language_Use_Minimal_Complete_Actions_Access is access function
-     (Next_Token : in Token_ID;
-      Config     : in Configuration)
-     return Boolean;
-   --  Return True if using Minimal_Complete_Actions is appropriate.
+   type Language_Use_Minimal_Complete_Actions_Access is access procedure
+     (Current_Token        : in     Token_ID;
+      Next_Token           : in     Token_ID;
+      Config               : in     Configuration;
+      Use_Complete         :    out Boolean;
+      Matching_Begin_Token :    out Token_ID);
+   --  Set Use_Complete True if using Minimal_Complete_Actions is
+   --  appropriate. Set Matching_Begin_Token to token that starts a production
+   --  matching Next_Token (and following tokens, if any).
    --
    --  For example, if Next_Token is a block end, return True to complete
-   --  the current statement/declaration as quickly as possible..
+   --  the current statement/declaration as quickly as possible, and
+   --  Matching_Begin_Token to the corresponding block begin.
 
    type Language_String_ID_Set_Access is access function
      (Descriptor        : in WisiToken.Descriptor;
@@ -131,8 +136,14 @@ package WisiToken.Parse.LR.Parser is
    --  For errors where no recovery is possible, raises Parse_Error with
    --  an appropriate error message.
 
+   overriding function Tree (Shared_Parser : in Parser) return 
Syntax_Trees.Tree;
+   --  If there is one parser in Parsers, return its tree. Otherwise,
+   --  raise Parse_Error for an ambiguous parse.
+
    overriding procedure Execute_Actions (Parser : in out LR.Parser.Parser);
-   --  Execute the grammar actions in 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.
 
    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 b2ba8be..590af07 100644
--- a/wisitoken-parse-lr-parser_lists.adb
+++ b/wisitoken-parse-lr-parser_lists.adb
@@ -2,7 +2,7 @@
 --
 --  see spec
 --
---  Copyright (C) 2014-2018  All Rights Reserved.
+--  Copyright (C) 2014 - 2019  All Rights Reserved.
 --
 --  The WisiToken package is free software; you can redistribute it
 --  and/or modify it under terms of the GNU General Public License as
@@ -140,19 +140,24 @@ package body WisiToken.Parse.LR.Parser_Lists is
    is begin
       return Parser_State_Lists.Constant_Reference (Cursor.Ptr).Verb;
    end Verb;
+
    procedure Terminate_Parser
-     (Parsers : in out List;
-      Current : in out Cursor'Class;
-      Message : in     String;
-      Trace   : in out WisiToken.Trace'Class)
+     (Parsers   : in out List;
+      Current   : in out Cursor'Class;
+      Message   : in     String;
+      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;
    begin
       if Trace_Parse > Outline then
          Trace.Put_Line
            (Integer'Image (Current.Label) & ": terminate (" &
               Trimmed_Image (Integer (Parsers.Count) - 1) & " active)" &
-              (if Message'Length > 0 then ": " & Message else ""));
+              ": " & Message & Image
+                (State.Tree.Min_Terminal_Index (State.Current_Token),
+                 Terminals, Trace.Descriptor.all));
       end if;
 
       Current.Free;
@@ -163,9 +168,10 @@ package body WisiToken.Parse.LR.Parser_Lists is
    end Terminate_Parser;
 
    procedure Duplicate_State
-     (Parsers : in out List;
-      Current : in out Cursor'Class;
-      Trace   : in out WisiToken.Trace'Class)
+     (Parsers   : in out List;
+      Current   : in out Cursor'Class;
+      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;
@@ -225,7 +231,7 @@ package body WisiToken.Parse.LR.Parser_Lists is
          --  terminated earlier.
          if Other.Total_Recover_Cost = Current.Total_Recover_Cost then
             if Other.Max_Recover_Ops_Length = Current.Max_Recover_Ops_Length 
then
-               Parsers.Terminate_Parser (Other, "duplicate state: random", 
Trace);
+               Parsers.Terminate_Parser (Other, "duplicate state: random", 
Trace, Terminals);
             else
                if Other.Max_Recover_Ops_Length > 
Current.Max_Recover_Ops_Length then
                   null;
@@ -233,7 +239,7 @@ package body WisiToken.Parse.LR.Parser_Lists is
                   Other := Cursor (Current);
                   Current.Next;
                end if;
-               Parsers.Terminate_Parser (Other, "duplicate state: ops length", 
Trace);
+               Parsers.Terminate_Parser (Other, "duplicate state: ops length", 
Trace, Terminals);
             end if;
          else
             if Other.Total_Recover_Cost > Current.Total_Recover_Cost then
@@ -242,7 +248,7 @@ package body WisiToken.Parse.LR.Parser_Lists is
                Other := Cursor (Current);
                Current.Next;
             end if;
-            Parsers.Terminate_Parser (Other, "duplicate state: cost", Trace);
+            Parsers.Terminate_Parser (Other, "duplicate state: cost", Trace, 
Terminals);
          end if;
       end if;
    end Duplicate_State;
@@ -290,6 +296,7 @@ package body WisiToken.Parse.LR.Parser_Lists is
          New_Item :=
            (Shared_Token           => Item.Shared_Token,
             Recover_Insert_Delete  => Item.Recover_Insert_Delete,
+            Prev_Deleted           => Item.Prev_Deleted,
             Current_Token          => Item.Current_Token,
             Inc_Shared_Token       => Item.Inc_Shared_Token,
             Stack                  => Item.Stack,
diff --git a/wisitoken-parse-lr-parser_lists.ads 
b/wisitoken-parse-lr-parser_lists.ads
index dcaa268..0dae578 100644
--- a/wisitoken-parse-lr-parser_lists.ads
+++ b/wisitoken-parse-lr-parser_lists.ads
@@ -2,7 +2,7 @@
 --
 --  Generalized LR parser state.
 --
---  Copyright (C) 2014-2015, 2017, 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2014-2015, 2017 - 2019 Free Software Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -60,6 +60,11 @@ package WisiToken.Parse.LR.Parser_Lists is
       Recover_Insert_Delete : Config_Op_Queues.Queue;
       --  Tokens in that were inserted during error recovery, or should be
       --  deleted/skipped when read. Contains only Insert and Delete ops.
+      --  Used/emptied by main parse.
+
+      Prev_Deleted : Recover_Token_Index_Arrays.Vector;
+      --  Tokens deleted by previous error recovery; don't process in new
+      --  error recovery.
 
       Current_Token : Syntax_Trees.Node_Index := 
Syntax_Trees.Invalid_Node_Index;
       --  Current terminal, in Tree
@@ -131,19 +136,25 @@ package WisiToken.Parse.LR.Parser_Lists is
    function Verb (Cursor : in Parser_Lists.Cursor) return 
All_Parse_Action_Verbs;
 
    procedure Terminate_Parser
-     (Parsers : in out List;
-      Current : in out Cursor'Class;
-      Message : in     String;
-      Trace   : in out WisiToken.Trace'Class);
+     (Parsers   : in out List;
+      Current   : in out Cursor'Class;
+      Message   : in     String;
+      Trace     : in out WisiToken.Trace'Class;
+      Terminals : in     Base_Token_Arrays.Vector);
    --  Terminate Current. Current is set to no element.
+   --
+   --  Terminals is used to report the current token in the message.
 
    procedure Duplicate_State
-     (Parsers : in out List;
-      Current : in out Cursor'Class;
-      Trace   : in out WisiToken.Trace'Class);
+     (Parsers   : in out List;
+      Current   : in out Cursor'Class;
+      Trace     : in out WisiToken.Trace'Class;
+      Terminals : in     Base_Token_Arrays.Vector);
    --  If any other parser in Parsers has a stack equivalent to Current,
    --  Terminate one of them. Current is either unchanged, or advanced to
    --  the next parser.
+   --
+   --  Terminals is used to report the current token in the message.
 
    type State_Reference (Element : not null access Parser_State) is null record
    with Implicit_Dereference => Element;
@@ -212,11 +223,13 @@ package WisiToken.Parse.LR.Parser_Lists is
      (Container : aliased in List'Class;
       Position  :         in Parser_Node_Access)
      return Constant_Reference_Type;
+   pragma Inline (Constant_Reference);
 
    function Reference
      (Container : aliased in out List'Class;
       Position  :         in     Parser_Node_Access)
      return State_Reference;
+   pragma Inline (Reference);
 
    function Persistent_State_Ref (Position : in Parser_Node_Access) return 
State_Access;
 
diff --git a/wisitoken-parse-lr-parser_no_recover.adb 
b/wisitoken-parse-lr-parser_no_recover.adb
index 48d9e4f..d8ce31e 100644
--- a/wisitoken-parse-lr-parser_no_recover.adb
+++ b/wisitoken-parse-lr-parser_no_recover.adb
@@ -1,491 +1,511 @@
---  Abstract :
---
---  See spec.
---
---  Copyright (C) 2002 - 2005, 2008 - 2015, 2017, 2018 Free Software 
Foundation, Inc.
---
---  This file is part of the WisiToken package.
---
---  The WisiToken package 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, or
---  (at your option) any later version. The WisiToken package 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 the WisiToken package;
---  see file GPL.txt. 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
---  this unit, or you link this unit with other files to produce an
---  executable, this unit does not by itself cause the resulting
---  executable to be covered by the GNU General Public License. This
---  exception does not however invalidate any other reasons why the
---  executable file might be covered by the GNU Public License.
-
-pragma License (Modified_GPL);
-
-package body WisiToken.Parse.LR.Parser_No_Recover is
-
-   procedure Reduce_Stack_1
-     (Current_Parser : in     Parser_Lists.Cursor;
-      Action         : in     Reduce_Action_Rec;
-      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.
-   begin
-      for I in reverse Children_Tree'Range loop
-         Children_Tree (I) := Parser_State.Stack.Pop.Token;
-      end loop;
-
-      Nonterm := Parser_State.Tree.Add_Nonterm
-        (Action.Production, Children_Tree, Action.Action, Default_Virtual => 
False);
-      --  Computes Nonterm.Byte_Region
-
-      if Trace_Parse > Detail then
-         Trace.Put_Line (Parser_State.Tree.Image (Nonterm, 
Trace.Descriptor.all, Include_Children => True));
-      end if;
-   end Reduce_Stack_1;
-
-   procedure Do_Action
-     (Action         : in Parse_Action_Rec;
-      Current_Parser : in Parser_Lists.Cursor;
-      Shared_Parser  : in Parser)
-   is
-      Parser_State : Parser_Lists.Parser_State renames 
Current_Parser.State_Ref;
-      Trace        : WisiToken.Trace'Class renames Shared_Parser.Trace.all;
-      Nonterm      : WisiToken.Syntax_Trees.Valid_Node_Index;
-   begin
-      if Trace_Parse > Detail then
-         Trace.Put
-           (Integer'Image (Current_Parser.Label) & ": " &
-              Trimmed_Image (Parser_State.Stack.Peek.State) & ": " &
-              Parser_State.Tree.Image (Parser_State.Current_Token, 
Trace.Descriptor.all) & " : ");
-         Put (Trace, Action);
-         Trace.New_Line;
-      end if;
-
-      case Action.Verb is
-      when Shift =>
-         Current_Parser.Set_Verb (Shift);
-         Parser_State.Stack.Push ((Action.State, Parser_State.Current_Token));
-         Parser_State.Tree.Set_State (Parser_State.Current_Token, 
Action.State);
-
-      when Reduce =>
-         Current_Parser.Set_Verb (Reduce);
-
-         Reduce_Stack_1 (Current_Parser, Action, Nonterm, Trace);
-
-         Parser_State.Stack.Push
-           ((State    => Goto_For
-               (Table => Shared_Parser.Table.all,
-                State => Parser_State.Stack (1).State,
-                ID    => Action.Production.LHS),
-             Token    => Nonterm));
-
-         Parser_State.Tree.Set_State (Nonterm, Parser_State.Stack (1).State);
-
-         if Trace_Parse > Detail then
-            Trace.Put_Line (" ... goto state " & Trimmed_Image 
(Parser_State.Stack.Peek.State));
-         end if;
-
-      when Accept_It =>
-         Current_Parser.Set_Verb (Accept_It);
-         Reduce_Stack_1
-           (Current_Parser,
-            (Reduce, Action.Production, Action.Action, Action.Check, 
Action.Token_Count),
-            Nonterm, Trace);
-
-         Parser_State.Tree.Set_Root (Nonterm);
-
-      when Error =>
-         Current_Parser.Set_Verb (Action.Verb);
-
-         --  We don't raise Syntax_Error here; another parser may be able to
-         --  continue.
-
-         declare
-            Expecting : constant Token_ID_Set := LR.Expecting
-              (Shared_Parser.Table.all, 
Current_Parser.State_Ref.Stack.Peek.State);
-         begin
-            Parser_State.Errors.Append
-              ((Label          => LR.Action,
-                First_Terminal => Trace.Descriptor.First_Terminal,
-                Last_Terminal  => Trace.Descriptor.Last_Terminal,
-                Error_Token    => Parser_State.Current_Token,
-                Expecting      => Expecting,
-                Recover        => (others => <>)));
-
-            if Trace_Parse > Outline then
-               Put
-                 (Trace,
-                  Integer'Image (Current_Parser.Label) & ": expecting: " &
-                    Image (Expecting, Trace.Descriptor.all));
-               Trace.New_Line;
-            end if;
-         end;
-      end case;
-   end Do_Action;
-
-   --  Return the type of parser cycle to execute.
-   --
-   --  Accept : all Parsers.Verb return Accept - done parsing.
-   --
-   --  Shift : some Parsers.Verb return Shift.
-   --
-   --  Reduce : some Parsers.Verb return Reduce.
-   --
-   --  Error : all Parsers.Verb return Error.
-   procedure Parse_Verb
-     (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;
-   begin
-      for Parser_State of Shared_Parser.Parsers loop
-         case Parser_State.Verb is
-         when Shift =>
-            Shift_Count := Shift_Count + 1;
-
-         when Reduce =>
-            Verb := Reduce;
-            return;
-
-         when Accept_It =>
-            Accept_Count := Accept_Count + 1;
-
-         when Error =>
-            Error_Count := Error_Count + 1;
-
-         when Pause | Shift_Recover =>
-            --  This is parser_no_recover
-            raise SAL.Programmer_Error;
-         end case;
-      end loop;
-
-      if Shared_Parser.Parsers.Count = Accept_Count then
-         Verb := Accept_It;
-
-      elsif Shared_Parser.Parsers.Count = Error_Count then
-         Verb := Error;
-
-      elsif Shift_Count > 0 then
-         Verb := Shift;
-
-      else
-         raise SAL.Programmer_Error;
-      end if;
-   end Parse_Verb;
-
-   ----------
-   --  Public subprograms, declaration order
-
-   overriding procedure Finalize (Object : in out Parser)
-   is begin
-      Free_Table (Object.Table);
-   end Finalize;
-
-   procedure New_Parser
-     (Parser               :    out          LR.Parser_No_Recover.Parser;
-      Trace                : not null access WisiToken.Trace'Class;
-      Lexer                : in              WisiToken.Lexer.Handle;
-      Table                : in              Parse_Table_Ptr;
-      User_Data            : in              
WisiToken.Syntax_Trees.User_Data_Access;
-      Max_Parallel         : in              SAL.Base_Peek_Type := 
Default_Max_Parallel;
-      First_Parser_Label   : in              Integer            := 1;
-      Terminate_Same_State : in              Boolean            := True)
-   is
-      use all type Syntax_Trees.User_Data_Access;
-   begin
-      Parser.Lexer                := Lexer;
-      Parser.Trace                := Trace;
-      Parser.Table                := Table;
-      Parser.User_Data            := User_Data;
-      Parser.Max_Parallel         := Max_Parallel;
-      Parser.First_Parser_Label   := First_Parser_Label;
-      Parser.Terminate_Same_State := Terminate_Same_State;
-
-      if User_Data /= null then
-         User_Data.Set_Lexer_Terminals (Lexer, 
Parser.Terminals'Unchecked_Access);
-      end if;
-   end New_Parser;
-
-   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;
-
-      Current_Verb   : All_Parse_Action_Verbs;
-      Current_Parser : Parser_Lists.Cursor;
-      Action         : Parse_Action_Node_Ptr;
-
-      procedure Check_Error (Check_Parser : in out Parser_Lists.Cursor)
-      is begin
-         if Check_Parser.Verb = Error then
-            --  This parser errored on last input. This is how grammar 
conflicts
-            --  are resolved when the input text is valid, so we terminate this
-            --  parser.
-
-            if Shared_Parser.Parsers.Count = 1 then
-               raise Syntax_Error;
-            else
-               Shared_Parser.Parsers.Terminate_Parser (Check_Parser, "", 
Shared_Parser.Trace.all);
-            end if;
-         else
-            Check_Parser.Next;
-         end if;
-      end Check_Error;
-
-   begin
-      --  The user must call Lexer.Reset_* to set the input text.
-      Shared_Parser.Lex_All;
-
-      if Shared_Parser.User_Data /= null then
-         Shared_Parser.User_Data.Reset;
-      end if;
-      Shared_Parser.Shared_Tree.Clear;
-
-      Shared_Parser.Parsers := Parser_Lists.New_List
-        (Shared_Tree => Shared_Parser.Shared_Tree'Unchecked_Access);
-
-      Shared_Parser.Parsers.First.State_Ref.Stack.Push 
((Shared_Parser.Table.State_First, others => <>));
-
-      Main_Loop :
-      loop
-         --  exit on Accept_It action or syntax error.
-
-         Parse_Verb (Shared_Parser, Current_Verb);
-
-         case Current_Verb is
-         when Shift =>
-            --  All parsers just shifted a token; get the next token
-
-            for Parser_State of Shared_Parser.Parsers loop
-               Parser_State.Shared_Token  := Parser_State.Shared_Token + 1;
-               Parser_State.Current_Token := Parser_State.Tree.Add_Terminal
-                 (Parser_State.Shared_Token, Shared_Parser.Terminals);
-            end loop;
-
-         when Accept_It =>
-            --  All parsers accepted.
-            declare
-               Count : constant SAL.Base_Peek_Type := 
Shared_Parser.Parsers.Count;
-            begin
-               if Count = 1 then
-                  --  Nothing more to do
-                  if Trace_Parse > Outline then
-                     Trace.Put_Line (Integer'Image 
(Shared_Parser.Parsers.First.Label) & ": succeed");
-                  end if;
-                  exit Main_Loop;
-
-               else
-                  --  More than one parser is active; ambiguous parse.
-                  declare
-                     Token : Base_Token renames Shared_Parser.Terminals 
(Shared_Parser.Terminals.Last_Index);
-                  begin
-                     raise WisiToken.Parse_Error with Error_Message
-                       (Shared_Parser.Lexer.File_Name, Token.Line, 
Token.Column,
-                        "Ambiguous parse:" & SAL.Base_Peek_Type'Image (Count) 
& " parsers active.");
-                  end;
-               end if;
-            end;
-
-         when Reduce =>
-            null;
-
-         when Error =>
-            --  All parsers errored; terminate with error. Semantic_State has 
all
-            --  the required info (recorded by Error in Do_Action), so we just
-            --  raise the exception.
-            raise Syntax_Error;
-
-         when Pause | Shift_Recover =>
-            --  This is parser_no_recover
-            raise SAL.Programmer_Error;
-         end case;
-
-         --  We don't use 'for Parser_State of Parsers loop' here,
-         --  because terminate on error and spawn on conflict require
-         --  changing the parser list.
-         Current_Parser := Shared_Parser.Parsers.First;
-         loop
-            exit when Current_Parser.Is_Done;
-
-            if Shared_Parser.Terminate_Same_State and
-              Current_Verb = Shift
-            then
-               Shared_Parser.Parsers.Duplicate_State (Current_Parser, 
Shared_Parser.Trace.all);
-               --  If Duplicate_State terminated Current_Parser, 
Current_Parser now
-               --  points to the next parser. Otherwise it is unchanged.
-            end if;
-
-            exit when Current_Parser.Is_Done;
-
-            if Trace_Parse > Extra then
-               Trace.Put_Line
-                 ("current_verb: " & Parse_Action_Verbs'Image (Current_Verb) &
-                    "," & Integer'Image (Current_Parser.Label) &
-                    ".verb: " & Parse_Action_Verbs'Image 
(Current_Parser.Verb));
-            end if;
-
-            --  Each branch of the following 'if' calls either 
Current_Parser.Free
-            --  (which advances to the next parser) or Current_Parser.Next.
-
-            if Current_Parser.Verb = Current_Verb then
-               if Trace_Parse > Extra then
-                  Parser_Lists.Put_Top_10 (Trace, Current_Parser);
-               end if;
-
-               declare
-                  State : Parser_Lists.Parser_State renames 
Current_Parser.State_Ref.Element.all;
-               begin
-                  Action := Action_For
-                    (Table => Shared_Parser.Table.all,
-                     State => State.Stack.Peek.State,
-                     ID    => State.Tree.ID (State.Current_Token));
-               end;
-
-               if Action.Next /= null then
-                  --  Conflict; spawn a new parser (before modifying 
Current_Parser
-                  --  stack).
-
-                  if Shared_Parser.Parsers.Count = Shared_Parser.Max_Parallel 
then
-                     declare
-                        Parser_State : Parser_Lists.Parser_State renames 
Current_Parser.State_Ref;
-                        Token : Base_Token renames Shared_Parser.Terminals 
(Parser_State.Shared_Token);
-                     begin
-                        raise WisiToken.Parse_Error with Error_Message
-                          (Shared_Parser.Lexer.File_Name, Token.Line, 
Token.Column,
-                           ": too many parallel parsers required in grammar 
state" &
-                             State_Index'Image (Parser_State.Stack.Peek.State) 
&
-                             "; simplify grammar, or increase max-parallel (" &
-                             SAL.Base_Peek_Type'Image 
(Shared_Parser.Max_Parallel) & ")");
-                     end;
-                  else
-                     if Trace_Parse > Outline then
-                        Trace.Put_Line
-                          ("spawn parser from " & Trimmed_Image 
(Current_Parser.Label) &
-                             " (" & Trimmed_Image (1 + Integer 
(Shared_Parser.Parsers.Count)) & " active)");
-                     end if;
-
-                     Shared_Parser.Parsers.Prepend_Copy (Current_Parser);
-                     Do_Action (Action.Next.Item, Shared_Parser.Parsers.First, 
Shared_Parser);
-
-                     declare
-                        Temp : Parser_Lists.Cursor := 
Shared_Parser.Parsers.First;
-                     begin
-                        Check_Error (Temp);
-                     end;
-                  end if;
-               end if;
-
-               Do_Action (Action.Item, Current_Parser, Shared_Parser);
-               Check_Error (Current_Parser);
-
-            else
-               --  Current parser is waiting for others to catch up
-               Current_Parser.Next;
-            end if;
-         end loop;
-      end loop Main_Loop;
-
-      --  We don't raise Syntax_Error for lexer errors, since they are all
-      --  recovered, either by inserting a quote, or by ignoring the
-      --  character.
-   end Parse;
-
-   overriding procedure Execute_Actions (Parser : in out 
LR.Parser_No_Recover.Parser)
-   is
-      use all type Syntax_Trees.User_Data_Access;
-
-      procedure Process_Node
-        (Tree : in out Syntax_Trees.Tree;
-         Node : in     Syntax_Trees.Valid_Node_Index)
-      is
-         use all type Syntax_Trees.Node_Label;
-      begin
-         if Tree.Label (Node) /= Nonterm then
-            return;
-         end if;
-
-         declare
-            use all type Syntax_Trees.Semantic_Action;
-            Tree_Children : constant Syntax_Trees.Valid_Node_Index_Array := 
Tree.Children (Node);
-         begin
-            Parser.User_Data.Reduce (Tree, Node, Tree_Children);
-
-            if Tree.Action (Node) /= null then
-               Tree.Action (Node) (Parser.User_Data.all, Tree, Node, 
Tree_Children);
-            end if;
-         end;
-      end Process_Node;
-
-   begin
-      if Parser.User_Data /= null then
-         for Parser_State of Parser.Parsers loop
-            Parser_State.Tree.Process_Tree (Process_Node'Access);
-         end loop;
-      end if;
-   end Execute_Actions;
-
-   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
-      pragma Assert (Parser_State.Tree.Flushed);
-      return Parser.Parsers.Count > 1 or Parser_State.Errors.Length > 0 or 
Parser.Lexer.Errors.Length > 0;
-   end Any_Errors;
-
-   overriding procedure Put_Errors (Parser : in LR.Parser_No_Recover.Parser)
-   is
-      use Ada.Text_IO;
-
-      Parser_State : Parser_Lists.Parser_State renames 
Parser.Parsers.First_Constant_State_Ref;
-      Descriptor   : WisiToken.Descriptor renames Parser.Trace.Descriptor.all;
-   begin
-      for Item of Parser.Lexer.Errors loop
-         Put_Line
-           (Current_Error,
-            Parser.Lexer.File_Name & ":0:0: lexer unrecognized character at" & 
Buffer_Pos'Image (Item.Char_Pos));
-      end loop;
-
-      for Item of Parser_State.Errors loop
-         case Item.Label is
-         when Action =>
-            declare
-               Token : Base_Token renames Parser.Terminals 
(Parser_State.Tree.Min_Terminal_Index (Item.Error_Token));
-            begin
-               Put_Line
-                 (Current_Error,
-                  Error_Message
-                    (Parser.Lexer.File_Name, Token.Line, Token.Column,
-                     "syntax error: expecting " & Image (Item.Expecting, 
Descriptor) &
-                       ", found '" & Parser.Lexer.Buffer_Text 
(Token.Byte_Region) & "'"));
-            end;
-
-         when Check =>
-            null;
-
-         when Message =>
-            Put_Line (Current_Error, -Item.Msg);
-         end case;
-
-      end loop;
-   end Put_Errors;
-
-end WisiToken.Parse.LR.Parser_No_Recover;
+--  Abstract :
+--
+--  See spec.
+--
+--  Copyright (C) 2002 - 2005, 2008 - 2015, 2017 - 2019 Free Software 
Foundation, Inc.
+--
+--  This file is part of the WisiToken package.
+--
+--  The WisiToken package 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, or
+--  (at your option) any later version. The WisiToken package 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 the WisiToken package;
+--  see file GPL.txt. 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
+--  this unit, or you link this unit with other files to produce an
+--  executable, this unit does not by itself cause the resulting
+--  executable to be covered by the GNU General Public License. This
+--  exception does not however invalidate any other reasons why the
+--  executable file might be covered by the GNU Public License.
+
+pragma License (Modified_GPL);
+
+package body WisiToken.Parse.LR.Parser_No_Recover is
+
+   procedure Reduce_Stack_1
+     (Current_Parser : in     Parser_Lists.Cursor;
+      Action         : in     Reduce_Action_Rec;
+      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.
+   begin
+      for I in reverse Children_Tree'Range loop
+         Children_Tree (I) := Parser_State.Stack.Pop.Token;
+      end loop;
+
+      Nonterm := Parser_State.Tree.Add_Nonterm
+        (Action.Production, Children_Tree, Action.Action, Default_Virtual => 
False);
+      --  Computes Nonterm.Byte_Region
+
+      if Trace_Parse > Detail then
+         Trace.Put_Line (Parser_State.Tree.Image (Nonterm, 
Trace.Descriptor.all, Include_Children => True));
+      end if;
+   end Reduce_Stack_1;
+
+   procedure Do_Action
+     (Action         : in Parse_Action_Rec;
+      Current_Parser : in Parser_Lists.Cursor;
+      Shared_Parser  : in Parser)
+   is
+      Parser_State : Parser_Lists.Parser_State renames 
Current_Parser.State_Ref;
+      Trace        : WisiToken.Trace'Class renames Shared_Parser.Trace.all;
+      Nonterm      : WisiToken.Syntax_Trees.Valid_Node_Index;
+   begin
+      if Trace_Parse > Detail then
+         Trace.Put
+           (Integer'Image (Current_Parser.Label) & ": " &
+              Trimmed_Image (Parser_State.Stack.Peek.State) & ": " &
+              Parser_State.Tree.Image (Parser_State.Current_Token, 
Trace.Descriptor.all) & " : ");
+         Put (Trace, Action);
+         Trace.New_Line;
+      end if;
+
+      case Action.Verb is
+      when Shift =>
+         Current_Parser.Set_Verb (Shift);
+         Parser_State.Stack.Push ((Action.State, Parser_State.Current_Token));
+         Parser_State.Tree.Set_State (Parser_State.Current_Token, 
Action.State);
+
+      when Reduce =>
+         Current_Parser.Set_Verb (Reduce);
+
+         Reduce_Stack_1 (Current_Parser, Action, Nonterm, Trace);
+
+         Parser_State.Stack.Push
+           ((State    => Goto_For
+               (Table => Shared_Parser.Table.all,
+                State => Parser_State.Stack (1).State,
+                ID    => Action.Production.LHS),
+             Token    => Nonterm));
+
+         Parser_State.Tree.Set_State (Nonterm, Parser_State.Stack (1).State);
+
+         if Trace_Parse > Detail then
+            Trace.Put_Line (" ... goto state " & Trimmed_Image 
(Parser_State.Stack.Peek.State));
+         end if;
+
+      when Accept_It =>
+         Current_Parser.Set_Verb (Accept_It);
+         Reduce_Stack_1
+           (Current_Parser,
+            (Reduce, Action.Production, Action.Action, Action.Check, 
Action.Token_Count),
+            Nonterm, Trace);
+
+         Parser_State.Tree.Set_Root (Nonterm);
+
+      when Error =>
+         Current_Parser.Set_Verb (Action.Verb);
+
+         --  We don't raise Syntax_Error here; another parser may be able to
+         --  continue.
+
+         declare
+            Expecting : constant Token_ID_Set := LR.Expecting
+              (Shared_Parser.Table.all, 
Current_Parser.State_Ref.Stack.Peek.State);
+         begin
+            Parser_State.Errors.Append
+              ((Label          => LR.Action,
+                First_Terminal => Trace.Descriptor.First_Terminal,
+                Last_Terminal  => Trace.Descriptor.Last_Terminal,
+                Error_Token    => Parser_State.Current_Token,
+                Expecting      => Expecting,
+                Recover        => (others => <>)));
+
+            if Trace_Parse > Outline then
+               Put
+                 (Trace,
+                  Integer'Image (Current_Parser.Label) & ": expecting: " &
+                    Image (Expecting, Trace.Descriptor.all));
+               Trace.New_Line;
+            end if;
+         end;
+      end case;
+   end Do_Action;
+
+   --  Return the type of parser cycle to execute.
+   --
+   --  Accept : all Parsers.Verb return Accept - done parsing.
+   --
+   --  Shift : some Parsers.Verb return Shift.
+   --
+   --  Reduce : some Parsers.Verb return Reduce.
+   --
+   --  Error : all Parsers.Verb return Error.
+   procedure Parse_Verb
+     (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;
+   begin
+      for Parser_State of Shared_Parser.Parsers loop
+         case Parser_State.Verb is
+         when Shift =>
+            Shift_Count := Shift_Count + 1;
+
+         when Reduce =>
+            Verb := Reduce;
+            return;
+
+         when Accept_It =>
+            Accept_Count := Accept_Count + 1;
+
+         when Error =>
+            Error_Count := Error_Count + 1;
+
+         when Pause =>
+            --  This is parser_no_recover
+            raise SAL.Programmer_Error;
+         end case;
+      end loop;
+
+      if Shared_Parser.Parsers.Count = Accept_Count then
+         Verb := Accept_It;
+
+      elsif Shared_Parser.Parsers.Count = Error_Count then
+         Verb := Error;
+
+      elsif Shift_Count > 0 then
+         Verb := Shift;
+
+      else
+         raise SAL.Programmer_Error;
+      end if;
+   end Parse_Verb;
+
+   ----------
+   --  Public subprograms, declaration order
+
+   overriding procedure Finalize (Object : in out Parser)
+   is begin
+      Free_Table (Object.Table);
+   end Finalize;
+
+   procedure New_Parser
+     (Parser               :    out          LR.Parser_No_Recover.Parser;
+      Trace                : not null access WisiToken.Trace'Class;
+      Lexer                : in              WisiToken.Lexer.Handle;
+      Table                : in              Parse_Table_Ptr;
+      User_Data            : in              
WisiToken.Syntax_Trees.User_Data_Access;
+      Max_Parallel         : in              SAL.Base_Peek_Type := 
Default_Max_Parallel;
+      First_Parser_Label   : in              Integer            := 1;
+      Terminate_Same_State : in              Boolean            := True)
+   is
+      use all type Syntax_Trees.User_Data_Access;
+   begin
+      Parser.Lexer                := Lexer;
+      Parser.Trace                := Trace;
+      Parser.Table                := Table;
+      Parser.User_Data            := User_Data;
+      Parser.Max_Parallel         := Max_Parallel;
+      Parser.First_Parser_Label   := First_Parser_Label;
+      Parser.Terminate_Same_State := Terminate_Same_State;
+
+      if User_Data /= null then
+         User_Data.Set_Lexer_Terminals (Lexer, 
Parser.Terminals'Unchecked_Access);
+      end if;
+   end New_Parser;
+
+   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;
+
+      Current_Verb   : All_Parse_Action_Verbs;
+      Current_Parser : Parser_Lists.Cursor;
+      Action         : Parse_Action_Node_Ptr;
+
+      procedure Check_Error (Check_Parser : in out Parser_Lists.Cursor)
+      is begin
+         if Check_Parser.Verb = Error then
+            --  This parser errored on last input. This is how grammar 
conflicts
+            --  are resolved when the input text is valid, so we terminate this
+            --  parser.
+
+            if Shared_Parser.Parsers.Count = 1 then
+               raise Syntax_Error;
+            else
+               Shared_Parser.Parsers.Terminate_Parser
+                 (Check_Parser, "", Shared_Parser.Trace.all, 
Shared_Parser.Terminals);
+            end if;
+         else
+            Check_Parser.Next;
+         end if;
+      end Check_Error;
+
+   begin
+      if Shared_Parser.User_Data /= null then
+         Shared_Parser.User_Data.Reset;
+      end if;
+
+      Shared_Parser.Lex_All;
+
+      Shared_Parser.Shared_Tree.Clear;
+
+      Shared_Parser.Parsers := Parser_Lists.New_List
+        (Shared_Tree => Shared_Parser.Shared_Tree'Unchecked_Access);
+
+      Shared_Parser.Parsers.First.State_Ref.Stack.Push 
((Shared_Parser.Table.State_First, others => <>));
+
+      Main_Loop :
+      loop
+         --  exit on Accept_It action or syntax error.
+
+         Parse_Verb (Shared_Parser, Current_Verb);
+
+         case Current_Verb is
+         when Shift =>
+            --  All parsers just shifted a token; get the next token
+
+            for Parser_State of Shared_Parser.Parsers loop
+               Parser_State.Shared_Token  := Parser_State.Shared_Token + 1;
+               Parser_State.Current_Token := Parser_State.Tree.Add_Terminal
+                 (Parser_State.Shared_Token, Shared_Parser.Terminals);
+            end loop;
+
+         when Accept_It =>
+            --  All parsers accepted.
+            declare
+               Count : constant SAL.Base_Peek_Type := 
Shared_Parser.Parsers.Count;
+            begin
+               if Count = 1 then
+                  --  Nothing more to do
+                  if Trace_Parse > Outline then
+                     Trace.Put_Line (Integer'Image 
(Shared_Parser.Parsers.First.Label) & ": succeed");
+                  end if;
+                  exit Main_Loop;
+
+               else
+                  --  More than one parser is active; ambiguous parse.
+                  declare
+                     Token : Base_Token renames Shared_Parser.Terminals 
(Shared_Parser.Terminals.Last_Index);
+                  begin
+                     raise WisiToken.Parse_Error with Error_Message
+                       (Shared_Parser.Lexer.File_Name, Token.Line, 
Token.Column,
+                        "Ambiguous parse:" & SAL.Base_Peek_Type'Image (Count) 
& " parsers active.");
+                  end;
+               end if;
+            end;
+
+         when Reduce =>
+            null;
+
+         when Error =>
+            --  All parsers errored; terminate with error. Semantic_State has 
all
+            --  the required info (recorded by Error in Do_Action), so we just
+            --  raise the exception.
+            raise Syntax_Error;
+
+         when Pause =>
+            --  This is parser_no_recover
+            raise SAL.Programmer_Error;
+         end case;
+
+         --  We don't use 'for Parser_State of Parsers loop' here,
+         --  because terminate on error and spawn on conflict require
+         --  changing the parser list.
+         Current_Parser := Shared_Parser.Parsers.First;
+         loop
+            exit when Current_Parser.Is_Done;
+
+            if Shared_Parser.Terminate_Same_State and
+              Current_Verb = Shift
+            then
+               Shared_Parser.Parsers.Duplicate_State
+                 (Current_Parser, Shared_Parser.Trace.all, 
Shared_Parser.Terminals);
+               --  If Duplicate_State terminated Current_Parser, 
Current_Parser now
+               --  points to the next parser. Otherwise it is unchanged.
+            end if;
+
+            exit when Current_Parser.Is_Done;
+
+            if Trace_Parse > Extra then
+               Trace.Put_Line
+                 ("current_verb: " & Parse_Action_Verbs'Image (Current_Verb) &
+                    "," & Integer'Image (Current_Parser.Label) &
+                    ".verb: " & Parse_Action_Verbs'Image 
(Current_Parser.Verb));
+            end if;
+
+            --  Each branch of the following 'if' calls either 
Current_Parser.Free
+            --  (which advances to the next parser) or Current_Parser.Next.
+
+            if Current_Parser.Verb = Current_Verb then
+               if Trace_Parse > Extra then
+                  Parser_Lists.Put_Top_10 (Trace, Current_Parser);
+               end if;
+
+               declare
+                  State : Parser_Lists.Parser_State renames 
Current_Parser.State_Ref.Element.all;
+               begin
+                  Action := Action_For
+                    (Table => Shared_Parser.Table.all,
+                     State => State.Stack.Peek.State,
+                     ID    => State.Tree.ID (State.Current_Token));
+               end;
+
+               if Action.Next /= null then
+                  --  Conflict; spawn a new parser (before modifying 
Current_Parser
+                  --  stack).
+
+                  if Shared_Parser.Parsers.Count = Shared_Parser.Max_Parallel 
then
+                     declare
+                        Parser_State : Parser_Lists.Parser_State renames 
Current_Parser.State_Ref;
+                        Token : Base_Token renames Shared_Parser.Terminals 
(Parser_State.Shared_Token);
+                     begin
+                        raise WisiToken.Parse_Error with Error_Message
+                          (Shared_Parser.Lexer.File_Name, Token.Line, 
Token.Column,
+                           ": too many parallel parsers required in grammar 
state" &
+                             State_Index'Image (Parser_State.Stack.Peek.State) 
&
+                             "; simplify grammar, or increase max-parallel (" &
+                             SAL.Base_Peek_Type'Image 
(Shared_Parser.Max_Parallel) & ")");
+                     end;
+                  else
+                     if Trace_Parse > Outline then
+                        Trace.Put_Line
+                          ("spawn parser from " & Trimmed_Image 
(Current_Parser.Label) &
+                             " (" & Trimmed_Image (1 + Integer 
(Shared_Parser.Parsers.Count)) & " active)");
+                     end if;
+
+                     Shared_Parser.Parsers.Prepend_Copy (Current_Parser);
+                     Do_Action (Action.Next.Item, Shared_Parser.Parsers.First, 
Shared_Parser);
+
+                     declare
+                        Temp : Parser_Lists.Cursor := 
Shared_Parser.Parsers.First;
+                     begin
+                        Check_Error (Temp);
+                     end;
+                  end if;
+               end if;
+
+               Do_Action (Action.Item, Current_Parser, Shared_Parser);
+               Check_Error (Current_Parser);
+
+            else
+               --  Current parser is waiting for others to catch up
+               Current_Parser.Next;
+            end if;
+         end loop;
+      end loop Main_Loop;
+
+      --  We don't raise Syntax_Error for lexer errors, since they are all
+      --  recovered, either by inserting a quote, or by ignoring the
+      --  character.
+   end Parse;
+
+   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
+        (Tree : in out Syntax_Trees.Tree;
+         Node : in     Syntax_Trees.Valid_Node_Index)
+      is
+         use all type Syntax_Trees.Node_Label;
+      begin
+         if Tree.Label (Node) /= Nonterm then
+            return;
+         end if;
+
+         declare
+            use all type Syntax_Trees.Semantic_Action;
+            Tree_Children : constant Syntax_Trees.Valid_Node_Index_Array := 
Tree.Children (Node);
+         begin
+            Parser.User_Data.Reduce (Tree, Node, Tree_Children);
+
+            if Tree.Action (Node) /= null then
+               Tree.Action (Node) (Parser.User_Data.all, Tree, Node, 
Tree_Children);
+            end if;
+         end;
+      end Process_Node;
+
+   begin
+      if Parser.User_Data /= null then
+         if Parser.Parsers.Count > 1 then
+            raise Syntax_Error with "ambiguous parse; can't execute actions";
+         end if;
+
+         declare
+            Parser_State : Parser_Lists.Parser_State renames 
Parser.Parsers.First_State_Ref.Element.all;
+         begin
+            Parser_State.Tree.Process_Tree (Process_Node'Access);
+         end;
+      end if;
+   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
+      if Parser.Parsers.Count > 1 then
+         raise WisiToken.Parse_Error with "ambigous parse";
+      else
+         return Parser.Parsers.First_State_Ref.Tree;
+      end if;
+   end Tree;
+
+   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
+      pragma Assert (Parser_State.Tree.Flushed);
+      return Parser.Parsers.Count > 1 or Parser_State.Errors.Length > 0 or 
Parser.Lexer.Errors.Length > 0;
+   end Any_Errors;
+
+   overriding procedure Put_Errors (Parser : in LR.Parser_No_Recover.Parser)
+   is
+      use Ada.Text_IO;
+
+      Parser_State : Parser_Lists.Parser_State renames 
Parser.Parsers.First_Constant_State_Ref;
+      Descriptor   : WisiToken.Descriptor renames Parser.Trace.Descriptor.all;
+   begin
+      for Item of Parser.Lexer.Errors loop
+         Put_Line
+           (Current_Error,
+            Parser.Lexer.File_Name & ":0:0: lexer unrecognized character at" & 
Buffer_Pos'Image (Item.Char_Pos));
+      end loop;
+
+      for Item of Parser_State.Errors loop
+         case Item.Label is
+         when Action =>
+            declare
+               Token : Base_Token renames Parser.Terminals 
(Parser_State.Tree.Min_Terminal_Index (Item.Error_Token));
+            begin
+               Put_Line
+                 (Current_Error,
+                  Error_Message
+                    (Parser.Lexer.File_Name, Token.Line, Token.Column,
+                     "syntax error: expecting " & Image (Item.Expecting, 
Descriptor) &
+                       ", found '" & Parser.Lexer.Buffer_Text 
(Token.Byte_Region) & "'"));
+            end;
+
+         when Check =>
+            null;
+
+         when Message =>
+            Put_Line (Current_Error, -Item.Msg);
+         end case;
+
+      end loop;
+   end Put_Errors;
+
+end WisiToken.Parse.LR.Parser_No_Recover;
diff --git a/wisitoken-parse-lr-parser_no_recover.ads 
b/wisitoken-parse-lr-parser_no_recover.ads
index 1f91e3d..33665d7 100644
--- a/wisitoken-parse-lr-parser_no_recover.ads
+++ b/wisitoken-parse-lr-parser_no_recover.ads
@@ -6,7 +6,7 @@
 --  to not depend on wisitoken-lr-mckenzie_recover, so editing that
 --  does not cause everything to be regenerated/compiled.
 --
---  Copyright (C) 2002, 2003, 2009, 2010, 2013-2015, 2017, 2018 Free Software 
Foundation, Inc.
+--  Copyright (C) 2002, 2003, 2009, 2010, 2013 - 2015, 2017 - 2019 Free 
Software Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -71,6 +71,8 @@ package WisiToken.Parse.LR.Parser_No_Recover is
    --  For other errors, raises Parse_Error with an appropriate error
    --  message.
 
+   overriding function Tree (Parser : in LR.Parser_No_Recover.Parser) return 
Syntax_Trees.Tree;
+
    overriding function Any_Errors (Parser : in LR.Parser_No_Recover.Parser) 
return Boolean;
 
    overriding procedure Put_Errors (Parser : in LR.Parser_No_Recover.Parser);
diff --git a/wisitoken-parse-lr.adb b/wisitoken-parse-lr.adb
index 74dcb98..79f4f2e 100644
--- a/wisitoken-parse-lr.adb
+++ b/wisitoken-parse-lr.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  Copyright (C) 2013-2015, 2017, 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2013-2015, 2017, 2018, 2019 Free Software Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -29,7 +29,9 @@ pragma License (GPL);
 
 with Ada.Exceptions;
 with Ada.Strings.Maps;
+with Ada.Strings.Fixed;
 with Ada.Text_IO;
+with GNATCOLL.Mmap;
 package body WisiToken.Parse.LR is
 
    ----------
@@ -59,16 +61,17 @@ package body WisiToken.Parse.LR is
    begin
       case Item.Verb is
       when Shift =>
-         Trace.Put ("shift and goto state" & State_Index'Image (Item.State));
+         Trace.Put ("shift and goto state" & State_Index'Image (Item.State), 
Prefix => False);
 
       when Reduce =>
          Trace.Put
            ("reduce" & Count_Type'Image (Item.Token_Count) & " tokens to " &
-              Image (Item.Production.LHS, Trace.Descriptor.all));
+              Image (Item.Production.LHS, Trace.Descriptor.all),
+            Prefix => False);
       when Accept_It =>
-         Trace.Put ("accept it");
+         Trace.Put ("accept it", Prefix => False);
       when Error =>
-         Trace.Put ("ERROR");
+         Trace.Put ("ERROR", Prefix => False);
       end case;
    end Put;
 
@@ -137,37 +140,11 @@ package body WisiToken.Parse.LR is
       return List.Next;
    end Next;
 
-   function Compare_Minimal_Action (Left, Right : in Minimal_Action) return 
SAL.Compare_Result
-   is begin
-      if Left.Verb > Right.Verb then
-         return SAL.Greater;
-      elsif Left.Verb < Right.Verb then
-         return SAL.Less;
-      else
-         case Left.Verb is
-         when Shift =>
-            if Left.ID > Right.ID then
-               return SAL.Greater;
-            elsif Left.ID < Right.ID then
-               return SAL.Less;
-            else
-               return SAL.Equal;
-            end if;
-         when Reduce =>
-            if Left.Nonterm > Right.Nonterm then
-               return SAL.Greater;
-            elsif Left.Nonterm < Right.Nonterm then
-               return SAL.Less;
-            else
-               return SAL.Equal;
-            end if;
-         end case;
-      end if;
-   end Compare_Minimal_Action;
-
    function Strict_Image (Item : in Minimal_Action) return String
    is begin
       case Item.Verb is
+      when Pause =>
+         return "(Verb => Shift)";
       when Shift =>
          return "(Shift," & Token_ID'Image (Item.ID) & "," & State_Index'Image 
(Item.State) & ")";
       when Reduce =>
@@ -176,13 +153,6 @@ package body WisiToken.Parse.LR is
       end case;
    end Strict_Image;
 
-   procedure Set_Minimal_Action (List : out Minimal_Action_Lists.List; Actions 
: in Minimal_Action_Array)
-   is begin
-      for Action of Actions loop
-         List.Insert (Action);
-      end loop;
-   end Set_Minimal_Action;
-
    function First (State : in Parse_State) return Action_List_Iterator
    is begin
       return Iter : Action_List_Iterator := (Node => State.Action_List, Item 
=> null) do
@@ -552,66 +522,103 @@ package body WisiToken.Parse.LR is
      return Parse_Table_Ptr
    is
       use Ada.Text_IO;
-      use Ada.Strings.Unbounded;
 
-      File  : File_Type;
-      Line  : Unbounded_String;
-      First : Integer;
-      Last  : Integer := 0;
+      File            : GNATCOLL.Mmap.Mapped_File;
+      Region          : GNATCOLL.Mmap.Mapped_Region;
+      Buffer          : GNATCOLL.Mmap.Str_Access;
+      Buffer_Abs_Last : Integer; --  Buffer'Last, except Buffer has no bounds
+      Buffer_Last     : Integer := 0; -- Last char read from Buffer
 
-      Delimiters : constant Ada.Strings.Maps.Character_Set := 
Ada.Strings.Maps.To_Set (" ;");
+      Delimiters : constant Ada.Strings.Maps.Character_Set := 
Ada.Strings.Maps.To_Set (" ;" & ASCII.LF);
 
-      function Last_Char return Character
+      function Check_Semicolon return Boolean
       is begin
-         if Last = 0 then
-            return Element (Line, Last + 1);
+         if Buffer (Buffer_Last) = ';' then
+            --  There is a space, newline, or newline and space after ';'. 
Leave
+            --  Buffer_Last on newline for Check_New_Line.
+            Buffer_Last := Buffer_Last + 1;
+            return True;
          else
-            return Element (Line, Last);
+            return False;
          end if;
-      end Last_Char;
+      end Check_Semicolon;
 
-      procedure Skip_Char
+      procedure Check_Semicolon
       is begin
-         if Last > 0 then
-            Last := Last + 1;
-            if Last > Length (Line) then
-               Last := 0;
-            end if;
-         end if;
-         if Last = 0 then
-            Line := +Get_Line (File);
-            Last := -1 + Index_Non_Blank (Line);
+         if Buffer (Buffer_Last) = ';' then
+            --  There is a space, newline, or newline and space after ';'. 
Leave
+            --  Buffer_Last on newline for Check_New_Line.
+            Buffer_Last := Buffer_Last + 1;
+         else
+            raise SAL.Programmer_Error with Error_Message
+              (File_Name, 1, Ada.Text_IO.Count (Buffer_Last),
+               "expecting semicolon, found '" & Buffer (Buffer_Last) & "'");
          end if;
-      end Skip_Char;
+      end Check_Semicolon;
 
-      function Next_Value return String
+      function Check_EOI return Boolean
       is begin
-         First := Last + 1;
-         Last  := Index (Line, Delimiters, First);
-         return Result : constant String := Slice (Line, First, (if Last = 0 
then Length (Line) else Last - 1))
-         do
-            if Last = 0 then
-               Line := +Get_Line (File);
-               Last := -1 + Index_Non_Blank (Line);
+         return Buffer_Last >= Buffer_Abs_Last;
+      end Check_EOI;
+
+      procedure Check_New_Line
+      is
+         use Ada.Strings.Maps;
+      begin
+         if Buffer (Buffer_Last) = ASCII.LF then
+            --  There is a space or semicolon after some newlines.
+            if Is_In (Buffer (Buffer_Last + 1), Delimiters) then
+               Buffer_Last := Buffer_Last + 1;
             end if;
-         end return;
+         else
+            raise SAL.Programmer_Error with Error_Message
+              (File_Name, 1, Ada.Text_IO.Count (Buffer_Last),
+               "expecting new_line, found '" & Buffer (Buffer_Last) & "'");
+         end if;
+      end Check_New_Line;
+
+      type Buffer_Region is record
+         First : Integer;
+         Last  : Integer;
+      end record;
+
+      function Next_Value return Buffer_Region;
+      pragma Inline (Next_Value);
+
+      function Next_Value return Buffer_Region
+      is
+         use Ada.Strings.Fixed;
+         First : constant Integer := Buffer_Last + 1;
+      begin
+         Buffer_Last := Index (Buffer.all, Delimiters, First);
+         return (First, Buffer_Last - 1);
       end Next_Value;
 
+      procedure Raise_Gen_Next_Value_Constraint_Error (Name : String; Region : 
Buffer_Region);
+      pragma No_Return (Raise_Gen_Next_Value_Constraint_Error);
+
+      procedure Raise_Gen_Next_Value_Constraint_Error (Name : String; Region : 
Buffer_Region)
+      is begin
+         --  Factored out from Gen_Next_Value to make Inline efficient.
+         raise SAL.Programmer_Error with Error_Message
+           (File_Name, 1, Ada.Text_IO.Count (Region.First),
+            "expecting " & Name & ", found '" & Buffer (Region.First .. 
Region.Last) & "'");
+      end Raise_Gen_Next_Value_Constraint_Error;
+
       generic
          type Value_Type is (<>);
          Name : in String;
       function Gen_Next_Value return Value_Type;
+      pragma Inline (Gen_Next_Value);
 
       function Gen_Next_Value return Value_Type
       is
-         Val : constant String := Next_Value;
+         Region : constant Buffer_Region := Next_Value;
       begin
-         return Value_Type'Value (Val);
+         return Value_Type'Value (Buffer (Region.First .. Region.Last));
       exception
       when Constraint_Error =>
-         raise SAL.Programmer_Error with Error_Message
-           (File_Name, Line_Number_Type (Ada.Text_IO.Line (File) - 1), 
Ada.Text_IO.Count (First),
-            "expecting " & Name & ", found '" & Val & "'");
+         Raise_Gen_Next_Value_Constraint_Error (Name, Region);
       end Gen_Next_Value;
 
       function Next_State_Index is new Gen_Next_Value (State_Index, 
"State_Index");
@@ -621,8 +628,10 @@ package body WisiToken.Parse.LR is
       function Next_Boolean is new Gen_Next_Value (Boolean, "Boolean");
       function Next_Count_Type is new Gen_Next_Value 
(Ada.Containers.Count_Type, "Count_Type");
    begin
-      Open (File, In_File, File_Name);
-      Line := +Get_Line (File);
+      File            := GNATCOLL.Mmap.Open_Read (File_Name);
+      Region          := GNATCOLL.Mmap.Read (File);
+      Buffer          := GNATCOLL.Mmap.Data (Region);
+      Buffer_Abs_Last := GNATCOLL.Mmap.Last (Region);
 
       declare
          --  We don't read the discriminants in the aggregate, because
@@ -637,6 +646,8 @@ package body WisiToken.Parse.LR is
          Table : constant Parse_Table_Ptr := new Parse_Table
            (State_First, State_Last, First_Terminal, Last_Terminal, 
First_Nonterminal, Last_Nonterminal);
       begin
+         Check_New_Line;
+
          Table.McKenzie_Param := McKenzie_Param;
 
          for State of Table.States loop
@@ -646,6 +657,7 @@ package body WisiToken.Parse.LR is
                State.Productions (I).LHS := Next_Token_ID;
                State.Productions (I).RHS := Next_Integer;
             end loop;
+            Check_New_Line;
 
             declare
                Node_I       : Action_Node_Ptr := new Action_Node;
@@ -691,8 +703,7 @@ package body WisiToken.Parse.LR is
                            Actions_Done := True;
                         end case;
 
-                        if Element (Line, Last) = ';' then
-                           Skip_Char;
+                        if Check_Semicolon then
                            Action_Done := True;
 
                            if not Actions_Done then
@@ -705,6 +716,8 @@ package body WisiToken.Parse.LR is
                         Node_J.Next := new Parse_Action_Node;
                         Node_J      := Node_J.Next;
                      end loop;
+
+                     Check_New_Line;
                   end;
 
                   exit when Actions_Done;
@@ -713,9 +726,9 @@ package body WisiToken.Parse.LR is
                end loop;
             end;
 
-            if Element (Line, 1) = ';' then
+            if Check_Semicolon then
                --  No Gotos
-               Skip_Char;
+               null;
             else
                declare
                   Node_I : Goto_Node_Ptr := new Goto_Node;
@@ -724,46 +737,44 @@ package body WisiToken.Parse.LR is
                   loop
                      Node_I.Symbol := Next_Token_ID;
                      Node_I.State  := Next_State_Index;
-                     exit when Element (Line, Last) = ';';
+                     exit when Check_Semicolon;
                      Node_I.Next   := new Goto_Node;
                      Node_I        := Node_I.Next;
                   end loop;
-                  Skip_Char;
                end;
             end if;
+            Check_New_Line;
 
-            declare
-               Verb         : Minimal_Verbs;
-               ID           : Token_ID;
-               Action_State : State_Index;
-               Count        : Ada.Containers.Count_Type;
-            begin
-               loop
-                  if Last_Char = ';' then
-                     Skip_Char;
-                     exit;
-                  end if;
-
-                  Verb := Next_Parse_Action_Verbs;
+            if Check_Semicolon then
+               --  No minimal action
+               null;
+            else
+               declare
+                  Verb         : constant Minimal_Verbs := 
Next_Parse_Action_Verbs;
+                  ID           : Token_ID;
+                  Action_State : State_Index;
+                  Count        : Ada.Containers.Count_Type;
+               begin
                   case Verb is
+                  when Pause =>
+                     null; --  Generate.LR.Put_Text_Rep does not output this
+
                   when Shift =>
                      ID           := Next_Token_ID;
                      Action_State := Next_State_Index;
-                     State.Minimal_Complete_Actions.Insert ((Shift, ID, 
Action_State));
+                     State.Minimal_Complete_Action := (Shift, ID, 
Action_State);
                   when Reduce =>
                      ID    := Next_Token_ID;
                      Count := Next_Count_Type;
-                     State.Minimal_Complete_Actions.Insert ((Reduce, ID, 
Count));
+                     State.Minimal_Complete_Action := (Reduce, ID, Count);
                   end case;
-               end loop;
-            end;
-            --  loop exits on End_Error
+               end;
+               Check_Semicolon;
+            end if;
+            Check_New_Line;
+
+            exit when Check_EOI;
          end loop;
-         --  real return value in End_Error handler; this satisfies the 
compiler
-         return null;
-      exception
-      when End_Error =>
-         Close (File);
          return Table;
       end;
    exception
@@ -771,16 +782,11 @@ package body WisiToken.Parse.LR is
       raise User_Error with "parser table text file '" & File_Name & "' not 
found.";
 
    when SAL.Programmer_Error =>
-      if Is_Open (File) then
-         Close (File);
-      end if;
       raise;
+
    when E : others =>
-      if Is_Open (File) then
-         Close (File);
-      end if;
       raise SAL.Programmer_Error with Error_Message
-        (File_Name, Line_Number_Type (Ada.Text_IO.Line (File) - 1), 
Ada.Text_IO.Count (First),
+        (File_Name, 1, Ada.Text_IO.Count (Buffer_Last),
          Ada.Exceptions.Exception_Name (E) & ": " & 
Ada.Exceptions.Exception_Message (E));
    end Get_Text_Rep;
 
@@ -834,6 +840,18 @@ package body WisiToken.Parse.LR is
       Item.Cost := Key;
    end Set_Key;
 
+   procedure Accumulate (Data : in McKenzie_Data; Counts : in out 
Strategy_Counts)
+   is
+      procedure Proc (Config : in Configuration)
+      is begin
+         for I in Config.Strategy_Counts'Range loop
+            Counts (I) := Counts (I) + Config.Strategy_Counts (I);
+         end loop;
+      end Proc;
+   begin
+      Data.Results.Process (Proc'Unrestricted_Access);
+   end Accumulate;
+
    function Image
      (Item       : in Parse_Error;
       Tree       : in Syntax_Trees.Tree;
diff --git a/wisitoken-parse-lr.ads b/wisitoken-parse-lr.ads
index 6c9db82..24ca512 100644
--- a/wisitoken-parse-lr.ads
+++ b/wisitoken-parse-lr.ads
@@ -9,7 +9,7 @@
 --
 --  See wisitoken.ads
 --
---  Copyright (C) 2002, 2003, 2009, 2010, 2013-2015, 2017 - 2018 Free Software 
Foundation, Inc.
+--  Copyright (C) 2002, 2003, 2009, 2010, 2013 - 2015, 2017 - 2019 Free 
Software Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -36,9 +36,9 @@ pragma License (Modified_GPL);
 
 with Ada.Containers.Indefinite_Doubly_Linked_Lists;
 with Ada.Unchecked_Deallocation;
-with SAL.Gen_Bounded_Definite_Vectors.Gen_Sorted;
+with SAL.Gen_Array_Image;
 with SAL.Gen_Bounded_Definite_Vectors.Gen_Image_Aux;
-with SAL.Gen_Definite_Doubly_Linked_Lists_Sorted.Gen_Image;
+with SAL.Gen_Bounded_Definite_Vectors.Gen_Sorted;
 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;
@@ -48,10 +48,10 @@ with WisiToken.Semantic_Checks;
 with WisiToken.Syntax_Trees;
 package WisiToken.Parse.LR is
 
-   type All_Parse_Action_Verbs is (Pause, Shift_Recover, Shift, Reduce, 
Accept_It, Error);
+   type All_Parse_Action_Verbs is (Pause, Shift, Reduce, Accept_It, Error);
    subtype Parse_Action_Verbs is All_Parse_Action_Verbs range Shift .. Error;
-   subtype Minimal_Verbs is All_Parse_Action_Verbs range Shift .. Reduce;
-   --  Pause, Shift_Recover are only used for error recovery.
+   subtype Minimal_Verbs is All_Parse_Action_Verbs range Pause .. Reduce;
+   --  Pause is only used for error recovery.
 
    type Parse_Action_Rec (Verb : Parse_Action_Verbs := Shift) is record
       case Verb is
@@ -79,7 +79,7 @@ package WisiToken.Parse.LR is
    --  Ada aggregate syntax, leaving out Action, Check in reduce; for debug 
output
 
    procedure Put (Trace : in out WisiToken.Trace'Class; Item : in 
Parse_Action_Rec);
-   --  Put a line for Item in parse trace format.
+   --  Put a line for Item in parse trace format, with no prefix.
 
    function Equal (Left, Right : in Parse_Action_Rec) return Boolean;
    --  Ignore Action, Check.
@@ -116,8 +116,12 @@ package WisiToken.Parse.LR is
    function State (List : in Goto_Node_Ptr) return State_Index;
    function Next (List : in Goto_Node_Ptr) return Goto_Node_Ptr;
 
-   type Minimal_Action (Verb : Minimal_Verbs := Shift) is record
+   type Minimal_Action (Verb : Minimal_Verbs := Pause) is record
       case Verb is
+      when Pause =>
+         --  In this case, 'Pause' means no minimal action.
+         null;
+
       when Shift =>
          ID    : Token_ID;
          State : State_Index;
@@ -128,29 +132,18 @@ package WisiToken.Parse.LR is
       end case;
    end record;
 
-   function Compare_Minimal_Action (Left, Right : in Minimal_Action) return 
SAL.Compare_Result;
-
-   type Minimal_Action_Array is array (Positive range <>) of Minimal_Action;
-
-   package Minimal_Action_Lists is new 
SAL.Gen_Definite_Doubly_Linked_Lists_Sorted
-     (Minimal_Action, Compare_Minimal_Action);
-
    function Strict_Image (Item : in Minimal_Action) return String;
    --  Strict Ada aggregate syntax, for generated code.
 
-   function Image is new Minimal_Action_Lists.Gen_Image (Strict_Image);
-
-   procedure Set_Minimal_Action (List : out Minimal_Action_Lists.List; Actions 
: in Minimal_Action_Array);
-
    type Parse_State is record
       Productions : Production_ID_Arrays.Vector;
-      --  Used in error recovery.
+      --  Used in some language-specfic error recovery.
       Action_List : Action_Node_Ptr;
       Goto_List   : Goto_Node_Ptr;
 
-      Minimal_Complete_Actions : Minimal_Action_Lists.List;
-      --  Set of parse actions that will most quickly complete the
-      --  productions in this state; used in error recovery
+      Minimal_Complete_Action : Minimal_Action;
+      --  Parse action that will most quickly complete a
+      --  production in this state; used in error recovery
    end record;
 
    type Parse_State_Array is array (State_Index range <>) of Parse_State;
@@ -331,8 +324,9 @@ package WisiToken.Parse.LR is
       McKenzie_Param : in McKenzie_Param_Type;
       Productions    : in WisiToken.Productions.Prod_Arrays.Vector)
      return Parse_Table_Ptr;
-   --  Read machine-readable text format of states from a file File_Name.
-   --  Result has actions, checks from Productions.
+   --  Read machine-readable text format of states (as output by
+   --  WisiToken.Generate.LR.Put_Text_Rep) from file File_Name. Result
+   --  has actions, checks from Productions.
 
    ----------
    --  For McKenzie_Recover. Declared here because Parser_Lists needs
@@ -496,6 +490,11 @@ package WisiToken.Parse.LR is
    --  which is true if they were copied from the parser stack, and not
    --  pushed by recover.
 
+   type Strategies is (Language_Fix, Minimal_Complete, Matching_Begin, 
Explore_Table, String_Quote);
+
+   type Strategy_Counts is array (Strategies) of Natural;
+   function Image is new SAL.Gen_Array_Image (Strategies, Natural, 
Strategy_Counts, Trimmed_Image);
+
    type Configuration is record
       Stack : Recover_Stacks.Stack;
       --  Initially built from the parser stack, then the stack after the
@@ -548,6 +547,9 @@ package WisiToken.Parse.LR is
       --  remaining ops at Current_Shared_Token.
 
       Cost : Natural := 0;
+
+      Strategy_Counts : LR.Strategy_Counts := (others => 0);
+      --  Count of strategies that produced Ops.
    end record;
    type Configuration_Access is access all Configuration;
    for Configuration_Access'Storage_Size use 0;
@@ -573,9 +575,11 @@ package WisiToken.Parse.LR is
       Results       : Config_Heaps.Heap_Type;
       Success       : Boolean := False;
    end record;
-
    type McKenzie_Access is access all McKenzie_Data;
 
+   procedure Accumulate (Data : in McKenzie_Data; Counts : in out 
Strategy_Counts);
+   --  Sum Results.Strategy_Counts.
+
    type Parse_Error_Label is (Action, Check, Message);
 
    type Parse_Error
diff --git a/wisitoken-parse-packrat-generated.adb 
b/wisitoken-parse-packrat-generated.adb
index 0e1adb0..0c909e5 100644
--- a/wisitoken-parse-packrat-generated.adb
+++ b/wisitoken-parse-packrat-generated.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  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
@@ -21,7 +21,7 @@ package body WisiToken.Parse.Packrat.Generated is
 
    overriding procedure Parse (Parser : aliased in out Generated.Parser)
    is
-      --  'aliased required for Base_Tree'Access. WORKAROUND: that was
+      --  'aliased' required for Base_Tree'Access. WORKAROUND: that was
       --  enough when Parser type was declared in generated Main; now that
       --  it's a derived type, it doesn't work. So we use Unchecked_Access.
 
@@ -63,6 +63,11 @@ package body WisiToken.Parse.Packrat.Generated is
 
    end Parse;
 
+   overriding function Tree (Parser : in Generated.Parser) return 
Syntax_Trees.Tree
+   is begin
+      return Parser.Tree;
+   end Tree;
+
    overriding function Any_Errors (Parser : in Generated.Parser) return Boolean
    is
       use all type Ada.Containers.Count_Type;
diff --git a/wisitoken-parse-packrat-generated.ads 
b/wisitoken-parse-packrat-generated.ads
index 80d6cee..2eddd56 100644
--- a/wisitoken-parse-packrat-generated.ads
+++ b/wisitoken-parse-packrat-generated.ads
@@ -7,7 +7,7 @@
 --
 --  see parent.
 --
---  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
@@ -46,12 +46,14 @@ package WisiToken.Parse.Packrat.Generated is
       end case;
    end record;
 
-   package Memos is new SAL.Gen_Unbounded_Definite_Vectors (Token_Index, 
Memo_Entry);
+   package Memos is new SAL.Gen_Unbounded_Definite_Vectors
+     (Token_Index, Memo_Entry, Default_Element => (others => <>));
 
    subtype Result_Type is Memo_Entry
    with Dynamic_Predicate => Result_Type.State in Result_States;
 
-   package Derivs is new SAL.Gen_Unbounded_Definite_Vectors (Token_ID, 
Memos.Vector);
+   package Derivs is new SAL.Gen_Unbounded_Definite_Vectors
+     (Token_ID, Memos.Vector, Default_Element => Memos.Empty_Vector);
 
    type Parse_WisiToken_Accept is access
      --  WORKAROUND: using Packrat.Parser'Class here hits a GNAT Bug box in 
GPL 2018.
@@ -64,6 +66,7 @@ package WisiToken.Parse.Packrat.Generated is
    end record;
 
    overriding procedure Parse (Parser : aliased in out Generated.Parser);
+   overriding function Tree (Parser : in Generated.Parser) return 
Syntax_Trees.Tree;
    overriding function Any_Errors (Parser : in Generated.Parser) return 
Boolean;
    overriding procedure Put_Errors (Parser : in Generated.Parser);
 
diff --git a/wisitoken-parse-packrat-procedural.adb 
b/wisitoken-parse-packrat-procedural.adb
index 1e5024c..5dc4ef1 100644
--- a/wisitoken-parse-packrat-procedural.adb
+++ b/wisitoken-parse-packrat-procedural.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  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
@@ -248,4 +248,9 @@ package body WisiToken.Parse.Packrat.Procedural is
       end if;
    end Parse;
 
+   overriding function Tree (Parser : in Procedural.Parser) return 
Syntax_Trees.Tree
+   is begin
+      return Parser.Tree;
+   end Tree;
+
 end WisiToken.Parse.Packrat.Procedural;
diff --git a/wisitoken-parse-packrat-procedural.ads 
b/wisitoken-parse-packrat-procedural.ads
index ecd14de..76c1aee 100644
--- a/wisitoken-parse-packrat-procedural.ads
+++ b/wisitoken-parse-packrat-procedural.ads
@@ -9,7 +9,7 @@
 --
 --  See parent.
 --
---  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
@@ -49,7 +49,8 @@ package WisiToken.Parse.Packrat.Procedural is
       end case;
    end record;
 
-   package Memos is new SAL.Gen_Unbounded_Definite_Vectors (Token_Index, 
Memo_Entry);
+   package Memos is new SAL.Gen_Unbounded_Definite_Vectors
+     (Token_Index, Memo_Entry, Default_Element => (others => <>));
    type Derivs is array (Token_ID range <>) of Memos.Vector;
 
    type Parser (First_Nonterminal, Last_Nonterminal : Token_ID) is new 
Packrat.Parser with
@@ -70,6 +71,8 @@ package WisiToken.Parse.Packrat.Procedural is
      return Procedural.Parser;
 
    overriding procedure Parse (Parser : aliased in out Procedural.Parser);
+   overriding function Tree (Parser : in Procedural.Parser) return 
Syntax_Trees.Tree;
+
    overriding function Any_Errors (Parser : in Procedural.Parser) return 
Boolean
      is (False);
    --  All errors are reported by Parse raising Syntax_Error.
diff --git a/wisitoken-parse.adb b/wisitoken-parse.adb
index 52870e0..09280d9 100644
--- a/wisitoken-parse.adb
+++ b/wisitoken-parse.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  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
@@ -42,7 +42,7 @@ package body WisiToken.Parse is
                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.EOF_ID then
+            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 (Token.Line + 1) := 
Parser.Terminals.Last_Index + 1;
             end if;
@@ -71,7 +71,7 @@ package body WisiToken.Parse is
 
    procedure Lex_All (Parser : in out Base_Parser)
    is
-      EOF_ID : constant Token_ID := Parser.Trace.Descriptor.EOF_ID;
+      EOF_ID : constant Token_ID := Parser.Trace.Descriptor.EOI_ID;
    begin
       Parser.Lexer.Errors.Clear;
       Parser.Terminals.Clear;
diff --git a/wisitoken-parse.ads b/wisitoken-parse.ads
index 38574ed..c2fb500 100644
--- a/wisitoken-parse.ads
+++ b/wisitoken-parse.ads
@@ -2,7 +2,7 @@
 --
 --  Subprograms common to more than one parser, higher-level than in 
wisitoken.ads
 --
---  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
@@ -44,7 +44,7 @@ package WisiToken.Parse is
    --
    --  The user must first call Lexer.Reset_* to set the input text.
 
-   procedure Parse (Shared_Parser : aliased in out Base_Parser) is abstract;
+   procedure Parse (Parser : aliased in out Base_Parser) is abstract;
    --  Call Lex_All, then execute parse algorithm to parse the tokens,
    --  storing the result in Parser for Execute_Actions.
    --
@@ -55,6 +55,9 @@ package WisiToken.Parse is
    --  For other errors, raises Parse_Error with an appropriate error
    --  message.
 
+   function Tree (Parser : in Base_Parser) return Syntax_Trees.Tree is 
abstract;
+   --  Return the syntax tree resulting from the parse.
+
    function Any_Errors (Parser : in Base_Parser) return Boolean is abstract;
 
    procedure Put_Errors (Parser : in Base_Parser) is abstract;
diff --git a/wisitoken-parse_table-mode.el b/wisitoken-parse_table-mode.el
new file mode 100644
index 0000000..5cce27a
--- /dev/null
+++ b/wisitoken-parse_table-mode.el
@@ -0,0 +1,96 @@
+;; wisitoken-parse_table-mode.el --- For navigating in a parse table as output 
by wisitoken-bnf-generate. -*- lexical-binding:t -*-
+;;
+;; Copyright (C) 2017 - 2019  Free Software Foundation, Inc.
+;;
+;; Author: Stephen Leake <stephen_leake@stephe-leake.org>
+;; Maintainer: Stephen Leake <stephen_leake@stephe-leake.org>
+;; Keywords: parser
+;; Version: 1.0
+;; package-requires: ((emacs "25.1"))
+;; URL: http://www.nongnu.org/ada-mode/wisi/wisi.html
+;;
+;; 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 'xref)
+
+(defun wisitoken-parse_table--xref-backend () 'wisitoken-parse_table)
+
+(cl-defgeneric xref-backend-identifier-completion-table ((_backend (eql 
wisitoken-parse_table)))
+  ;; could complete on nonterms, find productions
+  nil)
+
+(cl-defmethod xref-backend-identifier-at-point ((_backend (eql 
wisitoken-parse_table)))
+  ;; if we are on one of:
+  ;; - ’goto state nnn’ in a state action
+  ;; => return nnn state
+  ;;
+  ;; or
+  ;; - foo <= bar baz
+  ;; => return nonterminal name at point
+  ;;
+  ;; - 'reduce n tokens to <nonterminal> <prod_id>'
+  ;; => return 'prod_id: name'
+  (save-excursion
+    (cond
+     ((save-excursion
+       (end-of-line)
+       (or (looking-back "goto state \\([0-9]+\\),?" (line-beginning-position))
+           (looking-back "( \\([0-9]+\\))" (line-beginning-position))))
+      (match-string 1))
+
+     ((save-excursion
+       (back-to-indentation)
+       (looking-at "[a-zA-Z_]+ + => reduce [0-9]+ tokens to \\([a-z0-9_]+\\) 
\\([0-9.]+\\)"))
+      (concat (match-string 2) ": " (match-string 1)))
+
+     (t
+      (thing-at-point 'symbol)))))
+
+(cl-defgeneric xref-backend-definitions ((_backend (eql 
wisitoken-parse_table)) identifier)
+  ;; IDENTIFIER is from xref-back-identifier-at-point; a state number or a 
nonterminal
+  (let ((state-p (string-match "\\`[0-9]+\\'" identifier))
+       (prod_id-p (string-match "\\`[0-9.]+: " identifier)))
+    (save-excursion
+      (goto-char (point-min))
+      (cond
+       (state-p
+       (search-forward-regexp (concat "^State " identifier ":$")))
+
+       (prod_id-p
+       (search-forward-regexp (concat identifier " <=")))
+
+       (t
+       (search-forward-regexp (concat "^[0-9.]+: " identifier " <=")))
+       )
+      (list (xref-make identifier (xref-make-buffer-location (current-buffer) 
(match-beginning 0))))
+      )))
+
+;;;###autoload
+(define-minor-mode wisitoken-parse_table-mode
+  "Provides navigation in wisi-generate parse table output."
+  nil ":parse_table" nil
+  (add-hook 'xref-backend-functions #'wisitoken-parse_table--xref-backend nil 
t)
+
+  (if wisitoken-parse_table-mode
+      (read-only-mode 0)
+    (read-only-mode 1)
+  ))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.parse_table.*\\'" . 
wisitoken-parse_table-mode))
+
+(provide 'wisitoken-parse_table-mode)
+;; end of file
diff --git a/wisitoken-productions.ads b/wisitoken-productions.ads
index 0ae651e..854b302 100644
--- a/wisitoken-productions.ads
+++ b/wisitoken-productions.ads
@@ -2,7 +2,7 @@
 --
 --  Type and operations for building grammar productions.
 --
---  Copyright (C) 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2018 - 2019 Free Software Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -31,14 +31,16 @@ package WisiToken.Productions is
       Check  : WisiToken.Semantic_Checks.Semantic_Check;
    end record;
 
-   package RHS_Arrays is new SAL.Gen_Unbounded_Definite_Vectors (Natural, 
Right_Hand_Side);
+   package RHS_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
+     (Natural, Right_Hand_Side, Default_Element => (others => <>));
 
    type Instance is record
       LHS  : Token_ID := Invalid_Token_ID;
       RHSs : RHS_Arrays.Vector;
    end record;
 
-   package Prod_Arrays is new SAL.Gen_Unbounded_Definite_Vectors (Token_ID, 
Instance);
+   package Prod_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
+     (Token_ID, Instance, Default_Element => (others => <>));
 
    function Image
      (LHS        : in Token_ID;
@@ -51,14 +53,16 @@ package WisiToken.Productions is
    procedure Put (Grammar : Prod_Arrays.Vector; Descriptor : in 
WisiToken.Descriptor);
    --  Put Image of each production to Ada.Text_IO.Current_Output.
 
-   package Line_Number_Arrays is new SAL.Gen_Unbounded_Definite_Vectors 
(Natural, WisiToken.Line_Number_Type);
+   package Line_Number_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
+     (Natural, Line_Number_Type, Default_Element => Invalid_Line_Number);
 
    type Prod_Source_Line_Map is record
-      Line    : Line_Number_Type;
+      Line    : Line_Number_Type := Invalid_Line_Number;
       RHS_Map : Line_Number_Arrays.Vector;
    end record;
 
-   package Source_Line_Maps is new SAL.Gen_Unbounded_Definite_Vectors 
(Token_ID, Prod_Source_Line_Map);
+   package Source_Line_Maps is new SAL.Gen_Unbounded_Definite_Vectors
+     (Token_ID, Prod_Source_Line_Map, Default_Element => (others => <>));
    --  For line numbers of productions in source files.
 
 end WisiToken.Productions;
diff --git a/wisitoken-semantic_checks.adb b/wisitoken-semantic_checks.adb
index e90fb59..26dcb49 100644
--- a/wisitoken-semantic_checks.adb
+++ b/wisitoken-semantic_checks.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
@@ -132,4 +132,21 @@ package body WisiToken.Semantic_Checks is
       return (Label => Ok);
    end Merge_Names;
 
+   function Terminate_Partial_Parse
+     (Partial_Parse_Active    : in Boolean;
+      Partial_Parse_Byte_Goal : in Buffer_Pos;
+      Recover_Active          : in Boolean;
+      Nonterm                 : in Recover_Token)
+     return Check_Status
+   is begin
+      if Partial_Parse_Active and then
+        (not Recover_Active) and then
+        Nonterm.Byte_Region.Last >= Partial_Parse_Byte_Goal
+      then
+         raise WisiToken.Partial_Parse;
+      else
+         return (Label => Ok);
+      end if;
+   end Terminate_Partial_Parse;
+
 end WisiToken.Semantic_Checks;
diff --git a/wisitoken-semantic_checks.ads b/wisitoken-semantic_checks.ads
index 5fb5807..acb0ce8 100644
--- a/wisitoken-semantic_checks.ads
+++ b/wisitoken-semantic_checks.ads
@@ -2,7 +2,7 @@
 --
 --  Grammar semantic check routines.
 --
---  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
@@ -45,9 +45,10 @@ package WisiToken.Semantic_Checks is
    function Image (Item : in Check_Status; Descriptor : WisiToken.Descriptor) 
return String;
 
    type Semantic_Check is access function
-     (Lexer   : access constant WisiToken.Lexer.Instance'Class;
-      Nonterm : in out Recover_Token;
-      Tokens  : in     Recover_Token_Array)
+     (Lexer          : access constant WisiToken.Lexer.Instance'Class;
+      Nonterm        : in out Recover_Token;
+      Tokens         : in     Recover_Token_Array;
+      Recover_Active : in     Boolean)
      return Check_Status;
    --  Called during parsing and error recovery to implement higher level
    --  checks, such as block name matching in Ada.
@@ -86,4 +87,13 @@ package WisiToken.Semantic_Checks is
    --  If Tokens (Last_Index).Name is Null_Buffer_Region, use Tokens
    --  (Last_Index).Byte_Region instead.
 
+   function Terminate_Partial_Parse
+     (Partial_Parse_Active    : in Boolean;
+      Partial_Parse_Byte_Goal : in Buffer_Pos;
+      Recover_Active          : in Boolean;
+      Nonterm                 : in Recover_Token)
+     return Check_Status;
+   pragma Inline (Terminate_Partial_Parse);
+   --  If Active, raise Wisitoken.Partial_Parse; otherwise return Ok.
+
 end WisiToken.Semantic_Checks;
diff --git a/wisitoken-syntax_trees.adb b/wisitoken-syntax_trees.adb
index fa6602c..ac780a3 100644
--- a/wisitoken-syntax_trees.adb
+++ b/wisitoken-syntax_trees.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  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
@@ -33,9 +33,12 @@ package body WisiToken.Syntax_Trees is
 
    procedure Move_Branch_Point (Tree : in out Syntax_Trees.Tree; Required_Node 
: in Valid_Node_Index);
 
+   type Visit_Parent_Mode is (Before, After);
+
    function Process_Tree
      (Tree         : in Syntax_Trees.Tree;
       Node         : in Valid_Node_Index;
+      Visit_Parent : in Visit_Parent_Mode;
       Process_Node : access function
         (Tree : in Syntax_Trees.Tree;
          Node : in Valid_Node_Index)
@@ -355,12 +358,78 @@ package body WisiToken.Syntax_Trees is
          end if;
       end Process;
 
-      Junk : constant Boolean := Process_Tree (Tree, Node, Process'Access);
+      Junk : constant Boolean := Process_Tree (Tree, Node, After, 
Process'Access);
       pragma Unreferenced (Junk);
    begin
       return Found;
    end Find_Descendant;
 
+   function Find_Min_Terminal_Index
+     (Tree  : in Syntax_Trees.Tree;
+      Index : in Token_Index)
+     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
+         function Compute (N : in Syntax_Trees.Node) return Boolean
+         is begin
+            if N.Label /= Nonterm then
+               return True;
+            elsif Index = N.Min_Terminal_Index then
+               Found := Node;
+               return False;
+            else
+               return True;
+            end if;
+         end Compute;
+      begin
+         return Compute
+           ((if Node <= Tree.Last_Shared_Node
+             then Tree.Shared_Tree.Nodes (Node)
+             else Tree.Branched_Nodes (Node)));
+      end Process;
+
+      Junk : constant Boolean := Process_Tree (Tree, Tree.Root, Before, 
Process'Access);
+      pragma Unreferenced (Junk);
+   begin
+      return Found;
+   end Find_Min_Terminal_Index;
+
+   function Find_Max_Terminal_Index
+     (Tree  : in Syntax_Trees.Tree;
+      Index : in Token_Index)
+     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
+         function Compute (N : in Syntax_Trees.Node) return Boolean
+         is begin
+            if N.Label /= Nonterm then
+               return True;
+            elsif Index = N.Max_Terminal_Index then
+               Found := Node;
+               return False;
+            else
+               return True;
+            end if;
+         end Compute;
+      begin
+         return Compute
+           ((if Node <= Tree.Last_Shared_Node
+             then Tree.Shared_Tree.Nodes (Node)
+             else Tree.Branched_Nodes (Node)));
+      end Process;
+
+      Junk : constant Boolean := Process_Tree (Tree, Tree.Root, Before, 
Process'Access);
+      pragma Unreferenced (Junk);
+   begin
+      return Found;
+   end Find_Max_Terminal_Index;
+
    function Find_Sibling
      (Tree : in Syntax_Trees.Tree;
       Node : in Valid_Node_Index;
@@ -791,6 +860,7 @@ package body WisiToken.Syntax_Trees is
    function Process_Tree
      (Tree         : in Syntax_Trees.Tree;
       Node         : in Valid_Node_Index;
+      Visit_Parent : in Visit_Parent_Mode;
       Process_Node : access function
         (Tree : in Syntax_Trees.Tree;
          Node : in Valid_Node_Index)
@@ -799,15 +869,25 @@ package body WisiToken.Syntax_Trees is
    is
       function Compute (N : in Syntax_Trees.Node) return Boolean
       is begin
+         if Visit_Parent = Before then
+            if not Process_Node (Tree, Node) then
+               return False;
+            end if;
+         end if;
+
          if N.Label = Nonterm then
             for Child of N.Children loop
-               if not Process_Tree (Tree, Child, Process_Node) then
+               if not Process_Tree (Tree, Child, Visit_Parent, Process_Node) 
then
                   return False;
                end if;
             end loop;
          end if;
 
-         return Process_Node (Tree, Node);
+         if Visit_Parent = After then
+            return Process_Node (Tree, Node);
+         else
+            return True;
+         end if;
       end Compute;
    begin
       if Node <= Tree.Last_Shared_Node then
@@ -915,6 +995,7 @@ package body WisiToken.Syntax_Trees is
       else
          Tree.Branched_Nodes (Node).Augmented := Value;
       end if;
+      Tree.Shared_Tree.Augmented_Present := True;
    end Set_Augmented;
 
    procedure Set_Children
diff --git a/wisitoken-syntax_trees.ads b/wisitoken-syntax_trees.ads
index b9cf380..d5cbec1 100644
--- a/wisitoken-syntax_trees.ads
+++ b/wisitoken-syntax_trees.ads
@@ -7,7 +7,7 @@
 --  We provide Base_Tree and Tree in one package, because only Tree
 --  needs an API; the only way Base_Tree is accessed is via Tree.
 --
---  Copyright (C) 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2018 - 2019 Free Software Foundation, Inc.
 
 --  There is one syntax tree for each parser. There is one shared
 --  Terminals array, matching the actual input text.
@@ -58,7 +58,8 @@ package WisiToken.Syntax_Trees is
    type Valid_Node_Index_Array is array (Positive_Index_Type range <>) of 
Valid_Node_Index;
    --  Index matches Base_Token_Array, Augmented_Token_Array
 
-   package Valid_Node_Index_Arrays is new SAL.Gen_Unbounded_Definite_Vectors 
(Positive_Index_Type, Valid_Node_Index);
+   package Valid_Node_Index_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
+     (Positive_Index_Type, Valid_Node_Index, Default_Element => 
Valid_Node_Index'First);
    --  Index matches Valid_Node_Index_Array.
 
    type Node_Label is (Shared_Terminal, Virtual_Terminal, Nonterm);
@@ -88,12 +89,12 @@ package WisiToken.Syntax_Trees is
    --  input stream.
 
    procedure Delete_Token
-     (Data        : in out User_Data_Type;
+     (User_Data   : in out User_Data_Type;
       Token_Index : in     WisiToken.Token_Index)
    is null;
    --  Token at Token_Index was deleted in error recovery; update
-   --  remaining tokens as needed. Called from Execute_Actions for each
-   --  deleted token, before processing the syntax tree.
+   --  remaining tokens and Tree as needed. Called from Execute_Actions
+   --  for each deleted token, before processing the syntax tree.
 
    procedure Reduce
      (User_Data : in out User_Data_Type;
@@ -264,7 +265,7 @@ package WisiToken.Syntax_Trees is
       ID   : in Token_ID)
      return Node_Index
    with Pre => Tree.Is_Nonterm (Node);
-   --  Return the child of Node that contains ID, or Invalid_Node_Index if
+   --  Return the child of Node whose ID is ID, or Invalid_Node_Index if
    --  none match.
 
    function Find_Descendant
@@ -272,7 +273,25 @@ package WisiToken.Syntax_Trees is
       Node : in Valid_Node_Index;
       ID   : in Token_ID)
      return Node_Index;
-   --  Return the child of Node that contains ID (may be Node), or
+   --  Return the descendant of Node (may be Node) whose ID is ID, or
+   --  Invalid_Node_Index if none match.
+
+   function Find_Min_Terminal_Index
+     (Tree  : in Syntax_Trees.Tree;
+      Index : in Token_Index)
+     return Node_Index
+   with Post => Find_Min_Terminal_Index'Result = Invalid_Node_Index or else
+                Tree.Is_Nonterm (Find_Min_Terminal_Index'Result);
+   --  Return the first node whose Min_Terminal_Index is Index, or
+   --  Invalid_Node_Index if none match.
+
+   function Find_Max_Terminal_Index
+     (Tree  : in Syntax_Trees.Tree;
+      Index : in Token_Index)
+     return Node_Index
+   with Post => Find_Max_Terminal_Index'Result = Invalid_Node_Index or else
+                Tree.Is_Nonterm (Find_Max_Terminal_Index'Result);
+   --  Return the first node whose Max_Terminal_Index is Index, or
    --  Invalid_Node_Index if none match.
 
    procedure Set_Root (Tree : in out Syntax_Trees.Tree; Root : in 
Valid_Node_Index);
@@ -372,7 +391,8 @@ private
 
    subtype Nonterm_Node is Node (Nonterm);
 
-   package Node_Arrays is new SAL.Gen_Unbounded_Definite_Vectors 
(Valid_Node_Index, Node);
+   package Node_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
+     (Valid_Node_Index, Node, Default_Element => (others => <>));
 
    type Base_Tree is new Ada.Finalization.Controlled with record
       Nodes : Node_Arrays.Vector;
diff --git a/wisitoken-text_io_trace.adb b/wisitoken-text_io_trace.adb
index daa9f4c..77fb331 100644
--- a/wisitoken-text_io_trace.adb
+++ b/wisitoken-text_io_trace.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  Copyright (C) 2017 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
@@ -17,30 +17,65 @@
 
 pragma License (Modified_GPL);
 
+with Ada.Calendar.Formatting;
+with Ada.Strings.Fixed;
 package body WisiToken.Text_IO_Trace is
 
+   function Insert_Prefix_At_Newlines (Trace : in Text_IO_Trace.Trace; Item : 
in String) return String
+   is
+      use Ada.Strings.Fixed;
+      use Ada.Strings.Unbounded;
+      Result : Unbounded_String;
+      First : Integer := Item'First;
+      Last : Integer;
+   begin
+      loop
+         Last := Index (Pattern => "" & ASCII.LF, Source => Item (First .. 
Item'Last));
+         exit when Last = 0;
+         Result := Result & Item (First .. Last) & Trace.Prefix;
+         First := Last + 1;
+      end loop;
+      Result := Result & Item (First .. Item'Last);
+      return -Result;
+   end Insert_Prefix_At_Newlines;
+
+   ----------
+   --  Public subprograms, declaration order
+
    overriding
-   procedure Put (Trace : in out Text_IO_Trace.Trace; Item : in String)
+   procedure Set_Prefix (Trace : in out Text_IO_Trace.Trace; Prefix : in 
String)
+   is begin
+      Trace.Prefix := +Prefix;
+   end Set_Prefix;
+
+   overriding
+   procedure Put (Trace : in out Text_IO_Trace.Trace; Item : in String; Prefix 
: in Boolean := True)
    is
       use Ada.Text_IO;
    begin
       if Trace.File /= null and then Is_Open (Trace.File.all) then
-         Ada.Text_IO.Put (Trace.File.all, Item);
+         Ada.Text_IO.Put (Trace.File.all, (if Prefix then -Trace.Prefix else 
"") & Item);
       else
-         Ada.Text_IO.Put (Item);
+         Ada.Text_IO.Put ((if Prefix then -Trace.Prefix else "") & Item);
       end if;
    end Put;
 
    overriding
    procedure Put_Line (Trace : in out Text_IO_Trace.Trace; Item : in String)
    is
+      use Ada.Strings.Fixed;
       use Ada.Text_IO;
+      Temp : constant String :=
+        (if 0 /= Index (Item, "" & ASCII.LF)
+         then Insert_Prefix_At_Newlines (Trace, Item)
+         else Item);
    begin
+
       if Trace.File /= null and then Is_Open (Trace.File.all) then
-         Ada.Text_IO.Put_Line (Trace.File.all, Item);
+         Ada.Text_IO.Put_Line (Trace.File.all, -Trace.Prefix & Temp);
          Ada.Text_IO.Flush (Trace.File.all);
       else
-         Ada.Text_IO.Put_Line (Item);
+         Ada.Text_IO.Put_Line (-Trace.Prefix & Temp);
          Ada.Text_IO.Flush;
       end if;
    end Put_Line;
@@ -57,6 +92,14 @@ package body WisiToken.Text_IO_Trace is
       end if;
    end New_Line;
 
+   overriding
+   procedure Put_Clock (Trace : in out Text_IO_Trace.Trace; Label : in String)
+   is begin
+      Trace.Put_Line
+        (Ada.Calendar.Formatting.Image
+           (Ada.Calendar.Clock, Include_Time_Fraction => True) & " " & Label);
+   end Put_Clock;
+
    procedure Set_File (Trace : in out Text_IO_Trace.Trace; File : in 
Ada.Text_IO.File_Access)
    is begin
       Trace.File := File;
diff --git a/wisitoken-text_io_trace.ads b/wisitoken-text_io_trace.ads
index ad421dd..c6892c8 100644
--- a/wisitoken-text_io_trace.ads
+++ b/wisitoken-text_io_trace.ads
@@ -2,7 +2,7 @@
 --
 --  Trace output to Ada.Text_IO
 --
---  Copyright (C) 2017 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
@@ -24,7 +24,10 @@ package WisiToken.Text_IO_Trace is
    --  Defaults to Ada.Text_IO.Standard_Output
 
    overriding
-   procedure Put (Trace : in out Text_IO_Trace.Trace; Item : in String);
+   procedure Set_Prefix (Trace : in out Text_IO_Trace.Trace; Prefix : in 
String);
+
+   overriding
+   procedure Put (Trace : in out Text_IO_Trace.Trace; Item : in String; Prefix 
: in Boolean := True);
 
    overriding
    procedure Put_Line (Trace : in out Text_IO_Trace.Trace; Item : in String);
@@ -32,6 +35,9 @@ package WisiToken.Text_IO_Trace is
    overriding
    procedure New_Line (Trace : in out Text_IO_Trace.Trace);
 
+   overriding
+   procedure Put_Clock (Trace : in out Text_IO_Trace.Trace; Label : in String);
+
    procedure Set_File (Trace : in out Text_IO_Trace.Trace; File : in 
Ada.Text_IO.File_Access);
    --  Set file for trace output. Default is Text_IO.Current_Output.
 
@@ -40,6 +46,7 @@ package WisiToken.Text_IO_Trace is
 
 private
    type Trace is limited new WisiToken.Trace with record
-      File : Ada.Text_IO.File_Access;
+      File   : Ada.Text_IO.File_Access;
+      Prefix : Ada.Strings.Unbounded.Unbounded_String;
    end record;
 end WisiToken.Text_IO_Trace;
diff --git a/wisitoken-user_guide.info b/wisitoken-user_guide.info
index 4d2bb1d..6d04498 100644
--- a/wisitoken-user_guide.info
+++ b/wisitoken-user_guide.info
@@ -415,8 +415,8 @@ File: wisitoken-user_guide.info,  Node: Other declarations, 
 Prev: Error recover
      'wisi-face-apply' actions must be declared, so the elisp and Ada
      code aggree on what they mean.
 
-'%elisp_indent <name>'
-     Declare a name for an elisp indent variable.
+'%elisp_indent <elisp name> <Ada name>'
+     Declare elisp and Ada names for an indent variable.
 
      When generating Ada code for Emacs, the elisp indent variables used
      in 'wisi-indent' actions must be declared, so the elisp and Ada
@@ -487,11 +487,9 @@ File: wisitoken-user_guide.info,  Node: Other 
declarations,  Prev: Error recover
 '%start'
      The start token for the grammar.
 
-'regexp_name <name>'
-     Declare a named regular expression.  The name may then occur in
-     another regular expression.
-
-     The re2c lexer supports this usage; other lexers may not.
+'re2c_regexp <name> <value>'
+     Declare a named regular expression with re2c name and syntax.  The
+     name may then occur in another re2c regular expression.
 
 
 File: wisitoken-user_guide.info,  Node: Nonterminals,  Next: Conditional code, 
 Prev: Declarations,  Up: Grammar File Syntax
@@ -550,7 +548,7 @@ Node: Keywords7260
 Node: Tokens7569
 Node: Error recovery9217
 Node: Other declarations12453
-Node: Nonterminals16476
-Node: Conditional code17193
+Node: Nonterminals16475
+Node: Conditional code17192
 
 End Tag Table
diff --git a/wisitoken.adb b/wisitoken.adb
index ce72d3f..698c18e 100644
--- a/wisitoken.adb
+++ b/wisitoken.adb
@@ -2,7 +2,7 @@
 --
 --  See spec
 --
---  Copyright (C) 2009, 2014-2015, 2017, 2018 Free Software Foundation, Inc.
+--  Copyright (C) 2009, 2014-2015, 2017 - 2019 Free Software Foundation, Inc.
 --
 --  This file is part of the WisiToken package.
 --
@@ -40,7 +40,7 @@ package body WisiToken is
 
    function Image (Item : in Token_ID; Desc : in Descriptor) return String
    is begin
-      return (if Item = Invalid_Token_ID then "" else Desc.Image (Item).all);
+      return (if Item = Invalid_Token_ID then "-" else Desc.Image (Item).all);
    end Image;
 
    procedure Put_Tokens (Descriptor : in WisiToken.Descriptor)
diff --git a/wisitoken.ads b/wisitoken.ads
index 81fc519..4e9d88b 100644
--- a/wisitoken.ads
+++ b/wisitoken.ads
@@ -1,431 +1,450 @@
---  Abstract:
---
---  Root of WisiToken lexer/parser generator and exector.
---
---  The token type is an integer subtype, not an enumeration type, to
---  avoid making this package generic, which would make all other
---  packages generic.
---
---  Additional information about a token can be stored in the
---  'augmented' field of the syntax tree; see
---  wisitoken-syntax_trees.ads.
---
---  References:
---
---  [dragon] "Compilers Principles, Techniques, and Tools" by Aho,
---  Sethi, and Ullman (aka: "The [Red] Dragon Book" due to the dragon
---  on the cover).
---
---  Copyright (C) 2009, 2010, 2013 - 2015, 2017, 2018 Free Software 
Foundation, Inc.
---
---  This file is part of the WisiToken package.
---
---  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.
---
---  This software was originally developed with the name OpenToken by
---  the following company, and was released as open-source software as
---  a service to the community:
---
---           FlightSafety International Simulation Systems Division
---                    Broken Arrow, OK  USA  918-259-4000
-
-pragma License (Modified_GPL);
-
-with Ada.Containers;
-with Ada.Strings.Unbounded;
-with Ada.Text_IO;
-with Ada.Unchecked_Deallocation;
-with SAL.Gen_Trimmed_Image;
-with SAL.Gen_Unbounded_Definite_Queues;
-with SAL.Gen_Unbounded_Definite_Vectors.Gen_Image;
-with SAL.Gen_Unbounded_Definite_Vectors.Gen_Image_Aux;
-package WisiToken is
-
-   Syntax_Error : exception; -- no recovery for a syntax error was found
-
-   Parse_Error : exception; -- a non-recoverable non-fatal error was 
encountered; editing the input can fix the error.
-
-   Fatal_Error : exception; -- Error in code or grammar; editing input cannot 
fix error.
-
-   Grammar_Error : exception;
-   --  Grammar file has bad syntax, or grammar is not consistent (ie
-   --  unused tokens, missing productions, invalid actions)
-
-   User_Error : exception; -- other user error (ie command line parameter)
-
-   --  SAL.Programmer_Error : exception; -- a programming convention has been 
violated
-
-   subtype Positive_Index_Type is SAL.Peek_Type;
-   function Trimmed_Image is new SAL.Gen_Trimmed_Image (SAL.Base_Peek_Type);
-
-   type Unknown_State_Index is new Integer range -1 .. Integer'Last;
-   subtype State_Index is Unknown_State_Index range 0 .. 
Unknown_State_Index'Last;
-   Unknown_State : constant Unknown_State_Index := -1;
-
-   function Trimmed_Image is new SAL.Gen_Trimmed_Image (Unknown_State_Index);
-
-   package State_Index_Queues is new SAL.Gen_Unbounded_Definite_Queues 
(State_Index);
-   package State_Index_Arrays is new SAL.Gen_Unbounded_Definite_Vectors 
(Positive, State_Index);
-   function Image is new State_Index_Arrays.Gen_Image (Trimmed_Image);
-
-   ----------
-   --  Token IDs
-
-   type Token_ID is range 0 .. Integer'Last; -- 0 origin to match elisp array
-
-   Invalid_Token_ID : constant Token_ID := Token_ID'Last;
-
-   type Token_ID_Array_String is array (Token_ID range <>) of access constant 
String;
-   type Token_ID_Array_Natural is array (Token_ID range <>) of Natural;
-
-   type Descriptor
-     (First_Terminal    : Token_ID;
-      Last_Terminal     : Token_ID;
-      First_Nonterminal : Token_ID;
-      Last_Nonterminal  : Token_ID;
-      EOF_ID            : Token_ID;
-      Accept_ID         : Token_ID)
-   is record
-      --  Tokens in the range Token_ID'First .. First_Terminal - 1 are
-      --  non-reporting (comments, whitespace), and thus are not used in
-      --  generating parse tables.
-      --
-      --  Tokens in the range Last_Terminal + 1 .. Last_Nonterminal are
-      --  the nonterminals of a grammar.
-      --
-      --  Components are discriminants if they can be specified statically.
-
-      Case_Insensitive : Boolean; -- keywords and names
-      New_Line_ID      : Token_ID;
-      Comment_ID       : Token_ID;
-      Left_Paren_ID    : Token_ID;
-      Right_Paren_ID   : Token_ID;
-      --  If the language does not define these tokens, set them to
-      --  Invalid_Token_ID.
-
-      String_1_ID  : Token_ID; -- delimited by ', error if New_Line_ID
-      String_2_ID  : Token_ID; -- delimited by ", error if New_Line_ID
-      --
-      --  Support for missing quote error recovery. If the language does not
-      --  have two kinds of string literals, set one or both of these to
-      --  Invalid_Token_ID.
-
-      Embedded_Quote_Escape_Doubled : Boolean;
-      --  True if quote characters embedded in strings are escaped by
-      --  doubling (as in Ada); false if by preceding with backslash (as in
-      --  C).
-
-      Image : Token_ID_Array_String (Token_ID'First .. Last_Nonterminal);
-      --  User names for tokens.
-
-      Terminal_Image_Width : Integer;
-      Image_Width          : Integer; --  max width of Image
-
-      --  LALR generate needs a 'Propagate_ID' lookahead that is distinct
-      --  from all terminals. Since lookaheads are Token_ID_Set, we need to
-      --  allocate First_Terminal .. Last_Terminal for LR1 generate, and
-      --  First_Terminal .. Propagate_ID for LALR generate, so we define
-      --  Last_Lookahead. After the LR table is generated, Last_Lookahead is
-      --  no longer used.
-      Last_Lookahead : Token_ID;
-   end record;
-
-   function Padded_Image (Item : in Token_ID; Desc : in Descriptor) return 
String;
-   --  Return Desc.Image (Item), padded to Terminal_Image_Width (if Item
-   --  is a terminal) or to Image_Width.
-
-   function Image (Item : in Token_ID; Desc : in Descriptor) return String;
-   --  Return Desc.Image (Item), or empty string for Invalid_Token_ID.
-
-   function Trimmed_Image is new SAL.Gen_Trimmed_Image (Token_ID);
-
-   procedure Put_Tokens (Descriptor : in WisiToken.Descriptor);
-   --  Put user readable token list (token_id'first ..
-   --  descriptor.last_nonterminal) to Ada.Text_IO.Current_Output
-
-   function Find_ID (Descriptor : in WisiToken.Descriptor; Name : in String) 
return Token_ID;
-   --  Return index of Name in Descriptor.Image. If not found, raise 
Programmer_Error.
-
-   type Token_ID_Array is array (Positive range <>) of Token_ID;
-
-   package Token_ID_Arrays is new SAL.Gen_Unbounded_Definite_Vectors 
(Positive, Token_ID);
-
-   function Image is new Token_ID_Arrays.Gen_Image_Aux (WisiToken.Descriptor, 
Image);
-   function Trimmed_Image is new Token_ID_Arrays.Gen_Image (Trimmed_Image);
-
-   procedure To_Vector (Item : in Token_ID_Array; Vector : in out 
Token_ID_Arrays.Vector);
-
-   function Shared_Prefix (A, B : in Token_ID_Arrays.Vector) return Natural;
-   --  Return last index in A of a prefix shared between A, B; 0 if none.
-
-   type Token_ID_Set is array (Token_ID range <>) of Boolean;
-
-   function "&" (Left : in Token_ID_Set; Right : in Token_ID) return 
Token_ID_Set;
-   --  Include Left and Right in result.
-
-   function To_Token_ID_Set (First, Last : in Token_ID; Item : in 
Token_ID_Array) return Token_ID_Set;
-   --  First, Last determine size of result.
-   --  For each element in Item, set result (element) True.
-
-   procedure To_Set (Item : in Token_ID_Arrays.Vector; Set : out Token_ID_Set);
-   --  For each element of Item, set Set (element) True.
-
-   function To_Array (Item : in Token_ID_Set) return Token_ID_Arrays.Vector;
-
-   function Any (Item : in Token_ID_Set) return Boolean;
-
-   function Count (Item : in Token_ID_Set) return Integer;
-   --  Count of True elements.
-
-   function Image
-     (Item      : in Token_ID_Set;
-      Desc      : in Descriptor;
-      Max_Count : in Integer := Integer'Last;
-      Inverted  : in Boolean := False)
-     return String;
-   --  For diagnostics; not Ada syntax.
-
-   type Token_Array_Token_Set is array (Token_ID range <>, Token_ID range <>) 
of Boolean;
-
-   function Slice (Item : in Token_Array_Token_Set; I : in Token_ID) return 
Token_ID_Set;
-   function Any (Item : in Token_Array_Token_Set; I : in Token_ID) return 
Boolean;
-   function Any (Item : in Token_Array_Token_Set) return Boolean;
-   procedure Or_Slice (Item : in out Token_Array_Token_Set; I : in Token_ID; 
Value : in Token_ID_Set);
-
-   procedure Put (Descriptor : in WisiToken.Descriptor; Item : in 
Token_Array_Token_Set);
-   --  Put Item to Ada.Text_IO.Current_Output, using valid Ada aggregate
-   --  syntax.
-
-   type Token_Array_Token_ID is array (Token_ID range <>) of Token_ID;
-
-   package Token_Sequence_Arrays is new SAL.Gen_Unbounded_Definite_Vectors 
(Token_ID, Token_ID_Arrays.Vector);
-
-   ----------
-   --  Production IDs; see wisitoken-productions.ads for more
-
-   type Production_ID is record
-      LHS : Token_ID := Invalid_Token_ID;
-      RHS : Natural  := 0;
-      --  Index into the production table.
-   end record;
-
-   Invalid_Production_ID : constant Production_ID := (others => <>);
-
-   function Image (Item : in Production_ID) return String;
-   --  Ada positional aggregate syntax, for code generation.
-
-   function Trimmed_Image (Item : in Production_ID) return String;
-   --  Nonterm.rhs_index, both integers, no leading or trailing space;
-   --  for parse table output and diagnostics.
-
-   Prod_ID_Image_Width : constant Integer := 7;
-   --  Max width of Trimmed_Image
-
-   function Padded_Image (Item : in Production_ID; Width : in Integer) return 
String;
-   --  Trimmed_Image padded with leading spaces to Width
-
-   package Production_ID_Arrays is new SAL.Gen_Unbounded_Definite_Vectors 
(Positive, Production_ID);
-   function Image is new Production_ID_Arrays.Gen_Image (Image);
-   function Trimmed_Image is new Production_ID_Arrays.Gen_Image 
(Trimmed_Image);
-
-   type Production_ID_Array is array (Natural range <>) of Production_ID;
-
-   function To_Vector (Item : in Production_ID_Array) return 
Production_ID_Arrays.Vector;
-   function "+" (Item : in Production_ID_Array) return 
Production_ID_Arrays.Vector renames To_Vector;
-   function "+" (Item : in Production_ID) return Production_ID_Arrays.Vector 
is (To_Vector ((1 => Item)));
-
-   ----------
-   --  Tokens
-
-   type Base_Buffer_Pos is range 0 .. Integer'Last;
-   subtype Buffer_Pos is Base_Buffer_Pos range 1 .. Base_Buffer_Pos'Last; -- 
match Emacs buffer origin.
-   type Buffer_Region is record
-      First : Buffer_Pos;
-      Last  : Base_Buffer_Pos; --  allow representing null range.
-   end record;
-
-   Invalid_Buffer_Pos : constant Buffer_Pos    := Buffer_Pos'Last;
-   Null_Buffer_Region : constant Buffer_Region := (Buffer_Pos'Last, 
Buffer_Pos'First);
-
-   function Length (Region : in Buffer_Region) return Natural is (Natural 
(Region.Last - Region.First + 1));
-
-   function Inside (Pos : in Buffer_Pos; Region : in Buffer_Region) return 
Boolean
-     is (Region.First <= Pos and Pos <= Region.Last);
-
-   function Image (Item : in Buffer_Region) return String;
-
-   function "and" (Left, Right : in Buffer_Region) return Buffer_Region;
-   --  Return region enclosing both Left and Right.
-
-   type Line_Number_Type is range 1 .. Natural'Last; -- Match Emacs buffer 
line numbers.
-
-   Invalid_Line_Number : constant Line_Number_Type := Line_Number_Type'Last;
-
-   type Base_Token is tagged record
-      --  Base_Token is used in the core parser. The parser only needs ID;
-      --  semantic checks need Byte_Region to compare names. Line, Col, and
-      --  Char_Region are included for error messages.
-      ID : Token_ID := Invalid_Token_ID;
-
-      Byte_Region : Buffer_Region := Null_Buffer_Region;
-      --  Index into the Lexer buffer for the token text.
-
-      Line   : Line_Number_Type  := Invalid_Line_Number;
-      Column : Ada.Text_IO.Count := 0;
-      --  At start of token.
-
-      Char_Region : Buffer_Region := Null_Buffer_Region;
-      --  Character position, useful for finding the token location in Emacs
-      --  buffers.
-   end record;
-
-   type Base_Token_Class_Access is access all Base_Token'Class;
-   type Base_Token_Class_Access_Array is array (Positive_Index_Type range <>) 
of Base_Token_Class_Access;
-
-   function Image
-     (Item       : in Base_Token;
-      Descriptor : in WisiToken.Descriptor)
-     return String;
-   --  For debug/test messages.
-
-   procedure Free is new Ada.Unchecked_Deallocation (Base_Token'Class, 
Base_Token_Class_Access);
-
-   Invalid_Token : constant Base_Token := (others => <>);
-
-   type Base_Token_Index is range 0 .. Integer'Last;
-   subtype Token_Index is Base_Token_Index range 1 .. Base_Token_Index'Last;
-
-   type Token_Index_Array is array (Natural range <>) of Token_Index;
-
-   type Base_Token_Array is array (Positive_Index_Type range <>) of Base_Token;
-
-   package Base_Token_Arrays is new SAL.Gen_Unbounded_Definite_Vectors 
(Token_Index, Base_Token);
-   type Base_Token_Array_Access is access all Base_Token_Arrays.Vector;
-
-   Invalid_Token_Index : constant Base_Token_Index := 
Base_Token_Arrays.No_Index;
-
-   package Line_Begin_Token_Vectors is new SAL.Gen_Unbounded_Definite_Vectors 
(Line_Number_Type, Base_Token_Index);
-
-   function Image is new Base_Token_Arrays.Gen_Image_Aux 
(WisiToken.Descriptor, Image);
-
-   function Image
-     (Token      : in Base_Token_Index;
-      Terminals  : in Base_Token_Arrays.Vector;
-      Descriptor : in WisiToken.Descriptor)
-     return String;
-
-   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.
-      ID : Token_ID := Invalid_Token_ID;
-
-      Byte_Region : Buffer_Region := Null_Buffer_Region;
-      --  Byte_Region is used to detect empty tokens, for cost and other 
issues.
-
-      Min_Terminal_Index : Base_Token_Index := Invalid_Token_Index;
-      --  For terminals, index of this token in Shared_Parser.Terminals. For
-      --  nonterminals, minimum of contained tokens. For virtuals,
-      --  Invalid_Token_Index. Used for push_back of nonterminals.
-
-      Name : Buffer_Region := Null_Buffer_Region;
-      --  Set and used by semantic_checks.
-
-      Virtual : Boolean := False;
-      --  For terminals, True if inserted by recover. For nonterminals, True
-      --  if any contained token has Virtual = True. Used by Semantic_Checks
-      --  and push_back.
-   end record;
-
-   function Image
-     (Item       : in Recover_Token;
-      Descriptor : in WisiToken.Descriptor)
-     return String;
-
-   type Recover_Token_Array is array (Positive_Index_Type range <>) of 
Recover_Token;
-
-   package Recover_Token_Arrays is new SAL.Gen_Unbounded_Definite_Vectors 
(Token_Index, Recover_Token);
-
-   function Image is new Recover_Token_Arrays.Gen_Image_Aux 
(WisiToken.Descriptor, Image);
-
-   ----------
-   --  Trace, debug
-
-   Trace_Parse : Integer  := 0;
-   --  If Trace_Parse > 0, Parse prints messages helpful for debugging
-   --  the grammar and/or the parser; higher value prints more.
-   --
-   --  Trace_Parse levels; output info if Trace_Parse > than:
-   --
-   Outline     : constant := 0; -- spawn/terminate parallel parsers, error 
recovery enter/exit
-   Detail      : constant := 1; -- add each parser cycle
-   Extra       : constant := 2; -- add pending semantic state operations
-   Lexer_Debug : constant := 3; -- add lexer debug
-
-   Trace_McKenzie : Integer  := 0;
-   --  If Trace_McKenzie > 0, Parse prints messages helpful for debugging 
error recovery.
-   --
-   --  Outline - error recovery enter/exit
-   --  Detail  - add each error recovery configuration
-   --  Extra   - add error recovery parse actions
-
-   Trace_Action : Integer := 0;
-   --  Output during Execute_Action, and unit tests.
-
-   Trace_Generate : Integer := 0;
-   --  Output during grammar generation.
-
-   Debug_Mode : Boolean := False;
-   --  If True, Output stack traces, propagate exceptions to top level.
-   --  Otherwise, be robust to errors, so user does not notice them.
-
-   type Trace (Descriptor : not null access constant WisiToken.Descriptor) is 
abstract tagged limited null record;
-   --  Output for tests/debugging. Descriptor included here because many
-   --  uses of Trace will use Image (Item, Descriptor);
-
-   procedure Put (Trace : in out WisiToken.Trace; Item : in String) is 
abstract;
-   --  Put Item to the Trace display.
-
-   procedure Put_Line (Trace : in out WisiToken.Trace; Item : in String) is 
abstract;
-   --  Put Item to the Trace display, followed by a newline.
-
-   procedure New_Line (Trace : in out WisiToken.Trace) is abstract;
-   --  Put a newline to the Trace display.
-
-   ----------
-   --  Misc
-
-   function "+" (Item : in String) return 
Ada.Strings.Unbounded.Unbounded_String
-     renames Ada.Strings.Unbounded.To_Unbounded_String;
-
-   function "-" (Item : in Ada.Strings.Unbounded.Unbounded_String) return 
String
-     renames Ada.Strings.Unbounded.To_String;
-
-   function Trimmed_Image is new SAL.Gen_Trimmed_Image (Integer);
-   function Trimmed_Image is new SAL.Gen_Trimmed_Image 
(Ada.Containers.Count_Type);
-
-   function Error_Message
-     (File_Name : in String;
-      Line      : in Line_Number_Type;
-      Column    : in Ada.Text_IO.Count;
-      Message   : in String)
-     return String;
-   --  Return Gnu-formatted error message.
-
-   type Names_Array is array (Integer range <>) of access constant String;
-   type Names_Array_Access is access Names_Array;
-   type Names_Array_Array is array (WisiToken.Token_ID range <>) of 
Names_Array_Access;
-
-end WisiToken;
+--  Abstract:
+--
+--  Root of WisiToken lexer/parser generator and exector.
+--
+--  The token type is an integer subtype, not an enumeration type, to
+--  avoid making this package generic, which would make all other
+--  packages generic.
+--
+--  Additional information about a token can be stored in the
+--  'augmented' field of the syntax tree; see
+--  wisitoken-syntax_trees.ads.
+--
+--  References:
+--
+--  [dragon] "Compilers Principles, Techniques, and Tools" by Aho,
+--  Sethi, and Ullman (aka: "The [Red] Dragon Book" due to the dragon
+--  on the cover).
+--
+--  Copyright (C) 2009, 2010, 2013 - 2015, 2017 - 2019 Free Software 
Foundation, Inc.
+--
+--  This file is part of the WisiToken package.
+--
+--  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.
+--
+--  This software was originally developed with the name OpenToken by
+--  the following company, and was released as open-source software as
+--  a service to the community:
+--
+--           FlightSafety International Simulation Systems Division
+--                    Broken Arrow, OK  USA  918-259-4000
+
+pragma License (Modified_GPL);
+
+with Ada.Containers;
+with Ada.Strings.Unbounded;
+with Ada.Text_IO;
+with Ada.Unchecked_Deallocation;
+with SAL.Gen_Trimmed_Image;
+with SAL.Gen_Unbounded_Definite_Queues;
+with SAL.Gen_Unbounded_Definite_Vectors.Gen_Image;
+with SAL.Gen_Unbounded_Definite_Vectors.Gen_Image_Aux;
+package WisiToken is
+
+   Partial_Parse : exception; -- a partial parse terminated.
+
+   Syntax_Error : exception; -- no recovery for a syntax error was found
+
+   Parse_Error : exception; -- a non-recoverable non-fatal error was 
encountered; editing the input can fix the error.
+
+   Fatal_Error : exception; -- Error in code or grammar; editing input cannot 
fix error.
+
+   Grammar_Error : exception;
+   --  Grammar file has bad syntax, or grammar is not consistent (ie
+   --  unused tokens, missing productions, invalid actions)
+
+   User_Error : exception; -- other user error (ie command line parameter)
+
+   --  SAL.Programmer_Error : exception; -- a programming convention has been 
violated
+
+   subtype Positive_Index_Type is SAL.Peek_Type;
+   function Trimmed_Image is new SAL.Gen_Trimmed_Image (SAL.Base_Peek_Type);
+
+   type Unknown_State_Index is new Integer range -1 .. Integer'Last;
+   subtype State_Index is Unknown_State_Index range 0 .. 
Unknown_State_Index'Last;
+   Unknown_State : constant Unknown_State_Index := -1;
+
+   function Trimmed_Image is new SAL.Gen_Trimmed_Image (Unknown_State_Index);
+
+   package State_Index_Queues is new SAL.Gen_Unbounded_Definite_Queues 
(State_Index);
+   package State_Index_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
+     (Positive, State_Index, Default_Element => State_Index'Last);
+   function Image is new State_Index_Arrays.Gen_Image (Trimmed_Image);
+
+   ----------
+   --  Token IDs
+
+   type Token_ID is range 0 .. Integer'Last; -- 0 origin to match elisp array
+
+   Invalid_Token_ID : constant Token_ID := Token_ID'Last;
+
+   type Token_ID_Array_String is array (Token_ID range <>) of access constant 
String;
+   type Token_ID_Array_Natural is array (Token_ID range <>) of Natural;
+
+   type Descriptor
+     (First_Terminal    : Token_ID;
+      Last_Terminal     : Token_ID;
+      First_Nonterminal : Token_ID;
+      Last_Nonterminal  : Token_ID;
+      EOI_ID            : Token_ID;
+      Accept_ID         : Token_ID)
+   is record
+      --  Tokens in the range Token_ID'First .. First_Terminal - 1 are
+      --  non-reporting (comments, whitespace), and thus are not used in
+      --  generating parse tables.
+      --
+      --  Tokens in the range Last_Terminal + 1 .. Last_Nonterminal are
+      --  the nonterminals of a grammar.
+      --
+      --  Components are discriminants if they can be specified statically.
+
+      Case_Insensitive : Boolean; -- keywords and names
+      New_Line_ID      : Token_ID;
+      Comment_ID       : Token_ID;
+      Left_Paren_ID    : Token_ID;
+      Right_Paren_ID   : Token_ID;
+      --  If the language does not define these tokens, set them to
+      --  Invalid_Token_ID.
+
+      String_1_ID  : Token_ID; -- delimited by ', error if New_Line_ID
+      String_2_ID  : Token_ID; -- delimited by ", error if New_Line_ID
+      --
+      --  Support for missing quote error recovery. If the language does not
+      --  have two kinds of string literals, set one or both of these to
+      --  Invalid_Token_ID.
+
+      Embedded_Quote_Escape_Doubled : Boolean;
+      --  True if quote characters embedded in strings are escaped by
+      --  doubling (as in Ada); false if by preceding with backslash (as in
+      --  C).
+
+      Image : Token_ID_Array_String (Token_ID'First .. Last_Nonterminal);
+      --  User names for tokens.
+
+      Terminal_Image_Width : Integer;
+      Image_Width          : Integer; --  max width of Image
+
+      --  LALR generate needs a 'Propagate_ID' lookahead that is distinct
+      --  from all terminals. Since lookaheads are Token_ID_Set, we need to
+      --  allocate First_Terminal .. Last_Terminal for LR1 generate, and
+      --  First_Terminal .. Propagate_ID for LALR generate, so we define
+      --  Last_Lookahead. After the LR table is generated, Last_Lookahead is
+      --  no longer used.
+      Last_Lookahead : Token_ID;
+   end record;
+
+   function Padded_Image (Item : in Token_ID; Desc : in Descriptor) return 
String;
+   --  Return Desc.Image (Item), padded to Terminal_Image_Width (if Item
+   --  is a terminal) or to Image_Width.
+
+   function Image (Item : in Token_ID; Desc : in Descriptor) return String;
+   --  Return Desc.Image (Item), or empty string for Invalid_Token_ID.
+
+   function Trimmed_Image is new SAL.Gen_Trimmed_Image (Token_ID);
+
+   procedure Put_Tokens (Descriptor : in WisiToken.Descriptor);
+   --  Put user readable token list (token_id'first ..
+   --  descriptor.last_nonterminal) to Ada.Text_IO.Current_Output
+
+   function Find_ID (Descriptor : in WisiToken.Descriptor; Name : in String) 
return Token_ID;
+   --  Return index of Name in Descriptor.Image. If not found, raise 
Programmer_Error.
+
+   type Token_ID_Array is array (Positive range <>) of Token_ID;
+
+   package Token_ID_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
+     (Positive, Token_ID, Default_Element => Invalid_Token_ID);
+
+   function Image is new Token_ID_Arrays.Gen_Image_Aux (Descriptor, Image);
+   function Trimmed_Image is new Token_ID_Arrays.Gen_Image (Trimmed_Image);
+
+   procedure To_Vector (Item : in Token_ID_Array; Vector : in out 
Token_ID_Arrays.Vector);
+
+   function Shared_Prefix (A, B : in Token_ID_Arrays.Vector) return Natural;
+   --  Return last index in A of a prefix shared between A, B; 0 if none.
+
+   type Token_ID_Set is array (Token_ID range <>) of Boolean;
+
+   function "&" (Left : in Token_ID_Set; Right : in Token_ID) return 
Token_ID_Set;
+   --  Include Left and Right in result.
+
+   function To_Token_ID_Set (First, Last : in Token_ID; Item : in 
Token_ID_Array) return Token_ID_Set;
+   --  First, Last determine size of result.
+   --  For each element in Item, set result (element) True.
+
+   procedure To_Set (Item : in Token_ID_Arrays.Vector; Set : out Token_ID_Set);
+   --  For each element of Item, set Set (element) True.
+
+   function To_Array (Item : in Token_ID_Set) return Token_ID_Arrays.Vector;
+
+   function Any (Item : in Token_ID_Set) return Boolean;
+
+   function Count (Item : in Token_ID_Set) return Integer;
+   --  Count of True elements.
+
+   function Image
+     (Item      : in Token_ID_Set;
+      Desc      : in Descriptor;
+      Max_Count : in Integer := Integer'Last;
+      Inverted  : in Boolean := False)
+     return String;
+   --  For diagnostics; not Ada syntax.
+
+   type Token_Array_Token_Set is array (Token_ID range <>, Token_ID range <>) 
of Boolean;
+
+   function Slice (Item : in Token_Array_Token_Set; I : in Token_ID) return 
Token_ID_Set;
+   function Any (Item : in Token_Array_Token_Set; I : in Token_ID) return 
Boolean;
+   function Any (Item : in Token_Array_Token_Set) return Boolean;
+   procedure Or_Slice (Item : in out Token_Array_Token_Set; I : in Token_ID; 
Value : in Token_ID_Set);
+
+   procedure Put (Descriptor : in WisiToken.Descriptor; Item : in 
Token_Array_Token_Set);
+   --  Put Item to Ada.Text_IO.Current_Output, using valid Ada aggregate
+   --  syntax.
+
+   type Token_Array_Token_ID is array (Token_ID range <>) of Token_ID;
+
+   package Token_Sequence_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
+     (Token_ID, Token_ID_Arrays.Vector, Default_Element => 
Token_ID_Arrays.Empty_Vector);
+
+   ----------
+   --  Production IDs; see wisitoken-productions.ads for more
+
+   type Production_ID is record
+      LHS : Token_ID := Invalid_Token_ID;
+      RHS : Natural  := 0;
+      --  Index into the production table.
+   end record;
+
+   Invalid_Production_ID : constant Production_ID := (others => <>);
+
+   function Image (Item : in Production_ID) return String;
+   --  Ada positional aggregate syntax, for code generation.
+
+   function Trimmed_Image (Item : in Production_ID) return String;
+   --  Nonterm.rhs_index, both integers, no leading or trailing space;
+   --  for parse table output and diagnostics.
+
+   Prod_ID_Image_Width : constant Integer := 7;
+   --  Max width of Trimmed_Image
+
+   function Padded_Image (Item : in Production_ID; Width : in Integer) return 
String;
+   --  Trimmed_Image padded with leading spaces to Width
+
+   package Production_ID_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
+     (Positive, Production_ID, Default_Element => Invalid_Production_ID);
+   function Image is new Production_ID_Arrays.Gen_Image (Image);
+   function Trimmed_Image is new Production_ID_Arrays.Gen_Image 
(Trimmed_Image);
+
+   type Production_ID_Array is array (Natural range <>) of Production_ID;
+
+   function To_Vector (Item : in Production_ID_Array) return 
Production_ID_Arrays.Vector;
+   function "+" (Item : in Production_ID_Array) return 
Production_ID_Arrays.Vector renames To_Vector;
+   function "+" (Item : in Production_ID) return Production_ID_Arrays.Vector 
is (To_Vector ((1 => Item)));
+
+   ----------
+   --  Tokens
+
+   type Base_Buffer_Pos is range 0 .. Integer'Last;
+   subtype Buffer_Pos is Base_Buffer_Pos range 1 .. Base_Buffer_Pos'Last; -- 
match Emacs buffer origin.
+   type Buffer_Region is record
+      First : Buffer_Pos;
+      Last  : Base_Buffer_Pos; --  allow representing null range.
+   end record;
+
+   Invalid_Buffer_Pos : constant Buffer_Pos    := Buffer_Pos'Last;
+   Null_Buffer_Region : constant Buffer_Region := (Buffer_Pos'Last, 
Buffer_Pos'First);
+
+   function Length (Region : in Buffer_Region) return Natural is (Natural 
(Region.Last - Region.First + 1));
+
+   function Inside (Pos : in Buffer_Pos; Region : in Buffer_Region) return 
Boolean
+     is (Region.First <= Pos and Pos <= Region.Last);
+
+   function Image (Item : in Buffer_Region) return String;
+
+   function "and" (Left, Right : in Buffer_Region) return Buffer_Region;
+   --  Return region enclosing both Left and Right.
+
+   type Line_Number_Type is range 1 .. Natural'Last; -- Match Emacs buffer 
line numbers.
+
+   Invalid_Line_Number : constant Line_Number_Type := Line_Number_Type'Last;
+
+   type Base_Token is tagged record
+      --  Base_Token is used in the core parser. The parser only needs ID;
+      --  semantic checks need Byte_Region to compare names. Line, Col, and
+      --  Char_Region are included for error messages.
+      ID : Token_ID := Invalid_Token_ID;
+
+      Byte_Region : Buffer_Region := Null_Buffer_Region;
+      --  Index into the Lexer buffer for the token text.
+
+      Line   : Line_Number_Type  := Invalid_Line_Number;
+      Column : Ada.Text_IO.Count := 0;
+      --  At start of token.
+
+      Char_Region : Buffer_Region := Null_Buffer_Region;
+      --  Character position, useful for finding the token location in Emacs
+      --  buffers.
+   end record;
+
+   type Base_Token_Class_Access is access all Base_Token'Class;
+   type Base_Token_Class_Access_Array is array (Positive_Index_Type range <>) 
of Base_Token_Class_Access;
+
+   function Image
+     (Item       : in Base_Token;
+      Descriptor : in WisiToken.Descriptor)
+     return String;
+   --  For debug/test messages.
+
+   procedure Free is new Ada.Unchecked_Deallocation (Base_Token'Class, 
Base_Token_Class_Access);
+
+   Invalid_Token : constant Base_Token := (others => <>);
+
+   type Base_Token_Index is range 0 .. Integer'Last;
+   subtype Token_Index is Base_Token_Index range 1 .. Base_Token_Index'Last;
+
+   Invalid_Token_Index : constant Base_Token_Index := Base_Token_Index'First;
+
+   type Token_Index_Array is array (Natural range <>) of Token_Index;
+
+   package Recover_Token_Index_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
+     (Natural, Base_Token_Index, Default_Element => Invalid_Token_Index);
+
+   type Base_Token_Array is array (Positive_Index_Type range <>) of Base_Token;
+
+   package Base_Token_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
+     (Token_Index, Base_Token, Default_Element => (others => <>));
+   type Base_Token_Array_Access is access all Base_Token_Arrays.Vector;
+
+   package Line_Begin_Token_Vectors is new SAL.Gen_Unbounded_Definite_Vectors
+     (Line_Number_Type, Base_Token_Index, Default_Element => 
Invalid_Token_Index);
+
+   function Image is new Base_Token_Arrays.Gen_Image_Aux 
(WisiToken.Descriptor, Image);
+
+   function Image
+     (Token      : in Base_Token_Index;
+      Terminals  : in Base_Token_Arrays.Vector;
+      Descriptor : in WisiToken.Descriptor)
+     return String;
+
+   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.
+      ID : Token_ID := Invalid_Token_ID;
+
+      Byte_Region : Buffer_Region := Null_Buffer_Region;
+      --  Byte_Region is used to detect empty tokens, for cost and other 
issues.
+
+      Min_Terminal_Index : Base_Token_Index := Invalid_Token_Index;
+      --  For terminals, index of this token in Shared_Parser.Terminals. For
+      --  nonterminals, minimum of contained tokens. For virtuals,
+      --  Invalid_Token_Index. Used for push_back of nonterminals.
+
+      Name : Buffer_Region := Null_Buffer_Region;
+      --  Set and used by semantic_checks.
+
+      Virtual : Boolean := False;
+      --  For terminals, True if inserted by recover. For nonterminals, True
+      --  if any contained token has Virtual = True. Used by Semantic_Checks
+      --  and push_back.
+   end record;
+
+   function Image
+     (Item       : in Recover_Token;
+      Descriptor : in WisiToken.Descriptor)
+     return String;
+
+   type Recover_Token_Array is array (Positive_Index_Type range <>) of 
Recover_Token;
+
+   package Recover_Token_Arrays is new SAL.Gen_Unbounded_Definite_Vectors
+     (Token_Index, Recover_Token, Default_Element => (others => <>));
+
+   function Image is new Recover_Token_Arrays.Gen_Image_Aux 
(WisiToken.Descriptor, Image);
+
+   ----------
+   --  Trace, debug
+
+   Trace_Parse : Integer  := 0;
+   --  If Trace_Parse > 0, Parse prints messages helpful for debugging
+   --  the grammar and/or the parser; higher value prints more.
+   --
+   --  Trace_Parse levels; output info if Trace_Parse > than:
+   --
+   Outline     : constant := 0; -- spawn/terminate parallel parsers, error 
recovery enter/exit
+   Detail      : constant := 1; -- add each parser cycle
+   Extra       : constant := 2; -- add pending semantic state operations
+   Lexer_Debug : constant := 3; -- add lexer debug
+
+   Trace_McKenzie : Integer  := 0;
+   --  If Trace_McKenzie > 0, Parse prints messages helpful for debugging 
error recovery.
+   --
+   --  Outline - error recovery enter/exit
+   --  Detail  - add each error recovery configuration
+   --  Extra   - add error recovery parse actions
+
+   Trace_Action : Integer := 0;
+   --  Output during Execute_Action, and unit tests.
+
+   Trace_Generate : Integer := 0;
+   --  Output during grammar generation.
+
+   Debug_Mode : Boolean := False;
+   --  If True, Output stack traces, propagate exceptions to top level.
+   --  Otherwise, be robust to errors, so user does not notice them.
+
+   type Trace (Descriptor : not null access constant WisiToken.Descriptor) is 
abstract tagged limited null record;
+   --  Output for tests/debugging. Descriptor included here because many
+   --  uses of Trace will use Image (Item, Descriptor);
+
+   procedure Set_Prefix (Trace : in out WisiToken.Trace; Prefix : in String) 
is abstract;
+   --  Prepend Prefix to all subsequent messages. Usefull for adding
+   --  comment syntax.
+
+   procedure Put (Trace : in out WisiToken.Trace; Item : in String; Prefix : 
in Boolean := True) is abstract;
+   --  Put Item to the Trace display. If Prefix is True, prepend the stored 
prefix.
+
+   procedure Put_Line (Trace : in out WisiToken.Trace; Item : in String) is 
abstract;
+   --  Put Item to the Trace display, followed by a newline.
+
+   procedure New_Line (Trace : in out WisiToken.Trace) is abstract;
+   --  Put a newline to the Trace display.
+
+   procedure Put_Clock (Trace : in out WisiToken.Trace; Label : in String) is 
abstract;
+   --  Put Ada.Calendar.Clock to Trace.
+
+   ----------
+   --  Misc
+
+   function "+" (Item : in String) return 
Ada.Strings.Unbounded.Unbounded_String
+     renames Ada.Strings.Unbounded.To_Unbounded_String;
+
+   function "-" (Item : in Ada.Strings.Unbounded.Unbounded_String) return 
String
+     renames Ada.Strings.Unbounded.To_String;
+
+   function Trimmed_Image is new SAL.Gen_Trimmed_Image (Integer);
+   function Trimmed_Image is new SAL.Gen_Trimmed_Image 
(Ada.Containers.Count_Type);
+
+   function Error_Message
+     (File_Name : in String;
+      Line      : in Line_Number_Type;
+      Column    : in Ada.Text_IO.Count;
+      Message   : in String)
+     return String;
+   --  Return Gnu-formatted error message.
+
+   type Names_Array is array (Integer range <>) of access constant String;
+   type Names_Array_Access is access Names_Array;
+   type Names_Array_Array is array (WisiToken.Token_ID range <>) of 
Names_Array_Access;
+
+end WisiToken;
diff --git a/wisitoken_grammar_actions.ads b/wisitoken_grammar_actions.ads
index 25b68e8..359a73d 100644
--- a/wisitoken_grammar_actions.ads
+++ b/wisitoken_grammar_actions.ads
@@ -29,7 +29,7 @@ package Wisitoken_Grammar_Actions is
       Last_Terminal                 => 25,
       First_Nonterminal             => 26,
       Last_Nonterminal              => 37,
-      EOF_ID                        => 25,
+      EOI_ID                        => 25,
       Accept_ID                     => 26,
       Case_Insensitive              => False,
       New_Line_ID                   => 1,
diff --git a/wisitoken_grammar_main.adb b/wisitoken_grammar_main.adb
index 5470e30..b8cb1c5 100644
--- a/wisitoken_grammar_main.adb
+++ b/wisitoken_grammar_main.adb
@@ -56,7 +56,6 @@ package body Wisitoken_Grammar_Main is
             Add_Goto (Table.States (0), 32, 4);
             Add_Goto (Table.States (0), 36, 5);
             Add_Goto (Table.States (0), 37, 6);
-            Set_Minimal_Action (Table.States (0).Minimal_Complete_Actions, (1 
=> (Shift, 18, 1)));
             Add_Action (Table.States (1), 3, 7);
             Add_Action (Table.States (1), 4, 8);
             Add_Action (Table.States (1), 5, 9);
@@ -66,17 +65,11 @@ package body Wisitoken_Grammar_Main is
             Add_Action (Table.States (1), 22, 13);
             Add_Error (Table.States (1));
             Add_Goto (Table.States (1), 28, 14);
-            Set_Minimal_Action (Table.States (1).Minimal_Complete_Actions, 
((Shift, 3, 7), (Shift, 4, 8), (Shift, 5,
-            9), (Shift, 6, 10), (Shift, 22, 13)));
             Add_Action (Table.States (2), 13, 15);
             Add_Error (Table.States (2));
-            Set_Minimal_Action (Table.States (2).Minimal_Complete_Actions, (1 
=> (Shift, 13, 15)));
             Add_Action (Table.States (3), (18, 22, 25), (36, 0), 1, null, 
null);
-            Set_Minimal_Action (Table.States (3).Minimal_Complete_Actions, (1 
=> (Reduce, 36, 1)));
             Add_Action (Table.States (4), (18, 22, 25), (36, 1), 1, null, 
null);
-            Set_Minimal_Action (Table.States (4).Minimal_Complete_Actions, (1 
=> (Reduce, 36, 1)));
             Add_Action (Table.States (5), (18, 22, 25), (37, 0), 1, null, 
null);
-            Set_Minimal_Action (Table.States (5).Minimal_Complete_Actions, (1 
=> (Reduce, 37, 1)));
             Add_Action (Table.States (6), 18, 1);
             Add_Action (Table.States (6), 22, 2);
             Add_Action (Table.States (6), 25, Accept_It, (26, 0), 1, null, 
null);
@@ -87,21 +80,15 @@ package body Wisitoken_Grammar_Main is
             Add_Action (Table.States (7), 22, 17);
             Add_Error (Table.States (7));
             Add_Goto (Table.States (7), 29, 18);
-            Set_Minimal_Action (Table.States (7).Minimal_Complete_Actions, (1 
=> (Shift, 22, 17)));
             Add_Action (Table.States (8), 5, 19);
             Add_Error (Table.States (8));
-            Set_Minimal_Action (Table.States (8).Minimal_Complete_Actions, (1 
=> (Shift, 5, 19)));
             Add_Action (Table.States (9), 22, 20);
             Add_Error (Table.States (9));
-            Set_Minimal_Action (Table.States (9).Minimal_Complete_Actions, (1 
=> (Shift, 22, 20)));
             Add_Action (Table.States (10), (1 =>  22), (28, 0), 1, null, null);
-            Set_Minimal_Action (Table.States (10).Minimal_Complete_Actions, (1 
=> (Reduce, 28, 1)));
             Add_Action (Table.States (11), 17, 21);
             Add_Error (Table.States (11));
-            Set_Minimal_Action (Table.States (11).Minimal_Complete_Actions, (1 
=> (Shift, 17, 21)));
             Add_Action (Table.States (12), 17, 22);
             Add_Error (Table.States (12));
-            Set_Minimal_Action (Table.States (12).Minimal_Complete_Actions, (1 
=> (Shift, 17, 22)));
             Add_Action (Table.States (13), 8, 23);
             Add_Action (Table.States (13), 10, 24);
             Add_Action (Table.States (13), 14, 25);
@@ -116,10 +103,8 @@ package body Wisitoken_Grammar_Main is
             Add_Error (Table.States (13));
             Add_Goto (Table.States (13), 30, 32);
             Add_Goto (Table.States (13), 31, 33);
-            Set_Minimal_Action (Table.States (13).Minimal_Complete_Actions, (1 
=> (Reduce, 27, 2)));
             Add_Action (Table.States (14), 22, 34);
             Add_Error (Table.States (14));
-            Set_Minimal_Action (Table.States (14).Minimal_Complete_Actions, (1 
=> (Shift, 22, 34)));
             Add_Action (Table.States (15), 12, Reduce, (34, 0), 0, null, null);
             Add_Action (Table.States (15), 18, Reduce, (34, 0), 0, null, null);
             Add_Action (Table.States (15), 19, Reduce, (34, 0), 0, null, null);
@@ -128,44 +113,27 @@ package body Wisitoken_Grammar_Main is
             Add_Goto (Table.States (15), 33, 36);
             Add_Goto (Table.States (15), 34, 37);
             Add_Goto (Table.States (15), 35, 38);
-            Set_Minimal_Action (Table.States (15).Minimal_Complete_Actions, (1 
=> (Reduce, 33, 0)));
             Add_Action (Table.States (16), (18, 22, 25), (37, 1), 2, null, 
null);
-            Set_Minimal_Action (Table.States (16).Minimal_Complete_Actions, (1 
=> (Reduce, 37, 2)));
             Add_Action (Table.States (17), (9, 22), (29, 0), 1, null, null);
-            Set_Minimal_Action (Table.States (17).Minimal_Complete_Actions, (1 
=> (Reduce, 29, 1)));
             Add_Action (Table.States (18), 9, 39);
             Add_Action (Table.States (18), 22, 40);
             Add_Error (Table.States (18));
-            Set_Minimal_Action (Table.States (18).Minimal_Complete_Actions, (1 
=> (Shift, 9, 39)));
             Add_Action (Table.States (19), (18, 22, 25), (27, 5), 3, 
declaration_5'Access, null);
-            Set_Minimal_Action (Table.States (19).Minimal_Complete_Actions, (1 
=> (Reduce, 27, 3)));
             Add_Action (Table.States (20), 15, 41);
             Add_Error (Table.States (20));
-            Set_Minimal_Action (Table.States (20).Minimal_Complete_Actions, (1 
=> (Shift, 15, 41)));
             Add_Action (Table.States (21), 22, 42);
             Add_Error (Table.States (21));
-            Set_Minimal_Action (Table.States (21).Minimal_Complete_Actions, (1 
=> (Shift, 22, 42)));
             Add_Action (Table.States (22), 22, 43);
             Add_Error (Table.States (22));
-            Set_Minimal_Action (Table.States (22).Minimal_Complete_Actions, (1 
=> (Shift, 22, 43)));
             Add_Action (Table.States (23), (8, 10, 14, 15, 18, 20, 21, 22, 23, 
24, 25), (31, 8), 1, null, null);
-            Set_Minimal_Action (Table.States (23).Minimal_Complete_Actions, (1 
=> (Reduce, 31, 1)));
             Add_Action (Table.States (24), (8, 10, 14, 15, 18, 20, 21, 22, 23, 
24, 25), (31, 4), 1, null, null);
-            Set_Minimal_Action (Table.States (24).Minimal_Complete_Actions, (1 
=> (Reduce, 31, 1)));
             Add_Action (Table.States (25), (8, 10, 14, 15, 18, 20, 21, 22, 23, 
24, 25), (31, 0), 1, null, null);
-            Set_Minimal_Action (Table.States (25).Minimal_Complete_Actions, (1 
=> (Reduce, 31, 1)));
             Add_Action (Table.States (26), (8, 10, 14, 15, 18, 20, 21, 22, 23, 
24, 25), (31, 2), 1, null, null);
-            Set_Minimal_Action (Table.States (26).Minimal_Complete_Actions, (1 
=> (Reduce, 31, 1)));
             Add_Action (Table.States (27), (8, 10, 14, 15, 18, 20, 21, 22, 23, 
24, 25), (31, 5), 1, null, null);
-            Set_Minimal_Action (Table.States (27).Minimal_Complete_Actions, (1 
=> (Reduce, 31, 1)));
             Add_Action (Table.States (28), (8, 10, 14, 15, 18, 20, 21, 22, 23, 
24, 25), (31, 3), 1, null, null);
-            Set_Minimal_Action (Table.States (28).Minimal_Complete_Actions, (1 
=> (Reduce, 31, 1)));
             Add_Action (Table.States (29), (8, 10, 14, 15, 18, 20, 21, 22, 23, 
24, 25), (31, 1), 1, null, null);
-            Set_Minimal_Action (Table.States (29).Minimal_Complete_Actions, (1 
=> (Reduce, 31, 1)));
             Add_Action (Table.States (30), (8, 10, 14, 15, 18, 20, 21, 22, 23, 
24, 25), (31, 6), 1, null, null);
-            Set_Minimal_Action (Table.States (30).Minimal_Complete_Actions, (1 
=> (Reduce, 31, 1)));
             Add_Action (Table.States (31), (8, 10, 14, 15, 18, 20, 21, 22, 23, 
24, 25), (31, 7), 1, null, null);
-            Set_Minimal_Action (Table.States (31).Minimal_Complete_Actions, (1 
=> (Reduce, 31, 1)));
             Add_Action (Table.States (32), 8, 23);
             Add_Action (Table.States (32), 10, 24);
             Add_Action (Table.States (32), 14, 25);
@@ -179,9 +147,7 @@ package body Wisitoken_Grammar_Main is
             Add_Action (Table.States (32), 25, Reduce, (27, 2), 3, 
declaration_2'Access, null);
             Add_Error (Table.States (32));
             Add_Goto (Table.States (32), 31, 44);
-            Set_Minimal_Action (Table.States (32).Minimal_Complete_Actions, (1 
=> (Reduce, 27, 3)));
             Add_Action (Table.States (33), (8, 10, 14, 15, 18, 20, 21, 22, 23, 
24, 25), (30, 0), 1, null, null);
-            Set_Minimal_Action (Table.States (33).Minimal_Complete_Actions, (1 
=> (Reduce, 30, 1)));
             Add_Action (Table.States (34), 8, 23);
             Add_Action (Table.States (34), 10, 24);
             Add_Action (Table.States (34), 14, 25);
@@ -194,38 +160,27 @@ package body Wisitoken_Grammar_Main is
             Add_Error (Table.States (34));
             Add_Goto (Table.States (34), 30, 45);
             Add_Goto (Table.States (34), 31, 33);
-            Set_Minimal_Action (Table.States (34).Minimal_Complete_Actions, (1 
=> (Shift, 8, 23)));
             Add_Action (Table.States (35), (11, 12, 18, 19, 22), (35, 0), 1, 
null, null);
-            Set_Minimal_Action (Table.States (35).Minimal_Complete_Actions, (1 
=> (Reduce, 35, 1)));
             Add_Action (Table.States (36), 12, 46);
             Add_Action (Table.States (36), 18, 47);
             Add_Action (Table.States (36), 19, 48);
             Add_Error (Table.States (36));
-            Set_Minimal_Action (Table.States (36).Minimal_Complete_Actions, (1 
=> (Shift, 19, 48)));
             Add_Action (Table.States (37), (12, 18, 19), (33, 0), 1, null, 
null);
-            Set_Minimal_Action (Table.States (37).Minimal_Complete_Actions, (1 
=> (Reduce, 33, 1)));
             Add_Action (Table.States (38), 11, 49);
             Add_Action (Table.States (38), 12, Reduce, (34, 1), 1, null, null);
             Add_Action (Table.States (38), 18, Reduce, (34, 1), 1, null, null);
             Add_Action (Table.States (38), 19, Reduce, (34, 1), 1, null, null);
             Add_Action (Table.States (38), 22, 50);
             Add_Error (Table.States (38));
-            Set_Minimal_Action (Table.States (38).Minimal_Complete_Actions, (1 
=> (Reduce, 34, 1)));
             Add_Action (Table.States (39), (18, 22, 25), (27, 1), 4, 
declaration_1'Access, null);
-            Set_Minimal_Action (Table.States (39).Minimal_Complete_Actions, (1 
=> (Reduce, 27, 4)));
             Add_Action (Table.States (40), (9, 22), (29, 1), 2, null, null);
-            Set_Minimal_Action (Table.States (40).Minimal_Complete_Actions, (1 
=> (Reduce, 29, 2)));
             Add_Action (Table.States (41), 22, 51);
             Add_Error (Table.States (41));
-            Set_Minimal_Action (Table.States (41).Minimal_Complete_Actions, (1 
=> (Shift, 22, 51)));
             Add_Action (Table.States (42), 16, 52);
             Add_Error (Table.States (42));
-            Set_Minimal_Action (Table.States (42).Minimal_Complete_Actions, (1 
=> (Shift, 16, 52)));
             Add_Action (Table.States (43), 16, 53);
             Add_Error (Table.States (43));
-            Set_Minimal_Action (Table.States (43).Minimal_Complete_Actions, (1 
=> (Shift, 16, 53)));
             Add_Action (Table.States (44), (8, 10, 14, 15, 18, 20, 21, 22, 23, 
24, 25), (30, 1), 2, null, null);
-            Set_Minimal_Action (Table.States (44).Minimal_Complete_Actions, (1 
=> (Reduce, 30, 2)));
             Add_Action (Table.States (45), 8, 23);
             Add_Action (Table.States (45), 10, 24);
             Add_Action (Table.States (45), 14, 25);
@@ -239,7 +194,6 @@ package body Wisitoken_Grammar_Main is
             Add_Action (Table.States (45), 25, Reduce, (27, 0), 4, 
declaration_0'Access, null);
             Add_Error (Table.States (45));
             Add_Goto (Table.States (45), 31, 44);
-            Set_Minimal_Action (Table.States (45).Minimal_Complete_Actions, (1 
=> (Reduce, 27, 4)));
             Add_Action (Table.States (46), 12, Reduce, (34, 0), 0, null, null);
             Add_Action (Table.States (46), 18, Reduce, (34, 0), 0, null, null);
             Add_Action (Table.States (46), 19, Reduce, (34, 0), 0, null, null);
@@ -247,47 +201,31 @@ package body Wisitoken_Grammar_Main is
             Add_Error (Table.States (46));
             Add_Goto (Table.States (46), 34, 54);
             Add_Goto (Table.States (46), 35, 38);
-            Set_Minimal_Action (Table.States (46).Minimal_Complete_Actions, (1 
=> (Reduce, 34, 0)));
             Add_Action (Table.States (47), 4, 55);
             Add_Action (Table.States (47), 5, 56);
             Add_Error (Table.States (47));
-            Set_Minimal_Action (Table.States (47).Minimal_Complete_Actions, 
((Shift, 4, 55), (Shift, 5, 56)));
             Add_Action (Table.States (48), (18, 22, 25), (32, 0), 4, 
nonterminal_0'Access, null);
-            Set_Minimal_Action (Table.States (48).Minimal_Complete_Actions, (1 
=> (Reduce, 32, 4)));
             Add_Action (Table.States (49), 11, 57);
             Add_Action (Table.States (49), 12, Reduce, (34, 2), 2, null, null);
             Add_Action (Table.States (49), 18, Reduce, (34, 2), 2, null, null);
             Add_Action (Table.States (49), 19, Reduce, (34, 2), 2, null, null);
             Add_Error (Table.States (49));
-            Set_Minimal_Action (Table.States (49).Minimal_Complete_Actions, (1 
=> (Reduce, 34, 2)));
             Add_Action (Table.States (50), (11, 12, 18, 19, 22), (35, 1), 2, 
null, null);
-            Set_Minimal_Action (Table.States (50).Minimal_Complete_Actions, (1 
=> (Reduce, 35, 2)));
             Add_Action (Table.States (51), (18, 22, 25), (27, 4), 5, 
declaration_4'Access, null);
-            Set_Minimal_Action (Table.States (51).Minimal_Complete_Actions, (1 
=> (Reduce, 27, 5)));
             Add_Action (Table.States (52), (1 =>  22), (28, 1), 4, null, null);
-            Set_Minimal_Action (Table.States (52).Minimal_Complete_Actions, (1 
=> (Reduce, 28, 4)));
             Add_Action (Table.States (53), (1 =>  22), (28, 2), 4, null, null);
-            Set_Minimal_Action (Table.States (53).Minimal_Complete_Actions, (1 
=> (Reduce, 28, 4)));
             Add_Action (Table.States (54), (12, 18, 19), (33, 1), 3, null, 
null);
-            Set_Minimal_Action (Table.States (54).Minimal_Complete_Actions, (1 
=> (Reduce, 33, 3)));
             Add_Action (Table.States (55), 5, 58);
             Add_Error (Table.States (55));
-            Set_Minimal_Action (Table.States (55).Minimal_Complete_Actions, (1 
=> (Shift, 5, 58)));
             Add_Action (Table.States (56), 22, 59);
             Add_Error (Table.States (56));
-            Set_Minimal_Action (Table.States (56).Minimal_Complete_Actions, (1 
=> (Shift, 22, 59)));
             Add_Action (Table.States (57), (12, 18, 19), (34, 3), 3, null, 
null);
-            Set_Minimal_Action (Table.States (57).Minimal_Complete_Actions, (1 
=> (Reduce, 34, 3)));
             Add_Action (Table.States (58), (12, 18, 19), (33, 3), 4, null, 
null);
-            Set_Minimal_Action (Table.States (58).Minimal_Complete_Actions, (1 
=> (Reduce, 33, 4)));
             Add_Action (Table.States (59), 15, 60);
             Add_Error (Table.States (59));
-            Set_Minimal_Action (Table.States (59).Minimal_Complete_Actions, (1 
=> (Shift, 15, 60)));
             Add_Action (Table.States (60), 22, 61);
             Add_Error (Table.States (60));
-            Set_Minimal_Action (Table.States (60).Minimal_Complete_Actions, (1 
=> (Shift, 22, 61)));
             Add_Action (Table.States (61), (12, 18, 19), (33, 2), 6, null, 
null);
-            Set_Minimal_Action (Table.States (61).Minimal_Complete_Actions, (1 
=> (Reduce, 33, 6)));
          end Subr_1;
       begin
          Subr_1;
@@ -296,7 +234,7 @@ package body Wisitoken_Grammar_Main is
       WisiToken.Parse.LR.Parser_No_Recover.New_Parser
         (Parser,
          Trace,
-         Lexer.New_Lexer (Trace),
+         Lexer.New_Lexer (Trace.Descriptor),
          Table,
          User_Data,
          Max_Parallel         => 15,
diff --git a/wisitoken_grammar_re2c.c b/wisitoken_grammar_re2c.c
index b45ab3a..6ad6b86 100644
--- a/wisitoken_grammar_re2c.c
+++ b/wisitoken_grammar_re2c.c
@@ -39,7 +39,7 @@ typedef struct wisi_lexer
    int            line_token_start; // line at start of current token
    unsigned char* marker;           // saved cursor
    size_t         marker_pos;       // saved character position
-   size_t         marker_line;      // saved line 
+   size_t         marker_line;      // saved line
    unsigned char* context;          // saved cursor
    size_t         context_pos;      // saved character position
    int            context_line;     // saved line
@@ -54,13 +54,16 @@ typedef struct wisi_lexer
 wisi_lexer* wisitoken_grammar_new_lexer
    (unsigned char* input, size_t length, int verbosity)
 {
-   wisi_lexer* result  = malloc (sizeof (wisi_lexer));
-   result->buffer      = input;
-   result->buffer_last = input + length - 1;
-   result->cursor      = input;
-   result->char_pos    = 1;
-   result->line        = (*result->cursor == 0x0A) ? 2 : 1;
-   result->verbosity   = verbosity;
+   wisi_lexer* result        = malloc (sizeof (wisi_lexer));
+   result->buffer            = input;
+   result->buffer_last       = input + length - 1;
+   result->cursor            = input;
+   result->byte_token_start  = input;
+   result->char_pos          = 1;
+   result->char_token_start  = 1;
+   result->line              = (*result->cursor == 0x0A) ? 2 : 1;
+   result->line_token_start  = result->line;
+   result->verbosity         = verbosity;
    return result;
 }
 
@@ -98,10 +101,13 @@ static void debug(wisi_lexer* lexer, int state, unsigned 
char ch)
 
 static void skip(wisi_lexer* lexer)
 {
-   if (lexer->cursor <= lexer->buffer_last) ++lexer->cursor;
    if (lexer->cursor <= lexer->buffer_last)
+   {
+      ++lexer->cursor;
       if (DO_COUNT) ++lexer->char_pos;
-   if (*lexer->cursor == 0x0A) ++lexer->line;
+      if (lexer->cursor <= lexer->buffer_last)
+         if (*lexer->cursor == 0x0A) ++lexer->line;
+   }
 }
 #define YYSKIP() skip(lexer)
 #define YYBACKUP() lexer->marker = lexer->cursor; lexer->marker_pos = 
lexer->char_pos;lexer->marker_line = lexer->line
@@ -158,10 +164,7 @@ int wisitoken_grammar_next_token
    }
 
    lexer->byte_token_start = lexer->cursor;
-   if (DO_COUNT)
-      lexer->char_token_start = lexer->char_pos;
-   else
-      lexer->char_token_start = lexer->char_pos + 1;
+   lexer->char_token_start = lexer->char_pos;
    if (*lexer->cursor == 0x0A)
       lexer->line_token_start = lexer->line-1;
    else
@@ -170,7 +173,7 @@ int wisitoken_grammar_next_token
    while (*id == -1 && status == 0)
    {
 
-#line 174 "../wisitoken_grammar_re2c.c"
+#line 177 "../wisitoken_grammar_re2c.c"
 {
        YYCTYPE yych;
        unsigned int yyaccept = 0;
@@ -262,21 +265,21 @@ yy2:
        YYSKIP ();
 yy3:
        YYDEBUG(3, YYPEEK ());
-#line 230 "../wisitoken_grammar.re2c"
+#line 233 "../wisitoken_grammar.re2c"
        {status = ERROR_unrecognized_character; continue;}
-#line 268 "../wisitoken_grammar_re2c.c"
+#line 271 "../wisitoken_grammar_re2c.c"
 yy4:
        YYDEBUG(4, YYPEEK ());
        YYSKIP ();
        YYDEBUG(5, YYPEEK ());
-#line 228 "../wisitoken_grammar.re2c"
+#line 231 "../wisitoken_grammar.re2c"
        {*id =  25; continue;}
-#line 275 "../wisitoken_grammar_re2c.c"
+#line 278 "../wisitoken_grammar_re2c.c"
 yy6:
        YYDEBUG(6, YYPEEK ());
        YYSKIP ();
        YYDEBUG(7, YYPEEK ());
-#line 197 "../wisitoken_grammar.re2c"
+#line 200 "../wisitoken_grammar.re2c"
        { lexer->byte_token_start = lexer->cursor;
           lexer->char_token_start = lexer->char_pos;
           if (*lexer->cursor == 0x0A)
@@ -284,14 +287,14 @@ yy6:
           else
              lexer->line_token_start = lexer->line;
           continue; }
-#line 288 "../wisitoken_grammar_re2c.c"
+#line 291 "../wisitoken_grammar_re2c.c"
 yy8:
        YYDEBUG(8, YYPEEK ());
        YYSKIP ();
        YYDEBUG(9, YYPEEK ());
-#line 204 "../wisitoken_grammar.re2c"
+#line 207 "../wisitoken_grammar.re2c"
        {*id =  1; continue;}
-#line 295 "../wisitoken_grammar_re2c.c"
+#line 298 "../wisitoken_grammar_re2c.c"
 yy10:
        YYDEBUG(10, YYPEEK ());
        YYSKIP ();
@@ -468,9 +471,9 @@ yy12:
        }
 yy13:
        YYDEBUG(13, YYPEEK ());
-#line 221 "../wisitoken_grammar.re2c"
+#line 224 "../wisitoken_grammar.re2c"
        {*id =  18; continue;}
-#line 474 "../wisitoken_grammar_re2c.c"
+#line 477 "../wisitoken_grammar_re2c.c"
 yy14:
        YYDEBUG(14, YYPEEK ());
        yyaccept = 0;
@@ -631,16 +634,16 @@ yy15:
        YYDEBUG(15, YYPEEK ());
        YYSKIP ();
        YYDEBUG(16, YYPEEK ());
-#line 217 "../wisitoken_grammar.re2c"
+#line 220 "../wisitoken_grammar.re2c"
        {*id =  14; continue;}
-#line 637 "../wisitoken_grammar_re2c.c"
+#line 640 "../wisitoken_grammar_re2c.c"
 yy17:
        YYDEBUG(17, YYPEEK ());
        YYSKIP ();
        YYDEBUG(18, YYPEEK ());
-#line 223 "../wisitoken_grammar.re2c"
+#line 226 "../wisitoken_grammar.re2c"
        {*id =  20; continue;}
-#line 644 "../wisitoken_grammar_re2c.c"
+#line 647 "../wisitoken_grammar_re2c.c"
 yy19:
        YYDEBUG(19, YYPEEK ());
        YYSKIP ();
@@ -662,16 +665,16 @@ yy19:
        }
 yy21:
        YYDEBUG(21, YYPEEK ());
-#line 224 "../wisitoken_grammar.re2c"
+#line 227 "../wisitoken_grammar.re2c"
        {*id =  21; continue;}
-#line 668 "../wisitoken_grammar_re2c.c"
+#line 671 "../wisitoken_grammar_re2c.c"
 yy22:
        YYDEBUG(22, YYPEEK ());
        YYSKIP ();
        YYDEBUG(23, YYPEEK ());
-#line 216 "../wisitoken_grammar.re2c"
+#line 219 "../wisitoken_grammar.re2c"
        {*id =  13; continue;}
-#line 675 "../wisitoken_grammar_re2c.c"
+#line 678 "../wisitoken_grammar_re2c.c"
 yy24:
        YYDEBUG(24, YYPEEK ());
        YYSKIP ();
@@ -682,30 +685,30 @@ yy24:
        }
 yy25:
        YYDEBUG(25, YYPEEK ());
-#line 222 "../wisitoken_grammar.re2c"
+#line 225 "../wisitoken_grammar.re2c"
        {*id =  19; continue;}
-#line 688 "../wisitoken_grammar_re2c.c"
+#line 691 "../wisitoken_grammar_re2c.c"
 yy26:
        YYDEBUG(26, YYPEEK ());
        YYSKIP ();
        YYDEBUG(27, YYPEEK ());
-#line 220 "../wisitoken_grammar.re2c"
+#line 223 "../wisitoken_grammar.re2c"
        {*id =  17; continue;}
-#line 695 "../wisitoken_grammar_re2c.c"
+#line 698 "../wisitoken_grammar_re2c.c"
 yy28:
        YYDEBUG(28, YYPEEK ());
        YYSKIP ();
        YYDEBUG(29, YYPEEK ());
-#line 218 "../wisitoken_grammar.re2c"
+#line 221 "../wisitoken_grammar.re2c"
        {*id =  15; continue;}
-#line 702 "../wisitoken_grammar_re2c.c"
+#line 705 "../wisitoken_grammar_re2c.c"
 yy30:
        YYDEBUG(30, YYPEEK ());
        YYSKIP ();
        YYDEBUG(31, YYPEEK ());
-#line 219 "../wisitoken_grammar.re2c"
+#line 222 "../wisitoken_grammar.re2c"
        {*id =  16; continue;}
-#line 709 "../wisitoken_grammar_re2c.c"
+#line 712 "../wisitoken_grammar_re2c.c"
 yy32:
        YYDEBUG(32, YYPEEK ());
        YYSKIP ();
@@ -781,9 +784,9 @@ yy33:
        }
 yy34:
        YYDEBUG(34, YYPEEK ());
-#line 225 "../wisitoken_grammar.re2c"
+#line 228 "../wisitoken_grammar.re2c"
        {*id =  22; continue;}
-#line 787 "../wisitoken_grammar_re2c.c"
+#line 790 "../wisitoken_grammar_re2c.c"
 yy35:
        YYDEBUG(35, YYPEEK ());
        YYSKIP ();
@@ -836,9 +839,9 @@ yy41:
        YYDEBUG(41, YYPEEK ());
        YYSKIP ();
        YYDEBUG(42, YYPEEK ());
-#line 215 "../wisitoken_grammar.re2c"
+#line 218 "../wisitoken_grammar.re2c"
        {*id =  12; continue;}
-#line 842 "../wisitoken_grammar_re2c.c"
+#line 845 "../wisitoken_grammar_re2c.c"
 yy43:
        YYDEBUG(43, YYPEEK ());
        YYSKIP ();
@@ -1016,9 +1019,9 @@ yy46:
        }
 yy47:
        YYDEBUG(47, YYPEEK ());
-#line 226 "../wisitoken_grammar.re2c"
+#line 229 "../wisitoken_grammar.re2c"
        {*id =  23; continue;}
-#line 1022 "../wisitoken_grammar_re2c.c"
+#line 1025 "../wisitoken_grammar_re2c.c"
 yy48:
        YYDEBUG(48, YYPEEK ());
        YYSKIP ();
@@ -1353,23 +1356,23 @@ yy54:
        YYDEBUG(54, YYPEEK ());
        YYSKIP ();
        YYDEBUG(55, YYPEEK ());
-#line 214 "../wisitoken_grammar.re2c"
+#line 217 "../wisitoken_grammar.re2c"
        {*id =  11; skip_to(lexer, ")%"); continue;}
-#line 1359 "../wisitoken_grammar_re2c.c"
+#line 1362 "../wisitoken_grammar_re2c.c"
 yy56:
        YYDEBUG(56, YYPEEK ());
        YYSKIP ();
        YYDEBUG(57, YYPEEK ());
-#line 213 "../wisitoken_grammar.re2c"
+#line 216 "../wisitoken_grammar.re2c"
        {*id =  10; skip_to(lexer, "]%"); continue;}
-#line 1366 "../wisitoken_grammar_re2c.c"
+#line 1369 "../wisitoken_grammar_re2c.c"
 yy58:
        YYDEBUG(58, YYPEEK ());
        YYSKIP ();
        YYDEBUG(59, YYPEEK ());
-#line 212 "../wisitoken_grammar.re2c"
+#line 215 "../wisitoken_grammar.re2c"
        {*id =  9; skip_to(lexer, "}%"); continue;}
-#line 1373 "../wisitoken_grammar_re2c.c"
+#line 1376 "../wisitoken_grammar_re2c.c"
 yy60:
        YYDEBUG(60, YYPEEK ());
        YYSKIP ();
@@ -1538,9 +1541,9 @@ yy62:
        }
 yy63:
        YYDEBUG(63, YYPEEK ());
-#line 227 "../wisitoken_grammar.re2c"
+#line 230 "../wisitoken_grammar.re2c"
        {*id =  24; continue;}
-#line 1544 "../wisitoken_grammar_re2c.c"
+#line 1547 "../wisitoken_grammar_re2c.c"
 yy64:
        YYDEBUG(64, YYPEEK ());
        YYSKIP ();
@@ -2060,9 +2063,9 @@ yy70:
        }
 yy72:
        YYDEBUG(72, YYPEEK ());
-#line 205 "../wisitoken_grammar.re2c"
+#line 208 "../wisitoken_grammar.re2c"
        {*id =  2; continue;}
-#line 2066 "../wisitoken_grammar_re2c.c"
+#line 2069 "../wisitoken_grammar_re2c.c"
 yy73:
        YYDEBUG(73, YYPEEK ());
        YYSKIP ();
@@ -2152,9 +2155,9 @@ yy75:
        }
 yy76:
        YYDEBUG(76, YYPEEK ());
-#line 208 "../wisitoken_grammar.re2c"
+#line 211 "../wisitoken_grammar.re2c"
        {*id =  5; continue;}
-#line 2158 "../wisitoken_grammar_re2c.c"
+#line 2161 "../wisitoken_grammar_re2c.c"
 yy77:
        YYDEBUG(77, YYPEEK ());
        YYSKIP ();
@@ -2590,9 +2593,9 @@ yy87:
        }
 yy88:
        YYDEBUG(88, YYPEEK ());
-#line 207 "../wisitoken_grammar.re2c"
+#line 210 "../wisitoken_grammar.re2c"
        {*id =  4; continue;}
-#line 2596 "../wisitoken_grammar_re2c.c"
+#line 2599 "../wisitoken_grammar_re2c.c"
 yy89:
        YYDEBUG(89, YYPEEK ());
        YYSKIP ();
@@ -2690,9 +2693,9 @@ yy92:
        }
 yy93:
        YYDEBUG(93, YYPEEK ());
-#line 206 "../wisitoken_grammar.re2c"
+#line 209 "../wisitoken_grammar.re2c"
        {*id =  3; continue;}
-#line 2696 "../wisitoken_grammar_re2c.c"
+#line 2699 "../wisitoken_grammar_re2c.c"
 yy94:
        YYDEBUG(94, YYPEEK ());
        YYSKIP ();
@@ -2806,9 +2809,9 @@ yy99:
        }
 yy100:
        YYDEBUG(100, YYPEEK ());
-#line 211 "../wisitoken_grammar.re2c"
+#line 214 "../wisitoken_grammar.re2c"
        {*id =  8; continue;}
-#line 2812 "../wisitoken_grammar_re2c.c"
+#line 2815 "../wisitoken_grammar_re2c.c"
 yy101:
        YYDEBUG(101, YYPEEK ());
        YYSKIP ();
@@ -2898,9 +2901,9 @@ yy103:
        }
 yy104:
        YYDEBUG(104, YYPEEK ());
-#line 209 "../wisitoken_grammar.re2c"
+#line 212 "../wisitoken_grammar.re2c"
        {*id =  6; continue;}
-#line 2904 "../wisitoken_grammar_re2c.c"
+#line 2907 "../wisitoken_grammar_re2c.c"
 yy105:
        YYDEBUG(105, YYPEEK ());
        YYSKIP ();
@@ -3006,11 +3009,11 @@ yy109:
        }
 yy110:
        YYDEBUG(110, YYPEEK ());
-#line 210 "../wisitoken_grammar.re2c"
+#line 213 "../wisitoken_grammar.re2c"
        {*id =  7; continue;}
-#line 3012 "../wisitoken_grammar_re2c.c"
+#line 3015 "../wisitoken_grammar_re2c.c"
 }
-#line 231 "../wisitoken_grammar.re2c"
+#line 234 "../wisitoken_grammar.re2c"
 
       }
    *byte_position = lexer->byte_token_start - lexer->buffer + 1;
diff --git a/wisitoken_grammar_runtime.adb b/wisitoken_grammar_runtime.adb
index 412ffef..3795b1a 100644
--- a/wisitoken_grammar_runtime.adb
+++ b/wisitoken_grammar_runtime.adb
@@ -2,7 +2,7 @@
 --
 --  See spec.
 --
---  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
@@ -39,7 +39,7 @@ package body WisiToken_Grammar_Runtime is
          Region : Buffer_Region renames Data.Terminals.all (Tree.Terminal 
(Tree_Index)).Byte_Region;
       begin
          if -Tree.ID (Tree_Index) in RAW_CODE_ID | REGEXP_ID | ACTION_ID then
-            --  strip delimiters.
+            --  Strip delimiters. We don't strip leading/trailing spaces to 
preserve indent.
             return Data.Grammar_Lexer.Buffer_Text ((Region.First + 2, 
Region.Last - 2));
 
          elsif -Tree.ID (Tree_Index) in STRING_LITERAL_ID | 
STRING_LITERAL_CASE_INS_ID and Strip_Quotes then
@@ -140,7 +140,7 @@ package body WisiToken_Grammar_Runtime is
                declare
                   Text : constant String := Get_Text (Data, Tree, Tokens (2));
                begin
-                  if Text'Length > 0 then
+                  if Text'Length > 0 and (for some C of Text => C /= ' ') then
                      RHS.Action := +Text;
                      Data.Action_Count := Data.Action_Count + 1;
                   end if;
@@ -220,7 +220,6 @@ package body WisiToken_Grammar_Runtime is
       Data.Language_Params   := (others => <>);
       WisiToken.BNF.Free (Data.Generate_Set);
       Data.Tokens            := (others => <>);
-      Data.User_Names        := (others => <>);
       Data.Conflicts.Clear;
       Data.McKenzie_Recover  := (others => <>);
       Data.Rule_Count        := 0;
@@ -437,10 +436,10 @@ package body WisiToken_Grammar_Runtime is
                   null;
 
                elsif Kind = "elisp_face" then
-                  Data.User_Names.Faces.Append (Get_Text (Data, Tree, Tokens 
(3), Strip_Quotes => True));
+                  Data.Tokens.Faces.Append (Get_Text (Data, Tree, Tokens (3), 
Strip_Quotes => True));
 
                elsif Kind = "elisp_indent" then
-                  Data.User_Names.Indents.Append
+                  Data.Tokens.Indents.Append
                     ((Name  => +Get_Child_Text (Data, Tree, Tokens (3), 1, 
Strip_Quotes => True),
                       Value => +Get_Child_Text (Data, Tree, Tokens (3), 2)));
 
diff --git a/wisitoken_grammar_runtime.ads b/wisitoken_grammar_runtime.ads
index 62aaf9a..9c9d0ac 100644
--- a/wisitoken_grammar_runtime.ads
+++ b/wisitoken_grammar_runtime.ads
@@ -41,7 +41,6 @@ package WisiToken_Grammar_Runtime is
       Raw_Code         : WisiToken.BNF.Raw_Code;
       Language_Params  : WisiToken.BNF.Language_Param_Type;
       Tokens           : aliased WisiToken.BNF.Tokens;
-      User_Names       : WisiToken.BNF.User_Names;
       Conflicts        : WisiToken.BNF.Conflict_Lists.List;
       McKenzie_Recover : WisiToken.BNF.McKenzie_Recover_Param_Type;
 



reply via email to

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