compiler-init/sci18n.c
author David Anderson <dvander@alliedmods.net>
Thu Jul 13 20:03:46 2006 +0000 (2006-07-13)
branchdvander
changeset 1 bf68e1639dba
permissions -rw-r--r--
initial import of compiler test
dvander@1
     1
/*  Codepage translation to Unicode, and UTF-8 support
dvander@1
     2
 *
dvander@1
     3
 *  The translation is based on codepage mapping files that are distributed
dvander@1
     4
 *  by the Unicode consortium, see ftp://ftp.unicode.org/Public/MAPPINGS/.
dvander@1
     5
 *
dvander@1
     6
 *  Character sets with a maximum of 256 codes are translated via a lookup
dvander@1
     7
 *  table (these are Single-Byte Character Sets). Character sets like Shift-JIS
dvander@1
     8
 *  with single-byte characters and multi-byte characters (introduced by a
dvander@1
     9
 *  leader byte) are split into two tables: the 256-entry lookup table for
dvander@1
    10
 *  the single-byte characters and an extended table for the multi-byte
dvander@1
    11
 *  characters. The extended table is allocated dynamically; the lookup table
dvander@1
    12
 *  is allocated statically, so loading SBCS tables cannot fail (if the tables
dvander@1
    13
 *  themselves are valid, of course).
dvander@1
    14
 *
dvander@1
    15
 *  Copyright (c) ITB CompuPhase, 2004-2006
dvander@1
    16
 *
dvander@1
    17
 *  This software is provided "as-is", without any express or implied warranty.
dvander@1
    18
 *  In no event will the authors be held liable for any damages arising from
dvander@1
    19
 *  the use of this software.
dvander@1
    20
 *
dvander@1
    21
 *  Permission is granted to anyone to use this software for any purpose,
dvander@1
    22
 *  including commercial applications, and to alter it and redistribute it
dvander@1
    23
 *  freely, subject to the following restrictions:
dvander@1
    24
 *
dvander@1
    25
 *  1.  The origin of this software must not be misrepresented; you must not
dvander@1
    26
 *      claim that you wrote the original software. If you use this software in
dvander@1
    27
 *      a product, an acknowledgment in the product documentation would be
dvander@1
    28
 *      appreciated but is not required.
dvander@1
    29
 *  2.  Altered source versions must be plainly marked as such, and must not be
dvander@1
    30
 *      misrepresented as being the original software.
dvander@1
    31
 *  3.  This notice may not be removed or altered from any source distribution.
dvander@1
    32
 *
dvander@1
    33
 *  Version: $Id: CODEPAGE.C,v 1.0 2004-02-18 12:13:04+01 thiadmer Exp thiadmer $
dvander@1
    34
 */
dvander@1
    35
#include <assert.h>
dvander@1
    36
#include <stdio.h>
dvander@1
    37
#include <stddef.h>
dvander@1
    38
#include <stdlib.h>
dvander@1
    39
#include <string.h>
dvander@1
    40
#include "sc.h"
dvander@1
    41
dvander@1
    42
#if !defined TRUE
dvander@1
    43
  #define FALSE         0
dvander@1
    44
  #define TRUE          1
dvander@1
    45
#endif
dvander@1
    46
#if !defined _MAX_PATH
dvander@1
    47
  #define _MAX_PATH     250
dvander@1
    48
#endif
dvander@1
    49
#if !defined DIRSEP_CHAR
dvander@1
    50
  #if defined LINUX || defined __FreeBSD__ || defined __OpenBSD__
dvander@1
    51
    #define DIRSEP_CHAR '/'
dvander@1
    52
  #elif defined macintosh
dvander@1
    53
    #define DIRSEP_CHAR ':'
dvander@1
    54
  #else
dvander@1
    55
    #define DIRSEP_CHAR '\\'
dvander@1
    56
  #endif
dvander@1
    57
#endif
dvander@1
    58
dvander@1
    59
#if !defined ELEMENTS
dvander@1
    60
  #define ELEMENTS(array)       (sizeof(array) / sizeof(array[0]))
dvander@1
    61
#endif
dvander@1
    62
dvander@1
    63
#if !defined NO_CODEPAGE
dvander@1
    64
dvander@1
    65
