This file is indexed.

/usr/share/gap/lib/helpbase.gi is in gap-libs 4r7p5-2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

   1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
#############################################################################
##  
#W  helpbase.gi                 GAP Library                      Frank Lübeck
##  
##  
#Y  Copyright (C)  2001,  Lehrstuhl D für Mathematik,  RWTH Aachen,  Germany
#Y  (C) 2001 School Math and Comp. Sci., University of St Andrews, Scotland
#Y  Copyright (C) 2002 The GAP Group
##  
## The files helpbase.g{d,i} contain the interface between GAP's online help
## and the actual help books.
##  

#############################################################################
##  
#F  # # # # # internal utility functions dealing with strings  # # # # # # #
##  

#############################################################################
##  
#F  StringStreamInputTextFile( <filename> ) . . . . . . .  
##                 content of file as string stream, all '\r' are removed
##  
##  This is useful for text files with text to display, because the files
##  can come with UNIX or DOS/Win line breaks.
##  If this turns out to be of general interest, it can be officially
##  documented.
##  
InstallGlobalFunction(StringStreamInputTextFile, function(fname)
  local s;
  s := StringFile(fname);
  if s = fail then
    return s;
  fi;
  RemoveCharacters(s,"\r");
  return InputTextString(s);
end);

#############################################################################
##  
#F  IsDocumentedWord( <word>[, false ] ) . . . . . . .  check documentation for
#F  <word> in a search string
##  
##  Returns 'true' if <word> appears as word in some search string of the help
##  system. By default this is checked case sensitively. If the optional second
##  argument 'false' is given, the check is case insensitive.
##  
##  This utility will first be used in some debug tools showing what is newly
##  installed by loading a package. Can be documented if desired.
##  
# avoid warning for vars from GAPDoc package
if not IsBound(StripEscapeSequences) then
  StripEscapeSequences := 0;
fi;

BindGlobal( "IsDocumentedWord", function( arg ) 
  local inid, word, case, simple, cword, book, matches, a, match;

  inid:= Union( CHARS_DIGITS, CHARS_UALPHA, "_", CHARS_LALPHA );
  word := arg[1];
  if Length( arg ) > 1 and arg[2] = false then
    case:= LowercaseString;
  else
    case:= IdFunc;
  fi;
  simple:= SIMPLE_STRING( word );
  cword:= case( word );
  for book in HELP_KNOWN_BOOKS[1] do
    matches:= HELP_GET_MATCHES( [ book ], simple, true );
    for a in Concatenation( matches ) do
      match:= case( StripEscapeSequences( a[1].entries[ a[2] ][1] ) );
      if cword in SplitString( match, "", Difference( match, inid ) ) then
        return true;
      fi;
    od;
  od;
  return false;
end);

#############################################################################
## 
##  TRANSATL . . . . . . . . . . list of pairs of different spelling patterns
##
##  One could add more patterns following the following rules:
##  - Each element of `TRANSATL' should be a list of length two.
##  - Do not use capital letters; instead, truncate the first letter 
##    of the word if might or might not be capitalised.
##  - Usage of patterns where one of spelling variants is an initial 
##    substring of another is permitted.
##  - Modification of these rules (or using the patterns where one of
##    spelling variants is the trailing substring of another) may require
##    changing algorithms used in `FindMultiSpelledHelpEntries' and
##    `SuggestedSpellings'.

BindGlobal( "TRANSATL", 
            [ [ "atalogue", "atalog" ],
              [ "olour", "olor" ],
              [ "entre", "enter" ],
              [ "isation", "ization" ],
              [ "ise", "ize" ],
              [ "abeling", "abelling" ],
              [ "olvable", "oluble" ],
              [ "yse", "yze" ] ] );


