3b76f0f8e0
Adds src/main.el + manifest.el for each vessel in this workspace, ported from the Rust sources during the El consolidation pass on 2026-04-30. Each vessel now has both Rust (legacy) and El (target) sources side-by-side; Rust will be removed once the El paths are verified at runtime, vessel by vessel. Per-vessel work was split across multiple parallel agents reading the Rust to understand intent, then designing idiomatic El. Not 1:1 transliteration. Each ported vessel includes: - manifest.el per spec/language.md \u00a715.1 - src/main.el with the vessel's public surface - Compile verified via dist/platform/elc + cc against the el_runtime Known gaps surfaced during the port (held for follow-up): HMAC-SHA256 and base64 crypto, HTTP status code in handler returns, request headers in handler signatures, subprocess primitives, streaming responses, struct/enum types, browser/JS codegen target. Codegen bug list of 9 items tracked separately. The El sources here are runtime- ready under the canonical C runtime; the gaps are language/runtime extensions still in flight.
1470 lines
52 KiB
C
1470 lines
52 KiB
C
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#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 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_string(body, EL_STR("id"));
|
|
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;
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
el_runtime_init_args(argc, argv);
|
|
lsp_main();
|
|
return 0;
|
|
}
|
|
|