#
#  File: properties.k3
#  Calculate properties of rules
#
#  Copyright 2005 Radboud University of Nijmegen
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU Library General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
#  CVS ID: "$Id: properties.k3,v 1.52 2006/08/09 11:13:09 marcs Exp $"
#
MODULE properties =
	PID_TO_NONT{}
	P_NODES{}	P_NODE_COUNT
	TEMP_LIST
	FREE_LIST
	NEW_ELEMS_LIST
	TRUE_LIST
	FIRST{}		CHANGED_FIRST
	LAST{}		CHANGED_LAST
	FOLLOW{}	CHANGED_FOLLOW
        NONFALSE{}      CHANGED_NONFALSE
	NONPREDICATE{}	CHANGED_NONPREDICATE
	GENERATING{}	CHANGED_GENERATING
	SINGLE_GEN{}
	REACHABLE{}
	PENALTY{}	CHANGED_PENALTY
	LEFT_RECURSIVE{}GRAMMAR_LEFT_RECURSIVE
	CYCLIC
	BLOCKS{}	BLOCKED
        RULE_TYPES{}
        INVISIBLES.

DEFINES G_SET,
	DIRSET,
	ACTION init properties (>NONTERMINAL),
	ACTION log properties,

        ACTION analyse nonfalseness,
	ACTION analyse predicates,
	ACTION calculate first sets,
	ACTION calculate last sets,
	ACTION calculate follow sets,
	ACTION analyse left recursion,
#	ACTION analyse hidden left recursion,
#	ACTION construct left recursive paths,
	ACTION analyse cyclicity,
	ACTION analyse reachability,
	ACTION analyse penalties,
	ACTION analyse string generation,
	ACTION check for commit in rules and options,
        ACTION check for conflicting rule types,

	TEST grammar is left recursive,
	TEST is reachable (>NONTERMINAL),
	TEST is left recursive (>NONTERMINAL),
	TEST is left recursive (>NONTERMINAL, >ALT),
	TEST is nonfalse (>ALT),
	FUNCTION find penalty (>ALT, BOOL>),
	FUNCTION get director set (>NONTERMINAL, DIRSET>),
	FUNCTION get director set (>NONTERMINAL, >ALT, DIRSET>),
	FUNCTION get unrestricted director set (DIRSET>),
	TEST is unrestricted (>DIRSET),

	RULE_TYPE,
        ACTION notify properties of newly created >NONTERMINAL,
        FUNCTION get computed type (>NONTERMINAL, RULE_TYPE>),
        FUNCTION get computed type (>NONTERMINAL, TEXT>),
        ACTION store specified rule type (>NONTERMINAL, >RULE_TYPE),
        TEST rule types are equal (>RULE_TYPE1, >RULE_TYPE2),
        FUNCTION convert >RULE_TYPE to TEXT>,
        TEST computed rule type is cond (>NONTERMINAL),

	FUNCTION convert >DIRSET to terminal G_SET>,
	FUNCTION convert >G_SET to nonterminal LIST>,
	FUNCTION convert >G_SET to lexicon nonterminal LIST>,
	FUNCTION convert >G_SET to terminal LIST>,
	FUNCTION convert >G_SET to regexp skip LIST>,
	FUNCTION convert >G_SET to regexp match LIST>,
	ACTION log (>TEXT, >G_SET),

        ACTION mark nonterminal invisible (>ID, >INT),
        TEST is marked invisible (>NONTERMINAL),
        TEST is invisible in tree (>NONTERMINAL).

USES globals, options, rule, io, id, heap, list, textstorage, transduction.

P_NODE_COUNT = INT.
P_NODES      = P_NODE.
PID_TO_NONT  = NONTERMINAL.
PID          = INT.
TEMP_LIST    = PID_LIST.
FREE_LIST    = PID_LIST.

#
# Initialization
#
PRELUDE init properties:
  init property nodes,
    init nonfalseness analysis,
    init predicate analysis,
    init first set analysis,
    init last set analysis,
    init follow set analysis,
    init left recursion analysis,
    init cyclicity analysis,
    init penalty analysis,
    init generating analysis,
    init blocks,
    init invisibles.

ACTION init properties (>NONTERMINAL):
  make empty (G_SET),
    create node (NONTERMINAL, first, G_SET, PID1),
    create node (NONTERMINAL, last, G_SET, PID2),
    create node (NONTERMINAL, follow, G_SET, PID3),
    create node (NONTERMINAL, nonfalse, 1, PID4),
    create node (NONTERMINAL, nonpredicate, 1, PID5),
    create node (NONTERMINAL, single gen, G_SET, PID6),
    create node (NONTERMINAL, generating, 1, PID7),
    create node (NONTERMINAL, penalty, 1, PID8),
    set first set node (NONTERMINAL, PID1),
    set last set node (NONTERMINAL, PID2),
    set follow set node (NONTERMINAL, PID3),
    set nonfalse node (NONTERMINAL, PID4),
    set nonpredicate node (NONTERMINAL, PID5),
    set single gen node (NONTERMINAL, PID6),
    set generating node (NONTERMINAL, PID7),
    set penalty node (NONTERMINAL, PID8).


#
# Logging to log file
#
FILTER :: plain left recursive;
	  hidden left recursive;
	  left recursive;
          cyclic;
          nonfalse;
          predicate;
          unreachable;
          not generating;
          penalized.

ACTION log properties:
  log nonterminals (unreachable),
  log newline,
  log nonterminals (not generating),
  log newline,
  log nonterminals (predicate),
  log newline,
  log nonterminals (nonfalse),
  log newline,
  log nonterminals (cyclic),
  log newline,
  log nonterminals (left recursive),
  log newline,
  log detailed properties,
  clear heap.

ACTION log newline:
  flush log.

ACTION log penalties:
  log nonterminals (penalized).

ACTION log nonterminals (>FILTER):
  reset heap,
    add nonterminals to heap (FILTER),
    get property announcer (FILTER, TEXT),
    get heap size (INT),
      ( [ INT -> 0 ], log (TEXT + " none.");
        log (TEXT), sort heap, log heap,
	  ( [ FILTER -> left recursive ],
	      ( if positive memo option;
		error ("There are left recursive nonterminals, " +
		       "which is only supported with positive memo")
	      );
	    +
	  )
      ).

FUNCTION get property announcer (>FILTER, TEXT>):
  [ FILTER -> unreachable ],	       [ "Unreachable nonterminals:" -> TEXT ];
  [ FILTER -> left recursive ],	       [ "Left-recursive nonterminals:" -> TEXT ];
#  [ FILTER -> hidden left recursive ], [ "Hidden left-recursive nonterminals:" -> TEXT ];
#  [ FILTER -> plain left recursive ],  [ "Plain left-recursive nonterminals:" -> TEXT ];
  [ FILTER -> predicate ],	       [ "Predicates:" -> TEXT ];
  [ FILTER -> nonfalse ],	       [ "Other nonfalse nonterminals:" -> TEXT ];
  [ FILTER -> cyclic ],		       [ "Cyclic nonterminals:" -> TEXT ];
  [ FILTER -> not generating ],	       [ "Nonterminals not generating:" -> TEXT ];
  [ FILTER -> penalized ],	       [ "Penalized nonterminals:" -> TEXT ];
  ?.

ACTION add nonterminals to heap (>FILTER):
  get number of syntax nonterminals (INT),
    add nonterminals to heap (INT, FILTER).

ACTION add nonterminals to heap (>INT, >FILTER):
  [ INT -> 0 ];
  [ INT - 1 -> INT ],
     add nonterminal to heap (INT, FILTER),
     add nonterminals to heap (INT, FILTER).

ACTION add nonterminal to heap (>INT, >FILTER):
  convert INT to NONTERMINAL,
    ( where FILTER allows NONTERMINAL,
        is displayable (NONTERMINAL),
          convert NONTERMINAL to TEXT, add to heap (TEXT);
      +
    ).

TEST is displayable (>NONTERMINAL):
  nonterminal is undefined and unused (NONTERMINAL),
    -;
  is anonymous nonterminal (NONTERMINAL),
    ( if debug option;
      -
    );
  +.

TEST where >FILTER allows >NONTERMINAL:
  [ FILTER -> unreachable ],
    ( is reachable (NONTERMINAL), -;
      +
    );
  [ FILTER -> left recursive ],
    ( is left recursive (NONTERMINAL);
      -
    );
#  [ FILTER -> hidden left recursive ],
#    ( is left recursive (NONTERMINAL, HIDDEN), [ HIDDEN -> true ];
#      -
#    );
#  [ FILTER -> plain left recursive ],
#    ( is left recursive (NONTERMINAL, HIDDEN), [ HIDDEN -> false ];
#      -
#    );
  [ FILTER -> nonfalse ],
    ( is predicate (NONTERMINAL), -;
      is nonfalse (NONTERMINAL);
      -
    );
  [ FILTER -> predicate ],
    ( is predicate (NONTERMINAL);
      -
    );
  [ FILTER -> cyclic ],
    ( is cyclic (NONTERMINAL);
      -
    );
  [ FILTER -> not generating ],
    ( is generating (NONTERMINAL), -;
      +
    );
  [ FILTER -> penalized ],
    ( is penalized (NONTERMINAL);
      -
    );
  ?.

ACTION log detailed properties:
  if full analysis option,
    get number of syntax nonterminals (INT),
    log detailed properties (INT);
  +.

ACTION log detailed properties (>INT):
  [ INT -> 0 ];
  [ INT - 1 -> INT ],
    maybe log detailed properties (INT),
    log detailed properties (INT).

#
# TODO: how can we specify details on for lexicon nonterminal??
#
ACTION maybe log detailed properties (>INT):
  convert INT to NONTERMINAL,
    ( if details (NONTERMINAL), log detailed properties (NONTERMINAL);
      +
    ).

TEST if details (>NONTERMINAL):
  get pragmats (NONTERMINAL, PRAGMATS),
    if details (PRAGMATS).

ACTION log detailed properties (>NONTERMINAL):
  convert NONTERMINAL to TEXT, log ("Detailed properties of: " + TEXT),
    ( is lexicon nonterminal (NONTERMINAL),
        log reachability (NONTERMINAL),
        log follow set (NONTERMINAL),
        flush log;
      log reachability (NONTERMINAL),
        log emptiness (NONTERMINAL),
        log left recursion (NONTERMINAL),
        log first set (NONTERMINAL),
        log last set (NONTERMINAL),
        log follow set (NONTERMINAL),
        flush log
    ).

ACTION log reachability (>NONTERMINAL):
  is reachable (NONTERMINAL);
  log ("Not reachable.").

ACTION log first set (>NONTERMINAL):
  get first set (NONTERMINAL, G_SET),
    log ("Starters are:", G_SET).

ACTION log last set (>NONTERMINAL):
  get last set (NONTERMINAL, G_SET),
    log ("Enders are:", G_SET).

ACTION log follow set (>NONTERMINAL):
  get follow set (NONTERMINAL, G_SET),
    log ("Followers are:", G_SET).

ACTION log emptiness (>NONTERMINAL):
  is predicate (NONTERMINAL), log ("Nonfalse: yes, predicate");
  is nonfalse (NONTERMINAL),  log ("Nonfalse: yes");
  log ("Nonfalse: no").

ACTION log left recursion (>NONTERMINAL):
  is left recursive (NONTERMINAL, HIDDEN, PATHS),
    ( [ HIDDEN -> true], log ("Left-recursive: yes, hidden"), log (PATHS);
      log ("Left-recursive: yes"), log (PATHS)
    );
  log ("Left-recursive: no").

#
# Log G_SET
#
ACTION log (>TEXT, >G_SET):
  is empty (G_SET), log (TEXT + " <empty>");
  log (TEXT), set log indent (2), log (G_SET).

OBJECT :: nonterminal; terminal; regexp skip; regexp match; other; number.

ACTION log (>G_SET):
  reset heap,
    add to heap and sort (G_SET),
    log heap.

# Note that we did not add lexicon nonterminals to the nonterminal list.

ACTION add to heap and sort (>G_SET):
  [ G_SET -> nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL ],
    get heap size (INT1),
    add to heap (nonterminal, SLIST1),
    add to heap (nonterminal, SLIST2),
    get heap size (INT2),
    add to heap (terminal, SLIST3),
    get heap size (INT3),
    add to heap (regexp match, SLIST5),
    get heap size (INT4),
    add to heap (regexp skip, SLIST4),
    get heap size (INT5),
    add other to heap (BOOL),
    get heap size (INT6),
    sort heap segment (INT1, INT2),
    sort heap segment (INT2, INT3),
    sort heap segment (INT3, INT4),
    sort heap segment (INT4, INT5),
    sort heap segment (INT5, INT6).

ACTION add to heap (>OBJECT, >SLIST):
  convert SLIST to LIST,
    add to heap (OBJECT, LIST).

ACTION add to heap (>OBJECT, >LIST):
  pop (LIST, INT),
    ( convert (OBJECT, INT) to (TEXT),
        add to heap (TEXT),
        add to heap (OBJECT, LIST);
      add to heap (OBJECT, LIST)
    );
  +.

ACTION add other to heap (>BOOL):
  [ BOOL -> false ];
  add to heap ("$OTHER").

TEST convert (>OBJECT, >INT) to (TEXT>):
  [ OBJECT -> nonterminal ],
    convert INT to NONTERMINAL,
      ( is anonymous nonterminal (NONTERMINAL), -;
        convert NONTERMINAL to TEXT
      );
  [ OBJECT -> terminal ],
    convert syntax terminal to id (INT, ID),
    convert id to name (ID, TEXT);
  [ OBJECT -> regexp skip ],
    convert regexp skip to id (INT, ID),
    convert id to name (ID, TEXT),
    ["$SKIP(" + TEXT + ")" -> TEXT];
  [ OBJECT -> regexp match ],
    convert regexp match to id (INT, ID),
    convert id to name (ID, TEXT),
    ["$MATCH(" + TEXT + ")" -> TEXT];
  [ OBJECT -> number ], bindec (INT, TEXT);
  ?.