#############################################################################
##
##  SuggestedSpellings
## 
##  This function is used by HELP_GET_MATCHES to check if the search topic
##  might have different spellings, lookign for patterns from `TRANSATL'.
##  
##  It returns a list of suggested spellings of a string, for example:
##
##  gap> SuggestedSpellings("TriangulizeMat");
##  [ "TrianguliseMat", "TriangulizeMat" ]
##  gap> SuggestedSpellings("CentralizerSolvableGroup");
##  [ "CentraliserSolubleGroup", "CentraliserSolvableGroup", 
##    "CentralizerSolubleGroup", "CentralizerSolvableGroup" ]
##
##  This approach may suggest wrong spellings for topics containing the 
##  substring "Size" or "size", since it's not possible to detect whether 
##  "size" is a part of another word or a word itselg (e.g. both spellings 
##  "emphasize" and  "emphasise" may be used). However, this only creates 
##  a tiny and really neglectible overhead (try e.g. `??SizesCentralisers'
##  or `??Centralizers, Normalizers and Intersections'); however it ensures 
##  that help searches may be successfull even if they use inconsistent 
##  spelling. In practice, we expect that the majority of help searches 
##  will match no more than one pattern. One could use the utility function 
##  `FindMultiSpelledHelpEntries' below to see that the help system contains 
##  about a dozen of entries which contains two occurrences of some patterns, 
##  and none with three or more of them.
##
BindGlobal( "SuggestedSpellings", function( topic )
local positions, patterns, pattern, where, what, variant, pos,
      newwhere, newwhat, i, chop, begin, topics;

positions:=[];
patterns:=[];

# Loop through all spelling patterns to check if there are any matches.
# Record starting positions and data about matching patters.

for pattern in TRANSATL do
  # for each pattern we record starting positions and data separately
  # to deal with double matches where one variant is a subset of another
  where := [];
  what := [];
  for variant in pattern do
    pos  := POSITION_SUBSTRING( topic, variant, 0 );
    while pos <> fail do
      Add( where, pos );
      Add( what, rec( start   := pos, 
                      finish  := pos+Length(variant)-1,
                      variant := variant, 
                      pattern := pattern ) );   
      pos := POSITION_SUBSTRING( topic, variant, pos+Length(variant) );
    od;
  od;   
  if Length(where) > 0 then # we have at least one match 
    # now check if we have a double match ( like in "catalogue" and "catalog" )
    if Length( Set( where ) ) = Length( where ) then
      # no double matches, just store the data (SortParallel will be applied later)
      Append( positions, where );
      Append( patterns, what );
    else
      # we have double match - create the new list, taking only the
      # match with the longer substring
      SortParallel( where, what );
      newwhere:=[ where[1] ];
      newwhat:=[ what[1] ];
      for i in [ 2..Length(where)] do
        if where[i]<>where[i-1] then
          Add(newwhere,where[i]);
          Add(newwhat,what[i]);
        else
          if Length( what[i].variant ) > Length( what[i-1].variant ) then
            newwhat[Length(newwhat)]:=what[i];
          fi;
        fi;
      od;
      Append( positions, newwhere );
      Append( patterns, newwhat );
    fi;
  fi;
od;

if Length(positions) = 0 then # no matches - return immeditely
  return [ topic ];
fi;  

# sort data about matches accordingly to their positions in `topic'.
SortParallel( positions, patterns );

# Now chop the string 'topic' into a list of lists, each of them either
# a list of all variants from the respective spelling pattern or just
# a one-element list with the "glueing" string between two pattersn or
# a pattern and the beginning or end of the string. 

chop:=[];
begin:=1;
for i in [1..Length(positions)] do
  Add( chop, [ topic{[begin..patterns[i].start-1 ]} ] );
  Add( chop, patterns[i].pattern );
  begin := Minimum( patterns[i].finish+1, Length(topic) );
od;

if begin < Length( topic ) then
  Add( chop, [ topic{[begin..Length(topic)]} ] );
fi;

# Take the cartesian product of 'chop' and form spelling suggestions 
# as concatenations of its elements. 

topics := List( Cartesian(chop), Concatenation );
Sort( topics );
return( topics );

end);


#############################################################################
##
#F  FindMultiSpelledHelpEntries() . . . . . . check documentation for entries 
##                             which might have 2 or more different spellings
##
##  This utility may be used in checks of the help system by GAP developers.
##
##  `HELP_GET_MATCHES' uses `SuggestedSpellings' to look for other possible
##  spellings, e.g. Normaliser/Normalizer, Center/Centre, Solvable/Soluble, 
##  Analyse/Analyze, Factorisation/Factorization etc. 
##
##  "FindMultiSpelledHelpEntries" reports help entries that contains more
##  than one occurence of spelling patterns from the `TRANSATL' list.
##  It may falsely report entries containing the substring "Size" or "size",
##  since it's not possible to detect whether "size" is a part of another 
##  word or a word itselg (e.g. both spellings "emphasize" and  "emphasise" 
##  may be used).
##
BindGlobal( "FindMultiSpelledHelpEntries", function( )
local report, pair, word, book, matches, a, match, patterns, i, j, w, pos, nr, hits;
report:=[];               
for pair in TRANSATL do
  word := pair[1];
  for book in HELP_KNOWN_BOOKS[1] do
    matches:= HELP_GET_MATCHES( [ book ], word, false );
    for a in Concatenation( matches ) do
      match:= StripEscapeSequences( a[1].entries[ a[2] ][1] );
      patterns:=[];
      for i in [1..Length(TRANSATL)] do
        patterns[i]:=[];
        for j in [1..Length(TRANSATL[i])] do
          w:=TRANSATL[i][j];               
          nr:=0;
          pos := POSITION_SUBSTRING( match, w, 0 );
          while pos <> fail do
            nr:=nr+1;
            pos := POSITION_SUBSTRING( match, w, pos+Length(w) );
          od;
        patterns[i][j]:=nr;
        od;
      od;
      # we just check that there are two or more matches, but in principle
      # we calculated all to distinguish between different cases: different
      # patterns; different spellings of same pattern; same spelling of 
      # same pattern appears more than once.
      hits := Sum(Flat(patterns));
      if hits >= 1 then
        AddSet( report, [ hits, book, match ] );
      fi;
    od;
  od;
od;
return report;
end);

if StripEscapeSequences = 0 then
  Unbind(StripEscapeSequences);
fi;


#############################################################################
##  
#F  MATCH_BEGIN( <a>, <b> )
##  
##  tries to match beginning of words, where words are separated by single
##  spaces; return `true' or `false'.
##  
##  No form of  normalization is applied to  <a> or <b>, so this  should be done
##  before calling MATCH_BEGIN.
##  
InstallGlobalFunction(MATCH_BEGIN, function( a, b ) local p,q;

    if Length(a)=0 and Length(b)=0 then
      return true;
    fi;

##      if 0 = Length(b) or Length(a) < Length(b)  then
    if Length(a) < Length(b)  then
        return false;
    fi;

    p:=Position(b,' ');
    if p=fail then
      return a{[1..Length(b)]} = b;
    else
      q:=Position(a,' ');
      if q=fail then
        q:=Length(a)+1;
      fi;
      # cope with blanks
      return MATCH_BEGIN(a{[1..q-1]},b{[1..p-1]}) and
             MATCH_BEGIN(a{[q+1..Length(a)]},b{[p+1..Length(b)]});
    fi;

end);

