fix sprintf to take care of %%
[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_string.h"
31 #include "os_wtoi.h"
32 #include "os_string.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             pToken[iToken].outputType = InternalType::ScilabNull;
121
122             //identify destination type
123             //format : %[flags][width][.precision][length]specifier
124             //pToken[iToken].pwstToken
125
126             //find %
127             wchar_t* pwstPercent = wcsstr(pToken[iToken].pwstToken, L"%");
128             if (pwstPercent != NULL && bPercentPercent == false)
129             {
130                 //looking for flags
131                 if (*(pwstPercent + 1) == L'-' ||
132                         *(pwstPercent + 1) == L'+' ||
133                         *(pwstPercent + 1) == L' ' ||
134                         *(pwstPercent + 1) == L'#' ||
135                         *(pwstPercent + 1) == L'0')
136                 {
137                     pwstPercent++;
138                 }
139
140                 //looking for width
141                 pToken[iToken].width = -1;
142                 if (*(pwstPercent + 1) == L'*')
143                 {
144                     pwstPercent++;
145                 }
146                 else
147                 {
148                     //number
149                     if (iswdigit(*(pwstPercent + 1)))
150                     {
151                         pToken[iToken].width = os_wtoi(pwstPercent + 1);
152                         while (iswdigit(*(pwstPercent + 1)))
153                         {
154                             pwstPercent++;
155                         }
156                     }
157                 }
158
159                 //looking for precision
160                 pToken[iToken].prec = -1;
161                 if (*(pwstPercent + 1) == L'.')
162                 {
163                     pwstPercent++;
164
165                     if (iswdigit(*(pwstPercent + 1)))
166                     {
167                         pToken[iToken].prec = os_wtoi(pwstPercent + 1);
168                         while (iswdigit(*(pwstPercent + 1)))
169                         {
170                             pwstPercent++;
171                         }
172                     }
173                 }
174
175                 //looking for length
176                 if (*(pwstPercent + 1) == L'h' ||
177                         *(pwstPercent + 1) == L'l' ||
178                         *(pwstPercent + 1) == L'L')
179                 {
180                     pToken[iToken].bLengthFlag = true;
181                     pwstPercent++;
182                 }
183                 else
184                 {
185                     pToken[iToken].bLengthFlag = false;
186                 }
187
188                 wchar_t wcType = *(pwstPercent + 1);
189                 pToken[iToken].typePos = (pwstPercent + 1) - pToken[iToken].pwstToken;
190                 switch (wcType)
191                 {
192                     case L'i' : //integer
193                     case L'd' : //integer
194                     case L'o' : //octal
195                     case L'u' : //unsigned
196                     case L'x' : //hex
197                     case L'X' : //HEX
198                         if (_pArgs[iPosArg].type != InternalType::ScilabDouble)
199                         {
200                             Scierror(999, _("%s: Wrong number of input arguments: data doesn't fit with format.\n"), _pstName);
201                             *_piOutputRows = 0;
202                             return NULL;
203                         }
204
205                         pToken[iToken].outputType = InternalType::ScilabInt32;
206
207                         iPosArg++;
208                         break;
209                     case L'f' : //float
210                     case L'e' : //exp
211                     case L'E' : //EXP
212                     case L'g' : //shorter between float or exp
213                     case L'G' : //shorter between float or EXP
214                         if (_pArgs[iPosArg].type != InternalType::ScilabDouble)
215                         {
216                             Scierror(999, _("%s: Wrong number of input arguments: data doesn't fit with format.\n"), _pstName);
217                             *_piOutputRows = 0;
218                             return NULL;
219                         }
220
221                         pToken[iToken].outputType = InternalType::ScilabDouble;
222                         iPosArg++;
223                         break;
224                     case L's' :
225                     case L'c' :
226                         if (_pArgs[iPosArg].type != InternalType::ScilabString)
227                         {
228                             Scierror(999, _("%s: Wrong number of input arguments: data doesn't fit with format.\n"), _pstName);
229                             *_piOutputRows = 0;
230                             return NULL;
231                         }
232
233                         if (pToken[iToken].bLengthFlag == false)
234                         {
235                             updatel(&(pToken[iToken]));
236                         }
237
238                         pToken[iToken].outputType = InternalType::ScilabString;
239                         iPosArg++;
240                         break;
241                     default :
242                         //houston ...
243                         break;
244                 }
245             }
246
247             pwstStart = pwstEnd;
248             iToken++;
249         }
250
251         FREE(pwstFirstOutput);
252         pwstFirstOutput = NULL;
253
254         int iLoop = 1;
255         int iFirstArg = 1;
256         if (strcmp(_pstName, "mfprintf") == 0)
257         {
258             iFirstArg = 2;
259         }
260
261         if (in.size() > 1)
262         {
263             iLoop = in[iFirstArg]->getAs<GenericType>()->getRows();
264             for (int i = iFirstArg + 1 ; i < in.size() ; i++)
265             {
266                 iLoop = std::min(iLoop, in[i]->getAs<GenericType>()->getRows());
267             }
268         }
269
270         if (*_piNewLine || (*_piOutputRows) > 1)
271         {
272             (*_piOutputRows) *= iLoop;
273         }
274
275         std::wostringstream oFirstOutput;
276         for (int j = 0 ; j < iLoop ; j++)
277         {
278             //copy the 0th token
279             oFirstOutput << pToken[0].pwstToken;
280             iPosArg = 0;
281             //start at 1, the 0th is always without %
282             for (int i = 1 ; i < _iArgsCount + 1 ; i++)
283             {
284                 void* pvVal = NULL;
285                 if (pToken[i].outputType == InternalType::ScilabDouble)
286                 {
287                     wchar_t pwstTemp[bsiz];
288                     double dblVal = in[_pArgs[iPosArg].iArg]->getAs<Double>()->get(j, _pArgs[iPosArg].iPos);
289
290                     if (finite(dblVal))
291                     {
292                         os_swprintf(pwstTemp, bsiz, pToken[i].pwstToken, dblVal);
293                     }
294                     else
295                     {
296                         wchar_t* newToken = addl(&pToken[i]);
297                         if (ISNAN(dblVal))
298                         {
299                             os_swprintf(pwstTemp, bsiz, newToken, NanString);
300                         }
301                         else if (std::signbit(dblVal))
302                         {
303                             os_swprintf(pwstTemp, bsiz, newToken, NegInfString);
304                         }
305                         else
306                         {
307                             os_swprintf(pwstTemp, bsiz, newToken, InfString);
308                         }
309
310                         delete[] newToken;
311                     }
312                     iPosArg++;
313                     oFirstOutput << pwstTemp;
314                 }
315                 else if (pToken[i].outputType == InternalType::ScilabInt32)
316                 {
317                     wchar_t pwstTemp[bsiz];
318                     double dblVal = in[_pArgs[iPosArg].iArg]->getAs<Double>()->get(j, _pArgs[iPosArg].iPos);
319                     os_swprintf(pwstTemp, bsiz, pToken[i].pwstToken, (int)dblVal);
320                     iPosArg++;
321                     oFirstOutput << pwstTemp;
322                 }
323                 else if (pToken[i].outputType == InternalType::ScilabString)
324                 {
325                     wchar_t* pwstStr = NULL;
326
327                     if (in[iPosArg + 1]->isDouble() && ISNAN(in[iPosArg + 1]->getAs<types::Double>()->get(0)))
328                     {
329                         pwstStr = NanString;
330                     }
331                     else if (in[iPosArg + 1]->isDouble() && finite(in[iPosArg + 1]->getAs<types::Double>()->get(0)) == false)
332                     {
333                         if (std::signbit(in[iPosArg + 1]->getAs<types::Double>()->get(0)))
334                         {
335                             pwstStr = NegInfString;
336                         }
337                         else
338                         {
339                             pwstStr = InfString;
340                         }
341                     }
342                     else
343                     {
344                         pwstStr = in[_pArgs[iPosArg].iArg]->getAs<types::String>()->get(j, _pArgs[iPosArg].iPos);
345                     }
346
347                     int posC = (int)wcscspn(pToken[i].pwstToken, L"c");
348                     int posS = (int)wcscspn(pToken[i].pwstToken, L"s");
349
350                     if (posS == 0 || posC == 0)
351                     {
352                         *_piOutputRows = 0;
353                         return NULL;
354                     }
355
356                     bool bC = posC < posS;
357                     int len = 1;
358                     if (pToken[i].prec == -1)
359                     {
360                         if (bC == false)
361                         {
362                             len = (int)wcslen(pwstStr);
363                         }
364                     }
365                     else
366                     {
367                         if (bC == false)
368                         {
369                             len = std::min(pToken[i].prec, (int)wcslen(pwstStr));
370                         }
371                     }
372
373                     int tokenLen = (int)wcslen(pToken[i].pwstToken);
374                     len += tokenLen;
375                     len = std::max(len, pToken[i].width);
376                     //add len of string after token like "%20s>>>" add space for ">>>"
377                     len += (tokenLen - (bC ? posC : posS));
378                     wchar_t* pwstTemp = (wchar_t*)MALLOC((len + 1) * sizeof(wchar_t));
379
380                     if (bC)
381                     {
382                         os_swprintf(pwstTemp, len + 1, pToken[i].pwstToken, pwstStr[0]);
383                     }
384                     else
385                     {
386                         os_swprintf(pwstTemp, len + 1, pToken[i].pwstToken, pwstStr);
387                     }
388
389                     oFirstOutput << pwstTemp;
390                     FREE(pwstTemp);
391                     iPosArg++;
392                 }
393                 else
394                 {
395                     // management of %%
396                     oFirstOutput << pToken[i].pwstToken;
397                 }
398             }
399         }
400
401         pwstFirstOutput = os_wcsdup((wchar_t*)oFirstOutput.str().c_str());
402     }
403
404     pwstOutput = (wchar_t**)MALLOC((*_piOutputRows) * sizeof(wchar_t*));
405
406     size_t iLen = wcslen(pwstFirstOutput);
407     int iStart = 0;
408     int j = 0;
409     for (int i = 0 ; i < iLen ; i++)
410     {
411         if (pwstFirstOutput[i] == L'\n')
412         {
413             int iSize = i - iStart;
414             pwstOutput[j] = (wchar_t*)MALLOC(sizeof(wchar_t) * (iSize + 1));
415             wcsncpy(pwstOutput[j], pwstFirstOutput + iStart, iSize);
416             pwstOutput[j][iSize] = L'\0';
417             iStart = i + 1;
418             j++;
419         }
420     }
421
422     if (j == (*_piOutputRows) - 1)
423     {
424         pwstOutput[j] = os_wcsdup(pwstFirstOutput + iStart);
425     }
426
427     FREE(pwstFirstOutput);
428     return pwstOutput;
429 }
430 /*--------------------------------------------------------------------------*/
431 // replace "\\n" "\\r" "\\t" "\\r\\n" by '\n' '\r' '\t' '\n'
432 // count number of lines
433 // indicate if one '\n' is at the end of string
434 static wchar_t* replaceAndCountLines(const wchar_t* _pwstInput, int* _piLines, int* _piNewLine)
435 {
436     size_t iInputLen = wcslen(_pwstInput);
437     wchar_t* pwstFirstOutput = (wchar_t*)MALLOC(sizeof(wchar_t) * (iInputLen + 1));
438
439     int iPos = 0;
440     *_piLines = 1;
441
442     for (int i = 0 ; i < iInputLen ; i++)
443     {
444         if (_pwstInput[i] == L'\\')
445         {
446             if (iInputLen == i + 1)
447             {
448                 continue;
449             }
450
451             switch (_pwstInput[i + 1])
452             {
453                 case L'n' :
454                     pwstFirstOutput[iPos++] = L'\n';
455                     (*_piLines)++;
456                     i++;
457                     break;
458                 case L'r' :
459                     if (iInputLen > i + 3 && _pwstInput[i + 2] == L'\\' && _pwstInput[i + 3] == L'n')
460                     {
461                         pwstFirstOutput[iPos++] = L'\n';
462                         (*_piLines)++;
463                         i += 3;
464                     }
465                     else
466                     {
467                         pwstFirstOutput[iPos++] = L'\r';
468                         i++;
469                     }
470                     break;
471                 case L't' :
472                     pwstFirstOutput[iPos++] = L'\t';
473                     i++;
474                     break;
475                 case L'\\':
476                     pwstFirstOutput[iPos++] = L'\\';
477                     i++;
478                     break;
479                 default:
480                     break;
481             }
482         }
483         else
484         {
485             pwstFirstOutput[iPos++] = _pwstInput[i];
486         }
487     }
488
489     // do not count '\n' if it's at the end of string
490     // it will be manage by piNewLine
491     if (pwstFirstOutput[iPos - 1] == '\n')
492     {
493         (*_piLines)--;
494         (*_piNewLine) = 1;
495     }
496
497     pwstFirstOutput[iPos] = 0;
498     return pwstFirstOutput;
499 }
500 /*--------------------------------------------------------------------------*/
501 wchar_t* addl(TokenDef* token)
502 {
503     //replace %s or %c by %ls or %lc to wide char compatibility
504     int iPos = token->typePos;
505     int sizeTotal = (int)wcslen(token->pwstToken);
506     wchar_t* pwstToken = new wchar_t[sizeTotal + 2];
507
508     wcsncpy(pwstToken, token->pwstToken, iPos);
509     pwstToken[iPos] = L'l';
510     pwstToken[iPos + 1] = L's';
511     wcsncpy(&pwstToken[iPos + 2], token->pwstToken + iPos + 1, sizeTotal - (iPos + 1));
512     pwstToken[sizeTotal + 1]  = L'\0';
513
514     return pwstToken;
515 }
516 /*--------------------------------------------------------------------------*/
517 void updatel(TokenDef* token)
518 {
519     wchar_t* newToken = addl(token);
520     delete[] token->pwstToken;
521     token->pwstToken = newToken;
522 }