talc/talc-bin/src/helper.rs
2024-11-03 14:55:49 -05:00

177 lines
4.6 KiB
Rust

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<RefCell<Vm>>,
}
impl TalcHelper {
pub fn new(vm: Rc<RefCell<Vm>>) -> 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<Self::Candidate>)> {
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::<Vec<String>>();
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
| TokenKind::Imaginary => "\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<ValidationResult> {
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)
}
}
}