Blob Blame History Raw
# Suomi-malaga, suomen kielen muoto-opin kuvaus.
#
# Tekijänoikeus © 2006 Hannu Väisänen (Etunimi.Sukunimi@joensuu.fi)
#                 2006 - 2010 Harri Pitkänen (hatapitk@iki.fi)
#
# Tämä ohjelma on vapaa; tätä ohjelmaa on sallittu levittää
# edelleen ja muuttaa GNU yleisen lisenssin (GPL lisenssin)
# ehtojen mukaan sellaisina kuin Free Software Foundation
# on ne julkaissut; joko Lisenssin version 2, tai (valinnan
# mukaan) minkä tahansa myöhemmän version mukaisesti.
#
# Tätä ohjelmaa levitetään siinä toivossa, että se olisi
# hyödyllinen, mutta ilman mitään takuuta; ilman edes
# hiljaista takuuta kaupallisesti hyväksyttävästä laadusta tai
# soveltuvuudesta tiettyyn tarkoitukseen. Katso GPL
# lisenssistä lisää yksityiskohtia.
#
# Tämän ohjelman mukana pitäisi tulla kopio GPL
# lisenssistä; jos näin ei ole, kirjoita osoitteeseen Free
# Software Foundation Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
#
# Tämän ohjeman linkittäminen staattisesti tai dynaamisesti
# muihin moduuleihin on ohjelmaan perustuvan teoksen
# tekemistä, joka on siis GPL lisenssin ehtojen alainen.
#
#
# 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, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING.  If not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#
# Linking this program statically or dynamically with other modules is
# making a combined work based on this program.  Thus, the terms and
# conditions of the GNU General Public License cover the whole
# combination.


initial <>, rules erisnimi, laatusana,
                  lukusana, sanan_alku, teonsana,
                  laatusana_laatusanasta, paikannimen_lainen_johdos;

include "config.inc";
include "suomi.inc";
include "mor.inc";

# Palauttaa yes, jos $sana voi liittyä listaan $vasen yhdyssanassa,
# jossa yhdysviiva käyttö on sallittua ja pakollista täsmälleen silloin,
# kun alkuosa päättyy samaan vokaaliin jolla loppuosa alkaa.
# Listan $vasen on sisällettävä vähintään yksi elementti.
subrule vokaaliehdollinen_yhdysviiva_ok($vasen, $sana):
  define $i := length($vasen);
  if ($vasen.$i.luokka = tavuviiva) then
    if ($i = 1) then
      return yes;
    end;
    if $vasen.$i.vokaaliehto = no then
      return yes;
    end;
    define $edellinen := $vasen.($i - 1);
    if (vokaali(first($sana)) and first($sana) = last($edellinen.alku)) then
      return yes;
    # is- ("teknillis-tieteellinen", "Kaakkois-Suomi")
    # FIXME: tämä ei ole tarkalleen ottaen oikein, mutta toistaiseksi riittävän hyvä
    elseif ($edellinen.alku matches ".*is") then
      return yes;
    elseif $edellinen.tiedot /= nil and sitlaina in $edellinen.tiedot then
      return yes;
    else
      return no;
    end;
  else
    if (vokaali(first($sana)) and first($sana) = last($vasen.$i.alku)) then
      return no;
    else
      return yes;
    end;
  end;
end;

define @ehtotapa := <ehtotapa, ehtotapa_ttA, ehtotapa_tA>;

define @mahtotapa := <mahtotapa_le, mahtotapa_ne, mahtotapa_re, mahtotapa_se,
                      mahtotapa_ttA, mahtotapa_tA>;

define @käskytapa := <käskytapa, käskytapa_ttA,
                      käskytapa_tA>;

define @nimitapa_1 := <nimitapa_1_A,  nimitapa_1_dA, nimitapa_1_lA,
                       nimitapa_1_nA, nimitapa_1_rA, nimitapa_1_tA>;
define @nimitapa_2 := <nimitapa_2, nimitapa_2_ttA, nimitapa_2_tA>;
define @nimitapa_3 := <nimitapa_3, nimitapa_3_ttA, nimitapa_3_tA>;

# Johtimet teonsanoista

define @johdin_tU := <johdin_dU, johdin_lU, johdin_nU, johdin_rU, johdin_tU, johdin_tU_lU_oltu>;

define @johtimet_nimisana_teonsanasta :=
       <johdin_mA, johdin_UUs, johdin_Us, johdin_ntA, johdin_nti, johdin_Us_ksen>
       + @johdin_jA;

define @johtimet_laatusana_teonsanasta :=
       <johdin_tOn, johdin_vA, johdin_vE, johdin_tAvA, johdin_ttAvA,
        johdin_ttU> + @johdin_nUt + @johdin_tU;

define @johtimet_seikkasana_teonsanasta := <johdin_ittAin>; # esim. "pamahduksittain"

define @johdin_teonsanasta := @johtimet_nimisana_teonsanasta
                            + @johtimet_laatusana_teonsanasta
                            + @johtimet_seikkasana_teonsanasta;


# Johtimet nimisanoista

define @johtimet_nimisana_nimisanasta := <johdin_tAr, johdin_lAinen>;

define @johtimet_laatusana_nimisanasta := <johdin_inen, johdin_tOn, johdin_mAinen>;

define @johdin_nimisanasta := @johtimet_nimisana_nimisanasta
                            + @johtimet_laatusana_nimisanasta
                            + <johdin_ittAin>
                            + @johdin_jA; #FIXME: onko tämä oikein?


# Johtimet laatusanoista
define @johtimet_nimisana_laatusanasta := <johdin_Us, johdin_UUs>;
define @johtimet_laatusana_laatusanasta := <johdin_nlainen>;
define @johtimet_laatusanasta := @johtimet_nimisana_laatusanasta
                               + @johtimet_laatusana_laatusanasta;

combi_rule sanan_alku ($vasen, $oikea, $sana):
  if $oikea.luokka = lyhenne then
    if (kaksoispiste in $oikea.jatko) then
      result <[alku: $sana] + $oikea>, rules kaksoispiste;
    end;
    if (tavuviiva in $oikea.jatko) then
      result <[alku: $sana] + $oikea>, rules yhdysmerkki_lyhenteen_jälkeen;
    end;
    if (loppu in $oikea.jatko) then
      result <[alku: $sana] + $oikea>, accept;
    end;


  elseif $oikea.luokka in <sidesana, huudahdussana> then
    result <[alku: $sana] + $oikea>, accept;

  elseif $oikea.luokka = etuliite then
    if nimisana in $oikea.jatko and laatusana in $oikea.jatko then
      result <[alku: $sana] + $oikea>, rules
        inen_johdos_nimisanasta, laatusana, paikannimen_lainen_johdos;
    elseif nimisana in $oikea.jatko then
      result <[alku: $sana] + $oikea>, rules inen_johdos_nimisanasta, paikannimen_lainen_johdos;
    elseif laatusana in $oikea.jatko then
      result <[alku: $sana] + $oikea>, rules laatusana, paikannimen_lainen_johdos, inen_johdos_nimisanasta;
    end;
    if tavuviiva in $oikea.jatko then
      result <[alku: $sana] + $oikea>, rules yhdysmerkki_etuliitteen_jälkeen;
    else
      result <[alku: $sana] + $oikea>, accept;
    end;
    if teonsana in $oikea.jatko then
      result <[alku: $sana] + $oikea>, rules teonsana;
    elseif nimisana in $oikea.jatko then
      result <[alku: $sana] + $oikea>, rules nimisana_teonsanasta;
    end;
    if teonsanan_johdoksen_etuliite in $oikea.jatko then
      result <[alku: $sana] + $oikea>, rules nimisana_teonsanasta;
    end;
    result <[alku: $sana] + $oikea>, rules etuliite, nimisana;

  elseif $oikea.luokka = kieltosana then
    if kieltosanan_liitesana in $oikea.jatko then
      result <[alku: $sana] + $oikea>, rules kieltosanan_liitesana;
    end;
    result <[alku: $sana] + $oikea>, accept;

  elseif $oikea.luokka in <seikkasana, suhdesana> then
    if (loppu in $oikea.jatko) then
      result <[alku: $sana] + $oikea>, accept;
    end;
    if (liitesana in $oikea.jatko) then
      result <[alku: $sana] + $oikea>, rules liitesana;
    end;
    if (omistusliite in $oikea.jatko) then
      result <[alku: $sana] + $oikea>, rules omistusliite;
    end;
    result <[alku: $sana] + $oikea>, rules ei_ys_sijapääte; # FIXME: esiehto testaamatta

  elseif $oikea.luokka = asemosana then
    if loppu in $oikea.jatko then
      result <[alku: $sana] + $oikea>, accept;
    end;
    if liitesana in $oikea.jatko then
      result <[alku: $sana] + $oikea>, rules liitesana;
    end;
    result <[alku: $sana] + $oikea>,
           rules asemosanan_sijapääte;

  elseif $oikea.luokka = kieltosanan_etuliite then
    result <[alku: $sana] + $oikea>, rules kieltosana;

  elseif $oikea.luokka = tavuviiva then
    result <[vokaaliehto: no, alku: "-"] + $oikea>,
           rules nimisana, erisnimi, laatusana, lyhennesääntö,
                 etuliite, nimisana_teonsanasta, lukusanan_jälkiliite, inen_johdos_nimisanasta_ehdollinen,
                 lukusana;

  elseif $oikea.luokka in <nimisana, nimi_laatusana> then
    ? $oikea.tiedot = nil or not ysj in $oikea.tiedot;

    if ($oikea.luokka = nimisana and voittoaste in $oikea.jatko) then
      result <[alku: $sana] + $oikea>, rules voittoaste;
    end;
    if (tavuviiva in $oikea.jatko and ($oikea.tiedot = nil or
        (not ei_ysa in $oikea.tiedot and not ei_ys in $oikea.tiedot))) then
      result <[alku: $sana] + $oikea>, rules nimisana, nimisana_laatusanasta,
             nimisana_teonsanasta, nimi_laatusanan_etuliite, paikannimen_lainen_johdos,
             inen_johdos_nimisanasta,
             inen_päätteinen_laatusana_ei_nimisanajohdosta; # Kieli=poliittinen
      if ($oikea.luokka = nimisana or $oikea.aluetta_tarkentava_etuliite = nil) then
        result <[alku: $sana] + $oikea>, rules yhdysmerkki_yleisnimen_jälkeen;
      end;
    end;
    if loppu in $oikea.jatko then
      if $oikea.etuliite = yes then
        result <[alku: $sana] + $oikea + [luokka: etuliite]>, accept;
      else
        result <[alku: $sana] + $oikea>, accept;
      end;
    end;
    if omistusliite in $oikea.jatko then
      result <[alku: $sana] + $oikea>, rules omistusliite;
    end;
    if liitesana in $oikea.jatko then
      result <[alku: $sana] + $oikea>, rules liitesana;
    end;
    if $oikea.luokka = nimisana then
      result <[alku: $sana] + $oikea>, rules nimisanan_sijapääte, nimisanan_johdos;
    else
      result <$oikea + [alku: $sana, luokka: nimisana, jatko: $oikea.jatko - <johdin_UUs>]>,
             rules nimisanan_sijapääte, nimisanan_johdos;
    end;
  end;
end;

combi_rule etuliite ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = etuliite;

  ? $oikea.tiedot = nil or not ei_ysj in $oikea.tiedot;
  ? vokaaliehdollinen_yhdysviiva_ok($vasen, $sana);
  define $res := $vasen + <[alku: $sana] + $oikea>;

  if nimisana in $oikea.jatko and laatusana in $oikea.jatko then
    result $res, rules
      inen_johdos_nimisanasta, laatusana, paikannimen_lainen_johdos, nimisana_teonsanasta;
  elseif nimisana in $oikea.jatko then
    result $res, rules inen_johdos_nimisanasta, paikannimen_lainen_johdos, nimisana_teonsanasta;
  elseif laatusana in $oikea.jatko then
    result $res, rules laatusana, paikannimen_lainen_johdos, inen_johdos_nimisanasta;
  end;

  if (teonsana in $oikea.jatko) then
    result $res, rules teonsana;
  end;
  if teonsanan_johdoksen_etuliite in $oikea.jatko then
      result $res, rules nimisana_teonsanasta;
  end;

  if (kieltosana in $oikea.jatko) then
    result $res, rules kieltosana;
  else
    if tavuviiva in $oikea.jatko then
      result $res, rules yhdysmerkki_etuliitteen_jälkeen;
    else
      result $res, accept;
    end;
    result $res, rules etuliite, nimisana;
  end;
end;

###### ETULIITESÄÄNNÖT ALKAVAT

combi_rule nimisanan_etuliite ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = etuliite;

  ? $oikea.tiedot = nil or not ei_ysj in $oikea.tiedot;
  ? vokaaliehdollinen_yhdysviiva_ok($vasen, $sana);
  define $res := $vasen + <[alku: $sana] + $oikea>;

  if nimisana in $oikea.jatko then
    result $res, rules inen_johdos_nimisanasta, nimisana_laatusanasta;
  end;

  ? not kieltosana in $oikea.jatko;
  if tavuviiva in $oikea.jatko then
    result $res, rules yhdysmerkki_etuliitteen_jälkeen;
  end;
  result $res, rules etuliite, nimisana, nimisana_teonsanasta;
