package jspell;

$VERSION = '0.06';

$ENV{'LC_CTYPE'} = $ENV{'LC_CTYPE'} || 'pt_PT';
use locale;
##use Data::Dumper;

my $MODE;

BEGIN{
  use Exporter ();
  @ISA=qw(Exporter);
  @EXPORT = qw(&setstopwords &der &flags &nlgrep &nlgrep1 &nlgrep2 &nlgrep3 &rad
	       &featags &ok &onethat  &verif &setmode   &fea &any2str &mkradtxt
               &jspell_dict) ;

  
  use IPC::Open3;  # para redireccionar stdin, stdout stderr dum comando

  $MODE = {nm => "af", flags => 0 };
  $delim = '===';
  %stop =();

  # This should be done using the top level makefile/configure system...
  chop($jspell = `which jspell`);
  #$jspell = '/home/jj/bin/jspell' unless (-e $jspell );
  die ("cant find jspell\n") unless (-e $jspell );

  chomp($jspelllib = `jspell-dict --dic-dir`);

  # This should be done using the Makefile.PL
  chop($agrep = `which agrep`);
  #$agrep = '/home/jj/bin/agrep' unless (-e $agrep );
  warn ("cant find agrep\n") unless (-e $agrep );

  $tmp="/tmp";
}


## OO
sub new {
  my ($self, $dr, $pers, $flag);
  local $/="\n";
  $self = shift if ref($_[0]) eq "HASH";

  $self->{dictionary} = shift;
  $self->{pdictionary} = shift ||
    (defined($ENV{HOME})?"$ENV{HOME}/.jspell.$self->{dictionary}":"");

  $pers = $self->{pdictionary}?"-p $self->{pdictionary}":"";
  $flag = defined($self->{'undef'})?$self->{'undef'}:"-y";

  ## Get meta info
  my $meta_file = meta_file($self->{dictionary});
  if (-f $meta_file) {
    open META, $meta_file or die "$!";
    while(<META>) {
      next if m!^\s*$!;
      next if m!^\s*#!;
      s!#.*$!!;
      if (m!^(\w+):\s*(.*)!) {
        $self->{meta}{_}{$1} = $2;
      }
      if (m!^(\w+)=(\w+):\s*(.*)!) {
        $self->{meta}{$1}{$2} = $3;
      }
    }
    close META;
  } else {
    $self->{meta} = {};
  }

  $self->{pid} = open3($self->{DW},$self->{DR},$self->{DE},
		       "$jspell -d $self->{dictionary} -a $pers -W 0 $flag -o'%s!%s:%s:%s:%s'") ||
			 die "Cannot find 'jspell'";
  binmode($self->{DW},":bytes");
  binmode($self->{DR},":bytes");
  $dr = $self->{DR};
  my $first_line = <$dr>;

  $self->{mode} ||= $MODE;
  my $dw = $self->{DW};
  print $dw _mode($self->{mode});

  if ($first_line  =~ /Jspell/) { return bless $self }  #amen
  else                          { return undef}
}


##
sub jspell_dict{
    my $name = shift;
    local $/="\n";
    my $opt;
    if (ref($name) eq "HASH") {
	$opt = $name;
	$name = shift;
    }
    my $pers=shift || (defined( $ENV{HOME}) ? "$ENV{HOME}/.jspell": "" );
    $pers = "-p $pers" if $pers;

    my $flag = defined($opt->{'undef'})?$opt->{'undef'}:"-y";

    $pid1 = open3(DW,DR,DE,
		  "$jspell -d $name -a $pers -W 0 $flag -o'%s!%s:%s:%s:%s'")||
		      die "cant find 'jspell' program";
    binmode(DW,":bytes");
    binmode(DR,":bytes");

    my $first_line = <DR>;                       #skip first line
    ($first_line =~ /Jspell/i) 
}

sub _mode{
  my $m = shift;
  my $r="";
  if ($m->{nm}) {
    if ($m->{nm} eq "af")
      { $r .= "\$G\n\$P\n\$y\n" }
    elsif ($m->{nm} eq "full")
      { $r .= "\$G\n\$Y\n\$m\n" }
    elsif ($m->{nm} eq "cc")
      { $r .= "\$G\n\$P\n\$Y\n" }
    else {}
  }
  if ($m->{flags})          {$r .= "\$z\n"}
  else                      {$r .= "\$Z\n"}
  $r;
}

