Torque3D Documentation / _generateds / stringFunctions.cpp

stringFunctions.cpp

Engine/source/core/strings/stringFunctions.cpp

More...

Public Typedefs

char
nat_char 

Public Functions

int
dItoa(int n, char s)
dPrintf(const char * format, ... )
dSprintf(char * buffer, U32 bufferSize, const char * format, ... )
dSscanf(const char * buffer, const char * format, ... )
char *
dStrcatl(char * dst, dsize_t dstSize, ... )
dStrcmp(const UTF16 * str1, const UTF16 * str2)
char *
dStrcpyl(char * dst, dsize_t dstSize, ... )
char *
dStrdup_r(const char * src, const char * fileName, dsize_t lineNumber)
bool
dStrEndsWith(const char * str1, const char * str2)

Check if one string ends with another.

bool
dStrEqual(const char * str1, const char * str2)

Safe form of dStrcmp: checks both strings for NULL before comparing.

char *
dStrichr(char * str, char ch)
const char *
dStrichr(const char * str, char ch)
char *
dStripPath(const char * filename)

Strip the path from the input filename.

char *
dStristr(char * str1, const char * str2)
const char *
dStristr(const char * str1, const char * str2)
char *
dStrlwr(char * str)
int
dStrrev(char * str)
bool
dStrStartsWith(const char * str1, const char * str2)

Check if one string starts with another.

char *
dStrupr(char * str)
dVprintf(const char * format, va_list arglist)
dVsprintf(char * buffer, U32 bufferSize, const char * format, va_list arglist)
strnatcmp0(const nat_char * a, const nat_char * b, S32 fold_case)

Detailed Description

Public Typedefs

typedef char nat_char 

Public Functions

compare_left(const nat_char * a, const nat_char * b)

compare_right(const nat_char * a, const nat_char * b)

dItoa(int n, char s)

dPrintf(const char * format, ... )

dSprintf(char * buffer, U32 bufferSize, const char * format, ... )

dSscanf(const char * buffer, const char * format, ... )

dStrcatl(char * dst, dsize_t dstSize, ... )

dStrcmp(const UTF16 * str1, const UTF16 * str2)

dStrcpyl(char * dst, dsize_t dstSize, ... )

dStrdup_r(const char * src, const char * fileName, dsize_t lineNumber)

dStrEndsWith(const char * str1, const char * str2)

Check if one string ends with another.

dStrEqual(const char * str1, const char * str2)

Safe form of dStrcmp: checks both strings for NULL before comparing.

dStrichr(char * str, char ch)

dStrichr(const char * str, char ch)

dStripPath(const char * filename)

Strip the path from the input filename.

dStristr(char * str1, const char * str2)

dStristr(const char * str1, const char * str2)

dStrlwr(char * str)

dStrnatcasecmp(const nat_char * a, const nat_char * b)

dStrnatcmp(const nat_char * a, const nat_char * b)

dStrrev(char * str)

dStrStartsWith(const char * str1, const char * str2)

Check if one string starts with another.

dStrupr(char * str)

dVprintf(const char * format, va_list arglist)

dVsprintf(char * buffer, U32 bufferSize, const char * format, va_list arglist)

nat_isdigit(nat_char a)

nat_isspace(nat_char a)

nat_toupper(nat_char a)

