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