talc/talc-bin/src/helper.rs

186 lines
4.7 KiB
Rust

use std::{borrow::Cow, cell::RefCell, collections::HashMap, rc::Rc};
use rustyline::{completion::Completer, highlight::Highlighter, hint::Hinter, validate::{ValidationContext, ValidationResult, Validator}, Helper, Result};
use talc_lang::{lstring::LStr, Lexer, Vm};
#[derive(Clone, Copy)]
enum TokenType {
String, Symbol, Number, Literal
}
pub struct TalcHelper {
vm: Rc<RefCell<Vm>>,
lex: Lexer,
token_types: HashMap<usize, TokenType>,
}
macro_rules! load_tokens {
($token_types:expr, $lex:expr, {$($($tok:literal)|+ => $ty:expr,)*}) => {{
$($(
$token_types.insert($lex.lex($tok).next().unwrap().unwrap().1.0, $ty);
)*)*
}};
}
impl TalcHelper {
pub fn new(vm: Rc<RefCell<Vm>>) -> Self {
let lex = Lexer::new();
let mut token_types = HashMap::new();
load_tokens!(token_types, lex, {
"\"\"" | "''" => TokenType::String,
":a" | ":''" | ":\"\"" => TokenType::Symbol,
"0" | "0.0" | "0x0" | "0b0" | "0o0" | "0s0" => TokenType::Number,
"true" | "false" | "nil" => TokenType::Literal,
});
Self {
vm,
lex,
token_types,
}
}
}
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 tokens = self.lex.lex(line).peekable();
let mut buf = String::new();
let mut last = 0;
while let Some(Ok((l, tok, r))) = tokens.next() {
buf += &line[last..l];
last = r;
let tokty = self.token_types.get(&tok.0);
buf += match tokty {
Some(TokenType::Literal) => "\x1b[93m",
Some(TokenType::Number) => "\x1b[93m",
Some(TokenType::String) => "\x1b[92m",
Some(TokenType::Symbol) => "\x1b[96m",
None => "",
};
buf += tok.1;
if tokty.is_some() { buf += "\x1b[0m" }
}
buf += &line[last..];
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> {
let tokens = self.lex.lex(ctx.input());
let mut delims = Vec::new();
let mut mismatch = None;
for token in tokens {
let token = match token {
Ok(t) => t,
Err(e) => return Ok(ValidationResult::Invalid(
Some(e.to_string()))),
};
let t = token.1.1;
match t {
"(" | "{" | "[" | "if" | "while" | "for" | "try"
=> delims.push(token.1.1),
")" => match delims.pop() {
Some("(") => (),
v => { mismatch = Some((v, t)); break }
},
"}" => match delims.pop() {
Some("{") => (),
v => { mismatch = Some((v, t)); break }
},
"]" => match delims.pop() {
Some("[") => (),
v => { mismatch = Some((v, t)); break }
},
"then" => match delims.pop() {
Some("if") => delims.push(t),
v => { mismatch = Some((v, t)); break }
}
"catch" => match delims.pop() {
Some("try") => delims.push(t),
v => { mismatch = Some((v, t)); break }
}
"do" => match delims.last().copied() {
Some("while" | "for" | "catch") => {
delims.pop();
delims.push(t);
},
_ => delims.push(t)
},
"elif" | "else" => match delims.pop() {
Some("then") => delims.push(t),
v => { mismatch = Some((v, t)); break }
},
"end" => match delims.pop() {
Some("then" | "elif" | "else" | "do" | "try") => (),
v => { mismatch = Some((v, t)); break }
},
_ => (),
}
}
match mismatch {
Some((None, b)) => return Ok(ValidationResult::Invalid(Some(
format!(" found unmatched {b}")))),
Some((Some(a), b)) => return Ok(ValidationResult::Invalid(Some(
format!(" found {a} matched with {b}")))),
_ => (),
}
if delims.is_empty() {
Ok(ValidationResult::Valid(None))
} else {
Ok(ValidationResult::Incomplete)
}
}
}