Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use std::str::CharIndices;
// support arguments like '4', 'ab', '4.0', '>=10.14', '*123'
fn acceptable_arg_character(c: char) -> bool {
c.is_alphanumeric() || c == '.' || c == '-' || c == '<' || c == '>' || c == '=' || c == '*'
}
// A crappy parser for parsing strings like "translate(1, 3) blahblah"
// Returns a tuple with three components:
// - First component is the function name (e.g. "translate")
// - Second component is the list of arguments (e.g. vec!["1", "3"])
// - Third component is the rest of the string "blahblah"
pub fn parse_function(s: &str) -> (&str, Vec<&str>, &str) {
// XXX: This is not particularly easy to read. Sorry.
struct Parser<'a> {
itr: CharIndices<'a>,
start: usize,
o: Option<(usize, char)>,
}
impl<'a> Parser<'a> {
fn skip_whitespace(&mut self) {
while let Some(k) = self.o {
if !k.1.is_whitespace() {
break;
}
self.start = k.0 + k.1.len_utf8();
self.o = self.itr.next();
}
}
}
let mut c = s.char_indices();
let o = c.next();
let mut p = Parser {
itr: c,
start: 0,
o,
};
p.skip_whitespace();
let mut end = p.start;
while let Some(k) = p.o {
if !k.1.is_alphabetic() && k.1 != '_' && k.1 != '-' {
break;
}
end = k.0 + k.1.len_utf8();
p.o = p.itr.next();
}
let name = &s[p.start .. end];
let mut args = Vec::new();
p.skip_whitespace();
if let Some(k) = p.o {
if k.1 != '(' {
return (name, args, &s[p.start ..]);
}
p.start = k.0 + k.1.len_utf8();
p.o = p.itr.next();
}
loop {
p.skip_whitespace();
let mut end = p.start;
let mut brackets: Vec<char> = Vec::new();
while let Some(k) = p.o {
let prev_bracket_count = brackets.len();
match k.1 {
'[' | '(' => brackets.push(k.1),
']' | ')' => {
let open_bracket = match k.1 {
']' => '[',
')' => '(',
_ => panic!(),
};
match brackets.pop() {
// Allow final closing ) for command invocation after args
None if k.1 == ')' => break,
Some(bracket) if bracket == open_bracket => {}
_ => panic!("Unexpected closing bracket {}", k.1),
}
}
_ => {}
}
let not_in_bracket = brackets.is_empty() && prev_bracket_count == 0;
if !acceptable_arg_character(k.1) && not_in_bracket {
break;
}
end = k.0 + k.1.len_utf8();
p.o = p.itr.next();
}
args.push(&s[p.start .. end]);
p.skip_whitespace();
if let Some(k) = p.o {
p.start = k.0 + k.1.len_utf8();
p.o = p.itr.next();
// unless we find a comma we're done
if k.1 != ',' {
if k.1 != ')' {
panic!("Unexpected closing character: {}", k.1);
}
break;
}
} else {
break;
}
}
(name, args, &s[p.start ..])
}
#[test]
fn test() {
assert_eq!(parse_function("rotate(40)").0, "rotate");
assert_eq!(parse_function(" rotate(40)").0, "rotate");
assert_eq!(parse_function(" rotate (40)").0, "rotate");
assert_eq!(parse_function(" rotate ( 40 )").1[0], "40");
assert_eq!(parse_function("rotate(-40.0)").1[0], "-40.0");
assert_eq!(parse_function("drop-shadow(0, [1, 2, 3, 4], 5)").1[0], "0");
assert_eq!(parse_function("drop-shadow(0, [1, 2, 3, 4], 5)").1[1], "[1, 2, 3, 4]");
assert_eq!(parse_function("drop-shadow(0, [1, 2, 3, 4], 5)").1[2], "5");
assert_eq!(parse_function("drop-shadow(0, [1, 2, [3, 4]], 5)").1[1], "[1, 2, [3, 4]]");
assert_eq!(parse_function("func(nest([1, 2]), [3, 4])").1[0], "nest([1, 2])");
assert_eq!(parse_function("func(nest([1, 2]), [nest(3), nest(4)])").1[1], "[nest(3), nest(4)]");
}