text:
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
******************************************************************************
*
*   Copyright (C) 2003-2016, International Business Machines
*   Corporation and others.  All Rights Reserved.
*
******************************************************************************
*   file name:  ucnv_ext.cpp
*   encoding:   UTF-8
*   tab size:   8 (not used)
*   indentation:4
*
*   created on: 2003jun13
*   created by: Markus W. Scherer
*
*   Conversion extensions
*/

#include "unicode/utypes.h"

#if !UCONFIG_NO_CONVERSION && !UCONFIG_NO_LEGACY_CONVERSION

#include "unicode/uset.h"
#include "unicode/ustring.h"
#include "ucnv_bld.h"
#include "ucnv_cnv.h"
#include "ucnv_ext.h"
#include "cmemory.h"
#include "uassert.h"

/* to Unicode --------------------------------------------------------------- */

/*
 * @return lookup value for the byte, if found; else 0
 */
static inline uint32_t
ucnv_extFindToU(const uint32_t *toUSection, int32_t length, uint8_t byte) {
    uint32_t word0, word;
    int32_t i, start, limit;

    /* check the input byte against the lowest and highest section bytes */
    start=(int32_t)UCNV_EXT_TO_U_GET_BYTE(toUSection[0]);
    limit=(int32_t)UCNV_EXT_TO_U_GET_BYTE(toUSection[length-1]);
    if(byte<start || limit<byte) {
        return 0; /* the byte is out of range */
    }

    if(length==((limit-start)+1)) {
        /* direct access on a linear array */
        return UCNV_EXT_TO_U_GET_VALUE(toUSection[byte-start]); /* could be 0 */
    }

    /* word0 is suitable for <=toUSection[] comparison, word for <toUSection[] */
    word0=UCNV_EXT_TO_U_MAKE_WORD(byte, 0);

    /*
     * Shift byte once instead of each section word and add 0xffffff.
     * We will compare the shifted/added byte (bbffffff) against
     * section words which have byte values in the same bit position.
     * If and only if byte bb < section byte ss then bbffffff<ssvvvvvv
     * for all v=0..f
     * so we need not mask off the lower 24 bits of each section word.
     */
    word=word0|UCNV_EXT_TO_U_VALUE_MASK;

    /* binary search */
    start=0;
    limit=length;
    for(;;) {
        i=limit-start;
        if(i<=1) {
            break; /* done */
        }
        /* start<limit-1 */

        if(i<=4) {
            /* linear search for the last part */
            if(word0<=toUSection[start]) {
                break;
            }
            if(++start<limit && word0<=toUSection[start]) {
                break;
            }
            if(++start<limit && word0<=toUSection[start]) {
                break;
            }
            /* always break at start==limit-1 */
            ++start;
            break;
        }

        i=(start+limit)/2;
        if(word<toUSection[i]) {
            limit=i;
        } else {
            start=i;
        }
    }

    /* did we really find it? */
    if(start<limit && byte==UCNV_EXT_TO_U_GET_BYTE(word=toUSection[start])) {
        return UCNV_EXT_TO_U_GET_VALUE(word); /* never 0 */
    } else {
        return 0; /* not found */
    }
}

/*
 * TRUE if not an SI/SO stateful converter,
 * or if the match length fits with the current converter state
 */
#define UCNV_EXT_TO_U_VERIFY_SISO_MATCH(sisoState, match) \
    ((sisoState)<0 || ((sisoState)==0) == (match==1))

/*
 * this works like ucnv_extMatchFromU() except
 * - the first character is in pre
 * - no trie is used
 * - the returned matchLength is not offset by 2
 */
static int32_t
ucnv_extMatchToU(const int32_t *cx, int8_t sisoState,
                 const char *pre, int32_t preLength,
                 const char *src, int32_t srcLength,
                 uint32_t *pMatchValue,
                 UBool /*useFallback*/, UBool flush) {
    const uint32_t *toUTable, *toUSection;

    uint32_t value, matchValue;
    int32_t i, j, idx, length, matchLength;
    uint8_t b;

    if(cx==NULL || cx[UCNV_EXT_TO_U_LENGTH]<=0) {
        return 0; /* no extension data, no match */
    }

    /* initialize */
    toUTable=UCNV_EXT_ARRAY(cx, UCNV_EXT_TO_U_INDEX, uint32_t);
    idx=0;

    matchValue=0;
    i=j=matchLength=0;

    if(sisoState==0) {
        /* SBCS state of an SI/SO stateful converter, look at only exactly 1 byte */
        if(preLength>1) {
            return 0; /* no match of a DBCS sequence in SBCS mode */
        } else if(preLength==1) {
            srcLength=0;
        } else /* preLength==0 */ {
            if(srcLength>1) {
                srcLength=1;
            }
        }
        flush=TRUE;
    }

    /* we must not remember fallback matches when not using fallbacks */

    /* match input units until there is a full match or the input is consumed */
    for(;;) {
        /* go to the next section */
        toUSection=toUTable+idx;

        /* read first pair of the section */
        value=*toUSection++;
        length=UCNV_EXT_TO_U_GET_BYTE(value);
        value=UCNV_EXT_TO_U_GET_VALUE(value);
        if( value!=0 &&
            (UCNV_EXT_TO_U_IS_ROUNDTRIP(value) ||
             TO_U_USE_FALLBACK(useFallback)) &&
            UCNV_EXT_TO_U_VERIFY_SISO_MATCH(sisoState, i+j)
        ) {
            /* remember longest match so far */
            matchValue=value;
            matchLength=i+j;
        }

        /* match pre[] then src[] */
        if(i<preLength) {
            b=(uint8_t)pre[i++];
        } else if(j<srcLength) {
            b=(uint8_t)src[j++];
        } else {
            /* all input consumed, partial match */
            if(flush || (length=(i+j))>UCNV_EXT_MAX_BYTES) {
                /*
                 * end of the entire input stream, stop with the longest match so far
                 * or: partial match must not be longer than UCNV_EXT_MAX_BYTES
                 * because it must fit into state buffers
                 */
                break;
            } else {
                /* continue with more input next time */
                return -length;
            }
        }

        /* search for the current UChar */
        value=ucnv_extFindToU(toUSection, length, b);
        if(value==0) {
            /* no match here, stop with the longest match so far */
            break;
        } else {
            if(UCNV_EXT_TO_U_IS_PARTIAL(value)) {
                /* partial match, continue */
                idx=(int32_t)UCNV_EXT_TO_U_GET_PARTIAL_INDEX(value);
            } else {
                if( (UCNV_EXT_TO_U_IS_ROUNDTRIP(value) ||
                     TO_U_USE_FALLBACK(useFallback)) &&
                    UCNV_EXT_TO_U_VERIFY_SISO_MATCH(sisoState, i+j)
                ) {
                    /* full match, stop with result */
                    matchValue=value;
                    matchLength=i+j;
                } else {
                    /* full match on fallback not taken, stop with the longest match so far */
                }
                break;
            }
        }
    }

    if(matchLength==0) {
        /* no match at all */
        return 0;
    }

    /* return result */
    *pMatchValue=UCNV_EXT_TO_U_MASK_ROUNDTRIP(matchValue);
    return matchLength;
}

static inline void
ucnv_extWriteToU(UConverter *cnv, const int32_t *cx,
                 uint32_t value,
                 UChar **target, const UChar *targetLimit,
                 int32_t **offsets, int32_t srcIndex,
                 UErrorCode *pErrorCode) {
    /* output the result */
    if(UCNV_EXT_TO_U_IS_CODE_POINT(value)) {
        /* output a single code point */
        ucnv_toUWriteCodePoint(
            cnv, UCNV_EXT_TO_U_GET_CODE_POINT(value),
            target, targetLimit,
            offsets, srcIndex,
            pErrorCode);
    } else {
        /* output a string - with correct data we have resultLength>0 */
        ucnv_toUWriteUChars(
            cnv,
            UCNV_EXT_ARRAY(cx, UCNV_EXT_TO_U_UCHARS_INDEX, UChar)+
                UCNV_EXT_TO_U_GET_INDEX(value),
            UCNV_EXT_TO_U_GET_LENGTH(value),
            target, targetLimit,
            offsets, srcIndex,
            pErrorCode);
    }
}

/*
 * get the SI/SO toU state (state 0 is for SBCS, 1 for DBCS),
 * or 1 for DBCS-only,
 * or -1 if the converter is not SI/SO stateful
 *
 * Note: For SI/SO stateful converters getting here,
 * cnv->mode==0 is equivalent to firstLength==1.
 */
#define UCNV_SISO_STATE(cnv) \
    ((cnv)->sharedData->mbcs.outputType==MBCS_OUTPUT_2_SISO ? (int8_t)(cnv)->mode : \
     (cnv)->sharedData->mbcs.outputType==MBCS_OUTPUT_DBCS_ONLY ? 1 : -1)

/*
 * target<targetLimit; set error code for overflow
 */
