From dde69276d47818174c35523c8ed86b6888b6d02b Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 13 Jan 2020 14:36:40 +0100 Subject: [PATCH] =?UTF-8?q?Refactor=20expressions=20and=20create=20tuples?= =?UTF-8?q?=20and=20objects=20=F0=9F=A7=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/func/mod.rs | 2 +- src/library/maps/axis.rs | 27 ++-- src/library/maps/mod.rs | 38 ++++-- src/library/maps/padding.rs | 9 +- src/library/mod.rs | 18 +-- src/syntax/color.rs | 2 +- src/syntax/expr.rs | 245 +++++++++++++++--------------------- src/syntax/parsing.rs | 97 +++++++++++++- src/syntax/span.rs | 4 +- src/syntax/tokens.rs | 4 +- tests/parse.rs | 2 +- 11 files changed, 266 insertions(+), 182 deletions(-) diff --git a/src/func/mod.rs b/src/func/mod.rs index 01c77327e..5f4918d92 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -16,7 +16,7 @@ pub mod prelude { pub use crate::layout::prelude::*; pub use crate::syntax::{ ParseContext, ParseResult, - SyntaxTree, FuncCall, FuncArgs, PosArg, KeyArg, + SyntaxTree, FuncCall, FuncArgs, Expression, Ident, ExpressionKind, Spanned, Span }; diff --git a/src/library/maps/axis.rs b/src/library/maps/axis.rs index 238d146a7..3c3a8c118 100644 --- a/src/library/maps/axis.rs +++ b/src/library/maps/axis.rs @@ -34,6 +34,13 @@ key!(AxisKey, "axis", "secondary" | "s" => Generic(Secondary), ); +key!(Direction, "direction", + "left-to-right" | "ltr" => LeftToRight, + "right-to-left" | "rtl" => RightToLeft, + "top-to-bottom" | "ttb" => TopToBottom, + "bottom-to-top" | "btt" => BottomToTop, +); + /// A map for storing extents along axes. #[derive(Debug, Clone, PartialEq)] pub struct ExtentMap(ConsistentMap); @@ -41,27 +48,27 @@ pub struct ExtentMap(ConsistentMap); impl ExtentMap { /// Parse an extent map from the function args. /// - /// If `enforce` is true other arguments will create an error, otherwise + /// If `all` is true other arguments will create an error, otherwise /// they are left intact. - pub fn new(args: &mut FuncArgs, enforce: bool) -> ParseResult> { + pub fn new(args: &mut FuncArgs, all: bool) -> ParseResult> { let mut map = ConsistentMap::new(); - for arg in args.keys() { - let key = match arg.v.key.v.as_str() { + for arg in args.iter_keys() { + let key = match arg.key.v.as_str() { "width" | "w" => AxisKey::Specific(Horizontal), "height" | "h" => AxisKey::Specific(Vertical), "primary-size" | "ps" => AxisKey::Generic(Primary), "secondary-size" | "ss" => AxisKey::Generic(Secondary), - _ => if enforce { + _ => if all { error!("expected dimension") } else { - args.add_key(arg); + args.add_key_pair(arg); continue; } }; - let e = E::from_expr(arg.v.value)?; + let e = E::from_expr(arg.value)?; map.add(key, e)?; } @@ -98,9 +105,9 @@ impl PosAxisMap { map.add_opt(PosAxisKey::First, args.get_pos_opt::()?)?; map.add_opt(PosAxisKey::Second, args.get_pos_opt::()?)?; - for arg in args.keys() { - let axis = AxisKey::from_ident(&arg.v.key)?; - let value = E::from_expr(arg.v.value)?; + for arg in args.iter_keys() { + let axis = AxisKey::from_ident(&arg.key)?; + let value = E::from_expr(arg.value)?; map.add(PosAxisKey::Keyword(axis), value)?; } diff --git a/src/library/maps/mod.rs b/src/library/maps/mod.rs index 5e130d53e..a868ce6c7 100644 --- a/src/library/maps/mod.rs +++ b/src/library/maps/mod.rs @@ -36,6 +36,37 @@ pub_use_mod!(axis); pub_use_mod!(alignment); pub_use_mod!(padding); + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum DefaultKey { + Some(T), + None, +} + +impl Into> for DefaultKey { + fn into(self) -> Option { + match self { + DefaultKey::Some(v) => Some(v), + DefaultKey::None => None, + } + } +} + +impl ExpressionKind for DefaultKey where T: ExpressionKind { + const NAME: &'static str = T::NAME; + + fn from_expr(expr: Spanned) -> ParseResult> { + if let Expression::Ident(ident) = &expr.v { + match ident.as_str() { + "default" => return Ok(DefaultKey::None), + _ => {}, + } + } + + T::from_expr(expr).map(|v| DefaultKey::Some(v)) + } +} + /// A deduplicating map type useful for storing possibly redundant arguments. #[derive(Debug, Clone, PartialEq)] pub struct ConsistentMap where K: Hash + Eq { @@ -95,10 +126,3 @@ impl ConsistentMap where K: Hash + Eq { self.map.iter() } } - -key!(Direction, "direction", - "left-to-right" | "ltr" => LeftToRight, - "right-to-left" | "rtl" => RightToLeft, - "top-to-bottom" | "ttb" => TopToBottom, - "bottom-to-top" | "btt" => BottomToTop, -); diff --git a/src/library/maps/padding.rs b/src/library/maps/padding.rs index 4bbbb754f..e2d0ea09e 100644 --- a/src/library/maps/padding.rs +++ b/src/library/maps/padding.rs @@ -46,11 +46,12 @@ impl PaddingMap { /// Parse a padding map from the function args. pub fn new(args: &mut FuncArgs) -> ParseResult { let mut map = ConsistentMap::new(); - map.add_opt(PaddingKey::All, args.get_pos_opt::>()?)?; + map.add_opt(PaddingKey::All, + args.get_pos_opt::>()?.map(Into::into))?; - for arg in args.keys() { - let key = PaddingKey::from_ident(&arg.v.key)?; - let size = Option::::from_expr(arg.v.value)?; + for arg in args.iter_keys() { + let key = PaddingKey::from_ident(&arg.key)?; + let size = DefaultKey::::from_expr(arg.value)?.into(); map.add(key, size)?; } diff --git a/src/library/mod.rs b/src/library/mod.rs index 92c3c9488..f86259048 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -62,7 +62,7 @@ function! { FontFamilyFunc { body: parse!(optional: body, ctx), list: { - args.pos().map(|arg| match arg.v { + args.iter_pos().map(|arg| match arg.v { Expression::Str(s) | Expression::Ident(Ident(s)) => Ok(s.to_lowercase()), _ => error!("expected identifier or string"), @@ -118,7 +118,7 @@ function! { FontWeightFunc { body: parse!(optional: body, ctx), weight: match args.get_pos::()? { - Expression::Num(weight) => { + Expression::Number(weight) => { let weight = weight.round() as i16; FontWeight( if weight < 100 { 100 } @@ -264,13 +264,15 @@ function! { axis: AxisKey::Specific(axis), spacing: FSize::from_expr(args.get_pos::>()?)?, } - } else if let Some(arg) = args.get_key_next() { - let axis = AxisKey::from_ident(&arg.v.key) - .map_err(|_| error!(@unexpected_argument))?; - - let spacing = FSize::from_expr(arg.v.value)?; - SpacingFunc { axis, spacing } } else { + for arg in args.iter_keys() { + let axis = AxisKey::from_ident(&arg.key) + .map_err(|_| error!(@unexpected_argument))?; + + let spacing = FSize::from_expr(arg.value)?; + return Ok(SpacingFunc { axis, spacing }); + } + error!("expected axis and spacing") } } diff --git a/src/syntax/color.rs b/src/syntax/color.rs index 716cb6883..7f34fad71 100644 --- a/src/syntax/color.rs +++ b/src/syntax/color.rs @@ -15,7 +15,7 @@ pub enum ColorToken { Brace, ExprIdent, - ExprString, + ExprStr, ExprNumber, ExprSize, ExprBool, diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index ed5e50df8..e2df3c4e6 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -1,125 +1,16 @@ use super::*; -/// The arguments passed to a function. -#[derive(Debug, Clone, PartialEq)] -pub struct FuncArgs { - pub pos: Vec>, - pub key: Vec>, -} - -impl FuncArgs { - /// Create an empty collection of arguments. - pub fn new() -> FuncArgs { - FuncArgs { - pos: vec![], - key: vec![], - } - } - - /// Add a positional argument. - pub fn add_pos(&mut self, arg: Spanned) { - self.pos.push(arg); - } - - /// Add a keyword argument. - pub fn add_key(&mut self, arg: Spanned) { - self.key.push(arg); - } - - /// Force-extract the first positional argument. - pub fn get_pos(&mut self) -> ParseResult { - expect(self.get_pos_opt()) - } - - /// Extract the first positional argument. - pub fn get_pos_opt(&mut self) -> ParseResult> { - Ok(if !self.pos.is_empty() { - let spanned = self.pos.remove(0); - Some(E::from_expr(spanned)?) - } else { - None - }) - } - - /// Iterator over positional arguments. - pub fn pos(&mut self) -> std::vec::IntoIter> { - let vec = std::mem::replace(&mut self.pos, vec![]); - vec.into_iter() - } - - /// Force-extract a keyword argument. - pub fn get_key(&mut self, name: &str) -> ParseResult { - expect(self.get_key_opt(name)) - } - - /// Extract a keyword argument. - pub fn get_key_opt(&mut self, name: &str) -> ParseResult> { - Ok(if let Some(index) = self.key.iter().position(|arg| arg.v.key.v.0 == name) { - let value = self.key.swap_remove(index).v.value; - Some(E::from_expr(value)?) - } else { - None - }) - } - - /// Extract any keyword argument. - pub fn get_key_next(&mut self) -> Option> { - self.key.pop() - } - - /// Iterator over all keyword arguments. - pub fn keys(&mut self) -> std::vec::IntoIter> { - let vec = std::mem::replace(&mut self.key, vec![]); - vec.into_iter() - } - - /// Clear the argument lists. - pub fn clear(&mut self) { - self.pos.clear(); - self.key.clear(); - } - - /// Whether both the positional and keyword argument lists are empty. - pub fn is_empty(&self) -> bool { - self.pos.is_empty() && self.key.is_empty() - } -} - -/// Extract the option expression kind from the option or return an error. -fn expect(opt: ParseResult>) -> ParseResult { - match opt { - Ok(Some(spanned)) => Ok(spanned), - Ok(None) => error!("expected {}", E::NAME), - Err(e) => Err(e), - } -} - -/// A positional argument passed to a function. -pub type PosArg = Expression; - -/// A keyword argument passed to a function. -#[derive(Debug, Clone, PartialEq)] -pub struct KeyArg { - pub key: Spanned, - pub value: Spanned, -} - -/// Either a positional or keyword argument. -#[derive(Debug, Clone, PartialEq)] -pub enum DynArg { - Pos(Spanned), - Key(Spanned), -} - /// An argument or return value. #[derive(Clone, PartialEq)] pub enum Expression { Ident(Ident), Str(String), - Num(f64), + Number(f64), Size(Size), Bool(bool), + Tuple(Tuple), + Object(Object), } impl Display for Expression { @@ -128,20 +19,17 @@ impl Display for Expression { match self { Ident(i) => write!(f, "{}", i), Str(s) => write!(f, "{:?}", s), - Num(n) => write!(f, "{}", n), + Number(n) => write!(f, "{}", n), Size(s) => write!(f, "{}", s), Bool(b) => write!(f, "{}", b), + Tuple(t) => write!(f, "{}", t), + Object(o) => write!(f, "{}", o), } } } -debug_display!(Expression); - -pub struct Tuple; -pub struct Object; - /// An identifier. -#[derive(Clone, PartialEq)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Ident(pub String); impl Ident { @@ -164,16 +52,98 @@ impl Display for Ident { } } +/// A sequence of expressions. +#[derive(Clone, PartialEq)] +pub struct Tuple { + pub items: Vec>, +} + +impl Tuple { + pub fn new() -> Tuple { + Tuple { items: vec![] } + } + + pub fn add(&mut self, item: Spanned) { + self.items.push(item); + } +} + +impl Display for Tuple { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "(")?; + + let mut first = true; + for item in &self.items { + if !first { + write!(f, ", ")?; + } + write!(f, "{}", item.v)?; + first = false; + } + + write!(f, ")") + } +} + +/// A key-value collection of identifiers and associated expressions. +#[derive(Clone, PartialEq)] +pub struct Object { + pub pairs: Vec, +} + +#[derive(Clone, PartialEq)] +pub struct Pair { + pub key: Spanned, + pub value: Spanned, +} + +impl Object { + pub fn new() -> Object { + Object { pairs: vec![] } + } + + pub fn add(&mut self, key: Spanned, value: Spanned) { + self.pairs.push(Pair { key, value }); + } + + pub fn add_pair(&mut self, pair: Pair) { + self.pairs.push(pair); + } +} + +impl Display for Object { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{{ ")?; + + let mut first = true; + for pair in &self.pairs { + if !first { + write!(f, ", ")?; + } + write!(f, "{}: {}", pair.key.v, pair.value.v)?; + first = false; + } + + write!(f, " }}") + } +} + debug_display!(Ident); +debug_display!(Expression); +debug_display!(Tuple); +debug_display!(Object); + /// Kinds of expressions. pub trait ExpressionKind: Sized { + /// The name of the expression in an `expected ` error. const NAME: &'static str; /// Create from expression. fn from_expr(expr: Spanned) -> ParseResult; } +/// Implements the expression kind trait for a type. macro_rules! kind { ($type:ty, $name:expr, $($patterns:tt)*) => { impl ExpressionKind for $type { @@ -190,15 +160,18 @@ macro_rules! kind { }; } -kind!(Expression, "expression", e => e); -kind!(Ident, "identifier", Expression::Ident(ident) => ident); -kind!(String, "string", Expression::Str(string) => string); -kind!(f64, "number", Expression::Num(num) => num); -kind!(bool, "boolean", Expression::Bool(boolean) => boolean); -kind!(Size, "size", Expression::Size(size) => size); +kind!(Expression, "expression", e => e); +kind!(Ident, "identifier", Expression::Ident(ident) => ident); +kind!(String, "string", Expression::Str(string) => string); +kind!(f64, "number", Expression::Number(num) => num); +kind!(bool, "boolean", Expression::Bool(boolean) => boolean); +kind!(Size, "size", Expression::Size(size) => size); +kind!(Tuple, "tuple", Expression::Tuple(tuple) => tuple); +kind!(Object, "object", Expression::Object(object) => object); + kind!(ScaleSize, "number or size", - Expression::Size(size) => ScaleSize::Absolute(size), - Expression::Num(scale) => ScaleSize::Scaled(scale as f32) + Expression::Size(size) => ScaleSize::Absolute(size), + Expression::Number(scale) => ScaleSize::Scaled(scale as f32) ); impl ExpressionKind for Spanned where T: ExpressionKind { @@ -206,22 +179,6 @@ impl ExpressionKind for Spanned where T: ExpressionKind { fn from_expr(expr: Spanned) -> ParseResult> { let span = expr.span; - T::from_expr(expr) - .map(|v| Spanned::new(v, span)) - } -} - -impl ExpressionKind for Option where T: ExpressionKind { - const NAME: &'static str = T::NAME; - - fn from_expr(expr: Spanned) -> ParseResult> { - if let Expression::Ident(ident) = &expr.v { - match ident.as_str() { - "default" | "none" => return Ok(None), - _ => {}, - } - } - - T::from_expr(expr).map(|v| Some(v)) + T::from_expr(expr).map(|v| Spanned { v, span }) } } diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index 112c2f656..47322485c 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -47,6 +47,99 @@ impl PartialEq for FuncCall { } } +#[derive(Debug)] +pub struct FuncArgs { + positional: Tuple, + keyword: Object, +} + +impl FuncArgs { + fn new() -> FuncArgs { + FuncArgs { + positional: Tuple::new(), + keyword: Object::new(), + } + } + + /// Add a positional argument. + pub fn add_pos(&mut self, item: Spanned) { + self.positional.add(item); + } + + /// Force-extract the first positional argument. + pub fn get_pos(&mut self) -> ParseResult { + expect(self.get_pos_opt()) + } + + /// Extract the first positional argument. + pub fn get_pos_opt(&mut self) -> ParseResult> { + Ok(if !self.positional.items.is_empty() { + let spanned = self.positional.items.remove(0); + Some(E::from_expr(spanned)?) + } else { + None + }) + } + + /// Add a keyword argument. + pub fn add_key(&mut self, key: Spanned, value: Spanned) { + self.keyword.add(key, value); + } + + /// Add a keyword argument from an existing pair. + pub fn add_key_pair(&mut self, pair: Pair) { + self.keyword.add_pair(pair); + } + + /// Force-extract a keyword argument. + pub fn get_key(&mut self, name: &str) -> ParseResult { + expect(self.get_key_opt(name)) + } + + /// Extract a keyword argument. + pub fn get_key_opt(&mut self, name: &str) -> ParseResult> { + self.keyword.pairs.iter() + .position(|p| p.key.v.0 == name) + .map(|index| { + let value = self.keyword.pairs.swap_remove(index).value; + E::from_expr(value) + }) + .transpose() + } + + /// Iterator over positional arguments. + pub fn iter_pos(&mut self) -> std::vec::IntoIter> { + let tuple = std::mem::replace(&mut self.positional, Tuple::new()); + tuple.items.into_iter() + } + + /// Iterator over all keyword arguments. + pub fn iter_keys(&mut self) -> std::vec::IntoIter { + let object = std::mem::replace(&mut self.keyword, Object::new()); + object.pairs.into_iter() + } + + /// Clear the argument lists. + pub fn clear(&mut self) { + self.positional.items.clear(); + self.keyword.pairs.clear(); + } + + /// Whether both the positional and keyword argument lists are empty. + pub fn is_empty(&self) -> bool { + self.positional.items.is_empty() && self.keyword.pairs.is_empty() + } +} + +/// Extract the option expression kind from the option or return an error. +fn expect(opt: ParseResult>) -> ParseResult { + match opt { + Ok(Some(spanned)) => Ok(spanned), + Ok(None) => error!("expected {}", E::NAME), + Err(e) => Err(e), + } +} + /// Parses source code into a syntax tree given a context. pub fn parse(src: &str, ctx: ParseContext) -> SyntaxTree { Parser::new(src, ctx).parse() @@ -346,7 +439,7 @@ impl<'s> Parser<'s> { Comma => Some(ColorToken::Comma), Equals => Some(ColorToken::Equals), ExprIdent(_) => Some(ColorToken::ExprIdent), - ExprString(_) => Some(ColorToken::ExprString), + ExprStr(_) => Some(ColorToken::ExprStr), ExprNumber(_) => Some(ColorToken::ExprNumber), ExprSize(_) => Some(ColorToken::ExprSize), ExprBool(_) => Some(ColorToken::ExprBool), @@ -387,7 +480,7 @@ fn name(token: Token) -> &'static str { Comma => "comma", Equals => "equals sign", ExprIdent(_) => "identifier", - ExprString(_) => "string", + ExprStr(_) => "string", ExprNumber(_) => "number", ExprSize(_) => "size", ExprBool(_) => "bool", diff --git a/src/syntax/span.rs b/src/syntax/span.rs index 10188ed44..e5c6912b8 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Display, Formatter}; /// Annotates a value with the part of the source code it corresponds to. -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct Spanned { pub v: T, pub span: Span, @@ -37,7 +37,7 @@ impl Display for Spanned where T: std::fmt::Debug { debug_display!(Spanned; T where T: std::fmt::Debug); /// Describes a slice of source code. -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct Span { pub start: Position, pub end: Position, diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index ae5cfe480..295a4382f 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -45,7 +45,7 @@ pub enum Token<'s> { /// An identifier in a function header: `center`. ExprIdent(&'s str), /// A quoted string in a function header: `"..."`. - ExprString(&'s str), + ExprStr(&'s str), /// A number in a function header: `3.14`. ExprNumber(f64), /// A size in a function header: `12pt`. @@ -220,7 +220,7 @@ impl<'s> Tokens<'s> { fn parse_string(&mut self) -> Token<'s> { let mut escaped = false; - ExprString(self.read_string_until(|n| { + ExprStr(self.read_string_until(|n| { if n == '"' && !escaped { return true; } else if n == '\\' { diff --git a/tests/parse.rs b/tests/parse.rs index 02c4f9b7b..3e46dd4a1 100644 --- a/tests/parse.rs +++ b/tests/parse.rs @@ -10,7 +10,7 @@ use Token::{ LeftParen as LP, RightParen as RP, LeftBrace as LBR, RightBrace as RBR, Colon as CL, Comma as CM, Equals as EQ, - ExprIdent as ID, ExprString as STR, ExprSize as SIZE, + ExprIdent as ID, ExprStr as STR, ExprSize as SIZE, ExprNumber as NUM, ExprBool as BOOL, Star as ST, Underscore as U, Backtick as B, Text as T, };