diff --git a/lib/custom_streams.gi b/lib/custom_streams.gi index 01798f9ddc0..7d76888b29a 100644 --- a/lib/custom_streams.gi +++ b/lib/custom_streams.gi @@ -343,11 +343,8 @@ InstallMethod( PrintFormattingStatus, "output text custom", ## InstallMethod( SetPrintFormattingStatus, "output text custom", [IsOutputTextCustomRep and IsOutputTextStream, - IsBool], + IsObject], function( str, stat) - if stat = fail then - Error("Print formatting status must be true or false"); - else - str!.formatting := stat; - fi; + CheckValidPrintFormattingStatus(stat); + str!.formatting := stat; end); diff --git a/lib/streams.gd b/lib/streams.gd index 0f39b669730..8c4942e4550 100644 --- a/lib/streams.gd +++ b/lib/streams.gd @@ -1015,7 +1015,7 @@ DeclareGlobalFunction( "InputOutputLocalProcess" ); ## ## <#/GAPDoc> ## -DeclareOperation( "SetPrintFormattingStatus", [IsOutputStream, IsBool] ); +DeclareOperation( "SetPrintFormattingStatus", [IsOutputStream, IsObject] ); DeclareOperation( "PrintFormattingStatus", [IsOutputStream] ); @@ -1259,3 +1259,5 @@ DeclareGlobalFunction( "InputFromUser" ); ## <#/GAPDoc> ## DeclareGlobalFunction( "OpenExternal" ); + +DeclareGlobalFunction( "CheckValidPrintFormattingStatus"); diff --git a/lib/streams.gi b/lib/streams.gi index cbd7059fadf..b8342136268 100644 --- a/lib/streams.gi +++ b/lib/streams.gi @@ -929,13 +929,10 @@ InstallMethod( PrintFormattingStatus, "output text string", ## InstallMethod( SetPrintFormattingStatus, "output text string", [IsOutputTextStringRep and IsOutputTextStream, - IsBool], + IsObject], function( str, stat) - if stat = fail then - Error("Print formatting status must be true or false"); - else - str![2] := stat; - fi; + CheckValidPrintFormattingStatus(stat); + str![2] := stat; end); @@ -1130,13 +1127,10 @@ InstallMethod( PrintFormattingStatus, "output text file", ## InstallMethod( SetPrintFormattingStatus, "output text file", [IsOutputTextFileRep and IsOutputTextStream, - IsBool], + IsObject], function( str, stat) - if stat = fail then - Error("Print formatting status must be true or false"); - else - str![3] := stat; - fi; + CheckValidPrintFormattingStatus(stat); + str![3] := stat; end); ## formatting status for stdout or current output @@ -1151,7 +1145,7 @@ function(str) fi; end); -InstallOtherMethod( SetPrintFormattingStatus, "for stdout", [IsString, IsBool], +InstallOtherMethod( SetPrintFormattingStatus, "for stdout", [IsString, IsObject], function(str, status) if str = "*stdout*" then SET_PRINT_FORMATTING_STDOUT(status); @@ -1255,9 +1249,7 @@ InstallMethod( SetPrintFormattingStatus, "output text none", [IsOutputTextNoneRep and IsOutputTextNone, IsBool], function( str, stat) - if stat = fail then - Error("Print formatting status must be true or false"); - fi; + CheckValidPrintFormattingStatus(stat); end); @@ -1676,14 +1668,35 @@ InstallMethod( SetPrintFormattingStatus, "for non-text output stream", TryNextMethod(); fi; - if stat = true then - Error("non-text streams support onlyPrint formatting status false"); - elif stat = fail then - Error("Print formatting status must be true or false"); + CheckValidPrintFormattingStatus(stat); + if (( IsBool(stat) and stat = true ) or + ( IsRecord(stat) and (stat.linewrap or stat.indent) ) ) then + Error("non-text streams do not support print formatting"); fi; end); +InstallGlobalFunction( "CheckValidPrintFormattingStatus", +function(fs) + local r; + if IsBool(fs) then + if fs = fail then + Error("Formatting status cannot be 'fail'"); + fi; + elif IsRecord(fs) then + if Set(RecNames(fs)) <> ["indent", "linewrap"] then + Error("Formatting status records must contain only 'indent' and 'linewrap'"); + fi; + for r in ["indent","linewrap"] do + if not fs.(r) in [false, true] then + Error(Concatenation(r, " must be a Boolean in Formatting status")); + fi; + od; + else + Error("Formatting status must be a boolean or a record"); + fi; +end); + ############################################################################# ## #M FileDescriptorOfStream( ) diff --git a/src/io.c b/src/io.c index ec4d529a3a6..b3ecb42deda 100644 --- a/src/io.c +++ b/src/io.c @@ -32,7 +32,9 @@ #include "lists.h" #include "modules.h" #include "plist.h" +#include "precord.h" #include "read.h" +#include "records.h" #include "scanner.h" #include "stringobj.h" #include "symbols.h" @@ -115,8 +117,8 @@ struct IOModuleState { Int NoSplitLine; - BOOL PrintFormattingForStdout; - BOOL PrintFormattingForErrout; + StreamFormat PrintFormattingForStdout; + StreamFormat PrintFormattingForErrout; }; // for debugging from GDB / lldb, we mark this as extern inline @@ -851,7 +853,7 @@ UInt OpenOutput(TypOutputFile * output, const Char * filename, BOOL append) else if (streq(filename, "*errout*")) output->format = IO()->PrintFormattingForErrout; else - output->format = TRUE; + output->format = MakeStreamFormat(TRUE, TRUE); output->indent = 0; // variables related to line splitting, very bad place to split @@ -886,7 +888,8 @@ UInt OpenOutputStream(TypOutputFile * output, Obj stream) output->file = -1; output->line[0] = '\0'; output->pos = 0; - output->format = (CALL_1ARGS(PrintFormattingStatus, stream) == True); + output->format = + StreamFormatFromObj(CALL_1ARGS(PrintFormattingStatus, stream)); output->indent = 0; // variables related to line splitting, very bad place to split @@ -1236,21 +1239,21 @@ static void PutChrTo(TypOutputFile * stream, Char ch) // '\01', increment indentation level if ( ch == '\01' ) { - if (!stream->format) - return; + if (NoStreamFormat(stream->format)) + return; - // add hint to break line - addLineBreakHint(stream, stream->pos, 16*stream->indent, 1); + // add hint to break line + addLineBreakHint(stream, stream->pos, 16 * stream->indent, 1); } // '\02', decrement indentation level else if ( ch == '\02' ) { - if (!stream->format) - return; + if (NoStreamFormat(stream->format)) + return; - // if this is a better place to split the line remember it - addLineBreakHint(stream, stream->pos, 16*stream->indent, -1); + // if this is a better place to split the line remember it + addLineBreakHint(stream, stream->pos, 16 * stream->indent, -1); } // '\03', print line @@ -1282,12 +1285,11 @@ static void PutChrTo(TypOutputFile * stream, Char ch) // and dump it from the buffer stream->pos = 0; - if (stream->format) - { + if (stream->format.indent) { // indent for next line for ( i = 0; i < stream->indent; i++ ) stream->line[ stream->pos++ ] = ' '; - } + } // reset line break hints stream->hints[0] = -1; @@ -1318,55 +1320,57 @@ static void PutChrTo(TypOutputFile * stream, Char ch) /* if we are going to split at the end of the line, and we are formatting discard blanks */ - if ( stream->format && spos == stream->pos && ch == ' ' ) { - ; + if (stream->format.linewrap && spos == stream->pos && ch == ' ') { + ; } // full line, acceptable split position - else if ( stream->format && spos != 0 ) { - - // add character to the line, terminate it - stream->line[ stream->pos++ ] = ch; - stream->line[ stream->pos++ ] = '\0'; - - // copy the rest after the best split position to a safe place - for ( i = spos; i < stream->pos; i++ ) - str[ i-spos ] = stream->line[ i ]; - str[ i-spos] = '\0'; - - // print line up to the best split position - stream->line[ spos++ ] = '\n'; - stream->line[ spos ] = '\0'; - PutLineTo( stream, spos ); - spos--; + else if (stream->format.linewrap && spos != 0) { + + // add character to the line, terminate it + stream->line[stream->pos++] = ch; + stream->line[stream->pos++] = '\0'; + + // copy the rest after the best split position to a safe place + for (i = spos; i < stream->pos; i++) + str[i - spos] = stream->line[i]; + str[i - spos] = '\0'; + + // print line up to the best split position + stream->line[spos++] = '\n'; + stream->line[spos] = '\0'; + PutLineTo(stream, spos); + spos--; + + stream->pos = 0; + if (stream->format.indent) { + // indent for the rest + for (i = 0; i < stream->hints[3 * hint + 2]; i++) + stream->line[stream->pos++] = ' '; + spos -= stream->hints[3 * hint + 2]; + } - // indent for the rest - stream->pos = 0; - for ( i = 0; i < stream->hints[3*hint+2]; i++ ) - stream->line[ stream->pos++ ] = ' '; - spos -= stream->hints[3*hint+2]; - - // copy the rest onto the next line - for ( i = 0; str[ i ] != '\0'; i++ ) - stream->line[ stream->pos++ ] = str[ i ]; - // recover line break hints for copied rest - for ( i = hint+1; stream->hints[3*i] != -1; i++ ) - { - stream->hints[3*(i-hint-1)] = stream->hints[3*i]-spos; - stream->hints[3*(i-hint-1)+1] = stream->hints[3*i+1]; - stream->hints[3*(i-hint-1)+2] = stream->hints[3*i+2]; - } - stream->hints[3*(i-hint-1)] = -1; + // copy the rest onto the next line + for (i = 0; str[i] != '\0'; i++) + stream->line[stream->pos++] = str[i]; + // recover line break hints for copied rest + for (i = hint + 1; stream->hints[3 * i] != -1; i++) { + stream->hints[3 * (i - hint - 1)] = stream->hints[3 * i] - spos; + stream->hints[3 * (i - hint - 1) + 1] = + stream->hints[3 * i + 1]; + stream->hints[3 * (i - hint - 1) + 2] = + stream->hints[3 * i + 2]; + } + stream->hints[3 * (i - hint - 1)] = -1; } // full line, no split position else { - if (stream->format) - { - /* append a '\',*/ - stream->line[ stream->pos++ ] = '\\'; - stream->line[ stream->pos++ ] = '\n'; + if (stream->format.linewrap) { + // append a '\' + stream->line[stream->pos++] = '\\'; + stream->line[stream->pos++] = '\n'; } // and print the line stream->line[ stream->pos ] = '\0'; @@ -1376,8 +1380,8 @@ static void PutChrTo(TypOutputFile * stream, Char ch) stream->pos = 0; stream->line[ stream->pos++ ] = ch; - if (stream->format) - stream->hints[0] = -1; + if (stream->format.indent) + stream->hints[0] = -1; } } @@ -1883,6 +1887,42 @@ void SPrTo(Char *buffer, UInt maxlen, const Char *format, Int arg1, Int arg2) } +// Convert GAP-level print formatting objects to kernel level, and vice-versa + +StreamFormat StreamFormatFromObj(Obj o) +{ + if (o == True) { + return MakeStreamFormat(TRUE, TRUE); + } + if (o == False) { + return MakeStreamFormat(FALSE, FALSE); + } + + RequirePlainRec("Stream formatting", o); + + Obj indent = ElmPRec(o, RNamName("indent")); + Obj linewrap = ElmPRec(o, RNamName("linewrap")); + + RequireTrueOrFalse("indent in stream formatting", indent); + RequireTrueOrFalse("linewrap in stream formatting", linewrap); + + BOOL i = (indent == True); + BOOL l = (linewrap == True); + + return MakeStreamFormat(l, i); +} + +Obj ObjFromStreamFormat(StreamFormat sf) +{ + Obj o = NEW_PREC(2); + Obj indent = sf.indent ? True : False; + Obj linewrap = sf.linewrap ? True : False; + AssPRec(o, RNamName("indent"), indent); + AssPRec(o, RNamName("linewrap"), linewrap); + return o; +} + + static Obj FuncINPUT_FILENAME(Obj self) { if (IO()->Input == 0) @@ -1899,7 +1939,7 @@ static Obj FuncINPUT_LINENUMBER(Obj self) static Obj FuncSET_PRINT_FORMATTING_STDOUT(Obj self, Obj val) { - BOOL format = (val != False); + StreamFormat format = StreamFormatFromObj(val); TypOutputFile * output = IO()->Output; while (output) { if (!output->stream && output->file == 1) @@ -1912,12 +1952,12 @@ static Obj FuncSET_PRINT_FORMATTING_STDOUT(Obj self, Obj val) static Obj FuncPRINT_FORMATTING_STDOUT(Obj self) { - return IO()->PrintFormattingForStdout ? True : False; + return ObjFromStreamFormat(IO()->PrintFormattingForStdout); } static Obj FuncSET_PRINT_FORMATTING_ERROUT(Obj self, Obj val) { - BOOL format = (val != False); + StreamFormat format = StreamFormatFromObj(val); TypOutputFile * output = IO()->Output; while (output) { if (!output->stream && output->file == 3) @@ -1930,7 +1970,7 @@ static Obj FuncSET_PRINT_FORMATTING_ERROUT(Obj self, Obj val) static Obj FuncPRINT_FORMATTING_ERROUT(Obj self) { - return IO()->PrintFormattingForErrout ? True : False; + return ObjFromStreamFormat(IO()->PrintFormattingForErrout); } /**************************************************************************** @@ -1949,8 +1989,8 @@ static Obj FuncCALL_WITH_FORMATTING_STATUS(Obj self, Obj status, Obj func, Obj a if (!output) ErrorMayQuit("CALL_WITH_FORMATTING_STATUS called while no output is open", 0, 0); - BOOL old = output->format; - output->format = (status != False); + StreamFormat old = output->format; + output->format = StreamFormatFromObj(status); Obj result; GAP_TRY @@ -2031,8 +2071,8 @@ static Int InitKernel ( IO()->Output = 0; IO()->InputLog = 0; IO()->OutputLog = 0; - IO()->PrintFormattingForStdout = TRUE; - IO()->PrintFormattingForErrout = TRUE; + IO()->PrintFormattingForStdout = MakeStreamFormat(TRUE, TRUE); + IO()->PrintFormattingForErrout = MakeStreamFormat(TRUE, TRUE); OpenOutput(&IO()->DefaultOutput, "*stdout*", FALSE); diff --git a/src/io.h b/src/io.h index 3e76a9e4f5c..c7a3fad4b86 100644 --- a/src/io.h +++ b/src/io.h @@ -92,6 +92,25 @@ enum { MAXLENOUTPUTLINE = 4096, }; +typedef struct { + BOOL linewrap; + BOOL indent; +} StreamFormat; + +EXPORT_INLINE StreamFormat MakeStreamFormat(BOOL linewrap, BOOL indent) +{ + StreamFormat s = { .linewrap = linewrap, .indent = indent }; + return s; +} + +EXPORT_INLINE BOOL NoStreamFormat(StreamFormat sf) +{ + return !sf.linewrap && !sf.indent; +} + + +StreamFormat StreamFormatFromObj(Obj o); +Obj ObjFromStreamFormat(StreamFormat sf); /**************************************************************************** ** @@ -113,7 +132,7 @@ struct TypOutputFile { char line[MAXLENOUTPUTLINE]; Int pos; - BOOL format; + StreamFormat format; Int indent; // each hint is a triple (position, value, indent) diff --git a/tst/testinstall/streams.tst b/tst/testinstall/streams.tst index d35765f7bb6..27502296143 100644 --- a/tst/testinstall/streams.tst +++ b/tst/testinstall/streams.tst @@ -1,4 +1,4 @@ -#@local dir,fname,file,line,stream,tmpdir,res,streams,i +#@local dir,fname,file,line,stream,tmpdir,res,streams,i,func,linewrap,indent gap> START_TEST("streams.tst"); # @@ -102,6 +102,56 @@ gap> CloseStream(stream); gap> StringFile(fname); "a very long line that GAP is going to wrap at 80 chars by default if we don't\ do anything about it\n" +gap> func := function(x) if x then return "a very long line that GAP is going to wrap at 80 chars by default if we don't do anything about it"; fi; end;; +gap> for linewrap in [false,true] do +> for indent in [false,true] do +> res := ""; +> stream := OutputTextString(res, true); +> SetPrintFormattingStatus(stream, rec(linewrap := linewrap, indent := indent)); +> PrintTo(stream, func); +> CloseStream(stream); +> Print([linewrap, indent, res],"\n"); +> od; +> od; +[ false, false, + "function ( x )\nif x then\nreturn \"a very long line that GAP is going to w\ +rap at 80 chars by default if we don't do anything about it\";\nfi;\nreturn;\n\ +end" ] +[ false, true, + "function ( x )\n if x then\n return \"a very long line that GAP i\ +s going to wrap at 80 chars by default if we don't do anything about it\";\n \ + fi;\n return;\nend" ] +[ true, false, + "function ( x )\nif x then\nreturn \"a very long line that GAP is going to w\ +rap at 80 chars by default if w\\\ne don't do anything about it\";\nfi;\nretur\ +n;\nend" ] +[ true, true, + "function ( x )\n if x then\n return \n \"a very long line\ + that GAP is going to wrap at 80 chars by default if\\\n we don't do anything \ +about it\";\n fi;\n return;\nend" ] +gap> stream := OutputTextString(res, true); +OutputTextString(181) +gap> SetPrintFormattingStatus(stream, true); +gap> PrintFormattingStatus(stream); +true +gap> SetPrintFormattingStatus(stream, false); +gap> PrintFormattingStatus(stream); +false +gap> SetPrintFormattingStatus(stream, rec(indent := false, linewrap := true)); +gap> PrintFormattingStatus(stream); +rec( indent := false, linewrap := true ) +gap> SetPrintFormattingStatus(stream, fail); +Error, Formatting status cannot be 'fail' +gap> SetPrintFormattingStatus(stream, 6); +Error, Formatting status must be a boolean or a record +gap> SetPrintFormattingStatus(stream, rec(indent := false)); +Error, Formatting status records must contain only 'indent' and 'linewrap' +gap> SetPrintFormattingStatus(stream, rec(indent := false, linewrap := 12)); +Error, linewrap must be a Boolean in Formatting status +gap> SetPrintFormattingStatus(stream, rec(indent := false, linewrap := false, extra := true)); +Error, Formatting status records must contain only 'indent' and 'linewrap' +gap> PrintFormattingStatus(stream); +rec( indent := false, linewrap := true ) # # string streams @@ -207,7 +257,7 @@ true gap> WriteByte(stream, 300); Error, must an integer between 0 and 255 gap> SetPrintFormattingStatus(stream, fail); -Error, Print formatting status must be true or false +Error, Formatting status cannot be 'fail' # too many open files gap> streams := [ ];; diff --git a/tst/testspecial/print-formatting.g.out b/tst/testspecial/print-formatting.g.out index 91e7af87412..d79b3e81a95 100644 --- a/tst/testspecial/print-formatting.g.out +++ b/tst/testspecial/print-formatting.g.out @@ -1,23 +1,23 @@ gap> # test formatting status for stdout gap> old := PrintFormattingStatus("*stdout*"); -true +rec( indent := true, linewrap := true ) gap> SetPrintFormattingStatus("*stdout*", false); gap> PrintFormattingStatus("*stdout*"); -false +rec( indent := false, linewrap := false ) gap> Display(x -> x); function ( x ) return x; end gap> SetPrintFormattingStatus("*stdout*", true); gap> PrintFormattingStatus("*stdout*"); -true +rec( indent := true, linewrap := true ) gap> Display(x -> x); function ( x ) return x; end gap> SetPrintFormattingStatus("*stdout*", old);; gap> PrintFormattingStatus("*stdout*"); -true +rec( indent := true, linewrap := true ) gap> gap> # test formatting status for errout gap> 1/0; # trigger a break loop @@ -25,22 +25,22 @@ Error, Rational operations: must not be zero not in any function at *stdin*:14 type 'quit;' to quit to outer loop brk> old := PrintFormattingStatus("*errout*"); -true +rec( indent := true, linewrap := true ) brk> SetPrintFormattingStatus("*errout*", false); brk> PrintFormattingStatus("*errout*"); -false +rec( indent := false, linewrap := false ) brk> Display(x -> x); function ( x ) return x; end brk> SetPrintFormattingStatus("*errout*", true); brk> PrintFormattingStatus("*errout*"); -true +rec( indent := true, linewrap := true ) brk> Display(x -> x); function ( x ) return x; end brk> SetPrintFormattingStatus("*errout*", old);; brk> PrintFormattingStatus("*errout*"); -true +rec( indent := true, linewrap := true ) brk> QUIT;