#if !defined MAXCODEPAGE
dvander@1
    66
  #define MAXCODEPAGE   12      /* typically "cp" + 4 digits + ".txt" */
dvander@1
    67
#endif
dvander@1
    68
#define INVALID         0xffffu /* 0xffff and 0xfffe are invalid Unicode characters */
dvander@1
    69
#define LEADBYTE        0xfffeu
dvander@1
    70
dvander@1
    71
struct wordpair {
dvander@1
    72
  unsigned short index;
dvander@1
    73
  wchar_t code;
dvander@1
    74
};
dvander@1
    75
static char cprootpath[_MAX_PATH] = { DIRSEP_CHAR, '\0' };
dvander@1
    76
static wchar_t bytetable[256];
dvander@1
    77
static struct wordpair *wordtable = NULL;
dvander@1
    78
static unsigned wordtablesize = 0;
dvander@1
    79
static unsigned wordtabletop = 0;
dvander@1
    80
dvander@1
    81
dvander@1
    82
/* read in a line delimited by '\r' or '\n'; do NOT store the '\r' or '\n' into
dvander@1
    83
 * the string and ignore empty lines
dvander@1
    84
 * returns 1 for success and 0 for failure
dvander@1
    85
 */
dvander@1
    86
static int cp_readline(FILE *fp,char *string,size_t size)
dvander@1
    87
{
dvander@1
    88
  size_t count=0;
dvander@1
    89
  int c;
dvander@1
    90
  assert(size>1);
dvander@1
    91
  while ((c=fgetc(fp))!=EOF && count<size-1) {
dvander@1
    92
    if (c=='\r' || c=='\n') {
dvander@1
    93
      if (count>0)  /* '\r' or '\n' ends a string */
dvander@1
    94
        break;
dvander@1
    95
      /* if count==0, the line started with a '\r' or '\n', or perhaps line
dvander@1
    96
       * ends in the file are '\r\n' and we read and stopped on the '\r' of
dvander@1
    97
       * the preceding line
dvander@1
    98
       */
dvander@1
    99
    } else {
dvander@1
   100
      string[count++]=(char)c;
dvander@1
   101
    } /* if */
dvander@1
   102
  } /* while */
dvander@1
   103
  string[count]='\0';
dvander@1
   104
  return count>0;
dvander@1
   105
}
dvander@1
   106
dvander@1
   107
/* cp_path() sets the directory where all codepage files must be found (if
dvander@1
   108
 * the parameter to cp_set() specifies a full path, that is used instead).
dvander@1
   109
 * The path is specified into two parts: root and directory; the full path
dvander@1
   110
 * for the codepage direcory is just the concatenation of the two, with a
dvander@1
   111
 * directory separator in between. The directory is given in two parts,
dvander@1
   112
 * because often a program already retrieves its "home" directory and the
dvander@1
   113
 * codepages are most conveniently stored in a subdirectory of this home
dvander@1
   114
 * directory.
dvander@1
   115
 */
dvander@1
   116
SC_FUNC int cp_path(const char *root, const char *directory)
dvander@1
   117
{
dvander@1
   118
  size_t len1,len2;
dvander@1
   119
  int add_slash1,add_slash2;
dvander@1
   120
dvander@1
   121
  len1= (root!=NULL) ? strlen(root) : 0;
dvander@1
   122
  add_slash1= (len1==0 || root[len1-1]!=DIRSEP_CHAR);
dvander@1
   123
  len2= (directory!=NULL) ? strlen(directory) : 0;
dvander@1
   124
  add_slash2= (len2>0 && root[len2-1]!=DIRSEP_CHAR);
dvander@1
   125
  if (len1+add_slash1+len2+add_slash2>=(_MAX_PATH-MAXCODEPAGE))
dvander@1
   126
    return FALSE;       /* full filename may not fit */
dvander@1
   127
  if (root!=NULL)
dvander@1
   128
    strcpy(cprootpath,root);
dvander@1
   129
  if (add_slash1) {
dvander@1
   130
    assert(len1==0 || cprootpath[len1]=='\0');
dvander@1
   131
    cprootpath[len1]=DIRSEP_CHAR;
dvander@1
   132
    cprootpath[len1+1]='\0';
dvander@1
   133
  } /* if */
dvander@1
   134
  if (directory!=NULL)
dvander@1
   135
    strcat(cprootpath,directory);
dvander@1
   136
  if (add_slash2) {
dvander@1
   137
    assert(cprootpath[len1+add_slash1+len2]=='\0');
dvander@1
   138
    cprootpath[len1+add_slash1+len2]=DIRSEP_CHAR;
dvander@1
   139
    cprootpath[len1+add_slash1+len2+1]='\0';
dvander@1
   140
  } /* if */
dvander@1
   141
  cp_set(NULL);         /* start with a "linear" table (no translation) */
dvander@1
   142
  return TRUE;
dvander@1
   143
}
dvander@1
   144
