IVs in mtats
[p5sagit/p5-mst-13.2.git] / utf8.c
diff --git a/utf8.c b/utf8.c
index 76eb932..3ab402c 100644 (file)
--- a/utf8.c
+++ b/utf8.c
@@ -134,8 +134,49 @@ Perl_is_utf8_char(pTHX_ U8 *s)
     return len;
 }
 
+/*
+=for apidoc Am|is_utf8_string|U8 *s|STRLEN len
+
+Returns true if first C<len> bytes of the given string form valid a UTF8
+string, false otherwise.
+
+=cut
+*/
+
+bool
+Perl_is_utf8_string(pTHX_ U8 *s, STRLEN len)
+{
+    U8* x=s;
+    U8* send=s+len;
+    int c;
+    while (x < send) {
+        c = is_utf8_char(x);
+        x += c;
+        if (!c || x > send)
+            return 0;
+    }
+    return 1;
+}
+
+/*
+=for apidoc Am|U8* s|utf8_to_uv_chk|I32 *retlen|I32 checking
+
+Returns the character value of the first character in the string C<s>
+which is assumed to be in UTF8 encoding; C<retlen> will be set to the
+length, in bytes, of that character, and the pointer C<s> will be
+advanced to the end of the character.
+
+If C<s> does not point to a well-formed UTF8 character, the behaviour
+is dependent on the value of C<checking>: if this is true, it is
+assumed that the caller will raise a warning, and this function will
+set C<retlen> to C<-1> and return. If C<checking> is not true, an optional UTF8
+warning is produced.
+
+=cut
+*/
+
 UV