U_CFUNC UBool
ucnv_extInitialMatchToU(UConverter *cnv, const int32_t *cx,
                        int32_t firstLength,
                        const char **src, const char *srcLimit,
                        UChar **target, const UChar *targetLimit,
                        int32_t **offsets, int32_t srcIndex,
                        UBool flush,
                        UErrorCode *pErrorCode) {
    uint32_t value = 0;  /* initialize output-only param to 0 to silence gcc */
    int32_t match;

    /* try to match */
    match=ucnv_extMatchToU(cx, (int8_t)UCNV_SISO_STATE(cnv),
                           (const char *)cnv->toUBytes, firstLength,
                           *src, (int32_t)(srcLimit-*src),
                           &value,
                           cnv->useFallback, flush);
    if(match>0) {
        /* advance src pointer for the consumed input */
        *src+=match-firstLength;

        /* write result to target */
        ucnv_extWriteToU(cnv, cx,
                         value,
                         target, targetLimit,
                         offsets, srcIndex,
                         pErrorCode);
        return TRUE;
    } else if(match<0) {
        /* save state for partial match */
        const char *s;
        int32_t j;

        /* copy the first code point */
        s=(const char *)cnv->toUBytes;
        cnv->preToUFirstLength=(int8_t)firstLength;
        for(j=0; j<firstLength; ++j) {
            cnv->preToU[j]=*s++;
        }

        /* now copy the newly consumed input */
        s=*src;
        match=-match;
        for(; j<match; ++j) {
            cnv->preToU[j]=*s++;
        }
        *src=s; /* same as *src=srcLimit; because we reached the end of input */
        cnv->preToULength=(int8_t)match;
        return TRUE;
    } else /* match==0 no match */ {
        return FALSE;
    }
}

U_CFUNC UChar32
ucnv_extSimpleMatchToU(const int32_t *cx,
                       const char *source, int32_t length,
                       UBool useFallback) {
    uint32_t value = 0;  /* initialize output-only param to 0 to silence gcc */
    int32_t match;

    if(length<=0) {
        return 0xffff;
    }

    /* try to match */
    match=ucnv_extMatchToU(cx, -1,
                           source, length,
                           NULL, 0,
                           &value,
                           useFallback, TRUE);
    if(match==length) {
        /* write result for simple, single-character conversion */
        if(UCNV_EXT_TO_U_IS_CODE_POINT(value)) {
            return UCNV_EXT_TO_U_GET_CODE_POINT(value);
        }
    }

    /*
     * return no match because
     * - match>0 && value points to string: simple conversion cannot handle multiple code points
     * - match>0 && match!=length: not all input consumed, forbidden for this function
     * - match==0: no match found in the first place
     * - match<0: partial match, not supported for simple conversion (and flush==TRUE)
     */
    return 0xfffe;
}

/*
 * continue partial match with new input
 * never called for simple, single-character conversion
 */
U_CFUNC void
ucnv_extContinueMatchToU(UConverter *cnv,
                         UConverterToUnicodeArgs *pArgs, int32_t srcIndex,
                         UErrorCode *pErrorCode) {
    uint32_t value = 0;  /* initialize output-only param to 0 to silence gcc */
    int32_t match, length;

    match=ucnv_extMatchToU(cnv->sharedData->mbcs.extIndexes, (int8_t)UCNV_SISO_STATE(cnv),
                           cnv->preToU, cnv->preToULength,
                           pArgs->source, (int32_t)(pArgs->sourceLimit-pArgs->source),
                           &value,
                           cnv->useFallback, pArgs->flush);
    if(match>0) {
        if(match>=cnv->preToULength) {
            /* advance src pointer for the consumed input */
            pArgs->source+=match-cnv->preToULength;
            cnv->preToULength=0;
        } else {
            /* the match did not use all of preToU[] - keep the rest for replay */
            length=cnv->preToULength-match;
            uprv_memmove(cnv->preToU, cnv->preToU+match, length);
            cnv->preToULength=(int8_t)-length;
        }

        /* write result */
        ucnv_extWriteToU(cnv, cnv->sharedData->mbcs.extIndexes,
                         value,
                         &pArgs->target, pArgs->targetLimit,
                         &pArgs->offsets, srcIndex,
                         pErrorCode);
    } else if(match<0) {
        /* save state for partial match */
        const char *s;
        int32_t j;

        /* just _append_ the newly consumed input to preToU[] */
        s=pArgs->source;
        match=-match;
        for(j=cnv->preToULength; j<match; ++j) {
            cnv->preToU[j]=*s++;
        }
        pArgs->source=s; /* same as *src=srcLimit; because we reached the end of input */
        cnv->preToULength=(int8_t)match;
    } else /* match==0 */ {
        /*
         * no match
         *
         * We need to split the previous input into two parts:
         *
         * 1. The first codepage character is unmappable - that's how we got into
         *    trying the extension data in the first place.
         *    We need to move it from the preToU buffer
         *    to the error buffer, set an error code,
         *    and prepare the rest of the previous input for 2.
         *
         * 2. The rest of the previous input must be converted once we
         *    come back from the callback for the first character.
         *    At that time, we have to try again from scratch to convert
         *    these input characters.
         *    The replay will be handled by the ucnv.c conversion code.
         */

        /* move the first codepage character to the error field */
        uprv_memcpy(cnv->toUBytes, cnv->preToU, cnv->preToUFirstLength);
        cnv->toULength=cnv->preToUFirstLength;

        /* move the rest up inside the buffer */
        length=cnv->preToULength-cnv->preToUFirstLength;
        if(length>0) {
            uprv_memmove(cnv->preToU, cnv->preToU+cnv->preToUFirstLength, length);
        }

        /* mark preToU for replay */
        cnv->preToULength=(int8_t)-length;

        /* set the error code for unassigned */
        *pErrorCode=U_INVALID_CHAR_FOUND;
    }
}

/* from Unicode ------------------------------------------------------------- */

// Use roundtrips, "good one-way" mappings, and some normal fallbacks.
static inline UBool
extFromUUseMapping(UBool useFallback, uint32_t value, UChar32 firstCP) {
    return
        ((value&UCNV_EXT_FROM_U_STATUS_MASK)!=0 ||
            FROM_U_USE_FALLBACK(useFallback, firstCP)) &&
        (value&UCNV_EXT_FROM_U_RESERVED_MASK)==0;
}

/*
 * @return index of the UChar, if found; else <0
 */
static inline int32_t
ucnv_extFindFromU(const UChar *fromUSection, int32_t length, UChar u) {
    int32_t i, start, limit;

    /* binary search */
    start=0;
    limit=length;
    for(;;) {
        i=limit-start;
        if(i<=1) {
            break; /* done */
        }
        /* start<limit-1 */

        if(i<=4) {
            /* linear search for the last part */
            if(u<=fromUSection[start]) {
                break;
            }
            if(++start<limit && u<=fromUSection[start]) {
                break;
            }
            if(++start<limit && u<=fromUSection[start]) {
                break;
            }
            /* always break at start==limit-1 */
            ++start;
            break;
        }

        i=(start+limit)/2;
        if(u<fromUSection[i]) {
            limit=i;
        } else {
            start=i;
        }
    }

    /* did we really find it? */
    if(start<limit && u==fromUSection[start]) {
        return start;
    } else {
        return -1; /* not found */
    }
}

/*
 * @param cx pointer to extension data; if NULL, returns 0
 * @param firstCP the first code point before all the other UChars
 * @param pre UChars that must match; !initialMatch: partial match with them
 * @param preLength length of pre, >=0
 * @param src UChars that can be used to complete a match
 * @param srcLength length of src, >=0
 * @param pMatchValue [out] output result value for the match from the data structure
 * @param useFallback "use fallback" flag, usually from cnv->useFallback
 * @param flush TRUE if the end of the input stream is reached
 * @return >1: matched, return value=total match length (number of input units matched)
 *          1: matched, no mapping but request for <subchar1>
 *             (only for the first code point)
 *          0: no match
 *         <0: partial match, return value=negative total match length
 *             (partial matches are never returned for flush==TRUE)
 *             (partial matches are never returned as being longer than UCNV_EXT_MAX_UCHARS)
 *         the matchLength is 2 if only firstCP matched, and >2 if firstCP and
 *         further code units matched
 */
