From 0f122d937b47efcb04db03878025145e3fbc04f7 Mon Sep 17 00:00:00 2001 From: Wilf Wilson Date: Thu, 8 Apr 2021 09:55:03 +0100 Subject: [PATCH] Check arguments to `FreeSemigroup` more thoroughly See https://github.com/gap-system/gap/issues/1385 It was previously possible to use `FreeSemigroup` to create a seemingly free semigroup with zero generators (rank zero), but this object believed itself to be infinite, and to contain elements (such a semigroup would be empty). Note that free semigroups of rank zero are not documented as being supported. Some other ways that one might want to create free semigroups of rank zero led to unhelpful error messages, and in general, the argument checking of `FreeSemigroup` was not very thorough and gave sometimes unhelpful error messages. In this commit, I overhaul the way that `FreeSemigroup` checks its arguments, in particular always disallowing a free semigroup of rank zero, and overall making the checking more robust, and making the error messages more descriptive. This addresses the _bugs_ reported in issue #1385, although it does not address the _feature request_ in that issue to support free semigroups of rank zero. I also took the opportunity to fix a typo in the documentation for `FreeSemigroup`, and to make a clarification. Further improvements could be made to this documentation, but I leave that to the future. --- lib/semigrp.gd | 16 +-- lib/smgrpfre.gi | 122 +++++++++++++---- .../2021-04-08-empty-FreeSemigroup.tst | 11 ++ tst/testinstall/smgrpfre.tst | 127 ++++++++++++++++++ 4 files changed, 239 insertions(+), 37 deletions(-) create mode 100644 tst/testbugfix/2021-04-08-empty-FreeSemigroup.tst create mode 100644 tst/testinstall/smgrpfre.tst diff --git a/lib/semigrp.gd b/lib/semigrp.gd index 38eb26a299..9cf4c9df75 100644 --- a/lib/semigrp.gd +++ b/lib/semigrp.gd @@ -277,11 +277,10 @@ DeclareAttribute("CayleyGraphDualSemigroup",IsSemigroup); ############################################################################# ## -#F FreeSemigroup( [,] ) -#F FreeSemigroup( [,], ) -#F FreeSemigroup( [,], , ... ) -#F FreeSemigroup( [,] ) -#F FreeSemigroup( [,]infinity, , ) +#F FreeSemigroup( [, ][, ] ) +#F FreeSemigroup( [, ][, , ...] ) +#F FreeSemigroup( [, ] ) +#F FreeSemigroup( [, ]infinity[, [, ]] ) ## ## <#GAPDoc Label="FreeSemigroup"> ## @@ -292,17 +291,18 @@ DeclareAttribute("CayleyGraphDualSemigroup",IsSemigroup); ## Label="for various names"/> ## -## ## ## ## Called with a positive integer rank, ## returns ## a free semigroup on rank generators. -## If the optional argument name is given then the generators are +## If the optional argument name (a string) is given, +## then the generators are ## printed as name1, name2 etc., ## that is, each name is the concatenation of the string name and an -## integer from 1 to range. +## integer from 1 to rank. ## The default for name is the string "s". ##