### Logging derivational paths 

ACTION log (>PATHS):
  pop (PATHS, PATH),
    log (PATHS),
    log (PATH);
  +.

ACTION log (>PATH):
  set log indent (2),
    ( pop (PATH, CALL),
        put log ("-> "),
        put (CALL),
        set log indent (5),
        log rest (PATH);
      +
    ).

ACTION log rest (>PATH):
  pop (PATH, CALL),
    put log (", "),
    put (CALL),
    log rest (PATH);
  put log ("."),
    flush log.

ACTION put (>CALL):
  [ CALL -> call NONTERMINAL in alt INT ],
    bindec (INT, TEXT1),
    convert NONTERMINAL to TEXT2,
    put log ("alt " + TEXT1 + " -> " + TEXT2).

#
# Logging the heap
#
ACTION log heap:
  set log indent (2),
    put heap,
    flush log.

ACTION put heap:
  get heap size (INT),
    ( [ INT -> 0 ], put log ("<empty>");
      put heap (0, INT - 1)
    ).

ACTION put heap (>INT1, >INT2):
  [ INT1 = INT2 ],
    get from heap (INT1, TEXT),
    put log (TEXT + ".");
  get from heap (INT1, TEXT),
    put log (TEXT + ", "),
    put heap (INT1 + 1, INT2).

#
# Logging the property graph (for debugging)
#
ACTION log property nodes (>TEXT):
  get number of property nodes (INT),
    log ("PROPERTY NODES (" + TEXT + "):\n"),
    log property nodes (INT).

ACTION log property nodes (>INT):
  [ INT -> 0 ];
  [ INT - 1 -> INT ],
    log property node (INT),
    flush log,
    log property nodes (INT).

ACTION log property node (>PID):
  convert PID to TEXT1,
    convert pid to nonterminal (PID, NONTERMINAL),
    convert NONTERMINAL to TEXT3,
    get node (PID, P_NODE),
      ( [ P_NODE -> set TYPE value G_SET1 newly found G_SET2 dependent OUT ],
          convert TYPE to TEXT2,
          log ("Node " + TEXT1 + ": " + TEXT2 + ", " + TEXT3),
          log ("Value:", G_SET1),
          log ("Newly found:", G_SET2),
          log ("Dependent nodes:", OUT);
        [ P_NODE -> boolean TYPE falsity INT dependent OUT ],
          convert TYPE to TEXT2,
          log ("Node " + TEXT1 + ": " + TEXT2 + ", " + TEXT3),
          convert INT to TEXT,
          log ("Falsity: " + TEXT),
          log ("Dependent nodes: ", OUT);
        [ P_NODE -> destroyed ],
          log ("Node " + TEXT1 + ": destroyed, " + TEXT3);
        ?
      ).

FUNCTION convert >TYPE to TEXT>:
  [ TYPE -> first ],	   [ "first set" -> TEXT ];
  [ TYPE -> last ],	   [ "last set" -> TEXT ];
  [ TYPE -> follow ],	   [ "follow set" -> TEXT ];
  [ TYPE -> nonfalse ],	   [ "nonfalse" -> TEXT ];
  [ TYPE -> nonpredicate], [ "non-predicate" -> TEXT];
  [ TYPE -> single gen ],  [ "single generated" -> TEXT ];
  [ TYPE -> and ],	   [ "and" -> TEXT ];
  [ TYPE -> generating ],  [ "generating" -> TEXT ];
  [ TYPE -> penalty ],     [ "penalty" -> TEXT ];
  ?. 

ACTION log (>TEXT, >SLIST):
  is empty (SLIST), log (TEXT + " <empty>"); 
  log (TEXT), reset heap,
    add to heap (number, SLIST),
    sort heap, log heap.

#
# Node Operations
#
P_NODE :: set TYPE value G_SET newly found G_SET dependent OUT;
          boolean TYPE falsity INT dependent OUT;
          destroyed.

TYPE :: first;
        last;
        follow;
        single gen;
        nonfalse;
        nonpredicate;
        generating;
        penalty;
        and.

OUT       = PID_SLIST.
PID_SLIST = SLIST.
PID_LIST  = LIST.

ACTION init property nodes:
  [ 0 -> P_NODE_COUNT ], empty (TEMP_LIST), empty (FREE_LIST).

FUNCTION get node (>PID, P_NODE>):
  [ P_NODES{PID} -> P_NODE ].

ACTION store node (>PID, >P_NODE):
  [ P_NODE -> P_NODES{PID} ].

ACTION allocate node (PID>):
  pop (FREE_LIST, PID);
  [ P_NODE_COUNT -> PID ], [ P_NODE_COUNT + 1 -> P_NODE_COUNT ].

FUNCTION get number of property nodes (INT>):
  [ P_NODE_COUNT -> INT ].

FUNCTION convert pid to nonterminal (>PID, NONTERMINAL>):
  [ PID_TO_NONT{PID} -> NONTERMINAL ].

ACTION create node (>NONTERMINAL, >TYPE, >G_SET, PID>):
  allocate node (PID),
    #trace (PID, TYPE, NONTERMINAL),
    make empty (G_SET1),
    empty (OUT),
    [ set TYPE value G_SET1 newly found G_SET dependent OUT -> P_NODE ],
    store node (PID, P_NODE),
    [ NONTERMINAL -> PID_TO_NONT{PID} ].

ACTION create node (>NONTERMINAL, >TYPE, >INT, PID>):
  allocate node (PID),
    #trace (PID, TYPE, NONTERMINAL),
    empty (OUT),
    [ boolean TYPE falsity INT dependent OUT -> P_NODE ],
    store node (PID, P_NODE),
    [ NONTERMINAL -> PID_TO_NONT{PID} ].

ACTION trace (>PID, >TYPE, >NONTERMINAL):
  bindec (PID, TEXT1), convert TYPE to TEXT2, convert NONTERMINAL to TEXT3,
    message ("Allocated pid " + TEXT1 + " (" + TEXT2 + ") for " + TEXT3).

ACTION trace arrow (>PID1, >PID2):
  bindec (PID1, TEXT1), bindec (PID2, TEXT2),
    message ("Dependency: " + TEXT1 + " -> " + TEXT2).

ACTION mark temporary (>PID):
  push (TEMP_LIST, PID).

ACTION recycle nodes:
  pop (TEMP_LIST, PID), free node (PID), recycle nodes;
  +.

ACTION free node (>PID):
  store node (PID, destroyed), push (FREE_LIST, PID).

ACTION add arrows (>PID_SLIST, >PID):
  pop (PID_SLIST, PID1),
    add arrow (PID1, PID),
    add arrows (PID_SLIST, PID);
  +.

ACTION add arrow (>PID1, >PID2):
  #trace arrow (PID1, PID2),
    get node (PID1, P_NODE1),
    add arrow (P_NODE1, PID2),
    store node (PID1, P_NODE1).

#
# For set properties, we use a set of outgoing arrows, since it is not
# needed to propagate a subset twice to some superset.
#
# For boolean nodes we also use use a set of outgoing arrows.
# Consequently, be carefull in recording the correct trueness-value
# of and-nodes, since trueness will be propagated at most once to each
# dependent node.
#
FUNCTION add arrow (>P_NODE>, >PID):
  [ P_NODE -> set TYPE value G_SET1 newly found G_SET2 dependent OUT ], add (OUT, PID),
    [ set TYPE value G_SET1 newly found G_SET2 dependent OUT -> P_NODE ];
  [ P_NODE -> boolean TYPE falsity INT dependent OUT ], add (OUT, PID),
    [ boolean TYPE falsity INT dependent OUT -> P_NODE ];
  ?.

FUNCTION get dependent nodes (>PID, PID_SLIST>):
  get node (PID, P_NODE), get dependent nodes (P_NODE, PID_SLIST).

FUNCTION get dependent nodes (>P_NODE, OUT>):
  [ P_NODE -> set TYPE value G_SET1 newly found G_SET2 dependent OUT ];
  [ P_NODE -> boolean TYPE falsity INT dependent OUT ];
  ?.

TEST is true (>PID):
  get node (PID, P_NODE), is true (P_NODE).

TEST is false (>PID):
  get node (PID, P_NODE), is false (P_NODE).

TEST is true (>P_NODE):
  [ P_NODE -> boolean TYPE falsity INT dependent OUT ],
    ( [ INT -> 0 ];
      -
    );
  ?.

TEST is false (>P_NODE):
  [ P_NODE -> boolean TYPE falsity INT dependent OUT ],
    ( greater (INT, 0);
      -
    );
  ?.

FUNCTION get value (>PID, G_SET>):
  get node (PID, P_NODE), get value (P_NODE, G_SET).

FUNCTION get value (>P_NODE, G_SET>):
  [ P_NODE -> set TYPE value G_SET newly found G_SET1 dependent OUT ];
  ?.

FUNCTION get newly found (>PID, G_SET>):
  get node (PID, P_NODE), get newly found (P_NODE, G_SET).

FUNCTION get newly found (>P_NODE, G_SET>):
  [ P_NODE -> set TYPE value G_SET1 newly found G_SET dependent OUT ];
  ?.

ACTION store newly found (>PID, >G_SET):
  get node (PID, P_NODE), store newly found (P_NODE, G_SET),
    store node (PID, P_NODE).

FUNCTION store newly found (>P_NODE>, >G_SET):
  [ P_NODE -> set TYPE value G_SET1 newly found G_SET2 dependent OUT ],
    [ set TYPE value G_SET1 newly found G_SET dependent OUT -> P_NODE ];
  ?.

ACTION add newly found (>PID, >G_SET):
  get newly found (PID, G_SET1), add (G_SET1, G_SET),
    store newly found (PID, G_SET1).

#
# Paths in graph
#
ACTION find path (>PID1, >PID2, BOOL>):
  free blocks,
    get dependent nodes (PID1, PID_SLIST),
    find on front (PID2, PID_SLIST, BOOL).
    #debug find path (PID1, PID2, BOOL).

#FUNCTION debug find path (>PID1, >PID2, >BOOL):
#  convert PID1 to TEXT1,
#    convert PID2 to TEXT2,
#    convert BOOL to TEXT,
#    debug ("find path " + TEXT1 + " -> " + TEXT2 + ": " + TEXT).

#FUNCTION debug front (>PID_SLIST):
#  convert PID_SLIST to TEXT,
#    debug ("front: " + TEXT).

ACTION find on front (>PID, >PID_SLIST, BOOL>):
  is empty (PID_SLIST), [ false -> BOOL ];
  is element (PID, PID_SLIST), [ true -> BOOL ];
  calculate new front (PID_SLIST),
    find on front (PID, PID_SLIST, BOOL).

#
# Calculate new front, containing the dependent nodes of all nodes
# in the list, minus all previously blocked nodes. New nodes on the
# front are blocked.
#
ACTION calculate new front (>PID_SLIST>):
  pop (PID_SLIST, PID),
    calculate new front (PID_SLIST),
    calculate new front (PID, PID_SLIST1),
    add (PID_SLIST, PID_SLIST1);
  +.

ACTION calculate new front (>PID, PID_SLIST>):
  get dependent nodes (PID, PID_SLIST),
    convert PID_SLIST to PID_LIST,
    filter blocked (PID_LIST),
    convert sorted PID_LIST to PID_SLIST.

# Delete blocked nodes from PID_LIST, block other nodes.
# Important: keep list sorted.

ACTION filter blocked (>PID_LIST>):
  pop (PID_LIST, PID), filter blocked (PID_LIST),
    ( is blocked (PID);
      set block (PID), push (PID_LIST, PID)
    );
  +.

#
# Fixed point computations
#
#
# Set valued properties
#
# Initially, the current value of a node should be empty, while 
# the seed is stored as newly found.
# Note that X not in dependent OUT of X!!
#
NEW_ELEMS_LIST = PID_LIST.

ACTION calculate set property:
  pop new elements (PID),
    process newly founds (PID),
    calculate set property;
  +.

ACTION process newly founds (>PID):
  get node (PID, P_NODE),
    [ P_NODE -> set TYPE value G_SET1 newly found G_SET2 dependent OUT ],
    remove (G_SET2, G_SET1),
      ( is empty (G_SET2),
          [ set TYPE value G_SET1 newly found G_SET2 dependent OUT -> P_NODE ],
          store node (PID, P_NODE);
        add (G_SET1, G_SET2),
          make empty (G_SET3),
          [ set TYPE value G_SET1 newly found G_SET3 dependent OUT -> P_NODE ],
          store node (PID, P_NODE),
          record newly found (PID, OUT, G_SET2)
      );
  ?.

ACTION record newly found (>PID, >PID_SLIST, >G_SET):
  convert PID_SLIST to PID_LIST,
    record newly found (PID, PID_LIST, G_SET).

ACTION record newly found (>PID, >PID_LIST, >G_SET):
  pop (PID_LIST, PID1),
    ( [ PID = PID1 ], # redundant
        record newly found (PID, PID_LIST, G_SET);
      record newly found (PID1, G_SET),
        record newly found (PID, PID_LIST, G_SET)
    );
  +.

ACTION record newly found (>PID, >G_SET):
  get node (PID, P_NODE),
    [ P_NODE -> set TYPE value G_SET1 newly found G_SET2 dependent OUT ],
    add (G_SET2, G_SET),
    [ set TYPE value G_SET1 newly found G_SET2 dependent OUT -> P_NODE ],
    store node (PID, P_NODE),
    push new elements (PID);
  ?.

ACTION push new elements (>PID):
  push (NEW_ELEMS_LIST, PID).

PRED pop new elements (PID>):
  pop (NEW_ELEMS_LIST, PID).

#
# Boolean properties
#
# The changed list contains the nodes whose property is a priori true
# An or-node has falsity 1. An and-node had falsity equal to the number
# of incoming arrows.
#
TRUE_LIST = PID_LIST.

ACTION calculate boolean property: 
  pop true list (PID),
    process trueness (PID),
    calculate boolean property;
  +.

ACTION process trueness (>PID):
  get node (PID, P_NODE),
    process trueness (P_NODE),
    store node (PID, P_NODE).

