############################################################################# ## ## This file is part of GAP, a system for computational discrete algebra. ## This file's authors include Frank Celler. ## ## Copyright of GAP belongs to its developers, whose names are too numerous ## to list here. Please refer to the COPYRIGHT file for details. ## ## SPDX-License-Identifier: GPL-2.0-or-later ## ## This file contains functions for strings. ##
InstallGlobalFunction(IsLowerAlphaChar,x->x in CHARS_LALPHA);
############################################################################# ## #F IsAlphaChar(<c>) ##
InstallGlobalFunction(IsAlphaChar,x->x in CHARS_ALPHA);
############################################################################# ## #F DaysInYear( <year> ) . . . . . . . . . days in a year, knows leap-years ##
InstallGlobalFunction(DaysInYear , function ( year ) if year mod 4 in [1,2,3] or year mod 400 in [100,200,300] then
return 365; else
return 366; fi; end);
############################################################################# ## #F DaysInMonth( <month>, <year> ) . . . . days in a month, knows leap-years ##
InstallGlobalFunction(DaysInMonth , function ( month, year ) if month in [ 1, 3, 5, 7, 8, 10, 12 ] then
return 31; elif month in [ 4, 6, 9, 11 ] then
return 30; elif month = 2 and
(year mod 4 in [1,2,3] or year mod 400 in [100,200,300]) then
return 28; elif month = 2then
return 29; else
return fail; fi; end);
############################################################################# ## #F DMYDay( <day> ) . . . convert days since 01-Jan-1970 into day-month-year ##
InstallGlobalFunction(DMYDay , function ( day )
local year, month;
year := 1970; while DaysInYear(year) <= day do
day := day - DaysInYear(year);
year := year + 1;
od; while day < 0do
year := year - 1;
day := day + DaysInYear(year);
od;
month := 1; while DaysInMonth(month,year) <= day do
day := day - DaysInMonth(month,year);
month := month + 1;
od;
return [ day+1, month, year ]; end);
############################################################################# ## #F DayDMY( <dmy> ) . . . convert day-month-year into days since 01-Jan-1970 ##
InstallGlobalFunction(DayDMY , function ( dmy )
local year, month, day;
day := dmy[1]-1;
month := dmy[2];
year := dmy[3]; if DaysInMonth(month, year) = fail or day < 0 or
day > DaysInMonth(month, year) - 1then
return fail; fi; while1 < month do
month := month - 1;
day := day + DaysInMonth( month, year );
od; while1970 < year do
year := year - 1;
day := day + DaysInYear( year );
od; while year < 1970do
day := day - DaysInYear( year );
year := year + 1;
od;
return day; end);
############################################################################# ## #F WeekDay( <date> ) . . . . . . . . . . . . . . . . . . . weekday of a date ##
InstallGlobalFunction(WeekDay , function ( date ) if IsList( date ) then date := DayDMY( date ); fi;
return NameWeekDay[ (date + 3) mod 7 + 1 ]; end);
############################################################################# ## #F SecondsDMYhms( <DMYhms> ) . . . . . . . . . seconds since 1/1/1970/0/0/0 ##
InstallGlobalFunction(SecondsDMYhms, function(DMYhms)
local d, res, s;
d := DayDMY(DMYhms{[1..3]}); if d = fail then
return fail; fi;
res := d * 24 * 60^2;
s := DMYhms{[4..6]}; if not (s[1] in [0..23] and s[2] in [0..59] and s[3] in [0..59]) then
return fail; fi;
Add(s, 0);
return res + SecHMSM(s) / 1000; end);
############################################################################# ## #F StringDate( <date> ) . . . . . . . . convert date into a readable string ##
InstallGlobalFunction(StringDate , function ( date ) if IsInt( date ) then date := DMYDay( date ); fi;
return Concatenation(
String(date[1],2), "-",
NameMonth[date[2]], "-",
String(date[3],4) ); end);
############################################################################# ## #F HMSMSec( <sec> ) . . . . . . convert milliseconds into hour-min-sec-mill ##
InstallGlobalFunction(HMSMSec , function ( sec )
local hour, minute, second, milli;
hour := QuoInt( sec, 3600000 );
minute := QuoInt( sec, 60000 ) mod 60;
second := QuoInt( sec, 1000 ) mod 60;
milli := sec mod 1000;
return [ hour, minute, second, milli ]; end);
# hand special cases (in particular 0, 1, -1) if n in [-3..3] then
return String( n ); fi;
if n < 0then
n := -n;
str := "-"; else
str := ""; fi;
facs := Collected( Factors(Integers, n ) ); for i in [ 1 .. Length( facs ) ] do if i > 1then Append( str, "*" ); fi;
Append( str, String( facs[ i ][ 1 ] ) ); if facs[ i ][ 2 ] > 1then
Append( str, "^" );
Append( str, String( facs[ i ][ 2 ] ) ); fi;
od;
return str; end);
############################################################################ ## #F WordAlp( <alpha>, <nr> ) . . . . . . <nr>-th word over alphabet <alpha> ## ## returns a string that is the <nr>-th word over the alphabet <alpha>, ## w.r. to word length and lexicographical order. The empty word is ## 'WordAlp( <alpha>, 0 )'. ##
InstallGlobalFunction(WordAlp , function( alpha, nr )
local lalpha, # length of the alphabet
word, # the result
nrmod; # position of letter
lalpha:= Length( alpha );
word:= ""; while nr <> 0do
nrmod:= nr mod lalpha; if nrmod = 0then nrmod:= lalpha; fi;
Add( word, alpha[ nrmod ] );
nr:= ( nr - nrmod ) / lalpha;
od;
return Reversed( word ); end);
z := 0;
m := 1;
p := 1;
d := false; for i in [ 1 .. Length(string) ] do if i = p and string[i] = '-'then
m := -1; if Length(string) = 1then
return fail; fi; elif string[i] = '/' and IsBound(n) then
return fail; elif string[i] = '/' and not IsBound(n) then if IsRat(d) then
z := d * z; fi;
d := false;
n := m * z;
m := 1;
p := i+1;
z := 0; elif string[i] = '.' and IsRat(d) then
return fail; elif string[i] = '.' and not IsRat(d) then
d := 1; else
s := Position( CHARS_DIGITS, string[i] ); if s <> fail then
z := 10 * z + (s-1); else
return fail; fi; if IsRat(d) then
d := d / 10; fi; fi;
od; if IsRat(d) then
z := d * z; fi; if IsBound(n) then
return m * n / z; else
return m * z; fi; end );
############################################################################# ## #M ViewObj(<string>) #M ViewObj(<char>) ## ## The difference to PrintObj is that printable non-ASCII characters are ## output directly. Use PrintObj to get a result which can be safely reread ## by GAP or used for cut and paste. ##
# The first list is sorted and contains special characters. The second list # contains characters that should instead be printed after a `\'.
BindGlobal("SPECIAL_CHARS_VIEW_STRING", MakeImmutable(
[ List(Concatenation([0..31],[34,92],[127..255]), CHAR_INT), [ "\\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", "\\\"", "\\\\", "\\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","\\240","\\241","\\242", "\\243","\\244","\\245","\\246","\\247","\\250","\\251","\\252","\\253", "\\254","\\255","\\256","\\257","\\260","\\261","\\262","\\263","\\264", "\\265","\\266","\\267","\\270","\\271","\\272","\\273","\\274","\\275", "\\276","\\277","\\300","\\301","\\302","\\303","\\304","\\305","\\306", "\\307","\\310","\\311","\\312","\\313","\\314","\\315","\\316","\\317", "\\320","\\321","\\322","\\323","\\324","\\325","\\326","\\327","\\330", "\\331","\\332","\\333","\\334","\\335","\\336","\\337","\\340","\\341", "\\342","\\343","\\344","\\345","\\346","\\347","\\350","\\351","\\352", "\\353","\\354","\\355","\\356","\\357","\\360","\\361","\\362","\\363", "\\364","\\365","\\366","\\367","\\370","\\371","\\372","\\373","\\374", "\\375","\\376","\\377" ]]));
InstallMethod(ViewObj, "IsChar", true, [IsChar], 0, function(x)
local pos;
Print("'");
pos := Position(SPECIAL_CHARS_VIEW_STRING[1], x); if pos <> fail then
Print( SPECIAL_CHARS_VIEW_STRING[2][pos] ); else
Print( [ x ] ); fi;
Print("\'"); end);
# we overwrite this in GAPDoc such that Unicode can be used depending on string # and terminal encoding
InstallMethod(ViewObj, "IsString", true, [IsString and IsFinite],0, function(s)
local x, pos;
Print("\""); for x in s do
pos := Position(SPECIAL_CHARS_VIEW_STRING[1], x); if pos <> fail then
Print( SPECIAL_CHARS_VIEW_STRING[2][pos] ); else
Print( [ x ] ); fi;
od;
Print("\""); end);
InstallMethod(ViewObj,"empty strings",true,[IsString and IsEmpty],0, function(e) if IsStringRep(e) then
Print("\"\""); else
Print("[ ]"); fi; end);
InstallMethod( SplitString, "for a string and two characters",
true,
[ IsString, IsChar, IsChar ], 0, function( string, d1, d2 )
return SplitString( string, [d1], [d2] ); end );
InstallMethod( SplitString, "for two strings and a character",
true,
[ IsString, IsString, IsChar ], 0, function( string, seps, d )
return SplitString( string, seps, [d] ); end );
InstallMethod( SplitString, "for a string, a character and a string",
true,
[ IsString, IsChar, IsString ], 0, function( string, d, wspace )
return SplitString( string, [d], wspace ); end );
InstallOtherMethod( SplitString, "for a string and a character",
true,
[ IsString, IsChar ], 0, function( string, d )
return SplitString( string, [d], "" ); end );
InstallOtherMethod(PositionSublist, "for two args in IsStringRep", true,
[IS_STRING_REP, IS_STRING_REP], 0, function( string, sub )
return POSITION_SUBSTRING(string, sub, 0); end );
InstallOtherMethod(PositionSublist, "for two args in IsStringRep and offset",
true, [IS_STRING_REP, IS_STRING_REP, IsInt], 0, function( string, sub, off ) if off<0then
off := 0; fi;
return POSITION_SUBSTRING(string, sub, off); end );
############################################################################# ## #F NormalizedWhitespace( <str> ) . . . . . . . copy of string with normalized #F white space ## ## doesn't work in place like the kernel function `NormalizeWhitespace' ##
InstallGlobalFunction("NormalizedWhitespace", function ( str )
local res;
res := ShallowCopy( str );
NormalizeWhitespace( res );
return res; end);
############################################################################# ## #F RemoveCharacters( <string>, <todelete> ) ## # moved into kernels string.c ## InstallGlobalFunction( "RemoveCharacters", function( string, todelete ) ## local len, posto, posfrom, i; ## ## len:= Length( string ); ## posto:= 0; ## posfrom:= 1; ## while posfrom <= len do ## if not string[ posfrom ] in todelete then ## posto:= posto + 1; ## string[ posto ]:= string[ posfrom ]; ## fi; ## posfrom:= posfrom + 1; ## od; ## for i in [ len, len-1 .. posto + 1 ] do ## Unbind( string[i] ); ## od; ## end );
############################################################################# ## #F EvalString( <expr> ) . . . . . . . . . . . . evaluate a string expression ##
_EVALSTRINGTMP := 0;
InstallGlobalFunction("EvalString", function( s )
local a, f, res;
a := "_EVALSTRINGTMP:=";
Append(a, s); # The code handling syntax error messages breaks if the semicolon is the # last character in the input stream. We thus add a line break just as one # would by pressing the <RETURN> key while in the REPL.
Append(a, ";\n");
Unbind(_EVALSTRINGTMP);
f := InputTextString(a);
Read(f); if not IsBound(_EVALSTRINGTMP) then
Error("Could not evaluate string.\n"); fi;
res := _EVALSTRINGTMP;
Unbind(_EVALSTRINGTMP);
return res; end);
Unbind(_EVALSTRINGTMP);
############################################################################# ## #F JoinStringsWithSeparator( <list>[, <sep>] ) ##
InstallGlobalFunction("JoinStringsWithSeparator", function( arg )
local str, sep, res, i;
str := List(arg[1], String); if Length(str) = 0then return ""; fi; if Length(arg) > 1then sep := arg[2]; else sep := ","; fi;
res := ShallowCopy(str[1]); for i in [2 .. Length(str)] do
Append(res, sep);
Append(res, str[i]);
od;
return res; end );
############################################################################# ## #F Chomp( <str> ) . . remove trailing '\n' or "\r\n" from string if present ##
InstallGlobalFunction(Chomp, function(str)
if IsString(str) and str <> "" and Last(str) = '\n'then if1 < Length(str) and str[Length(str) - 1] = '\r'then
return str{[1 .. Length(str) - 2]}; fi;
return str{[1 .. Length(str) - 1]}; else
return str; fi; end);
############################################################################# ## #F StringFile( <name> ) . . . . . . return content of file <name> as string #F FileString( <name>, <string>[, <append> ] ) . . write <string> to <name> ## ## <#GAPDoc Label="StringFile"> ## <ManSection > ## <Func Arg="filename" Name="StringFile" /> ## <Func Arg="filename, str[, append]" Name="FileString" /> ## <Description> ## The function <Ref Func="StringFile" /> returns the content of ## file <A>filename</A> as a string. This works efficiently with ## arbitrary (binary or text) files. If something went wrong, this ## function returns <K>fail</K>. ## <P/> ## ## Conversely the function <Ref Func="FileString" /> writes the ## content of a string <A>str</A> into the file <A>filename</A>. If ## the optional third argument <A>append</A> is given and equals ## <K>true</K> then the content of <A>str</A> is appended to the ## file. Otherwise previous content of the file is deleted. This ## function returns the number of bytes written or <K>fail</K> if ## something went wrong.<P/> ## ## Both functions are quite efficient, even with large files. ## </Description> ## </ManSection> ## <#/GAPDoc> ##
InstallGlobalFunction(StringFile, function(name)
local f, str;
f := InputTextFile(name); if f=fail then
return fail; fi;
str := READ_STRING_FILE(f![1]); if str = fail then
CloseStream(f);
Error("in StringFile: ", LastSystemError().message, " (", LastSystemError().number, ")\n");
return fail; fi;
CloseStream(f);
return str; end);
# arg: filename, string[, append] (default for append is false)
InstallGlobalFunction(FileString, function(arg)
local name, str, append, out;
name := arg[1];
str := arg[2]; if Length(arg)>2then
append := arg[3]; else
append := false; fi; if not (IsString(name) and IsString(str) and IsBool(append)) then
Error("Usage: FileString(<name>, <str> [, <append> ])"); fi;
out := OutputTextFile(name, append); if out=fail then
return fail; fi;
IS_STRING_CONV(str); if WRITE_STRING_FILE_NC(out![1], str) = fail then
CloseStream(out);
Error("in FileString: ", LastSystemError().message, " (", LastSystemError().number, ")\n");
return fail; fi;
CloseStream(out);
return Length(str); end);
BindGlobal("RCSVSplitString",function(s,sep)
local l, i, start,nodob,str;
l:=[];
i:=1; while i<=Length(s) do if s[i]=sep then
Add(l,"");
i:=i+1; elif s[i]='"'then # find next ", treating "" special
str:="";
start:=i+1;
repeat while (i+1<=Length(s) and s[i+1]<>'"') or
(i+2=Length(s) and s[i+2]<>sep) do
i:=i+1;
od; if Length(s)>=i+2 and s[i+2]='"'then
str:=Concatenation(str,s{[start..i+1]});
i:=i+2;
start:=i+1;
nodob:=false; else
nodob:=true; fi;
until nodob; # not closed "..." ? if start<i and i=Length(s) and str=""then
return fail; fi; if Length(str)>0then
Add(l,Concatenation(str,s{[start..i]})); else
Add(l,s{[start..i]}); fi;
i:=i+3; # skip ", else
start:=i; while i<Length(s) and s[i+1]<>sep do
i:=i+1;
od;
Add(l,s{[start..i]});
i:=i+2; # skip comma fi;
od;
return l; end);
BindGlobal("RCSVReadLine",function(f)
local l, b;
l:=""; while not IsEndOfStream(f) do
b:=ReadByte(f); if b<>fail then if b<0then
b:=b+256; fi; if b=10 or b=13then
return l; fi;
Add(l,CHAR_INT(b)); fi;
od;
return l; end);
InstallGlobalFunction(ReadCSV,function(arg)
local nohead,file,sep,f, line, fields, l, r, i,s,t,add,dir;
file:=arg[1];
if not IsReadableFile(file) then
i:=file;
file:=Concatenation(i,".csv"); if not IsReadableFile(file) then
file:=Concatenation(i,".xls"); if not IsReadableFile(file) then
Error("file ",i," does not exist or is not readable"); fi; fi; fi;
if LowercaseString(file{[Length(file)-3..Length(file)]})=".xls" or
LowercaseString(file{[Length(file)-4..Length(file)]})=".xlsx"then
dir:=DirectoryTemporary();
i:=file;
file:=Filename(dir,"temp.csv");
Exec(Concatenation("xls2csv -x \"",i,"\" -c \"",file,"\"")); else
dir:=fail; fi;
nohead:=false; if Length(arg)>1then if IsBool(arg[2]) then
nohead:=arg[2]; fi;
sep:=Last(arg); if IsString(sep) then
sep:=sep[1]; elif not IsChar(sep) then
sep:=','; fi; else
sep:=','; fi;
f:=InputTextFile(file); if f=fail then return f;fi; # wrong file if nohead<>true then
line:=RCSVReadLine(f);
line:=Chomp(line); if'"' in line and sep=','then
fields:=RCSVSplitString(line,sep); else
fields:=SplitString(line,sep); fi; # field names with blank or empty are awkward for i in [1..Length(fields)] do if' ' in fields[i] then
fields[i]:=ReplacedString(fields[i]," ","_"); elif Length(fields[i])=0then
fields[i]:=Concatenation("field",String(i)); fi;
od; else
fields:=List([1..10000],i->Concatenation("field",String(i))); fi;
l:=[]; while not IsEndOfStream(f) do
line:=RCSVReadLine(f); if line<>fail then
line:=Chomp(line); if'"' in line and sep=','then
r:=RCSVSplitString(line,sep); while r=fail do
r:=RCSVReadLine(f);
line:=Concatenation(line," ",r);
r:=RCSVSplitString(line,sep);
od;
line:=r; else
line:=SplitString(line,sep); fi;
r:=rec();
add:=false; for i in [1..Length(fields)] do if IsBound(line[i]) and Length(line[i])>0then
s:=line[i]; # openoffice and Excel translate booleans differently. if s="TRUE"then s:="1"; elif s="FALSE"then s:="0"; else
t:=Rat(s); if not IsBool(t) and not '.' in s then
s:=t; fi; fi;
r.(fields[i]):=s;
add:=true; fi;
od; if add then
Add(l,r); fi; fi;
od;
CloseStream(f); if dir<>fail then
RemoveFile(file); fi;
return l; end);
InstallGlobalFunction(PrintCSV,function(arg)
local stream,l,printEntry, rf, r, i, j, oldStreamFormattingStatus, close;
if IsString(arg[1]) then
stream:=OutputTextFile(arg[1],false);
close:=true; elif IsOutputStream(arg[1]) then
stream:=arg[1];
close:=false; else
Error("PrintCSV: filename must be a string or an output stream"); fi;
l:=arg[2];
printEntry:=function(s)
local p,q;
q:=false; if not IsString(s) then
s:=String(s); elif IsString(s) and ForAll(s,x->x in CHARS_DIGITS or x in "+-") and Int(s)<>fail and AbsInt(Int(s))>10^9then
q:=true; fi;
p:=Position(s,'\n'); while p<>fail do
s:=Concatenation(s{[1..p-1]},s{[p+1..Length(s)]});
p:=Position(s,'\n');
od;
p:=PositionSublist(s," "); while p<>fail do
s:=Concatenation(s{[1..p-1]},s{[p+1..Length(s)]});
p:=PositionSublist(s," ");
od;
if'"' in s then
p:=1; while p<=Length(s) do if s[p]='"'then
s:=Concatenation(s{[1..p]},s{[p..Length(s)]});
p:=p+1; fi;
p:=p+1;
od; fi;
if',' in s or '"' in s then
s:=Concatenation("\"",s,"\""); elif q=true then # integers as string
s:=Concatenation("\"_",s,"\""); fi;
AppendTo(stream,s,"\c"); end;
oldStreamFormattingStatus:=PrintFormattingStatus(stream);
SetPrintFormattingStatus(stream,false); if Length(arg)>2then
rf:=arg[3]; else
rf:=[]; for i in l do
r:=RecNames(i); for j in r do if not j in rf then
Add(rf,j); fi;
od;
od; # sort record fields
Sort(rf,function(a,b)
local ap; # check trailing numbers
ap:=Length(a); while ap>0 and a[ap] in CHARS_DIGITS do
ap:=ap-1;
od; if Length(b)>=ap and ForAll([ap+1..Length(b)],j->b[j] in CHARS_DIGITS) then
return Int(a{[ap+1..Length(a)]})<Int(b{[ap+1..Length(b)]}); fi;
return a<b; end); fi;
PrintTo(stream);
if ValueOption("noheader")<>true then
printEntry(rf[1]); for j in [2..Length(rf)] do
AppendTo(stream,",");
printEntry(ReplacedString(rf[j],"_"," "));
od;
AppendTo(stream,"\n"); fi;
for i in l do for j in [1..Length(rf)] do if j>1then
AppendTo(stream,","); fi; if IsBound(i.(rf[j])) then
printEntry(i.(rf[j])); fi;
od;
AppendTo(stream,"\n");
od;
SetPrintFormattingStatus(stream,oldStreamFormattingStatus); if close then
CloseStream(stream); fi; end);
# Format commands # RLC: alignment # M: Math mode # MN: Math mode but names, characters are put into mbox # F: Number displayed in factored form # P: Minipage environment (25mm per default) # B: Background color # option `rows' colors alternating rows
InstallGlobalFunction(LaTeXTable,function(file,l)
local f,i,j,format,cold,a,e,z,str,new,box,lc,mini,color,alt,renum;
alt:=ValueOption("rows")<>fail;
color:=fail; # row 1 indicates which columns are relevant and their formatting
cold:=ShallowCopy(l[1]);
f:=RecNames(cold);
renum:=[]; for i in ShallowCopy(f) do
a:=Filtered(cold.(i),x->x in CHARS_DIGITS); if LENGTH(a)>0then
cold.(i):=Filtered(cold.(i),x->not x in CHARS_DIGITS);
Add(renum,Int(a)); fi;
if cold.(i)="B"then # color indicator
color:=i;
Unbind(cold.(i));
f:=Difference(f,[i]); else
cold.(i):=UppercaseString(cold.(i)); fi;
od;
# resort columns if numbers are given if Length(renum)=Length(f) then
a:=ShallowCopy(renum);
a:=Sortex(a);
f:=Permuted(f,a); fi;
PrintTo(file); # header
format:=""; for i in [1..Length(f)] do if i>1then Append(format,"|");fi; if'R' in cold.(f[i]) then
Add(format,'r'); elif'C' in cold.(f[i]) then
Add(format,'c'); else
Add(format,'l'); fi;
od;
# header
AppendTo(file,"\\begin{tabular}{",format,"}\n"); for i in [1..Length(f)] do if i>1then AppendTo(file,"&");fi;
AppendTo(file,l[2].(f[i]),"\n");
od;
AppendTo(file,"\\\\\n");
AppendTo(file,"\\hline\n");
#entries for j in [3..Length(l)] do if color<>fail and IsBound(l[j].(color)) then
AppendTo(file,"\\rowcolor{",l[j].(color),"}%\n"); elif alt and IsEvenInt(j) then # light grey color
AppendTo(file,"\\rowcolor{lgrey}%\n"); fi; for i in [1..Length(f)] do if i>1then AppendTo(file,"&");fi; if IsBound(l[j].(f[i])) then
str:=l[j].(f[i]); # fix _integer to keep long integers from Excel if IsList(str) and Length(str)>0 and str[1]='_' and
Int(str{[2..Length(str)]})<>fail then
str:=str{[2..Length(str)]}; fi;
if'P' in cold.(f[i]) then
mini:=true;
AppendTo(file,"\\begin{minipage}{25mm}%\n"); else
mini:=false; fi; if'F' in cold.(f[i]) then if IsInt(str) then
a:=str; else # transform str in normal format
str:=Filtered(str,x->x<>',');
z:=0;
a:=Position(str,'E'); if a<>fail then
z:=Int(Filtered(str{[a+1..Length(str)]},x->x<>'+'));
str:=str{[1..a-1]}; fi;
a:=Position(str,'.'); if a<>fail then
z:=z-(Length(str)-a);
str:=Filtered(str,x->x<>'.'); fi;
a:=Int(str)*10^z; fi;
a:=Collected(Factors(a));
AppendTo(file,"$"); for z in [1..Length(a)] do if z>1 and e=false then
AppendTo(file,"\n{\\cdot}"); fi;
AppendTo(file,a[z][1]); if a[z][2]>1then
AppendTo(file,"^{",a[z][2],"}");
e:=true; else
e:=false; fi;
od;
AppendTo(file,"$\n"); elif'M' in cold.(f[i]) and 'N' in cold.(f[i]) then # make strings ``names'' in mbox
new:="";
box:=false;
lc:=false; for a in str do
z:=a in CHARS_UALPHA or a in CHARS_LALPHA; if z and box=false then if lc='\\'then# actual command
box:=fail; else
Append(new,"\\mbox{");
box:=true; fi; elif box=true and not z then
Append(new,"}");
box:=false; elif box=fail and not z then
box:=false; # command over fi;
Add(new,a);
lc:=a; # last character
od; if box=true then
Append(new,"}"); fi;
AppendTo(file,"$",new,"$\n");
elif'M' in cold.(f[i]) then
AppendTo(file,"$",str,"$\n"); else
AppendTo(file,str,"\n"); fi; if mini then
AppendTo(file,"\\end{minipage}%\n"); fi; fi;
od;
AppendTo(file,"\\\\\n");
od;
AppendTo(file,"\\end{tabular}\n"); end);
############################################################################# ## #F Convenience method to inform users how to concatenate strings. ## ## Note that we could also have this method do the following ## return Concatenation(a,b); ## instead of raising an error. But this leads to inefficient code when ## concatenating many strings. So in order to not encourage such bad code, ## we instead tell the user the proper way to do this. ##
InstallOtherMethod(\+, [IsString,IsString], function(a,b)
Error("concatenating strings via + is not supported, use Concatenation(<a>,<b>) instead"); end);
############################################################################# ## #F StringOfMemoryAmount( <m> ) returns an appropriate human-readable string ## representation of <m> bytes ##
InstallGlobalFunction(StringOfMemoryAmount, function(m)
local whole, frac, shift, s, units; if not IsInt(m) or m < 0then
Error("StringOfMemoryAmount: amount must be a non-negative integer number of bytes"); fi;
whole := m;
frac := 0;
shift := 0; while whole >= 1024do
frac := whole mod 1024;
whole := Int(whole / 1024);
shift := shift+1;
od;
s := ShallowCopy(String(whole)); if whole < 100then
Append(s,".");
Append(s,String(Int(frac*10/1024))); if whole < 10then
Append(s, String(Int(frac*100/1024) mod 10)); fi; fi;
units := ["B","KB","MB","GB","TB","PB","EB","YB","ZB"];
Append(s, units[shift+1]);
return s; end);
InstallGlobalFunction(PrintToFormatted, function(stream, s, data...)
local pos, len, nextbrace, endbrace,
argcounter, var,
splitReplacementField, toprint, namedIdUsed;
# Set to true if we ever use a named id in a replacement field
namedIdUsed := false;
# Split a replacement field {..} at [startpos..endpos]
splitReplacementField := function(startpos, endpos)
local posbang, format;
posbang := Position(s, '!', startpos-1); if posbang = fail or posbang > endpos then
posbang := endpos + 1; fi;
format := s{[posbang + 1 .. endpos]}; # If no format, default to "s" if format = ""then
format := "s"; fi;
return rec(id := s{[startpos..posbang-1]}, format := format); end;
argcounter := 1;
len := Length(s);
pos := 0;
if not (IsOutputStream(stream) or IsString(stream)) or not IsString(s) then
ErrorNoReturn("Usage: PrintToFormatted(<stream>, <string>, <data>...)"); fi;
while pos < len do
nextbrace := Position(s, '{', pos);
endbrace := Position(s, '}', pos); # Scan until we find an '{'. # Produce an error if we find '}', unless it is part of '}}'. while IsInt(endbrace) and (nextbrace = fail or endbrace < nextbrace) do if endbrace + 1 <= len and s[endbrace + 1] = '}'then # Found }} with no { before it, insert everything up to # including the first }, skipping the second.
AppendTo(stream, s{[pos+1..endbrace]});
pos := endbrace + 1;
endbrace := Position(s, '}', pos); else
ErrorNoReturn("Mismatched '}' at position ",endbrace); fi;
od;
if nextbrace = fail then # In this case, endbrace = fail, or we would not have left # previous while loop
AppendTo(stream, s{[pos+1..len]});
return; fi;
AppendTo(stream, s{[pos+1..nextbrace-1]});
# If this is {{, then print a { and call 'continue' if nextbrace+1 <= len and s[nextbrace+1] = '{'then
AppendTo(stream, "{");
pos := nextbrace + 1;
continue; fi;
if endbrace = fail then
ErrorNoReturn("Invalid format string, no matching '}' at position ", nextbrace); fi;
# Check if we are mixing giving id, and not giving id. if (argcounter > 1 and toprint.id <> "") or (namedIdUsed and toprint.id = "") then
ErrorNoReturn("replacement field must either all have an id, or all have no id"); fi;
if toprint.id = ""then if Length(data) < argcounter then
ErrorNoReturn("out of bounds -- used ",argcounter," replacement fields without id when there are only ",Length(data), " arguments"); fi;
var := data[argcounter];
argcounter := argcounter + 1; elif Int(toprint.id) <> fail then
namedIdUsed := true; if Int(toprint.id) < 1 or Int(toprint.id) > Length(data) then
ErrorNoReturn("out of bounds -- asked for {",Int(toprint.id),"} when there are only ",Length(data), " arguments"); fi;
var := data[Int(toprint.id)]; else
namedIdUsed := true; if not IsRecord(data[1]) then
ErrorNoReturn("first data argument must be a record when using {",toprint.id,"}"); fi; if not IsBound(data[1].(toprint.id)) then
ErrorNoReturn("no record member '",toprint.id,"'"); fi;
var := data[1].(toprint.id); fi;
pos := endbrace;
if toprint.format = "s"then if not IsString(var) then
var := String(var); fi;
AppendTo(stream, var); elif toprint.format = "v"then
AppendTo(stream, ViewString(var)); elif toprint.format = "d"then
AppendTo(stream, DisplayString(var)); else ErrorNoReturn("Invalid format: '", toprint.format, "'"); fi;
od; end);
InstallGlobalFunction(StringFormatted, function(s, data...)
local str, stream; if not IsString(s) then
ErrorNoReturn("Usage: StringFormatted(<string>, <data>...)"); fi;
str := "";
stream := OutputTextString(str, false);
SetPrintFormattingStatus(stream, false);
InstallGlobalFunction(PrintFormatted, function(args...) # Do some very baic argument checking if not (Length(args) > 1 and IsString(args[1])) then
ErrorNoReturn("Usage: PrintFormatted(<string>, <data>...)"); fi;
# We can't use PrintTo, as we do not know where Print is currently # directed
Print(CallFuncList(StringFormatted, args)); end);
InstallGlobalFunction(Pluralize, function(args...)
local nargs, i, count, include_num, str, len, out;
#Int and one string #Int and two strings #One string #Two strings
nargs := Length(args); if nargs >= 1 and IsInt(args[1]) and args[1] >= 0then
i := 2;
count := args[1];
include_num := true; else
i := 1;
include_num := false; # if not given, assume pluralization is wanted. fi;
if not (nargs in [i, i + 1] and
IsString(args[i]) and
(nargs = i or IsString(args[i + 1]))) then
ErrorNoReturn("Usage: Pluralize([<count>, ]<string>[, <plural>])"); fi;
str := args[i];
len := Length(str);
if len = 0then
ErrorNoReturn("the argument <str> must be a non-empty string"); elif include_num and count = 1then# no pluralization needed
return Concatenation("\>1\< ", str); elif nargs = i + 1then# pluralization given
out := args[i + 1]; elif len <= 2then
out := Concatenation(str, "s");
# Guess and return the plural form of <str>. # Inspired by the "Ruby on Rails" inflection rules.
# Uncountable nouns elif str in ["equipment", "information"] then
out := str;
# Irregular plurals elif str = "axis"then
out := "axes"; elif str = "child"then
out := "children"; elif str = "person"then
out := "people";
# Peculiar endings elif EndsWith(str, "ix") or EndsWith(str, "ex") then
out := Concatenation(str{[1 .. len - 2]}, "ices"); elif EndsWith(str, "x") then
out := Concatenation(str, "es"); elif EndsWith(str, "tum") or EndsWith(str, "ium") then
out := Concatenation(str{[1 .. len - 2]}, "a"); elif EndsWith(str, "sis") then
out := Concatenation(str{[1 .. len - 3]}, "ses"); elif EndsWith(str, "fe") and not EndsWith(str, "ffe") then
out := Concatenation(str{[1 .. len - 2]}, "ves"); elif EndsWith(str, "lf") or EndsWith(str, "rf") or EndsWith(str, "loaf") then
out := Concatenation(str{[1 .. len - 1]}, "ves"); elif EndsWith(str, "y") and not str[len - 1] in "aeiouy"then
out := Concatenation(str{[1 .. len - 1]}, "ies"); elif str{[len - 1, len]} in ["ch", "ss", "sh"] then
out := Concatenation(str, "es"); elif EndsWith(str, "s") then
out := str;
# Default to appending 's' else
out := Concatenation(str, "s"); fi;
if include_num then
return Concatenation("\>", String(args[1]), "\< ", out); fi;
return out; end);
Messung V0.5 in Prozent
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.32Bemerkung:
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-10-11)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.