# Slight variant: returns -1 on false and number of exact matching
# words on true (>=0). Can be used to rank some matches higher.
InstallGlobalFunction(MATCH_BEGIN_COUNT, function( a, b )
  local p, q, r;

  if Length(a)=0 and Length(b)=0 then
    return 0;
  fi;

  if Length(a) < Length(b)  then
      return -1;
  fi;

  p:=Position(b,' ');
  if p=fail then
    p:=Position(a,' ');
    if p<>fail then
      a:=a{[1..p-1]};
    fi;
    if Length(b)<=Length(a) and a{[1..Length(b)]} = b then
      if Length(a)= Length(b) then
        return 1;
      else
        return 0;
      fi;
    else
      return -1;
    fi;
  else
    q:=Position(a,' ');
    if q=fail then
      q:=Length(a)+1;
    fi;
    # cope with blanks
    if MATCH_BEGIN(a{[1..q-1]},b{[1..p-1]}) then
      r := MATCH_BEGIN_COUNT(a{[q+1..Length(a)]},b{[p+1..Length(b)]}); 
      if r >= 0 then
        if p = q then
          return 1+r;
        else
          return 0;
        fi;
      else
        return -1;
      fi;
    else
      return -1;
    fi; 
  fi;
end);


#############################################################################
##  
#F  FILLED_LINE( <left>, <right>, <fill> )
##  
##  return string starting with string <left>, a number of characters <fill>
##  and ending with string <right> 6 characters before end of screen.
##  
InstallGlobalFunction(FILLED_LINE, function( l, r, f )
    local   w,  n;

    w := SizeScreen()[1] - 8;
    if w < 8  then
        return "";
    fi;
    if w-7 < Length(l)  then
        l := Concatenation( l{[1..w-7]}, "..." );
    fi;
    if w-7 < Length(r)  then
        r := Concatenation( r{[1..w-7]}, "..." );
    fi;
    if w-7 < Length(l) + Length(r)  then
        r := Concatenation( r{[1..w-7-Length(l)]}, "..." );
    fi;
 
    w := w - Length(l) - Length(r);
    n := ShallowCopy(l);
    Add( n, ' ' );
    while 0 < w  do
        Add( n, f );
        w := w - 1;
    od;
    Add( n, ' ' );
    Append( n, r );

    return n;

end);

InstallGlobalFunction(SIMPLE_STRING, function(str)
  local trans;
  # we simply list here in Position i how character i-1 should be translated
  trans :=Concatenation(
"\000\>\<\c\004\005\006\007\b\t\n\013\014\r\016\017\020\021\022\023\024\025",
"\026\027\030\031\032\033\034\035\036\037  \000   &\000  *+ -./",
"0123456789: <=>? abcd",
"efghijklmnopqrstuvwxyz[\000]^_\000abcdefghijklmnopqrstuvwxyz{ }~",
"\177\200\201\202",
"\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225",
"\226\227\230\231\232\233\234\235\236\237\238",
"\241\242\244\244\246\246\250\250\251\252\253\254\255\256\257\260\261\262",
"\264\264\265\266\270\270\271\272\276\276\276\276\277aaaaaa",
"aceeeeiiiidnooooo\327ouuuuypsaaaaaaaceeeeiiiidnooooo\367ouuuuypy"
);

  CONV_STRING(str);
  str := trans{List(str, INT_CHAR) + 1};
  # we throw away zero characters (and so backslashes and quotes)
  str := Filtered(str,x->x<>'\000');
  NormalizeWhitespace(str);
  return str;
end);


#############################################################################
##  
##  Each book for GAP's help system  has to be initialized by entries in
##  HELP_KNOWN_BOOKS. These contain a short name (a single word), a long
##  name and the directory of the documentation.
##  
##  For  the   main  books   of  the  GAP   library  this   is  included
##  here,   for    packages   these   initializations   are    done   by
##  `LoadPackageDocumentation'.
##  
##  In the  path for a  help book  there must be   a file `manual.six'. It
##  contains the  indexing information used for  the search of  a topic in
##  the GAP help. The  format of the file is  not prescribed. But if it is
##  different from the current GAP  library documentation format then  the
##  first line must be
##  
##  #SIXFORMAT myownformat
##  
##  Then a  function HELP_BOOK_HANDLER.myownformat.ReadSix is used to read
##  the rest of the file. (See HELP_BOOK_HANDLER below.)
##  
# in first list: normalized names of books
# in second list: for each book a list 
#                 [short name, long name, 
#                  directory containing the manual.six file] 
InstallValue(HELP_KNOWN_BOOKS, [[],[]]);

# if book with normalized name is already installed, we overwrite, if dir
# is the same (so short and long can be changed)
# or if short corresponds to an installed "(not loaded)" version,
# else we raise an error
# dir can be given as string relative to GAP's home or as directory object