ACTION process trueness (>P_NODE>):
  [ P_NODE -> boolean TYPE falsity INT dependent OUT ],
    ( [ INT -> 0 ];
      [ INT -> 1 ], record trueness (OUT), [ 0 -> INT ],
        [ boolean TYPE falsity INT dependent OUT -> P_NODE ];
      [ INT - 1 -> INT ],
        [ boolean TYPE falsity INT dependent OUT -> P_NODE ]
    );
  ?.

ACTION record trueness (>PID_SLIST):
  convert PID_SLIST to PID_LIST,
    record trueness (PID_LIST).

ACTION record trueness (>PID_LIST):
  pop (PID_LIST, PID),
    record trueness (PID),
    record trueness (PID_LIST);
  +.

ACTION record trueness (>PID):
  push true list (PID).

ACTION push true list (>PID):
  push (TRUE_LIST, PID).

PRED pop true list (PID>):
  pop (TRUE_LIST, PID).

#
# Sets of grammatical objects
# For efficiency reasons we use buckets for different grammatical objects
# Note that we only have to know that there is an $OTHER in a first set or not
#
G_SET :: nont SLIST lexicon SLIST term SLIST skip SLIST match SLIST other BOOL.

FUNCTION make empty (G_SET>):
  empty (SLIST),
    [ nont SLIST lexicon SLIST term SLIST skip SLIST match SLIST other false -> G_SET ].

TEST is empty (>G_SET):
  [ G_SET -> nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL ],
    is empty (SLIST1), is empty (SLIST2), is empty (SLIST3),
    is empty (SLIST4), is empty (SLIST5), [ BOOL -> false ].

TEST is nonproductive (>G_SET):
  [ G_SET -> nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL ],
    is empty (SLIST2), is empty (SLIST3), is empty (SLIST4),
    is empty (SLIST5), [ BOOL -> false ].

FUNCTION remove (>G_SET1>, >G_SET2):
  [ G_SET1 -> nont SLIST0 lexicon SLIST1 term SLIST2 skip SLIST3 match SLIST4 other BOOL1 ],
    [ G_SET2 -> nont SLIST5 lexicon SLIST6 term SLIST7 skip SLIST8 match SLIST9 other BOOL2 ],
    remove (SLIST0, SLIST5),
    remove (SLIST1, SLIST6),
    remove (SLIST2, SLIST7),
    remove (SLIST3, SLIST8),
    remove (SLIST4, SLIST9),
    and not (BOOL1, BOOL2),
    [ nont SLIST0 lexicon SLIST1 term SLIST2 skip SLIST3 match SLIST4 other BOOL1 -> G_SET1 ].

FUNCTION add (>G_SET1>, >G_SET2):
  [ G_SET1 -> nont SLIST0 lexicon SLIST1 term SLIST2 skip SLIST3 match SLIST4 other BOOL1 ],
    [ G_SET2 -> nont SLIST5 lexicon SLIST6 term SLIST7 skip SLIST8 match SLIST9 other BOOL2 ],
    add (SLIST0, SLIST5),
    add (SLIST1, SLIST6),
    add (SLIST2, SLIST7),
    add (SLIST3, SLIST8),
    add (SLIST4, SLIST9),
    or (BOOL1, BOOL2),
    [ nont SLIST0 lexicon SLIST1 term SLIST2 skip SLIST3 match SLIST4 other BOOL1 -> G_SET1 ].

FUNCTION delete nonterminals (>G_SET>):
  [ G_SET -> nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL ],
    empty (SLIST),
    [ nont SLIST lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL -> G_SET ].

FUNCTION convert >G_SET to nonterminal LIST>:
  [ G_SET -> nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL ],
    convert SLIST1 to LIST.

FUNCTION convert >G_SET to lexicon nonterminal LIST>:
  [ G_SET -> nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL ],
    convert SLIST2 to LIST.

FUNCTION convert >G_SET to terminal LIST>:
  [ G_SET -> nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL ],
    convert SLIST3 to LIST.

FUNCTION convert >G_SET to regexp skip LIST>:
  [ G_SET -> nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL ],
    convert SLIST4 to LIST.

FUNCTION convert >G_SET to regexp match LIST>:
  [ G_SET -> nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL ],
    convert SLIST5 to LIST.

FUNCTION add >NONTERMINAL to >G_SET>:
  convert NONTERMINAL to INT,
    [ G_SET -> nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL ],
    add (SLIST1, INT),
    [ nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL -> G_SET ].

FUNCTION add lexicon >NONTERMINAL to >G_SET>:
  convert NONTERMINAL to INT,
    [ G_SET -> nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL ],
    add (SLIST2, INT),
    [ nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL -> G_SET ].

FUNCTION add terminal >ID to >G_SET>:
  convert id to syntax terminal (ID, INT),
    [ G_SET -> nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL ],
    add (SLIST3, INT),
    [ nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL -> G_SET ].

FUNCTION add regexp skip >ID to >G_SET>:
  convert id to regexp skip (ID, INT),
    [ G_SET -> nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL ],
    add (SLIST4, INT),
    [ nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL -> G_SET ].

FUNCTION add regexp match >ID to >G_SET>:
  convert id to regexp match (ID, INT),
    [ G_SET -> nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL ],
    add (SLIST5, INT),
    [ nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL -> G_SET ].

FUNCTION add other to >G_SET>:
  [ G_SET -> nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other BOOL ],
    [ nont SLIST1 lexicon SLIST2 term SLIST3 skip SLIST4 match SLIST5 other true -> G_SET ].

#
# Blocks
# We use the block to limit recursion in a depth first search
# MS: Some searches can use an implicit marking
#
BLOCKS :: blocked;
          free.

BLOCKED = LIST.

ACTION init blocks:
  empty (BLOCKED).

ACTION init blocks (>INT):
  [ INT -> 0 ], empty (BLOCKED);
  [ INT - 1 -> INT ], [ free -> BLOCKS{INT} ], init blocks (INT).

ACTION set block (>INT):
  [ blocked -> BLOCKS{INT} ], push (BLOCKED, INT).

TEST is blocked (>INT):
  [ BLOCKS{INT} -> blocked ].

ACTION free block (>INT):
  [ free -> BLOCKS{INT} ].

ACTION free blocks:
  free blocks (BLOCKED), empty (BLOCKED).

ACTION set blocks (>LIST):
  pop (LIST, INT),
    set block (INT),
    set blocks (LIST);
  +.

ACTION free blocks (>LIST):
  pop (LIST, INT),
    free block (INT),
    free blocks (LIST);
  +.

#
# Left-recursion detection
#
# A nonterminal N is left-recursive iff there exists a production
# N ->+ N alpha. Left recursion is detected by searching the first set
# graph for cycles.
#
# Required: first set graph.
#
LEFT_RECURSIVE :: true with HIDDEN and PATHS;
                  false.

GRAMMAR_LEFT_RECURSIVE = BOOL.
HIDDEN                 = BOOL.

ACTION init left recursion analysis:
  [ false -> GRAMMAR_LEFT_RECURSIVE ].

ACTION analyse left recursion:
  get number of property nodes (INT1),
    init blocks (INT1),
    get number of syntax nonterminals (INT2),
    analyse left recursion (INT2).

ACTION analyse left recursion (>INT):
  [ INT -> 0 ];
  [ INT - 1 -> INT ],
    convert INT to NONTERMINAL,
    analyse left recursion of nonterminal (NONTERMINAL),
    analyse left recursion (INT).

ACTION analyse left recursion of nonterminal (>NONTERMINAL):
  is lexicon nonterminal (NONTERMINAL),
    get first set node (NONTERMINAL, PID),
    mark not left recursive (NONTERMINAL);
  get first set node (NONTERMINAL, PID), find path (PID, PID, BOOL),
    ( [ BOOL -> true ], mark left recursive (NONTERMINAL);
      mark not left recursive (NONTERMINAL)
    ).

ACTION mark not left recursive (>NONTERMINAL):
  convert NONTERMINAL to INT, [ false -> LEFT_RECURSIVE{INT} ].

ACTION mark left recursive (>NONTERMINAL):
  convert NONTERMINAL to INT, [ false -> HIDDEN ], empty (PATHS),
    [ true with HIDDEN and PATHS -> LEFT_RECURSIVE{INT} ],
    [ true -> GRAMMAR_LEFT_RECURSIVE ].

ACTION mark hidden left recursive (>NONTERMINAL):
  convert NONTERMINAL to INT,
    ( [ LEFT_RECURSIVE{INT} -> true with HIDDEN1 and PATHS ], [ true -> HIDDEN ],
        [ true with HIDDEN and PATHS -> LEFT_RECURSIVE{INT} ];
      ?
    ).

ACTION store left recursive paths (>NONTERMINAL, >PATHS):
  convert NONTERMINAL to INT,
    ( [ LEFT_RECURSIVE{INT} -> true with HIDDEN and PATHS1 ],
        [ true with HIDDEN and PATHS -> LEFT_RECURSIVE{INT} ];
      ?
    ).

TEST is left recursive (>NONTERMINAL, HIDDEN>, PATHS>):
  convert NONTERMINAL to INT,
    [ LEFT_RECURSIVE{INT} -> true with HIDDEN and PATHS ].

TEST is left recursive (>NONTERMINAL, HIDDEN>):
  convert NONTERMINAL to INT,
    [ LEFT_RECURSIVE{INT} -> true with HIDDEN and PATHS ].

TEST is left recursive (>NONTERMINAL):
  is lexicon nonterminal (NONTERMINAL), -;
  convert NONTERMINAL to INT,
    [ LEFT_RECURSIVE{INT} -> true with HIDDEN and PATHS ].

TEST grammar is left recursive:
  [ GRAMMAR_LEFT_RECURSIVE -> true ].

#
# Left recursive alternatives
#
TEST is left recursive (>NONTERMINAL, >ALT):
  is left recursive (NONTERMINAL), is left recursive (ALT).

TEST is left recursive (>ALT):
  [ ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS ],
    ( is left recursive (MEMBERS);
      -
    );
  [ ALT -> FREQ empty COMMITED SECONDPASS ],
    -;
  ?.

TEST is left recursive (>MEMBERS):
  [ MEMBERS -> MEMBER MEMBERS1 ],
    ( is left recursive (MEMBER);
      is nonfalse (MEMBER), is left recursive (MEMBERS1)
    ).

TEST is left recursive (>MEMBER):
  where member is call (MEMBER, NONTERMINAL),
    ( is left recursive (NONTERMINAL);
      -
    );
  where member is option (MEMBER, ALTS, NONTERMINAL),
    ( is left recursive (NONTERMINAL);
      -
    );
  where member is final group (MEMBER, ALTS, NONTERMINAL),
    ( is left recursive (NONTERMINAL);
      -
    ).

TEST is left recursive (>ALTS):
  [ ALTS -> ALT ALTS1 ],
    ( is left recursive (ALT);
      is left recursive (ALTS1)
    ).

#
# Derivational paths
#
# A left-recursive path is a production N ->+ N, alpha. For each alternative
# of each nonterminal with details, a left recursive path is
# constructed by depth first search for a left-recursive call. A path
# is identified by nonterminal names and alternative numbers.
#
PATHS :: PATHS PATH;
         nil.

FUNCTION empty (PATHS>):
  [ nil -> PATHS ].

FUNCTION push (>PATHS>, >PATH):
  [ PATHS PATH -> PATHS ].

TEST pop (>PATHS>, PATH>):
  [ PATHS -> PATHS PATH ].

PATH :: PATH CALL;
        nil.

CALL :: call NONTERMINAL in alt ALT_NR.

ALT_NR = INT.

FUNCTION empty (PATH>):
  [ nil -> PATH ].

FUNCTION add call (>PATH>, >ALT_NR, >NONTERMINAL):
  [ call NONTERMINAL in alt ALT_NR -> CALL ],
    push (PATH, CALL).

FUNCTION push (>PATH>, >CALL):
  [ PATH CALL -> PATH ].

TEST pop (>PATH>, CALL>):
  [ PATH -> PATH CALL ].

# TODO: implemention not finished.

ACTION construct left recursive paths:
  get number of syntax nonterminals (INT),
    construct left recursive paths (INT).

ACTION construct left recursive paths (>INT):
  [ INT -> 0 ];
  [ INT - 1 -> INT ],
    construct left recursive paths of nonterminal (INT),
    construct left recursive paths (INT).

ACTION construct left recursive paths of nonterminal (>INT):
  convert INT to NONTERMINAL,
    is left recursive (NONTERMINAL),
      if details (NONTERMINAL),
        construct left recursive paths (NONTERMINAL, PATHS),
        store left recursive paths (NONTERMINAL, PATHS);
  +.

#
# For each alternative in each definition, we search the grammar
# depth first for the first left-recursive path.
#
FUNCTION construct left recursive paths (>NONTERMINAL, PATHS>):
  get definitions (NONTERMINAL, DEFS), empty (PATHS),
    construct left recursive paths (NONTERMINAL, DEFS, 0|ALT_NR, PATHS).

FUNCTION construct left recursive paths (>NONTERMINAL, >DEFS, >ALT_NR>,>PATHS>):
  [ DEFS -> nil ];
  [ DEFS -> DEF DEFS1 ],
    construct left recursive paths (NONTERMINAL, DEF, ALT_NR, PATHS),
    construct left recursive paths (NONTERMINAL, DEFS1, ALT_NR, PATHS);
  ?.

FUNCTION construct left recursive paths (>NONTERMINAL, >DEF, >ALT_NR>, >PATHS>):
  [ DEF -> FORMALS at POS with ALTS ],
    construct left recursive paths (NONTERMINAL, ALTS, ALT_NR, PATHS).

FUNCTION construct left recursive paths (>NONTERMINAL, >ALTS, >ALT_NR>,>PATHS>):
  [ ALTS -> nil ];
  [ ALTS -> ALT ALTS1 ], [ ALT_NR + 1 -> ALT_NR ],
    construct left recursive path (NONTERMINAL, ALT, ALT_NR, PATHS),
    construct left recursive paths (NONTERMINAL, ALTS1, ALT_NR, PATHS);
  ?.