-Perl_utf8_to_uv(pTHX_ U8* s, I32* retlen)
+Perl_utf8_to_uv_chk(pTHX_ U8* s, I32* retlen, bool checking)
 {
     UV uv = *s;
     int len;
@@ -146,7 +187,12 @@ Perl_utf8_to_uv(pTHX_ U8* s, I32* retlen)
     }
     if (!(uv & 0x40)) {
         dTHR;
-       if (ckWARN_d(WARN_UTF8))     
+       if (checking && retlen) {
+           *retlen = -1;
+           return 0;
+       }
+
+       if (ckWARN_d(WARN_UTF8))
            Perl_warner(aTHX_ WARN_UTF8, "Malformed UTF-8 character");
        if (retlen)
            *retlen = 1;
@@ -168,7 +214,12 @@ Perl_utf8_to_uv(pTHX_ U8* s, I32* retlen)
     while (len--) {
        if ((*s & 0xc0) != 0x80) {
             dTHR;
-           if (ckWARN_d(WARN_UTF8))     
+           if (checking && retlen) {
+               *retlen = -1;
+               return 0;
+            }
+
+           if (ckWARN_d(WARN_UTF8))
                Perl_warner(aTHX_ WARN_UTF8, "Malformed UTF-8 character");
            if (retlen)
                *retlen -= len + 1;
@@ -180,7 +231,28 @@ Perl_utf8_to_uv(pTHX_ U8* s, I32* retlen)
     return uv;
 }
 
-/* utf8_distance(a,b) is intended to be a - b in pointer arithmetic */
+/*
+=for apidoc Am|U8* s|utf8_to_uv|I32 *retlen
+
+Returns the character value of the first character in the string C<s>
+which is assumed to be in UTF8 encoding; C<retlen> will be set to the
+length, in bytes, of that character, and the pointer C<s> will be
+advanced to the end of the character.
+
+If C<s> does not point to a well-formed UTF8 character, an optional UTF8
+warning is produced.
+
+=cut
+*/
+
+UV
+Perl_utf8_to_uv(pTHX_ U8* s, I32* retlen)
+{
+ return Perl_utf8_to_uv_chk(aTHX_ s, retlen, 0);
+}
+
+/* utf8_distance(a,b) returns the number of UTF8 characters between
+   the pointers a and b                                                        */
 
 I32
 Perl_utf8_distance(pTHX_ U8 *a, U8 *b)
@@ -222,19 +294,108 @@ Perl_utf8_hop(pTHX_ U8 *s, I32 off)
     return s;
 }
 
-/* XXX NOTHING CALLS THE FOLLOWING TWO ROUTINES YET!!! */
 /*
- * Convert native or reversed UTF-16 to UTF-8.
+=for apidoc Am|U8 *|utf8_to_bytes|U8 *s|STRLEN *len
+
+Converts a string C<s> of length C<len> from UTF8 into byte encoding.
+Unlike C<bytes_to_utf8>, this over-writes the original string, and
+updates len to contain the new length.
+Returns zero on failure, setting C<len> to -1.
+
+=cut
+*/
+
+U8 *
+Perl_utf8_to_bytes(pTHX_ U8* s, STRLEN *len)
+{
+    dTHR;
+    U8 *send;
+    U8 *d;
+    U8 *save;
+
+    send = s + *len;
+    d = save = s;
+
+    /* ensure valid UTF8 and chars < 256 before updating string */
+    while (s < send) {
+       U8 c = *s++;
+        if (c >= 0x80 &&
+           ( (s >= send) || ((*s++ & 0xc0) != 0x80) || ((c & 0xfe) != 0xc2))) {
+           *len = -1;
+           return 0;
+       }
+    }
+    s = save;
+    while (s < send) {
+        if (*s < 0x80)
+            *d++ = *s++;
+        else {
+            I32 ulen;
+            *d++ = (U8)utf8_to_uv(s, &ulen);
+            s += ulen;
+        }
+    }
+    *d = '\0';
+    *len = d - save;
+    return save;
+}
+
+/*
+=for apidoc Am|U8 *|bytes_to_utf8|U8 *s|STRLEN *len
+
+Converts a string C<s> of length C<len> from ASCII into UTF8 encoding.
+Returns a pointer to the newly-created string, and sets C<len> to
+reflect the new length.
+
+=cut
+*/
+
+U8*
+Perl_bytes_to_utf8(pTHX_ U8* s, STRLEN *len)
+{
+    dTHR;
+    U8 *send;
+    U8 *d;
+    U8 *dst;
+    send = s + (*len);
+
+    Newz(801, d, (*len) * 2 + 1, U8);
+    dst = d;
+
+    while (s < send) {
+        if (*s < 0x80)
+            *d++ = *s++;
+        else {
+            UV uv = *s++;
+            *d++ = (( uv >>  6)         | 0xc0);
+            *d++ = (( uv        & 0x3f) | 0x80);
+        }
+    }
+    *d = '\0';
+    *len = d-dst;
+    return dst;
+}
+
+/*
+ * Convert native (big-endian) or reversed (little-endian) UTF-16 to UTF-8.
  *
  * Destination must be pre-extended to 3/2 source.  Do not use in-place.
  * We optimize for native, for obvious reasons. */
 
 U8*
-Perl_utf16_to_utf8(pTHX_ U16* p, U8* d, I32 bytelen)
+Perl_utf16_to_utf8(pTHX_ U8* p, U8* d, I32 bytelen, I32 *newlen)
 {
-    U16* pend = p + bytelen / 2;
+    U8* pend;
+    U8* dstart = d;
+
+    if (bytelen & 1)
+       Perl_croak(aTHX_ "panic: utf16_to_utf8: odd bytelen");
+
+    pend = p + bytelen;
+
     while (p < pend) {
-       UV uv = *p++;
+       UV uv = (p[0] << 8) + p[1]; /* UTF-16BE */
+       p += 2;
        if (uv < 0x80) {
            *d++ = uv;
            continue;
@@ -246,13 +407,9 @@ Perl_utf16_to_utf8(pTHX_ U16* p, U8* d, I32 bytelen)
        }
        if (uv >= 0xd800 && uv < 0xdbff) {      /* surrogates */
             dTHR;
-           int low = *p++;
-           if (low < 0xdc00 || low >= 0xdfff) {
-               if (ckWARN_d(WARN_UTF8))     
-                   Perl_warner(aTHX_ WARN_UTF8, "Malformed UTF-16 surrogate");
-               p--;
-               uv = 0xfffd;
-           }
+           UV low = *p++;
+           if (low < 0xdc00 || low >= 0xdfff)
+               Perl_croak(aTHX_ "Malformed UTF-16 surrogate");
            uv = ((uv - 0xd800) << 10) + (low - 0xdc00) + 0x10000;
        }
        if (uv < 0x10000) {
@@ -269,13 +426,14 @@ Perl_utf16_to_utf8(pTHX_ U16* p, U8* d, I32 bytelen)
            continue;
        }
     }
+    *newlen = d - dstart;
     return d;
 }
 
 /* Note: this one is slightly destructive of the source. */
 
 U8*
-Perl_utf16_to_utf8_reversed(pTHX_ U16* p, U8* d, I32 bytelen)
+Perl_utf16_to_utf8_reversed(pTHX_ U8* p, U8* d, I32 bytelen, I32 *newlen)
 {
     U8* s = (U8*)p;
     U8* send = s + bytelen;
@@ -285,7 +443,7 @@ Perl_utf16_to_utf8_reversed(pTHX_ U16* p, U8* d, I32 bytelen)
        s[1] = tmp;
        s += 2;
     }
-    return utf16_to_utf8(p, d, bytelen);
+    return utf16_to_utf8(p, d, bytelen, newlen);
 }
 
 /* for now these are all defined (inefficiently) in terms of the utf8 versions */
