add basic config structure parsing and add some validation, mostly to demonstrate

This commit is contained in:
elkowar 2021-07-18 19:48:16 +02:00
parent de9d979ce5
commit bfb7c5a27b
No known key found for this signature in database
GPG key ID: E321AD71B1D1F27F
14 changed files with 300 additions and 46 deletions

View file

@ -1,23 +1,28 @@
use eww_config::{ast::*, config::*, format_diagnostic::ToDiagnostic};
use eww_config::{
config::*,
format_diagnostic::ToDiagnostic,
parser::{ast::*, element::FromAst},
};
fn main() {
let mut files = codespan_reporting::files::SimpleFiles::new();
let input = r#"
(heyho :foo { "foo \" } bar " }
:baz {(foo == bar ? 12.2 : 12)}
(heyho ; :foo { "foo \" } bar " }
; :baz {(foo == bar ? 12.2 : 12)}
(foo)
(defwidget foo [something bla] "foo")
(baz))"#;
let file_id = files.add("foo.eww", input);
let ast = eww_config::parse_string(file_id, input);
match ast.and_then(Element::<Ast, Ast>::from_ast) {
let ast = eww_config::parser::parse_string(file_id, input);
match ast.and_then(eww_config::parser::element::Element::<Ast, Ast>::from_ast) {
Ok(ast) => {
println!("{:?}", ast);
}
Err(err) => {
dbg!(&err);
let diag = err.to_diagnostic(&files);
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);

40
examples/validation.rs Normal file
View file

@ -0,0 +1,40 @@
use eww_config::{
config::{widget_definition::WidgetDefinition, widget_use::WidgetUse, *},
error::AstError,
format_diagnostic::ToDiagnostic,
parser::{ast::*, element::FromAst},
};
fn main() {
let mut files = codespan_reporting::files::SimpleFiles::new();
let input_use = r#"
(foo :something 12
:bla "bruh"
"some text")
"#;
let input_def = r#"
(defwidget foo [something bla] "foo")
"#;
let file_id_use = files.add("use.eww", input_use);
let file_id_def = files.add("def.eww", input_def);
let parsed_use = WidgetUse::from_ast(eww_config::parser::parse_string(file_id_use, input_use).unwrap()).unwrap();
let parsed_def = WidgetDefinition::from_ast(eww_config::parser::parse_string(file_id_def, input_def).unwrap()).unwrap();
let defs = maplit::hashmap! {
"foo".to_string() => parsed_def,
};
match validate::validate(&defs, &parsed_use) {
Ok(ast) => {
println!("{:?}", ast);
}
Err(err) => {
let err = AstError::ValidationError(err);
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);
term::emit(&mut writer, &config, &files, &diag).unwrap();
}
}
}

View file

@ -0,0 +1,3 @@
pub mod validate;
pub mod widget_definition;
pub mod widget_use;

42
src/config/validate.rs Normal file
View file

@ -0,0 +1,42 @@
use std::collections::HashMap;
use simplexpr::SimplExpr;
use crate::{
error::AstResult,
parser::{
ast::{Ast, AstIterator, Span},
element::{Element, FromAst},
},
spanned,
value::{AttrName, VarName},
};
use super::{widget_definition::WidgetDefinition, widget_use::WidgetUse};
#[derive(Debug, thiserror::Error)]
pub enum ValidationError {
#[error("Unknown widget referenced: {1}")]
UnknownWidget(Span, String),
#[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 },
}
pub fn validate(defs: &HashMap<String, WidgetDefinition>, 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) {
return Err(ValidationError::MissingAttr {
widget_name: def.name.to_string(),
arg_name: expected.clone(),
arg_list_span: def.args_span,
use_span: content.span,
});
}
}
} else {
return Err(ValidationError::UnknownWidget(content.span, content.name.to_string()));
}
Ok(())
}

View file