FUNCTION construct left recursive path (>NONTERMINAL, >ALT, >ALT_NR, >PATHS>):
  [ ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS ],
    find left recursive path (NONTERMINAL, MEMBERS, ALT_NR, PATH),
      push (PATHS, PATH);
  +.

TEST find left recursive path (>NONTERMINAL, >MEMBERS, >ALT_NR, PATH>):
  -.

#
# MS: Apparently this code is not used
#
#  [MEMBERS -> MEMBER MEMBERS1],
#    (where member is call (MEMBER, NONTERMINAL1),
#         ([NONTERMINAL = NONTERMINAL1],
#            empty (PATH),
#            add call (PATH, ALT_NR, NONTERMINAL);
#          get definitions (NONTERMINAL, DEFS),
#            (find left recursive path (NONTERMINAL, DEFS, 0|ALT_NR, PATH),
#               add call (PATH, ALT_NR, NONTERMINAL);
#             is nonfalse (NONTERMINAL),
#               find left recursive path (NONTERMINAL, MEMBERS1, ALT_NR,PATH)));
#     where member is group or option (MEMBER, ALTS),
#       find left recursive path (NONTERMINAL, ALTS, ALT_NR, PATH);
#     is nonfalse (MEMBER),
#       find left recursive path (NONTERMINAL, MEMBERS1, ALT_NR, PATH)).
#
#TEST find left recursive path (>NONTERMINAL, >DEFS, >ALT_NR>, PATH>):
#  [DEFS -> DEF DEFS1],
#    (find left recursive path (NONTERMINAL, DEF, PATH),
#     find left recursive path (NONTERMINAL, DEFS1, PATH)).
#
#TEST find left recursive path (>NONTERMINAL, >DEF, PATH>):
#  [DEF -> FORMALS at POS with ALTS],
#    find left recursive path (NONTERMINAL, ALTS, PATHS).
#
#TEST find left recursive path (>NONTERMINAL, >ALTS, >ALT_NR>, >PATHS>):
#  [ALTS -> ALT ALTS1],
#    find left recursive path (NONTERMINAL, ALT, ALT_NR, PATHS);
#    [ALT_NR + 1 -> ALT_NR],
#    find left recursive path (NONTERMINAL, ALTS1, ALT_NR, PATHS);
#  ?.
#
#TEST find left recursive path (>NONTERMINAL, >ALT, >ALT_NR, >PATHS>):
#  [ALT -> FREQ MEMBERS COMMITED SECONDPASS],
#    find left recursive path (NONTERMINAL, MEMBERS, ALT_NR, PATH),
#      push (PATHS, PATH);
#  +.

#
# Hidden left recursion
#
# A nonterminal N is hidden left-recursive, iff there exists a
# production N ->+ alpha, N, beta ->+ N, beta (alpha not empty).
# Required: nonfalseness, first set graph.
#
# MS: this code is incorrect
#
ACTION analyse hidden left recursion:
  get number of syntax nonterminals (INT),
    analyse hidden left recursion (INT).

ACTION analyse hidden left recursion (>INT):
  [ INT -> 0 ];
  [ INT - 1 -> INT ],
    convert INT to NONTERMINAL,
    analyse hidden left recursion of nonterminal (NONTERMINAL),
    analyse hidden left recursion (INT).

ACTION analyse hidden left recursion of nonterminal (>NONTERMINAL):
  is left recursive (NONTERMINAL),
    get definitions (NONTERMINAL, DEFS),
    analyse hidden left recursion (NONTERMINAL, DEFS);
  +.

ACTION analyse hidden left recursion (>NONTERMINAL, >DEFS):
  [ DEFS -> nil ];
  [ DEFS -> DEF DEFS1 ],
    analyse hidden left recursion (NONTERMINAL, DEF),
    analyse hidden left recursion (NONTERMINAL, DEFS1);
  ?.

ACTION analyse hidden left recursion (>NONTERMINAL, >DEF):
  [ DEF -> FORMALS at POS with ALTS ],
    analyse hidden left recursion (NONTERMINAL, ALTS).

ACTION analyse hidden left recursion (>NONTERMINAL, >ALTS):
  [ ALTS -> nil ];
  [ ALTS -> ALT ALTS1 ],
    analyse hidden left recursion (NONTERMINAL, ALT),
    analyse hidden left recursion (NONTERMINAL, ALTS1);
  ?.

ACTION analyse hidden left recursion (>NONTERMINAL, >ALT):
  [ ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS ],
    analyse hidden left recursion (NONTERMINAL, MEMBERS, false);
  [ ALT -> FREQ empty COMMITED SECONDPASS ];
  ?.

ACTION analyse hidden left recursion (>NONTERMINAL, >MEMBERS, >HIDDEN):
  [ MEMBERS -> nil ];
  [ MEMBERS -> MEMBER MEMBERS1 ],
    analyse hidden left recursion (NONTERMINAL, MEMBER, HIDDEN),
      ( is nonfalse (MEMBER),
          analyse hidden left recursion (NONTERMINAL, MEMBERS1, true);
        +
      );
  ?.

ACTION analyse hidden left recursion (>NONTERMINAL, >MEMBER, >HIDDEN):
  where member is call (MEMBER, NONTERMINAL1),
    analyse hidden left recursion (NONTERMINAL, NONTERMINAL1, HIDDEN);
  where member is option (MEMBER, ALTS, NONTERMINAL1),
    analyse hidden left recursion (NONTERMINAL, NONTERMINAL1, HIDDEN);
  where member is final group (MEMBER, ALTS, NONTERMINAL1),
    analyse hidden left recursion (NONTERMINAL, NONTERMINAL1, HIDDEN);
  +.

#
# Check whether NONT1 can be left-recursive due to calling
# nonterminal NONT2. As an optimization, first check whether NONT2 is
# left-recursive, otherwise there can't be a left-recursive call from NONT2.
# Proof: suppose N ->+ X, alpha ->+ N, beta, alpha.
# Then also X ->+ N, beta ->+ X, alpha, beta.
# If the first-set graph contains a path from nonterminal NONT1 to NONT2,
# then NONT1 is in the first set of NONT2, and therefore NONT2 left-produces
# NONT1. If also NONT1 left-produces NONT2 (which is the case), then we
# have found a left-recursive call of NONT1.
#
ACTION analyse hidden left recursion (>NONTERMINAL1, >NONTERMINAL2, >HIDDEN):
  [ HIDDEN -> true ],
    is left recursive (NONTERMINAL2),
      get first set node (NONTERMINAL1, PID1),
      get first set node (NONTERMINAL2, PID2),
      find path (PID1, PID2, BOOL),
        ( [ BOOL -> false ];
          mark hidden left recursive (NONTERMINAL1)
	);
  +.

#ACTION report hidden left recursion (>NONTERMINAL):
#  convert NONTERMINAL to TEXT,
#    error ("nonterminal `" + TEXT + "' has hidden left-recursion").

#
# Reachability analysis
#
# Let R be the root nonterminal of the grammar. Nonterminal N is reachable
# iff there exists a derivation R ->* alpha, N, beta.
#
REACHABLE = BOOL.

ACTION mark reachable (>NONTERMINAL):
  convert NONTERMINAL to INT, [ true -> REACHABLE{INT} ].

TEST is reachable (>NONTERMINAL):
  convert NONTERMINAL to INT, [ REACHABLE{INT} -> true ].

ACTION analyse reachability:
  clear reachability,
    mark start nonterminal reachable,
    mark user nonterminals reachable.

ACTION clear reachability:
  get number of syntax nonterminals (INT),
    clear reachability (INT),
    init blocks (INT).

ACTION clear reachability (>INT):
  [ INT -> 0 ];
  [ INT - 1 -> INT ], [ false -> REACHABLE{INT} ], clear reachability (INT).

ACTION mark start nonterminal reachable:
  get start nonterminal (NONTERMINAL),
    mark reachable (NONTERMINAL).

ACTION mark user nonterminals reachable:
  get root nonterminal (NONTERMINAL),
    analyse reachability (NONTERMINAL).

ACTION analyse reachability (>NONTERMINAL):
  convert NONTERMINAL to INT,
    ( is blocked (INT);
      set block (INT),
        mark reachable (NONTERMINAL),
        get definitions (NONTERMINAL, DEFS),
        analyse reachability (DEFS)
    ).

ACTION analyse reachability (>DEFS):
  [ DEFS -> nil ];
  [ DEFS -> DEF DEFS1 ],
    analyse reachability (DEF),
    analyse reachability (DEFS1);
  ?.

ACTION analyse reachability (>DEF):
  [ DEF -> FORMALS at POS with ALTS ], analyse reachability (ALTS).

ACTION analyse reachability (>ALTS):
  [ ALTS -> nil ];
  [ ALTS -> ALT ALTS1 ],
    analyse reachability (ALT),
    analyse reachability (ALTS1);
  ?.

ACTION analyse reachability (>ALT):
  [ ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS ],
    analyse reachability (MEMBERS);
  +.

ACTION analyse reachability (>MEMBERS):
  [ MEMBERS -> nil ];
  [ MEMBERS -> MEMBER MEMBERS1 ],
    analyse reachability (MEMBER),
    analyse reachability (MEMBERS1);
  ?.

ACTION analyse reachability (>MEMBER):
  where member is call (MEMBER, NONTERMINAL),
    analyse reachability (NONTERMINAL);
  where member is option (MEMBER, ALTS, NONTERMINAL),
    mark reachable (NONTERMINAL),
    analyse reachability (ALTS),
    analyse reachability (NONTERMINAL);
  where member is final group (MEMBER, ALTS, NONTERMINAL),
    mark reachable (NONTERMINAL),
    analyse reachability (ALTS),
    analyse reachability (NONTERMINAL);
  +.

#
# Reachability of penalties
#
# For each nonterminal, we calculate whether it can produce a penalty.
# In order to know whether a memo can be totally blocked, or blocked
# only above some penalty level, we need to know whether a memo test
# can be followed by a penalty.
#
PENALTY          = PID.
CHANGED_PENALTY  = PID_LIST.

ACTION init penalty analysis:
  empty (CHANGED_PENALTY).

TEST is penalized (>NONTERMINAL):
  get penalty node (NONTERMINAL, PID), is true (PID).

FUNCTION get penalty node (>NONTERMINAL, PID>):
  convert NONTERMINAL to INT, [ PENALTY{INT} ->PID ].

ACTION set penalty node (>NONTERMINAL, >PID):
  convert NONTERMINAL to INT, [ PID -> PENALTY{INT} ].

ACTION mark changed penalty (>PID):
  push (CHANGED_PENALTY, PID).

ACTION analyse penalties:
  build penalty graph,
    #log property nodes ("after building penalty graph"),
    display time ("builded penalty graph"),
    get penalties from changed list,
    calculate boolean property.

ACTION get penalties from changed list:
  [ CHANGED_PENALTY -> TRUE_LIST ], empty (CHANGED_PENALTY).

ACTION build penalty graph:
  get number of syntax nonterminals (INT),
    build penalty graph (INT).

ACTION build penalty graph (>INT):
  [ INT -> 0 ];
  [ INT - 1 -> INT ],
    build penalty graph for nonterminal (INT),
    build penalty graph (INT).

ACTION build penalty graph for nonterminal (>INT):
  convert INT to NONTERMINAL,
    get definitions (NONTERMINAL, DEFS),
    get penalty node (NONTERMINAL, PID),
    build penalty graph (PID, DEFS).

ACTION build penalty graph (>PID, >DEFS):
  [ DEFS -> nil];
  [ DEFS -> DEF DEFS1 ],
    build penalty graph (PID, DEF),
    build penalty graph (PID, DEFS1);
  ?.

ACTION build penalty graph (>PID, >DEF):
  [ DEF -> FORMALS at POS with ALTS ],
    build penalty graph (PID, ALTS).

ACTION build penalty graph (>PID, >ALTS):
  [ ALTS -> nil ];
  [ ALTS -> ALT ALTS1 ],
    build penalty graph (PID, ALT),
    build penalty graph (PID, ALTS1);
  ?.

ACTION build penalty graph (>PID, >ALT):
  [ ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS ],
    build penalty graph (PID, MEMBERS);
  [ ALT -> FREQ empty COMMITED SECONDPASS ];
  ?.

ACTION build penalty graph (>PID, >MEMBERS):
  [MEMBERS -> nil];
  [MEMBERS -> MEMBER MEMBERS1],
    build penalty graph (PID, MEMBER),
    build penalty graph (PID, MEMBERS1);
  ?.

ACTION build penalty graph (>PID, >MEMBER):
  where member is call (MEMBER, NONTERMINAL),
    get penalty node (NONTERMINAL, PID1),
    add arrow (PID1, PID);
  where member is option (MEMBER, ALTS, NONTERMINAL),
    get penalty node (NONTERMINAL, PID1),
    add arrow (PID1, PID),
    build penalty graph (PID1, ALTS);
  where member is final group (MEMBER, ALTS, NONTERMINAL),
    get penalty node (NONTERMINAL, PID1),
    add arrow (PID1, PID),
    build penalty graph (PID1, ALTS);
  where member is penalty (MEMBER),
    mark changed penalty (PID);
  +.

### Penalizedness

FUNCTION find penalty (>ALT, BOOL>):
  [ ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS ], find penalty (MEMBERS, BOOL);
  [ ALT -> FREQ empty COMMITED SECONDPASS ], [ false -> BOOL ];
  ?.

FUNCTION find penalty (>MEMBERS, BOOL>):
  [ MEMBERS -> nil ], [ false -> BOOL ];
  [ MEMBERS -> MEMBER MEMBERS1 ], find penalty (MEMBER, BOOL),
    ( [ BOOL -> true];
      find penalty (MEMBERS1, BOOL)
    );
  ?.

