improve sprintf on %s and %c
[scilab.git] / scilab / modules / output_stream / src / cpp / scilab_sprintf.cpp
1 /*
2  *  Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  *  Copyright (C) 2010 - DIGITEO - Antoine ELIAS
4  *  Copyright (C) 2013 - Scilab Enterprises - Calixte DENIZET
5  *  Copyright (C) 2013 - Scilab Enterprises - Cedric Delamarre
6  *
7  *  This file must be used under the terms of the CeCILL.
8  *  This source file is licensed as described in the file COPYING, which
9  *  you should have received as part of this distribution.  The terms
10  *  are also available at
11  *  http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt
12  *
13  */
14
15 #include <stdio.h>
16 #include <cmath>
17 #include "types.hxx"
18 #include "double.hxx"
19 #include "string.hxx"
20 #include "scilab_sprintf.hxx"
21
22 using namespace types;
23
24 extern "C"
25 {
26 #include "Scierror.h"
27 #include "sci_malloc.h"
28 #include "localization.h"
29 #include "charEncoding.h"
30 #include "os_wcsdup.h"
31 #include "os_wtoi.h"
32 #include "os_swprintf.h"
33 }
34
35 #define NanString L"Nan"
36 #define InfString L"Inf"
37 #define NegInfString L"-Inf"
38
39 static wchar_t* replaceAndCountLines(const wchar_t* _pwstInput, int* _piLines, int* _piNewLine);
40 static wchar_t* addl(TokenDef* token);
41 static void updatel(TokenDef* token);
42 wchar_t** scilab_sprintf(const char* _pstName, const wchar_t* _pwstInput, typed_list &in, ArgumentPosition* _pArgs, int _iArgsCount, int* _piOutputRows, int* _piNewLine)
43 {
44     /* Force Windows display to have two-digit exponent. */
45 #ifdef _MSC_VER
46     _set_output_format(_TWO_DIGIT_EXPONENT);
47 #endif
48     wchar_t** pwstOutput = NULL;
49     wchar_t* pwstFirstOutput = NULL;
50     *_piNewLine = 0;
51     size_t pos = 0;
52
53     //\n \n\r \r \t to string
54     //find number of lines
55     // replace \\n \\t... by \n \t...
56     pwstFirstOutput = replaceAndCountLines(_pwstInput, _piOutputRows, _piNewLine);
57
58     //no arg, just return _pwstInput value
59     if (_iArgsCount)
60     {
61         //store all sub parts of the input string
62         //for the input string "bla1 %s bla2 %d bla3"
63         //store  :
64         //pwstToken[0] : "bla1 "
65         //pwstToken[1] : "%s bla2 "
66         //pwstToken[2] : "%d bla3"
67
68         size_t iStart = 0;
69         size_t iEnd = 0;
70         int iToken = 0;
71         int iPosArg = 0;
72
73         TokenDef* pToken = new TokenDef[_iArgsCount + 1];
74         wchar_t* pwstStart = pwstFirstOutput;
75
76         bool bFinish = false;
77         bool bPercentPercent = false;
78
79         while (!bFinish)
80         {
81             wchar_t* pwstEnd = wcsstr(pwstStart + (iToken == 0 ? 0 : 1), L"%");
82             iStart = pwstStart - pwstFirstOutput;
83             bPercentPercent = false;
84             if (pwstEnd != NULL)
85             {
86                 if (iToken && pwstStart[1] == L'%')
87                 {
88                     //manage "%%"
89                     pwstEnd = wcsstr(pwstEnd + 1, L"%");
90                     if (pwstEnd == NULL)
91                     {
92                         //end of string
93                         iEnd = wcslen(pwstFirstOutput);
94                         bFinish = true;
95                     }
96                     else
97                     {
98                         iEnd = pwstEnd - pwstFirstOutput;
99                     }
100
101                     // skip the first %
102                     iStart++;
103                     bPercentPercent = true;
104                 }
105                 else
106                 {
107                     iEnd = pwstEnd - pwstFirstOutput;
108                 }
109             }
110             else
111             {
112                 //end of string
113                 iEnd = wcslen(pwstFirstOutput);
114                 bFinish = true;
115             }
116
117             pToken[iToken].pwstToken = new wchar_t[iEnd - iStart + 1];
118             wcsncpy(pToken[iToken].pwstToken, pwstFirstOutput + iStart, iEnd - iStart);
119             pToken[iToken].pwstToken[iEnd - iStart] = L'\0';
120
121             //identify destination type
122             //format : %[flags][width][.precision][length]specifier
123             //pToken[iToken].pwstToken
124
125             //find %
126             wchar_t* pwstPercent = wcsstr(pToken[iToken].pwstToken, L"%");
127             if (pwstPercent != NULL && bPercentPercent == false)
128             {
129                 //looking for flags
130                 if (*(pwstPercent + 1) == L'-' ||
131                         *(pwstPercent + 1) == L'+' ||
132                         *(pwstPercent + 1) == L' ' ||
133                         *(pwstPercent + 1) == L'#' ||
134                         *(pwstPercent + 1) == L'0')
135                 {
136                     pwstPercent++;
137                 }
138
139                 //looking for width
140                 pToken[iToken].width = -1;
141                 if (*(pwstPercent + 1) == L'*')
142                 {
143                     pwstPercent++;
144                 }
145                 else
146                 {
147                     //number
148                     if (iswdigit(*(pwstPercent + 1)))
149                     {
150                         pToken[iToken].width = os_wtoi(pwstPercent + 1);
151                         while (iswdigit(*(pwstPercent + 1)))
152                         {
153                             pwstPercent++;
154                         }
155                     }
156                 }
157
158                 //looking for precision
159                 pToken[iToken].prec = -1;
160                 if (*(pwstPercent + 1) == L'.')
161                 {
162                     pwstPercent++;
163
164                     if (iswdigit(*(pwstPercent + 1)))
165                     {
166                         pToken[iToken].prec = os_wtoi(pwstPercent + 1);
167                         while (iswdigit(*(pwstPercent + 1)))
168                         {
169                             pwstPercent++;
170                         }
171                     }
172                 }
173
174                 //looking for length
175                 if (*(pwstPercent + 1) == L'h' ||
176                         *(pwstPercent + 1) == L'l' ||
177                         *(pwstPercent + 1) == L'L')
178                 {
179                     pToken[iToken].bLengthFlag = true;
180                     pwstPercent++;
181                 }
182                 else
183                 {
184                     pToken[iToken].bLengthFlag = false;
185                 }
186
187                 wchar_t wcType = *(pwstPercent + 1);
188                 pToken[iToken].typePos = (pwstPercent + 1) - pToken[iToken].pwstToken;
189                 switch (wcType)
190                 {
191                     case L'i' : //integer
192                     case L'd' : //integer
193                     case L'o' : //octal
194                     case L'u' : //unsigned
195                     case L'x' : //hex
196                     case L'X' : //HEX
197                         if (_pArgs[iPosArg].type != InternalType::ScilabDouble)
198                         {
199                             Scierror(999, _("%s: Wrong number of input arguments: data doesn't fit with format.\n"), _pstName);
200                             *_piOutputRows = 0;
201                             return NULL;
202                         }
203
204                         pToken[iToken].outputType = InternalType::ScilabInt32;
205
206                         iPosArg++;
207                         break;
208                     case L'f' : //float
209                     case L'e' : //exp
210                     case L'E' : //EXP
211                     case L'g' : //shorter between float or exp
212                     case L'G' : //shorter between float or EXP
213                         if (_pArgs[iPosArg].type != InternalType::ScilabDouble)
214                         {
215                             Scierror(999, _("%s: Wrong number of input arguments: data doesn't fit with format.\n"), _pstName);
216                             *_piOutputRows = 0;
217                             return NULL;
218                         }
219
220                         pToken[iToken].outputType = InternalType::ScilabDouble;
221                         iPosArg++;
222                         break;
223                     case L's' :
224                     case L'c' :
225                         if (_pArgs[iPosArg].type != InternalType::ScilabString)
226                         {
227                             Scierror(999, _("%s: Wrong number of input arguments: data doesn't fit with format.\n"), _pstName);
228                             *_piOutputRows = 0;
229                             return NULL;
230                         }
231
232                         if (pToken[iToken].bLengthFlag == false)
233                         {
234                             updatel(&(pToken[iToken]));
235                         }
236
237                         pToken[iToken].outputType = InternalType::ScilabString;
238                         iPosArg++;
239                         break;
240                     default :
241                         //houston ...
242                         break;
243                 }
244             }
245
246             pwstStart = pwstEnd;
247             iToken++;
248         }
249
250         FREE(pwstFirstOutput);
251         pwstFirstOutput = NULL;
252
253         int iLoop = 1;
254         int iFirstArg = 1;
255         if (strcmp(_pstName, "mfprintf") == 0)
256         {
257             iFirstArg = 2;
258         }
259
260         if (in.size() > 1)
261         {
262             iLoop = in[iFirstArg]->getAs<GenericType>()->getRows();
263             for (int i = iFirstArg + 1 ; i < in.size() ; i++)
264             {
265                 iLoop = std::min(iLoop, in[i]->getAs<GenericType>()->getRows());
266             }
267         }
268
269         if (*_piNewLine || (*_piOutputRows) > 1)
270         {
271             (*_piOutputRows) *= iLoop;
272         }
273
274         std::wostringstream oFirstOutput;
275         for (int j = 0 ; j < iLoop ; j++)
276         {
277             //copy the 0th token
278             oFirstOutput << pToken[0].pwstToken;
279             iPosArg = 0;
280             //start at 1, the 0th is always without %
281             for (int i = 1 ; i < _iArgsCount + 1 ; i++)
282             {
283                 void* pvVal = NULL;
284                 if (pToken[i].outputType == InternalType::ScilabDouble)
285                 {
286                     wchar_t pwstTemp[bsiz];
287                     double dblVal = in[_pArgs[iPosArg].iArg]->getAs<Double>()->get(j, _pArgs[iPosArg].iPos);
288
289                     if (finite(dblVal))
290                     {
291                         os_swprintf(pwstTemp, bsiz, pToken[i].pwstToken, dblVal);
292                     }
293                     else
294                     {
295                         wchar_t* newToken = addl(&pToken[i]);
296                         if (ISNAN(dblVal))
297                         {
298                             os_swprintf(pwstTemp, bsiz, newToken, NanString);
299                         }
300                         else if (std::signbit(dblVal))
301                         {
302                             os_swprintf(pwstTemp, bsiz, newToken, NegInfString);
303                         }
304                         else
305                         {
306                             os_swprintf(pwstTemp, bsiz, newToken, InfString);
307                         }
308
309                         delete[] newToken;
310                     }
311                     iPosArg++;
312                     oFirstOutput << pwstTemp;
313                 }
314                 else if (pToken[i].outputType == InternalType::ScilabInt32)
315                 {
316                     wchar_t pwstTemp[bsiz];
317                     double dblVal = in[_pArgs[iPosArg].iArg]->getAs<Double>()->get(j, _pArgs[iPosArg].iPos);
318                     os_swprintf(pwstTemp, bsiz, pToken[i].pwstToken, (int)dblVal);
319                     iPosArg++;
320                     oFirstOutput << pwstTemp;
321                 }
322                 else if (pToken[i].outputType == InternalType::ScilabString)
323                 {
324                     wchar_t* pwstStr = NULL;
325
326                     if (in[iPosArg + 1]->isDouble() && ISNAN(in[iPosArg + 1]->getAs<types::Double>()->get(0)))
327                     {
328                         pwstStr = NanString;
329                     }
330                     else if (in[iPosArg + 1]->isDouble() && finite(in[iPosArg + 1]->getAs<types::Double>()->get(0)) == false)
331                     {
332                         if (std::signbit(in[iPosArg + 1]->getAs<types::Double>()->get(0)))
333                         {
334                             pwstStr = NegInfString;
335                         }
336                         else
337                         {
338                             pwstStr = InfString;
339                         }
340                     }
341                     else
342                     {
343                         pwstStr = in[_pArgs[iPosArg].iArg]->getAs<types::String>()->get(j, _pArgs[iPosArg].iPos);
344                     }
345
346                     int posC = (int)wcscspn(pToken[i].pwstToken, L"c");
347                     int posS = (int)wcscspn(pToken[i].pwstToken, L"s");
348
349                     if (posS == 0 || posC == 0)
350                     {
351                         *_piOutputRows = 0;
352                         return NULL;
353                     }
354
355                     bool bC = posC < posS;
356                     int len = 1;
357                     if (pToken[i].prec == -1)
358                     {
359                         if (bC == false)
360                         {
361                             len = (int)wcslen(pwstStr);
362                         }
363                     }
364                     else
365                     {
366                         if (bC == false)
367                         {
368                             len = std::min(pToken[i].prec, (int)wcslen(pwstStr));
369                         }
370                     }
371
372                     len += (int)wcslen(pToken[i].pwstToken);
373                     len = std::max(len, pToken[i].width);
374                     wchar_t* pwstTemp = (wchar_t*)MALLOC((len + 1) * sizeof(wchar_t));
375
376                     if (bC)
377                     {
378                         os_swprintf(pwstTemp, len + 1, pToken[i].pwstToken, pwstStr[0]);
379                     }
380                     else
381                     {
382                         os_swprintf(pwstTemp, len + 1, pToken[i].pwstToken, pwstStr);
383                     }
384
385                     oFirstOutput << pwstTemp;
386                     FREE(pwstTemp);
387                     iPosArg++;
388                 }
389                 else
390                 {
391                     // management of %%
392                     oFirstOutput << pToken[i].pwstToken;
393                 }
394             }
395         }
396
397         pwstFirstOutput = os_wcsdup((wchar_t*)oFirstOutput.str().c_str());
398     }
399
400     pwstOutput = (wchar_t**)MALLOC((*_piOutputRows) * sizeof(wchar_t*));
401
402     size_t iLen = wcslen(pwstFirstOutput);
403     int iStart = 0;
404     int j = 0;
405     for (int i = 0 ; i < iLen ; i++)
406     {
407         if (pwstFirstOutput[i] == L'\n')
408         {
409             int iSize = i - iStart;
410             pwstOutput[j] = (wchar_t*)MALLOC(sizeof(wchar_t) * (iSize + 1));
411             wcsncpy(pwstOutput[j], pwstFirstOutput + iStart, iSize);
412             pwstOutput[j][iSize] = L'\0';
413             iStart = i + 1;
414             j++;
415         }
416     }
417
418     if (j == (*_piOutputRows) - 1)
419     {
420         pwstOutput[j] = os_wcsdup(pwstFirstOutput + iStart);
421     }
422
423     FREE(pwstFirstOutput);
424     return pwstOutput;
425 }
426 /*--------------------------------------------------------------------------*/
427 // replace "\\n" "\\r" "\\t" "\\r\\n" by '\n' '\r' '\t' '\n'
428 // count number of lines
429 // indicate if one '\n' is at the end of string
430 static wchar_t* replaceAndCountLines(const wchar_t* _pwstInput, int* _piLines, int* _piNewLine)
431 {
432     size_t iInputLen = wcslen(_pwstInput);
433     wchar_t* pwstFirstOutput = (wchar_t*)MALLOC(sizeof(wchar_t) * (iInputLen + 1));
434
435     int iPos = 0;
436     *_piLines = 1;
437
438     for (int i = 0 ; i < iInputLen ; i++)
439     {
440         if (_pwstInput[i] == L'\\')
441         {
442             if (iInputLen == i + 1)
443             {
444                 continue;
445             }
446
447             switch (_pwstInput[i + 1])
448             {
449                 case L'n' :
450                     pwstFirstOutput[iPos++] = L'\n';
451                     (*_piLines)++;
452                     i++;
453                     break;
454                 case L'r' :
455                     if (iInputLen > i + 3 && _pwstInput[i + 2] == L'\\' && _pwstInput[i + 3] == L'n')
456                     {
457                         pwstFirstOutput[iPos++] = L'\n';
458                         (*_piLines)++;
459                         i += 3;
460                     }
461                     else
462                     {
463                         pwstFirstOutput[iPos++] = L'\r';
464                         i++;
465                     }
466                     break;
467                 case L't' :
468                     pwstFirstOutput[iPos++] = L'\t';
469                     i++;
470                     break;
471                 case L'\\':
472                     pwstFirstOutput[iPos++] = L'\\';
473                     i++;
474                     break;
475                 default:
476                     break;
477             }
478         }
479         else
480         {
481             pwstFirstOutput[iPos++] = _pwstInput[i];
482         }
483     }
484
485     // do not count '\n' if it's at the end of string
486     // it will be manage by piNewLine
487     if (pwstFirstOutput[iPos - 1] == '\n')
488     {
489         (*_piLines)--;
490         (*_piNewLine) = 1;
491     }
492
493     pwstFirstOutput[iPos] = 0;
494     return pwstFirstOutput;
495 }
496 /*--------------------------------------------------------------------------*/
497 wchar_t* addl(TokenDef* token)
498 {
499     //replace %s or %c by %ls or %lc to wide char compatibility
500     int iPos = token->typePos;
501     int sizeTotal = (int)wcslen(token->pwstToken);
502     wchar_t* pwstToken = new wchar_t[sizeTotal + 2];
503
504     wcsncpy(pwstToken, token->pwstToken, iPos);
505     pwstToken[iPos] = L'l';
506     pwstToken[iPos + 1] = L's';
507     wcsncpy(&pwstToken[iPos + 2], token->pwstToken + iPos + 1, sizeTotal - (iPos + 1));
508     pwstToken[sizeTotal + 1]  = L'\0';
509
510     return pwstToken;
511 }
512 /*--------------------------------------------------------------------------*/
513 void updatel(TokenDef* token)
514 {
515     wchar_t* newToken = addl(token);
516     delete[] token->pwstToken;
517     token->pwstToken = newToken;
518 }