@ -0,0 +1,44 @@
use std::collections::HashMap;
use simplexpr::SimplExpr;
use crate::{
error::AstResult,
parser::{
ast::{Ast, AstIterator, Span},
element::{Element, FromAst},
},
spanned,
value::{AttrName, VarName},
};
use super::widget_use::WidgetUse;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct WidgetDefinition {
pub name: String,
pub expected_args: Vec<AttrName>,
pub widget: WidgetUse,
pub span: Span,
pub args_span: Span,
}
impl FromAst for WidgetDefinition {
fn from_ast(e: Ast) -> AstResult<Self> {
let span = e.span();
spanned!(e.span(), {
let list = e.as_list()?;
let mut iter = AstIterator::new(list.into_iter());
let (_, def_type) = iter.expect_symbol()?;
assert!(def_type == "defwidget");
let (_, name) = iter.expect_symbol()?;
let (args_span, expected_args) = iter.expect_array()?;
let expected_args = expected_args.into_iter().map(|x| x.as_symbol().map(AttrName)).collect::<AstResult<_>>()?;
let widget = iter.expect_any().and_then(WidgetUse::from_ast)?;
// TODO verify that this was the last element in the list
// iter.expect_done()?;
Self { name, expected_args, widget, span, args_span }
})
}
}

43
src/config/widget_use.rs Normal file
View file