FUNCTION find penalty (>MEMBER, BOOL>):
  where member is penalty (MEMBER),
    [ true -> BOOL ];
  where member is option (MEMBER, ALTS, NONTERMINAL),
    find penalty (NONTERMINAL, BOOL);
  where member is call (MEMBER, NONTERMINAL),
    find penalty (NONTERMINAL, BOOL);
  where member is final group (MEMBER, ALTS, NONTERMINAL),
    find penalty (NONTERMINAL, BOOL);
  [ false -> BOOL ].

FUNCTION find penalty (>NONTERMINAL, BOOL>):
  is penalized (NONTERMINAL), [ true -> BOOL ];
  [ false -> BOOL ].

FUNCTION find penalty (>ALTS, BOOL>):
  [ ALTS -> nil ], [ false -> BOOL ];
  [ ALTS -> ALT ALTS1 ], find penalty (ALT, BOOL),
    ( [ BOOL -> true];
      find penalty (ALTS1, BOOL)
    );
  ?.

#
# String-generation
#
# A nonterminal N is generating iff for every production N ->+ alpha there
# exists a terminal production alpha ->+ beta. All nonterminals should be
# generating in order to ensure that they have terminal productions.
#
GENERATING         = PID.
CHANGED_GENERATING = PID_LIST.

ACTION init generating analysis:
  empty (CHANGED_GENERATING).

TEST is generating (>NONTERMINAL):
  get generating node (NONTERMINAL, PID),
    is true (PID).

FUNCTION get generating node (>NONTERMINAL, PID>):
  convert NONTERMINAL to INT,
    [GENERATING{INT} -> PID].

ACTION set generating node (>NONTERMINAL, >PID):
  convert NONTERMINAL to INT,
    [PID -> GENERATING{INT}].

ACTION mark changed generating (>PID):
  push (CHANGED_GENERATING, PID).

ACTION analyse string generation:
  build generating graph,
    #log property nodes ("after building generating graph"),
    display time ("builded generating graph"),
    get generating from changed list,
    calculate boolean property,
    recycle nodes,
    report nonterminals not generating.

ACTION get generating from changed list:
  [CHANGED_GENERATING -> TRUE_LIST],
    empty (CHANGED_GENERATING).

ACTION build generating graph:
  get number of syntax nonterminals (INT),
    build generating graph (INT).

ACTION build generating graph (>INT):
  [INT -> 0];
  [INT - 1 -> INT],
    convert INT to NONTERMINAL,
    build generating graph for nonterminal (NONTERMINAL),
    build generating graph (INT).

ACTION build generating graph for nonterminal (>NONTERMINAL):
  is lexicon nonterminal (NONTERMINAL),
    get generating node (NONTERMINAL, PID),
    mark changed generating (PID);
  get definitions (NONTERMINAL, DEFS),
    get generating node (NONTERMINAL, PID),
    build generating graph (PID, DEFS).

ACTION build generating graph (>PID, >DEFS):
  [DEFS -> nil];
  [DEFS -> DEF DEFS1],
    build generating graph (PID, DEF),
    build generating graph (PID, DEFS1);
  ?.

ACTION build generating graph (>PID, >DEF):
  [DEF -> FORMALS at POS with ALTS],
    build generating graph (PID, ALTS).

ACTION build generating graph (>PID, >ALTS):
  [ALTS -> nil];
  [ALTS -> ALT ALTS1],
    build generating graph (PID, ALT),
    build generating graph (PID, ALTS1);
  ?.

# When string-generation of an alternative A is dependent on string-generation
# of n other nonterminals or groups, the number of dependencies is counted,
# and an n-way and-node is created for deciding on string-generation of A.

ACTION build generating graph (>PID, >ALT):
  [ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS],
    empty (PID_SLIST),
    build generating graph (MEMBERS, PID_SLIST),
    get size (PID_SLIST, INT),
      ([INT -> 0],
         mark changed generating (PID);
       convert pid to nonterminal (PID, NONTERMINAL),
         create node (NONTERMINAL, and, INT, PID1),
         mark temporary (PID1),
         add arrows (PID_SLIST, PID1),
         add arrow (PID1, PID));
  [ALT -> FREQ empty COMMITED SECONDPASS],
    mark changed generating (PID);
  ?.

ACTION build generating graph (>MEMBERS, >PID_SLIST>):
  [MEMBERS -> nil];
  [MEMBERS -> MEMBER MEMBERS1],
    build generating graph (MEMBER, PID_SLIST),
    build generating graph (MEMBERS1, PID_SLIST);
  ?.

# Options are always generating. Therefore, we mark them generating,
# and do not need to add dependencies.

ACTION build generating graph (>MEMBER, >PID_SLIST>):
  where member is call (MEMBER, NONTERMINAL),
    get generating node (NONTERMINAL, PID),
    add (PID_SLIST, PID);
  where member is final group (MEMBER, ALTS, NONTERMINAL),
    get generating node (NONTERMINAL, PID),
    add (PID_SLIST, PID),
    build generating graph (PID, ALTS);
  where member is option (MEMBER, ALTS, NONTERMINAL),
    get generating node (NONTERMINAL, PID),
    mark changed generating (PID),	# option is generating due to empty alt
    build generating graph (PID, ALTS);
  +.

ACTION report nonterminals not generating:
  get number of syntax nonterminals (INT),
    report nonterminals not generating (INT).

ACTION report nonterminals not generating (>INT):
  [INT -> 0];
  [INT - 1 -> INT],
    maybe report nonterminal not generating (INT),
    report nonterminals not generating (INT). 

ACTION maybe report nonterminal not generating (>INT):
  convert INT to NONTERMINAL,
    ( is generating (NONTERMINAL);
      is anonymous nonterminal (NONTERMINAL);
      convert NONTERMINAL to TEXT,
       [ "nonterminal `" + TEXT + "' is not generating" -> TEXT],
       error (TEXT)
    ).

#
# Generating check
#
#GENERATING :: always;
#              sometimes;
#              never.
#
#FUNCTION convert >GENERATING to TEXT>:
#  [GENERATING -> always], ["always" -> TEXT];
#  [GENERATING -> sometimes], ["sometimes" -> TEXT];
#  [GENERATING -> never], ["never" -> TEXT];
#  ?.
#
#ACTION check generating rule types:
#  get number of syntax nonterminals (INT),
#    check generating rule types (INT).
#
#ACTION check generating rule types (>INT):
#  [INT -> 0];
#  [INT - 1 -> INT],
#    check generating rule type (INT),
#    check generating rule types (INT).
#
#ACTION check generating rule type (>INT):
#  convert INT to NONTERMINAL,
#    (is start nonterminal (NONTERMINAL);
# FIXME: check for type of lexicon nonterminal
#     is lexicon nonterminal (NONTERMINAL);
#     nonterminal is defined (NONTERMINAL),
#       calculate generating type (NONTERMINAL, GENERATING),
#       convert GENERATING to RULE_TYPE1,
#       get specified rule type for nonterminal (NONTERMINAL, RULE_TYPE2),
#       check on type equality (RULE_TYPE1, RULE_TYPE2, NONTERMINAL);
#     +).
#
#FUNCTION calculate generating type (>NONTERMINAL, GENERATING>):
#  get definitions (NONTERMINAL, DEFS),
#    ([DEFS -> DEF DEFS],
#       calculate generating type (DEF, GENERATING),
#       calculate generating type (DEFS, GENERATING);
#     ?).
#
#FUNCTION calculate generating type (>DEFS, >GENERATING>):
#  [DEFS -> nil];
#  [DEFS -> DEF DEFS],
#    calculate generating type (DEF, GENERATING1),
#    calculate gentype for alternatives (GENERATING, GENERATING1, GENERATING),
#    calculate generating type (DEFS, GENERATING);
#  ?.
#
#FUNCTION calculate gentype for alternatives (>GENERATING1, >GENERATING2, GENERATING>):
#  [GENERATING1 -> always],
#    [GENERATING2 -> always],
#    [always -> GENERATING];
#  [GENERATING1 -> never],
#    [GENERATING2 -> never],
#    [never -> GENERATING];
#  [sometimes -> GENERATING].
#
#FUNCTION calculate generating type (>DEF, GENERATING>):
#  [DEF -> FORMALS at POS with ALTS],
#    ([ALTS -> ALT ALTS],
#       calculate generating type (ALT, GENERATING),
#       calculate generating type (ALTS, GENERATING);
#     ?).
#
#FUNCTION calculate generating type (>ALTS, >GENERATING>):
#  [ALTS -> nil];
#  [ALTS -> ALT ALTS],
#    calculate generating type (ALT, GENERATING1),
#    calculate gentype for alternatives (GENERATING, GENERATING1, GENERATING),
#    calculate generating type (ALTS, GENERATING);
#  ?.
#
#FUNCTION calculate generating type (>ALT, GENERATING>):
#  [ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS],
#    [never -> GENERATING],
#    calculate generating type (MEMBERS, GENERATING);
#  [ALT -> FREQ empty COMMITED SECONDPASS],
#    [never -> GENERATING];
#  ?.
#
#FUNCTION calculate generating type (>MEMBERS, >GENERATING>):
#  [MEMBERS -> nil];
#  [MEMBERS -> MEMBER MEMBERS],
#    calculate generating type (MEMBER, GENERATING1),
#    calculate gentype for members (GENERATING, GENERATING1, GENERATING),
#    calculate generating type (MEMBERS, GENERATING);
#  ?.
#
#FUNCTION calculate gentype for members(>GENERATING1, >GENERATING2, GENERATING>):
#  [GENERATING1 -> always],
#    [always -> GENERATING];
#  [GENERATING1 -> sometimes],
#    ([GENERATING2 -> always],
#       [always -> GENERATING];
#     [sometimes -> GENERATING]);
#  [GENERATING2 -> GENERATING].
#
#FUNCTION calculate generating type (>MEMBER, GENERATING>):
#  where member is call (MEMBER, NONTERMINAL),
#    get specified rule type for nonterminal (NONTERMINAL, RULE_TYPE),
#    convert RULE_TYPE to GENERATING;
#  where member is final group (MEMBER, ALTS),
#    ([ALTS -> ALT ALTS],
#       calculate generating type (ALT, GENERATING),
#       calculate generating type (ALTS, GENERATING);
#     ?);
#  where member is option (MEMBER, ALTS, NONTERMINAL),
#    [never -> GENERATING],
#    calculate generating type (ALTS, GENERATING);
#  where member is terminal (MEMBER),
#    [always -> GENERATING];
#  [never -> GENERATING].
#
#FUNCTION convert >RULE_TYPE to GENERATING>:
#  [RULE_TYPE -> unknown], [always -> GENERATING];
#  [RULE_TYPE -> rule], [always -> GENERATING];
#  [RULE_TYPE -> option], [sometimes -> GENERATING];
#  [RULE_TYPE -> cond], [never -> GENERATING];
#  ?.
#
#FUNCTION convert >GENERATING to RULE_TYPE>:
#  [GENERATING -> always], [rule -> RULE_TYPE];
#  [GENERATING -> sometimes], [option -> RULE_TYPE];
#  [GENERATING -> never], [cond -> RULE_TYPE];
#  ?.
#
#TEST is generating (>NONTERMINAL):
#  get specified rule type for nonterminal (NONTERMINAL, RULE_TYPE),
#    convert RULE_TYPE to GENERATING,
#    ([GENERATING -> never], -;
#     +).


### Error checking:

#ACTION report nonterminals not generating:
#  get number of syntax nonterminals (INT),
#    report nonterminals not generating (INT).

#ACTION report nonterminals not generating (>INT):
#  [INT -> 0];
#  [INT - 1 -> INT],
#    maybe report nonterminal not generating (INT),
#    report nonterminals not generating (INT). 

#ACTION maybe report nonterminal not generating (>INT):
#  convert INT to NONTERMINAL,
#    (is generating (NONTERMINAL);
#     is anonymous nonterminal (NONTERMINAL);
#     nonterminal is not used (NONTERMINAL);
#     convert NONTERMINAL to TEXT,
#       ["nonterminal `" + TEXT + "' is not generating" -> TEXT],
#       warning (TEXT)).


##############
#            #
#   Cycles   #
#            #
##############

# A nonterminal N is cyclic iff there exists a production N ->+ N.
# Cyclic nonterminals are forbidden, because they lead to an infinite
# number of parse trees.
# A nonterminal is cyclic, iff it single-generates itself. Therefore,
# we build a single-generation graph. However, instead of calculating
# sets of single productions, we check the graph for cycles, which is
# much cheaper in space and time.

CYCLIC = SLIST.

ACTION init cyclicity analysis:
  empty (CYCLIC).

ACTION analyse cyclicity:
  build single gen graph,
    get number of syntax nonterminals (INT),
    analyse cyclicity (INT).

ACTION analyse cyclicity (>INT):
  [INT -> 0];
  [INT - 1 -> INT],
    analyse cyclicity of nonterminal (INT),
    analyse cyclicity (INT).

ACTION analyse cyclicity of nonterminal (>INT):
  convert INT to NONTERMINAL,
    get single gen node (NONTERMINAL, PID),
    find path (PID, PID, BOOL),
      ([BOOL -> true],
         mark cyclic (NONTERMINAL),
         report cycle (NONTERMINAL);
       +).

ACTION mark cyclic (>NONTERMINAL):
  convert NONTERMINAL to INT,
    add (CYCLIC, INT).

TEST is cyclic (>NONTERMINAL):
  convert NONTERMINAL to INT,
    is element (INT, CYCLIC).

ACTION report cycle (>NONTERMINAL):
  is predicate (NONTERMINAL);
  is anonymous nonterminal (NONTERMINAL);
  convert NONTERMINAL to TEXT,
    ["nonterminal `" + TEXT + "' is cyclic" -> TEXT],
    error (TEXT).

########################
#                      #
#   Single generated   #
#                      #
########################

# Nonterminals N single-generates symbol x if there exists some
# production N ->+ x. Since we need only the structure of the graph
# for calculating cyclicity of nonterminals, inital single-generation
# sets (the seeds) are not stored.
# Required property: nonfalseness.

SINGLE_GEN = PID.