InstallGlobalFunction(HELP_ADD_BOOK, function( short, long, dir )
  local sortfun, str, hnb, pos;
  # we sort books with main books first and packages alphabetically,
  # (looks a bit lengthy)
  sortfun := function(a, b)
    local main, pa, pb;
    main := ["tutorial", "reference", "changes"];
    pa := Position(main, a);
    pb := Position(main, b);
    if pa <> fail then
      if pb = fail then
        return true;
      else
        return pa <= pb;
      fi;
    else
      if pb <> fail then
        return false;
      else
        return a < b;
      fi;
    fi;
  end;
  str := SIMPLE_STRING(short);
  hnb := HELP_KNOWN_BOOKS;
  # check if we reinstall a known book (with possibly other names)
  pos := First([1..Length(hnb[2])], i-> dir = hnb[2][i][3]);
  if not (Position(hnb[1], str) in [fail, pos]) then
    Info(InfoWarning, 1, "Overwriting already installed help book '",str,"'.");
    Unbind(HELP_BOOKS_INFO.(str));
    pos := Position(hnb[1], str);
  fi;
  if pos = fail then
    # Perhaps we want to replace a "(not loaded)" book by another one.
    pos:= Position( hnb[1], Concatenation( str, " not loaded" ) );
    if pos = fail then
      pos := Length(hnb[1]) + 1;
    else
      Unbind(HELP_BOOKS_INFO.(hnb[1][pos]));
    fi;
  elif IsBound(HELP_BOOKS_INFO.(hnb[1][pos])) then
    # rename help book info if already loaded
    HELP_BOOKS_INFO.(str) := HELP_BOOKS_INFO.(hnb[1][pos]);
    # adjust .bookname
    HELP_BOOKS_INFO.(str).bookname := short;
    Unbind(HELP_BOOKS_INFO.(hnb[1][pos]));
  fi;
  hnb[1][pos] := str;
  hnb[2][pos] := [short, long, dir];
  SortParallel(hnb[1], hnb[2], sortfun);
end);


InstallGlobalFunction(HELP_REMOVE_BOOK, function( short )
  local str, pos;
  
  str := SIMPLE_STRING(short);
  pos := Position(HELP_KNOWN_BOOKS[1], str);
  if pos = fail then
    Error("Book with normalized name ", str, " is not installed.");
  else
    Remove (HELP_KNOWN_BOOKS[1], pos);
    Remove (HELP_KNOWN_BOOKS[2], pos);
    Unbind (HELP_BOOKS_INFO.(str));
  fi;
end);


#############################################################################
##  
#V  HELP_BOOK_HANDLER
##  
##  We use a record to store handler for different tasks with a help book.
##  The handler  for  the current  library books  is called "default".   A
##  handler is a record with some  functions as components, at least there
##  must be:
##  
##  - ReadSix          # reading a BOOK_INFO from a manual.six stream
##  - ShowChapters     # returns text or lines with chapter headers
##  - ShowSections     # same for section headers
##  - SearchMatches    # returns list of numbers referring to entries in 
##                     # BOOK_INFO's .entries list
##  - MatchPrevChap    # number of match for "<<" (last in HELP_LAST.BOOK
##  - MatchNextChap    # number of match for ">>"  and HELP_LAST.MATCH)
##  - MatchPrev        # number of match for "<"
##  - MatchNext        # number of match for ">"
##  - HelpData         # returns for given number of entry in .entries the
##                     # corresponding help data for a given format
##                     # (a special format is "ref" for cross references,
##                     # see HELP_BOOK_HANDLER.HelpDataRef below for
##                     # details)
##  The `default' handler functions will be assigned helpdef.g, see there for
##  more details on the interfaces of each of these functions.
##  
InstallValue(HELP_BOOK_HANDLER, rec(default:=rec()));

#############################################################################
##  
#V  HELP_BOOKS_INFO . . . . . . . . . . . . .  collected info about the books
##
##  The record <HELP_BOOKS_INFO>  contains for each loaded  help book an
##  entry  describing the  information found  in the  "manual.six" file.
##  This information is  stored in a record with at  least the following
##  components, which are used by this generic interface to the help system:
##
##  bookname:	    
##  
##    The short name of the book, e.g. "ref", "matrix", "EDIM".
##  
##  entries: 
##  
##    List of entries for the  search, each entry must  be a list. In  the
##    first position there must be a string which is shown for this match,
##    in case several matches for a topic where found.
##  
##  formats: (not necessary ???)
##    
##    List of output formats available for this book (like ["text", "url", ..]),
##    this must contain at least "text".
##    
##  The  remaining positions  in    the  .entries lists and/or     further
##  components  in  this help  book record  depend  on  the format  of the
##  documentation and the corresponding handler functions.
##  
InstallValue(HELP_BOOKS_INFO, rec());

