1#############################################################################
2####
3##
4#W  anupqios.gi            ANUPQ package                          Greg Gamble
5##
6##  This file installs core functions used with iostreams.
7##
8#Y  Copyright (C) 2001  Lehrstuhl D fuer Mathematik,  RWTH Aachen,  Germany
9##
10
11#############################################################################
12##
13#F  PQ_START( <workspace>, <setupfile> ) . . . open a stream for a pq process
14##
15##  ensures the images file written by the `pq' binary when in  the  Standard
16##  Presentation menu is empty, opens an io stream  to  a  `pq'  process  (if
17##  <setupfile> is `fail') or a file stream for a setup file (if  <setupfile>
18##  is a filename i.e. a string) and returns  a  record  with  fields  `menu'
19##  (current menu for the `pq' binary), `opts' (the runtime switches used  by
20##  the `pq' process), `workspace' (the value of <workspace> which should  be
21##  a positive integer), and `stream' (the io or file stream opened).
22##
23InstallGlobalFunction(PQ_START, function( workspace, setupfile )
24local opts, iorec, topqlogfile;
25  PrintTo(ANUPQData.SPimages, ""); #to ensure it's empty
26  if setupfile = fail then
27    opts := [ "-G" ];
28  else
29    opts := [ "-i", "-k", "-g" ];
30  fi;
31  if workspace <> 10000000 then
32    Append( opts, [ "-s", String(workspace) ] );
33  fi;
34  iorec := rec( menu := "SP",
35                opts := opts,
36                workspace := workspace );
37  if setupfile = fail then
38    iorec.stream := InputOutputLocalProcess( ANUPQData.tmpdir,
39                                             ANUPQData.binary,
40                                             opts );
41    if iorec.stream = fail then
42      Error( "failed to launch child process" );
43    fi;
44    # menus are flushed at InfoANUPQ level 6, prompts at level 5
45    FLUSH_PQ_STREAM_UNTIL(iorec.stream, 6, 5, PQ_READ_NEXT_LINE, IS_PQ_PROMPT);
46  else
47    iorec.stream := OutputTextFile(setupfile, false);
48    iorec.setupfile := setupfile;
49    ToPQk(iorec, [], [ "#call pq with flags: '",
50                       JoinStringsWithSeparator(opts, " "),
51                       "'" ]);
52  fi;
53  return iorec;
54end );
55
56#############################################################################
57##
58#F  PqStart(<G>,<workspace> : <options>) . Initiate interactive ANUPQ session
59#F  PqStart(<G> : <options>)
60#F  PqStart(<workspace> : <options>)
61#F  PqStart( : <options>)
62##
63##  activate an iostream for an interactive {\ANUPQ} process (i.e.  `PqStart'
64##  starts up a `pq' binary process and opens a {\GAP} iostream  to  ``talk''
65##  to that process) and returns an integer <i> that can be used to  identify
66##  that process. The argument <G>, if given, should be an *fp group* or  *pc
67##  group* that the user  intends  to  manipute  using  interactive  {\ANUPQ}
68##  functions. If `PqStart' is given an integer argument <workspace> then the
69##  `pq' binary is started up with a workspace (an  integer  array)  of  size
70##  <workspace> (i.e. $4 \times <workspace>$ bytes in a 32-bit  environment);
71##  otherwise, the `pq' binary sets a default workspace of $10000000$.
72##
73##  The only <options> currently recognised  by  `PqStart'  are  `Prime'  and
74##  `Exponent' (see~"Pq" for details) and if provided  they  are  essentially
75##  global for the interactive {\ANUPQ} process, except that any  interactive
76##  function interacting with the process and passing new  values  for  these
77##  options will over-ride the global values.
78##
79InstallGlobalFunction(PqStart, function(arg)
80local opts, iorec, procId, G, workspace, optname;
81
82  if 2 < Length(arg) then
83    Error("at most two arguments expected.\n");
84  fi;
85
86  if not IsEmpty(arg) and IsGroup( arg[1] ) then
87    G := arg[1];
88    if not( IsFpGroup(G) or IsPcGroup(G) ) then
89      Error( "argument <G> should be an fp group or a pc group\n" );
90    fi;
91    arg := arg{[2 .. Length(arg)]};
92  fi;
93
94  if not IsEmpty(arg) then
95    workspace := arg[1];
96    if not IsPosInt(workspace) then
97      Error("argument <workspace> should be a positive integer.\n");
98    fi;
99  else
100    workspace := 10000000;
101  fi;
102
103  iorec := PQ_START( workspace, fail );
104  if IsBound( G ) then
105    iorec.group := G;
106  fi;
107  iorec.calltype := "interactive";
108  for optname in ANUPQGlobalOptions do
109    VALUE_PQ_OPTION(optname, iorec);
110  od;
111
112  procId := Length(ANUPQData.io) + 1;
113  iorec.procId := procId;
114  ANUPQData.io[ procId ] := iorec;
115  return procId;
116end);
117
118#############################################################################
119##
120#F  PqQuit( <i> )  . . . . . . . . . . . . Close an interactive ANUPQ session
121#F  PqQuit()
122##
123##  closes the stream of the <i>th or default  interactive  {\ANUPQ}  process
124##  and unbinds its `ANUPQData.io' record.
125##
126InstallGlobalFunction(PqQuit, function(arg)
127local ioIndex;
128
129  ioIndex := ANUPQ_IOINDEX(arg);
130  # No need to bother about descending through the menus.
131  CloseStream(ANUPQData.io[ioIndex].stream);
132  Unbind(ANUPQData.io[ioIndex]);
133end);
134
135#############################################################################
136##
137#F  PqQuitAll() . . . . . . . . . . . .  Close all interactive ANUPQ sessions
138##
139##  closes the streams of all active interactive {\ANUPQ} process and unbinds
140##  their `ANUPQData.io' records.
141##
142InstallGlobalFunction(PqQuitAll, function()
143local ioIndex;
144
145  for ioIndex in [1 .. Length(ANUPQData.io)] do
146    if IsBound(ANUPQData.io[ioIndex]) then
147      CloseStream(ANUPQData.io[ioIndex].stream);
148      Unbind(ANUPQData.io[ioIndex]);
149    fi;
150  od;
151end);
152
153#############################################################################
154##
155#F  ANUPQ_IOINDEX . . . . the number identifying an interactive ANUPQ session
156##
157##  returns the index of the record in the `ANUPQData.io' list  corresponding
158##  to an interactive {\ANUPQ} session. With  no  argument  the  first  bound
159##  index in `ANUPQData.io' is returned. With integer (first)  argument  <i>,
160##  <i> is returned if `ANUPQData.io[<i>]' is bound.
161##
162InstallGlobalFunction(ANUPQ_IOINDEX, function(arglist)
163local ioIndex;
164
165  if IsEmpty(arglist) then
166    # Find the first bound ioIndex
167    ioIndex := 1;
168    while not(IsBound(ANUPQData.io[ioIndex])) and
169          ioIndex < Length(ANUPQData.io) do
170      ioIndex := ioIndex + 1;
171    od;
172    if IsBound(ANUPQData.io[ioIndex]) then
173      return ioIndex;
174    else
175      Info(InfoANUPQ + InfoWarning, 1,
176           "No interactive ANUPQ sessions are currently active");
177      return fail;
178    fi;
179  elif IsBound(ANUPQData.io[ arglist[1] ]) then
180    return arglist[1];
181  else
182    Error("no such interactive ANUPQ session\n");
183  fi;
184end);
185
186#############################################################################
187##
188#F  ANUPQ_IOINDEX_ARG_CHK .  Checks ANUPQ_IOINDEX has the right no. of arg'ts
189##
190InstallGlobalFunction(ANUPQ_IOINDEX_ARG_CHK, function(arglist)
191  if Length(arglist) > 1 then
192    Info(InfoANUPQ + InfoWarning, 1,
193         "Expected 0 or 1 arguments, all but first argument ignored");
194  fi;
195end);
196
197#############################################################################
198##
199#F  ANUPQDataRecord([<i>]) . . . . . . . returns the data record of a process
200##
201InstallGlobalFunction(ANUPQDataRecord, function( arg )
202  if not IsEmpty(arg) and arg[1] = 0 and IsBound( ANUPQData.ni ) then
203    return ANUPQData.ni;
204  else
205    return ANUPQData.io[ CallFuncList(PqProcessIndex, arg) ];
206  fi;
207end);
208
209#############################################################################
210##
211#F  PqProcessIndex( <i> ) . . . . . . . . . . . User version of ANUPQ_IOINDEX
212#F  PqProcessIndex()
213##
214##  If given (at least) one integer  argument  `PqProcessIndex'  returns  its
215##  first argument if it corresponds to  an  active  interactive  process  or
216##  raises an error; otherwise, with no arguments,  it  returns  the  default
217##  active interactive process. If the user provides more than  one  argument
218##  then all arguments other than the  first  argument  are  ignored  (and  a
219##  warning is issued to `Info' at `InfoANUPQ' or `InfoWarning' level 1).
220##
221InstallGlobalFunction(PqProcessIndex, function(arg)
222  ANUPQ_IOINDEX_ARG_CHK(arg);
223  return ANUPQ_IOINDEX(arg);
224end);
225
226#############################################################################
227##
228#F  PqProcessIndices() . . . . the list of active interactive ANUPQ processes
229##
230##  returns the list of (integer) indices of all active interactive  {\ANUPQ}
231##  processes.
232##
233InstallGlobalFunction(PqProcessIndices, function()
234  return Filtered( [1..Length(ANUPQData.io)], i -> IsBound( ANUPQData.io[i] ) );
235end);
236
237#############################################################################
238##
239#F  IsPqProcessAlive( <i> ) . .  checks an interactive ANUPQ process iostream
240#F  IsPqProcessAlive()
241##
242##  return  `true'  if  the  {\GAP}  iostream  of  the  <i>th  (or   default)
243##  interactive {\ANUPQ} process is alive (i.e. can still be written to),  or
244##  `false', otherwise.
245##
246InstallGlobalFunction(IsPqProcessAlive, function(arg)
247  return not IsEndOfStream( ANUPQData.io[ PqProcessIndex(arg) ].stream );
248end);
249
250#############################################################################
251##
252#V  PQ_MENUS . . . . . . . . . . . data describing the menus of the pq binary
253##
254##  a record whose fields are abbreviated names of  the  menus  of  the  `pq'
255##  binary and whose values are themselves records with fields:
256##
257##    name
258##        long name of menu;
259##    depth
260##        the number of times 0 must be passed to the `pq' binary for  it  to
261##        exit;
262##    prev
263##        the menu one gets to from the current menu via option 0 (or `""' in
264##        the case of the menu `SP';
265##    nextopt
266##        a record whose fields are the new menus of greater depth  that  can
267##        be reached by an option of the current menu, and whose  values  are
268##        the corresponding numbers of the options of the current menu needed
269##        to get to the new menus.
270##
271InstallValue(PQ_MENUS, rec(
272  SP  := rec( name  := "Standard Presentation Menu",
273              depth := 1, prev  := "",   nextopt := rec( pQ := 7 ) ),
274  pQ  := rec( name  := "(Main) p-Quotient Menu",
275              depth := 2, prev  := "SP", nextopt := rec( pG  := 9, ApQ := 8 ) ),
276  pG  := rec( name  := "(Main) p-Group Generation Menu",
277              depth := 3, prev  := "pQ", nextopt := rec( ApG := 6 ) ),
278  ApQ := rec( name  := "Advanced p-Quotient Menu",
279              depth := 3, prev  := "pQ", nextopt := rec() ),
280  ApG := rec( name  := "Advanced p-Group Gen'n Menu",
281              depth := 4, prev  := "pG", nextopt := rec() )
282  ) );
283
284#############################################################################
285##
286#F  PQ_MENU( <datarec>, <newmenu> ) . . . . . . change/get menu of pq process
287#F  PQ_MENU( <datarec> )
288##
289InstallGlobalFunction(PQ_MENU, function(arg)
290local datarec, newmenu, nextmenu, tomenu, infolev;
291  datarec := arg[1];
292  if 2 = Length(arg) then
293    newmenu := arg[2];
294    if datarec.menu in ["SP", "pQ"] and newmenu in ["ApQ", "pG", "ApG"] then
295      PQ_GRP_EXISTS_CHK( datarec ); #We try to avoid seg-faults!
296    fi;
297    while datarec.menu <> newmenu do
298      if PQ_MENUS.(datarec.menu).depth >= PQ_MENUS.(newmenu).depth then
299        datarec.menu := PQ_MENUS.(datarec.menu).prev;
300        tomenu := PQ_MENUS.(datarec.menu).name;
301        ToPQk(datarec, [ 0 ], [ "  #to ", tomenu]);
302        infolev := 5;
303      elif datarec.menu = "pQ" and newmenu = "ApQ" then
304        datarec.menu := "ApQ";
305        tomenu := PQ_MENUS.(datarec.menu).name;
306        ToPQk(datarec, [ PQ_MENUS.pQ.nextopt.ApQ ], [ "  #to ", tomenu ]);
307        infolev := 6;
308      else
309        nextmenu := RecNames( PQ_MENUS.(datarec.menu).nextopt )[1];
310        tomenu := PQ_MENUS.(nextmenu).name;
311        ToPQk(datarec, [ PQ_MENUS.(datarec.menu).nextopt.(nextmenu) ],
312                       [ "  #to ", tomenu ]);
313        datarec.menu := nextmenu;
314        infolev := 6;
315      fi;
316      # menus are flushed at InfoANUPQ level 6, prompts at level 5
317      if not IsBound(datarec.setupfile) then
318        FLUSH_PQ_STREAM_UNTIL(datarec.stream, infolev, 5, PQ_READ_NEXT_LINE,
319                              IS_PQ_PROMPT);
320      fi;
321    od;
322  fi;
323  return datarec.menu;
324end);
325
326#############################################################################
327##
328#F  IS_PQ_PROMPT( <line> ) . . . .  checks whether the line is a prompt of pq
329##
330##  returns `true' if the string  <line>  is  a  `pq'  prompt,  or  otherwise
331##  returns `false'.
332##
333InstallGlobalFunction(IS_PQ_PROMPT,
334  line -> IS_ALL_PQ_LINE(line) and ANUPQData.linetype = "prompt"
335);
336
337#############################################################################
338##
339#F  IS_ALL_PQ_LINE( <line> ) . checks whether line is a complete line from pq
340##
341##  returns `true' if the string <line> is a `pq' prompt or  a  request  from
342##  `pq' to {\GAP} to compute stabilisers or simply ends  in  a  newline  and
343##  sets `ANUPQData.linetype' to `"prompt"', `"request"'  or  `"hasnewline"',
344##  accordingly; otherwise `ANUPQData.linetype' is  set  to  `"unknown"'  and
345##  `false' is returned.
346##
347InstallGlobalFunction(IS_ALL_PQ_LINE, function( line )
348local len;
349  ANUPQData.linetype := "unknown";
350  len := Length(line);
351  if 0 < len then
352    if line[len] = '\n' then
353      if 4 < len  and line{[1 .. 3]} = "GAP" and line[len - 1] = '!' then
354        ANUPQData.linetype := "request";
355      elif 6 < len and line{[1 .. 6]} in ["Enter ", "Input "] then
356        ANUPQData.linetype := "prompt";
357      else
358        ANUPQData.linetype := "hasnewline";
359      fi;
360    elif line = "Select option: " or
361         1 < len and line{[len - 1 .. len]} = "? "  or
362         8 < len and line{[len - 1 .. len]} = ": " and
363                     line{[1 .. 6]} in ["Enter ", "Input ", "Add ne"] then
364      ANUPQData.linetype := "prompt";
365    fi;
366  fi;
367  return ANUPQData.linetype <> "unknown";
368end);
369
370#############################################################################
371##
372#F  PQ_READ_ALL_LINE( <iostream> ) .  read line from pq but poss. return fail
373##
374##  reads a complete line from <iostream> or return `fail'.
375##
376InstallGlobalFunction(PQ_READ_ALL_LINE,
377  iostream -> ReadAllLine(iostream, false, IS_ALL_PQ_LINE)
378);
379
380#############################################################################
381##
382#F  PQ_READ_NEXT_LINE( <iostream> ) . read line from pq but never return fail
383##
384##  Essentially, like `PQ_READ_ALL_LINE' but we know there is a complete line
385##  to be got, so we wait for it, before returning.
386##
387InstallGlobalFunction(PQ_READ_NEXT_LINE,
388  iostream -> ReadAllLine(iostream, true, IS_ALL_PQ_LINE)
389);
390
391#############################################################################
392##
393#F  FLUSH_PQ_STREAM_UNTIL(<stream>,<infoLev>,<infoLevMy>,<readln>,<IsMyLine>)
394##  . . .  . . . . . . . . . . . read lines from a stream until a wanted line
395##
396##  calls <readln> (which should be one of `ReadLine', `PQ_READ_NEXT_LINE' or
397##  `PQ_READ_ALL_LINE') to read lines from a stream <stream> and `Info's each
398##  line read at `InfoANUPQ' level <infoLev> until a line <line> is read  for
399##  which `<IsMyLine>(<line>)' is `true'; <line> is `Info'-ed at  `InfoANUPQ'
400##  level <infoLevMy> and returned. <IsMyLine>  should  be  a  boolean-valued
401##  function that expects a string as its only argument,  and  <infoLev>  and
402##  <infoLevMy> should be positive integers. An <infoLevMy> of 10 means  that
403##  the  line  <line>  matched  by  `<IsMyLine>(<line>)'  should   never   be
404##  `Info'-ed.
405##
406InstallGlobalFunction(FLUSH_PQ_STREAM_UNTIL,
407function(stream, infoLev, infoLevMy, readln, IsMyLine)
408local line;
409  line := readln(stream);
410  while not IsMyLine(line) do
411    Info(InfoANUPQ, infoLev, Chomp(line));
412    line := readln(stream);
413  od;
414  if line <> fail and infoLevMy < 10 then
415    Info(InfoANUPQ, infoLevMy, Chomp(line));
416  fi;
417  return line;
418end);
419
420#############################################################################
421##
422#V  PQ_ERROR_EXIT_MESSAGES . . . error messages emitted by the pq before exit
423##
424##  A list of the error messages the `pq' emits just before exiting.
425##
426InstallValue(PQ_ERROR_EXIT_MESSAGES,
427  [ "Evaluation in compute_degree may cause integer overflow",
428    "A relation is too long -- increase the value of MAXWORD",
429    "Ran out of space during computation" ]);
430
431#############################################################################
432##
433#F  FILTER_PQ_STREAM_UNTIL_PROMPT( <datarec> )
434##
435##  reads `pq' output from `<datarec>.stream' until a `pq' prompt and `Info's
436##  any lines that are prompts, blank lines, menu exits  or  start  with  the
437##  strings in the list `<datarec>.filter' (if bound) at `InfoANUPQ' level 5;
438##  all  other  lines  are  either  `Info'-ed  at  `InfoANUPQ'  level  3   if
439##  `datarec.nonuser' is set, or, more usually, are `Info'-ed at  `InfoANUPQ'
440##  level 2 if  they  are  computation  times  or  at  `InfoANUPQ'  level  1,
441##  otherwise.
442##
443InstallGlobalFunction(FILTER_PQ_STREAM_UNTIL_PROMPT, function( datarec )
444local match, filter, lowlev, ctimelev;
445  filter := ["Exiting", "pq,", "Now enter",
446             "Presentation listing images", "(use generators x1,x2"];
447  if IsBound(datarec.match) then
448    if datarec.match = true then
449      match := ["Group:", "Group completed"];
450    else
451      match := [datarec.match];
452    fi;
453  fi;
454  if IsBound(datarec.filter) then
455    Append(filter, datarec.filter);
456  fi;
457  if ValueOption("nonuser") = true then
458    lowlev := 3;
459    ctimelev := 3;
460  else
461    ctimelev := 2;
462    if not IsBound(datarec.OutputLevel) or datarec.OutputLevel = 0 then
463      lowlev := 3;
464    else
465      lowlev := 1;
466    fi;
467  fi;
468  repeat
469    datarec.line := PQ_READ_NEXT_LINE(datarec.stream);
470    if ANUPQData.linetype in ["prompt", "request"] then
471      Info( InfoANUPQ, 5,        Chomp(datarec.line) );
472      break;
473    elif ForAny(["seconds", "Lused", "*** Final "],
474                s -> PositionSublist(datarec.line, s) <> fail) then
475      Info( InfoANUPQ, ctimelev, Chomp(datarec.line) );
476    elif datarec.line = "\n" or
477         ForAny( filter, s -> IsMatchingSublist(datarec.line, s) ) then
478      Info( InfoANUPQ, 5,        Chomp(datarec.line) );
479    elif PositionSublist(datarec.line, " saved on file") <> fail then
480      Info( InfoANUPQ, ctimelev, Chomp(datarec.line) );
481    elif ForAny( PQ_ERROR_EXIT_MESSAGES,
482                 s -> IsMatchingSublist(datarec.line, s) ) then
483      Info( InfoANUPQ + InfoWarning, 1, Chomp(datarec.line) );
484      Error( "pq program terminated, with error condition:\n  ", datarec.line );
485    else
486      Info( InfoANUPQ, lowlev,   Chomp(datarec.line) );
487    fi;
488    if IsBound(match) then
489      if ForAny( match, s -> IsMatchingSublist(datarec.line, s) ) then
490        datarec.matchedline := datarec.line;
491        datarec.complete := IsBound(datarec.complete) and datarec.complete or
492                            IsMatchingSublist(datarec.line, "Group completed");
493      fi;
494    elif IsBound(datarec.matchlist) and
495         ForAny( datarec.matchlist,
496                 s -> PositionSublist(datarec.line, s) <> fail ) then
497      Add(datarec.matchedlines, datarec.line);
498    fi;
499  until false;
500end);
501
502#############################################################################
503##
504#F  ToPQk( <datarec>, <cmd>, <comment> ) . . . . . . .  writes to a pq stream
505##
506##  writes  <cmd>  (and  <comment>,   in   setup   file   case)   to   stream
507##  `<datarec>.stream' and `Info's <cmd> and <comment> at `InfoANUPQ' level 3
508##  after a ```ToPQ> ''' prompt, and returns `true' if successful and  `fail'
509##  otherwise. The ``k'' at the end of the  function  name  is  mnemonic  for
510##  ``keyword'' (for ``keyword'' inputs to the `pq' binary one never wants to
511##  flush output).
512##
513InstallGlobalFunction(ToPQk, function(datarec, cmd, comment)
514local ok, line, i, j, closed, fragment, sepchars, words, filterones;
515
516  if not IsOutputTextStream(datarec.stream) and
517     IsEndOfStream(datarec.stream) then
518    Error("sorry! Process stream has died!\n");
519  fi;
520  if cmd in ["gens", "rels"] then
521    # these are done specially because of their potential to be enormously long
522    if cmd = "gens" then
523      line := "generators { ";
524      sepchars := ", ";
525    else
526      line := "relators   { ";
527      sepchars := "*^, ";
528    fi;
529    words := datarec.(cmd);
530    filterones := cmd = "rels" and not IsBound(datarec.Relators) and
531                  (IsFpGroup(datarec.group) or not IsPGroup(datarec.group));
532    i := 1;
533    while filterones and i <= Length(words) and IsOne(words[i]) do
534      i := i + 1;
535    od;
536    if i <= Length(words) then
537      Append(line, String(words[i]));
538      i := i + 1;
539    fi;
540    ok := true;
541    closed := false;
542    repeat
543      while filterones and i <= Length(words) and IsOne(words[i]) do
544        i := i + 1;
545      od;
546      # i is the index of the next word to be added to line or > #words
547      if i <= Length(words) then
548        # if number of non-trivial words is 0 or 1 no comma is ever added
549        Append(line, ", ");
550        Append(line, String(words[i]));
551        i := i + 1;
552      else
553        Append(line, " }");
554        if cmd = "rels" then
555          Append(line, ";");
556        fi;
557        closed := true; # not quite equivalent to: i > Length(words)
558      fi;
559      while ok and (Length(line) >= 69 or (closed and Length(line) > 0)) do
560        if Length(line) >= 69 then
561          # find a nice break if we can
562          j := 68;
563          while j > 4 and not line[j] in sepchars do j := j - 1; od;
564          # no nice break
565          if j = 4 then
566            j := 69;
567            while j < Length(line) and not line[j] in sepchars do
568              j := j + 1;
569            od;
570          fi;
571          fragment := line{[1 .. j]};
572        else
573          fragment := line;
574          j := Length(line);
575        fi;
576        if j = Length(line) and closed then
577          line := "";
578        else
579          line := Concatenation("  ", line{[j + 1 .. Length(line)]});
580        fi;
581        Info(InfoANUPQ, 4, "ToPQ> ", fragment);
582        if IsBound( datarec.setupfile) then
583          ok := WriteLine(datarec.stream, fragment);
584        else
585          ok := WriteLine(datarec.stream, fragment);
586          if IsBound( ANUPQData.topqlogfile ) then
587            WriteLine(ANUPQData.logstream, fragment);
588          fi;
589        fi;
590      od;
591    until closed or not ok;
592  else
593    # We add a null string in case <cmd> or <comment> is []
594    # ... so that `Concatenation( List(., String) );' statements return strings
595    Add(cmd, "");
596    Add(comment, "");
597    cmd     := Concatenation( List(cmd, String) );
598    comment := Concatenation( List(comment, String) );
599    Info(InfoANUPQ, 4, "ToPQ> ", cmd, comment);
600    if IsBound( datarec.setupfile) then
601      ok := WriteLine(datarec.stream, Concatenation(cmd, comment));
602    else
603      ok := WriteLine(datarec.stream, cmd);
604      if IsBound( ANUPQData.topqlogfile ) then
605        WriteLine(ANUPQData.logstream, Concatenation(cmd, comment));
606      fi;
607    fi;
608  fi;
609  if ok = fail then
610    Error("write to stream failed\n");
611  fi;
612  return ok;
613end);
614
615#############################################################################
616##
617#F  ToPQ(<datarec>, <cmd>, <comment>) . .  write to pq (& for iostream flush)
618##
619##  calls `ToPQk' to write <cmd> (and  <comment>,  in  setup  file  case)  to
620##  stream `<datarec>.stream' and `Info' <cmd> and <comment>  at  `InfoANUPQ'
621##  level 3 after a ```ToPQ> ''' prompt, and then, if we are not just writing
622##  a setup file (determined by  checking  whether  `<datarec>.setupfile'  is
623##  bound), calls `FILTER_PQ_STREAM_UNTIL_PROMPT' to filter lines  to  `Info'
624##  at the various `InfoANUPQ' levels. If we are not writing a setup file the
625##  last line flushed is saved in `<datarec>.line'.
626##
627InstallGlobalFunction(ToPQ, function(datarec, cmd, comment)
628  ToPQk(datarec, cmd, comment);
629  if not IsBound( datarec.setupfile ) then
630    FILTER_PQ_STREAM_UNTIL_PROMPT(datarec);
631
632    while ANUPQData.linetype = "request" do
633      HideGlobalVariables( "ANUPQglb", "F", "gens", "relativeOrders",
634                           "ANUPQsize", "ANUPQagsize" );
635      Read( Filename( ANUPQData.tmpdir, "GAP_input" ) );
636      Read( Filename( ANUPQData.tmpdir, "GAP_rep" ) );
637      UnhideGlobalVariables( "ANUPQglb", "F", "gens", "relativeOrders",
638                             "ANUPQsize", "ANUPQagsize" );
639      ToPQk( datarec, [ "pq, stabiliser is ready!" ], [] );
640      FILTER_PQ_STREAM_UNTIL_PROMPT(datarec);
641    od;
642  fi;
643end);
644
645#############################################################################
646##
647#F  ToPQ_BOOL( <datarec>, <optval>, <comment> ) . . . .  pass a boolean to pq
648##
649##  converts a {\GAP} boolean  <optval>  to  a  C  boolean  and  appends  the
650##  appropriate adjustment to the string <comment> before calling `ToPQ'  (we
651##  assume that <optval> is boolean ... `VALUE_PQ_OPTION' should already have
652##  checked that).
653##
654InstallGlobalFunction( ToPQ_BOOL, function( datarec, optval, comment )
655  if optval = true then
656    ToPQ( datarec, [ 1 ], [ "  #do ", comment ] );
657  else
658    ToPQ( datarec, [ 0 ], [ "  #do not ", comment ] );
659  fi;
660end);
661
662#############################################################################
663##
664#F  PqRead( <i> )  . . .  primitive read of a single line from ANUPQ iostream
665#F  PqRead()
666##
667##  read a complete line of  {\ANUPQ}  output,  from  the  <i>th  or  default
668##  interactive {\ANUPQ} process, if there is output to be read  and  returns
669##  `fail' otherwise. When successful, the  line  is  returned  as  a  string
670##  complete with trailing newline, colon, or question-mark character. Please
671##  note that it is possible to be ``too  quick''  (i.e.~the  return  can  be
672##  `fail' purely because the output from {\ANUPQ} is not there yet), but  if
673##  `PqRead' finds any output at all, it waits for a complete line.  `PqRead'
674##  also writes the line read via `Info' at `InfoANUPQ' level 2.  It  doesn't
675##  try to distinguish banner and menu output from other output of  the  `pq'
676##  binary.
677##
678InstallGlobalFunction(PqRead, function(arg)
679local line;
680
681  line := PQ_READ_ALL_LINE( ANUPQData.io[ PqProcessIndex(arg) ].stream );
682  Info(InfoANUPQ, 2, Chomp(line));
683  return line;
684end);
685
686#############################################################################
687##
688#F  PqReadAll( <i> ) . . . . . read all current output from an ANUPQ iostream
689#F  PqReadAll()
690##
691##  read and return as many *complete* lines of  {\ANUPQ}  output,  from  the
692##  <i>th or default interactive {\ANUPQ} process, as there are to  be  read,
693##  *at the time of the call*,  as  a  list  of  strings  with  any  trailing
694##  newlines removed and returns the empty list otherwise.  `PqReadAll'  also
695##  writes each line read via `Info' at `InfoANUPQ' level 2. It  doesn't  try
696##  to distinguish banner and menu output  from  other  output  of  the  `pq'
697##  binary. Whenever `PqReadAll' finds only a partial line, it waits for  the
698##  complete line, thus increasing the probability that it has  captured  all
699##  the output to be had from {\ANUPQ}.
700##
701InstallGlobalFunction(PqReadAll, function(arg)
702local lines, stream, line;
703
704  stream := ANUPQData.io[ PqProcessIndex(arg) ].stream;
705  lines := [];
706  line := PQ_READ_ALL_LINE(stream);
707  while line <> fail do
708    line := Chomp(line);
709    Info(InfoANUPQ, 2, line);
710    Add(lines, line);
711    line := PQ_READ_ALL_LINE(stream);
712  od;
713  return lines;
714end);
715
716#############################################################################
717##
718#F  PqReadUntil( <i>, <IsMyLine> ) .  read from ANUPQ iostream until a cond'n
719#F  PqReadUntil( <IsMyLine> )
720#F  PqReadUntil( <i>, <IsMyLine>, <Modify> )
721#F  PqReadUntil( <IsMyLine>, <Modify> )
722##
723##  read complete lines  of  {\ANUPQ}  output,  from  the  <i>th  or  default
724##  interactive {\ANUPQ} process, ``chomps'' them (i.e.~removes any  trailing
725##  newline character), emits them to `Info' at `InfoANUPQ' level 2  (without
726##  trying to distinguish banner and menu output from  other  output  of  the
727##  `pq' binary), and applies the function <Modify> (where <Modify>  is  just
728##  the identity map/function for the first two forms)  until  a  ``chomped''
729##  line  <line>  for  which  `<IsMyLine>(  <Modify>(<line>)  )'   is   true.
730##  `PqReadUntil' returns the list of <Modify>-ed ``chomped'' lines read.
731##
732InstallGlobalFunction(PqReadUntil, function(arg)
733local idx1stfn, stream, IsMyLine, Modify, lines, line;
734
735  idx1stfn := First([1..Length(arg)], i -> IsFunction(arg[i]));
736  if idx1stfn = fail then
737    Error("expected at least one function argument\n");
738  elif Length(arg) > idx1stfn + 1 then
739    Error("expected 1 or 2 function arguments, not ",
740          Length(arg) - idx1stfn + 1, "\n");
741  elif idx1stfn > 2  then
742    Error("expected 0 or 1 integer arguments, not ", idx1stfn - 1, "\n");
743  else
744    stream := ANUPQData.io[ ANUPQ_IOINDEX(arg{[1..idx1stfn - 1]}) ].stream;
745    IsMyLine := arg[idx1stfn];
746    if idx1stfn = Length(arg) then
747      Modify := line -> line; # The identity function
748    else
749      Modify := arg[Length(arg)];
750    fi;
751    lines := [];
752    repeat
753      line := Chomp( PQ_READ_NEXT_LINE(stream) );
754      Info(InfoANUPQ, 2, line);
755      line := Modify(line);
756      Add(lines, line);
757    until IsMyLine(line);
758    return lines;
759  fi;
760end);
761
762#############################################################################
763##
764#F  PqWrite( <i>, <string> ) . . . . . . .  primitive write to ANUPQ iostream
765#F  PqWrite( <string> )
766##
767##  write <string> to the <i>th  or  default  interactive  {\ANUPQ}  process;
768##  <string> must be in exactly the form the {\ANUPQ} standalone expects. The
769##  command is echoed via `Info' at `InfoANUPQ' level 3 (with a  ```ToPQ> '''
770##  prompt); i.e.~do `SetInfoLevel(InfoANUPQ, 3);' to see what is transmitted
771##  to the `pq' binary. `PqWrite' returns `true' if successful in writing  to
772##  the stream of the interactive {\ANUPQ} process, and `fail' otherwise.
773##
774InstallGlobalFunction(PqWrite, function(arg)
775local ioIndex, line;
776
777  if Length(arg) in [1, 2] then
778    ioIndex := ANUPQ_IOINDEX(arg{[1..Length(arg) - 1]});
779    return ToPQk( ANUPQData.io[ioIndex], arg{[Length(arg)..Length(arg)]}, [] );
780  else
781    Error("expected 1 or 2 arguments ... not ", Length(arg), " arguments\n");
782  fi;
783end);
784
785#############################################################################
786##
787#F  ANUPQ_ARG_CHK( <funcname>, <args> ) . . . . check args of int/non-int fns
788##
789##  checks the argument list <args> for a function that has both  interactive
790##  and non-interactive versions, where <funcname> is the generic name of the
791##  function. If <args> has length more than 1 then it contains  options  for
792##  the function that have been passed in one of the {\GAP} 3-compatible ways
793##  only available non-interactively. `ANUPQ_ARG_CHK' returns <datarec> which
794##  is   either   `ANUPQData.ni'   in    the    non-interactive    case    or
795##  `ANUPQData.io[<i>]' for some <i> in the interactive case,  after  setting
796##  <datarec>.calltype' to one  of  `"interactive"',  `"non-interactive"'  or
797##  `"GAP3compatible"'.
798##
799InstallGlobalFunction(ANUPQ_ARG_CHK, function(funcname, args)
800local ioIndex, datarec, optrec, optnames;
801  PQ_OTHER_OPTS_CHK( funcname, IsEmpty(args) or IsPosInt( args[1] ) );
802  if IsEmpty(args) or IsPosInt( args[1] ) then
803    datarec := ANUPQData.io[ CallFuncList( PqProcessIndex, args ) ];
804    datarec.outfname := ANUPQData.outfile; # not always needed
805    #datarec.calltype := "interactive";    # PqStart sets this
806    if not IsBound(datarec.group) then
807      Error( "huh! Interactive process has no group\n" );
808    elif IsMatchingSublist(funcname, "PqDescendants") then
809      if not IsPcGroup( datarec.group ) then
810        Error( "group of process must be a pc group\n" );
811      fi;
812    else # Check for Prime, ClassBound if nec.
813      PQ_OPTION_CHECK( funcname, datarec );
814    fi;
815  elif 1 = Length(args) then
816    if not IsPcGroup( args[1] ) then
817      if IsMatchingSublist(funcname, "PqDescendants") then
818        Error( "first argument <args[1]> must be a pc group\n" );
819      elif not IsFpGroup( args[1] ) then
820        Error( "first argument <args[1]> must be a pc group or an fp group\n" );
821      fi;
822    fi;
823    ANUPQData.ni := PQ_START( VALUE_PQ_OPTION( "PqWorkspace", 10000000 ),
824                              VALUE_PQ_OPTION( "SetupFile" ) );
825    datarec := ANUPQData.ni;
826    datarec.group := args[1];
827    datarec.calltype := "non-interactive";
828    datarec.procId := 0;
829    PQ_OPTION_CHECK( funcname, datarec ); # Check for Prime, ClassBound if nec.
830    if IsBound( datarec.setupfile ) then
831      datarec.outfname := "PQ_OUTPUT";
832    else
833      datarec.outfname := ANUPQData.outfile; # not always needed
834    fi;
835  else
836    # GAP 3 way of passing options is supported in non-interactive use
837    if funcname = "PqDescendantsTreeCoclassOne" then
838      Error("GAP 3-compatible ways of passing options not supported");
839    elif IsRecord(args[2]) then
840      optrec := ShallowCopy(args[2]);
841      optnames := Set( REC_NAMES(optrec) );
842      SubtractSet( optnames, Set( ANUPQoptions.(funcname) ) );
843      if not IsEmpty(optnames) then
844        Error(ANUPQoptError( funcname, optnames ), "\n");
845      fi;
846    else
847      optrec := ANUPQextractOptions(funcname, args{[2 .. Length(args)]});
848    fi;
849    PushOptions(optrec);
850    PQ_FUNCTION.(funcname)( args{[1]} );
851    PopOptions();
852    datarec := ANUPQData.ni;
853    datarec.calltype := "GAP3compatible";
854    datarec.procId := 0;
855  fi;
856  return datarec;
857end );
858
859#############################################################################
860##
861#F  PQ_COMPLETE_NONINTERACTIVE_FUNC_CALL( <datarec> )
862##
863##  writes the final commands to the `pq' setup file so that the `pq'  binary
864##  makes a clean exit, or just closes the stream to kill the `pq' process.
865##
866InstallGlobalFunction(PQ_COMPLETE_NONINTERACTIVE_FUNC_CALL, function(datarec)
867  if IsBound( datarec.setupfile ) then
868    PQ_MENU(datarec, "SP");
869    ToPQk(datarec, [ 0 ], [ "  #exit program" ]);
870  fi;
871  CloseStream(datarec.stream);
872
873  if IsBound( datarec.setupfile ) then
874    Info(InfoANUPQ, 1, "Input file: '", datarec.setupfile, "' written.");
875    Info(InfoANUPQ, 1, "Run `pq' with '", datarec.opts, "' flags.");
876    Info(InfoANUPQ, 1, "The result will be saved in: '",
877                       datarec.outfname, "'.");
878  fi;
879end );
880
881#############################################################################
882##
883#F  ToPQLog([<filename>]) . . . . . . log or stop logging pq commands to file
884##
885##  With string argument <filename>,  `ToPQLog'  opens  the  file  with  name
886##  <filename> for logging; all commands written to the `pq' binary (that are
887##  `Info'-ed behind a ```ToPQ> ''' prompt at `InfoANUPQ' level 4)  are  then
888##  also written to that  file  (but  without  prompts).  With  no  argument,
889##  `ToPQLog' stops logging to whatever file was being logged to. If  a  file
890##  was already being logged to, that file is closed and the file  with  name
891##  <filename> is opened for logging.
892##
893InstallGlobalFunction(ToPQLog, function(arg)
894  if not( IsEmpty(arg) or IsString( arg[1] ) ) then
895    Error( "expected no arguments or one string argument\n" );
896  fi;
897  if IsBound(ANUPQData.topqlogfile) then
898    CloseStream(ANUPQData.logstream);
899    PQ_UNBIND(ANUPQData, ["topqlogfile", "logstream"]);
900  elif IsEmpty(arg) then
901    Info(InfoANUPQ + InfoWarning, 1, "No file currently being logged to.");
902    return;
903  fi;
904  if not( IsEmpty(arg) ) and IsString(arg[1]) then
905    ANUPQData.topqlogfile := arg[1];
906    ANUPQData.logstream := OutputTextFile(ANUPQData.topqlogfile, false);
907  fi;
908end);
909
910#E  anupqios.gi . . . . . . . . . . . . . . . . . . . . . . . . . . ends here
911