FUNCTION get single gen node (>NONTERMINAL, PID>):
  convert NONTERMINAL to INT,
    [SINGLE_GEN{INT} -> PID].

ACTION set single gen node (>NONTERMINAL, >PID):
  convert NONTERMINAL to INT,
    [PID -> SINGLE_GEN{INT}].

ACTION build single gen graph:
  get number of syntax nonterminals (INT),
    build single gen graph (INT),
    display time ("builded single gen graph").
    #log property nodes ("after building single gen graph").

ACTION build single gen graph (>INT):
  [INT -> 0];
  [INT - 1 -> INT],
    build single gen graph for nonterminal (INT),
    build single gen graph (INT).

ACTION build single gen graph for nonterminal (>INT):
  convert INT to NONTERMINAL,
    get definitions (NONTERMINAL, DEFS),
    get single gen node (NONTERMINAL, PID),
    build single gen graph (PID, DEFS).

ACTION build single gen graph (>PID, >DEFS):
  [DEFS -> nil];
  [DEFS -> DEF DEFS1],
    build single gen graph (PID, DEF),
    build single gen graph (PID, DEFS1);
  ?.

ACTION build single gen graph (>PID, >DEF):
  [DEF -> FORMALS at POS with ALTS],
    build single gen graph (PID, ALTS).

ACTION build single gen graph (>PID, >ALTS):
  [ALTS -> nil];
  [ALTS -> ALT ALTS1],
    build single gen graph (PID, ALT),
    build single gen graph (PID, ALTS1);
  ?.

ACTION build single gen graph (>PID, >ALT):
  [ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS],
    build single gen graph (PID, MEMBERS, true, BOOL);
  [ALT -> FREQ empty COMMITED SECONDPASS];
  ?.

ACTION build single gen graph (>PID, >MEMBERS, >BOOL1, BOOL2>):
  [MEMBERS -> nil],
    [true -> BOOL2];
  [MEMBERS -> MEMBER MEMBERS1],
    (is nonfalse (MEMBER),
       build single gen graph (PID, MEMBERS1, BOOL1, BOOL2),
       build single gen graph (PID, MEMBER, BOOL1, BOOL2);
     build single gen graph (PID, MEMBERS1, false, BOOL),
       build single gen graph (PID, MEMBER, BOOL1, BOOL),
       [false -> BOOL2]);
  ?.

ACTION build single gen graph (>PID, >MEMBER, >BOOL1, >BOOL2):
  where member is call (MEMBER, NONTERMINAL),
    ([BOOL1 -> true],
       [BOOL2 -> true],
         get single gen node (NONTERMINAL, PID1),
         add arrow (PID1, PID);
     +);
  where member is option (MEMBER, ALTS, NONTERMINAL),
    get single gen node (NONTERMINAL, PID1),
    build single gen graph (PID1, ALTS),
    ([BOOL1 -> true],
       [BOOL2 -> true],
       add arrow (PID1, PID);
     +);
  where member is final group (MEMBER, ALTS, NONTERMINAL),
    get single gen node (NONTERMINAL, PID1),
    build single gen graph (PID1, ALTS),
    ([BOOL1 -> true],
       [BOOL2 -> true],
       add arrow (PID1, PID);
     +);
  +.

#
# Nonfalseness
#
# A nonterminal is nonfalse iff one of its alternatives is nonfalse.
# An alternative is nonfalse iff it is empty, or all of its members are
# nonfalse. A member is nonfalse, iff it can produce empty.
#
NONFALSE          = PID.
CHANGED_NONFALSE  = PID_LIST.

ACTION init nonfalseness analysis:
  empty (CHANGED_NONFALSE).

TEST is nonfalse (>NONTERMINAL):
  is lexicon nonterminal (NONTERMINAL), -;
  get nonfalse node (NONTERMINAL, PID),
    is true (PID).

FUNCTION get nonfalse node (>NONTERMINAL, PID>):
  convert NONTERMINAL to INT,
    [ NONFALSE{INT} -> PID ].

ACTION set nonfalse node (>NONTERMINAL, >PID):
  convert NONTERMINAL to INT,
    [ PID -> NONFALSE{INT} ].

ACTION mark changed nonfalse (>PID):
  push (CHANGED_NONFALSE, PID).

ACTION analyse nonfalseness:
  build nonfalse graph,
    display time ("builded nonfalse graph"),
    get nonfalses from changed list,
    calculate boolean property,
    recycle nodes.

ACTION get nonfalses from changed list:
  [ CHANGED_NONFALSE -> TRUE_LIST ], empty (CHANGED_NONFALSE).

#
# Initally, nonfalseness of a nonterminal is assumed false. This assumption
# is represented by a boolean node with falsity 1. For each alternative,
# this node has one incoming arrow. If one of the alternatives is empty,
# then the nonterminal is nonfalse.
#
ACTION build nonfalse graph:
  get number of syntax nonterminals (INT),
    build nonfalse graph (INT).

ACTION build nonfalse graph (>INT):
  [ INT -> 0 ];
  [ INT - 1 -> INT ],
    convert INT to NONTERMINAL,
    build nonfalse graph (NONTERMINAL),
    build nonfalse graph (INT).

NONFALSENESS = BOOL.

ACTION build nonfalse graph (>NONTERMINAL):
  is lexicon nonterminal (NONTERMINAL);
  get definitions (NONTERMINAL, DEFS),
    get nonfalse node (NONTERMINAL, PID),
    build nonfalse graph (PID, DEFS).

ACTION build nonfalse graph (>PID, >DEFS):
  [ DEFS -> nil ];
  [ DEFS -> DEF DEFS1 ],
    build nonfalse graph (PID, DEF),
    build nonfalse graph (PID, DEFS1);
  ?.

ACTION build nonfalse graph (>PID, >DEF):
  [ DEF -> FORMALS at POS with ALTS ], build nonfalse graph (PID, ALTS).

ACTION build nonfalse graph (>PID, >ALTS):
  [ ALTS -> nil ];
  [ ALTS -> ALT ALTS1 ],
    build nonfalse graph (PID, ALT),
    build nonfalse graph (PID, ALTS1);
  ?.

#
# If some member of an alternative is a terminal, then it is not nonfalse.
# When nonfalseness of an alternative A is dependent on nonfalseness
# of n other nonterminals or groups, the number of dependencies is counted,
# and then a n-way and-node is created for deciding on nonfalseness of A.
#
ACTION build nonfalse graph (>PID, >ALT):
  [ ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS ], empty (PID_SLIST),
    build nonfalse graph (MEMBERS, PID_SLIST, true|NONFALSENESS),
    get size (PID_SLIST, INT),
      ( [ NONFALSENESS -> false ];
        [ INT -> 0 ], mark changed nonfalse (PID);
        convert pid to nonterminal (PID, NONTERMINAL),
          create node (NONTERMINAL, and, INT, PID1),
          mark temporary (PID1),
          add arrows (PID_SLIST, PID1),
          add arrow (PID1, PID)
     );
  [ ALT -> FREQ empty COMMITED SECONDPASS ], mark changed nonfalse (PID);
  ?.

#
# Check whether all members may be nonfalse. If so, yield the dependencies.
# If some member is a terminal or regexp or lexicon nonterminal, the member
# cannot be nonfalse. Otherwise, if the member is a nonterminal or group,
# nonfalseness of that member is required.
#
ACTION build nonfalse graph (>MEMBERS, >PID_SLIST>, >NONFALSENESS>):
  [ MEMBERS -> nil ];
  [ MEMBERS -> MEMBER MEMBERS1 ],
    build nonfalse graph (MEMBER, PID_SLIST, NONFALSENESS),
    build nonfalse graph (MEMBERS1, PID_SLIST, NONFALSENESS);
  ?.

#
# An option is always nonfalse. Therefore, we mark it as nonfalse and
# do not need to add a dependency for it.
#
ACTION build nonfalse graph (>MEMBER, >PID_SLIST>, >NONFALSENESS>):
  where member is call (MEMBER, NONTERMINAL),
    get nonfalse node (NONTERMINAL, PID),
    add (PID_SLIST, PID);
  where member is final group (MEMBER, ALTS, NONTERMINAL),
    get nonfalse node (NONTERMINAL, PID),
    add (PID_SLIST, PID),
    build nonfalse graph (PID, ALTS);
  where member is option (MEMBER, ALTS, NONTERMINAL),
    get nonfalse node (NONTERMINAL, PID),
    mark changed nonfalse (PID),	# option is nonfalse due to empty alt
    build nonfalse graph (PID, ALTS);
  where member is regexp (MEMBER, ID),
    convert id to name (ID, TEXT), strip quotes (TEXT),
      ( regexp can match empty string (TEXT);
	[ false -> NONFALSENESS ]
      );
  where member is terminal (MEMBER),
    [ false -> NONFALSENESS ];
  +.

EXTERNAL TEST regexp can match empty string (>TEXT):401

#
# Use the following functions only after nonfalseness analysis!
#
TEST is nonfalse (>ALT):
  [ ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS ],
    ( is nonfalse (MEMBERS);
      -
    );
  [ ALT -> FREQ empty COMMITED SECONDPASS ];
  ?.

TEST is nonfalse (>MEMBERS):
  [ MEMBERS -> nil ];
  [ MEMBERS -> MEMBER MEMBERS1 ],
    is nonfalse (MEMBER), is nonfalse (MEMBERS1).

TEST is nonfalse (>MEMBER):
  where member is regexp (MEMBER, ID),
    convert id to name (ID, TEXT), strip quotes (TEXT),
      ( regexp can match empty string (TEXT);
        -
      );
  where member is terminal (MEMBER), -;
  where member is call (MEMBER, NONTERMINAL),
    ( is nonfalse (NONTERMINAL);
      -
    );
  where member is final group (MEMBER, ALTS, NONTERMINAL),
    ( is nonfalse (NONTERMINAL);
      -
    );
  +.

TEST is nonfalse (>ALTS):
  [ ALTS -> ALT ALTS1],
    ( is nonfalse (ALT);
      is nonfalse (ALTS1)
    ).

#
# Predicates
#
# A nonterminal is a predicate iff all of its alternatives are predicates.
# An alternative is a predicate iff it is empty, or all members are
# predicates. A member is predicate iff it produces empty.
#
NONPREDICATE         = PID.
CHANGED_NONPREDICATE = PID_LIST.

ACTION init predicate analysis:
  empty (CHANGED_NONPREDICATE).

TEST is predicate (>NONTERMINAL):
  get nonpredicate node (NONTERMINAL, PID), is false (PID).

FUNCTION get nonpredicate node (>NONTERMINAL, PID>):
  convert NONTERMINAL to INT, [ NONPREDICATE{INT} -> PID ].

ACTION set nonpredicate node (>NONTERMINAL, >PID):
  convert NONTERMINAL to INT, [ PID -> NONPREDICATE{INT} ].

ACTION mark changed nonpredicate (>PID):
  push (CHANGED_NONPREDICATE, PID).

ACTION analyse predicates:
  build nonpredicate graph,
    display time ("builded non-predicate graph"),
    get nonpredicates from changed list,
    calculate boolean property.

ACTION get nonpredicates from changed list:
  [ CHANGED_NONPREDICATE -> TRUE_LIST ], empty (CHANGED_NONPREDICATE).

#
# Predicateness is calculated by calculating the inverse property of
# nonpredicateness. Initially each nonterminal is assumed to be a
# nonpredicate, represented by a boolean node with falsity 1, and
# an incoming arrow for each alternative.
#
ACTION build nonpredicate graph:
  get number of syntax nonterminals (INT),
    build nonpredicate graph (INT).

ACTION build nonpredicate graph (>INT):
  [ INT -> 0 ];
  [ INT - 1 -> INT ],
    convert INT to NONTERMINAL,
    build nonpredicate graph for nonterminal (NONTERMINAL),
    build nonpredicate graph (INT).

ACTION build nonpredicate graph for nonterminal (>NONTERMINAL):
  is lexicon nonterminal (NONTERMINAL),
    get nonpredicate node (NONTERMINAL, PID),
    mark changed nonpredicate (PID);
  get definitions (NONTERMINAL, DEFS),
    get nonpredicate node (NONTERMINAL, PID),
    build nonpredicate graph (PID, DEFS).

ACTION build nonpredicate graph (>PID, >DEFS):
  [ DEFS -> nil];
  [ DEFS -> DEF DEFS1 ],
    build nonpredicate graph (PID, DEF),
    build nonpredicate graph (PID, DEFS1);
  ?.

ACTION build nonpredicate graph (>PID, >DEF):
  [ DEF -> FORMALS at POS with ALTS ],
    build nonpredicate graph (PID, ALTS).

ACTION build nonpredicate graph (>PID, >ALTS):
  [ ALTS -> nil ];
  [ ALTS -> ALT ALTS1 ],
    build nonpredicate graph (PID, ALT),
    build nonpredicate graph (PID, ALTS1);
  ?.

#
# An alternative is nonpredicate if one of its members is nonpredicate.
#
ACTION build nonpredicate graph (>PID, >ALT):
  [ ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS ],
    build nonpredicate graph (PID, MEMBERS);
  [ ALT -> FREQ empty COMMITED SECONDPASS ];
  ?.

ACTION build nonpredicate graph (>PID, >MEMBERS):
  [ MEMBERS -> nil ];
  [ MEMBERS -> MEMBER MEMBERS1 ],
    build nonpredicate graph (PID, MEMBER),
    build nonpredicate graph (PID, MEMBERS1);
  ?.

ACTION build nonpredicate graph (>PID, >MEMBER):
  where member is call (MEMBER, NONTERMINAL),
    get nonpredicate node (NONTERMINAL, PID1),
    add arrow (PID1, PID);
  where member is final group (MEMBER, ALTS, NONTERMINAL),
    get nonpredicate node (NONTERMINAL, PID1),
    add arrow (PID1, PID),
    build nonpredicate graph (PID1, ALTS);
  where member is option (MEMBER, ALTS, NONTERMINAL),
    get nonpredicate node (NONTERMINAL, PID1),
    add arrow (PID1, PID),
    build nonpredicate graph (PID1, ALTS);
  where member is terminal (MEMBER),
    mark changed nonpredicate (PID);
  +.

