use aura_lang::lexer::{Lexer, Token, TokenKind};
use crate::stdlib::Stdlib;
pub struct Signature {
pub label: String,
pub params: Vec<String>,
pub active: u32,
pub doc: Option<String>,
}
fn local_def_params<'a>(toks: &[Token<'a>], name: &str) -> Option<Vec<&'a str>> {
for i in 0..toks.len() {
if !matches!(toks[i].kind, TokenKind::Def) {
continue;
}
if !matches!(toks.get(i + 1).map(|t| &t.kind), Some(TokenKind::Ident(n)) if *n == name) {
continue;
}
let mut params = Vec::new();
let mut j = i + 3; while j < toks.len() && !matches!(toks[j].kind, TokenKind::RParen) {
if let TokenKind::Ident(p) = &toks[j].kind {
params.push(*p);
}
j += 1;
}
return Some(params);
}
None
}
pub fn signature_at(stdlib: &Stdlib, text: &str, offset: usize) -> Option<Signature> {
let toks = Lexer::new(text, 0).tokenize().ok()?;
let cur = toks
.iter()
.rposition(|t| (t.span.start as usize) < offset)?;
let mut depth = 0i32;
let mut commas = 0u32;
let mut i = cur;
let open = loop {
match toks[i].kind {
TokenKind::RParen => depth += 1,
TokenKind::LParen => {
if depth == 0 {
break i;
}
depth -= 1;
}
TokenKind::Comma if depth == 0 => commas += 1,
TokenKind::Newline if depth == 0 => return None,
_ => {}
}
i = i.checked_sub(1)?;
};
let callee = open.checked_sub(1)?;
let TokenKind::Ident(name) = &toks[callee].kind else {
return None; };
let is_method = callee >= 1 && matches!(toks[callee - 1].kind, TokenKind::Dot);
if !is_method {
if let Some(params) = local_def_params(&toks, name) {
let params: Vec<String> = params.into_iter().map(str::to_string).collect();
return Some(Signature {
label: format!("{name}({})", params.join(", ")),
params,
active: commas,
doc: None,
});
}
}
let entry = stdlib
.entries
.iter()
.find(|e| e.name == *name && (is_method) == (e.receiver != "builtins"))?;
Some(Signature {
label: entry.signature(),
params: entry.params.clone(),
active: commas,
doc: Some(entry.doc.clone()),
})
}
#[cfg(test)]
mod tests {
use super::*;
fn sig(text: &str) -> Option<Signature> {
signature_at(&Stdlib::load(), text, text.len())
}
#[test]
fn stdlib_method_signature_and_active_parameter() {
let s = sig("x: \"a-b\".replace(").expect("signature");
assert_eq!(s.label, "replace(from, to) -> String");
assert_eq!(s.params, vec!["from", "to"]);
assert_eq!(s.active, 0);
let s = sig("x: \"a-b\".replace(\"-\", ").expect("signature");
assert_eq!(s.active, 1);
}
#[test]
fn builtin_signature() {
let s = sig("x: env(").expect("signature");
assert_eq!(s.params, vec!["name", "default"]);
assert!(s.doc.unwrap_or_default().contains("Environment"));
}
#[test]
fn local_def_signature_wins_over_the_manifest() {
let s = sig("def build(a, b)\n v: 1\nend\nx: build(").expect("signature");
assert_eq!(s.label, "build(a, b)");
assert_eq!(s.params, vec!["a", "b"]);
}
#[test]
fn no_signature_outside_a_call() {
assert!(sig("x: 1 + 2\n").is_none());
assert!(sig("x: [1, 2]\n").is_none());
}
}