## Called in the second form, diff --git a/lib/smgrpfre.gi b/lib/smgrpfre.gi index 05de0ea24a..67369260d7 100644 --- a/lib/smgrpfre.gi +++ b/lib/smgrpfre.gi @@ -377,50 +377,113 @@ InstallMethod( GeneratorsSmallest, ############################################################################# ## -#F FreeSemigroup( ) -#F FreeSemigroup( , ) -#F FreeSemigroup( , , ... ) -#F FreeSemigroup( ) -#F FreeSemigroup( infinity, , ) +#F FreeSemigroup( [, ][, ] ) +#F FreeSemigroup( [, ][, , ...] ) +#F FreeSemigroup( [, ] ) +#F FreeSemigroup( [, ]infinity[, [, ]] ) ## InstallGlobalFunction( FreeSemigroup, function( arg ) local names, # list of generators names F, # family of free semigroup element objects zarg, lesy, # filter for letter or syllable words family - S; # free semigroup, result + S, # free semigroup, result + err, # string; helpful error message + form, # string: assumed argument form of call to FreeSemigroup + rank, + name, + init; lesy:=IsLetterWordsFamily; # default: - if IsFilter(arg[1]) then + if not IsEmpty(arg) and IsFilter(arg[1]) then lesy:=arg[1]; zarg:=arg{[2..Length(arg)]}; else zarg:=arg; fi; - # Get and check the argument list, and construct names if necessary. - if Length( zarg ) = 1 and zarg[1] = infinity then - names:= InfiniteListOfNames( "s" ); - elif Length( zarg ) = 2 and zarg[1] = infinity then - names:= InfiniteListOfNames( zarg[2] ); - elif Length( zarg ) = 3 and zarg[1] = infinity then - names:= InfiniteListOfNames( zarg[2], zarg[3] ); - elif Length( zarg ) = 1 and IsInt( zarg[1] ) and 0 < zarg[1] then - names:= List( [ 1 .. zarg[1] ], - i -> Concatenation( "s", String(i) ) ); - MakeImmutable( names ); - elif Length( zarg ) = 2 and IsInt( zarg[1] ) and 0 < zarg[1] then - names:= List( [ 1 .. zarg[1] ], - i -> Concatenation( zarg[2], String(i) ) ); - MakeImmutable( names ); - elif 1 <= Length( zarg ) and ForAll( zarg, IsString ) then - names:= zarg; - elif Length( zarg ) = 1 and IsList( zarg[1] ) - and not IsEmpty( zarg[1] ) - and ForAll( zarg[1], IsString ) then - names:= zarg[1]; + # Process and validate the argument list, constructing names if necessary + err := ""; + + if Length( zarg ) = 0 or zarg[1] = 0 + or (Length(zarg) = 1 and IsList( zarg[1] ) and IsEmpty( zarg[1] )) then + Error("free semigroups of rank zero are not supported"); + + # FreeSemigroup( [, ] ) + elif IsPosInt( zarg[1] ) and Length( zarg ) <= 2 then + + # Get default and optional arguments + rank := zarg[1]; + if Length( zarg ) = 1 then + name := "s"; + else + name := zarg[2]; + fi; + + # Error checking + if not IsString( name ) then + form := ", "; + err := " must be a string"; + + # Construct names + else + names:= List( [ 1 .. rank ], i -> Concatenation( name, String(i) ) ); + MakeImmutable( names ); + fi; + + # FreeSemigroup( [, , ...] ), or a list of such arguments + elif ForAll( zarg, IsString ) or Length( zarg ) = 1 and IsList( zarg[1] ) then + if Length( zarg ) = 1 and not IsString( zarg[1] ) then + form := "[ , , ... ]"; + names:= zarg[1]; + else + form := ", , ..."; + names:= zarg; + fi; + if not ForAll( names, s -> IsString(s) and not IsEmpty(s) ) then + err := "the names must be nonempty strings"; + fi; + + # FreeSemigroup( infinity[, [, ]] ) + elif zarg[1] = infinity and Length( zarg ) <= 3 then + + # Get default and optional arguments + name := "s"; + init := []; + if Length( zarg ) = 3 then + form := "infinity, , "; + name := zarg[2]; + init := zarg[3]; + elif Length( zarg ) = 2 then + form := "infinity, "; + name := zarg[2]; + fi; + + # Error checking + if not IsString( name ) then + err := " must be a string"; + fi; + if not ( IsList( init ) and ForAll( init, s -> IsString(s) and not IsEmpty(s) ) ) then + if not IsEmpty(err) then + Append(err, " and "); + fi; + Append(err, " must be a list of nonempty strings"); + fi; + + # Construct names + if IsEmpty(err) then + names:= InfiniteListOfNames( name, init ); + fi; + else - Error("usage: FreeSemigroup(,..),FreeSemigroup()"); + ErrorNoReturn("""usage: FreeSemigroup( [, ][, ] ) + FreeSemigroup( [, ][, , ...] ) + FreeSemigroup( [, ] ) + FreeSemigroup( [, ]infinity[, [, ]] )"""); + fi; + + if not IsEmpty(err) then + ErrorNoReturn(StringFormatted("FreeSemigroup( {} ): {}", form, err)); fi; # deal with letter words family types @@ -457,6 +520,7 @@ InstallGlobalFunction( FreeSemigroup, function( arg ) SetIsFreeSemigroup(S,true); SetIsWholeFamily( S, true ); SetIsTrivial( S, false ); + SetIsCommutative( S, Length(names) = 1 ); return S; end ); diff --git a/tst/testbugfix/2021-04-08-empty-FreeSemigroup.tst b/tst/testbugfix/2021-04-08-empty-FreeSemigroup.tst new file mode 100644 index 0000000000..50822d1839 --- /dev/null +++ b/tst/testbugfix/2021-04-08-empty-FreeSemigroup.tst @@ -0,0 +1,11 @@ +# See https://github.com/gap-system/gap/issues/1385 +gap> FreeSemigroup(); +Error, free semigroups of rank zero are not supported +gap> FreeSemigroup([]); +Error, free semigroups of rank zero are not supported +gap> FreeSemigroup(""); +Error, free semigroups of rank zero are not supported +gap> FreeSemigroup(0); +Error, free semigroups of rank zero are not supported +gap> FreeSemigroup(0, "name"); +Error, free semigroups of rank zero are not supported diff --git a/tst/testinstall/smgrpfre.tst b/tst/testinstall/smgrpfre.tst new file mode 100644 index 0000000000..668e2eaf97 --- /dev/null +++ b/tst/testinstall/smgrpfre.tst @@ -0,0 +1,127 @@ +#@local F +gap> START_TEST("smgrpfre.tst"); + +# FreeSemigroup +gap> FreeSemigroup(fail); +Error, usage: FreeSemigroup( [, ][, ] ) + FreeSemigroup( [, ][, , ...] ) + FreeSemigroup( [, ] ) + FreeSemigroup( [, ]infinity[, [, ]] ) + +# FreeSemigroup: rank 0 +gap> FreeSemigroup(); +Error, free semigroups of rank zero are not supported +gap> FreeSemigroup([]); +Error, free semigroups of rank zero are not supported +gap> FreeSemigroup(""); +Error, free semigroups of rank zero are not supported +gap> FreeSemigroup(0); +Error, free semigroups of rank zero are not supported +gap> FreeSemigroup(0, "name"); +Error, free semigroups of rank zero are not supported + +# FreeSemigroup(infinity[, name[, init]]) +gap> FreeSemigroup(infinity); + +gap> FreeSemigroup(infinity, fail); +Error, FreeSemigroup( infinity, ): must be a string +gap> FreeSemigroup(infinity, []); + +gap> FreeSemigroup(infinity, ""); + +gap> FreeSemigroup(infinity, "nicename"); + +gap> FreeSemigroup(infinity, fail, fail); +Error, FreeSemigroup( infinity, , ): must be a string and \ + must be a list of nonempty strings +gap> FreeSemigroup(infinity, "nicename", fail); +Error, FreeSemigroup( infinity, , ): must be a list of non\ +empty strings +gap> FreeSemigroup(infinity, "gen", []); + +gap> FreeSemigroup(infinity, "gen", [""]); +Error, FreeSemigroup( infinity, , ): must be a list of non\ +empty strings +gap> FreeSemigroup(infinity, "gen", ["starter"]); + +gap> FreeSemigroup(infinity, "gen", ["starter", ""]); +Error, FreeSemigroup( infinity, , ): must be a list of non\ +empty strings +gap> F := FreeSemigroup(infinity, "gen", ["starter", "second", "third"]); + +gap> GeneratorsOfSemigroup(F){[1 .. 4]}; +[ starter, second, third, gen4 ] +gap> FreeSemigroup(infinity, "gen", ["starter"], fail); +Error, usage: FreeSemigroup( [, ][, ] ) + FreeSemigroup( [, ][, , ...] ) + FreeSemigroup( [, ] ) + FreeSemigroup( [, ]infinity[, [, ]] ) + +# FreeSemigroup(rank[, name]) +gap> F := FreeSemigroup(1); + +gap> HasIsCommutative(F) and IsCommutative(F); +true +gap> F := FreeSemigroup(2); + +gap> HasIsCommutative(F) and not IsCommutative(F); +true +gap> F := FreeSemigroup(10); + +gap> F := FreeSemigroup(3, fail); +Error, FreeSemigroup( , ): must be a string +gap> F := FreeSemigroup(4, ""); + +gap> F := FreeSemigroup(5, []); + +gap> F := FreeSemigroup(4, "cheese"); + +gap> FreeSemigroup(3, "car", fail); +Error, usage: FreeSemigroup( [, ][, ] ) + FreeSemigroup( [, ][, , ...] ) + FreeSemigroup( [, ] ) + FreeSemigroup( [, ]infinity[, [, ]] ) + +# FreeSemigroup( [, , ...] ) +gap> FreeSemigroup("", "second"); +Error, FreeSemigroup( , , ... ): the names must be nonempty stri\ +ngs +gap> FreeSemigroup("first", ""); +Error, FreeSemigroup( , , ... ): the names must be nonempty stri\ +ngs +gap> FreeSemigroup("first", []); +Error, FreeSemigroup( , , ... ): the names must be nonempty stri\ +ngs +gap> FreeSemigroup([], []); +Error, FreeSemigroup( , , ... ): the names must be nonempty stri\ +ngs +gap> FreeSemigroup("bacon", "eggs", "beans"); + +gap> FreeSemigroup("shed"); + + +# FreeSemigroup( [ [, , ...] ] ) +gap> FreeSemigroup(["", "second"]); +Error, FreeSemigroup( [ , , ... ] ): the names must be nonempty \ +strings +gap> FreeSemigroup(["first", ""]); +Error, FreeSemigroup( [ , , ... ] ): the names must be nonempty \ +strings +gap> FreeSemigroup(["first", []]); +Error, FreeSemigroup( [ , , ... ] ): the names must be nonempty \ +strings +gap> FreeSemigroup([[], []]); +Error, FreeSemigroup( [ , , ... ] ): the names must be nonempty \ +strings +gap> FreeSemigroup(["bacon", "eggs", "beans"]); + +gap> FreeSemigroup(["grid"]); + +gap> FreeSemigroup(["grid"], fail); +Error, usage: FreeSemigroup( [, ][, ] ) + FreeSemigroup( [, ][, , ...] ) + FreeSemigroup( [, ] ) + FreeSemigroup( [, ]infinity[, [, ]] ) + +# +gap> STOP_TEST( "smgrpfre.tst", 1);