#
# First sets
#
# The first set of a nonterminal is the set of symbols with which a
# production may begin. It is required that nonfalseness has been
# calculated first.
#
FIRST         = PID.
CHANGED_FIRST = PID_LIST.

ACTION init first set analysis:
  empty (CHANGED_FIRST).

FIRST_SET = G_SET.

FUNCTION get first set (>NONTERMINAL, FIRST_SET>):
  get first set node (NONTERMINAL, PID),
    get value (PID, FIRST_SET).

FUNCTION get first set node (>NONTERMINAL, PID>):
  convert NONTERMINAL to INT, [ FIRST{INT} -> PID ].

ACTION set first set node (>NONTERMINAL, >PID):
  convert NONTERMINAL to INT, [ PID -> FIRST{INT} ].

ACTION mark changed first (>PID):
  push (CHANGED_FIRST, PID).

ACTION calculate first sets:
  build first set graph,
    get first sets from changed list,
    calculate set property.

ACTION get first sets from changed list:
  [ CHANGED_FIRST -> NEW_ELEMS_LIST ], empty (CHANGED_FIRST).

#
# First set graph
#
ACTION build first set graph:
  get number of syntax nonterminals (INT),
    build first set graph (INT),
    display time ("built first set graph").

ACTION build first set graph (>INT):
  [ INT -> 0 ];
  [ INT - 1 -> INT ],
    convert INT to NONTERMINAL,
    build first set graph for nonterminal (NONTERMINAL),
    build first set graph (INT).

ACTION build first set graph for nonterminal (>NONTERMINAL):
  get first set node (NONTERMINAL, PID), make empty (FIRST_SET),
    build first set graph (PID, NONTERMINAL, FIRST_SET),
      ( is empty (FIRST_SET);
        store newly found (PID, FIRST_SET),
          mark changed first (PID)
      ).

ACTION build first set graph (>PID, >NONTERMINAL, >FIRST_SET>):
  is lexicon nonterminal (NONTERMINAL),
    add lexicon NONTERMINAL to FIRST_SET;
  get definitions (NONTERMINAL, DEFS),
    build first set graph (PID, DEFS, FIRST_SET).

ACTION build first set graph (>PID, >DEFS, >FIRST_SET>):
  [ DEFS -> nil ];
  [ DEFS -> DEF DEFS1 ],
    build first set graph (PID, DEF, FIRST_SET),
    build first set graph (PID, DEFS1, FIRST_SET);
  ?.

ACTION build first set graph (>PID, >DEF, >FIRST_SET>):
  [ DEF -> FORMALS at POS with ALTS ],
    build first set graph (PID, ALTS, FIRST_SET).

ACTION build first set graph (>PID, >ALTS, >FIRST_SET>):
  [ ALTS -> nil ];
  [ ALTS -> ALT ALTS1 ],
    build first set graph (PID, ALT, FIRST_SET),
    build first set graph (PID, ALTS1, FIRST_SET);
  ?.

ACTION build first set graph (>PID, >ALT, >FIRST_SET>):
  [ ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS ],
    build first set graph (PID, MEMBERS, true, FIRST_SET);
  [ ALT -> FREQ empty COMMITED SECONDPASS];
  ?.

ACTION build first set graph (>PID, >MEMBERS, >BOOL, >FIRST_SET>):
  [ MEMBERS -> nil];
  [ MEMBERS -> MEMBER MEMBERS1 ],
    build first set graph (PID, MEMBER, BOOL, FIRST_SET),
      ( is nonfalse (MEMBER),
          build first set graph (PID, MEMBERS1, BOOL, FIRST_SET);
        build first set graph (PID, MEMBERS1, false, FIRST_SET)
      );
  ?.

ACTION build first set graph (>PID, >MEMBER, >BOOL, >FIRST_SET>):
  where member is call (MEMBER, NONTERMINAL),
    ( [ BOOL -> false ];
      optionally add NONTERMINAL to FIRST_SET,
        get first set node (NONTERMINAL, PID1),
        add arrow (PID1, PID)
    );
  where member is option (MEMBER, ALTS, NONTERMINAL),
    get first set node (NONTERMINAL, PID1),
    maybe add arrow (BOOL, PID1, PID),
    build first set graph (PID1, ALTS);
  where member is final group (MEMBER, ALTS, NONTERMINAL),
    get first set node (NONTERMINAL, PID1),
    maybe add arrow (BOOL, PID1, PID),
    build first set graph (PID1, ALTS);
  where member is terminal (MEMBER),
    ( [ BOOL -> false ];
      add terminal MEMBER to FIRST_SET
    );
  +.

ACTION build first set graph (>PID, >ALTS):
  make empty (FIRST_SET), build first set graph (PID, ALTS, FIRST_SET),
    ( is empty (FIRST_SET);
      store newly found (PID, FIRST_SET), mark changed first (PID)
    ).

FUNCTION optionally add >NONTERMINAL to >G_SET>:
  if full analysis option,
    ( is lexicon nonterminal (NONTERMINAL);
      add NONTERMINAL to G_SET
    );
  +.

FUNCTION add terminal >MEMBER to >G_SET>:
  [ MEMBER -> match regexp ID using EXPR1 targeting EXPR2 at POS ],
    add regexp match ID to G_SET;
  [ MEMBER -> skip regexp ID using EXPR1 targeting EXPR2 at POS ],
    add regexp skip ID to G_SET;
  [ MEMBER -> terminal ID using EXPR1 targeting EXPR2 at POS ],
    add terminal ID to G_SET;
  [ MEMBER -> other using EXPR1 targeting EXPR2 at POS ],
    add other to G_SET;
  ?.

#
# Calculation of director sets
#
#
# Assumes nonfalseness, first sets and follow sets are known.
# In order to save memory, director sets are only created when needed.
#
DIRSET :: unrestricted;                 # save memory when directors not needed
          FIRST_SET and FOLLOW_SET;     # nonfalse nonterminals
          FIRST_SET.                    # not nonfalse nonterminals

FUNCTION get director set (>NONTERMINAL, DIRSET>):
  get first set (NONTERMINAL, FIRST_SET),
    ( is nonfalse (NONTERMINAL),
        get follow set (NONTERMINAL, FOLLOW_SET),
        [ FIRST_SET and FOLLOW_SET -> DIRSET ];
      [ FIRST_SET -> DIRSET ]
    ).

FUNCTION get director set (>NONTERMINAL, >ALT, DIRSET>):
  get first set (NONTERMINAL, ALT, FIRST_SET),
    ( is nonfalse (ALT),
        get follow set (NONTERMINAL, FOLLOW_SET),
        [ FIRST_SET and FOLLOW_SET -> DIRSET ];
      [ FIRST_SET -> DIRSET ]
    ).

FUNCTION get unrestricted director set (DIRSET>):
  [ unrestricted -> DIRSET ].

TEST is unrestricted (>DIRSET):
  [ DIRSET -> unrestricted ].

FUNCTION convert >DIRSET to terminal G_SET>:
  [ DIRSET -> FIRST_SET and FOLLOW_SET ],
     delete nonterminals (FIRST_SET|G_SET1),
     delete nonterminals (FOLLOW_SET|G_SET2),
     add (G_SET1|G_SET, G_SET2);
  [ DIRSET -> FIRST_SET ],
    delete nonterminals (FIRST_SET|G_SET);
  ?. # unrestricted

FUNCTION get first set (>NONTERMINAL, >ALT, FIRST_SET>):
  make empty (FIRST_SET),
    (is lexicon nonterminal (NONTERMINAL),
       add lexicon NONTERMINAL to FIRST_SET;
     calculate first set (ALT, FIRST_SET)).

FUNCTION calculate first set (>ALT, >FIRST_SET>):
  [ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS],
    calculate first set (MEMBERS, FIRST_SET);
  [ALT -> FREQ empty COMMITED SECONDPASS];
  ?.

FUNCTION calculate first set (>MEMBERS, >FIRST_SET>):
  [MEMBERS -> nil];
  [MEMBERS -> MEMBER MEMBERS1],
    calculate first set (MEMBER, FIRST_SET),
      (is nonfalse (MEMBER),
         calculate first set (MEMBERS1, FIRST_SET);
       +);
  ?.

FUNCTION calculate first set (>MEMBER, >FIRST_SET>):
  where member is terminal (MEMBER),
    add terminal MEMBER to FIRST_SET;
  where member is call (MEMBER, NONTERMINAL),
    calculate first set (NONTERMINAL, FIRST_SET);
  where member is option (MEMBER, ALTS, NONTERMINAL),
    calculate first set (NONTERMINAL, FIRST_SET);
  where member is final group (MEMBER, ALTS, NONTERMINAL),
    calculate first set (NONTERMINAL, FIRST_SET);
  +.

# Note: we delete nonterminals from the first set, since we don't
# need them for the director set look-ahead.

FUNCTION calculate first set (>NONTERMINAL, >FIRST_SET>):
  is lexicon nonterminal (NONTERMINAL),
    add lexicon NONTERMINAL to FIRST_SET;
  get first set (NONTERMINAL, FIRST_SET1),
    delete nonterminals (FIRST_SET1),
    add (FIRST_SET, FIRST_SET1).

FUNCTION calculate first set (>ALTS, >FIRST_SET>):
  [ALTS -> nil];
  [ALTS -> ALT ALTS1],
    calculate first set (ALT, FIRST_SET),
    calculate first set (ALTS1, FIRST_SET);
  ?.

#
# Last sets
#
# The last set of a nonterminal is the set of symbols with which a
# production may end. It is required that nonfalseness has been
# calculated first.
# This property has been added for fun, it is not really needed.
#
LAST         = PID.
CHANGED_LAST = PID_LIST.

ACTION init last set analysis:
  empty (CHANGED_LAST).

LAST_SET = G_SET.

FUNCTION get last set (>NONTERMINAL, LAST_SET>):
  get last set node (NONTERMINAL, PID),
    get value (PID, LAST_SET).

FUNCTION get last set node (>NONTERMINAL, PID>):
  convert NONTERMINAL to INT,
    [LAST{INT} -> PID].

ACTION set last set node (>NONTERMINAL, >PID):
  convert NONTERMINAL to INT,
    [PID -> LAST{INT}].

ACTION mark changed last (>PID):
  push (CHANGED_LAST, PID).

ACTION calculate last sets:
  build last set graph,
    #log property nodes ("after building last set graph"),
    display time ("builded last set graph"),
    get last sets from changed list,
    calculate set property.

ACTION get last sets from changed list:
  [CHANGED_LAST -> NEW_ELEMS_LIST],
    empty (CHANGED_LAST).

ACTION build last set graph:
  get number of syntax nonterminals (INT),
    build last set graph (INT).

ACTION build last set graph (>INT):
  [INT -> 0];
  [INT - 1 -> INT],
    build last set graph for nonterminal (INT),
    build last set graph (INT).

ACTION build last set graph for nonterminal (>INT):
  convert INT to NONTERMINAL,
    get last set node (NONTERMINAL, PID),
    make empty (LAST_SET),
    build last set graph (PID, NONTERMINAL, LAST_SET),
      (is empty (LAST_SET);
       store newly found (PID, LAST_SET),
         mark changed last (PID)).

ACTION build last set graph (>PID, >NONTERMINAL, >LAST_SET>):
  is lexicon nonterminal (NONTERMINAL),
    add lexicon NONTERMINAL to LAST_SET;
  get definitions (NONTERMINAL, DEFS),
    build last set graph (PID, DEFS, LAST_SET).

ACTION build last set graph (>PID, >DEFS, >LAST_SET>):
  [DEFS -> nil];
  [DEFS -> DEF DEFS1],
    build last set graph (PID, DEF, LAST_SET),
    build last set graph (PID, DEFS1, LAST_SET);
  ?.

ACTION build last set graph (>PID, >DEF, >LAST_SET>):
  [DEF -> FORMALS at POS with ALTS],
    build last set graph (PID, ALTS, LAST_SET).

ACTION build last set graph (>PID, >ALTS, >LAST_SET>):
  [ALTS -> nil];
  [ALTS -> ALT ALTS1],
    build last set graph (PID, ALT, LAST_SET),
    build last set graph (PID, ALTS1, LAST_SET);
  ?.

ACTION build last set graph (>PID, >ALT, >LAST_SET>):
  [ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS],
    build last set graph (PID, MEMBERS, LAST_SET, BOOL);
  [ALT -> FREQ empty COMMITED SECONDPASS];
  ?.

# We scan the alternative from right to left, adding members to
# the last set, until a non-nonfalse member has been encountered.

ACTION build last set graph (>PID, >MEMBERS, >LAST_SET>, BOOL>):
  [MEMBERS -> nil],
    [true -> BOOL];
  [MEMBERS -> MEMBER MEMBERS1],
    build last set graph (PID, MEMBERS1, LAST_SET, BOOL),
    build last set graph (PID, MEMBER, LAST_SET, BOOL),
      (is nonfalse (MEMBER);
       [false -> BOOL]);
  ?.

ACTION build last set graph (>PID, >MEMBER, >LAST_SET>, >BOOL):
  where member is call (MEMBER, NONTERMINAL),
    ([BOOL -> false];
     optionally add NONTERMINAL to LAST_SET,
       get last set node (NONTERMINAL, PID1),
       add arrow (PID1, PID));
  where member is final group (MEMBER, ALTS, NONTERMINAL),
    get last set node (NONTERMINAL, PID1),
    build last set graph (PID1, ALTS),
    maybe add arrow (BOOL, PID1, PID);
  where member is option (MEMBER, ALTS, NONTERMINAL),
    get last set node (NONTERMINAL, PID1),
    build last set graph (PID1, ALTS),
    maybe add arrow (BOOL, PID1, PID);
  where member is terminal (MEMBER),
  where member is terminal (MEMBER),
    ([BOOL -> false];
     add terminal MEMBER to LAST_SET);
  +.