end;

combi_rule nimi_laatusanan_etuliite ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = etuliite;

  ? $oikea.tiedot = nil or not ei_ysj in $oikea.tiedot;
  ? vokaaliehdollinen_yhdysviiva_ok($vasen, $sana);
  define $res := $vasen + <[alku: $sana] + $oikea>;

  if nimisana in $oikea.jatko and laatusana in $oikea.jatko then
    result $res, rules
      inen_johdos_nimisanasta, laatusana, paikannimen_lainen_johdos;
  elseif nimisana in $oikea.jatko then
    result $res, rules inen_johdos_nimisanasta, paikannimen_lainen_johdos;
  elseif laatusana in $oikea.jatko then
    result $res, rules laatusana, paikannimen_lainen_johdos, inen_johdos_nimisanasta;
  end;

  if (kieltosana in $oikea.jatko) then
    result $res, rules kieltosana;
  else
    if tavuviiva in $oikea.jatko then
      result $res, rules yhdysmerkki_etuliitteen_jälkeen;
    else
      result $res, accept;
    end;
    result $res, rules etuliite, nimisana, nimisana_teonsanasta;
  end;
end;

###### ETULIITESÄÄNNÖT PÄÄTTYVÄT


combi_rule erisnimi ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka in @erisnimi;
  ? ($oikea.tiedot = nil) or ($index = 1) or
    (not ei_ysj in $oikea.tiedot and not ei_ys in $oikea.tiedot);

  define $res := $vasen + <[alku: $sana] + $oikea>;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  if tavuviiva in $oikea.jatko or loppu in $oikea.jatko then
    result $res, rules yhdysmerkki_erisnimen_jälkeen;
  end;
  result $res, rules erisnimen_sijapääte;
end;


# Esiehto: ei sanan alussa
combi_rule nimisana ($vasen, $oikea, $sana, $index):
  if ($oikea.luokka = seikkasana and $oikea.tiedot /= nil and
      ys_perusosa in $oikea.tiedot) then
    if (loppu in $oikea.jatko) then
      result $vasen + <[alku: $sana] + $oikea>, accept;
    end;
    if (liitesana in $oikea.jatko) then
      result $vasen + <[alku: $sana] + $oikea>, rules liitesana;
    end;
    if (omistusliite in $oikea.jatko) then
      result $vasen + <[alku: $sana] + $oikea>, rules omistusliite;
    end;
    result $vasen + <[alku: $sana] + $oikea>, rules ei_ys_sijapääte; # FIXME: esiehto testaamatta
  end;

  ? $oikea.luokka in <nimisana, nimi_laatusana>;

  define $edellinen := $vasen.($index - 1);

  ? ((nimisana in $edellinen.jatko)    or
     (tavuviiva in $edellinen.jatko and $edellinen.luokka in @nimisana + <laatusana>) or
     ($edellinen.luokka = lukusana)    or
     ($oikea.lähtöluokka = teonsana and
      (teonsana in $edellinen.jatko or teonsanan_johdoksen_etuliite in $edellinen.jatko)));

  ? ($oikea.tiedot = nil) or
    (not ei_ysj in $oikea.tiedot and not ei_ys in $oikea.tiedot);
  ? vokaaliehdollinen_yhdysviiva_ok($vasen, $sana) or ($oikea.tiedot /= nil and sitlaina in $oikea.tiedot and $edellinen.luokka = tavuviiva);

  define $res := $vasen + <[alku: $sana] + $oikea>;
  
  if $edellinen.luokka in <laatusana, nimi_laatusana> then
    if $edellinen.luokka = laatusana then
      result $res, rules johdin_laatusana_nimisanasta;
      stop;
    end;
  elseif $edellinen.lukutyyppi /= nil then
    if tavuviiva in $oikea.jatko then
      result $res, rules etuliite, tavuviiva, nimisana, nimisana_laatusanasta, nimisana_teonsanasta;
    end;
    stop;
  end;

  if ($oikea.luokka = nimisana and voittoaste in $oikea.jatko) then
    result $res, rules voittoaste;
  end;
  if (tavuviiva in $oikea.jatko and ($oikea.tiedot = nil or
      (not ei_ysa in $oikea.tiedot and not ei_ys in $oikea.tiedot))) then
    result $res, rules nimisana, nimisana_laatusanasta, nimisana_teonsanasta,
                       etuliite, paikannimen_lainen_johdos, yhdysmerkki_yleisnimen_jälkeen,
                       inen_johdos_nimisanasta, inen_päätteinen_laatusana_ei_nimisanajohdosta; # Kieli=poliittinen
  end;
  
  $res := $vasen + <$oikea + [alku: $sana, luokka: nimisana, lähtöluokka: $oikea.luokka]>;
  
  if (loppu in $oikea.jatko) then
    result $res, accept;
  end;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  result $res, rules nimisanan_sijapääte, nimisanan_johdos;
  
  if $oikea.erisnimen_pääte = yes and $vasen.(1).luokka /= tavuviiva then
    $res := $vasen + <$oikea + [alku: $sana, luokka: nimi]>;
    if omistusliite in $oikea.jatko then
      result $res, rules omistusliite;
    end;
    if loppu in $oikea.jatko then
      result $res, accept;
    end;
    if liitesana in $oikea.jatko then
      result $res, rules liitesana;
    end;
    result $res, rules erisnimen_sijapääte;
  end;
end;

###### JOHDETTUJEN SANOJEN ALOITUSSÄÄNNÖT ALKAVAT

# Teonsanasta johdettu nimisana, esim. sanoma <- sanoa.
combi_rule nimisana_teonsanasta ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = teonsana;
  if ($index greater 1) then
    ? vokaaliehdollinen_yhdysviiva_ok($vasen, $sana);
  end;
  ? $oikea.tiedot = nil or not ei_ysj in $oikea.tiedot;

  define $res := $vasen + <[alku: $sana] + $oikea>;
  if nimitapa_45 in $oikea.jatko then
    result $res, rules nimitapa_4;
  end;
  result $res, rules teonsanan_johdos; # FIXME: johdetun muodon on oltava nimisana
end;

# Teonsanasta johdettu laatusana, esim. kasvattava <- kasvattaa.
combi_rule laatusana_teonsanasta ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = teonsana;
  if ($index greater 1) then
    ? vokaaliehdollinen_yhdysviiva_ok($vasen, $sana);
  end;
  ? $oikea.tiedot = nil or not ei_ysj in $oikea.tiedot;

  define $res := $vasen + <[alku: $sana] + $oikea>;
  result $res, rules johdin_laatusana_teonsanasta;
end;

# Laatusanasta johdettu nimisana, esim. vapaus <- vapaa.
combi_rule nimisana_laatusanasta ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka in @laatusana;
  ? $oikea.tiedot = nil or (not ei_ys in $oikea.tiedot and not ei_ysj in $oikea.tiedot);
  ? vokaaliehdollinen_yhdysviiva_ok($vasen, $sana);
  result $vasen + <[alku: $sana] + $oikea>,
         rules johdin_nimisana_laatusanasta;
end;

# Laatusanasta johdettu laatusana, esim. paha+nlai+nen <- paha.
combi_rule laatusana_laatusanasta ($vasen, $oikea, $sana):
  ? $oikea.luokka in @laatusana;
  if (omanto_n in $oikea.jatko) then
    result $vasen + <[alku: $sana] + $oikea>, rules johdin_nlainen;
  end;
end;

# inen-johdinta edeltävä nimisana
# sääntö ei voi olla sanan alussa
combi_rule inen_johdos_nimisanasta ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka in <nimisana, nimi_laatusana>;
  ? johdin_inen in $oikea.jatko;
  ? $oikea.tiedot = nil or (not ei_ys in $oikea.tiedot and not ei_ysj in $oikea.tiedot);
  ? vokaaliehdollinen_yhdysviiva_ok($vasen, $sana);

  define $res := $vasen + <[alku: $sana] + $oikea>;
  define $edellinen := $vasen.($index - 1);
  if tavuviiva in $oikea.jatko and $edellinen.luokka /= etuliite then
    result $res, rules johdin_inen, inen_johdos_nimisanasta;
  else
    result $res, rules johdin_inen;
  end;
end;

# inen-johdinta edeltävä nimisana
# sääntö ei voi olla sanan alussa eikä koske sanoja, joille inen-johdin on aina sallittu
combi_rule inen_johdos_nimisanasta_ehdollinen ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka in <nimisana, nimi_laatusana>;
  ? johdin_inen in $oikea.jatko;
  ? $oikea.tiedot = nil or (not ei_ys in $oikea.tiedot and not ei_ysj in $oikea.tiedot and not inen in $oikea.tiedot);
  ? vokaaliehdollinen_yhdysviiva_ok($vasen, $sana);

  define $res := $vasen + <[alku: $sana] + $oikea>;
  define $edellinen := $vasen.($index - 1);
  if tavuviiva in $oikea.jatko and $edellinen.luokka /= etuliite then
    result $res, rules johdin_inen, inen_johdos_nimisanasta;
  else
    result $res, rules johdin_inen;
  end;
end;

# Paikannimestä johdettu kansallisuuden nimi
combi_rule paikannimen_lainen_johdos ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = paikannimi;
  ? johdin_lAinen in $oikea.jatko;
  if ($index greater 1) then
    ? vokaaliehdollinen_yhdysviiva_ok($vasen, $sana);
  end;
  result $vasen + <[alku: $sana] + $oikea>, rules paikannimen_lainen_johdin;
end;

###### JOHDETTUJEN SANOJEN ALOITUSSÄÄNNÖT PÄÄTTYVÄT

###### JOHDINSÄÄNNÖT ALKAVAT

combi_rule johdin_nlainen ($vasen, $oikea, $sana):
  ? $oikea.luokka = johdin_nlainen;

  define $res := $vasen + <[alku: $sana] + $oikea>;
  if tavuviiva in $oikea.jatko then
    result $res, rules nimisana, laatusana,
           tavuviiva, etuliite;
  end;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  result $res,
         rules laatusanan_sijapääte,
               johdin_nimisana_laatusanasta,
               voitto_yliaste;
end;

combi_rule johdin_inen ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = johdin_inen;

  $oikea.äs := $vasen.($index - 1).äs;
  define $res := $vasen + <[alku: $sana] + $oikea>;
  if tavuviiva in $oikea.jatko then
    result $res, rules nimisana, laatusana,
           tavuviiva, etuliite, nimisana_teonsanasta;
  end;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
    result $res, rules laatusanan_sijapääte,
                 johdin_nimisana_laatusanasta,
                 voitto_yliaste;
end;

combi_rule johdettu_paikannimi ($vasen, $oikea, $sana):
  ? $oikea.paikannimen_jälkiliite = yes;
  ? vokaaliehdollinen_yhdysviiva_ok($vasen, $sana);
  define $res := $vasen + <[alku: $sana] + $oikea>;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  if tavuviiva in $oikea.jatko then
    result $res, rules yhdysmerkki_erisnimen_jälkeen, johdettu_paikannimi;
  end;
  result $res, rules erisnimen_sijapääte;
end;

combi_rule paikannimen_lainen_johdin ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = johdin_lAinen;
  ? vokaalisointu_oikein_ei_aä($vasen.($index - 1), $oikea);

  define $res := $vasen + <[alku: $sana] + $oikea>;
  if tavuviiva in $oikea.jatko then
    result $res, rules nimisana, inen_johdos_nimisanasta_ehdollinen, yhdysmerkki_lainen_johdoksen_jälkeen,
                 nimisana_teonsanasta, paikannimen_lainen_johdos, inen_päätteinen_laatusana_ei_nimisanajohdosta, nimisana_laatusanasta;
  end;
  if omistusliite in $oikea.jatko then result $res, rules omistusliite; end;
  if liitesana in $oikea.jatko then result $res, rules liitesana; end;
  if loppu in $oikea.jatko then result $res, accept; end;
  if johdin_ittAin in $oikea.jatko then result $res, rules johdin_seikkasana_ittain; end;
  if voittoaste in $oikea.jatko or yliaste in $oikea.jatko then result $res, rules voitto_yliaste; end;
  result $res, rules laatusanan_sijapääte_omistusliitteellä, johdin_nimisana_laatusanasta;
end;

###### JOHDINSÄÄNNÖT PÄÄTTYVÄT