@ -0,0 +1,43 @@
use std::collections::HashMap;
use simplexpr::SimplExpr;
use crate::{
error::AstResult,
parser::{
ast::{Ast, AstIterator, Span},
element::{Element, FromAst},
},
spanned,
value::AttrName,
};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct WidgetUse {
pub name: String,
pub attrs: HashMap<AttrName, SimplExpr>,
pub children: Vec<WidgetUse>,
pub span: Span,
}
impl FromAst for WidgetUse {
fn from_ast(e: Ast) -> AstResult<Self> {
let span = e.span();
spanned!(e.span(), {
if let Ok(text) = e.as_value_ref() {
Self {
name: "text".to_string(),
attrs: maplit::hashmap! { AttrName("text".to_string()) => 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 (_, name) = iter.expect_symbol()?;
let attrs = iter.expect_key_values()?.into_iter().map(|(k, v)| (AttrName(k), v)).collect();
let children = iter.map(WidgetUse::from_ast).collect::<AstResult<Vec<_>>>()?;
Self { name, attrs, children, span }
}
})
}
}

View file

@ -1,6 +1,9 @@
use crate::parser::{
ast::{Ast, AstType, Span},
lexer, parse_error,
use crate::{
config::validate::ValidationError,
parser::{
ast::{Ast, AstType, Span},
lexer, parse_error,
},
};
use codespan_reporting::{diagnostic, files};
use thiserror::Error;
@ -11,10 +14,15 @@ pub type AstResult<T> = Result<T, AstError>;
pub enum AstError {
#[error("Definition invalid")]
InvalidDefinition(Option<Span>),
#[error("Expected a {1}, but got nothing")]
MissingNode(Option<Span>, AstType),
#[error("Expected another element, but got nothing")]
MissingNode(Option<Span>),
#[error("Wrong type of expression: Expected {1} but got {2}")]
WrongExprType(Option<Span>, AstType, AstType),
#[error("Expected to get a value, but got {1}")]
NotAValue(Option<Span>, AstType),
#[error(transparent)]
ValidationError(#[from] ValidationError),
#[error("Parse error: {source}")]
ParseError { file_id: Option<usize>, source: lalrpop_util::ParseError<usize, lexer::Token, parse_error::ParseError> },
@ -24,8 +32,10 @@ impl AstError {
pub fn get_span(&self) -> Option<Span> {
match self {
AstError::InvalidDefinition(span) => *span,
AstError::MissingNode(span, _) => *span,
AstError::MissingNode(span) => *span,
AstError::WrongExprType(span, ..) => *span,
AstError::NotAValue(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)),
}
}
@ -58,18 +68,18 @@ pub fn spanned(span: Span, err: impl Into<AstError>) -> AstError {
use AstError::*;
match err.into() {
AstError::InvalidDefinition(None) => AstError::InvalidDefinition(Some(span)),
AstError::MissingNode(None, x) => AstError::MissingNode(Some(span), x),
AstError::MissingNode(None) => AstError::MissingNode(Some(span)),
AstError::WrongExprType(None, x, y) => AstError::WrongExprType(Some(span), x, y),
x => x,
}
}
pub trait OptionAstErrorExt<T> {
fn or_missing(self, t: AstType) -> Result<T, AstError>;
fn or_missing(self) -> Result<T, AstError>;
}
impl<T> OptionAstErrorExt<T> for Option<T> {
fn or_missing(self, t: AstType) -> Result<T, AstError> {
self.ok_or(AstError::MissingNode(None, t))
fn or_missing(self) -> Result<T, AstError> {
self.ok_or(AstError::MissingNode(None))
}
}
@ -87,7 +97,7 @@ impl<T, E: Into<AstError>> AstResultExt<T> for Result<T, E> {
macro_rules! spanned {
($span:expr, $block:expr) => {{
let span = $span;
let result: Result<_, AstError> = try { $block };
result.at(span)
let result: Result<_, crate::error::AstError> = try { $block };
crate::error::AstResultExt::at(result, span)
}};
}

View file

@ -16,8 +16,8 @@ macro_rules! gen_diagnostic {
Diagnostic::error()
$(.with_message($msg))?
$(.with_labels(vec![
Label::primary($span.2, $span.0..$span.1)
$(.with_message($label))?
Label::primary($span.2, $span.0..$span.1)
$(.with_message($label))?
]))?
$(.with_notes(vec![$note]))?
};
@ -34,15 +34,29 @@ pub trait ToDiagnostic {
impl ToDiagnostic for AstError {
fn to_diagnostic(&self) -> Diagnostic<usize> {
let diag = Diagnostic::error();
if let Some(span) = self.get_span() {
use lalrpop_util::ParseError::*;
if let AstError::ValidationError(error) = self {
match error {
crate::config::validate::ValidationError::UnknownWidget(span, name) => gen_diagnostic! {
msg = format!("No widget named {} exists", name),
label = span => "Used here",
},
crate::config::validate::ValidationError::MissingAttr { widget_name, arg_name, arg_list_span, use_span } => {
let diag = gen_diagnostic! {
msg = format!("{}", error),
};
diag.with_labels(vec![
Label::secondary(use_span.2, use_span.0..use_span.1).with_message("Argument missing 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() {
match self {
AstError::InvalidDefinition(_) => todo!(),
AstError::MissingNode(_, expected) => gen_diagnostic! {
msg = format!("Missing {}", expected),
label = span => format!("Expected `{}` here", expected),
AstError::MissingNode(_) => gen_diagnostic! {
msg = "Expected another element",
label = span => "Expected another element here",
},
AstError::WrongExprType(_, expected, actual) => gen_diagnostic! {
@ -50,14 +64,20 @@ impl ToDiagnostic for AstError {
label = span => format!("Expected a `{}` here", expected),
note = format!("Expected: {}\nGot: {}", expected, actual),
},
AstError::NotAValue(_, actual) => gen_diagnostic! {
msg = format!("Expected value, but got {}", actual),
label = span => "Expected some value here",
note = format!("Got: {}", actual),
},
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::LexicalError(_) => lexical_error_to_diagnostic(span),
}),
_ => panic!(),
}
} else {
diag.with_message(format!("{}", self))
Diagnostic::error().with_message(format!("{}", self))
}
}
}
@ -86,7 +106,7 @@ fn lalrpop_error_to_diagnostic<T: std::fmt::Display, E>(
fn simplexpr_error_to_diagnostic(error: &simplexpr::error::Error, span: Span) -> Diagnostic<usize> {
use simplexpr::error::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),
Eval(error) => gen_diagnostic!(format!("{}", error), span),
Other(error) => gen_diagnostic!(format!("{}", error), span),

View file

@ -1,15 +1,21 @@
use itertools::Itertools;
use simplexpr::ast::SimplExpr;
use simplexpr::{ast::SimplExpr, dynval::DynVal};
use std::collections::HashMap;
use std::fmt::Display;
use super::element::FromAst;
use crate::error::{AstError, AstResult};
use crate::error::{AstError, AstResult, OptionAstErrorExt};
#[derive(Eq, PartialEq, Clone, Copy)]
pub struct Span(pub usize, pub usize, pub usize);
impl Into<simplexpr::Span> for Span {
fn into(self) -> simplexpr::Span {
simplexpr::Span(self.0, self.1, self.2)
}
}
impl std::fmt::Display for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}..{}", self.0, self.1)
@ -45,7 +51,7 @@ pub enum Ast {
Array(Span, Vec<Ast>),
Keyword(Span, String),
Symbol(Span, String),
Value(Span, String),
Value(Span, DynVal),
SimplExpr(Span, SimplExpr),
Comment(Span),
}
@ -69,7 +75,7 @@ macro_rules! as_func {
}
impl Ast {
as_func!(AstType::Value, as_value as_value_ref<String> = Ast::Value(_, x) => x);
as_func!(AstType::Value, as_value as_value_ref<DynVal> = Ast::Value(_, x) => x);
as_func!(AstType::Symbol, as_symbol as_symbol_ref<String> = Ast::Symbol(_, x) => x);
@ -155,7 +161,7 @@ macro_rules! return_or_put_back {
self.iter.put_back(other);
Err(AstError::WrongExprType(Some(span), expr_type, actual_type))
}
None => Err(AstError::MissingNode(None, expr_type)),
None => Err(AstError::MissingNode(None)),
}
}
};
@ -164,14 +170,20 @@ macro_rules! return_or_put_back {
impl<I: Iterator<Item = Ast>> AstIterator<I> {
return_or_put_back!(expect_symbol, AstType::Symbol, (Span, String) = Ast::Symbol(span, x) => (span, x));
return_or_put_back!(expect_string, AstType::Value, (Span, String) = Ast::Value(span, x) => (span, x));
return_or_put_back!(expect_value, AstType::Value, (Span, DynVal) = Ast::Value(span, x) => (span, x));
return_or_put_back!(expect_list, AstType::List, (Span, Vec<Ast>) = Ast::List(span, x) => (span, x));
return_or_put_back!(expect_array, AstType::Array, (Span, Vec<Ast>) = Ast::Array(span, x) => (span, x));
pub fn new(iter: I) -> Self {
AstIterator { iter: itertools::put_back(iter) }
}
pub fn expect_any<T: FromAst>(&mut self) -> AstResult<T> {
self.iter.next().or_missing().and_then(T::from_ast)
}
pub fn expect_key_values<T: FromAst>(&mut self) -> AstResult<HashMap<String, T>> {
parse_key_values(&mut self.iter)
}

View file

@ -1,16 +1,13 @@
use super::ast::{Ast, AstIterator, AstType, Span};
use crate::{error::*, parser, spanned};
use crate::{error::*, parser, spanned, value::AttrName};
use itertools::Itertools;
use simplexpr::ast::SimplExpr;
use std::{
collections::{HashMap, LinkedList},
iter::FromIterator,
str::FromStr,
};
type VarName = String;
type AttrValue = String;
type AttrName = String;
pub trait FromAst: Sized {
fn from_ast(e: Ast) -> AstResult<Self>;
}
@ -21,6 +18,17 @@ impl FromAst for Ast {
}
}
impl FromAst for SimplExpr {
fn from_ast(e: Ast) -> AstResult<Self> {
match e {
Ast::Symbol(span, x) => Ok(SimplExpr::VarRef(span.into(), x)),
Ast::Value(span, x) => Ok(SimplExpr::Literal(span.into(), x)),
Ast::SimplExpr(span, x) => Ok(x),
_ => Err(AstError::NotAValue(Some(e.span()), e.expr_type())),
}
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct Element<C, A> {
name: String,
@ -36,7 +44,7 @@ impl<C: FromAst, A: FromAst> FromAst for Element<C, A> {
let list = e.as_list()?;
let mut iter = AstIterator::new(list.into_iter());
let (_, name) = iter.expect_symbol()?;
let attrs = iter.expect_key_values()?;
let attrs = iter.expect_key_values()?.into_iter().map(|(k, v)| (AttrName(k), v)).collect();
let children = iter.map(C::from_ast).collect::<AstResult<Vec<_>>>()?;
Element { span, name, attrs, children }
})

View file

@ -61,12 +61,12 @@ regex_rules! {
r"\(" => |_| Token::LPren,
r"\)" => |_| Token::RPren,
r"\[" => |_| Token::LBrack,
r"\]" => |_| Token::LBrack,
r"\]" => |_| Token::RBrack,
r"true" => |_| Token::True,
r"false" => |_| Token::False,
r#""(?:[^"\\]|\\.)*""# => |x| Token::StrLit(x),
r#"[+-]?(?:[0-9]+[.])?[0-9]+"# => |x| Token::NumLit(x),
r#"[a-zA-Z_!\?<>/.*-+][^\s{}\(\)]*"# => |x| Token::Symbol(x),
r#"[a-zA-Z_!\?<>/.*-+][^\s{}\(\)\[\](){}]*"# => |x| Token::Symbol(x),
r#":\S+"# => |x| Token::Keyword(x),
r#";.*"# => |_| Token::Comment,
r"[ \t\n\f]+" => |_| Token::Skip
@ -141,7 +141,9 @@ impl Iterator for Lexer {
self.pos += len;
match LEXER_FNS[i](tok_str.to_string()) {
Token::Skip => {}
token => return Some(Ok((old_pos, token, self.pos))),
token => {
return Some(Ok((old_pos, token, self.pos)));
}
}
}
}

View file

@ -33,7 +33,7 @@ pub Ast: Ast = {
<l:@L> <expr:SimplExpr> <r:@R> => Ast::SimplExpr(Span(l, r, file_id), expr),
<x:Keyword> => x,
<x:Symbol> => x,
<l:@L> <x:Value> <r:@R> => Ast::Value(Span(l, r, file_id), x),
<l:@L> <x:Value> <r:@R> => Ast::Value(Span(l, r, file_id), x.into()),
<l:@L> "comment" <r:@R> => Ast::Comment(Span(l, r, file_id)),
};
@ -55,8 +55,8 @@ StrLit: String = {
SimplExpr: SimplExpr = {
<l:@L> <x:"simplexpr"> =>? {
let expr = x[1..x.len() - 1].to_string();
simplexpr::parse_string(&expr).map_err(|e| {
let span = e.get_span().map(|simplexpr::Span(simpl_l, simpl_r)| Span(1 + l + simpl_l, 1 + l + simpl_r, file_id));
simplexpr::parse_string(file_id, &expr).map_err(|e| {
let span = e.get_span().map(|simplexpr::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(span, e) }})
}
}

View file

@ -0,0 +1,17 @@
---
source: src/parser/element.rs
expression: "Element::<Ast, Ast>::from_ast(parser.parse(0, lexer).unwrap()).unwrap()"
---
Element {
name: "box",
attrs: {
":bar": "12",
":baz": "hi",
},
children: [
foo,
(bar),
],
span: 0..33,
}

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, \"1\".to_string()))"
---
Ok(
"1",
)