summaryrefslogtreecommitdiff
path: root/matrix-bin/src/helper.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--matrix-bin/src/helper.rs312
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);
+ }
+}