@@ -536,7 +694,10 @@ Perl_is_utf8_alnum(pTHX_ U8 *p)
     if (!is_utf8_char(p))
        return FALSE;
     if (!PL_utf8_alnum)
-       PL_utf8_alnum = swash_init("utf8", "IsAlnum", &PL_sv_undef, 0, 0);
+       /* NOTE: "IsWord", not "IsAlnum", since Alnum is a true
+        * descendant of isalnum(3), in other words, it doesn't
+        * contain the '_'. --jhi */
+       PL_utf8_alnum = swash_init("utf8", "IsWord", &PL_sv_undef, 0, 0);
     return swash_fetch(PL_utf8_alnum, p);
 /*    return *p == '_' || is_utf8_alpha(p) || is_utf8_digit(p); */
 #ifdef SURPRISINGLY_SLOWER  /* probably because alpha is usually true */
@@ -698,7 +859,7 @@ Perl_to_utf8_upper(pTHX_ U8 *p)
     if (!PL_utf8_toupper)
        PL_utf8_toupper = swash_init("utf8", "ToUpper", &PL_sv_undef, 4, 0);
     uv = swash_fetch(PL_utf8_toupper, p);
-    return uv ? uv : utf8_to_uv(p,0);
+    return uv ? uv : utf8_to_uv_chk(p,0,0);
 }
 
 UV
@@ -709,7 +870,7 @@ Perl_to_utf8_title(pTHX_ U8 *p)
     if (!PL_utf8_totitle)
        PL_utf8_totitle = swash_init("utf8", "ToTitle", &PL_sv_undef, 4, 0);
     uv = swash_fetch(PL_utf8_totitle, p);
-    return uv ? uv : utf8_to_uv(p,0);
+    return uv ? uv : utf8_to_uv_chk(p,0,0);
 }
 
 UV
@@ -720,7 +881,7 @@ Perl_to_utf8_lower(pTHX_ U8 *p)
     if (!PL_utf8_tolower)
        PL_utf8_tolower = swash_init("utf8", "ToLower", &PL_sv_undef, 4, 0);
     uv = swash_fetch(PL_utf8_tolower, p);
-    return uv ? uv : utf8_to_uv(p,0);
+    return uv ? uv : utf8_to_uv_chk(p,0,0);
 }
 
 /* a "swash" is a swatch hash */
@@ -730,7 +891,7 @@ Perl_swash_init(pTHX_ char* pkg, char* name, SV *listsv, I32 minbits, I32 none)
 {
     SV* retval;
     char tmpbuf[256];
-    dSP;    
+    dSP;
 
     if (!gv_stashpv(pkg, 0)) { /* demand load utf8 */
        ENTER;
@@ -754,7 +915,7 @@ Perl_swash_init(pTHX_ char* pkg, char* name, SV *listsv, I32 minbits, I32 none)
     if (PL_curcop == &PL_compiling)    /* XXX ought to be handled by lex_start */
        strncpy(tmpbuf, PL_tokenbuf, sizeof tmpbuf);
     if (call_method("SWASHNEW", G_SCALAR))
-       retval = newSVsv(*PL_stack_sp--);    
+       retval = newSVsv(*PL_stack_sp--);
     else
        retval = &PL_sv_undef;
     LEAVE;
@@ -810,11 +971,11 @@ Perl_swash_fetch(pTHX_ SV *sv, U8 *ptr)
            PUSHMARK(SP);
            EXTEND(SP,3);
            PUSHs((SV*)sv);
-           PUSHs(sv_2mortal(newSViv(utf8_to_uv(ptr, 0) & ~(needents - 1))));
+           PUSHs(sv_2mortal(newSViv(utf8_to_uv_chk(ptr, 0, 0) & ~(needents - 1))));
            PUSHs(sv_2mortal(newSViv(needents)));
            PUTBACK;
            if (call_method("SWASHGET", G_SCALAR))
-               retval = newSVsv(*PL_stack_sp--);    
+               retval = newSVsv(*PL_stack_sp--);
            else
                retval = &PL_sv_undef;
            POPSTACK;