combi_rule laatusana ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka in @laatusana;

  if ($index greater 1) then
    ? vokaaliehdollinen_yhdysviiva_ok($vasen, $sana);
    ? $oikea.tiedot = nil or (not ei_ys in $oikea.tiedot and not ei_ysj in $oikea.tiedot);
  else
    ? $oikea.tiedot = nil or not ysj in $oikea.tiedot;
  end;

  define $lähtöluokka := $oikea.luokka;
  define $res := $vasen + <$oikea + [alku: $sana, luokka: laatusana, lähtöluokka: $lähtöluokka]>;
  
  if $oikea.luokka = laatusana or ($index greater 1 and $vasen.($index - 1).luokka /= nimitapa_45) then
    if loppu in $oikea.jatko then
      result $res, accept;
    end;
    if liitesana in $oikea.jatko then
      result $res, rules liitesana;
    end;
  end;
  
  if tavuviiva in $oikea.jatko and
    ($oikea.tiedot = nil or not (ei_ys in $oikea.tiedot or ei_ysa in $oikea.tiedot)) then
    if $oikea.luokka /= laatusana or $oikea.inen_loppu = yes then
      result $res, rules laatusana;
    else
      result $res, rules inen_päätteinen_laatusana;
    end;
    if ($oikea.luokka = laatusana or $index greater 1) then
      result $res, rules etuliite, nimisana, inen_johdos_nimisanasta;
    end;
    if $oikea.aluetta_tarkentava_etuliite = yes then
      result $res, rules yhdysmerkki_laatusanan_jälkeen_ei_kansallisuutta;
    else
      result $res, rules yhdysmerkki_laatusanan_jälkeen;
    end;
  end;
  result $res,
         rules laatusanan_sijapääte,
               johdin_nimisana_laatusanasta,
               voitto_yliaste;
end;


combi_rule inen_päätteinen_laatusana ($vasen, $oikea, $sana, $index):
  ? $oikea.inen_loppu = yes;

  ? vokaaliehdollinen_yhdysviiva_ok($vasen, $sana);
  ? $oikea.tiedot = nil or (not ei_ys in $oikea.tiedot and not ei_ysj in $oikea.tiedot);

  # nimisana, laatusana ja etuliite jätetty pois, koska todennäköinen käyttö vähäistä
  # (teoriassa voi olla OK)
  define $res := $vasen + <$oikea + [alku: $sana, luokka: laatusana]>;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if tavuviiva in $oikea.jatko then
      result $res, rules tavuviiva, paikannimen_lainen_johdos;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  result $res, rules laatusanan_sijapääte, johdin_nimisana_laatusanasta, voitto_yliaste;
end;


combi_rule inen_päätteinen_laatusana_ei_nimisanajohdosta ($vasen, $oikea, $sana, $index):
  ? $oikea.inen_loppu = yes;

  ? vokaaliehdollinen_yhdysviiva_ok($vasen, $sana);
  ? $oikea.tiedot = nil or (not ei_ys in $oikea.tiedot and not ei_ysj in $oikea.tiedot);

  # nimisana, laatusana ja etuliite jätetty pois, koska todennäköinen käyttö vähäistä
  # (teoriassa voi olla OK)
  define $res := $vasen + <$oikea + [alku: $sana, luokka: laatusana]>;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if tavuviiva in $oikea.jatko then
      result $res, rules tavuviiva, paikannimen_lainen_johdos;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  result $res, rules laatusanan_sijapääte, voitto_yliaste;
end;


combi_rule lyhennesääntö ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = lyhenne;
  ? $oikea.alaluokka /= roomalainen;
  ? $index less 2 or $oikea.tiedot = nil or (not ei_ys in $oikea.tiedot and not ei_ysj in $oikea.tiedot);

  define $res := $vasen + <[alku: $sana] + $oikea>;
  if (kaksoispiste in $oikea.jatko) then result $res, rules kaksoispiste; end;
  if (tavuviiva in $oikea.jatko) then result $res, rules yhdysmerkki_lyhenteen_jälkeen; end;
  if (loppu in $oikea.jatko) then result $res, accept; end;
end;


combi_rule kaksoispiste ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = kaksoispiste;

  result $vasen + <[alku: $sana] + $oikea + [äs: $vasen.($index - 1).äs]>,
         rules kaksoispisteen_sijapääte, omistusliite, liitesana;
end;


combi_rule tavuviiva ($vasen, $oikea, $sana):
  ? $oikea.luokka = tavuviiva;

  define $res := $vasen + <[vokaaliehto: yes, alku: "-"] + $oikea>;
  result $res, accept;
  result $res,
         rules nimisana, erisnimi, laatusana, lyhennesääntö,
               etuliite, nimisana_teonsanasta;
end;


###### YHDYSMERKKISÄÄNNÖT ALKAVAT

combi_rule yhdysmerkki_yleisnimen_jälkeen ($vasen, $oikea, $sana):
  ? $oikea.luokka = tavuviiva;

  define $res := $vasen + <[vokaaliehto: yes, alku: "-"] + $oikea>;
  result $res, accept;
  result $res,
         rules nimisana, erisnimi, laatusana, lyhennesääntö, etuliite,
	       nimisana_teonsanasta, inen_johdos_nimisanasta_ehdollinen;
end;

combi_rule yhdysmerkki_erisnimen_jälkeen ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = tavuviiva;

  define $edellinen := $vasen.($index - 1);
  define $res := $vasen + <[vokaaliehto: no, alku: "-"] + $oikea>;
  result $res, accept;
  result $res, rules erisnimi;
  if ($edellinen.sijamuoto in <nimentö, omanto> or
      ($edellinen.luokka /= sijapääte and ($edellinen.luokka /= nimi or tavuviiva in $edellinen.jatko or liitesana in $edellinen.jatko))) then
    result $res, rules
           nimisana, nimisana_laatusanasta,
           etuliite, nimisana_teonsanasta, inen_päätteinen_laatusana_ei_nimisanajohdosta, inen_johdos_nimisanasta_ehdollinen;
  end;
end;

combi_rule yhdysmerkki_teonsanan_jälkeen ($vasen, $oikea, $sana):
  ? $oikea.luokka = tavuviiva;

  define $res := $vasen + <[vokaaliehto: yes, alku: "-"] + $oikea>;
  result $res, accept;
  result $res, rules nimisana, erisnimi, lyhennesääntö, etuliite;
end;

combi_rule yhdysmerkki_lyhenteen_jälkeen ($vasen, $oikea, $sana):
  ? $oikea.luokka = tavuviiva;

  define $res := $vasen + <[vokaaliehto: no, alku: "-"] + $oikea>;
  result $res, accept;
  result $res, rules
         nimisana, erisnimi, laatusana,
         etuliite, nimisana_teonsanasta;
end;

combi_rule yhdysmerkki_laatusanan_jälkeen ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = tavuviiva;

  define $res := $vasen + <[vokaaliehto: yes, alku: "-"] + $oikea>;
  if $index /= 2 or $vasen.($index - 1).lähtöluokka /= nimi_laatusana then
    result $res, accept;
    result $res, rules nimisana, erisnimi;
  end;
  result $res, rules
         laatusana, lyhennesääntö,
         etuliite, nimisana_teonsanasta, inen_johdos_nimisanasta,
         paikannimen_lainen_johdos;
end;

combi_rule yhdysmerkki_inen_johdoksen_jälkeen ($vasen, $oikea, $sana):
  ? $oikea.luokka = tavuviiva;

  define $res := $vasen + <[vokaaliehto: yes, alku: "-"] + $oikea>;
  result $res, accept;
  result $res, rules
         nimisana, laatusana,
         etuliite, nimisana_teonsanasta, inen_johdos_nimisanasta,
         paikannimen_lainen_johdos;
end;

combi_rule yhdysmerkki_laatusanan_jälkeen_ei_kansallisuutta($vasen, $oikea, $sana):
  ? $oikea.luokka = tavuviiva;

  define $res := $vasen + <[vokaaliehto: yes, alku: "-"] + $oikea>;
  result $res, accept;
  result $res, rules
         nimisana, erisnimi, laatusana, lyhennesääntö,
         etuliite, nimisana_teonsanasta, inen_johdos_nimisanasta;
end;

combi_rule yhdysmerkki_sanan_lopussa ($vasen, $oikea, $sana):
  ? $oikea.luokka = tavuviiva;
  result $vasen + <[vokaaliehto: no, alku: "-"] + $oikea>, accept;
end;

combi_rule yhdysmerkki_lainen_johdoksen_jälkeen ($vasen, $oikea, $sana):
  ? $oikea.luokka = tavuviiva;

  define $res := $vasen + <[vokaaliehto: no, alku: "-"] + $oikea>;
  result $res, accept;
  result $res,
         rules erisnimi, paikannimen_lainen_johdos,
               inen_päätteinen_laatusana, inen_johdos_nimisanasta;
end;

combi_rule merkki_numeron_jälkeen ($vasen, $oikea, $sana):
  ? $oikea.luokka in <tavuviiva, kaksoispiste>;

  define $res := $vasen + <[vokaaliehto: no, alku: "-"] + $oikea>;
  if $oikea.luokka = tavuviiva then
    result $res, accept;
    result $res, rules lukusanan_jälkiliite, inen_johdos_nimisanasta, inen_päätteinen_laatusana;
  else
    result $res, rules lukusana_numero, kaksoispisteen_sijapääte;
  end;
end;

combi_rule yhdysmerkki_etuliitteen_jälkeen ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = tavuviiva;

  define $res := $vasen + <[vokaaliehto: yes, alku: "-"] + $oikea>;
  result $res, accept;
  define $jatko := $vasen.($index - 1).jatko;
  if teonsana in $jatko then
    if nimisana in $jatko or laatusana in $jatko then
      result $res, rules nimisana, laatusana, erisnimi;
    end;
    result $res, rules etuliite, teonsana;
  else
    result $res, rules nimisana, laatusana, etuliite, erisnimi, nimisana_teonsanasta;
  end;
  if nimisana in $jatko then
    result $res, rules lyhennesääntö;
  end;
end;

combi_rule yhdysmerkki_ennen_paikannimea ($vasen, $oikea, $sana):
  ? $oikea.luokka = tavuviiva;

  define $res := $vasen + <[vokaaliehto: yes, alku: "-"] + $oikea>;
  result $res, accept;
  result $res, rules erisnimi;
end;

###### YHDYSMERKKISÄÄNNÖT PÄÄTTYVÄT


define @sti_edeltäjä := <laatusana, nimi_laatusana, voittoaste, yliaste,
                         johdin_tAvA, johdin_ttAvA, johdin_ttU,
                         johdin_vA, johdin_vE, johdin_lAinen>
                        + @johdin_nUt + @johdin_tU
                        + @johtimet_laatusana_nimisanasta
                        + @johtimet_laatusana_laatusanasta;

###### SIJAPÄÄTESÄÄNNÖT ALKAVAT

combi_rule erisnimen_sijapääte ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = sijapääte;

  define $edellinen := $vasen.($index - 1);

  ? $oikea.sija in $edellinen.jatko;
  
  if $oikea.sija = sisätulento_Vn then
    ? sisätulento_Vn_oikein(last($edellinen.alku), $sana);
  elseif $oikea.sija = sisätulento_hVn then
    ? sisätulento_hVn_oikein(last($edellinen.alku), $sana);
  else
    ? vokaalisointu_oikein($edellinen, $oikea);
  end;

  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;
  define $res := $vasen + <[alku: $sana] + $oikea>;
  if ($oikea.sija = kerronto_sti) then
    ? $edellinen.luokka in @sti_edeltäjä;
  elseif ($oikea.sijamuoto = omanto and
          $oikea.luku = yksikkö) then
    if omistusliite in $oikea.jatko then
      result $res, rules omistusliite;
    end;
    if $edellinen.luokka = paikannimi then
      result $res, rules paikannimen_lainen_johdos, inen_päätteinen_laatusana;
    end;
    result $res, rules yhdysmerkki_erisnimen_jälkeen, johdettu_paikannimi;
  else
    if omistusliite in $oikea.jatko and ($oikea.sija /= omanto_n or $edellinen.ei_yks = nil) then
       result $res, rules omistusliite;
    end;
    if tavuviiva in $oikea.jatko and $oikea.sijamuoto in <nimentö, omanto> then
      result $res, rules yhdysmerkki_erisnimen_jälkeen;
    end;
    if $oikea.sijamuoto = omanto and $edellinen.luokka = sukunimi and tavuviiva in $oikea.jatko then
      result $res, rules johdettu_paikannimi;
    end;
  end;
  if $oikea.sija /= omanto_n or $edellinen.ei_yks = nil then
    if loppu in $oikea.jatko then
      result $res, accept;
    end;
    if liitesana in $oikea.jatko then
      result $res, rules liitesana;
    end;
  end;
end;

