summaryrefslogtreecommitdiffstats
path: root/gfx/wr/wrench/src/parse_function.rs
blob: 92040b7680c3d01aaf037d0211f96bd4cfd55c0d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/* 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)]");
}