+/* This creates a single number by combining two, with 'before' being like the
+ * 10's digit, but this isn't necessarily base 10; it is base however many
+ * elements of the enum there are */
+#define GCBcase(before, after) ((GCB_ENUM_COUNT * before) + after)
+
+STATIC bool
+S_isGCB(const GCB_enum before, const GCB_enum after)
+{
+ /* returns a boolean indicating if there is a Grapheme Cluster Boundary
+ * between the inputs. See http://www.unicode.org/reports/tr29/ */
+
+ switch (GCBcase(before, after)) {
+
+ /* Break at the start and end of text.
+ GB1. sot ÷
+ GB2. ÷ eot
+
+ Break before and after controls except between CR and LF
+ GB4. ( Control | CR | LF ) ÷
+ GB5. ÷ ( Control | CR | LF )
+
+ Otherwise, break everywhere.
+ GB10. Any ÷ Any */
+ default:
+ return TRUE;
+
+ /* Do not break between a CR and LF.
+ GB3. CR × LF */
+ case GCBcase(GCB_CR, GCB_LF):
+ return FALSE;
+
+ /* Do not break Hangul syllable sequences.
+ GB6. L × ( L | V | LV | LVT ) */
+ case GCBcase(GCB_L, GCB_L):
+ case GCBcase(GCB_L, GCB_V):
+ case GCBcase(GCB_L, GCB_LV):
+ case GCBcase(GCB_L, GCB_LVT):
+ return FALSE;
+
+ /* GB7. ( LV | V ) × ( V | T ) */
+ case GCBcase(GCB_LV, GCB_V):
+ case GCBcase(GCB_LV, GCB_T):
+ case GCBcase(GCB_V, GCB_V):
+ case GCBcase(GCB_V, GCB_T):
+ return FALSE;
+
+ /* GB8. ( LVT | T) × T */
+ case GCBcase(GCB_LVT, GCB_T):
+ case GCBcase(GCB_T, GCB_T):
+ return FALSE;
+
+ /* Do not break between regional indicator symbols.
+ GB8a. Regional_Indicator × Regional_Indicator */
+ case GCBcase(GCB_Regional_Indicator, GCB_Regional_Indicator):
+ return FALSE;
+
+ /* Do not break before extending characters.
+ GB9. × Extend */
+ case GCBcase(GCB_Other, GCB_Extend):
+ case GCBcase(GCB_Extend, GCB_Extend):
+ case GCBcase(GCB_L, GCB_Extend):
+ case GCBcase(GCB_LV, GCB_Extend):
+ case GCBcase(GCB_LVT, GCB_Extend):
+ case GCBcase(GCB_Prepend, GCB_Extend):
+ case GCBcase(GCB_Regional_Indicator, GCB_Extend):
+ case GCBcase(GCB_SpacingMark, GCB_Extend):
+ case GCBcase(GCB_T, GCB_Extend):
+ case GCBcase(GCB_V, GCB_Extend):
+ return FALSE;
+
+ /* Do not break before SpacingMarks, or after Prepend characters.
+ GB9a. × SpacingMark */
+ case GCBcase(GCB_Other, GCB_SpacingMark):
+ case GCBcase(GCB_Extend, GCB_SpacingMark):
+ case GCBcase(GCB_L, GCB_SpacingMark):
+ case GCBcase(GCB_LV, GCB_SpacingMark):
+ case GCBcase(GCB_LVT, GCB_SpacingMark):
+ case GCBcase(GCB_Prepend, GCB_SpacingMark):
+ case GCBcase(GCB_Regional_Indicator, GCB_SpacingMark):
+ case GCBcase(GCB_SpacingMark, GCB_SpacingMark):
+ case GCBcase(GCB_T, GCB_SpacingMark):
+ case GCBcase(GCB_V, GCB_SpacingMark):
+ return FALSE;
+
+ /* GB9b. Prepend × */
+ case GCBcase(GCB_Prepend, GCB_Other):
+ case GCBcase(GCB_Prepend, GCB_L):
+ case GCBcase(GCB_Prepend, GCB_LV):
+ case GCBcase(GCB_Prepend, GCB_LVT):
+ case GCBcase(GCB_Prepend, GCB_Prepend):
+ case GCBcase(GCB_Prepend, GCB_Regional_Indicator):
+ case GCBcase(GCB_Prepend, GCB_T):
+ case GCBcase(GCB_Prepend, GCB_V):
+ return FALSE;
+ }
+
+ NOT_REACHED; /* NOTREACHED */
+}
+
+#define SBcase(before, after) ((SB_ENUM_COUNT * before) + after)
+
+STATIC bool
+S_isSB(pTHX_ SB_enum before,
+ SB_enum after,
+ const U8 * const strbeg,
+ const U8 * const curpos,
+ const U8 * const strend,
+ const bool utf8_target)
+{
+ /* returns a boolean indicating if there is a Sentence Boundary Break
+ * between the inputs. See http://www.unicode.org/reports/tr29/ */
+
+ U8 * lpos = (U8 *) curpos;
+ U8 * temp_pos;
+ SB_enum backup;
+
+ PERL_ARGS_ASSERT_ISSB;
+
+ /* Break at the start and end of text.
+ SB1. sot ÷
+ SB2. ÷ eot */
+ if (before == SB_EDGE || after == SB_EDGE) {
+ return TRUE;
+ }
+
+ /* SB 3: Do not break within CRLF. */
+ if (before == SB_CR && after == SB_LF) {
+ return FALSE;
+ }
+
+ /* Break after paragraph separators. (though why CR and LF are considered
+ * so is beyond me (khw)
+ SB4. Sep | CR | LF ÷ */
+ if (before == SB_Sep || before == SB_CR || before == SB_LF) {
+ return TRUE;
+ }
+
+ /* Ignore Format and Extend characters, except after sot, Sep, CR, or LF.
+ * (See Section 6.2, Replacing Ignore Rules.)
+ SB5. X (Extend | Format)* → X */
+ if (after == SB_Extend || after == SB_Format) {
+ return FALSE;
+ }
+
+ if (before == SB_Extend || before == SB_Format) {
+ before = backup_one_SB(strbeg, &lpos, utf8_target);
+ }
+
+ /* Do not break after ambiguous terminators like period, if they are
+ * immediately followed by a number or lowercase letter, if they are
+ * between uppercase letters, if the first following letter (optionally
+ * after certain punctuation) is lowercase, or if they are followed by
+ * "continuation" punctuation such as comma, colon, or semicolon. For
+ * example, a period may be an abbreviation or numeric period, and thus may
+ * not mark the end of a sentence.
+
+ * SB6. ATerm × Numeric */
+ if (before == SB_ATerm && after == SB_Numeric) {
+ return FALSE;
+ }
+
+ /* SB7. Upper ATerm × Upper */
+ if (before == SB_ATerm && after == SB_Upper) {
+ temp_pos = lpos;
+ if (SB_Upper == backup_one_SB(strbeg, &temp_pos, utf8_target)) {
+ return FALSE;
+ }
+ }
+
+ /* SB8a. (STerm | ATerm) Close* Sp* × (SContinue | STerm | ATerm)
+ * SB10. (STerm | ATerm) Close* Sp* × ( Sp | Sep | CR | LF ) */
+ backup = before;
+ temp_pos = lpos;
+ while (backup == SB_Sp) {
+ backup = backup_one_SB(strbeg, &temp_pos, utf8_target);
+ }
+ while (backup == SB_Close) {
+ backup = backup_one_SB(strbeg, &temp_pos, utf8_target);
+ }
+ if ((backup == SB_STerm || backup == SB_ATerm)
+ && ( after == SB_SContinue
+ || after == SB_STerm
+ || after == SB_ATerm
+ || after == SB_Sp
+ || after == SB_Sep
+ || after == SB_CR
+ || after == SB_LF))
+ {
+ return FALSE;
+ }
+
+ /* SB8. ATerm Close* Sp* × ( ¬(OLetter | Upper | Lower | Sep | CR | LF |
+ * STerm | ATerm) )* Lower */
+ if (backup == SB_ATerm) {
+ U8 * rpos = (U8 *) curpos;
+ SB_enum later = after;
+
+ while ( later != SB_OLetter
+ && later != SB_Upper
+ && later != SB_Lower
+ && later != SB_Sep
+ && later != SB_CR
+ && later != SB_LF
+ && later != SB_STerm
+ && later != SB_ATerm
+ && later != SB_EDGE)
+ {
+ later = advance_one_SB(&rpos, strend, utf8_target);
+ }
+ if (later == SB_Lower) {
+ return FALSE;
+ }
+ }
+
+ /* Break after sentence terminators, but include closing punctuation,
+ * trailing spaces, and a paragraph separator (if present). [See note
+ * below.]
+ * SB9. ( STerm | ATerm ) Close* × ( Close | Sp | Sep | CR | LF ) */
+ backup = before;
+ temp_pos = lpos;
+ while (backup == SB_Close) {
+ backup = backup_one_SB(strbeg, &temp_pos, utf8_target);
+ }
+ if ((backup == SB_STerm || backup == SB_ATerm)
+ && ( after == SB_Close
+ || after == SB_Sp
+ || after == SB_Sep
+ || after == SB_CR
+ || after == SB_LF))
+ {
+ return FALSE;
+ }
+
+
+ /* SB11. ( STerm | ATerm ) Close* Sp* ( Sep | CR | LF )? ÷ */
+ temp_pos = lpos;
+ backup = backup_one_SB(strbeg, &temp_pos, utf8_target);
+ if ( backup == SB_Sep
+ || backup == SB_CR
+ || backup == SB_LF)
+ {
+ lpos = temp_pos;
+ }
+ else {
+ backup = before;
+ }
+ while (backup == SB_Sp) {
+ backup = backup_one_SB(strbeg, &lpos, utf8_target);
+ }
+ while (backup == SB_Close) {
+ backup = backup_one_SB(strbeg, &lpos, utf8_target);
+ }
+ if (backup == SB_STerm || backup == SB_ATerm) {
+ return TRUE;
+ }
+
+ /* Otherwise, do not break.
+ SB12. Any × Any */
+
+ return FALSE;
+}
+
+STATIC SB_enum
+S_advance_one_SB(pTHX_ U8 ** curpos, const U8 * const strend, const bool utf8_target)
+{
+ SB_enum sb;
+
+ PERL_ARGS_ASSERT_ADVANCE_ONE_SB;
+
+ if (*curpos >= strend) {
+ return SB_EDGE;
+ }
+
+ if (utf8_target) {
+ do {
+ *curpos += UTF8SKIP(*curpos);
+ if (*curpos >= strend) {
+ return SB_EDGE;
+ }
+ sb = getSB_VAL_UTF8(*curpos, strend);
+ } while (sb == SB_Extend || sb == SB_Format);
+ }
+ else {
+ do {
+ (*curpos)++;
+ if (*curpos >= strend) {
+ return SB_EDGE;
+ }
+ sb = getSB_VAL_CP(**curpos);
+ } while (sb == SB_Extend || sb == SB_Format);
+ }
+
+ return sb;
+}
+
+STATIC SB_enum
+S_backup_one_SB(pTHX_ const U8 * const strbeg, U8 ** curpos, const bool utf8_target)
+{
+ SB_enum sb;
+
+ PERL_ARGS_ASSERT_BACKUP_ONE_SB;
+
+ if (*curpos < strbeg) {
+ return SB_EDGE;
+ }
+
+ if (utf8_target) {
+ U8 * prev_char_pos = reghopmaybe3(*curpos, -1, strbeg);
+ if (! prev_char_pos) {
+ return SB_EDGE;
+ }
+
+ /* Back up over Extend and Format. curpos is always just to the right
+ * of the characater whose value we are getting */
+ do {
+ U8 * prev_prev_char_pos;
+ if ((prev_prev_char_pos = reghopmaybe3((U8 *) prev_char_pos, -1,
+ strbeg)))
+ {
+ sb = getSB_VAL_UTF8(prev_prev_char_pos, prev_char_pos);
+ *curpos = prev_char_pos;
+ prev_char_pos = prev_prev_char_pos;
+ }
+ else {
+ *curpos = (U8 *) strbeg;
+ return SB_EDGE;
+ }
+ } while (sb == SB_Extend || sb == SB_Format);
+ }
+ else {
+ do {
+ if (*curpos - 2 < strbeg) {
+ *curpos = (U8 *) strbeg;
+ return SB_EDGE;
+ }
+ (*curpos)--;
+ sb = getSB_VAL_CP(*(*curpos - 1));
+ } while (sb == SB_Extend || sb == SB_Format);
+ }
+
+ return sb;
+}
+
+#define WBcase(before, after) ((WB_ENUM_COUNT * before) + after)
+
+STATIC bool
+S_isWB(pTHX_ WB_enum previous,
+ WB_enum before,
+ WB_enum after,
+ const U8 * const strbeg,
+ const U8 * const curpos,
+ const U8 * const strend,
+ const bool utf8_target)
+{
+ /* Return a boolean as to if the boundary between 'before' and 'after' is
+ * a Unicode word break, using their published algorithm. Context may be
+ * needed to make this determination. If the value for the character
+ * before 'before' is known, it is passed as 'previous'; otherwise that
+ * should be set to WB_UNKNOWN. The other input parameters give the
+ * boundaries and current position in the matching of the string. That
+ * is, 'curpos' marks the position where the character whose wb value is
+ * 'after' begins. See http://www.unicode.org/reports/tr29/ */
+
+ U8 * before_pos = (U8 *) curpos;
+ U8 * after_pos = (U8 *) curpos;
+
+ PERL_ARGS_ASSERT_ISWB;
+
+ /* WB1 and WB2: Break at the start and end of text. */
+ if (before == WB_EDGE || after == WB_EDGE) {
+ return TRUE;
+ }
+
+ /* WB 3: Do not break within CRLF. */
+ if (before == WB_CR && after == WB_LF) {
+ return FALSE;
+ }
+
+ /* WB 3a and WB 3b: Otherwise break before and after Newlines (including CR
+ * and LF) */
+ if ( before == WB_CR || before == WB_LF || before == WB_Newline
+ || after == WB_CR || after == WB_LF || after == WB_Newline)
+ {
+ return TRUE;
+ }
+
+ /* Ignore Format and Extend characters, except when they appear at the
+ * beginning of a region of text.
+ * WB4. X (Extend | Format)* → X. */
+
+ if (after == WB_Extend || after == WB_Format) {
+ return FALSE;
+ }
+
+ if (before == WB_Extend || before == WB_Format) {
+ before = backup_one_WB(&previous, strbeg, &before_pos, utf8_target);
+ }
+
+ switch (WBcase(before, after)) {
+ /* Otherwise, break everywhere (including around ideographs).
+ WB14. Any ÷ Any */
+ default:
+ return TRUE;
+
+ /* Do not break between most letters.
+ WB5. (ALetter | Hebrew_Letter) × (ALetter | Hebrew_Letter) */
+ case WBcase(WB_ALetter, WB_ALetter):
+ case WBcase(WB_ALetter, WB_Hebrew_Letter):
+ case WBcase(WB_Hebrew_Letter, WB_ALetter):
+ case WBcase(WB_Hebrew_Letter, WB_Hebrew_Letter):
+ return FALSE;
+
+ /* Do not break letters across certain punctuation.
+ WB6. (ALetter | Hebrew_Letter)
+ × (MidLetter | MidNumLet | Single_Quote) (ALetter
+ | Hebrew_Letter) */
+ case WBcase(WB_ALetter, WB_MidLetter):
+ case WBcase(WB_ALetter, WB_MidNumLet):
+ case WBcase(WB_ALetter, WB_Single_Quote):
+ case WBcase(WB_Hebrew_Letter, WB_MidLetter):
+ case WBcase(WB_Hebrew_Letter, WB_MidNumLet):
+ /*case WBcase(WB_Hebrew_Letter, WB_Single_Quote):*/
+ after = advance_one_WB(&after_pos, strend, utf8_target);
+ return after != WB_ALetter && after != WB_Hebrew_Letter;
+
+ /* WB7. (ALetter | Hebrew_Letter) (MidLetter | MidNumLet |
+ * Single_Quote) × (ALetter | Hebrew_Letter) */
+ case WBcase(WB_MidLetter, WB_ALetter):
+ case WBcase(WB_MidLetter, WB_Hebrew_Letter):
+ case WBcase(WB_MidNumLet, WB_ALetter):
+ case WBcase(WB_MidNumLet, WB_Hebrew_Letter):
+ case WBcase(WB_Single_Quote, WB_ALetter):
+ case WBcase(WB_Single_Quote, WB_Hebrew_Letter):
+ before
+ = backup_one_WB(&previous, strbeg, &before_pos, utf8_target);
+ return before != WB_ALetter && before != WB_Hebrew_Letter;
+
+ /* WB7a. Hebrew_Letter × Single_Quote */
+ case WBcase(WB_Hebrew_Letter, WB_Single_Quote):
+ return FALSE;
+
+ /* WB7b. Hebrew_Letter × Double_Quote Hebrew_Letter */
+ case WBcase(WB_Hebrew_Letter, WB_Double_Quote):
+ return advance_one_WB(&after_pos, strend, utf8_target)
+ != WB_Hebrew_Letter;
+
+ /* WB7c. Hebrew_Letter Double_Quote × Hebrew_Letter */
+ case WBcase(WB_Double_Quote, WB_Hebrew_Letter):
+ return backup_one_WB(&previous, strbeg, &before_pos, utf8_target)
+ != WB_Hebrew_Letter;
+
+ /* Do not break within sequences of digits, or digits adjacent to
+ * letters (“3a”, or “A3”).
+ WB8. Numeric × Numeric */
+ case WBcase(WB_Numeric, WB_Numeric):
+ return FALSE;
+
+ /* WB9. (ALetter | Hebrew_Letter) × Numeric */
+ case WBcase(WB_ALetter, WB_Numeric):
+ case WBcase(WB_Hebrew_Letter, WB_Numeric):
+ return FALSE;
+
+ /* WB10. Numeric × (ALetter | Hebrew_Letter) */
+ case WBcase(WB_Numeric, WB_ALetter):
+ case WBcase(WB_Numeric, WB_Hebrew_Letter):
+ return FALSE;
+
+ /* Do not break within sequences, such as “3.2” or “3,456.789”.
+ WB11. Numeric (MidNum | MidNumLet | Single_Quote) × Numeric
+ */
+ case WBcase(WB_MidNum, WB_Numeric):
+ case WBcase(WB_MidNumLet, WB_Numeric):
+ case WBcase(WB_Single_Quote, WB_Numeric):
+ return backup_one_WB(&previous, strbeg, &before_pos, utf8_target)
+ != WB_Numeric;
+
+ /* WB12. Numeric × (MidNum | MidNumLet | Single_Quote) Numeric
+ * */
+ case WBcase(WB_Numeric, WB_MidNum):
+ case WBcase(WB_Numeric, WB_MidNumLet):
+ case WBcase(WB_Numeric, WB_Single_Quote):
+ return advance_one_WB(&after_pos, strend, utf8_target)
+ != WB_Numeric;
+
+ /* Do not break between Katakana.
+ WB13. Katakana × Katakana */
+ case WBcase(WB_Katakana, WB_Katakana):
+ return FALSE;
+
+ /* Do not break from extenders.
+ WB13a. (ALetter | Hebrew_Letter | Numeric | Katakana |
+ ExtendNumLet) × ExtendNumLet */
+ case WBcase(WB_ALetter, WB_ExtendNumLet):
+ case WBcase(WB_Hebrew_Letter, WB_ExtendNumLet):
+ case WBcase(WB_Numeric, WB_ExtendNumLet):
+ case WBcase(WB_Katakana, WB_ExtendNumLet):
+ case WBcase(WB_ExtendNumLet, WB_ExtendNumLet):
+ return FALSE;
+
+ /* WB13b. ExtendNumLet × (ALetter | Hebrew_Letter | Numeric
+ * | Katakana) */
+ case WBcase(WB_ExtendNumLet, WB_ALetter):
+ case WBcase(WB_ExtendNumLet, WB_Hebrew_Letter):
+ case WBcase(WB_ExtendNumLet, WB_Numeric):
+ case WBcase(WB_ExtendNumLet, WB_Katakana):
+ return FALSE;
+
+ /* Do not break between regional indicator symbols.
+ WB13c. Regional_Indicator × Regional_Indicator */
+ case WBcase(WB_Regional_Indicator, WB_Regional_Indicator):
+ return FALSE;
+
+ }
+
+ NOT_REACHED; /* NOTREACHED */
+}
+
+STATIC WB_enum
+S_advance_one_WB(pTHX_ U8 ** curpos, const U8 * const strend, const bool utf8_target)
+{
+ WB_enum wb;
+
+ PERL_ARGS_ASSERT_ADVANCE_ONE_WB;
+
+ if (*curpos >= strend) {
+ return WB_EDGE;
+ }
+
+ if (utf8_target) {
+
+ /* Advance over Extend and Format */
+ do {
+ *curpos += UTF8SKIP(*curpos);
+ if (*curpos >= strend) {
+ return WB_EDGE;
+ }
+ wb = getWB_VAL_UTF8(*curpos, strend);
+ } while (wb == WB_Extend || wb == WB_Format);
+ }
+ else {
+ do {
+ (*curpos)++;
+ if (*curpos >= strend) {
+ return WB_EDGE;
+ }
+ wb = getWB_VAL_CP(**curpos);
+ } while (wb == WB_Extend || wb == WB_Format);
+ }
+
+ return wb;
+}
+
+STATIC WB_enum
+S_backup_one_WB(pTHX_ WB_enum * previous, const U8 * const strbeg, U8 ** curpos, const bool utf8_target)
+{
+ WB_enum wb;
+
+ PERL_ARGS_ASSERT_BACKUP_ONE_WB;
+
+ /* If we know what the previous character's break value is, don't have
+ * to look it up */
+ if (*previous != WB_UNKNOWN) {
+ wb = *previous;
+ *previous = WB_UNKNOWN;
+ /* XXX Note that doesn't change curpos, and maybe should */
+
+ /* But we always back up over these two types */
+ if (wb != WB_Extend && wb != WB_Format) {
+ return wb;
+ }
+ }
+
+ if (*curpos < strbeg) {
+ return WB_EDGE;
+ }
+
+ if (utf8_target) {
+ U8 * prev_char_pos = reghopmaybe3(*curpos, -1, strbeg);
+ if (! prev_char_pos) {
+ return WB_EDGE;
+ }
+
+ /* Back up over Extend and Format. curpos is always just to the right
+ * of the characater whose value we are getting */
+ do {
+ U8 * prev_prev_char_pos;
+ if ((prev_prev_char_pos = reghopmaybe3((U8 *) prev_char_pos,
+ -1,
+ strbeg)))
+ {
+ wb = getWB_VAL_UTF8(prev_prev_char_pos, prev_char_pos);
+ *curpos = prev_char_pos;
+ prev_char_pos = prev_prev_char_pos;
+ }
+ else {
+ *curpos = (U8 *) strbeg;
+ return WB_EDGE;
+ }
+ } while (wb == WB_Extend || wb == WB_Format);
+ }
+ else {
+ do {
+ if (*curpos - 2 < strbeg) {
+ *curpos = (U8 *) strbeg;
+ return WB_EDGE;
+ }
+ (*curpos)--;
+ wb = getWB_VAL_CP(*(*curpos - 1));
+ } while (wb == WB_Extend || wb == WB_Format);
+ }
+
+ return wb;
+}
+