From 48c4b046831ad37439ab901689d24d47a7bea29b Mon Sep 17 00:00:00 2001 From: Wilf Wilson Date: Fri, 11 Jun 2021 01:52:24 +0100 Subject: [PATCH] Introduce FreeXArgumentProcessor --- lib/magma.gd | 5 ++ lib/mgmfree.gi | 193 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+) diff --git a/lib/magma.gd b/lib/magma.gd index 8a48866c894..7ce62d9ae37 100644 --- a/lib/magma.gd +++ b/lib/magma.gd @@ -763,6 +763,11 @@ InParentFOA( "Centralizer", IsMagma, IsObject, DeclareAttribute ); DeclareOperation( "SquareRoots", [ IsMagma, IsMultiplicativeElement ] ); +################################################################################ +## +DeclareGlobalFunction("FreeXArgumentProcessor"); + + ############################################################################# ## #F FreeMagma( [, ] ) diff --git a/lib/mgmfree.gi b/lib/mgmfree.gi index 15e91b86ac1..471481eb15e 100644 --- a/lib/mgmfree.gi +++ b/lib/mgmfree.gi @@ -273,6 +273,199 @@ InstallMethod( MagmaGeneratorsOfFamily, F -> List( [ 1 .. Length( F!.names ) ], i -> ObjByExtRep( F, i ) ) ); +############################################################################# +## Currently used by: +## FreeGroup, FreeMonoid, FreeSemigroup, FreeMagmaWithOne, FreeMagma +InstallGlobalFunction( FreeXArgumentProcessor, +function( + func, # The name of the calling function + name, # The default prefix to use for generator names + arg, # The list of args passed to ; to be processed + optional_first_arg, # Whether allows filter as optional 1st arg + allow_rank_zero # Whether support rank-zero objects +) + local wfilt, # a filter for letter or syllable words family + err, # string; helpful error message + form, # string: surmised argument form of the call to + names, # list of generators names + rank, # Length( names ) + opt, + init, + word, + x, y1, y2; + + # Set up defaults. + wfilt := IsLetterWordsFamily; + err := ""; + form := fail; + names := fail; + + # Legacy documented feature to give filter for words family with an option. + if func = "FreeGroup" and ValueOption( "FreeGroupFamilyType" ) = "syllable" then + wfilt := IsSyllableWordsFamily; # optional -- used in PQ. + fi; + + # The usual way is to give filter in the optional first argument. + if not IsEmpty( arg ) and IsFilter( arg[1] ) then + if not optional_first_arg then + ErrorNoReturn( "the first argument must not be a filter" ); + fi; + wfilt := arg[1]; + Remove( arg, 1 ); + fi; + + # Process and validate the argument list, constructing names as necessary. + + if Length( arg ) = 0 or arg[1] = 0 + or ( Length( arg ) = 1 and IsList( arg[1] ) and IsEmpty( arg[1] ) ) then + + # We recognise this function call as a request for a zero-generator object. + if allow_rank_zero then + names := []; + else + Info( InfoWarning, 1, func, " cannot make an object with no generators" ); + fi; + + # Validate call of form: func( [, ] ). + elif Length( arg ) <= 2 and IsPosInt( arg[1] ) then + + rank := arg[1]; + + # Documented feature which allows generators to be given custom names in + # objects created by constructors like DihedralGroup or AbelianGroup. + opt := ValueOption( "generatorNames" ); # Note, may be overwritten by arg[2]. + if Length( arg ) = 1 and opt <> fail then + if IsString( opt ) and not IsEmpty( opt ) then + names := List( [ 1 .. rank ], i -> Concatenation( opt, String( i ) ) ); + MakeImmutable( names ); + elif ( IsList and IsFinite )( opt ) and rank <= Length( opt ) + and ForAll( [ 1 .. rank ], + s -> IsString( opt[s] ) and not IsEmpty( opt[s] ) ) then + names := MakeImmutable( opt{[ 1 .. rank ]} ); + else + ErrorNoReturn( Concatenation( + "Cannot process the `generatorNames` option: ", + "the value must be either a single nonempty string, or a list ", + "of sufficiently many nonempty strings ", + "(at least ", String( rank ), " strings, in this case)" ) ); + fi; + + else + if Length( arg ) = 2 then + name := arg[2]; + fi; + if not IsString( name ) then + form := ", "; + err := " must be a string"; + else + names := List( [ 1 .. rank ], i -> Concatenation( name, String( i ) ) ); + MakeImmutable( names ); + fi; + fi; + + # Validate call of form: func( [, , ...] ), or a list of such. + elif ForAll( arg, IsString ) or Length( arg ) = 1 and IsList( arg[1] ) then + + if Length( arg ) = 1 and not IsString( arg[1] ) then + form := "[, , ...]"; + names := arg[1]; + else + form := ", , ..."; + names := arg; + fi; + + # Error checking + if not IsFinite( names ) then + err := "there must be only finitely many names"; + elif not ForAll( names, s -> IsString( s ) and not IsEmpty( s ) ) then + err := "the names must be nonempty strings"; + fi; + + # Validate call of form: func( infinity[, ][, ] ). + elif Length( arg ) <= 3 and arg[1] = infinity then + + init := []; + if Length( arg ) = 3 then + form := "infinity, , "; + name := arg[2]; + init := arg[3]; + elif Length( arg ) = 2 then + if IsList( arg[2] ) and IsFinite( arg[2] ) and not IsEmpty( arg[2] ) + and ForAll( arg[2], IsString ) then + form := "infinity, "; + init := arg[2]; + else + form := "infinity, "; + name := arg[2]; + fi; + fi; + + # Error checking + if not IsString( name ) then + err := " must be a string"; + elif not ( IsList( init ) and IsFinite( init ) ) then + err := " must be a finite list"; + elif not ForAll( init, s -> IsString( s ) and not IsEmpty( s ) ) then + err := " must consist of nonempty strings"; + fi; + if IsEmpty( err ) then + names := InfiniteListOfNames( name, init ); + fi; + fi; + + # Call to was recognised as having a particular form, but was invalid. + if not IsEmpty( err ) then + ErrorNoReturn( StringFormatted( "{}( {} ): {}", func, form, err ) ); + fi; + + # Unrecognised call to . + if names = fail then + + # Adapt the error message slightly depending on whether the optional + # first argument is supported, and whether at least one argument must be + # given (i.e. whether objects of rank zero are supported). + x := ""; y1 := ""; y2 := ""; + if optional_first_arg then + x := "[, ]"; + fi; + if allow_rank_zero then + y1 := "["; y2 := "]"; + fi; + + ErrorNoReturn( StringFormatted( Concatenation( + #"Error, + "usage: {}( {}[, ] )\n", + " {}( {}{}[, [, ...]]{} )\n", + " {}( {} )\n", + " {}( {}infinity[, ][, ] )" + ), func, x, func, x, y1, y2, func, x, func, x ) ); + fi; + + # Process words family options now that we know how many generators there are. + if optional_first_arg then + if not wfilt in [ IsSyllableWordsFamily, IsLetterWordsFamily, + IsWLetterWordsFamily, IsBLetterWordsFamily ] then + ErrorNoReturn( Concatenation( + "the optional first argument must be one of ", + "IsSyllableWordsFamily, IsLetterWordsFamily, IsWLetterWordsFamily, ", + "and IsBLetterWordsFamily" ) ); + fi; + if wfilt <> IsSyllableWordsFamily then + if Length( names ) > 127 then + wfilt := IsWLetterWordsFamily; + elif wfilt = IsLetterWordsFamily then + wfilt := IsBLetterWordsFamily; + fi; + fi; + fi; + + return rec( + names := names, + lesy := wfilt, + ); +end ); + + ############################################################################# ## #F FreeMagma( )