GAP 4.8.9 installation with standard packages -- copy to your CoCalc project to get it
#T In BrowseGapDataAdd, no 'ret' argument is necessary if #T CALL_WITH_CATCH is used! ############################################################################# ## #W brutils.g GAP 4 package `Browse' Thomas Breuer ## #Y Copyright (C) 2007, Lehrstuhl D für Mathematik, RWTH Aachen, Germany ## ## This file contains some utilities for Browse applications. ## ############################################################################# ## #F BrowseData.SortAsIntegers( <a>, <b> ) ## ## Assume that the two strings <a>, <b> represent integers. ## This function compares these integers and not the strings themselves. ## ## `BrowseData.SortAsIntegers' is NOT recommended as the value of the ## `dynamic.sortFunctionsForColumns' entry for a column ## when it may happen that the column values are turned into category rows ## involving non-digit characters (typically a textual prefix). ## In these cases, the generalization given by ## `BrowseData.CompareAsNumbersAndNonnumbers' can be used instead. ## BrowseData.SortAsIntegers:= function( a, b ) local inta, intb; inta:= Int( a ); intb:= Int( b ); if IsInt( inta ) and IsInt( intb ) then return inta < intb; fi; return a < b; end; ############################################################################# ## #F BrowseData.ReplacedCharacterValueString( <val> ) #F BrowseData.CompareCharacterValues( <a>, <b> ) ## ## `BrowseData.CompareCharacterValues' is used to sort strings <a>, <b> ## representing the display values of character table entries. ## Strings involving the substring `" = "' (used in categories) ## are replaced by the expression to the right of this substring, ## strings representing integers are sorted by their values, ## `"."' is regarded as zero, ## other strings (denoting irrationalities) are larger than integers ## and sorted lexicographically. ## BrowseData.ReplacedCharacterValueString:= function( val ) local pos; pos:= PositionSublist( val, " = " ); if pos <> fail then val:= val{ [ pos+3 .. Length( val ) ] }; fi; if val = "." then return 0; else return Int( val ); fi; end; BrowseData.CompareCharacterValues:= function( a, b ) local v1, v2; v1:= BrowseData.ReplacedCharacterValueString( a ); v2:= BrowseData.ReplacedCharacterValueString( b ); if v1 <> fail then return v2 = fail or v1 < v2; elif v2 <> fail then return false; else return a < b; fi; end; ############################################################################# ## #F BrowseData.SplitStringIntoNumbersAndNonnumbers( <str> ) ## ## This function was used (by comparing its results for two strings) in an ## early version of `BrowseData.CompareAsNumbersAndNonnumbers'. ## Since this approach is very inefficient, it is not recommended. ## The function itself, however, may be interesting if one is interested in ## the structure of the string <str>. ## (One can view this function as an example of bad programming, ## so we leave it in the code already for this purpose.) ## ## Note, however, that in order to *sort a list of strings* w.r.t. the ## function `BrowseData.CompareAsNumbersAndNonnumbers', ## it may be more efficient to replace the given strings by the values ## under `BrowseData.SplitStringIntoNumbersAndNonnumbers', and to sort this ## list w.r.t. GAP's \< (using the kernel function) than to sort the given ## strings with second argument `BrowseData.CompareAsNumbersAndNonnumbers'. ## BrowseData.SplitStringIntoNumbersAndNonnumbers:= function( str ) local len, result, i, ii; len:= Length( str ); result:= []; i:= 1; while i <= len do ii:= i; while ii <= len and not str[ ii ] in DIGITS do ii:= ii + 1; od; Add( result, str{ [ i .. ii-1 ] } ); i:= ii; while ii <= len and str[ ii ] in DIGITS do ii:= ii + 1; od; Add( result, Int( str{ [ i .. ii-1 ] } ) ); i:= ii; od; return result; end; ############################################################################# ## #F BrowseData.CompareAsNumbersAndNonnumbers( <nam1>, <nam2> ) ## ## This is used for sorting a list of strings such that parts consisting of ## digits are compared w.r.t. their values as integers. ## ## Two typical applications are ## - sorting category rows which represent integer values ## but which have a prefix that is not an integer and ## - sorting a list of group names such that for example ## `"A9"' precedes `"A10"'. ## BrowseData.CompareAsNumbersAndNonnumbers:= function( nam1, nam2 ) local len1, len2, len, digit, comparenumber, i; # Essentially the code does the following, just more efficiently. # return BrowseData.SplitStringIntoNumbersAndNonnumbers( nam1 ) < # BrowseData.SplitStringIntoNumbersAndNonnumbers( nam2 ); len1:= Length( nam1 ); len2:= Length( nam2 ); len:= len1; if len2 < len then len:= len2; fi; digit:= false; comparenumber:= 0; for i in [ 1 .. len ] do if nam1[i] in DIGITS then if nam2[i] in DIGITS then digit:= true; if comparenumber = 0 then # first digit of a number, or previous digits were equal if nam1[i] < nam2[i] then comparenumber:= 1; elif nam1[i] <> nam2[i] then comparenumber:= -1; fi; fi; else # if digit then the current number in `nam2' is shorter, # so `nam2' is smaller; # if not digit then a number starts in `nam1' but not in `nam2', # so `nam1' is smaller return not digit; fi; elif nam2[i] in DIGITS then # if digit then the current number in `nam1' is shorter, # so `nam1' is smaller; # if not digit then a number starts in `nam2' but not in `nam1', # so `nam2' is smaller return digit; else # both characters are non-digits if digit then # first evaluate the current numbers (which have the same length) if comparenumber = 1 then # nam1 is smaller return true; elif comparenumber = -1 then # nam2 is smaller return false; fi; digit:= false; fi; # now compare the non-digits if nam1[i] <> nam2[i] then return nam1[i] < nam2[i]; fi; fi; od; if digit then # The suffix of the shorter string is a number. # If the longer string continues with a digit then it is larger, # otherwise the first digits of the number decide. if len < len1 and nam1[ len+1 ] in DIGITS then # nam2 is smaller return false; elif len < len2 and nam2[ len+1 ] in DIGITS then # nam1 is smaller return true; elif comparenumber = 1 then # nam1 is smaller return true; elif comparenumber = -1 then # nam2 is smaller return false; fi; fi; # Now the longer string is larger. return len1 < len2; end; ############################################################################# ## #F BrowseData.CompareLenLex( <val1>, <val2> ) #F BrowseData.CompareLenLexRev( <val1>, <val2> ) ## ## These functions can be useful for sorting nonnegative integer valued ## columns by the integer values, w.r.t. ascending and descending ordering, ## respectively. ## Note that a constant prefix (which may occur in category rows) ## need not be removed. ## BrowseData.CompareLenLex := function( val1, val2 ) if Length( val1 ) < Length( val2 ) then return true; elif Length( val2 ) < Length( val1 ) then return false; fi; return val1 < val2; end; BrowseData.CompareLenLexRev := function( val1, val2 ) if Length( val2 ) < Length( val1 ) then return true; elif Length( val1 ) < Length( val2 ) then return false; fi; return val2 < val1; end; ############################################################################# ## #F BrowseData.HeaderWithRowCounter( <t>, <header>, <nrallrows> ) ## ## This function shows the string <header> underlined, ## followed by the number of unhidden rows in the table <t> in brackets; ## note that the number of shown rows can be larger if some rows appear ## under several category rows. ## <nrallrows> is the number of rows in the (uncategorized) table; ## if the number of unhidden rows is smaller than this number then also ## <nrallrows> is shown in the header. ## BrowseData.HeaderWithRowCounter:= function( t, header, nrallrows ) local shown, indir, i; shown:= []; indir:= t.dynamic.indexRow; for i in [ 2, 4 .. Length( t.dynamic.isRejectedRow )-1 ] do if not t.dynamic.isRejectedRow[i] then Add( shown, indir[i] ); fi; od; # Calling `AddSet' in the loop is much slower. shown:= Set( shown ); if nrallrows = Length( shown ) then return [ "", [ NCurses.attrs.UNDERLINE, true, header, NCurses.attrs.NORMAL, " (", String( nrallrows ), " entries)" ], "" ]; else return [ "", [ NCurses.attrs.UNDERLINE, true, header, NCurses.attrs.NORMAL, " (", String( Length( shown ) ), " out of ", String( nrallrows ), " entries)" ], "" ]; fi; end; ############################################################################# ## #F BrowseData.ReallyFormatParagraph( <str>[, <len>][, <flush>][...] ) ## ## In addition to `FormatParagraph', ## break also too long words that do not contain whitespace. ## BrowseData.ReallyFormatParagraph:= function( arg ) local max, flush, result, line, linelen, rest, offset; if Length( arg ) = 1 or not IsPosInt( arg[2] ) then max:= 78; else max:= arg[2]; fi; if Length( arg ) >= 2 and IsString( arg[2] ) then flush:= arg[2]; elif Length( arg ) >= 3 and IsString( arg[3] ) then flush:= arg[3]; else flush:= "both"; fi; result:= []; for line in SplitString( CallFuncList( FormatParagraph, arg ), "\n" ) do linelen:= Length( line ); if linelen <= max then Add( result, line ); elif flush <> "left" then rest:= linelen mod max; offset:= 0; if rest <> 0 then Add( result, String( line{ [ 1 .. rest ] }, max ) ); offset:= rest; fi; repeat Add( result, line{ [ 1 .. max ] + offset } ); offset:= offset + max; until offset >= linelen; else offset:= 0; repeat Add( result, line{ [ 1 .. max ] + offset } ); offset:= offset + max; until offset + max >= linelen; if offset < linelen then Add( result, line{ [ 1 + offset .. linelen ] } ); fi; fi; od; return JoinStringsWithSeparator( result, "\n" ); end; ############################################################################# ## #F BrowseData.ReplacedEntry( <value>, <from>, <to> ) ## BrowseData.ReplacedEntry:= function( value, from, to ) local pos; pos:= Position( from, value ); if pos <> fail then value:= to[ pos ]; fi; return value; end; ############################################################################# ## #F BrowseData.SimplifiedString( <string> ) ## ## Replace non-ASCII unicode characters by approximations ## involving only ASCII characters. ## (This may become unnecessary in the future.) ## ## This function is used in `BrowseBibliography', `BrowseGapManuals', ## and `BrowseGapPackages', ## since non-ASCII characters may look strange in visual mode, ## and they may mess up line length computations in browse tables, ## BrowseData.SimplifiedString:= function( string ) local uni; uni:= Unicode( string ); if uni = fail then uni:= Unicode( string, "latin1" ); fi; return Encode( SimplifiedUnicodeString( uni, "ASCII" ), GAPInfo.TermEncoding ); end; ############################################################################# ## #F BrowseData.SimplifiedSubgroupName( <name> ) ## ## Apply some heuristics to the string <name>, and return a simplified name. ## BrowseData.SimplifiedSubgroupName:= function( name ) local pos, pos2, namereplacements, pair; # Replace `{<n>}' by <n> if <n> is an integer, and by `(<n>)' otherwise. pos:= Position( name, '{' ); while pos <> fail do pos2:= Position( name, '}', pos ); if Int( name{ [ pos+1 .. pos2-1 ] } ) <> fail then name:= Concatenation( name{ [ 1 .. pos-1 ] }, name{ [ pos+1 .. pos2-1 ] }, name{ [ pos2+1 .. Length( name ) ] } ); else name:= Concatenation( name{ [ 1 .. pos-1 ] }, "(", name{ [ pos+1 .. pos2-1 ] }, ")", name{ [ pos2+1 .. Length( name ) ] } ); fi; pos:= Position( name, '{' ); od; # Strip LaTeX markup from subgroup names. namereplacements:= [ [ "\\leq", "<" ], [ "\\times", "x" ], [ "\\rightarrow", "->" ], [ "^(\\prime)", "'" ], [ "^\\prime", "'" ], [ "$", "" ], [ "2_1", "2&uscore;1" ], [ "3_1", "3&uscore;1" ], [ "2_2", "2&uscore;2" ], [ "3_2", "3&uscore;2" ], [ "2_3", "2&uscore;3" ], [ "_+", "&uscore;+" ], [ "_-", "&uscore;-" ], [ "2^2", "2⁁2" ], [ "_", "" ], [ "^+", "+" ], [ "^-", "-" ], [ "^2E6", "2E6" ], [ "(^2F4(2)')", "2F4(2)'" ], [ "^2F4(2)'", "2F4(2)'" ], [ "(^3D4(2))", "3D4(2)" ], [ "F(3+)", "F3+" ], [ "&uscore;", "_" ], [ "⁁", "^" ], ]; for pair in namereplacements do name:= ReplacedString( name, pair[1], pair[2] ); od; return NormalizedWhitespace( name ); end; ############################################################################# ## #F BrowseData.StrippedPath( <filename> ) ## BrowseData.StrippedPath:= function( filename ) local pos; pos:= Position( filename, '/' ); while pos <> fail do filename:= filename{ [ pos+1 .. Length( filename ) ] }; pos:= Position( filename, '/' ); od; return filename; end; ############################################################################# ## #F BrowseData.SortKeyFunctionBibRec( <record> ) ## ## This function can be used as the value of the 'sortKeyFunction' component ## of a record that is the argument of a call to 'BrowseBibliography'. ## ## It is used this way by AtlasRep's 'BrowseBibliographySporadicSimple' and ## by MFER's 'BrowseBibliographyPermReprSporadicSimple'. ## ## The idea is to sort/identify bibliography entries by ## 1. the list of authors ## (case insensitive, using ASCII versions of the names as returned by ## 'BrowseData.SimplifiedString'), ## 2. the year, ## 3. if applicable the additional distinguishing letter in the label ## (such that [XY05a] precedes [XY05b]), ## 4. the title ## (case insensitive, after the normalization of whitespace). ## BrowseData.SortKeyFunctionBibRec:= function( r ) local key; key:= []; if IsBound( r.authorAsList ) then Add( key, List( r.authorAsList, a -> ( a[1] ) ) ); elif IsBound( r.editorAsList ) then Add( key, List( r.editorAsList, a -> ( a[1] ) ) ); else Add( key, [ "zzz" ] ); fi; key[1]:= List( key[1], x -> LowercaseString( BrowseData.SimplifiedString( x ) ) ); if IsBound( r.year ) then Add( key, r.year ); else Add( key, "" ); fi; if IsAlphaChar( r.Label[ Length( r.Label ) ] ) then Add( key, r.Label{ [ Length( r.Label ) ] } ); else Add( key, "" ); fi; if IsBound( r.title ) then Add( key, LowercaseString( NormalizedWhitespace( r.title ) ) ); else Add( key, "" ); fi; return key; end; ############################################################################# ## #F BrowseGapDataAdd( <title>, <call>, <ret>, <documentation> ) ## ## <#GAPDoc Label="BrowseGapDataAdd_man"> ## <ManSection> ## <Func Name="BrowseGapDataAdd" Arg="title, call, ret, documentation"/> ## ## <Description> ## This function extends the list <C>BrowseData.GapDataOverviews</C> ## by a new entry. ## The list is used by <Ref Func="BrowseGapData"/>. ## <P/> ## <A>title</A> must be a string of length at most <M>76</M>; it will be ## shown in the browse table that is opened by <Ref Func="BrowseGapData"/>. ## <A>call</A> must be a function that takes no arguments; it will be called ## when <A>title</A> is <Q>clicked</Q>. ## <A>ret</A> must be <K>true</K> if <A>call</A> has a return value ## and if <Ref Func="BrowseGapData"/> shall return this value, ## and <K>false</K> otherwise. ## <A>documentation</A> must be a string that describes what happens when ## the function <A>call</A> is called; it will be shown in the footer of ## the table opened by <Ref Func="BrowseGapData"/> ## when <A>title</A> is selected. ## </Description> ## </ManSection> ## <#/GAPDoc> ## BindGlobal( "BrowseGapDataAdd", function( title, call, ret, documentation ) local currpkg, pos; if not IsBound( BrowseData.GapDataOverviews ) then BrowseData.GapDataOverviews:= []; fi; if not ( IsString( title ) and Length( title ) <= 76 ) then Error( "<title> must be a string of length at most 76" ); elif not IsFunction( call ) then Error( "<call> must be a function" ); elif not ret in [ true, false ] then Error( "<ret> must be `true' or `false'" ); elif not IsString( documentation ) then Error( "<documentation> must be a string" ); fi; if IsBound( GAPInfo.PackageCurrent ) then currpkg:= GAPInfo.PackageCurrent.PackageName; if LowercaseString( currpkg ) <> "browse" then title:= Concatenation( title, " (", currpkg, ")" ); fi; fi; pos:= PositionProperty( BrowseData.GapDataOverviews, x -> x[1] = title ); if pos = fail then AddSet( BrowseData.GapDataOverviews, [ title, call, ret, documentation ] ); elif REREADING then BrowseData.GapDataOverviews[ pos ]:= [ title, call, ret, documentation ]; else Error( "an entry with title <title> is already stored" ); fi; end ); ############################################################################# ## #E