combi_rule voitto_yliasteen_sijapääte ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = sijapääte;

  define $edellinen := $vasen.($index - 1);

  ? $oikea.sija in $edellinen.jatko;
  ? $oikea.sija /= kerronto_sti or $edellinen.luokka in @sti_edeltäjä;
  
  if $oikea.sija = sisätulento_Vn then
    ? sisätulento_Vn_oikein(last($edellinen.alku), $sana);
  elseif $oikea.sija = sisätulento_hVn then
    ? sisätulento_hVn_oikein(last($edellinen.alku), $sana);
  else
    ? vokaalisointu_oikein($edellinen, $oikea);
  end;

  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;
  define $res := $vasen + <[alku: $sana] + $oikea>;

  if ($oikea.sija = kerronto_sti) then
    result $res, accept;
    result $res, rules liitesana;
  else
    if omistusliite in $oikea.jatko then
      result $res, rules omistusliite;
    end;
    if loppu in $oikea.jatko then
      result $res, accept;
    end;
    if liitesana in $oikea.jatko then
      result $res, rules liitesana;
    end;
    define $kantasana := $vasen.(length($vasen)-1);
    if ($oikea.sijamuoto = omanto and
        ($kantasana.tiedot = nil or
         not (ei_ysa in $kantasana.tiedot and ei_ys in $kantasana.tiedot))) then
      result $res, rules inen_johdos_nimisanasta;
    end;
  end;

  if $vasen.($index - 2).luokka in <nimisana, nimi_laatusana> and
     $oikea.sijamuoto = omanto then
    result $res, rules nimisana, nimisana_teonsanasta, nimisana_laatusanasta,
                 yhdysmerkki_yleisnimen_jälkeen, nimisanan_etuliite;
  end;
end;

combi_rule nimisanan_sijapääte ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = sijapääte;
  ? $oikea.sija /= kerronto_sti;

  define $edellinen := $vasen.($index - 1);

  ? $oikea.sija in $edellinen.jatko;
  
  if $oikea.sija = sisätulento_Vn then
    ? sisätulento_Vn_oikein(last($edellinen.alku), $sana);
  elseif $oikea.sija = sisätulento_hVn then
    ? sisätulento_hVn_oikein(last($edellinen.alku), $sana);
  else
    ? vokaalisointu_oikein($edellinen, $oikea);
  end;

  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;
  define $res := $vasen + <[alku: $sana] + $oikea>;

  if ($oikea.sija = kerronto_sti) then
    ? $edellinen.luokka in @sti_edeltäjä;
    result $res, accept;
    result $res, rules liitesana;
  else
    if $oikea.sija /= omanto_n or $edellinen.ei_yks = nil then
      if omistusliite in $oikea.jatko then
        result $res, rules omistusliite;
      end;
      if loppu in $oikea.jatko then
        result $res, accept;
      end;
      if liitesana in $oikea.jatko then
        result $res, rules liitesana;
      end;
    end;
    if (tavuviiva in $oikea.jatko and ($edellinen.tiedot = nil or
        (not ei_ysa in $edellinen.tiedot and not ei_ys in $edellinen.tiedot))) then
      # "kissan+karva", "työhön+otto"
      if $oikea.sijamuoto = omanto then
        result $res,
               rules nimisana, nimisana_teonsanasta, yhdysmerkki_yleisnimen_jälkeen,
                     nimisanan_etuliite;
      end;
      if $oikea.sijamuoto = nimentö and $oikea.luku = yksikkö then
        result $res, rules nimisana_teonsanasta;
      end;
      if $edellinen.luokka in <laatusana, nimi_laatusana> then
        result $res, rules yhdysmerkki_laatusanan_jälkeen;
      end;
      if $oikea.sijamuoto = omanto then
        if $oikea.luku = yksikkö then
          result $res, rules laatusana, inen_johdos_nimisanasta;
        else
          result $res, rules nimisana_laatusanasta;
        end;
      end;
    end;
  end;
end;

combi_rule laatusanan_sijapääte ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = sijapääte;

  define $edellinen := $vasen.($index - 1);

  ? $oikea.sija in $edellinen.jatko;
  
  if $oikea.sija = sisätulento_Vn then
    ? sisätulento_Vn_oikein(last($edellinen.alku), $sana);
  elseif $oikea.sija = sisätulento_hVn then
    ? sisätulento_hVn_oikein(last($edellinen.alku), $sana);
  else
    ? vokaalisointu_oikein($edellinen, $oikea);
  end;

  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;
  define $res := $vasen + <[alku: $sana] + $oikea>;

  if ($oikea.sija = kerronto_sti) then
    ? $edellinen.luokka in @sti_edeltäjä;
    result $res, accept;
    result $res, rules liitesana;
  else
    if loppu in $oikea.jatko then
      result $res, accept;
    end;
    if liitesana in $oikea.jatko then
      result $res, rules liitesana;
    end;
    if (tavuviiva in $oikea.jatko and ($edellinen.tiedot = nil or
        (not ei_ysa in $edellinen.tiedot and not ei_ys in $edellinen.tiedot))) then
      if $edellinen.aluetta_tarkentava_etuliite = yes then
        result $res, rules yhdysmerkki_ennen_paikannimea;
      end;
      if $oikea.sijamuoto = omanto and $edellinen.lähtöluokka /= nimi_laatusana then
          result $res,
                 rules laatusana, laatusana_teonsanasta, tavuviiva;
        if $oikea.luku = yksikkö then
          result $res, rules inen_johdos_nimisanasta;
        end;
      end;
    end;
  end;
end;

combi_rule laatusanan_sijapääte_omistusliitteellä ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = sijapääte;

  define $edellinen := $vasen.($index - 1);

  ? $oikea.sija in $edellinen.jatko;
  
  if $oikea.sija = sisätulento_Vn then
    ? sisätulento_Vn_oikein(last($edellinen.alku), $sana);
  elseif $oikea.sija = sisätulento_hVn then
    ? sisätulento_hVn_oikein(last($edellinen.alku), $sana);
  else
    ? vokaalisointu_oikein($edellinen, $oikea);
  end;

  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;
  define $res := $vasen + <[alku: $sana] + $oikea>;

  if ($oikea.sija = kerronto_sti) then
    ? $edellinen.luokka in @sti_edeltäjä;
    result $res, accept;
    result $res, rules liitesana;
  else
    if omistusliite in $oikea.jatko then
      result $res, rules omistusliite;
    end;
    if loppu in $oikea.jatko then
      result $res, accept;
    end;
    if liitesana in $oikea.jatko then
      result $res, rules liitesana;
    end;
    if (tavuviiva in $oikea.jatko and ($edellinen.tiedot = nil or
        (not ei_ysa in $edellinen.tiedot and not ei_ys in $edellinen.tiedot))) then
      if $oikea.sijamuoto = omanto then
        result $res,
               rules laatusana, nimisana_teonsanasta, tavuviiva;
        if $oikea.luku = yksikkö then
          result $res, rules inen_johdos_nimisanasta;
        end;
      end;
      if $edellinen.luokka in <laatusana, nimi_laatusana> then
        result $res, rules yhdysmerkki_laatusanan_jälkeen;
      end;
    end;
  end;
end;

combi_rule ttU_sijapääte ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = sijapääte;

  define $edellinen := $vasen.($index - 1);

  ? $oikea.sija in $edellinen.jatko;
  
  if $oikea.sija = sisätulento_Vn then
    ? sisätulento_Vn_oikein(last($edellinen.alku), $sana);
  elseif $oikea.sija = sisätulento_hVn then
    ? sisätulento_hVn_oikein(last($edellinen.alku), $sana);
  else
    ? vokaalisointu_oikein($edellinen, $oikea);
  end;

  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;
  define $res := $vasen + <[alku: $sana] + $oikea>;

  if ($oikea.sija = kerronto_sti) then
    ? $edellinen.luokka in @sti_edeltäjä;
    result $res, accept;
    result $res, rules liitesana;
  else
    if $oikea.sija = osanto_A and omistusliite in $oikea.jatko then
      result $res, rules omistusliite;
    end;
    if loppu in $oikea.jatko then
      result $res, accept;
    end;
    if liitesana in $oikea.jatko then
      result $res, rules liitesana;
    end;
    if (tavuviiva in $oikea.jatko and ($edellinen.tiedot = nil or
        (not ei_ysa in $edellinen.tiedot and not ei_ys in $edellinen.tiedot))) then
      if $oikea.sijamuoto = omanto then
        result $res,
               rules laatusana, nimisana_teonsanasta, tavuviiva;
        if $oikea.luku = yksikkö then
          result $res, rules inen_johdos_nimisanasta;
        end;
      end;
      if $edellinen.luokka in <laatusana, nimi_laatusana> then
        result $res, rules yhdysmerkki_laatusanan_jälkeen;
      end;
    end;
  end;
end;

combi_rule asemosanan_sijapääte ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = sijapääte;

  define $edellinen := $vasen.($index - 1);

  ? $oikea.sija in $edellinen.jatko;
  
  if $oikea.sija = sisätulento_Vn then
    ? sisätulento_Vn_oikein(last($edellinen.alku), $sana);
  elseif $oikea.sija = sisätulento_hVn then
    ? sisätulento_hVn_oikein(last($edellinen.alku), $sana);
  else
    ? vokaalisointu_oikein($edellinen, $oikea);
  end;

  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;
  define $res := $vasen + <[alku: $sana] + $oikea>;

  if ($oikea.sija = kerronto_sti) then
    ? $edellinen.luokka in @sti_edeltäjä;
    result $res, accept;
    result $res, rules liitesana;
  else
    if loppu in $oikea.jatko then
      result $res, accept;
    end;
    if liitesana in $oikea.jatko and $oikea.sija /= omanto_nkA and $oikea.sija /= nimentö_tkA then
      result $res, rules liitesana;
    end;
    if $oikea.sijamuoto = omanto then
      result $res, rules inen_johdos_nimisanasta;
    end;
  end;
end;

# Sijapääte, jota ei voi käyttää yhdyssanan sisällä
combi_rule ei_ys_sijapääte ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = sijapääte;

  define $edellinen := $vasen.($index - 1);

  ? $oikea.sija in $edellinen.jatko;
  
  if $oikea.sija = sisätulento_Vn then
    ? sisätulento_Vn_oikein(last($edellinen.alku), $sana);
  elseif $oikea.sija = sisätulento_hVn then
    ? sisätulento_hVn_oikein(last($edellinen.alku), $sana);
  else
    ? vokaalisointu_oikein($edellinen, $oikea);
  end;

  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;
  define $res := $vasen + <[alku: $sana] + $oikea>;

  if ($oikea.sija = kerronto_sti) then
    ? $edellinen.luokka in @sti_edeltäjä;
    result $res, accept;
    result $res, rules liitesana;
  else
    if omistusliite in $oikea.jatko then
      result $res, rules omistusliite;
    end;
    if loppu in $oikea.jatko then
      result $res, accept;
    end;
    if liitesana in $oikea.jatko then
      result $res, rules liitesana;
    end;
  end;
end;

combi_rule kaksoispisteen_sijapääte ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = sijapääte;
  # sisätulento_Vn kielletty, koska kaksoispisteellä taivutettaessa pitkä vokaali
  # laitetaan aina näkyviin.
  ? not $oikea.sija in <keinonto_n, tulento_s, omanto_nkA, sisätulento_Vn, kohdanto_t>;
  ? not ei_kaksoispisteen_jälkeen in $oikea;

  define $edellinen := $vasen.($index - 1);
  ? vokaalisointu_oikein($edellinen, $oikea);
  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;

  define $res := $vasen + <[alku: $sana] + $oikea>;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
end;

###### SIJAPÄÄTESÄÄNNÖT PÄÄTTYVÄT


###### LUKUSANASÄÄNNÖT ALKAVAT

combi_rule lukusana ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = lukusana;
  define $sijamuoto := nil;
  define $luku := nil;

  if ($oikea.alaluokka = erikoisluku) then
    define $erikoisres := $vasen + <[alku: $sana] + $oikea>;
    if (loppu in $oikea.jatko) then
      result $erikoisres, accept;
    end;
    if (liitesana in $oikea.jatko) then
      result $erikoisres, rules liitesana;
    end;
    if (lukusanan_jälkiliite in $oikea.jatko) then
      result $erikoisres, rules lukusanan_jälkiliite;
    end;
    stop;
  end;

  if ($index greater 1) then
    define $edellinen := $vasen.($index - 1);
    ? $edellinen.lukutyyppi = nil or $edellinen.lukutyyppi = $oikea.lukutyyppi;
    $sijamuoto := $edellinen.sijamuoto;
    $luku := $edellinen.luku;
    # Estetään peräkkäiset "satasata", "tuhattuhat" jne.
    if ($edellinen.luokka = lukusana) then
      ? not ($oikea.alaluokka = $edellinen.alaluokka);
    end;
  end;
  define $res := $vasen + <[alku: $sana, sijamuoto: $sijamuoto, luku: $luku] + $oikea>;

  if ($oikea.alaluokka = yksiyhdeksän) then
    if (nimentö in $oikea.jatko) then
      if loppu in $oikea.jatko then
        result $res, accept;
      end;
      if liitesana in $oikea.jatko then
        result $res, rules liitesana;
      end;
      if tavuviiva in $oikea.jatko then
        result $res, rules lukusana_toista, lukusana_sisäkerroin,
                     lukusanan_jälkiliite,
                     nimisana, inen_johdos_nimisanasta, inen_päätteinen_laatusana;
      end;
    end;
    if ($oikea.lukutyyppi = järjestysluku and omistusliite in $oikea.jatko) then
      result $res, rules omistusliite;
    end;
  elseif ($oikea.alaluokka in <kymmenen, sata>) then
    if (nimentö in $oikea.jatko) then
      if loppu in $oikea.jatko then
        result $res, accept;
      end;
      if liitesana in $oikea.jatko then
        result $res, rules liitesana;
      end;
      result $res, rules lukusana_sisäkerroin,
                   lukusanan_jälkiliite,
                   nimisana, inen_johdos_nimisanasta, inen_päätteinen_laatusana;
    end;
  elseif ($oikea.alaluokka in <tuhat, miljoona> and nimentö in $oikea.jatko) then
    if omistusliite in $oikea.jatko then
      result $res, rules omistusliite;
    end;
    if loppu in $oikea.jatko then
      result $res, accept;
    end;
    if liitesana in $oikea.jatko then
      result $res, rules liitesana;
    end;
    result $res, rules lukusanan_jälkiliite,
                 nimisana, inen_johdos_nimisanasta, inen_päätteinen_laatusana;
   elseif ($oikea.alaluokka = numeromerkki) then
     $res := $vasen + <[alku: $sana, pilkku: no, rakenne: "q"] + $oikea>;
     result $res, rules lukusana_numero, merkki_numeron_jälkeen;
     result $res, accept;
  end;
  if ($oikea.alaluokka in <sata, tuhat>) then
    if (nimentö in $oikea.jatko) then
      result $res, rules lukusana;
    end;
  end;
  result $res, rules lukusana_sijapääte;

