2024-02-27 05:18:43 +00:00
|
|
|
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::{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(&res))
|
|
|
|
.map(|s| s.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> {
|
2024-03-08 00:38:57 +00:00
|
|
|
Cow::Owned(format!("\x1b[37m{hint}\x1b[0m"))
|
2024-02-27 05:18:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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") | Some("for") | Some("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)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|