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