dvander@1
   145
/* cp_set() loads a codepage from a file. The name parameter may be a
dvander@1
   146
 * filename (including a full path) or it may be a partial codepage name.
dvander@1
   147
 * If the name parameter is NULL, the codepage is cleared to be a "linear"
dvander@1
   148
 * table (no translation).
dvander@1
   149
 * The following files are attempted to open (where <name> specifies the
dvander@1
   150
 * value of the parameter):
dvander@1
   151
 *    <name>
dvander@1
   152
 *    <cprootpath>/<name>
dvander@1
   153
 *    <cprootpath>/<name>.txt
dvander@1
   154
 *    <cprootpath>/cp<name>
dvander@1
   155
 *    <cprootpath>/cp<name>.txt
dvander@1
   156
 */
dvander@1
   157
SC_FUNC int cp_set(const char *name)
dvander@1
   158
{
dvander@1
   159
  char filename[_MAX_PATH];
dvander@1
   160
  FILE *fp=NULL;
dvander@1
   161
  unsigned index;
dvander@1
   162
dvander@1
   163
  /* for name==NULL, set up an identity table */
dvander@1
   164
  if (name==NULL || *name=='\0') {
dvander@1
   165
    if (wordtable!=NULL) {
dvander@1
   166
      free(wordtable);
dvander@1
   167
      wordtable=NULL;
dvander@1
   168
      wordtablesize=0;
dvander@1
   169
      wordtabletop=0;
dvander@1
   170
    } /* if */
dvander@1
   171
    for (index=0; index<ELEMENTS(bytetable); index++)
dvander@1
   172
      bytetable[index]=(wchar_t)index;
dvander@1
   173
    return TRUE;
dvander@1
   174
  } /* if */
dvander@1
   175
dvander@1
   176
  /* try to open the file as-is */
dvander@1
   177
  if (strchr(name,DIRSEP_CHAR)!=NULL)
dvander@1
   178
    fp=fopen(name,"rt");
dvander@1
   179
  if (fp==NULL) {
dvander@1
   180
    /* try opening the file in the "root path" for codepages */
dvander@1
   181
    if (strlen(name)>MAXCODEPAGE)
dvander@1
   182
      return 0;
dvander@1
   183
    assert(strlen(name)+strlen(cprootpath)<_MAX_PATH);
dvander@1
   184
    strcpy(filename,cprootpath);
dvander@1
   185
    strcat(filename,name);
dvander@1
   186
    fp=fopen(filename,"rt");
dvander@1
   187
  } /* if */
dvander@1
   188
  if (fp==NULL) {
dvander@1
   189
    /* try opening the file in the "root path" for codepages, with a ".txt" extension */
dvander@1
   190
    if (strlen(name)+4>=MAXCODEPAGE)
dvander@1
   191
      return 0;
dvander@1
   192
    assert(strlen(filename)+4<_MAX_PATH);
dvander@1
   193
    strcat(filename,".txt");
dvander@1
   194
    fp=fopen(filename,"rt");
dvander@1
   195
  } /* if */
dvander@1
   196
  if (fp==NULL) {
dvander@1
   197
    /* try opening the file in the "root path" for codepages, with "cp" prefixed before the name */
dvander@1
   198
    if (strlen(name)+2>MAXCODEPAGE)
dvander@1
   199
      return 0;
dvander@1
   200
    assert(2+strlen(name)+strlen(cprootpath)<_MAX_PATH);
dvander@1
   201
    strcpy(filename,cprootpath);
dvander@1
   202
    strcat(filename,"cp");
dvander@1
   203
    strcat(filename,name);
dvander@1
   204
    fp=fopen(filename,"rt");
dvander@1
   205
  } /* if */
dvander@1
   206
  if (fp==NULL) {
dvander@1
   207
    /* try opening the file in the "root path" for codepages, with "cp" prefixed an ".txt" appended */
dvander@1
   208
    if (strlen(name)+2+4>MAXCODEPAGE)
dvander@1
   209
      return 0;
dvander@1
   210
    assert(strlen(filename)+4<_MAX_PATH);
dvander@1
   211
    strcat(filename,".txt");
dvander@1
   212
    fp=fopen(filename,"rt");
dvander@1
   213
  } /* if */
dvander@1
   214
  if (fp==NULL)
dvander@1
   215
    return FALSE;       /* all failed */
dvander@1
   216
dvander@1
   217
  /* clear the tables */
dvander@1
   218
  for (index=0; index<ELEMENTS(bytetable); index++)
dvander@1
   219
    bytetable[index]=INVALID;   /* special code meaning "not found" */
dvander@1
   220
  assert(wordtablesize==0 && wordtabletop==0 && wordtable==NULL
dvander@1
   221
         || wordtablesize>0 && wordtable!=NULL);
dvander@1
   222
  if (wordtable!=NULL) {
dvander@1
   223
    free(wordtable);
dvander@1
   224
    wordtable=NULL;
dvander@1
   225
    wordtablesize=0;
dvander@1
   226
    wordtabletop=0;
dvander@1
   227
  } /* if */
dvander@1
   228
dvander@1
   229
  /* read in the table */
dvander@1
   230
  while (cp_readline(fp,filename,sizeof filename)) {
dvander@1
   231
    char *ptr;
dvander@1
   232
    if ((ptr=strchr(filename,'#'))!=NULL)
dvander@1
   233
      *ptr='\0';                /* strip of comment */
dvander@1
   234
    for (ptr=filename; *ptr>0 && *ptr<' '; ptr++)
dvander@1
   235
      /* nothing */;            /* skip leading whitespace */
dvander@1
   236
    if (*ptr!='\0') {
dvander@1
   237
      /* content on line */
dvander@1
   238
      unsigned code=LEADBYTE;
dvander@1
   239
      int num=sscanf(ptr,"%i %i",&index,&code);
dvander@1
   240
      /* if sscanf() returns 1 and the index is in range 0..255, then the
dvander@1
   241
       * code is a DBCS lead byte; if sscanf() returns 2 and index>=256, this
dvander@1
   242
       * is a double byte pair (lead byte + follower)
dvander@1
   243
       */
dvander@1
   244
      if (num>=1 && index<256) {
dvander@1
   245
        bytetable[index]=(wchar_t)code;
dvander@1
   246
      } else if (num==2 && index>=256 && index<LEADBYTE) {
dvander@1
   247
        /* store the DBCS character in wordtable */
dvander@1
   248
        if (wordtabletop>=wordtablesize) {
dvander@1
   249
          /* grow the list */
dvander@1
   250
          int newsize;
dvander@1
   251
          struct wordpair *newblock;
dvander@1
   252
          newsize= (wordtablesize==0) ? 128 : 2*wordtablesize;
dvander@1
   253
          newblock=(struct wordpair *)malloc(newsize*sizeof(*wordtable));
dvander@1
   254
          if (newblock!=NULL) {
dvander@1
   255
            memcpy(newblock,wordtable,wordtabletop*sizeof(*wordtable));
dvander@1
   256
            free(wordtable);
dvander@1
   257
            wordtable=newblock;
dvander@1
   258
            wordtablesize=newsize;
dvander@1
   259
          } /* if */
dvander@1
   260
        } /* if */
dvander@1
   261
        if (wordtabletop<wordtablesize) {
dvander@1
   262
          /* insert at sorted position */
dvander@1
   263
          int pos=wordtabletop;
dvander@1
   264
          assert(wordtable!=NULL);
dvander@1
   265
          while (pos>0 && (unsigned)wordtable[pos-1].index>index) {
dvander@1
   266
            wordtable[pos]=wordtable[pos-1];
dvander@1
   267
            pos--;
dvander@1
   268
          } /* while */
dvander@1
   269
          wordtable[pos].index=(unsigned short)index;
dvander@1
   270
          wordtable[pos].code=(wchar_t)code;
dvander@1
   271
        } /* if */
dvander@1
   272
      } /* if */
dvander@1
   273
    } /* if */
dvander@1
   274
  } /* while */
dvander@1
   275
dvander@1
   276
  fclose(fp);
dvander@1
   277
  return TRUE;
dvander@1
   278
}
dvander@1
   279
