313 lines
9.1 KiB
Rust
313 lines
9.1 KiB
Rust
|
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);
|
||
|
}
|
||
|
}
|