1#############################################################################
2##
3#A  fsa4.g                  GAP library                  Derek Holt
4##
5##  1.3.00. created this file from GAP3 version fsa.g and started adapting
6##  it to GAP4.
7##
8##  24/9/98 - corrected dreadful bug in DeleteStateFSA
9##  This file contains those functions that deal with finite state automata.
10##
11## 1.5.96. - code added to deal with the new "list of words" and
12## "list of integers" set-record types.
13##
14DeclareInfoClass("InfoFSA");
15#############################################################################
16#V  _RWST_                external variable - temporary name of list of strings
17#V  _FSA		  external variable - finite state automaton
18#V  _FSA_determinize	  external variable -determinized finite state automaton
19#V  _FSA_min		  external variable - minimized finite state automaton
20#V  _FSA_not		  external variable - `not' finite state automaton
21#V  _FSA_star		  external variable - `star' finite state automaton
22#V  _FSA_reverse	  external variable - `reverse' finite state automaton
23#V  _FSA_exists	  	  external variable - `exists' finite state automaton
24#V  _FSA_swap_coords  	  external variable - `swap' finite state automaton
25#V  _FSA_and  	  	  external variable - `and' finite state automaton
26#V  _FSA_or  	  	  external variable - `or' finite state automaton
27#V  _FSA_concat	 	  external variable - `concat' finite state automaton
28_RWST_ := [];
29_FSA := rec();
30_FSA_determinize := rec();
31_FSA_min := rec();
32_FSA_not := rec();
33_FSA_star := rec();
34_FSA_reverse := rec();
35_FSA_exists := rec();
36_FSA_swap_coords := rec();
37_FSA_and := rec();
38_FSA_or := rec();
39_FSA_concat := rec();
40_KBExtDir  :=  DirectoriesPackagePrograms("kbmag");
41_KBTmpFileName := TmpName();
42
43#############################################################################
44##
45#F  IsFSA(<x>) . . . . . . . test whether x is an fsa
46##
47##  Public function.
48IsFSA := function ( x )
49    return IsRecord(x) and IsBound(x.isFSA) and x.isFSA;
50end;
51
52#############################################################################
53##
54#F  IsInitializedFSA(<x>) . . . . . . . test whether x is an intialized fsa
55##
56##  Public function.
57IsInitializedFSA := function ( x )
58    return IsRecord(x) and IsBound(x.isInitializedFSA) and x.isInitializedFSA;
59end;
60
61#############################################################################
62##
63#F  ExpandFieldSR(<list>) . . expand a sparsely stored name list
64##
65##  ExpandFieldSR takes a sparse <list> as argument and
66##  returns the corresponding dense list (which may have gaps).
67##  e.g. [[1,2],[3,4]] produces output [2,,4].
68##
69##  Private function.
70ExpandFieldSR := function ( list )
71    local newlist, term;
72    newlist := [];
73    for term in list do
74      newlist[term[1]] := term[2];
75    od;
76    return newlist;
77end;
78
79#############################################################################
80##
81#F  InitializeSR(<sr>) . . . . . . . initialize a set-record
82##
83##  <sr> should be a set-record conforming to the criteria.
84##  The criteria are checked, various other fields are calculated and set,
85##  and the existing fields are (partially) checked for validity.
86##
87##  Public function.
88InitializeSR := function ( sr )
89  local  i, j, k, s, ba, lba, nv, fld;
90    if not IsBound(sr.type) or not IsBound(sr.size) then
91       Error("Subfield 'type' or 'size' of set-record field is not set.");
92    fi;
93
94    if IsBound(sr.base) then
95      InitializeSR(sr.base);
96    fi;
97    if IsBound(sr.labels) then
98      InitializeSR(sr.labels);
99    fi;
100
101    # if the set record names are stored in sparse format, we
102    # convert to dense format for internal use - they will still be
103    # printed in sparse format.
104    k := sr.type;
105    if k="words" or k="identifiers" or k="strings" or
106                    k="list of words" or k="list of integers" then
107      if not IsBound(sr.format) or not IsBound(sr.names)
108                                        or not IsList(sr.names) then
109         Error(
110  "Subfield 'format' or 'names' of set-record field is not set or invalid.");
111      fi;
112      sr.printingFormat := sr.format;
113      if sr.format="sparse" then
114         sr.format := "dense";
115         sr.names := ExpandFieldSR(sr.names);
116      fi;
117    fi;
118
119    if k="words" or k="list of words" then
120      if not IsBound(sr.alphabet) or not IsList(sr.alphabet) then
121         Error(
122  "Subfield 'alphabet' of set-record field is not set or invalid.");
123      fi;
124    fi;
125
126    if k="labeled" or k="labelled" then
127      if not IsBound(sr.format)
128         or not IsBound(sr.labels) or not IsRecord(sr.labels)
129         or not IsBound(sr.setToLabels) or not IsList(sr.setToLabels) then
130         Error(
131           "Some required field for type \"labeled\"  is not set or invalid.");
132      fi;
133      sr.printingFormat := sr.format;
134      if  sr.format="sparse" then
135        sr.format := "dense";
136        sr.setToLabels := ExpandFieldSR(sr.setToLabels);
137      fi;
138    fi;
139
140    if k="product" then
141      if not IsBound(sr.padding) or not IsBound(sr.arity)
142          or not IsBound(sr.base) or not IsRecord(sr.base)  then
143         Error("Some required field for type \"product\" is not set.");
144      fi;
145      if IsBound(sr.base.names) then
146    # calculate names of set-record members
147        ba := Concatenation(sr.base.names,[sr.padding]);
148        lba := Length(ba);
149        nv := sr.arity;
150        sr.names := [];
151        fld := sr.names;
152        s := sr.size;
153        for i in [1..s] do
154          fld[i]:=[];
155          k := i-1;
156          for j in Reversed([1..nv]) do
157            fld[i][j] := ba[k mod lba + 1];
158              k := Int(k/lba);
159          od;
160        od;
161      fi;
162    fi;
163
164end;
165
166#############################################################################
167##
168#F  InitializeFSA(<fsa>) . . . . . . . initialize an fsa
169##
170##  <fsa> should be an fsa-record conforming to the criteria.
171##  The criteria are checked, various other fields are calculated and set,
172##  and the existing fields are (partially) checked for validity.
173##  The entries in the flag field ("DFA", "minimized", "BFS", etc.)
174##  are assumed to be valid if set.
175##
176##  Public function.
177InitializeFSA := function ( fsa )
178    local ns, ne, i, j;
179 # First check that the fsa has the 7 compulsory fields.
180    if not IsFSA(fsa) then
181       Error("Argument is not an fsa.");
182    fi;
183    if IsBound(fsa.isInitialized) and fsa.isInitializedFSA = true then
184       Print("This fsa is already initialized.\n");
185       return;
186    fi;
187    if not IsBound(fsa.alphabet) or not IsRecord(fsa.alphabet) then
188       Error("'alphabet' field is not set, or invalid.");
189    fi;
190    if not IsBound(fsa.states) or not IsRecord(fsa.states) then
191       Error("'states' field is not set, or invalid.");
192    fi;
193    if not IsBound(fsa.initial) or not IsList(fsa.initial) then
194       Error("'initial' field is not set, or invalid.");
195    fi;
196    if not IsBound(fsa.accepting) or not IsList(fsa.accepting) then
197       Error("'accepting' field is not set, or invalid.");
198    fi;
199    if not IsBound(fsa.table) or not IsRecord(fsa.table) then
200       Error("'table' field is not set, or invalid.");
201    fi;
202    if not IsBound(fsa.flags) or not IsList(fsa.flags) then
203       Error("'flags' field is not set, or invalid.");
204    fi;
205
206    InitializeSR(fsa.states);
207    InitializeSR(fsa.alphabet);
208
209    ns := fsa.states.size;
210    ne := fsa.alphabet.size;
211
212    fsa.initial := Set(fsa.initial);
213    fsa.accepting := Set(fsa.accepting);
214    fsa.flags := Set(fsa.flags);
215
216    if not IsBound(fsa.table.format) or not IsBound(fsa.table.transitions) then
217       Error("Subfield 'format' or 'transitions' of table field is not set.");
218    fi;
219    fsa.table.printingFormat := fsa.table.format;
220    if fsa.table.format = "dense nondeterministic" then
221	Error("Sorry - dense nondeterministic tables not yet supported.");
222    elif fsa.table.format = "dense deterministic" then
223      fsa.denseDTable := fsa.table.transitions;
224      if Length(fsa.denseDTable) <> ns then
225        Error("Length of transition table wrong.");
226      fi;
227      for i in [1..ns] do
228        for j in [1..ne] do
229          if not IsBound(fsa.denseDTable[i][j]) then
230            fsa.denseDTable[i][j] := 0;
231          fi;
232        od;
233      od;
234    elif fsa.table.format = "sparse" then
235      fsa.sparseTable := fsa.table.transitions;
236      if Length(fsa.sparseTable) <> ns then
237        Error("Length of transition table wrong.");
238      fi;
239    else
240      Error("Invalid transition table format.");
241    fi;
242
243    fsa.isInitializedFSA := true;
244end;
245
246#############################################################################
247##
248#F  FSA(alphabet) . . . . . . . make an initialized FSA with a
249##  specified alphabet and a single state which is both accepting and initial
250##
251##  Public function.
252FSA := function ( alphabet )
253    local F;
254    F := rec();
255    F.isFSA := true;
256    F.alphabet := alphabet;
257    F.states := rec(type := "simple",size := 1);
258    F.flags := ["DFA"];
259    F.initial := [1];
260    F.accepting := [1];
261    F.table := rec(
262      format := "dense deterministic",
263      numTransitions := 0,
264         transitions := [0 * [1..alphabet.size]]
265    );
266    InitializeFSA(F);
267
268    return F;
269end;
270
271#############################################################################
272#F  AlphabetFSA
273##
274##  Public function.
275AlphabetFSA := fsa -> fsa.alphabet;
276
277#############################################################################
278#F  StatesFSA
279##
280##  Public function.
281StatesFSA := fsa -> fsa.states;
282
283#############################################################################
284#F  NumberOfStatesFSA
285##
286##  Public function.
287NumberOfStatesFSA := fsa -> fsa.states.size;
288
289#############################################################################
290#F  NumberOfLettersFSA
291#F  SizeOfAlphabetFSA
292##
293##  Public function.
294NumberOfLettersFSA := fsa -> fsa.alphabet.size;
295SizeOfAlphabetFSA := fsa -> fsa.alphabet.size;
296
297#############################################################################
298##
299#F  IsDeterministicFSA(<fsa>) . . . . . . . test if fsa is deterministic
300##
301##  Tests if the fsa <fsa> is deterministic.
302##  The definition of deterministic used here is that of a partial
303##  deterministic finite state automaton, rather than a total one.
304##  This means, no epsilon-transtions, *at most* one initial state and
305##  *at most one* transition from any state with a given label.
306##  An FSA is is non-deterministic (NFA) if one of these conditions fails.
307##
308##  Public function.
309IsDeterministicFSA := function ( fsa )
310    local term, subterm, dfa_check;
311    if not IsInitializedFSA(fsa) then
312       InitializeFSA(fsa);
313    fi;
314    if "DFA" in fsa.flags then
315      return true;
316    fi;
317    if "NFA" in fsa.flags then
318      return false;
319    fi;
320    if Length(fsa.initial) > 1 then
321      AddSet(fsa.flags,"NFA");
322      return false;
323    fi;
324    if IsBound(fsa.denseDTable) then
325 # This must imply DFA
326      AddSet(fsa.flags,"DFA");
327      return true;
328    fi;
329    for term in fsa.sparseTable do
330    # sparseTable must be bound at this point
331      dfa_check:=[];
332      for subterm in term do
333        if subterm[1]=0 or subterm[1]="epsilon" or
334             IsBound(dfa_check[subterm[1]]) then
335          AddSet(fsa.flags,"NFA");
336          return false;
337        else
338          dfa_check[subterm[1]] := 1;
339        fi;
340      od;
341    od;
342
343    AddSet(fsa.flags,"DFA");
344    return true;
345end;
346
347#############################################################################
348##
349#F  SparseTableToDenseDTableFSA(<fsa>) . . get DenseDTable from SparseTable
350##  SparseTableToDenseDTableFSA calculates the DenseDTable of the fsa from
351##  the SparseTable and  returns it.
352##
353##  Private function.
354SparseTableToDenseDTableFSA := function ( fsa )
355    local denseDTable, sparseTable, row, newrow, dt, ne, term, i;
356    ne := fsa.alphabet.size;
357    sparseTable := fsa.sparseTable;
358    if IsBound(fsa.table.defaultTarget) then
359      dt:=fsa.table.defaultTarget;
360    else
361      dt:=0;
362    fi;
363    denseDTable := [];
364    for row in sparseTable do
365      newrow:=[];
366      for i in [1..ne] do newrow[i]:=dt; od;
367      for term in row do
368        if term[1]=0 or newrow[term[1]] <> dt then
369          AddSet(fsa.flags,"NFA");
370       Error("This fsa is nondeterministic, so cannot create DenseDTable.\n");
371        fi;
372        newrow[term[1]]:=term[2];
373      od;
374      Add(denseDTable,newrow);
375    od;
376    return denseDTable;
377end;
378
379#############################################################################
380##
381#F  DenseDTableToSparseTableFSA(<fsa>) . . get SparseTable from DenseDTable
382##  DenseDTableToSparseTableFSA calculates the SparseTable of the fsa
383##  from the DenseDTable and returns it.
384##
385##  Private function.
386DenseDTableToSparseTableFSA := function ( fsa )
387    local denseDTable, sparseTable, row, newrow, dt, ne, i;
388    ne := fsa.alphabet.size;
389    denseDTable := fsa.denseDTable;
390    if IsBound(fsa.table.defaultTarget) then
391      dt:=fsa.table.defaultTarget;
392    else
393      dt:=0;
394    fi;
395    sparseTable := [];
396    for row in denseDTable do
397      newrow:=[];
398      for i in [1..ne] do
399        if row[i] <> dt then
400          Add(newrow,[i,row[i]]);
401        fi;
402      od;
403      Add(sparseTable,newrow);
404    od;
405    return sparseTable;
406end;
407
408#############################################################################
409##
410#F  DenseDTableToBackTableFSA(<fsa>)  . . . . get BackTable from DenseDTable
411##  DenseDTableToBackTableFSA calculates the BackTable of the fsa from the
412##  DenseDTable and  returns it.
413##
414##  Private function.
415DenseDTableToBackTableFSA := function ( fsa )
416    local denseDTable, backTable, row, ne, ns, i, j;
417    ne := fsa.alphabet.size;
418    ns := fsa.states.size;
419    denseDTable := fsa.denseDTable;
420    backTable := [];
421    for i in [1..ns] do
422      backTable[i] := [];
423    od;
424    for i in [1..ns] do
425      row := denseDTable[i];
426      for j in [1..ne] do
427        if row[j] in [1..ns] then
428          Add(backTable[row[j]],[j,i]);
429        fi;
430      od;
431    od;
432    return backTable;
433end;
434
435#############################################################################
436##
437#F  SparseTableToBackTableFSA(<fsa>)  . . . . get BackTable from SparseTable
438##  SparseTableToBackTableFSA calculates the BackTable of the fsa from
439##  the SparseTable and returns it.
440##  The backTable still does not include "default-target" edges.
441##
442##  Private function.
443SparseTableToBackTableFSA := function ( fsa )
444    local sparseTable, backTable, row, ne, ns, i, term;
445    ne := fsa.alphabet.size;
446    ns := fsa.states.size;
447    sparseTable := fsa.sparseTable;
448    backTable := [];
449    for i in [1..ns] do
450      backTable[i] := [];
451    od;
452    for i in [1..ns] do
453      row := sparseTable[i];
454      for term in row do
455        if term[2] in [1..ns] then
456          Add(backTable[term[2]],[term[1],i]);
457        fi;
458      od;
459    od;
460    return backTable;
461end;
462
463#############################################################################
464##
465#F  DenseDTableFSA(<fsa>) . . . . . . . calculates DenseDTable of dfa
466##  DenseDTableFSA calculates the DenseDTable of the fsa <fsa> and returns it.
467##  Public function.
468DenseDTableFSA := function ( fsa )
469    if not IsInitializedFSA(fsa) then
470       InitializeFSA(fsa);
471    fi;
472    if not IsBound(fsa.denseDTable) then
473       fsa.denseDTable := SparseTableToDenseDTableFSA(fsa);
474    fi;
475    return fsa.denseDTable;
476end;
477
478
479#############################################################################
480##
481#F  SparseTableFSA(<fsa>) . . . . . . . calculates DenseDTable of fsa
482##  SparseTableFSA calculates the SparseTable of the fsa and returns it.
483##  Public function.
484SparseTableFSA := function ( fsa )
485    if not IsInitializedFSA(fsa) then
486       InitializeFSA(fsa);
487    fi;
488    if not IsBound(fsa.sparseTable) then
489       fsa.sparseTable := DenseDTableToSparseTableFSA(fsa);
490    fi;
491    return fsa.sparseTable;
492end;
493
494#############################################################################
495##
496#F  BackTableFSA(<fsa>) . . . . . . . calculates BackTable of fsa
497##  BackTableFSA calculates the BackTable of the fsa and returns it.
498##  If calculated from the SparseTable, it will not include "default-target"
499##  edges.
500##  Public function.
501BackTableFSA := function ( fsa )
502    if not IsInitializedFSA(fsa) then
503       InitializeFSA(fsa);
504    fi;
505    if not IsBound(fsa.backTable) then
506       if IsBound(fsa.denseDTable) then
507         fsa.backTable := DenseDTableToBackTableFSA(fsa);
508       else
509         fsa.backTable := SparseTableToBackTableFSA(fsa);
510       fi;
511    fi;
512    return fsa.backTable;
513end;
514
515#############################################################################
516##
517#F  LinePrintFSA(<line> [,<filename>]) . . . . . . . print the line x
518##
519##  LinePrintFSA prints the line (a string) to the terminal (default)
520##  or to file filename  if specified, formatting nicely.
521##  It works by building up the material to be printed line by line as strings,
522##  and calling LinePrintFSA to print each individual line.
523##
524LinePrintFSA := function ( arg )
525    local line, filename;
526    line := arg[1];
527    if Length(arg) = 1 then filename := "";
528    else filename := arg[2];
529    fi; if filename = "" then Print(line,"\n");
530    else AppendTo(filename,line,"\n");
531    fi;
532end;
533
534#############################################################################
535##
536#F  WordToStringSR( <word>, <gens>, <names> )
537##                                     . . . . . converts <word> to a string
538##
539## <word> is a word in genrators <gens>.
540## <names> is a list of printing strings for <gens>.
541## The word is converted into a string representing the word for printing.
542WordToStringSR := function ( word, gens, names )
543   local string, i, l, ls, ng, g, ct, lg;
544   l := Length(word);
545   if l=0 then
546      return "IdWord";
547   fi;
548   string := "";
549   lg := 0; ct := 0;
550   for i in [1..l] do
551     g := Position(gens,Subword(word,i,i));
552     if g <> lg and lg <> 0 then
553        if string<>"" then string := Concatenation(string,"*"); fi;
554
555        ng := names[lg];
556        if ct > 1 then
557           #Check to see if names[lg] ends "^-1"
558           ls := Length(ng);
559           if ls>3 and ng[ls-2]='^' and ng[ls-1]='-' and ng[ls]='1' then
560             string := Concatenation(string,ng{[1..ls-1]},String(ct));
561           else
562             string := Concatenation(string,ng,"^",String(ct));
563           fi;
564        else string := Concatenation(string,ng);
565        fi;
566        ct := 0;
567     fi;
568     lg := g; ct := ct+1;
569   od;
570   if string<>"" then string := Concatenation(string,"*"); fi;
571   ng := names[lg];
572   if ct > 1 then
573       #Check to see if names[lg] ends "^-1"
574       ls := Length(ng);
575       if ls>3 and ng[ls-2]='^' and ng[ls-1]='-' and ng[ls]='1' then
576         string := Concatenation(string,ng{[1..ls-1]},String(ct));
577       else
578         string := Concatenation(string,ng,"^",String(ct));
579       fi;
580   else string := Concatenation(string,ng);
581   fi;
582
583   return string;
584end;
585
586#############################################################################
587##
588#F  WriteSetRecordSR(<sr> [<name>, <filename>, <offset>, <endsymbol>])
589##                                  . . print the set record <sr>
590##
591##  WriteSetRecordSR prints the set record <sr> to the terminal (default)
592##  or to file <filename> if specified, formatting nicely.
593##  It works by building up the material to be printed line by line as strings,
594##  and calling LinePrintFSA to print each individual line.
595##  If the optional string <name> is present, printing is preceded by an
596##  assignment "name:=", so that the resulting file can be read back in.
597##  if the optional positive integer <offset> is present then each line
598##  is preceded by <offset> spaces.
599##  If the optional string <endsymbol> is present, then this is printed at
600##  the end (it is likely to be ";" or ",").
601##
602##  Private function.
603WriteSetRecordSR := function ( arg )
604    local  sr, filename, name, offset, endsymbol, offstring, line, ct, first,
605	   pn,  wstring, i, tempfn, ids;
606    sr := arg[1];
607    if (sr.type="identifiers" or sr.type="words" or sr.type="list of words")
608         and not IsBound(sr.printingStrings) then
609    ## To find out what the printing strings should be, the ony way is to
610    ## output the identifiers as strings to a temporary file and read back in.
611       tempfn:=TmpName();
612       if sr.type="identifiers" then
613         ids:=sr.names;
614       else
615         ids:=sr.alphabet;
616       fi;
617       PrintTo(tempfn,"_RWST_:=[");
618       for i in [1..Length(ids)] do
619         if i>1 then AppendTo(tempfn,","); fi;
620         AppendTo(tempfn,"\"",ids[i],"\"");
621       od;
622       AppendTo(tempfn,"];\n");
623       Read(tempfn);
624       sr.printingStrings:=_RWST_;
625       Exec(Concatenation("/bin/rm -f ",tempfn));
626    fi;
627    filename := "";
628    name := "";
629    endsymbol := "";
630    offset:=0;
631    if Length(arg)>=2 then
632      name := arg[2];
633    fi;
634    if Length(arg)>=3 then
635      filename := arg[3];
636    fi;
637    if Length(arg)>=4 then
638      offset := arg[4];
639    fi;
640    if Length(arg)>=5 then
641      endsymbol := arg[5];
642    fi;
643    if not IsInt(offset) or offset<=0 then
644       offset := 0;
645    fi;
646    offstring := String("",offset);
647    if name = "" then
648      line := "rec (";
649    else
650      line := Concatenation(String(name,16+offset-4)," := rec (");
651    fi;
652    LinePrintFSA(line,filename);
653
654    line := Concatenation(offstring,String("type",16),
655                                                 " := ","\"",sr.type,"\"",",");
656    LinePrintFSA(line,filename);
657    line := Concatenation(offstring,String("size",16)," := ",String(sr.size));
658    if sr.type <> "simple" then
659      line := Concatenation(line,",");
660    fi;
661    LinePrintFSA(line,filename);
662    if sr.type = "product" then
663      line:=Concatenation(offstring,String("arity",16)," := ",
664							String(sr.arity),",");
665      LinePrintFSA(line,filename);
666      line:=Concatenation(offstring,String("padding",16)," := _,");
667      LinePrintFSA(line,filename);
668      WriteSetRecordSR(sr.base,"base",filename,offset+4);
669    elif sr.type="words" or sr.type="identifiers" or sr.type="strings"
670         or sr.type="list of words" or  sr.type="list of integers" then
671      if sr.type="strings" then
672        pn := [];
673        for i in [1..sr.size] do
674           pn[i] := Concatenation("\"",sr.names[i],"\"");
675        od;
676      elif sr.type="words" or sr.type="identifiers" or
677           sr.type="list of words" then
678        pn := sr.printingStrings;
679      fi;
680      if sr.type="words"  or sr.type="list of words" then
681        line := Concatenation(offstring,String("alphabet",16)," := [");
682        ct := 1;
683        while ct <= Length(sr.alphabet) do
684          if ct=1 or Length(line)+Length(pn[ct]) <= 76 then
685            if ct > 1 then
686               line := Concatenation(line,",");
687            fi;
688            line := Concatenation(line,pn[ct]);
689          else
690            line := Concatenation(line,",");
691            LinePrintFSA(line,filename);
692            line := String("",21+offset);
693            line := Concatenation(line,pn[ct]);
694          fi;
695          ct := ct+1;
696        od;
697        line := Concatenation(line,"],");
698        LinePrintFSA(line,filename);
699      fi;
700      line := Concatenation(offstring,String("format",16)," := ");
701      line := Concatenation(line,"\"",sr.printingFormat,"\"",",");
702      LinePrintFSA(line,filename);
703      line := Concatenation(offstring,String("names",16)," := [");
704      if sr.printingFormat="dense" then
705 # recall that the names are always stored internally in dense format.
706        ct := 1;
707        while ct <= sr.size do
708          if sr.type="words" then
709            wstring := WordToStringSR(sr.names[ct],sr.alphabet,pn);
710          elif sr.type="list of words" then
711            wstring:="[";
712            for i in [1..Length(sr.names[ct])] do
713              if i>1 then
714                wstring:=Concatenation(wstring,",");
715              fi;
716              wstring:=Concatenation(wstring,
717                 WordToStringSR(sr.names[ct][i],sr.alphabet,pn) );
718            od;
719            wstring:=Concatenation(wstring,"]");
720          elif sr.type="list of integers" then
721            wstring:="[";
722            for i in [1..Length(sr.names[ct])] do
723              if i>1 then
724                wstring:=Concatenation(wstring,",");
725              fi;
726              wstring:=Concatenation(wstring,String(sr.names[ct][i]));
727            od;
728            wstring:=Concatenation(wstring,"]");
729          else wstring := pn[ct];
730          fi;
731          if ct=1 or Length(line)+Length(wstring) <= 76 then
732            if ct > 1 then
733               line := Concatenation(line,",");
734            fi;
735            line := Concatenation(line,wstring);
736          else
737            line := Concatenation(line,",");
738            LinePrintFSA(line,filename);
739            line := String("",21+offset);
740            line := Concatenation(line,wstring);
741          fi;
742          ct := ct+1;
743        od;
744        line := Concatenation(line,"]");
745        LinePrintFSA(line,filename);
746      else
747        ct := 1;
748        first := true;
749        while ct <= sr.size do
750          if IsBound(sr.names[ct]) then
751             if sr.type="words" then
752                 wstring :=
753                     WordToStringSR(sr.names[ct],sr.alphabet,pn);
754             elif sr.type="list of words" then
755               wstring:="[";
756               for i in [1..Length(sr.names[ct])] do
757                 if i>1 then
758                   wstring:=Concatenation(wstring,",");
759                 fi;
760                 wstring:=Concatenation(wstring,
761                   WordToStringSR(sr.names[ct][i],sr.alphabet,pn) );
762               od;
763               wstring:=Concatenation(wstring,"]");
764             elif sr.type="list of integers" then
765               wstring:="[";
766               for i in [1..Length(sr.names[ct])] do
767                 if i>1 then
768                   wstring:=Concatenation(wstring,",");
769                 fi;
770                 wstring:=Concatenation(wstring,String(sr.names[ct][i]));
771               od;
772               wstring:=Concatenation(wstring,"]");
773             else wstring := pn[ct];
774             fi;
775             if first then
776               line := Concatenation
777                     (line,"[",String(ct),",",wstring,"]");
778               first := false;
779             else
780               line := Concatenation(line,",");
781               LinePrintFSA(line,filename);
782               line := String("",21+offset);
783               line := Concatenation
784                     (line,"[",String(ct),",",wstring,"]");
785             fi;
786          fi;
787          ct := ct+1;
788        od;
789        LinePrintFSA(line,filename);
790        line := Concatenation(String("",20+offset),"]");
791        LinePrintFSA(line,filename);
792      fi;
793    elif sr.type = "labeled" or sr.type="labelled"  then
794      WriteSetRecordSR(sr.labels,"labels",filename,offset+4,",");
795      line := Concatenation(offstring,String("format",16)," := ");
796      line := Concatenation(line,"\"",sr.printingFormat,"\"",",");
797      LinePrintFSA(line,filename);
798      line := Concatenation(offstring,String("setToLabels",16)," := [");
799      if sr.printingFormat="dense" then
800        ct := 1;
801        while ct <= sr.size do
802          if not IsBound(sr.setToLabels[ct]) then
803            if ct>1 then
804              line := Concatenation(line,",");
805            fi;
806          elif ct=1 or
807           Length(line)+Length(String(sr.setToLabels[ct])) <= 76 then
808            if ct > 1 then
809               line := Concatenation(line,",");
810            fi;
811            line := Concatenation(line,String(sr.setToLabels[ct]));
812          else
813            line := Concatenation(line,",");
814            LinePrintFSA(line,filename);
815            line := String("",21+offset);
816            line := Concatenation(line,String(sr.setToLabels[ct]));
817          fi;
818          ct := ct+1;
819        od;
820        line := Concatenation(line,"]");
821        LinePrintFSA(line,filename);
822      else
823        ct := 1;
824        first := true;
825        while ct <= sr.size do
826          if IsBound(sr.setToLabels[ct]) then
827             if first then
828               line := Concatenation
829                     (line,"[",String(ct),",",String(sr.setToLabels[ct]),"]");
830               first := false;
831             else
832               line := Concatenation(line,",");
833               LinePrintFSA(line,filename);
834               line := String("",21+offset);
835               line := Concatenation
836                     (line,"[",String(ct),",",String(sr.setToLabels[ct]),"]");
837             fi;
838          fi;
839          ct := ct+1;
840        od;
841        LinePrintFSA(line,filename);
842        line := Concatenation(String("",20+offset),"]");
843        LinePrintFSA(line,filename);
844      fi;
845    elif sr.type <> "simple" then
846       Error("Invalid type for set record.");
847    fi;
848    if name = "" then
849      line := Concatenation(")",endsymbol);
850    else
851      line := String(")",16+offset-4);
852      line := Concatenation(line,endsymbol);
853    fi;
854    LinePrintFSA(line,filename);
855end;
856
857#############################################################################
858##
859#F  WriteFSA(<fsa> [<name>, <filename>, <endsymbol>]) . . print the fsa "fsa"
860##
861##  WriteFSA prints the fsa <fsa> to the terminal (default) or to
862##  file <filename> if specified, formatting nicely.
863##  It works by building up the material to be printed line by line as strings,
864##  and calling LinePrintFSA to print each individual line.
865##  If the optional string <name> is present, printing is preceded by an
866##  assignment "name:=", so that the resulting file can be read back in.
867##  If the optional string <endsymbol> is present, then this is printed at
868##  the end (it is likely to be ";" or ",").
869##
870##  Public function.
871WriteFSA := function ( arg )
872    local  fsa, ns, ne, filename, name, tabletype, table, endsymbol,
873           line, ct, first, i, j;
874    fsa := arg[1];
875    filename := "";
876    name := "";
877    endsymbol := "";
878    if Length(arg)>=2 then
879      name := arg[2];
880    fi;
881    if Length(arg)>=3 then
882      filename := arg[3];
883    fi;
884    if Length(arg)>=4 then
885      endsymbol := arg[4];
886    fi;
887
888    if not IsInitializedFSA(fsa) then
889      InitializeFSA(fsa);
890    fi;
891    ns := fsa.states.size;
892    ne := fsa.alphabet.size;
893    if filename = "" and name = "" then
894      Print("rec (\n");
895    elif filename = "" and name <> "" then
896      Print(name," := \nrec (\n");
897    elif filename <> "" and name = "" then
898      PrintTo(filename,"rec (\n");
899    else
900      PrintTo(filename,name," := \nrec (\n");
901    fi;
902
903    line := String("isFSA",16);
904    line := Concatenation(line," := true,");
905    LinePrintFSA(line,filename);
906
907    WriteSetRecordSR(fsa.alphabet,"alphabet",filename,4,",");
908    WriteSetRecordSR(fsa.states,"states",filename,4,",");
909
910    line := String("flags",16);
911    line := Concatenation(line," := [");
912    first := true;
913    for i in fsa.flags do
914      if not first then
915        line := Concatenation(line,",");
916      fi;
917      first := false;
918      line := Concatenation(line,"\"",i,"\"");
919    od;
920    line := Concatenation(line,"],");
921    LinePrintFSA(line,filename);
922
923    line := String("initial",16);
924    line := Concatenation(line," := [");
925    ct := 1;
926    while ct<= Length(fsa.initial) do
927      if ct=1 or Length(line)+Length(String(fsa.initial[ct])) <= 76 then
928        if ct > 1 then
929           line := Concatenation(line,",");
930        fi;
931        line := Concatenation(line,String(fsa.initial[ct]));
932      else
933        line := Concatenation(line,",");
934        LinePrintFSA(line,filename);
935        line := String("",21);
936        line := Concatenation(line,String(fsa.initial[ct]));
937      fi;
938      ct := ct+1;
939    od;
940    line := Concatenation(line,"],");
941    LinePrintFSA(line,filename);
942
943    line := String("accepting",16);
944    line := Concatenation(line," := [");
945    ct := 1;
946    if ns>1 and fsa.accepting=[1..ns] then
947       line := Concatenation(line,"1..",String(ns));
948    else
949      while ct<= Length(fsa.accepting) do
950        if ct=1 or Length(line)+Length(String(fsa.accepting[ct])) <= 76 then
951          if ct > 1 then
952             line := Concatenation(line,",");
953          fi;
954          line := Concatenation(line,String(fsa.accepting[ct]));
955        else
956          line := Concatenation(line,",");
957          LinePrintFSA(line,filename);
958          line := String("",21);
959          line := Concatenation(line,String(fsa.accepting[ct]));
960        fi;
961        ct := ct+1;
962      od;
963    fi;
964    line := Concatenation(line,"],");
965    LinePrintFSA(line,filename);
966
967    tabletype := fsa.table.printingFormat;
968
969    if tabletype="dense deterministic"  then
970      if IsDeterministicFSA(fsa) = false then
971        fsa.table.printingFormat := "sparse";
972        tabletype := fsa.table.printingFormat;
973      elif not IsBound(fsa.denseDTable) then
974        DenseDTableFSA(fsa);
975      fi;
976    fi;
977    if tabletype="sparse" and not IsBound(fsa.sparseTable) then
978      SparseTableFSA(fsa);
979    fi;
980    if tabletype="dense deterministic" then
981      table := fsa.denseDTable;
982      fsa.table.format := "dense deterministic";
983 # Calculate number of nontrivial transitions
984      ct := 0;
985      for i in [1..ns] do
986        for j in [1..ne] do
987          if table[i][j] <> 0 then
988            ct := ct+1;
989          fi;
990        od;
991      od;
992    else
993      table := fsa.sparseTable;
994      fsa.table.format := "sparse";
995 # Calculate number of nontrivial transitions
996      ct := 0;
997      for i in [1..ns] do
998        ct := ct + Length(table[i]);
999      od;
1000    fi;
1001    fsa.table.numTransitions := ct;
1002
1003    line := Concatenation(String("table",16)," := rec(");
1004    LinePrintFSA(line,filename);
1005    line := Concatenation(String("format",20),
1006                                      " := ","\"",fsa.table.format,"\"",",");
1007    LinePrintFSA(line,filename);
1008    line := Concatenation(String("numTransitions",20)," := ",
1009                           String(fsa.table.numTransitions),",");
1010    LinePrintFSA(line,filename);
1011    if tabletype = "sparse" and IsBound(fsa.table.defaultTarget) then
1012      line := Concatenation(line,String("defaultTarget",20)," := ");
1013      line := Concatenation(line,String(fsa.table.defaultTarget),",");
1014      LinePrintFSA(line,filename);
1015    fi;
1016    line := Concatenation(String("transitions",20)," := [");
1017    if ns=0 then
1018      LinePrintFSA(line,filename);
1019    fi;
1020    first := true;
1021    for i in [1..ns] do
1022      if first then
1023        line := Concatenation(line,"[");
1024        first := false;
1025      else
1026        line := Concatenation(String("",25),"[");
1027      fi;
1028      ct := 1;
1029      while ct<= Length(table[i]) do
1030        if ct=1 or Length(line)+Length(String(table[i][ct])) <= 76 then
1031          if ct > 1 then
1032             line := Concatenation(line,",");
1033          fi;
1034          line := Concatenation(line,String(table[i][ct]));
1035        else
1036          line := Concatenation(line,",");
1037          LinePrintFSA(line,filename);
1038          line := String("",25);
1039          line := Concatenation(line,String(table[i][ct]));
1040        fi;
1041        ct := ct+1;
1042      od;
1043      line := Concatenation(line,"]");
1044      if i<ns then
1045        line := Concatenation(line,",");
1046      fi;
1047      LinePrintFSA(line,filename);
1048    od;
1049    line := Concatenation(String("",20),"]");
1050    LinePrintFSA(line,filename);
1051    line := Concatenation(String("",16),")");
1052    LinePrintFSA(line,filename);
1053
1054    line := Concatenation(")",endsymbol);
1055    LinePrintFSA(line,filename);
1056end;
1057
1058#############################################################################
1059##
1060#F  ElementNumberSR(<sr>, <el>)  . . . get number of set-record element.
1061##
1062##  <sr> should be a set-record. <el> should either be a positive integer
1063##  representing an element of <sr> or a name for such an element.
1064##  In either case, the number of the element is returned.
1065##  False is returned if <el> is invalid.
1066##
1067##  Private function.
1068ElementNumberSR := function ( sr, el )
1069  local IdWord;
1070  if IsAssocWordWithOne(el) then
1071    IdWord := el^0;
1072  else IdWord := false;
1073  fi;
1074  if IsInt(el) then
1075    if el>sr.size+1 then
1076      return false;
1077    else
1078      return el;
1079    fi;
1080  elif el=IdWord then
1081    return sr.size+1;
1082  elif IsBound(sr.names) then
1083    return Position(sr.names,el);
1084  else
1085    return false;
1086  fi;
1087end;
1088
1089#############################################################################
1090##
1091#F  TargetDFA(<fsa>,<e>,<s>) . . . . . . . target of edge in a dfa
1092##  TargetDFA calculates  and returns the target of the edge in the dfa <fsa>
1093##  with edge <e> and source-state <s>.
1094##  0 is returned if there is no edge.
1095##  <e> can either be the number of an edge or an edge-name.
1096##  <s> can either be the number of a state or a state-name.
1097##  The returned value has same type as <s> (or 0).
1098##  Public function.
1099TargetDFA := function ( fsa,e,s )
1100    local tname, term, row, ns, t, ng;
1101    if not IsInitializedFSA(fsa) then
1102       InitializeFSA(fsa);
1103    fi;
1104    if IsDeterministicFSA(fsa)=false  then
1105       Error("First argument is not a dfa.");
1106    fi;
1107    if IsList(e) then
1108      ng := fsa.alphabet.base.size;
1109      e := (ElementNumberSR(fsa.alphabet.base,e[1])-1) * (ng+1) +
1110       ElementNumberSR(fsa.alphabet.base,e[2]);
1111    else
1112      e := ElementNumberSR(fsa.alphabet,e);
1113    fi;
1114    if e=false then
1115      Error("Second argument is not a valid edge number or label.");
1116    fi;
1117    tname := not IsInt(s);
1118    s := ElementNumberSR(fsa.states,s);
1119    if s=false then
1120       Error("Third argument is not a valid state number or name.");
1121    fi;
1122
1123    ns := fsa.states.size;
1124    if IsBound(fsa.denseDTable) then
1125      t := fsa.denseDTable[s][e];
1126      if t=0 then
1127        return 0;
1128      fi;
1129      if tname then
1130        return  fsa.states.names[t];
1131      fi;
1132      return t;
1133    fi;
1134
1135    row := fsa.sparseTable[s];
1136    for term in row do
1137      if term[1]=e then
1138        t := term[2];
1139        if tname then
1140          if t=0 then
1141            return 0;
1142          fi;
1143          return fsa.states.names[t];
1144        fi;
1145        return t;
1146      fi;
1147    od;
1148    if IsBound(fsa.table.defaultTarget) then
1149      t := fsa.table.defaultTarget;
1150    else
1151      return 0;
1152    fi;
1153    if tname then
1154      if t=0 then
1155        return 0;
1156      fi;
1157      return fsa.states.names[t];
1158    fi;
1159    return t;
1160
1161end;
1162
1163#############################################################################
1164##
1165#F  TargetsFSA(<fsa>,<e>,<s>) . . . . . . . targets of edge
1166##  TargetsFSA calculates the targets of the edges in the fsa <fsa>
1167##  with edge <e> and source-state <s>.
1168##  The result is returned as a list of targets.
1169##  <l> can either be the number of an edge-label or an edge-label-name.
1170##  <s> can either be the number of a state or a state-name.
1171##  The members of the returned list have same type as <s>.
1172##  Public function.
1173TargetsFSA := function ( fsa,e,s )
1174    local tname, term, targets, row, ns, t, ng;
1175    if not IsInitializedFSA(fsa) then
1176       InitializeFSA(fsa);
1177    fi;
1178    if IsList(e) then
1179      ng := fsa.alphabet.base.size;
1180      e := (ElementNumberSR(fsa.alphabet.base,e[1])-1) * (ng+1) +
1181       ElementNumberSR(fsa.alphabet.base,e[2]);
1182    else
1183      e := ElementNumberSR(fsa.alphabet,e);
1184    fi;
1185    if e=false then
1186      Error("Second argument is not a valid edge number or label.");
1187    fi;
1188    tname := not IsInt(s);
1189    s := ElementNumberSR(fsa.states,s);
1190    if s=false then
1191       Error("Third argument is not a valid state number or name.");
1192    fi;
1193
1194    ns := fsa.states.size;
1195    if IsBound(fsa.denseDTable) then
1196      if e=0 then
1197         return [];
1198      fi;
1199      t := fsa.denseDTable[s][e];
1200      if t=0 then
1201        return [];
1202      fi;
1203      if tname then
1204        return [ fsa.states.names[t] ];
1205      fi;
1206      return [ t ];
1207    fi;
1208
1209    row := fsa.sparseTable[s];
1210    targets := [];
1211    for term in row do
1212      if term[1]=e then
1213        if tname then
1214          if term[2]>0 and term[2]<=ns then
1215            Add(targets,fsa.states.names[term[2]]);
1216          fi;
1217        else
1218          Add(targets,term[2]);
1219        fi;
1220      fi;
1221    od;
1222    if targets=[] and IsBound(fsa.table.defaultTarget) then
1223      if tname then
1224        Add(targets,fsa.states.names[fsa.table.defaultTarget]);
1225      else
1226        Add(targets,fsa.table.defaultTarget);
1227      fi;
1228    fi;
1229    return targets;
1230
1231end;
1232
1233#############################################################################
1234##
1235#F  SourcesFSA(<fsa>,<e>,<s>) . . . . . . . sources of edge
1236##  SourcesFSA calculates the sources of the edges in the fsa <fsa>
1237##  with edge <e> and source-state <s>.
1238##  The result is returned as a list of sources.
1239##  <l> can either be the number of an edge-label or an edge-label-name.
1240##  <s> can either be the number of a state or a state-name.
1241##  The members of the returned list have same type as <s>.
1242##  Public function.
1243SourcesFSA := function ( fsa,e,s )
1244    local tname, term, sources, row, ns, i, none, ng;
1245    if not IsInitializedFSA(fsa) then
1246       InitializeFSA(fsa);
1247    fi;
1248    if IsList(e) then
1249      ng := fsa.alphabet.base.size;
1250      e := (ElementNumberSR(fsa.alphabet.base,e[1])-1) * (ng+1) +
1251       ElementNumberSR(fsa.alphabet.base,e[2]);
1252    else
1253      e := ElementNumberSR(fsa.alphabet,e);
1254    fi;
1255    if e=false then
1256      Error("Second argument is not a valid edge number or label.");
1257    fi;
1258    tname := not IsInt(s);
1259    s := ElementNumberSR(fsa.states,s);
1260    if s=false then
1261       Error("Third argument is not a valid state number or name.");
1262    fi;
1263
1264  #We need the backTable for this.
1265    BackTableFSA(fsa);
1266    row := fsa.backTable[s];
1267    sources := [];
1268    ns := fsa.states.size;
1269    for term in row do
1270      if term[1]=e then
1271        if tname then
1272          if term[2]>0 and term[2]<=ns then
1273            Add(sources,fsa.states.names[term[2]]);
1274          fi;
1275        else
1276          Add(sources,term[2]);
1277        fi;
1278      fi;
1279    od;
1280    if IsBound(fsa.table.defaultTarget) and fsa.table.defaultTarget=s and
1281          IsBound(fsa.sparseTable) then
1282    # there may be some "default-target" edges.
1283       for i in [1..ns] do
1284         row := fsa.sparseTable[i];
1285         none := true;
1286         for term in row do
1287           if term[1]=e then none:=false; fi;
1288         od;
1289         if none then
1290           Add(sources,i);
1291         fi;
1292       od;
1293       sources := Set(sources);
1294    fi;
1295    return sources;
1296end;
1297
1298#############################################################################
1299##
1300#F  IsAcceptedWordDFA(<fsa>,<w>) . . . . . . . tests if word accepted by fsa
1301##  IsAcceptedWordDFA tests whether the word <w> is accepted by the
1302##  deterministic fsa <fsa>, and returns true or false.
1303##  <w> can either be a list of labels or a list of label numbers,
1304##  or a word in the labels for <fsa>.
1305##  (In the last case, the labels must be abstract generators.)
1306##  For an n-variable fsa, it can also be a list of n words (which will
1307##  be padded out at the end by the padding symbol).
1308##  Public function.
1309IsAcceptedWordDFA := function ( fsa,w )
1310    local state, label, len, iw, inw, nl, ps, ns, nv, i, pos, dead;
1311    if not IsInitializedFSA(fsa) then
1312       InitializeFSA(fsa);
1313    fi;
1314    if IsDeterministicFSA(fsa)=false then
1315       Error("Argument is not a dfa.");
1316    fi;
1317    if fsa.initial=[] then
1318      return false;
1319    fi;
1320    ns := fsa.states.size;
1321    state := fsa.initial[1];
1322    iw := IsWord(w);
1323    inw := false;
1324    if not iw and not IsList(w) then
1325      Error("Second argument must be a word or a list.");
1326    fi;
1327    if IsBound(fsa.alphabet.arity) and IsList(w) and IsWord(w[1]) then
1328      if not IsBound(fsa.alphabet.base.names) then
1329        Error("Can only span n-tuple of words if base-alphabet has names.");
1330      fi;
1331      if not IsBound(fsa.alphabet.padding) then
1332        Error("Can only span n-tuple of words if there is a padding symbol.");
1333      fi;
1334      ps := fsa.alphabet.padding;
1335      inw := true;
1336      nv := fsa.alphabet.arity;
1337      nl := [];
1338      for i in [1..nv] do
1339        nl[i] := Length(w[i]);
1340      od;
1341      len := Maximum(nl);
1342    elif iw then
1343      len := Length(w);
1344    else
1345      len := Length(w);
1346    fi;
1347    dead := false;
1348    pos := 1;
1349    while not dead and pos <= len do
1350      if inw then
1351         label := [];
1352         for i in [1..nv] do
1353           if pos <= nl[i] then
1354             label[i] := Subword(w[i],pos,pos);
1355           else
1356             label[i] := ps;
1357           fi;
1358         od;
1359      elif iw then
1360        label := Subword(w,pos,pos);
1361      else
1362        label := w[pos];
1363      fi;
1364      state := TargetDFA(fsa,label,state);
1365      if not state in [1..ns] then
1366        dead:=true;
1367      fi;
1368      pos := pos+1;
1369    od;
1370    if dead then
1371      return false;
1372    fi;
1373    return state in fsa.accepting;
1374end;
1375
1376#############################################################################
1377##
1378#F  WordTargetDFA(<fsa>,<w>) . . . . . . . target of word under DFA
1379##  WordTargetDFA finds the target state when the word <w> is read by
1380##  the deterministic fsa <fsa>, and returns this state or 0.
1381##  <w> can either be a list of labels or a list of label numbers,
1382##  or a word in the labels for <fsa>.
1383##  (In the last case, the labels must be abstract generators.)
1384##  For an n-variable fsa, it can also be a list of n words (which will
1385##  be padded out at the end by the padding symbol).
1386##  Public function.
1387WordTargetDFA := function ( fsa,w )
1388    local state, label, len, iw, inw, nl, ps, ns, nv, i, pos, dead;
1389    if not IsInitializedFSA(fsa) then
1390       InitializeFSA(fsa);
1391    fi;
1392    if IsDeterministicFSA(fsa)=false then
1393       Error("Argument is not a dfa.");
1394    fi;
1395    if fsa.initial=[] then
1396      return 0;
1397    fi;
1398    ns := fsa.states.size;
1399    state := fsa.initial[1];
1400    iw := IsWord(w);
1401    inw := false;
1402    if not iw and not IsList(w) then
1403      Error("Second argument must be a word or a list.");
1404    fi;
1405    if IsBound(fsa.alphabet.arity) and IsList(w) and IsWord(w[1]) then
1406      if not IsBound(fsa.alphabet.base.names) then
1407        Error("Can only span n-tuple of words if base-alphabet has names.");
1408      fi;
1409      if not IsBound(fsa.alphabet.padding) then
1410        Error("Can only span n-tuple of words if there is a padding symbol.");
1411      fi;
1412      ps := fsa.alphabet.padding;
1413      inw := true;
1414      nv := fsa.alphabet.arity;
1415      nl := [];
1416      for i in [1..nv] do
1417        nl[i] := Length(w[i]);
1418      od;
1419      len := Maximum(nl);
1420    elif iw then
1421      len := Length(w);
1422    else
1423      len := Length(w);
1424    fi;
1425    dead := false;
1426    pos := 1;
1427    while not dead and pos <= len do
1428      if inw then
1429         label := [];
1430         for i in [1..nv] do
1431           if pos <= nl[i] then
1432             label[i] := Subword(w[i],pos,pos);
1433           else
1434             label[i] := ps;
1435           fi;
1436         od;
1437      elif iw then
1438        label := Subword(w,pos,pos);
1439      else
1440        label := w[pos];
1441      fi;
1442      state := TargetDFA(fsa,label,state);
1443      if not state in [1..ns] then
1444        dead:=true;
1445      fi;
1446      pos := pos+1;
1447    od;
1448    if dead then
1449      return 0;
1450    fi;
1451    return state;
1452end;
1453
1454#############################################################################
1455##
1456#F  AddStateFSA(<fsa>[,<name>]) . . . . . . . adds state to an fsa
1457##  AddStateFSA adds a state to the end of the statelist of the fsa <fsa>.
1458##  It has the optional name or label <name> (but if the state-type is
1459##  "named", then the name must be supplied).
1460##  Public function.
1461AddStateFSA := function ( arg )
1462    local fsa, name, ns, new, i, dt;
1463    fsa := arg[1];
1464    if fsa.states.type = "product" then
1465      Error("Cannot alter a product-type state-list");
1466    fi;
1467    if Length(arg)=1 and IsBound(fsa.states.names) then
1468       Error("You must supply a name for the new state.");
1469    fi;
1470    name:="";
1471    if Length(arg)=2 then
1472      name:=arg[2];
1473    fi;
1474    if not IsInitializedFSA(fsa) then
1475       InitializeFSA(fsa);
1476    fi;
1477    fsa.states.size := fsa.states.size+1;
1478    ns := fsa.states.size;
1479    if name<>"" and IsBound(fsa.states.names) then
1480        fsa.states.names[ns] := name;
1481    fi;
1482    if IsBound(fsa.denseDTable) then
1483      if IsBound(fsa.table.defaultTarget) then
1484        dt := fsa.table.defaultTarget;
1485      else
1486        dt := 0;
1487      fi;
1488      new:=[];
1489      for i in [1..fsa.alphabet.size] do new[i]:=dt; od;
1490      Add(fsa.denseDTable,new);
1491    fi;
1492    if IsBound(fsa.sparseTable) then
1493      new:=[];
1494      Add(fsa.sparseTable,new);
1495    fi;
1496    Unbind(fsa.backTable);
1497    RemoveSet(fsa.flags,"minimized");
1498    RemoveSet(fsa.flags,"trim");
1499    RemoveSet(fsa.flags,"accessible");
1500end;
1501
1502#############################################################################
1503##
1504#F  DeleteListFSA(<l>,<n>) . . . . . . . delete element from list
1505##  DeleteListFSA deletes the <n>-th element from list <l>, and closes
1506##  up. Used for deleting state from an fsa.
1507##
1508DeleteListFSA := function(l,n)
1509    local i, len;
1510    len := Length(l);
1511    for i in [n..len] do
1512      if IsBound(l[i+1]) then
1513        l[i]:=l[i+1];
1514      else
1515        Unbind(l[i]);
1516      fi;
1517    od;
1518end;
1519
1520#############################################################################
1521##
1522#F  DeleteStateFSA(<fsa>) . . . . . . . delete state from an fsa
1523##  DeleteStateFSA deletes the final state of the fsa <fsa>.
1524##  All edges to and from that state are deleted.
1525##  To delete a state other than the final, first call PermuteStatesFSA
1526##  Public function.
1527DeleteStateFSA := function ( fsa )
1528    local  ns, ng, row, i, j;
1529    if fsa.states.type = "product" then
1530      Error("Cannot alter a product-type state-list");
1531    fi;
1532    if not IsInitializedFSA(fsa) then
1533       InitializeFSA(fsa);
1534    fi;
1535    ns := fsa.states.size;
1536    ng := fsa.alphabet.size;
1537    fsa.states.size:=ns-1;
1538    if IsBound(fsa.states.names) then
1539      Unbind(fsa.states.names[ns]);
1540    fi;
1541    if IsBound(fsa.states.setToLabels) then
1542      Unbind(fsa.states.setToLabels[ns]);
1543    fi;
1544    j := Position(fsa.initial,ns);
1545    if j <> fail then
1546      DeleteListFSA(fsa.initial,j);
1547    fi;
1548    j := Position(fsa.accepting,ns);
1549    if j <> fail then
1550      DeleteListFSA(fsa.accepting,j);
1551    fi;
1552    if IsBound(fsa.table.defaultTarget) and fsa.table.defaultTarget=ns then
1553      Unbind(fsa.table.defaultTarget);
1554    fi;
1555    if IsBound(fsa.denseDTable) then
1556      for i in [1..ns-1] do
1557        row:=fsa.denseDTable[i];
1558        for j in [1..ng] do
1559          if row[j]=ns then
1560            row[j]:=0;
1561          fi;
1562        od;
1563      od;
1564      DeleteListFSA(fsa.denseDTable,ns);
1565    fi;
1566    if IsBound(fsa.sparseTable) then
1567      for i in [1..ns-1] do
1568        row:=fsa.sparseTable[i];
1569        j := 1;
1570        while j <= Length(row) do
1571          if row[j][2]=ns then
1572            DeleteListFSA(row,j);
1573            j:=j-1;
1574          fi;
1575          j:=j+1;
1576        od;
1577      od;
1578      DeleteListFSA(fsa.sparseTable,ns);
1579    fi;
1580    Unbind(fsa.backTable);
1581    RemoveSet(fsa.flags,"NFA");
1582    RemoveSet(fsa.flags,"BFS");
1583    RemoveSet(fsa.flags,"minimized");
1584    RemoveSet(fsa.flags,"trim");
1585    RemoveSet(fsa.flags,"accessible");
1586end;
1587
1588#############################################################################
1589##
1590#F  PermuteStatesFSA(<fsa>,p) . . . . . . . permute states of an fsa
1591##  PermuteStatesFSA permutes the states of the fsa <fsa>.
1592##  <p> should be a permutation of the state numbers.
1593##  What was state n is renumbered state n^p.
1594##  Public function.
1595PermuteStatesFSA := function ( fsa,p )
1596    local  ns, term, row, i, j, new, ne;
1597    if fsa.states.type = "product" then
1598      Error("Cannot alter a product-type state-list");
1599    fi;
1600    if not IsInitializedFSA(fsa) then
1601       InitializeFSA(fsa);
1602    fi;
1603    ns := fsa.states.size;
1604    ne := fsa.alphabet.size;
1605    if p = () then
1606      return;
1607    fi;
1608    if not IsPerm(p) or LargestMovedPointPerm(p)>ns then
1609      Error("Second argument is invalid.");
1610    fi;
1611    if IsBound(fsa.states.names) then
1612      new := [];
1613      for i in [1..ns] do
1614         new[i^p]:=fsa.states.names[i];
1615      od;
1616      fsa.states.names := new;
1617    fi;
1618    if IsBound(fsa.states.setToLabels) then
1619      new := [];
1620      for i in [1..ns] do
1621         if IsBound(fsa.states.setToLabels[i]) then
1622           new[i^p]:=fsa.states.setToLabels[i];
1623         fi;
1624      od;
1625      fsa.states.setToLabels := new;
1626    fi;
1627    for i in [1..Length(fsa.initial)] do
1628       fsa.initial[i]:=fsa.initial[i]^p;
1629    od;
1630    fsa.initial := Set(fsa.initial);
1631    for i in [1..Length(fsa.accepting)] do
1632       fsa.accepting[i]:=fsa.accepting[i]^p;
1633    od;
1634    fsa.accepting := Set(fsa.accepting);
1635    if IsBound(fsa.table.defaultTarget) and fsa.table.defaultTarget>0 then
1636      fsa.table.defaultTarget := fsa.table.defaultTarget^p;
1637    fi;
1638    if IsBound(fsa.denseDTable) then
1639      new := [];
1640      for i in [1..ns] do
1641        row := fsa.denseDTable[i];
1642        new[i^p] := row;
1643        for j in [1..ne] do
1644          if row[j]>0 then
1645            row[j] := row[j]^p;
1646          fi;
1647        od;
1648      od;
1649      fsa.denseDTable := new;
1650    fi;
1651    if IsBound(fsa.sparseTable) then
1652      new := [];
1653      for i in [1..ns] do
1654        row := fsa.sparseTable[i];
1655        new[i^p] := row;
1656        for term in row do
1657          if term[2]>0 then
1658            term[2] := term[2]^p;
1659          fi;
1660        od;
1661      od;
1662      fsa.sparseTable := new;
1663    fi;
1664    if fsa.table.format = "dense deterministic" then
1665      fsa.table.transitions := fsa.denseDTable;
1666    else
1667      fsa.table.transitions := fsa.sparseTable;
1668    fi;
1669    Unbind(fsa.backTable);
1670    RemoveSet(fsa.flags,"BFS");
1671end;
1672
1673#############################################################################
1674##
1675#F  AddLetterFSA(<fsa>[,<name>]) . . . . . . . adds letter to alphabet of fsa
1676##  AddLetterFSA adds an extra symbol to the alphabet of the fsa <fsa>.
1677##  It has the optional name or label <name> (but if the alphabet-type is
1678##  a named type, then the name must be supplied).
1679##  Public function.
1680AddLetterFSA := function ( arg )
1681    local  fsa, name,  ne, new, term;
1682    fsa := arg[1];
1683    if not IsInitializedFSA(fsa) then
1684       InitializeFSA(fsa);
1685    fi;
1686    if fsa.alphabet.type = "product" then
1687      Error("Cannot alter a product-type alphabet");
1688    fi;
1689    if Length(arg)=1 and IsBound(fsa.alphabet.names) then
1690       Error("You must supply a name for the new state.");
1691    fi;
1692    name := "";
1693    if Length(arg)=2 then
1694      name := arg[2];
1695    fi;
1696    if not IsInitializedFSA(fsa) then
1697       Error("First argument is not an initialized fsa.");
1698    fi;
1699    fsa.alphabet.size := fsa.alphabet.size+1;
1700    ne := fsa.alphabet.size;
1701    if name<>""  and  IsBound(fsa.alphabet.names) then
1702        fsa.alphabet.names[ne] := name;
1703    fi;
1704    if IsBound(fsa.denseDTable) then
1705      for term in fsa.denseDTable do
1706        if IsBound(fsa.table.defaultTarget) then
1707          Add(term,fsa.table.defaultTarget);
1708        else
1709          Add(term,0);
1710        fi;
1711      od;
1712    fi;
1713end;
1714
1715#############################################################################
1716##
1717#F  DeleteLetterFSA(<fsa>) . . . . . . . deletes alphabet letter from an fsa
1718##  DeleteLetterFSA deletes the final alphabet label from the fsa <fsa>.
1719##  All edges with that label are deleted.
1720##  To delete an edge-label other than the final, first call
1721##  PermuteLettersFSA
1722##  Public function.
1723DeleteLetterFSA := function ( fsa )
1724    local  ns, ne, term, row, i, j;
1725    if not IsInitializedFSA(fsa) then
1726       InitializeFSA(fsa);
1727    fi;
1728    if fsa.alphabet.type = "product" then
1729      Error("Cannot alter a product-type alphabet");
1730    fi;
1731    ns := fsa.states.size;
1732    ne := fsa.alphabet.size;
1733    fsa.alphabet.size := ne-1;
1734    if IsBound(fsa.alphabet.names) then
1735      Unbind(fsa.alphabet.names[ne]);
1736    fi;
1737    if IsBound(fsa.alphabet.setToLabels) then
1738      Unbind(fsa.alphabet.setToLabels[ne]);
1739    fi;
1740    RemoveSet(fsa.flags,"BFS");
1741    RemoveSet(fsa.flags,"minimized");
1742    if IsBound(fsa.denseDTable) then
1743      for row in fsa.denseDTable do
1744        DeleteListFSA(row,ne);
1745      od;
1746    fi;
1747    if IsBound(fsa.sparseTable) then
1748      for row in fsa.sparseTable do
1749        j := 1;
1750        while j <= Length(row) do
1751          if row[j][1]=ne then
1752            DeleteListFSA(row,j);
1753          fi;
1754          j:=j+1;
1755        od;
1756      od;
1757    fi;
1758    Unbind(fsa.backTable);
1759    RemoveSet(fsa.flags,"NFA");
1760    RemoveSet(fsa.flags,"BFS");
1761    RemoveSet(fsa.flags,"minimized");
1762    RemoveSet(fsa.flags,"trim");
1763    RemoveSet(fsa.flags,"accessible");
1764end;
1765
1766#############################################################################
1767##
1768#F  PermuteLettersFSA(<fsa>,p) . . . . . . . permute alphabet labels of an fsa
1769##  PermuteLettersFSA permutes the alphabet labels of the fsa <fsa>.
1770##  <p> should be a permutation of the alphabet labels numbers.
1771##  What was edge-label n is renumbered alphabet label n^p.
1772##  Public function.
1773PermuteLettersFSA := function ( fsa,p )
1774    local  ns, ne, term, row, i, j, new;
1775    if not IsInitializedFSA(fsa) then
1776       InitializeFSA(fsa);
1777    fi;
1778    if fsa.alphabet.type = "product" then
1779      Error("Cannot alter a product-type alphabet");
1780    fi;
1781    ns := fsa.states.size;
1782    ne := fsa.alphabet.size;
1783    if not IsPerm(p) or LargestMovedPointPerm(p)>ne then
1784      Error("Second argument is invalid.");
1785    fi;
1786    if IsBound(fsa.alphabet.names) then
1787      new := [];
1788      for i in [1..ne] do
1789        new[i^p]:=fsa.alphabet.names[i];
1790      od;
1791      fsa.alphabet.names := new;
1792    fi;
1793    if IsBound(fsa.alphabet.setToLabels) then
1794      new := [];
1795      for i in [1..ne] do
1796         if IsBound(fsa.alphabet.setToLabels[i]) then
1797           new[i^p]:=fsa.alphabet.setToLabels[i];
1798         fi;
1799      od;
1800      fsa.alphabet.setToLabels := new;
1801    fi;
1802    if IsBound(fsa.denseDTable) then
1803      for i in [1..ns] do
1804        row := fsa.denseDTable[i];
1805        new := [];
1806        for j in [1..ne] do
1807          new[j^p] := row[j];
1808        od;
1809        fsa.denseDTable[i] := new;
1810      od;
1811    fi;
1812    if IsBound(fsa.sparseTable) then
1813      new := [];
1814      for i in [1..ns] do
1815        row := fsa.sparseTable[i];
1816        for term in row do
1817          if term[1]>0 then
1818            term[1] := term[1]^p;
1819          fi;
1820        od;
1821      od;
1822    fi;
1823    if fsa.table.format = "dense deterministic" then
1824      fsa.table.transitions := fsa.denseDTable;
1825    else
1826      fsa.table.transitions := fsa.sparseTable;
1827    fi;
1828    Unbind(fsa.backTable);
1829    RemoveSet(fsa.flags,"BFS");
1830end;
1831
1832#############################################################################
1833#F  AddEdgeFSA(<fsa>,<e>,<s>,<t>) . . . . . . . adds edge to an fsa
1834##  AddEdge adds an edge with source <s>, label <e> and target <t>
1835##  to the fsa <fsa> (if there isn't one already.
1836##  <s> and <t> can be either numbers or names of states,
1837##  and <e> a number or name of an edge-label.
1838##  Public function.
1839AddEdgeFSA := function ( fsa, e, s, t )
1840    local row, term, ng;
1841    if not IsInitializedFSA(fsa) then
1842       InitializeFSA(fsa);
1843    fi;
1844    if IsList(e) then
1845      ng := fsa.alphabet.base.size;
1846      e := (ElementNumberSR(fsa.alphabet,e[1])-1) * (ng+1) +
1847       ElementNumberSR(fsa.alphabet,e[2]);
1848    else
1849      e := ElementNumberSR(fsa.alphabet,e);
1850    fi;
1851    if e=false then
1852      Error("Second argument is not a valid edge number or label.");
1853    fi;
1854    s := ElementNumberSR(fsa.states,s);
1855    if s=false then
1856       Error("Third argument is not a valid state number or name.");
1857    fi;
1858    t := ElementNumberSR(fsa.states,t);
1859    if t=false then
1860       Error("Fourth argument is not a valid state number or name.");
1861    fi;
1862    if e=0 or (IsBound(fsa.denseDTable) and fsa.denseDTable[s][e]>0 and
1863       fsa.denseDTable[s][e]<=fsa.states.size and
1864       fsa.denseDTable[s][e] <> t) then
1865    # makes non-deterministic.
1866      Print("Warning: gone non-deterministic!\n");
1867      SparseTableFSA(fsa);
1868      fsa.table.format := "sparse";
1869      fsa.table.transitions := fsa.sparseTable;
1870      Unbind(fsa.denseDTable);
1871      RemoveSet(fsa.flags,"DFA");
1872      AddSet(fsa.flags,"NFA");
1873    fi;
1874    if IsBound(fsa.denseDTable) then
1875      fsa.denseDTable[s][e] := t;
1876    fi;
1877    if IsBound(fsa.sparseTable) then
1878      row := fsa.sparseTable[s];
1879      for term in row do
1880        if term[1]=e and term[2]>0 and term[2]<=fsa.states.size
1881            and term[2] <> t then
1882          #makes non-deterministic
1883          RemoveSet(fsa.flags,"DFA");
1884          AddSet(fsa.flags,"NFA");
1885          fsa.table.format := "sparse";
1886        fi;
1887      od;
1888      Add(row,[e,t]);
1889      fsa.sparseTable[s]:=Set(row);
1890    fi;
1891    Unbind(fsa.backTable);
1892    RemoveSet(fsa.flags,"BFS");
1893    RemoveSet(fsa.flags,"minimized");
1894end;
1895
1896#############################################################################
1897##
1898#F  DeleteEdgeFSA(<fsa>,<e>,<s>,<t>) . . . . . . . deletes edge from an fsa
1899##  DeleteEdgeFSA deletes an edge with source <s>, label <e> and target <t>
1900##  from the fsa <fsa> (if there is one).
1901##  <s> and <t> can be either numbers or names of states (but both the same),
1902##  and <e> a number or name of an edge-label.
1903##  Public function.
1904DeleteEdgeFSA := function ( fsa, e, s, t )
1905    local ng, row, term, subterm,  j, dfa_check;
1906    if not IsInitializedFSA(fsa) then
1907       InitializeFSA(fsa);
1908    fi;
1909    if IsList(e) then
1910      ng := fsa.alphabet.base.size;
1911      e := (ElementNumberSR(fsa.alphabet,e[1])-1) * (ng+1) +
1912       ElementNumberSR(fsa.alphabet,e[2]);
1913    else
1914      e := ElementNumberSR(fsa.alphabet,e);
1915    fi;
1916    if e=false then
1917      Error("Second argument is not a valid edge number or label.");
1918    fi;
1919    s := ElementNumberSR(fsa.states,s);
1920    if s=false then
1921       Error("Third argument is not a valid state number or name.");
1922    fi;
1923    t := ElementNumberSR(fsa.states,t);
1924    if t=false then
1925       Error("Fourth argument is not a valid state number or name.");
1926    fi;
1927    if IsBound(fsa.denseDTable) and fsa.denseDTable[s][e] = t then
1928      fsa.denseDTable[s][e]:=0;
1929    fi;
1930    if IsBound(fsa.sparseTable) then
1931      row := fsa.sparseTable[s];
1932      j := 1;
1933      while j <= Length(row) do
1934        term := row[j];
1935        if term[1]=e and term[2] = t then
1936          DeleteListFSA(row,j);
1937        fi;
1938        j := j+1;
1939      od;
1940    fi;
1941    Unbind(fsa.backTable);
1942    RemoveSet(fsa.flags,"BFS");
1943    RemoveSet(fsa.flags,"minimized");
1944    RemoveSet(fsa.flags,"NFA");
1945    # may have gone deterministic.
1946    RemoveSet(fsa.flags,"accessible");
1947    RemoveSet(fsa.flags,"trim");
1948end;
1949
1950#############################################################################
1951##
1952#F  AcceptingStatesFSA(<fsa>) . . . the accepting states of <fsa>
1953##
1954AcceptingStatesFSA := function(fsa)
1955  return fsa.accepting;
1956end;
1957
1958#############################################################################
1959##
1960#F  SetAcceptingFSA(<fsa>, <s>, <flag>) . . . set category of state <s>
1961##
1962##  s should be a number or name of a state in fsa <fsa>. This state
1963##  is made into an accepting state or not according to whether
1964##  <flag> is true or false.
1965##
1966##  Public function
1967SetAcceptingFSA := function(fsa, s, flag)
1968    if not IsInitializedFSA(fsa) then
1969       InitializeFSA(fsa);
1970    fi;
1971    s := ElementNumberSR(fsa.states,s);
1972    if s=false then
1973       Error("Second argument is not a valid state number or name.");
1974    fi;
1975    if flag = true then
1976      AddSet(fsa.accepting,s);
1977    else
1978      RemoveSet(fsa.accepting,s);
1979    fi;
1980    RemoveSet(fsa.flags,"trim");
1981    RemoveSet(fsa.flags,"minimized");
1982end;
1983
1984#############################################################################
1985##
1986#F  InitialStatesFSA(<fsa>) . . . the initial states of <fsa>
1987##
1988InitialStatesFSA := function(fsa)
1989  return fsa.initial;
1990end;
1991
1992#############################################################################
1993##
1994#F  SetInitialFSA(<fsa>, <s>, <flag>) . . . set initiality of state <s>
1995##
1996##  s should be a number or name of a state in fsa <fsa>. This state
1997##  is made into an initial state or not according to whether
1998##  <flag> is true or false.
1999##
2000##  Public function
2001SetInitialFSA := function(fsa, s, flag)
2002    if not IsInitializedFSA(fsa) then
2003       InitializeFSA(fsa);
2004    fi;
2005    s := ElementNumberSR(fsa.states,s);
2006    if s=false then
2007       Error("Second argument is not a valid state number or name.");
2008    fi;
2009    if flag = true then
2010      AddSet(fsa.initial,s);
2011    else
2012      RemoveSet(fsa.initial,s);
2013    fi;
2014    RemoveSet(fsa.flags,"trim");
2015    RemoveSet(fsa.flags,"accessible");
2016    RemoveSet(fsa.flags,"BFS");
2017    RemoveSet(fsa.flags,"minimized");
2018    if Length(fsa.initial) > 1 then
2019      RemoveSet(fsa.flags,"DFA");
2020    else
2021      RemoveSet(fsa.flags,"NFA");
2022    fi;
2023end;
2024
2025#############################################################################
2026##
2027#F  IsAccessibleFSA(<fsa>) . . . . . . . test whether fsa is a accessible fsa
2028##
2029## An accessible FSA is one in which there is a word in the language
2030## leading to every state.
2031## The string "accessible" is inserted in the list of flags when it is known
2032## to be accessible.
2033## Note that "trim" implies "accessible".
2034##
2035## Public function.
2036IsAccessibleFSA := function ( fsa )
2037    local ns, ne, ct, i, j, got, s, x, t;
2038    if not IsInitializedFSA(fsa) then
2039       InitializeFSA(fsa);
2040    fi;
2041    if "accessible" in fsa.flags or "trim" in fsa.flags then
2042      return true;
2043    fi;
2044    ns := fsa.states.size;
2045    ne := fsa.alphabet.size;
2046 # Find all accessible states i.e. states accessible from an initial state.
2047    got := ShallowCopy(fsa.initial);
2048    ct := Length(got);
2049    i := 1;
2050    while i <= ct do
2051      s := got[i];
2052      for j in [0..ne] do
2053        x := TargetsFSA(fsa,j,s);
2054        for t in x do
2055          if t>0 and not t in got then
2056            ct := ct+1;
2057            got[ct] := t;
2058          fi;
2059        od;
2060      od;
2061      i := i+1;
2062    od;
2063    if ct <> ns then
2064 # there are some inaccessible states, so fsa is not accessible.
2065      return false;
2066    fi;
2067
2068    AddSet(fsa.flags,"accessible");
2069    return true;
2070end;
2071
2072#############################################################################
2073##
2074#F  AccessibleFSA(<fsa>) . . . . . . . replace fsa by an accessible fsa
2075##
2076## AccessibleFSA(<fsa>) removes non-accessible from
2077## <fsa> to make it accessible, without changing the accepted language.
2078## An accessible FSA is one in which there is a word in the language
2079## leading to every state.
2080## The string "accessible" is inserted in the list of flags when it is known
2081## to be accessible.
2082##
2083## Public function.
2084AccessibleFSA := function ( fsa )
2085    local ns, ne, ct, i, j, got, s, x, t;
2086    if not IsInitializedFSA(fsa) then
2087       InitializeFSA(fsa);
2088    fi;
2089    if "accessible" in fsa.flags or "trim" in fsa.flags then
2090      return;
2091    fi;
2092    ns := fsa.states.size;
2093    ne := fsa.alphabet.size;
2094 # Find all accessible states i.e. states accessible from an initial state.
2095    got := ShallowCopy(fsa.initial);
2096    ct := Length(got);
2097    i := 1;
2098    while i <= ct do
2099      s := got[i];
2100      for j in [0..ne] do
2101        x := TargetsFSA(fsa,j,s);
2102        for t in x do
2103          if t>0 and not t in got then
2104            ct := ct+1;
2105            got[ct] := t;
2106          fi;
2107        od;
2108      od;
2109      i := i+1;
2110    od;
2111    if ct <> ns then
2112 # there are some inaccessible states, so remove them - because we can only
2113 # remove the last state, we have to work from the back.
2114      for s in Reversed([1..ns]) do
2115        if not s in got then
2116          if s<ns then
2117            PermuteStatesFSA(fsa,(s,ns));
2118          fi;
2119          DeleteStateFSA(fsa);
2120          ns := ns-1;
2121        fi;
2122      od;
2123    fi;
2124
2125    AddSet(fsa.flags,"accessible");
2126end;
2127
2128#############################################################################
2129##
2130#F  IsTrimFSA(<fsa>) . . . . . . . test whether fsa is a trim fsa
2131##
2132## A trim FSA is one in which there is an accepted word in the language
2133## through every state.
2134## The string "trim" is inserted in the list of flags when it is known
2135## to be trim.
2136##
2137## Public function.
2138IsTrimFSA := function ( fsa )
2139    local ns, ne, ct, i, j, got, s, x, t;
2140    if not IsInitializedFSA(fsa) then
2141       InitializeFSA(fsa);
2142    fi;
2143    if "trim" in fsa.flags  then
2144      return true;
2145    fi;
2146    ns := fsa.states.size;
2147    ne := fsa.alphabet.size;
2148 # First find all accessible states
2149 # i.e. states accessible from an initial state.
2150    got := ShallowCopy(fsa.initial);
2151    ct := Length(got);
2152    i := 1;
2153    while i <= ct do
2154      s := got[i];
2155      for j in [0..ne] do
2156        x := TargetsFSA(fsa,j,s);
2157        for t in x do
2158          if t>0 and not t in got then
2159            ct := ct+1;
2160            got[ct] := t;
2161          fi;
2162        od;
2163      od;
2164      i := i+1;
2165    od;
2166    if ct <> ns then
2167 # there are some inaccessible states, so fsa is not trim.
2168      return false;
2169    fi;
2170
2171 # Next find all co-accessible states
2172 # i.e. states from which a path starts to an accepting state
2173    got := ShallowCopy(fsa.accepting);
2174    ct := Length(got);
2175    i := 1;
2176    while i <= ct do
2177      s := got[i];
2178      for j in [0..ne] do
2179        x := SourcesFSA(fsa,j,s);
2180        for t in x do
2181          if not t in got then
2182            ct := ct+1;
2183            got[ct] := t;
2184          fi;
2185        od;
2186      od;
2187      i := i+1;
2188    od;
2189    if ct <> ns then
2190 # there are some non co-accessible states, so fsa is not trim.
2191      return false;
2192    fi;
2193    AddSet(fsa.flags,"trim");
2194    return true;
2195end;
2196
2197#############################################################################
2198##
2199#F  TrimFSA(<fsa>) . . . . . . . replace fsa by a trim fsa
2200##
2201## TrimFSA(<fsa>) removes non-accessible and non-coaccessible states from
2202## <fsa> to make it trim, without changing the accepted language.
2203## A trim FSA is one in which there is an accepted word in the language
2204## through every state.
2205## The string "trim" is inserted in the list of flags when it is known
2206## to be trim.
2207## (Removing the non-coaccessible states cannot possibly make any other
2208##  states non-accessible, so there is no need to repeat the process.)
2209##
2210## Public function.
2211TrimFSA := function ( fsa )
2212    local ns, ne, ct, i, j, got, s, x, t;
2213    if not IsInitializedFSA(fsa) then
2214       InitializeFSA(fsa);
2215    fi;
2216    if "trim" in fsa.flags  then
2217      return;
2218    fi;
2219    ns := fsa.states.size;
2220    ne := fsa.alphabet.size;
2221 # First find all accessible states
2222 # i.e. states accessible from an initial state.
2223    got := ShallowCopy(fsa.initial);
2224    ct := Length(got);
2225    i := 1;
2226    while i <= ct do
2227      s := got[i];
2228      for j in [0..ne] do
2229        x := TargetsFSA(fsa,j,s);
2230        for t in x do
2231          if t>0 and not t in got then
2232            ct := ct+1;
2233            got[ct] := t;
2234          fi;
2235        od;
2236      od;
2237      i := i+1;
2238    od;
2239    if ct <> ns then
2240 # there are some inaccessible states, so remove them - because we can only
2241 # remove the last state, we have to work from the back.
2242      for s in Reversed([1..ns]) do
2243        if not s in got then
2244          if s<ns then
2245            PermuteStatesFSA(fsa,(s,ns));
2246          fi;
2247          DeleteStateFSA(fsa);
2248          ns := ns-1;
2249        fi;
2250      od;
2251    fi;
2252
2253 # Next find all co-accessible states
2254 # i.e. states from which a path starts to an accepting state
2255    got := ShallowCopy(fsa.accepting);
2256    ct := Length(got);
2257    i := 1;
2258    while i <= ct do
2259      s := got[i];
2260      for j in [0..ne] do
2261        x := SourcesFSA(fsa,j,s);
2262        for t in x do
2263          if not t in got then
2264            ct := ct+1;
2265            got[ct] := t;
2266          fi;
2267        od;
2268      od;
2269      i := i+1;
2270    od;
2271    if ct <> ns then
2272 # there are some non-coaccessible states, so remove them - because we can only
2273 # remove the last state, we have to work from the back.
2274      for s in Reversed([1..ns]) do
2275        if not s in got then
2276          if s<ns then
2277            PermuteStatesFSA(fsa,(s,ns));
2278          fi;
2279          DeleteStateFSA(fsa);
2280          ns := ns-1;
2281        fi;
2282      od;
2283    fi;
2284    AddSet(fsa.flags,"trim");
2285    RemoveSet(fsa.flags,"accessible"); #trim implies accessible
2286    RemoveSet(fsa.flags,"BFS");
2287end;
2288
2289#############################################################################
2290##
2291#F  IsBFSFSA(<fsa>) . . . . . . . decide if fsa has the "bfs" property
2292##
2293## IsBFSFSA(<fsa>) decides if the fsa <fsa> has the breadth-first-search
2294## property. This means that it is accessible and, scanning the transition table
2295## along the states, one encounters the states in ascending numerical order.
2296## It is useful for comparing two fsa's.
2297##
2298## Public function.
2299IsBFSFSA := function ( fsa )
2300    local ns, ne, ct, i, j, got, s, x, t;
2301    if not IsInitializedFSA(fsa) then
2302       InitializeFSA(fsa);
2303    fi;
2304    if "BFS" in fsa.flags  then
2305      return true;
2306    fi;
2307    if not IsAccessibleFSA(fsa) then
2308      return false;
2309    fi;
2310    ns := fsa.states.size;
2311    ne := fsa.alphabet.size;
2312    if fsa.initial <> [1..Length(fsa.initial)] then
2313      return false;
2314    fi;
2315    ct := Length(fsa.initial);
2316    i := 1;
2317    while i <= ct do
2318      for j in [0..ne] do
2319        x := TargetsFSA(fsa,j,i);
2320        for t in x do
2321          if t>ct then
2322            if t <> ct+1 then
2323              return false;
2324            fi;
2325            ct := ct+1;
2326            if ct = ns then
2327              AddSet(fsa.flags,"BFS");
2328              return true;
2329            fi;
2330          fi;
2331        od;
2332      od;
2333      i := i+1;
2334    od;
2335
2336end;
2337
2338#############################################################################
2339##
2340#F  BFSFSA(<fsa>) . . . . . . . replace fsa by an fsa with the "bfs" property
2341##
2342## BFSFSA(<fsa>) replaces the fsa <fsa> by one with the same language
2343## that has the breadth-first-search property. This means that, scanning
2344## the transition table along the states, one encounters the states
2345## in ascending numerical order. It is useful for comparing two fsa's.
2346## It first makes the fsa trim, and then calculates the required
2347## state-permutation to achieve bfs-form, and calls PermuteStatesFSA.
2348##
2349## Public function.
2350BFSFSA := function ( fsa )
2351    local ns, ne, ct, perm, i, j, got, s, x, t;
2352    if not IsInitializedFSA(fsa) then
2353       InitializeFSA(fsa);
2354    fi;
2355    if "BFS" in fsa.flags  then
2356      return;
2357    fi;
2358    AccessibleFSA(fsa);
2359    ns := fsa.states.size;
2360    ne := fsa.alphabet.size;
2361 # We calculate the required permutation by building up the list perm
2362 # perm[i]=j means that the i-th state in the new order will be the
2363 # current j-th state - so we will call PermuteStatesFSA with the
2364 # inverse of perm.
2365    perm := ShallowCopy(fsa.initial);
2366    ct := Length(perm);
2367    i := 1;
2368    while i <= ct do
2369      s := perm[i];
2370      for j in [0..ne] do
2371        x := TargetsFSA(fsa,j,s);
2372        for t in x do
2373          if t>0 and not t in perm then
2374            ct := ct+1;
2375            perm[ct] := t;
2376          fi;
2377        od;
2378      od;
2379      i := i+1;
2380    od;
2381
2382    perm := PermList(perm)^-1;
2383    PermuteStatesFSA(fsa,perm);
2384    AddSet(fsa.flags,"BFS");
2385end;
2386
2387#############################################################################
2388##
2389#F  PSizeFSA(<fsa>,[<state-list>]) . . . . . number of accepted paths of an fsa
2390##
2391##  <fsa> should be a finite state automaton.
2392##  The number of accepted paths is calculated and returned.
2393##  WARNING: if there are epsilon transitions, then this is not necessarily
2394##  the same as the size of the accepted language.
2395##  If this is infinite, "infinity" is returned.
2396##  If <fsa. is not trim, then a diagnostic will be printed.
2397##
2398##  If the optional argument [<state-list>] is present, then the number
2399##  of accepted strings starting at one of the states in <state-list> will
2400##  be returned instead, BUT ONLY IF THE TOTAL ACCEPTED LANGUAGE IS FINITE.
2401##  Public function.
2402PSizeFSA := function ( arg )
2403    local fsa, slist, ns, ne, indeg, st, olist, nacc, total, ct, i, j, s, t, x;
2404    fsa := arg[1];
2405    if Length(arg)=2 then
2406      slist := arg[2];
2407    else
2408      slist := fsa.initial;
2409    fi;
2410    if not IsInitializedFSA(fsa) then
2411       InitializeFSA(fsa);
2412    fi;
2413    if not IsTrimFSA(fsa) then
2414      Print("#The fsa is not trim. Call TrimFSA(fsa) to make it trim.\n");
2415      return "unknown";
2416    fi;
2417
2418    ns := fsa.states.size;
2419    if ns=0 then
2420      return 0;
2421    fi;
2422    ne := fsa.alphabet.size;
2423
2424 # We first count the number of edges going into each vertex.
2425    indeg := [];
2426    for s in [1..ns] do
2427      indeg[s] := 0;
2428    od;
2429    for s in [1..ns] do
2430      for i in [0..ne] do
2431        x := TargetsFSA(fsa,i,s);
2432        for t in x do
2433          if t > 0 then indeg[t] := indeg[t]+1; fi;
2434        od;
2435      od;
2436    od;
2437
2438 # Now we seek to order the states so that if state s <= state t, then there
2439 # is no path from state t to state s. If this is not possible, then the
2440 # accepted language must be infinite.
2441 # The ordering will be in the list olist.
2442
2443    st := 0;
2444    for s in fsa.initial do
2445      if indeg[s]=0 then
2446        st := s;
2447      fi;
2448    od;
2449    if st = 0 then
2450      return infinity;
2451    fi;
2452    olist := [st];
2453    ct := 1;
2454    i := 1;
2455    while i<=ct do
2456      s := olist[i];
2457      for j in [0..ne] do
2458        x := TargetsFSA(fsa,j,s);
2459        for t in x do
2460          if t>0 then
2461            indeg[t] := indeg[t]-1;
2462            if indeg[t]=0 then
2463              ct := ct+1;
2464              olist[ct] := t;
2465            fi;
2466          fi;
2467        od;
2468      od;
2469      i := i+1;
2470    od;
2471    if ct <> ns then
2472      return infinity;
2473    fi;
2474
2475 # We have built the list, so the accepted language is finite. Now we work
2476 # backwards through the list, calculating the number of accepted words
2477 # starting at that state.
2478 # We store the numbers in nacc.
2479
2480  indeg := 0; #no longer needed.
2481  nacc := [];
2482  for i in Reversed([1..ns]) do
2483    s := olist[i];
2484    nacc[s] := 0;
2485    for j in [0..ne] do
2486      x := TargetsFSA(fsa,j,s);
2487      for t in x do
2488        if t>0 then nacc[s] := nacc[s]+nacc[t]; fi;
2489      od;
2490    od;
2491    if s in fsa.accepting then
2492      nacc[s] := nacc[s]+1;
2493    fi;
2494  od;
2495
2496 # Finally we count the total number of accepted strings starting from
2497 # one of the states in slist.
2498  total := 0;
2499  for s in slist do
2500    total := total+nacc[s];
2501  od;
2502  return total;
2503end;
2504
2505#############################################################################
2506##
2507#F  LSizeDFA(<fsa>,[<initial state>]) . . . . . size of language of an fsa
2508##
2509##  <fsa> should be a deterministic finite state automaton.
2510##  The size of the accepted language is calculated.
2511##  This should be quicker than PSizeFSA for deterministic automata.
2512##  If the language is infinite, "infinity" is returned.
2513##  If <fsa> is not trim, then a diagnostic will be printed.
2514##
2515##  If the optional argument [<initial state>] is present, then the number
2516##  of accepted strings starting at the state in <initial state> will
2517##  be returned instead.
2518##
2519##  Public function.
2520LSizeDFA := function ( arg )
2521    local fsa, slist, ns, ne, ttable, indeg, st, olist, nacc, total, ct,
2522          i, j, s, t, accstates;
2523    fsa := arg[1];
2524    if not IsInitializedFSA(fsa) then
2525       InitializeFSA(fsa);
2526    fi;
2527    if IsDeterministicFSA(fsa)=false  then
2528       Error("First argument is not a dfa.");
2529    fi;
2530    if not IsTrimFSA(fsa) then
2531      Print("#The fsa is not trim. Call TrimFSA(fsa) to make it trim.\n");
2532      return "unknown";
2533    fi;
2534
2535    ns := fsa.states.size;
2536    if ns=0 then
2537      return 0;
2538    fi;
2539    ne := fsa.alphabet.size;
2540
2541 # First make sure that the dense deterministic table is present.
2542    DenseDTableFSA(fsa);
2543    ttable := fsa.denseDTable;
2544
2545    if Length(arg)=2 then
2546      slist := [arg[2]];
2547      #In this case, we restrict attention to states that can be reached from
2548      #initial states.
2549      accstates := ShallowCopy(slist);
2550      ct := 1;
2551      i := 1;
2552      while i<=ct do
2553        s := accstates[i];
2554        for j in [1..ne] do
2555          t := ttable[s][j];
2556          if t > 0 and Position(accstates,t) = fail then
2557            ct := ct+1;
2558            Add(accstates,t);
2559          fi;
2560        od;
2561        i := i+1;
2562      od;
2563    else
2564      slist := fsa.initial;
2565      accstates := [1..ns];
2566    fi;
2567
2568 # We first count the number of edges going into each vertex.
2569    indeg := [];
2570    for s in accstates do
2571      indeg[s] := 0;
2572    od;
2573    for s in accstates do
2574      for i in [1..ne] do
2575        t := ttable[s][i];
2576        if t > 0 then indeg[t] := indeg[t]+1; fi;
2577      od;
2578    od;
2579
2580 # Now we seek to order the states so that if state s <= state t, then there
2581 # is no path from state t to state s. If this is not possible, then the
2582 # accepted language must be infinite.
2583 # The ordering will be in the list olist.
2584
2585    ns := Length(accstates);
2586    st := slist[1];
2587    if indeg[st] <> 0 then
2588      return infinity;
2589    fi;
2590    olist := [st];
2591    ct := 1;
2592    i := 1;
2593    while i<=ct do
2594      s := olist[i];
2595      for j in [1..ne] do
2596        t := ttable[s][j];
2597        if t>0 then
2598            indeg[t] := indeg[t]-1;
2599            if indeg[t]=0 then
2600              ct := ct+1;
2601              olist[ct] := t;
2602            fi;
2603        fi;
2604      od;
2605      i := i+1;
2606    od;
2607    if ct <> ns then
2608      return infinity;
2609    fi;
2610
2611 # We have built the list, so the accepted language is finite. Now we work
2612 # backwards through the list, calculating the number of accepted words
2613 # starting at that state.
2614 # We store the numbers in nacc.
2615
2616  indeg := 0; #no longer needed.
2617  nacc := [];
2618  for i in Reversed([1..ns]) do
2619    s := olist[i];
2620    nacc[s] := 0;
2621    for j in [1..ne] do
2622      t := ttable[s][j];
2623      if t>0 then nacc[s] := nacc[s]+nacc[t]; fi;
2624    od;
2625    if s in fsa.accepting then
2626      nacc[s] := nacc[s]+1;
2627    fi;
2628  od;
2629
2630 # Finally we count the total number of accepted strings starting from
2631 # one of the states in slist.
2632  total := 0;
2633  for s in slist do
2634    total := total+nacc[s];
2635  od;
2636  return total;
2637end;
2638
2639#############################################################################
2640##
2641#F  ListWordSR(<sr>,<w>) . . . . converts word in sr-generators to list
2642##
2643##  ListWordSR converts the word <w> in the generators of the
2644##  set-record <sr> to a list of integers.
2645##  It only works if <sr> has type "identifiers" or "product".
2646##  In the latter case, w should be a list of n words, where n is the
2647##  "arity" of the set-record.
2648##
2649##  Public function.
2650ListWordSR := function ( sr, w )
2651    local i, j, l, n, wl, gens, prod, nv, tup, id;
2652    if not IsRecord(sr) or not IsBound(sr.type) or
2653           (sr.type <> "identifiers" and sr.type <> "product") then
2654       Error("First argument must be a set-record of type \"identifiers\".");
2655    fi;
2656    prod := sr.type="product";
2657 #We deal with product type separately.
2658    if prod then
2659      nv := sr.arity;
2660      if not IsList(w) or Length(w)<>nv then
2661         Error("Second argument must be a list of length sr.arity.");
2662      fi;
2663      l := 0;
2664      for i in w do
2665        if not IsWord(i) then
2666           Error("An entry in second argument is not a word.");
2667        fi;
2668        if Length(i)>l then
2669          l := Length(i);
2670        fi;
2671      od;
2672      wl := [];
2673      gens := sr.names;
2674      for i in [1..l] do
2675        tup := [];
2676        for j in [1..nv] do
2677          if i > Length(w[j]) then
2678            id := sr.padding;
2679          else
2680            id := Subword(w[j],i,i);
2681          fi;
2682          tup[j] := id;
2683        od;
2684        n := Position(gens,tup);
2685        if n=fail then
2686          Error("Invalid tuple in word.");
2687        fi;
2688        Add(wl,n);
2689      od;
2690    else
2691      if not IsWord(w) then
2692         Error("Second argument is not a word.");
2693      fi;
2694      l := Length(w);
2695      wl := [];
2696      gens := sr.names;
2697      for i in [1..l] do
2698        n := Position(gens,Subword(w,i,i));
2699        if n=fail then
2700          Error("Invalid generator in word.");
2701        fi;
2702        Add(wl,n);
2703      od;
2704    fi;
2705
2706    return wl;
2707end;
2708
2709#############################################################################
2710##
2711#F  WordListSR(<sr>,<wl>) . . . . converts list of sr-generators to word
2712##
2713##  WordListSR converts the list of positive integers <wl> to a word
2714##  in the generators of the rewriting system <sr>. Each integer in the
2715##  list must be a valid generator number.
2716##  This is the inverse function to ListWordSR.
2717##  However, it works when sr has type "identifiers", "strings" or
2718##  "products".
2719##
2720##  Public function.
2721WordListSR := function ( sr, wl )
2722    local i, j, l, w, gens, ng, nv, tup, IdWord;
2723    if sr.type="identifiers"   and IsAssocWordWithOne(sr.names[1]) then
2724      IdWord := sr.names[1]^0;
2725    elif sr.type="words"   and IsAssocWordWithOne(sr.alphabet[1]) then
2726      IdWord := sr.alphabet[1]^0;
2727    else IdWord:=false;
2728    fi;
2729    if not IsRecord(sr) or not IsBound(sr.type) or (sr.type <> "identifiers"
2730        and sr.type <> "words"  and sr.type <> "strings" and
2731        sr.type <> "product") then
2732       Error("First argument must be a set-record of appropriate type.");
2733    fi;
2734    if not IsList(wl) then
2735       Error("Second argument is not a list.");
2736    fi;
2737    l := Length(wl);
2738    gens := sr.names;
2739    ng := sr.size;
2740    if l=0 then
2741      if sr.type="identifiers" or sr.type="words" then
2742        return IdWord;
2743      elif sr.type="strings" then
2744        return "";
2745      else
2746        nv := sr.arity;
2747        return List([1..nv],i->IdWord);
2748      fi;
2749    else
2750      if not wl[1] in [1..ng] then
2751        Error("List element is not a valid generator number.");
2752      fi;
2753      w := ShallowCopy(gens[wl[1]]);
2754    fi;
2755    for i in [2..l] do
2756      if not wl[i] in [1..ng] then
2757        Error("List element is not a valid generator number.");
2758      fi;
2759      if sr.type="identifiers" or sr.type="words" then
2760        w := w*gens[wl[i]];
2761      elif sr.type="strings" then
2762        w := Concatenation(w,gens[wl[i]]);
2763      else
2764        tup := gens[wl[i]];
2765        nv := sr.arity;
2766        for j in [1..nv] do
2767          w[j] := w[j]*tup[j];
2768        od;
2769      fi;
2770    od;
2771
2772    return w;
2773end;
2774
2775#############################################################################
2776##
2777#F  LEnumerateDFA(<fsa>, <min>, <max>, [<is>] ) . . enumerate language of dfa
2778##
2779##  <fsa> should be a deterministic finite state automaton.
2780##  All words in the language accepted by <fsa> having length l
2781##  satisfying <min> <= l <= <max> will be calculated and output in a
2782##  list. These will be in lexacographical order.
2783##  To get shortlex order, call SortLEnumerateDFA, which merely calls this
2784##  function repeatedly.
2785##  If the optional argument <is> is present, the initial state will be
2786##  taken to be is.
2787##
2788##  Public function.
2789LEnumerateDFA := function ( arg )
2790    local fsa, min, max, is,  words, convert, ttable, sr, ne, as, cword,
2791          cstatelist, clength, done, backtrack, cstate, fe, i;
2792
2793    if Length(arg) < 3 then
2794       Error("LEnumerateDFA must have at least three arguments");
2795    fi;
2796    fsa:=arg[1]; min:=arg[2]; max:=arg[3];
2797    if not IsInitializedFSA(fsa) then
2798       InitializeFSA(fsa);
2799    fi;
2800    if  IsDeterministicFSA(fsa)=false  then
2801       Error("First argument is not a dfa.");
2802    fi;
2803    if not IsInt(min) or not IsInt(max) or min<0 or min>max then
2804      Error("2nd and 3rd arguments must be nonneg. integers with 2nd <= 3rd.");
2805    fi;
2806    if fsa.initial=[] and Length(arg)=3 then
2807      return [];
2808    fi;
2809    if Length(arg)>=4 then is:=arg[4]; else is:=fsa.initial[1]; fi;
2810
2811 # The enumeration process itself will use lists of integers. These will be
2812 # converted to words if necessary and collected in the lists "words" for
2813 # output.
2814
2815    sr := fsa.alphabet;
2816    ne := sr.size;
2817    convert := sr.type="identifiers" or sr.type="strings" or sr.type="words"
2818                or sr.type="product";
2819
2820  # Make sure that the dense deterministic table is present.
2821    DenseDTableFSA(fsa);
2822    ttable := fsa.denseDTable;
2823
2824    words := [];
2825  # cword will be the current word in the search (as a list of integers),
2826  # clength its current length, and cstatelist the list of states of fsa
2827  # arising when scanning the word.
2828    cword := [];
2829    cstatelist := [is];
2830    clength := 0;
2831
2832    as := fsa.accepting;
2833  # Now the backtrack search begins
2834    done := false;
2835    while not done do
2836  # first check if we want the current word.
2837      if clength>=min and cstatelist[clength+1] in as then
2838         if convert then
2839           Add(words,WordListSR(sr,cword));
2840         else
2841           Add(words,ShallowCopy(cword));
2842         fi;
2843      fi;
2844
2845  # now proceed to next word in search.
2846      fe := 1;
2847      backtrack:=true;
2848      while backtrack and not done  do
2849        if clength < max then
2850          cstate := cstatelist[clength+1];
2851          i := fe;
2852          while backtrack and i<= ne do
2853            if ttable[cstate][i] > 0 then
2854           # found next node
2855              clength := clength+1;
2856              cword[clength] := i;
2857              cstatelist[clength+1] := ttable[cstate][i];
2858              backtrack := false;
2859            fi;
2860            i := i+1;
2861          od;
2862        fi;
2863        if backtrack then
2864           if clength=0 then
2865             done := true;
2866           else
2867             fe := cword[clength]+1;
2868             Unbind(cword[clength]);
2869             clength := clength-1;
2870           fi;
2871        fi;
2872      od;
2873    od;
2874
2875    return words;
2876end;
2877
2878#############################################################################
2879##
2880#F  SortLEnumerateDFA(<fsa>, <min>, <max> [,<is>])
2881##  . . . . . . . . . . . . . . . . . . . enumerate language of dfa and sort
2882##
2883##  This function merely calls LEnumerateFSA repeatedly to get the shortlex
2884##  order of output.
2885##
2886##  If the optional argument <is> is present, the initial state will be
2887##  taken to be is.
2888##
2889##  Public function.
2890SortLEnumerateDFA := function ( arg )
2891    local fsa, min, max, is,  words, i;
2892
2893    if Length(arg) < 3 then
2894       Error("SortLEnumerateDFA must have at least three arguments");
2895    fi;
2896    fsa:=arg[1]; min:=arg[2]; max:=arg[3];
2897
2898    words := [];
2899    for i in [min..max] do
2900      if Length(arg)=3 then
2901        words := Concatenation(words,LEnumerateDFA(fsa,i,i));
2902      else
2903        is := arg[4];
2904        words := Concatenation(words,LEnumerateDFA(fsa,i,i,is));
2905      fi;
2906    od;
2907
2908    return words;
2909end;
2910
2911#############################################################################
2912##
2913#F  SizeLEnumerateDFA(<fsa>, <min>, <max> [,<is>])
2914##  . . . . . . . . . . . . . . . . . . . size of enumerated language of dfa
2915##
2916##  <fsa> should be a deterministic finite state automaton.
2917##  This is like LEnumerateFSA, but only the number of accepted words is
2918##  output.
2919##
2920##  If the optional argument <is> is present, the initial state will be
2921##  taken to be is.
2922##
2923##  Public function.
2924SizeLEnumerateDFA := function ( arg )
2925    local fsa, min, max, is, nwords, ttable, sr, ne, as, cword, cstatelist,
2926          clength, done, backtrack, cstate, fe, i;
2927
2928    if Length(arg) < 3 then
2929       Error("LEnumerateDFA must have at least three arguments");
2930    fi;
2931    fsa:=arg[1]; min:=arg[2]; max:=arg[3];
2932
2933    if not IsInitializedFSA(fsa) then
2934       InitializeFSA(fsa);
2935    fi;
2936    if IsDeterministicFSA(fsa)=false  then
2937       Error("First argument is not a dfa.");
2938    fi;
2939    if not IsInt(min) or not IsInt(max) or min<0 or min>max then
2940      Error("2nd and 3rd arguments must be nonneg. integers with 2nd <= 3rd.");
2941    fi;
2942    if fsa.initial=[] and Length(arg)=3 then
2943      return 0;
2944    fi;
2945
2946    if Length(arg)>=4 then is:=arg[4]; else is:=fsa.initial[1]; fi;
2947
2948  # The enumeration process itself will use lists of integers.
2949
2950    sr := fsa.alphabet;
2951    ne := sr.size;
2952
2953  # Make sure that the dense deterministic table is present.
2954    DenseDTableFSA(fsa);
2955    ttable := fsa.denseDTable;
2956
2957    nwords := 0;
2958  # cword will be the current word in the search (as a list of integers),
2959  # clength its current length, and cstatelist the list of states of fsa
2960  # arising when scanning the word.
2961    cword := [];
2962    cstatelist := [is];
2963    clength := 0;
2964
2965    as := fsa.accepting;
2966  # Now the backtrack search begins
2967    done := false;
2968    while not done do
2969  # first check if we want the current word.
2970      if clength>=min and cstatelist[clength+1] in as then
2971        nwords := nwords+1;
2972      fi;
2973
2974  # now proceed to next word in search.
2975      fe := 1;
2976      backtrack:=true;
2977      while backtrack and not done  do
2978        if clength < max then
2979          cstate := cstatelist[clength+1];
2980          i := fe;
2981          while backtrack and i<= ne do
2982            if ttable[cstate][i] > 0 then
2983           # found next node
2984              clength := clength+1;
2985              cword[clength] := i;
2986              cstatelist[clength+1] := ttable[cstate][i];
2987              backtrack := false;
2988            fi;
2989            i := i+1;
2990          od;
2991        fi;
2992        if backtrack then
2993           if clength=0 then
2994             done := true;
2995           else
2996             fe := cword[clength]+1;
2997             Unbind(cword[clength]);
2998             clength := clength-1;
2999           fi;
3000        fi;
3001      od;
3002    od;
3003
3004    return nwords;
3005end;
3006
3007#############################################################################
3008##
3009#F  SubstitutedListFSA(<l>,<pos1>,<pos2>,<ss>)
3010##                        . . . . . . . . . substitute a new list in a list
3011##
3012##  The part of the list <l> form position <pos1> to <pos2> is substituted by
3013##  the list <ss>. This is easy if lengths are equal. Otherwise not so.
3014##
3015##  Private function.
3016SubstitutedListFSA := function(l,pos1,pos2,ss)
3017    local len, oldlen, newlen, diff, i;
3018    len := Length(l);
3019    oldlen := pos2-pos1+1; newlen := Length(ss);
3020    if oldlen=newlen then
3021      l{[pos1..pos2]} := ss;
3022    elif newlen<oldlen then
3023      diff := oldlen-newlen;
3024      for i in [pos2+1..len] do l[i-diff]:=l[i]; od;
3025      for i in [len-diff+1..len] do Unbind(l[i]); od;
3026      l{[pos1..pos2-diff]} := ss;
3027    elif newlen>oldlen then
3028      diff := newlen-oldlen;
3029      for i in Reversed([pos2+1..len]) do l[i+diff] := l[i]; od;
3030      l{[pos1..pos2+diff]} := ss;
3031    fi;
3032end;
3033
3034#############################################################################
3035##
3036#F  DeterminizeFSA(<fsa>) . . . call external program to determinize fsa <fsa>
3037##
3038## Determinized FSA is returned.
3039## Public function.
3040DeterminizeFSA := function(fsa)
3041  local callstring, filename, alph;
3042
3043    if not IsInitializedFSA(fsa) then
3044       InitializeFSA(fsa);
3045    fi;
3046    if  IsDeterministicFSA(fsa)  then
3047       return  fsa;
3048    fi;
3049    ## We replace the alphabet by simple-type alphabet for the
3050    ## I/O phase.
3051    alph := fsa.alphabet;
3052    fsa.alphabet := rec(type:="simple",size:=alph.size);
3053    InitializeSR(fsa.alphabet);
3054    filename := Concatenation(_KBTmpFileName,".fsafordet");
3055    WriteFSA(fsa,"_FSA",filename,";");
3056    callstring := Filename(_KBExtDir,"nfadeterminize");
3057    if InfoLevel(InfoFSA)=0 then
3058       callstring := Concatenation(callstring," -silent ");
3059    elif InfoLevel(InfoFSA)>1 then
3060       callstring := Concatenation(callstring," -v ");
3061    fi;
3062    callstring := Concatenation(callstring," ",filename);
3063    Info(InfoFSA,1,"Calling fsa determinization program.\n");
3064    Info(InfoFSA,3,"  ",callstring);
3065    Exec(callstring);
3066    Info(InfoFSA,1,"External fsa determinization program complete.\n");
3067    if not READ(Concatenation(_KBTmpFileName,".fsafordet.determinize")) then
3068       Error("Could not open determinized fsa file");
3069    fi;
3070    Exec(Concatenation("/bin/rm -f ",_KBTmpFileName,".fsafordet*"));
3071    InitializeFSA(_FSA_determinize);
3072    fsa.alphabet := alph;
3073    _FSA_min.alphabet := alph;
3074    return _FSA_determinize;
3075end;
3076
3077#############################################################################
3078##
3079#F  MinimizeFSA(<fsa>) . . . call external program to minimize fsa <fsa>
3080##
3081## Minimized FSA is returned.
3082## Public function.
3083MinimizeFSA := function(fsa)
3084  local callstring, filename, alph;
3085
3086    if not IsInitializedFSA(fsa) then
3087       InitializeFSA(fsa);
3088    fi;
3089    if  IsDeterministicFSA(fsa)=false  then
3090       Error("First argument is not a dfa.");
3091    fi;
3092    ## We replace the alphabet by simple-type alphabet for the
3093    ## I/O phase.
3094    alph := fsa.alphabet;
3095    fsa.alphabet := rec(type:="simple",size:=alph.size);
3096    InitializeSR(fsa.alphabet);
3097    filename := Concatenation(_KBTmpFileName,".fsaformin");
3098    WriteFSA(fsa,"_FSA",filename,";");
3099    callstring := Filename(_KBExtDir,"fsamin");
3100    if InfoLevel(InfoFSA)=0 then
3101       callstring := Concatenation(callstring," -silent ");
3102    elif InfoLevel(InfoFSA)>1 then
3103       callstring := Concatenation(callstring," -v ");
3104    fi;
3105    callstring := Concatenation(callstring," ",filename);
3106    Info(InfoFSA,1,"Calling fsa minimization program.\n");
3107    Info(InfoFSA,3,"  ",callstring);
3108    Exec(callstring);
3109    Info(InfoFSA,1,"External fsa minimization program complete.\n");
3110    if not READ(Concatenation(_KBTmpFileName,".fsaformin.min")) then
3111       Error("Could not open minimized fsa file");
3112    fi;
3113    Exec(Concatenation("/bin/rm -f ",_KBTmpFileName,".fsaformin*"));
3114    InitializeFSA(_FSA_min);
3115    fsa.alphabet := alph;
3116    _FSA_min.alphabet := alph;
3117    return _FSA_min;
3118end;
3119
3120#############################################################################
3121##
3122#F  NotFSA(<fsa>) . . . call external program to negate fsa <fsa>
3123##
3124## An FSA is returned in which a word is accepted iff it is not
3125## accepted by <fsa>.
3126## Public function.
3127NotFSA := function(fsa)
3128  local callstring, filename, alph;
3129
3130    if not IsInitializedFSA(fsa) then
3131       InitializeFSA(fsa);
3132    fi;
3133    if  IsDeterministicFSA(fsa)=false  then
3134       Error("First argument is not a dfa.");
3135    fi;
3136    ## We replace the alphabet by simple-type alphabet for the
3137    ## I/O phase.
3138    alph := fsa.alphabet;
3139    fsa.alphabet := rec(type:="simple",size:=alph.size);
3140    InitializeSR(fsa.alphabet);
3141    filename := Concatenation(_KBTmpFileName,".fsafornot");
3142    WriteFSA(fsa,"_FSA",filename,";");
3143    callstring := Filename(_KBExtDir,"fsanot");
3144    if InfoLevel(InfoFSA)=0 then
3145       callstring := Concatenation(callstring," -silent ");
3146    elif InfoLevel(InfoFSA)>1 then
3147       callstring := Concatenation(callstring," -v ");
3148    fi;
3149    callstring := Concatenation(callstring," ",filename);
3150    Info(InfoFSA,1,"Calling fsa `not' program.\n");
3151    Info(InfoFSA,3,"  ",callstring);
3152    Exec(callstring);
3153    Info(InfoFSA,1,"External fsa `not' program complete.\n");
3154    if not READ(Concatenation(_KBTmpFileName,".fsafornot.not")) then
3155       Error("Could not open `not' fsa file");
3156    fi;
3157    Exec(Concatenation("/bin/rm -f ",_KBTmpFileName,".fsafornot*"));
3158    InitializeFSA(_FSA_not);
3159    fsa.alphabet := alph;
3160    _FSA_not.alphabet := alph;
3161    return _FSA_not;
3162end;
3163
3164#############################################################################
3165##
3166#F  StarFSA(<fsa>) . . . call external program to star fsa <fsa>
3167##
3168## An FSA is returned in which a word is accepted iff it is a
3169## concatenation of 0 or more words accepted by <fsa>.
3170## Public function.
3171StarFSA := function(fsa)
3172  local callstring, filename, alph;
3173
3174    if not IsInitializedFSA(fsa) then
3175       InitializeFSA(fsa);
3176    fi;
3177    if  IsDeterministicFSA(fsa)=false  then
3178       Error("First argument is not a dfa.");
3179    fi;
3180    ## We replace the alphabet by simple-type alphabet for the
3181    ## I/O phase.
3182    alph := fsa.alphabet;
3183    fsa.alphabet := rec(type:="simple",size:=alph.size);
3184    InitializeSR(fsa.alphabet);
3185    filename := Concatenation(_KBTmpFileName,".fsaforstar");
3186    WriteFSA(fsa,"_FSA",filename,";");
3187    callstring := Filename(_KBExtDir,"fsastar");
3188    if InfoLevel(InfoFSA)=0 then
3189       callstring := Concatenation(callstring," -silent ");
3190    elif InfoLevel(InfoFSA)>1 then
3191       callstring := Concatenation(callstring," -v ");
3192    fi;
3193    callstring := Concatenation(callstring," ",filename);
3194    Info(InfoFSA,1,"Calling fsa `star' program.\n");
3195    Info(InfoFSA,3,"  ",callstring);
3196    Exec(callstring);
3197    Info(InfoFSA,1,"External fsa `star' program complete.\n");
3198    if not READ(Concatenation(_KBTmpFileName,".fsaforstar.star")) then
3199       Error("Could not open `star' fsa file");
3200    fi;
3201    Exec(Concatenation("/bin/rm -f ",_KBTmpFileName,".fsaforstar*"));
3202    InitializeFSA(_FSA_star);
3203    fsa.alphabet := alph;
3204    _FSA_star.alphabet := alph;
3205    return _FSA_star;
3206end;
3207
3208#############################################################################
3209##
3210#F  ReverseFSA(<fsa>,[<subsets>]) . call external program to reverse fsa <fsa>
3211##
3212## An FSA is returned in which a word is accepted iff it is the reverses
3213## of a word accepted by <fsa>.
3214## If the optional 'subsets' argument is true, then the states of the
3215## returned fsa are given labels specifying the subsets of the original
3216## state-set to which they correspond.
3217## Public function.
3218ReverseFSA := function(arg)
3219  local callstring, filename, alph, fsa, subsets;
3220
3221    fsa := arg[1];
3222    if not IsInitializedFSA(fsa) then
3223       InitializeFSA(fsa);
3224    fi;
3225    if  IsDeterministicFSA(fsa)=false  then
3226       Error("First argument is not a dfa.");
3227    fi;
3228    subsets := false;
3229    if Length(arg)>=2 and arg[2]=true then
3230      subsets:=true;
3231    fi;
3232    ## We replace the alphabet by simple-type alphabet for the
3233    ## I/O phase.
3234    alph := fsa.alphabet;
3235    fsa.alphabet := rec(type:="simple",size:=alph.size);
3236    InitializeSR(fsa.alphabet);
3237    filename := Concatenation(_KBTmpFileName,".fsaforreverse");
3238    WriteFSA(fsa,"_FSA",filename,";");
3239    callstring := Filename(_KBExtDir,"fsareverse");
3240    if InfoLevel(InfoFSA)=0 then
3241       callstring := Concatenation(callstring," -silent ");
3242    elif InfoLevel(InfoFSA)>1 then
3243       callstring := Concatenation(callstring," -v ");
3244    fi;
3245    if subsets then
3246      callstring := Concatenation(callstring," -s ");
3247    fi;
3248    callstring := Concatenation(callstring," ",filename);
3249    Info(InfoFSA,1,"Calling fsa `reverse' program.\n");
3250    Info(InfoFSA,3,"  ",callstring);
3251    Exec(callstring);
3252    Info(InfoFSA,1,"External fsa `reverse' program complete.\n");
3253    if not READ(Concatenation(_KBTmpFileName,".fsaforreverse.reverse")) then
3254       Error("Could not open `reverse' fsa file");
3255    fi;
3256    Exec(Concatenation("/bin/rm -f ",_KBTmpFileName,".fsaforreverse*"));
3257    InitializeFSA(_FSA_reverse);
3258    fsa.alphabet := alph;
3259    _FSA_reverse.alphabet := alph;
3260    return _FSA_reverse;
3261end;
3262
3263#############################################################################
3264##
3265#F  ExistsFSA(<fsa>) . . . call external program to 'exist' fsa <fsa>
3266##
3267## Here <fsa> must be a 2-variable FSA.
3268## An FSA is returned in which a word  w1 is accepted iff
3269## (w1,w2) is accepted by <fsa> for some word w2.
3270## Public function.
3271ExistsFSA := function(fsa)
3272  local callstring, filename, alph;
3273
3274    if not IsInitializedFSA(fsa) then
3275       InitializeFSA(fsa);
3276    fi;
3277    if  IsDeterministicFSA(fsa)=false  then
3278       Error("First argument is not a dfa.");
3279    fi;
3280    if fsa.alphabet.type <> "product" or fsa.alphabet.arity <> 2 then
3281       Error("Alphabet of argument is not 2-variable.");
3282    fi;
3283    ## We replace the base alphabet by simple-type alphabet for the
3284    ## I/O phase.
3285    alph := fsa.alphabet.base;
3286    fsa.alphabet.base := rec(type:="simple",size:=alph.size);
3287    InitializeSR(fsa.alphabet.base);
3288    filename := Concatenation(_KBTmpFileName,".fsaforexists");
3289    WriteFSA(fsa,"_FSA",filename,";");
3290    callstring := Filename(_KBExtDir,"fsaexists");
3291    if InfoLevel(InfoFSA)=0 then
3292       callstring := Concatenation(callstring," -silent ");
3293    elif InfoLevel(InfoFSA)>1 then
3294       callstring := Concatenation(callstring," -v ");
3295    fi;
3296    callstring := Concatenation(callstring," ",filename);
3297    Info(InfoFSA,1,"Calling fsa `exists' program.\n");
3298    Info(InfoFSA,3,"  ",callstring);
3299    Exec(callstring);
3300    Info(InfoFSA,1,"External fsa `exists' program complete.\n");
3301    if not READ(Concatenation(_KBTmpFileName,".fsaforexists.exists")) then
3302       Error("Could not open `exists' fsa file");
3303    fi;
3304    Exec(Concatenation("/bin/rm -f ",_KBTmpFileName,".fsaforexists*"));
3305    InitializeFSA(_FSA_exists);
3306    fsa.alphabet.base := alph;
3307    _FSA_exists.alphabet := alph;
3308    return _FSA_exists;
3309end;
3310
3311#############################################################################
3312##
3313#F  SwapCoordsFSA(<fsa>)
3314##     . . . call external program to swap co-ordinates of fsa <fsa>
3315##
3316## Here <fsa> must be a 2-variable FSA.
3317## An 2-variable FSA is returned in which (w1,w2) is accepted iff
3318## (w2,w1) is accepted by <fsa>.
3319## Public function.
3320SwapCoordsFSA := function(fsa)
3321  local callstring, filename, alph;
3322
3323    if not IsInitializedFSA(fsa) then
3324       InitializeFSA(fsa);
3325    fi;
3326    if  IsDeterministicFSA(fsa)=false  then
3327       Error("First argument is not a dfa.");
3328    fi;
3329    if fsa.alphabet.type <> "product" or fsa.alphabet.arity <> 2 then
3330       Error("Alphabet of argument is not 2-variable.");
3331    fi;
3332    ## We replace the base alphabet by simple-type alphabet for the
3333    ## I/O phase.
3334    alph := fsa.alphabet.base;
3335    fsa.alphabet.base := rec(type:="simple",size:=alph.size);
3336    InitializeSR(fsa.alphabet.base);
3337    filename := Concatenation(_KBTmpFileName,".fsaforswap_coords");
3338    WriteFSA(fsa,"_FSA",filename,";");
3339    callstring := Filename(_KBExtDir,"fsaswapcoords");
3340    if InfoLevel(InfoFSA)=0 then
3341       callstring := Concatenation(callstring," -silent ");
3342    elif InfoLevel(InfoFSA)>1 then
3343       callstring := Concatenation(callstring," -v ");
3344    fi;
3345    callstring := Concatenation(callstring," ",filename," ",filename,".o");
3346 #Print(callstring,"\n");
3347    Info(InfoFSA,1,"Calling fsa `swap_coords' program.\n");
3348    Info(InfoFSA,3,"  ",callstring);
3349    Exec(callstring);
3350    Info(InfoFSA,1,"External fsa `swap_coords' program complete.\n");
3351    if not
3352       READ(Concatenation(_KBTmpFileName,".fsaforswap_coords.o"))
3353          then Error("Could not open `swapcoords' fsa file");
3354    fi;
3355    Exec(Concatenation("/bin/rm -f ",_KBTmpFileName,".fsaforswap_coords*"));
3356    InitializeFSA(_FSA_swap_coords);
3357    fsa.alphabet.base := alph;
3358    _FSA_swap_coords.alphabet.base := alph;
3359    return _FSA_swap_coords;
3360end;
3361
3362#############################################################################
3363##
3364#F  AndFSA(<fsa1>, <fsa2>) . . call external program to and fsa's <fsa1>,<fsa2>
3365##
3366## An FSA is returned in which a word is accepted iff it is a
3367## accepted by both of the fsa's <fsa1> and <fsa2>.
3368## Public function.
3369AndFSA := function(fsa1, fsa2)
3370  local callstring, filename1, filename2, filename3, alph;
3371
3372    if not IsInitializedFSA(fsa1) then
3373       InitializeFSA(fsa1);
3374    fi;
3375    if not IsInitializedFSA(fsa2) then
3376       InitializeFSA(fsa2);
3377    fi;
3378    if  IsDeterministicFSA(fsa1)=false or IsDeterministicFSA(fsa2)=false then
3379       Error("One of the arguments is not a dfa.");
3380    fi;
3381    ## We replace the alphabet by simple-type alphabet for the
3382    ## I/O phase.
3383    alph := fsa1.alphabet;
3384    if alph <> fsa2.alphabet then
3385      Error("Arguments have different alphabets.");
3386    fi;
3387    fsa1.alphabet := rec(type:="simple",size:=alph.size);
3388    InitializeSR(fsa1.alphabet);
3389    fsa2.alphabet := rec(type:="simple",size:=alph.size);
3390    InitializeSR(fsa2.alphabet);
3391    filename1 := Concatenation(_KBTmpFileName,".fsaforand1");
3392    filename2 := Concatenation(_KBTmpFileName,".fsaforand2");
3393    filename3 := Concatenation(_KBTmpFileName,".fsaforand3");
3394    WriteFSA(fsa1,"_FSA",filename1,";");
3395    WriteFSA(fsa2,"_FSA",filename2,";");
3396    callstring := Filename(_KBExtDir,"fsaand");
3397    if InfoLevel(InfoFSA)=0 then
3398       callstring := Concatenation(callstring," -silent ");
3399    elif InfoLevel(InfoFSA)>1 then
3400       callstring := Concatenation(callstring," -v ");
3401    fi;
3402    callstring := Concatenation(callstring," ",
3403                     filename1," ",filename2," ",filename3);
3404    Info(InfoFSA,1,"Calling fsa `and' program.\n");
3405    Info(InfoFSA,3,"  ",callstring);
3406    Exec(callstring);
3407    Info(InfoFSA,1,"External fsa `and' program complete.\n");
3408    if not READ(filename3) then
3409       Error("Could not open `and' fsa file");
3410    fi;
3411    Exec(Concatenation("/bin/rm -f ",_KBTmpFileName,".fsaforand*"));
3412    InitializeFSA(_FSA_and);
3413    fsa1.alphabet := alph;
3414    fsa2.alphabet := alph;
3415    _FSA_and.alphabet := alph;
3416    return _FSA_and;
3417end;
3418
3419#############################################################################
3420##
3421#F  OrFSA(<fsa1>, <fsa2>) . . . call external program to or fsa's <fsa1>,<fsa2>
3422##
3423## An FSA is returned in which a word is accepted iff it is a
3424## accepted by either of the fsa's <fsa1> or <fsa2>.
3425## Public function.
3426OrFSA := function(fsa1, fsa2)
3427  local callstring, filename1, filename2, filename3, alph;
3428
3429    if not IsInitializedFSA(fsa1) then
3430       InitializeFSA(fsa1);
3431    fi;
3432    if not IsInitializedFSA(fsa2) then
3433       InitializeFSA(fsa2);
3434    fi;
3435    if  IsDeterministicFSA(fsa1)=false or IsDeterministicFSA(fsa2)=false then
3436       Error("One of the arguments is not a dfa.");
3437    fi;
3438    ## We replace the alphabet by simple-type alphabet for the
3439    ## I/O phase.
3440    alph := fsa1.alphabet;
3441    if alph <> fsa2.alphabet then
3442      Error("Arguments have different alphabets.");
3443    fi;
3444    fsa1.alphabet := rec(type:="simple",size:=alph.size);
3445    InitializeSR(fsa1.alphabet);
3446    fsa2.alphabet := rec(type:="simple",size:=alph.size);
3447    InitializeSR(fsa2.alphabet);
3448    filename1 := Concatenation(_KBTmpFileName,".fsaforor1");
3449    filename2 := Concatenation(_KBTmpFileName,".fsaforor2");
3450    filename3 := Concatenation(_KBTmpFileName,".fsaforor3");
3451    WriteFSA(fsa1,"_FSA",filename1,";");
3452    WriteFSA(fsa2,"_FSA",filename2,";");
3453    callstring := Filename(_KBExtDir,"fsaor");
3454    if InfoLevel(InfoFSA)=0 then
3455       callstring := Concatenation(callstring," -silent ");
3456    elif InfoLevel(InfoFSA)>1 then
3457       callstring := Concatenation(callstring," -v ");
3458    fi;
3459    callstring := Concatenation(callstring," ",
3460                     filename1," ",filename2," ",filename3);
3461    Info(InfoFSA,1,"Calling fsa `or' program.\n");
3462    Info(InfoFSA,3,"  ",callstring);
3463    Exec(callstring);
3464    Info(InfoFSA,1,"External fsa `or' program complete.\n");
3465    if not READ(filename3) then
3466       Error("Could not open `or' fsa file");
3467    fi;
3468    Exec(Concatenation("/bin/rm -f ",_KBTmpFileName,".fsaforor*"));
3469    InitializeFSA(_FSA_or);
3470    fsa1.alphabet := alph;
3471    fsa2.alphabet := alph;
3472    _FSA_or.alphabet := alph;
3473    return _FSA_or;
3474end;
3475
3476#############################################################################
3477##
3478#F  ConcatFSA(<fsa1>, <fsa2>)
3479##            . . . call external program to concat fsa's <fsa1>,<fsa2>
3480##
3481## An FSA is returned in which a word is accepted iff it is the concatenation
3482## of words accepted by the fsa's <fsa1> and <fsa2>.
3483## Public function.
3484ConcatFSA := function(fsa1, fsa2)
3485  local callstring, filename1, filename2, filename3, alph;
3486
3487    if not IsInitializedFSA(fsa1) then
3488       InitializeFSA(fsa1);
3489    fi;
3490    if not IsInitializedFSA(fsa2) then
3491       InitializeFSA(fsa2);
3492    fi;
3493    if  IsDeterministicFSA(fsa1)=false or IsDeterministicFSA(fsa2)=false then
3494       Error("One of the arguments is not a dfa.");
3495    fi;
3496    ## We replace the alphabet by simple-type alphabet for the
3497    ## I/O phase.
3498    alph := fsa1.alphabet;
3499    if alph <> fsa2.alphabet then
3500      Error("Arguments have different alphabets.");
3501    fi;
3502    fsa1.alphabet := rec(type:="simple",size:=alph.size);
3503    InitializeSR(fsa1.alphabet);
3504    fsa2.alphabet := rec(type:="simple",size:=alph.size);
3505    InitializeSR(fsa2.alphabet);
3506    filename1 := Concatenation(_KBTmpFileName,".fsaforconcat1");
3507    filename2 := Concatenation(_KBTmpFileName,".fsaforconcat2");
3508    filename3 := Concatenation(_KBTmpFileName,".fsaforconcat3");
3509    WriteFSA(fsa1,"_FSA",filename1,";");
3510    WriteFSA(fsa2,"_FSA",filename2,";");
3511    callstring := Filename(_KBExtDir,"fsaconcat");
3512    if InfoLevel(InfoFSA)=0 then
3513       callstring := Concatenation(callstring," -silent ");
3514    elif InfoLevel(InfoFSA)>1 then
3515       callstring := Concatenation(callstring," -v ");
3516    fi;
3517    callstring := Concatenation(callstring," ",
3518                     filename1," ",filename2," ",filename3);
3519    Info(InfoFSA,1,"Calling fsa `concat' program.\n");
3520    Info(InfoFSA,3,"  ",callstring);
3521    Exec(callstring);
3522    Info(InfoFSA,1,"External fsa `concat' program complete.\n");
3523    if not READ(filename3) then
3524       Error("Could not open `concat' fsa file");
3525    fi;
3526    Exec(Concatenation("/bin/rm -f ",_KBTmpFileName,".fsaforconcat*"));
3527    InitializeFSA(_FSA_concat);
3528    fsa1.alphabet := alph;
3529    fsa2.alphabet := alph;
3530    _FSA_concat.alphabet := alph;
3531    return _FSA_concat;
3532end;
3533
3534#############################################################################
3535##
3536#F  LanguagesEqualFSA(<fsa1>, <fsa2>)
3537##            . . . decide whether  <fsa1>, <fsa2> havbe the same language
3538##
3539## <fsa1> and <fsa2> must have the same alphabet, or an error results.
3540## true or false is returned according to whether <fsa1> and <fsa2> have
3541## the same language.
3542##
3543## Public function.
3544LanguagesEqualFSA := function(fsa1, fsa2)
3545    local mfsa1, mfsa2;
3546    if not IsInitializedFSA(fsa1) then
3547       InitializeFSA(fsa1);
3548    fi;
3549    if not IsInitializedFSA(fsa2) then
3550       InitializeFSA(fsa2);
3551    fi;
3552    if  IsDeterministicFSA(fsa1)=false or IsDeterministicFSA(fsa2)=false then
3553       Error("One of the arguments is not a dfa.");
3554    fi;
3555    if fsa1.alphabet <> fsa2.alphabet then
3556      Error("Parameters do not have the same alphabet.");
3557    fi;
3558
3559    mfsa1 := MinimizeFSA(fsa1);
3560    mfsa2 := MinimizeFSA(fsa2);
3561    BFSFSA(mfsa1);
3562    BFSFSA(mfsa2);
3563    return DenseDTableFSA(mfsa1) = DenseDTableFSA(mfsa2);
3564end;
3565
3566#############################################################################
3567##
3568#F  GrowthFSA(<fsa>) . . . call external program to find growth function
3569##                         of <fsa>
3570##
3571## A rational function is returned. The coefficient of x^n in this function is
3572## the number of words of length n in the language of <fsa>,
3573##
3574## Public function.
3575GrowthFSA := function(fsa)
3576  local callstring, filename, alph, gf;
3577
3578    if not IsInitializedFSA(fsa) then
3579       InitializeFSA(fsa);
3580    fi;
3581    if  IsDeterministicFSA(fsa)=false  then
3582       Error("First argument is not a dfa.");
3583    fi;
3584    ## We replace the alphabet by simple-type alphabet for the
3585    ## I/O phase.
3586    alph := fsa.alphabet;
3587    fsa.alphabet := rec(type:="simple",size:=alph.size);
3588    InitializeSR(fsa.alphabet);
3589    filename := Concatenation(_KBTmpFileName,".fsaforgrowth");
3590    WriteFSA(fsa,"_FSA",filename,";");
3591    callstring := Filename(_KBExtDir,"fsagrowth");
3592    if InfoLevel(InfoFSA)>1 then
3593       callstring := Concatenation(callstring," -v ");
3594    fi;
3595    callstring := Concatenation(callstring," ",filename);
3596    Info(InfoFSA,1,"Calling fsa `growth' program.\n");
3597    Info(InfoFSA,3,"  ",callstring);
3598    Exec(callstring);
3599    Info(InfoFSA,1,"External fsa `growth' program complete.\n");
3600    gf := ReadAsFunction(Concatenation(_KBTmpFileName,".fsaforgrowth.growth"));
3601    Exec(Concatenation("/bin/rm -f ",_KBTmpFileName,".fsaforgrowth*"));
3602    fsa.alphabet := alph;
3603    if gf=fail then
3604      return fail;
3605    fi;
3606    return gf();
3607end;
3608