static int32_t
ucnv_extMatchFromU(const int32_t *cx,
                   UChar32 firstCP,
                   const UChar *pre, int32_t preLength,
                   const UChar *src, int32_t srcLength,
                   uint32_t *pMatchValue,
                   UBool useFallback, UBool flush) {
    const uint16_t *stage12, *stage3;
    const uint32_t *stage3b;

    const UChar *fromUTableUChars, *fromUSectionUChars;
    const uint32_t *fromUTableValues, *fromUSectionValues;

    uint32_t value, matchValue;
    int32_t i, j, idx, length, matchLength;
    UChar c;

    if(cx==NULL) {
        return 0; /* no extension data, no match */
    }

    /* trie lookup of firstCP */
    idx=firstCP>>10; /* stage 1 index */
    if(idx>=cx[UCNV_EXT_FROM_U_STAGE_1_LENGTH]) {
        return 0; /* the first code point is outside the trie */
    }

    stage12=UCNV_EXT_ARRAY(cx, UCNV_EXT_FROM_U_STAGE_12_INDEX, uint16_t);
    stage3=UCNV_EXT_ARRAY(cx, UCNV_EXT_FROM_U_STAGE_3_INDEX, uint16_t);
    idx=UCNV_EXT_FROM_U(stage12, stage3, idx, firstCP);

    stage3b=UCNV_EXT_ARRAY(cx, UCNV_EXT_FROM_U_STAGE_3B_INDEX, uint32_t);
    value=stage3b[idx];
    if(value==0) {
        return 0;
    }

    /*
     * Tests for (value&UCNV_EXT_FROM_U_RESERVED_MASK)==0:
     * Do not interpret values with reserved bits used, for forward compatibility,
     * and do not even remember intermediate results with reserved bits used.
     */

    if(UCNV_EXT_TO_U_IS_PARTIAL(value)) {
        /* partial match, enter the loop below */
        idx=(int32_t)UCNV_EXT_FROM_U_GET_PARTIAL_INDEX(value);

        /* initialize */
        fromUTableUChars=UCNV_EXT_ARRAY(cx, UCNV_EXT_FROM_U_UCHARS_INDEX, UChar);
        fromUTableValues=UCNV_EXT_ARRAY(cx, UCNV_EXT_FROM_U_VALUES_INDEX, uint32_t);

        matchValue=0;
        i=j=matchLength=0;

        /* we must not remember fallback matches when not using fallbacks */

        /* match input units until there is a full match or the input is consumed */
        for(;;) {
            /* go to the next section */
            fromUSectionUChars=fromUTableUChars+idx;
            fromUSectionValues=fromUTableValues+idx;

            /* read first pair of the section */
            length=*fromUSectionUChars++;
            value=*fromUSectionValues++;
            if(value!=0 && extFromUUseMapping(useFallback, value, firstCP)) {
                /* remember longest match so far */
                matchValue=value;
                matchLength=2+i+j;
            }

            /* match pre[] then src[] */
            if(i<preLength) {
                c=pre[i++];
            } else if(j<srcLength) {
                c=src[j++];
            } else {
                /* all input consumed, partial match */
                if(flush || (length=(i+j))>UCNV_EXT_MAX_UCHARS) {
                    /*
                     * end of the entire input stream, stop with the longest match so far
                     * or: partial match must not be longer than UCNV_EXT_MAX_UCHARS
                     * because it must fit into state buffers
                     */
                    break;
                } else {
                    /* continue with more input next time */
                    return -(2+length);
                }
            }

            /* search for the current UChar */
            idx=ucnv_extFindFromU(fromUSectionUChars, length, c);
            if(idx<0) {
                /* no match here, stop with the longest match so far */
                break;
            } else {
                value=fromUSectionValues[idx];
                if(UCNV_EXT_FROM_U_IS_PARTIAL(value)) {
                    /* partial match, continue */
                    idx=(int32_t)UCNV_EXT_FROM_U_GET_PARTIAL_INDEX(value);
                } else {
                    if(extFromUUseMapping(useFallback, value, firstCP)) {
                        /* full match, stop with result */
                        matchValue=value;
                        matchLength=2+i+j;
                    } else {
                        /* full match on fallback not taken, stop with the longest match so far */
                    }
                    break;
                }
            }
        }

        if(matchLength==0) {
            /* no match at all */
            return 0;
        }
    } else /* result from firstCP trie lookup */ {
        if(extFromUUseMapping(useFallback, value, firstCP)) {
            /* full match, stop with result */
            matchValue=value;
            matchLength=2;
        } else {
            /* fallback not taken */
            return 0;
        }
    }

    /* return result */
    if(matchValue==UCNV_EXT_FROM_U_SUBCHAR1) {
        return 1; /* assert matchLength==2 */
    }

    *pMatchValue=matchValue;
    return matchLength;
}

/*
 * @param value fromUnicode mapping table value; ignores roundtrip and reserved bits
 */
static inline void
ucnv_extWriteFromU(UConverter *cnv, const int32_t *cx,
                   uint32_t value,
                   char **target, const char *targetLimit,
                   int32_t **offsets, int32_t srcIndex,
                   UErrorCode *pErrorCode) {
    uint8_t buffer[1+UCNV_EXT_MAX_BYTES];
    const uint8_t *result;
    int32_t length, prevLength;

    length=UCNV_EXT_FROM_U_GET_LENGTH(value);
    value=(uint32_t)UCNV_EXT_FROM_U_GET_DATA(value);

    /* output the result */
    if(length<=UCNV_EXT_FROM_U_MAX_DIRECT_LENGTH) {
        /*
         * Generate a byte array and then write it below.
         * This is not the fastest possible way, but it should be ok for
         * extension mappings, and it is much simpler.
         * Offset and overflow handling are only done once this way.
         */
        uint8_t *p=buffer+1; /* reserve buffer[0] for shiftByte below */
        switch(length) {
        case 3:
            *p++=(uint8_t)(value>>16);
            U_FALLTHROUGH;
        case 2:
            *p++=(uint8_t)(value>>8);
            U_FALLTHROUGH;
        case 1:
            *p++=(uint8_t)value;
            U_FALLTHROUGH;
        default:
            break; /* will never occur */
        }
        result=buffer+1;
    } else {
        result=UCNV_EXT_ARRAY(cx, UCNV_EXT_FROM_U_BYTES_INDEX, uint8_t)+value;
    }

    /* with correct data we have length>0 */

    if((prevLength=cnv->fromUnicodeStatus)!=0) {
        /* handle SI/SO stateful output */
        uint8_t shiftByte;

        if(prevLength>1 && length==1) {
            /* change from double-byte mode to single-byte */
            shiftByte=(uint8_t)UCNV_SI;
            cnv->fromUnicodeStatus=1;
        } else if(prevLength==1 && length>1) {
            /* change from single-byte mode to double-byte */
            shiftByte=(uint8_t)UCNV_SO;
            cnv->fromUnicodeStatus=2;
        } else {
            shiftByte=0;
        }

        if(shiftByte!=0) {
            /* prepend the shift byte to the result bytes */
            buffer[0]=shiftByte;
            if(result!=buffer+1) {
                uprv_memcpy(buffer+1, result, length);
            }
            result=buffer;
            ++length;
        }
    }

    ucnv_fromUWriteBytes(cnv, (const char *)result, length,
                         target, targetLimit,
                         offsets, srcIndex,
                         pErrorCode);
}

/*
 * target<targetLimit; set error code for overflow
 */
U_CFUNC UBool
ucnv_extInitialMatchFromU(UConverter *cnv, const int32_t *cx,
                          UChar32 cp,
                          const UChar **src, const UChar *srcLimit,
                          char **target, const char *targetLimit,
                          int32_t **offsets, int32_t srcIndex,
                          UBool flush,
                          UErrorCode *pErrorCode) {
    uint32_t value = 0;  /* initialize output-only param to 0 to silence gcc */
    int32_t match;

    /* try to match */
    match=ucnv_extMatchFromU(cx, cp,
                             NULL, 0,
                             *src, (int32_t)(srcLimit-*src),
                             &value,
                             cnv->useFallback, flush);

    /* reject a match if the result is a single byte for DBCS-only */
    if( match>=2 &&
        !(UCNV_EXT_FROM_U_GET_LENGTH(value)==1 &&
          cnv->sharedData->mbcs.outputType==MBCS_OUTPUT_DBCS_ONLY)
    ) {
        /* advance src pointer for the consumed input */
        *src+=match-2; /* remove 2 for the initial code point */

        /* write result to target */
        ucnv_extWriteFromU(cnv, cx,
                           value,
                           target, targetLimit,
                           offsets, srcIndex,
                           pErrorCode);
        return TRUE;
    } else if(match<0) {
        /* save state for partial match */
        const UChar *s;
        int32_t j;

        /* copy the first code point */
        cnv->preFromUFirstCP=cp;

        /* now copy the newly consumed input */
        s=*src;
        match=-match-2; /* remove 2 for the initial code point */
        for(j=0; j<match; ++j) {
            cnv->preFromU[j]=*s++;
        }
        *src=s; /* same as *src=srcLimit; because we reached the end of input */
        cnv->preFromULength=(int8_t)match;
        return TRUE;
    } else if(match==1) {
        /* matched, no mapping but request for <subchar1> */
        cnv->useSubChar1=TRUE;
        return FALSE;
    } else /* match==0 no match */ {
        return FALSE;
    }
}

/*
 * Used by ISO 2022 implementation.
 * @return number of bytes in *pValue; negative number if fallback; 0 for no mapping
 */
U_CFUNC int32_t
ucnv_extSimpleMatchFromU(const int32_t *cx,
                         UChar32 cp, uint32_t *pValue,
                         UBool useFallback) {
    uint32_t value;
    int32_t match;

    /* try to match */
    match=ucnv_extMatchFromU(cx,
                             cp,
                             NULL, 0,
                             NULL, 0,
                             &value,
                             useFallback, TRUE);
    if(match>=2) {
        /* write result for simple, single-character conversion */
        int32_t length;
        int isRoundtrip;

        isRoundtrip=UCNV_EXT_FROM_U_IS_ROUNDTRIP(value);
        length=UCNV_EXT_FROM_U_GET_LENGTH(value);
        value=(uint32_t)UCNV_EXT_FROM_U_GET_DATA(value);

        if(length<=UCNV_EXT_FROM_U_MAX_DIRECT_LENGTH) {
            *pValue=value;
            return isRoundtrip ? length : -length;
#if 0 /* not currently used */
        } else if(length==4) {
            /* de-serialize a 4-byte result */
            const uint8_t *result=UCNV_EXT_ARRAY(cx, UCNV_EXT_FROM_U_BYTES_INDEX, uint8_t)+value;
            *pValue=
                ((uint32_t)result[0]<<24)|
                ((uint32_t)result[1]<<16)|
                ((uint32_t)result[2]<<8)|
                result[3];
            return isRoundtrip ? 4 : -4;
#endif
        }
    }

    /*
     * return no match because
     * - match>1 && resultLength>4: result too long for simple conversion
     * - match==1: no match found, <subchar1> preferred
     * - match==0: no match found in the first place
     * - match<0: partial match, not supported for simple conversion (and flush==TRUE)
     */
    return 0;
}

/*
 * continue partial match with new input, requires cnv->preFromUFirstCP>=0
 * never called for simple, single-character conversion
 */
