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