177 lines
4.6 KiB
Rust
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)
|
|
}
|
|
|
|
}
|
|
}
|