Missed test changes and a bit of debugging code that should
[p5sagit/p5-mst-13.2.git] / ext / Encode / Encode.xs
index 1695c8f..8e225cd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- $Id: Encode.xs,v 1.55 2003/02/28 01:40:27 dankogai Exp dankogai $
+ $Id: Encode.xs,v 2.6 2005/09/08 14:17:17 dankogai Exp dankogai $
  */
 
 #define PERL_NO_GET_CONTEXT
 #define ENCODE_XS_USEFP   1
 
 #define UNIMPLEMENTED(x,y) y x (SV *sv, char *encoding) {dTHX;   \
-                        Perl_croak(aTHX_ "panic_unimplemented"); \
+                         Perl_croak(aTHX_ "panic_unimplemented"); \
                         return (y)0; /* fool picky compilers */ \
-                        }
+                         }
 /**/
 
 UNIMPLEMENTED(_encoded_utf8_to_bytes, I32)
 UNIMPLEMENTED(_encoded_bytes_to_utf8, I32)
 
+#define UTF8_ALLOW_STRICT 0
+#define UTF8_ALLOW_NONSTRICT (UTF8_ALLOW_ANY &                    \
+                              ~(UTF8_ALLOW_CONTINUATION |         \
+                                UTF8_ALLOW_NON_CONTINUATION |     \
+                                UTF8_ALLOW_LONG))
+
+static SV* fallback_cb = (SV*)NULL ;
+
 void
 Encode_XSEncoding(pTHX_ encode_t * enc)
 {
@@ -58,6 +66,29 @@ call_failure(SV * routine, U8 * done, U8 * dest, U8 * orig)
 #define ERR_DECODE_NOMAP "%s \"\\x%02" UVXf "\" does not map to Unicode"
 
 static SV *
+do_fallback_cb(pTHX_ UV ch)
+{
+    dSP;
+    int argc;
+    SV* retval;
+    ENTER;
+    SAVETMPS;
+    PUSHMARK(sp);
+    XPUSHs(sv_2mortal(newSVnv((UV)ch)));
+    PUTBACK;
+    argc = call_sv(fallback_cb, G_SCALAR);
+    SPAGAIN;
+    if (argc != 1){
+       croak("fallback sub must return scalar!");
+    }
+    retval = newSVsv(POPs);
+    PUTBACK;
+    FREETMPS;
+    LEAVE;
+    return retval;
+}
+
+static SV *
 encode_method(pTHX_ encode_t * enc, encpage_t * dir, SV * src,
              int check, STRLEN * offset, SV * term, int * retcode)
 {
@@ -104,7 +135,7 @@ encode_method(pTHX_ encode_t * enc, encpage_t * dir, SV * src,
        }
        switch (code) {
        case ENCODE_NOSPACE:
-       {
+       {       
            STRLEN more = 0; /* make sure you initialize! */
            STRLEN sleft;
            sdone += slen;
@@ -139,12 +170,14 @@ encode_method(pTHX_ encode_t * enc, encpage_t * dir, SV * src,
            continue;
        }
        case ENCODE_NOREP:
-           /* encoding */
+           /* encoding */      
            if (dir == enc->f_utf8) {
                STRLEN clen;
                UV ch =
                    utf8n_to_uvuni(s+slen, (SvCUR(src)-slen),
                                   &clen, UTF8_ALLOW_ANY|UTF8_CHECK_ONLY);
+               /* if non-representable multibyte prefix at end of current buffer - break*/
+               if (clen > tlen - sdone) break;
                if (check & ENCODE_DIE_ON_ERR) {
                    Perl_croak(aTHX_ ERR_ENCODE_NOMAP,
                               (UV)ch, enc->name[0]);
@@ -157,24 +190,16 @@ encode_method(pTHX_ encode_t * enc, encpage_t * dir, SV * src,
                if (check & ENCODE_RETURN_ON_ERR){
                    goto ENCODE_SET_SRC;
                }
-               if (check & ENCODE_PERLQQ){
-                   SV* perlqq =
-                       sv_2mortal(newSVpvf("\\x{%04"UVxf"}", (UV)ch));
-                   sdone += slen + clen;
-                   ddone += dlen + SvCUR(perlqq);
-                   sv_catsv(dst, perlqq);
-               }else if (check & ENCODE_HTMLCREF){
-                   SV* htmlcref =
-                       sv_2mortal(newSVpvf("&#%" UVuf ";", (UV)ch));
-                   sdone += slen + clen;
-                   ddone += dlen + SvCUR(htmlcref);
-                   sv_catsv(dst, htmlcref);
-               }else if (check & ENCODE_XMLCREF){
-                   SV* xmlcref =
-                       sv_2mortal(newSVpvf("&#x%" UVxf ";", (UV)ch));
+               if (check & (ENCODE_PERLQQ|ENCODE_HTMLCREF|ENCODE_XMLCREF)){
+                   SV* subchar = 
+                       (fallback_cb != (SV*)NULL) ? do_fallback_cb(aTHX_ ch) :
+                       newSVpvf(check & ENCODE_PERLQQ ? "\\x{%04"UVxf"}" :
+                                check & ENCODE_HTMLCREF ? "&#%" UVuf ";" :
+                                "&#x%" UVxf ";", (UV)ch);
                    sdone += slen + clen;
-                   ddone += dlen + SvCUR(xmlcref);
-                   sv_catsv(dst, xmlcref);
+                   ddone += dlen + SvCUR(subchar);
+                   sv_catsv(dst, subchar);
+                   SvREFCNT_dec(subchar);
                } else {
                    /* fallback char */
                    sdone += slen + clen;
@@ -186,25 +211,28 @@ encode_method(pTHX_ encode_t * enc, encpage_t * dir, SV * src,
            else {
                if (check & ENCODE_DIE_ON_ERR){
                    Perl_croak(aTHX_ ERR_DECODE_NOMAP,
-                             enc->name[0], (UV)s[slen]);
+                              enc->name[0], (UV)s[slen]);
                    return &PL_sv_undef; /* never reaches but be safe */
                }
                if (check & ENCODE_WARN_ON_ERR){
                    Perl_warner(
                        aTHX_ packWARN(WARN_UTF8),
                        ERR_DECODE_NOMAP,
-                       enc->name[0], (UV)s[slen]);
+                               enc->name[0], (UV)s[slen]);
                }
                if (check & ENCODE_RETURN_ON_ERR){
                    goto ENCODE_SET_SRC;
                }
                if (check &
                    (ENCODE_PERLQQ|ENCODE_HTMLCREF|ENCODE_XMLCREF)){
-                   SV* perlqq =
-                       sv_2mortal(newSVpvf("\\x%02" UVXf, (UV)s[slen]));
+                   SV* subchar = 
+                       (fallback_cb != (SV*)NULL) ? 
+                       do_fallback_cb(aTHX_ (UV)s[slen]) :
+                       newSVpvf("\\x%02" UVXf, (UV)s[slen]);
                    sdone += slen + 1;
-                   ddone += dlen + SvCUR(perlqq);
-                   sv_catsv(dst, perlqq);
+                   ddone += dlen + SvCUR(subchar);
+                   sv_catsv(dst, subchar);
+                   SvREFCNT_dec(subchar);
                } else {
                    sdone += slen + 1;
                    ddone += dlen + strlen(FBCHAR_UTF8);
@@ -213,7 +241,7 @@ encode_method(pTHX_ encode_t * enc, encpage_t * dir, SV * src,
            }
            /* settle variables when fallback */
            d    = (U8 *)SvEND(dst);
-           dlen = SvLEN(dst) - ddone - 1;
+            dlen = SvLEN(dst) - ddone - 1;
            s    = (U8*)SvPVX(src) + sdone;
            slen = tlen - sdone;
            break;
@@ -256,18 +284,119 @@ encode_method(pTHX_ encode_t * enc, encpage_t * dir, SV * src,
     return dst;
 }
 
-MODULE = Encode         PACKAGE = Encode::utf8  PREFIX = Method_
-
-PROTOTYPES: DISABLE
+static bool
+strict_utf8(pTHX_ SV* sv)
+{
+    HV* hv;
+    SV** svp;
+    sv = SvRV(sv);
+    if (!sv || SvTYPE(sv) != SVt_PVHV)
+        return 0;
+    hv = (HV*)sv;
+    svp = hv_fetch(hv, "strict_utf8", 11, 0);
+    if (!svp)
+        return 0;
+    return SvTRUE(*svp);
+}
 
-void
-Method_renew(obj)
-SV *   obj
-CODE:
+static U8*
+process_utf8(pTHX_ SV* dst, U8* s, U8* e, int check,
+             bool encode, bool strict, bool stop_at_partial)
 {
-    XSRETURN(1);
+    UV uv;
+    STRLEN ulen;
+
+    SvPOK_only(dst);
+    SvCUR_set(dst,0);
+
+    while (s < e) {
+        if (UTF8_IS_INVARIANT(*s)) {
+            sv_catpvn(dst, (char *)s, 1);
+            s++;
+            continue;
+        }
+
+        if (UTF8_IS_START(*s)) {
+            U8 skip = UTF8SKIP(s);
+            if ((s + skip) > e) {
+                /* Partial character */
+                /* XXX could check that rest of bytes are UTF8_IS_CONTINUATION(ch) */
+                if (stop_at_partial || (check & ENCODE_STOP_AT_PARTIAL))
+                    break;
+
+                goto malformed_byte;
+            }
+
+            uv = utf8n_to_uvuni(s, e - s, &ulen,
+                                UTF8_CHECK_ONLY | (strict ? UTF8_ALLOW_STRICT :
+                                                            UTF8_ALLOW_NONSTRICT)
+                               );
+#if 1 /* perl-5.8.6 and older do not check UTF8_ALLOW_LONG */
+           if (strict && uv > PERL_UNICODE_MAX)
+               ulen = -1;
+#endif
+            if (ulen == -1) {
+                if (strict) {
+                    uv = utf8n_to_uvuni(s, e - s, &ulen,
+                                        UTF8_CHECK_ONLY | UTF8_ALLOW_NONSTRICT);
+                    if (ulen == -1)
+                        goto malformed_byte;
+                    goto malformed;
+                }
+                goto malformed_byte;
+            }
+
+
+             /* Whole char is good */
+             sv_catpvn(dst,(char *)s,skip);
+             s += skip;
+             continue;
+        }
+
+        /* If we get here there is something wrong with alleged UTF-8 */
+    malformed_byte:
+        uv = (UV)*s;
+        ulen = 1;
+
+    malformed:
+        if (check & ENCODE_DIE_ON_ERR){
+            if (encode)
+                Perl_croak(aTHX_ ERR_ENCODE_NOMAP, uv, "utf8");
+            else
+                Perl_croak(aTHX_ ERR_DECODE_NOMAP, "utf8", uv);
+        }
+        if (check & ENCODE_WARN_ON_ERR){
+            if (encode)
+                Perl_warner(aTHX_ packWARN(WARN_UTF8),
+                            ERR_ENCODE_NOMAP, uv, "utf8");
+            else
+                Perl_warner(aTHX_ packWARN(WARN_UTF8),
+                            ERR_DECODE_NOMAP, "utf8", uv);
+        }
+        if (check & ENCODE_RETURN_ON_ERR) {
+                break;
+        }
+        if (check & (ENCODE_PERLQQ|ENCODE_HTMLCREF|ENCODE_XMLCREF)){
+            SV* subchar = newSVpvf(check & ENCODE_PERLQQ ? (ulen == 1 ? "\\x%02" UVXf : "\\x{%04" UVXf "}"):
+                                   check & ENCODE_HTMLCREF ? "&#%" UVuf ";" :
+                                   "&#x%" UVxf ";", uv);
+            sv_catsv(dst, subchar);
+            SvREFCNT_dec(subchar);
+        } else {
+            sv_catpv(dst, FBCHAR_UTF8);
+        }
+        s += ulen;
+    }
+    *SvEND(dst) = '\0';
+
+    return s;
 }
 
+
+MODULE = Encode                PACKAGE = Encode::utf8  PREFIX = Method_
+
+PROTOTYPES: DISABLE
+
 void
 Method_decode_xs(obj,src,check = 0)
 SV *   obj
@@ -279,8 +408,26 @@ CODE:
     U8 *s = (U8 *) SvPV(src, slen);
     U8 *e = (U8 *) SvEND(src);
     SV *dst = newSV(slen>0?slen:1); /* newSV() abhors 0 -- inaba */
-    SvPOK_only(dst);
-    SvCUR_set(dst,0);
+
+    /* 
+     * PerlIO check -- we assume the object is of PerlIO if renewed
+     */
+    int renewed = 0;
+    dSP; ENTER; SAVETMPS;
+    PUSHMARK(sp);
+    XPUSHs(obj);
+    PUTBACK;
+    if (call_method("renewed",G_SCALAR) == 1) {
+       SPAGAIN;
+       renewed = POPi;
+       PUTBACK; 
+#if 0
+       fprintf(stderr, "renewed == %d\n", renewed);
+#endif
+    }
+    FREETMPS; LEAVE;
+    /* end PerlIO check */
+
     if (SvUTF8(src)) {
        s = utf8_to_bytes(s,&slen);
        if (s) {
@@ -292,48 +439,8 @@ CODE:
            croak("Cannot decode string with wide characters");
        }
     }
-    while (s < e) {
-       if (UTF8_IS_INVARIANT(*s) || UTF8_IS_START(*s)) {
-           U8 skip = UTF8SKIP(s);
-           if ((s + skip) > e) {
-               /* Partial character - done */
-               break;
-           }
-           else if (is_utf8_char(s)) {
-               /* Whole char is good */
-               sv_catpvn(dst,(char *)s,skip);
-               s += skip;
-               continue;
-           }
-           else {
-               /* starts ok but isn't "good" */
-           }
-       }
-       else {
-           /* Invalid start byte */
-       }
-       /* If we get here there is something wrong with alleged UTF-8 */
-       if (check & ENCODE_DIE_ON_ERR){
-           Perl_croak(aTHX_ ERR_DECODE_NOMAP, "utf8", (UV)*s);
-           XSRETURN(0);
-       }
-       if (check & ENCODE_WARN_ON_ERR){
-           Perl_warner(aTHX_ packWARN(WARN_UTF8),
-                       ERR_DECODE_NOMAP, "utf8", (UV)*s);
-       }
-       if (check & ENCODE_RETURN_ON_ERR) {
-               break;
-       }
-       if (check & (ENCODE_PERLQQ|ENCODE_HTMLCREF|ENCODE_XMLCREF)){
-           SV* perlqq = newSVpvf("\\x%02" UVXf, (UV)*s);
-           sv_catsv(dst, perlqq);
-           SvREFCNT_dec(perlqq);
-       } else {
-           sv_catpv(dst, FBCHAR_UTF8);
-       }
-       s++;
-    }
-    *SvEND(dst) = '\0';
+
+    s = process_utf8(aTHX_ dst, s, e, check, 0, strict_utf8(aTHX_ obj), renewed);
 
     /* Clear out translated part of source unless asked not to */
     if (check && !(check & ENCODE_LEAVE_SRC)){
@@ -360,9 +467,15 @@ CODE:
     U8 *e = (U8 *) SvEND(src);
     SV *dst = newSV(slen>0?slen:1); /* newSV() abhors 0 -- inaba */
     if (SvUTF8(src)) {
-       /* Already encoded - trust it and just copy the octets */
-       sv_setpvn(dst,(char *)s,(e-s));
-       s = e;
+       /* Already encoded */
+       if (strict_utf8(aTHX_ obj)) {
+           s = process_utf8(aTHX_ dst, s, e, check, 1, 1, 0);
+       }
+        else {
+            /* trust it and just copy the octets */
+           sv_setpvn(dst,(char *)s,(e-s));
+           s = e;
+        }
     }
     else {
        /* Native bytes - can always encode */
@@ -376,8 +489,8 @@ CODE:
                 *d++ = (U8)UTF8_EIGHT_BIT_LO(uv);
             }
        }
-       SvCUR_set(dst, d- (U8 *)SvPVX(dst));
-       *SvEND(dst) = '\0';
+        SvCUR_set(dst, d- (U8 *)SvPVX(dst));
+       *SvEND(dst) = '\0';
     }
 
     /* Clear out translated part of source unless asked not to */
@@ -394,7 +507,7 @@ CODE:
     XSRETURN(1);
 }
 
-MODULE = Encode         PACKAGE = Encode::XS    PREFIX = Method_
+MODULE = Encode                PACKAGE = Encode::XS    PREFIX = Method_
 
 PROTOTYPES: ENABLE
 
@@ -406,9 +519,17 @@ CODE:
     XSRETURN(1);
 }
 
+int
+Method_renewed(obj)
+SV *    obj
+CODE:
+    RETVAL = 0;
+OUTPUT:
+    RETVAL
+
 void
 Method_name(obj)
-SV *    obj
+SV *   obj
 CODE:
 {
     encode_t *enc = INT2PTR(encode_t *, SvIV(SvRV(obj)));
@@ -434,7 +555,7 @@ CODE:
     }
     sv_catsv(dst, encode_method(aTHX_ enc, enc->t_utf8, src, check,
                                &offset, term, &code));
-    SvIVX(off) = (IV)offset;
+    SvIV_set(off, (IV)offset);
     if (code == ENCODE_FOUND_TERM) {
        ST(0) = &PL_sv_yes;
     }else{
@@ -444,31 +565,57 @@ CODE:
 }
 
 void
-Method_decode(obj,src,check = 0)
-SV *    obj
-SV *    src
-int     check
+Method_decode(obj,src,check_sv = &PL_sv_no)
+SV *   obj
+SV *   src
+SV *   check_sv
 CODE:
 {
+    int check;
     encode_t *enc = INT2PTR(encode_t *, SvIV(SvRV(obj)));
     if (SvUTF8(src)) {
        sv_utf8_downgrade(src, FALSE);
     }
+    if (SvROK(check_sv)){
+       if (fallback_cb == (SV*)NULL){
+            fallback_cb = newSVsv(check_sv); /* First time */
+        }else{
+            SvSetSV(fallback_cb, check_sv); /* Been here before */
+       }
+       check = ENCODE_PERLQQ|ENCODE_LEAVE_SRC; /* same as FB_PERLQQ */
+    }else{
+       fallback_cb = (SV*)NULL;
+       check = SvIV(check_sv);
+    }
     ST(0) = encode_method(aTHX_ enc, enc->t_utf8, src, check,
                          NULL, Nullsv, NULL);
     SvUTF8_on(ST(0));
     XSRETURN(1);
 }
 
+
+
 void
-Method_encode(obj,src,check = 0)
-SV *    obj
-SV *    src
-int     check
+Method_encode(obj,src,check_sv = &PL_sv_no)
+SV *   obj
+SV *   src
+SV *   check_sv
 CODE:
 {
+    int check;
     encode_t *enc = INT2PTR(encode_t *, SvIV(SvRV(obj)));
     sv_utf8_upgrade(src);
+    if (SvROK(check_sv)){
+       if (fallback_cb == (SV*)NULL){
+            fallback_cb = newSVsv(check_sv); /* First time */
+        }else{
+            SvSetSV(fallback_cb, check_sv); /* Been here before */
+       }
+       check = ENCODE_PERLQQ|ENCODE_LEAVE_SRC; /* same as FB_PERLQQ */
+    }else{
+       fallback_cb = (SV*)NULL;
+       check = SvIV(check_sv);
+    }
     ST(0) = encode_method(aTHX_ enc, enc->f_utf8, src, check,
                          NULL, Nullsv, NULL);
     XSRETURN(1);
@@ -476,7 +623,7 @@ CODE:
 
 void
 Method_needs_lines(obj)
-SV *    obj
+SV *   obj
 CODE:
 {
     /* encode_t *enc = INT2PTR(encode_t *, SvIV(SvRV(obj))); */
@@ -486,7 +633,7 @@ CODE:
 
 void
 Method_perlio_ok(obj)
-SV *    obj
+SV *   obj
 CODE:
 {
     /* encode_t *enc = INT2PTR(encode_t *, SvIV(SvRV(obj))); */
@@ -548,16 +695,16 @@ CODE:
        if (SvTRUE(check)) {
            /* Must do things the slow way */
            U8 *dest;
-           /* We need a copy to pass to check() */
+            /* We need a copy to pass to check() */
            U8 *src  = (U8*)savepv((char *)s);
            U8 *send = s + len;
 
            New(83, dest, len, U8); /* I think */
 
            while (s < send) {
-               if (*s < 0x80){
+                if (*s < 0x80){
                    *dest++ = *s++;
-               } else {
+                } else {
                    STRLEN ulen;
                    UV uv = *s++;
 
@@ -571,7 +718,7 @@ CODE:
                    else if (!(uv & 0x02)) { ulen = 6;  uv &= 0x01; }
                    else if (!(uv & 0x01)) { ulen = 7;  uv = 0; }
                    else                   { ulen = 13; uv = 0; }
-
+               
                    /* Note change to utf8.c variable naming, for variety */
                    while (ulen--) {
                        if ((*s & 0xc0) != 0x80){
@@ -598,8 +745,8 @@ OUTPUT:
 
 bool
 is_utf8(sv, check = 0)
-SV *    sv
-int     check
+SV *   sv
+int    check
 CODE:
 {
     if (SvGMAGICAL(sv)) /* it could be $1, for example */
@@ -621,7 +768,7 @@ OUTPUT:
 
 SV *
 _utf8_on(sv)
-SV *    sv
+SV *   sv
 CODE:
 {
     if (SvPOK(sv)) {
@@ -637,7 +784,7 @@ OUTPUT:
 
 SV *
 _utf8_off(sv)
-SV *    sv
+SV *   sv
 CODE:
 {
     if (SvPOK(sv)) {
@@ -701,6 +848,13 @@ OUTPUT:
     RETVAL
 
 int
+STOP_AT_PARTIAL()
+CODE:
+    RETVAL = ENCODE_STOP_AT_PARTIAL;
+OUTPUT:
+    RETVAL
+
+int
 FB_DEFAULT()
 CODE:
     RETVAL = ENCODE_FB_DEFAULT;
@@ -754,4 +908,3 @@ BOOT:
 #include "def_t.h"
 #include "def_t.exh"
 }
-