--- /dev/null
+(**************************************************************************)
+(* *)
+(* Menhir *)
+(* *)
+(* François Pottier, INRIA Rocquencourt *)
+(* Yann Régis-Gianas, PPS, Université Paris Diderot *)
+(* *)
+(* Copyright 2005-2008 Institut National de Recherche en Informatique *)
+(* et en Automatique. All rights reserved. This file is distributed *)
+(* under the terms of the Q Public License version 1.0, with the change *)
+(* described in file LICENSE. *)
+(* *)
+(**************************************************************************)
+
+open Grammar
+
+(* This module constructs an LR(1) automaton by following Pager's method, that
+ is, by merging states on the fly when they are weakly compatible. *)
+
+(* ------------------------------------------------------------------------ *)
+(* Nodes. *)
+
+type node = {
+
+ (* A node number, assigned during construction. *)
+
+ raw_number: int;
+
+ (* A node number, assigned after conflict resolution has taken
+ place and after inacessible nodes have been removed. This
+ yields sequential numbers, from the client's point of view. *)
+
+ mutable number: int;
+
+ (* Each node is associated with a state. This state can change
+ during construction as nodes are merged. *)
+
+ mutable state: Lr0.lr1state;
+
+ (* Each node carries information about its outgoing transitions
+ and about its reductions. *)
+
+ mutable transitions: node SymbolMap.t;
+ mutable reductions: Production.index list TerminalMap.t;
+
+ (* Tokens for which there are several possible behaviors are
+ conflict tokens. *)
+
+ mutable conflict_tokens: TerminalSet.t;
+
+ (* Transitions are also stored in reverse, so as to allow reverse
+ traversals of the automaton. *)
+
+ mutable predecessors: node list;
+
+ (* If a node has any incoming transitions, then they all carry
+ the same symbol. This is it. *)
+
+ mutable incoming_symbol: Symbol.t option;
+
+ (* Transient marks are used during construction and traversal. *)
+
+ mutable mark: Mark.t;
+
+ (* (New as of 2012/01/23.) This flag records whether a shift/reduce
+ conflict in this node was solved in favor of neither (%nonassoc).
+ This is later used to forbid a default reduction at this node. *)
+
+ mutable forbid_default_reduction: bool;
+
+ }
+
+module Node = struct
+ type t = node
+ let compare node1 node2 =
+ node1.number - node2.number
+end
+
+module NodeSet =
+ Set.Make (Node)
+
+module NodeMap =
+ Map.Make (Node)
+
+(* ------------------------------------------------------------------------ *)
+
+(* Output debugging information if [--follow-construction] is enabled. *)
+
+let follow_transition (again : bool) (source : node) (symbol : Symbol.t) (state : Lr0.lr1state) =
+ if Settings.follow then
+ Printf.fprintf stderr
+ "%s transition out of state r%d along symbol %s.\nProposed target state:\n%s"
+ (if again then "Re-examining" else "Examining")
+ source.raw_number
+ (Symbol.print symbol)
+ (Lr0.print_closure state)
+
+let follow_state (msg : string) (node : node) (print : bool) =
+ if Settings.follow then
+ Printf.fprintf stderr
+ "%s: r%d.\n%s\n"
+ msg
+ node.raw_number
+ (if print then Lr0.print_closure node.state else "")
+
+(* ------------------------------------------------------------------------ *)
+
+(* The following two mutually recursive functions are invoked when the state
+ associated with an existing node grows. The node's descendants are examined
+ and grown into a fixpoint is reached.
+
+ This work is performed in an eager manner: we do not attempt to build any
+ new transitions until all existing nodes have been suitably grown. Indeed,
+ building new transitions requires making merging decisions, and such
+ decisions cannot be made on a sound basis unless all existing nodes have
+ been suitably grown. Otherwise, one could run into a dead end where two
+ successive, incompatible merging decisions are made, because the
+ consequences of the first decision (growing descendant nodes) were not made
+ explicit before the second decision was taken. This was a bug in versions
+ of Menhir ante 20070520.
+
+ Although I wrote this code independently, I later found out that it seems
+ quite similar to the code in Karl Schimpf's Ph.D. thesis (1981), page 35.
+
+ It is necessary that all existing transitions be explicit before the [grow]
+ functions are called. In other words, if it has been decided that there will
+ be a transition from [node1] to [node2], then [node1.transitions] must be
+ updated before [grow] is invoked. *)
+
+(* [grow node state] grows the existing node [node], if necessary, so that its
+ associated state subsumes [state]. If this represents an actual (strict)
+ growth, then [node]'s descendants are grown as well. *)
+
+let rec grow node state =
+ if Lr0.subsume state node.state then
+ follow_state "Target state is unaffected" node false
+ else begin
+
+ (* In versions of Menhir prior to June 2008, I wrote this:
+
+ If I know what I am doing, then the new state that is being
+ merged into the existing state should be compatible, in
+ Pager's sense, with the existing node. In other words,
+ compatibility should be preserved through transitions.
+
+ and the code contained this assertion:
+
+ assert (Lr0.compatible state node.state);
+ assert (Lr0.eos_compatible state node.state);
+
+ However, this was wrong. See, for instance, the sample grammars
+ cocci.mly and boris-mini.mly. The problem is particularly clearly
+ apparent in boris-mini.mly, where it only involves inclusion of
+ states -- the definition of Pager's weak compatibility does not
+ enter the picture. Here is, roughly, what is going on.
+
+ Assume we have built some state A, which, along some symbol S,
+ has a transition to itself. This means, in fact, that computing
+ the successor of A along S yields a *subset* of A, that is,
+ succ(A, S) <= A.
+
+ Then, we wish to build a new state A', which turns out to be a
+ superset of A, so we decide to grow A. (The fact that A is a
+ subset of A' implies that A and A' are Pager-compatible.) As
+ per the code below, we immediately update the state A in place,
+ to become A'. Then, we inspect the transition along symbol S.
+ We find that the state succ(A', S) must be merged into A'.
+
+ In this situation, the assertions above require succ(A', S)
+ to be compatible with A'. However, this is not necessarily
+ the case. By monotonicity of succ, we do have succ(A, S) <=
+ succ(A', S). But nothing says that succ(A', S) are related
+ with respect to inclusion, or even Pager-compatible. The
+ grammar in boris-mini.mly shows that they are not.
+
+ *)
+
+ (* Grow [node]. *)
+
+ node.state <- Lr0.union state node.state;
+ follow_state "Growing existing state" node true;
+
+ (* Grow [node]'s successors. *)
+
+ grow_successors node
+
+ end
+
+(* [grow_successors node] grows [node]'s successors. *)
+
+(* Note that, if there is a cycle in the graph, [grow_successors] can be
+ invoked several times at a single node [node], with [node.state] taking on
+ a new value every time. In such a case, this code should be correct,
+ although probably not very efficient. *)
+
+and grow_successors node =
+ SymbolMap.iter (fun symbol (successor_node : node) ->
+ let successor_state = Lr0.transition symbol node.state in
+ follow_transition true node symbol successor_state;
+ grow successor_node successor_state
+ ) node.transitions
+
+(* ------------------------------------------------------------------------ *)
+
+(* Data structures maintained during the construction of the automaton. *)
+
+(* A queue of pending nodes, whose outgoing transitions have not yet
+ been built. *)
+
+let queue : node Queue.t =
+ Queue.create()
+
+(* A mapping of LR(0) node numbers to lists of nodes. This allows us to
+ efficiently find all existing nodes that are core-compatible with a
+ newly found state. *)
+
+let map : node list array =
+ Array.create Lr0.n []
+
+(* A counter that allows assigning raw numbers to nodes. *)
+
+let num =
+ ref 0
+
+(* ------------------------------------------------------------------------ *)
+
+(* [create state] creates a new node that stands for the state [state].
+ It is expected that [state] does not subsume, and is not subsumed by,
+ any existing state. *)
+
+let create (state : Lr0.lr1state) : node =
+
+ (* Allocate a new node. *)
+
+ let node = {
+ state = state;
+ transitions = SymbolMap.empty;
+ reductions = TerminalMap.empty;
+ conflict_tokens = TerminalSet.empty;
+ raw_number = Misc.postincrement num;
+ number = 0; (* temporary placeholder *)
+ mark = Mark.none;
+ predecessors = [];
+ incoming_symbol = None;
+ forbid_default_reduction = false;
+ } in
+
+ (* Update the mapping of LR(0) cores to lists of nodes. *)
+
+ let k = Lr0.core state in
+ assert (k < Lr0.n);
+ map.(k) <- node :: map.(k);
+
+ (* Enqueue this node for further examination. *)
+
+ Queue.add node queue;
+
+ (* Debugging output. *)
+
+ follow_state "Creating a new state" node false;
+
+ (* Return the freshly created node. *)
+
+ node
+
+(* ------------------------------------------------------------------------ *)
+
+(* Materializing a transition turns its target state into a (fresh or
+ existing). There are three scenarios: the proposed new state can be
+ subsumed by an existing state, compatible with an existing state, or
+ neither. *)
+
+exception Subsumed of node
+
+exception Compatible of node
+
+let materialize (source : node) (symbol : Symbol.t) (target : Lr0.lr1state) : unit =
+ try
+
+ (* Debugging output. *)
+
+ follow_transition false source symbol target;
+
+ (* Find all existing core-compatible states. *)
+
+ let k = Lr0.core target in
+ assert (k < Lr0.n);
+ let similar = map.(k) in
+
+ (* Check whether one of these states subsumes the candidate new state. If
+ so, there is no need to create a new node: just reuse the existing
+ one. *)
+
+ (* 20110124: require error compatibility in addition to subsumption. *)
+
+ List.iter (fun node ->
+ if Lr0.subsume target node.state &&
+ Lr0.error_compatible target node.state then
+ raise (Subsumed node)
+ ) similar;
+
+ (* Check whether one of the existing states is compatible, in Pager's
+ sense, with the new state. If so, there is no need to create a new
+ state: just merge the new state into the existing one. *)
+
+ (* 20110124: require error compatibility in addition to the existing
+ compatibility criteria. *)
+
+ if Settings.pager then
+ List.iter (fun node ->
+ if Lr0.compatible target node.state &&
+ Lr0.eos_compatible target node.state &&
+ Lr0.error_compatible target node.state then
+ raise (Compatible node)
+ ) similar;
+
+ (* Both of the above checks have failed. Create a new node. Two states
+ that are in the subsumption relation are also compatible. This implies
+ that the newly created node does not subsume any existing states. *)
+
+ source.transitions <- SymbolMap.add symbol (create target) source.transitions
+
+ with
+
+ | Subsumed node ->
+
+ (* Join an existing target node. *)
+
+ follow_state "Joining existing state" node false;
+ source.transitions <- SymbolMap.add symbol node source.transitions
+
+ | Compatible node ->
+
+ (* Join and grow an existing target node. It seems important that the
+ new transition is created before [grow_successors] is invoked, so
+ that all transition decisions made so far are explicit. *)
+
+ node.state <- Lr0.union target node.state;
+ follow_state "Joining and growing existing state (Pager says, fine)" node true;
+ source.transitions <- SymbolMap.add symbol node source.transitions;
+ grow_successors node
+
+(* ------------------------------------------------------------------------ *)
+
+(* The actual construction process. *)
+
+(* Populate the queue with the start nodes and store them in an array. *)
+
+let entry : node ProductionMap.t =
+ ProductionMap.map (fun (k : Lr0.node) ->
+ create (Lr0.start k)
+ ) Lr0.entry
+
+(* Pick a node in the queue, that is, a node whose transitions have not yet
+ been built. Build these transitions, and continue. *)
+
+(* Note that building a transition can cause existing nodes to grow, so
+ [node.state] is not necessarily invariant throughout the inner loop. *)
+
+let () =
+ Misc.qiter (fun node ->
+ List.iter (fun symbol ->
+ materialize node symbol (Lr0.transition symbol node.state)
+ ) (Lr0.outgoing_symbols (Lr0.core node.state))
+ ) queue
+
+(* Record how many nodes were constructed. *)
+
+let n =
+ !num
+
+let () =
+ Error.logA 1 (fun f -> Printf.fprintf f "Built an LR(1) automaton with %d states.\n" !num)
+
+(* ------------------------------------------------------------------------ *)
+(* We now perform one depth-first traversal of the automaton,
+ recording predecessor edges, numbering nodes, sorting nodes
+ according to their incoming symbol, building reduction tables, and
+ finding out which nodes have conflicts. *)
+
+(* A count of all nodes. *)
+
+let () =
+ num := 0
+
+(* A list of all nodes. *)
+
+let nodes : node list ref =
+ ref []
+
+(* A list of nodes with conflicts. *)
+
+let conflict_nodes : node list ref =
+ ref []
+
+(* Counts of nodes with shift/reduce and reduce/reduce conflicts. *)
+
+let shift_reduce =
+ ref 0
+
+let reduce_reduce =
+ ref 0
+
+(* Count of the shift/reduce conflicts that could be silently
+ resolved. *)
+
+let silently_solved =
+ ref 0
+
+(* A mapping of symbols to lists of nodes that admit this incoming
+ symbol. *)
+
+let incoming : node list SymbolMap.t ref =
+ ref SymbolMap.empty
+
+(* Go ahead. *)
+
+let () =
+
+ let marked = Mark.fresh() in
+
+ let rec visit node =
+ if not (Mark.same node.mark marked) then begin
+ node.mark <- marked;
+ nodes := node :: !nodes;
+
+ (* Number this node. *)
+
+ let number = !num in
+ num := number + 1;
+ node.number <- number;
+
+ (* Insertion of a new reduce action into the table of reductions. *)
+
+ let addl prod tok reductions =
+ let prods =
+ try
+ TerminalMap.lookup tok reductions
+ with Not_found ->
+ []
+ in
+ TerminalMap.add tok (prod :: prods) reductions
+ in
+
+ (* Build the reduction table. Here, we gather all potential
+ reductions, without attempting to solve shift/reduce
+ conflicts on the fly, because that would potentially hide
+ shift/reduce/reduce conflicts, which we want to be aware
+ of. *)
+
+ let reductions =
+ List.fold_left (fun reductions (toks, prod) ->
+ TerminalSet.fold (addl prod) toks reductions
+ ) TerminalMap.empty (Lr0.reductions node.state)
+ in
+
+ (* Detect conflicts. Attempt to solve shift/reduce conflicts
+ when unambiguously allowed by priorities. *)
+
+ let has_shift_reduce = ref false
+ and has_reduce_reduce = ref false in
+
+ node.reductions <-
+ TerminalMap.fold (fun tok prods reductions ->
+ if SymbolMap.mem (Symbol.T tok) node.transitions then begin
+
+ (* There is a transition in addition to the reduction(s). We
+ have (at least) a shift/reduce conflict. *)
+
+ assert (not (Terminal.equal tok Terminal.sharp));
+ match prods with
+ | [] ->
+ assert false
+ | [ prod ] ->
+ begin
+
+ (* This is a single shift/reduce conflict. If priorities tell
+ us how to solve it, we follow that and modify the automaton. *)
+
+ match Precedence.shift_reduce tok prod with
+
+ | Precedence.ChooseShift ->
+
+ (* Suppress the reduce action. *)
+
+ incr silently_solved;
+ reductions
+
+ | Precedence.ChooseReduce ->
+
+ (* Record the reduce action and suppress the shift transition.
+ The automaton is modified in place. This can have the subtle
+ effect of making some nodes unreachable. Any conflicts in these
+ nodes will then be ignored (as they should be). *)
+
+ incr silently_solved;
+ node.transitions <- SymbolMap.remove (Symbol.T tok) node.transitions;
+ TerminalMap.add tok prods reductions
+
+ | Precedence.ChooseNeither ->
+
+ (* Suppress the reduce action and the shift transition. *)
+
+ incr silently_solved;
+ node.transitions <- SymbolMap.remove (Symbol.T tok) node.transitions;
+ node.forbid_default_reduction <- true;
+ reductions
+
+ | Precedence.DontKnow ->
+
+ (* Priorities don't allow concluding. Record the
+ existence of a shift/reduce conflict. *)
+
+ node.conflict_tokens <- Grammar.TerminalSet.add tok node.conflict_tokens;
+ has_shift_reduce := true;
+ TerminalMap.add tok prods reductions
+
+ end
+
+ | prod1 :: prod2 :: _ ->
+
+ (* This is a shift/reduce/reduce conflict. If the priorities
+ are such that each individual shift/reduce conflict is solved
+ in favor of shifting or in favor of neither, then solve the entire
+ composite conflict in the same way. Otherwise, report the conflict. *)
+
+ let choices = List.map (Precedence.shift_reduce tok) prods in
+
+ if List.for_all (fun choice ->
+ match choice with
+ | Precedence.ChooseShift -> true
+ | _ -> false
+ ) choices then begin
+
+ (* Suppress the reduce action. *)
+
+ silently_solved := !silently_solved + List.length prods;
+ reductions
+
+ end
+ else if List.for_all (fun choice ->
+ match choice with
+ | Precedence.ChooseNeither -> true
+ | _ -> false
+ ) choices then begin
+
+ (* Suppress the reduce action and the shift transition. *)
+
+ silently_solved := !silently_solved + List.length prods;
+ node.transitions <- SymbolMap.remove (Symbol.T tok) node.transitions;
+ reductions
+
+ end
+ else begin
+
+ (* Record a shift/reduce/reduce conflict. Keep all reductions. *)
+
+ node.conflict_tokens <- Grammar.TerminalSet.add tok node.conflict_tokens;
+ has_shift_reduce := true;
+ has_reduce_reduce := true;
+ TerminalMap.add tok prods reductions
+
+ end
+
+ end
+ else
+ let () =
+ match prods with
+ | []
+ | [ _ ] ->
+ ()
+ | prod1 :: prod2 :: _ ->
+
+ (* There is no transition in addition to the reduction(s). We
+ have a pure reduce/reduce conflict. Do nothing about it at
+ this point. *)
+
+ node.conflict_tokens <- Grammar.TerminalSet.add tok node.conflict_tokens;
+ has_reduce_reduce := true
+
+ in
+ TerminalMap.add tok prods reductions
+
+ ) reductions TerminalMap.empty;
+
+ (* Record statistics about conflicts. *)
+
+ if not (TerminalSet.is_empty node.conflict_tokens) then begin
+ conflict_nodes := node :: !conflict_nodes;
+ if !has_shift_reduce then
+ incr shift_reduce;
+ if !has_reduce_reduce then
+ incr reduce_reduce
+ end;
+
+ (* Continue the depth-first traversal. Record predecessors edges
+ as we go. No ancestor appears twice in a list of
+ predecessors, because two nodes cannot be related by two
+ edges that carry distinct symbols. *)
+
+ SymbolMap.iter (fun symbol son ->
+ begin
+ match son.incoming_symbol with
+ | None ->
+ son.incoming_symbol <- Some symbol;
+ let others =
+ try
+ SymbolMap.find symbol !incoming
+ with Not_found ->
+ []
+ in
+ incoming := SymbolMap.add symbol (son :: others) !incoming
+ | Some symbol' ->
+ assert (Symbol.equal symbol symbol')
+ end;
+ son.predecessors <- node :: son.predecessors;
+ visit son
+ ) node.transitions
+ end
+ in
+
+ ProductionMap.iter (fun _ node -> visit node) entry
+
+let nodes =
+ List.rev !nodes (* list is now sorted by increasing node numbers *)
+
+let conflict_nodes =
+ !conflict_nodes
+
+let incoming =
+ !incoming
+
+let () =
+ if !silently_solved = 1 then
+ Error.logA 1 (fun f -> Printf.fprintf f "One shift/reduce conflict was silently solved.\n")
+ else if !silently_solved > 1 then
+ Error.logA 1 (fun f -> Printf.fprintf f "%d shift/reduce conflicts were silently solved.\n" !silently_solved);
+ if !num < n then
+ Error.logA 1 (fun f -> Printf.fprintf f "Only %d states remain after resolving shift/reduce conflicts.\n" !num)
+
+let () =
+ Grammar.diagnostics()
+
+let n =
+ !num
+
+let forbid_default_reduction node =
+ node.forbid_default_reduction
+
+(* ------------------------------------------------------------------------ *)
+(* Breadth-first iteration over all nodes. *)
+
+let bfs =
+ let module B = Breadth.Make (struct
+ type vertex = node
+ type label = Symbol.t
+ let set_mark node m = node.mark <- m
+ let get_mark node = node.mark
+ let entry f = ProductionMap.iter (fun _ node -> f node) entry
+ let successors f node = SymbolMap.iter f node.transitions
+ end) in
+ B.search
+
+(* ------------------------------------------------------------------------ *)
+(* Iteration over all nodes. *)
+
+let fold f accu =
+ List.fold_left f accu nodes
+
+let iter f =
+ fold (fun () node -> f node) ()
+
+let map f =
+ List.map f nodes
+
+let foldx f =
+ fold (fun accu node ->
+ match node.incoming_symbol with
+ | None -> accu
+ | Some _ -> f accu node)
+
+let iterx f =
+ iter (fun node ->
+ match node.incoming_symbol with
+ | None -> ()
+ | Some _ -> f node)
+
+(* -------------------------------------------------------------------------- *)
+(* Our output channel. *)
+
+let out =
+ lazy (open_out (Settings.base ^ ".automaton"))
+
+(* ------------------------------------------------------------------------ *)
+(* If requested, dump a verbose description of the automaton. *)
+
+let () =
+ Time.tick "Construction of the LR(1) automaton";
+ if Settings.dump then begin
+ fold (fun () node ->
+ let out = Lazy.force out in
+ Printf.fprintf out "State %d%s:\n%s"
+ node.number
+ (if Settings.follow then Printf.sprintf " (r%d)" node.raw_number else "")
+ (Lr0.print node.state);
+ SymbolMap.iter (fun symbol node ->
+ Printf.fprintf out "-- On %s shift to state %d\n"
+ (Symbol.print symbol) node.number
+ ) node.transitions;
+ TerminalMap.iter (fun tok prods ->
+ List.iter (fun prod ->
+ (* TEMPORARY factoriser les symboles qui conduisent a reduire une meme production *)
+ Printf.fprintf out "-- On %s " (Terminal.print tok);
+ match Production.classify prod with
+ | Some nt ->
+ Printf.fprintf out "accept %s\n" (Nonterminal.print false nt)
+ | None ->
+ Printf.fprintf out "reduce production %s\n" (Production.print prod)
+ ) prods
+ ) node.reductions;
+ if not (TerminalSet.is_empty node.conflict_tokens) then
+ Printf.fprintf out "** Conflict on %s\n" (TerminalSet.print node.conflict_tokens);
+ Printf.fprintf out "\n%!"
+ ) ();
+ Time.tick "Dumping the LR(1) automaton"
+ end
+
+(* ------------------------------------------------------------------------ *)
+(* [reverse_dfs goal] performs a reverse depth-first search through
+ the automaton, starting at node [goal], and marking the nodes
+ traversed. It returns a function that tells whether a node is
+ marked, that is, whether a path leads from that node to the goal
+ node. *)
+
+let reverse_dfs goal =
+
+ let mark = Mark.fresh() in
+
+ let marked node =
+ Mark.same node.mark mark
+ in
+
+ let rec visit node =
+ if not (marked node) then begin
+ node.mark <- mark;
+ List.iter visit node.predecessors
+ end
+ in
+
+ visit goal;
+ marked
+
+(* ------------------------------------------------------------------------ *)
+(* Iterating over all nodes that are targets of edges carrying a
+ certain symbol. The sources of the corresponding edges are also
+ provided. *)
+
+let targets f accu symbol =
+ let targets =
+ try
+ SymbolMap.find symbol incoming
+ with Not_found ->
+ (* There are no incoming transitions on the start symbols. *)
+ []
+ in
+ List.fold_left (fun accu target ->
+ f accu target.predecessors target
+ ) accu targets
+
+(* ------------------------------------------------------------------------ *)
+(* Converting a start node into the single item that it contains. *)
+
+let start2item node =
+ let state : Lr0.lr1state = node.state in
+ let core : Lr0.node = Lr0.core state in
+ let items : Item.Set.t = Lr0.items core in
+ assert (Item.Set.cardinal items = 1);
+ Item.Set.choose items
+
+(* ------------------------------------------------------------------------ *)
+(* Accessors. *)
+
+let number node =
+ node.number
+
+let state node =
+ node.state
+
+let transitions node =
+ node.transitions
+
+let reductions node =
+ node.reductions
+
+let conflicts f =
+ List.iter (fun node ->
+ f node.conflict_tokens node
+ ) conflict_nodes
+
+let incoming_symbol node =
+ node.incoming_symbol
+
+let predecessors node =
+ node.predecessors
+
+(* ------------------------------------------------------------------------ *)
+
+(* This inverts a mapping of tokens to productions into a mapping of
+ productions to sets of tokens. *)
+
+(* This is needed, in [CodeBackend], to avoid producing two (or more)
+ separate branches that call the same [reduce] function. Instead,
+ we generate just one branch, guarded by a [POr] pattern. *)
+
+let invert reductions : TerminalSet.t ProductionMap.t =
+ TerminalMap.fold (fun tok prods inverse ->
+ let prod = Misc.single prods in
+ let toks =
+ try
+ ProductionMap.lookup prod inverse
+ with Not_found ->
+ TerminalSet.empty
+ in
+ ProductionMap.add prod (TerminalSet.add tok toks) inverse
+ ) reductions ProductionMap.empty
+
+(* ------------------------------------------------------------------------ *)
+(* Computing which terminal symbols a state is willing to act upon.
+
+ This function is currently unused, but could be used as part of an error
+ reporting system.
+
+ One must keep in mind that, due to the merging of states, a state might be
+ willing to perform a reduction on a certain token, yet the reduction can
+ take us to another state where this token causes an error. In other words,
+ the set of terminal symbols that is computed here is really an
+ over-approximation of the set of symbols that will not cause an error. And
+ there seems to be no way of performing an exact computation, as we would
+ need to know not only the current state, but the contents of the stack as
+ well. *)
+
+let acceptable_tokens (s : node) =
+
+ (* If this state is willing to act on the error token, ignore it -- we do
+ not wish to report that an error would be accepted in this state :-) *)
+
+ let transitions =
+ SymbolMap.remove (Symbol.T Terminal.error) (transitions s)
+ and reductions =
+ TerminalMap.remove Terminal.error (reductions s)
+ in
+
+ (* Accumulate the tokens carried by outgoing transitions. *)
+
+ let covered =
+ SymbolMap.fold (fun symbol _ covered ->
+ match symbol with
+ | Symbol.T tok ->
+ TerminalSet.add tok covered
+ | Symbol.N _ ->
+ covered
+ ) transitions TerminalSet.empty
+ in
+
+ (* Accumulate the tokens that permit reduction. *)
+
+ let covered =
+ ProductionMap.fold (fun _ toks covered ->
+ TerminalSet.union toks covered
+ ) (invert reductions) covered
+ in
+
+ (* That's it. *)
+
+ covered
+
+(* ------------------------------------------------------------------------ *)
+(* Report statistics. *)
+
+(* Produce the reports. *)
+
+let () =
+ if !shift_reduce = 1 then
+ Error.grammar_warning [] "one state has shift/reduce conflicts."
+ else if !shift_reduce > 1 then
+ Error.grammar_warning [] (Printf.sprintf "%d states have shift/reduce conflicts." !shift_reduce);
+ if !reduce_reduce = 1 then
+ Error.grammar_warning [] "one state has reduce/reduce conflicts."
+ else if !reduce_reduce > 1 then
+ Error.grammar_warning [] (Printf.sprintf "%d states have reduce/reduce conflicts." !reduce_reduce)
+
+(* There is a global check for errors at the end of [Invariant], so we do
+ not need to check & stop here. *)
+
+(* ------------------------------------------------------------------------ *)
+(* When requested by the code generator, apply default conflict
+ resolution to ensure that the automaton is deterministic. *)
+
+(* [best prod prods] chooses which production should be reduced
+ among the list [prod :: prods]. It fails if no best choice
+ exists. *)
+
+let rec best choice = function
+ | [] ->
+ choice
+ | prod :: prods ->
+ match Precedence.reduce_reduce choice prod with
+ | Some choice ->
+ best choice prods
+ | None ->
+ Error.signal
+ (Production.positions choice @ Production.positions prod)
+ (Printf.sprintf
+ "will not resolve reduce/reduce conflict between\n\
+ productions that originate in distinct source files:\n%s\n%s"
+ (Production.print choice)
+ (Production.print prod));
+ choice (* dummy *)
+
+(* Go ahead. *)
+
+let default_conflict_resolution () =
+
+ let shift_reduce =
+ ref 0
+ and reduce_reduce =
+ ref 0
+ in
+
+ List.iter (fun node ->
+
+ node.reductions <-
+ TerminalMap.fold (fun tok prods reductions ->
+ try
+ let (_ : node) =
+ SymbolMap.find (Symbol.T tok) node.transitions
+ in
+ (* There is a transition at this symbol, so this
+ is a (possibly multiway) shift/reduce conflict.
+ Resolve in favor of shifting by suppressing all
+ reductions. *)
+ shift_reduce := List.length prods + !shift_reduce;
+ reductions
+ with Not_found ->
+ (* There is no transition at this symbol. Check
+ whether we have multiple reductions. *)
+ match prods with
+ | [] ->
+ assert false
+ | [ _ ] ->
+ TerminalMap.add tok prods reductions
+ | prod :: ((_ :: _) as prods) ->
+ (* We have a reduce/reduce conflict. Resolve, if
+ possible, in favor of a single reduction.
+ This reduction must be preferrable to each
+ of the others. *)
+ reduce_reduce := List.length prods + !reduce_reduce;
+ TerminalMap.add tok [ best prod prods ] reductions
+
+ ) node.reductions TerminalMap.empty
+
+ ) conflict_nodes;
+
+ if !shift_reduce = 1 then
+ Error.warning [] "one shift/reduce conflict was arbitrarily resolved."
+ else if !shift_reduce > 1 then
+ Error.warning [] (Printf.sprintf "%d shift/reduce conflicts were arbitrarily resolved." !shift_reduce);
+ if !reduce_reduce = 1 then
+ Error.warning [] "one reduce/reduce conflict was arbitrarily resolved."
+ else if !reduce_reduce > 1 then
+ Error.warning [] (Printf.sprintf "%d reduce/reduce conflicts were arbitrarily resolved." !reduce_reduce);
+
+ (* Now, ensure that states that have a reduce action at the
+ pseudo-token "#" have no other action. *)
+
+ let ambiguities =
+ ref 0
+ in
+
+ fold (fun () node ->
+
+ try
+ let prods, reductions = TerminalMap.lookup_and_remove Terminal.sharp node.reductions in
+ let prod = Misc.single prods in
+
+ (* This node has a reduce action at "#". Determine whether there
+ exist other actions. If there exist any other actions,
+ suppress this reduce action, and signal an ambiguity.
+
+ We signal an ambiguity even in the case where all actions at
+ this node call for reducing a single production. Indeed, in
+ that case, even though we know that this production must be
+ reduced, we do not know whether we should first discard the
+ current token (and call the lexer). *)
+
+ let has_ambiguity = ref false in
+ let toks = ref TerminalSet.empty in
+
+ TerminalMap.iter (fun tok prods ->
+ node.reductions <- reductions;
+ has_ambiguity := true;
+ toks := TerminalSet.add tok !toks
+ ) reductions;
+
+ SymbolMap.iter (fun symbol _ ->
+ match symbol with
+ | Symbol.N _ ->
+ ()
+ | Symbol.T tok ->
+ node.reductions <- reductions;
+ has_ambiguity := true;
+ toks := TerminalSet.add tok !toks
+ ) node.transitions;
+
+ if !has_ambiguity then begin
+ incr ambiguities;
+ if Settings.dump then begin
+ Printf.fprintf (Lazy.force out)
+ "State %d has an end-of-stream conflict. There is a tension between\n\
+ (1) %s\n\
+ without even requesting a lookahead token, and\n\
+ (2) checking whether the lookahead token is %s%s,\n\
+ which would require some other action.\n\n"
+ (number node)
+ (match Production.classify prod with
+ | Some nt ->
+ Printf.sprintf "accepting %s" (Nonterminal.print false nt)
+ | None ->
+ Printf.sprintf "reducing production %s" (Production.print prod))
+ (if TerminalSet.cardinal !toks > 1 then "one of " else "")
+ (TerminalSet.print !toks)
+ end
+ end
+
+ with Not_found ->
+ ()
+
+ ) ();
+
+ if !ambiguities = 1 then
+ Error.grammar_warning [] "one state has an end-of-stream conflict."
+ else if !ambiguities > 1 then
+ Error.grammar_warning [] (Printf.sprintf "%d states have an end-of-stream conflict." !ambiguities)
+