#include #include #include "el_runtime.h" el_val_t is_digit_ch(el_val_t ch); el_val_t is_alpha_ch(el_val_t ch); el_val_t is_ident_ch(el_val_t ch); el_val_t extract_prefix(el_val_t source, el_val_t pos); el_val_t extract_token(el_val_t source, el_val_t pos); el_val_t pos_to_line_col(el_val_t source, el_val_t pos); el_val_t json_escape(el_val_t s); el_val_t json_str(el_val_t s); el_val_t json_kv_str(el_val_t k, el_val_t v); el_val_t json_kv_int(el_val_t k, el_val_t v); el_val_t json_kv_float(el_val_t k, el_val_t v); el_val_t json_kv_raw(el_val_t k, el_val_t raw_v); el_val_t json_array_of(el_val_t items); el_val_t keywords_list(void); el_val_t builtin_type_names(void); el_val_t builtin_type_doc(el_val_t name); el_val_t builtin_fns(void); el_val_t keyword_doc(el_val_t kw); el_val_t first_ident_in(el_val_t rest); el_val_t scan_decls(el_val_t source); el_val_t list_contains_str(el_val_t xs, el_val_t needle); el_val_t prefix_score(el_val_t label, el_val_t prefix); el_val_t completion_json(el_val_t label, el_val_t kind, el_val_t detail, el_val_t doc, el_val_t score); el_val_t lsp_complete(el_val_t source, el_val_t pos); el_val_t lsp_hover(el_val_t source, el_val_t pos); el_val_t diag_json(el_val_t message, el_val_t severity, el_val_t line, el_val_t col); el_val_t lsp_diagnostics(el_val_t source); el_val_t outline_item_json(el_val_t kind, el_val_t name, el_val_t line); el_val_t lsp_outline(el_val_t source); el_val_t format_strip_trailing(el_val_t line); el_val_t lsp_format(el_val_t source); el_val_t lsp_type_graph(el_val_t source); el_val_t collect_struct_fields(el_val_t source, el_val_t type_name); el_val_t referenced_type(el_val_t field_str, el_val_t known_types); el_val_t lsp_definition(el_val_t source, el_val_t pos); el_val_t lsp_references(el_val_t source, el_val_t pos); el_val_t lsp_jsonrpc(el_val_t method, el_val_t params_json); el_val_t read_headers(void); el_val_t lsp_stdio_loop(void); el_val_t lsp_main(void); el_val_t pkey(el_val_t name, el_val_t field); el_val_t put_str(el_val_t name, el_val_t field, el_val_t value); el_val_t put_bool(el_val_t name, el_val_t field, el_val_t value); el_val_t get_str(el_val_t name, el_val_t field); el_val_t get_bool(el_val_t name, el_val_t field); el_val_t index_list(void); el_val_t index_set(el_val_t names); el_val_t index_contains(el_val_t name); el_val_t index_add(el_val_t name); el_val_t register_plugin(el_val_t name, el_val_t version, el_val_t description, el_val_t installed, el_val_t enabled, el_val_t hooks, el_val_t first_party); el_val_t plugin_init(void); el_val_t json_escape_str(el_val_t s); el_val_t quote_json(el_val_t s); el_val_t hooks_to_json(el_val_t hooks_csv); el_val_t plugin_to_json(el_val_t name); el_val_t plugin_list_json(void); el_val_t plugin_get_json(el_val_t name); el_val_t plugin_install(el_val_t name); el_val_t plugin_remove(el_val_t name); el_val_t plugin_enable(el_val_t name); el_val_t plugin_disable(el_val_t name); el_val_t host_main(void); el_val_t jstr(el_val_t s); el_val_t jerr(el_val_t msg); el_val_t ok_obj(void); el_val_t url_decode(el_val_t s); el_val_t hex_digit(el_val_t ch); el_val_t hex_to_int(el_val_t hex); el_val_t char_from_code(el_val_t code); el_val_t split_path_query(el_val_t path); el_val_t query_get(el_val_t query, el_val_t key); el_val_t cfg_port(void); el_val_t cfg_project_path(void); el_val_t cfg_engram_url(void); el_val_t cfg_el_binary(void); el_val_t safe_join(el_val_t rel); el_val_t route_status(void); el_val_t route_config(void); el_val_t route_files_list(el_val_t query); el_val_t route_file_read(el_val_t query); el_val_t route_file_write(el_val_t body); el_val_t route_lsp_complete(el_val_t query); el_val_t route_lsp_hover(el_val_t query); el_val_t route_lsp_errors(el_val_t query); el_val_t route_lsp_jsonrpc(el_val_t body); el_val_t route_outline(el_val_t query); el_val_t route_definition(el_val_t query); el_val_t route_references(el_val_t query); el_val_t route_format(el_val_t body); el_val_t route_type_graph(el_val_t query); el_val_t active_theme(void); el_val_t route_themes_list(void); el_val_t route_themes_set(el_val_t body); el_val_t route_plugins_list(void); el_val_t route_plugins_install(el_val_t body); el_val_t route_plugins_remove(el_val_t body); el_val_t route_plugins_enable(el_val_t body); el_val_t route_plugins_disable(el_val_t body); el_val_t route_snippets(void); el_val_t settings_path(void); el_val_t default_settings_json(void); el_val_t route_settings_get(void); el_val_t route_settings_save(el_val_t body); el_val_t route_settings_reset(void); el_val_t route_build_or_run(el_val_t action, el_val_t body); el_val_t route_build(el_val_t body); el_val_t route_run(el_val_t body); el_val_t route_reason(el_val_t body); el_val_t route_activate_preview(el_val_t query); el_val_t route_index(void); el_val_t route_health(void); el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body); el_val_t server_main(void); el_val_t is_digit_ch(el_val_t ch) { if (str_eq(ch, EL_STR("0"))) { return 1; } if (str_eq(ch, EL_STR("1"))) { return 1; } if (str_eq(ch, EL_STR("2"))) { return 1; } if (str_eq(ch, EL_STR("3"))) { return 1; } if (str_eq(ch, EL_STR("4"))) { return 1; } if (str_eq(ch, EL_STR("5"))) { return 1; } if (str_eq(ch, EL_STR("6"))) { return 1; } if (str_eq(ch, EL_STR("7"))) { return 1; } if (str_eq(ch, EL_STR("8"))) { return 1; } if (str_eq(ch, EL_STR("9"))) { return 1; } return 0; return 0; } el_val_t is_alpha_ch(el_val_t ch) { el_val_t lower = str_to_lower(ch); if (str_eq(ch, EL_STR(""))) { return 0; } if (!str_eq(lower, str_to_upper(lower))) { return 1; } if (str_contains(EL_STR("abcdefghijklmnopqrstuvwxyz"), ch)) { return 1; } if (str_contains(EL_STR("ABCDEFGHIJKLMNOPQRSTUVWXYZ"), ch)) { return 1; } return 0; return 0; } el_val_t is_ident_ch(el_val_t ch) { if (is_digit_ch(ch)) { return 1; } if (is_alpha_ch(ch)) { return 1; } if (str_eq(ch, EL_STR("_"))) { return 1; } return 0; return 0; } el_val_t extract_prefix(el_val_t source, el_val_t pos) { el_val_t n = str_len(source); el_val_t end = pos; if (end > n) { end = n; } el_val_t start = end; el_val_t chars = native_string_chars(source); el_val_t total = native_list_len(chars); el_val_t scanning = 1; while (scanning) { if (start <= 0) { scanning = 0; } else { el_val_t prev = native_list_get(chars, (start - 1)); if (is_ident_ch(prev)) { start = (start - 1); } else { scanning = 0; } } } return str_slice(source, start, end); return 0; } el_val_t extract_token(el_val_t source, el_val_t pos) { el_val_t n = str_len(source); if (pos > n) { return EL_STR(""); } el_val_t chars = native_string_chars(source); el_val_t total = native_list_len(chars); el_val_t start = pos; el_val_t go_left = 1; while (go_left) { if (start <= 0) { go_left = 0; } else { el_val_t prev = native_list_get(chars, (start - 1)); if (is_ident_ch(prev)) { start = (start - 1); } else { go_left = 0; } } } el_val_t end = pos; el_val_t go_right = 1; while (go_right) { if (end >= total) { go_right = 0; } else { el_val_t cur = native_list_get(chars, end); if (is_ident_ch(cur)) { end = (end + 1); } else { go_right = 0; } } } return str_slice(source, start, end); return 0; } el_val_t pos_to_line_col(el_val_t source, el_val_t pos) { el_val_t n = str_len(source); el_val_t cap = pos; if (cap > n) { cap = n; } el_val_t chars = native_string_chars(source); el_val_t i = 0; el_val_t line = 1; el_val_t col = 1; while (i < cap) { el_val_t ch = native_list_get(chars, i); if (str_eq(ch, EL_STR("\n"))) { line = (line + 1); col = 1; } else { col = (col + 1); } i = (i + 1); } return el_map_new(2, "line", line, "col", col); return 0; } el_val_t json_escape(el_val_t s) { el_val_t chars = native_string_chars(s); el_val_t n = native_list_len(chars); el_val_t out = EL_STR(""); el_val_t i = 0; while (i < n) { el_val_t ch = native_list_get(chars, i); if (str_eq(ch, EL_STR("\""))) { out = el_str_concat(out, EL_STR("\\\"")); } else { if (str_eq(ch, EL_STR("\\"))) { out = el_str_concat(out, EL_STR("\\\\")); } else { if (str_eq(ch, EL_STR("\n"))) { out = el_str_concat(out, EL_STR("\\n")); } else { if (str_eq(ch, EL_STR("\r"))) { out = el_str_concat(out, EL_STR("\\r")); } else { if (str_eq(ch, EL_STR("\t"))) { out = el_str_concat(out, EL_STR("\\t")); } else { out = el_str_concat(out, ch); } } } } } i = (i + 1); } return out; return 0; } el_val_t json_str(el_val_t s) { return el_str_concat(el_str_concat(EL_STR("\""), json_escape(s)), EL_STR("\"")); return 0; } el_val_t json_kv_str(el_val_t k, el_val_t v) { return el_str_concat(el_str_concat(json_str(k), EL_STR(":")), json_str(v)); return 0; } el_val_t json_kv_int(el_val_t k, el_val_t v) { return el_str_concat(el_str_concat(json_str(k), EL_STR(":")), int_to_str(v)); return 0; } el_val_t json_kv_float(el_val_t k, el_val_t v) { return el_str_concat(el_str_concat(json_str(k), EL_STR(":")), float_to_str(v)); return 0; } el_val_t json_kv_raw(el_val_t k, el_val_t raw_v) { return el_str_concat(el_str_concat(json_str(k), EL_STR(":")), raw_v); return 0; } el_val_t json_array_of(el_val_t items) { el_val_t n = native_list_len(items); el_val_t out = EL_STR("["); el_val_t i = 0; while (i < n) { if (i > 0) { out = el_str_concat(out, EL_STR(",")); } out = el_str_concat(out, native_list_get(items, i)); i = (i + 1); } return el_str_concat(out, EL_STR("]")); return 0; } el_val_t keywords_list(void) { el_val_t ks = native_list_empty(); ks = native_list_append(ks, EL_STR("let")); ks = native_list_append(ks, EL_STR("fn")); ks = native_list_append(ks, EL_STR("type")); ks = native_list_append(ks, EL_STR("enum")); ks = native_list_append(ks, EL_STR("match")); ks = native_list_append(ks, EL_STR("return")); ks = native_list_append(ks, EL_STR("activate")); ks = native_list_append(ks, EL_STR("where")); ks = native_list_append(ks, EL_STR("sealed")); ks = native_list_append(ks, EL_STR("if")); ks = native_list_append(ks, EL_STR("else")); ks = native_list_append(ks, EL_STR("for")); ks = native_list_append(ks, EL_STR("in")); ks = native_list_append(ks, EL_STR("while")); ks = native_list_append(ks, EL_STR("true")); ks = native_list_append(ks, EL_STR("false")); ks = native_list_append(ks, EL_STR("test")); ks = native_list_append(ks, EL_STR("seed")); ks = native_list_append(ks, EL_STR("assert")); ks = native_list_append(ks, EL_STR("target")); ks = native_list_append(ks, EL_STR("protocol")); ks = native_list_append(ks, EL_STR("impl")); ks = native_list_append(ks, EL_STR("import")); ks = native_list_append(ks, EL_STR("from")); ks = native_list_append(ks, EL_STR("as")); ks = native_list_append(ks, EL_STR("with")); ks = native_list_append(ks, EL_STR("retry")); ks = native_list_append(ks, EL_STR("times")); ks = native_list_append(ks, EL_STR("fallback")); ks = native_list_append(ks, EL_STR("reason")); ks = native_list_append(ks, EL_STR("parallel")); ks = native_list_append(ks, EL_STR("trace")); ks = native_list_append(ks, EL_STR("requires")); ks = native_list_append(ks, EL_STR("deploy")); ks = native_list_append(ks, EL_STR("to")); ks = native_list_append(ks, EL_STR("via")); ks = native_list_append(ks, EL_STR("vessel")); ks = native_list_append(ks, EL_STR("cgi")); return ks; return 0; } el_val_t builtin_type_names(void) { el_val_t ts = native_list_empty(); ts = native_list_append(ts, EL_STR("Int")); ts = native_list_append(ts, EL_STR("Float")); ts = native_list_append(ts, EL_STR("String")); ts = native_list_append(ts, EL_STR("Bool")); ts = native_list_append(ts, EL_STR("Uuid")); ts = native_list_append(ts, EL_STR("Void")); ts = native_list_append(ts, EL_STR("Any")); return ts; return 0; } el_val_t builtin_type_doc(el_val_t name) { if (str_eq(name, EL_STR("Int"))) { return EL_STR("64-bit signed integer"); } if (str_eq(name, EL_STR("Float"))) { return EL_STR("64-bit IEEE 754 double"); } if (str_eq(name, EL_STR("String"))) { return EL_STR("UTF-8 string"); } if (str_eq(name, EL_STR("Bool"))) { return EL_STR("Boolean value"); } if (str_eq(name, EL_STR("Uuid"))) { return EL_STR("RFC 4122 UUID"); } if (str_eq(name, EL_STR("Void"))) { return EL_STR("Unit type — no value"); } if (str_eq(name, EL_STR("Any"))) { return EL_STR("Dynamically-typed value"); } return EL_STR(""); return 0; } el_val_t builtin_fns(void) { el_val_t xs = native_list_empty(); xs = native_list_append(xs, EL_STR("println")); xs = native_list_append(xs, EL_STR("fn(value: String) -> Void")); xs = native_list_append(xs, EL_STR("print")); xs = native_list_append(xs, EL_STR("fn(value: String) -> Void")); xs = native_list_append(xs, EL_STR("readline")); xs = native_list_append(xs, EL_STR("fn() -> String")); xs = native_list_append(xs, EL_STR("args")); xs = native_list_append(xs, EL_STR("fn() -> [String]")); xs = native_list_append(xs, EL_STR("int_to_str")); xs = native_list_append(xs, EL_STR("fn(n: Int) -> String")); xs = native_list_append(xs, EL_STR("str_to_int")); xs = native_list_append(xs, EL_STR("fn(s: String) -> Int")); xs = native_list_append(xs, EL_STR("str_len")); xs = native_list_append(xs, EL_STR("fn(s: String) -> Int")); xs = native_list_append(xs, EL_STR("str_slice")); xs = native_list_append(xs, EL_STR("fn(s: String, start: Int, end: Int) -> String")); xs = native_list_append(xs, EL_STR("str_concat")); xs = native_list_append(xs, EL_STR("fn(a: String, b: String) -> String")); xs = native_list_append(xs, EL_STR("str_contains")); xs = native_list_append(xs, EL_STR("fn(s: String, sub: String) -> Bool")); xs = native_list_append(xs, EL_STR("str_starts_with")); xs = native_list_append(xs, EL_STR("fn(s: String, p: String) -> Bool")); xs = native_list_append(xs, EL_STR("str_ends_with")); xs = native_list_append(xs, EL_STR("fn(s: String, suf: String) -> Bool")); xs = native_list_append(xs, EL_STR("str_to_upper")); xs = native_list_append(xs, EL_STR("fn(s: String) -> String")); xs = native_list_append(xs, EL_STR("str_to_lower")); xs = native_list_append(xs, EL_STR("fn(s: String) -> String")); xs = native_list_append(xs, EL_STR("str_split")); xs = native_list_append(xs, EL_STR("fn(s: String, sep: String) -> [String]")); xs = native_list_append(xs, EL_STR("str_trim")); xs = native_list_append(xs, EL_STR("fn(s: String) -> String")); xs = native_list_append(xs, EL_STR("str_replace")); xs = native_list_append(xs, EL_STR("fn(s: String, from: String, to: String) -> String")); xs = native_list_append(xs, EL_STR("str_index_of")); xs = native_list_append(xs, EL_STR("fn(s: String, sub: String) -> Int")); xs = native_list_append(xs, EL_STR("float_to_str")); xs = native_list_append(xs, EL_STR("fn(f: Float) -> String")); xs = native_list_append(xs, EL_STR("str_to_float")); xs = native_list_append(xs, EL_STR("fn(s: String) -> Float")); xs = native_list_append(xs, EL_STR("math_sqrt")); xs = native_list_append(xs, EL_STR("fn(n: Float) -> Float")); xs = native_list_append(xs, EL_STR("math_log")); xs = native_list_append(xs, EL_STR("fn(n: Float) -> Float")); xs = native_list_append(xs, EL_STR("math_sin")); xs = native_list_append(xs, EL_STR("fn(n: Float) -> Float")); xs = native_list_append(xs, EL_STR("math_cos")); xs = native_list_append(xs, EL_STR("fn(n: Float) -> Float")); xs = native_list_append(xs, EL_STR("math_pi")); xs = native_list_append(xs, EL_STR("fn() -> Float")); xs = native_list_append(xs, EL_STR("el_abs")); xs = native_list_append(xs, EL_STR("fn(n: Int) -> Int")); xs = native_list_append(xs, EL_STR("el_min")); xs = native_list_append(xs, EL_STR("fn(a: Int, b: Int) -> Int")); xs = native_list_append(xs, EL_STR("el_max")); xs = native_list_append(xs, EL_STR("fn(a: Int, b: Int) -> Int")); xs = native_list_append(xs, EL_STR("el_list_empty")); xs = native_list_append(xs, EL_STR("fn() -> [Any]")); xs = native_list_append(xs, EL_STR("el_list_len")); xs = native_list_append(xs, EL_STR("fn(l: [Any]) -> Int")); xs = native_list_append(xs, EL_STR("el_list_get")); xs = native_list_append(xs, EL_STR("fn(l: [Any], i: Int) -> Any")); xs = native_list_append(xs, EL_STR("el_list_append")); xs = native_list_append(xs, EL_STR("fn(l: [Any], v: Any) -> [Any]")); xs = native_list_append(xs, EL_STR("list_join")); xs = native_list_append(xs, EL_STR("fn(l: [String], sep: String) -> String")); xs = native_list_append(xs, EL_STR("list_range")); xs = native_list_append(xs, EL_STR("fn(start: Int, end: Int) -> [Int]")); xs = native_list_append(xs, EL_STR("el_map_get")); xs = native_list_append(xs, EL_STR("fn(m: Map, k: String) -> Any")); xs = native_list_append(xs, EL_STR("el_map_set")); xs = native_list_append(xs, EL_STR("fn(m: Map, k: String, v: Any) -> Map")); xs = native_list_append(xs, EL_STR("json_parse")); xs = native_list_append(xs, EL_STR("fn(s: String) -> Any")); xs = native_list_append(xs, EL_STR("json_stringify")); xs = native_list_append(xs, EL_STR("fn(v: Any) -> String")); xs = native_list_append(xs, EL_STR("json_get_string")); xs = native_list_append(xs, EL_STR("fn(j: String, k: String) -> String")); xs = native_list_append(xs, EL_STR("json_get_int")); xs = native_list_append(xs, EL_STR("fn(j: String, k: String) -> Int")); xs = native_list_append(xs, EL_STR("json_get_bool")); xs = native_list_append(xs, EL_STR("fn(j: String, k: String) -> Bool")); xs = native_list_append(xs, EL_STR("http_get")); xs = native_list_append(xs, EL_STR("fn(url: String) -> String")); xs = native_list_append(xs, EL_STR("http_post")); xs = native_list_append(xs, EL_STR("fn(url: String, body: String) -> String")); xs = native_list_append(xs, EL_STR("fs_read")); xs = native_list_append(xs, EL_STR("fn(path: String) -> String")); xs = native_list_append(xs, EL_STR("fs_write")); xs = native_list_append(xs, EL_STR("fn(path: String, content: String) -> Bool")); xs = native_list_append(xs, EL_STR("fs_list")); xs = native_list_append(xs, EL_STR("fn(path: String) -> [String]")); xs = native_list_append(xs, EL_STR("env")); xs = native_list_append(xs, EL_STR("fn(key: String) -> String")); xs = native_list_append(xs, EL_STR("uuid_new")); xs = native_list_append(xs, EL_STR("fn() -> String")); xs = native_list_append(xs, EL_STR("time_now")); xs = native_list_append(xs, EL_STR("fn() -> Int")); xs = native_list_append(xs, EL_STR("time_now_utc")); xs = native_list_append(xs, EL_STR("fn() -> Int")); xs = native_list_append(xs, EL_STR("sleep_ms")); xs = native_list_append(xs, EL_STR("fn(ms: Int) -> Void")); xs = native_list_append(xs, EL_STR("engram_node")); xs = native_list_append(xs, EL_STR("fn(content: String, kind: String, salience: Float) -> String")); xs = native_list_append(xs, EL_STR("engram_search")); xs = native_list_append(xs, EL_STR("fn(q: String, limit: Int) -> [Map]")); xs = native_list_append(xs, EL_STR("engram_activate")); xs = native_list_append(xs, EL_STR("fn(q: String, depth: Int) -> [Map]")); xs = native_list_append(xs, EL_STR("engram_neighbors")); xs = native_list_append(xs, EL_STR("fn(id: String) -> [Map]")); xs = native_list_append(xs, EL_STR("engram_connect")); xs = native_list_append(xs, EL_STR("fn(from: String, to: String, w: Float, rel: String) -> Void")); xs = native_list_append(xs, EL_STR("dharma_connect")); xs = native_list_append(xs, EL_STR("fn(cgi: String) -> String")); xs = native_list_append(xs, EL_STR("dharma_send")); xs = native_list_append(xs, EL_STR("fn(channel: String, content: String) -> String")); xs = native_list_append(xs, EL_STR("dharma_emit")); xs = native_list_append(xs, EL_STR("fn(event: String, payload: String) -> Void")); xs = native_list_append(xs, EL_STR("dharma_field")); xs = native_list_append(xs, EL_STR("fn(event: String) -> Map")); xs = native_list_append(xs, EL_STR("llm_call")); xs = native_list_append(xs, EL_STR("fn(model: String, prompt: String) -> String")); return xs; return 0; } el_val_t keyword_doc(el_val_t kw) { if (str_eq(kw, EL_STR("let"))) { return EL_STR("let name: Type = expr — declare an immutable binding."); } if (str_eq(kw, EL_STR("fn"))) { return EL_STR("fn name(params) -> Ret { body } — define a function."); } if (str_eq(kw, EL_STR("type"))) { return EL_STR("type Name { field: Type } — struct definition."); } if (str_eq(kw, EL_STR("enum"))) { return EL_STR("enum Name { Variant1, Variant2(Type) } — enum definition."); } if (str_eq(kw, EL_STR("match"))) { return EL_STR("match expr { Pattern => body } — pattern match."); } if (str_eq(kw, EL_STR("return"))) { return EL_STR("return value — exit the enclosing function."); } if (str_eq(kw, EL_STR("if"))) { return EL_STR("if cond { ... } else { ... } — conditional."); } if (str_eq(kw, EL_STR("for"))) { return EL_STR("for item in collection { ... } — iterate."); } if (str_eq(kw, EL_STR("while"))) { return EL_STR("while cond { ... } — loop while cond is truthy."); } if (str_eq(kw, EL_STR("import"))) { return EL_STR("import \"path.el\" — pull in another module."); } if (str_eq(kw, EL_STR("from"))) { return EL_STR("from module import { Name } — selective import."); } if (str_eq(kw, EL_STR("vessel"))) { return EL_STR("vessel \"name\" { ... } — manifest declaration."); } if (str_eq(kw, EL_STR("cgi"))) { return EL_STR("cgi \"name\" { dharma_id: ... } — CGI identity."); } if (str_eq(kw, EL_STR("activate"))) { return EL_STR("activate Type where \"query\" — spreading-activation retrieval."); } if (str_eq(kw, EL_STR("sealed"))) { return EL_STR("sealed { ... } — capability-restricted block."); } if (str_eq(kw, EL_STR("protocol"))) { return EL_STR("protocol Name { fn method(self) -> R } — trait-like."); } if (str_eq(kw, EL_STR("impl"))) { return EL_STR("impl Protocol for Type { ... } — protocol implementation."); } if (str_eq(kw, EL_STR("with"))) { return EL_STR("with retry/fallback clause."); } if (str_eq(kw, EL_STR("retry"))) { return EL_STR("retry times N — retry policy."); } if (str_eq(kw, EL_STR("fallback"))) { return EL_STR("fallback { default } — fallback on failure."); } if (str_eq(kw, EL_STR("true"))) { return EL_STR("Boolean literal."); } if (str_eq(kw, EL_STR("false"))) { return EL_STR("Boolean literal."); } return EL_STR(""); return 0; } el_val_t first_ident_in(el_val_t rest) { el_val_t trimmed = str_trim(rest); el_val_t n = str_len(trimmed); if (n == 0) { return EL_STR(""); } el_val_t chars = native_string_chars(trimmed); el_val_t i = 0; el_val_t total = native_list_len(chars); while (i < total) { el_val_t ch = native_list_get(chars, i); if (!is_ident_ch(ch)) { return str_slice(trimmed, 0, i); } i = (i + 1); } return trimmed; return 0; } el_val_t scan_decls(el_val_t source) { el_val_t lines = str_split(source, EL_STR("\n")); el_val_t n = native_list_len(lines); el_val_t types = native_list_empty(); el_val_t kinds = native_list_empty(); el_val_t fns = native_list_empty(); el_val_t tlines = native_list_empty(); el_val_t flines = native_list_empty(); el_val_t i = 0; while (i < n) { el_val_t line = native_list_get(lines, i); el_val_t trimmed = str_trim(line); el_val_t lineno = (i + 1); if (str_starts_with(trimmed, EL_STR("fn "))) { el_val_t name = first_ident_in(str_slice(trimmed, 3, str_len(trimmed))); if (!str_eq(name, EL_STR(""))) { fns = native_list_append(fns, name); flines = native_list_append(flines, lineno); } } else { if (str_starts_with(trimmed, EL_STR("type "))) { el_val_t name = first_ident_in(str_slice(trimmed, 5, str_len(trimmed))); if (!str_eq(name, EL_STR(""))) { types = native_list_append(types, name); kinds = native_list_append(kinds, EL_STR("type")); tlines = native_list_append(tlines, lineno); } } else { if (str_starts_with(trimmed, EL_STR("enum "))) { el_val_t name = first_ident_in(str_slice(trimmed, 5, str_len(trimmed))); if (!str_eq(name, EL_STR(""))) { types = native_list_append(types, name); kinds = native_list_append(kinds, EL_STR("enum")); tlines = native_list_append(tlines, lineno); } } else { if (str_starts_with(trimmed, EL_STR("protocol "))) { el_val_t name = first_ident_in(str_slice(trimmed, 9, str_len(trimmed))); if (!str_eq(name, EL_STR(""))) { types = native_list_append(types, name); kinds = native_list_append(kinds, EL_STR("protocol")); tlines = native_list_append(tlines, lineno); } } } } } i = (i + 1); } return el_map_new(5, "types", types, "type_kind", kinds, "fns", fns, "type_lines", tlines, "fn_lines", flines); return 0; } el_val_t list_contains_str(el_val_t xs, el_val_t needle) { el_val_t n = native_list_len(xs); el_val_t i = 0; while (i < n) { if (str_eq(native_list_get(xs, i), needle)) { return 1; } i = (i + 1); } return 0; return 0; } el_val_t prefix_score(el_val_t label, el_val_t prefix) { if (str_eq(prefix, EL_STR(""))) { return el_from_float(0.5); } el_val_t lower_label = str_to_lower(label); el_val_t lower_prefix = str_to_lower(prefix); if (str_starts_with(lower_label, lower_prefix)) { el_val_t lp = str_len(lower_prefix); el_val_t ll = str_len(lower_label); if (ll == 0) { return el_from_float(0.0); } return (int_to_float(lp) / int_to_float(ll)); } return el_from_float(0.0); return 0; } el_val_t completion_json(el_val_t label, el_val_t kind, el_val_t detail, el_val_t doc, el_val_t score) { el_val_t parts = native_list_empty(); parts = native_list_append(parts, json_kv_str(EL_STR("label"), label)); parts = native_list_append(parts, json_kv_str(EL_STR("kind"), kind)); parts = native_list_append(parts, json_kv_str(EL_STR("detail"), detail)); if (str_eq(doc, EL_STR(""))) { parts = native_list_append(parts, json_kv_raw(EL_STR("documentation"), EL_STR("null"))); } else { parts = native_list_append(parts, json_kv_str(EL_STR("documentation"), doc)); } parts = native_list_append(parts, json_kv_float(EL_STR("score"), score)); return el_str_concat(el_str_concat(EL_STR("{"), list_join(parts, EL_STR(","))), EL_STR("}")); return 0; } el_val_t lsp_complete(el_val_t source, el_val_t pos) { el_val_t prefix = extract_prefix(source, pos); el_val_t lower_prefix = str_to_lower(prefix); el_val_t prefix_empty = str_eq(prefix, EL_STR("")); el_val_t items = native_list_empty(); el_val_t kws = keywords_list(); el_val_t nk = native_list_len(kws); el_val_t i = 0; while (i < nk) { el_val_t kw = native_list_get(kws, i); if (prefix_empty) { el_val_t s = prefix_score(kw, prefix); items = native_list_append(items, completion_json(kw, EL_STR("keyword"), EL_STR("keyword"), keyword_doc(kw), s)); } else { if (str_starts_with(kw, prefix)) { el_val_t s = prefix_score(kw, prefix); items = native_list_append(items, completion_json(kw, EL_STR("keyword"), EL_STR("keyword"), keyword_doc(kw), s)); } } i = (i + 1); } el_val_t bts = builtin_type_names(); el_val_t nb = native_list_len(bts); i = 0; while (i < nb) { el_val_t name = native_list_get(bts, i); el_val_t lname = str_to_lower(name); if (prefix_empty) { el_val_t s = (el_from_float(0.5) + el_from_float(0.1)); items = native_list_append(items, completion_json(name, EL_STR("type"), builtin_type_doc(name), el_str_concat(EL_STR("Built-in type: "), builtin_type_doc(name)), s)); } else { if (str_starts_with(lname, lower_prefix)) { el_val_t s = el_str_concat(prefix_score(name, prefix), el_from_float(0.1)); items = native_list_append(items, completion_json(name, EL_STR("type"), builtin_type_doc(name), el_str_concat(EL_STR("Built-in type: "), builtin_type_doc(name)), s)); } } i = (i + 1); } el_val_t decls = scan_decls(source); el_val_t utypes = el_get_field(decls, EL_STR("types")); el_val_t ukinds = el_get_field(decls, EL_STR("type_kind")); el_val_t nu = native_list_len(utypes); i = 0; while (i < nu) { el_val_t name = native_list_get(utypes, i); el_val_t kind = native_list_get(ukinds, i); el_val_t lname = str_to_lower(name); el_val_t include = 0; if (prefix_empty) { include = 1; } if (str_starts_with(lname, lower_prefix)) { include = 1; } if (include) { el_val_t s = el_str_concat(prefix_score(name, prefix), el_from_float(0.15)); items = native_list_append(items, completion_json(name, EL_STR("type"), el_str_concat(el_str_concat(kind, EL_STR(" ")), name), el_str_concat(el_str_concat(el_str_concat(EL_STR("User-defined "), kind), EL_STR(" ")), name), s)); } i = (i + 1); } el_val_t bfns = builtin_fns(); el_val_t nf = native_list_len(bfns); i = 0; while (i < nf) { el_val_t name = native_list_get(bfns, i); el_val_t sig = native_list_get(bfns, (i + 1)); el_val_t lname = str_to_lower(name); el_val_t include = 0; if (prefix_empty) { include = 1; } if (str_starts_with(lname, lower_prefix)) { include = 1; } if (include) { el_val_t s = el_str_concat(prefix_score(name, prefix), el_from_float(0.11)); items = native_list_append(items, completion_json(name, EL_STR("function"), sig, el_str_concat(el_str_concat(el_str_concat(EL_STR("Built-in: "), name), EL_STR(" :: ")), sig), s)); } i = (i + 2); } el_val_t ufns = el_get_field(decls, EL_STR("fns")); el_val_t nuf = native_list_len(ufns); i = 0; while (i < nuf) { el_val_t name = native_list_get(ufns, i); el_val_t lname = str_to_lower(name); el_val_t include = 0; if (prefix_empty) { include = 1; } if (str_starts_with(lname, lower_prefix)) { include = 1; } if (include) { el_val_t s = el_str_concat(prefix_score(name, prefix), el_from_float(0.12)); items = native_list_append(items, completion_json(name, EL_STR("function"), el_str_concat(EL_STR("fn "), name), el_str_concat(EL_STR("User function: "), name), s)); } i = (i + 1); } return json_array_of(items); return 0; } el_val_t lsp_hover(el_val_t source, el_val_t pos) { el_val_t token = extract_token(source, pos); if (str_eq(token, EL_STR(""))) { return EL_STR("null"); } el_val_t doc = builtin_type_doc(token); if (!str_eq(doc, EL_STR(""))) { el_val_t parts = native_list_empty(); parts = native_list_append(parts, json_kv_str(EL_STR("type_name"), token)); parts = native_list_append(parts, json_kv_str(EL_STR("documentation"), doc)); parts = native_list_append(parts, json_kv_raw(EL_STR("engram_node_type"), EL_STR("null"))); return el_str_concat(el_str_concat(EL_STR("{"), list_join(parts, EL_STR(","))), EL_STR("}")); } el_val_t decls = scan_decls(source); el_val_t utypes = el_get_field(decls, EL_STR("types")); el_val_t ukinds = el_get_field(decls, EL_STR("type_kind")); el_val_t nu = native_list_len(utypes); el_val_t i = 0; while (i < nu) { el_val_t name = native_list_get(utypes, i); if (str_eq(name, token)) { el_val_t kind = native_list_get(ukinds, i); el_val_t parts = native_list_empty(); parts = native_list_append(parts, json_kv_str(EL_STR("type_name"), token)); parts = native_list_append(parts, json_kv_str(EL_STR("documentation"), el_str_concat(el_str_concat(kind, EL_STR(" ")), token))); parts = native_list_append(parts, json_kv_raw(EL_STR("engram_node_type"), EL_STR("null"))); return el_str_concat(el_str_concat(EL_STR("{"), list_join(parts, EL_STR(","))), EL_STR("}")); } i = (i + 1); } el_val_t ufns = el_get_field(decls, EL_STR("fns")); el_val_t nuf = native_list_len(ufns); i = 0; while (i < nuf) { el_val_t name = native_list_get(ufns, i); if (str_eq(name, token)) { el_val_t parts = native_list_empty(); parts = native_list_append(parts, json_kv_str(EL_STR("type_name"), token)); parts = native_list_append(parts, json_kv_str(EL_STR("documentation"), el_str_concat(EL_STR("fn "), token))); parts = native_list_append(parts, json_kv_raw(EL_STR("engram_node_type"), EL_STR("null"))); return el_str_concat(el_str_concat(EL_STR("{"), list_join(parts, EL_STR(","))), EL_STR("}")); } i = (i + 1); } el_val_t bfns = builtin_fns(); el_val_t nf = native_list_len(bfns); i = 0; while (i < nf) { el_val_t name = native_list_get(bfns, i); if (str_eq(name, token)) { el_val_t sig = native_list_get(bfns, (i + 1)); el_val_t parts = native_list_empty(); parts = native_list_append(parts, json_kv_str(EL_STR("type_name"), token)); parts = native_list_append(parts, json_kv_str(EL_STR("documentation"), el_str_concat(el_str_concat(el_str_concat(EL_STR("fn "), token), EL_STR(" :: ")), sig))); parts = native_list_append(parts, json_kv_raw(EL_STR("engram_node_type"), EL_STR("null"))); return el_str_concat(el_str_concat(EL_STR("{"), list_join(parts, EL_STR(","))), EL_STR("}")); } i = (i + 2); } el_val_t kdoc = keyword_doc(token); if (!str_eq(kdoc, EL_STR(""))) { el_val_t parts = native_list_empty(); parts = native_list_append(parts, json_kv_str(EL_STR("type_name"), token)); parts = native_list_append(parts, json_kv_str(EL_STR("documentation"), kdoc)); parts = native_list_append(parts, json_kv_raw(EL_STR("engram_node_type"), EL_STR("null"))); return el_str_concat(el_str_concat(EL_STR("{"), list_join(parts, EL_STR(","))), EL_STR("}")); } return EL_STR("null"); return 0; } el_val_t diag_json(el_val_t message, el_val_t severity, el_val_t line, el_val_t col) { el_val_t parts = native_list_empty(); parts = native_list_append(parts, json_kv_str(EL_STR("message"), message)); parts = native_list_append(parts, json_kv_str(EL_STR("severity"), severity)); if (line > 0) { parts = native_list_append(parts, json_kv_int(EL_STR("line"), line)); parts = native_list_append(parts, json_kv_int(EL_STR("col"), col)); } else { parts = native_list_append(parts, json_kv_raw(EL_STR("line"), EL_STR("null"))); parts = native_list_append(parts, json_kv_raw(EL_STR("col"), EL_STR("null"))); } return el_str_concat(el_str_concat(EL_STR("{"), list_join(parts, EL_STR(","))), EL_STR("}")); return 0; } el_val_t lsp_diagnostics(el_val_t source) { el_val_t chars = native_string_chars(source); el_val_t n = native_list_len(chars); el_val_t diags = native_list_empty(); el_val_t depth_brace = 0; el_val_t depth_paren = 0; el_val_t depth_brack = 0; el_val_t i = 0; el_val_t line = 1; el_val_t col = 1; el_val_t in_string = 0; el_val_t string_start_line = 0; el_val_t string_start_col = 0; el_val_t in_comment = 0; while (i < n) { el_val_t ch = native_list_get(chars, i); if (in_comment) { if (str_eq(ch, EL_STR("\n"))) { in_comment = 0; line = (line + 1); col = 1; } else { col = (col + 1); } i = (i + 1); } else { if (in_string) { if (str_eq(ch, EL_STR("\\"))) { i = (i + 1); col = (col + 1); if (i < n) { el_val_t esc = native_list_get(chars, i); if (str_eq(esc, EL_STR("\n"))) { line = (line + 1); col = 1; } else { col = (col + 1); } i = (i + 1); } } else { if (str_eq(ch, EL_STR("\""))) { in_string = 0; i = (i + 1); col = (col + 1); } else { if (str_eq(ch, EL_STR("\n"))) { line = (line + 1); col = 1; i = (i + 1); } else { col = (col + 1); i = (i + 1); } } } } else { if (str_eq(ch, EL_STR("\""))) { in_string = 1; string_start_line = line; string_start_col = col; i = (i + 1); col = (col + 1); } else { if (str_eq(ch, EL_STR("/"))) { el_val_t next_i = (i + 1); el_val_t is_line_comment = 0; if (next_i < n) { el_val_t next_ch = native_list_get(chars, next_i); if (str_eq(next_ch, EL_STR("/"))) { is_line_comment = 1; } } if (is_line_comment) { in_comment = 1; i = (i + 2); col = (col + 2); } else { i = (i + 1); col = (col + 1); } } else { if (str_eq(ch, EL_STR("{"))) { depth_brace = (depth_brace + 1); i = (i + 1); col = (col + 1); } else { if (str_eq(ch, EL_STR("}"))) { depth_brace = (depth_brace - 1); if (depth_brace < 0) { diags = native_list_append(diags, diag_json(EL_STR("unmatched `}`"), EL_STR("error"), line, col)); depth_brace = 0; } i = (i + 1); col = (col + 1); } else { if (str_eq(ch, EL_STR("("))) { depth_paren = (depth_paren + 1); i = (i + 1); col = (col + 1); } else { if (str_eq(ch, EL_STR(")"))) { depth_paren = (depth_paren - 1); if (depth_paren < 0) { diags = native_list_append(diags, diag_json(EL_STR("unmatched `)`"), EL_STR("error"), line, col)); depth_paren = 0; } i = (i + 1); col = (col + 1); } else { if (str_eq(ch, EL_STR("["))) { depth_brack = (depth_brack + 1); i = (i + 1); col = (col + 1); } else { if (str_eq(ch, EL_STR("]"))) { depth_brack = (depth_brack - 1); if (depth_brack < 0) { diags = native_list_append(diags, diag_json(EL_STR("unmatched `]`"), EL_STR("error"), line, col)); depth_brack = 0; } i = (i + 1); col = (col + 1); } else { if (str_eq(ch, EL_STR("\n"))) { line = (line + 1); col = 1; i = (i + 1); } else { i = (i + 1); col = (col + 1); } } } } } } } } } } } } if (in_string) { diags = native_list_append(diags, diag_json(EL_STR("unterminated string literal"), EL_STR("error"), string_start_line, string_start_col)); } if (depth_brace > 0) { diags = native_list_append(diags, diag_json(EL_STR("unclosed `{` (missing `}`)"), EL_STR("error"), 0, 0)); } if (depth_paren > 0) { diags = native_list_append(diags, diag_json(EL_STR("unclosed `(` (missing `)`)"), EL_STR("error"), 0, 0)); } if (depth_brack > 0) { diags = native_list_append(diags, diag_json(EL_STR("unclosed `[` (missing `]`)"), EL_STR("error"), 0, 0)); } return json_array_of(diags); return 0; } el_val_t outline_item_json(el_val_t kind, el_val_t name, el_val_t line) { el_val_t parts = native_list_empty(); parts = native_list_append(parts, json_kv_str(EL_STR("kind"), kind)); parts = native_list_append(parts, json_kv_str(EL_STR("name"), name)); parts = native_list_append(parts, json_kv_int(EL_STR("line"), line)); return el_str_concat(el_str_concat(EL_STR("{"), list_join(parts, EL_STR(","))), EL_STR("}")); return 0; } el_val_t lsp_outline(el_val_t source) { el_val_t lines = str_split(source, EL_STR("\n")); el_val_t n = native_list_len(lines); el_val_t items = native_list_empty(); el_val_t i = 0; while (i < n) { el_val_t line = native_list_get(lines, i); el_val_t trimmed = str_trim(line); el_val_t lineno = (i + 1); if (str_starts_with(trimmed, EL_STR("fn "))) { el_val_t name = first_ident_in(str_slice(trimmed, 3, str_len(trimmed))); if (!str_eq(name, EL_STR(""))) { items = native_list_append(items, outline_item_json(EL_STR("fn"), name, lineno)); } } else { if (str_starts_with(trimmed, EL_STR("type "))) { el_val_t name = first_ident_in(str_slice(trimmed, 5, str_len(trimmed))); if (!str_eq(name, EL_STR(""))) { items = native_list_append(items, outline_item_json(EL_STR("type"), name, lineno)); } } else { if (str_starts_with(trimmed, EL_STR("enum "))) { el_val_t name = first_ident_in(str_slice(trimmed, 5, str_len(trimmed))); if (!str_eq(name, EL_STR(""))) { items = native_list_append(items, outline_item_json(EL_STR("enum"), name, lineno)); } } else { if (str_starts_with(trimmed, EL_STR("protocol "))) { el_val_t name = first_ident_in(str_slice(trimmed, 9, str_len(trimmed))); if (!str_eq(name, EL_STR(""))) { items = native_list_append(items, outline_item_json(EL_STR("protocol"), name, lineno)); } } else { if (str_starts_with(trimmed, EL_STR("impl "))) { el_val_t rest = str_trim(str_slice(trimmed, 5, str_len(trimmed))); el_val_t brace = str_index_of(rest, EL_STR("{")); el_val_t name = rest; if (brace > 0) { name = str_trim(str_slice(rest, 0, brace)); } if (!str_eq(name, EL_STR(""))) { items = native_list_append(items, outline_item_json(EL_STR("impl"), name, lineno)); } } } } } } i = (i + 1); } return json_array_of(items); return 0; } el_val_t format_strip_trailing(el_val_t line) { el_val_t n = str_len(line); if (n == 0) { return line; } el_val_t chars = native_string_chars(line); el_val_t i = (n - 1); el_val_t trimming = 1; while (trimming) { if (i < 0) { trimming = 0; } else { el_val_t ch = native_list_get(chars, i); if (str_eq(ch, EL_STR(" "))) { i = (i - 1); } else { if (str_eq(ch, EL_STR("\t"))) { i = (i - 1); } else { trimming = 0; } } } } return str_slice(line, 0, (i + 1)); return 0; } el_val_t lsp_format(el_val_t source) { el_val_t lines = str_split(source, EL_STR("\n")); el_val_t n = native_list_len(lines); el_val_t cleaned = native_list_empty(); el_val_t i = 0; el_val_t blank_run = 0; while (i < n) { el_val_t line = native_list_get(lines, i); el_val_t stripped = format_strip_trailing(line); if (str_eq(stripped, EL_STR(""))) { blank_run = (blank_run + 1); if (blank_run <= 2) { cleaned = native_list_append(cleaned, EL_STR("")); } } else { blank_run = 0; cleaned = native_list_append(cleaned, stripped); } i = (i + 1); } el_val_t joined = list_join(cleaned, EL_STR("\n")); el_val_t final = joined; if (!str_ends_with(final, EL_STR("\n"))) { final = el_str_concat(final, EL_STR("\n")); } el_val_t changed = EL_STR("false"); if (!str_eq(final, source)) { changed = EL_STR("true"); } return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{"), json_kv_str(EL_STR("content"), final)), EL_STR(",")), json_kv_raw(EL_STR("changed"), changed)), EL_STR("}")); return 0; } el_val_t lsp_type_graph(el_val_t source) { el_val_t nodes = native_list_empty(); el_val_t edges = native_list_empty(); el_val_t bts = builtin_type_names(); el_val_t nb = native_list_len(bts); el_val_t i = 0; while (i < nb) { el_val_t name = native_list_get(bts, i); el_val_t parts = native_list_empty(); parts = native_list_append(parts, json_kv_str(EL_STR("id"), name)); parts = native_list_append(parts, json_kv_str(EL_STR("name"), name)); parts = native_list_append(parts, json_kv_str(EL_STR("kind"), EL_STR("builtin"))); parts = native_list_append(parts, json_kv_raw(EL_STR("fields"), EL_STR("[]"))); nodes = native_list_append(nodes, el_str_concat(el_str_concat(EL_STR("{"), list_join(parts, EL_STR(","))), EL_STR("}"))); i = (i + 1); } el_val_t decls = scan_decls(source); el_val_t utypes = el_get_field(decls, EL_STR("types")); el_val_t ukinds = el_get_field(decls, EL_STR("type_kind")); el_val_t tlines = el_get_field(decls, EL_STR("type_lines")); el_val_t nu = native_list_len(utypes); i = 0; el_val_t bts_dup = builtin_type_names(); while (i < nu) { el_val_t name = native_list_get(utypes, i); el_val_t kind = native_list_get(ukinds, i); if (!list_contains_str(bts_dup, name)) { el_val_t fields = collect_struct_fields(source, name); el_val_t field_jsons = native_list_empty(); el_val_t fn2 = native_list_len(fields); el_val_t j = 0; while (j < fn2) { field_jsons = native_list_append(field_jsons, json_str(native_list_get(fields, j))); j = (j + 1); } el_val_t parts = native_list_empty(); parts = native_list_append(parts, json_kv_str(EL_STR("id"), name)); parts = native_list_append(parts, json_kv_str(EL_STR("name"), name)); parts = native_list_append(parts, json_kv_str(EL_STR("kind"), kind)); parts = native_list_append(parts, json_kv_raw(EL_STR("fields"), json_array_of(field_jsons))); nodes = native_list_append(nodes, el_str_concat(el_str_concat(EL_STR("{"), list_join(parts, EL_STR(","))), EL_STR("}"))); j = 0; while (j < fn2) { el_val_t field_str = native_list_get(fields, j); el_val_t target = referenced_type(field_str, utypes); if (!str_eq(EL_NULL, EL_STR(""))) { el_val_t eparts = native_list_empty(); eparts = native_list_append(eparts, json_kv_str(EL_STR("from"), name)); eparts = native_list_append(eparts, json_kv_str(EL_STR("to"), EL_NULL)); eparts = native_list_append(eparts, json_kv_str(EL_STR("label"), EL_STR("field"))); edges = native_list_append(edges, el_str_concat(el_str_concat(EL_STR("{"), list_join(eparts, EL_STR(","))), EL_STR("}"))); } j = (j + 1); } } i = (i + 1); } return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{"), json_kv_raw(EL_STR("nodes"), json_array_of(nodes))), EL_STR(",")), json_kv_raw(EL_STR("edges"), json_array_of(edges))), EL_STR("}")); return 0; } el_val_t collect_struct_fields(el_val_t source, el_val_t type_name) { el_val_t lines = str_split(source, EL_STR("\n")); el_val_t n = native_list_len(lines); el_val_t fields = native_list_empty(); el_val_t inside = 0; el_val_t i = 0; el_val_t header_a = el_str_concat(EL_STR("type "), type_name); el_val_t header_b = el_str_concat(EL_STR("enum "), type_name); el_val_t header_c = el_str_concat(EL_STR("protocol "), type_name); while (i < n) { el_val_t line = native_list_get(lines, i); el_val_t trimmed = str_trim(line); if (!inside) { if (str_starts_with(trimmed, header_a)) { inside = 1; } if (str_starts_with(trimmed, header_b)) { inside = 1; } if (str_starts_with(trimmed, header_c)) { inside = 1; } } else { if (str_starts_with(trimmed, EL_STR("}"))) { return fields; } if (!str_eq(trimmed, EL_STR("{"))) { if (!str_eq(trimmed, EL_STR(""))) { if (!str_starts_with(trimmed, EL_STR("//"))) { el_val_t entry = trimmed; if (str_ends_with(entry, EL_STR(","))) { entry = str_slice(entry, 0, (str_len(entry) - 1)); } fields = native_list_append(fields, str_trim(entry)); } } } } i = (i + 1); } return fields; return 0; } el_val_t referenced_type(el_val_t field_str, el_val_t known_types) { el_val_t colon = str_index_of(field_str, EL_STR(":")); if (colon < 0) { return EL_STR(""); } el_val_t after = str_trim(str_slice(field_str, (colon + 1), str_len(field_str))); el_val_t trimmed_l = after; if (str_starts_with(trimmed_l, EL_STR("["))) { el_val_t end = str_index_of(trimmed_l, EL_STR("]")); if (end > 1) { trimmed_l = str_trim(str_slice(trimmed_l, 1, end)); } } el_val_t ident = first_ident_in(trimmed_l); if (str_eq(ident, EL_STR(""))) { return EL_STR(""); } el_val_t bts = builtin_type_names(); if (list_contains_str(bts, ident)) { return ident; } if (list_contains_str(known_types, ident)) { return ident; } return EL_STR(""); return 0; } el_val_t lsp_definition(el_val_t source, el_val_t pos) { el_val_t word = extract_token(source, pos); if (str_eq(word, EL_STR(""))) { return EL_STR("null"); } el_val_t patterns = native_list_empty(); patterns = native_list_append(patterns, el_str_concat(EL_STR("fn "), word)); patterns = native_list_append(patterns, el_str_concat(EL_STR("type "), word)); patterns = native_list_append(patterns, el_str_concat(EL_STR("enum "), word)); patterns = native_list_append(patterns, el_str_concat(EL_STR("protocol "), word)); el_val_t np = native_list_len(patterns); el_val_t lines = str_split(source, EL_STR("\n")); el_val_t nl = native_list_len(lines); el_val_t i = 0; while (i < nl) { el_val_t line = native_list_get(lines, i); el_val_t j = 0; while (j < np) { el_val_t pat = native_list_get(patterns, j); el_val_t idx = str_index_of(line, pat); if (idx >= 0) { el_val_t parts = native_list_empty(); parts = native_list_append(parts, json_kv_int(EL_STR("line"), (i + 1))); parts = native_list_append(parts, json_kv_int(EL_STR("col"), (idx + 1))); parts = native_list_append(parts, json_kv_str(EL_STR("snippet"), str_trim(line))); return el_str_concat(el_str_concat(EL_STR("{"), list_join(parts, EL_STR(","))), EL_STR("}")); } j = (j + 1); } i = (i + 1); } return EL_STR("null"); return 0; } el_val_t lsp_references(el_val_t source, el_val_t pos) { el_val_t word = extract_token(source, pos); if (str_eq(word, EL_STR(""))) { return EL_STR("[]"); } el_val_t lines = str_split(source, EL_STR("\n")); el_val_t nl = native_list_len(lines); el_val_t refs = native_list_empty(); el_val_t i = 0; while (i < nl) { el_val_t line = native_list_get(lines, i); el_val_t line_chars = native_string_chars(line); el_val_t llen = native_list_len(line_chars); el_val_t wlen = str_len(word); el_val_t off = 0; el_val_t scanning = 1; while (scanning) { el_val_t idx = str_index_of(str_slice(line, off, str_len(line)), word); if (idx < 0) { scanning = 0; } else { el_val_t abs = (off + idx); el_val_t before_ok = 1; if (abs > 0) { el_val_t prev = native_list_get(line_chars, (abs - 1)); if (is_ident_ch(prev)) { before_ok = 0; } } el_val_t after_ok = 1; el_val_t after_pos = (abs + wlen); if (after_pos < llen) { el_val_t nxt = native_list_get(line_chars, after_pos); if (is_ident_ch(nxt)) { after_ok = 0; } } if (before_ok) { if (after_ok) { el_val_t parts = native_list_empty(); parts = native_list_append(parts, json_kv_int(EL_STR("line"), (i + 1))); parts = native_list_append(parts, json_kv_int(EL_STR("col"), (abs + 1))); parts = native_list_append(parts, json_kv_str(EL_STR("snippet"), str_trim(line))); refs = native_list_append(refs, el_str_concat(el_str_concat(EL_STR("{"), list_join(parts, EL_STR(","))), EL_STR("}"))); } } off = (abs + wlen); if (off >= str_len(line)) { scanning = 0; } } } i = (i + 1); } return json_array_of(refs); return 0; } el_val_t lsp_jsonrpc(el_val_t method, el_val_t params_json) { el_val_t source = json_get_string(params_json, EL_STR("source")); el_val_t pos = json_get_int(params_json, EL_STR("pos")); if (str_eq(method, EL_STR("textDocument/completion"))) { return lsp_complete(source, pos); } if (str_eq(method, EL_STR("textDocument/hover"))) { return lsp_hover(source, pos); } if (str_eq(method, EL_STR("textDocument/diagnostic"))) { return lsp_diagnostics(source); } if (str_eq(method, EL_STR("textDocument/formatting"))) { return lsp_format(source); } if (str_eq(method, EL_STR("textDocument/documentSymbol"))) { return lsp_outline(source); } if (str_eq(method, EL_STR("textDocument/definition"))) { return lsp_definition(source, pos); } if (str_eq(method, EL_STR("textDocument/references"))) { return lsp_references(source, pos); } if (str_eq(method, EL_STR("el/typeGraph"))) { return lsp_type_graph(source); } return el_str_concat(el_str_concat(EL_STR("{\"error\":\"unknown method: "), json_escape(method)), EL_STR("\"}")); return 0; } el_val_t read_headers(void) { el_val_t content_len = 0; el_val_t scanning = 1; while (scanning) { el_val_t line = readline(); if (str_eq(line, EL_STR(""))) { scanning = 0; } else { el_val_t cl_marker = EL_STR("Content-Length:"); if (str_starts_with(line, cl_marker)) { el_val_t val = str_trim(str_slice(line, str_len(cl_marker), str_len(line))); content_len = str_to_int(val); } } } return el_map_new(1, "content_length", content_len); return 0; } el_val_t lsp_stdio_loop(void) { el_val_t running = 1; while (running) { el_val_t headers = read_headers(); el_val_t body = readline(); if (str_eq(body, EL_STR(""))) { running = 0; } else { el_val_t method = json_get_string(body, EL_STR("method")); el_val_t id_val = json_get_raw(body, EL_STR("id")); if (str_eq(id_val, EL_STR(""))) { id_val = EL_STR("null"); } el_val_t params_raw = json_get_raw(body, EL_STR("params")); el_val_t result = lsp_jsonrpc(method, params_raw); el_val_t resp = el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"jsonrpc\":\"2.0\",\"id\":"), id_val), EL_STR(",\"result\":")), result), EL_STR("}")); el_val_t frame = el_str_concat(el_str_concat(el_str_concat(EL_STR("Content-Length: "), int_to_str(str_len(resp))), EL_STR("\r\n\r\n")), resp); print(frame); } } return 0; } el_val_t lsp_main(void) { el_val_t argv = args(); el_val_t n = native_list_len(argv); if (n > 0) { el_val_t cmd = native_list_get(argv, 0); if (str_eq(cmd, EL_STR("version"))) { println(EL_STR("el-lsp 0.1.0")); return 0; } if (str_eq(cmd, EL_STR("probe"))) { el_val_t sample = EL_STR("fn main() -> Void { let x: Int = 42 }"); println(lsp_diagnostics(sample)); return 0; } } lsp_stdio_loop(); return 0; } el_val_t pkey(el_val_t name, el_val_t field) { return el_str_concat(el_str_concat(el_str_concat(EL_STR("plugin/"), name), EL_STR("/")), field); return 0; } el_val_t put_str(el_val_t name, el_val_t field, el_val_t value) { state_set(pkey(name, field), value); return 0; } el_val_t put_bool(el_val_t name, el_val_t field, el_val_t value) { if (value) { state_set(pkey(name, field), EL_STR("true")); } if (!value) { state_set(pkey(name, field), EL_STR("false")); } return 0; } el_val_t get_str(el_val_t name, el_val_t field) { return state_get(pkey(name, field)); return 0; } el_val_t get_bool(el_val_t name, el_val_t field) { return str_eq(state_get(pkey(name, field)), EL_STR("true")); return 0; } el_val_t index_list(void) { el_val_t raw = state_get(EL_STR("plugins/index")); if (str_eq(raw, EL_STR(""))) { return native_list_empty(); } return str_split(raw, EL_STR(",")); return 0; } el_val_t index_set(el_val_t names) { state_set(EL_STR("plugins/index"), list_join(names, EL_STR(","))); return 0; } el_val_t index_contains(el_val_t name) { el_val_t names = index_list(); el_val_t n = native_list_len(names); el_val_t i = 0; while (i < n) { if (str_eq(native_list_get(names, i), name)) { return 1; } i = (i + 1); } return 0; return 0; } el_val_t index_add(el_val_t name) { if (!index_contains(name)) { el_val_t names = index_list(); names = native_list_append(names, name); index_set(names); } return 0; } el_val_t register_plugin(el_val_t name, el_val_t version, el_val_t description, el_val_t installed, el_val_t enabled, el_val_t hooks, el_val_t first_party) { put_str(name, EL_STR("name"), name); put_str(name, EL_STR("version"), version); put_str(name, EL_STR("description"), description); put_bool(name, EL_STR("installed"), installed); put_bool(name, EL_STR("enabled"), enabled); put_str(name, EL_STR("hooks"), hooks); put_bool(name, EL_STR("first_party"), first_party); index_add(name); return 0; } el_val_t plugin_init(void) { if (str_eq(state_get(EL_STR("plugins/initialised")), EL_STR("true"))) { return 0; } register_plugin(EL_STR("el-theme-dark"), EL_STR("1.0.0"), EL_STR("Dark theme — the default Engram IDE theme."), 1, 1, EL_STR(""), 1); register_plugin(EL_STR("el-theme-light"), EL_STR("1.0.0"), EL_STR("Light theme for high-ambient-light environments."), 0, 0, EL_STR(""), 1); register_plugin(EL_STR("el-fmt"), EL_STR("0.1.0"), EL_STR("Code formatter — auto-formats .el files on save."), 1, 1, EL_STR("on_save"), 1); register_plugin(EL_STR("el-doc"), EL_STR("0.1.0"), EL_STR("Documentation generator — produces HTML docs from type definitions."), 0, 0, EL_STR("custom_tool"), 1); register_plugin(EL_STR("el-test"), EL_STR("0.1.0"), EL_STR("Test runner with inline results displayed in the editor gutter."), 0, 0, EL_STR("on_build_complete,custom_panel"), 1); state_set(EL_STR("plugins/initialised"), EL_STR("true")); return 0; } el_val_t json_escape_str(el_val_t s) { el_val_t chars = native_string_chars(s); el_val_t n = native_list_len(chars); el_val_t out = EL_STR(""); el_val_t i = 0; while (i < n) { el_val_t ch = native_list_get(chars, i); if (str_eq(ch, EL_STR("\""))) { out = el_str_concat(out, EL_STR("\\\"")); } else { if (str_eq(ch, EL_STR("\\"))) { out = el_str_concat(out, EL_STR("\\\\")); } else { if (str_eq(ch, EL_STR("\n"))) { out = el_str_concat(out, EL_STR("\\n")); } else { out = el_str_concat(out, ch); } } } i = (i + 1); } return out; return 0; } el_val_t quote_json(el_val_t s) { return el_str_concat(el_str_concat(EL_STR("\""), json_escape_str(s)), EL_STR("\"")); return 0; } el_val_t hooks_to_json(el_val_t hooks_csv) { if (str_eq(hooks_csv, EL_STR(""))) { return EL_STR("[]"); } el_val_t xs = str_split(hooks_csv, EL_STR(",")); el_val_t n = native_list_len(xs); el_val_t out = EL_STR("["); el_val_t i = 0; while (i < n) { if (i > 0) { out = el_str_concat(out, EL_STR(",")); } out = el_str_concat(out, quote_json(native_list_get(xs, i))); i = (i + 1); } return el_str_concat(out, EL_STR("]")); return 0; } el_val_t plugin_to_json(el_val_t name) { el_val_t parts = native_list_empty(); parts = native_list_append(parts, el_str_concat(EL_STR("\"name\":"), quote_json(get_str(name, EL_STR("name"))))); parts = native_list_append(parts, el_str_concat(EL_STR("\"version\":"), quote_json(get_str(name, EL_STR("version"))))); parts = native_list_append(parts, el_str_concat(EL_STR("\"description\":"), quote_json(get_str(name, EL_STR("description"))))); el_val_t installed = get_str(name, EL_STR("installed")); el_val_t enabled = get_str(name, EL_STR("enabled")); el_val_t first_party = get_str(name, EL_STR("first_party")); parts = native_list_append(parts, el_str_concat(EL_STR("\"installed\":"), installed)); parts = native_list_append(parts, el_str_concat(EL_STR("\"enabled\":"), enabled)); parts = native_list_append(parts, el_str_concat(EL_STR("\"hooks\":"), hooks_to_json(get_str(name, EL_STR("hooks"))))); parts = native_list_append(parts, el_str_concat(EL_STR("\"first_party\":"), first_party)); return el_str_concat(el_str_concat(EL_STR("{"), list_join(parts, EL_STR(","))), EL_STR("}")); return 0; } el_val_t plugin_list_json(void) { plugin_init(); el_val_t names = index_list(); el_val_t n = native_list_len(names); el_val_t out = EL_STR("["); el_val_t i = 0; while (i < n) { if (i > 0) { out = el_str_concat(out, EL_STR(",")); } out = el_str_concat(out, plugin_to_json(native_list_get(names, i))); i = (i + 1); } return el_str_concat(out, EL_STR("]")); return 0; } el_val_t plugin_get_json(el_val_t name) { plugin_init(); if (!index_contains(name)) { return EL_STR("null"); } return plugin_to_json(name); return 0; } el_val_t plugin_install(el_val_t name) { plugin_init(); if (!index_contains(name)) { return EL_STR("{\"error\":\"plugin registry is not available (registry URL not configured)\"}"); } if (get_bool(name, EL_STR("installed"))) { return el_str_concat(el_str_concat(EL_STR("{\"error\":\"plugin '"), json_escape_str(name)), EL_STR("' is already installed\"}")); } put_bool(name, EL_STR("installed"), 1); put_bool(name, EL_STR("enabled"), 1); return plugin_to_json(name); return 0; } el_val_t plugin_remove(el_val_t name) { plugin_init(); if (!index_contains(name)) { return el_str_concat(el_str_concat(EL_STR("{\"error\":\"plugin '"), json_escape_str(name)), EL_STR("' not found\"}")); } if (str_eq(name, EL_STR("el-theme-dark"))) { return el_str_concat(el_str_concat(EL_STR("{\"error\":\"plugin '"), json_escape_str(name)), EL_STR("' cannot be removed: it is a required built-in\"}")); } put_bool(name, EL_STR("installed"), 0); put_bool(name, EL_STR("enabled"), 0); return EL_STR("{\"ok\":true}"); return 0; } el_val_t plugin_enable(el_val_t name) { plugin_init(); if (!index_contains(name)) { return el_str_concat(el_str_concat(EL_STR("{\"error\":\"plugin '"), json_escape_str(name)), EL_STR("' not found\"}")); } put_bool(name, EL_STR("enabled"), 1); return EL_STR("{\"ok\":true}"); return 0; } el_val_t plugin_disable(el_val_t name) { plugin_init(); if (!index_contains(name)) { return el_str_concat(el_str_concat(EL_STR("{\"error\":\"plugin '"), json_escape_str(name)), EL_STR("' not found\"}")); } put_bool(name, EL_STR("enabled"), 0); return EL_STR("{\"ok\":true}"); return 0; } el_val_t host_main(void) { el_val_t argv = args(); el_val_t n = native_list_len(argv); if (n == 0) { println(plugin_list_json()); return 0; } el_val_t cmd = native_list_get(argv, 0); if (str_eq(cmd, EL_STR("list"))) { println(plugin_list_json()); return 0; } if (str_eq(cmd, EL_STR("get"))) { if (n >= 2) { println(plugin_get_json(native_list_get(argv, 1))); } return 0; } if (str_eq(cmd, EL_STR("install"))) { if (n >= 2) { println(plugin_install(native_list_get(argv, 1))); } return 0; } if (str_eq(cmd, EL_STR("remove"))) { if (n >= 2) { println(plugin_remove(native_list_get(argv, 1))); } return 0; } if (str_eq(cmd, EL_STR("enable"))) { if (n >= 2) { println(plugin_enable(native_list_get(argv, 1))); } return 0; } if (str_eq(cmd, EL_STR("disable"))) { if (n >= 2) { println(plugin_disable(native_list_get(argv, 1))); } return 0; } println(EL_STR("usage: el-plugin-host [list|get|install|remove|enable|disable] ")); return 0; } el_val_t jstr(el_val_t s) { el_val_t chars = native_string_chars(s); el_val_t n = native_list_len(chars); el_val_t out = EL_STR("\""); el_val_t i = 0; while (i < n) { el_val_t ch = native_list_get(chars, i); if (str_eq(ch, EL_STR("\""))) { out = el_str_concat(out, EL_STR("\\\"")); } else { if (str_eq(ch, EL_STR("\\"))) { out = el_str_concat(out, EL_STR("\\\\")); } else { if (str_eq(ch, EL_STR("\n"))) { out = el_str_concat(out, EL_STR("\\n")); } else { if (str_eq(ch, EL_STR("\r"))) { out = el_str_concat(out, EL_STR("\\r")); } else { if (str_eq(ch, EL_STR("\t"))) { out = el_str_concat(out, EL_STR("\\t")); } else { out = el_str_concat(out, ch); } } } } } i = (i + 1); } return el_str_concat(out, EL_STR("\"")); return 0; } el_val_t jerr(el_val_t msg) { return el_str_concat(el_str_concat(EL_STR("{\"error\":"), jstr(msg)), EL_STR("}")); return 0; } el_val_t ok_obj(void) { return EL_STR("{\"ok\":true}"); return 0; } el_val_t url_decode(el_val_t s) { el_val_t chars = native_string_chars(s); el_val_t n = native_list_len(chars); el_val_t out = EL_STR(""); el_val_t i = 0; while (i < n) { el_val_t ch = native_list_get(chars, i); if (str_eq(ch, EL_STR("+"))) { out = el_str_concat(out, EL_STR(" ")); i = (i + 1); } else { if (str_eq(ch, EL_STR("%"))) { if ((i + 2) < n) { el_val_t hex = el_str_concat(native_list_get(chars, (i + 1)), native_list_get(chars, (i + 2))); el_val_t code = hex_to_int(hex); out = el_str_concat(out, char_from_code(code)); i = (i + 3); } else { out = el_str_concat(out, ch); i = (i + 1); } } else { out = el_str_concat(out, ch); i = (i + 1); } } } return out; return 0; } el_val_t hex_digit(el_val_t ch) { if (str_eq(ch, EL_STR("0"))) { return 0; } if (str_eq(ch, EL_STR("1"))) { return 1; } if (str_eq(ch, EL_STR("2"))) { return 2; } if (str_eq(ch, EL_STR("3"))) { return 3; } if (str_eq(ch, EL_STR("4"))) { return 4; } if (str_eq(ch, EL_STR("5"))) { return 5; } if (str_eq(ch, EL_STR("6"))) { return 6; } if (str_eq(ch, EL_STR("7"))) { return 7; } if (str_eq(ch, EL_STR("8"))) { return 8; } if (str_eq(ch, EL_STR("9"))) { return 9; } if (str_eq(ch, EL_STR("a"))) { return 10; } if (str_eq(ch, EL_STR("A"))) { return 10; } if (str_eq(ch, EL_STR("b"))) { return 11; } if (str_eq(ch, EL_STR("B"))) { return 11; } if (str_eq(ch, EL_STR("c"))) { return 12; } if (str_eq(ch, EL_STR("C"))) { return 12; } if (str_eq(ch, EL_STR("d"))) { return 13; } if (str_eq(ch, EL_STR("D"))) { return 13; } if (str_eq(ch, EL_STR("e"))) { return 14; } if (str_eq(ch, EL_STR("E"))) { return 14; } if (str_eq(ch, EL_STR("f"))) { return 15; } return (/* if-expr */ ((str_eq(ch, EL_STR("F"))) ? (el_val_t)1 : (el_val_t)0) - 1); return 0; } el_val_t hex_to_int(el_val_t hex) { el_val_t chars = native_string_chars(hex); el_val_t n = native_list_len(chars); el_val_t acc = 0; el_val_t i = 0; while (i < n) { el_val_t d = hex_digit(native_list_get(chars, i)); if (d < 0) { return acc; } acc = el_str_concat((acc * 16), d); i = (i + 1); } return acc; return 0; } el_val_t char_from_code(el_val_t code) { el_val_t table = EL_STR(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"); if (code < 32) { return EL_STR(" "); } if (code > 126) { return EL_STR("?"); } return str_slice(table, (code - 32), (code - 31)); return 0; } el_val_t split_path_query(el_val_t path) { el_val_t q = str_index_of(path, EL_STR("?")); if (q < 0) { return el_map_new(2, "path", path, "query", EL_STR("")); } return el_map_new(2, "path", str_slice(path, 0, q), "query", str_slice(path, (q + 1), str_len(path))); return 0; } el_val_t query_get(el_val_t query, el_val_t key) { if (str_eq(query, EL_STR(""))) { return EL_STR(""); } el_val_t pairs = str_split(query, EL_STR("&")); el_val_t n = native_list_len(pairs); el_val_t prefix = el_str_concat(key, EL_STR("=")); el_val_t i = 0; while (i < n) { el_val_t pair = native_list_get(pairs, i); if (str_starts_with(pair, prefix)) { return url_decode(str_slice(pair, str_len(prefix), str_len(pair))); } if (str_eq(pair, key)) { return EL_STR(""); } i = (i + 1); } return EL_STR(""); return 0; } el_val_t cfg_port(void) { el_val_t p = env(EL_STR("EL_IDE_PORT")); if (str_eq(p, EL_STR(""))) { return 7771; } return str_to_int(p); return 0; } el_val_t cfg_project_path(void) { el_val_t p = env(EL_STR("EL_IDE_PROJECT_PATH")); if (str_eq(p, EL_STR(""))) { return EL_STR("."); } return p; return 0; } el_val_t cfg_engram_url(void) { el_val_t u = env(EL_STR("EL_ENGRAM_URL")); if (str_eq(u, EL_STR(""))) { return EL_STR("http://localhost:8742"); } return u; return 0; } el_val_t cfg_el_binary(void) { el_val_t b = env(EL_STR("EL_BINARY")); if (str_eq(b, EL_STR(""))) { return EL_STR("el"); } return b; return 0; } el_val_t safe_join(el_val_t rel) { el_val_t root = cfg_project_path(); if (str_contains(rel, EL_STR(".."))) { return EL_STR(""); } if (str_starts_with(rel, EL_STR("/"))) { return EL_STR(""); } if (str_eq(rel, EL_STR("."))) { return root; } return el_str_concat(el_str_concat(root, EL_STR("/")), rel); return 0; } el_val_t route_status(void) { el_val_t parts = native_list_empty(); parts = native_list_append(parts, el_str_concat(EL_STR("\"project_name\":"), jstr(cfg_project_path()))); parts = native_list_append(parts, el_str_concat(EL_STR("\"project_path\":"), jstr(cfg_project_path()))); parts = native_list_append(parts, el_str_concat(EL_STR("\"version\":"), jstr(EL_STR("0.1.0")))); parts = native_list_append(parts, el_str_concat(EL_STR("\"engine\":"), jstr(EL_STR("el-ide")))); return el_str_concat(el_str_concat(EL_STR("{"), list_join(parts, EL_STR(","))), EL_STR("}")); return 0; } el_val_t route_config(void) { el_val_t parts = native_list_empty(); parts = native_list_append(parts, el_str_concat(EL_STR("\"port\":"), int_to_str(cfg_port()))); parts = native_list_append(parts, el_str_concat(EL_STR("\"project_path\":"), jstr(cfg_project_path()))); parts = native_list_append(parts, el_str_concat(EL_STR("\"engram_url\":"), jstr(cfg_engram_url()))); parts = native_list_append(parts, el_str_concat(EL_STR("\"el_binary\":"), jstr(cfg_el_binary()))); return el_str_concat(el_str_concat(EL_STR("{"), list_join(parts, EL_STR(","))), EL_STR("}")); return 0; } el_val_t route_files_list(el_val_t query) { el_val_t rel = query_get(query, EL_STR("path")); if (str_eq(rel, EL_STR(""))) { rel = EL_STR("."); } el_val_t target = safe_join(rel); if (str_eq(EL_NULL, EL_STR(""))) { return jerr(EL_STR("path escapes project root")); } el_val_t entries = fs_list(EL_NULL); el_val_t n = native_list_len(entries); el_val_t out = EL_STR("["); el_val_t i = 0; while (i < n) { el_val_t name = native_list_get(entries, i); el_val_t skip = 0; if (str_starts_with(name, EL_STR("."))) { skip = 1; } if (str_eq(name, EL_STR("target"))) { skip = 1; } if (str_eq(name, EL_STR("node_modules"))) { skip = 1; } if (str_eq(name, EL_STR("dist"))) { skip = 1; } if (!skip) { if (i > 0) { if (!str_eq(out, EL_STR("["))) { out = el_str_concat(out, EL_STR(",")); } } el_val_t dot = str_index_of(name, EL_STR(".")); el_val_t is_dir = EL_STR("false"); if (dot < 0) { is_dir = EL_STR("true"); } out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(out, EL_STR("{\"name\":")), jstr(name)), EL_STR(",\"path\":")), jstr(el_str_concat(el_str_concat(rel, EL_STR("/")), name))), EL_STR(",\"is_dir\":")), is_dir), EL_STR(",\"children\":null}")); } i = (i + 1); } return el_str_concat(out, EL_STR("]")); return 0; } el_val_t route_file_read(el_val_t query) { el_val_t rel = query_get(query, EL_STR("path")); if (str_eq(rel, EL_STR(""))) { return jerr(EL_STR("missing path parameter")); } el_val_t target = safe_join(rel); if (str_eq(EL_NULL, EL_STR(""))) { return jerr(EL_STR("path escapes project root")); } el_val_t content = fs_read(EL_NULL); return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"path\":"), jstr(rel)), EL_STR(",\"content\":")), jstr(content)), EL_STR("}")); return 0; } el_val_t route_file_write(el_val_t body) { el_val_t rel = json_get_string(body, EL_STR("path")); el_val_t content = json_get_string(body, EL_STR("content")); if (str_eq(rel, EL_STR(""))) { return jerr(EL_STR("missing path")); } el_val_t target = safe_join(rel); if (str_eq(EL_NULL, EL_STR(""))) { return jerr(EL_STR("path escapes project root")); } el_val_t ok = fs_write(EL_NULL, content); if (ok) { return ok_obj(); } return jerr(EL_STR("cannot write file")); return 0; } el_val_t route_lsp_complete(el_val_t query) { el_val_t source = query_get(query, EL_STR("source")); el_val_t pos = str_to_int(query_get(query, EL_STR("pos"))); return lsp_complete(source, pos); return 0; } el_val_t route_lsp_hover(el_val_t query) { el_val_t source = query_get(query, EL_STR("source")); el_val_t pos = str_to_int(query_get(query, EL_STR("pos"))); return lsp_hover(source, pos); return 0; } el_val_t route_lsp_errors(el_val_t query) { el_val_t source = query_get(query, EL_STR("source")); return lsp_diagnostics(source); return 0; } el_val_t route_lsp_jsonrpc(el_val_t body) { el_val_t method = json_get_string(body, EL_STR("method")); el_val_t params_raw = json_get_raw(body, EL_STR("params")); el_val_t id_val = json_get_raw(body, EL_STR("id")); if (str_eq(id_val, EL_STR(""))) { id_val = EL_STR("null"); } el_val_t result = lsp_jsonrpc(method, params_raw); return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"jsonrpc\":\"2.0\",\"id\":"), id_val), EL_STR(",\"result\":")), result), EL_STR("}")); return 0; } el_val_t route_outline(el_val_t query) { el_val_t source = query_get(query, EL_STR("source")); return lsp_outline(source); return 0; } el_val_t route_definition(el_val_t query) { el_val_t source = query_get(query, EL_STR("source")); el_val_t pos = str_to_int(query_get(query, EL_STR("pos"))); return lsp_definition(source, pos); return 0; } el_val_t route_references(el_val_t query) { el_val_t source = query_get(query, EL_STR("source")); el_val_t pos = str_to_int(query_get(query, EL_STR("pos"))); return lsp_references(source, pos); return 0; } el_val_t route_format(el_val_t body) { el_val_t content = json_get_string(body, EL_STR("content")); return lsp_format(content); return 0; } el_val_t route_type_graph(el_val_t query) { el_val_t source = query_get(query, EL_STR("source")); return lsp_type_graph(source); return 0; } el_val_t active_theme(void) { el_val_t t = state_get(EL_STR("ide/active_theme")); if (str_eq(t, EL_STR(""))) { return EL_STR("dark"); } return t; return 0; } el_val_t route_themes_list(void) { el_val_t active = active_theme(); el_val_t names = native_list_empty(); names = native_list_append(names, EL_STR("dark")); names = native_list_append(names, EL_STR("light")); names = native_list_append(names, EL_STR("neuron")); names = native_list_append(names, EL_STR("high-contrast")); el_val_t labels = native_list_empty(); labels = native_list_append(labels, EL_STR("Dark")); labels = native_list_append(labels, EL_STR("Light")); labels = native_list_append(labels, EL_STR("Neuron")); labels = native_list_append(labels, EL_STR("High Contrast")); el_val_t n = native_list_len(names); el_val_t out = EL_STR("["); el_val_t i = 0; while (i < n) { if (i > 0) { out = el_str_concat(out, EL_STR(",")); } el_val_t nm = native_list_get(names, i); el_val_t lbl = native_list_get(labels, i); el_val_t act = EL_STR("false"); if (str_eq(nm, active)) { act = EL_STR("true"); } out = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(out, EL_STR("{\"name\":")), jstr(nm)), EL_STR(",\"label\":")), jstr(lbl)), EL_STR(",\"active\":")), act), EL_STR("}")); i = (i + 1); } return el_str_concat(out, EL_STR("]")); return 0; } el_val_t route_themes_set(el_val_t body) { el_val_t name = json_get_string(body, EL_STR("name")); el_val_t valid = 0; if (str_eq(name, EL_STR("dark"))) { valid = 1; } if (str_eq(name, EL_STR("light"))) { valid = 1; } if (str_eq(name, EL_STR("neuron"))) { valid = 1; } if (str_eq(name, EL_STR("high-contrast"))) { valid = 1; } if (!valid) { return jerr(el_str_concat(EL_STR("unknown theme: "), name)); } state_set(EL_STR("ide/active_theme"), name); return el_str_concat(el_str_concat(EL_STR("{\"ok\":true,\"active\":"), jstr(name)), EL_STR("}")); return 0; } el_val_t route_plugins_list(void) { return plugin_list_json(); return 0; } el_val_t route_plugins_install(el_val_t body) { return plugin_install(json_get_string(body, EL_STR("name"))); return 0; } el_val_t route_plugins_remove(el_val_t body) { return plugin_remove(json_get_string(body, EL_STR("name"))); return 0; } el_val_t route_plugins_enable(el_val_t body) { return plugin_enable(json_get_string(body, EL_STR("name"))); return 0; } el_val_t route_plugins_disable(el_val_t body) { return plugin_disable(json_get_string(body, EL_STR("name"))); return 0; } el_val_t route_snippets(void) { el_val_t snips = native_list_empty(); snips = native_list_append(snips, EL_STR("{\"label\":\"fn\",\"description\":\"Function definition\",\"body\":\"fn ${1:name}(${2:params}) -> ${3:Ret} {\\n\\t${4:body}\\n}\"}")); snips = native_list_append(snips, EL_STR("{\"label\":\"type\",\"description\":\"Struct\",\"body\":\"type ${1:Name} {\\n\\t${2:field}: ${3:Type},\\n}\"}")); snips = native_list_append(snips, EL_STR("{\"label\":\"enum\",\"description\":\"Enum\",\"body\":\"enum ${1:Name} {\\n\\t${2:Variant},\\n}\"}")); snips = native_list_append(snips, EL_STR("{\"label\":\"match\",\"description\":\"Match expression\",\"body\":\"match ${1:expr} {\\n\\t${2:pat} => ${3:body},\\n\\t_ => ${4:default},\\n}\"}")); snips = native_list_append(snips, EL_STR("{\"label\":\"let\",\"description\":\"Let binding\",\"body\":\"let ${1:name}: ${2:Type} = ${3:value}\"}")); snips = native_list_append(snips, EL_STR("{\"label\":\"if\",\"description\":\"If expression\",\"body\":\"if ${1:cond} {\\n\\t${2:body}\\n}\"}")); snips = native_list_append(snips, EL_STR("{\"label\":\"for\",\"description\":\"For loop\",\"body\":\"for ${1:item} in ${2:iter} {\\n\\t${3:body}\\n}\"}")); snips = native_list_append(snips, EL_STR("{\"label\":\"activate\",\"description\":\"Activate\",\"body\":\"activate ${1:Type}\"}")); return el_str_concat(el_str_concat(EL_STR("["), list_join(snips, EL_STR(","))), EL_STR("]")); return 0; } el_val_t settings_path(void) { el_val_t home = env(EL_STR("HOME")); if (str_eq(home, EL_STR(""))) { home = EL_STR("/tmp"); } return el_str_concat(home, EL_STR("/.el-ide/settings.json")); return 0; } el_val_t default_settings_json(void) { return EL_STR("{\"theme\":\"dark\",\"fontSize\":13,\"tabSize\":4,\"wordWrap\":false,\"formatOnSave\":false,\"vimMode\":false,\"minimap\":true,\"elBinaryPath\":\"el\",\"engramUrl\":\"http://localhost:8742\",\"stickyScroll\":true,\"lineNumbers\":true,\"bracketMatching\":true}"); return 0; } el_val_t route_settings_get(void) { el_val_t s = fs_read(settings_path()); if (str_eq(s, EL_STR(""))) { return default_settings_json(); } return s; return 0; } el_val_t route_settings_save(el_val_t body) { el_val_t blob = json_get_raw(body, EL_STR("settings")); if (str_eq(blob, EL_STR(""))) { return jerr(EL_STR("missing settings")); } fs_write(settings_path(), blob); return blob; return 0; } el_val_t route_settings_reset(void) { fs_write(settings_path(), default_settings_json()); return default_settings_json(); return 0; } el_val_t route_build_or_run(el_val_t action, el_val_t body) { el_val_t rel_file = json_get_string(body, EL_STR("file")); if (str_eq(rel_file, EL_STR(""))) { rel_file = EL_STR("src/main.el"); } el_val_t target = safe_join(rel_file); if (str_eq(EL_NULL, EL_STR(""))) { return jerr(EL_STR("path escapes project root")); } return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"event\":\"info\",\"action\":"), jstr(action)), EL_STR(",\"file\":")), jstr(rel_file)), EL_STR(",\"note\":\"el-ide-server: synchronous build requires a runtime exec primitive — el binary not invoked\"}")); return 0; } el_val_t route_build(el_val_t body) { return route_build_or_run(EL_STR("build"), body); return 0; } el_val_t route_run(el_val_t body) { return route_build_or_run(EL_STR("run"), body); return 0; } el_val_t route_reason(el_val_t body) { el_val_t url = el_str_concat(cfg_engram_url(), EL_STR("/api/reason")); el_val_t resp = http_post(url, body); if (str_eq(resp, EL_STR(""))) { return jerr(EL_STR("engram unreachable")); } return resp; return 0; } el_val_t route_activate_preview(el_val_t query) { el_val_t type_name = query_get(query, EL_STR("type_name")); el_val_t q = query_get(query, EL_STR("query")); el_val_t url = el_str_concat(cfg_engram_url(), EL_STR("/api/activate-preview")); el_val_t body = el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"type_name\":"), jstr(type_name)), EL_STR(",\"query\":")), jstr(q)), EL_STR(",\"limit\":5}")); el_val_t resp = http_post(url, body); if (str_eq(resp, EL_STR(""))) { return EL_STR("{\"count\":0,\"nodes\":[],\"connected\":false}"); } return resp; return 0; } el_val_t route_index(void) { el_val_t path = el_str_concat(cfg_project_path(), EL_STR("/ide/index.html")); el_val_t html = fs_read(path); if (!str_eq(html, EL_STR(""))) { return html; } return el_str_concat(el_str_concat(EL_STR("el-ide

