//! Completion engine — prefix-based + semantic scoring. use serde::{Deserialize, Serialize}; use el_types::{TypeDef, TypeEnv}; // ── Types ───────────────────────────────────────────────────────────────────── #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Completion { pub label: String, pub kind: CompletionKind, pub detail: String, pub documentation: Option, /// Activation strength: higher = more relevant, completions sorted desc. pub score: f32, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "lowercase")] pub enum CompletionKind { Variable, Function, Type, Keyword, } // ── Keywords ────────────────────────────────────────────────────────────────── const KEYWORDS: &[&str] = &[ "let", "fn", "type", "enum", "match", "return", "activate", "where", "sealed", "if", "else", "for", "in", "while", "true", "false", "test", "seed", "assert", "target", "protocol", "impl", "import", "from", "as", "with", "retry", "times", "fallback", "reason", "parallel", "trace", "requires", "deploy", "to", "via", ]; // ── Builtin functions ───────────────────────────────────────────────────────── const BUILTIN_FUNCTIONS: &[(&str, &str)] = &[ ("println", "fn(value: String) -> Void"), ("print", "fn(value: String) -> Void"), ("int_to_str", "fn(n: Int) -> String"), ("str_to_int", "fn(s: String) -> Int"), ("string_len", "fn(s: String) -> Int"), ("str_slice", "fn(s: String, start: Int, end: Int) -> String"), ("str_concat", "fn(a: String, b: String) -> String"), ("str_contains", "fn(s: String, sub: String) -> Bool"), ("str_starts_with", "fn(s: String, prefix: String) -> Bool"), ("str_ends_with", "fn(s: String, suffix: String) -> Bool"), ("str_to_upper", "fn(s: String) -> String"), ("str_to_lower", "fn(s: String) -> String"), ("str_split", "fn(s: String, sep: String) -> [String]"), ("str_trim", "fn(s: String) -> String"), ("str_replace", "fn(s: String, from: String, to: String) -> String"), ("str_index_of", "fn(s: String, sub: String) -> Int"), ("float_to_str", "fn(f: Float) -> String"), ("str_to_float", "fn(s: String) -> Float"), ("array_push", "fn(arr: [T], val: T) -> [T]"), ("array_len", "fn(arr: [T]) -> Int"), ("array_get", "fn(arr: [T], i: Int) -> T"), ("list_len", "fn(list: [T]) -> Int"), ("list_get", "fn(list: [T], i: Int) -> T"), ("list_map", "fn(list: [T], f: fn(T) -> U) -> [U]"), ("list_filter", "fn(list: [T], f: fn(T) -> Bool) -> [T]"), ("list_reduce", "fn(list: [T], init: U, f: fn(U, T) -> U) -> U"), ("map_create", "fn() -> Map"), ("map_set", "fn(m: Map, key: String, val: T) -> Map"), ("map_get", "fn(m: Map, key: String) -> T"), ("map_has", "fn(m: Map, key: String) -> Bool"), ("math_abs", "fn(n: Float) -> Float"), ("math_sqrt", "fn(n: Float) -> Float"), ("math_floor", "fn(n: Float) -> Int"), ("math_ceil", "fn(n: Float) -> Int"), ("math_round", "fn(n: Float) -> Int"), ("math_min", "fn(a: Float, b: Float) -> Float"), ("math_max", "fn(a: Float, b: Float) -> Float"), ("math_pow", "fn(base: Float, exp: Float) -> Float"), ("now_millis", "fn() -> Int"), ("time_now_utc", "fn() -> String"), ("time_to_parts", "fn(ts: String) -> Map"), ("time_format", "fn(ts: String, fmt: String) -> String"), ("llm_call", "fn(prompt: String) -> String"), ("llm_parallel", "fn(prompts: [String]) -> [String]"), ("random_int", "fn(min: Int, max: Int) -> Int"), ("random_float", "fn() -> Float"), ("parse_json", "fn(s: String) -> Map"), ("to_json", "fn(v: T) -> String"), ("http_get", "fn(url: String) -> String"), ("http_post", "fn(url: String, body: String) -> String"), ("read_file", "fn(path: String) -> String"), ("write_file", "fn(path: String, content: String) -> Void"), ("env_get", "fn(key: String) -> String"), ("sleep_ms", "fn(ms: Int) -> Void"), ("uuid_new", "fn() -> String"), ("hash_sha256", "fn(s: String) -> String"), ]; const BUILTIN_TYPES: &[(&str, &str)] = &[ ("Int", "64-bit signed integer"), ("Float", "64-bit IEEE 754 double"), ("String", "UTF-8 string"), ("Bool", "Boolean value"), ("Uuid", "RFC 4122 UUID"), ("Void", "Unit type — no value"), ]; // ── Entry point ─────────────────────────────────────────────────────────────── /// Produce completions at `cursor_pos` in `source`. /// /// Extracts the identifier prefix before the cursor and returns all /// completions whose label starts with that prefix, sorted by score /// (prefix match score + semantic boost). pub fn completions_at(env: &TypeEnv, source: &str, cursor_pos: usize) -> Vec { let prefix = extract_prefix(source, cursor_pos); let mut results: Vec = Vec::new(); // Keywords for &kw in KEYWORDS { if kw.starts_with(&prefix) { let score = prefix_score(kw, &prefix) + if matches!(kw, "activate" | "sealed") { 0.2 } else { 0.0 }; results.push(Completion { label: kw.to_string(), kind: CompletionKind::Keyword, detail: "keyword".into(), documentation: keyword_doc(kw), score, }); } } // Built-in types for &(name, desc) in BUILTIN_TYPES { if name.to_lowercase().starts_with(&prefix.to_lowercase()) || prefix.is_empty() { results.push(Completion { label: name.to_string(), kind: CompletionKind::Type, detail: desc.into(), documentation: Some(format!("Built-in type: {desc}")), score: prefix_score(name, &prefix) + 0.1, }); } } // User-defined types from env for (type_name, def) in &env.types { // Skip built-ins already listed above if BUILTIN_TYPES.iter().any(|(n, _)| *n == type_name) { continue; } if type_name.to_lowercase().starts_with(&prefix.to_lowercase()) || prefix.is_empty() { let (detail, doc, extra_score) = match def { TypeDef::Struct { fields, .. } => { let field_list = fields .iter() .map(|(f, t)| format!("{f}: {t}")) .collect::>() .join(", "); (format!("struct {{ {field_list} }}"), Some(format!("User-defined struct with fields: {field_list}")), 0.15) } TypeDef::Enum { variants, .. } => { let v_list = variants.iter().map(|v| v.name.clone()).collect::>().join(", "); (format!("enum {{ {v_list} }}"), Some(format!("Enum variants: {v_list}")), 0.15) } TypeDef::Primitive(_) => { ("primitive type".into(), None, 0.05) } TypeDef::Protocol { methods, .. } => { let m_list = methods.iter().map(|m| m.name.clone()).collect::>().join(", "); (format!("protocol {{ {m_list} }}"), Some(format!("Protocol methods: {m_list}")), 0.15) } }; results.push(Completion { label: type_name.clone(), kind: CompletionKind::Type, detail, documentation: doc, score: prefix_score(type_name, &prefix) + extra_score, }); } } // Builtin functions for &(name, sig) in BUILTIN_FUNCTIONS { if name.to_lowercase().starts_with(&prefix.to_lowercase()) || prefix.is_empty() { results.push(Completion { label: name.to_string(), kind: CompletionKind::Function, detail: sig.into(), documentation: Some(format!("Built-in function: {name}\n{sig}")), score: prefix_score(name, &prefix) + 0.11, }); } } // Functions from env (user-defined) for (fn_name, fn_type) in &env.functions { if fn_name.to_lowercase().starts_with(&prefix.to_lowercase()) || prefix.is_empty() { results.push(Completion { label: fn_name.clone(), kind: CompletionKind::Function, detail: fn_type.to_string(), documentation: Some(format!("Function: {fn_name} :: {fn_type}")), score: prefix_score(fn_name, &prefix) + 0.12, }); } } // Sort by score descending, deduplicate by label results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal)); results.dedup_by(|a, b| a.label == b.label); results } // ── Helpers ─────────────────────────────────────────────────────────────────── /// Extract the identifier fragment immediately before `cursor_pos`. fn extract_prefix(source: &str, cursor_pos: usize) -> String { let end = cursor_pos.min(source.len()); let bytes = source.as_bytes(); let mut start = end; while start > 0 && (bytes[start - 1].is_ascii_alphanumeric() || bytes[start - 1] == b'_') { start -= 1; } source[start..end].to_string() } /// Score based on how much of the label is matched by the prefix. /// Returns a value in [0, 1]. fn prefix_score(label: &str, prefix: &str) -> f32 { if prefix.is_empty() { return 0.5; } let lower_label = label.to_lowercase(); let lower_prefix = prefix.to_lowercase(); if lower_label.starts_with(&lower_prefix) { // Exact prefix match — score by how complete the prefix is (lower_prefix.len() as f32 / lower_label.len() as f32).min(1.0) } else { 0.0 } } fn keyword_doc(kw: &str) -> Option { let doc = match kw { "activate" => "Spreading-activation query: `activate TypeName where \"query\"`\nReturns `[TypeName]` from the Engram knowledge graph.", "sealed" => "Quantum-sealed block: marks sensitive code for runtime protection.", "let" => "Declare an immutable binding: `let name: Type = expr`", "fn" => "Define a function: `fn name(params) -> ReturnType { body }`", "type" => "Define a struct type: `type Name { field: Type }`", "enum" => "Define an enum: `enum Name { Variant1, Variant2(Type) }`", "match" => "Pattern match: `match expr { Pattern => result }`", "return" => "Return a value from a function.", "where" => "Used in `activate T where \"query\"`", "if" => "Conditional: `if cond { then } else { else }`", "else" => "Else branch of an if expression.", "for" => "For loop: `for item in collection { body }`", "while" => "While loop: `while cond { body }`", "in" => "Used in `for item in collection`", "true" | "false" => "Boolean literal", "protocol" => "Define a protocol (trait): `protocol Name { fn method(self) -> Ret; }`", "impl" => "Implement a protocol: `impl Protocol for Type { fn method(self) -> Ret { ... } }`", "import" => "Import from a module: `import { Name } from \"module\"`", "from" => "Used in import: `import { Name } from \"module\"`", "as" => "Alias in import: `import { Name as Alias } from \"module\"`", "with" => "With clause for retry/fallback: `with retry times 3`", "retry" => "Retry policy: `retry times N`", "times" => "Used in retry: `retry times N`", "fallback" => "Fallback value on failure: `fallback { default_expr }`", "reason" => "Reason clause: provides context to Engram reasoning engine.", "parallel" => "Parallel execution: `parallel { task1; task2 }`", "trace" => "Emit a trace event: `trace \"message\"`", "requires" => "Dependency declaration: `requires Module`", "deploy" => "Deploy declaration: `deploy service to target via method`", "to" => "Used in deploy: `deploy X to Y`", "via" => "Used in deploy: `deploy X via method`", "test" => "Test declaration: `test \"name\" { assertions }`", "seed" => "Seed data block: `seed { ... }`", "assert" => "Assertion: `assert condition, \"message\"`", "target" => "Target annotation: `target { ... }`", _ => return None, }; Some(doc.to_string()) }