#
#  File: rule.k3
#  Module with all syntax rule handling routines.
#
#  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: rule.k3,v 1.79 2006/08/09 11:13:09 marcs Exp $"
#
MODULE rule = ROOT_NONT
              RULES{}.

DEFINES	DEFS,
	DEF,
        FUNCTION count sons (>DEFS, INT>),

	LOCALS,
        FUNCTION append >EXPR to >LOCALS>,

	EXPRS,
        FUNCTION append >EXPR to >EXPRS>,
        FUNCTION append >EXPRS1 to >EXPRS2>,
        FUNCTION count (>EXPRS, INT>),
	EXPR,
        TEST is integer (>EXPR),
        TEST is set (>EXPR),
        TEST is text (>EXPR),
        TEST is user variable (>EXPR),
        TEST is user affix terminal (>EXPR),
        TEST is system affix (>EXPR),

	ACTUALS,
	FORMALS,
        FUNCTION convert >FORMALS pack to user TEXT>,
        FUNCTION convert >LOCALS pack to user TEXT>,
        TEST convert >EXPR to local INT> using >LOCALS,
        TEST convert >EXPR to formal INT> using >FORMALS,
        FUNCTION convert >EXPR to local INT> using >FORMALS and >LOCALS,

        NR_FORMALS,
        FUNCTION new NR_FORMALS>,
        FUNCTION convert >NR_FORMALS to INT>,
        FUNCTION convert >NR_FORMALS to TEXT>,
        FUNCTION incr >NR_FORMALS>,
        FUNCTION count NR_FORMALS> in >DEFS,
        FUNCTION count NR_FORMALS> in >FORMALS,

        NR_LOCALS,
        FUNCTION new NR_LOCALS>,
        FUNCTION convert >NR_LOCALS to INT>,
        FUNCTION convert >NR_LOCALS to TEXT>,
        FUNCTION incr >NR_LOCALS>,
        FUNCTION maximize >NR_LOCALS1> with >NR_LOCALS2,

	ALTS, ALT,
        FUNCTION convert >ALTS to TEXT>,
        FUNCTION convert >ALT to TEXT>,

        RULE,
        FUNCTION get syntax rule (>NONTERMINAL, RULE>),

        SPECS,

	MEMBERS,
        FUNCTION append >MEMBERS1 to >MEMBERS>,
        FUNCTION append >MEMBER1 to >MEMBERS>,
        FUNCTION convert >MEMBERS to TEXT>,

	MEMBER,
        ACTION generate internal final group (>ALTS, MEMBER>),
        TEST where member is son (>MEMBER),
        TEST where member is penalty (>MEMBER),
        TEST where member is call (>MEMBER, NONTERMINAL>),
        TEST where member is option (>MEMBER, ALTS>),
        TEST where member is option (>MEMBER, ALTS>, POS>),
        TEST where member is option (>MEMBER, ALTS>, NONTERMINAL>),
        TEST where member is option (>MEMBER, NONTERMINAL>, EXPRS>),
        TEST where member is final group (>MEMBER),
        TEST where member is final group (>MEMBER, ALTS>),
        TEST where member is final group (>MEMBER, ALTS>, NONTERMINAL>),
        TEST where member is terminal (>MEMBER),
	TEST where member is regexp (>MEMBER, ID>),

	GUARDS,
	GUARD,
	FREQ,
	COMMITED,
	SPEC,
        FUNCTION get param id (>SPEC, >INT, ID>),
	ASPEC,
	FSPEC,
	OIDS,
        OPT_SPEC,
        FUNCTION empty (OPT_SPEC>),
        FUNCTION convert >OPT_SPEC to NONTERMINAL>,
        FUNCTION convert >OPT_SPEC to FORMALS>,

	ACTION collect specifications,
	ACTION analyse syntax rules,
        ACTION transform free word order members to final groups,

	ACTION init syntax rule (>NONTERMINAL),
	ACTION enter lexicon rule (>NONTERMINAL, >RULE_TYPE, >FORMALS, >POS),
	ACTION enter syntax rule (>NONTERMINAL, >RULE_TYPE, >FORMALS, >ALTS,
                                  >POS),
	ACTION enter specification (>NONTERMINAL, >RULE_TYPE, >FORMALS, >POS),
	ACTION enter actual specification (>NONTERMINAL, >ACTUALS),
	ACTION enter root nonterminal (>NONTERMINAL),

	FUNCTION get root nonterminal (NONTERMINAL>),
	FUNCTION get actual specification (>NONTERMINAL, ASPEC>),
	FUNCTION get formal specification (>NONTERMINAL, FSPEC>),
	FUNCTION get definitions (>NONTERMINAL, DEFS>),
	ACTION store definitions (>NONTERMINAL, >DEFS),
	TEST is committed (>ALT),

	TEST is equivalent (>MEMBER1, >MEMBER2),
	FUNCTION unspecified (FREQ>),

        TEST nonterminal is not used (>NONTERMINAL),
        TEST nonterminal is defined (>NONTERMINAL),
        TEST nonterminal is undefined and unused (>NONTERMINAL),

        TEST where member is free word order alternative (>MEMBERS, MEMBERS1>),
        FUNCTION make free word order member (>MEMBERS, MEMBERS1>),

	ACTION write lexicon rules to lif,

        FUNCTION convert >EXPR to system TEXT>,
        FUNCTION convert >EXPR to user TEXT>,
        FUNCTION convert >EXPR to affix ID>,

        ACTION dump ast,
	ACTION dump (>MEMBERS),
        ACTION dump (>EXPRS),
	ACTION dump syntax nonterminal (>NONTERMINAL).

USES globals, io, id, affix, set, properties, optimize, transduction, textstorage.

#
# Module initialization
#
PRELUDE init rule handling:
  [ empty -> ROOT_NONT ].

#
# Root nonterminal
#
# The root nonterminal S specified in the grammar is called by a special
# start nonterminal S', for which a rule S' -> S, $eos$, pass2 is created.
#
ROOT_NONT :: NONTERMINAL; empty.

ACTION enter root nonterminal (>NONTERMINAL):
  [ NONTERMINAL -> ROOT_NONT ],
    convert NONTERMINAL to ID and ARITY,
    convert ARITY to INT,
      ( [ INT -> 0 ], enter actual specification (NONTERMINAL, nil);
        convert NONTERMINAL to TEXT,
          error ("grammar root \"" + TEXT + "\" should have arity 0")
      ).

FUNCTION get root nonterminal (NONTERMINAL>):
  [ ROOT_NONT -> NONTERMINAL ];
  ?.

#
# Specifications
#
# There are three types of specifications:
# 1. user specifications (specifications, defines-list);
# 2. formal specifications (lhs of rule definitions);
# 3. actual specifications (calls in rhs).
# Specifications are used for checking parameter types.
#
SPECS :: USPEC ASPEC FSPEC.

USPEC :: SPEC.				# specifications
ASPEC :: SPEC.				# actual paramaters (calls)
FSPEC :: SPEC.				# formal parameters (rule definitions)

#
# A specification is a list of lists, containing all affix ids occuring
# at parameter positions. After parsing, all set ids occuring at the same
# parameter positions, are collected into one domain.
#
SPEC :: empty;				# initially
        OIDS SPEC;			# after entering a specification
        nil.

OIDS :: ID OIDS;			# list of all ids at some position
        nil;
        collected ID.			# ids after collecting specifications

FUNCTION get param id (>SPEC, >INT, ID>):
   [ INT -> 0 ],
      abort ("get param id failed on zero argument"),
      [ 0 -> ID ];			# unreachable code to keep CDLC from complaining
   [ INT -> 1],
      ( [ SPEC -> OIDS SPEC ], [ OIDS -> collected ID ];
        ?
      );
   [ SPEC -> OIDS SPEC ],
      get param id (SPEC, INT - 1, ID);
   ?.
#
# When a nonterminal has been specified before, it is checked whether
# the new specification is equal to the previous specification.
# TODO (low priority): enforce type equivalence instead of equality.
#
ACTION enter specification (>NONTERMINAL, >RULE_TYPE, >FORMALS, >POS):
  get specifications (NONTERMINAL, SPECS),
    convert FORMALS to SPEC1,
    [SPECS -> SPEC2 ASPEC FSPEC],
      ([SPEC2 -> empty],
         [SPEC1 ASPEC FSPEC -> SPECS],
         store specifications (NONTERMINAL, SPECS),
         store specified rule type (NONTERMINAL, RULE_TYPE);
       [SPEC1 = SPEC2],
         store specified rule type (NONTERMINAL, RULE_TYPE);
       report invalid specification (NONTERMINAL, POS)).

ACTION report invalid specification (>NONTERMINAL, >POS):
  convert NONTERMINAL to TEXT,
    (is lexicon nonterminal (NONTERMINAL),
       interface error ("specification of nonterminal \"" + TEXT +
                        "\" not conform lexicon specification", POS);
     error ("specification of nonterminal \"" + TEXT +
            "\" not conform previous specification", POS)).

ACTION enter actual specification (>NONTERMINAL, >ACTUALS):
  get specifications (NONTERMINAL, SPECS),
    enter actual specification (ACTUALS, SPECS),
    store specifications (NONTERMINAL, SPECS).

FUNCTION enter actual specification (>ACTUALS, >SPECS>):
  convert ACTUALS to SPEC1, [ SPECS -> USPEC SPEC2 FSPEC ],
    add SPEC1 to SPEC2, [ USPEC SPEC2 FSPEC -> SPECS ].

FUNCTION enter formal specification (>FORMALS, >SPECS>):
  convert FORMALS to SPEC1, [ SPECS -> USPEC ASPEC SPEC2 ],
    add SPEC1 to SPEC2, [ USPEC ASPEC SPEC2 -> SPECS ].

FUNCTION enter lexicon specification (>FORMALS, >SPECS>):
  convert FORMALS to SPEC,
    [ SPECS -> USPEC ASPEC FSPEC ],
    [ SPEC ASPEC SPEC -> SPECS ].	# replace user and formal specs

FUNCTION convert >FORMALS to SPEC>:
  [ FORMALS -> EXPRS ], convert EXPRS to SPEC.

FUNCTION convert >ACTUALS to SPEC>:
  [ ACTUALS -> EXPRS ], convert EXPRS to SPEC.

FUNCTION convert >EXPRS to SPEC>:
  [ EXPRS -> nil ], [ nil -> SPEC ];
  [ EXPRS -> EXPR EXPRS1 ],
    convert EXPR to OIDS,
    convert EXPRS1 to SPEC1,
    [ OIDS SPEC1 -> SPEC ];
  ?.

FUNCTION convert >EXPR to OIDS>:
  [ EXPR -> erroneous ], [ nil -> OIDS ];
  convert EXPR to affix ID, [ ID nil -> OIDS ].

FUNCTION add >SPEC1 to >SPEC2>:
  [ SPEC1 -> empty ];
  [ SPEC2 -> empty ], [ SPEC1 -> SPEC2 ];
  add nonempty SPEC1 to SPEC2.

FUNCTION add nonempty >SPEC1 to >SPEC2>:
  [ SPEC1 -> nil ],
    ( [ SPEC2 -> nil ];
      ?
    );
  [ SPEC1 -> OIDS1 SPEC3 ],
    ( [ SPEC2 -> OIDS2 SPEC4 ],
        add OIDS1 to OIDS2,
        add nonempty SPEC3 to SPEC4,
        [ OIDS2 SPEC4 -> SPEC2 ];
      ?
    );
  ?.

#
# We keep OIDS sorted for efficient adding without introducing duplicates.
#
FUNCTION add >OIDS1 to >OIDS2>:
  [ OIDS1 -> nil ];
  [ OIDS2 -> nil ], [ OIDS1 -> OIDS2 ];
  [ OIDS1 -> ID1 OIDS3 ], [ OIDS2 -> ID2 OIDS4 ],
    ( [ ID1 = ID2 ],   add OIDS3 to OIDS4, [ ID2 OIDS4 -> OIDS2 ];
      less (ID1, ID2), add OIDS3 to OIDS2, [ ID1 OIDS2 -> OIDS2 ];
      add OIDS1 to OIDS4, [ ID2 OIDS4 -> OIDS2 ]
    );
  ?.

