Files
CST-agent/vendor/sashidown/src/block_scan.c
T
WHCandCursor 1f1fcba7ad Initial CST-Agent: portable Linux/Termux/Windows coding agent.
Vendored sashidown, OpenAI-compatible env (OPENAI_*), and bootstrap scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 22:42:32 +08:00

338 lines
10 KiB
C

#include "block_scan.h"
#include <string.h>
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;
}