diff --git a/src/config/attributes.rs b/src/config/attributes.rs new file mode 100644 index 0000000..8a4154a --- /dev/null +++ b/src/config/attributes.rs @@ -0,0 +1,109 @@ +use std::{ + collections::HashMap, + convert::{TryFrom, TryInto}, +}; + +use simplexpr::{dynval::DynVal, eval::EvalError, SimplExpr}; + +use crate::{ + parser::{ + ast::{Ast, Span}, + from_ast::FromAst, + }, + value::AttrName, +}; + +#[derive(Debug, thiserror::Error)] +pub enum AttrError { + #[error("Missing required attribute {0}")] + MissingRequiredAttr(Span, AttrName), + + #[error("Failed to parse attribute value {0} in this context")] + AttrTypeError(Span, AttrName), + + #[error("{1}")] + EvaluationError(Span, EvalError), +} + +impl AttrError { + pub fn span(&self) -> Span { + match self { + AttrError::MissingRequiredAttr(span, _) => *span, + AttrError::AttrTypeError(span, _) => *span, + AttrError::EvaluationError(span, _) => *span, + } + } +} + +#[derive(Debug)] +pub struct UnusedAttrs { + definition_span: Span, + attrs: Vec<(Span, AttrName)>, +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] +pub struct AttrEntry { + pub key_span: Span, + pub value: SimplExpr, +} + +impl AttrEntry { + pub fn new(key_span: Span, value: SimplExpr) -> AttrEntry { + AttrEntry { key_span, value } + } +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize)] +pub struct Attributes { + pub span: Span, + pub attrs: HashMap, +} + +impl Attributes { + pub fn new(span: Span, attrs: HashMap) -> Self { + Attributes { span, attrs } + } + + pub fn eval_required>(&mut self, key: &str) -> Result { + let key = AttrName(key.to_string()); + match self.attrs.remove(&key) { + Some(AttrEntry { key_span, value }) => { + let value_span = value.span(); + let dynval = value.eval_no_vars().map_err(|err| AttrError::EvaluationError(value_span.into(), err))?; + T::try_from(dynval).map_err(|_| AttrError::AttrTypeError(value_span.into(), key.clone())) + } + None => Err(AttrError::MissingRequiredAttr(self.span, key.clone())), + } + } + + // pub fn parse_required>(&mut self, key: &str) -> Result { + // let key = AttrName(key.to_string()); + // match self.attrs.remove(&key) { + // Some(value) => match value.value.try_into() { + // Ok(value) => Ok(value), + // Err(_) => Err(AttrError::AttrTypeError(value.value.span().into(), key.clone())), + // }, + // None => Err(AttrError::MissingRequiredAttr(self.span, key.clone())), + // } + // } + // + // pub fn parse_optional>(&mut self, key: &str) -> Result, AttrError> { + // let key = AttrName(key.to_string()); + // match self.attrs.remove(&key) { + // Some(value) => match value.value.try_into() { + // Ok(value) => Ok(Some(value)), + // Err(_) => Err(AttrError::AttrTypeError(value.value.span().into(), key.clone())), + // }, + // None => Ok(None), + // } + // } + + /// Consumes the attributes to return a list of unused attributes which may be used to emit a warning. + /// TODO actually use this and implement warnings,... lol + pub fn get_unused(self, definition_span: Span) -> UnusedAttrs { + UnusedAttrs { + definition_span, + attrs: self.attrs.into_iter().map(|(k, v)| (v.key_span.to(v.value.span().into()), k)).collect(), + } + } +} diff --git a/src/config/config.rs b/src/config/config.rs index 17085b5..b86b28f 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -27,8 +27,7 @@ impl FromAst for TopLevel { fn from_ast(e: Ast) -> AstResult { let span = e.span(); spanned!(e.span(), { - let list = e.as_list()?; - let mut iter = AstIterator::new(list.into_iter()); + let mut iter = e.try_ast_iter()?; let (sym_span, element_name) = iter.expect_symbol()?; match element_name.as_str() { x if x == WidgetDefinition::get_element_name() => { diff --git a/src/config/mod.rs b/src/config/mod.rs index 39e20af..3c51d23 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,3 +1,4 @@ +pub mod attributes; mod config; pub mod config_parse_error; pub mod script_var_definition; diff --git a/src/config/script_var_definition.rs b/src/config/script_var_definition.rs index d245ced..9d6d907 100644 --- a/src/config/script_var_definition.rs +++ b/src/config/script_var_definition.rs @@ -48,9 +48,9 @@ impl FromAstElementContent for PollScriptVar { fn from_tail>(span: Span, mut iter: AstIterator) -> AstResult { let (_, name) = iter.expect_symbol()?; - let attrs: HashMap = iter.expect_key_values()?; - let interval = attrs.get("interval").unwrap(); - let interval = crate::util::parse_duration(interval).map_err(|e| AstError::Other(Some(span), e.into()))?; + let mut attrs = iter.expect_key_values()?; + let interval: String = attrs.eval_required("interval")?; + let interval = crate::util::parse_duration(&interval).map_err(|e| AstError::Other(Some(span), e.into()))?; let (_, script) = iter.expect_literal()?; Ok(Self { name: VarName(name), command: VarSource::Shell(script.to_string()), interval }) } diff --git a/src/config/validate.rs b/src/config/validate.rs index dc9678b..99b049b 100644 --- a/src/config/validate.rs +++ b/src/config/validate.rs @@ -26,7 +26,7 @@ pub enum ValidationError { pub fn validate(defs: &HashMap, content: &WidgetUse) -> Result<(), ValidationError> { if let Some(def) = defs.get(&content.name) { for expected in def.expected_args.iter() { - if !content.attrs.contains_key(expected) { + if !content.attrs.attrs.contains_key(expected) { return Err(ValidationError::MissingAttr { widget_name: def.name.to_string(), arg_name: expected.clone(), diff --git a/src/config/widget_use.rs b/src/config/widget_use.rs index 516b862..2c5e314 100644 --- a/src/config/widget_use.rs +++ b/src/config/widget_use.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use simplexpr::SimplExpr; use crate::{ + config::attributes::AttrEntry, error::AstResult, parser::{ ast::{Ast, AstIterator, Span}, @@ -11,10 +12,13 @@ use crate::{ spanned, value::AttrName, }; + +use super::attributes::Attributes; + #[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] pub struct WidgetUse { pub name: String, - pub attrs: HashMap, + pub attrs: Attributes, pub children: Vec, pub span: Span, } @@ -26,15 +30,22 @@ impl FromAst for WidgetUse { if let Ok(text) = e.as_literal_ref() { Self { name: "text".to_string(), - attrs: maplit::hashmap! { AttrName("text".to_string()) => SimplExpr::Literal(span.into(), text.clone()) }, + attrs: Attributes::new( + span.into(), + maplit::hashmap! { + AttrName("text".to_string()) => AttrEntry::new( + span.into(), + SimplExpr::Literal(span.into(), text.clone()) + ) + }, + ), children: Vec::new(), span, } } else { - let list = e.as_list()?; - let mut iter = AstIterator::new(list.into_iter()); + let mut iter = e.try_ast_iter()?; let (_, name) = iter.expect_symbol()?; - let attrs = iter.expect_key_values()?.into_iter().map(|(k, v)| (AttrName(k), v)).collect(); + let attrs = iter.expect_key_values()?; let children = iter.map(WidgetUse::from_ast).collect::>>()?; Self { name, attrs, children, span } } diff --git a/src/error.rs b/src/error.rs index 8b6fcf2..53126f9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,5 @@ use crate::{ - config::validate::ValidationError, + config::{attributes::AttrError, validate::ValidationError}, parser::{ ast::{Ast, AstType, Span}, lexer, parse_error, @@ -22,9 +22,20 @@ pub enum AstError { NotAValue(Option, AstType), #[error("Expected element {1}, but read {2}")] MismatchedElementName(Option, String, String), + #[error("{1}")] Other(Option, Box), + #[error(transparent)] + AttrError(#[from] AttrError), + + //#[error("{msg}: {source}")] + // Context { + // span: Option, + //#[source] + // source: Box, + // msg: String, + //}, #[error(transparent)] ValidationError(#[from] ValidationError), @@ -40,7 +51,9 @@ impl AstError { AstError::WrongExprType(span, ..) => *span, AstError::NotAValue(span, ..) => *span, AstError::MismatchedElementName(span, ..) => *span, + AstError::AttrError(err) => Some(err.span()), AstError::Other(span, ..) => *span, + // AstError::Context { span, .. } => *span, AstError::ValidationError(error) => None, // TODO none here is stupid AstError::ParseError { file_id, source } => file_id.and_then(|id| get_parse_error_span(id, source)), } @@ -80,6 +93,7 @@ pub fn spanned(span: Span, err: impl Into) -> AstError { MissingNode(None) => MissingNode(Some(span)), NotAValue(None, x) => NotAValue(Some(span), x), MismatchedElementName(None, x, y) => MismatchedElementName(Some(span), x, y), + // Context { span: None, source, msg } => Context { span: Some(span), source, msg }, Other(None, x) => Other(Some(span), x), x => x, } @@ -98,12 +112,21 @@ pub trait AstResultExt { fn at(self, span: Span) -> Result; } +pub trait Context { + fn context(self, span: Span, msg: String) -> Result; +} + impl> AstResultExt for Result { fn at(self, span: Span) -> Result { self.map_err(|err| spanned(span, err)) } } +// impl Context for Result { +// fn context(self, span: Span, msg: String) -> Result { +// self.map_err(|x| AstError::Context { msg, span: Some(span), source: Box::new(x) }) +//} + #[macro_export] macro_rules! spanned { ($span:expr, $block:expr) => {{ diff --git a/src/parser/ast.rs b/src/parser/ast.rs index af1b513..0861ab7 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -5,16 +5,45 @@ use std::collections::HashMap; use std::fmt::Display; use super::from_ast::FromAst; -use crate::error::{AstError, AstResult, OptionAstErrorExt}; +use crate::{ + config::attributes::{AttrEntry, Attributes}, + error::{AstError, AstResult, OptionAstErrorExt}, + value::AttrName, +}; #[derive(Eq, PartialEq, Clone, Copy, serde::Serialize)] pub struct Span(pub usize, pub usize, pub usize); +impl Span { + /// Get the span that includes this and the other span completely. + /// Will panic if the spans are from different file_ids. + pub fn to(mut self, other: Span) -> Self { + assert!(other.2 == self.2); + self.1 = other.1; + self + } + + pub fn ending_at(mut self, end: usize) -> Self { + self.1 = end; + self + } + + pub fn with_length(mut self, end: usize) -> Self { + self.1 = self.0; + self + } +} + impl Into for Span { fn into(self) -> simplexpr::Span { simplexpr::Span(self.0, self.1, self.2) } } +impl From for Span { + fn from(x: simplexpr::Span) -> Span { + Span(x.0, x.1, x.2) + } +} impl std::fmt::Display for Span { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -47,7 +76,7 @@ impl Display for AstType { } } -#[derive(PartialEq, Eq, Clone)] +#[derive(PartialEq, Eq, Clone, serde::Serialize)] pub enum Ast { List(Span, Vec), Array(Span, Vec), @@ -119,6 +148,12 @@ impl Ast { _ => Err(AstError::WrongExprType(Some(self.span()), AstType::IntoPrimitive, self.expr_type())), } } + + pub fn try_ast_iter(self) -> AstResult>> { + let span = self.span(); + let list = self.as_list()?; + Ok(AstIterator::new(span, list.into_iter())) + } } impl std::fmt::Display for Ast { @@ -152,6 +187,7 @@ impl std::fmt::Debug for Ast { } pub struct AstIterator> { + remaining_span: Span, iter: itertools::PutBack, } @@ -160,7 +196,11 @@ macro_rules! return_or_put_back { pub fn $name(&mut self) -> AstResult<$t> { let expr_type = $expr_type; match self.next() { - Some($p) => Ok($ret), + Some($p) => { + let (span, value) = $ret; + self.remaining_span.1 = span.1; + Ok((span, value)) + } Some(other) => { let span = other.span(); let actual_type = other.expr_type(); @@ -182,16 +222,16 @@ impl> AstIterator { return_or_put_back!(expect_array, AstType::Array, (Span, Vec) = Ast::Array(span, x) => (span, x)); - pub fn new(iter: I) -> Self { - AstIterator { iter: itertools::put_back(iter) } + pub fn new(span: Span, iter: I) -> Self { + AstIterator { remaining_span: span, iter: itertools::put_back(iter) } } pub fn expect_any(&mut self) -> AstResult { self.iter.next().or_missing().and_then(T::from_ast) } - pub fn expect_key_values(&mut self) -> AstResult> { - parse_key_values(&mut self.iter) + pub fn expect_key_values(&mut self) -> AstResult { + parse_key_values(self) } } @@ -203,25 +243,31 @@ impl> Iterator for AstIterator { } } -/// Parse consecutive `:keyword value` pairs from an expression iterator into a HashMap. Transforms the keys using the FromExpr trait. -fn parse_key_values>(iter: &mut itertools::PutBack) -> AstResult> { +/// Parse consecutive `:keyword value` pairs from an expression iterator into an [Attributes]. +fn parse_key_values(iter: &mut AstIterator>) -> AstResult { let mut data = HashMap::new(); + let mut attrs_span = Span(iter.remaining_span.0, iter.remaining_span.0, iter.remaining_span.1); loop { match iter.next() { - Some(Ast::Keyword(span, kw)) => match iter.next() { + Some(Ast::Keyword(key_span, kw)) => match iter.next() { Some(value) => { - data.insert(kw, T::from_ast(value)?); + attrs_span.1 = iter.remaining_span.0; + let attr_value = AttrEntry { key_span, value: value.as_simplexpr()? }; + data.insert(AttrName(kw), attr_value); } None => { - iter.put_back(Ast::Keyword(span, kw)); - return Ok(data); + iter.iter.put_back(Ast::Keyword(key_span, kw)); + attrs_span.1 = iter.remaining_span.0; + return Ok(Attributes::new(attrs_span, data)); } }, - Some(expr) => { - iter.put_back(expr); - return Ok(data); + next => { + if let Some(expr) = next { + iter.iter.put_back(expr); + } + attrs_span.1 = iter.remaining_span.0; + return Ok(Attributes::new(attrs_span, data)); } - None => return Ok(data), } } } diff --git a/src/parser/from_ast.rs b/src/parser/from_ast.rs index 8404439..21ab4bd 100644 --- a/src/parser/from_ast.rs +++ b/src/parser/from_ast.rs @@ -35,8 +35,7 @@ impl FromAst for T { fn from_ast(e: Ast) -> AstResult { let span = e.span(); spanned!(e.span(), { - let list = e.as_list()?; - let mut iter = AstIterator::new(list.into_iter()); + let mut iter = e.try_ast_iter()?; let (_, element_name) = iter.expect_symbol()?; if Self::get_element_name() != element_name { return Err(AstError::MismatchedElementName(Some(span), Self::get_element_name().to_string(), element_name));