#
# Collecting specifications
#
# After parsing, all affix ids occuring at each position are collected,
# and an anonymous affix rule is created for these affixes. This ensures
# that all affixes occuring at the same position are joined into the
# same domain.
#
ACTION collect specifications:
  get number of syntax nonterminals (INT),
    collect specifications (INT).

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

ACTION collect specifications of nonterminal (>INT):
  convert INT to NONTERMINAL,
    get specifications (NONTERMINAL, SPECS),
    collect specifications (SPECS),
    store specifications (NONTERMINAL, SPECS).

ACTION collect specifications (>SPECS>):
  [ SPECS -> SPEC1 SPEC2 SPEC3 ],
    add SPEC1 to (empty|SPEC),
    add SPEC2 to SPEC,
    add SPEC3 to SPEC,
    collect specifications (SPEC),
    collect specifications (SPEC1),
    collect specifications (SPEC2),
    collect specifications (SPEC3),
    [ SPEC1 SPEC2 SPEC3 -> SPECS ].

ACTION collect specifications (>SPEC>):
  [ SPEC -> empty ];
  collect nonempty specifications (SPEC).

ACTION collect nonempty specifications (>SPEC>):
  [ SPEC -> nil ];
  [ SPEC -> OIDS SPEC1 ],
    collect specifications (OIDS),
    collect specifications (SPEC1),
    [ OIDS SPEC1 -> SPEC ];
  ?.

ACTION collect specifications (>OIDS>):
  convert OIDS to MALTS,
    create anonymous affix rule (MALTS, ID),
    [ collected ID -> OIDS ].

FUNCTION convert >OIDS to MALTS>:
  [ OIDS -> nil ], [ nil -> MALTS ];
  [ OIDS -> ID OIDS1 ],
    convert OIDS1 to MALTS,
    add unique (MALTS, ID);
  ?.

#
# Syntax rules
#
# For each nonterminal, we store its rule in an array, indexed by its number.
# A rule contains:
# - specifications (parameter types);
# - definitions, (lhs/rhs pairs)
# - graph (abstract code generated for the rule);
#
RULES :: RULE.
RULE :: DEFS SPECS.

FUNCTION get definitions (>NONTERMINAL, DEFS>):
  get syntax rule (NONTERMINAL, RULE),
    [ RULE -> DEFS SPECS ].

ACTION store definitions (>NONTERMINAL, >DEFS):
  get syntax rule (NONTERMINAL, RULE),
    [ RULE -> DEFS1 SPECS ],
    [ DEFS SPECS -> RULE ],
    store syntax rule (NONTERMINAL, RULE).

#
# Definitions
#
# A rule may have multiple definitions, one for each left hand side.
# After generating the graph, definitions are deleted for reclaiming memory.
#
DEFS :: DEF DEFS;
        nil.

FUNCTION postpend (>DEFS>, >DEF):
  [ DEFS -> nil ], [ DEF nil -> DEFS ];
  [ DEFS -> DEF1 DEFS1 ],
    postpend (DEFS1, DEF),
    [ DEF1 DEFS1 -> DEFS ];
  ?.

DEF :: FORMALS at POS with ALTS.

FUNCTION count sons (>DEFS, INT>):
  [ DEFS -> nil ], [ 0 -> INT ];
  [ DEFS -> DEF DEFS ],
    count sons (DEF, INT),
    count sons (DEFS, INT1),
    maximize INT with INT1;
  ?.

FUNCTION count sons (>DEF, INT>):
  [ DEF -> FORMALS at POS with ALTS ], count sons (ALTS, INT).

#
# Alternatives
#
ALTS :: ALT ALTS;
        nil.

FUNCTION append >ALTS1 to >ALTS2>:
  [ ALTS2 -> nil ], [ ALTS1 -> ALTS2 ];
  [ ALTS2 -> ALT2 ALTS2 ],
    append ALTS1 to ALTS2,
    [ ALT2 ALTS2 -> ALTS2 ];
  ?.

FUNCTION count sons (>ALTS, INT>):
  [ ALTS -> nil ], [ 0 -> INT ];
  [ ALTS -> ALT ALTS ],
    count sons (ALT, INT),
    count sons (ALTS, INT1),
    maximize INT with INT1;
  ?.

#
# There are two types of alternatives:
# 1. alternatives consisting of members;
# 2. empty alternatives.
# Since the difference between the two is only in the occurence of locals,
# it should disappear
#
ALT :: FREQ MEMBERS LOCALS COMMITED SECONDPASS;
       FREQ empty COMMITED SECONDPASS.

COMMITED :: BOOL.

FUNCTION count alts and sum freq (>ALTS, INT>, FREQ>):
  [ ALTS -> nil ], [ 0 -> INT ], [ 0 -> FREQ ];
  [ ALTS -> ALT ALTS ],
    count alts and sum freq (ALTS, INT, FREQ), get FREQ1 from ALT,
    [ INT + 1 -> INT ], [ FREQ + FREQ1 -> FREQ ];
  ?.

FUNCTION get FREQ> from >ALT:
  [ ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS ];
  [ ALT -> FREQ empty COMMITED SECONDPASS ];
  ?.

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

FUNCTION count sons (>ALT, INT>):
  [ ALT -> FREQ empty COMMITED SECONDPASS ], [ 0 -> INT ];
  [ ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS ],
    count sons (MEMBERS, INT);
  ?.

#
# Locals
#
LOCALS :: EXPRS.

FUNCTION append >EXPR to >LOCALS>:
  [ LOCALS -> EXPRS ],
    append EXPR to EXPRS,
    [ EXPRS -> LOCALS ].

#
# Members
#
MEMBERS :: MEMBER MEMBERS;
           nil.

FUNCTION append >MEMBER1 to >MEMBERS>:
  [ MEMBERS -> nil ], [ MEMBER1 nil -> MEMBERS ];
  [ MEMBERS -> MEMBER MEMBERS ],
    append MEMBER1 to MEMBERS,
    [ MEMBER MEMBERS -> MEMBERS ];
  ?.

FUNCTION append >MEMBERS1 to >MEMBERS>:
  [ MEMBERS -> nil ], [ MEMBERS1 -> MEMBERS ];
  [ MEMBERS -> MEMBER MEMBERS ],
    append MEMBERS1 to MEMBERS,
    [ MEMBER MEMBERS -> MEMBERS ];
  ?.

FUNCTION count sons (>MEMBERS, INT>):
  [ MEMBERS -> nil ], [ 0 -> INT ];
  [ MEMBERS -> MEMBER MEMBERS ], count sons (MEMBERS, INT),
    ( where member is son (MEMBER), [ INT + 1 -> INT ];
      where member is final group (MEMBER),
        count sons in final group (MEMBER, INT);
      +
    );
  ?.

#
# A Member
#
MEMBER :: guard GUARDS;
          call ID def NONTERMINAL with ACTUALS at POS;
          option ALTS with OPT_SPEC at POS;
          group ALTS at POS;
          free word order MEMBERS;
          final group ALTS at POS with NONTERMINAL; # NONTERMINAL for property calculation
          penalty INT;
          terminal ID using EXPR targeting EXPR at POS;
          skip regexp ID using EXPR targeting EXPR at POS;
          match regexp ID using EXPR targeting EXPR at POS;
	  other using EXPR targeting EXPR at POS;
	  input pos using EXPR targeting EXPR at POS;
          position INT;
	  erroneous.

TEST where member is penalty (>MEMBER):
  [ MEMBER -> penalty INT ].

TEST where member is call (>MEMBER, NONTERMINAL>):
  [ MEMBER -> call ID def NONTERMINAL with ACTUALS at POS ].

TEST where member is option (>MEMBER, ALTS>):
  [ MEMBER -> option ALTS with OPT_SPEC at POS ].

TEST where member is option (>MEMBER, ALTS>, POS>):
  [ MEMBER -> option ALTS with OPT_SPEC at POS ].

TEST where member is option (>MEMBER, ALTS>, NONTERMINAL>):
  [ MEMBER -> option ALTS with OPT_SPEC at POS ],
    convert OPT_SPEC to NONTERMINAL.

TEST where member is option (>MEMBER, NONTERMINAL>, EXPRS>):
  [ MEMBER -> option ALTS with OPT_SPEC at POS ],
    convert OPT_SPEC to NONTERMINAL,
    convert OPT_SPEC to FORMALS,
    [ FORMALS -> EXPRS ].

TEST where member is final group (>MEMBER):
  [ MEMBER -> final group ALTS at POS with NONTERMINAL ].

TEST where member is final group (>MEMBER, ALTS>):
  [ MEMBER -> final group ALTS at POS with NONTERMINAL ].

TEST where member is final group (>MEMBER, ALTS>, NONTERMINAL>):
  [ MEMBER -> final group ALTS at POS with NONTERMINAL ].

TEST where member is terminal (>MEMBER):
  [ MEMBER -> terminal ID using EXPR1 targeting EXPR2 at POS ];
  [ MEMBER -> skip regexp ID using EXPR1 targeting EXPR2 at POS ];
  [ MEMBER -> match regexp ID using EXPR1 targeting EXPR2 at POS ];
  [ MEMBER -> other using EXPR1 targeting EXPR2 at POS ].

TEST where member is regexp (>MEMBER, ID>):
  [ MEMBER -> match regexp ID using EXPR1 targeting EXPR2 at POS ];
  [ MEMBER -> skip regexp ID using EXPR1 targeting EXPR2 at POS ].

TEST where member is free word order alternative (>MEMBERS, MEMBERS1>):
  [ MEMBERS -> MEMBER nil ], [ MEMBER -> free word order MEMBERS1 ].

FUNCTION make free word order member (>MEMBERS, MEMBERS1>):
  [ free word order MEMBERS -> MEMBER ], [ MEMBER nil -> MEMBERS1 ].

# MS: Hier willen we misschien een POS mee kunnen geven voor foutboodschappen
ACTION generate internal final group (>ALTS, MEMBER>):
  create dummy nonterminal (NONTERMINAL),
    [ final group ALTS at internal with NONTERMINAL -> MEMBER ].

TEST where member is son (>MEMBER):
  [ MEMBER -> call ID def NONTERMINAL with ACTUALS at POS ];
  [ MEMBER -> option ALTS with OPT_SPEC at POS ].

FUNCTION count sons in final group (>MEMBER, INT>):
  where member is final group (MEMBER, ALTS), count sons (ALTS, INT);
  ?.

#
# Guards:
#
GUARDS :: GUARD GUARDS;
          nil.

GUARD :: restrict EXPR to EXPR at POS;
         erroneous.

#
# Formal parameters
#
FORMALS :: EXPRS.

FUNCTION convert >FORMALS to system TEXT>:
  [ FORMALS -> EXPRS ], convert EXPRS to system TEXT.

NR_FORMALS :: INT formals.

FUNCTION new NR_FORMALS>:
  [ 0 formals -> NR_FORMALS ].

FUNCTION incr >NR_FORMALS>:
  [ NR_FORMALS -> INT formals ], [ INT + 1 -> INT ], [ INT formals -> NR_FORMALS ].

FUNCTION convert >NR_FORMALS to INT>:
  [ NR_FORMALS -> INT formals ].

FUNCTION convert >NR_FORMALS to TEXT>:
  convert NR_FORMALS to INT, convert INT to TEXT.

#
# MS Waarom geen synoniem definitie gebruikt?
#
NR_LOCALS :: INT locals.

FUNCTION new NR_LOCALS>:
  [ 0 locals -> NR_LOCALS ].

FUNCTION incr >NR_LOCALS>:
  [ NR_LOCALS -> INT locals ], [ INT + 1 -> INT ], [ INT locals -> NR_LOCALS ].

FUNCTION convert >NR_LOCALS to INT>:
  [ NR_LOCALS -> INT locals ].

FUNCTION convert >NR_LOCALS to TEXT>:
  convert NR_LOCALS to INT, convert INT to TEXT.

FUNCTION maximize >NR_LOCALS1> with >NR_LOCALS2:
  [ NR_LOCALS1 -> INT1 locals ], [ NR_LOCALS2 -> INT2 locals ],
    maximize INT1 with INT2,
    [ INT1 locals -> NR_LOCALS1 ].

FUNCTION maximize >INT1> with >INT2:
  greater (INT2, INT1), [ INT2 -> INT1 ];
  +.

#
# Actual parameters
#
ACTUALS :: EXPRS.