#############################################################################
##  
#F  HELP_BOOK_INFO( <book> )  . . . . . . . . . . . . . get info about a book
##  
##  Returns  the  corresponding HELP_BOOKS_INFO  entry  or  reads  in  the
##  corresponding manual.six file, if not yet done.
##  
##  <book> must be a record, which is just returned, or the short name of a 
##  known book.
##  
InstallGlobalFunction(HELP_BOOK_INFO, function( book )
  local pos, bnam, nnam, path, dirs, six, stream, line, handler;

  # if this is already a record return it
  if IsRecord(book)  then
    return book;
  fi;
  
  book := LowercaseString(book);
  pos := Position(HELP_KNOWN_BOOKS[1], book);
  if pos = fail  then
    # try to match beginning 
    pos := Filtered(HELP_KNOWN_BOOKS[1], bn-> MATCH_BEGIN(bn, book));
    if Length(pos) = 0 then
      # give up
      return fail;
    else
      pos := Position(HELP_KNOWN_BOOKS[1], pos[1]);
    fi;
  fi;
  # now we have the (short) name of the book
  bnam := HELP_KNOWN_BOOKS[1][pos];
  
  if IsBound(HELP_BOOKS_INFO.(bnam)) then
    # done
    return HELP_BOOKS_INFO.(bnam);
  fi;

  # get the filename of the "manual.six" file
  path := HELP_KNOWN_BOOKS[2][pos][3];
  if IsDirectory(path) then
    dirs := [path];
  else
    dirs := DirectoriesLibrary( path );
  fi;

  six  := Filename( dirs, "manual.six" );
  if six = fail  then
    # give up
    return fail;
  fi;

  # read the manual.six file
  # read the first non-empty line to find out the handler for the corresponding
  # manual format (no explicit format implies the "default" handler)
  stream := InputTextFile(six);
  line := "";
  while Length(line) = 0 do
    line := ReadLine(stream);
    if line=fail then
      CloseStream(stream);
      return fail;
    fi;
    line := NormalizedWhitespace(line);
  od;
  if Length(line)>10 and line{[1..10]}="#SIXFORMAT" then
    handler := line{[12..Length(line)]};
    NormalizeWhitespace(handler);
  else
    handler := "default";
    CloseStream(stream);
    stream := InputTextFile(six);
  fi;
  # give up if handler functions are not (yet) loaded
  if not IsBound(HELP_BOOK_HANDLER.(handler)) then
    Print("\n#W WARNING: No handler for help book `",
          HELP_KNOWN_BOOKS[2][pos][1],
          "' available,\n#W removing this book.\n");
    if handler = "GapDocGAP" then
      Print("#W HINT: Install and load the GAPDoc package, see\n",
            "#W http://www.math.rwth-aachen.de/~Frank.Luebeck/GAPDoc\n");
    fi;
    HELP_KNOWN_BOOKS[1][pos] := Concatenation("XXXX ", bnam, ": THROWN OUT");
    HELP_KNOWN_BOOKS[2][pos][2] := "NOT AVAILABLE (no handler)";
    return fail;
  fi;
  HELP_BOOKS_INFO.(bnam) := HELP_BOOK_HANDLER.(handler).ReadSix(stream);
  
  # adjust some entries used on the interface level
  HELP_BOOKS_INFO.(bnam).handler := handler;
  HELP_BOOKS_INFO.(bnam).bookname := HELP_KNOWN_BOOKS[2][pos][1];

  # done
  return HELP_BOOKS_INFO.(bnam);
end);

#############################################################################
##  
##  
#F  # # # # # # # # # # generic show functions  # # # # # # # # # # # # # # #
##  

#############################################################################
##  
##  The central  function for the help  system is, of course,  `HELP' below.
##  Depending on  the search  string it may  trigger different  actions. The
##  functions for these actions are defined first. Many of them delegate the
##  actual work to the handler functions for the available books.
##  



#############################################################################
##  
#F  HELP_SHOW_BOOKS( ignored... ) . . . . . . . . . . .  show available books
##  
InstallGlobalFunction(HELP_SHOW_BOOKS, function( arg )
  local books;

  books := ["             Table of currently available help books",
            FILLED_LINE( "short name for ? commands", "Description", '_')];
  Append(books, List(HELP_KNOWN_BOOKS[2], a-> FILLED_LINE(a[1], a[2], ' ')));
  Pager(books);
  return true;
  
end);

#############################################################################
##
#F  HELP_SHOW_CHAPTERS( <book> )  . . . . . . . . . . . . . show all chapters
##
InstallGlobalFunction(HELP_SHOW_CHAPTERS, function(book)
  local   info;
  # delegate to handler 
  info := HELP_BOOK_INFO(book);
  if info = fail then
    Print("#W Help: Book ", book, " not found.\n");
  else
    HELP_LAST.BOOK := book;
    HELP_LAST.MATCH := 1;
    Pager(HELP_BOOK_HANDLER.(info.handler).ShowChapters(info));
  fi;
  return true;  
end);

#############################################################################
##
#F  HELP_SHOW_SECTIONS( <book> )  . . . . . . . . . . . . . show all sections
##
InstallGlobalFunction(HELP_SHOW_SECTIONS, function(book)
  local   info;
  # delegate to handler 
  info := HELP_BOOK_INFO(book);
  if info = fail then
    Print("#W Help: Book ", book, " not found.\n");
  else
    HELP_LAST.BOOK := book;
    HELP_LAST.MATCH := 1;
    Pager(HELP_BOOK_HANDLER.(info.handler).ShowSections(info));
  fi;
  return true;  
end);

#############################################################################
##  
#F  HELP_PRINT_MATCH( <match> ) . . . . . . the core function which finally
##  gets the data for displaying the help and displays it
##  
##  <match> is [book, entrynr]
##  
InstallGlobalFunction(HELP_PRINT_MATCH, function(match)
  local book, entrynr, viewer, hv, pos, type, data;
  book := HELP_BOOK_INFO(match[1]);
  entrynr := match[2];
  viewer:= UserPreference("HelpViewers");
  if HELP_LAST.NEXT_VIEWER = false then
    hv := viewer;
  else
    pos := Position( viewer, HELP_LAST.VIEWER );
    if pos = fail then
      hv := viewer;
    else
      hv := viewer{Concatenation([pos+1..Length(viewer)],[1..pos])};
    fi;
    HELP_LAST.NEXT_VIEWER := false;
  fi;
  for viewer in hv do
    # type of data we need now depends on help viewer 
    type := HELP_VIEWER_INFO.(viewer).type;
    # get the data via appropriate handler
    data := HELP_BOOK_HANDLER.(book.handler).HelpData(book, entrynr, type);
    if data <> fail then
      # show the data
      HELP_VIEWER_INFO.(viewer).show(data);
      break;
    fi;
    HELP_LAST.VIEWER := viewer;
  od;
  HELP_LAST.BOOK := book;
  HELP_LAST.MATCH := entrynr;
  HELP_LAST.VIEWER := viewer;
  return true;
end);

