blob: c302747808601f4df62d816b791cdf8dd2087b92 [file]
/* See LICENSE file for copyright and license details. */
#include <stdbool.h>
#include <stddef.h>
#include "../gen/sentence.h"
#include "../grapheme.h"
#include "util.h"
static inline enum sentence_break_property
get_break_prop(uint_least32_t cp)
{
if (likely(cp <= 0x10FFFF)) {
return (enum sentence_break_property)
sentence_break_minor[sentence_break_major[cp >> 8] +
(cp & 0xff)];
} else {
return SENTENCE_BREAK_PROP_OTHER;
}
}
static size_t
next_sentence_break(const void *str, size_t len, size_t (*get_codepoint)
(const void *, size_t, size_t, uint_least32_t *))
{
struct {
enum sentence_break_property a, b, c, d;
} raw, skip;
enum sentence_break_property res;
uint_least32_t cp;
uint_least8_t aterm_close_sp_level = 0,
saterm_close_sp_parasep_level = 0;
size_t off, tmp, new_off;
/* check degenerate cases */
if (str == NULL || len == 0) {
return 0;
}
/*
* Apply sentence breaking algorithm (UAX #29), see
* https://unicode.org/reports/tr29/#Sentence_Boundary_Rules
*
* There are 4 slots (a, b, c, d) of "break" properties and
* we check if there is a break in the middle between b and c.
*
* The position of this middle spot is determined by off,
* which gives the offset of the first element on the right
* hand side of said spot, or, in other words, gives the number
* of elements on the left hand side.
*
* It is further complicated by the fact that the algorithm
* expects you to skip certain characters for the second
* half of the rules (after SB5). Thus, we do not only have
* the "raw" properties as described above, but also the "skip"
* properties, where the skip.a and skip.b, for instance,
* give the two preceding character properties behind the
* currently investigated breakpoint.
*
*/
/*
* Initialize the different properties such that we have
* a good state after the state-update in the loop
*/
raw.b = NUM_SENTENCE_BREAK_PROPS;
if ((off = get_codepoint(str, len, 0, &cp)) >= len) {
/*
* A line is at least one codepoint long, so we can
* safely return here
*/
return len;
}
raw.c = get_break_prop(cp);
(void)get_codepoint(str, len, off, &cp);
raw.d = get_break_prop(cp);
skip.a = skip.b = NUM_SENTENCE_BREAK_PROPS;
for (; off < len; off = new_off) {
/*
* Update left side (a and b) of the skip state by
* "shifting in" the raw.c property as long as it is
* not one of the "ignored" character properties.
* While at it, update the RI-counter.
*
*/
if (raw.c != SENTENCE_BREAK_PROP_EXTEND &&
raw.c != SENTENCE_BREAK_PROP_FORMAT) {
skip.a = skip.b;
skip.b = raw.c;
/*
* Here comes a bit of magic. The rules
* SB8, SB8a, SB9 and SB10 have very complicated
* left-hand-side-rules of the form
*
* ATerm Close* Sp*
* SATerm Close*
* SATerm Close* Sp*
* SATerm Close* Sp* ParaSep?
*
* but instead of backtracking, we keep the
* state as some kind of "power level" in
* two variables
*
* aterm_close_sp_level
* saterm_close_sp_parasep_level
*
* that go from 0 to 3/4:
*
* 0: we are not in the sequence
* 1: we have one ATerm/SATerm to the left of
* the middle spot
* 2: we have one ATerm/SATerm and one or more
* Close to the left of the middle spot
* 3: we have one ATerm/SATerm, zero or more
* Close and one or more Sp to the left of
* the middle spot.
* 4: we have one SATerm, zero or more Close,
* zero or more Sp and one ParaSep to the
* left of the middle spot.
*
*/
if ((aterm_close_sp_level == 0 ||
aterm_close_sp_level == 1) &&
skip.b == SENTENCE_BREAK_PROP_ATERM) {
/* sequence has begun */
aterm_close_sp_level = 1;
} else if ((aterm_close_sp_level == 1 ||
aterm_close_sp_level == 2) &&
skip.b == SENTENCE_BREAK_PROP_CLOSE) {
/* close-sequence begins or continued */
aterm_close_sp_level = 2;
} else if ((aterm_close_sp_level == 1 ||
aterm_close_sp_level == 2 ||
aterm_close_sp_level == 3) &&
skip.b == SENTENCE_BREAK_PROP_SP) {
/* sp-sequence begins or continued */
aterm_close_sp_level = 3;
} else {
/* sequence broke */
aterm_close_sp_level = 0;
}
if ((saterm_close_sp_parasep_level == 0 ||
saterm_close_sp_parasep_level == 1) &&
(skip.b == SENTENCE_BREAK_PROP_STERM ||
skip.b == SENTENCE_BREAK_PROP_ATERM)) {
/* sequence has begun */
saterm_close_sp_parasep_level = 1;
} else if ((saterm_close_sp_parasep_level == 1 ||
saterm_close_sp_parasep_level == 2) &&
skip.b == SENTENCE_BREAK_PROP_CLOSE) {
/* close-sequence begins or continued */
saterm_close_sp_parasep_level = 2;
} else if ((saterm_close_sp_parasep_level == 1 ||
saterm_close_sp_parasep_level == 2 ||
saterm_close_sp_parasep_level == 3) &&
skip.b == SENTENCE_BREAK_PROP_SP) {
/* sp-sequence begins or continued */
saterm_close_sp_parasep_level = 3;
} else if ((saterm_close_sp_parasep_level == 1 ||
saterm_close_sp_parasep_level == 2 ||
saterm_close_sp_parasep_level == 3) &&
(skip.b == SENTENCE_BREAK_PROP_SEP ||
skip.b == SENTENCE_BREAK_PROP_CR ||
skip.b == SENTENCE_BREAK_PROP_LF)) {
/* ParaSep at the end of the sequence */
saterm_close_sp_parasep_level = 4;
} else {
/* sequence broke */
saterm_close_sp_parasep_level = 0;
}
}
/*
* Update right side (b and c) of the skip state by
* starting at the breakpoint and detecting the two
* following non-ignored character classes
*
*/
skip.c = NUM_SENTENCE_BREAK_PROPS;
for (tmp = off; tmp < len; ) {
tmp += get_codepoint(str, len, tmp, &cp);
res = get_break_prop(cp);
if (res != SENTENCE_BREAK_PROP_EXTEND &&
res != SENTENCE_BREAK_PROP_FORMAT) {
skip.c = res;
break;
}
}
skip.d = NUM_SENTENCE_BREAK_PROPS;
for (; tmp < len; ) {
tmp += get_codepoint(str, len, tmp, &cp);
res = get_break_prop(cp);
if (res != SENTENCE_BREAK_PROP_EXTEND &&
res != SENTENCE_BREAK_PROP_FORMAT) {
skip.d = res;
break;
}
}
/*
* Update the raw state by simply shifting everything
* in and, if we still have data left, determining
* the character class of the next codepoint.
*
*/
raw.a = raw.b;
raw.b = raw.c;
raw.c = raw.d;
if ((new_off = off + get_codepoint(str, len, off, &cp)) < len) {
get_codepoint(str, len, new_off, &cp);
raw.d = get_break_prop(cp);
} else {
raw.d = NUM_SENTENCE_BREAK_PROPS;
}
/* SB3 */
if (raw.b == SENTENCE_BREAK_PROP_CR &&
raw.c == SENTENCE_BREAK_PROP_LF) {
continue;
}
/* SB4 */
if (raw.b == SENTENCE_BREAK_PROP_SEP ||
raw.b == SENTENCE_BREAK_PROP_CR ||
raw.b == SENTENCE_BREAK_PROP_LF) {
break;
}
/* SB5 */
if (raw.c == SENTENCE_BREAK_PROP_EXTEND ||
raw.c == SENTENCE_BREAK_PROP_FORMAT) {
continue;
}
/* SB6 */
if (skip.b == SENTENCE_BREAK_PROP_ATERM &&
skip.c == SENTENCE_BREAK_PROP_NUMERIC) {
continue;
}
/* SB7 */
if (off > 1 &&
(skip.a == SENTENCE_BREAK_PROP_UPPER ||
skip.a == SENTENCE_BREAK_PROP_LOWER) &&
skip.b == SENTENCE_BREAK_PROP_ATERM &&
skip.c == SENTENCE_BREAK_PROP_UPPER) {
continue;
}
/* SB8 */
if (aterm_close_sp_level == 1 ||
aterm_close_sp_level == 2 ||
aterm_close_sp_level == 3) {
/*
* This is the most complicated rule, requiring
* the right-hand-side to satisfy the regular expression
*
* ( ¬(OLetter | Upper | Lower | ParaSep | SATerm) )* Lower
*
* which we simply check "manually" given LUT-lookups
* are very cheap.
*
*/
for (tmp = off, res = NUM_SENTENCE_BREAK_PROPS; tmp < len; ) {
tmp += get_codepoint(str, len, tmp, &cp);
res = get_break_prop(cp);
if (res == SENTENCE_BREAK_PROP_OLETTER ||
res == SENTENCE_BREAK_PROP_UPPER ||
res == SENTENCE_BREAK_PROP_LOWER ||
res == SENTENCE_BREAK_PROP_SEP ||
res == SENTENCE_BREAK_PROP_CR ||
res == SENTENCE_BREAK_PROP_LF ||
res == SENTENCE_BREAK_PROP_STERM ||
res == SENTENCE_BREAK_PROP_ATERM) {
break;
}
}
if (res == SENTENCE_BREAK_PROP_LOWER) {
continue;
}
}
/* SB8a */
if ((saterm_close_sp_parasep_level == 1 ||
saterm_close_sp_parasep_level == 2 ||
saterm_close_sp_parasep_level == 3) &&
(skip.c == SENTENCE_BREAK_PROP_SCONTINUE ||
skip.c == SENTENCE_BREAK_PROP_STERM ||
skip.c == SENTENCE_BREAK_PROP_ATERM)) {
continue;
}
/* SB9 */
if ((saterm_close_sp_parasep_level == 1 ||
saterm_close_sp_parasep_level == 2) &&
(skip.c == SENTENCE_BREAK_PROP_CLOSE ||
skip.c == SENTENCE_BREAK_PROP_SP ||
skip.c == SENTENCE_BREAK_PROP_SEP ||
skip.c == SENTENCE_BREAK_PROP_CR ||
skip.c == SENTENCE_BREAK_PROP_LF)) {
continue;
}
/* SB10 */
if ((saterm_close_sp_parasep_level == 1 ||
saterm_close_sp_parasep_level == 2 ||
saterm_close_sp_parasep_level == 3) &&
(skip.c == SENTENCE_BREAK_PROP_SP ||
skip.c == SENTENCE_BREAK_PROP_SEP ||
skip.c == SENTENCE_BREAK_PROP_CR ||
skip.c == SENTENCE_BREAK_PROP_LF)) {
continue;
}
/* SB11 */
if (saterm_close_sp_parasep_level == 1 ||
saterm_close_sp_parasep_level == 2 ||
saterm_close_sp_parasep_level == 3 ||
saterm_close_sp_parasep_level == 4) {
break;
}
/* SB998 */
continue;
}
return off;
}
size_t
grapheme_next_sentence_break(const uint_least32_t *str, size_t len)
{
return next_sentence_break(str, len, get_codepoint);
}
size_t
grapheme_next_sentence_break_utf8(const char *str, size_t len)
{
return next_sentence_break(str, len, get_codepoint_utf8);
}