U_CFUNC void
ucnv_extContinueMatchFromU(UConverter *cnv,
                           UConverterFromUnicodeArgs *pArgs, int32_t srcIndex,
                           UErrorCode *pErrorCode) {
    uint32_t value = 0;  /* initialize output-only param to 0 to silence gcc */
    int32_t match;

    match=ucnv_extMatchFromU(cnv->sharedData->mbcs.extIndexes,
                             cnv->preFromUFirstCP,
                             cnv->preFromU, cnv->preFromULength,
                             pArgs->source, (int32_t)(pArgs->sourceLimit-pArgs->source),
                             &value,
                             cnv->useFallback, pArgs->flush);
    if(match>=2) {
        match-=2; /* remove 2 for the initial code point */

        if(match>=cnv->preFromULength) {
            /* advance src pointer for the consumed input */
            pArgs->source+=match-cnv->preFromULength;
            cnv->preFromULength=0;
        } else {
            /* the match did not use all of preFromU[] - keep the rest for replay */
            int32_t length=cnv->preFromULength-match;
            u_memmove(cnv->preFromU, cnv->preFromU+match, length);
            cnv->preFromULength=(int8_t)-length;
        }

        /* finish the partial match */
        cnv->preFromUFirstCP=U_SENTINEL;

        /* write result */
        ucnv_extWriteFromU(cnv, cnv->sharedData->mbcs.extIndexes,
                           value,
                           &pArgs->target, pArgs->targetLimit,
                           &pArgs->offsets, srcIndex,
                           pErrorCode);
    } else if(match<0) {
        /* save state for partial match */
        const UChar *s;
        int32_t j;

        /* just _append_ the newly consumed input to preFromU[] */
        s=pArgs->source;
        match=-match-2; /* remove 2 for the initial code point */
        for(j=cnv->preFromULength; j<match; ++j) {
            U_ASSERT(j>=0);
            cnv->preFromU[j]=*s++;
        }
        pArgs->source=s; /* same as *src=srcLimit; because we reached the end of input */
        cnv->preFromULength=(int8_t)match;
    } else /* match==0 or 1 */ {
        /*
         * no match
         *
         * We need to split the previous input into two parts:
         *
         * 1. The first code point is unmappable - that's how we got into
         *    trying the extension data in the first place.
         *    We need to move it from the preFromU buffer
         *    to the error buffer, set an error code,
         *    and prepare the rest of the previous input for 2.
         *
         * 2. The rest of the previous input must be converted once we
         *    come back from the callback for the first code point.
         *    At that time, we have to try again from scratch to convert
         *    these input characters.
         *    The replay will be handled by the ucnv.c conversion code.
         */

        if(match==1) {
            /* matched, no mapping but request for <subchar1> */
            cnv->useSubChar1=TRUE;
        }

        /* move the first code point to the error field */
        cnv->fromUChar32=cnv->preFromUFirstCP;
        cnv->preFromUFirstCP=U_SENTINEL;

        /* mark preFromU for replay */
        cnv->preFromULength=-cnv->preFromULength;

        /* set the error code for unassigned */
        *pErrorCode=U_INVALID_CHAR_FOUND;
    }
}

static UBool
extSetUseMapping(UConverterUnicodeSet which, int32_t minLength, uint32_t value) {
    if(which==UCNV_ROUNDTRIP_SET) {
        // Add only code points for which the roundtrip flag is set.
        // Do not add any fallbacks, even if ucnv_fromUnicode() would use them
        // (fallbacks from PUA). See the API docs for ucnv_getUnicodeSet().
        //
        // By analogy, also do not add "good one-way" mappings.
        //
        // Do not add entries with reserved bits set.
        if(((value&(UCNV_EXT_FROM_U_ROUNDTRIP_FLAG|UCNV_EXT_FROM_U_RESERVED_MASK))!=
                UCNV_EXT_FROM_U_ROUNDTRIP_FLAG)) {
            return FALSE;
        }
    } else /* UCNV_ROUNDTRIP_AND_FALLBACK_SET */ {
        // Do not add entries with reserved bits set.
        if((value&UCNV_EXT_FROM_U_RESERVED_MASK)!=0) {
            return FALSE;
        }
    }
    // Do not add <subchar1> entries or other (future?) pseudo-entries
    // with an output length of 0.
    return UCNV_EXT_FROM_U_GET_LENGTH(value)>=minLength;
}

static void
ucnv_extGetUnicodeSetString(const UConverterSharedData *sharedData,
                            const int32_t *cx,
                            const USetAdder *sa,
                            UConverterUnicodeSet which,
                            int32_t minLength,
                            UChar32 firstCP,
                            UChar s[UCNV_EXT_MAX_UCHARS], int32_t length,
                            int32_t sectionIndex,
                            UErrorCode *pErrorCode) {
    const UChar *fromUSectionUChars;
    const uint32_t *fromUSectionValues;

    uint32_t value;
    int32_t i, count;

    fromUSectionUChars=UCNV_EXT_ARRAY(cx, UCNV_EXT_FROM_U_UCHARS_INDEX, UChar)+sectionIndex;
    fromUSectionValues=UCNV_EXT_ARRAY(cx, UCNV_EXT_FROM_U_VALUES_INDEX, uint32_t)+sectionIndex;

    /* read first pair of the section */
    count=*fromUSectionUChars++;
    value=*fromUSectionValues++;

    if(extSetUseMapping(which, minLength, value)) {
        if(length==U16_LENGTH(firstCP)) {
            /* add the initial code point */
            sa->add(sa->set, firstCP);
        } else {
            /* add the string so far */
            sa->addString(sa->set, s, length);
        }
    }

    for(i=0; i<count; ++i) {
        /* append this code unit and recurse or add the string */
        s[length]=fromUSectionUChars[i];
        value=fromUSectionValues[i];

        if(value==0) {
            /* no mapping, do nothing */
        } else if(UCNV_EXT_FROM_U_IS_PARTIAL(value)) {
            ucnv_extGetUnicodeSetString(
                sharedData, cx, sa, which, minLength,
                firstCP, s, length+1,
                (int32_t)UCNV_EXT_FROM_U_GET_PARTIAL_INDEX(value),
                pErrorCode);
        } else if(extSetUseMapping(which, minLength, value)) {
            sa->addString(sa->set, s, length+1);
        }
    }
}

U_CFUNC void
ucnv_extGetUnicodeSet(const UConverterSharedData *sharedData,
                      const USetAdder *sa,
                      UConverterUnicodeSet which,
                      UConverterSetFilter filter,
                      UErrorCode *pErrorCode) {
    const int32_t *cx;
    const uint16_t *stage12, *stage3, *ps2, *ps3;
    const uint32_t *stage3b;

    uint32_t value;
    int32_t st1, stage1Length, st2, st3, minLength;

    UChar s[UCNV_EXT_MAX_UCHARS];
    UChar32 c;
    int32_t length;

    cx=sharedData->mbcs.extIndexes;
    if(cx==NULL) {
        return;
    }

    stage12=UCNV_EXT_ARRAY(cx, UCNV_EXT_FROM_U_STAGE_12_INDEX, uint16_t);
    stage3=UCNV_EXT_ARRAY(cx, UCNV_EXT_FROM_U_STAGE_3_INDEX, uint16_t);
    stage3b=UCNV_EXT_ARRAY(cx, UCNV_EXT_FROM_U_STAGE_3B_INDEX, uint32_t);

    stage1Length=cx[UCNV_EXT_FROM_U_STAGE_1_LENGTH];

    /* enumerate the from-Unicode trie table */
    c=0; /* keep track of the current code point while enumerating */

    if(filter==UCNV_SET_FILTER_2022_CN) {
        minLength=3;
    } else if( sharedData->mbcs.outputType==MBCS_OUTPUT_DBCS_ONLY ||
               filter!=UCNV_SET_FILTER_NONE
    ) {
        /* DBCS-only, ignore single-byte results */
        minLength=2;
    } else {
        minLength=1;
    }

    /*
     * the trie enumeration is almost the same as
     * in MBCSGetUnicodeSet() for MBCS_OUTPUT_1
     */
    for(st1=0; st1<stage1Length; ++st1) {
        st2=stage12[st1];
        if(st2>stage1Length) {
            ps2=stage12+st2;
            for(st2=0; st2<64; ++st2) {
                if((st3=(int32_t)ps2[st2]<<UCNV_EXT_STAGE_2_LEFT_SHIFT)!=0) {
                    /* read the stage 3 block */
                    ps3=stage3+st3;

                    do {
                        value=stage3b[*ps3++];
                        if(value==0) {
                            /* no mapping, do nothing */
                        } else if(UCNV_EXT_FROM_U_IS_PARTIAL(value)) {
                            // Recurse for partial results.
                            length=0;
                            U16_APPEND_UNSAFE(s, length, c);
                            ucnv_extGetUnicodeSetString(
                                sharedData, cx, sa, which, minLength,
                                c, s, length,
                                (int32_t)UCNV_EXT_FROM_U_GET_PARTIAL_INDEX(value),
                                pErrorCode);
                        } else if(extSetUseMapping(which, minLength, value)) {
                            switch(filter) {
                            case UCNV_SET_FILTER_2022_CN:
                                if(!(UCNV_EXT_FROM_U_GET_LENGTH(value)==3 && UCNV_EXT_FROM_U_GET_DATA(value)<=0x82ffff)) {
                                    continue;
                                }
                                break;
                            case UCNV_SET_FILTER_SJIS:
                                if(!(UCNV_EXT_FROM_U_GET_LENGTH(value)==2 && (value=UCNV_EXT_FROM_U_GET_DATA(value))>=0x8140 && value<=0xeffc)) {
                                    continue;
                                }
                                break;
                            case UCNV_SET_FILTER_GR94DBCS:
                                if(!(UCNV_EXT_FROM_U_GET_LENGTH(value)==2 &&
                                     (uint16_t)((value=UCNV_EXT_FROM_U_GET_DATA(value))-0xa1a1)<=(0xfefe - 0xa1a1) &&
                                     (uint8_t)(value-0xa1)<=(0xfe - 0xa1))) {
                                    continue;
                                }
                                break;
                            case UCNV_SET_FILTER_HZ:
                                if(!(UCNV_EXT_FROM_U_GET_LENGTH(value)==2 &&
                                     (uint16_t)((value=UCNV_EXT_FROM_U_GET_DATA(value))-0xa1a1)<=(0xfdfe - 0xa1a1) &&
                                     (uint8_t)(value-0xa1)<=(0xfe - 0xa1))) {
                                    continue;
                                }
                                break;
                            default:
                                /*
                                 * UCNV_SET_FILTER_NONE,
                                 * or UCNV_SET_FILTER_DBCS_ONLY which is handled via minLength
                                 */
                                break;
                            }
                            sa->add(sa->set, c);
                        }
                    } while((++c&0xf)!=0);
                } else {
                    c+=16; /* empty stage 3 block */
                }
            }
        } else {
            c+=1024; /* empty stage 2 block */
        }
    }
}

