1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 use std::str::CharIndices;
6 
7 // support arguments like '4', 'ab', '4.0', '>=10.14', '*123'
acceptable_arg_character(c: char) -> bool8 fn acceptable_arg_character(c: char) -> bool {
9     c.is_alphanumeric() || c == '.' || c == '-' || c == '<' || c == '>' || c == '=' || c == '*'
10 }
11 
12 // A crappy parser for parsing strings like "translate(1, 3) blahblah"
13 // Returns a tuple with three components:
14 // - First component is the function name (e.g. "translate")
15 // - Second component is the list of arguments (e.g. vec!["1", "3"])
16 // - Third component is the rest of the string "blahblah"
parse_function(s: &str) -> (&str, Vec<&str>, &str)17 pub fn parse_function(s: &str) -> (&str, Vec<&str>, &str) {
18     // XXX: This is not particularly easy to read. Sorry.
19     struct Parser<'a> {
20         itr: CharIndices<'a>,
21         start: usize,
22         o: Option<(usize, char)>,
23     }
24     impl<'a> Parser<'a> {
25         fn skip_whitespace(&mut self) {
26             while let Some(k) = self.o {
27                 if !k.1.is_whitespace() {
28                     break;
29                 }
30                 self.start = k.0 + k.1.len_utf8();
31                 self.o = self.itr.next();
32             }
33         }
34     }
35     let mut c = s.char_indices();
36     let o = c.next();
37     let mut p = Parser {
38         itr: c,
39         start: 0,
40         o,
41     };
42 
43     p.skip_whitespace();
44 
45     let mut end = p.start;
46     while let Some(k) = p.o {
47         if !k.1.is_alphabetic() && k.1 != '_' && k.1 != '-' {
48             break;
49         }
50         end = k.0 + k.1.len_utf8();
51         p.o = p.itr.next();
52     }
53 
54     let name = &s[p.start .. end];
55     let mut args = Vec::new();
56 
57     p.skip_whitespace();
58 
59     if let Some(k) = p.o {
60         if k.1 != '(' {
61             return (name, args, &s[p.start ..]);
62         }
63         p.start = k.0 + k.1.len_utf8();
64         p.o = p.itr.next();
65     }
66 
67     loop {
68         p.skip_whitespace();
69 
70         let mut end = p.start;
71         let mut brackets: Vec<char> = Vec::new();
72         while let Some(k) = p.o {
73             let prev_bracket_count = brackets.len();
74             match k.1 {
75                 '[' | '(' => brackets.push(k.1),
76                 ']' | ')' => {
77                     let open_bracket = match k.1 {
78                         ']' => '[',
79                         ')' => '(',
80                         _ => panic!(),
81                     };
82                     match brackets.pop() {
83                         // Allow final closing ) for command invocation after args
84                         None if k.1 == ')' => break,
85                         Some(bracket) if bracket == open_bracket => {}
86                         _ => panic!("Unexpected closing bracket {}", k.1),
87                     }
88                 }
89                 _ => {}
90             }
91 
92             let not_in_bracket = brackets.is_empty() && prev_bracket_count == 0;
93             if !acceptable_arg_character(k.1) && not_in_bracket {
94                 break;
95             }
96             end = k.0 + k.1.len_utf8();
97             p.o = p.itr.next();
98         }
99 
100         args.push(&s[p.start .. end]);
101 
102         p.skip_whitespace();
103 
104         if let Some(k) = p.o {
105             p.start = k.0 + k.1.len_utf8();
106             p.o = p.itr.next();
107             // unless we find a comma we're done
108             if k.1 != ',' {
109                 if k.1 != ')' {
110                     panic!("Unexpected closing character: {}", k.1);
111                 }
112                 break;
113             }
114         } else {
115             break;
116         }
117     }
118     (name, args, &s[p.start ..])
119 }
120 
121 #[test]
test()122 fn test() {
123     assert_eq!(parse_function("rotate(40)").0, "rotate");
124     assert_eq!(parse_function("  rotate(40)").0, "rotate");
125     assert_eq!(parse_function("  rotate  (40)").0, "rotate");
126     assert_eq!(parse_function("  rotate  (  40 )").1[0], "40");
127     assert_eq!(parse_function("rotate(-40.0)").1[0], "-40.0");
128     assert_eq!(parse_function("drop-shadow(0, [1, 2, 3, 4], 5)").1[0], "0");
129     assert_eq!(parse_function("drop-shadow(0, [1, 2, 3, 4], 5)").1[1], "[1, 2, 3, 4]");
130     assert_eq!(parse_function("drop-shadow(0, [1, 2, 3, 4], 5)").1[2], "5");
131     assert_eq!(parse_function("drop-shadow(0, [1, 2, [3, 4]], 5)").1[1], "[1, 2, [3, 4]]");
132     assert_eq!(parse_function("func(nest([1, 2]), [3, 4])").1[0], "nest([1, 2])");
133     assert_eq!(parse_function("func(nest([1, 2]), [nest(3), nest(4)])").1[1], "[nest(3), nest(4)]");
134 }
135