improved repl & errors

This commit is contained in:
TriMill 2022-09-18 02:05:01 -04:00
parent 48cc98a6ed
commit 674d2ced7d
9 changed files with 291 additions and 57 deletions

12
Cargo.lock generated
View File

@ -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"

View File

@ -9,3 +9,4 @@ edition = "2021"
complexpr = { path = "../complexpr" }
rustyline = "10.0.0"
backtrace = "0.3.66"
rustyline-derive = "0.7.0"

185
complexpr-bin/src/helper.rs Normal file
View File

@ -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<usize, bool> {
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<ValidationResult> {
Ok(validate_brackets(ctx.input()))
}
}
impl Completer for CxprHelper {
fn complete(&self, line: &str, pos: usize, _: &rustyline::Context<'_>)
-> rustyline::Result<(usize, Vec<Self::Candidate>)> {
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::<Vec<String>>();
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;
}

View File

@ -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<dyn std::error::Error>> {
}
fn repl() -> Result<(), Box<dyn std::error::Error>> {
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);

View File

@ -47,6 +47,10 @@ impl Environment {
}
}
}
pub fn items(&self) -> &HashMap<Rc<str>, Value> {
&self.map
}
}
impl Default for Environment {

View File

@ -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<Rc<str>> = Some(Rc::from("<pipeline>")));
thread_local!(static FORLOOP_NAME: Option<Rc<str>> = Some(Rc::from("<for loop>")));
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<Value, RuntimeError> {
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<Valu
fn pipecolon_inner(_: Vec<Value>, data: Rc<RefCell<Vec<Value>>>, iter_data: Rc<RefCell<Vec<CIterator>>>) -> Result<Value, RuntimeError> {
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<Value>, data: Rc<RefCell<Vec<Value>>>, 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<Value>, data: Rc<RefCell<Vec<Value>>>, iter_data: R
}
}
pub fn eval_pipeline(lhs: &Expr, rhs: &Expr, op: &Token, env: EnvRef) -> Result<Value, RuntimeError> {
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<Value, RuntimeError> {
fn eval_pipeline_inner(l: Value, r: &Func, op: &Token) -> Result<Value, RuntimeError> {
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<Value, RuntimeE
TokenType::PipeQuestion => {
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<Value, RuntimeE
let mut result = Value::Nil;
let mut first_iter = true;
for v in l.iter().map_err(|e| RuntimeError::new(e, op.pos.clone()))? {
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![result, v]).map_err(|e| e.complete(op.pos.clone()))?;
result = r.call(vec![result, v]).map_err(exit_pipe(&op.pos))?;
}
}
Ok(result)
@ -392,12 +404,12 @@ fn eval_pipeline_inner(l: Value, r: Value, op: &Token) -> Result<Value, RuntimeE
let mut first_iter = true;
let lst = l.iter().map_err(|e| RuntimeError::new(e, op.pos.clone()))?.collect::<Vec<Result<Value, RuntimeError>>>();
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::<Vec<Result<Value, RuntimeError>>>();
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)
},

View File

@ -18,6 +18,16 @@ pub struct Position {
pub file: Option<Rc<str>>
}
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("<unknown>"),
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<Position>,
pub fn_name: Option<Rc<str>>,
}
@ -48,7 +58,7 @@ impl RuntimeError {
}
}
pub fn new_incomplete<S>(message: S) -> Self
pub fn new_no_pos<S>(message: S) -> Self
where S: Into<String> {
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<Rc<str>>, 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<Rc<str>>) -> 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<String> 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("<anonymous fn>"),
frame.pos.file.as_ref().map(|o| o.as_ref()).unwrap_or("<unknown>"),
frame.pos.line,
frame.pos.col
)?;
let fn_name = frame.fn_name.as_ref().map(|o| o.as_ref()).unwrap_or("<anonymous fn>");
match &frame.pos {
Some(pos) => write!(f, "\n In {} at {}", fn_name, pos)?,
None => write!(f, "\n In {} at <unknown>", fn_name)?,
}
}
Ok(())
}

View File

@ -131,7 +131,7 @@ fn fn_range(args: Vec<Value>) -> Result<Value, RuntimeError> {
}
fn fn_len(args: Vec<Value>) -> Result<Value, RuntimeError> {
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<Value>) -> Result<Value, RuntimeError> {
@ -213,9 +213,9 @@ fn skip_inner(_: Vec<Value>, data: Rc<RefCell<Vec<Value>>>, iter_data: Rc<RefCel
fn fn_skip(args: Vec<Value>) -> Result<Value, RuntimeError> {
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<Value>) -> Result<Value, RuntimeError> {
}
fn fn_forall(args: Vec<Value>) -> Result<Value, RuntimeError> {
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<Value>) -> Result<Value, RuntimeError> {
}
fn fn_exists(args: Vec<Value>) -> Result<Value, RuntimeError> {
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() {

View File

@ -72,11 +72,11 @@ impl Func {
}
}
pub fn name(&self) -> Option<&str> {
pub fn name(&self) -> Option<Rc<str>> {
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<Value>) -> Result<Value, RuntimeError> {
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))
}
}