FUNCTION convert >ACTUALS to system TEXT>:
  [ ACTUALS -> EXPRS ], convert EXPRS to system TEXT.

#
# Expressions
#
EXPRS :: EXPR EXPRS;
         nil.

FUNCTION append >EXPR1 to >EXPRS>:
  [ EXPRS -> nil ], [ EXPR1 nil -> EXPRS ];
  [ EXPRS -> EXPR EXPRS ],
    append EXPR1 to EXPRS,
    [ EXPR EXPRS -> EXPRS ];
  ?.

FUNCTION append >EXPRS1 to >EXPRS2>:
  [ EXPRS2 -> nil ], [ EXPRS1 -> EXPRS2 ];
  [ EXPRS2 -> EXPR2 EXPRS2 ],
    append EXPRS1 to EXPRS2,
    [ EXPR2 EXPRS2 -> EXPRS2 ];
  ?.

EXPR :: affix ID;			# any affix (e.g. numb, FIRST, TEXT)
        variable ID instance INT;	# variable with index (e.g. A1, TEXT1)
        set ID;				# affix set (e.g. FIRST|THIRD)
        text const ID;			# text denotation (e.g. "string")
	integer const INT;		# integer denotation (e.g. 12)
        system affix ID;		# Anonymous frame variable for $MATCH/$SKIP/$POS/terminals
	absent;				# Absent affix for $MATCH/$SKIP/$POS/terminals
        erroneous.

TEST is integer (>EXPR):
  [ EXPR -> affix ID ],
    ( id is integer affix (ID);
      -
    );
  [ EXPR -> variable ID instance INT ],
    ( id is integer affix (ID);
      -
    );
  [ EXPR -> integer const INT ].

TEST is set (>EXPR):
  [ EXPR -> affix ID ],
    ( id is set affix (ID);
      -
    );
  [ EXPR -> variable ID instance INT ],
    ( id is set affix (ID);
      -
    );
  [ EXPR -> set ID ],
    ( id is set affix (ID);
      -
    ).

TEST is text (>EXPR):
  [ EXPR -> affix ID ],
    ( id is text affix (ID);
      -
    );
  [ EXPR -> variable ID instance INT ],
    ( id is text affix (ID);
      -
    );
  [ EXPR -> text const ID ],
    ( id is text affix (ID);
      -
    ).

TEST is user affix terminal (>EXPR):
  [ EXPR -> affix ID ], id is user affix terminal (ID).

TEST is user variable (>EXPR):
  [ EXPR -> affix ID ], id is user variable (ID);
  [ EXPR -> variable ID instance INT ].

TEST is system affix (>EXPR):
  [ EXPR -> system affix ID ].

TEST where variable in set (>EXPR):
  [EXPR -> set ID],
    where variable in set (ID).

TEST is invalid domain (>EXPR):
  convert EXPR to affix ID,
    id is invalid affix (ID).

FUNCTION convert >EXPR to affix ID>:
  [ EXPR -> affix ID ];
  [ EXPR -> variable ID instance INT ];
  [ EXPR -> set ID ];
  [ EXPR -> text const ID ], get text affix (ID);
  [ EXPR -> integer const INT ], get integer affix (ID);
  [ EXPR -> system affix ID ];
  ?.

FUNCTION count (>EXPRS, INT>):
  [ EXPRS -> nil ], [ 0 -> INT ];
  [ EXPRS -> EXPR EXPRS1 ], count (EXPRS1, INT1), [ 1 + INT1 -> INT ];
  ?.

FUNCTION convert >EXPR to local INT> using >FORMALS and >LOCALS:
  convert EXPR to formal INT using FORMALS;
  convert EXPR to local INT using LOCALS,
    count NR_FORMALS in FORMALS,
    convert NR_FORMALS to INT1,
    [ INT + INT1 -> INT ];
  ?.

TEST convert >EXPR to formal INT> using >FORMALS:
  [ FORMALS -> EXPRS ], get index (EXPR, EXPRS, INT).

TEST convert >EXPR to local INT> using >LOCALS:
  [ LOCALS -> EXPRS ], get index (EXPR, EXPRS, INT).

TEST get index (>EXPR, >EXPRS, INT>):
  [ EXPRS -> EXPR1 EXPRS1 ],
    ( [ EXPR = EXPR1 ], [ 0 -> INT ];
      get index (EXPR, EXPRS1, INT1), [ INT1 + 1 -> INT ]
    ).

FUNCTION convert >EXPRS to system TEXT>:
  [ EXPRS -> nil ], [ "<nil>" -> TEXT ];
  [ EXPRS -> EXPR EXPRS1 ], convert EXPR to system TEXT,
    ( [ EXPRS1 -> nil ];
      convert EXPRS1 to system TEXT1,
        [ TEXT + ", " + TEXT1 -> TEXT ]
    );
  ?.

FUNCTION convert >FORMALS pack to user TEXT>:
  [ FORMALS -> EXPRS ],
    convert EXPRS pack to user TEXT.

FUNCTION convert >LOCALS pack to user TEXT>:
  [ LOCALS -> EXPRS ],
    convert EXPRS pack to user TEXT.

FUNCTION convert >EXPRS pack to user TEXT>:
  [ EXPRS -> nil ], [ "" -> TEXT ];
  [ EXPRS -> EXPR EXPRS ], convert EXPR to user TEXT,
    ( [ EXPRS -> nil ], [ "(" + TEXT + ")" -> TEXT ];
      convert EXPRS tail to user TEXT1, [ "(" + TEXT + TEXT1 + ")" -> TEXT ]
    );
  ?.

FUNCTION convert >EXPRS tail to user TEXT>:
  [ EXPRS -> nil ], [ "" -> TEXT ];
  [ EXPRS -> EXPR EXPRS ],
    convert EXPR to user TEXT1,
    convert EXPRS tail to user TEXT2,
    [ ", " + TEXT1 + TEXT2 -> TEXT ];
  ?.

FUNCTION convert >EXPR to user TEXT>:
  [ EXPR -> affix ID ], convert id to name (ID, TEXT);
  [ EXPR -> variable ID instance INT], convert id to name (ID, TEXT1),
    convert INT to TEXT2, [ TEXT1 + TEXT2 -> TEXT ];
  [ EXPR -> set ID ], convert set ID to TEXT;
  [ EXPR -> text const ID ], convert id to name (ID, TEXT);
  [ EXPR -> integer const INT ], convert INT to TEXT;
  [ EXPR -> system affix ID ], convert id to name (ID, TEXT);
  [ EXPR -> absent ], [ "<absent>" -> TEXT ];
  ?.

FUNCTION convert >EXPR to system TEXT>:
  [ EXPR -> affix ID ], convert ID to TEXT1, convert id to name (ID, TEXT2),
    [ "affix (" + TEXT1 + ") " + TEXT2 -> TEXT ];
  [ EXPR -> variable ID instance INT ], convert id to name (ID, TEXT1), convert INT to TEXT2,
    [ "variable " + TEXT1 + " instance " + TEXT2 -> TEXT ];
  [ EXPR -> set ID], convert id to name (ID, TEXT),
    [ "set " + TEXT -> TEXT ];
  [ EXPR -> text const ID], convert id to name (ID, TEXT),
    [ "text const " + TEXT -> TEXT ];
  [ EXPR -> integer const INT ], convert INT to TEXT,
    [ "integer const " + TEXT -> TEXT ];
  [ EXPR -> system affix ID ], convert id to name (ID, TEXT),
    [ "system affix " + TEXT -> TEXT ];
  [ EXPR -> absent ], [ "<absent>" -> TEXT ];
  [ EXPR -> erroneous ], [ "*ERRONEOUS*" -> TEXT ];
  ?.

#
# Rule initialization
#
ACTION init syntax rule (>NONTERMINAL):
  convert NONTERMINAL to INT, empty (RULE),
    [ RULE -> RULES{INT} ].

FUNCTION empty (RULE>):
  [ nil -> DEFS ],
    [ empty empty empty -> SPECS ],
    [ DEFS SPECS -> RULE ].

#
# Entering of rules
#
ACTION enter syntax rule (>NONTERMINAL, >RULE_TYPE, >FORMALS, >ALTS, >POS):
  is lexicon nonterminal (NONTERMINAL),
    convert NONTERMINAL to TEXT,
    interface error ("nonterminal \"" + TEXT +
                     "\" is already defined in the lexicon", POS);
  get syntax rule (NONTERMINAL, RULE),
    [RULE -> DEFS SPECS],
    enter formal specification (FORMALS, SPECS),
    [FORMALS at POS with ALTS  -> DEF],
    postpend (DEFS, DEF),
    [DEFS SPECS -> RULE],
    store syntax rule (NONTERMINAL, RULE),
    store specified rule type (NONTERMINAL, RULE_TYPE).

ACTION enter lexicon rule (>NONTERMINAL, >RULE_TYPE, >FORMALS, >POS):
  get syntax rule (NONTERMINAL, RULE),
    [RULE -> DEFS SPECS],
    enter lexicon specification (FORMALS, SPECS),
    [nil -> ALTS],
    [FORMALS at POS with ALTS  -> DEF],
    postpend (DEFS, DEF),
    [DEFS SPECS -> RULE],
    store syntax rule (NONTERMINAL, RULE),
    store specified rule type (NONTERMINAL, RULE_TYPE).


#
# Access Routines
#
FUNCTION get syntax rule (>NONTERMINAL, RULE>):
  assert valid NONTERMINAL,
    convert NONTERMINAL to INT,
    [RULES{INT} -> RULE].

ACTION store syntax rule (>NONTERMINAL, >RULE):
  convert NONTERMINAL to INT,
    [RULE -> RULES{INT}].

FUNCTION get specifications (>NONTERMINAL, SPECS>):
  get syntax rule (NONTERMINAL, RULE),
    [RULE -> DEFS SPECS].

ACTION store specifications (>NONTERMINAL, >SPECS):
  get syntax rule (NONTERMINAL, RULE),
    [RULE -> DEFS SPECS1],
    [DEFS SPECS -> RULE],
    store syntax rule (NONTERMINAL, RULE).

ACTION store actual specification (>NONTERMINAL, >ASPEC):
  get specifications (NONTERMINAL, SPECS),
    [SPECS -> USPEC ASPEC1 FSPEC],
    [USPEC ASPEC FSPEC -> SPECS],
    store specifications (NONTERMINAL, SPECS).

FUNCTION get actual specification (>NONTERMINAL, ASPEC>):
  get syntax rule (NONTERMINAL, RULE),
    [RULE -> DEFS SPECS],
    [SPECS -> USPEC ASPEC FSPEC].

FUNCTION get formal specification (>NONTERMINAL, FSPEC>):
  get syntax rule (NONTERMINAL, RULE),
    [RULE -> DEFS SPECS],
    [SPECS -> USPEC ASPEC FSPEC].

FUNCTION get user specification (>NONTERMINAL, USPEC>):
  get syntax rule (NONTERMINAL, RULE),
    [RULE -> DEFS SPECS],
    [SPECS -> USPEC ASPEC FSPEC].

FUNCTION get formal domain (>NONTERMINAL, >INT, ID>):
  get formal specification (NONTERMINAL, FSPEC),
    [FSPEC -> SPEC],
    get domain (SPEC, INT, ID).

FUNCTION get domain (>SPEC, >INT, ID>):
  [SPEC -> OIDS SPEC1],
    ([INT -> 1],
       ([OIDS -> collected ID];
        ?);
     get domain (SPEC1, INT - 1, ID));
  ?.


#########################
#                       #
#   Semantic analysis   #
#                       #
#########################

# Semantic analysis consists of two phases:
# 1. check if calls are to local or imported rules;
# 2. global analysis: type-check specifications;
# 3. local analysis: type-check rules, sematics checks on members.
#    and determine locals and group nonterminals on the fly.


ACTION analyse syntax rules:
  global analysis,
    display time ("global analysis"),
    local analysis,
    display time ("local analysis").


### Global analysis

SPECTYPE :: FORMACT; specified.
FORMACT  :: formal; actual.

ACTION global analysis:
  get number of syntax nonterminals (INT),
    global analysis (INT).

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

# It is checked whether each collected domain is included in some
# user specified domain, or in a predefined domain (text or int).