sub setmode{
  my $self = undef;
  $self = shift if (ref($_[0]) eq "jspell");

  my $dw = $self?$self->{DW}:\*DW;
  my $mode = shift;

  if($mode){
     if( $self){$self->{mode} = $mode }   # save current mode
     else      {$MODE = $mode}
     print $dw _mode($mode); }
  else {
     if( $self){return $self->{mode} }   # save current mode
     else      {return $MODE }
  }
}

sub flags {
  my $self = undef;
  $self = shift if (ref($_[0]) eq "jspell");

  my $w = shift;
  my ($a,$dr);
  local $/="\n";

  if ($self) {
    print {$self->{DW}} "\$\"$w\n";
    $dr = $self->{DR};
    $a = <$dr>;
  } else {
    print DW "\$\"$w\n";
    $a=<DR>;
  }
  chop $a;
  my @b = split(/[# ,]+/,$a);
  @b;
}

sub der {
  my $self = undef;
  $self = shift if (ref($_[0]) eq "jspell");

  my $w = shift;
  my @der;
  if ($self) {
    @der = $self->flags($w);
  } else {
    @der = flags($w);
  }
  my %res = ();
  my $command;

  if ($self) {
    $command = sprintf("echo '%s'|$jspell -d $self->{dictionary} -e -o '' ",join("\n",@der));
  } else {
    ### Isto é sempre 'port'???
    $command = sprintf("echo '%s'|$jspell -d port -e -o '' ",join("\n",@der));
  }

  local $/ = "\n";

  for (`$command`) {
    chop;
    s/(=|, | $)//g;
    for(split) { $res{$_}++; }
  }

  my $irrcomm;

  if ($self) {
    # This need to be tested
    my $irr_file = irr_file($self->{dictionary});
    $irrcomm = sprintf("grep '^%s=' $irr_file",$w);
  } else {
    $irrcomm = sprintf("grep '^%s=' $jspelllib/port.irr",$w);
  }
  for (`$irrcomm`){
    chop;
    for (split(/[= ]+/,$_)) { $res{$_}++; }
  }

  keys %res;
}

sub setstopwords {
  for(@_){ $stop{$_}=1;}
}

sub nlgrepold {
  my $proc=shift;
  my $file_list=join(' ',@_);
  local $/="\n";

  open(TMPp,"> $tmp/_jspell$$") || die(" can't open tmp ");
  for (der($proc)) { print TMPp "$_\n" unless $stop{$_}; }
  close(TMPp);

  my @res=();
  for (`$agrep -h -i -w -f $tmp/_jspell$$ $file_list`) {
    push(@res,$_);
  }
  unlink "$tmp/_jspell$$";
  @res;
}

sub nlgrep{
  my %opt=(max=>10000, sep => "\n",radtxt=>0); # max=int, sep:str, radtxt:bool
  if(ref($_[0]) eq "HASH"){ %opt=(%opt,%{shift(@_)}); }

  my $p=shift;

  my $pattern = $opt{radtxt} ? $p : join("|",(der($p)));
  my $p2 = qr/\b(?:$pattern)\b/i;

  my @file_list=@_;
  local $/=$opt{sep};
  
  my @res=();
  my $n = 0;
  for( @file_list){
    open(F,$_) or die("cant open $_\n");
    while(<F>){
      if(/$p2/){
          chomp;
          s/$delim.*//g if $opt{radtxt};
          push(@res,$_);
          last if $n++ == $opt{max};
      }
    } 
    close F;
    last if $n == $opt{max};
  }
  @res;
}

sub nlgrepold2 {
  my $p=shift;
  my %opt=();           # max=int, sep:str, radtxt:bool
  if(ref($p) eq "HASH"){
    %opt=%$p;
    $p=shift}

  my $file_list=join(' ',@_);
  local $/=$opt{sep} || "\n";

  my $max="";
  $max = "|head -$opt{max}" if $opt{'max'};
  my $sep="";
  $sep = "-d '$opt{sep}' -t " if $opt{sep};

  unless($opt{radtxt}){
    open(TMPp,"> $tmp/_jspell$$") || die(" can't open tmp ");
    for (der($p)) { print TMPp "$_\n" unless $stop{$_}; }
    close(TMPp);
  }

  my @res=();
  if(defined $opt{radtxt}){
    for (`$agrep -h -i -w '$p' $file_list  $max`) {
      chomp;
      s/$delim.*//g;
      push(@res,$_);
    } }
  else{
    for (`$agrep $sep -h -i -w -f $tmp/_jspell$$ $file_list $max`) {
      chomp;
      push(@res,$_);
    } }
  unlink "$tmp/_jspell$$" unless $opt{radtxt};
  @res;
}

sub nlgrep1{
  nlgrep({radtxt=>1},@_);
}
 # my $proc=shift;
 # my $file_list=join(' ',@_);
 # local $/="\n";

 # my @res=();
 # for (`$agrep -h -i -w '$proc' $file_list`) {
 #   if( /(.*?)$delim/){ push(@res,$1) };
 # }
 # @res;
#}

sub nlgrep3 {
  my ($w,$qt,@f)=@_;
  nlgrep({max=>$qt},$w,@f);
}

sub nlgrep2 {
  my ($w,$rs,@f)=@_;
  nlgrep({sep=>$rs},$w,@f);
}

sub rad {
  my $self = undef;
  $self = shift if (ref($_[0]) eq "jspell");

  my $w = shift;

  return () if $w =~ /\!/;

  my %rad = ();
  my $a_ = "";
  local $/ = "\n";

  if ($self) {
    my ($dw,$dr) = ($self->{DW},$self->{DR});
    print $dw " $w\n";
    for ($a_=<$dr>;$a_ ne "\n";$a_=<$dr>) {       # l^e as respostas
      chop $a_;
      %rad = ($a_ =~ m/(?: |:)([^ =:,!]+)(\!)/g ) ;
    }
  } else {
    print DW " $w\n";
    for ($a_=<DR>;$a_ ne "\n";$a_=<DR>) {       # l^e as respostas
      chop $a_;
      %rad = ($a_ =~ m/(?: |:)([^ =:,!]+)(\!)/g ) ;
    }
  }
  (keys %rad);
}

sub fea{
  my $self = undef;
  $self = shift if (ref($_[0]) eq "jspell");

  my $w = shift;

  local $/="\n";

  my @r = ();
  my ($a, $rad, $cla, $flags);

  return () if $w =~ /\!/;

  my ($dw,$dr);
  if ($self) {
    ($dw,$dr) = ($self->{DW},$self->{DR})
  } else {
    ($dw,$dr) = (\*DW, \*DR);
  }

  print $dw " $w\n";
  $a = <$dr>;

  for (;($a ne "\n"); $a=<$dr>) {       # l^e as respostas
    for($a){
      chop;
      my ($lixo,$clas);
      if(/(.*?) :(.*)/){$clas = $2 ; $lixo =$1}
      else             {$clas = $_ ; $lixo =""}

      for(split(/[,;] /,$clas)){
        ($rad,$cla)= m{(.+?)\!:*(.*)$};

	# Não sei porquê, mas acontece por vezes de $cla ser 'undef'
	# Não sei bem o que devemos fazer... de momento, estou simplesmente
	# a passar o código à frente.
	if ($cla) {
	  if ($cla =~ s/\/(.*)$//) { $flags = $1 }
	  else                     { $flags = "" }

	  $cla =~ s/:+$//g;
	  $cla =~ s/:+/,/g;

	  my %ana;
	  my @attrs = split /,/, $cla;
	  for (@attrs) {
	    if (m!=!) {
	      $ana{$`}=$';
	    } else {
	      print STDERR "** WARNING: Feature-structure parse error: $cla (for word '$w')\n";
	    }
	  }

	  $ana{"flags"} = $flags if $flags;

	  if ($lixo =~ /^&/) {
	    $rad =~ s/(.*?)= //;
	    $ana{"guess"} = lc($1);
	    $ana{"unknown"} = 1;
	  }
	  if ($rad ne "" ) {
	    push(@r,+{"rad" => $rad, %ana});
	  }
	}
      }
    }}
  (@r);
}

# verif: cond:fs x ele:fs -> bool
# 
sub verif{ my ($a,$b)=@_;
 for (keys %$a) { return 0 if (!defined($b->{$_}) || $a->{$_} ne $b->{$_}); }
 return 1 ;
}

# onethat: cond:fs x ele:fs-set -> fs
# x st exist x in ele : verif(cond , x)

sub onethat {
  my ($a,@b)=@_;
  for (@b) {
    return %$_ if verif($a,$_);
  }
  return () ;
}

# ok: cond:fs x ele:fs-set -> bool
# exist x in ele : verif(cond , x)

sub ok{ my ($a,@b)=@_;
 for (@b){ return 1 if verif($a,$_); }
 return 0 ;
}

sub any2str {
  my ($r,$i)= @_;
  $i ||= 0;
  if($i eq "compact"){
    if (ref($r) eq "HASH" )
      { "{". hash2str($r,$i) . "}" }
    elsif (ref($r) eq "ARRAY")
      { "[" . join(",", map (any2str($_,$i), @$r)) . "]" }
    else {"$r"}
  }
  else {
    my $ind = ($i >= 0)? (" " x $i) : "";
    if (ref($r) eq "HASH" )
      { "$ind {". hash2str($r,abs($i)+3) . "}" }
    elsif (ref($r) eq "ARRAY")
      { "$ind [\n" . join("\n", map (any2str($_,abs($i)+3), @$r)) . "]" }
    else {"$ind$r"}
  }
}

sub hash2str {
  my($r,$i)=($_[0],$_[1]);
  my $c="";
  if($i eq "compact"){
    for(keys %$r) {$c .= any2str($_,$i). "=". any2str($r->{$_},$i). ",";}
    chop($c);
  }
  else {
    for(keys %$r) {$c .= "\n". any2str($_,$i). " => ". any2str($r->{$_},-$i);}
  }
  $c;
}

sub show_fea {
  my $struct = shift;
  for (keys %$struct) {

    if (/^N$/) {
      print "Number: ",(($struct->{$_} eq "p")?"plural":"singular"),"\n";
      next;
    }

    if (/^G$/) {
      print "Genre: ",(($struct->{$_} eq "m")?"masculine":"feminine"),"\n";
      next;
    }

    if (/^CAT$/) {
      my %significado = (
			 nc => 'common name',
			 adj => 'adjective',
			 a_nc => 'common_name / adjective',
			 adv => 'adverb',
			 prep => 'preposition',
			 in => '??',
			 v => 'verb',
			 pind => '??',
			 con => '??',
			 cp => '??',
			);
      print "Categorie: ",$significado{$struct->{$_}},"\n";
      next;
    }

    print "$_ => $struct->{$_}\n";
  }
}

sub mkradtxt { 
  my ($f1,$f2)=@_;
  open(F1,$f1) or die("cant open $f1\n");
  open(F2,"> $f2") or die("cant create $f2\n");
  while(<F1>){
    chomp;
    print F2 "$_$delim";
    while(/((\w|-)+)/g){print F2 " ",join(" ",rad($1)) unless $stop{$1}};
    print F2 "\n";
  }
  close F1;
  close F2;
}

sub featagsrad{
  my $self = undef;
  $self = shift if (ref($_[0]) eq "jspell");

  my $palavra = shift;

  if ($self) {
    (map {cat2small(%$_).":$_->{rad}"} ($self->fea($palavra)));
  } else {
    (map {cat2small(%$_).":$_->{rad}"} (fea($palavra)));
  }
}


sub featags{
  my $self = undef;
  $self = shift if (ref($_[0]) eq "jspell");

  my $palavra = shift;

  if ($self) {
    (map {cat2small(%$_)} ($self->fea($palavra)));
  } else {
    (map {cat2small(%$_)} (fea($palavra)));
  }
}

sub cat2small {
  my %b = @_;
  if ($b{'CAT'} eq 'art'){   # Artigos: o léxico já prevê todos...
    return "ART";  # por isso, NUNCA SE DEVE CHEGAR AQUI!!!
    # 16 tags
  } elsif ($b{'CAT'} eq 'card'){ # Numerais cardinais:
    return "DNCNP";
    # o léxico já prevê os que flectem (1 e 2); o resto é tudo neutro plural.

  } elsif ($b{'CAT'} eq 'nord'){ # Numerais ordinais:
    return "\UDNO$b{'G'}$b{'N'}";
  }
  # Pronomes:
  elsif ($b{'CAT'} eq 'ppes' || $b{'CAT'} eq 'prel' ||
	 $b{'CAT'} eq 'ppos' || $b{'CAT'} eq 'pdem' ||
	 $b{'CAT'} eq 'pind' || $b{'CAT'} eq 'pint'){
    if    ($b{'CAT'} eq 'ppes'){ $b{'CAT'} = 'PS';} # Pronomes pessoais
    elsif ($b{'CAT'} eq 'prel'){ $b{'CAT'} = 'PR';} # Pronomes relativos
    elsif ($b{'CAT'} eq 'ppos'){ $b{'CAT'} = 'PP';} # Pronomes possessivos
    elsif ($b{'CAT'} eq 'pdem'){ $b{'CAT'} = 'PD';} # Pronomes demonstrativos
    elsif ($b{'CAT'} eq 'pint'){ $b{'CAT'} = 'PI';} # Pronomes interrogativos
    elsif ($b{'CAT'} eq 'pind'){ $b{'CAT'} = 'PF';} # Pronomes indefinidos
    if ($b{'G'} eq '_'){ $b{'G'} = 'N';}
    if ($b{'N'} eq '_'){ $b{'N'} = 'N';}
    return "\U$b{'CAT'}$b{'C'}$b{'G'}$b{'P'}$b{'N'}";
    #                        $b{'C'}: caso latino.
  }
  # Nomes:
  elsif ($b{'CAT'} eq 'nc'){ # Nomes comuns:
    if ($b{'G'} eq '_' || $b{'G'} eq ''){ $b{'G'} = 'N';}
    if ($b{'N'} eq '_' || $b{'N'} eq ''){ $b{'N'} = 'N';}
    return "\U$b{'CAT'}$b{'G'}$b{'N'}";
  }
  elsif ($b{'CAT'} eq 'np'){ # Nomes próprios:
    if ($b{'G'} eq '_' || $b{'G'} eq ''){ $b{'G'} = 'N';}
    if ($b{'N'} eq '_' || $b{'N'} eq ''){ $b{'N'} = 'N';}
    return "\U$b{'CAT'}$b{'G'}$b{'N'}";
  }
  # Adjectivos:
  elsif ($b{'CAT'} eq 'adj'){
    if ($b{'G'} eq '_'){ $b{'G'} = 'N';}
    if ($b{'G'} eq '2'){ $b{'G'} = 'N';}
    if ($b{'N'} eq '_'){ $b{'N'} = 'N';}
    #    elsif ($b{'N'} eq ''){
    #      $b{'N'} = 'N';
    #    }
    return "\UJ$b{'G'}$b{'N'}";
  }
  # Adjectivos que podem funcionar como nomes comuns:
  elsif ($b{'CAT'} eq 'a_nc'){
    if ($b{'G'} eq '_'){ $b{'G'} = 'N';}
    if ($b{'G'} eq '2'){ $b{'G'} = 'N';}
    if ($b{'N'} eq '_'){ $b{'N'} = 'N';}
    #    elsif ($b{'N'} eq ''){
    #      $b{'N'} = 'N';
    #    }
    return "\UX$b{'G'}$b{'N'}";
  }
  # Verbos:
  elsif ($b{'CAT'} eq 'v'){
    # formas nominais:
    if    ($b{'T'} eq 'inf'){ $b{'T'} = 'N';} # infinitivo impessoal
    # (Nome do verbo)
    elsif ($b{'T'} eq 'ppa'){ $b{'T'} = 'PP';} # Particípio Passado
    elsif ($b{'T'} eq 'g'){ $b{'T'} = 'G';} # Gerúndio
    # modo indicativo:
    elsif ($b{'T'} eq 'p'){ $b{'T'} = 'IH';} # presente (Hoje)
    elsif ($b{'T'} eq 'pp'){ $b{'T'} = 'IP';} # pretérito Perfeito
    elsif ($b{'T'} eq 'pi'){ $b{'T'} = 'II';} # pretérito Imperfeito
    elsif ($b{'T'} eq 'pmp'){ $b{'T'} = 'IM';} # pretérito Mais-que-perfeito
    elsif ($b{'T'} eq 'f'){ $b{'T'} = 'IF';} # Futuro
    # modo conjuntivo (Se):
    elsif ($b{'T'} eq 'pc'){ $b{'T'} = 'SH';} # presente (Hoje)
    elsif ($b{'T'} eq 'pic'){ $b{'T'} = 'SI';} # pretérito Imperfeito
    elsif ($b{'T'} eq 'fc'){ $b{'T'} = 'PI';} # Futuro
    # (ver Infinitivo (Pessoal ou Presente) abaixo.
    # modo iMperativo:
    elsif ($b{'T'} eq 'i'){ $b{'T'} = 'MH';} # presente (Hoje)
    # modo Condicional:
    elsif ($b{'T'} eq 'c'){ $b{'T'} = 'CH';} # presente (Hoje)
    # modo Infinitivo (Pessoal ou Presente):
    elsif ($b{'T'} eq 'ip'){ $b{'T'} = 'PI';}
    # Futuro conjuntivo? Só se tiver um "se" antes! -> regras sintácticas...
    # modo&tempo não previstos ainda...
    else{                    $b{'T'} = '_UNKNOWN';}
    # converter 'P=1_3' em 'P=_': provisório(?)!
    if ($b{'P'} eq '1_3'){ $b{'P'} = '_';} # único sítio com '_' como rhs!!!
    return "\U$b{'CAT'}$b{'T'}$b{'G'}$b{'P'}$b{'N'}";
    #                               Género, só para VPP.
    # +/- 70 tags
  }
  # Preposições¹:
  elsif ($b{'CAT'} eq 'prep'){ return "\UP";}
  # Advérbios²:
  elsif ($b{'CAT'} eq 'adv'){ return "\UADV";}
  # Conjunções²:
  elsif ($b{'CAT'} eq 'con'){ return "\UC";}
  # Interjeições¹:
  elsif ($b{'CAT'} eq 'in'){ return "\UI";}
  # ¹: não sei se a tag devia ser tão atómica, mas para já não há confusão!
  # Contracções¹:
  elsif ($b{'CAT'} =~ m/^cp(.*)/){
    if ($b{'G'} eq '_'){ $b{'G'} = 'N';}
    if ($b{'N'} eq '_'){ $b{'N'} = 'N';}
    return "\U&$b{'G'}$b{'N'}";
  }
  # ²: falta estruturar estes no próprio dicionário...
  # Palavras do dicionário com categoria vazia ou sem categoria,
  # palavras não existentes ou sequências aleatórias de caracteres:
  elsif ($b{'CAT'} eq ''){ return "\UUNDEFINED";}
  # restantes categorias (...?)
  else { return "\UUNTREATED";}

}

sub meta_file {
  my $dic_file = shift;
  if ($dic_file =~ m!\.hash$!) {
    # we have a local dictionary
    $dic_file =~ s/\.hash/.meta/;
  } else {
    $dic_file = "$jspelllib/$dic_file.meta"
  }
  return $dic_file;
}


sub irr_file {
  my $irr_file = shift;
  if ($irr_file =~ m!\.hash$!) {
    # we have a local dictionary
    $irr_file =~ s/\.hash/.irr/;
  } else {
    $irr_file = "$jspelllib/$irr_file.irr"
  }
  return $irr_file;
}



1;
__END__

=head1 NAME

Jspell.pm - a perl module for natural language processing

=head1 SYNOPSIS

 #----( Object Oriented way )-------

  $pt_dict = jspell::new($dict);        # open dict
  $pt_dict = jspell::new($dict, $pdict) # open dict and personal dict

  $pt_dict->flags($word);      # list of possible roots and flags
  $pt_dict->der($word);        # list of derived words
  $pt_dict->rad($word);        # list of possible normalized words
  $pt_dict->fea(word);	       # list of analisis
  $pt_dict->featags(word);     # list of analisis tags
  $pt_dict->featagsrad(word);  # list of analisis tags/lemmas

  $pt_dict->setmode({nm => 0, flags=> 1})  # no near misses; show flags 
                               # ids in the fea

 #---( old way )----------------

  jspell_dict(dict,[personaldict])      -> open a dictionary hash file

  flags(word)          	-> list of possible roots and flags
  der(word)            	-> list of derived words
  rad(word)            	-> list of possible normalized words
  fea(word)            	-> list of analisis
  featags(word)        	-> list of analisis (one word output)

  setmode({flags => 1, ...})     -> show flags ids in the fea
  setmode({flags => 0, ...})     -> dont show flags
  setmode({nm =>0, ...})         -> no near misses
  setmode({nm =>"full", ...})    -> full near misses
  setmode({nm =>"af", ...})      -> near misses using only affix rules
  setmode({nm =>"cc", ...})      -> near misses by  change one char

  nlgrep(word,filename*) 		-> list of lines

  nlgrep1(agreppatt,filename*) 	        -> list of lines in pre-norm. files
  nlgrep2(word,RecSeparator,filename*) 	-> list of lines
  nlgrep3(word,N,filename*)	 	-> list up to N lines

  setstopwords(word*)	 	        -> words to be avoid in nlgrep

 #---(generic functions )------------------------

  mkradtxt(file1,file)    -> makes a pre-norm file of file1

  any2str(FS)             -> converts FS in a string

  verif(FS,FS)            -> fs2 verifies fs1
  ok(FS,FS-set)           -> there is one fs in fs2 that verif. fs1
  onethat(FS,FS-set)      -> returns a fs in fs2 that verifies fs1

=head1 DESCRIPTION

jspell.pm uses the "jspell" program in background.

You need: jspell, agrep

=head1 Example

  #!/usr/local/bin/perl

  use jspell;
  my $a;
  jspell_dict("port");

  while(<>){
    chop;
    $a= { NAME => $_,
          FLA  => [flags($_)],
          DER  => [der($_)],
          PRO  => [nlgrep($_,"/home/jj/public_html/pln/proverbio.dic")] };

    $a->{RAD}    = [rad($_)];
    $a->{JSPELL} = [fea($_)];

    print any2str($a,2);
  }

=head1 DESCRIPTION

=head2 C<jspell_dict>

Loads a dictionary (and optionally a personal dictionary).

This should be the first function to be called (before flags, der, rad, fea,
nlgrep, nlgrep2, nlgrep3; nlgrep1 is independent of jspell_dict).

   jspell_dict("port","personal.dic");
   jspell_dict("port");

=head2 C<fea>

  @l = fea(word)

Returns a list of analisys of a word. Each analisys is a list of attribute 
value pairs. Attributes available: CAT, T, G, N, P, ....

=head2 C<rad>

  @l = rad(word)

Returns a list of possible lemmas.

=head2 C<nlgrep>

 nlgrep({options}, word, file*)

Finds lines (registers) containing words derived from a given word.

Several options available:

 nlgrep({max => 20}, word, files) -- define a maximum nunmber of lines
 nlgrep({sep => "===" }, word, files) -- define a register separator
 nlgrep({radtxt => 1 }, word, files) -- files are in radtxt format

=head2 C<featags>

  @l = featags(word)

Similar to C<fea>.
Returns a list of one-word tags describing the analisys of a word.

=head2 C<featagsrad>

  @l = featagsrad(word)

Similar to C<featags>.
Returns a list of string with "onewordtag:lemma" describing the analisys 
of a word.

=head2 C<verif>

 verif: cond:fs x ele:fs -> bool

Verifies if first FS is a subset of the second


=head2 C<ok>

 ok: cond:fs x ele:fs-set -> bool
 exist x in ele : verif(cond , x)

Verifies if first FS is a subset of any of the other FSs

=head2 C<onethat>

 onethat: cond:fs x ele:fs-set -> fs
 x st exist x in ele : verif(cond , x)

returns a FS fs1 such that it verifies the conditions of the first FS

=head1 AUTHOR

 J.Joao Almeida jj@di.uminho.pt

=head1 SEE ALSO

 perl(1).
 jspell(1)
 agrep(1)

=head1 BUGS

=cut


