#include "block_scan.h" #include int block_count_heading(const char *input, size_t len, size_t pos, size_t *content_start) { size_t i = pos; int level = 0; while (i < len && input[i] == '#') { level++; i++; } if (level == 0 || level > 6) return 0; if (i < len && input[i] != ' ' && input[i] != '\n') return 0; while (i < len && input[i] == ' ') i++; *content_start = i; return level; } int block_is_fence(const char *input, size_t len, size_t pos, int *fence_len, char *fence_char) { size_t i = pos; while (i < len && (input[i] == ' ' || input[i] == '\t')) i++; if (i + 2 >= len) return 0; char ch = input[i]; if (ch != '`' && ch != '~') return 0; if (input[i + 1] != ch || input[i + 2] != ch) return 0; int fl = 3; i += 3; while (i < len && input[i] == ch) { fl++; i++; } *fence_len = fl; if (fence_char) *fence_char = ch; return 1; } 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) { size_t i = pos; while (i < len && (input[i] == ' ' || input[i] == '\t')) i++; i += (size_t)fence_len; (void)fence_char; while (i < len && (input[i] == ' ' || input[i] == '\t')) i++; *info_start = i; size_t end = i; while (end < len && input[end] != '\n' && input[end] != '\r') end++; while (end > *info_start && (input[end - 1] == ' ' || input[end - 1] == '\t')) end--; *info_end = end; } int block_is_thematic_break(const char *input, size_t pos, size_t le) { size_t i = pos; while (i < le && (input[i] == ' ' || input[i] == '\t')) i++; if (i >= le) return 0; char marker = input[i]; if (marker != '*' && marker != '-' && marker != '_') return 0; int count = 0; for (; i < le; i++) { char c = input[i]; if (c == marker) { count++; } else if (c == ' ' || c == '\t') { continue; } else if (c == '\r') { continue; } else { return 0; } } return count >= 3; } int block_is_setext_underline(const char *input, size_t pos, size_t le, int *level) { size_t i = pos; while (i < le && (input[i] == ' ' || input[i] == '\t')) i++; if (i >= le) return 0; char marker = input[i]; if (marker != '=' && marker != '-') return 0; size_t start = i; while (i < le && input[i] == marker) i++; if (i == start) return 0; while (i < le && (input[i] == ' ' || input[i] == '\t' || input[i] == '\r')) i++; if (i < le) return 0; /* Thematic-break-looking '---' is still a valid setext underline when * paired with a preceding text line (caller decides context). */ if (level) *level = (marker == '=') ? 1 : 2; return 1; } int block_is_blockquote(const char *input, size_t len, size_t pos, size_t *content_start) { size_t i = pos; while (i < len && (input[i] == ' ' || input[i] == '\t')) i++; if (i >= len || input[i] != '>') return 0; i++; if (i < len && input[i] == ' ') i++; *content_start = i; return 1; } int block_is_ul(const char *input, size_t len, size_t pos, size_t *content_start) { size_t i = pos; while (i < len && (input[i] == ' ' || input[i] == '\t')) i++; if (i >= len) return 0; if (input[i] != '-' && input[i] != '*' && input[i] != '+') return 0; if (i + 1 < len && input[i + 1] != ' ') return 0; i += 2; *content_start = i; return 1; } int block_parse_task_marker(const char *input, size_t len, size_t content_start, int *task, size_t *text_start) { size_t i = content_start; if (i + 3 > len) return 0; if (input[i] != '[') return 0; char mark = input[i + 1]; if (input[i + 2] != ']') return 0; if (mark != ' ' && mark != 'x' && mark != 'X') return 0; i += 3; if (i < len && input[i] == ' ') i++; *task = (mark == ' ') ? 1 : 2; *text_start = i; return 1; } int block_is_ol(const char *input, size_t len, size_t pos, size_t *content_start, int *num) { size_t i = pos; while (i < len && (input[i] == ' ' || input[i] == '\t')) i++; size_t num_start = i; { unsigned char c = (unsigned char)input[i]; if (i >= len || c < '0' || c > '9') return 0; } while (i < len) { unsigned char c = (unsigned char)input[i]; if (c < '0' || c > '9') break; i++; } if (i >= len || input[i] != '.') return 0; if (i + 1 < len && input[i + 1] != ' ') return 0; *num = 0; for (size_t j = num_start; j < i; j++) *num = *num * 10 + (input[j] - '0'); i += 2; *content_start = i; return 1; } int block_line_starts_table(const char *input, size_t len, size_t pos) { size_t i = pos; while (i < len && (input[i] == ' ' || input[i] == '\t')) i++; if (i >= len) return 0; /* GFM: leading '|' OR a pipe somewhere on the line with a separator below. * Cheap reject: must contain '|' before newline. */ const char *nl = memchr(input + i, '\n', len - i); size_t line_len = nl ? (size_t)(nl - (input + i)) : (len - i); return memchr(input + i, '|', line_len) != NULL; } 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) { size_t i = pos; while (i < len && (input[i] == ' ' || input[i] == '\t')) i++; if (i + 1 >= len || input[i] != '$' || input[i + 1] != '$') return 0; i += 2; *content_start = i; size_t le = block_line_end(ctx, input, len, pos); if (i + 1 < le && input[le - 2] == '$' && input[le - 1] == '$') { *content_end = le - 2; return 1; } return 0; } 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) { if (pos == le || (pos + 1 == le && input[pos] == '\r')) return 0; /* Next line starts after the newline at `le` (line_end points at '\n'). */ if (le >= len || input[le] != '\n') return is_final ? 0 : -1; size_t next = le + 1; if (next >= len) return is_final ? 0 : -1; if (next >= stop && !is_final) return -1; /* Cheap reject before full underline scan. */ size_t u = next; while (u < len && (input[u] == ' ' || input[u] == '\t')) u++; if (u >= len || (input[u] != '=' && input[u] != '-')) return 0; size_t nle = block_line_end(ctx, input, len, next); int level = 0; if (!block_is_setext_underline(input, next, nle, &level)) return 0; info->kind = LK_HEADING; info->heading_level = level; info->content_start = pos; while (info->content_start < le && (input[info->content_start] == ' ' || input[info->content_start] == '\t')) info->content_start++; info->content_end = le; while (info->content_end > info->content_start && (input[info->content_end - 1] == ' ' || input[info->content_end - 1] == '\t' || input[info->content_end - 1] == '\r')) info->content_end--; info->fence_len = 0; info->fence_char = 0; info->info_start = 0; info->info_end = 0; info->task = 0; *end_pos = block_skip_line(ctx, input, len, next); return 1; } line_info_t block_classify_line(sashidown_ctx_t *ctx, const char *input, size_t len, size_t pos, size_t le) { line_info_t info; memset(&info, 0, sizeof(info)); info.kind = LK_PARA; info.content_start = pos; info.content_end = le; if (pos == le || (pos + 1 == le && input[pos] == '\r')) { info.kind = LK_EMPTY; return info; } size_t i = pos; while (i < len && (input[i] == ' ' || input[i] == '\t')) i++; unsigned char first = (i < len) ? (unsigned char)input[i] : 0; /* Thematic breaks only possible for - / * / _ lines. */ if ((first == '*' || first == '-' || first == '_') && block_is_thematic_break(input, pos, le)) { info.kind = LK_HR; return info; } switch (first) { case '#': info.heading_level = block_count_heading(input, len, pos, &info.content_start); if (info.heading_level > 0) { info.kind = LK_HEADING; info.content_end = le; return info; } break; case '`': case '~': if (block_is_fence(input, len, pos, &info.fence_len, &info.fence_char)) { info.kind = LK_FENCE; block_fence_info_range(input, len, pos, info.fence_len, info.fence_char, &info.info_start, &info.info_end); return info; } break; case '>': if (block_is_blockquote(input, len, pos, &info.content_start)) { info.kind = LK_BQ; return info; } break; case '-': case '*': case '+': if (block_is_ul(input, len, pos, &info.content_start)) { info.kind = LK_UL; size_t text = info.content_start; int task = 0; if (block_parse_task_marker(input, le, info.content_start, &task, &text)) { info.task = task; info.content_start = text; } return info; } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { int ol_num = 0; if (block_is_ol(input, len, pos, &info.content_start, &ol_num)) { info.kind = LK_OL; return info; } break; } case '$': { size_t cs, ce; if (i + 1 < len && input[i] == '$' && input[i + 1] == '$') { if (i == pos) { info.kind = LK_LATEX_BLOCK; return info; } if (block_is_latex_line(ctx, input, len, pos, &cs, &ce)) { info.kind = LK_LATEX_LINE; info.content_start = cs; info.content_end = ce; return info; } } break; } default: break; } info.kind = LK_PARA; return info; }