ACTION global analysis of nonterminal (>INT):
  convert INT to NONTERMINAL,
    (is start nonterminal (NONTERMINAL);
     get specifications (NONTERMINAL, SPECS),
       [SPECS -> USPEC ASPEC FSPEC],
       check specified domain (NONTERMINAL, USPEC),
       check formal domain (NONTERMINAL, FSPEC),
       check actual domain (NONTERMINAL, ASPEC)).

ACTION check specified domain (>NONTERMINAL, >USPEC):
  [USPEC -> SPEC],
    ([SPEC -> empty];
     check affix domain (NONTERMINAL, specified, SPEC, 1)).

ACTION check formal domain (>NONTERMINAL, >FSPEC):
  [FSPEC -> SPEC],
    ([SPEC -> empty],
       (nonterminal is not used (NONTERMINAL);
        is lexicon nonterminal (NONTERMINAL);
        report undefined nonterminal (NONTERMINAL));
     check affix domain (NONTERMINAL, formal, SPEC, 1)).

TEST nonterminal is defined (>NONTERMINAL):
  is start nonterminal (NONTERMINAL);
  get specifications (NONTERMINAL, SPECS),
    [SPECS -> USPEC ASPEC FSPEC],
    [FSPEC -> SPEC],
    ([SPEC -> empty],
       -;
     +).

TEST nonterminal is not used (>NONTERMINAL):
  is start nonterminal (NONTERMINAL);
  get specifications (NONTERMINAL, SPECS),
    [SPECS -> USPEC ASPEC FSPEC],
    [ASPEC -> SPEC],
    [SPEC -> empty].

TEST nonterminal is undefined and unused (>NONTERMINAL):
  nonterminal is defined (NONTERMINAL),
    -;
  nonterminal is not used (NONTERMINAL).

ACTION check actual domain (>NONTERMINAL, >ASPEC):
  [ASPEC -> SPEC],
    ([SPEC -> empty],
       report unused nonterminal (NONTERMINAL);
     check affix domain (NONTERMINAL, actual, SPEC, 1)).

ACTION check affix domain (>NONTERMINAL, >SPECTYPE, >SPEC, >INT):
  [SPEC -> nil];
  [SPEC -> OIDS SPEC1],
    ([OIDS -> collected ID],
       check affix domain (NONTERMINAL, SPECTYPE, ID, INT),
       check affix domain (NONTERMINAL, SPECTYPE, SPEC1, INT + 1);
     ?);
  ?.

ACTION check affix domain (>NONTERMINAL, >SPECTYPE, >ID, >INT):
  id is invalid affix (ID),
    report invalid domain (NONTERMINAL, SPECTYPE, INT);
  +.

ACTION report invalid domain (>NONTERMINAL, >SPECTYPE, >INT):
  convert NONTERMINAL to TEXT1,
    convert SPECTYPE to TEXT2,
    convert INT to TEXT3,
    error ("domain generated by " + TEXT2 + " parameter " + TEXT3 +
           " of nonterminal \"" + TEXT1 +
           "\" is not included in any domain").

ACTION report undefined nonterminal (>NONTERMINAL):
  convert NONTERMINAL to TEXT,
    error ("nonterminal \"" + TEXT + "\" is not defined").

ACTION report unused nonterminal (>NONTERMINAL):
  get pragmats (NONTERMINAL, PRAGMATS),
    (if warnings (PRAGMATS),
       convert NONTERMINAL to TEXT,
       warning ("nonterminal \"" + TEXT + "\" is not used");
     +).

FUNCTION convert >SPECTYPE to TEXT>:
  [SPECTYPE -> FORMACT],
    convert FORMACT to TEXT;
  [SPECTYPE -> specified],
    ["specified" -> TEXT];
  ?.

FUNCTION convert >FORMACT to TEXT>:
  [FORMACT -> formal],
    ["formal" -> TEXT];
  [FORMACT -> actual],
    ["actual" -> TEXT];
  ?.


### Local analysis

DOMAIN_TYPE :: FORMACT NONTERMINAL;
               prompt.

ACTION local analysis:
  local analysis (0).

ACTION local analysis (>INT):
  get number of syntax nonterminals (INT1),
    ([INT = INT1];
     convert INT to NONTERMINAL,
       local analysis of nonterminal (NONTERMINAL),
       local analysis (INT + 1)).

ACTION local analysis of nonterminal (>NONTERMINAL):
  get syntax rule (NONTERMINAL, RULE),
    [RULE -> DEFS SPECS],
    local analysis (NONTERMINAL, DEFS),
    [DEFS SPECS -> RULE],
    store syntax rule (NONTERMINAL, RULE).

ACTION local analysis (>NONTERMINAL, >DEFS>):
  [DEFS -> nil];
  [DEFS -> DEF DEFS],
    local analysis (NONTERMINAL, DEF),
    local analysis (NONTERMINAL, DEFS),
    [DEF DEFS -> DEFS];
  ?.

ACTION local analysis (>NONTERMINAL, >DEF>):
  [DEF -> FORMALS at POS with ALTS],
    analyse formals (NONTERMINAL, FORMALS, POS),
    analyse alternatives (FORMALS, ALTS),
    [FORMALS at POS with ALTS  -> DEF].

ACTION analyse alternatives (>FORMALS, >ALTS>):
  [ALTS -> nil];
  [ALTS -> ALT ALTS],
    analyse alternative (FORMALS, ALT),
    analyse alternatives (FORMALS, ALTS),
    [ALT ALTS -> ALTS];
  ?.

#
# MS: to add: check transduction here
#
ACTION analyse alternative (>FORMALS, >ALT>):
  [ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS],
    analyse alternative (FORMALS, LOCALS, MEMBERS),
    [FREQ MEMBERS LOCALS COMMITED SECONDPASS -> ALT];
  [ALT -> FREQ empty COMMITED SECONDPASS];
  ?.

ACTION analyse alternative (>FORMALS, >LOCALS>, >MEMBERS>):
  analyse members (MEMBERS),
    search for locals (FORMALS, LOCALS, MEMBERS),
    determine option calls (FORMALS, LOCALS, MEMBERS).


### Search for locals

# TODO: left-factoring of locals.

LOCAL_TYPE :: user variable;	# passed to groups and options as formals
              system;		# anon set ids and matches/skips/terminals
              all.

FUNCTION search for locals (>FORMALS, >LOCALS>, >MEMBERS):
  search for locals (all, FORMALS, LOCALS, MEMBERS).

FUNCTION search for locals (>LOCAL_TYPE, >FORMALS, >LOCALS>, >MEMBERS):
  [ MEMBERS -> MEMBER MEMBERS1 ],
    search for locals (LOCAL_TYPE, FORMALS, LOCALS, MEMBER),
    search for locals (LOCAL_TYPE, FORMALS, LOCALS, MEMBERS1);
  [ MEMBERS -> nil ];
  ?.

FUNCTION search for locals (>LOCAL_TYPE, >FORMALS, >LOCALS>, >MEMBER):
  [ MEMBER -> guard GUARDS ],
    search for locals (LOCAL_TYPE, FORMALS, LOCALS, GUARDS);
  [ MEMBER -> call ID def NONTERMINAL with ACTUALS at POS ],
    search for locals (LOCAL_TYPE, ACTUALS, FORMALS, LOCALS);
  [ MEMBER -> terminal ID using EXPR1 targeting EXPR2 at POS ],
    search for locals (LOCAL_TYPE, EXPR1, FORMALS, LOCALS),
    search for locals (LOCAL_TYPE, EXPR2, FORMALS, LOCALS);
  [ MEMBER -> skip regexp ID using EXPR1 targeting EXPR2 at POS ],
    search for locals (LOCAL_TYPE, EXPR1, FORMALS, LOCALS),
    search for locals (LOCAL_TYPE, EXPR2, FORMALS, LOCALS);
  [ MEMBER -> match regexp ID using EXPR1 targeting EXPR2 at POS ],
    search for locals (LOCAL_TYPE, EXPR1, FORMALS, LOCALS),
    search for locals (LOCAL_TYPE, EXPR2, FORMALS, LOCALS);
  [ MEMBER -> other using EXPR1 targeting EXPR2 at POS ],
    search for locals (LOCAL_TYPE, EXPR1, FORMALS, LOCALS),
    search for locals (LOCAL_TYPE, EXPR2, FORMALS, LOCALS);
  [ MEMBER -> input pos using EXPR1 targeting EXPR2 at POS ],
    search for locals (LOCAL_TYPE, EXPR1, FORMALS, LOCALS),
    search for locals (LOCAL_TYPE, EXPR2, FORMALS, LOCALS);
  where member is option (MEMBER, ALTS),
    search for locals in group or option (LOCAL_TYPE, FORMALS, LOCALS, ALTS);
  [ MEMBER -> free word order MEMBERS ],
    unspecified (FREQ), [ none -> SECONDPASS ],
    [ FREQ MEMBERS nil false SECONDPASS -> ALT ],
    [ ALT nil -> ALTS ],
    search for locals (LOCAL_TYPE, FORMALS, LOCALS, ALT);
  where member is final group (MEMBER, ALTS),
    search for locals (LOCAL_TYPE, FORMALS, LOCALS, ALTS);
  [ MEMBER -> penalty INT ];
  [ MEMBER -> position INT ];
  ?.

FUNCTION search for locals (>LOCAL_TYPE, >FORMALS, >LOCALS>, >GUARDS):
  [ GUARDS -> GUARD GUARDS1 ],
    search for locals (LOCAL_TYPE, FORMALS, LOCALS, GUARD),
    search for locals (LOCAL_TYPE, FORMALS, LOCALS, GUARDS1);
  [ GUARDS -> nil ];
  ?.

FUNCTION search for locals (>LOCAL_TYPE, >FORMALS, >LOCALS>, >GUARD):
  [ GUARD -> restrict EXPR1 to EXPR2 at POS ],
    search for locals (LOCAL_TYPE, EXPR1, FORMALS, LOCALS);
  ?.

# User variables in groups and options should be added to locals
# in order to bind them to other variables in the rule.
# Anonymous variables (generated  by system) in groups and options
# should not be added; these are local to the anonymous rule.

FUNCTION search for locals in group or option (>LOCAL_TYPE, >FORMALS, >LOCALS>, >ALT):
  [ ALT nil -> ALTS ],
    search for locals in group or option (LOCAL_TYPE, FORMALS, LOCALS, ALTS).

FUNCTION search for locals in group or option (>LOCAL_TYPE, >FORMALS, >LOCALS>, >ALTS):
  [ LOCAL_TYPE -> all ],
    search for locals (user variable, FORMALS, LOCALS, ALTS);
  [ LOCAL_TYPE -> user variable ],
    search for locals (user variable, FORMALS, LOCALS, ALTS);
  [ LOCAL_TYPE -> system ];
  ?.

FUNCTION search for locals (>LOCAL_TYPE, >FORMALS, >LOCALS>, >ALTS):
  [ ALTS -> ALT ALTS1 ],
    search for locals (LOCAL_TYPE, FORMALS, LOCALS, ALT),
    search for locals (LOCAL_TYPE, FORMALS, LOCALS, ALTS1);
  [ ALTS -> nil ];
  ?.

FUNCTION search for locals (>LOCAL_TYPE, >FORMALS, >LOCALS>, >ALT):
  [ ALT -> FREQ MEMBERS LOCALS1 COMMITED SECONDPASS ],
    search for locals (LOCAL_TYPE, FORMALS, LOCALS, MEMBERS);
  [ ALT -> FREQ empty COMMITED SECONDPASS ];
  ?.

FUNCTION search for locals (>LOCAL_TYPE, >ACTUALS, >FORMALS, >LOCALS>):
  [ ACTUALS -> EXPRS ],
    search for locals (LOCAL_TYPE, EXPRS, FORMALS, LOCALS).

FUNCTION search for locals (>LOCAL_TYPE, >EXPRS, >FORMALS, >LOCALS>):
  [ EXPRS -> EXPR EXPRS1 ],
    search for locals (LOCAL_TYPE, EXPR, FORMALS, LOCALS),
    search for locals (LOCAL_TYPE, EXPRS1, FORMALS, LOCALS);
  [ EXPRS -> nil ];
  ?.

FUNCTION search for locals (>LOCAL_TYPE, >EXPR, >FORMALS, >LOCALS>):
  convert EXPR to formal INT using FORMALS;
  convert EXPR to local INT using LOCALS;
  add expression used as local (LOCAL_TYPE, EXPR, LOCALS).