dvander@1
   280
SC_FUNC cell cp_translate(const unsigned char *string,const unsigned char **endptr)
dvander@1
   281
{
dvander@1
   282
  wchar_t result;
dvander@1
   283
dvander@1
   284
  result=bytetable[*string++];
dvander@1
   285
  /* check whether this is a leader code */
dvander@1
   286
  if ((unsigned)result==LEADBYTE && wordtable!=NULL) {
dvander@1
   287
    /* look up the code via binary search */
dvander@1
   288
    int low,high,mid;
dvander@1
   289
    unsigned short index=(unsigned short)(((*(string-1)) << 8) | *string);
dvander@1
   290
    string++;
dvander@1
   291
    assert(wordtabletop>0);
dvander@1
   292
    low=0;
dvander@1
   293
    high=wordtabletop-1;
dvander@1
   294
    while (low<high) {
dvander@1
   295
      mid=(low+high)/2;
dvander@1
   296
      assert(low<=mid && mid<high);
dvander@1
   297
      if (index>wordtable[mid].index)
dvander@1
   298
        low=mid+1;
dvander@1
   299
      else
dvander@1
   300
        high=mid;
dvander@1
   301
    } /* while */
dvander@1
   302
    assert(low==high);
dvander@1
   303
    if (wordtable[low].index==index)
dvander@1
   304
      result=wordtable[low].code;
dvander@1
   305
  } /* if */
dvander@1
   306
dvander@1
   307
  if (endptr!=NULL)
dvander@1
   308
    *endptr=string;
dvander@1
   309
  return (cell)result;
dvander@1
   310
}
dvander@1
   311