#endif /* #if !UCONFIG_NO_LEGACY_CONVERSION */

repo_language: C

mime_type: text/x-c


text:
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
*******************************************************************************
*
*   Copyright (C) 1997-2016, International Business Machines
*   Corporation and others.  All Rights Reserved.
*
*******************************************************************************
*   file name:  loclikely.cpp
*   encoding:   UTF-8
*   tab size:   8 (not used)
*   indentation:4
*
*   created on: 2010feb25
*   created by: Markus W. Scherer
*
*   Code for likely and minimized locale subtags, separated out from other .cpp files
*   that then do not depend on resource bundle code and likely-subtags data.
*/

#include "unicode/bytestream.h"
#include "unicode/utypes.h"
#include "unicode/locid.h"
#include "unicode/putil.h"
#include "unicode/uchar.h"
#include "unicode/uloc.h"
#include "unicode/ures.h"
#include "unicode/uscript.h"
#include "bytesinkutil.h"
#include "charstr.h"
#include "cmemory.h"
#include "cstring.h"
#include "ulocimp.h"
#include "ustr_imp.h"

/**
 * These are the canonical strings for unknown languages, scripts and regions.
 **/
static const char* const unknownLanguage = "und";
static const char* const unknownScript = "Zzzz";
static const char* const unknownRegion = "ZZ";

/**
 * This function looks for the localeID in the likelySubtags resource.
 *
 * @param localeID The tag to find.
 * @param buffer A buffer to hold the matching entry
 * @param bufferLength The length of the output buffer
 * @return A pointer to "buffer" if found, or a null pointer if not.
 */
static const char*  U_CALLCONV
findLikelySubtags(const char* localeID,
                  char* buffer,
                  int32_t bufferLength,
                  UErrorCode* err) {
    const char* result = NULL;

    if (!U_FAILURE(*err)) {
        int32_t resLen = 0;
        const UChar* s = NULL;
        UErrorCode tmpErr = U_ZERO_ERROR;
        icu::LocalUResourceBundlePointer subtags(ures_openDirect(NULL, "likelySubtags", &tmpErr));
        if (U_SUCCESS(tmpErr)) {
            icu::CharString und;
            if (localeID != NULL) {
                if (*localeID == '\0') {
                    localeID = unknownLanguage;
                } else if (*localeID == '_') {
                    und.append(unknownLanguage, *err);
                    und.append(localeID, *err);
                    if (U_FAILURE(*err)) {
                        return NULL;
                    }
                    localeID = und.data();
                }
            }
            s = ures_getStringByKey(subtags.getAlias(), localeID, &resLen, &tmpErr);

            if (U_FAILURE(tmpErr)) {
                /*
                 * If a resource is missing, it's not really an error, it's
                 * just that we don't have any data for that particular locale ID.
                 */
                if (tmpErr != U_MISSING_RESOURCE_ERROR) {
                    *err = tmpErr;
                }
            }
            else if (resLen >= bufferLength) {
                /* The buffer should never overflow. */
                *err = U_INTERNAL_PROGRAM_ERROR;
            }
            else {
                u_UCharsToChars(s, buffer, resLen + 1);
                if (resLen >= 3 &&
                    uprv_strnicmp(buffer, unknownLanguage, 3) == 0 &&
                    (resLen == 3 || buffer[3] == '_')) {
                    uprv_memmove(buffer, buffer + 3, resLen - 3 + 1);
                }
                result = buffer;
            }
        } else {
            *err = tmpErr;
        }
    }

    return result;
}

/**
 * Append a tag to a buffer, adding the separator if necessary.  The buffer
 * must be large enough to contain the resulting tag plus any separator
 * necessary. The tag must not be a zero-length string.
 *
 * @param tag The tag to add.
 * @param tagLength The length of the tag.
 * @param buffer The output buffer.
 * @param bufferLength The length of the output buffer.  This is an input/ouput parameter.
 **/
static void U_CALLCONV
appendTag(
    const char* tag,
    int32_t tagLength,
    char* buffer,
    int32_t* bufferLength,
    UBool withSeparator) {

    if (withSeparator) {
        buffer[*bufferLength] = '_';
        ++(*bufferLength);
    }

    uprv_memmove(
        &buffer[*bufferLength],
        tag,
        tagLength);

    *bufferLength += tagLength;
}

/**
 * Create a tag string from the supplied parameters.  The lang, script and region
 * parameters may be NULL pointers. If they are, their corresponding length parameters
 * must be less than or equal to 0.
 *
 * If any of the language, script or region parameters are empty, and the alternateTags
 * parameter is not NULL, it will be parsed for potential language, script and region tags
 * to be used when constructing the new tag.  If the alternateTags parameter is NULL, or
 * it contains no language tag, the default tag for the unknown language is used.
 *
 * If the length of the new string exceeds the capacity of the output buffer,
 * the function copies as many bytes to the output buffer as it can, and returns
 * the error U_BUFFER_OVERFLOW_ERROR.
 *
 * If an illegal argument is provided, the function returns the error
 * U_ILLEGAL_ARGUMENT_ERROR.
 *
 * Note that this function can return the warning U_STRING_NOT_TERMINATED_WARNING if
 * the tag string fits in the output buffer, but the null terminator doesn't.
 *
 * @param lang The language tag to use.
 * @param langLength The length of the language tag.
 * @param script The script tag to use.
 * @param scriptLength The length of the script tag.
 * @param region The region tag to use.
 * @param regionLength The length of the region tag.
 * @param trailing Any trailing data to append to the new tag.
 * @param trailingLength The length of the trailing data.
 * @param alternateTags A string containing any alternate tags.
 * @param sink The output sink receiving the tag string.
 * @param err A pointer to a UErrorCode for error reporting.
 **/
static void U_CALLCONV
createTagStringWithAlternates(
    const char* lang,
    int32_t langLength,
    const char* script,
    int32_t scriptLength,
    const char* region,
    int32_t regionLength,
    const char* trailing,
    int32_t trailingLength,
    const char* alternateTags,
    icu::ByteSink& sink,
    UErrorCode* err) {

    if (U_FAILURE(*err)) {
        goto error;
    }
    else if (langLength >= ULOC_LANG_CAPACITY ||
             scriptLength >= ULOC_SCRIPT_CAPACITY ||
             regionLength >= ULOC_COUNTRY_CAPACITY) {
        goto error;
    }
    else {
        /**
         * ULOC_FULLNAME_CAPACITY will provide enough capacity
         * that we can build a string that contains the language,
         * script and region code without worrying about overrunning
         * the user-supplied buffer.
         **/
        char tagBuffer[ULOC_FULLNAME_CAPACITY];
        int32_t tagLength = 0;
        UBool regionAppended = FALSE;

        if (langLength > 0) {
            appendTag(
                lang,
                langLength,
                tagBuffer,
                &tagLength,
                /*withSeparator=*/FALSE);
        }
        else if (alternateTags == NULL) {
            /*
             * Use the empty string for an unknown language, if
             * we found no language.
             */
        }
        else {
            /*
             * Parse the alternateTags string for the language.
             */
            char alternateLang[ULOC_LANG_CAPACITY];
            int32_t alternateLangLength = sizeof(alternateLang);

            alternateLangLength =
                uloc_getLanguage(
                    alternateTags,
                    alternateLang,
                    alternateLangLength,
                    err);
            if(U_FAILURE(*err) ||
                alternateLangLength >= ULOC_LANG_CAPACITY) {
                goto error;
            }
            else if (alternateLangLength == 0) {
                /*
                 * Use the empty string for an unknown language, if
                 * we found no language.
                 */
            }
            else {
                appendTag(
                    alternateLang,
                    alternateLangLength,
                    tagBuffer,
                    &tagLength,
                    /*withSeparator=*/FALSE);
            }
        }

        if (scriptLength > 0) {
            appendTag(
                script,
                scriptLength,
                tagBuffer,
                &tagLength,
                /*withSeparator=*/TRUE);
        }
        else if (alternateTags != NULL) {
            /*
             * Parse the alternateTags string for the script.
             */
            char alternateScript[ULOC_SCRIPT_CAPACITY];

            const int32_t alternateScriptLength =
                uloc_getScript(
                    alternateTags,
                    alternateScript,
                    sizeof(alternateScript),
                    err);

            if (U_FAILURE(*err) ||
                alternateScriptLength >= ULOC_SCRIPT_CAPACITY) {
                goto error;
            }
            else if (alternateScriptLength > 0) {
                appendTag(
                    alternateScript,
                    alternateScriptLength,
                    tagBuffer,
                    &tagLength,
                    /*withSeparator=*/TRUE);
            }
        }

        if (regionLength > 0) {
            appendTag(
                region,
                regionLength,
                tagBuffer,
                &tagLength,
                /*withSeparator=*/TRUE);

            regionAppended = TRUE;
        }
        else if (alternateTags != NULL) {
            /*
             * Parse the alternateTags string for the region.
             */
            char alternateRegion[ULOC_COUNTRY_CAPACITY];

            const int32_t alternateRegionLength =
                uloc_getCountry(
                    alternateTags,
                    alternateRegion,
                    sizeof(alternateRegion),
                    err);
            if (U_FAILURE(*err) ||
                alternateRegionLength >= ULOC_COUNTRY_CAPACITY) {
                goto error;
            }
            else if (alternateRegionLength > 0) {
                appendTag(
                    alternateRegion,
                    alternateRegionLength,
                    tagBuffer,
                    &tagLength,
                    /*withSeparator=*/TRUE);

                regionAppended = TRUE;
            }
        }

        /**
         * Copy the partial tag from our internal buffer to the supplied
         * target.
         **/
        sink.Append(tagBuffer, tagLength);

        if (trailingLength > 0) {
            if (*trailing != '@') {
                sink.Append("_", 1);
                if (!regionAppended) {
                    /* extra separator is required */
                    sink.Append("_", 1);
                }
            }

            /*
             * Copy the trailing data into the supplied buffer.
             */
            sink.Append(trailing, trailingLength);
        }

        return;
    }

error:

    /**
     * An overflow indicates the locale ID passed in
     * is ill-formed.  If we got here, and there was
     * no previous error, it's an implicit overflow.
     **/
    if (*err ==  U_BUFFER_OVERFLOW_ERROR ||
        U_SUCCESS(*err)) {
        *err = U_ILLEGAL_ARGUMENT_ERROR;
    }
}