FUNCTION add expression used as local (>LOCAL_TYPE, >EXPR, >LOCALS>):
  [ LOCAL_TYPE -> user variable ],
    (is user variable (EXPR),
       add expression used as local (EXPR, LOCALS);
     +);
  [ LOCAL_TYPE -> system ],
    (is user variable (EXPR);
     add expression used as local (EXPR, LOCALS));
  [ LOCAL_TYPE -> all ],
    add expression used as local (EXPR, LOCALS);
  ?.

FUNCTION add expression used as local (>EXPR, >LOCALS>):
  [ EXPR -> absent ];
  append EXPR to LOCALS.

#
# Anonymous locals
#
FUNCTION search for anonymous locals (>ALTS>):
  [ ALTS -> nil ];
  [ ALTS -> ALT ALTS1 ],
    search for anonymous locals (ALT),
    search for anonymous locals (ALTS1),
    [ ALT ALTS1 -> ALTS ];
  ?.

FUNCTION search for anonymous locals (>ALT>):
  search for anonymous locals (ALT, LOCALS).

FUNCTION search for anonymous locals (>ALT>, LOCALS>):
  [ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS],
    search for anonymous locals (MEMBERS, LOCALS),
    [FREQ MEMBERS LOCALS COMMITED SECONDPASS -> ALT];
  [ALT -> FREQ empty COMMITED SECONDPASS],
    [nil -> LOCALS];
  ?.

FUNCTION search for anonymous locals (>MEMBERS, >LOCALS>):
  search for locals (system, nil, LOCALS, MEMBERS).

#
# Group calls
#
OPT_SPEC :: NONTERMINAL with FORMALS;
            empty.

ACTION determine option calls (>FORMALS, >LOCALS, >MEMBERS>):
  [MEMBERS -> nil];
  [MEMBERS -> MEMBER MEMBERS],
    determine option calls (FORMALS, LOCALS, MEMBER),
    determine option calls (FORMALS, LOCALS, MEMBERS),
    [MEMBER MEMBERS -> MEMBERS];
  ?.

ACTION determine option calls (>FORMALS, >LOCALS, >MEMBER>):
  [MEMBER -> group ALTS at POS],
    determine option calls in group (FORMALS, LOCALS, ALTS),
    [group ALTS at POS -> MEMBER];
  [MEMBER -> final group ALTS at POS with NONTERMINAL],
    determine option calls in group (FORMALS, LOCALS, ALTS),
    [final group ALTS at POS with NONTERMINAL -> MEMBER];
  [MEMBER -> option ALTS with OPT_SPEC1 at POS],
    determine option calls in option (FORMALS, LOCALS, ALTS, OPT_SPEC),
    [option ALTS with OPT_SPEC at POS -> MEMBER];
  +.

ACTION determine option calls in group (>FORMALS, >LOCALS, >ALTS>):
  [ALTS -> nil];
  [ALTS -> ALT ALTS],
    determine option calls in group (FORMALS, LOCALS, ALT),
    determine option calls in group (FORMALS, LOCALS, ALTS),
    [ALT ALTS -> ALTS];
  ?.

ACTION determine option calls in group (>FORMALS, >LOCALS, >ALT>):
  [ALT -> FREQ MEMBERS LOCALS1 COMMITED SECONDPASS],
    determine option calls (FORMALS, LOCALS, MEMBERS),
    [FREQ MEMBERS LOCALS COMMITED SECONDPASS -> ALT];
  [ALT -> FREQ empty COMMITED SECONDPASS];
  ?.

ACTION determine option calls in option (>FORMALS, >LOCALS, >ALTS>, OPT_SPEC>):
  determine option formals (FORMALS, LOCALS, ALTS, FORMALS1),
    determine option calls in option (FORMALS1, LOCALS, ALTS),
    create anonymous nonterminal (FORMALS1, NONTERMINAL),
    [NONTERMINAL with FORMALS1 -> OPT_SPEC],
    create anonymous syntax rule (NONTERMINAL, FORMALS1, ALTS).

ACTION determine option calls in option (>FORMALS, >LOCALS, >ALTS>):
  [ALTS -> nil];
  [ALTS -> ALT ALTS],
    determine option calls in option (FORMALS, LOCALS, ALT),
    determine option calls in option (FORMALS, LOCALS, ALTS),
    [ALT ALTS -> ALTS];
  ?.

ACTION determine option calls in option (>FORMALS, >LOCALS, >ALT>):
  [ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS],
    # all locals are blindly added to the group formals
    # by 'determine group formals', do not add them again,
    # therefore pass nil as locals.
    determine option calls (FORMALS, nil, MEMBERS),
    [FREQ MEMBERS LOCALS COMMITED SECONDPASS -> ALT];
  [ALT -> FREQ empty COMMITED SECONDPASS];
  ?.

# TODO: only pass formals and locals that are used both inside and outside
# group or option. For the moment, pass all formals and non-anonymous locals.

ACTION determine option formals (>FORMALS, >LOCALS, >ALTS, FORMALS2>):
  [LOCALS -> EXPRS1],
    [FORMALS -> EXPRS2],
    append EXPRS1 to EXPRS2,
    [nil -> EXPRS3],
    maybe add EXPRS2 to option formals EXPRS3 on occurance in ALTS,
    [EXPRS3 -> FORMALS2].

ACTION maybe add >EXPRS1 to option formals >EXPRS2> on occurance in >ALTS:
  [EXPRS1 -> nil];
  [EXPRS1 -> EXPR EXPRS],
    maybe add EXPR to option formals EXPRS2 on occurance in ALTS,
    maybe add EXPRS to option formals EXPRS2 on occurance in ALTS;
  ?.

FUNCTION maybe add >EXPR to option formals >EXPRS> on occurance in >ALTS:
  is user variable (EXPR),
    expr is used in alts (EXPR, ALTS),
    [EXPR EXPRS -> EXPRS];
  +.

TEST expr is used in alts (>EXPR, >ALTS):
  [ALTS -> ALT ALTS],
    (expr is used in alt (EXPR, ALT);
     expr is used in alts (EXPR, ALTS);
     -);
  [ALTS -> nil],
    -;
  ?.

TEST expr is used in alt (>EXPR, >ALT):
  [ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS],
    (expr is used in members (EXPR, MEMBERS);
     -);
  [ALT -> FREQ empty COMMITED SECONDPASS],
    -;
  ?.

TEST expr is used in members (>EXPR, >MEMBERS):
  [MEMBERS -> MEMBER MEMBERS],
    (expr is used in member (EXPR, MEMBER);
     expr is used in members (EXPR, MEMBERS);
     -);
  [MEMBERS -> nil],
    -;
  ?.

TEST expr is used in member (>EXPR, >MEMBER):
  [MEMBER -> guard GUARDS],
    (expr is used in guards (EXPR, GUARDS);
     -);
  [MEMBER -> call ID def NONTERMINAL with ACTUALS at POS],
    (expr is used in actuals (EXPR, ACTUALS);
     -);
  [MEMBER -> option ALTS with OPT_SPEC at POS],
    (expr is used in alts (EXPR, ALTS);
     -);
  [MEMBER -> group ALTS at POS],
    (expr is used in alts (EXPR, ALTS);
     -);
  [MEMBER -> free word order MEMBERS],
    (expr is used in members (EXPR, MEMBERS);
     -);
  [MEMBER -> final group ALTS at POS with NONTERMINAL],
    (expr is used in alts (EXPR, ALTS);
     -);
  -.

TEST expr is used in guards (>EXPR, >GUARDS):
  [GUARDS -> GUARD GUARDS],
    (expr is used in guard (EXPR, GUARD);
     expr is used in guards (EXPR, GUARDS);
     -);
  [GUARDS -> nil],
    -;
  ?.

TEST expr is used in guard (>EXPR, >GUARD):
  [GUARD -> restrict EXPR1 to EXPR2 at POS],
    ([EXPR = EXPR1];
     [EXPR = EXPR2];
     -);
  [GUARD -> erroneous],
    ?;
  ?.

TEST expr is used in actuals (>EXPR, >ACTUALS):
  [ACTUALS -> EXPRS],
    expr is used in exprs (EXPR, EXPRS).

TEST expr is used in exprs (>EXPR, >EXPRS):
  [EXPRS -> EXPR1 EXPRS],
    ([EXPR = EXPR1];
     expr is used in exprs (EXPR, EXPRS);
     -);
  [EXPRS -> nil],
    -;
  ?.

ACTION create anonymous nonterminal (>FORMALS, NONTERMINAL>):
  create anonymous id (ID),
    [FORMALS -> EXPRS],
    count NR_FORMALS in EXPRS,
    convert NR_FORMALS to INT,
    mark id as anonymous nonterminal (ID, arity INT, NONTERMINAL).

# Hier willen we een POS als affix
ACTION create anonymous syntax rule (>NONTERMINAL, >FORMALS, >ALTS):
  convert FORMALS to collected SPEC,
    create empty second pass (SECONDPASS),
    [2 empty false SECONDPASS -> ALT],
    [ALT ALTS -> ALTS],
    [empty SPEC SPEC -> SPECS],
    [FORMALS at internal with ALTS -> DEF],
    [DEF nil -> DEFS],
    [DEFS SPECS -> RULE],
    store syntax rule (NONTERMINAL, RULE).

# Since anonymous rules are generated after collecting specifications,
# we convert the formals of the anonymous rule to a collected specification.
# Since there is only one specification for each anonymous rule, there is
# no need for creating anonymous affix rules joining multiple specifications
# into one domain.

FUNCTION convert >FORMALS to collected SPEC>:
  [ FORMALS -> EXPRS ], convert EXPRS to collected SPEC.

FUNCTION convert >EXPRS to collected SPEC>:
  [ EXPRS -> nil ], [ nil -> SPEC ];
  [ EXPRS -> EXPR EXPRS1 ],
    convert EXPR to affix ID,
    convert EXPRS1 to collected SPEC1,
    [ collected ID SPEC1 -> SPEC ];
  ?.

FUNCTION empty (OPT_SPEC>):
  [ empty -> OPT_SPEC ].

FUNCTION convert >OPT_SPEC to NONTERMINAL>:
  [ OPT_SPEC -> NONTERMINAL with FORMALS ];
  ?.

FUNCTION convert >OPT_SPEC to TEXT>:
  [ OPT_SPEC -> empty ], [ "*empty*" -> TEXT ];
  [ OPT_SPEC -> NONTERMINAL with FORMALS ],
    convert NONTERMINAL to TEXT1,
    convert FORMALS to system TEXT2,
    [ TEXT1 + "(" + TEXT2 + ")" -> TEXT];
  ?.

FUNCTION convert >OPT_SPEC to FORMALS>:
  [ OPT_SPEC -> NONTERMINAL with FORMALS ];
  ?.

FUNCTION count NR_FORMALS> in >DEFS:
  [ DEFS -> nil ], new NR_FORMALS;
  [ DEFS -> DEF DEFS ], # all defs have same arity, so count for the first def
    count NR_FORMALS in DEF;
  ?.

FUNCTION count NR_FORMALS> in >DEF:
  [ DEF -> FORMALS at POS with ALTS ], [ FORMALS -> EXPRS ],
    count NR_FORMALS in EXPRS.

FUNCTION count NR_FORMALS> in >EXPRS:
  [ EXPRS -> nil ], new NR_FORMALS;
  [ EXPRS -> EXPR EXPRS ],
    count NR_FORMALS in EXPRS,
    incr NR_FORMALS;
  ?.

FUNCTION count NR_FORMALS> in >FORMALS:
  [ FORMALS -> EXPRS ], count NR_FORMALS in EXPRS.

#
# Analysis of members
#
ACTION analyse members (>MEMBERS):
  [ MEMBERS -> nil];
  [ MEMBERS -> MEMBER MEMBERS1 ],
    analyse member (MEMBER),
    analyse members (MEMBERS1);
  ?.

