summaryrefslogtreecommitdiff
path: root/matrix-bin
diff options
context:
space:
mode:
Diffstat (limited to 'matrix-bin')
-rw-r--r--matrix-bin/Cargo.toml2
-rw-r--r--matrix-bin/src/helper.rs312
-rw-r--r--matrix-bin/src/main.rs38
-rw-r--r--matrix-bin/src/repl.rs27
4 files changed, 363 insertions, 16 deletions
diff --git a/matrix-bin/Cargo.toml b/matrix-bin/Cargo.toml
index c4b4d5a..bdb8fb8 100644
--- a/matrix-bin/Cargo.toml
+++ b/matrix-bin/Cargo.toml
@@ -12,4 +12,4 @@ clap = { version = "4", features = [ "derive" ] }
ctrlc = "3"
matrix = { path = "../matrix" }
matrix-stdlib = { path = "../matrix-stdlib" }
-rustyline = "13"
+rustyline = { version = "13", features = [ "derive" ] }
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);
+ }
+}
diff --git a/matrix-bin/src/main.rs b/matrix-bin/src/main.rs
index 909ee77..225b353 100644
--- a/matrix-bin/src/main.rs
+++ b/matrix-bin/src/main.rs
@@ -1,9 +1,10 @@
-use std::{path::PathBuf, io::{self, Read, IsTerminal}, fs};
-use clap::Parser as ArgParser;
+use std::{path::PathBuf, io::{self, Read, IsTerminal}, fs, cell::RefCell, rc::Rc};
+use clap::{Parser as ArgParser, ColorChoice};
use matrix::{compiler::{Compiler, CompilerBuilder}, vm::Vm, parse::{Parser, ParserBuilder}, value::Value};
use repl::Repl;
mod repl;
+mod helper;
#[derive(Debug, ArgParser)]
#[command(version, long_about = None)]
@@ -19,6 +20,10 @@ pub struct Args {
#[arg(short, long)]
debug: bool,
+ /// Choses color
+ #[arg(short, long)]
+ color: Option<ColorChoice>,
+
/// Disables optimizations
#[arg(long)]
disable_optimizations: bool,
@@ -27,8 +32,11 @@ pub struct Args {
pub struct State<'a> {
parser: Parser,
compiler: Compiler<'a>,
- vm: Vm,
- repl: bool
+ vm: Rc<RefCell<Vm>>,
+ repl: bool,
+ color: bool,
+ #[allow(unused)]
+ debug: bool,
}
impl<'a> State<'a> {
@@ -60,20 +68,32 @@ impl<'a> State<'a> {
matrix_stdlib::load(&mut vm);
- (Self { parser, vm, compiler, repl }, file)
+ let color = match args.color {
+ Some(ColorChoice::Auto) | None => {
+ io::stdout().is_terminal()
+ },
+ Some(ColorChoice::Always) => true,
+ Some(ColorChoice::Never) => false,
+ };
+
+ (Self { parser, vm: Rc::new(RefCell::new(vm)), compiler, repl, debug: args.debug, color }, file)
}
pub fn execute(&mut self, code: String) -> matrix::Result<Value> {
let ast = self.parser.parse(code)?;
let fun = self.compiler.compile(&ast)?;
- let val = self.vm.run(fun)?;
+ let val = self.vm.try_borrow_mut().unwrap().run(fun)?;
Ok(val)
}
}
-pub fn error(err: matrix::Error) {
- println!("\x1b[31m\x1b[1mError:\x1b[0m {err}");
+pub fn error(err: matrix::Error, state: &State) {
+ if state.color {
+ println!("\x1b[31m\x1b[1mError:\x1b[0m {err}");
+ } else {
+ println!("Error: {err}");
+ }
}
fn read_stdin() -> String {
@@ -93,7 +113,7 @@ fn main() {
if let Some(file) = file {
if let Err(err) = state.execute(file) {
- error(err);
+ error(err, &state);
}
}
diff --git a/matrix-bin/src/repl.rs b/matrix-bin/src/repl.rs
index 1b5addc..f2964d4 100644
--- a/matrix-bin/src/repl.rs
+++ b/matrix-bin/src/repl.rs
@@ -1,9 +1,9 @@
use std::{io::Write, sync::atomic::Ordering};
use matrix::{value::Value, vm::Interupt};
-use rustyline::Config;
+use rustyline::{Config, EditMode, ColorMode, Editor, CompletionType};
-use crate::State;
+use crate::{State, helper::MatrixHelper};
pub struct Repl<'a> {
state: State<'a>
@@ -17,24 +17,39 @@ impl<'a> Repl<'a> {
pub fn run(&mut self) {
- let interupt = self.state.vm.interupt();
+ let interupt = self.state.vm.borrow().interupt();
ctrlc::set_handler(move || {
interupt.store(Interupt::KeyboardInterupt as usize, Ordering::SeqCst);
}).unwrap();
let config = Config::builder()
.check_cursor_position(true)
+ .completion_type(CompletionType::List)
+ .edit_mode(EditMode::Emacs)
+ .color_mode(if self.state.color { ColorMode::Enabled } else { ColorMode::Disabled })
.build();
- let mut rl = rustyline::DefaultEditor::with_config(config).unwrap();
+
+ let helper = MatrixHelper::new(self.state.vm.clone());
+
+ let mut rl = Editor::with_config(config).unwrap();
+ rl.set_helper(Some(helper));
+
loop {
let Ok(line) = rl.readline(">> ") else {
break;
};
+ if let Err(_) = rl.add_history_entry(&line) {
+ break;
+ };
match self.state.execute(line) {
- Err(err) => crate::error(err),
+ Err(err) => crate::error(err, &self.state),
Ok(val) => {
if val != Value::Nil {
- println!("{val}");
+ if self.state.color {
+ println!("{val:#}");
+ } else {
+ println!("{val}");
+ }
}
}
}