/**
 * Create a tag string from the supplied parameters.  The lang, script and region
 * parameters may be NULL pointers. If they are, their corresponding length parameters
 * must be less than or equal to 0.  If the lang parameter is an empty string, the
 * default value for an unknown language is written to the output buffer.
 *
 * If the length of the new string exceeds the capacity of the output buffer,
 * the function copies as many bytes to the output buffer as it can, and returns
 * the error U_BUFFER_OVERFLOW_ERROR.
 *
 * If an illegal argument is provided, the function returns the error
 * U_ILLEGAL_ARGUMENT_ERROR.
 *
 * @param lang The language tag to use.
 * @param langLength The length of the language tag.
 * @param script The script tag to use.
 * @param scriptLength The length of the script tag.
 * @param region The region tag to use.
 * @param regionLength The length of the region tag.
 * @param trailing Any trailing data to append to the new tag.
 * @param trailingLength The length of the trailing data.
 * @param sink The output sink receiving the tag string.
 * @param err A pointer to a UErrorCode for error reporting.
 **/
static void U_CALLCONV
createTagString(
    const char* lang,
    int32_t langLength,
    const char* script,
    int32_t scriptLength,
    const char* region,
    int32_t regionLength,
    const char* trailing,
    int32_t trailingLength,
    icu::ByteSink& sink,
    UErrorCode* err)
{
    createTagStringWithAlternates(
                lang,
                langLength,
                script,
                scriptLength,
                region,
                regionLength,
                trailing,
                trailingLength,
                NULL,
                sink,
                err);
}

/**
 * Parse the language, script, and region subtags from a tag string, and copy the
 * results into the corresponding output parameters. The buffers are null-terminated,
 * unless overflow occurs.
 *
 * The langLength, scriptLength, and regionLength parameters are input/output
 * parameters, and must contain the capacity of their corresponding buffers on
 * input.  On output, they will contain the actual length of the buffers, not
 * including the null terminator.
 *
 * If the length of any of the output subtags exceeds the capacity of the corresponding
 * buffer, the function copies as many bytes to the output buffer as it can, and returns
 * the error U_BUFFER_OVERFLOW_ERROR.  It will not parse any more subtags once overflow
 * occurs.
 *
 * If an illegal argument is provided, the function returns the error
 * U_ILLEGAL_ARGUMENT_ERROR.
 *
 * @param localeID The locale ID to parse.
 * @param lang The language tag buffer.
 * @param langLength The length of the language tag.
 * @param script The script tag buffer.
 * @param scriptLength The length of the script tag.
 * @param region The region tag buffer.
 * @param regionLength The length of the region tag.
 * @param err A pointer to a UErrorCode for error reporting.
 * @return The number of chars of the localeID parameter consumed.
 **/
static int32_t U_CALLCONV
parseTagString(
    const char* localeID,
    char* lang,
    int32_t* langLength,
    char* script,
    int32_t* scriptLength,
    char* region,
    int32_t* regionLength,
    UErrorCode* err)
{
    const char* position = localeID;
    int32_t subtagLength = 0;

    if(U_FAILURE(*err) ||
       localeID == NULL ||
       lang == NULL ||
       langLength == NULL ||
       script == NULL ||
       scriptLength == NULL ||
       region == NULL ||
       regionLength == NULL) {
        goto error;
    }

    subtagLength = ulocimp_getLanguage(position, lang, *langLength, &position);
    u_terminateChars(lang, *langLength, subtagLength, err);

    /*
     * Note that we explicit consider U_STRING_NOT_TERMINATED_WARNING
     * to be an error, because it indicates the user-supplied tag is
     * not well-formed.
     */
    if(U_FAILURE(*err)) {
        goto error;
    }

    *langLength = subtagLength;

    /*
     * If no language was present, use the empty string instead.
     * Otherwise, move past any separator.
     */
    if (_isIDSeparator(*position)) {
        ++position;
    }

    subtagLength = ulocimp_getScript(position, script, *scriptLength, &position);
    u_terminateChars(script, *scriptLength, subtagLength, err);

    if(U_FAILURE(*err)) {
        goto error;
    }

    *scriptLength = subtagLength;

    if (*scriptLength > 0) {
        if (uprv_strnicmp(script, unknownScript, *scriptLength) == 0) {
            /**
             * If the script part is the "unknown" script, then don't return it.
             **/
            *scriptLength = 0;
        }

        /*
         * Move past any separator.
         */
        if (_isIDSeparator(*position)) {
            ++position;
        }
    }

    subtagLength = ulocimp_getCountry(position, region, *regionLength, &position);
    u_terminateChars(region, *regionLength, subtagLength, err);

    if(U_FAILURE(*err)) {
        goto error;
    }

    *regionLength = subtagLength;

    if (*regionLength > 0) {
        if (uprv_strnicmp(region, unknownRegion, *regionLength) == 0) {
            /**
             * If the region part is the "unknown" region, then don't return it.
             **/
            *regionLength = 0;
        }
    } else if (*position != 0 && *position != '@') {
        /* back up over consumed trailing separator */
        --position;
    }

exit:

    return (int32_t)(position - localeID);

error:

    /**
     * If we get here, we have no explicit error, it's the result of an
     * illegal argument.
     **/
    if (!U_FAILURE(*err)) {
        *err = U_ILLEGAL_ARGUMENT_ERROR;
    }

    goto exit;
}