ACTION analyse member (>MEMBER):
  [ MEMBER -> guard GUARDS ], analyse guards (GUARDS);
  [ MEMBER -> call ID def NONTERMINAL with ACTUALS at POS ],
    analyse call (NONTERMINAL, ACTUALS, POS);
  where member is option (MEMBER, ALTS, POS),
    analyse option (ALTS, POS),
    analyse alternatives (ALTS);
  [ MEMBER -> free word order MEMBERS ], analyse members (MEMBERS);
  where member is final group (MEMBER, ALTS), analyse alternatives (ALTS);
  [ MEMBER -> penalty INT];
  [ MEMBER -> terminal ID using EXPR1 targeting EXPR2 at POS],
    check if text affix (EXPR2, "regexp skip", POS);
  [ MEMBER -> skip regexp ID using EXPR1 targeting EXPR2 at POS ],
    check if text affix (EXPR2, "regexp skip", POS);
  [ MEMBER -> match regexp ID using EXPR1 targeting EXPR2 at POS ],
    check if text affix (EXPR2, "regexp match", POS);
  [ MEMBER -> other using EXPR1 targeting EXPR2 at POS ],
    check if text affix (EXPR2, "other", POS);
  [ MEMBER -> input pos using EXPR1 targeting EXPR2 at POS ],
    check if text affix (EXPR2, "input position", POS);
  [ MEMBER -> position INT ];
  ?.

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

ACTION analyse alternative (>ALT):
  [ ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS ], analyse members (MEMBERS);
  [ ALT -> FREQ empty COMMITED SECONDPASS ];
  ?.

#
# Formal parameters
#
# Check whether formals conform to user specification, if any;
# check whether formals are included in a user domain;
# check whether formals do not contain duplicate variables;
# check whether formal set expressions do not contain variables.
#
ACTION analyse formals (>NONTERMINAL, >FORMALS, >POS):
  [ FORMALS -> EXPRS ],
    check specification (formal against specified NONTERMINAL, EXPRS, POS),
    check affix domain (formal NONTERMINAL, EXPRS, POS),
    check no duplicate variables (formal NONTERMINAL, EXPRS, POS),
    check no variable in sets (formal NONTERMINAL, EXPRS, POS).

#
# Calls
#
# Check whether actuals conform to formal specification;
# check whether actuals are included in a user domain;
# check whether actuals do not contain duplicate variables;
# check whether actuals set expressions do not contain variables.
#
ACTION analyse call (>NONTERMINAL, >ACTUALS, >POS):
  [ ACTUALS -> EXPRS ],
    check specification (actual against specified NONTERMINAL, EXPRS, POS),
    check specification (actual against formal NONTERMINAL, EXPRS, POS),
    check affix domain (actual NONTERMINAL, EXPRS, POS),
    check no duplicate variables (actual NONTERMINAL, EXPRS, POS),
    check no variable in sets (actual NONTERMINAL, EXPRS, POS).

#
# Guards
#
ACTION analyse guards (>GUARDS):
  [ GUARDS -> nil ];
  [ GUARDS -> GUARD GUARDS1 ],
    analyse guard (GUARD),
    analyse guards (GUARDS1);
  ?.

# Let {A::B} be a guard. Then A and B should have the same type and domain.
# Furthermore, A should be a variable, B should not contain variables,
# and B should be included in a user specified domain.

ACTION analyse guard (>GUARD):
  [ GUARD -> restrict EXPR1 to EXPR2 at POS ],
    type check guard (EXPR1, EXPR2, POS),
    check guard lhs (EXPR1, POS),
    check guard rhs (EXPR2, POS);
  ?.

ACTION type check guard (>EXPR1, >EXPR2, >POS):
  convert EXPR1 to affix ID1,
    convert EXPR2 to affix ID2,
      (id is invalid affix (ID2); # reported at 'check guard rhs'
       id is integer affix (ID1),
         (id is integer affix (ID2);
          convert EXPR2 to user TEXT,
            error ("guard parameter \"" + TEXT + "\" is not an integer", POS));
       id is text affix (ID1),
         (id is text affix (ID2);
          convert EXPR2 to user TEXT,
            error ("guard parameter \"" + TEXT + "\" is not a text", POS));
       id is set affix (ID1),
         (id is set affix (ID2),
            (where set ID2 subset ID1;
             warning ("guard will always fail", POS));
          convert EXPR2 to user TEXT,
            error ("guard parameter \"" + TEXT + "\" is not a set", POS));
       ?).

TEST where set >ID1 subset >ID2:
  get set info (ID1, SET1),
    get set info (ID2, SET2),
      is subset (SET1, SET2).

ACTION check guard lhs (>EXPR, >POS):
  is user variable (EXPR);
  convert EXPR to user TEXT,
    error ("guard parameter \"" + TEXT + "\" is not a variable", POS).

ACTION check guard rhs (>EXPR, >POS):
  is invalid domain (EXPR),
    convert EXPR to user TEXT,
    error ("guard parameter \"" + TEXT + "\" has invalid domain", POS);
  is user variable (EXPR),
    convert EXPR to user TEXT,
    error ("guard parameter \"" + TEXT + "\" is a variable", POS);
  where variable in set (EXPR),
    convert EXPR to user TEXT,
    error ("guard parameter \"" + TEXT + "\" contains a variable", POS);
  +.

#
# Variables in sets
#
# Variables are not allowed in set denotations, since this makes it impossible
# calculate the set values at compile-time, and would complicate further
# restricts on that variable.
#
ACTION check no variable in sets (>DOMAIN_TYPE, >EXPRS, >POS):
  [ EXPRS -> nil ];
  [ EXPRS -> EXPR EXPRS1 ],
    check no variable in set (DOMAIN_TYPE, EXPR, POS),
    check no variable in sets (DOMAIN_TYPE, EXPRS1, POS);
  ?.

ACTION check no variable in set (>DOMAIN_TYPE, >EXPR, >POS):
  where variable in set (EXPR),
    report variable in set (DOMAIN_TYPE, EXPR, POS);
  +.

ACTION report variable in set (>DOMAIN_TYPE, >EXPR, >POS):
  [ DOMAIN_TYPE -> FORMACT NONTERMINAL ],
    convert NONTERMINAL to TEXT1,
    convert FORMACT to TEXT2,
    convert EXPR to user TEXT3,
    error ("variable in set at " + TEXT2 + " parameter \"" + TEXT3 +
           "\" of nonterminal \"" + TEXT1 + "\"", POS);
  [ DOMAIN_TYPE -> prompt ],
    convert EXPR to user TEXT,
    error ("variable in set at prompt parameter \"" + TEXT + "\"", POS);
  ?.

#
# Duplicate variables
#
# Duplicate variables in formal or actual parameter positions of one
# nonterminal are not allowed, since this specifies an equality test,
# which frustrates on the fly affix evaluation in the current run-time model.
#
ACTION check no duplicate variables (>DOMAIN_TYPE, >EXPRS, >POS):
  [ EXPRS -> nil ];
  [ EXPRS -> EXPR EXPRS1 ],
    check no duplicate variable (DOMAIN_TYPE, EXPR, EXPRS1, POS),
    check no duplicate variables (DOMAIN_TYPE, EXPRS1, POS);
  ?.

ACTION check no duplicate variable (>DOMAIN_TYPE, >EXPR, >EXPRS, >POS):
  is user variable (EXPR),
    is duplicate (EXPR, EXPRS),
      report duplicate variable (DOMAIN_TYPE, EXPR, POS);
  +.

TEST is duplicate (>EXPR, >EXPRS):
  [EXPRS -> EXPR1 EXPRS1],
    ([EXPR = EXPR1];
     is duplicate (EXPR, EXPRS1)).

ACTION report duplicate variable (>DOMAIN_TYPE, >EXPR, >POS):
  [DOMAIN_TYPE -> FORMACT NONTERMINAL],
    convert NONTERMINAL to TEXT1,
    convert FORMACT to TEXT2,
    convert EXPR to user TEXT3,
    error ("duplicate variable \"" + TEXT3 + "\" in " + TEXT2 +
           " parameters of nonterminal \"" + TEXT1 + "\"", POS);
  [DOMAIN_TYPE -> prompt],
    convert EXPR to user TEXT,
    error ("duplicate variable \"" + TEXT + "\"in prompt parameters", POS);
  ?.

#
# Check domains
#
# Check whether domain included in domain of some affix.
#
ACTION check affix domain (>DOMAIN_TYPE, >EXPRS, >POS):
  [EXPRS -> nil];
  [EXPRS -> EXPR EXPRS1],
    check affix domain (DOMAIN_TYPE, EXPR, POS),
    check affix domain (DOMAIN_TYPE, EXPRS1, POS);
  ?.

ACTION check affix domain (>DOMAIN_TYPE, >EXPR, >POS):
  is invalid domain (EXPR),
    report invalid domain (DOMAIN_TYPE, EXPR, POS);
  +.

ACTION report invalid domain (>DOMAIN_TYPE, >EXPR, >POS):
  [DOMAIN_TYPE -> FORMACT NONTERMINAL],
    convert NONTERMINAL to TEXT1,
    convert FORMACT to TEXT2,
    convert EXPR to user TEXT3,
    error ("domain generated by " + TEXT2 + " parameter \"" + TEXT3 +
           "\" of nonterminal \"" + TEXT1 + "\" is not included in any domain",
           POS);
  [DOMAIN_TYPE -> prompt],
    convert EXPR to user TEXT,
    error ("domain generated by prompt parameter \"" + TEXT +
           "\" is not included in any domain", POS);
  ?.

#
# Specifications
#
SPEC_TYPE :: FORMACT against specified NONTERMINAL;
             actual against formal NONTERMINAL.

#
# Check whether formals are included in user specification;
# check whether actuals are included in formal specification
# (and therefore also in user specification).
#
ACTION check specification (>SPEC_TYPE, >EXPRS, >POS):
  get specification (SPEC_TYPE, SPEC),
    ([SPEC -> empty];
     check specification (SPEC_TYPE, SPEC, EXPRS, POS)).

FUNCTION get specification (>SPEC_TYPE, SPEC>):
  [SPEC_TYPE -> FORMACT against specified NONTERMINAL],
    get user specification (NONTERMINAL, USPEC),
    [USPEC -> SPEC];
  [SPEC_TYPE -> actual against formal NONTERMINAL],
    get formal specification (NONTERMINAL, FSPEC),
    [FSPEC -> SPEC];
  ?.

ACTION check specification (>SPEC_TYPE, >SPEC, >EXPRS, >POS):
  [SPEC -> nil],
    ([EXPRS -> nil];
     ?);
  [SPEC -> OIDS SPEC1],
    ([EXPRS -> EXPR EXPRS1],
       check specification (SPEC_TYPE, OIDS, EXPR, POS),
       check specification (SPEC_TYPE, SPEC1, EXPRS1, POS);
     ?);
  ?.

ACTION check specification (>SPEC_TYPE, >OIDS, >EXPR, >POS):
  [OIDS -> collected ID1],
     convert EXPR to affix ID2,
       (id is invalid affix (ID1);	# checked by 'check specified domain'
        id is invalid affix (ID2);	# checked by 'check affix domain'
        id is integer affix (ID1),
          (id is integer affix (ID2);
           report invalid type ("an integer", SPEC_TYPE, EXPR, POS));
        id is text affix (ID1),
          (id is text affix (ID2);
           report invalid type ("a text", SPEC_TYPE, EXPR, POS));
        id is set affix (ID1),
          (id is set affix (ID2),
             check specified set (SPEC_TYPE, ID1, ID2, EXPR, POS);
           report invalid type ("a set", SPEC_TYPE, EXPR, POS));
        ?);
  ?.

ACTION check specified set (>SPEC_TYPE, >ID1, >ID2, >EXPR, >POS):
  get set info (ID1, SET1),
    get set info (ID2, SET2),
      ([SPEC_TYPE -> FORMACT against specified NONTERMINAL],
         (is subset (SET2, SET1);
          report unspecified domain (FORMACT, NONTERMINAL, EXPR, POS));
       [SPEC_TYPE -> actual against formal NONTERMINAL],
         (is disjoint (SET1, SET2),
            report disjoint actual parameter (NONTERMINAL, EXPR, POS);
          +);
       ?).

ACTION report unspecified domain (>FORMACT, >NONTERMINAL, >EXPR, >POS):
  convert NONTERMINAL to TEXT1,
    convert FORMACT to TEXT2,
    convert EXPR to user TEXT3,
    error ("domain generated by " + TEXT2 + " parameter \"" + TEXT3 +
           "\" of nonterminal \"" + TEXT1 + "\" is not specified", POS).

