significantly better error message handling everywhere

This commit is contained in:
elkowar 2021-07-23 13:45:30 +02:00
parent a8b256987c
commit 9f70a22cf0
No known key found for this signature in database
GPG key ID: E321AD71B1D1F27F
21 changed files with 181 additions and 187 deletions

View file

@ -1,9 +1,4 @@
use crate::{ use crate::{config, display_backend, error_handling_ctx, eww_state, script_var_handler::*, EwwPaths};
config::{self, EwwConfig},
display_backend, error_handling_ctx, eww_state,
script_var_handler::*,
EwwPaths,
};
use anyhow::*; use anyhow::*;
use debug_stub_derive::*; use debug_stub_derive::*;
use eww_shared_util::VarName; use eww_shared_util::VarName;
@ -14,10 +9,7 @@ use simplexpr::dynval::DynVal;
use std::collections::HashMap; use std::collections::HashMap;
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use yuck::{ use yuck::{
config::{ config::window_geometry::{AnchorPoint, WindowGeometry},
file_provider::FsYuckFiles,
window_geometry::{AnchorPoint, WindowGeometry},
},
value::Coords, value::Coords,
}; };
@ -123,20 +115,18 @@ impl App {
&mut error_handling_ctx::ERROR_HANDLING_CTX.lock().unwrap(), &mut error_handling_ctx::ERROR_HANDLING_CTX.lock().unwrap(),
&self.paths.get_yuck_path(), &self.paths.get_yuck_path(),
); );
match config_result { match config_result.and_then(|new_config| self.load_config(new_config)) {
Ok(new_config) => self.handle_command(DaemonCommand::UpdateConfig(new_config)), Ok(()) => {}
Err(e) => {
errors.push(e);
}
}
let css_result = crate::util::parse_scss_from_file(&self.paths.get_eww_scss_path());
match css_result {
Ok(new_css) => self.handle_command(DaemonCommand::UpdateCss(new_css)),
Err(e) => errors.push(e), Err(e) => errors.push(e),
} }
let errors = errors.into_iter().map(|e| error_handling_ctx::format_error(e)).join("\n"); let css_result = crate::util::parse_scss_from_file(&self.paths.get_eww_scss_path());
match css_result.and_then(|css| self.load_css(&css)) {
Ok(()) => {}
Err(e) => errors.push(e),
}
let errors = errors.into_iter().map(|e| error_handling_ctx::format_error(&e)).join("\n");
if errors.is_empty() { if errors.is_empty() {
sender.send(DaemonResponse::Success(String::new()))?; sender.send(DaemonResponse::Success(String::new()))?;
} else { } else {
@ -202,14 +192,9 @@ impl App {
} }
}; };
// if let Err(err) = result { if let Err(err) = result {
// if let Some(ast_error) = err.root_cause().downcast_ref::<AstError>() { error_handling_ctx::print_error(&err);
// println!("ast error: {:?}", ast_error); }
//} else {
// dbg!(err.root_cause());
//}
crate::print_result_err!("while handling event", &result);
} }
fn stop_application(&mut self) { fn stop_application(&mut self) {
@ -397,7 +382,7 @@ fn get_monitor_geometry(n: i32) -> gdk::Rectangle {
fn respond_with_error<T>(sender: DaemonResponseSender, result: Result<T>) -> Result<()> { fn respond_with_error<T>(sender: DaemonResponseSender, result: Result<T>) -> Result<()> {
match result { match result {
Ok(_) => sender.send(DaemonResponse::Success(String::new())), Ok(_) => sender.send(DaemonResponse::Success(String::new())),
Err(e) => sender.send(DaemonResponse::Failure(error_handling_ctx::format_error(e))), Err(e) => sender.send(DaemonResponse::Failure(error_handling_ctx::format_error(&e))),
} }
.context("sending response from main thread") .context("sending response from main thread")
} }

View file

@ -1,6 +1,13 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use yuck::{config::file_provider::FsYuckFiles, error::AstError, format_diagnostic::ToDiagnostic}; use codespan_reporting::diagnostic::Diagnostic;
use eww_shared_util::DUMMY_SPAN;
use simplexpr::eval::EvalError;
use yuck::{
config::file_provider::FsYuckFiles,
error::AstError,
format_diagnostic::{eval_error_to_diagnostic, ToDiagnostic},
};
lazy_static::lazy_static! { lazy_static::lazy_static! {
pub static ref ERROR_HANDLING_CTX: Arc<Mutex<FsYuckFiles>> = Arc::new(Mutex::new(FsYuckFiles::new())); pub static ref ERROR_HANDLING_CTX: Arc<Mutex<FsYuckFiles>> = Arc::new(Mutex::new(FsYuckFiles::new()));
@ -10,41 +17,35 @@ pub fn clear_files() {
*ERROR_HANDLING_CTX.lock().unwrap() = FsYuckFiles::new(); *ERROR_HANDLING_CTX.lock().unwrap() = FsYuckFiles::new();
} }
pub fn print_error(err: anyhow::Error) { pub fn print_error(err: &anyhow::Error) {
match err.downcast_ref::<AstError>() { match err.downcast_ref::<AstError>() {
Some(err) => { Some(err) => {
print_ast_error(err); eprintln!("{:?}\n{}", err, stringify_diagnostic(err.to_diagnostic()));
}
None => {
log::error!("{:?}", err);
} }
None => match err.downcast_ref::<EvalError>() {
Some(err) => {
eprintln!("{:?}\n{}", err, stringify_diagnostic(eval_error_to_diagnostic(err, err.span().unwrap_or(DUMMY_SPAN))));
}
None => {
log::error!("{:?}", err);
}
},
} }
} }
pub fn print_ast_error(err: &AstError) { pub fn format_error(err: &anyhow::Error) -> String {
let diag = err.to_diagnostic();
use codespan_reporting::term;
let config = term::Config::default();
let mut writer = term::termcolor::StandardStream::stderr(term::termcolor::ColorChoice::Always);
let files = ERROR_HANDLING_CTX.lock().unwrap();
term::emit(&mut writer, &config, &*files, &diag).unwrap();
}
pub fn format_error(err: anyhow::Error) -> String {
match err.downcast_ref::<AstError>() { match err.downcast_ref::<AstError>() {
Some(err) => format_ast_error(err), Some(err) => stringify_diagnostic(err.to_diagnostic()),
None => format!("{:?}", err), None => format!("{:?}", err),
} }
} }
pub fn format_ast_error(err: &AstError) -> String { pub fn stringify_diagnostic(diagnostic: Diagnostic<usize>) -> String {
let diag = err.to_diagnostic();
use codespan_reporting::term; use codespan_reporting::term;
let config = term::Config::default(); let config = term::Config::default();
// let mut writer = term::termcolor::StandardStream::stderr(term::termcolor::ColorChoice::Always);
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut writer = term::termcolor::Ansi::new(&mut buf); let mut writer = term::termcolor::Ansi::new(&mut buf);
let files = ERROR_HANDLING_CTX.lock().unwrap(); let files = ERROR_HANDLING_CTX.lock().unwrap();
term::emit(&mut writer, &config, &*files, &diag).unwrap(); term::emit(&mut writer, &config, &*files, &diagnostic).unwrap();
String::from_utf8(buf).unwrap() String::from_utf8(buf).unwrap()
} }

View file

@ -4,6 +4,8 @@ use std::{collections::HashMap, sync::Arc};
use simplexpr::{dynval::DynVal, SimplExpr}; use simplexpr::{dynval::DynVal, SimplExpr};
use crate::error_handling_ctx;
/// Handler that gets executed to apply the necessary parts of the eww state to /// Handler that gets executed to apply the necessary parts of the eww state to
/// a gtk widget. These are created and initialized in EwwState::resolve. /// a gtk widget. These are created and initialized in EwwState::resolve.
pub struct StateChangeHandler { pub struct StateChangeHandler {
@ -28,9 +30,13 @@ impl StateChangeHandler {
match resolved_attrs { match resolved_attrs {
Ok(resolved_attrs) => { Ok(resolved_attrs) => {
crate::print_result_err!("while updating UI based after state change", &(self.func)(resolved_attrs)) if let Err(err) = &(self.func)(resolved_attrs).context("Error while updating UI after state change") {
error_handling_ctx::print_error(&err);
}
}
Err(err) => {
error_handling_ctx::print_error(&err);
} }
Err(err) => log::error!("Error while resolving attributes: {:?}", err),
} }
} }
} }

View file

@ -38,7 +38,6 @@ fn main() {
let log_level_filter = if opts.log_debug { log::LevelFilter::Debug } else { log::LevelFilter::Info }; let log_level_filter = if opts.log_debug { log::LevelFilter::Debug } else { log::LevelFilter::Info };
if std::env::var("RUST_LOG").is_ok() { if std::env::var("RUST_LOG").is_ok() {
println!("hey");
pretty_env_logger::init_timed(); pretty_env_logger::init_timed();
} else { } else {
pretty_env_logger::formatted_timed_builder().filter(Some("eww"), log_level_filter).init(); pretty_env_logger::formatted_timed_builder().filter(Some("eww"), log_level_filter).init();
@ -94,7 +93,7 @@ fn main() {
}; };
if let Err(e) = result { if let Err(e) = result {
error_handling_ctx::print_error(e); error_handling_ctx::print_error(&e);
std::process::exit(1); std::process::exit(1);
} }
} }

View file

@ -1,7 +1,12 @@
use crate::{EwwPaths, app, config, error_handling_ctx, eww_state::*, ipc_server, script_var_handler, util}; use crate::{app, config, error_handling_ctx, eww_state::*, ipc_server, script_var_handler, util, EwwPaths};
use anyhow::*; use anyhow::*;
use yuck::config::file_provider::FsYuckFiles;
use std::{collections::HashMap, os::unix::io::AsRawFd, path::Path}; use std::{
collections::HashMap,
os::unix::io::AsRawFd,
path::Path,
sync::{atomic::Ordering, Arc},
};
use tokio::sync::mpsc::*; use tokio::sync::mpsc::*;
pub fn initialize_server(paths: EwwPaths) -> Result<()> { pub fn initialize_server(paths: EwwPaths) -> Result<()> {
@ -29,10 +34,10 @@ pub fn initialize_server(paths: EwwPaths) -> Result<()> {
log::info!("Loading paths: {}", &paths); log::info!("Loading paths: {}", &paths);
// disgusting global state, I hate this, but https://github.com/buffet told me that this is what I should do for peak maintainability
error_handling_ctx::clear_files(); error_handling_ctx::clear_files();
let eww_config =
let eww_config = config::EwwConfig::read_from_file(&mut error_handling_ctx::ERROR_HANDLING_CTX.lock().unwrap(), &paths.get_yuck_path())?; config::EwwConfig::read_from_file(&mut error_handling_ctx::ERROR_HANDLING_CTX.lock().unwrap(), &paths.get_yuck_path())?;
gtk::init()?; gtk::init()?;
@ -109,27 +114,36 @@ fn init_async_part(paths: EwwPaths, ui_send: UnboundedSender<app::DaemonCommand>
/// Watch configuration files for changes, sending reload events to the eww app when the files change. /// Watch configuration files for changes, sending reload events to the eww app when the files change.
async fn run_filewatch<P: AsRef<Path>>(config_dir: P, evt_send: UnboundedSender<app::DaemonCommand>) -> Result<()> { async fn run_filewatch<P: AsRef<Path>>(config_dir: P, evt_send: UnboundedSender<app::DaemonCommand>) -> Result<()> {
use notify::Watcher; use notify::{RecommendedWatcher, RecursiveMode, Watcher};
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let mut watcher: notify::RecommendedWatcher = let mut watcher: RecommendedWatcher = Watcher::new_immediate(move |res: notify::Result<notify::Event>| match res {
notify::Watcher::new_immediate(move |res: notify::Result<notify::Event>| match res { Ok(event) => {
Ok(event) => { let relevant_files_changed = event.paths.iter().any(|path| {
if let Err(err) = tx.send(event.paths) { let ext = path.extension().unwrap_or_default();
ext == "yuck" || ext == "scss"
});
if !relevant_files_changed {
if let Err(err) = tx.send(()) {
log::warn!("Error forwarding file update event: {:?}", err); log::warn!("Error forwarding file update event: {:?}", err);
} }
} }
Err(e) => log::error!("Encountered Error While Watching Files: {}", e), }
})?; Err(e) => log::error!("Encountered Error While Watching Files: {}", e),
watcher.watch(&config_dir, notify::RecursiveMode::Recursive)?; })?;
watcher.watch(&config_dir, RecursiveMode::Recursive)?;
// make sure to not trigger reloads too much by only accepting one reload every 500ms.
let debounce_done = Arc::new(std::sync::atomic::AtomicBool::new(true));
crate::loop_select_exiting! { crate::loop_select_exiting! {
Some(paths) = rx.recv() => { Some(()) = rx.recv() => {
for path in paths { let debounce_done = debounce_done.clone();
let extension = path.extension().unwrap_or_default(); if debounce_done.swap(false, Ordering::SeqCst) {
if extension != "xml" && extension != "scss" { tokio::spawn(async move {
continue; tokio::time::sleep(std::time::Duration::from_millis(500)).await;
} debounce_done.store(true, Ordering::SeqCst);
});
let (daemon_resp_sender, mut daemon_resp_response) = tokio::sync::mpsc::unbounded_channel(); let (daemon_resp_sender, mut daemon_resp_response) = tokio::sync::mpsc::unbounded_channel();
evt_send.send(app::DaemonCommand::ReloadConfigAndCss(daemon_resp_sender))?; evt_send.send(app::DaemonCommand::ReloadConfigAndCss(daemon_resp_sender))?;

View file

@ -122,21 +122,6 @@ impl<T: AsRef<str>> T {
} }
} }
pub fn parse_duration(s: &str) -> Result<std::time::Duration> {
use std::time::Duration;
if s.ends_with("ms") {
Ok(Duration::from_millis(s.trim_end_matches("ms").parse()?))
} else if s.ends_with('s') {
Ok(Duration::from_secs(s.trim_end_matches('s').parse()?))
} else if s.ends_with('m') {
Ok(Duration::from_secs(s.trim_end_matches('m').parse::<u64>()? * 60))
} else if s.ends_with('h') {
Ok(Duration::from_secs(s.trim_end_matches('h').parse::<u64>()? * 60 * 60))
} else {
Err(anyhow!("unrecognized time format: {}", s))
}
}
pub trait IterAverage { pub trait IterAverage {
fn avg(self) -> f32; fn avg(self) -> f32;
} }

View file

@ -64,18 +64,7 @@ fn build_builtin_gtk_widget(
) -> Result<Option<gtk::Widget>> { ) -> Result<Option<gtk::Widget>> {
let mut bargs = let mut bargs =
BuilderArgs { eww_state, widget, window_name, unhandled_attrs: widget.attrs.keys().collect(), widget_definitions }; BuilderArgs { eww_state, widget, window_name, unhandled_attrs: widget.attrs.keys().collect(), widget_definitions };
let gtk_widget = match widget_to_gtk_widget(&mut bargs) { let gtk_widget = widget_to_gtk_widget(&mut bargs)?;
Ok(Some(gtk_widget)) => gtk_widget,
result => {
return result.with_context(|| {
format!(
"{}Error building widget {}",
bargs.widget.span.map(|x| format!("{} |", x)).unwrap_or_default(),
bargs.widget.name,
)
})
}
};
// run resolve functions for superclasses such as range, orientable, and widget // run resolve functions for superclasses such as range, orientable, and widget
@ -85,7 +74,7 @@ fn build_builtin_gtk_widget(
let child_widget = child.render(bargs.eww_state, window_name, widget_definitions).with_context(|| { let child_widget = child.render(bargs.eww_state, window_name, widget_definitions).with_context(|| {
format!( format!(
"{}error while building child '{:#?}' of '{}'", "{}error while building child '{:#?}' of '{}'",
widget.span.map(|x| format!("{} |", x)).unwrap_or_default(), format!("{} | ", widget.span),
&child, &child,
&gtk_widget.get_widget_name() &gtk_widget.get_widget_name()
) )
@ -106,7 +95,7 @@ fn build_builtin_gtk_widget(
if !bargs.unhandled_attrs.is_empty() { if !bargs.unhandled_attrs.is_empty() {
log::error!( log::error!(
"{}: Unknown attribute used in {}: {}", "{}: Unknown attribute used in {}: {}",
widget.span.map(|x| format!("{} | ", x)).unwrap_or_default(), format!("{} | ", widget.span),
widget.name, widget.name,
bargs.unhandled_attrs.iter().map(|x| x.to_string()).join(", ") bargs.unhandled_attrs.iter().map(|x| x.to_string()).join(", ")
) )

View file

@ -1,23 +1,19 @@
#![allow(clippy::option_map_unit_fn)] #![allow(clippy::option_map_unit_fn)]
use super::{run_command, BuilderArgs}; use super::{run_command, BuilderArgs};
use crate::{ use crate::{enum_parse, eww_state, resolve_block, util::list_difference, widgets::widget_node};
enum_parse, eww_state, resolve_block,
util::{list_difference, parse_duration},
widgets::widget_node,
};
use anyhow::*; use anyhow::*;
use gdk::WindowExt; use gdk::WindowExt;
use glib; use glib;
use gtk::{self, prelude::*, ImageExt}; use gtk::{self, prelude::*, ImageExt};
use std::{cell::RefCell, collections::HashMap, rc::Rc}; use std::{cell::RefCell, collections::HashMap, rc::Rc, time::Duration};
use yuck::parser::from_ast::FromAst; use yuck::{config::validate::ValidationError, error::AstError, parser::from_ast::FromAst};
// TODO figure out how to // TODO figure out how to
// TODO https://developer.gnome.org/gtk3/stable/GtkFixed.html // TODO https://developer.gnome.org/gtk3/stable/GtkFixed.html
//// widget definitions //// widget definitions
pub(super) fn widget_to_gtk_widget(bargs: &mut BuilderArgs) -> Result<Option<gtk::Widget>> { pub(super) fn widget_to_gtk_widget(bargs: &mut BuilderArgs) -> Result<gtk::Widget> {
let gtk_widget = match bargs.widget.name.as_str() { let gtk_widget = match bargs.widget.name.as_str() {
"box" => build_gtk_box(bargs)?.upcast(), "box" => build_gtk_box(bargs)?.upcast(),
"scale" => build_gtk_scale(bargs)?.upcast(), "scale" => build_gtk_scale(bargs)?.upcast(),
@ -35,9 +31,11 @@ pub(super) fn widget_to_gtk_widget(bargs: &mut BuilderArgs) -> Result<Option<gtk
"checkbox" => build_gtk_checkbox(bargs)?.upcast(), "checkbox" => build_gtk_checkbox(bargs)?.upcast(),
"revealer" => build_gtk_revealer(bargs)?.upcast(), "revealer" => build_gtk_revealer(bargs)?.upcast(),
"if-else" => build_if_else(bargs)?.upcast(), "if-else" => build_if_else(bargs)?.upcast(),
_ => return Ok(None), _ => {
Err(AstError::ValidationError(ValidationError::UnknownWidget(bargs.widget.name_span, bargs.widget.name.to_string())))?
}
}; };
Ok(Some(gtk_widget)) Ok(gtk_widget)
} }
/// attributes that apply to all widgets /// attributes that apply to all widgets
@ -287,7 +285,7 @@ fn build_gtk_revealer(bargs: &mut BuilderArgs) -> Result<gtk::Revealer> {
// @prop reveal - sets if the child is revealed or not // @prop reveal - sets if the child is revealed or not
prop(reveal: as_bool) { gtk_widget.set_reveal_child(reveal); }, prop(reveal: as_bool) { gtk_widget.set_reveal_child(reveal); },
// @prop duration - the duration of the reveal transition // @prop duration - the duration of the reveal transition
prop(duration: as_string = "500ms") { gtk_widget.set_transition_duration(parse_duration(&duration)?.as_millis() as u32); }, prop(duration: as_duration = Duration::from_millis(500)) { gtk_widget.set_transition_duration(duration.as_millis() as u32); },
}); });
Ok(gtk_widget) Ok(gtk_widget)
} }

View file

@ -4,7 +4,10 @@ use dyn_clone;
use eww_shared_util::{AttrName, Span, VarName}; use eww_shared_util::{AttrName, Span, VarName};
use simplexpr::SimplExpr; use simplexpr::SimplExpr;
use std::collections::HashMap; use std::collections::HashMap;
use yuck::config::{widget_definition::WidgetDefinition, widget_use::WidgetUse}; use yuck::{
config::{validate::ValidationError, widget_definition::WidgetDefinition, widget_use::WidgetUse},
error::AstError,
};
pub trait WidgetNode: std::fmt::Debug + dyn_clone::DynClone + Send + Sync { pub trait WidgetNode: std::fmt::Debug + dyn_clone::DynClone + Send + Sync {
fn get_name(&self) -> &str; fn get_name(&self) -> &str;
@ -14,9 +17,8 @@ pub trait WidgetNode: std::fmt::Debug + dyn_clone::DynClone + Send + Sync {
/// ///
/// Also registers all the necessary state-change handlers in the eww_state. /// Also registers all the necessary state-change handlers in the eww_state.
/// ///
/// This may return `Err` in case there was an actual error while parsing or /// This may return `Err` in case there was an actual error while parsing
/// resolving the widget, Or `Ok(None)` if the widget_use just didn't match any /// or when the widget_use did not match any widget name
/// widget name.
fn render( fn render(
&self, &self,
eww_state: &mut EwwState, eww_state: &mut EwwState,
@ -56,14 +58,23 @@ impl WidgetNode for UserDefined {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Generic { pub struct Generic {
pub name: String, pub name: String,
pub span: Option<Span>, pub name_span: Span,
pub span: Span,
pub children: Vec<Box<dyn WidgetNode>>, pub children: Vec<Box<dyn WidgetNode>>,
pub attrs: HashMap<AttrName, SimplExpr>, pub attrs: HashMap<AttrName, SimplExpr>,
} }
impl Generic { impl Generic {
pub fn get_attr(&self, key: &str) -> Result<&SimplExpr> { pub fn get_attr(&self, key: &str) -> Result<&SimplExpr> {
self.attrs.get(key).context(format!("attribute '{}' missing from use of '{}'", key, &self.name)) Ok(self.attrs.get(key).ok_or_else(|| {
AstError::ValidationError(ValidationError::MissingAttr {
widget_name: self.name.to_string(),
arg_name: AttrName(key.to_string()),
use_span: self.span,
// TODO set this when available
arg_list_span: None,
})
})?)
} }
/// returns all the variables that are referenced in this widget /// returns all the variables that are referenced in this widget
@ -78,7 +89,7 @@ impl WidgetNode for Generic {
} }
fn get_span(&self) -> Option<Span> { fn get_span(&self) -> Option<Span> {
self.span Some(self.span)
} }
fn render( fn render(
@ -87,8 +98,9 @@ impl WidgetNode for Generic {
window_name: &str, window_name: &str,
widget_definitions: &HashMap<String, WidgetDefinition>, widget_definitions: &HashMap<String, WidgetDefinition>,
) -> Result<gtk::Widget> { ) -> Result<gtk::Widget> {
crate::widgets::build_builtin_gtk_widget(eww_state, window_name, widget_definitions, self)? Ok(crate::widgets::build_builtin_gtk_widget(eww_state, window_name, widget_definitions, self)?.ok_or_else(|| {
.with_context(|| format!("Unknown widget '{}'", self.get_name())) AstError::ValidationError(ValidationError::UnknownWidget(self.name_span, self.get_name().to_string()))
})?)
} }
} }
@ -112,7 +124,8 @@ pub fn generate_generic_widget_node(
} else { } else {
Ok(Box::new(Generic { Ok(Box::new(Generic {
name: w.name, name: w.name,
span: Some(w.span), name_span: w.name_span,
span: w.span,
attrs: w attrs: w
.attrs .attrs
.attrs .attrs

View file

@ -6,7 +6,7 @@ use std::{fmt, iter::FromIterator, str::FromStr};
pub type Result<T> = std::result::Result<T, ConversionError>; pub type Result<T> = std::result::Result<T, ConversionError>;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
#[error("Failed to turn {value} into a value of type {target_type}")] #[error("Failed to turn `{value}` into a value of type {target_type}")]
pub struct ConversionError { pub struct ConversionError {
pub value: DynVal, pub value: DynVal,
pub target_type: &'static str, pub target_type: &'static str,
@ -93,6 +93,12 @@ macro_rules! impl_dynval_from {
impl_dynval_from!(bool, i32, u32, f32, u8, f64, &str); impl_dynval_from!(bool, i32, u32, f32, u8, f64, &str);
impl From<std::time::Duration> for DynVal {
fn from(d: std::time::Duration) -> Self {
DynVal(format!("{}ms", d.as_millis()), None)
}
}
impl From<&serde_json::Value> for DynVal { impl From<&serde_json::Value> for DynVal {
fn from(v: &serde_json::Value) -> Self { fn from(v: &serde_json::Value) -> Self {
DynVal( DynVal(

View file

@ -19,6 +19,4 @@ lalrpop_mod!(
pub simplexpr_parser pub simplexpr_parser
); );
pub fn parse_string(file_id: usize, s: &str) -> Result<SimplExpr, error::Error> { pub use parser::parse_string;
parser::parse_string(file_id, s)
}

View file

@ -56,11 +56,12 @@ pub type SpannedResult<Tok, Loc, Error> = Result<(Loc, Tok, Loc), Error>;
pub struct Lexer<'input> { pub struct Lexer<'input> {
lexer: logos::SpannedIter<'input, Token>, lexer: logos::SpannedIter<'input, Token>,
byte_offset: usize,
} }
impl<'input> Lexer<'input> { impl<'input> Lexer<'input> {
pub fn new(text: &'input str) -> Self { pub fn new(byte_offset: usize, text: &'input str) -> Self {
Lexer { lexer: logos::Lexer::new(text).spanned() } Lexer { lexer: logos::Lexer::new(text).spanned(), byte_offset }
} }
} }
@ -69,10 +70,11 @@ impl<'input> Iterator for Lexer<'input> {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let (token, range) = self.lexer.next()?; let (token, range) = self.lexer.next()?;
let range = (range.start + self.byte_offset, range.end + self.byte_offset);
if token == Token::Error { if token == Token::Error {
Some(Err(LexicalError(range.start, range.end))) Some(Err(LexicalError(range.0, range.1)))
} else { } else {
Some(Ok((range.start, token, range.end))) Some(Ok((range.0, token, range.1)))
} }
} }
} }

View file

@ -6,8 +6,8 @@ use crate::{
error::{Error, Result}, error::{Error, Result},
}; };
pub fn parse_string(file_id: usize, s: &str) -> Result<SimplExpr> { pub fn parse_string(byte_offset: usize, file_id: usize, s: &str) -> Result<SimplExpr> {
let lexer = lexer::Lexer::new(s); let lexer = lexer::Lexer::new(byte_offset, s);
let parser = crate::simplexpr_parser::ExprParser::new(); let parser = crate::simplexpr_parser::ExprParser::new();
parser.parse(file_id, lexer).map_err(|e| Error::from_parse_error(file_id, e)) parser.parse(file_id, lexer).map_err(|e| Error::from_parse_error(file_id, e))
} }
@ -20,7 +20,7 @@ mod tests {
use crate::parser::lexer::Lexer; use crate::parser::lexer::Lexer;
::insta::with_settings!({sort_maps => true}, { ::insta::with_settings!({sort_maps => true}, {
$( $(
::insta::assert_debug_snapshot!(p.parse(0, Lexer::new($text))); ::insta::assert_debug_snapshot!(p.parse(0, Lexer::new(0, $text)));
)* )*
}); });
}} }}

View file

@ -118,7 +118,10 @@ impl Config {
} }
pub fn generate_from_main_file(files: &mut impl YuckFiles, path: &str) -> AstResult<Self> { pub fn generate_from_main_file(files: &mut impl YuckFiles, path: &str) -> AstResult<Self> {
let (span, top_levels) = files.load(path).map_err(|err| AstError::Other(None, Box::new(err)))?; let (span, top_levels) = files.load(path).map_err(|err| match err {
FilesError::IoError(err) => AstError::Other(None, Box::new(err)),
FilesError::AstError(x) => x,
})?;
Self::generate(files, top_levels) Self::generate(files, top_levels)
} }
} }

View file

@ -16,7 +16,7 @@ pub enum ValidationError {
UnknownWidget(Span, String), UnknownWidget(Span, String),
#[error("Missing attribute `{arg_name}` in use of widget `{widget_name}`")] #[error("Missing attribute `{arg_name}` in use of widget `{widget_name}`")]
MissingAttr { widget_name: String, arg_name: AttrName, arg_list_span: Span, use_span: Span }, MissingAttr { widget_name: String, arg_name: AttrName, arg_list_span: Option<Span>, use_span: Span },
} }
pub fn validate(defs: &HashMap<String, WidgetDefinition>, content: &WidgetUse) -> Result<(), ValidationError> { pub fn validate(defs: &HashMap<String, WidgetDefinition>, content: &WidgetUse) -> Result<(), ValidationError> {
@ -26,7 +26,7 @@ pub fn validate(defs: &HashMap<String, WidgetDefinition>, content: &WidgetUse) -
return Err(ValidationError::MissingAttr { return Err(ValidationError::MissingAttr {
widget_name: def.name.to_string(), widget_name: def.name.to_string(),
arg_name: expected.clone(), arg_name: expected.clone(),
arg_list_span: def.args_span, arg_list_span: Some(def.args_span),
use_span: content.span, use_span: content.span,
}); });
} }

View file

@ -17,20 +17,22 @@ pub struct WidgetUse {
pub attrs: Attributes, pub attrs: Attributes,
pub children: Vec<WidgetUse>, pub children: Vec<WidgetUse>,
pub span: Span, pub span: Span,
pub name_span: Span,
} }
impl FromAst for WidgetUse { impl FromAst for WidgetUse {
fn from_ast(e: Ast) -> AstResult<Self> { fn from_ast(e: Ast) -> AstResult<Self> {
let span = e.span(); let span = e.span();
if let Ok(text) = e.as_literal_ref() { if let Ok(value) = e.clone().as_simplexpr() {
Ok(Self { Ok(Self {
name: "label".to_string(), name: "label".to_string(),
name_span: span.point_span(),
attrs: Attributes::new( attrs: Attributes::new(
span.into(), span,
maplit::hashmap! { maplit::hashmap! {
AttrName("text".to_string()) => AttrEntry::new( AttrName("text".to_string()) => AttrEntry::new(
span.into(), span,
Ast::Literal(span.into(), text.clone()) Ast::SimplExpr(span.into(), value.clone())
) )
}, },
), ),
@ -39,10 +41,10 @@ impl FromAst for WidgetUse {
}) })
} else { } else {
let mut iter = e.try_ast_iter()?; let mut iter = e.try_ast_iter()?;
let (_, name) = iter.expect_symbol()?; let (name_span, name) = iter.expect_symbol()?;
let attrs = iter.expect_key_values()?; let attrs = iter.expect_key_values()?;
let children = iter.map(WidgetUse::from_ast).collect::<AstResult<Vec<_>>>()?; let children = iter.map(WidgetUse::from_ast).collect::<AstResult<Vec<_>>>()?;
Ok(Self { name, attrs, children, span }) Ok(Self { name, attrs, children, span, name_span })
} }
} }
} }

View file

@ -89,20 +89,6 @@ fn get_parse_error_span(
} }
} }
// pub fn spanned(span: Span, err: impl Into<AstError>) -> AstError {
// use AstError::*;
// match err.into() {
// UnknownToplevel(s, x) => UnknownToplevel(Some(s.unwrap_or(span)), x),
// MissingNode(s) => MissingNode(Some(s.unwrap_or(span))),
// WrongExprType(s, x, y) => WrongExprType(Some(s.unwrap_or(span)), x, y),
// UnknownToplevel(s, x) => UnknownToplevel(Some(s.unwrap_or(span)), x),
// MissingNode(s) => MissingNode(Some(s.unwrap_or(span))),
// NotAValue(s, x) => NotAValue(Some(s.unwrap_or(span)), x),
// MismatchedElementName(s, expected, got) => MismatchedElementName(Some(s.unwrap_or(span)), expected, got),
// Other(s, x) => Other(Some(s.unwrap_or(span)), x),
// x @ ConversionError(_) | x @ AttrError(_) | x @ ValidationError(_) | x @ ParseError { .. } => x,
//}
pub trait OptionAstErrorExt<T> { pub trait OptionAstErrorExt<T> {
fn or_missing(self, span: Span) -> Result<T, AstError>; fn or_missing(self, span: Span) -> Result<T, AstError>;
} }

View file

@ -33,6 +33,20 @@ macro_rules! gen_diagnostic {
}}; }};
} }
pub trait DiagnosticExt: Sized {
fn with_opt_label(self, label: Option<Label<usize>>) -> Self;
}
impl DiagnosticExt for Diagnostic<usize> {
fn with_opt_label(self, label: Option<Label<usize>>) -> Self {
if let Some(label) = label {
self.with_labels(vec![label])
} else {
self
}
}
}
pub trait ToDiagnostic { pub trait ToDiagnostic {
fn to_diagnostic(&self) -> Diagnostic<usize>; fn to_diagnostic(&self) -> Diagnostic<usize>;
} }
@ -49,10 +63,8 @@ impl ToDiagnostic for AstError {
let diag = gen_diagnostic! { let diag = gen_diagnostic! {
msg = format!("{}", error), msg = format!("{}", error),
}; };
diag.with_labels(vec![ diag.with_opt_label(Some(span_to_secondary_label(*use_span).with_message("Argument missing here")))
Label::secondary(use_span.2, use_span.0..use_span.1).with_message("Argument missing here"), .with_opt_label(arg_list_span.map(|s| span_to_secondary_label(s).with_message("but is required here")))
Label::secondary(arg_list_span.2, arg_list_span.0..arg_list_span.1).with_message("but is required here"),
])
} }
} }
} else if let Some(span) = self.get_span() { } else if let Some(span) = self.get_span() {
@ -76,7 +88,7 @@ impl ToDiagnostic for AstError {
AstError::ParseError { file_id, source } => lalrpop_error_to_diagnostic(source, span, |error| match error { AstError::ParseError { file_id, source } => lalrpop_error_to_diagnostic(source, span, |error| match error {
parse_error::ParseError::SimplExpr(_, error) => simplexpr_error_to_diagnostic(error, span), parse_error::ParseError::SimplExpr(_, error) => simplexpr_error_to_diagnostic(error, span),
parse_error::ParseError::LexicalError(_) => lexical_error_to_diagnostic(span), parse_error::ParseError::LexicalError(span) => lexical_error_to_diagnostic(*span),
}), }),
AstError::MismatchedElementName(_, expected, got) => gen_diagnostic! { AstError::MismatchedElementName(_, expected, got) => gen_diagnostic! {
msg = format!("Expected element `{}`, but found `{}`", expected, got), msg = format!("Expected element `{}`, but found `{}`", expected, got),
@ -120,17 +132,23 @@ fn lalrpop_error_to_diagnostic<T: std::fmt::Display, E>(
} }
} }
fn simplexpr_error_to_diagnostic(error: &simplexpr::error::Error, span: Span) -> Diagnostic<usize> { // TODO this needs a lot of improvement
pub fn simplexpr_error_to_diagnostic(error: &simplexpr::error::Error, span: Span) -> Diagnostic<usize> {
use simplexpr::error::Error::*; use simplexpr::error::Error::*;
match error { match error {
ParseError { source, .. } => lalrpop_error_to_diagnostic(source, span, move |error| lexical_error_to_diagnostic(span)), ParseError { source, .. } => lalrpop_error_to_diagnostic(source, span, move |error| lexical_error_to_diagnostic(span)),
ConversionError(error) => conversion_error_to_diagnostic(error, span), ConversionError(error) => conversion_error_to_diagnostic(error, span),
Eval(error) => gen_diagnostic!(error, span), Eval(error) => eval_error_to_diagnostic(error, span),
Other(error) => gen_diagnostic!(error, span), Other(error) => gen_diagnostic!(error, span),
Spanned(_, error) => gen_diagnostic!(error, span), Spanned(span, error) => gen_diagnostic!(error, span),
} }
} }
// TODO this needs a lot of improvement
pub fn eval_error_to_diagnostic(error: &simplexpr::eval::EvalError, span: Span) -> Diagnostic<usize> {
gen_diagnostic!(error, error.span().unwrap_or(span))
}
fn conversion_error_to_diagnostic(error: &dynval::ConversionError, span: Span) -> Diagnostic<usize> { fn conversion_error_to_diagnostic(error: &dynval::ConversionError, span: Span) -> Diagnostic<usize> {
let diag = gen_diagnostic! { let diag = gen_diagnostic! {
msg = format!("{}", error), msg = format!("{}", error),

View file

@ -2,7 +2,7 @@ use itertools::Itertools;
use simplexpr::{ast::SimplExpr, dynval::DynVal}; use simplexpr::{ast::SimplExpr, dynval::DynVal};
use std::collections::HashMap; use std::collections::HashMap;
use eww_shared_util::Span; use eww_shared_util::{Span, VarName};
use std::fmt::Display; use std::fmt::Display;
use super::{ast_iterator::AstIterator, from_ast::FromAst}; use super::{ast_iterator::AstIterator, from_ast::FromAst};
@ -94,10 +94,10 @@ impl Ast {
pub fn as_simplexpr(self) -> AstResult<SimplExpr> { pub fn as_simplexpr(self) -> AstResult<SimplExpr> {
match self { match self {
// do I do this? // TODO do I do this?
// Ast::Array(_, _) => todo!(), // Ast::Array(span, elements) => todo!()
// Ast::Symbol(_, _) => todo!(), Ast::Symbol(span, x) => Ok(SimplExpr::VarRef(span, VarName(x))),
Ast::Literal(span, x) => Ok(SimplExpr::Literal(span.into(), x)), Ast::Literal(span, x) => Ok(SimplExpr::Literal(span, x)),
Ast::SimplExpr(span, x) => Ok(x), Ast::SimplExpr(span, x) => Ok(x),
_ => Err(AstError::WrongExprType(self.span(), AstType::IntoPrimitive, self.expr_type())), _ => Err(AstError::WrongExprType(self.span(), AstType::IntoPrimitive, self.expr_type())),
} }
@ -126,16 +126,6 @@ impl std::fmt::Display for Ast {
} }
impl std::fmt::Debug for Ast { impl std::fmt::Debug for Ast {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use Ast::*;
write!(f, "{}", self) write!(f, "{}", self)
// match self {
// List(span, x) => f.debug_tuple(&format!("List<{}>", span)).field(x).finish(),
// Array(span, x) => f.debug_tuple(&format!("Array<{}>", span)).field(x).finish(),
// Keyword(span, x) => write!(f, "Number<{}>({})", span, x),
// Symbol(span, x) => write!(f, "Symbol<{}>({})", span, x),
// Value(span, x) => write!(f, "Value<{}>({})", span, x),
// SimplExpr(span, x) => write!(f, "SimplExpr<{}>({})", span, x),
// Comment(span) => write!(f, "Comment<{}>", span),
//}
} }
} }

View file

@ -97,7 +97,7 @@ impl Iterator for Lexer {
let string = &self.source[self.pos..]; let string = &self.source[self.pos..];
if string.starts_with('{') { if string.starts_with('{') {
self.pos += 1; // self.pos += 1;
let expr_start = self.pos; let expr_start = self.pos;
let mut in_string = false; let mut in_string = false;
loop { loop {
@ -107,8 +107,8 @@ impl Iterator for Lexer {
let string = &self.source[self.pos..]; let string = &self.source[self.pos..];
if string.starts_with('}') && !in_string { if string.starts_with('}') && !in_string {
let tok_str = &self.source[expr_start..self.pos];
self.pos += 1; self.pos += 1;
let tok_str = &self.source[expr_start..self.pos];
return Some(Ok((expr_start, Token::SimplExpr(tok_str.to_string()), self.pos - 1))); return Some(Ok((expr_start, Token::SimplExpr(tok_str.to_string()), self.pos - 1)));
} else if string.starts_with('"') { } else if string.starts_with('"') {
self.pos += 1; self.pos += 1;

View file

@ -59,9 +59,8 @@ StrLit: String = {
SimplExpr: SimplExpr = { SimplExpr: SimplExpr = {
<l:@L> <x:"simplexpr"> =>? { <l:@L> <x:"simplexpr"> =>? {
let expr = x[1..x.len() - 1].to_string(); let expr = x[1..x.len() - 1].to_string();
simplexpr::parse_string(file_id, &expr).map_err(|e| { simplexpr::parse_string(l + 1, file_id, &expr).map_err(|e| {
let span = e.get_span().map(|Span(simpl_l, simpl_r, file_id)| Span(1 + l + simpl_l, 1 + l + simpl_r, file_id)); ParseError::User { error: parse_error::ParseError::SimplExpr(e.get_span(), e) }})
ParseError::User { error: parse_error::ParseError::SimplExpr(span, e) }})
} }
} }