diff options
Diffstat (limited to 'matrix-bin/src/helper.rs')
-rw-r--r-- | matrix-bin/src/helper.rs | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/matrix-bin/src/helper.rs b/matrix-bin/src/helper.rs new file mode 100644 index 0000000..c0ac5ec --- /dev/null +++ b/matrix-bin/src/helper.rs @@ -0,0 +1,312 @@ +use std::{borrow::Cow, rc::Rc, cell::RefCell}; + +use matrix::{lex::{Lexer, TokenData, Token}, vm::Vm}; +use rustyline::{validate::{Validator, ValidationResult, ValidationContext}, highlight::Highlighter, Helper, Hinter, completion::Completer}; + +#[derive(Helper, Hinter)] +pub struct MatrixHelper { + vm: Rc<RefCell<Vm>> +} + +impl MatrixHelper { + pub fn new(vm: Rc<RefCell<Vm>>) -> Self { + Self { vm } + } +} + +macro_rules! unmatched { + ($token:expr, $matched:expr) => { + Ok(::rustyline::validate::ValidationResult::Invalid(Some(format!("Token '{:?}' at {}:{} does not have a matching '{:?}'", $token.data, $token.pos.row, $token.pos.col, $matched)))) + }; +} + +impl Validator for MatrixHelper { + + fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result<ValidationResult> { + let mut lexer = Lexer::new(ctx.input()); + let mut stack = Vec::new(); + + loop { + use TokenData as T; + let token = match lexer.next_token() { + Ok(t) if t.data == T::Eof => break, + Ok(t) => t, + Err(err) => return Ok(ValidationResult::Invalid(Some(format!("{err}")))) + }; + + match token.data { + T::LeftParen | + T::LeftBrack | + T::LeftBrace | + T::For | + T::Try => { stack.push(token.data); }, + T::RightParen => + match stack.pop() { + Some(T::LeftParen) => {}, + _ => return unmatched!(token, T::LeftParen) + }, + T::RightBrack => + match stack.pop() { + Some(T::LeftBrack) => {}, + _ => return unmatched!(token, T::LeftBrack) + }, + T::RightBrace => + match stack.pop() { + Some(T::LeftBrace) => {}, + _ => return unmatched!(token, T::LeftBrace) + }, + T::In => + match stack.pop() { + Some(T::For) => {}, + _ => return unmatched!(token, T::For) + }, + T::Catch => + match stack.pop() { + Some(T::Try) => {}, + _ => return unmatched!(token, T::Try) + }, + _ => {} + }; + } + + if stack.is_empty() { + return Ok(ValidationResult::Valid(None)) + } else { + return Ok(ValidationResult::Incomplete) + } + } + + fn validate_while_typing(&self) -> bool { + true + } +} + +fn find_matching_bracket(line: &str, pos: usize) -> Option<usize> { + if pos >= line.len() { + return None + } + let c = line.as_bytes()[pos]; + let (target, fwd) = match c { + b'(' => (b')', true), + b')' => (b'(', false), + b'[' => (b']', true), + b']' => (b'[', false), + b'{' => (b'}', true), + b'}' => (b'{', false), + _ => return None, + }; + let mut depth = 0; + let mut idx = 0; + if fwd { + let bytes = &line.as_bytes()[pos+1..]; + for &b in bytes { + if b == c { + depth += 1; + } else if b == target { + if depth == 0 { + return Some(pos + idx + 1) + } else { + depth -= 1; + } + } + idx += 1; + } + } else { + let bytes = &line.as_bytes()[..pos]; + for &b in bytes.iter().rev() { + if b == c { + depth += 1; + } else if b == target { + if depth == 0 { + return Some(pos - idx - 1) + } else { + depth -= 1; + } + } + idx += 1; + } + } + None +} + +fn get_token_at(line: &str, pos: usize) -> Option<Token> { + use TokenData as T; + let mut lexer = Lexer::new(line); + loop { + match lexer.next_token() { + Ok(Token { data: T::Eof, ..}) => return None, + Ok(t) if t.bidx <= pos && (t.bidx + t.blen) > pos => return Some(t), + Err(_) => return None, + _ => continue + } + } +} + +fn find_matching_ident(line: &str, pos: usize) -> Option<Rc<str>> { + use TokenData as T; + match get_token_at(line, pos) { + Some(Token { data: T::Ident(ident), ..}) => Some(ident), + _ => return None + } +} + +impl Highlighter for MatrixHelper { + fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { + + let matching = find_matching_bracket(line, pos); + let ident = find_matching_ident(line, pos); + + use TokenData as T; + let mut lexer = Lexer::new(line); + let mut buf = String::new(); + let mut last = 0; + loop { + let token = match lexer.next_token() { + Ok(t) if t.data == T::Eof => break, + Ok(t) => t, + Err(_) => break + }; + let bl = token.bidx; + let br = token.bidx + token.blen; + buf += &line[last..bl]; + last = br; + + let color = match token.data { + T::LeftParen | + T::RightParen | + T::LeftBrack | + T::RightBrack | + T::LeftBrace | + T::RightBrace + => { + match matching { + Some(idx) if bl == idx || bl == pos => "\x1b[33;40m", + _ => "" + } + } + T::Int(_) | + T::True | + T::False | + T::Nil | + T::Float(_) | + T::Complex(_) + => "\x1b[33m", + T::Ident(tok) => { + match (ident.as_ref(), lexer.peek_token()) { + (Some(ident), Ok(t)) if t.data == T::LeftParen && ident.as_ref() == tok.as_ref() => "\x1b[34;40m", + (Some(ident), _) if ident.as_ref() == tok.as_ref() => "\x1b[40m", + (_, Ok(t)) if t.data == T::LeftParen => "\x1b[34m", + _ => "" + } + } + T::Regex(_) => "\x1b[31m", + T::String(_) => "\x1b[32m", + T::If | + T::Else | + T::While | + T::Let | + T::Function | + T::Continue | + T::Break | + T::Do | + T::Loop | + T::Return | + T::For | + T::In | + T::Try | + T::Catch + => "\x1b[38;2;203;166;247m", + _ => { + buf += &token.str; + continue; + } + }; + + let clear = "\x1b[0m"; + + buf += &format!("{color}{}{clear}", token.str); + } + + buf += &line[last..]; + + Cow::Owned(buf) + } + + fn highlight_prompt<'b, 's: 'b, 'p: 'b>( + &'s self, + prompt: &'p str, + default: bool, + ) -> std::borrow::Cow<'b, str> { + let _ = default; + Cow::Owned(format!("\x1b[35m{prompt}\x1b[0m")) + } + + fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { + Cow::Owned(format!(" \x1b[31;40m Error: \x1b[0;40m{hint} \x1b[0m")) + } + + fn highlight_candidate<'c>( + &self, + candidate: &'c str, + completion: rustyline::CompletionType, + ) -> std::borrow::Cow<'c, str> { + let _ = completion; + Cow::Borrowed(candidate) + } + + fn highlight_char(&self, line: &str, _pos: usize, forced: bool) -> bool { + forced || !line.is_empty() + } +} + +impl Completer for MatrixHelper { + type Candidate = Rc<str>; + + fn complete( + &self, + line: &str, + pos: usize, + ctx: &rustyline::Context<'_>, + ) -> rustyline::Result<(usize, Vec<Self::Candidate>)> { + + let mut idx = 0; + let mut buf = String::new(); + let mut start = 0; + + for char in line.chars() { + if buf.is_empty() && char.is_alphabetic() { + start = idx; + buf.push(char); + } else if !buf.is_empty() && char.is_alphanumeric() { + buf.push(char); + } else { + if idx >= pos { + break; + } else { + buf.clear(); + } + } + idx += char.len_utf8(); + } + + let _ = (line, pos, ctx); + let globals = self.vm.borrow().global_names(); + let names: Vec<Rc<str>> = globals + .borrow() + .clone() + .into_iter() + .filter(|n| n.starts_with(&buf)) + .collect(); + + if buf.is_empty() { + start = pos; + } + + Ok((start, names)) + } + + fn update(&self, line: &mut rustyline::line_buffer::LineBuffer, start: usize, elected: &str, cl: &mut rustyline::Changeset) { + let end = line.pos(); + line.replace(start..end, elected, cl); + } +} |