ACTION report disjoint actual parameter (>NONTERMINAL, >EXPR, >POS):
  convert NONTERMINAL to TEXT1,
    convert EXPR to user TEXT2,
    warning ("call to nonterminal \"" + TEXT1 +
             "\" will always fail due to actual parameter \"" + TEXT2 + "\"", POS).

ACTION report invalid type (>TEXT, >SPEC_TYPE, >EXPR, >POS):
  [SPEC_TYPE -> FORMACT against specified NONTERMINAL],
    report invalid type (TEXT, FORMACT, NONTERMINAL, EXPR, POS);
  [SPEC_TYPE -> actual against formal NONTERMINAL],
    get user specification (NONTERMINAL, USPEC),
      ([USPEC -> empty],
         report invalid type (TEXT, actual, NONTERMINAL, EXPR, POS);
       +);	# reported by checking actuals against user specification
  ?.

ACTION report invalid type (>TEXT, >FORMACT, >NONTERMINAL, >EXPR, >POS):
  convert NONTERMINAL to TEXT1,
    convert FORMACT to TEXT2,
    convert EXPR to user TEXT3,
    error (TEXT2 + " parameter \"" + TEXT3 + "\" of nonterminal \"" + TEXT1 +
           "\" is not " + TEXT, POS).

#
# Special names
# Regular expressions and input position need only check
# if their optional affix expression is a text affix
#
ACTION check if text affix (>EXPR, >TEXT, >POS):
  [ EXPR -> absent ];
  convert EXPR to affix ID,
    ( id is text affix (ID);
      convert EXPR to user TEXT2,
        error (TEXT + " variable \"" + TEXT2 + "\" is not a text", POS)
    ).

#
# Options
#
ACTION analyse option (>ALTS, >POS):
  has empty alt (ALTS),
    warning ("option has empty alternative", POS);
  +.
    
TEST has empty alt (>ALTS):
  [ALTS -> ALT ALTS1],
    (is empty (ALT);
     has empty alt (ALTS1)).
    
TEST is empty (>ALT):
  [ALT -> FREQ empty COMMITED SECONDPASS].
  
TEST is empty alternative (>ALT):
  is empty (ALT).
  

##################
#                #
#   Frequence    #
#                #
##################

FREQ :: INT.

ACTION analyse frequence:
  if frequence option,
    warning ("Probabilistic parsing not supported!");
#    get number of syntax nonterminals (INT),
#    analyse frequence (INT);
  +.

FUNCTION unspecified (FREQ>):
  [0 -> FREQ].

FUNCTION convert >FREQ to TEXT>:
  [FREQ -> INT],
    convert INT to TEXT.


#########################
#                       #
#  AST transformations  #
#                       #
#########################

### Transform free word order members to final groups:

ACTION transform free word order members to final groups:
  get number of syntax nonterminals (INT),
    transform to final group (INT).

ACTION transform to final group (>INT):
  [INT -> 0];
  [INT - 1 -> INT],
    convert INT to NONTERMINAL,
    transform to final group (NONTERMINAL),
    transform to final group (INT).

ACTION transform to final group (>NONTERMINAL):
  is start nonterminal (NONTERMINAL);
  is lexicon nonterminal (NONTERMINAL);
  get syntax rule (NONTERMINAL, RULE),
    [RULE -> DEFS SPECS],
    transform to final group (DEFS),
    [DEFS SPECS -> RULE],
    store syntax rule (NONTERMINAL, RULE).

ACTION transform to final group (>DEFS>):
  [DEFS -> nil];
  [DEFS -> DEF DEFS],
    transform to final group (DEF),
    transform to final group (DEFS),
    [DEF DEFS -> DEFS];
  ?.

ACTION transform to final group (>DEF>):
  [DEF -> FORMALS at POS with ALTS],
    transform to final group (ALTS),
    [FORMALS at POS with ALTS -> DEF].

ACTION transform to final group (>ALTS>):
  [ALTS -> ALT ALTS],
    transform to final group (ALT),
    transform to final group (ALTS),
    [ALT ALTS -> ALTS];
  [ALTS -> nil];
  ?.

ACTION transform to final group (>ALT>):
  [ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS],
    (is free word order alternative (MEMBERS),
       transform free word order alt to final group (MEMBERS, SECONDPASS),
       [FREQ MEMBERS LOCALS COMMITED SECONDPASS -> ALT];
     +);
  [ALT -> FREQ empty COMMITED SECONDPASS];
  ?.

TEST is free word order alternative (>MEMBERS):
  [MEMBERS -> MEMBER nil],
    [MEMBER -> free word order MEMBERS1];
  -.

# FIXME: transduction
ACTION transform free word order alt to final group (>MEMBERS>, >SECONDPASS>):
  [MEMBERS -> MEMBER nil],
    [MEMBER -> free word order MEMBERS1],
    make fwo final group (MEMBERS1, SECONDPASS, MEMBER2),
    [MEMBER2 nil -> MEMBERS],
    create no SECONDPASS;
  ?.

ACTION make fwo final group (>MEMBERS, >SECONDPASS, MEMBER>):
  [MEMBERS -> MEMBER1 MEMBERS1],
    ([MEMBERS1 -> MEMBER2 nil],
       make fwo alt group (MEMBER1, MEMBER2, SECONDPASS, MEMBER);
     make fwo final group (MEMBERS, nil, SECONDPASS, MEMBER));
  ?.

ACTION make fwo final group (>MEMBERS1, >MEMBERS2, >SECONDPASS, MEMBER>):
  make fwo final group (MEMBERS1, MEMBERS2, SECONDPASS, ALTS),
    generate internal final group (ALTS, MEMBER).

ACTION make fwo final group (>MEMBERS1, >MEMBERS2, >SECONDPASS, ALTS>):
  [MEMBERS1 -> nil],
    [nil -> ALTS];
  [MEMBERS1 -> MEMBER1 MEMBERS1],
    prepend MEMBERS1 to MEMBERS2 into MEMBERS3,
    make fwo final group (MEMBERS3, SECONDPASS, MEMBER),
    create no SECONDPASS1,
    make alt from two members (MEMBER1, MEMBER, SECONDPASS1, ALT),
    [MEMBER1 MEMBERS2 -> MEMBERS2],
    make fwo final group (MEMBERS1, MEMBERS2, SECONDPASS, ALTS),
    [ALT ALTS -> ALTS];
  ?.

FUNCTION prepend >MEMBERS1 to >MEMBERS2 into MEMBERS>:
  [MEMBERS1 -> nil],
    [MEMBERS2 -> MEMBERS];
  [MEMBERS1 -> MEMBER1 MEMBERS1],
    prepend MEMBERS1 to MEMBERS2 into MEMBERS,
    [MEMBER1 MEMBERS -> MEMBERS];
  ?.

ACTION make fwo alt group (>MEMBER1, >MEMBER2, >SECONDPASS, MEMBER>):
  make alt from two members (MEMBER2, MEMBER1, SECONDPASS, ALT),
    [ALT nil -> ALTS],
    make alt from two members (MEMBER1, MEMBER2, SECONDPASS, ALT),
    [ALT ALTS -> ALTS],
    generate internal final group (ALTS, MEMBER).

FUNCTION make alt from two members (>MEMBER1, >MEMBER2, >SECONDPASS, ALT>):
  [MEMBER2 nil -> MEMBERS],
    [MEMBER1 MEMBERS -> MEMBERS],
    make internal alt from members (MEMBERS, SECONDPASS, ALT).

FUNCTION make internal alt from members (>MEMBERS, >SECONDPASS, ALT>):
  unspecified (FREQ),
    [nil -> LOCALS],
    [false -> COMMITED],
    [FREQ MEMBERS LOCALS COMMITED SECONDPASS -> ALT].


###############
#             #
#   Dumping   #
#             #
###############

ACTION dump ast:
  log ("============> BEGIN AST DUMP <============"),
    get number of syntax nonterminals (INT),
    dump syntax rule (INT),
    log ("============> END AST DUMP <============").

ACTION dump syntax rule (>INT):
  [INT -> 0];
  [INT - 1 -> INT],
    convert INT to NONTERMINAL,
    dump syntax nonterminal (NONTERMINAL),
    dump syntax rule (INT).

ACTION dump syntax nonterminal (>NONTERMINAL):
  get syntax rule (NONTERMINAL, RULE),
    [RULE -> DEFS SPECS],
    convert NONTERMINAL to TEXT1,
    get computed type (NONTERMINAL, TEXT3),
    log ("rule for nonterminal " + TEXT3 + " \"" + TEXT1 + "\""),
    dump specs (SPECS),
    dump defs (DEFS),
    get pragmats (NONTERMINAL, PRAGMATS),
    dump pragmats (PRAGMATS).

ACTION dump pragmats (>PRAGMATS):
  convert PRAGMATS to TEXT,
    log ("      pragmats: " + TEXT ).

ACTION dump specs (>SPECS):
  [SPECS -> USPEC ASPEC FSPEC],
    [USPEC -> SPEC],
    convert  SPEC to  TEXT,
    log ("      uspec(" + TEXT + ")"),
    [ASPEC -> SPEC],
    convert  SPEC to  TEXT,
    log ("      aspec(" + TEXT + ")"),
    [FSPEC -> SPEC],
    convert  SPEC to  TEXT,
    log ("      fspec(" + TEXT + ")").

FUNCTION convert >SPEC to TEXT>:
  [SPEC -> empty],
    ["<empty>" -> TEXT];
  [SPEC -> nil],
    ["" -> TEXT];
  [SPEC -> OIDS SPEC1],
    ([SPEC1 -> nil],
       convert OIDS to TEXT;
     convert OIDS to TEXT1,
       convert SPEC1 to TEXT2,
       [TEXT1 + "," + TEXT2 -> TEXT]);
  ?.

FUNCTION convert >OIDS to TEXT>:
  [OIDS -> nil],
    ["" -> TEXT];
  [OIDS -> ID OIDS1],
    ([OIDS1 -> nil],
       convert id to name (ID, TEXT);
     convert id to name (ID, TEXT1),
       convert  OIDS1 to  TEXT2,
       [TEXT1 + "|" + TEXT2 -> TEXT]);
  [OIDS -> collected ID],
    convert id to name (ID, TEXT1),
    ["[" + TEXT1 + "]" -> TEXT];
  ?.

ACTION dump defs (>DEFS):
  [DEFS -> nil];
  [DEFS -> DEF DEFS],
    dump def (DEF),
    dump defs (DEFS);
  ?.

ACTION dump def (>DEF):
  [DEF -> FORMALS at POS with ALTS],
    log ("--- Begin def ---\nformals: "),
    [FORMALS -> EXPRS],
    dump (EXPRS),
    log ("with alts: "),
    dump (ALTS),
    log ("--- End def ---").

ACTION dump (>ALTS):
  convert ALTS to TEXT,
    log (TEXT).

FUNCTION convert >ALTS to TEXT>:
  [ALTS -> nil],
    ["" -> TEXT];
  [ALTS -> ALT ALTS],
    convert ALT to TEXT1,
    convert ALTS to TEXT2,
    [TEXT1 + TEXT2 -> TEXT];
  ?.

FUNCTION convert >ALT to TEXT>:
  [ALT -> FREQ MEMBERS LOCALS COMMITED SECONDPASS],
    convert FREQ to TEXT1,
    convert COMMITED to TEXT2,
    ["Frequency: " + TEXT1 + "; " + TEXT2 + " -- begin locals --" -> TEXT],
    [LOCALS -> EXPRS],
    convert EXPRS to TEXT3,
    [TEXT + TEXT3 + " -- end locals --" -> TEXT],
    convert MEMBERS to TEXT4,
    convert SECONDPASS to TEXT5,
    [TEXT + TEXT4 + TEXT5 + "\n" -> TEXT];
  [ALT -> FREQ empty COMMITED SECONDPASS],
    convert FREQ to TEXT1,
    convert COMMITED to TEXT2,
    convert SECONDPASS to TEXT3,
    ["Frequency: " + TEXT1 + "; " + TEXT2 + "\nempty alt" + TEXT3 + "\n" -> TEXT];
  ?.

FUNCTION convert >COMMITED to TEXT>:
  [ COMMITED -> true ],  [ "commited" -> TEXT ];
  [ COMMITED -> false ], [ "not commited" -> TEXT ];
  ?.