strnatcmp0(const nat_char * a, const nat_char * b, S32 fold_case)

  1
  2//-----------------------------------------------------------------------------
  3// Copyright (c) 2012 GarageGames, LLC
  4//
  5// Permission is hereby granted, free of charge, to any person obtaining a copy
  6// of this software and associated documentation files (the "Software"), to
  7// deal in the Software without restriction, including without limitation the
  8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9// sell copies of the Software, and to permit persons to whom the Software is
 10// furnished to do so, subject to the following conditions:
 11//
 12// The above copyright notice and this permission notice shall be included in
 13// all copies or substantial portions of the Software.
 14//
 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 21// IN THE SOFTWARE.
 22//-----------------------------------------------------------------------------
 23
 24#include <stdarg.h>
 25#include <stdio.h>
 26
 27#include "core/strings/stringFunctions.h"
 28#include "platform/platform.h"
 29
 30
 31#if defined(TORQUE_OS_WIN) || defined(TORQUE_OS_XBOX) || defined(TORQUE_OS_XENON)
 32// This standard function is not defined when compiling with VC7...
 33#define vsnprintf _vsnprintf
 34#endif
 35
 36
 37//-----------------------------------------------------------------------------
 38
 39// Original code from: http://sourcefrog.net/projects/natsort/
 40// Somewhat altered here.
 41//TODO: proper UTF8 support; currently only working for single-byte characters
 42
 43/* -*- mode: c; c-file-style: "k&r" -*-
 44
 45  strnatcmp.c -- Perform 'natural order' comparisons of strings in C.
 46  Copyright (C) 2000, 2004 by Martin Pool <mbp sourcefrog net>
 47
 48  This software is provided 'as-is', without any express or implied
 49  warranty.  In no event will the authors be held liable for any damages
 50  arising from the use of this software.
 51
 52  Permission is granted to anyone to use this software for any purpose,
 53  including commercial applications, and to alter it and redistribute it
 54  freely, subject to the following restrictions:
 55
 56  1. The origin of this software must not be misrepresented; you must not
 57     claim that you wrote the original software. If you use this software
 58     in a product, an acknowledgment in the product documentation would be
 59     appreciated but is not required.
 60  2. Altered source versions must be plainly marked as such, and must not be
 61     misrepresented as being the original software.
 62  3. This notice may not be removed or altered from any source distribution.
 63*/
 64
 65
 66/* partial change history:
 67 *
 68 * 2004-10-10 mbp: Lift out character type dependencies into macros.
 69 *
 70 * Eric Sosman pointed out that ctype functions take a parameter whose
 71 * value must be that of an unsigned int, even on platforms that have
 72 * negative chars in their default char type.
 73 */
 74
 75typedef char nat_char;
 76
 77/* These are defined as macros to make it easier to adapt this code to
 78 * different characters types or comparison functions. */
 79static inline int
 80nat_isdigit( nat_char a )
 81{
 82   return dIsdigit( a );
 83}
 84
 85
 86static inline int
 87nat_isspace( nat_char a )
 88{
 89   return dIsspace( a );
 90}
 91
 92
 93static inline nat_char
 94nat_toupper( nat_char a )
 95{
 96   return dToupper( a );
 97}
 98
 99
