diff --git a/Cargo.lock b/Cargo.lock index 0b95324..da9c4bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,6 +83,7 @@ dependencies = [ "backtrace", "complexpr", "rustyline", + "rustyline-derive", ] [[package]] @@ -384,6 +385,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustyline-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "107c3d5d7f370ac09efa62a78375f94d94b8a33c61d8c278b96683fb4dbf2d8d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "scopeguard" version = "1.1.0" diff --git a/complexpr-bin/Cargo.toml b/complexpr-bin/Cargo.toml index 71fd0b5..830640a 100644 --- a/complexpr-bin/Cargo.toml +++ b/complexpr-bin/Cargo.toml @@ -9,3 +9,4 @@ edition = "2021" complexpr = { path = "../complexpr" } rustyline = "10.0.0" backtrace = "0.3.66" +rustyline-derive = "0.7.0" diff --git a/complexpr-bin/src/helper.rs b/complexpr-bin/src/helper.rs new file mode 100644 index 0000000..9667606 --- /dev/null +++ b/complexpr-bin/src/helper.rs @@ -0,0 +1,185 @@ +use std::borrow::Cow; + +use complexpr::env::EnvRef; +use rustyline::{completion::Completer, validate::Validator}; +use rustyline::highlight::Highlighter; +use rustyline::hint::HistoryHinter; +use rustyline::validate::{MatchingBracketValidator, ValidationResult, ValidationContext}; +use rustyline_derive::{Helper, Hinter}; + +#[derive(Helper, Hinter)] +pub struct CxprHelper { + #[rustyline(Validator)] + pub validator: MatchingBracketValidator, + #[rustyline(Hinter)] + pub hinter: HistoryHinter, + pub colored_prompt: String, + pub env: EnvRef +} + +fn find_paired_bracket(line: &str, pos: usize) -> Result { + if pos >= line.len() { + return Err(false) + } + 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 Err(false), + }; + 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 Ok(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 Ok(pos - idx - 1) + } else { + depth -= 1; + } + } + idx += 1; + } + } + Err(true) +} + +impl Highlighter for CxprHelper { + fn highlight_prompt<'b, 's: 'b, 'p: 'b>( + &'s self, + prompt: &'p str, + default: bool, + ) -> Cow<'b, str> { + if default { + Cow::Borrowed(&self.colored_prompt) + } else { + Cow::Borrowed(prompt) + } + } + + fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { + match find_paired_bracket(line, pos) { + Err(false) => Cow::Borrowed(line), + Err(true) => { + let mut line = line.to_owned(); + line.replace_range(pos..=pos, &format!("\x1b[91m{}\x1b[0m", line.as_bytes()[pos] as char)); + Cow::Owned(line) + }, + Ok(match_pos) => { + let fst = pos.min(match_pos); + let snd = pos.max(match_pos); + let mut line = line.to_owned(); + line.replace_range(snd..=snd, &format!("\x1b[92m{}\x1b[0m", line.as_bytes()[snd] as char)); + line.replace_range(fst..=fst, &format!("\x1b[92m{}\x1b[0m", line.as_bytes()[fst] as char)); + Cow::Owned(line) + }, + } + } + + fn highlight_char(&self, line: &str, _: usize) -> bool { + !line.is_empty() + } +} + +fn validate_brackets(input: &str) -> ValidationResult { + let mut stack = vec![]; + let mut in_string = false; + let mut in_char = false; + let mut in_escape = false; + for c in input.chars() { + if in_string { + if in_escape { + in_escape = false + } else if c == '\\' { + in_escape = true + } else if c == '"' { + in_string = false + } + } else if in_char { + if in_escape { + in_escape = false + } else if c == '\\' { + in_escape = true + } else if c == '\'' { + in_char = false + } + } else { + match c { + '(' | '[' | '{' => stack.push(c), + ')' | ']' | '}' => match (stack.pop(), c) { + (Some('('), ')') | (Some('['), ']') | (Some('{'), '}') => (), + (Some(c), _) => return ValidationResult::Invalid( + Some(format!(" << Mismatched brackets: {:?} is not properly closed", c)) + ), + (None, c) => return ValidationResult::Invalid( + Some(format!(" << Mismatched brackets: {:?} is unpaired", c)) + ), + }, + '"' => in_string = true, + '\'' => in_char = true, + _ => {} + } + } + } + if in_string { + ValidationResult::Incomplete + } else if stack.is_empty() { + ValidationResult::Valid(None) + } else { + ValidationResult::Incomplete + } +} + +impl Validator for CxprHelper { + fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result { + Ok(validate_brackets(ctx.input())) + } +} + +impl Completer for CxprHelper { + fn complete(&self, line: &str, pos: usize, _: &rustyline::Context<'_>) + -> rustyline::Result<(usize, Vec)> { + let mut res = String::new(); + for ch in line[..pos].chars().rev() { + match ch { + '0'..='9' | 'a'..='z' | 'A'..='Z' | '_' => res.push(ch), + _ => break + } + } + let res: String = res.chars().rev().collect(); + let mut keys = self.env.borrow().items().keys() + .filter(|x| x.starts_with(&res)) + .map(|s| s.to_string()) + .collect::>(); + keys.sort(); + Ok((pos - res.len(), keys)) + } + + fn update(&self, line: &mut rustyline::line_buffer::LineBuffer, start: usize, elected: &str) { + let end = line.pos(); + line.replace(start..end, elected); + } + + type Candidate = String; +} diff --git a/complexpr-bin/src/main.rs b/complexpr-bin/src/main.rs index a86bd12..0d3e3b9 100644 --- a/complexpr-bin/src/main.rs +++ b/complexpr-bin/src/main.rs @@ -1,8 +1,11 @@ -use std::{rc::Rc, cell::RefCell, fs, panic::{self, PanicInfo}}; +use std::{fs, panic::{self, PanicInfo}}; use backtrace::Backtrace; use complexpr::{env::Environment, interpreter::interpret, value::Value, stdlib}; -use rustyline::{self, error::ReadlineError}; +use rustyline::{self, error::ReadlineError, Config, CompletionType, EditMode, hint::HistoryHinter, validate::MatchingBracketValidator, Editor}; + +mod helper; +use helper::CxprHelper; const C_RESET: &str = "\x1b[0m"; const C_BLUE: &str = "\x1b[94m"; @@ -42,12 +45,24 @@ fn main() -> Result<(), Box> { } fn repl() -> Result<(), Box> { + let config = Config::builder() + .history_ignore_space(true) + .completion_type(CompletionType::List) + .edit_mode(EditMode::Emacs) + .build(); + let env = Environment::new().wrap(); + let h = CxprHelper { + hinter: HistoryHinter {}, + colored_prompt: PROMPT.to_owned(), + validator: MatchingBracketValidator::new(), + env: env.clone(), + }; + let mut rl = Editor::with_config(config)?; + rl.set_helper(Some(h)); println!("Press {}Ctrl+D{} to exit.", C_BLUE, C_RESET); - let mut rl = rustyline::Editor::<()>::new()?; - let env = Rc::new(RefCell::new(Environment::new())); stdlib::load(&mut env.borrow_mut()); loop { - let readline = rl.readline(PROMPT); + let readline = rl.readline(">> "); match readline { Ok(line) => { let result = interpret(&line, None, Some(env.clone()), true); diff --git a/complexpr/src/env.rs b/complexpr/src/env.rs index f81c4e7..4bd3658 100644 --- a/complexpr/src/env.rs +++ b/complexpr/src/env.rs @@ -47,6 +47,10 @@ impl Environment { } } } + + pub fn items(&self) -> &HashMap, Value> { + &self.map + } } impl Default for Environment { diff --git a/complexpr/src/eval.rs b/complexpr/src/eval.rs index 74f572c..e8bd258 100644 --- a/complexpr/src/eval.rs +++ b/complexpr/src/eval.rs @@ -4,6 +4,12 @@ use num_traits::Pow; use crate::{value::{Value, Complex, Func, CIterator}, expr::{Stmt, Expr}, token::{TokenType, Token, OpType}, RuntimeError, Position, env::{EnvRef, Environment}}; +thread_local!(static PIPE_NAME: Option> = Some(Rc::from(""))); +thread_local!(static FORLOOP_NAME: Option> = Some(Rc::from(""))); +fn exit_pipe(pos: &Position) -> impl FnOnce(RuntimeError) -> RuntimeError + '_ { + |e: RuntimeError| e.exit_fn(PIPE_NAME.with(|x| x.clone()), pos.clone()) +} + #[derive(Debug)] pub enum Unwind { @@ -75,7 +81,7 @@ pub fn eval_stmt(stmt: &Stmt, env: EnvRef) -> Result<(), Unwind> { } if let Ok(i) = iterator { for v in i { - let v = v.map_err(|e| e.complete(iter_pos.clone()))?; + let v = v.map_err(|e| e.exit_fn(FORLOOP_NAME.with(|x| x.clone()), iter_pos.clone()))?; let env = env.clone(); env.borrow_mut().set(name.clone(), v).expect("unreachable"); match eval_stmt(stmt, env) { @@ -157,13 +163,15 @@ pub fn eval_expr(expr: &Expr, env: EnvRef) -> Result { Ok(Value::from(map)) }, Expr::FuncCall { func, args, pos } => { - let func = eval_expr(func, env.clone())?; + let lhs = eval_expr(func, env.clone())?; let mut arg_values = Vec::with_capacity(args.len()); for arg in args { let result = eval_expr(arg, env.clone())?; arg_values.push(result); } - func.call(arg_values).map_err(|e| e.complete(pos.clone())) + let func = lhs.as_func() + .map_err(|e| RuntimeError::new(e, pos.clone()))?; + func.call(arg_values).map_err(|e| e.exit_fn(func.name(), pos.clone())) }, Expr::Index { lhs, index, pos } => { let l = eval_expr(lhs, env.clone())?; @@ -325,7 +333,8 @@ pub fn eval_comp(lhs: &Expr, rhs: &Expr, op: &Token, env: EnvRef) -> Result, data: Rc>>, iter_data: Rc>>) -> Result { let f = &data.borrow()[0]; if let Some(next) = iter_data.borrow_mut()[0].next() { - f.call(vec![next?]) + let func = f.as_func()?; + func.call(vec![next?]) } else { Ok(Value::Nil) } @@ -337,7 +346,8 @@ fn pipequestion_inner(_: Vec, data: Rc>>, iter_data: R let next = iter_data.borrow_mut()[0].next(); if let Some(next) = next { let next = next?; - let success = f.call(vec![next.clone()])?.truthy(); + let func = f.as_func()?; + let success = func.call(vec![next.clone()])?.truthy(); if success { return Ok(next) } @@ -347,20 +357,22 @@ fn pipequestion_inner(_: Vec, data: Rc>>, iter_data: R } } + pub fn eval_pipeline(lhs: &Expr, rhs: &Expr, op: &Token, env: EnvRef) -> Result { let l = eval_expr(lhs, env.clone())?; let r = eval_expr(rhs, env)?; - eval_pipeline_inner(l, r, op).map_err(|e| e.complete(op.pos.clone())) + let f = r.as_func().map_err(|e| RuntimeError::new(e, op.pos.clone()))?; + eval_pipeline_inner(l, f, op).map_err(exit_pipe(&op.pos)) } #[allow(clippy::needless_collect)] // collect is necesary to allow for rev() call -fn eval_pipeline_inner(l: Value, r: Value, op: &Token) -> Result { +fn eval_pipeline_inner(l: Value, r: &Func, op: &Token) -> Result { match op.ty { TokenType::PipePoint => r.call(vec![l]), TokenType::PipeColon => { Ok(Value::Func(Func::BuiltinClosure { arg_count: 0, - data: Rc::new(RefCell::new(vec![r])), + data: Rc::new(RefCell::new(vec![Value::Func(r.clone())])), iter_data: Rc::new(RefCell::new(vec![l.iter()?])), func: pipecolon_inner, })) @@ -368,7 +380,7 @@ fn eval_pipeline_inner(l: Value, r: Value, op: &Token) -> Result { Ok(Value::Func(Func::BuiltinClosure { arg_count: 0, - data: Rc::new(RefCell::new(vec![r])), + data: Rc::new(RefCell::new(vec![Value::Func(r.clone())])), iter_data: Rc::new(RefCell::new(vec![l.iter()?])), func: pipequestion_inner, })) @@ -377,12 +389,12 @@ fn eval_pipeline_inner(l: Value, r: Value, op: &Token) -> Result Result>>(); for v in lst.into_iter().rev() { - let v = v.map_err(|e| e.complete(op.pos.clone()))?; + let v = v.map_err(exit_pipe(&op.pos))?; if first_iter { result = v; first_iter = false; } else { - result = r.call(vec![v, result]).map_err(|e| e.complete(op.pos.clone()))?; + result = r.call(vec![v, result]).map_err(exit_pipe(&op.pos))?; } } Ok(result) @@ -414,9 +426,11 @@ pub fn eval_ternary(arg1: &Expr, arg2: &Expr, arg3: &Expr, op: &Token, env: EnvR let iter = eval_expr(arg1, env.clone())?; let mut result = eval_expr(arg2, env.clone())?; let func = eval_expr(arg3, env)?; + let func = func.as_func() + .map_err(|e| RuntimeError::new(e, op.pos.clone()))?; for v in iter.iter().map_err(|e| RuntimeError::new(e, op.pos.clone()))? { - let v = v.map_err(|e| e.complete(op.pos.clone()))?; - result = func.call(vec![result, v]).map_err(|e| e.complete(op.pos.clone()))?; + let v = v.map_err(exit_pipe(&op.pos))?; + result = func.call(vec![result, v]).map_err(exit_pipe(&op.pos))?; } Ok(result) }, @@ -424,10 +438,12 @@ pub fn eval_ternary(arg1: &Expr, arg2: &Expr, arg3: &Expr, op: &Token, env: EnvR let iter = eval_expr(arg1, env.clone())?; let mut result = eval_expr(arg2, env.clone())?; let func = eval_expr(arg3, env)?; + let func = func.as_func() + .map_err(|e| RuntimeError::new(e, op.pos.clone()))?; let lst = iter.iter().map_err(|e| RuntimeError::new(e, op.pos.clone()))?.collect::>>(); for v in lst.into_iter().rev() { - let v = v.map_err(|e| e.complete(op.pos.clone()))?; - result = func.call(vec![v, result]).map_err(|e| e.complete(op.pos.clone()))?; + let v = v.map_err(exit_pipe(&op.pos))?; + result = func.call(vec![v, result]).map_err(exit_pipe(&op.pos))?; } Ok(result) }, diff --git a/complexpr/src/lib.rs b/complexpr/src/lib.rs index 4c3660d..ea3db59 100644 --- a/complexpr/src/lib.rs +++ b/complexpr/src/lib.rs @@ -18,6 +18,16 @@ pub struct Position { pub file: Option> } +impl fmt::Display for Position { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}:{}", + self.file.as_ref().map(|x| x.as_ref()).unwrap_or(""), + self.line, + self.col + ) + } +} + #[derive(Debug)] pub struct ParserError { @@ -27,7 +37,7 @@ pub struct ParserError { #[derive(Debug)] pub struct Stackframe { - pub pos: Position, + pub pos: Option, pub fn_name: Option>, } @@ -48,7 +58,7 @@ impl RuntimeError { } } - pub fn new_incomplete(message: S) -> Self + pub fn new_no_pos(message: S) -> Self where S: Into { Self { message: message.into(), @@ -57,21 +67,14 @@ impl RuntimeError { } } - pub fn complete(mut self, last_pos: Position) -> Self { - if self.last_pos.is_none() { - self.last_pos = Some(last_pos); - } - self - } - pub fn exit_fn(mut self, fn_name: Option>, pos: Position) -> Self { - self.stacktrace.push(Stackframe { pos: self.last_pos.expect("RuntimeError never completed after construction"), fn_name }); + self.stacktrace.push(Stackframe { pos: self.last_pos, fn_name }); self.last_pos = Some(pos); self } pub fn finish(mut self, ctx_name: Option>) -> Self { - self.stacktrace.push(Stackframe { pos: self.last_pos.expect("RuntimeError never completed after construction"), fn_name: ctx_name }); + self.stacktrace.push(Stackframe { pos: self.last_pos, fn_name: ctx_name }); self.last_pos = None; self } @@ -79,13 +82,13 @@ impl RuntimeError { impl From for RuntimeError { fn from(s: String) -> Self { - Self::new_incomplete(s) + Self::new_no_pos(s) } } impl From<&str> for RuntimeError { fn from(s: &str) -> Self { - Self::new_incomplete(s) + Self::new_no_pos(s) } } @@ -102,14 +105,13 @@ impl fmt::Display for ParserError { impl fmt::Display for RuntimeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "{}", self.message)?; + write!(f, "{}", self.message)?; for frame in &self.stacktrace { - write!(f, "\n In {} at {}:{}:{}", - frame.fn_name.as_ref().map(|o| o.as_ref()).unwrap_or(""), - frame.pos.file.as_ref().map(|o| o.as_ref()).unwrap_or(""), - frame.pos.line, - frame.pos.col - )?; + let fn_name = frame.fn_name.as_ref().map(|o| o.as_ref()).unwrap_or(""); + match &frame.pos { + Some(pos) => write!(f, "\n In {} at {}", fn_name, pos)?, + None => write!(f, "\n In {} at ", fn_name)?, + } } Ok(()) } diff --git a/complexpr/src/stdlib/mod.rs b/complexpr/src/stdlib/mod.rs index a94f440..4470330 100644 --- a/complexpr/src/stdlib/mod.rs +++ b/complexpr/src/stdlib/mod.rs @@ -131,7 +131,7 @@ fn fn_range(args: Vec) -> Result { } fn fn_len(args: Vec) -> Result { - Ok(Value::Int(args[0].len().map_err(RuntimeError::new_incomplete)? as i64)) + Ok(Value::Int(args[0].len().map_err(RuntimeError::new_no_pos)? as i64)) } fn fn_has(args: Vec) -> Result { @@ -213,9 +213,9 @@ fn skip_inner(_: Vec, data: Rc>>, iter_data: Rc) -> Result { let n = match args[0] { - Value::Int(n) if n <= 0 => return Err(RuntimeError::new_incomplete("First argument to skip must be nonnegative")), + Value::Int(n) if n <= 0 => return Err(RuntimeError::new_no_pos("First argument to skip must be nonnegative")), Value::Int(n) => n, - _ => return Err(RuntimeError::new_incomplete("First argument to skip must be an integer")) + _ => return Err(RuntimeError::new_no_pos("First argument to skip must be an integer")) }; let it = args[1].iter()?; Ok(Value::Func(Func::BuiltinClosure { @@ -227,7 +227,7 @@ fn fn_skip(args: Vec) -> Result { } fn fn_forall(args: Vec) -> Result { - let func = &args[0]; + let func = &args[0].as_func()?; for item in args[1].iter()? { let item = item?; if !func.call(vec![item])?.truthy() { @@ -238,7 +238,7 @@ fn fn_forall(args: Vec) -> Result { } fn fn_exists(args: Vec) -> Result { - let func = &args[0]; + let func = &args[0].as_func()?; for item in args[1].iter()? { let item = item?; if func.call(vec![item])?.truthy() { diff --git a/complexpr/src/value.rs b/complexpr/src/value.rs index 2c11cfc..e90016c 100644 --- a/complexpr/src/value.rs +++ b/complexpr/src/value.rs @@ -72,11 +72,11 @@ impl Func { } } - pub fn name(&self) -> Option<&str> { + pub fn name(&self) -> Option> { match self { - Self::Builtin { name, .. } => Some(name.as_ref()), + Self::Builtin { name, .. } => Some(name.clone()), Self::BuiltinClosure { .. } => None, - Self::Func { name, .. } => name.as_ref().map(|s| s.as_ref()), + Self::Func { name, .. } => name.clone(), Self::Partial { inner, .. } => inner.name() } } @@ -109,7 +109,7 @@ impl Func { inner.call(filled_args) } }, - Ordering::Less if arg_values.is_empty() => Err(RuntimeError::new_incomplete( + Ordering::Less if arg_values.is_empty() => Err(RuntimeError::new_no_pos( format!("Cannot call this function with zero arguments: expected {}", self.arg_count()) )), Ordering::Less => match self { @@ -120,7 +120,7 @@ impl Func { } f => Ok(Value::Func(Func::Partial { inner: Box::new(f.clone()), filled_args: arg_values })) }, - Ordering::Greater => Err(RuntimeError::new_incomplete( + Ordering::Greater => Err(RuntimeError::new_no_pos( format!("Too many arguments for function: expected {}, got {}", self.arg_count(), arg_values.len()) )) } @@ -236,11 +236,10 @@ impl Value { } } - pub fn call(&self, args: Vec) -> Result { - if let Value::Func(f) = self { - f.call(args) - } else { - Err(RuntimeError::new_incomplete("Cannot call")) + pub fn as_func(&self) -> Result<&Func, String> { + match self { + Value::Func(f) => Ok(f), + v => Err(format!("{:?} is not a function", v)) } }