el-ide

Backend running. UI bundle missing at "), path), EL_STR(".

")); return 0; } el_val_t route_health(void) { return EL_STR("{\"status\":\"ok\",\"engine\":\"el-ide-server\"}"); return 0; } el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) { el_val_t parsed = split_path_query(path); el_val_t route = el_get_field(parsed, EL_STR("path")); el_val_t query = el_get_field(parsed, EL_STR("query")); if (str_eq(route, EL_STR("/"))) { return route_index(); } if (str_eq(route, EL_STR("/health"))) { return route_health(); } if (str_eq(route, EL_STR("/api/status"))) { return route_status(); } if (str_eq(route, EL_STR("/api/config"))) { return route_config(); } if (str_eq(route, EL_STR("/api/files"))) { return route_files_list(query); } if (str_eq(route, EL_STR("/api/file"))) { if (str_eq(method, EL_STR("GET"))) { return route_file_read(query); } if (str_eq(method, EL_STR("POST"))) { return route_file_write(body); } } if (str_eq(route, EL_STR("/api/search"))) { el_val_t q = query_get(query, EL_STR("q")); return el_str_concat(el_str_concat(EL_STR("{\"query\":"), jstr(q)), EL_STR(",\"results\":[]}")); } if (str_eq(route, EL_STR("/api/lsp/complete"))) { return route_lsp_complete(query); } if (str_eq(route, EL_STR("/api/lsp/hover"))) { return route_lsp_hover(query); } if (str_eq(route, EL_STR("/api/lsp/errors"))) { return route_lsp_errors(query); } if (str_eq(route, EL_STR("/api/lsp/activate-preview"))) { return route_activate_preview(query); } if (str_eq(route, EL_STR("/api/lsp/jsonrpc"))) { return route_lsp_jsonrpc(body); } if (str_eq(route, EL_STR("/api/type-graph"))) { return route_type_graph(query); } if (str_eq(route, EL_STR("/api/outline"))) { return route_outline(query); } if (str_eq(route, EL_STR("/api/definition"))) { return route_definition(query); } if (str_eq(route, EL_STR("/api/references"))) { return route_references(query); } if (str_eq(route, EL_STR("/api/format"))) { return route_format(body); } if (str_eq(route, EL_STR("/api/completions/snippet"))) { return route_snippets(); } if (str_eq(route, EL_STR("/api/themes"))) { return route_themes_list(); } if (str_eq(route, EL_STR("/api/themes/active"))) { return route_themes_set(body); } if (str_eq(route, EL_STR("/api/plugins"))) { return route_plugins_list(); } if (str_eq(route, EL_STR("/api/plugins/install"))) { return route_plugins_install(body); } if (str_eq(route, EL_STR("/api/plugins/remove"))) { return route_plugins_remove(body); } if (str_eq(route, EL_STR("/api/plugins/enable"))) { return route_plugins_enable(body); } if (str_eq(route, EL_STR("/api/plugins/disable"))) { return route_plugins_disable(body); } if (str_eq(route, EL_STR("/api/settings"))) { if (str_eq(method, EL_STR("GET"))) { return route_settings_get(); } if (str_eq(method, EL_STR("POST"))) { return route_settings_save(body); } if (str_eq(method, EL_STR("DELETE"))) { return route_settings_reset(); } } if (str_eq(route, EL_STR("/api/build"))) { return route_build(body); } if (str_eq(route, EL_STR("/api/run"))) { return route_run(body); } if (str_eq(route, EL_STR("/api/reason"))) { return route_reason(body); } if (str_eq(route, EL_STR("/api/git/status"))) { return EL_STR("[]"); } if (str_eq(route, EL_STR("/api/git/diff"))) { return EL_STR("\"\""); } return el_str_concat(el_str_concat(EL_STR("{\"error\":\"not found\",\"path\":"), jstr(route)), EL_STR("}")); return 0; } el_val_t server_main(void) { plugin_init(); el_val_t port = cfg_port(); println(el_str_concat(EL_STR("[el-ide-server] project="), cfg_project_path())); println(el_str_concat(EL_STR("[el-ide-server] engram="), cfg_engram_url())); println(el_str_concat(EL_STR("[el-ide-server] listening on http://0.0.0.0:"), int_to_str(port))); http_set_handler(EL_STR("handle_request")); http_serve(port, EL_STR("handle_request")); return 0; } int main(int argc, char** argv) { el_runtime_init_args(argc, argv); server_main(); return 0; }