2020-06-04 18:55:08 +02:00
|
|
|
|
use crate::output;
|
2020-06-07 18:56:28 +02:00
|
|
|
|
use ansi_term::Colour::{self, Cyan};
|
2020-06-04 19:43:43 +02:00
|
|
|
|
use kalk::parser;
|
2020-12-10 23:40:29 +01:00
|
|
|
|
use lazy_static::lazy_static;
|
2020-06-07 18:56:28 +02:00
|
|
|
|
use regex::Captures;
|
|
|
|
|
use regex::Regex;
|
|
|
|
|
use rustyline::completion::Completer;
|
2021-12-29 18:32:11 +01:00
|
|
|
|
use rustyline::config::Configurer;
|
2020-06-04 18:55:08 +02:00
|
|
|
|
use rustyline::error::ReadlineError;
|
2020-06-07 18:56:28 +02:00
|
|
|
|
use rustyline::highlight::Highlighter;
|
|
|
|
|
use rustyline::hint::Hinter;
|
|
|
|
|
use rustyline::validate::MatchingBracketValidator;
|
|
|
|
|
use rustyline::validate::ValidationContext;
|
|
|
|
|
use rustyline::validate::ValidationResult;
|
|
|
|
|
use rustyline::validate::Validator;
|
|
|
|
|
use rustyline::{Editor, Helper};
|
|
|
|
|
use std::borrow::Cow;
|
|
|
|
|
use std::borrow::Cow::Owned;
|
2020-12-10 23:40:29 +01:00
|
|
|
|
use std::collections::HashMap;
|
2021-09-04 12:51:31 +02:00
|
|
|
|
use std::fs;
|
2021-12-29 18:32:11 +01:00
|
|
|
|
use std::process;
|
2020-06-04 18:55:08 +02:00
|
|
|
|
|
2020-12-13 18:12:33 +01:00
|
|
|
|
pub fn start(mut parser: &mut parser::Context, precision: u32) {
|
2020-06-07 18:56:28 +02:00
|
|
|
|
let mut editor = Editor::<RLHelper>::new();
|
|
|
|
|
editor.set_helper(Some(RLHelper {
|
|
|
|
|
highlighter: LineHighlighter {},
|
|
|
|
|
validator: MatchingBracketValidator::new(),
|
|
|
|
|
}));
|
2021-09-04 12:51:31 +02:00
|
|
|
|
editor.set_max_history_size(30);
|
|
|
|
|
|
|
|
|
|
// Load history
|
|
|
|
|
let mut history_path = None;
|
|
|
|
|
if let Some(config_path) = dirs::config_dir() {
|
|
|
|
|
let mut config_path = config_path.clone();
|
|
|
|
|
config_path.push("kalker");
|
|
|
|
|
if let Ok(_) = fs::create_dir_all(config_path.as_path()) {
|
|
|
|
|
config_path.push("history.txt");
|
|
|
|
|
let history = config_path.into_os_string().into_string().unwrap();
|
|
|
|
|
editor.load_history(&history).ok();
|
|
|
|
|
history_path = Some(history)
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-30 19:21:38 +02:00
|
|
|
|
|
|
|
|
|
// If in tty, print the welcome message
|
|
|
|
|
if atty::is(atty::Stream::Stdin) && atty::is(atty::Stream::Stdout) {
|
2021-06-02 22:32:30 +02:00
|
|
|
|
println!("kalker");
|
2021-05-30 19:21:38 +02:00
|
|
|
|
println!(
|
|
|
|
|
"{}",
|
|
|
|
|
ansi_term::Color::Fixed(246).paint("Type 'help' for instructions.")
|
|
|
|
|
);
|
|
|
|
|
}
|
2020-06-04 18:55:08 +02:00
|
|
|
|
|
|
|
|
|
loop {
|
2020-12-10 21:13:04 +01:00
|
|
|
|
let prompt = if cfg!(windows) {
|
|
|
|
|
String::from(">> ")
|
|
|
|
|
} else {
|
|
|
|
|
Cyan.paint(">> ").to_string()
|
|
|
|
|
};
|
|
|
|
|
let readline = editor.readline(&prompt);
|
2020-06-04 18:55:08 +02:00
|
|
|
|
|
|
|
|
|
match readline {
|
|
|
|
|
Ok(input) => {
|
2020-06-04 19:43:43 +02:00
|
|
|
|
editor.add_history_entry(input.as_str());
|
2020-12-13 18:12:33 +01:00
|
|
|
|
eval_repl(&mut parser, &input, precision);
|
2020-06-04 18:55:08 +02:00
|
|
|
|
}
|
|
|
|
|
Err(ReadlineError::Interrupted) => break,
|
|
|
|
|
_ => break,
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-09-04 12:51:31 +02:00
|
|
|
|
|
|
|
|
|
if let Some(history_path) = history_path {
|
|
|
|
|
editor.save_history(&history_path).ok();
|
|
|
|
|
}
|
2020-06-04 18:55:08 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-12-13 18:12:33 +01:00
|
|
|
|
fn eval_repl(parser: &mut parser::Context, input: &str, precision: u32) {
|
2020-06-04 18:55:08 +02:00
|
|
|
|
match input {
|
|
|
|
|
"" => eprint!(""),
|
|
|
|
|
"clear" => print!("\x1B[2J"),
|
|
|
|
|
"exit" => process::exit(0),
|
2020-12-14 10:43:03 +01:00
|
|
|
|
"help" => print_cli_help(),
|
2020-12-13 18:12:33 +01:00
|
|
|
|
_ => output::eval(parser, input, precision),
|
2020-06-04 18:55:08 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-06-07 18:56:28 +02:00
|
|
|
|
|
2020-12-14 10:43:03 +01:00
|
|
|
|
fn print_cli_help() {
|
|
|
|
|
let help_text = include_str!("../help.txt");
|
|
|
|
|
println!("{}", help_text);
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-07 18:56:28 +02:00
|
|
|
|
struct LineHighlighter {}
|
|
|
|
|
|
|
|
|
|
impl Highlighter for LineHighlighter {
|
|
|
|
|
fn highlight<'l>(&self, line: &'l str, _: usize) -> Cow<'l, str> {
|
|
|
|
|
let mut coloured = line.to_string();
|
|
|
|
|
|
2020-06-09 15:43:53 +02:00
|
|
|
|
let reg = Regex::new(
|
|
|
|
|
r"(?x)
|
2021-12-29 18:32:11 +01:00
|
|
|
|
(?P<op>([+\-/*%^!×÷]|if|otherwise)) |
|
|
|
|
|
(?P<identifier>[^!-@\s_|^⌊⌋⌈⌉\[\]\{\}≠≥≤⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎]+(_\d+)?)",
|
2020-06-09 15:43:53 +02:00
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
2020-06-07 18:56:28 +02:00
|
|
|
|
|
|
|
|
|
coloured = reg
|
|
|
|
|
.replace_all(&coloured, |caps: &Captures| {
|
2020-06-09 15:43:53 +02:00
|
|
|
|
if let Some(cap) = caps.name("identifier") {
|
|
|
|
|
match cap.as_str() {
|
|
|
|
|
"rad" | "deg" | "°" => Colour::Yellow.paint(cap.as_str()).to_string(),
|
2020-12-14 11:25:08 +01:00
|
|
|
|
_ => Colour::Fixed(32).paint(cap.as_str()).to_string(),
|
2020-06-09 15:43:53 +02:00
|
|
|
|
}
|
|
|
|
|
} else if let Some(cap) = caps.name("op") {
|
|
|
|
|
Colour::Fixed(172).paint(cap.as_str()).to_string()
|
2020-06-07 18:56:28 +02:00
|
|
|
|
} else {
|
2020-06-09 15:43:53 +02:00
|
|
|
|
caps[0].to_string()
|
2020-06-07 18:56:28 +02:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
|
|
Owned(coloured)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct RLHelper {
|
|
|
|
|
highlighter: LineHighlighter,
|
|
|
|
|
validator: MatchingBracketValidator,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Helper for RLHelper {}
|
|
|
|
|
|
2020-12-10 23:40:29 +01:00
|
|
|
|
lazy_static! {
|
|
|
|
|
pub static ref COMPLETION_FUNCS: HashMap<&'static str, &'static str> = {
|
|
|
|
|
let mut m = HashMap::new();
|
|
|
|
|
m.insert("ceil", "⌈⌉");
|
|
|
|
|
m.insert("deg", "°");
|
|
|
|
|
m.insert("floor", "⌊⌋");
|
|
|
|
|
m.insert("gamma", "Γ");
|
|
|
|
|
m.insert("sum", "Σ()");
|
2021-06-01 15:52:41 +02:00
|
|
|
|
m.insert("prod", "∏()");
|
2021-05-16 15:15:34 +02:00
|
|
|
|
m.insert("integrate", "∫()");
|
2021-05-23 00:55:35 +02:00
|
|
|
|
m.insert("integral", "∫()");
|
2020-12-10 23:40:29 +01:00
|
|
|
|
m.insert("phi", "ϕ");
|
|
|
|
|
m.insert("pi", "π");
|
|
|
|
|
m.insert("sqrt", "√");
|
|
|
|
|
m.insert("tau", "τ");
|
|
|
|
|
m.insert("(", "()");
|
2021-05-31 13:46:06 +02:00
|
|
|
|
m.insert("!=", "≠");
|
|
|
|
|
m.insert(">=", "≥");
|
|
|
|
|
m.insert("<=", "≤");
|
2021-12-29 18:32:11 +01:00
|
|
|
|
m.insert("*", "×");
|
|
|
|
|
m.insert("/", "÷");
|
|
|
|
|
m.insert("asin", "sin⁻¹()");
|
|
|
|
|
m.insert("acos", "cos⁻¹()");
|
|
|
|
|
m.insert("atan", "tan⁻¹()");
|
|
|
|
|
m.insert("acot", "cot⁻¹()");
|
|
|
|
|
m.insert("acosec", "cosec⁻¹()");
|
|
|
|
|
m.insert("asec", "sec⁻¹()");
|
|
|
|
|
m.insert("asinh", "sinh⁻¹()");
|
|
|
|
|
m.insert("acosh", "cosh⁻¹()");
|
|
|
|
|
m.insert("atanh", "tanh⁻¹()");
|
|
|
|
|
m.insert("acoth", "coth⁻¹()");
|
|
|
|
|
m.insert("acosech", "cosech⁻¹()");
|
|
|
|
|
m.insert("asech", "sech⁻¹()");
|
|
|
|
|
m.insert("cbrt", "∛");
|
2020-12-10 23:40:29 +01:00
|
|
|
|
m
|
|
|
|
|
};
|
|
|
|
|
}
|
2020-06-07 18:56:28 +02:00
|
|
|
|
|
|
|
|
|
impl Completer for RLHelper {
|
|
|
|
|
type Candidate = String;
|
|
|
|
|
fn complete(
|
|
|
|
|
&self,
|
|
|
|
|
line: &str,
|
|
|
|
|
pos: usize,
|
|
|
|
|
_ctx: &rustyline::Context<'_>,
|
|
|
|
|
) -> Result<(usize, Vec<Self::Candidate>), ReadlineError> {
|
|
|
|
|
for key in COMPLETION_FUNCS.keys() {
|
2021-12-29 18:32:11 +01:00
|
|
|
|
let slice = &line[..pos];
|
|
|
|
|
if slice.ends_with(key) {
|
2020-06-08 21:58:55 +02:00
|
|
|
|
let value = *COMPLETION_FUNCS.get(key).unwrap();
|
|
|
|
|
return Ok((pos - key.len(), vec![value.to_string()]));
|
2020-06-07 18:56:28 +02:00
|
|
|
|
}
|
2021-12-29 18:32:11 +01:00
|
|
|
|
|
|
|
|
|
let mut subscript_digits = String::new();
|
|
|
|
|
for c in slice.chars().rev() {
|
|
|
|
|
if c.is_digit(10) {
|
|
|
|
|
subscript_digits.insert(0, c);
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if subscript_digits.len() > 0 {
|
|
|
|
|
let value = kalk::text_utils::digits_to_subscript(subscript_digits.chars());
|
|
|
|
|
return Ok((pos - subscript_digits.chars().count() - 1, vec![value]));
|
|
|
|
|
}
|
2020-06-07 18:56:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok((0, vec![line.to_string()]))
|
|
|
|
|
}
|
2020-06-08 21:58:55 +02:00
|
|
|
|
|
|
|
|
|
fn update(&self, line: &mut rustyline::line_buffer::LineBuffer, start: usize, elected: &str) {
|
|
|
|
|
line.backspace(line.pos() - start);
|
|
|
|
|
line.insert_str(line.pos(), elected);
|
2021-12-29 18:32:11 +01:00
|
|
|
|
line.move_forward(if elected.ends_with(")") {
|
|
|
|
|
elected.chars().count() - 1
|
|
|
|
|
} else {
|
|
|
|
|
elected.chars().count()
|
2020-06-09 10:37:57 +02:00
|
|
|
|
});
|
2020-06-08 21:58:55 +02:00
|
|
|
|
}
|
2020-06-07 18:56:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Highlighter for RLHelper {
|
|
|
|
|
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
|
2020-06-07 19:02:07 +02:00
|
|
|
|
Owned(Colour::Fixed(244).paint(hint).to_string())
|
2020-06-07 18:56:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
|
|
|
|
|
self.highlighter.highlight(line, pos)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn highlight_candidate<'c>(
|
|
|
|
|
&self,
|
|
|
|
|
candidate: &'c str,
|
|
|
|
|
_completion: rustyline::CompletionType,
|
|
|
|
|
) -> Cow<'c, str> {
|
|
|
|
|
self.highlighter.highlight(candidate, 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn highlight_char(&self, line: &str, _: usize) -> bool {
|
|
|
|
|
line.len() > 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Hinter for RLHelper {
|
2020-12-10 20:31:07 +01:00
|
|
|
|
type Hint = String;
|
|
|
|
|
|
2020-06-09 16:41:04 +02:00
|
|
|
|
fn hint(&self, _: &str, _: usize, _: &rustyline::Context) -> Option<String> {
|
|
|
|
|
None
|
2020-06-07 18:56:28 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Validator for RLHelper {
|
|
|
|
|
fn validate(&self, ctx: &mut ValidationContext) -> Result<ValidationResult, ReadlineError> {
|
2021-06-03 11:41:56 +02:00
|
|
|
|
let mut group_symbol_count = vec![0i32, 0i32, 0i32];
|
|
|
|
|
|
|
|
|
|
for c in ctx.input().chars() {
|
|
|
|
|
match c {
|
|
|
|
|
'⌈' | '⌉' => group_symbol_count[0] += 1,
|
|
|
|
|
'⌊' | '⌋' => group_symbol_count[1] += 1,
|
|
|
|
|
'|' => group_symbol_count[2] += 1,
|
|
|
|
|
_ => (),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !group_symbol_count.into_iter().all(|x| x % 2 == 0) {
|
|
|
|
|
Ok(ValidationResult::Incomplete)
|
|
|
|
|
} else {
|
|
|
|
|
self.validator.validate(ctx)
|
|
|
|
|
}
|
2020-06-07 18:56:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn validate_while_typing(&self) -> bool {
|
|
|
|
|
self.validator.validate_while_typing()
|
|
|
|
|
}
|
|
|
|
|
}
|