diff --git a/Cargo.lock b/Cargo.lock index bf2c2b7..f48cb10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "arrayref" version = "0.3.6" @@ -128,6 +137,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "crossbeam-utils" version = "0.8.4" @@ -145,6 +160,29 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "ctor" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "0.99.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "diff" version = "0.1.12" @@ -194,13 +232,20 @@ name = "eww_config" version = "0.1.0" dependencies = [ "codespan-reporting", + "derive_more", "insta", "itertools", "lalrpop", "lalrpop-util", + "lazy_static", "logos", "maplit", + "pretty_assertions", "regex", + "serde", + "serde_json", + "simplexpr", + "smart-default", "thiserror", ] @@ -233,6 +278,15 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.18" @@ -383,6 +437,24 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "output_vt100" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +dependencies = [ + "winapi", +] + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + [[package]] name = "petgraph" version = "0.5.1" @@ -414,6 +486,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "pretty_assertions" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b" +dependencies = [ + "ansi_term", + "ctor", + "diff", + "output_vt100", +] + [[package]] name = "proc-macro2" version = "1.0.27" @@ -478,12 +562,39 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.126" @@ -533,12 +644,40 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec" +[[package]] +name = "simplexpr" +version = "0.1.0" +dependencies = [ + "codespan-reporting", + "itertools", + "lalrpop", + "lalrpop-util", + "logos", + "maplit", + "regex", + "serde", + "serde_json", + "strum", + "thiserror", +] + [[package]] name = "siphasher" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27" +[[package]] +name = "smart-default" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "string_cache" version = "0.8.1" @@ -551,6 +690,27 @@ dependencies = [ "precomputed-hash", ] +[[package]] +name = "strum" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syn" version = "1.0.73" @@ -621,6 +781,18 @@ dependencies = [ "crunchy", ] +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + [[package]] name = "unicode-width" version = "0.1.8" diff --git a/Cargo.toml b/Cargo.toml index 4f2e5ae..79adb01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,16 @@ maplit = "1.0" codespan-reporting = "0.11" logos = "0.12" +derive_more = "0.99" +smart-default = "0.6" +serde = {version = "1.0", features = ["derive"]} +serde_json = "1.0" +lazy_static = "1.4" +pretty_assertions = "0.7" + + +simplexpr = { path = "../../projects/simplexpr" } + [build-dependencies] lalrpop = "0.19.5" diff --git a/examples/errors.rs b/examples/errors.rs index 7f156a3..3cfd837 100644 --- a/examples/errors.rs +++ b/examples/errors.rs @@ -3,7 +3,7 @@ use eww_config::{ast::*, config::*}; fn main() { let mut files = codespan_reporting::files::SimpleFiles::new(); - let input = "(12 :bar 22 (foo) (baz))"; + let input = r#"(hi :bar 22 :baz {"hi" asdfasdf * 2} (foo) (baz))"#; let file_id = files.add("foo.eww", input); let ast = eww_config::parse_string(file_id, input); diff --git a/src/ast.rs b/src/ast.rs index 81033fb..a3cc70b 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,4 +1,5 @@ use itertools::Itertools; +use simplexpr::ast::SimplExpr; use std::collections::HashMap; use crate::{config::FromAst, error::*}; @@ -22,9 +23,11 @@ impl std::fmt::Debug for Span { #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum AstType { List, + Array, Keyword, Symbol, Value, + SimplExpr, Comment, } @@ -37,10 +40,11 @@ impl Display for AstType { #[derive(PartialEq, Eq, Clone)] pub enum Ast { List(Span, Vec), - // ArgList(Span, Vec), + Array(Span, Vec), Keyword(Span, String), Symbol(Span, String), Value(Span, String), + SimplExpr(Span, SimplExpr), Comment(Span), } @@ -74,9 +78,11 @@ impl Ast { pub fn expr_type(&self) -> AstType { match self { Ast::List(..) => AstType::List, + Ast::Array(..) => AstType::Array, Ast::Keyword(..) => AstType::Keyword, Ast::Symbol(..) => AstType::Symbol, Ast::Value(..) => AstType::Value, + Ast::SimplExpr(..) => AstType::SimplExpr, Ast::Comment(_) => AstType::Comment, } } @@ -84,9 +90,11 @@ impl Ast { pub fn span(&self) -> Span { match self { Ast::List(span, _) => *span, + Ast::Array(span, _) => *span, Ast::Keyword(span, _) => *span, Ast::Symbol(span, _) => *span, Ast::Value(span, _) => *span, + Ast::SimplExpr(span, _) => *span, Ast::Comment(span) => *span, } } @@ -104,9 +112,11 @@ impl std::fmt::Display for Ast { use Ast::*; match self { List(_, x) => write!(f, "({})", x.iter().map(|e| format!("{}", e)).join(" ")), + Array(_, x) => write!(f, "({})", x.iter().map(|e| format!("{}", e)).join(" ")), Keyword(_, x) => write!(f, "{}", x), Symbol(_, x) => write!(f, "{}", x), Value(_, x) => write!(f, "{}", x), + SimplExpr(_, x) => write!(f, "{{{}}}", x), Comment(_) => write!(f, ""), } } @@ -116,9 +126,11 @@ impl std::fmt::Debug for Ast { use Ast::*; 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), } } diff --git a/src/config.rs b/src/config.rs index 20d1558..2753ea3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -57,7 +57,7 @@ mod test { fn test() { let parser = parser::AstParser::new(); insta::with_settings!({sort_maps => true}, { - let lexer = lexer::Lexer::new("(box :bar 12 :baz \"hi\" foo (bar))"); + let lexer = lexer::Lexer::new(0, "(box :bar 12 :baz \"hi\" foo (bar))"); insta::assert_debug_snapshot!( Element::::from_ast(parser.parse(0, lexer).unwrap()).unwrap() ); diff --git a/src/error.rs b/src/error.rs index f89e709..0e6ba14 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,13 +1,13 @@ use crate::{ ast::{Ast, AstType, Span}, - lexer, + lexer, parse_error, }; use codespan_reporting::{diagnostic, files}; use thiserror::Error; pub type AstResult = Result; -#[derive(Debug, PartialEq, Eq, Error)] +#[derive(Debug, Error)] pub enum AstError { #[error("Definition invalid")] InvalidDefinition(Option), @@ -17,7 +17,7 @@ pub enum AstError { WrongExprType(Option, AstType, AstType), #[error("Parse error: {source}")] - ParseError { file_id: Option, source: lalrpop_util::ParseError }, + ParseError { file_id: Option, source: lalrpop_util::ParseError }, } impl AstError { @@ -39,21 +39,27 @@ impl AstError { } } - pub fn from_parse_error(file_id: usize, err: lalrpop_util::ParseError) -> AstError { + pub fn from_parse_error( + file_id: usize, + err: lalrpop_util::ParseError, + ) -> AstError { AstError::ParseError { file_id: Some(file_id), source: err } } } fn get_parse_error_span( file_id: usize, - err: &lalrpop_util::ParseError, + err: &lalrpop_util::ParseError, ) -> Option { match err { lalrpop_util::ParseError::InvalidToken { location } => Some(Span(*location, *location, file_id)), lalrpop_util::ParseError::UnrecognizedEOF { location, expected } => Some(Span(*location, *location, file_id)), lalrpop_util::ParseError::UnrecognizedToken { token, expected } => Some(Span(token.0, token.2, file_id)), lalrpop_util::ParseError::ExtraToken { token } => Some(Span(token.0, token.2, file_id)), - lalrpop_util::ParseError::User { error } => None, + lalrpop_util::ParseError::User { error } => match error { + parse_error::ParseError::SimplExpr(span, error) => *span, + parse_error::ParseError::LexicalError(span) => Some(*span), + }, } } diff --git a/src/lexer.rs b/src/lexer.rs index 7a85c67..90d1b84 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -1,12 +1,17 @@ use logos::Logos; +use crate::{ast::Span, parse_error}; + #[derive(Logos, Debug, PartialEq, Eq, Clone)] pub enum Token { #[token("(")] LPren, - #[token(")")] RPren, + #[token("[")] + LBrack, + #[token("]")] + RBrack, #[token("true")] True, @@ -26,6 +31,9 @@ pub enum Token { #[regex(r#":\S+"#, |x| x.slice().to_string())] Keyword(String), + #[regex(r#"\{[^}]*\}"#, |x| x.slice().to_string())] + SimplExpr(String), + #[regex(r#";.*"#)] Comment, @@ -39,46 +47,41 @@ impl std::fmt::Display for Token { match self { Token::LPren => write!(f, "'('"), Token::RPren => write!(f, "')'"), + Token::LBrack => write!(f, "'['"), + Token::RBrack => write!(f, "']'"), Token::True => write!(f, "true"), Token::False => write!(f, "false"), Token::StrLit(x) => write!(f, "\"{}\"", x), Token::NumLit(x) => write!(f, "{}", x), Token::Symbol(x) => write!(f, "{}", x), Token::Keyword(x) => write!(f, "{}", x), + Token::SimplExpr(x) => write!(f, "{{{}}}", x), Token::Comment => write!(f, ""), Token::Error => write!(f, ""), } } } -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -pub struct LexicalError(usize, usize); - -impl std::fmt::Display for LexicalError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Lexical error at {}..{}", self.0, self.1) - } -} - pub type SpannedResult = Result<(Loc, Tok, Loc), Error>; pub struct Lexer<'input> { + file_id: usize, lexer: logos::SpannedIter<'input, Token>, } impl<'input> Lexer<'input> { - pub fn new(text: &'input str) -> Self { - Lexer { lexer: logos::Lexer::new(text).spanned() } + pub fn new(file_id: usize, text: &'input str) -> Self { + Lexer { file_id, lexer: logos::Lexer::new(text).spanned() } } } impl<'input> Iterator for Lexer<'input> { - type Item = SpannedResult; + type Item = SpannedResult; fn next(&mut self) -> Option { let (token, range) = self.lexer.next()?; if token == Token::Error { - Some(Err(LexicalError(range.start, range.end))) + Some(Err(parse_error::ParseError::LexicalError(Span(range.start, range.end, self.file_id)))) } else { Some(Ok((range.start, token, range.end))) } diff --git a/src/lib.rs b/src/lib.rs index f21dad6..cc06510 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,8 @@ pub mod ast; pub mod config; pub mod error; mod lexer; +mod parse_error; +pub mod value; use ast::Ast; use error::{AstError, AstResult}; @@ -18,7 +20,7 @@ use lalrpop_util::lalrpop_mod; lalrpop_mod!(pub parser); pub fn parse_string(file_id: usize, s: &str) -> AstResult { - let lexer = lexer::Lexer::new(s); + let lexer = lexer::Lexer::new(file_id, s); let parser = parser::AstParser::new(); Ok(parser.parse(file_id, lexer).map_err(|e| AstError::from_parse_error(file_id, e))?) } @@ -30,7 +32,7 @@ macro_rules! test_parser { ::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))); )* }); }} diff --git a/src/parse_error.rs b/src/parse_error.rs new file mode 100644 index 0000000..972fb6c --- /dev/null +++ b/src/parse_error.rs @@ -0,0 +1,10 @@ +use crate::ast::Span; + +#[derive(Debug, thiserror::Error)] +pub enum ParseError { + #[error("{1}")] + SimplExpr(Option, simplexpr::error::Error), + + #[error("Unknown token")] + LexicalError(Span), +} diff --git a/src/parser.lalrpop b/src/parser.lalrpop index c434c48..3ae49a8 100644 --- a/src/parser.lalrpop +++ b/src/parser.lalrpop @@ -1,22 +1,28 @@ use std::str::FromStr; -use crate::lexer::{Token, LexicalError}; +use crate::lexer::{Token}; use crate::ast::{Ast, Span}; +use simplexpr::ast::SimplExpr; +use simplexpr; +use lalrpop_util::ParseError; grammar(file_id: usize); extern { type Location = usize; - type Error = LexicalError; + type Error = crate::parse_error::ParseError; enum Token { "(" => Token::LPren, ")" => Token::RPren, + "[" => Token::LBrack, + "]" => Token::RBrack, "true" => Token::True, "false" => Token::False, "string" => Token::StrLit(), "number" => Token::NumLit(), "symbol" => Token::Symbol(), "keyword" => Token::Keyword(), + "simplexpr" => Token::SimplExpr(), "comment" => Token::Comment, } } @@ -24,6 +30,8 @@ extern { pub Ast: Ast = { "(" )+> ")" => Ast::List(Span(l, r, file_id), elems), + "[" )+> "]" => Ast::Array(Span(l, r, file_id), elems), + => Ast::SimplExpr(Span(l, r, file_id), expr), => x, => x, => Ast::Value(Span(l, r, file_id), x), @@ -45,6 +53,15 @@ StrLit: String = { }, }; +SimplExpr: 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)); + ParseError::User { error: crate::parse_error::ParseError::SimplExpr(span, e) }}) + } +} + Num: String = <"number"> => <>.to_string(); Bool: String = { diff --git a/src/value/coords.rs b/src/value/coords.rs new file mode 100644 index 0000000..da15e70 --- /dev/null +++ b/src/value/coords.rs @@ -0,0 +1,112 @@ +use derive_more::*; +use serde::{Deserialize, Serialize}; +use smart_default::SmartDefault; +use std::{fmt, str::FromStr}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Failed to parse \"{0}\" as a length value")] + NumParseFailed(String), + #[error("Inalid unit \"{0}\", must be either % or px")] + InvalidUnit(String), + #[error("Invalid format. Coordinates must be formated like 200x100")] + MalformedCoords, +} + +#[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Display, DebugCustom, SmartDefault)] +pub enum NumWithUnit { + #[display(fmt = "{}%", .0)] + #[debug(fmt = "{}%", .0)] + Percent(i32), + #[display(fmt = "{}px", .0)] + #[debug(fmt = "{}px", .0)] + #[default] + Pixels(i32), +} + +impl NumWithUnit { + pub fn relative_to(&self, max: i32) -> i32 { + match *self { + NumWithUnit::Percent(n) => ((max as f64 / 100.0) * n as f64) as i32, + NumWithUnit::Pixels(n) => n, + } + } +} + +impl FromStr for NumWithUnit { + type Err = Error; + + fn from_str(s: &str) -> Result { + lazy_static::lazy_static! { + static ref PATTERN: regex::Regex = regex::Regex::new("^(-?\\d+)(.*)$").unwrap(); + }; + + let captures = PATTERN.captures(s).ok_or_else(|| Error::NumParseFailed(s.to_string()))?; + let value = captures.get(1).unwrap().as_str().parse::().map_err(|_| Error::NumParseFailed(s.to_string()))?; + match captures.get(2).unwrap().as_str() { + "px" | "" => Ok(NumWithUnit::Pixels(value)), + "%" => Ok(NumWithUnit::Percent(value)), + unit => Err(Error::InvalidUnit(unit.to_string())), + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Display, Default)] +#[display(fmt = "{}*{}", x, y)] +pub struct Coords { + pub x: NumWithUnit, + pub y: NumWithUnit, +} + +impl FromStr for Coords { + type Err = Error; + + fn from_str(s: &str) -> Result { + let (x, y) = s + .split_once(|x: char| x.to_ascii_lowercase() == 'x' || x.to_ascii_lowercase() == '*') + .ok_or_else(|| Error::MalformedCoords)?; + Coords::from_strs(x, y) + } +} + +impl fmt::Debug for Coords { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "CoordsWithUnits({}, {})", self.x, self.y) + } +} + +impl Coords { + pub fn from_pixels(x: i32, y: i32) -> Self { + Coords { x: NumWithUnit::Pixels(x), y: NumWithUnit::Pixels(y) } + } + + /// parse a string for x and a string for y into a [`Coords`] object. + pub fn from_strs(x: &str, y: &str) -> Result { + Ok(Coords { x: x.parse()?, y: y.parse()? }) + } + + /// resolve the possibly relative coordinates relative to a given containers size + pub fn relative_to(&self, width: i32, height: i32) -> (i32, i32) { + (self.x.relative_to(width), self.y.relative_to(height)) + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_parse_num_with_unit() { + assert_eq!(NumWithUnit::Pixels(55), NumWithUnit::from_str("55").unwrap()); + assert_eq!(NumWithUnit::Pixels(55), NumWithUnit::from_str("55px").unwrap()); + assert_eq!(NumWithUnit::Percent(55), NumWithUnit::from_str("55%").unwrap()); + assert!(NumWithUnit::from_str("55pp").is_err()); + } + + #[test] + fn test_parse_coords() { + assert_eq!(Coords { x: NumWithUnit::Pixels(50), y: NumWithUnit::Pixels(60) }, Coords::from_str("50x60").unwrap()); + assert!(Coords::from_str("5060").is_err()); + } +} diff --git a/src/value/mod.rs b/src/value/mod.rs new file mode 100644 index 0000000..b3e80a0 --- /dev/null +++ b/src/value/mod.rs @@ -0,0 +1,41 @@ +use derive_more::*; +use serde::{Deserialize, Serialize}; + +pub mod coords; +pub use coords::*; + +/// The name of a variable +#[repr(transparent)] +#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, DebugCustom)] +#[debug(fmt = "VarName({})", .0)] +pub struct VarName(pub String); + +impl std::borrow::Borrow for VarName { + fn borrow(&self) -> &str { + &self.0 + } +} + +impl From<&str> for VarName { + fn from(s: &str) -> Self { + VarName(s.to_owned()) + } +} + +/// The name of an attribute +#[repr(transparent)] +#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, DebugCustom)] +#[debug(fmt="AttrName({})", .0)] +pub struct AttrName(pub String); + +impl std::borrow::Borrow for AttrName { + fn borrow(&self) -> &str { + &self.0 + } +} + +impl From<&str> for AttrName { + fn from(s: &str) -> Self { + AttrName(s.to_owned()) + } +}