ACTION build last set graph (>PID, >ALTS):
  make empty (LAST_SET),
    build last set graph (PID, ALTS, LAST_SET),
      (is empty (LAST_SET);
       store newly found (PID, LAST_SET),
         mark changed last (PID)).


#
# Follow sets
#
# The follow set of a nonterminal is the set of symbols that may follow
# the nonterminal in a production.
# Required properties: nonfalseness and first sets.
#
FOLLOW         = PID.
CHANGED_FOLLOW = PID_LIST.

ACTION init follow set analysis:
  empty (CHANGED_FOLLOW).

FOLLOW_SET = G_SET.

FUNCTION get follow set (>NONTERMINAL, FOLLOW_SET>):
  get follow set node (NONTERMINAL, PID),
    get value (PID, FOLLOW_SET).

FUNCTION get follow set node (>NONTERMINAL, PID>):
  convert NONTERMINAL to INT, [ FOLLOW{INT} -> PID ].
  
ACTION set follow set node (>NONTERMINAL, >PID):
  convert NONTERMINAL to INT, [ PID -> FOLLOW{INT} ].

ACTION mark changed follow (>PID):
  push (CHANGED_FOLLOW, PID).

ACTION calculate follow sets:
  build follow set graph,
    display time ("builded follow set graph"),
    get follow sets from changed list,
    calculate set property.

ACTION get follow sets from changed list:
  [ CHANGED_FOLLOW -> NEW_ELEMS_LIST ], empty (CHANGED_FOLLOW).

ACTION build follow set graph:
  get number of syntax nonterminals (INT),
    build follow set graph (INT),
    add eos to follow set of root.

ACTION add eos to follow set of root:
  get eos terminal (ID),
    make empty (FOLLOW_SET),
    add terminal ID to FOLLOW_SET,
    get root nonterminal (NONTERMINAL),
    get follow set node (NONTERMINAL, PID),
    add newly found (PID, FOLLOW_SET).

ACTION build follow set graph (>INT):
  [ INT -> 0 ];
  [ INT - 1 -> INT ],
    build follow set graph for nonterminal (INT),
    build follow set graph (INT).

#
# The follow set of each nonterminal should be calculated, therefore the
# follow set node of each nonterminal should be marked as changed.
# Follow sets nodes are marked as changed here, otherwise they will be
# added to the changed-list for each call.
#
ACTION build follow set graph for nonterminal (>INT):
  convert INT to NONTERMINAL,
    get definitions (NONTERMINAL, DEFS),
    get follow set node (NONTERMINAL, PID),
    build follow set graph (PID, DEFS),
    mark changed follow (PID).

ACTION build follow set graph (>PID, >DEFS):
  [ DEFS -> nil ];
  [ DEFS -> DEF DEFS1 ],
    build follow set graph (PID, DEF),
    build follow set graph (PID, DEFS1);
  ?.

ACTION build follow set graph (>PID, >DEF):
  [ DEF -> FORMALS at POS with ALTS ],
    build follow set graph (PID, ALTS).

ACTION build follow set graph (>PID, >ALTS):
  [ ALTS -> nil ];
  [ ALTS -> ALT ALTS1 ],
    build follow set graph (PID, ALT),
    build follow set graph (PID, ALTS1);
  ?.

ACTION build follow set graph (>PID, >ALT):
  [ ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS ],
    build follow set graph (PID, MEMBERS, FIRST_SET, BOOL);
  [ ALT -> FREQ empty COMMITED SECONDPASS ];
  ?.

#
# We scan the alternative from right to left, and keep track of the
# nonfalseness of the suffix of the alternative, and the first set
# of the suffix of the alternative. Each time a nonterminal is encountered,
# the current first set is added to its follow set, and an arrow from PID
# is added iff the suffix of the alternative is nonfalse. If the nonterminal
# is nonfalse, then its first set is added to first set of the suffix.
# Otherwise, its first set replaces the first set of the suffix, and the
# suffix prefixed with the nonterminal is not nonfalse.
# When a terminal is encountered, the first set of the suffix is replaced
# with the terminal, and the suffix prefixed with the terminal is not nonfalse.
#
ACTION build follow set graph (>PID, >MEMBERS, FIRST_SET>, BOOL>):
  [ MEMBERS -> nil ], make empty (FIRST_SET), [ true -> BOOL ];
  [ MEMBERS -> MEMBER MEMBERS1 ],
    build follow set graph (PID, MEMBERS1, FIRST_SET, BOOL),
    build follow set graph (PID, MEMBER, FIRST_SET, BOOL);
  ?.

ACTION build follow set graph (>PID, >MEMBER, >FIRST_SET>, >BOOL>):
  where member is call (MEMBER, NONTERMINAL),
    build follow set graph (PID, NONTERMINAL, FIRST_SET, BOOL);
  where member is option (MEMBER, ALTS, NONTERMINAL),
    build follow set graph (NONTERMINAL, ALTS),
    build follow set graph (PID, NONTERMINAL, FIRST_SET, BOOL);
  where member is final group (MEMBER, ALTS, NONTERMINAL),
    build follow set graph (NONTERMINAL, ALTS),
    build follow set graph (PID, NONTERMINAL, FIRST_SET, BOOL);
  where member is terminal (MEMBER),
    make empty (FIRST_SET), add terminal MEMBER to FIRST_SET,
    [ false -> BOOL ];
  +.

ACTION build follow set graph (>PID, >ALTS, >FIRST_SET>):
  [ ALTS -> nil ];
  [ ALTS -> ALT ALTS ],
    build follow set graph (PID, ALT, FIRST_SET),
    build follow set graph (PID, ALTS, FIRST_SET);
  ?.

ACTION build follow set graph (>PID, >ALT, >FIRST_SET>):
  [ ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS ],
    build follow set graph (PID, MEMBERS, FIRST_SET, BOOL);
  [ ALT -> FREQ empty COMMITED SECONDPASS ];
  ?.

ACTION build follow set graph (>PID, >NONTERMINAL, >FIRST_SET>, >BOOL>):
  get follow set node (NONTERMINAL, PID1),
    add newly found (PID1, FIRST_SET),
    maybe add arrow (BOOL, PID, PID1),
    get first set (NONTERMINAL, G_SET2),
    optionally add NONTERMINAL to G_SET2,
      ( is nonfalse (NONTERMINAL), add (FIRST_SET, G_SET2);
        [ G_SET2 -> FIRST_SET ], [ false -> BOOL ]
      ).

ACTION maybe add arrow (>BOOL, >PID1, >PID2):
  [ BOOL -> false ];
  add arrow (PID1, PID2).

ACTION build follow set graph (>NONTERMINAL, >ALTS):
  get follow set node (NONTERMINAL, PID),
    build follow set graph (PID, ALTS).


#
# Rule type functions and check 
#
RULE_TYPE :: cond;
             option;
	     rule;
             unknown.

FUNCTION get computed type (>NONTERMINAL, RULE_TYPE>):
  get first set (NONTERMINAL, FIRST_SET),
    is nonproductive (FIRST_SET),
       [cond -> RULE_TYPE];
    is nonfalse (NONTERMINAL),
      [option -> RULE_TYPE];
    [rule -> RULE_TYPE].

FUNCTION get computed type (>NONTERMINAL, TEXT>):
  get computed type (NONTERMINAL, RULE_TYPE),
    convert RULE_TYPE to TEXT.

FUNCTION convert >RULE_TYPE to TEXT>:
  [RULE_TYPE -> cond],
    ["COND" -> TEXT];
  [RULE_TYPE -> rule],
    ["RULE" -> TEXT];
  [RULE_TYPE -> option],
    ["OPTION" -> TEXT];
  [RULE_TYPE -> unknown],
    ["unknown" -> TEXT];
  ?.

TEST computed rule type is cond (>NONTERMINAL):
  get computed type (NONTERMINAL, RULE_TYPE),
    [RULE_TYPE -> cond].

TEST rule types are equal (>RULE_TYPE1, >RULE_TYPE2):
  [RULE_TYPE1 = RULE_TYPE2].


### Specified rule type storage:

RULE_TYPES = RULE_TYPE.

ACTION notify properties of newly created >NONTERMINAL:
  convert NONTERMINAL to INT,
    [unknown -> RULE_TYPES{INT}].

ACTION store specified rule type (>NONTERMINAL, >RULE_TYPE):
  convert NONTERMINAL to INT,
    make known (RULE_TYPE),
    [RULE_TYPES{INT} -> RULE_TYPE1],
    make known (RULE_TYPE1),
    check for conflicting rule types (NONTERMINAL, RULE_TYPE, RULE_TYPE1),
    [RULE_TYPE -> RULE_TYPES{INT}].

FUNCTION make known (>RULE_TYPE>):
  [RULE_TYPE -> unknown],
    [rule -> RULE_TYPE];
  +.

# stn: was ACTION because of error.
FUNCTION check for conflicting rule types (>NONTERMINAL, >RULE_TYPE1, >RULE_TYPE2):
  is anonymous nonterminal (NONTERMINAL);
  rule types are equal (RULE_TYPE1, RULE_TYPE2);
#  convert NONTERMINAL to TEXT1,
#    convert RULE_TYPE1 to TEXT2,
#    convert RULE_TYPE2 to TEXT3,
# FIXME: lexicon nonterminals should be checked too...
#    error ("nonterminal type mismatch:\n " + TEXT1 + " is defined as " + TEXT2 + " and " + TEXT3).
    +.

FUNCTION get specified rule type for nonterminal (>NONTERMINAL, RULE_TYPE>):
  convert NONTERMINAL to INT,
    [RULE_TYPES{INT} -> RULE_TYPE].


######################################
#                                    #
#  Check for conflicting rule types  #
#                                    #
######################################

ACTION check for conflicting rule types:
  get number of syntax nonterminals (INT),
    check for conflicting rule types (INT).

ACTION check for conflicting rule types (>INT):
  [INT -> 0];
  [INT - 1 -> INT],
    convert INT to NONTERMINAL,
    check for conflicting rule types (INT),
    check for conflicting rule types (NONTERMINAL).

# stn: was ACTION because of calling of ACTION check on type equality below
FUNCTION check for conflicting rule types (>NONTERMINAL):
  nonterminal is undefined and unused (NONTERMINAL);
  is start nonterminal (NONTERMINAL);
# FIXME: lexicon nonterminals should be checked too...
  is lexicon nonterminal (NONTERMINAL);
  get computed type (NONTERMINAL, RULE_TYPE),
    get specified rule type for nonterminal (NONTERMINAL, RULE_TYPE1),
    check on type equality (RULE_TYPE, RULE_TYPE1, NONTERMINAL).

# stn: was ACTION because of error
FUNCTION check on type equality (>RULE_TYPE, >RULE_TYPE1, >NONTERMINAL):
  is anonymous nonterminal (NONTERMINAL);
  rule types are equal (RULE_TYPE, RULE_TYPE1);
#  convert NONTERMINAL to TEXT1,
#    convert RULE_TYPE1 to TEXT2,
#    convert RULE_TYPE to TEXT3,
#    warning ("nonterminal type mismatch:\n " + TEXT1 + " is defined as " + TEXT2 + "\n but this should be " + TEXT3).
    +.


#######################
#                     #
#  Check for commits  #
#                     #
#######################

ACTION check for commit in rules and options:
  get number of syntax nonterminals (INT),
    check for commit in rules and options (INT).

ACTION check for commit in rules and options (>INT):
  [INT -> 0];
  [INT - 1 -> INT],
    check for commit in rules and options (INT),
    check for commit in rule or option (INT).

ACTION check for commit in rule or option (>INT):
  convert INT to NONTERMINAL,
    (is start nonterminal (NONTERMINAL);
     is lexicon nonterminal (NONTERMINAL);
     computed rule type is cond (NONTERMINAL);
     get definitions (NONTERMINAL, DEFS),
       check for commit in defs (DEFS, NONTERMINAL)).

ACTION check for commit in defs (>DEFS, >NONTERMINAL):
  [DEFS -> nil];
  [DEFS -> DEF DEFS],
    check for commit in def (DEF, NONTERMINAL),
    check for commit in defs (DEFS, NONTERMINAL);
  ?.

ACTION check for commit in def (>DEF, >NONTERMINAL):
  [DEF -> FORMALS at POS with ALTS],
    check for commit in alts (ALTS, NONTERMINAL).

ACTION check for commit in alts (>ALTS, >NONTERMINAL):
  [ALTS -> nil];
  [ALTS -> ALT ALTS],
    check for commit in alt (ALT, NONTERMINAL),
    check for commit in alts (ALTS, NONTERMINAL);
  ?.

ACTION check for commit in alt (>ALT, >NONTERMINAL):
  is committed (ALT),
    get computed type (NONTERMINAL, TEXT),
    convert NONTERMINAL to TEXT1,
    very verbose warning ("commit is used in " + TEXT + " \"" + TEXT1 + "\".");
  +.


#
# Invisible nonterminals
#
INV_NONT :: ID with ARITY.
INV_LIST :: INV_NONT INV_LIST;
            nil.
INVISIBLES = INV_LIST.

ACTION init invisibles:
  [nil -> INVISIBLES].

ACTION mark nonterminal invisible (>ID, >INT):
  convert INT to ARITY,
    [ID with ARITY -> INV_NONT],
    [INV_NONT INVISIBLES -> INVISIBLES].

TEST is marked invisible (>NONTERMINAL):
  convert NONTERMINAL to ID and ARITY,
    is marked invisible (ID, ARITY, INVISIBLES).

TEST is marked invisible (>ID, >ARITY, >INV_LIST):
  [INV_LIST -> INV_NONT INV_LIST],
    ([INV_NONT -> ID1 with ARITY1],
       [ID = ID1],
       [ARITY = ARITY1];
     is marked invisible (ID, ARITY, INV_LIST));
  [INV_LIST -> nil],
    -;
  ?.

TEST is invisible in tree (>NONTERMINAL):
  is marked invisible (NONTERMINAL);
  is anonymous nonterminal (NONTERMINAL);
  is start nonterminal (NONTERMINAL).