dvander@1
   312
#endif  /* NO_CODEPAGE */
dvander@1
   313
dvander@1
   314
#if !defined NO_UTF8
dvander@1
   315
SC_FUNC cell get_utf8_char(const unsigned char *string,const unsigned char **endptr)
dvander@1
   316
{
dvander@1
   317
  int follow=0;
dvander@1
   318
  long lowmark=0;
dvander@1
   319
  unsigned char ch;
dvander@1
   320
  cell result=0;
dvander@1
   321
dvander@1
   322
  if (endptr!=NULL)
dvander@1
   323
    *endptr=string;
dvander@1
   324
dvander@1
   325
  for ( ;; ) {
dvander@1
   326
    ch=*string++;
dvander@1
   327
dvander@1
   328
    if (follow>0 && (ch & 0xc0)==0x80) {
dvander@1
   329
      /* leader code is active, combine with earlier code */
dvander@1
   330
      result=(result << 6) | (ch & 0x3f);
dvander@1
   331
      if (--follow==0) {
dvander@1
   332
        /* encoding a character in more bytes than is strictly needed,
dvander@1
   333
         * is not really valid UTF-8; we are strict here to increase
dvander@1
   334
         * the chance of heuristic dectection of non-UTF-8 text
dvander@1
   335
         * (JAVA writes zero bytes as a 2-byte code UTF-8, which is invalid)
dvander@1
   336
         */
dvander@1
   337
        if (result<lowmark)
dvander@1
   338
          return -1;
dvander@1
   339
        /* the code positions 0xd800--0xdfff and 0xfffe & 0xffff do not
dvander@1
   340
         * exist in UCS-4 (and hence, they do not exist in Unicode)
dvander@1
   341
         */
dvander@1
   342
        if (result>=0xd800 && result<=0xdfff || result==0xfffe || result==0xffff)
dvander@1
   343
          return -1;
dvander@1
   344
      } /* if */
dvander@1
   345
      break;
dvander@1
   346
    } else if (follow==0 && (ch & 0x80)==0x80) {
dvander@1
   347
      /* UTF-8 leader code */
dvander@1
   348
      if ((ch & 0xe0)==0xc0) {
dvander@1
   349
        /* 110xxxxx 10xxxxxx */
dvander@1
   350
        follow=1;
dvander@1
   351
        lowmark=0x80L;
dvander@1
   352
        result=ch & 0x1f;
dvander@1
   353
      } else if ((ch & 0xf0)==0xe0) {
dvander@1
   354
        /* 1110xxxx 10xxxxxx 10xxxxxx (16 bits, BMP plane) */
dvander@1
   355
        follow=2;
dvander@1
   356
        lowmark=0x800L;
dvander@1
   357
        result=ch & 0x0f;
dvander@1
   358
      } else if ((ch & 0xf8)==0xf0) {
dvander@1
   359
        /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
dvander@1
   360
        follow=3;
dvander@1
   361
        lowmark=0x10000L;
dvander@1
   362
        result=ch & 0x07;
dvander@1
   363
      } else if ((ch & 0xfc)==0xf8) {
dvander@1
   364
        /* 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */
dvander@1
   365
        follow=4;
dvander@1
   366
        lowmark=0x200000L;
dvander@1
   367
        result=ch & 0x03;
dvander@1
   368
      } else if ((ch & 0xfe)==0xfc) {
dvander@1
   369
        /* 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx (32 bits) */
dvander@1
   370
        follow=5;
dvander@1
   371
        lowmark=0x4000000L;
dvander@1
   372
        result=ch & 0x01;
dvander@1
   373
      } else {
dvander@1
   374
        /* this is invalid UTF-8 */
dvander@1
   375
        return -1;
dvander@1
   376
      } /* if */
dvander@1
   377
    } else if (follow==0 && (ch & 0x80)==0x00) {
dvander@1
   378
      /* 0xxxxxxx (US-ASCII) */
dvander@1
   379
      result=ch;
dvander@1
   380
      break;
dvander@1
   381
    } else {
dvander@1
   382
      /* this is invalid UTF-8 */
dvander@1
   383
      return -1;
dvander@1
   384
    } /* if */
dvander@1
   385
dvander@1
   386
  } /* for */
dvander@1
   387
dvander@1
   388
  if (endptr!=NULL)
dvander@1
   389
    *endptr=string;
dvander@1
   390
  return result;
dvander@1
   391
}
dvander@1
   392