end;

combi_rule lukusana_sijapääte ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = sijapääte;
  define $edellinen := $vasen.($index - 1);
  ? $oikea.sija in $edellinen.jatko;
  ? vokaalisointu_oikein($edellinen, $oikea);
  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;

  ? $edellinen.sijamuoto = nil or $edellinen.sijamuoto = $oikea.sijamuoto;
  ? $edellinen.luku = nil or $edellinen.luku = $oikea.luku;
  ? $oikea.sija /= sisätulento_Vn or sisätulento_Vn_oikein(last($edellinen.alku), $sana);
  ? $edellinen.lukutyyppi /= järjestysluku or $oikea.sija /= kerronto_sti;

  define $res := $vasen + <[alku: $sana, lukutyyppi: $edellinen.lukutyyppi] + $oikea>;
  if ($edellinen.alaluokka = yksiyhdeksän) then
    if omistusliite in $oikea.jatko then
      result $res, rules omistusliite;
    end;
    result $res, rules lukusana_toista, lukusana_sisäkerroin,
                 lukusanan_jälkiliite,
                 nimisana, inen_johdos_nimisanasta, inen_päätteinen_laatusana;
  elseif ($edellinen.alaluokka = kymmenen) then
    if omistusliite in $oikea.jatko then
      result $res, rules omistusliite;
    end;
    result $res, rules lukusana_sisäkerroin, lukusanan_jälkiliite;
  else
    result $res, rules nimisana, lukusanan_jälkiliite, inen_johdos_nimisanasta, inen_päätteinen_laatusana;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if ($edellinen.alaluokka in <sata, tuhat>) then
    result $res, rules lukusana;
  end;
end;

combi_rule lukusana_sisäkerroin ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = lukusana;
  ? $oikea.alaluokka in <kymmenen, sata, tuhat, miljoona>;

  define $edellinen := $vasen.($index - 1);
  ? $edellinen.lukutyyppi = $oikea.lukutyyppi;
  define $res := $vasen + <[alku: $sana, sijamuoto: $edellinen.sijamuoto, luku: $edellinen.luku] + $oikea>;

  if ($edellinen.sijamuoto in <nil, nimentö>) then
    result $res, rules lukusana_sisäkerroin_osanto;
  else
    result $res, rules lukusana_sisäkerroin_sijapääte;
  end;
  
  if ($oikea.lukutyyppi = järjestysluku) then
    result $res, rules lukusana;
  end;
  
  if ($oikea.lukutyyppi = järjestysluku and loppu in $oikea.jatko) then
    if omistusliite in $oikea.jatko then
      result $res, rules omistusliite;
    end;
    if liitesana in $oikea.jatko then
      result $res, rules liitesana;
    end;
    result $res, accept;
  end;
end;

combi_rule lukusana_sisäkerroin_sijapääte ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = sijapääte;
  define $edellinen := $vasen.($index - 1);
  ? $oikea.sija in $edellinen.jatko;
  ? vokaalisointu_oikein($edellinen, $oikea);
  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;

  ? $edellinen.sijamuoto = nil or $edellinen.sijamuoto = $oikea.sijamuoto;
  ? $edellinen.luku = nil or $edellinen.luku = $oikea.luku;

  define $res := $vasen + <[alku: $sana, lukutyyppi: $edellinen.lukutyyppi] + $oikea>;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  result $res, rules lukusana,
               lukusanan_jälkiliite,
               nimisana, inen_johdos_nimisanasta, inen_päätteinen_laatusana;
end;

combi_rule lukusana_sisäkerroin_osanto ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = sijapääte;
  define $edellinen := $vasen.($index - 1);
  ? $oikea.sija in $edellinen.jatko;
  ? $oikea.sijamuoto = osanto;
  ? vokaalisointu_oikein($edellinen, $oikea);
  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;

  define $res := $vasen + <$oikea + [alku: $sana, lukutyyppi: $edellinen.lukutyyppi, sijamuoto: nil]>;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  result $res, rules lukusana, lukusanan_jälkiliite,
               nimisana, inen_johdos_nimisanasta, inen_päätteinen_laatusana;
end;

combi_rule lukusana_toista ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = lukusana;
  ? $oikea.alaluokka = toista;
  define $edellinen := $vasen.($index - 1);
  define $res := $vasen + <[alku: $sana, sijamuoto: $edellinen.sijamuoto,
                            luku: $edellinen.luku, lukutyyppi: $edellinen.lukutyyppi] + $oikea>;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  result $res, rules lukusana_sisäkerroin,
                     lukusanan_jälkiliite,
                     nimisana, inen_johdos_nimisanasta, inen_päätteinen_laatusana;
end;

combi_rule lukusanan_jälkiliite ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka in <lukusanan_jälkiliite, tavuviiva>;
  # FIXME: yhdysviivan käyttöä ei vielä tueta lukusanan ja sen jälkiliitteen välissä
  define $res := $vasen + <[alku: $sana] + $oikea>;
  define $edellinen := $vasen.($index - 1);
  if $oikea.luokka = tavuviiva then
    if $edellinen.luokka /= tavuviiva then
      result $res, accept;
    end;
  else
    ? $edellinen.lukutyyppi = nil or $edellinen.lukutyyppi = $oikea.lukutyyppi;
    if omistusliite in $oikea.jatko then
      result $res, rules omistusliite;
    end;
    if tavuviiva in $oikea.jatko then
      result $res, rules yhdysmerkki_yleisnimen_jälkeen;
    end;
    if loppu in $oikea.jatko then
      result $res, accept;
    end;
    if liitesana in $oikea.jatko then
      result $res, rules liitesana;
    end;
    result $res,
           rules ei_ys_sijapääte,
                 nimisana, etuliite,
                 nimisanan_johdos,
                 nimisana_laatusanasta, nimisana_teonsanasta;
  end;
end;

combi_rule lukusana_numero ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = lukusana;

  define $edellinen := $vasen.($index - 1);
  $oikea :=+ [pilkku: $edellinen.pilkku];
  define $res := $vasen + <[alku: $sana] + $oikea>;

  if $oikea.alaluokka = numeromerkki then
    result $res, rules lukusana_numero, merkki_numeron_jälkeen;
    result $res, accept;
  elseif $oikea.alaluokka = pilkku and $oikea.pilkku /= yes then
    $oikea :=+ [pilkku: yes];
    result $vasen + <[alku: $sana] + $oikea>, rules lukusana_numero;
  elseif $oikea.alaluokka = piste and $oikea.pilkku /= yes then
    result $res, rules lukusana_pvm_kk;
  end;
end;