#############################################################################
##  
#F  HELP_SHOW_PREV_CHAPTER( <book> ) . . . . . . . . show chapter introduction
##  
InstallGlobalFunction(HELP_SHOW_PREV_CHAPTER, function( arg )
  local   info,  match;
  if HELP_LAST.BOOK = 0 then
    Print("Help: no history so far.\n");
    return;
  fi;
  info := HELP_BOOK_INFO(HELP_LAST.BOOK);
  match := HELP_BOOK_HANDLER.(info.handler).MatchPrevChap(info, 
                   HELP_LAST.MATCH);
  if match[2] = fail then
    Print("Help:  no match found.\n");
  else
    HELP_PRINT_MATCH(match);
    HELP_LAST.MATCH := match[2];
    return true;
  fi;
end);

#############################################################################
##
#F  HELP_SHOW_NEXT_CHAPTER( <book> )  . . . . . . . . . . . show next chapter
##
InstallGlobalFunction(HELP_SHOW_NEXT_CHAPTER, function( arg )
  local   info,  match;
  if HELP_LAST.BOOK = 0 then
    Print("Help: no history so far.\n");
    return;
  fi;
  info := HELP_BOOK_INFO(HELP_LAST.BOOK);
  match := HELP_BOOK_HANDLER.(info.handler).MatchNextChap(info, 
                   HELP_LAST.MATCH);
  if match[2] = fail then
    Print("Help:  no match found.\n");
  else
    HELP_PRINT_MATCH(match);
    HELP_LAST.MATCH := match[2];
    return true;
  fi;
end);

#############################################################################
##
#F  HELP_SHOW_PREV( <book> )  . . . . . . . . . . . . . show previous section
##
InstallGlobalFunction(HELP_SHOW_PREV, function( arg )
  local   info,  match;
  if HELP_LAST.BOOK = 0 then
    Print("Help: no history so far.\n");
    return;
  fi;
  info := HELP_BOOK_INFO(HELP_LAST.BOOK);
  match := HELP_BOOK_HANDLER.(info.handler).MatchPrev(info, 
                   HELP_LAST.MATCH);
  if match[2] = fail then
    Print("Help:  no match found.\n");
  else
    HELP_PRINT_MATCH(match);
    HELP_LAST.MATCH := match[2];
    return true;
  fi;
end);

#############################################################################
##
#F  HELP_SHOW_NEXT( <book> )  . . . . . . . . . . . . . . . show next section
##
InstallGlobalFunction(HELP_SHOW_NEXT, function( arg )
  local   info,  match;
  if HELP_LAST.BOOK = 0 then
    Print("Help: no history so far.\n");
    return;
  fi;
  info := HELP_BOOK_INFO(HELP_LAST.BOOK);
  match := HELP_BOOK_HANDLER.(info.handler).MatchNext(info, 
                   HELP_LAST.MATCH);
  if match[2] = fail then
    Print("Help:  no match found.\n");
  else
    HELP_PRINT_MATCH(match);
    HELP_LAST.MATCH := match[2];
    return true;
  fi;
end);

#############################################################################
##
#F  HELP_SHOW_WELCOME( <book> ) . . . . . . . . . . . .  show welcome message
##
InstallGlobalFunction(HELP_SHOW_WELCOME, function( book )
    local   lines;

    lines := [
"    Welcome to GAP 4\n",
" Try '?tutorial: The Help system' (without quotes) for an introduction to",
" the help system.\n",
" '?chapters' and '?sections' will display tables of contents."
    ];
    Pager(lines);
    return true;
end);


#############################################################################
##
#F  HELP_GET_MATCHES( <book>, <topic>, <frombegin> )  . . .  search through 
#F  the books
##  
##  This function returns a list of two lists [exact, match] and these lists
##  consist of  pairs [book,  entrynumber], where  book is  a help  book and
##  entrynumber is the number of a  match in book.entries. As the names say,
##  the  first list  "exact"  contains  the exact  matches  and "match"  the
##  remaining ones.
##
InstallGlobalFunction(HELP_GET_MATCHES, function( books, topic, frombegin )
  local exact, match, em, b, x, topics, transatl, pair, newtopic;

  # First we try to produce some suggestions for possible different spellings
  # (see the global variable 'TRANSATL' for the list of spelling patterns).
  if topic = "size" then # "size" is a notable exception (lowercase is guaranteed)
    topics:=[ topic ];
  else
    topics:=SuggestedSpellings( topic );
  fi;

  # <exact> and <match> contain the topics matching
  exact := [];
  match := [];

  if IsString(books) or IsRecord(books) then
    books := [books];
  fi;
  
  # collect the matches (by number)
  books := List(books, HELP_BOOK_INFO);
  for b in books do
    for topic in topics do
      # now delegate the work to the handler functions
      if b<>fail then
        em := HELP_BOOK_HANDLER.(b.handler).SearchMatches(b, topic, frombegin);
        for x in em[1] do
	      Add(exact, [b, x]);
        od;
        for x in em[2] do
	      Add(match, [b, x]);
        od;
      fi;
    od;
  od;
  
  # we now join the two lists, this way the exact matches are displayed
  # first in case of multiple matches
  # Note: before GAP 4.5 this was only done in case of substring search.
  match := Concatenation(exact, match);
  exact := [];

  return [exact, match];
end);

