sourcepawn/compiler/sci18n.c
changeset 1952 b7aa2b626807
child 3951 c3f26df2183f
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/sourcepawn/compiler/sci18n.c	Sun Mar 30 07:00:22 2008 +0000
     1.3 @@ -0,0 +1,428 @@
     1.4 +/*  Codepage translation to Unicode, and UTF-8 support
     1.5 + *
     1.6 + *  The translation is based on codepage mapping files that are distributed
     1.7 + *  by the Unicode consortium, see ftp://ftp.unicode.org/Public/MAPPINGS/.
     1.8 + *
     1.9 + *  Character sets with a maximum of 256 codes are translated via a lookup
    1.10 + *  table (these are Single-Byte Character Sets). Character sets like Shift-JIS
    1.11 + *  with single-byte characters and multi-byte characters (introduced by a
    1.12 + *  leader byte) are split into two tables: the 256-entry lookup table for
    1.13 + *  the single-byte characters and an extended table for the multi-byte
    1.14 + *  characters. The extended table is allocated dynamically; the lookup table
    1.15 + *  is allocated statically, so loading SBCS tables cannot fail (if the tables
    1.16 + *  themselves are valid, of course).
    1.17 + *
    1.18 + *  Copyright (c) ITB CompuPhase, 2004-2006
    1.19 + *
    1.20 + *  This software is provided "as-is", without any express or implied warranty.
    1.21 + *  In no event will the authors be held liable for any damages arising from
    1.22 + *  the use of this software.
    1.23 + *
    1.24 + *  Permission is granted to anyone to use this software for any purpose,
    1.25 + *  including commercial applications, and to alter it and redistribute it
    1.26 + *  freely, subject to the following restrictions:
    1.27 + *
    1.28 + *  1.  The origin of this software must not be misrepresented; you must not
    1.29 + *      claim that you wrote the original software. If you use this software in
    1.30 + *      a product, an acknowledgment in the product documentation would be
    1.31 + *      appreciated but is not required.
    1.32 + *  2.  Altered source versions must be plainly marked as such, and must not be
    1.33 + *      misrepresented as being the original software.
    1.34 + *  3.  This notice may not be removed or altered from any source distribution.
    1.35 + *
    1.36 + *  Version: $Id$
    1.37 + */
    1.38 +#include <assert.h>
    1.39 +#include <stdio.h>
    1.40 +#include <stddef.h>
    1.41 +#include <stdlib.h>
    1.42 +#include <string.h>
    1.43 +#include "sc.h"
    1.44 +
    1.45 +#if !defined TRUE
    1.46 +  #define FALSE         0
    1.47 +  #define TRUE          1
    1.48 +#endif
    1.49 +#if !defined _MAX_PATH
    1.50 +  #define _MAX_PATH     250
    1.51 +#endif
    1.52 +#if !defined DIRSEP_CHAR
    1.53 +  #if defined LINUX || defined __FreeBSD__ || defined __OpenBSD__
    1.54 +    #define DIRSEP_CHAR '/'
    1.55 +  #elif defined macintosh
    1.56 +    #define DIRSEP_CHAR ':'
    1.57 +  #else
    1.58 +    #define DIRSEP_CHAR '\\'
    1.59 +  #endif
    1.60 +#endif
    1.61 +
    1.62 +#if !defined ELEMENTS
    1.63 +  #define ELEMENTS(array)       (sizeof(array) / sizeof(array[0]))
    1.64 +#endif
    1.65 +
    1.66 +#if !defined NO_CODEPAGE
    1.67 +
    1.68 +#if !defined MAXCODEPAGE
    1.69 +  #define MAXCODEPAGE   12      /* typically "cp" + 4 digits + ".txt" */
    1.70 +#endif
    1.71 +#define INVALID         0xffffu /* 0xffff and 0xfffe are invalid Unicode characters */
    1.72 +#define LEADBYTE        0xfffeu
    1.73 +
    1.74 +struct wordpair {
    1.75 +  unsigned short index;
    1.76 +  wchar_t code;
    1.77 +};
    1.78 +static char cprootpath[_MAX_PATH] = { DIRSEP_CHAR, '\0' };
    1.79 +static wchar_t bytetable[256];
    1.80 +static struct wordpair *wordtable = NULL;
    1.81 +static unsigned wordtablesize = 0;
    1.82 +static unsigned wordtabletop = 0;
    1.83 +
    1.84 +
    1.85 +/* read in a line delimited by '\r' or '\n'; do NOT store the '\r' or '\n' into
    1.86 + * the string and ignore empty lines
    1.87 + * returns 1 for success and 0 for failure
    1.88 + */
    1.89 +static int cp_readline(FILE *fp,char *string,size_t size)
    1.90 +{
    1.91 +  size_t count=0;
    1.92 +  int c;
    1.93 +  assert(size>1);
    1.94 +  while ((c=fgetc(fp))!=EOF && count<size-1) {
    1.95 +    if (c=='\r' || c=='\n') {
    1.96 +      if (count>0)  /* '\r' or '\n' ends a string */
    1.97 +        break;
    1.98 +      /* if count==0, the line started with a '\r' or '\n', or perhaps line
    1.99 +       * ends in the file are '\r\n' and we read and stopped on the '\r' of
   1.100 +       * the preceding line
   1.101 +       */
   1.102 +    } else {
   1.103 +      string[count++]=(char)c;
   1.104 +    } /* if */
   1.105 +  } /* while */
   1.106 +  string[count]='\0';
   1.107 +  return count>0;
   1.108 +}
   1.109 +
   1.110 +/* cp_path() sets the directory where all codepage files must be found (if
   1.111 + * the parameter to cp_set() specifies a full path, that is used instead).
   1.112 + * The path is specified into two parts: root and directory; the full path
   1.113 + * for the codepage direcory is just the concatenation of the two, with a
   1.114 + * directory separator in between. The directory is given in two parts,
   1.115 + * because often a program already retrieves its "home" directory and the
   1.116 + * codepages are most conveniently stored in a subdirectory of this home
   1.117 + * directory.
   1.118 + */
   1.119 +SC_FUNC int cp_path(const char *root, const char *directory)
   1.120 +{
   1.121 +  size_t len1,len2;
   1.122 +  int add_slash1,add_slash2;
   1.123 +
   1.124 +  len1= (root!=NULL) ? strlen(root) : 0;
   1.125 +  add_slash1= (len1==0 || root[len1-1]!=DIRSEP_CHAR);
   1.126 +  len2= (directory!=NULL) ? strlen(directory) : 0;
   1.127 +  add_slash2= (len2>0 && root[len2-1]!=DIRSEP_CHAR);
   1.128 +  if (len1+add_slash1+len2+add_slash2>=(_MAX_PATH-MAXCODEPAGE))
   1.129 +    return FALSE;       /* full filename may not fit */
   1.130 +  if (root!=NULL)
   1.131 +    strcpy(cprootpath,root);
   1.132 +  if (add_slash1) {
   1.133 +    assert(len1==0 || cprootpath[len1]=='\0');
   1.134 +    cprootpath[len1]=DIRSEP_CHAR;
   1.135 +    cprootpath[len1+1]='\0';
   1.136 +  } /* if */
   1.137 +  if (directory!=NULL)
   1.138 +    strcat(cprootpath,directory);
   1.139 +  if (add_slash2) {
   1.140 +    assert(cprootpath[len1+add_slash1+len2]=='\0');
   1.141 +    cprootpath[len1+add_slash1+len2]=DIRSEP_CHAR;
   1.142 +    cprootpath[len1+add_slash1+len2+1]='\0';
   1.143 +  } /* if */
   1.144 +  cp_set(NULL);         /* start with a "linear" table (no translation) */
   1.145 +  return TRUE;
   1.146 +}
   1.147 +
   1.148 +/* cp_set() loads a codepage from a file. The name parameter may be a
   1.149 + * filename (including a full path) or it may be a partial codepage name.
   1.150 + * If the name parameter is NULL, the codepage is cleared to be a "linear"
   1.151 + * table (no translation).
   1.152 + * The following files are attempted to open (where <name> specifies the
   1.153 + * value of the parameter):
   1.154 + *    <name>
   1.155 + *    <cprootpath>/<name>
   1.156 + *    <cprootpath>/<name>.txt
   1.157 + *    <cprootpath>/cp<name>
   1.158 + *    <cprootpath>/cp<name>.txt
   1.159 + */
   1.160 +SC_FUNC int cp_set(const char *name)
   1.161 +{
   1.162 +  char filename[_MAX_PATH];
   1.163 +  FILE *fp=NULL;
   1.164 +  unsigned index;
   1.165 +
   1.166 +  /* for name==NULL, set up an identity table */
   1.167 +  if (name==NULL || *name=='\0') {
   1.168 +    if (wordtable!=NULL) {
   1.169 +      free(wordtable);
   1.170 +      wordtable=NULL;
   1.171 +      wordtablesize=0;
   1.172 +      wordtabletop=0;
   1.173 +    } /* if */
   1.174 +    for (index=0; index<ELEMENTS(bytetable); index++)
   1.175 +      bytetable[index]=(wchar_t)index;
   1.176 +    return TRUE;
   1.177 +  } /* if */
   1.178 +
   1.179 +  /* try to open the file as-is */
   1.180 +  if (strchr(name,DIRSEP_CHAR)!=NULL)
   1.181 +    fp=fopen(name,"rt");
   1.182 +  if (fp==NULL) {
   1.183 +    /* try opening the file in the "root path" for codepages */
   1.184 +    if (strlen(name)>MAXCODEPAGE)
   1.185 +      return 0;
   1.186 +    assert(strlen(name)+strlen(cprootpath)<_MAX_PATH);
   1.187 +    strcpy(filename,cprootpath);
   1.188 +    strcat(filename,name);
   1.189 +    fp=fopen(filename,"rt");
   1.190 +  } /* if */
   1.191 +  if (fp==NULL) {
   1.192 +    /* try opening the file in the "root path" for codepages, with a ".txt" extension */
   1.193 +    if (strlen(name)+4>=MAXCODEPAGE)
   1.194 +      return 0;
   1.195 +    assert(strlen(filename)+4<_MAX_PATH);
   1.196 +    strcat(filename,".txt");
   1.197 +    fp=fopen(filename,"rt");
   1.198 +  } /* if */
   1.199 +  if (fp==NULL) {
   1.200 +    /* try opening the file in the "root path" for codepages, with "cp" prefixed before the name */
   1.201 +    if (strlen(name)+2>MAXCODEPAGE)
   1.202 +      return 0;
   1.203 +    assert(2+strlen(name)+strlen(cprootpath)<_MAX_PATH);
   1.204 +    strcpy(filename,cprootpath);
   1.205 +    strcat(filename,"cp");
   1.206 +    strcat(filename,name);
   1.207 +    fp=fopen(filename,"rt");
   1.208 +  } /* if */
   1.209 +  if (fp==NULL) {
   1.210 +    /* try opening the file in the "root path" for codepages, with "cp" prefixed an ".txt" appended */
   1.211 +    if (strlen(name)+2+4>MAXCODEPAGE)
   1.212 +      return 0;
   1.213 +    assert(strlen(filename)+4<_MAX_PATH);
   1.214 +    strcat(filename,".txt");
   1.215 +    fp=fopen(filename,"rt");
   1.216 +  } /* if */
   1.217 +  if (fp==NULL)
   1.218 +    return FALSE;       /* all failed */
   1.219 +
   1.220 +  /* clear the tables */
   1.221 +  for (index=0; index<ELEMENTS(bytetable); index++)
   1.222 +    bytetable[index]=INVALID;   /* special code meaning "not found" */
   1.223 +  assert(wordtablesize==0 && wordtabletop==0 && wordtable==NULL
   1.224 +         || wordtablesize>0 && wordtable!=NULL);
   1.225 +  if (wordtable!=NULL) {
   1.226 +    free(wordtable);
   1.227 +    wordtable=NULL;
   1.228 +    wordtablesize=0;
   1.229 +    wordtabletop=0;
   1.230 +  } /* if */
   1.231 +
   1.232 +  /* read in the table */
   1.233 +  while (cp_readline(fp,filename,sizeof filename)) {
   1.234 +    char *ptr;
   1.235 +    if ((ptr=strchr(filename,'#'))!=NULL)
   1.236 +      *ptr='\0';                /* strip of comment */
   1.237 +    for (ptr=filename; *ptr>0 && *ptr<' '; ptr++)
   1.238 +      /* nothing */;            /* skip leading whitespace */
   1.239 +    if (*ptr!='\0') {
   1.240 +      /* content on line */
   1.241 +      unsigned code=LEADBYTE;
   1.242 +      int num=sscanf(ptr,"%i %i",&index,&code);
   1.243 +      /* if sscanf() returns 1 and the index is in range 0..255, then the
   1.244 +       * code is a DBCS lead byte; if sscanf() returns 2 and index>=256, this
   1.245 +       * is a double byte pair (lead byte + follower)
   1.246 +       */
   1.247 +      if (num>=1 && index<256) {
   1.248 +        bytetable[index]=(wchar_t)code;
   1.249 +      } else if (num==2 && index>=256 && index<LEADBYTE) {
   1.250 +        /* store the DBCS character in wordtable */
   1.251 +        if (wordtabletop>=wordtablesize) {
   1.252 +          /* grow the list */
   1.253 +          int newsize;
   1.254 +          struct wordpair *newblock;
   1.255 +          newsize= (wordtablesize==0) ? 128 : 2*wordtablesize;
   1.256 +          newblock=(struct wordpair *)malloc(newsize*sizeof(*wordtable));
   1.257 +          if (newblock!=NULL) {
   1.258 +            memcpy(newblock,wordtable,wordtabletop*sizeof(*wordtable));
   1.259 +            free(wordtable);
   1.260 +            wordtable=newblock;
   1.261 +            wordtablesize=newsize;
   1.262 +          } /* if */
   1.263 +        } /* if */
   1.264 +        if (wordtabletop<wordtablesize) {
   1.265 +          /* insert at sorted position */
   1.266 +          int pos=wordtabletop;
   1.267 +          assert(wordtable!=NULL);
   1.268 +          while (pos>0 && (unsigned)wordtable[pos-1].index>index) {
   1.269 +            wordtable[pos]=wordtable[pos-1];
   1.270 +            pos--;
   1.271 +          } /* while */
   1.272 +          wordtable[pos].index=(unsigned short)index;
   1.273 +          wordtable[pos].code=(wchar_t)code;
   1.274 +        } /* if */
   1.275 +      } /* if */
   1.276 +    } /* if */
   1.277 +  } /* while */
   1.278 +
   1.279 +  fclose(fp);
   1.280 +  return TRUE;
   1.281 +}
   1.282 +
   1.283 +SC_FUNC cell cp_translate(const unsigned char *string,const unsigned char **endptr)
   1.284 +{
   1.285 +  wchar_t result;
   1.286 +
   1.287 +  result=bytetable[*string++];
   1.288 +  /* check whether this is a leader code */
   1.289 +  if ((unsigned)result==LEADBYTE && wordtable!=NULL) {
   1.290 +    /* look up the code via binary search */
   1.291 +    int low,high,mid;
   1.292 +    unsigned short index=(unsigned short)(((*(string-1)) << 8) | *string);
   1.293 +    string++;
   1.294 +    assert(wordtabletop>0);
   1.295 +    low=0;
   1.296 +    high=wordtabletop-1;
   1.297 +    while (low<high) {
   1.298 +      mid=(low+high)/2;
   1.299 +      assert(low<=mid && mid<high);
   1.300 +      if (index>wordtable[mid].index)
   1.301 +        low=mid+1;
   1.302 +      else
   1.303 +        high=mid;
   1.304 +    } /* while */
   1.305 +    assert(low==high);
   1.306 +    if (wordtable[low].index==index)
   1.307 +      result=wordtable[low].code;
   1.308 +  } /* if */
   1.309 +
   1.310 +  if (endptr!=NULL)
   1.311 +    *endptr=string;
   1.312 +  return (cell)result;
   1.313 +}
   1.314 +
   1.315 +#endif  /* NO_CODEPAGE */
   1.316 +
   1.317 +#if !defined NO_UTF8
   1.318 +SC_FUNC cell get_utf8_char(const unsigned char *string,const unsigned char **endptr)
   1.319 +{
   1.320 +  int follow=0;
   1.321 +  long lowmark=0;
   1.322 +  unsigned char ch;
   1.323 +  cell result=0;
   1.324 +
   1.325 +  if (endptr!=NULL)
   1.326 +    *endptr=string;
   1.327 +
   1.328 +  for ( ;; ) {
   1.329 +    ch=*string++;
   1.330 +
   1.331 +    if (follow>0 && (ch & 0xc0)==0x80) {
   1.332 +      /* leader code is active, combine with earlier code */
   1.333 +      result=(result << 6) | (ch & 0x3f);
   1.334 +      if (--follow==0) {
   1.335 +        /* encoding a character in more bytes than is strictly needed,
   1.336 +         * is not really valid UTF-8; we are strict here to increase
   1.337 +         * the chance of heuristic dectection of non-UTF-8 text
   1.338 +         * (JAVA writes zero bytes as a 2-byte code UTF-8, which is invalid)
   1.339 +         */
   1.340 +        if (result<lowmark)
   1.341 +          return -1;
   1.342 +        /* the code positions 0xd800--0xdfff and 0xfffe & 0xffff do not
   1.343 +         * exist in UCS-4 (and hence, they do not exist in Unicode)
   1.344 +         */
   1.345 +        if (result>=0xd800 && result<=0xdfff || result==0xfffe || result==0xffff)
   1.346 +          return -1;
   1.347 +      } /* if */
   1.348 +      break;
   1.349 +    } else if (follow==0 && (ch & 0x80)==0x80) {
   1.350 +      /* UTF-8 leader code */
   1.351 +      if ((ch & 0xe0)==0xc0) {
   1.352 +        /* 110xxxxx 10xxxxxx */
   1.353 +        follow=1;
   1.354 +        lowmark=0x80L;
   1.355 +        result=ch & 0x1f;
   1.356 +      } else if ((ch & 0xf0)==0xe0) {
   1.357 +        /* 1110xxxx 10xxxxxx 10xxxxxx (16 bits, BMP plane) */
   1.358 +        follow=2;
   1.359 +        lowmark=0x800L;
   1.360 +        result=ch & 0x0f;
   1.361 +      } else if ((ch & 0xf8)==0xf0) {
   1.362 +        /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
   1.363 +        follow=3;
   1.364 +        lowmark=0x10000L;
   1.365 +        result=ch & 0x07;
   1.366 +      } else if ((ch & 0xfc)==0xf8) {
   1.367 +        /* 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */
   1.368 +        follow=4;
   1.369 +        lowmark=0x200000L;
   1.370 +        result=ch & 0x03;
   1.371 +      } else if ((ch & 0xfe)==0xfc) {
   1.372 +        /* 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx (32 bits) */
   1.373 +        follow=5;
   1.374 +        lowmark=0x4000000L;
   1.375 +        result=ch & 0x01;
   1.376 +      } else {
   1.377 +        /* this is invalid UTF-8 */
   1.378 +        return -1;
   1.379 +      } /* if */
   1.380 +    } else if (follow==0 && (ch & 0x80)==0x00) {
   1.381 +      /* 0xxxxxxx (US-ASCII) */
   1.382 +      result=ch;
   1.383 +      break;
   1.384 +    } else {
   1.385 +      /* this is invalid UTF-8 */
   1.386 +      return -1;
   1.387 +    } /* if */
   1.388 +
   1.389 +  } /* for */
   1.390 +
   1.391 +  if (endptr!=NULL)
   1.392 +    *endptr=string;
   1.393 +  return result;
   1.394 +}
   1.395 +#endif
   1.396 +
   1.397 +SC_FUNC int scan_utf8(FILE *fp,const char *filename)
   1.398 +{
   1.399 +  #if defined NO_UTF8
   1.400 +    return 0;
   1.401 +  #else
   1.402 +    void *resetpos=pc_getpossrc(fp);
   1.403 +    int utf8=TRUE;
   1.404 +    int firstchar=TRUE,bom_found=FALSE;
   1.405 +    const unsigned char *ptr;
   1.406 +
   1.407 +    while (utf8 && pc_readsrc(fp,pline,sLINEMAX)!=NULL) {
   1.408 +      ptr=pline;
   1.409 +      if (firstchar) {
   1.410 +        /* check whether the very first character on the very first line
   1.411 +         * starts with a BYTE order mark
   1.412 +         */
   1.413 +        cell c=get_utf8_char(ptr,&ptr);
   1.414 +        bom_found= (c==0xfeff);
   1.415 +        utf8= (c>=0);
   1.416 +        firstchar=FALSE;
   1.417 +      } /* if */
   1.418 +      while (utf8 && *ptr!='\0')
   1.419 +        utf8= (get_utf8_char(ptr,&ptr)>=0);
   1.420 +    } /* while */
   1.421 +    pc_resetsrc(fp,resetpos);
   1.422 +    if (bom_found) {
   1.423 +      unsigned char bom[3];
   1.424 +      if (!utf8)
   1.425 +        error(77,filename);     /* malformed UTF-8 encoding */
   1.426 +      pc_readsrc(fp,bom,3);
   1.427 +      assert(bom[0]==0xef && bom[1]==0xbb && bom[2]==0xbf);
   1.428 +    } /* if */
   1.429 +    return utf8;
   1.430 +  #endif  /* NO_UTF8 */
   1.431 +}