Files
deusdata--codebase-memory-mcp/internal/cbm/lsp/rust_cargo.c
T
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

320 lines
11 KiB
C

/* rust_cargo.c — Hand-rolled TOML subset parser for Cargo.toml.
*
* Recognises only the shapes we need (per RUST_LSP_FOLLOWUP §A3):
* - `[section]`, `[a.b.c]`, `[workspace]`, `[dependencies]`
* - `key = "string"`, `key = 'string'`, `key = [...]`,
* `key = { ... }`
* - `members = ["a", "b/c"]`
*
* Everything else (numbers, dates, deep tables, comments past EOL) is
* skipped without error.
*/
#include "rust_cargo.h"
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
/* ── Tiny tokenizer ──────────────────────────────────────────── */
static int skip_ws_and_comment(const char* s, int len, int from) {
while (from < len) {
char c = s[from];
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { from++; continue; }
if (c == '#') {
while (from < len && s[from] != '\n') from++;
continue;
}
break;
}
return from;
}
static bool is_ident_char(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '_' || c == '-';
}
/* Parse a bare key (ident-like). Stores arena-allocated copy in *out. */
static int parse_key(CBMArena* a, const char* s, int len, int from,
const char** out) {
int start = from;
if (from < len && s[from] == '"') {
from++;
start = from;
while (from < len && s[from] != '"') {
if (s[from] == '\\' && from + 1 < len) from += 2;
else from++;
}
*out = cbm_arena_strndup(a, s + start, (size_t)(from - start));
if (from < len && s[from] == '"') from++;
return from;
}
while (from < len && is_ident_char(s[from])) from++;
if (from > start) {
*out = cbm_arena_strndup(a, s + start, (size_t)(from - start));
}
return from;
}
/* Parse a string literal (single or double quoted). */
static int parse_string(CBMArena* a, const char* s, int len, int from,
const char** out) {
if (from >= len) return from;
char q = s[from];
if (q != '"' && q != '\'') return from;
from++;
int start = from;
while (from < len && s[from] != q) {
if (s[from] == '\\' && from + 1 < len) from += 2;
else from++;
}
*out = cbm_arena_strndup(a, s + start, (size_t)(from - start));
if (from < len) from++;
return from;
}
/* Skip a value (used for keys we don't care about). Handles strings,
* arrays, inline tables, bare values. */
static int skip_value(const char* s, int len, int from) {
from = skip_ws_and_comment(s, len, from);
if (from >= len) return from;
char c = s[from];
if (c == '"' || c == '\'') {
from++;
while (from < len && s[from] != c) {
if (s[from] == '\\' && from + 1 < len) from += 2;
else from++;
}
if (from < len) from++;
return from;
}
if (c == '[' || c == '{') {
char open = c, close = (c == '[' ? ']' : '}');
int depth = 1;
from++;
while (from < len && depth > 0) {
char d = s[from];
if (d == '"' || d == '\'') {
from++;
while (from < len && s[from] != d) {
if (s[from] == '\\' && from + 1 < len) from += 2;
else from++;
}
if (from < len) from++;
continue;
}
if (d == open) depth++;
else if (d == close) depth--;
from++;
}
return from;
}
/* Bare value: skip to end of line. */
while (from < len && s[from] != '\n' && s[from] != '#') from++;
return from;
}
/* Parse `[section.path]` header — returns the section name as a flat
* dotted string, e.g. "dependencies" or "workspace.dependencies". */
static int parse_section(CBMArena* a, const char* s, int len, int from,
const char** out) {
if (from >= len || s[from] != '[') return from;
/* Skip leading `[` or `[[`. */
bool array_of_tables = false;
from++;
if (from < len && s[from] == '[') { array_of_tables = true; from++; }
int start = from;
while (from < len && s[from] != ']') from++;
*out = cbm_arena_strndup(a, s + start, (size_t)(from - start));
/* Consume closing `]` (or `]]`). */
if (from < len) from++;
if (array_of_tables && from < len && s[from] == ']') from++;
return from;
}
/* For the `[dependencies]` / `[dev-dependencies]` / `[workspace.dependencies]`
* sections, parse `key = value` lines until the next section. The value
* may be a string (the version) or an inline table. We capture both
* shapes — only the key (crate name) and optional `path = "..."` field
* matter for us. */
static int parse_dep_entry(CBMArena* a, const char* s, int len, int from,
CBMCargoManifest* out) {
from = skip_ws_and_comment(s, len, from);
if (from >= len || s[from] == '[') return from;
const char* key = NULL;
from = parse_key(a, s, len, from, &key);
from = skip_ws_and_comment(s, len, from);
if (from < len && s[from] == '=') {
from++;
from = skip_ws_and_comment(s, len, from);
}
const char* path_val = NULL;
if (from < len && s[from] == '{') {
/* Inline table — scan for `path = "..."`. */
int depth = 1;
from++;
while (from < len && depth > 0) {
from = skip_ws_and_comment(s, len, from);
if (from >= len) break;
char c = s[from];
if (c == '}') { depth--; from++; continue; }
if (c == ',') { from++; continue; }
/* sub-key */
const char* sub_key = NULL;
from = parse_key(a, s, len, from, &sub_key);
from = skip_ws_and_comment(s, len, from);
if (from < len && s[from] == '=') {
from++;
from = skip_ws_and_comment(s, len, from);
}
if (sub_key && strcmp(sub_key, "path") == 0) {
from = parse_string(a, s, len, from, &path_val);
} else {
from = skip_value(s, len, from);
}
}
} else {
from = skip_value(s, len, from);
}
if (key && out->dep_count < CBM_CARGO_MAX_DEPS) {
out->deps[out->dep_count].name = key;
out->deps[out->dep_count].path = path_val;
out->dep_count++;
}
return from;
}
/* ── Section dispatcher ──────────────────────────────────────── */
static int parse_package_kv(CBMArena* a, const char* s, int len, int from,
CBMCargoManifest* out) {
from = skip_ws_and_comment(s, len, from);
if (from >= len || s[from] == '[') return from;
const char* key = NULL;
from = parse_key(a, s, len, from, &key);
from = skip_ws_and_comment(s, len, from);
if (from < len && s[from] == '=') {
from++;
from = skip_ws_and_comment(s, len, from);
}
if (key && strcmp(key, "name") == 0) {
from = parse_string(a, s, len, from, &out->package_name);
} else if (key && strcmp(key, "version") == 0) {
from = parse_string(a, s, len, from, &out->package_version);
} else {
from = skip_value(s, len, from);
}
return from;
}
static int parse_workspace_kv(CBMArena* a, const char* s, int len, int from,
CBMCargoManifest* out) {
from = skip_ws_and_comment(s, len, from);
if (from >= len || s[from] == '[') return from;
out->is_workspace_root = true;
const char* key = NULL;
from = parse_key(a, s, len, from, &key);
from = skip_ws_and_comment(s, len, from);
if (from < len && s[from] == '=') {
from++;
from = skip_ws_and_comment(s, len, from);
}
if (key && strcmp(key, "members") == 0 && from < len && s[from] == '[') {
from++;
while (from < len && s[from] != ']') {
from = skip_ws_and_comment(s, len, from);
if (from < len && (s[from] == '"' || s[from] == '\'')) {
const char* mem = NULL;
from = parse_string(a, s, len, from, &mem);
if (mem && out->member_count < CBM_CARGO_MAX_MEMBERS) {
/* Derive a member NAME from the path's last segment. */
const char* last = mem;
for (const char* p = mem; *p; p++) {
if (*p == '/') last = p + 1;
}
out->members[out->member_count].member_name = last;
out->members[out->member_count].member_path = mem;
out->member_count++;
}
}
from = skip_ws_and_comment(s, len, from);
if (from < len && s[from] == ',') from++;
from = skip_ws_and_comment(s, len, from);
}
if (from < len) from++; /* consume `]` */
} else {
from = skip_value(s, len, from);
}
return from;
}
void cbm_cargo_parse(CBMArena* arena, const char* src, int src_len,
CBMCargoManifest* out) {
if (!arena || !src || !out) return;
memset(out, 0, sizeof(*out));
if (src_len <= 0) src_len = (int)strlen(src);
int from = 0;
/* Default: pre-header content treated as [package]. */
const char* section = "package";
while (from < src_len) {
from = skip_ws_and_comment(src, src_len, from);
if (from >= src_len) break;
if (src[from] == '[') {
const char* hdr = NULL;
from = parse_section(arena, src, src_len, from, &hdr);
section = hdr ? hdr : "";
continue;
}
if (!section) {
from = skip_value(src, src_len, from);
continue;
}
if (strcmp(section, "package") == 0) {
from = parse_package_kv(arena, src, src_len, from, out);
} else if (strcmp(section, "workspace") == 0) {
from = parse_workspace_kv(arena, src, src_len, from, out);
} else if (strcmp(section, "dependencies") == 0 ||
strcmp(section, "dev-dependencies") == 0 ||
strcmp(section, "build-dependencies") == 0 ||
strcmp(section, "workspace.dependencies") == 0) {
from = parse_dep_entry(arena, src, src_len, from, out);
} else {
/* Section we don't care about — skip the line. */
while (from < src_len && src[from] != '\n' && src[from] != '[') {
from++;
}
}
}
}
bool cbm_cargo_is_known_dep(const CBMCargoManifest* m, const char* head) {
if (!m || !head) return false;
for (int i = 0; i < m->dep_count; i++) {
if (m->deps[i].name && strcmp(m->deps[i].name, head) == 0) {
return true;
}
}
for (int i = 0; i < m->member_count; i++) {
if (m->members[i].member_name &&
strcmp(m->members[i].member_name, head) == 0) {
return true;
}
}
return false;
}
const CBMCargoMember* cbm_cargo_find_member(const CBMCargoManifest* m,
const char* name) {
if (!m || !name) return NULL;
for (int i = 0; i < m->member_count; i++) {
if (m->members[i].member_name &&
strcmp(m->members[i].member_name, name) == 0) {
return &m->members[i];
}
}
return NULL;
}