static UBool U_CALLCONV
createLikelySubtagsString(
    const char* lang,
    int32_t langLength,
    const char* script,
    int32_t scriptLength,
    const char* region,
    int32_t regionLength,
    const char* variants,
    int32_t variantsLength,
    icu::ByteSink& sink,
    UErrorCode* err) {
    /**
     * ULOC_FULLNAME_CAPACITY will provide enough capacity
     * that we can build a string that contains the language,
     * script and region code without worrying about overrunning
     * the user-supplied buffer.
     **/
    char likelySubtagsBuffer[ULOC_FULLNAME_CAPACITY];

    if(U_FAILURE(*err)) {
        goto error;
    }

    /**
     * Try the language with the script and region first.
     **/
    if (scriptLength > 0 && regionLength > 0) {

        const char* likelySubtags = NULL;

        icu::CharString tagBuffer;
        {
            icu::CharStringByteSink sink(&tagBuffer);
            createTagString(
                lang,
                langLength,
                script,
                scriptLength,
                region,
                regionLength,
                NULL,
                0,
                sink,
                err);
        }
        if(U_FAILURE(*err)) {
            goto error;
        }

        likelySubtags =
            findLikelySubtags(
                tagBuffer.data(),
                likelySubtagsBuffer,
                sizeof(likelySubtagsBuffer),
                err);
        if(U_FAILURE(*err)) {
            goto error;
        }

        if (likelySubtags != NULL) {
            /* Always use the language tag from the
               maximal string, since it may be more
               specific than the one provided. */
            createTagStringWithAlternates(
                        NULL,
                        0,
                        NULL,
                        0,
                        NULL,
                        0,
                        variants,
                        variantsLength,
                        likelySubtags,
                        sink,
                        err);
            return TRUE;
        }
    }

    /**
     * Try the language with just the script.
     **/
    if (scriptLength > 0) {

        const char* likelySubtags = NULL;

        icu::CharString tagBuffer;
        {
            icu::CharStringByteSink sink(&tagBuffer);
            createTagString(
                lang,
                langLength,
                script,
                scriptLength,
                NULL,
                0,
                NULL,
                0,
                sink,
                err);
        }
        if(U_FAILURE(*err)) {
            goto error;
        }

        likelySubtags =
            findLikelySubtags(
                tagBuffer.data(),
                likelySubtagsBuffer,
                sizeof(likelySubtagsBuffer),
                err);
        if(U_FAILURE(*err)) {
            goto error;
        }

        if (likelySubtags != NULL) {
            /* Always use the language tag from the
               maximal string, since it may be more
               specific than the one provided. */
            createTagStringWithAlternates(
                        NULL,
                        0,
                        NULL,
                        0,
                        region,
                        regionLength,
                        variants,
                        variantsLength,
                        likelySubtags,
                        sink,
                        err);
            return TRUE;
        }
    }

    /**
     * Try the language with just the region.
     **/
    if (regionLength > 0) {

        const char* likelySubtags = NULL;

        icu::CharString tagBuffer;
        {
            icu::CharStringByteSink sink(&tagBuffer);
            createTagString(
                lang,
                langLength,
                NULL,
                0,
                region,
                regionLength,
                NULL,
                0,
                sink,
                err);
        }
        if(U_FAILURE(*err)) {
            goto error;
        }

        likelySubtags =
            findLikelySubtags(
                tagBuffer.data(),
                likelySubtagsBuffer,
                sizeof(likelySubtagsBuffer),
                err);
        if(U_FAILURE(*err)) {
            goto error;
        }

        if (likelySubtags != NULL) {
            /* Always use the language tag from the
               maximal string, since it may be more
               specific than the one provided. */
            createTagStringWithAlternates(
                        NULL,
                        0,
                        script,
                        scriptLength,
                        NULL,
                        0,
                        variants,
                        variantsLength,
                        likelySubtags,
                        sink,
                        err);
            return TRUE;
        }
    }

    /**
     * Finally, try just the language.
     **/
    {
        const char* likelySubtags = NULL;

        icu::CharString tagBuffer;
        {
            icu::CharStringByteSink sink(&tagBuffer);
            createTagString(
                lang,
                langLength,
                NULL,
                0,
                NULL,
                0,
                NULL,
                0,
                sink,
                err);
        }
        if(U_FAILURE(*err)) {
            goto error;
        }

        likelySubtags =
            findLikelySubtags(
                tagBuffer.data(),
                likelySubtagsBuffer,
                sizeof(likelySubtagsBuffer),
                err);
        if(U_FAILURE(*err)) {
            goto error;
        }

        if (likelySubtags != NULL) {
            /* Always use the language tag from the
               maximal string, since it may be more
               specific than the one provided. */
            createTagStringWithAlternates(
                        NULL,
                        0,
                        script,
                        scriptLength,
                        region,
                        regionLength,
                        variants,
                        variantsLength,
                        likelySubtags,
                        sink,
                        err);
            return TRUE;
        }
    }

    return FALSE;

error:

    if (!U_FAILURE(*err)) {
        *err = U_ILLEGAL_ARGUMENT_ERROR;
    }

    return FALSE;
}

#define CHECK_TRAILING_VARIANT_SIZE(trailing, trailingLength) UPRV_BLOCK_MACRO_BEGIN { \
    int32_t count = 0; \
    int32_t i; \
    for (i = 0; i < trailingLength; i++) { \
        if (trailing[i] == '-' || trailing[i] == '_') { \
            count = 0; \
            if (count > 8) { \
                goto error; \
            } \
        } else if (trailing[i] == '@') { \
            break; \
        } else if (count > 8) { \
            goto error; \
        } else { \
            count++; \
        } \
    } \
} UPRV_BLOCK_MACRO_END

static void
_uloc_addLikelySubtags(const char* localeID,
                       icu::ByteSink& sink,
                       UErrorCode* err) {
    char lang[ULOC_LANG_CAPACITY];
    int32_t langLength = sizeof(lang);
    char script[ULOC_SCRIPT_CAPACITY];
    int32_t scriptLength = sizeof(script);
    char region[ULOC_COUNTRY_CAPACITY];
    int32_t regionLength = sizeof(region);
    const char* trailing = "";
    int32_t trailingLength = 0;
    int32_t trailingIndex = 0;
    UBool success = FALSE;

    if(U_FAILURE(*err)) {
        goto error;
    }
    if (localeID == NULL) {
        goto error;
    }

    trailingIndex = parseTagString(
        localeID,
        lang,
        &langLength,
        script,
        &scriptLength,
        region,
        &regionLength,
        err);
    if(U_FAILURE(*err)) {
        /* Overflow indicates an illegal argument error */
        if (*err == U_BUFFER_OVERFLOW_ERROR) {
            *err = U_ILLEGAL_ARGUMENT_ERROR;
        }

        goto error;
    }

    /* Find the length of the trailing portion. */
    while (_isIDSeparator(localeID[trailingIndex])) {
        trailingIndex++;
    }
    trailing = &localeID[trailingIndex];
    trailingLength = (int32_t)uprv_strlen(trailing);

    CHECK_TRAILING_VARIANT_SIZE(trailing, trailingLength);

    success =
        createLikelySubtagsString(
            lang,
            langLength,
            script,
            scriptLength,
            region,
            regionLength,
            trailing,
            trailingLength,
            sink,
            err);

    if (!success) {
        const int32_t localIDLength = (int32_t)uprv_strlen(localeID);

        /*
         * If we get here, we need to return localeID.
         */
        sink.Append(localeID, localIDLength);
    }

    return;

error:

    if (!U_FAILURE(*err)) {
        *err = U_ILLEGAL_ARGUMENT_ERROR;
    }
}

static void
_uloc_minimizeSubtags(const char* localeID,
                      icu::ByteSink& sink,
                      UErrorCode* err) {
    icu::CharString maximizedTagBuffer;

    char lang[ULOC_LANG_CAPACITY];
    int32_t langLength = sizeof(lang);
    char script[ULOC_SCRIPT_CAPACITY];
    int32_t scriptLength = sizeof(script);
    char region[ULOC_COUNTRY_CAPACITY];
    int32_t regionLength = sizeof(region);
    const char* trailing = "";
    int32_t trailingLength = 0;
    int32_t trailingIndex = 0;

    if(U_FAILURE(*err)) {
        goto error;
    }
    else if (localeID == NULL) {
        goto error;
    }

    trailingIndex =
        parseTagString(
            localeID,
            lang,
            &langLength,
            script,
            &scriptLength,
            region,
            &regionLength,
            err);
    if(U_FAILURE(*err)) {

        /* Overflow indicates an illegal argument error */
        if (*err == U_BUFFER_OVERFLOW_ERROR) {
            *err = U_ILLEGAL_ARGUMENT_ERROR;
        }

        goto error;
    }

    /* Find the spot where the variants or the keywords begin, if any. */
    while (_isIDSeparator(localeID[trailingIndex])) {
        trailingIndex++;
    }
    trailing = &localeID[trailingIndex];
    trailingLength = (int32_t)uprv_strlen(trailing);

    CHECK_TRAILING_VARIANT_SIZE(trailing, trailingLength);

    {
        icu::CharString base;
        {
            icu::CharStringByteSink sink(&base);
            createTagString(
                lang,
                langLength,
                script,
                scriptLength,
                region,
                regionLength,
                NULL,
                0,
                sink,
                err);
        }

        /**
         * First, we need to first get the maximization
         * from AddLikelySubtags.
         **/
        {
            icu::CharStringByteSink sink(&maximizedTagBuffer);
            ulocimp_addLikelySubtags(base.data(), sink, err);
        }
    }

    if(U_FAILURE(*err)) {
        goto error;
    }

    /**
     * Start first with just the language.
     **/
    {
        icu::CharString tagBuffer;
        {
            icu::CharStringByteSink sink(&tagBuffer);
            createLikelySubtagsString(
                lang,
                langLength,
                NULL,
                0,
                NULL,
                0,
                NULL,
                0,
                sink,
                err);
        }

        if(U_FAILURE(*err)) {
            goto error;
        }
        else if (!tagBuffer.isEmpty() && uprv_strnicmp(
                    maximizedTagBuffer.data(),
                    tagBuffer.data(),
                    tagBuffer.length()) == 0) {

            createTagString(
                        lang,
                        langLength,
                        NULL,
                        0,
                        NULL,
                        0,
                        trailing,
                        trailingLength,
                        sink,
                        err);
            return;
        }
    }

    /**
     * Next, try the language and region.
     **/
    if (regionLength > 0) {

        icu::CharString tagBuffer;
        {
            icu::CharStringByteSink sink(&tagBuffer);
            createLikelySubtagsString(
                lang,
                langLength,
                NULL,
                0,
                region,
                regionLength,
                NULL,
                0,
                sink,
                err);
        }

        if(U_FAILURE(*err)) {
            goto error;
        }
        else if (uprv_strnicmp(
                    maximizedTagBuffer.data(),
                    tagBuffer.data(),
                    tagBuffer.length()) == 0) {

            createTagString(
                        lang,
                        langLength,
                        NULL,
                        0,
                        region,
                        regionLength,
                        trailing,
                        trailingLength,
                        sink,
                        err);
            return;
        }
    }

    /**
     * Finally, try the language and script.  This is our last chance,
     * since trying with all three subtags would only yield the
     * maximal version that we already have.
     **/
    if (scriptLength > 0 && regionLength > 0) {
        icu::CharString tagBuffer;
        {
            icu::CharStringByteSink sink(&tagBuffer);
            createLikelySubtagsString(
                lang,
                langLength,
                script,
                scriptLength,
                NULL,
                0,
                NULL,
                0,
                sink,
                err);
        }

        if(U_FAILURE(*err)) {
            goto error;
        }
        else if (uprv_strnicmp(
                    maximizedTagBuffer.data(),
                    tagBuffer.data(),
                    tagBuffer.length()) == 0) {

            createTagString(
                        lang,
                        langLength,
                        script,
                        scriptLength,
                        NULL,
                        0,
                        trailing,
                        trailingLength,
                        sink,
                        err);
            return;
        }
    }

    {
        /**
         * If we got here, return the locale ID parameter.
         **/
        const int32_t localeIDLength = (int32_t)uprv_strlen(localeID);
        sink.Append(localeID, localeIDLength);
        return;
    }

error:

    if (!U_FAILURE(*err)) {
        *err = U_ILLEGAL_ARGUMENT_ERROR;
    }
}