100
101static S32
102compare_right(const nat_char* a, const nat_char* b)
103{
104   S32 bias = 0;
105
106   /* The longest run of digits wins.  That aside, the greatest
107   value wins, but we can't know that it will until we've scanned
108   both numbers to know that they have the same magnitude, so we
109   remember it in BIAS. */
110   for (;; a++, b++) {
111      if (!nat_isdigit(*a)  &&  !nat_isdigit(*b))
112         break;
113      else if (!nat_isdigit(*a))
114         return -1;
115      else if (!nat_isdigit(*b))
116         return +1;
117      else if (*a < *b) {
118         if (!bias)
119            bias = -1;
120      } else if (*a > *b) {
121         if (!bias)
122            bias = +1;
123      } else if (!*a  &&  !*b)
124         return bias;
125   }
126
127   return bias;
128}
129
130
131static int
132compare_left(const nat_char* a, const nat_char* b)
133{
134   /* Compare two left-aligned numbers: the first to have a
135   different value wins. */
136   for (;; a++, b++) {
137      if (!nat_isdigit(*a)  &&  !nat_isdigit(*b))
138         break;
139      else if (!nat_isdigit(*a))
140         return -1;
141      else if (!nat_isdigit(*b))
142         return +1;
143      else if (*a < *b)
144         return -1;
145      else if (*a > *b)
146         return +1;
147   }
148
149   return 0;
150}
151
152
153static S32 strnatcmp0(const nat_char* a, const nat_char* b, S32 fold_case)
154{
155   S32 ai, bi;
156   nat_char ca, cb;
157   S32 fractional, result;
158
159   ai = bi = 0;
160   while (1) {
161      ca = a[ai]; cb = b[bi];
162
163      /* skip over leading spaces or zeros */
164      while (nat_isspace(ca))
165         ca = a[++ai];
166
167      while (nat_isspace(cb))
168         cb = b[++bi];
169
170      /* process run of digits */
171      if (nat_isdigit(ca)  &&  nat_isdigit(cb)) {
172         fractional = (ca == '0' || cb == '0');
173
174         if (fractional) {
175            if ((result = compare_left(a+ai, b+bi)) != 0)
176               return result;
177         } else {
178            if ((result = compare_right(a+ai, b+bi)) != 0)
179               return result;
180         }
181      }
182
183      if (!ca && !cb) {
184         /* The strings compare the same.  Perhaps the caller
185         will want to call strcmp to break the tie. */
186         return 0;
187      }
188
189      if (fold_case) {
190         ca = nat_toupper(ca);
191         cb = nat_toupper(cb);
192      }
193
194      if (ca < cb)
195         return -1;
196      else if (ca > cb)
197         return +1;
198
199      ++ai; ++bi;
200   }
201}
202
203
204S32 dStrnatcmp(const nat_char* a, const nat_char* b) {
205   return strnatcmp0(a, b, 0);
206}
207
208
209/* Compare, recognizing numeric string and ignoring case. */
210S32 dStrnatcasecmp(const nat_char* a, const nat_char* b) {
211   return strnatcmp0(a, b, 1);
212}
213
214//------------------------------------------------------------------------------
215// non-standard string functions
216
217char *dStrdup_r(const char *src, const char *fileName, dsize_t lineNumber)
218{
219   char *buffer = (char *) dMalloc_r(dStrlen(src) + 1, fileName, lineNumber);
220   dStrcpy(buffer, src);
221   return buffer;
222}
223
224char* dStrichr( char* str, char ch )
225{
226   AssertFatal( str != NULL, "dStrichr - NULL string" );
227   
228   if( !ch )
229      return dStrchr( str, ch ); 
230   
231   char c = dToupper( ch );
232   while( *str )
233   {
234      if( dToupper( *str ) == c )
235         return str;
236         
237      ++ str;
238   }
239         
240   return NULL;
241}
242
243const char* dStrichr( const char* str, char ch )
244{
245   AssertFatal( str != NULL, "dStrichr - NULL string" );
246   
247   if( !ch )
248      return dStrchr( str, ch ); 
249
250   char c = dToupper( ch );
251   while( *str )
252   {
253      if( dToupper( *str ) == c )
254         return str;
255         
256      ++ str;
257   }
258      
259   return NULL;
260}
261
262// concatenates a list of src's onto the end of dst
263// the list of src's MUST be terminated by a NULL parameter
264// dStrcatl(dst, sizeof(dst), src1, src2, NULL);
265char* dStrcatl(char *dst, dsize_t dstSize, ...)
266{
267   const char* src = NULL;
268   char *p = dst;
269
270   AssertFatal(dstSize > 0, "dStrcatl: destination size is set zero");
271   dstSize--;  // leave room for string termination
272
273   // find end of dst
274   while (dstSize && *p++)
275      dstSize--;
276
277   va_list args;
278   va_start(args, dstSize);
279
280   // concatenate each src to end of dst
281   while ( (src = va_arg(args, const char*)) != NULL )
282   {
283      while( dstSize && *src )
284      {
285         *p++ = *src++;
286         dstSize--;
287      }
288   }
289
290   va_end(args);
291
292   // make sure the string is terminated
293   *p = 0;
294
295   return dst;
296}
297
298
299// copy a list of src's into dst
300// the list of src's MUST be terminated by a NULL parameter
301// dStrccpyl(dst, sizeof(dst), src1, src2, NULL);
302char* dStrcpyl(char *dst, dsize_t dstSize, ...)
303{
304   const char* src = NULL;
305   char *p = dst;
306
307   AssertFatal(dstSize > 0, "dStrcpyl: destination size is set zero");
308   dstSize--;  // leave room for string termination
309
310   va_list args;
311   va_start(args, dstSize);
312
313   // concatenate each src to end of dst
314   while ( (src = va_arg(args, const char*)) != NULL )
315   {
316      while( dstSize && *src )
317      {
318         *p++ = *src++;
319         dstSize--;
320      }
321   }
322
323   va_end(args);
324
325   // make sure the string is terminated
326   *p = 0;
327
328   return dst;
329}
330
331
332S32 dStrcmp( const UTF16 *str1, const UTF16 *str2)
333{
334#if defined(TORQUE_OS_WIN) || defined(TORQUE_OS_XBOX) || defined(TORQUE_OS_XENON)
335   return wcscmp( reinterpret_cast<const wchar_t *>( str1 ), reinterpret_cast<const wchar_t *>( str2 ) );
336#else
337   S32 ret;
338   const UTF16 *a, *b;
339   a = str1;
340   b = str2;
341
342   while( ((ret = *a - *b) == 0) && *a && *b )
343      a++, b++;
344
345   return ret;
346#endif
347}  
348
349char* dStrupr(char *str)
350{
351#if defined(TORQUE_OS_WIN) || defined(TORQUE_OS_XBOX) || defined(TORQUE_OS_XENON)
352   return _strupr(str);
353#else
354   if (str == NULL)
355      return(NULL);
356
357   char* saveStr = str;
358   while (*str)
359   {
360      *str = toupper(*str);
361      str++;
362   }
363   return saveStr;
364#endif
365}
366
367char* dStrlwr(char *str)
368{
369#if defined(TORQUE_OS_WIN) || defined(TORQUE_OS_XBOX) || defined(TORQUE_OS_XENON)
370   return _strlwr(str);
371#else
372   if (str == NULL)
373      return(NULL);
374
375   char* saveStr = str;
376   while (*str)
377   {
378      *str = tolower(*str);
379      str++;
380   }
381   return saveStr;
382#endif
383}
384
385//------------------------------------------------------------------------------
386// standard I/O functions
387
388void dPrintf(const char *format, ...)
389{
390   va_list args;
391   va_start(args, format);
392   vprintf(format, args);
393   va_end(args);
394}
395
396S32 dVprintf(const char *format, va_list arglist)
397{
398   return (S32)vprintf(format, arglist);
399}
400
401S32 dSprintf(char *buffer, U32 bufferSize, const char *format, ...)
402{
403   va_list args;
404   va_start(args, format);
405
406   S32 len = vsnprintf(buffer, bufferSize, format, args);
407   va_end(args);
408
409   AssertWarn( len < bufferSize, "Buffer too small in call to dSprintf!" );
410
411   return (len);
412}
413
414
415S32 dVsprintf(char *buffer, U32 bufferSize, const char *format, va_list arglist)
416{
417   S32 len = vsnprintf(buffer, bufferSize, format, arglist);
418   
419   AssertWarn( len < bufferSize, "Buffer too small in call to dVsprintf!" );
420
421   return (len);
422}
423
424
425S32 dSscanf(const char *buffer, const char *format, ...)
426{
427#if defined(TORQUE_OS_WIN) || defined(TORQUE_OS_XBOX) || defined(TORQUE_OS_XENON)
428   va_list args;
429   va_start(args, format);
430
431   // Boy is this lame.  We have to scan through the format string, and find out how many
432   //  arguments there are.  We'll store them off as void*, and pass them to the sscanf
433   //  function through specialized calls.  We're going to have to put a cap on the number of args that
434   //  can be passed, 8 for the moment.  Sigh.
435   static void* sVarArgs[20];
436   U32 numArgs = 0;
437
438   for (const char* search = format; *search != '\0'; search++) {
439      if (search[0] == '%' && search[1] != '%')
440         numArgs++;
441   }
442   AssertFatal(numArgs <= 20, "Error, too many arguments to lame implementation of dSscanf.  Fix implmentation");
443
444   // Ok, we have the number of arguments...
445   for (U32 i = 0; i < numArgs; i++)
446      sVarArgs[i] = va_arg(args, void*);
447   va_end(args);
448
449   switch (numArgs) {
450     case 0: return 0;
451     case 1:  return sscanf(buffer, format, sVarArgs[0]);
452     case 2:  return sscanf(buffer, format, sVarArgs[0], sVarArgs[1]);
453     case 3:  return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2]);
454     case 4:  return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3]);
455     case 5:  return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4]);
456     case 6:  return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5]);
457     case 7:  return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6]);
458     case 8:  return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7]);
459     case 9:  return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8]);
460     case 10: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9]);
461     case 11: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10]);
462     case 12: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11]);
463     case 13: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11], sVarArgs[12]);
464     case 14: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11], sVarArgs[12], sVarArgs[13]);
465     case 15: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11], sVarArgs[12], sVarArgs[13], sVarArgs[14]);
466     case 16: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11], sVarArgs[12], sVarArgs[13], sVarArgs[14], sVarArgs[15]);
467     case 17: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11], sVarArgs[12], sVarArgs[13], sVarArgs[14], sVarArgs[15], sVarArgs[16]);
468     case 18: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11], sVarArgs[12], sVarArgs[13], sVarArgs[14], sVarArgs[15], sVarArgs[16], sVarArgs[17]);
469     case 19: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11], sVarArgs[12], sVarArgs[13], sVarArgs[14], sVarArgs[15], sVarArgs[16], sVarArgs[17], sVarArgs[18]);
470     case 20: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11], sVarArgs[12], sVarArgs[13], sVarArgs[14], sVarArgs[15], sVarArgs[16], sVarArgs[17], sVarArgs[18], sVarArgs[19]);
471   }
472   return 0;
473#else
474   va_list args;
475   va_start(args, format);
476   S32 res = vsscanf(buffer, format, args);
477   va_end(args);
478   return res;
479#endif
480}
481
482/// Safe form of dStrcmp: checks both strings for NULL before comparing
483bool dStrEqual(const char* str1, const char* str2)
484{
485   if (!str1 || !str2)
486      return false;
487   else
488      return (dStrcmp(str1, str2) == 0);
489}
490
491/// Check if one string starts with another
492bool dStrStartsWith(const char* str1, const char* str2)
493{
494   return !dStrnicmp(str1, str2, dStrlen(str2));
495}
496
497/// Check if one string ends with another
498bool dStrEndsWith(const char* str1, const char* str2)
499{
500   const char *p = str1 + dStrlen(str1) - dStrlen(str2);
501   return ((p >= str1) && !dStricmp(p, str2));
502}
503
504/// Strip the path from the input filename
505char* dStripPath(const char* filename)
506{
507   const char* itr = filename + dStrlen(filename);
508   while(--itr != filename) {
509      if (*itr == '/' || *itr == '\\') {
510         itr++;
511         break;
512      }
513   }
514   return dStrdup(itr);
515}
516
517char* dStristr( char* str1, const char* str2 )
518{
519   if( !str1 || !str2 )
520      return NULL;
521
522   // Slow but at least we have it.
523
524   U32 str2len = strlen( str2 );
525   while( *str1 )
526   {
527      if( strncasecmp( str1, str2, str2len ) == 0 )
528         return str1;
529
530      ++ str1;
531   }
532
533   return NULL;
534}
535
536const char* dStristr( const char* str1, const char* str2 )
537{
538   return dStristr( const_cast< char* >( str1 ), str2 );
539}
540
541int dStrrev(char* str)
542{
543   int l=<a href="/coding/file/stringfunctions_8h/#stringfunctions_8h_1ab08bdbead2d56aa91fb39c1d4aa3a436">dStrlen</a>(str)-1; //get the string length
544   for(int x=0;x < l;x++,l--)
545   {
546      str[x]^=str[l];  //triple XOR Trick
547      str[l]^=str[x];  //for not using a temp
548      str[x]^=str[l];
549   }
550   return l;
551}
552
553int dItoa(int n, char s[])
554{
555   int i, sign;
556
557   if ((sign = n) < 0)  /* record sign */
558      n = -n;          /* make n positive */
559   i = 0;
560   do {       /* generate digits in reverse order */
561      s[i++] = n % 10 + '0';   /* get next digit */
562   } while ((n /= 10) > 0);     /* delete it */
563   if (sign < 0)
564      s[i++] = '-';
565   s[i] = '\0';
566   dStrrev(s);
567   return dStrlen(s);
568}
569