use std::{borrow::Cow, cell::RefCell, rc::Rc}; use rustyline::{completion::Completer, highlight::Highlighter, hint::Hinter, validate::{ValidationContext, ValidationResult, Validator}, Helper, Result}; use talc_lang::{lstring::LStr, parser::{Lexer, Pos, Span, TokenKind}, Vm}; pub struct TalcHelper { vm: Rc>, } impl TalcHelper { pub fn new(vm: Rc>) -> Self { Self { vm, } } } impl Helper for TalcHelper {} impl Completer for TalcHelper { type Candidate = String; fn complete( &self, line: &str, pos: usize, _ctx: &rustyline::Context<'_>, ) -> Result<(usize, Vec)> { let mut res = String::new(); for ch in line[..pos].chars().rev() { if matches!(ch, '0'..='9' | 'a'..='z' | 'A'..='Z' | '_') { res.push(ch); } else { break } } let res: String = res.chars().rev().collect(); let mut keys = self.vm.borrow().globals().keys() .map(|sym| sym.name()) .filter(|name| name.starts_with(LStr::from_str(&res))) .map(LStr::to_string) .collect::>(); keys.sort(); Ok((pos - res.as_bytes().len(), keys)) } } impl Hinter for TalcHelper { type Hint = String; } impl Highlighter for TalcHelper { fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { let mut lexer = Lexer::new(line); let mut buf = String::new(); let mut last = Pos::new(); while let Some(Ok(token)) = lexer.next() { if token.kind == TokenKind::Eof { break } buf += Span::new(last, token.span.start).of(line); last = token.span.end; let format = match token.kind { TokenKind::Nil | TokenKind::True | TokenKind::False | TokenKind::Integer | TokenKind::Float => "\x1b[93m", TokenKind::String => "\x1b[92m", TokenKind::Symbol => "\x1b[96m", _ => "", }; buf += format; buf += token.content; if !format.is_empty() { buf += "\x1b[0m" } } buf += &line[(last.idx as usize)..]; Cow::Owned(buf) } fn highlight_prompt<'b, 's: 'b, 'p: 'b>( &'s self, prompt: &'p str, _default: bool, ) -> Cow<'b, str> { Cow::Owned(format!("\x1b[94m{prompt}\x1b[0m")) } fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { Cow::Owned(format!("\x1b[37m{hint}\x1b[0m")) } fn highlight_char(&self, line: &str, _: usize, forced: bool) -> bool { forced || !line.is_empty() } } impl Validator for TalcHelper { fn validate(&self, ctx: &mut ValidationContext) -> Result { use TokenKind as K; let lexer = Lexer::new(ctx.input()); let mut delims = Vec::new(); let mut mismatch = None; for token in lexer { let token = match token { Ok(t) => t, Err(e) => { return Ok(ValidationResult::Invalid( Some(format!(" {e}")))) } }; let k = token.kind; let s = token.span; match k { K::Eof => break, K::LParen | K::LBrack | K::LBrace | K::If | K::While | K::For | K::Try => delims.push(token.kind), K::RParen => match delims.pop() { Some(K::LParen) => (), v => { mismatch = Some((v, k, s)); break } }, K::RBrack => match delims.pop() { Some(K::LBrack) => (), v => { mismatch = Some((v, k, s)); break } }, K::RBrace => match delims.pop() { Some(K::LBrace) => (), v => { mismatch = Some((v, k, s)); break } }, K::Then => match delims.pop() { Some(K::If | K::Elif) => delims.push(k), v => { mismatch = Some((v, k, s)); break } } K::Catch => match delims.pop() { Some(K::Try) => delims.push(k), v => { mismatch = Some((v, k, s)); break } } K::Do => match delims.last().copied() { Some(K::While | K::For | K::Catch) => { delims.pop(); delims.push(k); }, _ => delims.push(k) }, K::Elif | K::Else => match delims.pop() { Some(K::Then) => delims.push(k), v => { mismatch = Some((v, k, s)); break } }, K::End => match delims.pop() { Some(K::Then | K::Else | K::Do | K::Try | K::Catch) => (), v => { mismatch = Some((v, k, s)); break } }, _ => (), } } match mismatch { Some((None, b, s)) => return Ok(ValidationResult::Invalid(Some( format!(" found unmatched {b} at {s}")))), Some((Some(a), b, s)) => return Ok(ValidationResult::Invalid(Some( format!(" found {a} matched with {b} at {s}")))), _ => (), } if delims.is_empty() { Ok(ValidationResult::Valid(None)) } else { Ok(ValidationResult::Incomplete) } } }