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