static UBool
do_canonicalize(const char*    localeID,
         char* buffer,
         int32_t bufferCapacity,
         UErrorCode* err)
{
    uloc_canonicalize(
        localeID,
        buffer,
        bufferCapacity,
        err);

    if (*err == U_STRING_NOT_TERMINATED_WARNING ||
        *err == U_BUFFER_OVERFLOW_ERROR) {
        *err = U_ILLEGAL_ARGUMENT_ERROR;

        return FALSE;
    }
    else if (U_FAILURE(*err)) {

        return FALSE;
    }
    else {
        return TRUE;
    }
}

U_CAPI int32_t U_EXPORT2
uloc_addLikelySubtags(const char* localeID,
                      char* maximizedLocaleID,
                      int32_t maximizedLocaleIDCapacity,
                      UErrorCode* status) {
    if (U_FAILURE(*status)) {
        return 0;
    }

    icu::CheckedArrayByteSink sink(
            maximizedLocaleID, maximizedLocaleIDCapacity);

    ulocimp_addLikelySubtags(localeID, sink, status);
    int32_t reslen = sink.NumberOfBytesAppended();

    if (U_FAILURE(*status)) {
        return sink.Overflowed() ? reslen : -1;
    }

    if (sink.Overflowed()) {
        *status = U_BUFFER_OVERFLOW_ERROR;
    } else {
        u_terminateChars(
                maximizedLocaleID, maximizedLocaleIDCapacity, reslen, status);
    }

    return reslen;
}

U_CAPI void U_EXPORT2
ulocimp_addLikelySubtags(const char* localeID,
                         icu::ByteSink& sink,
                         UErrorCode* status) {
    char localeBuffer[ULOC_FULLNAME_CAPACITY];

    if (do_canonicalize(localeID, localeBuffer, sizeof localeBuffer, status)) {
        _uloc_addLikelySubtags(localeBuffer, sink, status);
    }
}

U_CAPI int32_t U_EXPORT2
uloc_minimizeSubtags(const char* localeID,
                     char* minimizedLocaleID,
                     int32_t minimizedLocaleIDCapacity,
                     UErrorCode* status) {
    if (U_FAILURE(*status)) {
        return 0;
    }

    icu::CheckedArrayByteSink sink(
            minimizedLocaleID, minimizedLocaleIDCapacity);

    ulocimp_minimizeSubtags(localeID, sink, status);
    int32_t reslen = sink.NumberOfBytesAppended();

    if (U_FAILURE(*status)) {
        return sink.Overflowed() ? reslen : -1;
    }

    if (sink.Overflowed()) {
        *status = U_BUFFER_OVERFLOW_ERROR;
    } else {
        u_terminateChars(
                minimizedLocaleID, minimizedLocaleIDCapacity, reslen, status);
    }

    return reslen;
}

U_CAPI void U_EXPORT2
ulocimp_minimizeSubtags(const char* localeID,
                        icu::ByteSink& sink,
                        UErrorCode* status) {
    char localeBuffer[ULOC_FULLNAME_CAPACITY];

    if (do_canonicalize(localeID, localeBuffer, sizeof localeBuffer, status)) {
        _uloc_minimizeSubtags(localeBuffer, sink, status);
    }
}

// Pairs of (language subtag, + or -) for finding out fast if common languages
// are LTR (minus) or RTL (plus).
static const char LANG_DIR_STRING[] =
        "root-en-es-pt-zh-ja-ko-de-fr-it-ar+he+fa+ru-nl-pl-th-tr-";

// Implemented here because this calls ulocimp_addLikelySubtags().
U_CAPI UBool U_EXPORT2
uloc_isRightToLeft(const char *locale) {
    UErrorCode errorCode = U_ZERO_ERROR;
    char script[8];
    int32_t scriptLength = uloc_getScript(locale, script, UPRV_LENGTHOF(script), &errorCode);
    if (U_FAILURE(errorCode) || errorCode == U_STRING_NOT_TERMINATED_WARNING ||
            scriptLength == 0) {
        // Fastpath: We know the likely scripts and their writing direction
        // for some common languages.
        errorCode = U_ZERO_ERROR;
        char lang[8];
        int32_t langLength = uloc_getLanguage(locale, lang, UPRV_LENGTHOF(lang), &errorCode);
        if (U_FAILURE(errorCode) || errorCode == U_STRING_NOT_TERMINATED_WARNING) {
            return FALSE;
        }
        if (langLength > 0) {
            const char* langPtr = uprv_strstr(LANG_DIR_STRING, lang);
            if (langPtr != NULL) {
                switch (langPtr[langLength]) {
                case '-': return FALSE;
                case '+': return TRUE;
                default: break;  // partial match of a longer code
                }
            }
        }
        // Otherwise, find the likely script.
        errorCode = U_ZERO_ERROR;
        icu::CharString likely;
        {
            icu::CharStringByteSink sink(&likely);
            ulocimp_addLikelySubtags(locale, sink, &errorCode);
        }
        if (U_FAILURE(errorCode) || errorCode == U_STRING_NOT_TERMINATED_WARNING) {
            return FALSE;
        }
        scriptLength = uloc_getScript(likely.data(), script, UPRV_LENGTHOF(script), &errorCode);
        if (U_FAILURE(errorCode) || errorCode == U_STRING_NOT_TERMINATED_WARNING ||
                scriptLength == 0) {
            return FALSE;
        }
    }
    UScriptCode scriptCode = (UScriptCode)u_getPropertyValueEnum(UCHAR_SCRIPT, script);
    return uscript_isRightToLeft(scriptCode);
}

U_NAMESPACE_BEGIN

UBool
Locale::isRightToLeft() const {
    return uloc_isRightToLeft(getBaseName());
}

U_NAMESPACE_END

// The following must at least allow for rg key value (6) plus terminator (1).
#define ULOC_RG_BUFLEN 8

U_CAPI int32_t U_EXPORT2
ulocimp_getRegionForSupplementalData(const char *localeID, UBool inferRegion,
                                     char *region, int32_t regionCapacity, UErrorCode* status) {
    if (U_FAILURE(*status)) {
        return 0;
    }
    char rgBuf[ULOC_RG_BUFLEN];
    UErrorCode rgStatus = U_ZERO_ERROR;

    // First check for rg keyword value
    int32_t rgLen = uloc_getKeywordValue(localeID, "rg", rgBuf, ULOC_RG_BUFLEN, &rgStatus);
    if (U_FAILURE(rgStatus) || rgLen != 6) {
        rgLen = 0;
    } else {
        // rgBuf guaranteed to be zero terminated here, with text len 6
        char *rgPtr = rgBuf;
        for (; *rgPtr!= 0; rgPtr++) {
            *rgPtr = uprv_toupper(*rgPtr);
        }
        rgLen = (uprv_strcmp(rgBuf+2, "ZZZZ") == 0)? 2: 0;
    }

    if (rgLen == 0) {
        // No valid rg keyword value, try for unicode_region_subtag
        rgLen = uloc_getCountry(localeID, rgBuf, ULOC_RG_BUFLEN, status);
        if (U_FAILURE(*status)) {
            rgLen = 0;
        } else if (rgLen == 0 && inferRegion) {
            // no unicode_region_subtag but inferRegion TRUE, try likely subtags
            rgStatus = U_ZERO_ERROR;
            icu::CharString locBuf;
            {
                icu::CharStringByteSink sink(&locBuf);
                ulocimp_addLikelySubtags(localeID, sink, &rgStatus);
            }
            if (U_SUCCESS(rgStatus)) {
                rgLen = uloc_getCountry(locBuf.data(), rgBuf, ULOC_RG_BUFLEN, status);
                if (U_FAILURE(*status)) {
                    rgLen = 0;
                }
            }
        }
    }

    rgBuf[rgLen] = 0;
    uprv_strncpy(region, rgBuf, regionCapacity);
    return u_terminateChars(region, regionCapacity, rgLen, status);
}


text:
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
 * Copyright (C) 2015, International Business Machines Corporation and
 * others. All Rights Reserved.
 */

#include "unicode/unistr.h"
#include "charstr.h"
#include "cstring.h"
#include "pluralmap.h"

U_NAMESPACE_BEGIN

static const char * const gPluralForms[] = {
        "other", "zero", "one", "two", "few", "many"};

PluralMapBase::Category
PluralMapBase::toCategory(const char *pluralForm) {
    for (int32_t i = 0; i < UPRV_LENGTHOF(gPluralForms); ++i) {
        if (uprv_strcmp(pluralForm, gPluralForms[i]) == 0) {
            return static_cast<Category>(i);
        }
    }
    return NONE;
}

PluralMapBase::Category
PluralMapBase::toCategory(const UnicodeString &pluralForm) {
    CharString cCategory;
    UErrorCode status = U_ZERO_ERROR;
    cCategory.appendInvariantChars(pluralForm, status);
    return U_FAILURE(status) ? NONE : toCategory(cCategory.data());
}

const char *PluralMapBase::getCategoryName(Category c) {
    int32_t index = c;
    return (index < 0 || index >= UPRV_LENGTHOF(gPluralForms)) ?
            NULL : gPluralForms[index];
}

U_NAMESPACE_END