@@ -1259,8 +1259,139 @@ InstallGlobalFunction(StringOfMemoryAmount, function(m)
12591259 return s;
12601260end );
12611261
1262-
1262+ InstallGlobalFunction(PrintToFormatted, function (stream, s, data... )
1263+ local pos, len, outstr, nextbrace, endbrace,
1264+ argcounter, var,
1265+ splitReplacementField, toprint, namedIdUsed;
1266+
1267+ # Set to true if we ever use a named id in a replacement field
1268+ namedIdUsed := false ;
1269+
1270+ # Split a replacement field {..} at [startpos..endpos]
1271+ splitReplacementField := function (startpos, endpos )
1272+ local posbang, format;
1273+ posbang := Position(s, ' !' , startpos- 1 );
1274+ if posbang = fail or posbang > endpos then
1275+ posbang := endpos + 1 ;
1276+ fi ;
1277+ format := s{[ posbang + 1 .. endpos]} ;
1278+ # If no format, default to "s"
1279+ if format = " " then
1280+ format := " s" ;
1281+ fi ;
1282+ return rec (id := s{[ startpos.. posbang- 1 ]} , format := format);
1283+ end ;
1284+
1285+ argcounter := 1 ;
1286+ len := Length(s);
1287+ pos := 0 ;
1288+
1289+ if not (IsOutputStream(stream) or IsString(stream)) or not IsString(s) then
1290+ ErrorNoReturn(" Usage: PrintToFormatted(<stream>, <string>, <data>...)" );
1291+ fi ;
1292+
1293+ while pos < len do
1294+ nextbrace := Position(s, ' {' , pos);
1295+ endbrace := Position(s, ' }' , pos);
1296+ # Scan until we find an '{'.
1297+ # Produce an error if we find '}', unless it is part of '}}'.
1298+ while IsInt(endbrace) and (nextbrace = fail or endbrace < nextbrace) do
1299+ if endbrace + 1 <= len and s[ endbrace + 1 ] = ' }' then
1300+ # Found }} with no { before it, insert everything up to
1301+ # including the first }, skipping the second.
1302+ AppendTo(stream, s{[ pos+ 1 .. endbrace]} );
1303+ pos := endbrace + 1 ;
1304+ endbrace := Position(s, ' }' , pos);
1305+ else
1306+ ErrorNoReturn(" Mismatched '}' at position " ,endbrace);
1307+ fi ;
1308+ od ;
1309+
1310+ if nextbrace = fail then
1311+ # In this case, endbrace = fail, or we would not have left
1312+ # previous while loop
1313+ AppendTo(stream, s{[ pos+ 1 .. len]} );
1314+ return ;
1315+ fi ;
1316+
1317+ AppendTo(stream, s{[ pos+ 1 .. nextbrace- 1 ]} );
1318+
1319+ # If this is {{, then print a { and call 'continue'
1320+ if nextbrace+ 1 <= len and s[ nextbrace+ 1 ] = ' {' then
1321+ AppendTo(stream, " {" );
1322+ pos := nextbrace + 1 ;
1323+ continue ;
1324+ fi ;
1325+
1326+ if endbrace = fail then
1327+ ErrorNoReturn(" Invalid format string, no matching '}' at position " , nextbrace);
1328+ fi ;
1329+
1330+ toprint := splitReplacementField(nextbrace+ 1 ,endbrace- 1 );
1331+
1332+ # Check if we are mixing giving id, and not giving id.
1333+ if (argcounter > 1 and toprint.id <> " " ) or (namedIdUsed and toprint.id = " " ) then
1334+ ErrorNoReturn(" replacement field must either all have an id, or all have no id" );
1335+ fi ;
1336+
1337+ if toprint.id = " " then
1338+ if Length(data) < argcounter then
1339+ ErrorNoReturn(" out of bounds -- used " ,argcounter," replacement fields without id when there are only " ,Length(data), " arguments" );
1340+ fi ;
1341+ var := data[ argcounter] ;
1342+ argcounter := argcounter + 1 ;
1343+ elif Int(toprint.id) <> fail then
1344+ namedIdUsed := true ;
1345+ if Int(toprint.id) < 1 or Int(toprint.id) > Length(data) then
1346+ ErrorNoReturn(" out of bounds -- asked for {" ,Int(toprint.id)," } when there are only " ,Length(data), " arguments" );
1347+ fi ;
1348+ var := data[ Int(toprint.id)] ;
1349+ else
1350+ namedIdUsed := true ;
1351+ if not IsRecord(data[ 1 ] ) then
1352+ ErrorNoReturn(" first data argument must be a record when using {" ,toprint.id," }" );
1353+ fi ;
1354+ if not IsBound (data[ 1 ] .(toprint.id)) then
1355+ ErrorNoReturn(" no record member '" ,toprint[ 1 ] .id," '" );
1356+ fi ;
1357+ var := data[ 1 ] .(toprint.id);
1358+ fi ;
1359+ pos := endbrace;
1360+
1361+ if toprint.format = " s" then
1362+ if not IsString(var) then
1363+ var := String(var);
1364+ fi ;
1365+ AppendTo(stream, var);
1366+ elif toprint.format = " v" then
1367+ AppendTo(stream, ViewString(var));
1368+ elif toprint.format = " d" then
1369+ AppendTo(stream, DisplayString(var));
1370+ else ErrorNoReturn(" Invalid format: '" , toprint.format, " '" );
1371+ fi ;
1372+ od ;
1373+ end );
1374+
1375+ InstallGlobalFunction(StringFormatted, function (s, data... )
1376+ local str;
1377+ if not IsString(s) then
1378+ ErrorNoReturn(" Usage: StringFormatted(<string>, <data>...)" );
1379+ fi ;
1380+ str := " " ;
1381+ CallFuncList(PrintToFormatted, Concatenation([ OutputTextString(str, false ), s] , data));
1382+ return str;
1383+ end );
1384+
1385+ InstallGlobalFunction(PrintFormatted, function (args... )
1386+ # Do some very baic argument checking
1387+ if not Length(args) > 1 and IsString(args[ 1 ] ) then
1388+ ErrorNoReturn(" Usage: PrintFormatted(<string>, <data>...)" );
1389+ fi ;
12631390
1391+ # We can't use PrintTo, as we do not know where Print is currently
1392+ # directed
1393+ Print(CallFuncList(StringFormatted, args));
1394+ end );
12641395
12651396# ############################################################################
12661397# #
0 commit comments