leo_errors/emitter/
mod.rsuse crate::LeoWarning;
use super::LeoError;
use core::{default::Default, fmt};
use std::{cell::RefCell, rc::Rc};
pub trait Emitter {
fn emit_err(&mut self, err: LeoError);
fn last_emitted_err_code(&self) -> Option<i32>;
fn emit_warning(&mut self, warning: LeoWarning);
}
pub struct StderrEmitter {
last_error_code: Option<i32>,
}
impl Emitter for StderrEmitter {
fn emit_err(&mut self, err: LeoError) {
self.last_error_code = Some(err.exit_code());
eprintln!("{err}");
}
fn last_emitted_err_code(&self) -> Option<i32> {
self.last_error_code
}
fn emit_warning(&mut self, warning: LeoWarning) {
eprintln!("{warning}");
}
}
#[derive(Debug)]
pub struct Buffer<T>(Vec<T>);
impl<T> Default for Buffer<T> {
fn default() -> Self {
Self(Vec::new())
}
}
impl<T> Buffer<T> {
pub fn push(&mut self, x: T) {
self.0.push(x);
}
pub fn into_inner(self) -> Vec<T> {
self.0
}
pub fn last_entry(&self) -> Option<&T> {
self.0.last()
}
}
impl<T: fmt::Display> fmt::Display for Buffer<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut iter = self.0.iter();
if let Some(x) = iter.next() {
x.fmt(f)?;
}
for x in iter {
f.write_fmt(format_args!("\n{x}"))?;
}
Ok(())
}
}
pub type ErrBuffer = Buffer<LeoError>;
pub type WarningBuffer = Buffer<LeoWarning>;
#[derive(Default, Clone)]
pub struct BufferEmitter(Rc<RefCell<ErrBuffer>>, Rc<RefCell<WarningBuffer>>);
impl BufferEmitter {
pub fn new() -> Self {
BufferEmitter(<_>::default(), <_>::default())
}
pub fn extract_errs(&self) -> ErrBuffer {
self.0.take()
}
pub fn extract_warnings(&self) -> WarningBuffer {
self.1.take()
}
}
impl Emitter for BufferEmitter {
fn emit_err(&mut self, err: LeoError) {
self.0.borrow_mut().push(err);
}
fn last_emitted_err_code(&self) -> Option<i32> {
let temp = &*self.0.borrow();
temp.last_entry().map(|entry| entry.exit_code())
}
fn emit_warning(&mut self, warning: LeoWarning) {
self.1.borrow_mut().push(warning);
}
}
struct HandlerInner {
err_count: usize,
warn_count: usize,
emitter: Box<dyn Emitter>,
}
impl HandlerInner {
fn emit_err(&mut self, err: LeoError) {
self.err_count = self.err_count.saturating_add(1);
self.emitter.emit_err(err);
}
fn last_emitted_err_code(&self) -> Option<i32> {
self.emitter.last_emitted_err_code()
}
fn emit_warning(&mut self, warning: LeoWarning) {
self.warn_count = self.warn_count.saturating_add(1);
self.emitter.emit_warning(warning);
}
}
pub struct Handler {
inner: RefCell<HandlerInner>,
}
impl Default for Handler {
fn default() -> Self {
Self::new(Box::new(StderrEmitter { last_error_code: None }))
}
}
impl Handler {
pub fn new(emitter: Box<dyn Emitter>) -> Self {
let inner = RefCell::new(HandlerInner { err_count: 0, warn_count: 0, emitter });
Self { inner }
}
pub fn new_with_buf() -> (Self, BufferEmitter) {
let buf = BufferEmitter::default();
let handler = Self::new(Box::new(buf.clone()));
(handler, buf)
}
pub fn with<T>(logic: impl for<'a> FnOnce(&'a Handler) -> Result<T, LeoError>) -> Result<T, ErrBuffer> {
let (handler, buf) = Handler::new_with_buf();
handler.extend_if_error(logic(&handler)).map_err(|_| buf.extract_errs())
}
pub fn emit_err<E: Into<LeoError>>(&self, err: E) {
self.inner.borrow_mut().emit_err(err.into());
}
pub fn emit_warning(&self, warning: LeoWarning) {
self.inner.borrow_mut().emit_warning(warning);
}
pub fn fatal_err(&self, err: LeoError) -> ! {
let code = err.exit_code();
self.emit_err(err);
std::process::exit(code);
}
pub fn err_count(&self) -> usize {
self.inner.borrow().err_count
}
pub fn warning_count(&self) -> usize {
self.inner.borrow().warn_count
}
pub fn had_errors(&self) -> bool {
self.err_count() > 0
}
pub fn last_err(&self) -> Result<(), Box<LeoError>> {
if let Some(code) = self.inner.borrow().last_emitted_err_code() {
Err(Box::new(LeoError::LastErrorCode(code)))
} else {
Ok(())
}
}
#[allow(clippy::result_unit_err)]
pub fn extend_if_error<T>(&self, res: Result<T, LeoError>) -> Result<T, ()> {
match res {
Ok(_) if self.had_errors() => Err(()),
Ok(x) => Ok(x),
Err(e) => {
self.emit_err(e);
Err(())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ParserError;
use leo_span::{Span, symbol::create_session_if_not_set_then};
#[test]
fn fresh_no_errors() {
let handler = Handler::new(Box::new(BufferEmitter::new()));
assert_eq!(handler.err_count(), 0);
assert!(!handler.had_errors());
}
#[test]
fn buffer_works() {
create_session_if_not_set_then(|_| {
let count_err = |s: String| s.lines().filter(|l| l.contains("Error")).count();
let res: Result<(), _> = Handler::with(|h| {
let s = Span::default();
assert_eq!(h.err_count(), 0);
h.emit_err(ParserError::invalid_import_list(s));
assert_eq!(h.err_count(), 1);
h.emit_err(ParserError::unexpected_eof(s));
assert_eq!(h.err_count(), 2);
Err(ParserError::spread_in_array_init(s).into())
});
assert_eq!(count_err(res.unwrap_err().to_string()), 3);
let res: Result<(), _> = Handler::with(|h| {
let s = Span::default();
h.emit_err(ParserError::invalid_import_list(s));
h.emit_err(ParserError::unexpected_eof(s));
Ok(())
});
assert_eq!(count_err(res.unwrap_err().to_string()), 2);
Handler::with(|_| Ok(())).unwrap();
})
}
}