Bug 1429298 - Part 3: Use macro for path parser. r=emilio
authorBoris Chiou <boris.chiou@gmail.com>
Fri, 13 Jul 2018 16:04:37 -0700
changeset 481060 d78aadc6a6bf4674fac3658c6207e73a319f58c9
parent 481059 04a196dc2cc2b0f8f4b3a6eeb4f088f9fc7aae4a
child 481061 f7b80e0bc7bf7c694848b8e5cf21c3fab8d4c1f9
push id232
push userfmarier@mozilla.com
push dateWed, 05 Sep 2018 20:45:54 +0000
reviewersemilio
bugs1429298
milestone63.0a1
Bug 1429298 - Part 3: Use macro for path parser. r=emilio There are a lot of duplicates, so we use macro to refine them. Differential Revision: https://phabricator.services.mozilla.com/D2966
servo/components/style/values/specified/motion.rs
--- a/servo/components/style/values/specified/motion.rs
+++ b/servo/components/style/values/specified/motion.rs
@@ -65,16 +65,44 @@ impl Parse for OffsetPath {
 }
 
 /// SVG Path parser.
 struct PathParser<'a> {
     chars: Peekable<Chars<'a>>,
     path: Vec<PathCommand>,
 }
 
+macro_rules! parse_arguments {
+    (
+        $parser:ident,
+        $abs:ident,
+        $enum:ident,
+        [ $para:ident => $func:ident $(, $other_para:ident => $other_func:ident)* ]
+    ) => {
+        {
+            loop {
+                let $para = $func(&mut $parser.chars)?;
+                $(
+                    skip_comma_wsp(&mut $parser.chars);
+                    let $other_para = $other_func(&mut $parser.chars)?;
+                )*
+                $parser.path.push(PathCommand::$enum { $para $(, $other_para)*, $abs });
+
+                // End of string or the next character is a possible new command.
+                if !skip_wsp(&mut $parser.chars) ||
+                   $parser.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
+                    break;
+                }
+                skip_comma_wsp(&mut $parser.chars);
+            }
+            Ok(())
+        }
+    }
+}
+
 impl<'a> PathParser<'a> {
     /// Parse a sub-path.
     fn parse_subpath(&mut self) -> Result<(), ()> {
         // Handle "moveto" Command first. If there is no "moveto", this is not a valid sub-path
         // (i.e. not a valid moveto-drawto-command-group).
         self.parse_moveto()?;
 
         // Handle other commands.
@@ -82,56 +110,40 @@ impl<'a> PathParser<'a> {
             skip_wsp(&mut self.chars);
             if self.chars.peek().map_or(true, |m| *m == 'M' || *m == 'm') {
                 break;
             }
 
             match self.chars.next() {
                 Some(command) => {
                     let abs = command.is_uppercase();
-                    match command {
-                        'Z' | 'z' => {
-                            // Note: A "closepath" coulbe be followed immediately by "moveto" or
-                            // any other command, so we don't break this loop.
-                            self.path.push(PathCommand::ClosePath);
-                        },
-                        'L' | 'l' => {
-                            skip_wsp(&mut self.chars);
-                            self.parse_lineto(abs)?;
-                        },
-                        'H' | 'h' => {
-                            skip_wsp(&mut self.chars);
-                            self.parse_h_lineto(abs)?;
-                        },
-                        'V' | 'v' => {
-                            skip_wsp(&mut self.chars);
-                            self.parse_v_lineto(abs)?;
-                        },
-                        'C' | 'c' => {
-                            skip_wsp(&mut self.chars);
-                            self.parse_curveto(abs)?;
-                        },
-                        'S' | 's' => {
-                            skip_wsp(&mut self.chars);
-                            self.parse_smooth_curveto(abs)?;
-                        },
-                        'Q' | 'q' => {
-                            skip_wsp(&mut self.chars);
-                            self.parse_quadratic_bezier_curveto(abs)?;
-                        },
-                        'T' | 't' => {
-                            skip_wsp(&mut self.chars);
-                            self.parse_smooth_quadratic_bezier_curveto(abs)?;
-                        },
-                        'A' | 'a' => {
-                            skip_wsp(&mut self.chars);
-                            self.parse_elliprical_arc(abs)?;
-                        },
-                        _ => return Err(()),
+                    macro_rules! parse_command {
+                        ( $($($p:pat)|+ => $parse_func:ident,)* ) => {
+                            match command {
+                                $(
+                                    $($p)|+ => {
+                                        skip_wsp(&mut self.chars);
+                                        self.$parse_func(abs)?;
+                                    },
+                                )*
+                                _ => return Err(()),
+                            }
+                        }
                     }
+                    parse_command!(
+                        'Z' | 'z' => parse_closepath,
+                        'L' | 'l' => parse_lineto,
+                        'H' | 'h' => parse_h_lineto,
+                        'V' | 'v' => parse_v_lineto,
+                        'C' | 'c' => parse_curveto,
+                        'S' | 's' => parse_smooth_curveto,
+                        'Q' | 'q' => parse_quadratic_bezier_curveto,
+                        'T' | 't' => parse_smooth_quadratic_bezier_curveto,
+                        'A' | 'a' => parse_elliprical_arc,
+                    );
                 },
                 _ => break, // no more commands.
             }
         }
         Ok(())
     }
 
     /// Parse "moveto" command.
@@ -153,179 +165,82 @@ impl<'a> PathParser<'a> {
         }
         skip_comma_wsp(&mut self.chars);
 
         // If a moveto is followed by multiple pairs of coordinates, the subsequent
         // pairs are treated as implicit lineto commands.
         self.parse_lineto(absolute)
     }
 
+    /// Parse "closepath" command.
+    fn parse_closepath(&mut self, _absolute: bool) -> Result<(), ()> {
+        self.path.push(PathCommand::ClosePath);
+        Ok(())
+    }
+
     /// Parse "lineto" command.
     fn parse_lineto(&mut self, absolute: bool) -> Result<(), ()> {
-        loop {
-            let point = parse_coord(&mut self.chars)?;
-            self.path.push(PathCommand::LineTo { point, absolute });
-
-            // End of string or the next character is a possible new command.
-            if !skip_wsp(&mut self.chars) ||
-               self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
-                break;
-            }
-            skip_comma_wsp(&mut self.chars);
-        }
-        Ok(())
+        parse_arguments!(self, absolute, LineTo, [ point => parse_coord ])
     }
 
     /// Parse horizontal "lineto" command.
     fn parse_h_lineto(&mut self, absolute: bool) -> Result<(), ()> {
-        loop {
-            let x = parse_number(&mut self.chars)?;
-            self.path.push(PathCommand::HorizontalLineTo { x, absolute });
-
-            // End of string or the next character is a possible new command.
-            if !skip_wsp(&mut self.chars) ||
-               self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
-                break;
-            }
-            skip_comma_wsp(&mut self.chars);
-        }
-        Ok(())
+        parse_arguments!(self, absolute, HorizontalLineTo, [ x => parse_number ])
     }
 
     /// Parse vertical "lineto" command.
     fn parse_v_lineto(&mut self, absolute: bool) -> Result<(), ()> {
-        loop {
-            let y = parse_number(&mut self.chars)?;
-            self.path.push(PathCommand::VerticalLineTo { y, absolute });
-
-            // End of string or the next character is a possible new command.
-            if !skip_wsp(&mut self.chars) ||
-               self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
-                break;
-            }
-            skip_comma_wsp(&mut self.chars);
-        }
-        Ok(())
+        parse_arguments!(self, absolute, VerticalLineTo, [ y => parse_number ])
     }
 
     /// Parse cubic Bézier curve command.
     fn parse_curveto(&mut self, absolute: bool) -> Result<(), ()> {
-        loop {
-            let control1 = parse_coord(&mut self.chars)?;
-            skip_comma_wsp(&mut self.chars);
-            let control2 = parse_coord(&mut self.chars)?;
-            skip_comma_wsp(&mut self.chars);
-            let point = parse_coord(&mut self.chars)?;
-
-            self.path.push(PathCommand::CurveTo { control1, control2, point, absolute });
-
-            // End of string or the next character is a possible new command.
-            if !skip_wsp(&mut self.chars) ||
-               self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
-                break;
-            }
-            skip_comma_wsp(&mut self.chars);
-        }
-        Ok(())
+        parse_arguments!(self, absolute, CurveTo, [
+            control1 => parse_coord, control2 => parse_coord, point => parse_coord
+        ])
     }
 
     /// Parse smooth "curveto" command.
     fn parse_smooth_curveto(&mut self, absolute: bool) -> Result<(), ()> {
-        loop {
-            let control2 = parse_coord(&mut self.chars)?;
-            skip_comma_wsp(&mut self.chars);
-            let point = parse_coord(&mut self.chars)?;
-
-            self.path.push(PathCommand::SmoothCurveTo { control2, point, absolute });
-
-            // End of string or the next character is a possible new command.
-            if !skip_wsp(&mut self.chars) ||
-               self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
-                break;
-            }
-            skip_comma_wsp(&mut self.chars);
-        }
-        Ok(())
+        parse_arguments!(self, absolute, SmoothCurveTo, [
+            control2 => parse_coord, point => parse_coord
+        ])
     }
 
     /// Parse quadratic Bézier curve command.
     fn parse_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> {
-        loop {
-            let control1 = parse_coord(&mut self.chars)?;
-            skip_comma_wsp(&mut self.chars);
-            let point = parse_coord(&mut self.chars)?;
-
-            self.path.push(PathCommand::QuadBezierCurveTo { control1, point, absolute });
-
-            // End of string or the next character is a possible new command.
-            if !skip_wsp(&mut self.chars) ||
-               self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
-                break;
-            }
-            skip_comma_wsp(&mut self.chars);
-        }
-        Ok(())
+        parse_arguments!(self, absolute, QuadBezierCurveTo, [
+            control1 => parse_coord, point => parse_coord
+        ])
     }
 
     /// Parse smooth quadratic Bézier curveto command.
     fn parse_smooth_quadratic_bezier_curveto(&mut self, absolute: bool) -> Result<(), ()> {
-        loop {
-            let point = parse_coord(&mut self.chars)?;
-
-            self.path.push(PathCommand::SmoothQuadBezierCurveTo { point, absolute });
-
-            // End of string or the next character is a possible new command.
-            if !skip_wsp(&mut self.chars) ||
-               self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
-                break;
-            }
-            skip_comma_wsp(&mut self.chars);
-        }
-        Ok(())
+        parse_arguments!(self, absolute, SmoothQuadBezierCurveTo, [ point => parse_coord ])
     }
 
     /// Parse elliptical arc curve command.
     fn parse_elliprical_arc(&mut self, absolute: bool) -> Result<(), ()> {
         // Parse a flag whose value is '0' or '1'; otherwise, return Err(()).
         let parse_flag = |iter: &mut Peekable<Chars>| -> Result<bool, ()> {
             let value = match iter.peek() {
                 Some(c) if *c == '0' || *c == '1' => *c == '1',
                 _ => return Err(()),
             };
             iter.next();
             Ok(value)
         };
-
-        loop {
-            let rx = parse_number(&mut self.chars)?;
-            skip_comma_wsp(&mut self.chars);
-            let ry = parse_number(&mut self.chars)?;
-            skip_comma_wsp(&mut self.chars);
-            let angle = parse_number(&mut self.chars)?;
-            skip_comma_wsp(&mut self.chars);
-            let large_arc_flag = parse_flag(&mut self.chars)?;
-            skip_comma_wsp(&mut self.chars);
-            let sweep_flag = parse_flag(&mut self.chars)?;
-            skip_comma_wsp(&mut self.chars);
-            let point = parse_coord(&mut self.chars)?;
-
-            self.path.push(
-                PathCommand::EllipticalArc {
-                    rx, ry, angle, large_arc_flag, sweep_flag, point, absolute
-                }
-            );
-
-            // End of string or the next character is a possible new command.
-            if !skip_wsp(&mut self.chars) ||
-               self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) {
-                break;
-            }
-            skip_comma_wsp(&mut self.chars);
-        }
-        Ok(())
+        parse_arguments!(self, absolute, EllipticalArc, [
+            rx => parse_number,
+            ry => parse_number,
+            angle => parse_number,
+            large_arc_flag => parse_flag,
+            sweep_flag => parse_flag,
+            point => parse_coord
+        ])
     }
 }
 
 /// The SVG path data.
 ///
 /// https://www.w3.org/TR/SVG11/paths.html#PathData
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue)]
 pub struct SVGPathData(Box<[PathCommand]>);