combi_rule lukusana_pvm_kk($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = lukusana;

  define $edellinen := $vasen.($index - 1);
  define $res := $vasen + <[alku: $sana] + $oikea>;

  if $oikea.alaluokka = numeromerkki then
    result $res, rules lukusana_pvm_kk;
  elseif $oikea.alaluokka = piste then
    # sallitaan enemmänkin pisteitä, esim. IP-osoitteet
    result $res, rules lukusana_numero_tai_piste;
    # sallitaan päivämäärä ilman vuotta (12.4.)
    result $res, accept;
  end;
end;

combi_rule lukusana_numero_tai_piste($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = lukusana;

  define $edellinen := $vasen.($index - 1);
  define $res := $vasen + <[alku: $sana] + $oikea>;

  if $oikea.alaluokka = numeromerkki then
    result $res, rules lukusana_numero_tai_piste;
    result $res, accept;
  elseif $oikea.alaluokka = piste then
    result $res, rules lukusana_numero_tai_piste;
  end;
end;

###### LUKUSANASÄÄNNÖT PÄÄTTYVÄT

combi_rule teonsana ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = teonsana;

  if ($index greater 1) then
    ? tavuviiva in $vasen.($index - 1).jatko;
    ? vokaaliehdollinen_yhdysviiva_ok($vasen, $sana);
    ? $oikea.tiedot = nil or (not ei_ysj in $oikea.tiedot and not ei_ys in $oikea.tiedot);
  else
    ? $oikea.tiedot = nil or (not ysj in $oikea.tiedot);
  end;

  define $res := $vasen + <[alku: $sana] + $oikea>;
  if tavuviiva in $oikea.jatko then
    result $res, rules yhdysmerkki_teonsanan_jälkeen;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  if liitesana_pi in $oikea.jatko then
    result $res, rules liitesana_pi;
  end;
  result $res, rules tekijämuodot, nimitavat, teonsanan_johdos;
end;


combi_rule tekijämuodot ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka in @kestämän_tekijäpääte + @tositavan_tekijäpääte_4
                     + @kertoman_tekijäpääte
                     + @ehtotapa + @mahtotapa + @käskytapa;

  define $edellinen := $vasen.($index - 1);
  ? $oikea.luokka in $edellinen.jatko;
  ? vokaalisointu_oikein($edellinen, $oikea);
  ? $oikea.luokka /= kestämän_tekijäpääte_y3 or
    taivutusvokaali(last($edellinen.alku)) = $sana;
  ? $oikea.tekijä = nil or $edellinen.tiedot = nil or (
      ((not yt in $edellinen.tiedot) or (not $oikea.tekijä in <tekijä_1, tekijä_2, tekijä_4> and not $oikea.luku = monikko))
     and
      ((not ym3 in $edellinen.tiedot) or (not $oikea.tekijä in <tekijä_1, tekijä_2, tekijä_4>))
    );

  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;
  if $edellinen.tapaluokka = mahtotapa and $oikea.luokka in @kestämän_tekijäpääte then $oikea.tapaluokka := mahtotapa; end;
  define $res := $vasen + <[alku: $sana] + $oikea>;
  result $res, accept;
  if (liitesana in $oikea.jatko) then
    result $res, rules liitesana;
  end;
  if (liitesana_pi in $oikea.jatko) then
    result $res, rules liitesana_pi;
  end;
end;

###### NIMITAPASÄÄNNÖT ALKAVAT

combi_rule nimitapa_4 ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = nimitapa_45 and $oikea.tapaluokka = nimitapa_4;

  if $oikea.äs = aä then
    $oikea.äs := $vasen.($index - 1).äs;
  end;
  define $res := $vasen + <[alku: $sana] + $oikea>;
  if (tavuviiva in $oikea.jatko) then
    result $res, rules nimisana, nimisana_laatusanasta, yhdysmerkki_sanan_lopussa;
  end;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  result $res, rules nimisanan_sijapääte;
end;

###### NIMITAPASÄÄNNÖT PÄÄTTYVÄT

combi_rule nimitavat ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka in @nimitapa_1 + @nimitapa_2 + @nimitapa_3 + <nimitapa_45>;

  define $edellinen := $vasen.($index - 1);
  ? $oikea.luokka in $edellinen.jatko;
  ? vokaalisointu_oikein($edellinen, $oikea);
  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;

  define $res := $vasen + <[alku: $sana] + $oikea>;

  if (tavuviiva in $oikea.jatko) then
    result $res, rules yhdysmerkki_sanan_lopussa, nimisana, laatusana, nimisana_teonsanasta, inen_johdos_nimisanasta_ehdollinen;
  end;
  if (nimitapa_1_pitkä in $oikea.jatko) then
    result $res, rules nimitapa_1_pitkä;
  end;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  result $res, rules nimisanan_sijapääte;
end;


combi_rule nimitapa_1_pitkä ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = nimitapa_1_pitkä;
  if $oikea.äs = aä then $oikea.äs := $vasen.($index - 1).äs; end;
  result $vasen + <[alku: $sana] + $oikea>, rules omistusliite;
end;


combi_rule kieltosana ($vasen, $oikea, $sana):
  # Esiehto: tämä sääntö on aina järjestyksessä toisen allomorfin sääntö,
  # ensimmäinen allomorfi on kieltosanan_etuliite.
  ? $oikea.luokka = kieltosana;
  ? $oikea.tapaluokka = tositapa;

  define $äs := ä;
  if $oikea.äs /= nil then
    $äs := $oikea.äs;
  elseif ($vasen.1).äs /= nil then
    $äs := ($vasen.1).äs;
  end;

  define $res := $vasen + <[alku: $sana] + $oikea + [äs: $äs]>;
  if kieltosanan_liitesana in $oikea.jatko then
    result $res, rules yhdyskieltosanan_liitesana;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
end;

###### LIITESANASÄÄNNÖT ALKAVAT

combi_rule liitesana ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = liitesana;

  define $edellinen := $vasen.($index - 1);
  ? vokaalisointu_oikein($edellinen, $oikea);

  if ($edellinen.tapaluokka = käskytapa) then
    ? not ($sana in <"ko", "kö">);
    if (($edellinen.tekijä /= tekijä_2) or ($edellinen.luku /= yksikkö)) then
      ? not ($sana in <"kaan", "kään">);
    end;
  end;

  if ($edellinen.kielto /= nil and $oikea.kielto /= no) then
    result $vasen + <$oikea + [alku: $sana, kielto: $edellinen.kielto]>, accept;
  else
    result $vasen + <$oikea + [alku: $sana]>, accept;
  end;
    
end;

cimbi_rule liitesana_pi ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = liitesana_pi;

  result $vasen + <[alku: $sana] + $oikea>, accept;
end;

###### LIITESANASÄÄNNÖT PÄÄTTYVÄT

combi_rule kieltosanan_liitesana ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = kieltosanan_liitesana;
  define $edellinen := $vasen.($index - 1);
  ? ($edellinen.äs = $oikea.äs) or ($oikea.äs = ä and $edellinen.äs /= a);
  if loppu in $oikea.jatko then
    result $vasen + <[alku: $sana] + $oikea>, accept;
  end;
end;

combi_rule yhdyskieltosanan_liitesana ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = liitesana;
  ? vokaalisointu_oikein($vasen.($index - 1), $oikea);
  if loppu in $oikea.jatko then
    result $vasen + <[alku: $sana] + $oikea>, accept;
  end;
end;

combi_rule omistusliite ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = omistusliite;

  define $edellinen := $vasen.($index - 1);

  ? vokaalisointu_oikein($edellinen, $oikea);

  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;
  define $result := $vasen + <[alku: $sana] + $oikea>;

  if ($sana in <"ni", "si", "nsa", "nsä", "mme", "nne">) then
    result $result, accept;
    result $result, rules liitesana;

  elseif (omistusliite3_oikein(last($edellinen.alku), $sana)) then
    if ($edellinen.luokka in <seikkasana, suhdesana>) or
       ($edellinen.luokka = nimisana and $edellinen.sijamuoto /= nil) then
      if (not ($edellinen.alku matches ".*(aa|ee|ii|oo|uu|yy|ää|öö)")) then
        result $result, accept;
        result $result, rules liitesana;
      end;
    elseif ($edellinen.luokka = sijapääte) then
      if (($edellinen.sija in <osanto_iA,
                              osanto_tA, osanto_ttA, osanto_itA,
                              osanto_jA,
                              olento_nA, olento_inA,
                              tulento_ksi,
                              sisäolento_ssA, sija_monikko_1,
                              ulkopaikallissija_llA,
                              ulkopaikallissija_illA,
                              vajanto_ttA, vajanto_ittA,
                              seuranto_ine>)) or
          ($edellinen.sija = osanto_A and not last($vasen.($index - 2).alku) in <"a", "ä">) then
        result $result, accept;
        result $result, rules liitesana;
      end;
    elseif ($edellinen.luokka in <nimitapa_1_pitkä, nimitapa_2> or $edellinen.tapaluokka = nimitapa_5) then
      result $result, accept; # "puhuakseen"
      result $result, rules liitesana;
    end;
  end;
end;

combi_rule voittoaste ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = voittoaste;

  define $edellinen := $vasen.($index - 1);
  ? vokaalisointu_oikein($edellinen, $oikea);
  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;

  define $res := $vasen + <[alku: $sana] + $oikea>;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  result $res, rules
         voitto_yliasteen_sijapääte, inen_johdos_nimisanasta;
end;
  

combi_rule voitto_yliaste ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka in <voittoaste, yliaste>;

  define $edellinen := $vasen.($index - 1);

  ? $oikea.luokka in $edellinen.jatko;
  ? vokaalisointu_oikein($edellinen, $oikea);
  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;

  define $res := $vasen + <[alku: $sana] + $oikea>;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  result $res, rules voitto_yliasteen_sijapääte;
  if ($oikea.luokka = voittoaste) then
    result $res, rules inen_johdos_nimisanasta, inen_päätteinen_laatusana;
  end;

  if johdin_UUs in $oikea.jatko then
    result $res, rules johdin_nimisana_laatusanasta;
  end;
end;

combi_rule johdin_seikkasana_ittain($vasen, $oikea, $sana, $index):
  ? $oikea.luokka = johdin_ittAin;
  define $edellinen := $vasen.($index - 1);
  ? vokaalisointu_oikein($edellinen, $oikea);

  define $res := $vasen + <[alku: $sana] + $oikea>;
  result $res, rules liitesana;
  result $res, accept;
end;

combi_rule teonsanan_johdos ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka in @johdin_teonsanasta + <johdin_inen>;

  define $edellinen := $vasen.($index - 1);
  ? $oikea.luokka in $edellinen.jatko;
  ? $oikea.luokka /= johdin_inen or not $edellinen.luokka in <johdin_ntA, johdin_nti, johdin_Us_ksen> or $index greater 3;
  ? vokaalisointu_oikein($edellinen, $oikea);
  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;

  if ($edellinen.luokka = teonsana) then
    $vasen.($index - 1) :=+ [lähtöluokka: teonsana];
    if $oikea.luokka in @johtimet_laatusana_teonsanasta then
      $vasen.($index - 1).luokka := laatusana;
    else
      $vasen.($index - 1).luokka := nimisana;
    end;
  end;
  define $res := $vasen + <[alku: $sana] + $oikea>;

  if ($oikea.luokka in <johdin_UUs, johdin_Us, johdin_ntA, johdin_nti, johdin_Us_ksen,
                        johdin_mA> + @johdin_jA) or $oikea.jluokka = nimisana then
    result $res, rules nimisanan_sijapääte;
  elseif $oikea.luokka = johdin_lUt then
    result $res, rules laatusanan_sijapääte;
  elseif $oikea.laatutapa = johdin_ttU then
    result $res, rules ttU_sijapääte;
  else
    result $res, rules laatusanan_sijapääte_omistusliitteellä;
  end;

  if tavuviiva in $oikea.jatko then
    if ($oikea.luokka in <johdin_UUs, johdin_Us, johdin_ntA, johdin_nti, johdin_Us_ksen>
        + @johdin_jA) or $oikea.jluokka in <nimisana, nimi_laatusana> then
      result $res, rules nimisana, nimisana_teonsanasta, inen_päätteinen_laatusana_ei_nimisanajohdosta,
                         nimisana_laatusanasta, inen_johdos_nimisanasta_ehdollinen,
                         nimisanan_etuliite, yhdysmerkki_yleisnimen_jälkeen;
    else
      result $res, rules inen_johdos_nimisanasta, yhdysmerkki_laatusanan_jälkeen,
                         inen_päätteinen_laatusana;
    end;
  end;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  result $res, rules teonsanan_johdos, voitto_yliaste;
end;


combi_rule nimisanan_johdos ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka in @johdin_nimisanasta + <johdin_UUs>;

  define $edellinen := $vasen.($index - 1);
  ? $oikea.luokka in $edellinen.jatko;
  ? $oikea.luokka /= johdin_UUs or (not $edellinen.luokka in <johdin_jA_kantaja, johdin_jA_kulkija, johdin_jA_myyjä> and $edellinen.lähtöluokka /= nimi_laatusana);

  ? vokaalisointu_oikein($edellinen, $oikea);
  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;
  define $res := $vasen + <[alku: $sana] + $oikea>;

  if ($oikea.luokka = johdin_inen) then
    if ($edellinen.tiedot /= nil and inen in $edellinen.tiedot) and
        (($index = 2) or
         (not ($vasen.($index - 2).luokka in <nimisana, laatusana, nimi_laatusana, etuliite>) and
          ($vasen.($index - 2).sijamuoto /= omanto or $vasen.($index - 2).luku /= yksikkö)) or
          ($index greater 3 and $vasen.($index - 3).lukutyyppi /= nil)) then
      if omistusliite in $oikea.jatko then
        result $res, rules omistusliite;
      end;
      if tavuviiva in $oikea.jatko then
        result $res, rules yhdysmerkki_inen_johdoksen_jälkeen;
      end;
      if loppu in $oikea.jatko then
        result $res, accept;
      end;
      if liitesana in $oikea.jatko then
        result $res, rules liitesana;
      end;
      result $res,
             rules laatusanan_sijapääte_omistusliitteellä, inen_johdos_nimisanasta,
                   voitto_yliaste, johdin_nimisana_laatusanasta;
    end;
    stop;
  end;

  if tavuviiva in $oikea.jatko then
    result $res, rules nimisana, nimisana_teonsanasta,
           etuliite, tavuviiva, inen_johdos_nimisanasta;
  end;
  if omistusliite in $oikea.jatko and not $oikea.luokka in @johtimet_laatusana_nimisanasta then
    result $res, rules omistusliite;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  if $oikea.luokka in @johtimet_nimisana_nimisanasta + <johdin_UUs> then
    result $res, rules nimisanan_sijapääte, nimisanan_johdos;
  elseif $oikea.luokka in @johtimet_laatusana_nimisanasta then
    result $res, rules laatusana, laatusanan_sijapääte, voitto_yliaste, johdin_nimisana_laatusanasta;
  elseif $oikea.luokka in @johdin_jA then
    result $res, rules nimisanan_sijapääte, johdin_nimisana_laatusanasta, nimisanan_johdos;
    if tavuviiva in $oikea.jatko then
      result $res, rules inen_päätteinen_laatusana_ei_nimisanajohdosta, nimisana_laatusanasta;
    end;
  end;
end;

combi_rule johdin_laatusana_teonsanasta ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka in @johtimet_laatusana_teonsanasta + <johdin_inen>;

  define $edellinen := $vasen.($index - 1);
  ? $oikea.luokka in $edellinen.jatko;
  ? vokaalisointu_oikein($edellinen, $oikea);
  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;

  if ($edellinen.luokka = teonsana) then
    $vasen.($index - 1) :=+ [lähtöluokka: teonsana];
    if $oikea.luokka in @johtimet_laatusana_teonsanasta then
      $vasen.($index - 1).luokka := laatusana;
    else
      $vasen.($index - 1).luokka := nimisana;
    end;
  end;
  define $res := $vasen + <[alku: $sana] + $oikea>;

  if $oikea.luokka = johdin_lUt then
    result $res, rules laatusanan_sijapääte;
  else
    result $res, rules laatusanan_sijapääte_omistusliitteellä;
  end;

  if tavuviiva in $oikea.jatko then
     result $res, rules inen_johdos_nimisanasta, yhdysmerkki_laatusanan_jälkeen,
                        inen_päätteinen_laatusana;
  end;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  result $res, rules teonsanan_johdos, voitto_yliaste;
end;


combi_rule johdin_laatusana_nimisanasta ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka in @johtimet_laatusana_nimisanasta;

  define $edellinen := $vasen.($index - 1);
  ? $oikea.luokka in $edellinen.jatko;

  ? vokaalisointu_oikein($edellinen, $oikea);
  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;
  define $res := $vasen + <[alku: $sana] + $oikea>;

  if ($oikea.luokka = johdin_inen) then
    if ($edellinen.tiedot /= nil and inen in $edellinen.tiedot) and
        (($index = 2) or
         (not ($vasen.($index - 2).luokka in <nimisana, laatusana, nimi_laatusana, etuliite>) and
          ($vasen.($index - 2).sijamuoto /= omanto or $vasen.($index - 2).luku /= yksikkö)) or
          ($index greater 3 and $vasen.($index - 3).lukutyyppi /= nil)) then
      if omistusliite in $oikea.jatko then
        result $res, rules omistusliite;
      end;
      if tavuviiva in $oikea.jatko then
        result $res, rules yhdysmerkki_yleisnimen_jälkeen;
      end;
      if loppu in $oikea.jatko then
        result $res, accept;
      end;
      if liitesana in $oikea.jatko then
        result $res, rules liitesana;
      end;
      result $res,
             rules laatusanan_sijapääte_omistusliitteellä, inen_johdos_nimisanasta,
                   voitto_yliaste, johdin_nimisana_laatusanasta;
    end;
    stop;
  end;

  if tavuviiva in $oikea.jatko then
    result $res, rules nimisana, laatusana, nimisana_teonsanasta,
           etuliite, tavuviiva, inen_johdos_nimisanasta;
  end;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  result $res, rules laatusanan_sijapääte, voitto_yliaste, johdin_nimisana_laatusanasta;
end;


combi_rule johdin_nimisana_laatusanasta ($vasen, $oikea, $sana, $index):
  ? $oikea.luokka in @johtimet_nimisana_laatusanasta;

  define $edellinen := $vasen.($index - 1);
  ? $oikea.luokka in $edellinen.jatko;

  ? vokaalisointu_oikein($edellinen, $oikea);
  if $oikea.äs = aä then $oikea.äs := $edellinen.äs; end;

  define $res := $vasen + <[alku: $sana] + $oikea>;
  if tavuviiva in $oikea.jatko and ($edellinen.tiedot = nil or
        (not ei_ysa in $edellinen.tiedot and not ei_ys in $edellinen.tiedot)) then
    result $res, rules nimisana, laatusana, nimisana_teonsanasta,
           etuliite, tavuviiva, inen_johdos_nimisanasta;
  end;
  if $index greater 2 and johdin_inen in $oikea.jatko then
    result $res, rules johdin_inen;
  end;
  if omistusliite in $oikea.jatko then
    result $res, rules omistusliite;
  end;
  if loppu in $oikea.jatko then
    result $res, accept;
  end;
  if liitesana in $oikea.jatko then
    result $res, rules liitesana;
  end;
  result $res,
          rules nimisanan_sijapääte,
                nimisanan_johdos;
end;


subrule sisätulento_Vn_oikein ($ed_kirjain, $sana):
  return ((($ed_kirjain matches @a) and ($sana matches "an?")) or
          (($ed_kirjain matches @e) and ($sana matches "en?")) or
          (($ed_kirjain matches @i) and ($sana matches "in?")) or
          (($ed_kirjain matches @o) and ($sana matches "on?")) or
          (($ed_kirjain matches @u) and ($sana matches "un?")) or
          (($ed_kirjain matches @y) and ($sana matches "yn?")) or
          (($ed_kirjain matches @ä) and ($sana matches "än?")) or
          (($ed_kirjain matches @ö) and ($sana matches "ön?")));
end;


subrule sisätulento_hVn_oikein ($ed_kirjain, $sana):
  return ((($ed_kirjain matches @a) and ($sana matches "han?")) or
          (($ed_kirjain matches @e) and ($sana matches "hen?")) or
          (($ed_kirjain matches @i) and ($sana matches "hin?")) or
          (($ed_kirjain matches @o) and ($sana matches "hon?")) or
          (($ed_kirjain matches @u) and ($sana matches "hun?")) or
          (($ed_kirjain matches @y) and ($sana matches "hyn?")) or
          (($ed_kirjain matches @ä) and ($sana matches "hän?")) or
          (($ed_kirjain matches @ö) and ($sana matches "hön?")) or
          (($ed_kirjain = "'"))); # Parfait'hen yms.
end;


# Onko vokaalisointu oikein silloin, kun tiedetään, että $oikea.äs ei ole aä
subrule vokaalisointu_oikein_ei_aä($edellinen, $oikea):
  return ($edellinen.äs = aä or $oikea.äs = $edellinen.äs);
end;

# Onko vokaalisointu oikein silloin, kun $oikea.äs voi olla aä
subrule vokaalisointu_oikein($edellinen, $oikea):
  return ($oikea.äs = aä or $edellinen.äs = aä or $oikea.äs = $edellinen.äs);
end;


subrule omistusliite3_oikein ($kirjain, $liite):
  return ((($liite = "an") and ($kirjain matches @a)) or
          (($liite = "en") and ($kirjain matches @e)) or
          (($liite = "in") and ($kirjain matches @i)) or
          (($liite = "on") and ($kirjain matches @o)) or
          (($liite = "un") and ($kirjain matches @u)) or
          (($liite = "yn") and ($kirjain matches @y)) or
          (($liite = "än") and ($kirjain matches @ä)) or
          (($liite = "ön") and ($kirjain matches @ö)));
end;

# Taivutuspäätteessä annettua kirjainta vastaava pidennetty vokaali
subrule taivutusvokaali($kirjain):
  if     ($kirjain matches @a) then return "a";
  elseif ($kirjain matches @e) then return "e";
  elseif ($kirjain matches @i) then return "i";
  elseif ($kirjain matches @o) then return "o";
  elseif ($kirjain matches @u) then return "u";
  elseif ($kirjain matches @y) then return "y";
  elseif ($kirjain matches @ä) then return "ä";
  elseif ($kirjain matches @ö) then return "ö";
  end;
  return "";
end;


#####################################################


output_filter tulosta ($tulos):
  foreach $j in length ($tulos):
    result tulostus_v2($tulos.$j);
  end;
end;

# Palauttaa tietueen rakenne-kentän mukaisen rakennekuvauksen oikean mittaisena.
# Jos $pienet_kirjaimet = yes, niin rakenteen i-merkit muutetaan p-merkeiksi.
subrule rakenne ($tietue, $pienet_kirjaimet):
  define $alen := length($tietue.alku);
  define $i := 0;
  define $tuloste := "=";
  define $kirjain := "";
  foreach $k in length($tietue.rakenne):
    $kirjain := substring($tietue.rakenne, $k);
    if $i less $alen then
      if $kirjain = "=" then
        $tuloste :=+ "=";
      else
        if $pienet_kirjaimet and $kirjain = "i" then
          $tuloste :=+ "p";
        else
          $tuloste :=+ $kirjain;
        end;
        $i :=+ 1;
      end;
    end;
  end;
  if $i less $alen then
    foreach $j in $alen - $i:
      $tuloste :=+ "p";
    end;
  end;
  return $tuloste;
end;

subrule tulostus_rakenne ($tietue):
  define $tuloste := "";
  define $tlen := length($tietue);
  define $tind := 0;
  define $arvattu_erisnimi := no;
  foreach $t in $tietue:
    $tind :=+ 1;
    if $t.rakenne /= nil then
      if ($t.luokka = paikannimi and
          (
            ($tlen greater $tind and $tietue.($tind+1).luokka = johdin_lAinen) or
            ($tlen greater $tind + 2 and $tietue.($tind+3).luokka = johdin_lAinen)
          )
         ) then
        $tuloste :=+ rakenne($t, yes);
      else
        $tuloste :=+ rakenne($t, no);
      end;
    elseif $t.luokka in <nimisana, seikkasana, laatusana, teonsana, huudahdussana, sidesana, kieltosanan_etuliite,
                         nimi_laatusana, asemosana, suhdesana, lukusanan_jälkiliite, etuliite, kieltosana> or
                         ($t.luokka = lukusana and not $t.alaluokka in <numeromerkki, pilkku, piste>) then
      $tuloste :=+ "=";
      if $t.aluetta_tarkentava_etuliite = yes and
         (($tind + 1 = $tlen and $tietue.($tind+1).luokka = tavuviiva) or
          ($tlen + 1 greater $tind and $tietue.($tind+1).luokka = tavuviiva and $tietue.($tind+2).luokka = paikannimi) or
          ($tlen + 2 greater $tind and $tietue.($tind+1).luokka in <johdin_inen, sijapääte> and $tietue.($tind+2).luokka = tavuviiva and $tietue.($tind+3).luokka = paikannimi)) then
        $tuloste :=+ "i";
      else
        $tuloste :=+ "p";
      end;
      foreach $i in length($t.alku) - 1:
        $tuloste :=+ "p";
      end;
    elseif $t.luokka in <etunimi, sukunimi, nimi, paikannimi> then
      $tuloste :=+ "=";
      if ($t.luokka = paikannimi and
          (
            ($tlen greater $tind and $tietue.($tind+1).luokka = johdin_lAinen) or
            ($tlen greater $tind + 2 and $tietue.($tind+3).luokka = johdin_lAinen) or
            ($tlen greater $tind + 1 and $tietue.($tind+1).luokka = sijapääte and $tietue.($tind+2).inen_loppu = yes)
          )
         ) or $t.erisnimen_pääte = yes then
        foreach $i in length($t.alku):
          $tuloste :=+ "p";
        end;
        if $t.erisnimen_pääte = yes then
          $arvattu_erisnimi := yes;
        end;
      else
        $tuloste :=+ "i";
        foreach $i in length($t.alku) - 1:
          $tuloste :=+ "p";
        end;
      end;
    elseif $t.luokka = lyhenne then
      $tuloste :=+ "=";
      foreach $i in length($t.alku):
        $tuloste :=+ "q";
      end;
    elseif $t.luokka = tavuviiva then
      $tuloste :=+ "-";
    elseif $t.luokka = kaksoispiste then
      $tuloste :=+ ":";
    elseif $t.luokka = lukusana then
      $tuloste :=+ "q";
    else
      foreach $i in length($t.alku):
        $tuloste :=+ "p";
      end;
    end;
  end;
  if $arvattu_erisnimi = yes then
    return "=i" + substring($tuloste, 3, length($tuloste));
  end;
  return $tuloste;
end;

subrule tulostus_luokka($tietue):
  define $tlen := length($tietue);
  define $omistusliite := no;
  foreach $i in $tlen:
    define $allomorfi := $tietue.($tlen + 1 - $i);
    if $allomorfi.jluokka /= nil then
      return $allomorfi.jluokka;
    end;
    if $allomorfi.luokka = johdin_inen then
      return laatusana;
    end;
    if $allomorfi.luokka in @johtimet_laatusana_nimisanasta then
      if $omistusliite then
        return nimisana;
      else
        return laatusana;
      end;
    end;
    if $allomorfi.luokka in <nimisana, seikkasana, laatusana, teonsana,
                             nimi_laatusana, asemosana, suhdesana,
                             huudahdussana, sidesana, etunimi, sukunimi,
                             paikannimi, nimi, kieltosana,
                             lukusana, etuliite> then
      return $allomorfi.luokka;
    end;
    if $allomorfi.luokka = tavuviiva then
      return etuliite;
    end;
    if $allomorfi.luokka = lyhenne then
      if $allomorfi.alaluokka = roomalainen then
        return lukusana;
      end;
      return lyhenne;
    end;
    if $allomorfi.luokka in @johtimet_nimisana_laatusanasta then
      return nimisana;
    end;
    if $allomorfi.luokka = johdin_ittAin then
      return seikkasana;
    end;
    if $allomorfi.luokka = johdin_lAinen then
      return laatusana;
    end;
    if $allomorfi.luokka = omistusliite then
      $omistusliite := yes;
    end;
  end;
  return nil;
end;

subrule tulostus_sijamuoto($tietue):
  define $tlen := length($tietue);
  foreach $i in $tlen:
    define $allomorfi := $tietue.($tlen + 1 - $i);
    if $allomorfi.sijamuoto /= nil then
      return $allomorfi.sijamuoto;
    end;
    if $allomorfi.luokka in <tavuviiva, johdin_ittAin, seikkasana> then
      return nil;
    end;
    if $allomorfi.alaluokka = erikoisluku then
      return nil;
    end;
    if $allomorfi.tapaluokka = nimitapa_5 then
      return nil;
    end;
    if $allomorfi.luokka in <nimisana, laatusana, nimi_laatusana, asemosana, lyhenne,
                             etunimi, sukunimi, paikannimi, nimi, lukusana, nimitapa_45, lukusanan_jälkiliite> then
      return nimentö;
    end;
  end;
  return nil;
end;

subrule tulostus_luku($tietue):
  define $tlen := length($tietue);
  foreach $i in $tlen:
    define $allomorfi := $tietue.($tlen + 1 - $i);
    if $allomorfi.luku /= nil then
      return $allomorfi.luku;
    end;
    if $allomorfi.luokka in <tavuviiva, johdin_ittAin, seikkasana> then
      return nil;
    end;
    if $allomorfi.sija = kerronto_sti then
      return nil;
    end;
    if $allomorfi.alaluokka = erikoisluku then
      return nil;
    end;
    if $allomorfi.tapaluokka = nimitapa_5 then
      return nil;
    end;
    if $allomorfi.luokka in <nimisana, laatusana, nimi_laatusana, asemosana, lyhenne,
                             etunimi, sukunimi, paikannimi, nimi, lukusana, nimitapa_45, lukusanan_jälkiliite> then
      return yksikkö;
    end;
  end;
  return nil;
end;

subrule tulostus_tekijä($tietue):
  define $tlen := length($tietue);
  foreach $i in $tlen:
    define $allomorfi := $tietue.($tlen + 1 - $i);
    if $allomorfi.tekijä /= nil then
      return $allomorfi.tekijä;
    end;
  end;
  return nil;
end;

subrule tulostus_tapaluokka($tietue):
  define $tlen := length($tietue);
  foreach $i in $tlen:
    define $allomorfi := $tietue.($tlen + 1 - $i);
    if $allomorfi.tapaluokka /= nil then
      return $allomorfi.tapaluokka;
    end;
  end;
  return nil;
end;

subrule tulostus_aikamuoto($tietue):
  define $tlen := length($tietue);
  foreach $i in $tlen:
    define $allomorfi := $tietue.($tlen + 1 - $i);
    if $allomorfi.aikamuoto /= nil then
      return $allomorfi.aikamuoto;
    end;
    if $allomorfi.luokka in @kestämän_tekijäpääte then
      return kestämä;
    end;
    if $allomorfi.luokka in @kertoman_tekijäpääte then
      return kertoma;
    end;
  end;
  return nil;
end;

subrule tulostus_vertailu($tietue):
  define $tlen := length($tietue);
  foreach $i in $tlen:
    define $allomorfi := $tietue.($tlen + 1 - $i);
    if $allomorfi.luokka in <voittoaste, yliaste> then
      return $allomorfi.luokka;
    end;
    if $allomorfi.luokka = nimisana then
      return perusaste;
    end;
  end;
  return perusaste;
end;

subrule tulostus_partisiipit($tietue):
  define $tlen := length($tietue);
  foreach $i in $tlen:
    define $allomorfi := $tietue.($tlen + 1 - $i);
    if $allomorfi.laatutapa /= nil then
      return [laatutapa: $allomorfi.laatutapa];
    end;
    if $allomorfi.jluokka = nimisana then
      return [];
    end;
    if $allomorfi.luokka = johdin_vA then
      return [laatutapa: johdin_vA];
    end;
    if $allomorfi.luokka in <johdin_tAvA, johdin_ttAvA> then
      return [laatutapa: johdin_tAvA];
    end;
    if $allomorfi.luokka in @johdin_nUt then
      return [laatutapa: johdin_nUt];
    end;
    if $allomorfi.luokka = johdin_tOn and $i less $tlen and
       ($tietue.($tlen - $i).luokka = johdin_mA or $tietue.($tlen - $i).johdin_mA = yes) then
      return [laatutapa: johdin_tOn];
    end;
    if $allomorfi.luokka = johdin_mA or $allomorfi.johdin_mA = yes then
      return [laatutapa: johdin_mA];
    end;
    if $allomorfi.luokka in <johdin_ttU, johdin_tU, johdin_dU, johdin_lU, johdin_nU, johdin_rU, johdin_tU_lU_oltu> then
      return [laatutapa: johdin_ttU];
    end;
    if $allomorfi.luokka in <nimisana, laatusana, nimi_laatusana, teonsana, tavuviiva, johdin_Us, johdin_UUs> then
      return [];
    end;
  end;
  return [];
end;

subrule tulostus_omistusliitteet($tietue):
  define $tlen := length($tietue);
  foreach $i in $tlen:
    define $allomorfi := $tietue.($tlen + 1 - $i);
    if $allomorfi.omistusliite /= nil then
      return [omistusliite: $allomorfi.omistusliite];
    end;
  end;
  return [];
end;

subrule tulostus_liitepartikkelit($tietue):
  define $tlen := length($tietue);
  define $res := [];
  foreach $i in $tlen:
    define $allomorfi := $tietue.($tlen + 1 - $i);
    if $allomorfi.kysymysliite /= nil then
      $res :=+ [kysymysliite: yes];
    end;
    if $allomorfi.fokus /= nil then
      $res :=+ [fokus: $allomorfi.fokus];
    end;
  end;
  return $res;
end;

subrule tulostus_kielto($tietue):
  define $tlen := length($tietue);
  foreach $i in $tlen:
    define $allomorfi := $tietue.($tlen + 1 - $i);
    if $allomorfi.kielto /= nil then
      if $tlen greater $i then
        define $edellinen := $tietue.($tlen - $i);
        if $edellinen.luokka in <kertoman_tekijäpääte_vahva, kertoman_tekijäpääte_heikko, kestämän_tekijäpääte_heikko> or
          ($edellinen.luokka = teonsana and $edellinen.tapaluokka = tositapa and $edellinen.aikamuoto = kestämä and $allomorfi.kielto = yes) then
          return no;
        end;
      end;
      return $allomorfi.kielto;
    end;
    if $allomorfi.tapaluokka = käskytapa or $allomorfi.luokka = omistusliite then
      return molemmat;
    end;
    if $allomorfi.luokka /= liitesana then
      return no;
    end;
  end;
  return no;
end;

subrule tulostus_nimisanan_attribuutit($tietue):
  define $tlen := length($tietue);
  foreach $i in $tlen:
    define $allomorfi := $tietue.($tlen + 1 - $i);
    if $allomorfi.paikannimen_jälkiliite = yes then
      return [paikannimen_jälkiliite: yes];
    end;
    if $allomorfi.luokka in <nimisana, nimi_laatusana, laatusana, tavuviiva, johdin_UUs> then
      return [];
    end;
  end;
  return [];
end;

subrule debug_sanan_perusmuodon_osa ($tietue, $vain_alku):
    define $res := "+";
    if ($vain_alku or $tietue.perusmuoto = nil) then
      $res :=+ $tietue.alku;
    else
      foreach $i in length($tietue.perusmuoto):
        define $c := substring($tietue.perusmuoto, $i, $i);
        if not $c in <"=", "|"> then
          $res :=+ $c;
        end;
      end;
    end;
    if ($tietue.lähtösana /= nil) then
      $res :=+ "(p=" + $tietue.lähtösana + ")";
    elseif ($tietue.perusmuoto /= nil and $tietue.luokka in <nimisana,
                             seikkasana, laatusana, teonsana,
                             nimi_laatusana, asemosana, suhdesana,
                             huudahdussana, sidesana, etunimi, sukunimi,
                             paikannimi, nimi, kieltosana, lyhenne,
                             lukusana>) then
      $res :=+ "(p=" + $tietue.perusmuoto + ")";
    elseif ($tietue.luokka in @johdin_teonsanasta + @johdin_nimisanasta + @johtimet_laatusanasta) then
      $res :=+ "(p=+";
      if ($tietue.perusmuoto /= nil) then
        $res :=+ $tietue.perusmuoto;
      else
        $res :=+ $tietue.alku;
      end;
      $res :=+ ")";
    end;
    if ($tietue.sourceid /= nil) then
      $res :=+ "(s=" + $tietue.sourceid + ")";
    end;
    return $res;
end;

subrule debug_lukusanan_perusmuodon_osa ($tietue):
  if ($tietue.perusmuoto = "kymmenen") then
    return "+kymmentä";
  elseif ($tietue.perusmuoto = "tuhat") then
    return "+tuhatta";
  elseif ($tietue.alaluokka in <sata, miljoona> and $tietue.lukutyyppi = perusluku) then
    return "+" + $tietue.perusmuoto + "a";
  else
    return debug_sanan_perusmuodon_osa($tietue, no);
  end;
end;

subrule debug_lukusanan_perusmuoto ($tietue, $i, $k, $s):
  # Kopioitu Hannu Väisäsen Sukija-versiosta
  if ($i = 1 and $tietue.$i.alaluokka in <sata, tuhat, miljoona>) then
    return "+" + $tietue.$i.perusmuoto;
  end;

  if ($tietue.$i.luokka = lukusanan_jälkiliite) then
    return "+" + $tietue.$i.perusmuoto;
  end;

  if ($tietue.$i.alaluokka = yksiyhdeksän) then
    if (($k greater 2) and ($i less $k) and ($tietue.($i+1).luokka = sijapääte) and ($tietue.($i+2).luokka /= lukusana)) then
      return "+" + $tietue.$i.alku;
    else
      return "+" + $tietue.$i.perusmuoto;
    end;
  elseif ($tietue.$i.perusmuoto = "kymmenen") then
    if (($i = $k) and (($s matches ".+sataa") or ($s = "sata"))) then
      return "+" + "kymmenen";
    else
      return "+" + "kymmentä";
    end;
  elseif ($tietue.$i.alaluokka in <sata, tuhat, miljoona>) then
    if ($tietue.($i+1).luokka in <lukusana, sijapääte>) then
      return debug_lukusanan_perusmuodon_osa($tietue.$i);
    else
      return "+" + $tietue.$i.perusmuoto;
    end;
  elseif ($tietue.$i.luokka = sijapääte) then
    if ($i less $k and $tietue.($i+1).luokka in <lukusana, lukusanan_jälkiliite>) then
      return "";
    else
      return "+" + $tietue.$i.alku;
    end;
  end;
  return debug_sanan_perusmuodon_osa($tietue.$i, no);
end;

subrule tulostus_debug_perusmuoto($tietue, $luokka):
  # Kopioitu Hannu Väisäsen Sukija-versiosta
  define $n := length ($tietue);
  define $k := $n;

  # Poistetaan sanan lopusta osat, jotka eivät vaikuta perusmuotoon.
  #
  repeat while ($tietue.$k.luokka in <sijapääte, liitesana, liitesana_s, liitesana_pi,
                                      kieltosanan_liitesana, omistusliite, kaksoispiste,
                                      voittoaste, yliaste,
                                      nimitapa_1_pitkä, nimitapa_45>
                                      + @tositavan_tekijäpääte_4 + @kertoman_tekijäpääte
                                      + @kestämän_tekijäpääte + @ehtotapa + @mahtotapa + @käskytapa
                                      + @nimitapa_1 + @nimitapa_2 + @nimitapa_3);
    $k :=- 1;
  end;

  define $s := "";
  define $skip := no;
  foreach $i in $k - 1:
    if $skip then
      $skip := no;
    else
      if ($tietue.$i.lukutyyppi /= nil and $luokka = lukusana) then
        $s :=+ debug_lukusanan_perusmuoto ($tietue, $i, $k, $s);
      elseif (($i+3) less $n and $tietue.$i.luokka = laatusana and $tietue.($i+1).luokka = sijapääte and
              $tietue.($i+2).luokka = tavuviiva and $tietue.($i+3).luokka = paikannimi) then
        # Uudessa-Seelannissa -> Uusi-Seelanti
        $s :=+ debug_sanan_perusmuodon_osa($tietue.$i, no);
        $skip := yes;
      else
        $s :=+ debug_sanan_perusmuodon_osa($tietue.$i, yes);
      end;
    end;
  end;
  if ($k = 1) then
    $s :=+ debug_sanan_perusmuodon_osa($tietue.$k, no);
  elseif ($tietue.$k.lukutyyppi /= nil and $tietue.$k.lukutyyppi = perusluku) then
    $s :=+ debug_lukusanan_perusmuoto ($tietue, $k, $k, $s);
  else
    $s :=+ debug_lukusanan_perusmuodon_osa($tietue.$k);
  end;
  return $s;
end;

subrule tulostus_teonsanan_attribuutit($tietue):
  define $tlen := length($tietue);
  foreach $i in $tlen:
    define $allomorfi := $tietue.($tlen + 1 - $i);
    if $allomorfi.tapaluokka in <nimitapa_2, nimitapa_3, nimitapa_4, nimitapa_5> then
      return [];
    end;
    if $allomorfi.vaatii_tapaluokan /= nil then
      return [vaatii_tapaluokan: $allomorfi.vaatii_tapaluokan, kielto: tulostus_kielto($tietue)];
    end;
  end;
  return [kielto: tulostus_kielto($tietue)];
end;

subrule tulostus_v2($tietue):
  define $res := nil;
  if @voikko_debug then
    define $luokka := tulostus_luokka($tietue);
    $res := [rakenne: tulostus_rakenne($tietue),
            luokka: $luokka,
            luku: tulostus_luku($tietue),
            sijamuoto: tulostus_sijamuoto($tietue),
            perusmuoto: tulostus_debug_perusmuoto($tietue, $luokka)]
          + tulostus_partisiipit($tietue)
          + tulostus_omistusliitteet($tietue)
          + tulostus_liitepartikkelit($tietue);
    if $res.luokka = teonsana then
      $res :=+ [aikamuoto: tulostus_aikamuoto($tietue)];
    elseif $res.luokka in <laatusana, nimi_laatusana> then
      $res :=+ [vertailu: tulostus_vertailu($tietue)];
    end;    
  else
    $res := [rakenne: tulostus_rakenne($tietue),
            luokka:  tulostus_luokka($tietue),
            luku: tulostus_luku($tietue),
            sijamuoto: tulostus_sijamuoto($tietue)];
  end;
  if $res.luokka = teonsana then
    $res :=+ [tekijä: tulostus_tekijä($tietue),
              tapaluokka: tulostus_tapaluokka($tietue)]
           + tulostus_teonsanan_attribuutit($tietue);
  elseif $res.luokka = nimisana then
    $res :=+ tulostus_nimisanan_attribuutit($tietue);
  elseif $res.luokka = kieltosana then
    $res :=+ [tekijä: tulostus_tekijä($tietue),
              tapaluokka: tulostus_tapaluokka($tietue)];
  end;
  define $tlen := length($tietue);
  if $tietue.<1, luokka> /= tavuviiva then
    foreach $i in $tlen:
      define $allomorfi := $tietue.($tlen + 1 - $i);
      if $allomorfi.vapaa_jälkiosa = yes then
        $res :=+ [vapaa_jälkiosa: yes];
        break;
      end;
    end;
  end;
  return $res;
end;

pruning_rule prune ($list):
  define $alut := <>;
  define $filter := <>;
  define $j := 0;
  foreach $an in $list:
    $alut :=+ <($an.1).alku>;
  end;
  $alut := set($alut);
  foreach $an in $list:
    if $j = 30 then
      $filter :=+ <no>;
    else
      if (length($an) greater 1) and ($an.1).alku + ($an.2).alku in $alut then
        $filter :=+ <no>;
      else
        $filter :=+ <yes>;
        $j :=+ 1;
      end;
    end;
  end;
  return $filter;
end;