diff options
| author | Zuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian> | 2026-01-20 12:51:23 +0000 |
|---|---|---|
| committer | Zuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian> | 2026-01-20 12:51:23 +0000 |
| commit | b106fe19b55e9fe3348b3a5c9992c21dac27b02c (patch) | |
| tree | 32663c9a791b7f45ce9bc61c5a87351d5dc2c782 | |
| parent | e5d8c4219cfe5629a3ce4dbff01406a1817a788f (diff) | |
Working a bit on the LSP + fixed some bugs
| -rw-r--r-- | Makefile | 1 | ||||
| -rw-r--r-- | src/codegen/codegen_main.c | 110 | ||||
| -rw-r--r-- | src/lsp/json_rpc.c | 234 | ||||
| -rw-r--r-- | src/lsp/lsp_analysis.c | 566 | ||||
| -rw-r--r-- | src/lsp/lsp_index.c | 6 | ||||
| -rw-r--r-- | src/lsp/lsp_project.c | 299 | ||||
| -rw-r--r-- | src/lsp/lsp_project.h | 59 | ||||
| -rw-r--r-- | src/parser/parser_stmt.c | 10 | ||||
| -rw-r--r-- | tests/features/test_match_ref.zc | 25 |
9 files changed, 1118 insertions, 192 deletions
@@ -25,6 +25,7 @@ SRCS = src/main.c \ src/lsp/lsp_main.c \ src/lsp/lsp_analysis.c \ src/lsp/lsp_index.c \ + src/lsp/lsp_project.c \ src/zen/zen_facts.c \ src/repl/repl.c \ src/plugins/plugin_manager.c diff --git a/src/codegen/codegen_main.c b/src/codegen/codegen_main.c index 7382827..97abfc7 100644 --- a/src/codegen/codegen_main.c +++ b/src/codegen/codegen_main.c @@ -14,33 +14,101 @@ static int struct_depends_on(ASTNode *s1, const char *target_name) return 0; } - // Only structs have dependencies that matter for ordering. - if (s1->type != NODE_STRUCT) + // Check structs + if (s1->type == NODE_STRUCT) { - return 0; - } + ASTNode *field = s1->strct.fields; + while (field) + { + if (field->type == NODE_FIELD && field->field.type) + { + char *type_str = field->field.type; - ASTNode *field = s1->strct.fields; - while (field) + // Skip pointers - they don't create ordering dependency. + if (strchr(type_str, '*')) + { + field = field->next; + continue; + } + + // Clean type string (remove struct/enum prefixes) + const char *clean = type_str; + if (strncmp(clean, "struct ", 7) == 0) + { + clean += 7; + } + else if (strncmp(clean, "enum ", 5) == 0) + { + clean += 5; + } + else if (strncmp(clean, "union ", 6) == 0) + { + clean += 6; + } + + // Check for match + size_t len = strlen(target_name); + if (strncmp(clean, target_name, len) == 0) + { + char next = clean[len]; + if (next == 0 || next == '[' || isspace(next)) + { + return 1; + } + } + } + field = field->next; + } + } + // Check enums (ADTs) + else if (s1->type == NODE_ENUM) { - if (field->type == NODE_FIELD && field->field.type) + ASTNode *variant = s1->enm.variants; + while (variant) { - char *type_str = field->field.type; - // Skip pointers - they don't create ordering dependency. - if (strchr(type_str, '*')) + if (variant->type == NODE_ENUM_VARIANT && variant->variant.payload) { - field = field->next; - continue; - } + char *type_str = type_to_string(variant->variant.payload); + if (type_str) + { + if (strchr(type_str, '*')) + { + free(type_str); + variant = variant->next; + continue; + } - // Check if this field's type matches target (struct or enum). - if (strcmp(type_str, target_name) == 0) - { - return 1; + const char *clean = type_str; + if (strncmp(clean, "struct ", 7) == 0) + { + clean += 7; + } + else if (strncmp(clean, "enum ", 5) == 0) + { + clean += 5; + } + else if (strncmp(clean, "union ", 6) == 0) + { + clean += 6; + } + + size_t len = strlen(target_name); + if (strncmp(clean, target_name, len) == 0) + { + char next = clean[len]; + if (next == 0 || next == '[' || isspace(next)) + { + free(type_str); + return 1; + } + } + free(type_str); + } } + variant = variant->next; } - field = field->next; } + return 0; } @@ -102,8 +170,8 @@ static ASTNode *topo_sort_structs(ASTNode *head) continue; } - // Enums and traits have no dependencies, emit first. - if (nodes[i]->type == NODE_ENUM || nodes[i]->type == NODE_TRAIT) + // Traits have no dependencies, emit first. + if (nodes[i]->type == NODE_TRAIT) { order[order_idx++] = i; emitted[i] = 1; @@ -111,7 +179,7 @@ static ASTNode *topo_sort_structs(ASTNode *head) continue; } - // For structs, check if all dependencies are emitted. + // For structs/enums, check if all dependencies are emitted. int can_emit = 1; for (int j = 0; j < count; j++) { diff --git a/src/lsp/json_rpc.c b/src/lsp/json_rpc.c index 903da71..c8c9a3b 100644 --- a/src/lsp/json_rpc.c +++ b/src/lsp/json_rpc.c @@ -3,108 +3,148 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <ctype.h> -// Basic JSON parsing helpers -char *get_json_string(const char *json, const char *key) +// Helper to skip whitespace +const char *skip_ws(const char *p) { - char search[256]; - sprintf(search, "\"%s\":\"", key); - char *p = strstr(json, search); - if (!p) + while (*p && isspace(*p)) { - return NULL; + p++; } - p += strlen(search); - char *end = strchr(p, '"'); - if (!end) + return p; +} + +// Robust JSON string extractor +// Finds "key" ... : ... "value" +char *get_json_string(const char *json, const char *key) +{ + char key_pattern[256]; + sprintf(key_pattern, "\"%s\"", key); + + char *p = strstr(json, key_pattern); + while (p) { - return NULL; + const char *cursor = p + strlen(key_pattern); + cursor = skip_ws(cursor); + if (*cursor == ':') + { + cursor++; // skip : + cursor = skip_ws(cursor); + if (*cursor == '"') + { + // Found start of value + cursor++; + const char *start = cursor; + // Find end " (handling escapes?) + // MVP: just find next " that is not escaped + while (*cursor) + { + if (*cursor == '"' && *(cursor - 1) != '\\') + { + break; + } + cursor++; + } + + int len = cursor - start; + char *res = malloc(len + 1); + strncpy(res, start, len); + res[len] = 0; + return res; + } + } + // False positive? Find next + p = strstr(p + 1, key_pattern); } - int len = end - p; - char *res = malloc(len + 1); - strncpy(res, p, len); - res[len] = 0; - return res; + return NULL; } -// Extract nested "text" from params/contentChanges/0/text or -// params/textDocument/text This is very hacky for MVP. proper JSON library -// needed. - +// Extract nested "text" from params... +// Since "text" might be huge and contain anything, we need to be careful. char *get_text_content(const char *json) { - char *p = strstr(json, "\"text\":\""); - if (!p) + // Search for "text" key + char *res = get_json_string(json, "text"); + if (!res) { return NULL; } - p += 8; - - size_t cap = strlen(p); - char *res = malloc(cap + 1); - char *dst = res; - while (*p) + size_t len = strlen(res); + char *unescaped = malloc(len + 1); + char *dst = unescaped; + char *src = res; + while (*src) { - if (*p == '\\') + if (*src == '\\') { - p++; - if (*p == 'n') + src++; + if (*src == 'n') { *dst++ = '\n'; } - else if (*p == 'r') + else if (*src == 'r') { *dst++ = '\r'; } - else if (*p == 't') + else if (*src == 't') { *dst++ = '\t'; } - else if (*p == '"') + else if (*src == '"') { *dst++ = '"'; } - else if (*p == '\\') + else if (*src == '\\') { *dst++ = '\\'; } else { - *dst++ = *p; // preserve others + *dst++ = *src; } - p++; - } - else if (*p == '"') - { - break; // End of string. } else { - *dst++ = *p++; + *dst++ = *src; } + src++; } *dst = 0; - return res; + free(res); + return unescaped; } -// Helper to get line/char void get_json_position(const char *json, int *line, int *col) { - char *pos = strstr(json, "\"position\":"); - if (!pos) - { - return; - } - char *l = strstr(pos, "\"line\":"); - if (l) + // Search for "line" and "character" + // Note: they are integers + + char *p = strstr(json, "\"line\""); + if (p) { - *line = atoi(l + 7); + p += 6; // skip "line" + p = (char *)skip_ws(p); + if (*p == ':') + { + p++; + p = (char *)skip_ws(p); + *line = atoi(p); + } } - char *c = strstr(pos, "\"character\":"); - if (c) + + p = strstr(json, "\"character\""); + if (p) { - *col = atoi(c + 12); + p += 11; + p = (char *)skip_ws(p); + if (*p == ':') + { + p++; + p = (char *)skip_ws(p); + *col = atoi(p); + } } } @@ -112,11 +152,42 @@ void lsp_check_file(const char *uri, const char *src); void lsp_goto_definition(const char *uri, int line, int col); void lsp_hover(const char *uri, int line, int col); void lsp_completion(const char *uri, int line, int col); +void lsp_document_symbol(const char *uri); +void lsp_references(const char *uri, int line, int col); +void lsp_signature_help(const char *uri, int line, int col); + +#include "lsp_project.h" void handle_request(const char *json_str) { - if (strstr(json_str, "\"method\":\"initialize\"")) + // Looser method detection + if (strstr(json_str, "initialize")) { + // Extract rootPath or rootUri + char *root = get_json_string(json_str, "rootPath"); + if (!root) + { + root = get_json_string(json_str, "rootUri"); + } + + // Clean up URI if needed + if (root && strncmp(root, "file://", 7) == 0) + { + char *clean = strdup(root + 7); + free(root); + root = clean; + } + + if (root) + { + lsp_project_init(root); + free(root); + } + else + { + lsp_project_init("."); + } + const char *response = "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{" "\"capabilities\":{\"textDocumentSync\":1," "\"definitionProvider\":true,\"hoverProvider\":true," @@ -127,16 +198,14 @@ void handle_request(const char *json_str) return; } - if (strstr(json_str, "\"method\":\"textDocument/didOpen\"") || - strstr(json_str, "\"method\":\"textDocument/didChange\"")) + if (strstr(json_str, "didOpen") || strstr(json_str, "didChange")) { - char *uri = get_json_string(json_str, "uri"); char *text = get_text_content(json_str); if (uri && text) { - fprintf(stderr, "zls: Checking %s\n", uri); + // fprintf(stderr, "zls: Checking %s\n", uri); lsp_check_file(uri, text); } @@ -150,7 +219,7 @@ void handle_request(const char *json_str) } } - if (strstr(json_str, "\"method\":\"textDocument/definition\"")) + if (strstr(json_str, "textDocument/definition")) { char *uri = get_json_string(json_str, "uri"); int line = 0, col = 0; @@ -158,13 +227,12 @@ void handle_request(const char *json_str) if (uri) { - fprintf(stderr, "zls: Definition request at %d:%d\n", line, col); lsp_goto_definition(uri, line, col); free(uri); } } - if (strstr(json_str, "\"method\":\"textDocument/hover\"")) + if (strstr(json_str, "textDocument/hover")) { char *uri = get_json_string(json_str, "uri"); int line = 0, col = 0; @@ -172,13 +240,12 @@ void handle_request(const char *json_str) if (uri) { - fprintf(stderr, "zls: Hover request at %d:%d\n", line, col); lsp_hover(uri, line, col); free(uri); } } - if (strstr(json_str, "\"method\":\"textDocument/completion\"")) + if (strstr(json_str, "textDocument/completion")) { char *uri = get_json_string(json_str, "uri"); int line = 0, col = 0; @@ -186,9 +253,44 @@ void handle_request(const char *json_str) if (uri) { - fprintf(stderr, "zls: Completion request at %d:%d\n", line, col); lsp_completion(uri, line, col); free(uri); } } + + if (strstr(json_str, "textDocument/documentSymbol")) + { + char *uri = get_json_string(json_str, "uri"); + if (uri) + { + lsp_document_symbol(uri); + free(uri); + } + } + + if (strstr(json_str, "textDocument/references")) + { + char *uri = get_json_string(json_str, "uri"); + int line = 0, col = 0; + get_json_position(json_str, &line, &col); + + if (uri) + { + lsp_references(uri, line, col); + free(uri); + } + } + + if (strstr(json_str, "textDocument/signatureHelp")) + { + char *uri = get_json_string(json_str, "uri"); + int line = 0, col = 0; + get_json_position(json_str, &line, &col); + + if (uri) + { + lsp_signature_help(uri, line, col); + free(uri); + } + } } diff --git a/src/lsp/lsp_analysis.c b/src/lsp/lsp_analysis.c index d455894..b8b9189 100644 --- a/src/lsp/lsp_analysis.c +++ b/src/lsp/lsp_analysis.c @@ -1,13 +1,10 @@ - #include "json_rpc.h" -#include "lsp_index.h" -#include "parser.h" +#include "lsp_project.h" // Includes lsp_index.h, parser.h #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> - -static LSPIndex *g_index = NULL; +#include <unistd.h> typedef struct Diagnostic { @@ -23,17 +20,15 @@ typedef struct Diagnostic *tail; } DiagnosticList; -static ParserContext *g_ctx = NULL; -static char *g_last_src = NULL; - // Callback for parser errors. void lsp_on_error(void *data, Token t, const char *msg) { DiagnosticList *list = (DiagnosticList *)data; - Diagnostic *d = xmalloc(sizeof(Diagnostic)); + // Simple allocation for MVP + Diagnostic *d = calloc(1, sizeof(Diagnostic)); d->line = t.line > 0 ? t.line - 1 : 0; d->col = t.col > 0 ? t.col - 1 : 0; - d->message = xstrdup(msg); + d->message = strdup(msg); d->next = NULL; if (!list->head) @@ -50,44 +45,39 @@ void lsp_on_error(void *data, Token t, const char *msg) void lsp_check_file(const char *uri, const char *json_src) { - // Prepare ParserContext (persistent). - if (g_ctx) + if (!g_project) { - - free(g_ctx); + // Fallback or lazy init? current dir + char cwd[1024]; + if (getcwd(cwd, sizeof(cwd))) + { + lsp_project_init(cwd); + } + else + { + lsp_project_init("."); + } } - g_ctx = calloc(1, sizeof(ParserContext)); - g_ctx->is_fault_tolerant = 1; + // Setup error capture on the global project context DiagnosticList diagnostics = {0}; - g_ctx->error_callback_data = &diagnostics; - g_ctx->on_error = lsp_on_error; - - // Prepare Lexer. - // Cache source. - if (g_last_src) - { - free(g_last_src); - } - g_last_src = strdup(json_src); - Lexer l; - lexer_init(&l, json_src); + // We attach the callback to 'g_project->ctx'. + // NOTE: If we use lsp_project_update_file, it uses g_project->ctx. + void *old_data = g_project->ctx->error_callback_data; + void (*old_cb)(void *, Token, const char *) = g_project->ctx->on_error; - ASTNode *root = parse_program(g_ctx, &l); + g_project->ctx->error_callback_data = &diagnostics; + g_project->ctx->on_error = lsp_on_error; - if (g_index) - { - lsp_index_free(g_index); - } - g_index = lsp_index_new(); - if (root) - { - lsp_build_index(g_index, root); - } + // Update and Parse + lsp_project_update_file(uri, json_src); - // Construct JSON Response (notification) + // Restore + g_project->ctx->on_error = old_cb; + g_project->ctx->error_callback_data = old_data; + // Construct JSON Response (publishDiagnostics) char *notification = malloc(128 * 1024); char *p = notification; p += sprintf(p, @@ -98,7 +88,6 @@ void lsp_check_file(const char *uri, const char *json_src) Diagnostic *d = diagnostics.head; while (d) { - p += sprintf(p, "{\"range\":{\"start\":{\"line\":%d,\"character\":%d},\"end\":" "{\"line\":%d," @@ -109,20 +98,17 @@ void lsp_check_file(const char *uri, const char *json_src) { p += sprintf(p, ","); } - d = d->next; } p += sprintf(p, "]}}"); - // Send notification. long len = strlen(notification); fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", len, notification); fflush(stdout); free(notification); - // Cleanup. Diagnostic *cur = diagnostics.head; while (cur) { @@ -135,58 +121,139 @@ void lsp_check_file(const char *uri, const char *json_src) void lsp_goto_definition(const char *uri, int line, int col) { - if (!g_index) + ProjectFile *pf = lsp_project_get_file(uri); + LSPIndex *idx = pf ? pf->index : NULL; + + if (!idx) { return; } - LSPRange *r = lsp_find_at(g_index, line, col); - if (r && r->type == RANGE_REFERENCE) - { - // Found reference, return definition - char resp[1024]; - sprintf(resp, - "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"uri\":\"%s\"," - "\"range\":{\"start\":{" - "\"line\":%d,\"character\":%d},\"end\":{\"line\":%d,\"character\":%" - "d}}}}", - uri, r->def_line, r->def_col, r->def_line, r->def_col); + LSPRange *r = lsp_find_at(idx, line, col); - fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(resp), resp); - fflush(stdout); - } - else if (r && r->type == RANGE_DEFINITION) + // 1. Check Local Index + if (r) { - // Already at definition? Return itself. - char resp[1024]; - sprintf(resp, - "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"uri\":\"%s\"," - "\"range\":{\"start\":{" - "\"line\":%d,\"character\":%d},\"end\":{\"line\":%d,\"character\":%" - "d}}}}", - uri, r->start_line, r->start_col, r->end_line, r->end_col); + if (r->type == RANGE_DEFINITION) + { + // Already at definition + char resp[1024]; + sprintf(resp, + "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"uri\":\"%s\"," + "\"range\":{\"start\":{" + "\"line\":%d,\"character\":%d},\"end\":{\"line\":%d,\"character\":%d}}}}", + uri, r->start_line, r->start_col, r->end_line, r->end_col); + fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(resp), resp); + fflush(stdout); + return; + } + else if (r->type == RANGE_REFERENCE && r->def_line >= 0) + { + LSPRange *def = lsp_find_at(idx, r->def_line, r->def_col); + int is_local = 0; + if (def && def->type == RANGE_DEFINITION) + { + // Check name congruence + char *ref_name = NULL; + char *def_name = NULL; - fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(resp), resp); - fflush(stdout); + if (r->node->type == NODE_EXPR_VAR) + { + ref_name = r->node->var_ref.name; + } + else if (r->node->type == NODE_EXPR_CALL && + r->node->call.callee->type == NODE_EXPR_VAR) + { + ref_name = r->node->call.callee->var_ref.name; + } + + if (def->node->type == NODE_FUNCTION) + { + def_name = def->node->func.name; + } + else if (def->node->type == NODE_VAR_DECL) + { + def_name = def->node->var_decl.name; + } + else if (def->node->type == NODE_CONST) + { + def_name = def->node->var_decl.name; + } + else if (def->node->type == NODE_STRUCT) + { + def_name = def->node->strct.name; + } + + if (ref_name && def_name && strcmp(ref_name, def_name) == 0) + { + is_local = 1; + } + } + + if (is_local) + { + char resp[1024]; + sprintf(resp, + "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"uri\":\"%s\"," + "\"range\":{\"start\":{" + "\"line\":%d,\"character\":%d},\"end\":{\"line\":%d,\"character\":%d}}}}", + uri, r->def_line, r->def_col, r->def_line, r->def_col); + fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(resp), resp); + fflush(stdout); + return; + } + } } - else + + // 2. Global Definition (if local failed) + if (r && r->node) { - // Null result - const char *null_resp = "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":null}"; - fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(null_resp), null_resp); - fflush(stdout); + char *name = NULL; + if (r->node->type == NODE_EXPR_VAR) + { + name = r->node->var_ref.name; + } + else if (r->node->type == NODE_EXPR_CALL && r->node->call.callee->type == NODE_EXPR_VAR) + { + name = r->node->call.callee->var_ref.name; + } + + if (name) + { + DefinitionResult def = lsp_project_find_definition(name); + if (def.uri && def.range) + { + char resp[1024]; + sprintf(resp, + "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"uri\":\"%s\"," + "\"range\":{\"start\":{" + "\"line\":%d,\"character\":%d},\"end\":{\"line\":%d,\"character\":%d}}}}", + def.uri, def.range->start_line, def.range->start_col, def.range->end_line, + def.range->end_col); + fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(resp), resp); + fflush(stdout); + return; + } + } } + + const char *null_resp = "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":null}"; + fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(null_resp), null_resp); + fflush(stdout); } void lsp_hover(const char *uri, int line, int col) { (void)uri; - if (!g_index) + ProjectFile *pf = lsp_project_get_file(uri); + LSPIndex *idx = pf ? pf->index : NULL; + + if (!idx) { return; } - LSPRange *r = lsp_find_at(g_index, line, col); + LSPRange *r = lsp_find_at(idx, line, col); char *text = NULL; if (r) @@ -197,7 +264,7 @@ void lsp_hover(const char *uri, int line, int col) } else if (r->type == RANGE_REFERENCE) { - LSPRange *def = lsp_find_at(g_index, r->def_line, r->def_col); + LSPRange *def = lsp_find_at(idx, r->def_line, r->def_col); if (def && def->type == RANGE_DEFINITION) { text = def->hover_text; @@ -208,7 +275,6 @@ void lsp_hover(const char *uri, int line, int col) if (text) { char *json = malloc(16384); - // content: { kind: markdown, value: text } sprintf(json, "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"contents\":{\"kind\":" "\"markdown\"," @@ -229,20 +295,18 @@ void lsp_hover(const char *uri, int line, int col) void lsp_completion(const char *uri, int line, int col) { - (void)uri; - if (!g_ctx) + ProjectFile *pf = lsp_project_get_file(uri); + // Need global project context + if (!g_project || !g_project->ctx || !pf) { return; } - // Context-aware completion (Dot access) - if (g_last_src) + // 1. Context-aware completion (Dot access) + if (pf->source) { - // Simple line access int cur_line = 0; - - char *ptr = g_last_src; - // Fast forward to line + char *ptr = pf->source; while (*ptr && cur_line < line) { if (*ptr == '\n') @@ -254,14 +318,12 @@ void lsp_completion(const char *uri, int line, int col) if (col > 0 && ptr[col - 1] == '.') { - // Found dot! - // Scan backwards for identifier: [whitespace] [ident] . + // Found dot! Scan backwards for identifier int i = col - 2; while (i >= 0 && (ptr[i] == ' ' || ptr[i] == '\t')) { i--; } - if (i >= 0) { int end_ident = i; @@ -279,7 +341,7 @@ void lsp_completion(const char *uri, int line, int col) var_name[len] = 0; char *type_name = NULL; - Symbol *sym = find_symbol_in_all(g_ctx, var_name); + Symbol *sym = find_symbol_in_all(g_project->ctx, var_name); if (sym) { @@ -297,7 +359,7 @@ void lsp_completion(const char *uri, int line, int col) { char clean_name[256]; char *src = type_name; - if (0 == strncmp(src, "struct ", 7)) + if (strncmp(src, "struct ", 7) == 0) { src += 7; } @@ -308,12 +370,13 @@ void lsp_completion(const char *uri, int line, int col) } *dst = 0; - // Lookup struct. - StructDef *sd = g_ctx->struct_defs; + // Lookup struct in GLOBAL registry + StructDef *sd = g_project->ctx->struct_defs; while (sd) { - if (0 == strcmp(sd->name, clean_name)) + if (strcmp(sd->name, clean_name) == 0) { + // Found struct! char *json_fields = malloc(1024 * 1024); char *pj = json_fields; pj += sprintf(pj, "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":["); @@ -331,65 +394,358 @@ void lsp_completion(const char *uri, int line, int col) pj += sprintf( pj, "{\"label\":\"%s\",\"kind\":5,\"detail\":\"field %s\"}", - field->field.name, field->field.type); // Kind 5 = Field + field->field.name, field->field.type); ffirst = 0; field = field->next; } } - pj += sprintf(pj, "]}"); fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(json_fields), json_fields); fflush(stdout); free(json_fields); - free(json); - return; // Done, yippee. + free(type_name); // type_to_string arg + return; } sd = sd->next; } + if (sym && sym->type_info) + { + free(type_name); + } } } } } } - char *json = xmalloc(1024 * 1024); // 1MB buffer. + // 2. Global Completion (Functions & Structs) + char *json = malloc(1024 * 1024); char *p = json; p += sprintf(p, "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":["); int first = 1; // Functions - FuncSig *f = g_ctx->func_registry; + FuncSig *f = g_project->ctx->func_registry; while (f) { if (!first) { p += sprintf(p, ","); } - p += sprintf(p, "{\"label\":\"%s\",\"kind\":3,\"detail\":\"fn %s(...)\"}", f->name, - f->name); // Kind 3 = Function + p += sprintf(p, "{\"label\":\"%s\",\"kind\":3,\"detail\":\"fn %s\"}", f->name, f->name); first = 0; f = f->next; } // Structs - StructDef *s = g_ctx->struct_defs; + StructDef *s = g_project->ctx->struct_defs; while (s) { if (!first) { p += sprintf(p, ","); } - p += sprintf(p, "{\"label\":\"%s\",\"kind\":22,\"detail\":\"struct %s\"}", s->name, - s->name); // Kind 22 = Struct + p += + sprintf(p, "{\"label\":\"%s\",\"kind\":22,\"detail\":\"struct %s\"}", s->name, s->name); first = 0; s = s->next; } p += sprintf(p, "]}"); + fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(json), json); + fflush(stdout); + free(json); +} +void lsp_document_symbol(const char *uri) +{ + ProjectFile *pf = lsp_project_get_file(uri); + if (!pf || !pf->index) + { + const char *null_resp = "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":null}"; + fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(null_resp), null_resp); + fflush(stdout); + return; + } + + char *json = malloc(1024 * 1024); + char *p = json; + p += sprintf(p, "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":["); + + int first = 1; + LSPRange *r = pf->index->head; + while (r) + { + if (r->type == RANGE_DEFINITION && r->node) + { + char *name = NULL; + int kind = 0; // 0 = default + + if (r->node->type == NODE_FUNCTION) + { + name = r->node->func.name; + kind = 12; // Function + } + else if (r->node->type == NODE_STRUCT) + { + name = r->node->strct.name; + kind = 23; // Struct + } + else if (r->node->type == NODE_VAR_DECL) + { + name = r->node->var_decl.name; + kind = 13; // Variable + } + else if (r->node->type == NODE_CONST) + { + name = r->node->var_decl.name; + kind = 14; // Constant + } + + if (name) + { + if (!first) + { + p += sprintf(p, ","); + } + p += sprintf(p, + "{\"name\":\"%s\",\"kind\":%d,\"location\":{\"uri\":\"%s\",\"range\":{" + "\"start\":{\"line\":%d,\"character\":%d},\"end\":{\"line\":%d," + "\"character\":%d}}}}", + name, kind, uri, r->start_line, r->start_col, r->end_line, r->end_col); + first = 0; + } + } + r = r->next; + } + + p += sprintf(p, "]}"); fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(json), json); fflush(stdout); free(json); } + +void lsp_references(const char *uri, int line, int col) +{ + ProjectFile *pf = lsp_project_get_file(uri); + if (!pf || !pf->index) + { + return; + } + + LSPRange *r = lsp_find_at(pf->index, line, col); + if (!r || !r->node) + { + // No symbol at cursor? + const char *null_resp = "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[]}"; + fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(null_resp), null_resp); + fflush(stdout); + return; + } + + char *name = NULL; + if (r->node->type == NODE_FUNCTION) + { + name = r->node->func.name; + } + else if (r->node->type == NODE_VAR_DECL) + { + name = r->node->var_decl.name; + } + else if (r->node->type == NODE_CONST) + { + name = r->node->var_decl.name; + } + else if (r->node->type == NODE_STRUCT) + { + name = r->node->strct.name; + } + else if (r->node->type == NODE_EXPR_VAR) + { + name = r->node->var_ref.name; + } + else if (r->node->type == NODE_EXPR_CALL && r->node->call.callee->type == NODE_EXPR_VAR) + { + name = r->node->call.callee->var_ref.name; + } + + if (!name) + { + const char *null_resp = "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":[]}"; + fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(null_resp), null_resp); + fflush(stdout); + return; + } + + ReferenceResult *refs = lsp_project_find_references(name); + + char *json = malloc(1024 * 1024); // Large buffer for references + char *p = json; + p += sprintf(p, "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":["); + + int first = 1; + ReferenceResult *curr = refs; + while (curr) + { + if (!first) + { + p += sprintf(p, ","); + } + p += sprintf(p, + "{\"uri\":\"%s\",\"range\":{\"start\":{\"line\":%d,\"character\":%d},\"end\":{" + "\"line\":%d,\"character\":%d}}}", + curr->uri, curr->range->start_line, curr->range->start_col, + curr->range->end_line, curr->range->end_col); + first = 0; + + ReferenceResult *next = curr->next; + free(curr); // Free linked list node as we go + curr = next; + } + + p += sprintf(p, "]}"); + fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(json), json); + fflush(stdout); + free(json); +} + +void lsp_signature_help(const char *uri, int line, int col) +{ + ProjectFile *pf = lsp_project_get_file(uri); + if (!g_project || !g_project->ctx || !pf || !pf->source) + { + return; + } + + // Scan backwards from cursor for '(' + char *ptr = pf->source; + int cur_line = 0; + while (*ptr && cur_line < line) + { + if (*ptr == '\n') + { + cur_line++; + } + ptr++; + } + + // We are at start of line. Advance to col. + if (ptr && col > 0) + { + ptr += col; + } + + // Safety check + if (ptr > pf->source + strlen(pf->source)) + { + return; + } + + // Scan backwards + char *p = ptr - 1; + while (p >= pf->source) + { + if (*p == ')') + { + // Nested call or closed. Bail for simple implementation. + // Or skip balanced parens (TODO for better robustness) + return; + } + if (*p == '(') + { + // Found open paren! + // Look for identifier before it. + char *ident_end = p - 1; + while (ident_end >= pf->source && isspace(*ident_end)) + { + ident_end--; + } + + if (ident_end < pf->source) + { + return; + } + + char *ident_start = ident_end; + while (ident_start >= pf->source && (isalnum(*ident_start) || *ident_start == '_')) + { + ident_start--; + } + ident_start++; + + // Extract name + int len = ident_end - ident_start + 1; + if (len <= 0 || len > 255) + { + return; + } + + char func_name[256]; + strncpy(func_name, ident_start, len); + func_name[len] = 0; + + // Lookup Function + FuncSig *fn = g_project->ctx->func_registry; + while (fn) + { + if (strcmp(fn->name, func_name) == 0) + { + char *json = malloc(4096); + char label[2048]; + // Reconstruct signature label + char params[1024] = ""; + int first = 1; + + // Use total_args and arg_types + for (int i = 0; i < fn->total_args; i++) + { + if (!first) + { + strcat(params, ", "); + } + + // Convert Type* to string + char *tstr = type_to_string(fn->arg_types[i]); + if (tstr) + { + strcat(params, tstr); + free(tstr); + } + else + { + strcat(params, "unknown"); + } + + first = 0; + } + + char *ret_str = type_to_string(fn->ret_type); + sprintf(label, "fn %s(%s) -> %s", fn->name, params, ret_str ? ret_str : "void"); + if (ret_str) + { + free(ret_str); + } + + sprintf(json, + "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"signatures\":[{\"label\":" + "\"%s\",\"parameters\":[]}]}}", + label); + + fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(json), json); + fflush(stdout); + free(json); + return; + } + fn = fn->next; + } + break; // Found paren but no func match + } + p--; + } + + const char *null_resp = "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":null}"; + fprintf(stdout, "Content-Length: %ld\r\n\r\n%s", strlen(null_resp), null_resp); + fflush(stdout); +} diff --git a/src/lsp/lsp_index.c b/src/lsp/lsp_index.c index d952b77..285dec3 100644 --- a/src/lsp/lsp_index.c +++ b/src/lsp/lsp_index.c @@ -148,6 +148,12 @@ void lsp_walk_node(LSPIndex *idx, ASTNode *node) lsp_walk_node(idx, node->var_decl.init_expr); } + else if (node->type == NODE_STRUCT) + { + char hover[256]; + sprintf(hover, "struct %s", node->strct.name); + lsp_index_add_def(idx, node->token, hover, node); + } // Reference logic. if (node->definition_token.line > 0 && node->definition_token.line != node->token.line) diff --git a/src/lsp/lsp_project.c b/src/lsp/lsp_project.c new file mode 100644 index 0000000..0af3acc --- /dev/null +++ b/src/lsp/lsp_project.c @@ -0,0 +1,299 @@ +#include "lsp_project.h" +#include <dirent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +LSPProject *g_project = NULL; + +static void scan_dir(const char *dir_path); + +void lsp_project_init(const char *root_path) +{ + if (g_project) + { + return; + } + + fprintf(stderr, "zls: Initializing project at %s\n", root_path); + + g_project = xcalloc(1, sizeof(LSPProject)); + g_project->root_path = xstrdup(root_path); + + // Create a persistent global context + g_project->ctx = xcalloc(1, sizeof(ParserContext)); + g_project->ctx->is_fault_tolerant = 1; + + // Scan workspace + scan_dir(root_path); +} + +static void scan_file(const char *path) +{ + // Skip if not .zc + const char *ext = strrchr(path, '.'); + if (!ext || strcmp(ext, ".zc") != 0) + { + return; + } + + fprintf(stderr, "zls: Indexing %s\n", path); + + char *src = load_file(path); + if (!src) + { + return; + } + + char uri[2048]; + sprintf(uri, "file://%s", path); + + lsp_project_update_file(uri, src); + free(src); +} + +static void scan_dir(const char *dir_path) +{ + DIR *d = opendir(dir_path); + if (!d) + { + return; + } + + struct dirent *dir; + while ((dir = readdir(d)) != NULL) + { + if (dir->d_name[0] == '.') + { + continue; + } + + char path[1024]; + snprintf(path, sizeof(path), "%s/%s", dir_path, dir->d_name); + + struct stat st; + if (stat(path, &st) == 0) + { + if (S_ISDIR(st.st_mode)) + { + scan_dir(path); + } + else if (S_ISREG(st.st_mode)) + { + scan_file(path); + } + } + } + closedir(d); +} + +ProjectFile *lsp_project_get_file(const char *uri) +{ + if (!g_project) + { + return NULL; + } + ProjectFile *curr = g_project->files; + while (curr) + { + if (strcmp(curr->uri, uri) == 0) + { + return curr; + } + curr = curr->next; + } + return NULL; +} + +static ProjectFile *add_project_file(const char *uri) +{ + ProjectFile *f = xcalloc(1, sizeof(ProjectFile)); + f->uri = xstrdup(uri); + // Simple path extraction from URI (file://...) + if (strncmp(uri, "file://", 7) == 0) + { + f->path = xstrdup(uri + 7); + } + else + { + f->path = xstrdup(uri); + } + + f->next = g_project->files; + g_project->files = f; + return f; +} + +void lsp_project_update_file(const char *uri, const char *src) +{ + if (!g_project) + { + return; + } + + ProjectFile *pf = lsp_project_get_file(uri); + if (!pf) + { + pf = add_project_file(uri); + } + + // Clear old index + if (pf->index) + { + lsp_index_free(pf->index); + pf->index = NULL; + } + + // Update source + if (pf->source) + { + free(pf->source); + } + pf->source = xstrdup(src); + + // Parse + Lexer l; + lexer_init(&l, src); + + ASTNode *root = parse_program(g_project->ctx, &l); + + // Build Index + pf->index = lsp_index_new(); + if (root) + { + lsp_build_index(pf->index, root); + } +} + +DefinitionResult lsp_project_find_definition(const char *name) +{ + // ... existing implementation ... + DefinitionResult res = {0}; + if (!g_project) + { + return res; + } + + ProjectFile *pf = g_project->files; + while (pf) + { + if (pf->index) + { + LSPRange *r = pf->index->head; + while (r) + { + if (r->type == RANGE_DEFINITION && r->node) + { + // Check name match + char *found_name = NULL; + if (r->node->type == NODE_FUNCTION) + { + found_name = r->node->func.name; + } + else if (r->node->type == NODE_VAR_DECL) + { + found_name = r->node->var_decl.name; + } + else if (r->node->type == NODE_CONST) + { + found_name = r->node->var_decl.name; + } + else if (r->node->type == NODE_STRUCT) + { + found_name = r->node->strct.name; + } + + if (found_name && strcmp(found_name, name) == 0) + { + res.uri = pf->uri; + res.range = r; + return res; + } + } + r = r->next; + } + } + pf = pf->next; + } + + return res; +} + +// Find all references to a symbol name project-wide + +ReferenceResult *lsp_project_find_references(const char *name) +{ + if (!g_project) + { + return NULL; + } + ReferenceResult *head = NULL; + ReferenceResult *tail = NULL; + + ProjectFile *pf = g_project->files; + while (pf) + { + if (pf->index) + { + LSPRange *r = pf->index->head; + while (r) + { + // We want REFERENCES that match the name + // Or DEFINITIONS that match the name (include decl) + char *scan_name = NULL; + + if (r->node) + { + if (r->node->type == NODE_FUNCTION) + { + scan_name = r->node->func.name; + } + else if (r->node->type == NODE_VAR_DECL) + { + scan_name = r->node->var_decl.name; + } + else if (r->node->type == NODE_CONST) + { + scan_name = r->node->var_decl.name; + } + else if (r->node->type == NODE_STRUCT) + { + scan_name = r->node->strct.name; + } + else if (r->node->type == NODE_EXPR_VAR) + { + scan_name = r->node->var_ref.name; + } + else if (r->node->type == NODE_EXPR_CALL && + r->node->call.callee->type == NODE_EXPR_VAR) + { + scan_name = r->node->call.callee->var_ref.name; + } + } + + if (scan_name && strcmp(scan_name, name) == 0) + { + ReferenceResult *new_res = calloc(1, sizeof(ReferenceResult)); + new_res->uri = pf->uri; + new_res->range = r; + + if (!head) + { + head = new_res; + tail = new_res; + } + else + { + tail->next = new_res; + tail = new_res; + } + } + + r = r->next; + } + } + pf = pf->next; + } + return head; +} diff --git a/src/lsp/lsp_project.h b/src/lsp/lsp_project.h new file mode 100644 index 0000000..9b4fe42 --- /dev/null +++ b/src/lsp/lsp_project.h @@ -0,0 +1,59 @@ +#ifndef LSP_PROJECT_H +#define LSP_PROJECT_H + +#include "parser.h" +#include "lsp_index.h" + +typedef struct ProjectFile +{ + char *path; // Absolute path + char *uri; // file:// URI + char *source; // Cached source content + LSPIndex *index; // File-specific index (local vars, refs) + struct ProjectFile *next; +} ProjectFile; + +typedef struct +{ + // Global symbol table (Structs, Functions, Globals) + // We reuse ParserContext for this, as it already supports registries. + ParserContext *ctx; + + // List of tracked files + ProjectFile *files; + + // Root directory + char *root_path; +} LSPProject; + +// Global project instance +extern LSPProject *g_project; + +// Initialize the project with a root directory +void lsp_project_init(const char *root_path); + +// Find a file in the project +ProjectFile *lsp_project_get_file(const char *uri); + +// Update a file (re-parse and re-index) +void lsp_project_update_file(const char *uri, const char *src); + +// Find definition globally +typedef struct +{ + char *uri; + LSPRange *range; +} DefinitionResult; + +DefinitionResult lsp_project_find_definition(const char *name); + +typedef struct ReferenceResult +{ + char *uri; + LSPRange *range; + struct ReferenceResult *next; +} ReferenceResult; + +ReferenceResult *lsp_project_find_references(const char *name); + +#endif diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c index 7433525..c5efe8a 100644 --- a/src/parser/parser_stmt.c +++ b/src/parser/parser_stmt.c @@ -335,6 +335,14 @@ ASTNode *parse_match(ParserContext *ctx, Lexer *l) zpanic_at(lexer_peek(l), "Expected =>"); } + // Create scope for the case to hold the binding + enter_scope(ctx); + if (binding) + { + // If ref binding, mark as pointer type so auto-deref (->) works + add_symbol(ctx, binding, is_ref ? "void*" : "unknown", NULL); + } + ASTNode *body; Token pk = lexer_peek(l); if (pk.type == TOK_LBRACE) @@ -355,6 +363,8 @@ ASTNode *parse_match(ParserContext *ctx, Lexer *l) body = parse_expression(ctx, l); } + exit_scope(ctx); + ASTNode *c = ast_create(NODE_MATCH_CASE); c->match_case.pattern = pattern; c->match_case.binding_name = binding; diff --git a/tests/features/test_match_ref.zc b/tests/features/test_match_ref.zc index 9734ffe..4e04b89 100644 --- a/tests/features/test_match_ref.zc +++ b/tests/features/test_match_ref.zc @@ -15,3 +15,28 @@ test "match_ref_int" { MyOption::None => assert(false, "fail") } } +// Mover struct to test move semantics vs ref +struct Mover { + val: int; +} + +test "match_ref_mover" { + var m = Mover { val: 100 }; + // MyOption<Mover> instantiation + var opt = MyOption<Mover>::Some(m); + + match opt { + MyOption::Some(ref x) => { + // x should be Mover* + assert(x.val == 100, "Mover ref access failed"); + x.val = 200; + }, + MyOption::None => assert(false, "Should be Some") + } + + // Check if modification persisted + match opt { + MyOption::Some(v) => assert(v.val == 200, "Mutation via ref failed"), + MyOption::None => {} + } +} |
