#ifndef SASHIDOWN_BLOCK_SCAN_H #define SASHIDOWN_BLOCK_SCAN_H #include "sashidown.h" #include "render.h" #include "fsm.h" #include "line_index.h" #include #include #include #ifdef __SSE2__ #include #elif defined(__ARM_NEON) || defined(__aarch64__) #include #endif #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Woverride-init" static inline size_t block_utf8_seq_len(unsigned char ch) { static const unsigned char tbl[256] = { [0x00 ... 0xFF] = 1, [0xC0 ... 0xDF] = 2, [0xE0 ... 0xEF] = 3, [0xF0 ... 0xF7] = 4, }; return tbl[ch]; } #pragma GCC diagnostic pop static inline size_t block_scan_plain_ascii(const char *restrict input, size_t end, size_t i) { #ifdef __SSE2__ const __m128i m_star = _mm_set1_epi8('*'); const __m128i m_us = _mm_set1_epi8('_'); const __m128i m_tick = _mm_set1_epi8('`'); const __m128i m_dol = _mm_set1_epi8('$'); const __m128i m_bang = _mm_set1_epi8('!'); const __m128i m_lb = _mm_set1_epi8('['); const __m128i m_amp = _mm_set1_epi8('&'); const __m128i m_lt = _mm_set1_epi8('<'); const __m128i m_gt = _mm_set1_epi8('>'); const __m128i m_bs = _mm_set1_epi8('\\'); const __m128i m_tilde = _mm_set1_epi8('~'); while (i + 16 <= end) { __m128i v = _mm_loadu_si128((const __m128i *)(input + i)); if (_mm_movemask_epi8(v)) break; /* high bit => UTF-8 */ __m128i hit = _mm_cmpeq_epi8(v, m_star); hit = _mm_or_si128(hit, _mm_cmpeq_epi8(v, m_us)); hit = _mm_or_si128(hit, _mm_cmpeq_epi8(v, m_tick)); hit = _mm_or_si128(hit, _mm_cmpeq_epi8(v, m_dol)); hit = _mm_or_si128(hit, _mm_cmpeq_epi8(v, m_bang)); hit = _mm_or_si128(hit, _mm_cmpeq_epi8(v, m_lb)); hit = _mm_or_si128(hit, _mm_cmpeq_epi8(v, m_amp)); hit = _mm_or_si128(hit, _mm_cmpeq_epi8(v, m_lt)); hit = _mm_or_si128(hit, _mm_cmpeq_epi8(v, m_gt)); hit = _mm_or_si128(hit, _mm_cmpeq_epi8(v, m_bs)); hit = _mm_or_si128(hit, _mm_cmpeq_epi8(v, m_tilde)); int mask = _mm_movemask_epi8(hit); if (mask) return i + (unsigned)__builtin_ctz(mask); i += 16; } #elif defined(__ARM_NEON) || defined(__aarch64__) while (i + 16 <= end) { uint8x16_t v = vld1q_u8((const uint8_t *)(input + i)); if (vmaxvq_u8(vandq_u8(v, vdupq_n_u8(0x80)))) break; uint8x16_t hit = vceqq_u8(v, vdupq_n_u8('*')); hit = vorrq_u8(hit, vceqq_u8(v, vdupq_n_u8('_'))); hit = vorrq_u8(hit, vceqq_u8(v, vdupq_n_u8('`'))); hit = vorrq_u8(hit, vceqq_u8(v, vdupq_n_u8('$'))); hit = vorrq_u8(hit, vceqq_u8(v, vdupq_n_u8('!'))); hit = vorrq_u8(hit, vceqq_u8(v, vdupq_n_u8('['))); hit = vorrq_u8(hit, vceqq_u8(v, vdupq_n_u8('&'))); hit = vorrq_u8(hit, vceqq_u8(v, vdupq_n_u8('<'))); hit = vorrq_u8(hit, vceqq_u8(v, vdupq_n_u8('>'))); hit = vorrq_u8(hit, vceqq_u8(v, vdupq_n_u8('\\'))); hit = vorrq_u8(hit, vceqq_u8(v, vdupq_n_u8('~'))); if (vmaxvq_u8(hit)) { uint8_t lanes[16]; vst1q_u8(lanes, hit); size_t pos = 0; while (pos < 16 && !lanes[pos]) pos++; return i + pos; } i += 16; } #endif size_t j = i; while (j + 4 <= end) { uint32_t v; memcpy(&v, input + j, 4); if (v & 0x80808080) break; if (CH_SCAN_STOP[(v & 0xFF)] || CH_SCAN_STOP[(v >> 8) & 0xFF] || CH_SCAN_STOP[(v >> 16) & 0xFF] || CH_SCAN_STOP[(v >> 24) & 0xFF]) break; j += 4; } while (j < end) { unsigned char c = (unsigned char)input[j]; if ((c & 0x80)) break; if (unlikely(CH_SCAN_STOP[c])) break; j++; } return j; } static inline size_t block_scan_utf8_run(const char *input, size_t end, size_t i) { size_t j = i; while (j < end) { unsigned char c = (unsigned char)input[j]; if (likely((c & 0x80) == 0)) break; size_t seq = block_utf8_seq_len(c); if (j + seq > end) break; j += seq; } return j; } static inline size_t block_scan_until_byte(const char *input, size_t end, size_t i, char delim) { if (i >= end) return i; const char *hit = memchr(input + i, delim, end - i); return hit ? (size_t)(hit - input) : end; } static inline size_t block_scan_until_2byte(const char *input, size_t end, size_t start, char c1, char c2) { size_t i = start; while (i + 1 < end) { const char *hit = memchr(input + i, c1, end - i); if (!hit) return end; i = (size_t)(hit - input); if (i + 1 < end && input[i + 1] == c2) return i; i++; } return end; } static inline size_t block_line_end(sashidown_ctx_t *ctx, const char *input, size_t len, size_t pos) { return line_end_ix((line_index_t *)ctx->line_ix, input, len, pos); } static inline size_t block_skip_line(sashidown_ctx_t *ctx, const char *input, size_t len, size_t pos) { return skip_line_ix((line_index_t *)ctx->line_ix, input, len, pos); } static inline void block_line_seek(sashidown_ctx_t *ctx, size_t pos) { line_index_seek((line_index_t *)ctx->line_ix, pos); } int block_count_heading(const char *input, size_t len, size_t pos, size_t *content_start); int block_is_fence(const char *input, size_t len, size_t pos, int *fence_len, char *fence_char); void block_fence_info_range(const char *input, size_t len, size_t pos, int fence_len, char fence_char, size_t *info_start, size_t *info_end); int block_is_thematic_break(const char *input, size_t pos, size_t le); int block_is_setext_underline(const char *input, size_t pos, size_t le, int *level); int block_is_blockquote(const char *input, size_t len, size_t pos, size_t *content_start); int block_is_ul(const char *input, size_t len, size_t pos, size_t *content_start); int block_is_ol(const char *input, size_t len, size_t pos, size_t *content_start, int *num); int block_parse_task_marker(const char *input, size_t len, size_t content_start, int *task, size_t *text_start); int block_line_starts_table(const char *input, size_t len, size_t pos); int block_is_latex_line(sashidown_ctx_t *ctx, const char *input, size_t len, size_t pos, size_t *content_start, size_t *content_end); /* Peek next-line setext underline. Does NOT advance line_index on failure. * `le` must be block_line_end(pos). Caller should only invoke for LK_PARA lines. * Returns 1 = heading, 0 = no, -1 = defer (need next line). */ int block_try_setext(sashidown_ctx_t *ctx, const char *input, size_t len, size_t pos, size_t le, size_t stop, int is_final, line_info_t *info, size_t *end_pos); line_info_t block_classify_line(sashidown_ctx_t *ctx, const char *input, size_t len, size_t pos, size_t le); #endif /* SASHIDOWN_BLOCK_SCAN_H */