diff --git a/src/animation.rs b/src/animation.rs index a7bb686795ac6171e5af02b149f416661e91925a..6ffccbb5c238cb21b37c09381b9ad3536ef1623b 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -22,12 +22,11 @@ pub struct Frame { impl Frame { pub(crate) fn new(attrs: Vec<OwnedAttribute>) -> Result<Frame> { let (tile_id, duration) = get_attrs!( - attrs, - required: [ - ("tileid", tile_id, |v:String| v.parse().ok()), - ("duration", duration, |v:String| v.parse().ok()), - ], - Error::MalformedAttributes("A frame must have tileid and duration".to_string()) + for v in attrs { + "tileid" => tile_id ?= v.parse::<u32>(), + "duration" => duration ?= v.parse::<u32>(), + } + (tile_id, duration) ); Ok(Frame { tile_id, duration }) } diff --git a/src/image.rs b/src/image.rs index 33014290eb252ccb95b15c7f7fa3d149d3a3d999..b29a310e8af03038ae009bb0b943379339c0bb17 100644 --- a/src/image.rs +++ b/src/image.rs @@ -84,16 +84,13 @@ impl Image { path_relative_to: impl AsRef<Path>, ) -> Result<Image> { let (c, (s, w, h)) = get_attrs!( - attrs, - optionals: [ - ("trans", trans, |v:String| v.parse().ok()), - ], - required: [ - ("source", source, Some), - ("width", width, |v:String| v.parse().ok()), - ("height", height, |v:String| v.parse().ok()), - ], - Error::MalformedAttributes("Image must have a source, width and height with correct types".to_string()) + for v in attrs { + Some("trans") => trans ?= v.parse(), + "source" => source = v, + "width" => width ?= v.parse::<i32>(), + "height" => height ?= v.parse::<i32>(), + } + (trans, (source, width, height)) ); parse_tag!(parser, "image", {}); diff --git a/src/layers/mod.rs b/src/layers/mod.rs index 1272cf4043bc1e948deee64738f6b3a5c2902cf9..ab0e9f53309735cb1b3b2ed6336f7fce66b8f9de 100644 --- a/src/layers/mod.rs +++ b/src/layers/mod.rs @@ -71,18 +71,18 @@ impl LayerData { tilesets: &[MapTilesetGid], ) -> Result<Self> { let (opacity, tint_color, visible, offset_x, offset_y, parallax_x, parallax_y, name, id) = get_attrs!( - attrs, - optionals: [ - ("opacity", opacity, |v:String| v.parse().ok()), - ("tintcolor", tint_color, |v:String| v.parse().ok()), - ("visible", visible, |v:String| v.parse().ok().map(|x:i32| x == 1)), - ("offsetx", offset_x, |v:String| v.parse().ok()), - ("offsety", offset_y, |v:String| v.parse().ok()), - ("parallaxx", parallax_x, |v:String| v.parse().ok()), - ("parallaxy", parallax_y, |v:String| v.parse().ok()), - ("name", name, Some), - ("id", id, |v:String| v.parse().ok()), - ] + for v in attrs { + Some("opacity") => opacity ?= v.parse(), + Some("tintcolor") => tint_color ?= v.parse(), + Some("visible") => visible ?= v.parse().map(|x:i32| x == 1), + Some("offsetx") => offset_x ?= v.parse(), + Some("offsety") => offset_y ?= v.parse(), + Some("parallaxx") => parallax_x ?= v.parse(), + Some("parallaxy") => parallax_y ?= v.parse(), + Some("name") => name = v, + Some("id") => id ?= v.parse(), + } + (opacity, tint_color, visible, offset_x, offset_y, parallax_x, parallax_y, name, id) ); let (ty, properties) = match tag { diff --git a/src/layers/object.rs b/src/layers/object.rs index 45c08d8928c4d5d9b1f0ce945407709497d7d349..1d6c63642bf239d648e20a0de2c912471bdf1f2d 100644 --- a/src/layers/object.rs +++ b/src/layers/object.rs @@ -25,10 +25,10 @@ impl ObjectLayerData { tilesets: Option<&[MapTilesetGid]>, ) -> Result<(ObjectLayerData, Properties)> { let c = get_attrs!( - attrs, - optionals: [ - ("color", colour, |v:String| v.parse().ok()), - ] + for v in attrs { + Some("color") => color ?= v.parse(), + } + color ); let mut objects = Vec::new(); let mut properties = HashMap::new(); diff --git a/src/layers/tile/finite.rs b/src/layers/tile/finite.rs index 0e018fcbf74cd1567d497d5ad9282a2bdda2d9ef..590ea3309ef84487124ea7e929ccf61115a944b4 100644 --- a/src/layers/tile/finite.rs +++ b/src/layers/tile/finite.rs @@ -46,11 +46,11 @@ impl FiniteTileLayerData { tilesets: &[MapTilesetGid], ) -> Result<Self> { let (e, c) = get_attrs!( - attrs, - optionals: [ - ("encoding", encoding, Some), - ("compression", compression, Some), - ] + for v in attrs { + Some("encoding") => encoding = v, + Some("compression") => compression = v, + } + (encoding, compression) ); let tiles = parse_data_line(e, c, parser, tilesets)?; diff --git a/src/layers/tile/infinite.rs b/src/layers/tile/infinite.rs index 9c79e5b1f96be9066ef087eba4960060a2fb51d6..0fc41117fe2baeebde4b0ece9c7606d3f49af41e 100644 --- a/src/layers/tile/infinite.rs +++ b/src/layers/tile/infinite.rs @@ -28,11 +28,11 @@ impl InfiniteTileLayerData { tilesets: &[MapTilesetGid], ) -> Result<Self> { let (e, c) = get_attrs!( - attrs, - optionals: [ - ("encoding", encoding, Some), - ("compression", compression, Some), - ] + for v in attrs { + Some("encoding") => encoding = v, + Some("compression") => compression = v, + } + (encoding, compression) ); let mut chunks = HashMap::<(i32, i32), Chunk>::new(); @@ -191,14 +191,13 @@ impl InternalChunk { tilesets: &[MapTilesetGid], ) -> Result<Self> { let (x, y, width, height) = get_attrs!( - attrs, - required: [ - ("x", x, |v: String| v.parse().ok()), - ("y", y, |v: String| v.parse().ok()), - ("width", width, |v: String| v.parse().ok()), - ("height", height, |v: String| v.parse().ok()), - ], - Error::MalformedAttributes("chunk must have x, y, width & height attributes".to_string()) + for v in attrs { + "x" => x ?= v.parse::<i32>(), + "y" => y ?= v.parse::<i32>(), + "width" => width ?= v.parse::<u32>(), + "height" => height ?= v.parse::<u32>(), + } + (x, y, width, height) ); let tiles = parse_data_line(encoding, compression, parser, tilesets)?; diff --git a/src/layers/tile/mod.rs b/src/layers/tile/mod.rs index ff321208f90ac38ab975c7481db43a37ea7426e3..109554c4bb9f5a3aedd182aeed557c394be447eb 100644 --- a/src/layers/tile/mod.rs +++ b/src/layers/tile/mod.rs @@ -101,12 +101,11 @@ impl TileLayerData { tilesets: &[MapTilesetGid], ) -> Result<(Self, Properties)> { let (width, height) = get_attrs!( - attrs, - required: [ - ("width", width, |v: String| v.parse().ok()), - ("height", height, |v: String| v.parse().ok()), - ], - Error::MalformedAttributes("layer parsing error, width and height attributes required".to_string()) + for v in attrs { + "width" => width ?= v.parse::<u32>(), + "height" => height ?= v.parse::<u32>(), + } + (width, height) ); let mut result = Self::Finite(Default::default()); let mut properties = HashMap::new(); diff --git a/src/map.rs b/src/map.rs index 8871985422d954c99b034c06c99cb521825962eb..23438034174033e5e02d4646d8c9712999f63a03 100644 --- a/src/map.rs +++ b/src/map.rs @@ -158,20 +158,17 @@ impl Map { cache: &mut impl ResourceCache, ) -> Result<Map> { let ((c, infinite), (v, o, w, h, tw, th)) = get_attrs!( - attrs, - optionals: [ - ("backgroundcolor", colour, |v:String| v.parse().ok()), - ("infinite", infinite, |v:String| Some(v == "1")), - ], - required: [ - ("version", version, Some), - ("orientation", orientation, |v:String| v.parse().ok()), - ("width", width, |v:String| v.parse().ok()), - ("height", height, |v:String| v.parse().ok()), - ("tilewidth", tile_width, |v:String| v.parse().ok()), - ("tileheight", tile_height, |v:String| v.parse().ok()), - ], - Error::MalformedAttributes("map must have version, width, height, tilewidth, tileheight and orientation with correct types".to_string()) + for v in attrs { + Some("backgroundcolor") => colour ?= v.parse(), + Some("infinite") => infinite = v == "1", + "version" => version = v, + "orientation" => orientation ?= v.parse::<Orientation>(), + "width" => width ?= v.parse::<u32>(), + "height" => height ?= v.parse::<u32>(), + "tilewidth" => tile_width ?= v.parse::<u32>(), + "tileheight" => tile_height ?= v.parse::<u32>(), + } + ((colour, infinite), (version, orientation, width, height, tile_width, tile_height)) ); let infinite = infinite.unwrap_or(false); @@ -278,6 +275,7 @@ pub enum Orientation { } impl FromStr for Orientation { + // TODO(0.11): Change error type to OrientationParseErr or similar type Err = (); fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { diff --git a/src/objects.rs b/src/objects.rs index b2f82817c2804b77accc66c66db0c482b076b6c8..d56ccecd42cc2b0faf900a6f602a7f63ae381247 100644 --- a/src/objects.rs +++ b/src/objects.rs @@ -82,24 +82,22 @@ impl ObjectData { tilesets: Option<&[MapTilesetGid]>, ) -> Result<ObjectData> { let ((id, tile, n, t, w, h, v, r), (x, y)) = get_attrs!( - attrs, - optionals: [ - ("id", id, |v:String| v.parse().ok()), - ("gid", tile, |v:String| v.parse().ok() - .and_then(|bits| LayerTileData::from_bits(bits, tilesets?))), - ("name", name, |v:String| v.parse().ok()), - ("type", obj_type, |v:String| v.parse().ok()), - ("width", width, |v:String| v.parse().ok()), - ("height", height, |v:String| v.parse().ok()), - ("visible", visible, |v:String| v.parse().ok().map(|x:i32| x == 1)), - ("rotation", rotation, |v:String| v.parse().ok()), - ], - required: [ - ("x", x, |v:String| v.parse().ok()), - ("y", y, |v:String| v.parse().ok()), - ], - Error::MalformedAttributes("objects must have an x and a y number".to_string()) + for v in attrs { + Some("id") => id ?= v.parse(), + Some("gid") => tile ?= v.parse(), + Some("name") => name ?= v.parse(), + Some("type") => obj_type ?= v.parse(), + Some("width") => width ?= v.parse(), + Some("height") => height ?= v.parse(), + Some("visible") => visible ?= v.parse().map(|x:i32| x == 1), + Some("rotation") => rotation ?= v.parse(), + + "x" => x ?= v.parse::<f32>(), + "y" => y ?= v.parse::<f32>(), + } + ((id, tile, name, obj_type, width, height, visible, rotation), (x, y)) ); + let tile = tile.and_then(|bits| LayerTileData::from_bits(bits, tilesets?)); let visible = v.unwrap_or(true); let width = w.unwrap_or(0f32); let height = h.unwrap_or(0f32); @@ -158,26 +156,22 @@ impl ObjectData { impl ObjectData { fn new_polyline(attrs: Vec<OwnedAttribute>) -> Result<ObjectShape> { - let s = get_attrs!( - attrs, - required: [ - ("points", points, Some), - ], - Error::MalformedAttributes("A polyline must have points".to_string()) + let points = get_attrs!( + for v in attrs { + "points" => points ?= ObjectData::parse_points(v), + } + points ); - let points = ObjectData::parse_points(s)?; Ok(ObjectShape::Polyline { points }) } fn new_polygon(attrs: Vec<OwnedAttribute>) -> Result<ObjectShape> { - let s = get_attrs!( - attrs, - required: [ - ("points", points, Some), - ], - Error::MalformedAttributes("A polygon must have points".to_string()) + let points = get_attrs!( + for v in attrs { + "points" => points ?= ObjectData::parse_points(v), + } + points ); - let points = ObjectData::parse_points(s)?; Ok(ObjectShape::Polygon { points }) } diff --git a/src/properties.rs b/src/properties.rs index 63335eabaddc7a8b2876280b7c416b8700f34344..27ab43864103e1720f3b14544096c7b4f38213e0 100644 --- a/src/properties.rs +++ b/src/properties.rs @@ -135,16 +135,13 @@ pub(crate) fn parse_properties( let mut p = HashMap::new(); parse_tag!(parser, "properties", { "property" => |attrs:Vec<OwnedAttribute>| { - let ((t, v_attr), k) = get_attrs!( - attrs, - optionals: [ - ("type", property_type, Some), - ("value", value, Some), - ], - required: [ - ("name", key, Some), - ], - Error::MalformedAttributes("property must have a name and a value".to_string()) + let (t, v_attr, k) = get_attrs!( + for attr in attrs { + Some("type") => obj_type = attr, + Some("value") => value = attr, + "name" => name = attr + } + (obj_type, value, name) ); let t = t.unwrap_or_else(|| "string".to_owned()); diff --git a/src/tile.rs b/src/tile.rs index ddb541317c99ffb0157ed5f59868988082ab996e..b697b6406d94eca5abbb34c0201b9977f818c204 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -66,15 +66,12 @@ impl TileData { path_relative_to: &Path, ) -> Result<(TileId, TileData)> { let ((tile_type, probability), id) = get_attrs!( - attrs, - optionals: [ - ("type", tile_type, |v:String| v.parse().ok()), - ("probability", probability, |v:String| v.parse().ok()), - ], - required: [ - ("id", id, |v:String| v.parse::<u32>().ok()), - ], - Error::MalformedAttributes("tile must have an id with the correct type".to_string()) + for v in attrs { + Some("type") => tile_type ?= v.parse(), + Some("probability") => probability ?= v.parse(), + "id" => id ?= v.parse::<u32>(), + } + ((tile_type, probability), id) ); let mut image = Option::None; diff --git a/src/tileset.rs b/src/tileset.rs index d907aa7df66b1e04a718517f64b7394d0b0e34a8..441be445c426f88e9557bd200fe50a38c1f12ca2 100644 --- a/src/tileset.rs +++ b/src/tileset.rs @@ -138,20 +138,17 @@ impl Tileset { map_path: &Path, ) -> Result<EmbeddedParseResult> { let ((spacing, margin, columns, name), (tilecount, first_gid, tile_width, tile_height)) = get_attrs!( - attrs, - optionals: [ - ("spacing", spacing, |v:String| v.parse().ok()), - ("margin", margin, |v:String| v.parse().ok()), - ("columns", columns, |v:String| v.parse().ok()), - ("name", name, Some), - ], - required: [ - ("tilecount", tilecount, |v:String| v.parse().ok()), - ("firstgid", first_gid, |v:String| v.parse().ok().map(Gid)), - ("tilewidth", width, |v:String| v.parse().ok()), - ("tileheight", height, |v:String| v.parse().ok()), - ], - Error::MalformedAttributes("tileset must have a firstgid, tilecount, tilewidth, and tileheight with correct types".to_string()) + for v in attrs { + Some("spacing") => spacing ?= v.parse(), + Some("margin") => margin ?= v.parse(), + Some("columns") => columns ?= v.parse(), + Some("name") => name = v, + "tilecount" => tilecount ?= v.parse::<u32>(), + "firstgid" => first_gid ?= v.parse::<u32>().map(Gid), + "tilewidth" => tile_width ?= v.parse::<u32>(), + "tileheight" => tile_height ?= v.parse::<u32>(), + } + ((spacing, margin, columns, name), (tilecount, first_gid, tile_width, tile_height)) ); let root_path = map_path.parent().ok_or(Error::PathIsNotFile)?.to_owned(); @@ -180,12 +177,11 @@ impl Tileset { map_path: &Path, ) -> Result<EmbeddedParseResult> { let (first_gid, source) = get_attrs!( - attrs, - required: [ - ("firstgid", first_gid, |v:String| v.parse().ok().map(Gid)), - ("source", name, Some), - ], - Error::MalformedAttributes("Tileset reference must have a firstgid and source with correct types".to_string()) + for v in attrs { + "firstgid" => first_gid ?= v.parse::<u32>().map(Gid), + "source" => source = v, + } + (first_gid, source) ); let tileset_path = map_path.parent().ok_or(Error::PathIsNotFile)?.join(source); @@ -202,19 +198,17 @@ impl Tileset { path: &Path, ) -> Result<Tileset> { let ((spacing, margin, columns, name), (tilecount, tile_width, tile_height)) = get_attrs!( - attrs, - optionals: [ - ("spacing", spacing, |v:String| v.parse().ok()), - ("margin", margin, |v:String| v.parse().ok()), - ("columns", columns, |v:String| v.parse().ok()), - ("name", name, Some), - ], - required: [ - ("tilecount", tilecount, |v:String| v.parse().ok()), - ("tilewidth", width, |v:String| v.parse().ok()), - ("tileheight", height, |v:String| v.parse().ok()), - ], - Error::MalformedAttributes("tileset must have a name, tile width and height with correct types".to_string()) + for v in attrs { + Some("spacing") => spacing ?= v.parse(), + Some("margin") => margin ?= v.parse(), + Some("columns") => columns ?= v.parse(), + Some("name") => name = v, + + "tilecount" => tilecount ?= v.parse::<u32>(), + "tilewidth" => tile_width ?= v.parse::<u32>(), + "tileheight" => tile_height ?= v.parse::<u32>(), + } + ((spacing, margin, columns, name), (tilecount, tile_width, tile_height)) ); let root_path = path.parent().ok_or(Error::PathIsNotFile)?.to_owned(); diff --git a/src/util.rs b/src/util.rs index 4e874f24775ad872345b70872c9d88b73ea3275a..a10222d520d558510098dc9f139626833f31ea68 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,55 +1,182 @@ /// Loops through the attributes once and pulls out the ones we ask it to. It -/// will check that the required ones are there. This could have been done with -/// attrs.find but that would be inefficient. +/// will check that the required ones are there. +/// +/// The syntax is: +/// ```ignore +/// get_attrs!( +/// for $attr in $attributes { +/// $($branch),* +/// } +/// $expression_to_return +/// ) +/// ``` +/// Where `$attributes` is anything that implements `Iterator<Item = OwnedAttribute>`, +/// and `$attr` is the value of the attribute (a String) going to be used in each branch. +/// +/// Each branch indicates a variable to be set once a certain attribute is found. +/// Its syntax is as follows: +/// ```ignore +/// "attribute name" => variable_name = expression_using_$attr, +/// ``` +/// +/// For instance: +/// ```ignore +/// "source" => source = v, +/// ``` +/// The variable set has an inferred type `T`. In this case, `source` is inferred to be a `String`, +/// and `$attr` has been named `v`. +/// +/// If `Some` encapsulates the attribute name (like so: `Some("attribute name")`) then the attribute +/// is meant to be optional, which will make the variable an `Option<T>` rather than `T`. Even if it +/// is technically an Option, the assignment is still done *as if it was `T`*, for instance: +/// ```ignore +/// Some("name") => name = v, +/// ``` +/// +/// Finally, branches can also use `?=` instead of `=`, which will make them accept a `Result<T, E>` +/// instead. If the expression results in an Err, the error will be handled internally and the +/// iteration will return early with a `Result<T, crate::Error>`. +/// +/// Here are some examples of valid branches: +/// ```ignore +/// Some("spacing") => spacing ?= v.parse(), +/// Some("margin") => margin ?= v.parse(), +/// Some("columns") => columns ?= v.parse(), +/// Some("name") => name = v, +/// +/// "tilecount" => tilecount ?= v.parse::<u32>(), +/// "tilewidth" => tile_width ?= v.parse::<u32>(), +/// "tileheight" => tile_height ?= v.parse::<u32>(), +/// ``` +/// +/// Finally, after the `for` block, `$expression_to_return` indicates what to return once the +/// iteration has finished. It may refer to variables declared previously. +/// +/// ## Example +/// ```ignore +/// let ((c, infinite), (v, o, w, h, tw, th)) = get_attrs!( +/// for v in attrs { +/// Some("backgroundcolor") => colour ?= v.parse(), +/// Some("infinite") => infinite = v == "1", +/// "version" => version = v, +/// "orientation" => orientation ?= v.parse::<Orientation>(), +/// "width" => width ?= v.parse::<u32>(), +/// "height" => height ?= v.parse::<u32>(), +/// "tilewidth" => tile_width ?= v.parse::<u32>(), +/// "tileheight" => tile_height ?= v.parse::<u32>(), +/// } +/// ((colour, infinite), (version, orientation, width, height, tile_width, tile_height)) +/// ); +/// ``` macro_rules! get_attrs { - ($attrs:expr, optionals: [$(($oName:pat, $oVar:ident, $oMethod:expr)),+ $(,)*] - , required: [$(($name:pat, $var:ident, $method:expr)),+ $(,)*], $err:expr) => { + ( + for $attr:ident in $attrs:ident { + $($branches:tt)* + } + $ret_expr:expr + ) => { { - $(let mut $oVar = None;)* - $(let mut $var = None;)* - $crate::util::match_attrs!($attrs, match: [$(($oName, $oVar, $oMethod)),+, $(($name, $var, $method)),+]); + $crate::util::let_attr_branches!($($branches)*); - if !(true $(&& $var.is_some())*) { - return Err($err); + for attr in $attrs.iter() { + let $attr = attr.value.clone(); + $crate::util::process_attr_branches!(attr; $($branches)*); } - ( - ($($oVar),*), - ($($var.unwrap()),*) - ) + + $crate::util::handle_attr_branches!($($branches)*); + + $ret_expr } }; - ($attrs:expr, optionals: [$(($oName:pat, $oVar:ident, $oMethod:expr)),+ $(,)*]) => { - { - $(let mut $oVar = None;)+ - $crate::util::match_attrs!($attrs, match: [$(($oName, $oVar, $oMethod)),+]); - ($($oVar),*) +} + +macro_rules! let_attr_branches { + () => {}; + + (Some($attr_pat_opt:literal) => $opt_var:ident $(?)?= $opt_expr:expr $(, $($tail:tt)*)?) => { + let mut $opt_var = None; + $crate::util::let_attr_branches!($($($tail)*)?); + }; + + ($attr_pat_opt:literal => $opt_var:ident $(?)?= $opt_expr:expr $(, $($tail:tt)*)?) => { + let mut $opt_var = None; + $crate::util::let_attr_branches!($($($tail)*)?); + }; +} + +pub(crate) use let_attr_branches; + +macro_rules! process_attr_branches { + ($attr:ident; ) => {}; + + ($attr:ident; Some($attr_pat_opt:literal) => $opt_var:ident = $opt_expr:expr $(, $($tail:tt)*)?) => { + if(&$attr.name.local_name == $attr_pat_opt) { + $opt_var = Some($opt_expr); + } + else { + $crate::util::process_attr_branches!($attr; $($($tail)*)?); } }; - ($attrs:expr, required: [$(($name:pat, $var:ident, $method:expr)),+ $(,)*], $err:expr) => { - { - $(let mut $var = None;)* - $crate::util::match_attrs!($attrs, match: [$(($name, $var, $method)),+]); - if !(true $(&& $var.is_some())*) { - return Err($err); - } + ($attr:ident; Some($attr_pat_opt:literal) => $opt_var:ident ?= $opt_expr:expr $(, $($tail:tt)*)?) => { + if(&$attr.name.local_name == $attr_pat_opt) { + $opt_var = Some($opt_expr.map_err(|_| + $crate::Error::MalformedAttributes( + concat!("Error parsing optional attribute '", $attr_pat_opt, "'").to_owned() + ) + )?); + } + else { + $crate::util::process_attr_branches!($attr; $($($tail)*)?); + } + }; - ($($var.unwrap()),*) + ($attr:ident; $attr_pat_opt:literal => $opt_var:ident = $opt_expr:expr $(, $($tail:tt)*)?) => { + if(&$attr.name.local_name == $attr_pat_opt) { + $opt_var = Some($opt_expr); + } + else { + $crate::util::process_attr_branches!($attr; $($($tail)*)?); } }; -} -macro_rules! match_attrs { - ($attrs:expr, match: [$(($name:pat, $var:ident, $method:expr)),*]) => { - for attr in $attrs.iter() { - match <String as AsRef<str>>::as_ref(&attr.name.local_name) { - $($name => $var = $method(attr.value.clone()),)* - _ => {} - } + ($attr:ident; $attr_pat_opt:literal => $opt_var:ident ?= $opt_expr:expr $(, $($tail:tt)*)?) => { + if(&$attr.name.local_name == $attr_pat_opt) { + $opt_var = Some($opt_expr.map_err(|_| + $crate::Error::MalformedAttributes( + concat!("Error parsing attribute '", $attr_pat_opt, "'").to_owned() + ) + )?); + } + else { + $crate::util::process_attr_branches!($attr; $($($tail)*)?); } } } +pub(crate) use process_attr_branches; + +macro_rules! handle_attr_branches { + () => {}; + + (Some($attr_pat_opt:literal) => $opt_var:ident $(?)?= $opt_expr:expr $(, $($tail:tt)*)?) => { + $crate::util::handle_attr_branches!($($($tail)*)?); + }; + + ($attr_pat_opt:literal => $opt_var:ident $(?)?= $opt_expr:expr $(, $($tail:tt)*)?) => { + let $opt_var = $opt_var + .ok_or_else(|| + Error::MalformedAttributes( + concat!("Missing attribute: ", $attr_pat_opt).to_owned() + ) + )?; + + $crate::util::handle_attr_branches!($($($tail)*)?); + }; +} + +pub(crate) use handle_attr_branches; + /// Goes through the children of the tag and will call the correct function for /// that child. Closes the tag. macro_rules! parse_tag { @@ -112,7 +239,6 @@ macro_rules! map_wrapper { pub(crate) use get_attrs; pub(crate) use map_wrapper; -pub(crate) use match_attrs; pub(crate) use parse_tag; use crate::{Gid, MapTilesetGid};