#############################################################################
##
#F  HELP_SHOW_MATCHES( <book>, <topic>, <frombegin> )  . . .  show list of
#F  matches or single match directly
##  
InstallGlobalFunction(HELP_SHOW_MATCHES, function( books, topic, frombegin )
  local   exact,  match,  x,  lines,  cnt,  i,  str,  n;

  # first get lists of exact and other matches
  x := HELP_GET_MATCHES( books, topic, frombegin );
  exact := x[1];
  match := x[2];
  
  # no topic found
  if 0 = Length(match) and 0 = Length(exact)  then
    Print( "Help: no matching entry found\n" );
    return false;
    
  # one exact or together one topic found
  elif 1 = Length(exact) or (0 = Length(exact) and 1 = Length(match)) then
    if Length(exact) = 0 then exact := match; fi;
    i := exact[1];
    str := Concatenation("Help: Showing `", i[1].bookname,": ", 
                                               i[1].entries[i[2]][1], "'\n");
    # to avoid line breaking when str contains escape sequences:
    n := 0;
    while n < Length(str) do
      Print(str{[n+1..Minimum(Length(str), 
                                    n + QuoInt(SizeScreen()[1] ,2))]}, "\c");
      n := n + QuoInt(SizeScreen()[1] ,2);
    od;
    HELP_PRINT_MATCH(i);
    return true;

  # more than one topic found, show overview in pager
  else
    lines := 
        ["Help: several entries match this topic - type ?2 to get match [2]\n"];
    HELP_LAST.TOPICS:=[];
    cnt := 0;
    # show exact matches first
    match := Concatenation(exact, match);
    for i  in match  do
      cnt := cnt+1;
      topic := Concatenation(i[1].bookname,": ",i[1].entries[i[2]][1]);
      Add(HELP_LAST.TOPICS, i);
      Add(lines,Concatenation("[",String(cnt),"] ",topic));
    od;
    Pager(rec(lines := lines, formatted := true));
    return true;
  fi;
end);

# choosing one of last shown  list of matches
InstallGlobalFunction(HELP_SHOW_FROM_LAST_TOPICS, function(nr)
  if nr = 0 or Length(HELP_LAST.TOPICS) < nr then
    Print("Help:  No such topic.\n");
    return false;
  fi;
  HELP_PRINT_MATCH(HELP_LAST.TOPICS[nr]);
  return true;
end);

##  A generic function for HELP_BOOK_HANDLER.(handler).HelpData(b, e, "ref")
##  This can be used to resolve cross references between books. The function 
##  returns a list r with six entries:
##  
##    - r[1]    search string including book name       "ref: Xyz"
##    - r[2]    (sub)section number as string           "5.14.2" or "3.1"
##    - r[3]    name of dvi-file (or fail)              "/doc/path/manual.dvi"
##    - r[4]    name of pdf-file (or fail)              "/doc/path/manual.pdf"
##    - r[5]    page number for r[3], r[4] (or fail)    37
##    - r[6]    URL (or fail)                           "/doc/htm/ch3.html#s4"
##    - r[7]    [chnr, secnr, subsecnr]                 [5,14,2] or [3,1,0]
##  
HELP_BOOK_HANDLER.HelpDataRef := function(book, entrynr)
  local    info,  handler,  entry,  chnr,  secnr,  pos,  res,  r;

  info := HELP_BOOK_INFO(book);
  handler := info.handler;
  entry := info.entries[entrynr];
 
  # the search and reference string
  res := [ Concatenation(info.bookname, ": ", entry[1]) ];
  # the section number string
  secnr := HELP_BOOK_HANDLER.(handler).HelpData(info, entrynr, "secnr");
  Add(res, secnr[2]);
  # dvi-file and page number
  r := HELP_BOOK_HANDLER.(handler).HelpData(info, entrynr, "dvi");
  if r = fail then
    Add(res, fail);
  else
    Add(res, r.file);
    res[5] := r.page;
  fi;
  # pdf-file and page number
  r := HELP_BOOK_HANDLER.(handler).HelpData(info, entrynr, "pdf");
  if r = fail then
    res[4] := fail;
  else
    res[4] := r.file;
    if not IsBound(res[5]) then
      res[5] := r.page;
    fi; 
  fi;
  if not IsBound(res[5]) then
    res[5] := fail;
  fi;      
  # URL
  r := HELP_BOOK_HANDLER.(handler).HelpData(info, entrynr, "url");
  if r = fail then
    Add(res, fail);
  else
    Add(res, r);
  fi;
  # [chnr, secnr, subsecnr] as list
  Add(res, secnr[1]);

  return res;
end;

##  From this info we generate a manual.lab file for a given book.
##  Note that the gapmacro format has subsection information in manual.lab
##  which is not available in manual.six! So, one cannot properly regenerate
##  a manual.lab file for those books.
InstallGlobalFunction(HELP_LAB_FILE, function(file, book)
  local fun, str, i;
  book := HELP_BOOK_INFO(book);
  fun := function(i)
    local r;
    r := HELP_BOOK_HANDLER.HelpDataRef(book, i);
    return Concatenation("\\makelabel {",
            SIMPLE_STRING(r[1]), "}{", r[2], "}\n");
  end;
  str := "";
  for i in [1..Length(book.entries)] do
    Append(str, fun(i));
  od;
  PrintTo(file, str);
end);


#############################################################################
##  
#F  HELP( <string> )  . . . . . . . . . . . . . . .  deal with a help request
##  
# here we store the last 16 requests 
HELP_RING_IDX :=  0;
HELP_RING_SIZE := 16;
InstallValue(HELP_BOOK_RING, ListWithIdenticalEntries( HELP_RING_SIZE, 
                                             ["tutorial"] ));
InstallValue(HELP_TOPIC_RING, ListWithIdenticalEntries( HELP_RING_SIZE, 
                                             "welcome to gap" ));
# here we store the last shown topic, initialized with 0 (leading to
# show "Tutorial: Help", see below)
InstallValue(HELP_LAST, rec(MATCH := 0, BOOK := 0, 
             NEXT_VIEWER := false, TOPICS := []));
NAMES_SYSTEM_GVARS:= "to be defined in init.g";