ACTION dump (>MEMBERS):
  convert MEMBERS to TEXT, log (TEXT).

FUNCTION convert >MEMBERS to TEXT>:
  [ MEMBERS -> nil ], [ "" -> TEXT ];
  [ MEMBERS -> MEMBER MEMBERS ],
    convert MEMBER to TEXT1,
    convert MEMBERS to TEXT2,
    [ TEXT1 + "\n" + TEXT2 -> TEXT ];
  ?.

FUNCTION convert >MEMBER to TEXT>:
  [ MEMBER -> guard GUARDS ], convert GUARDS to TEXT;
  [ MEMBER -> call ID def NONTERMINAL with ACTUALS at POS ],
    convert id to name (ID, TEXT1),
    convert ACTUALS to TEXT2,
    [ "call to " + TEXT1 + " with " + TEXT2 -> TEXT];
  [ MEMBER -> option ALTS with OPT_SPEC at POS ],
    convert OPT_SPEC to TEXT1, convert ALTS to TEXT2,
    [ "option " + TEXT1 + " [" + TEXT1 + "] end of option" -> TEXT];
  [ MEMBER -> group ALTS at POS], convert ALTS to TEXT,
    [ "group (" + TEXT + ") end of group" -> TEXT];
  [ MEMBER -> free word order MEMBERS], convert MEMBERS to TEXT1,
    [ "free word order alt: " + TEXT1 -> TEXT];
  where member is final group (MEMBER, ALTS), convert ALTS to TEXT,
    [ "final group (" + TEXT + ") end of final group" -> TEXT];
  [ MEMBER -> penalty INT], convert INT to TEXT,
    [ "penalty " + TEXT -> TEXT];
  [ MEMBER -> terminal ID using EXPR1 targeting EXPR2 at POS ],
    convert id to name (ID, TEXT1),
    convert EXPR1 to system TEXT2, convert EXPR2 to system TEXT3,
    [ "terminal " + TEXT1 + " using " + TEXT2 + " targeting " + TEXT3 -> TEXT ];
  [ MEMBER -> skip regexp ID using EXPR1 targeting EXPR2 at POS ],
    convert id to name (ID, TEXT1),
    convert EXPR1 to system TEXT2, convert EXPR2 to system TEXT3,
    [ "skip " + TEXT1 + " using " + TEXT2 + " targeting " + TEXT3 -> TEXT ];
  [ MEMBER -> match regexp ID using EXPR1 targeting EXPR2 at POS ],
    convert id to name (ID, TEXT1),
    convert EXPR1 to system TEXT2, convert EXPR2 to system TEXT3,
    [ "match " + TEXT1 + " using " + TEXT2 + " targeting " + TEXT3 -> TEXT ];
  [ MEMBER -> other using EXPR1 targeting EXPR2 at POS ],
    convert EXPR1 to system TEXT1, convert EXPR2 to system TEXT2,
    [ "other using " + TEXT1 + " targeting " + TEXT2 -> TEXT ];
  [ MEMBER -> input pos using EXPR1 targeting EXPR2 at POS ],
    convert EXPR1 to system TEXT1, convert EXPR2 to system TEXT2,
    [ "input pos using " + TEXT1 + " targeting " + TEXT2 -> TEXT ];
  [ MEMBER -> position INT ], convert INT to TEXT,
    [ "position " + TEXT -> TEXT ];
  [ MEMBER -> erroneous ], [ "*erroneous*" -> TEXT ];
  ?.

FUNCTION convert >ACTUALS to TEXT>:
  [ ACTUALS -> EXPRS ], convert EXPRS to TEXT.

ACTION dump (>EXPRS):
  convert EXPRS to TEXT, log (TEXT).

FUNCTION convert >EXPRS to TEXT>:
  [ EXPRS -> nil ], [ "" -> TEXT ];
  [ EXPRS -> EXPR EXPRS ],
    convert EXPR to system TEXT1,
    convert EXPRS to TEXT2,
    [ TEXT1 + "\n" + TEXT2 -> TEXT ];
  ?.

FUNCTION convert >GUARDS to TEXT>:
  [ GUARDS -> nil ], [ "" -> TEXT ];
  [ GUARDS -> GUARD GUARDS ],
    convert GUARD to TEXT1,
    convert GUARDS to TEXT2,
    [ TEXT1 + "\n" + TEXT2 -> TEXT ];
  ?.

FUNCTION convert >GUARD to TEXT>:
  [ GUARD -> erroneous ], [ "*erroneous*" -> TEXT ];
  [ GUARD -> restrict EXPR1 to EXPR2 at POS ],
    convert EXPR1 to system TEXT1,
    convert EXPR2 to system TEXT2,
    [ "restrict expr:" + TEXT1 + " to expr: " + TEXT2 -> TEXT ];
  ?.


#
# Test equivalence of members
# Used by left-factorization.
#
TEST is equivalent (>MEMBER1, >MEMBER2):
  [ MEMBER1 -> guard GUARDS1 ],
    ( [ MEMBER2 -> guard GUARDS2 ],
        is equivalent (GUARDS1, GUARDS2);
      -
    );
  [ MEMBER1 -> call ID1 def NONTERMINAL1 with ACTUALS1 at POS1 ],
    ( [ MEMBER2 -> call ID2 def NONTERMINAL2 with ACTUALS2 at POS2 ],
        [ NONTERMINAL1 = NONTERMINAL2 ], is equivalent (ACTUALS1, ACTUALS2);
      -
    );
  [ MEMBER1 -> terminal ID1 using EXPR1 targeting EXPR3 at POS1 ],
    ( [ MEMBER2 -> terminal ID2 using EXPR2 targeting EXPR4 at POS2 ],
        [ ID1 = ID2 ], is equivalent (EXPR3, EXPR4);
      -
    );
  [ MEMBER1 -> skip regexp ID1 using EXPR1 targeting EXPR3 at POS1 ],
    ( [ MEMBER2 -> skip regexp ID2 using EXPR2 targeting EXPR4 at POS2 ],
        [ ID1 = ID2 ], is equivalent (EXPR3, EXPR4);
      -
    );
  [ MEMBER1 -> match regexp ID1 using EXPR1 targeting EXPR3 at POS1 ],
    ( [ MEMBER2 -> match regexp ID2 using EXPR2 targeting EXPR4 at POS2 ],
        [ ID1 = ID2 ], is equivalent (EXPR3, EXPR4);
      -
    );
  [ MEMBER1 -> input pos using EXPR1 targeting EXPR3 at POS ],
    ( [ MEMBER2 -> input pos using EXPR2 targeting EXPR4 at POS ],
	is equivalent (EXPR3, EXPR4);
      -
    );
  [ MEMBER1 -> group ALTS1 at POS1 ],
    ( [ MEMBER2 -> group ALTS2 at POS2 ],
        is equivalent (ALTS1, ALTS2);
      -
    );
  [ MEMBER1 -> penalty INT1 ],
    ( [ MEMBER2 -> penalty INT2 ],
        [ INT1 = INT2 ];
      -
    );
  ?. # final group should not occur

# TODO: concatenate all guards in a rule,
# left-factorize equivalent prefixes of a list of guards.

TEST is equivalent (>GUARDS1, >GUARDS2):
  [GUARDS1 -> nil],
    ([GUARDS2 -> nil];
     -);
  [GUARDS1 -> GUARD1 GUARDS1],
    ([GUARDS2 -> GUARD2 GUARDS2],
       is equivalent (GUARD1, GUARD2),
         is equivalent (GUARDS1, GUARDS2);
     -);
  ?.

TEST is equivalent (>GUARD1, >GUARD2):
  [GUARD1 -> restrict EXPR1 to EXPR2 at POS],
    ([GUARD2 -> restrict EXPR3 to EXPR4 at POS2],
       (is equivalent (EXPR1, EXPR3),
          is equivalent (EXPR2, EXPR4);
        -);
     ?);
  ?.

TEST is equivalent (>ACTUALS1, >ACTUALS2):
  [ ACTUALS1 -> EXPRS1 ], [ ACTUALS2 -> EXPRS2 ], is equivalent (EXPRS1, EXPRS2).

TEST is equivalent (>EXPRS1, >EXPRS2):
  [EXPRS1 -> nil],
    ([EXPRS2 -> nil];
     -);
  [EXPRS1 -> EXPR1 EXPRS1],
    ([EXPRS2 -> EXPR2 EXPRS2],
       is equivalent (EXPR1, EXPR2),
         is equivalent (EXPRS1, EXPRS2);
     -);
  ?.

#
# Equivalent expressions, as used in actuals, are also equal, since
# anonymous affixes representing the same set are mapped onto the
# same id by affix.k3. TODO: check this!
#
TEST is equivalent (>EXPR1, >EXPR2):
  [ EXPR1 = EXPR2 ].

TEST is equivalent (>ALTS1, >ALTS2):
  [ALTS1 -> nil],
    ([ALTS2 -> nil];
     -);
  [ALTS1 -> ALT1 ALTS1],
    ([ALTS2 -> ALT2 ALTS2],
       is equivalent (ALT1, ALT2),
         is equivalent (ALTS1, ALTS2);
     -);
  ?.

TEST is equivalent (>ALT1, >ALT2):
  [ALT1 -> FREQ1 MEMBERS1 LOCALS1 COMMITED1 SECONDPASS1],
    ([ALT2 -> FREQ2 MEMBERS2 LOCALS2 COMMITED2 SECONDPASS2],
       [FREQ1 = FREQ2],
         [COMMITED1 = COMMITED2],
           [SECONDPASS1 = SECONDPASS2],
             is equivalent (MEMBERS1, MEMBERS2);
     -);
  [ALT1 -> FREQ1 empty COMMITED1 SECONDPASS1],
    ([ALT2 -> FREQ2 empty COMMITED2 SECONDPASS2],
       [FREQ1 = FREQ2],
         [COMMITED1 = COMMITED2],
           [SECONDPASS1 = SECONDPASS2];
     -);
  ?.

TEST is equivalent (>MEMBERS1, >MEMBERS2):
  [MEMBERS1 -> nil],
    ([MEMBERS2 -> nil];
     -);
  [MEMBERS1 -> MEMBER1 MEMBERS1],
    ([MEMBERS2 -> MEMBER2 MEMBERS2],
       is equivalent (MEMBER1, MEMBER2),
         is equivalent (MEMBERS1, MEMBERS2);
     -);
  ?.

#
# Lexicon Interface File writing
#
ACTION write lexicon rules to lif:
  get number of syntax nonterminals (INT),
    maybe write rules to lif (INT),
    write separator to lif.

ACTION maybe write rules to lif (>INT):
  [INT -> 0];
  [INT - 1 -> INT],
    maybe write rule to lif (INT),
    maybe write rules to lif (INT).

ACTION maybe write rule to lif (>INT):
  convert INT to NONTERMINAL,
    (is lexicon nonterminal (NONTERMINAL),
       convert NONTERMINAL to ID and ARITY,
       convert id to name (ID, TEXT),
       write to lif (TEXT),
       convert ARITY to TEXT,
       write to lif ("\t" + TEXT),
       convert INT to TEXT,
       write to lif ("\t" + TEXT),
       write formals to lif (NONTERMINAL),
       write to lif ("\n");
     +).

ACTION write formals to lif (>NONTERMINAL):
  get definitions (NONTERMINAL, DEFS),
    produce lex nont args (DEFS).
 
ACTION produce lex nont args (>DEFS):
  [DEFS -> DEF DEFS1],
    [DEF -> FORMALS at POS with ALTS],
    [FORMALS -> EXPRS],
    produce lex nont args (EXPRS);
  [DEFS -> nil],
    write ("Ieks: DEFS is nil!\n"),
    ?;
   ?.
 
ACTION produce lex nont args (>EXPRS):
  [EXPRS -> nil];
  [EXPRS -> EXPR EXPRS],
    produce lex nont arg (EXPR),
    produce lex nont args (EXPRS);
   ?.
 
ACTION produce lex nont arg (>EXPR):
  [EXPR -> affix ID],
    (id is set affix (ID),
       convert id to name (ID, TEXT),
       write to lif ("\t" + TEXT);
     id is integer affix (ID),
       write to lif ("\tINT");
     id is text affix (ID),
       write to lif ("\tTEXT");
     ?);
   ?.