#endif
dvander@1
   393
dvander@1
   394
SC_FUNC int scan_utf8(FILE *fp,const char *filename)
dvander@1
   395
{
dvander@1
   396
  #if defined NO_UTF8
dvander@1
   397
    return 0;
dvander@1
   398
  #else
dvander@1
   399
    void *resetpos=pc_getpossrc(fp);
dvander@1
   400
    int utf8=TRUE;
dvander@1
   401
    int firstchar=TRUE,bom_found=FALSE;
dvander@1
   402
    const unsigned char *ptr;
dvander@1
   403
dvander@1
   404
    while (utf8 && pc_readsrc(fp,pline,sLINEMAX)!=NULL) {
dvander@1
   405
      ptr=pline;
dvander@1
   406
      if (firstchar) {
dvander@1
   407
        /* check whether the very first character on the very first line
dvander@1
   408
         * starts with a BYTE order mark
dvander@1
   409
         */
dvander@1
   410
        cell c=get_utf8_char(ptr,&ptr);
dvander@1
   411
        bom_found= (c==0xfeff);
dvander@1
   412
        utf8= (c>=0);
dvander@1
   413
        firstchar=FALSE;
dvander@1
   414
      } /* if */
dvander@1
   415
      while (utf8 && *ptr!='\0')
dvander@1
   416
        utf8= (get_utf8_char(ptr,&ptr)>=0);
dvander@1
   417
    } /* while */
dvander@1
   418
    pc_resetsrc(fp,resetpos);
dvander@1
   419
    if (bom_found) {
dvander@1
   420
      unsigned char bom[3];
dvander@1
   421
      if (!utf8)
dvander@1
   422
        error(77,filename);     /* malformed UTF-8 encoding */
dvander@1
   423
      pc_readsrc(fp,bom,3);
dvander@1
   424
      assert(bom[0]==0xef && bom[1]==0xbb && bom[2]==0xbf);
dvander@1
   425
    } /* if */
dvander@1
   426
    return utf8;
dvander@1
   427
  #endif  /* NO_UTF8 */
dvander@1
   428
}