InstallGlobalFunction(HELP, function( str )
  local origstr, nwostr, p, book, books, move, add;

  origstr := ShallowCopy(str);
  nwostr := NormalizedWhitespace(origstr);
  
  # extract the book
  p := Position( str, ':' );
  if p <> fail  then
      book := str{[1..p-1]};
      str  := str{[p+1..Length(str)]};
  else
      book := "";
  fi;
  
  # normalizing for search
  book := SIMPLE_STRING(book);
  str := SIMPLE_STRING(str);
  
  # we check if `book' MATCH_BEGINs some of the available books
  books := Filtered(HELP_KNOWN_BOOKS[1], bn-> MATCH_BEGIN(bn, book));
  if Length(book) > 0 and Length(books) = 0 then
    Print("Help: None of the available books matches (try: '?books').\n");
    return;
  fi;
  
  # function to add a topic to the ring
  move := false;
  add  := function( books, topic )
      if not move  then
          HELP_RING_IDX := (HELP_RING_IDX+1) mod HELP_RING_SIZE;
          HELP_BOOK_RING[HELP_RING_IDX+1]  := books;
          HELP_TOPIC_RING[HELP_RING_IDX+1] := topic;
      fi;
  end;
  
  # if the topic is empty show the last shown one again 
  if  book = "" and str = ""  then
       if HELP_LAST.BOOK = 0 then
         HELP("Tutorial: Help");
       else
         HELP_PRINT_MATCH( [HELP_LAST.BOOK, HELP_LAST.MATCH] );
       fi;
       return;

  # if topic is "&" show last topic again, but with next viewer in viewer
  # list, or with last viewer again if there is no next one
  elif book = "" and str = "&" and Length(nwostr) = 1 then
       if HELP_LAST.BOOK = 0 then
         HELP("Tutorial: Help");
       else
         HELP_LAST.NEXT_VIEWER := true;
         HELP_PRINT_MATCH( [HELP_LAST.BOOK, HELP_LAST.MATCH] );
       fi;
       return;
  
  # if the topic is '-' we are interested in the previous search again
  elif book = "" and str = "-" and Length(nwostr) = 1  then
      HELP_RING_IDX := (HELP_RING_IDX-1) mod HELP_RING_SIZE;
      books := HELP_BOOK_RING[HELP_RING_IDX+1];
      str  := HELP_TOPIC_RING[HELP_RING_IDX+1];
      move := true;

  # if the topic is '+' we are interested in the last section again
  elif book = "" and str = "+" and Length(nwostr) = 1  then
      HELP_RING_IDX := (HELP_RING_IDX+1) mod HELP_RING_SIZE;
      books := HELP_BOOK_RING[HELP_RING_IDX+1];
      str  := HELP_TOPIC_RING[HELP_RING_IDX+1];
      move := true;
  fi;
  
  # number means topic from HELP_LAST.TOPICS list
  if book = "" and ForAll(str, a-> a in "0123456789") then
      HELP_SHOW_FROM_LAST_TOPICS(Int(str));
    
  # if the topic is '<' we are interested in the one before 'LastTopic'
  elif book = "" and str = "<" and Length(nwostr) = 1  then
      HELP_SHOW_PREV();

  # if the topic is '>' we are interested in the one after 'LastTopic'
  elif book = "" and str = ">" and Length(nwostr) = 1  then
      HELP_SHOW_NEXT();

  # if the topic is '<<' we are interested in the previous chapter intro
  elif book = "" and str = "<<"  then
      HELP_SHOW_PREV_CHAPTER();

  # if the topic is '>>' we are interested in the next chapter intro
  elif book = "" and str = ">>"  then
      HELP_SHOW_NEXT_CHAPTER();

  # if the subject is 'Welcome to GAP' display a welcome message
  elif book = "" and str = "welcome to gap"  then
      if HELP_SHOW_WELCOME(book)  then
          add( books, "Welcome to GAP" );
      fi;

  # if the topic is 'books' display the table of books
  elif book = "" and str = "books"  then
      if HELP_SHOW_BOOKS()  then
          add( books, "books" );
      fi;

  # if the topic is 'chapters' display the table of chapters
  elif str = "chapters"  or str = "contents" or book <> "" and str = "" then
      if ForAll(books, b->  HELP_SHOW_CHAPTERS(b)) then
        add( books, "chapters" );
      fi;

  # if the topic is 'sections' display the table of sections
  elif str = "sections"  then
      if ForAll(books, b-> HELP_SHOW_SECTIONS(b)) then
        add(books, "sections");
      fi;

  # if the topic is '?<string>' search the index for any entries for
  # which <string> is a substring (as opposed to an abbreviation)
  elif Length(str) > 0 and str[1] = '?'  then
      str := str{[2..Length(str)]};    
      NormalizeWhitespace(str);
      if HELP_SHOW_MATCHES( books, str, false)  then
          add( books, str );
      fi;

  # search for this topic
  elif HELP_SHOW_MATCHES( books, str, true )  then
      add( books, str );
  elif origstr in NAMES_SYSTEM_GVARS then
      Print( "Help: '", origstr, "' is currently undocumented.\n",
             "      For details, try ?Undocumented Variables\n" );
  elif book = "" and 
                 ForAny(HELP_KNOWN_BOOKS[1], bk -> MATCH_BEGIN(bk, str)) then
      Print( "Help: Are you looking for a certain book? (Trying '?", origstr, 
             ":' ...\n");
      HELP( Concatenation(origstr, ":") );
  else
     # seems unnecessary, since some message is already printed in all
     # cases above (?):
     # Print( "Help: Sorry, could not find a match for '", origstr, "'.\n");
  fi;
end);