rosc/
address.rs

1use crate::errors::OscError;
2
3use alloc::string::{String, ToString};
4use alloc::vec::Vec;
5use core::fmt::{Display, Formatter};
6use nom::branch::alt;
7use nom::bytes::complete::{is_a, is_not, tag, take, take_while1, take_while_m_n};
8use nom::character::complete::{char, satisfy};
9use nom::combinator::{all_consuming, complete, opt, recognize, verify};
10use nom::error::{ErrorKind, ParseError};
11use nom::multi::{many1, separated_list1};
12use nom::sequence::{delimited, pair, separated_pair};
13use nom::{IResult, Parser};
14use std::collections::HashSet;
15use std::iter::FromIterator;
16
17/// A valid OSC method address.
18///
19/// A valid OSC address begins with a `/` and contains at least a method name, e.g. `/tempo`.
20/// A plain address must not include any of the following characters `#*,/?[]{}`, since they're reserved for OSC address patterns.
21#[derive(Clone, Debug)]
22pub struct OscAddress(String);
23
24impl OscAddress {
25    pub fn new(address: String) -> Result<Self, OscError> {
26        match verify_address(&address) {
27            Ok(_) => Ok(OscAddress(address)),
28            Err(e) => Err(e),
29        }
30    }
31}
32
33impl Display for OscAddress {
34    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
35        write!(f, "{}", self.0)
36    }
37}
38
39/// With a Matcher OSC method addresses can be [matched](Matcher::match_address) against an OSC address pattern.
40/// Refer to the OSC specification for details about OSC address spaces: <http://opensoundcontrol.org/spec-1_0.html#osc-address-spaces-and-osc-addresses>
41#[derive(Clone, Debug)]
42pub struct Matcher {
43    pub pattern: String,
44    pattern_parts: Vec<AddressPatternComponent>,
45}
46
47impl Matcher {
48    /// Instantiates a new `Matcher` with the given address pattern.
49    /// An error will be returned if the given address pattern is invalid.
50    ///
51    /// Matcher should be instantiated once per pattern and reused because its construction requires parsing the address pattern which is computationally expensive.
52    ///
53    /// A valid address pattern begins with a `/` and contains at least a method name, e.g. `/tempo`.
54    /// OSC defines a couple of rules that look like regular expression but are subtly different:
55    ///
56    /// - `?` matches a single character
57    /// - `*` matches zero or more characters
58    /// - `[a-z]` are basically regex [character classes](https://www.regular-expressions.info/charclass.html)
59    /// - `{foo,bar}` is an alternative, matching either `foo` or `bar`
60    /// - everything else is matched literally
61    ///
62    /// Refer to the OSC specification for details about address pattern matching: <https://opensoundcontrol.stanford.edu/spec-1_0.html#osc-message-dispatching-and-pattern-matching>.
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// use rosc::address::Matcher;
68    ///
69    /// Matcher::new("/tempo").expect("valid address");
70    /// Matcher::new("").expect_err("address does not start with a slash");
71    /// ```
72    pub fn new(pattern: &str) -> Result<Self, OscError> {
73        verify_address_pattern(pattern)?;
74        let mut match_fn = all_consuming(many1(map_address_pattern_component));
75        let (_, pattern_parts) =
76            match_fn(pattern).map_err(|err| OscError::BadAddressPattern(err.to_string()))?;
77
78        Ok(Matcher {
79            pattern: pattern.into(),
80            pattern_parts,
81        })
82    }
83
84    /// Match an OSC address against an address pattern.
85    /// If the address matches the pattern the result will be `true`, otherwise `false`.
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// use rosc::address::{Matcher, OscAddress};
91    ///
92    /// let matcher = Matcher::new("/oscillator/[0-9]/{frequency,phase}").unwrap();
93    /// assert!(matcher.match_address(&OscAddress::new(String::from("/oscillator/1/frequency")).unwrap()));
94    /// assert!(matcher.match_address(&OscAddress::new(String::from("/oscillator/8/phase")).unwrap()));
95    /// assert_eq!(matcher.match_address(&OscAddress::new(String::from("/oscillator/4/detune")).unwrap()), false);
96    /// ```
97    pub fn match_address(&self, address: &OscAddress) -> bool {
98        // Trivial case
99        if address.0 == self.pattern {
100            return true;
101        }
102
103        let mut remainder = address.0.as_str();
104        let mut iter = self.pattern_parts.iter().peekable();
105
106        while let Some(part) = iter.next() {
107            // Match the the address component by component
108            let result = match part {
109                AddressPatternComponent::Tag(s) => match_literally(remainder, s),
110                AddressPatternComponent::WildcardSingle => match_wildcard_single(remainder),
111                AddressPatternComponent::Wildcard(l) => {
112                    match_wildcard(remainder, *l, iter.peek().copied())
113                }
114                AddressPatternComponent::CharacterClass(cc) => match_character_class(remainder, cc),
115                AddressPatternComponent::Choice(s) => match_choice(remainder, s),
116            };
117
118            remainder = match result {
119                Ok((i, _)) => i,
120                Err(_) => return false, // Component didn't match, goodbye
121            };
122        }
123
124        // Address is only matched if it was consumed entirely
125        remainder.is_empty()
126    }
127}
128
129/// Check whether a character is an allowed address character
130/// All printable ASCII characters except for a few special characters are allowed
131fn is_address_character(x: char) -> bool {
132    if !x.is_ascii() || x.is_ascii_control() {
133        return false;
134    }
135
136    ![' ', '#', '*', ',', '/', '?', '[', ']', '{', '}'].contains(&x)
137}
138
139/// Parser to turn a choice like '{foo,bar}' into a vector containing the choices, like ["foo", "bar"]
140fn pattern_choice(input: &str) -> IResult<&str, Vec<&str>> {
141    delimited(
142        char('{'),
143        separated_list1(tag(","), take_while1(is_address_character)),
144        char('}'),
145    )(input)
146}
147
148/// Parser to recognize a character class like [!a-zA-Z] and return '!a-zA-Z'
149fn pattern_character_class(input: &str) -> IResult<&str, &str> {
150    let inner = pair(
151        // It is important to read the leading negating '!' to make sure the rest of the parsed
152        // character class isn't empty. E.g. '[!]' is not a valid character class.
153        recognize(opt(tag("!"))),
154        // Read all remaining character ranges and single characters
155        // We also need to verify that ranges are increasing by ASCII value. For example, a-z is
156        // valid, but z-a or a-a is not.
157        recognize(many1(verify(
158            alt((
159                separated_pair(
160                    satisfy(is_address_character),
161                    char('-'),
162                    satisfy(is_address_character),
163                ),
164                // Need to map this into a tuple to make it compatible with the output of the
165                // separated pair parser above. Will always validate as true.
166                satisfy(is_address_character).map(|c| ('\0', c)),
167            )),
168            |(o1, o2): &(char, char)| o1 < o2,
169        ))),
170    );
171
172    delimited(char('['), recognize(inner), char(']'))(input)
173}
174
175/// A characters class is defined by a set or range of characters that it matches.
176/// For example, [a-z] matches all lowercase alphabetic letters. It can also contain multiple
177/// ranges, like [a-zA-Z]. Instead of a range you can also directly provide the characters to match,
178/// e.g. [abc123]. You can also combine this with ranges, like [a-z123].
179/// If the first characters is an exclamation point, the match is negated, e.g. [!0-9] will match
180/// anything except numbers.
181#[derive(Clone, Debug)]
182struct CharacterClass {
183    pub negated: bool,
184    pub characters: String,
185}
186
187/// Expand a character range like 'a-d' to all the letters contained in the range, e.g. 'abcd'
188/// This is done by converting the characters to their ASCII values and then getting every ASCII
189/// in between.
190fn expand_character_range(first: char, second: char) -> String {
191    let start = first as u8;
192    let end = second as u8;
193
194    let range = start..=end;
195
196    range
197        .into_iter()
198        .map(char::from)
199        .filter(|c| is_address_character(*c))
200        .collect()
201}
202
203impl CharacterClass {
204    pub fn new(s: &str) -> Self {
205        let mut input = s;
206        let negated;
207        match char::<_, nom::error::Error<&str>>('!')(input) {
208            Ok((i, _)) => {
209                negated = true;
210                input = i;
211            }
212            Err(_) => negated = false,
213        }
214
215        let characters = complete(many1(alt((
216            // '!' besides at beginning has no special meaning, but is legal
217            char::<_, nom::error::Error<&str>>('!').map(|_| String::from("")),
218            // attempt to match a range like a-z or 0-9
219            separated_pair(
220                satisfy(is_address_character),
221                char('-'),
222                satisfy(is_address_character),
223            )
224            .map(|(first, second)| expand_character_range(first, second)),
225            // Match characters literally
226            satisfy(is_address_character).map(|x| x.to_string()),
227            // Trailing dash
228            char('-').map(|_| String::from("-")),
229        ))))(input);
230
231        match characters {
232            Ok((_, o)) => CharacterClass {
233                negated,
234                characters: HashSet::<char>::from_iter(o.concat().chars())
235                    .iter()
236                    .collect(),
237            },
238            _ => {
239                panic!("Invalid character class formatting {}", s)
240            }
241        }
242    }
243}
244
245#[derive(Clone, Debug)]
246enum AddressPatternComponent {
247    Tag(String),
248    Wildcard(usize),
249    WildcardSingle,
250    CharacterClass(CharacterClass),
251    Choice(Vec<String>),
252}
253
254fn map_address_pattern_component(input: &str) -> IResult<&str, AddressPatternComponent> {
255    alt((
256        // Anything that's alphanumeric gets matched literally
257        take_while1(is_address_character)
258            .map(|s: &str| AddressPatternComponent::Tag(String::from(s))),
259        // Slashes must be separated into their own tag for the non-greedy implementation of wildcards
260        char('/').map(|c: char| AddressPatternComponent::Tag(c.to_string())),
261        tag("?").map(|_| AddressPatternComponent::WildcardSingle),
262        // Combinations of wildcards are a bit tricky.
263        // Multiple '*' wildcards in a row are equal to a single '*'.
264        // A '*' wildcard followed by any number of '?' wildcards is also equal to '*' but must
265        // match at least the same amount of characters as there are '?' wildcards in the combination.
266        // For example, '*??' must match at least 2 characters.
267        is_a("*?").map(|x: &str| AddressPatternComponent::Wildcard(x.matches('?').count())),
268        pattern_choice.map(|choices: Vec<&str>| {
269            AddressPatternComponent::Choice(choices.iter().map(|x| x.to_string()).collect())
270        }),
271        pattern_character_class
272            .map(|s: &str| AddressPatternComponent::CharacterClass(CharacterClass::new(s))),
273    ))(input)
274}
275
276fn match_literally<'a>(input: &'a str, pattern: &str) -> IResult<&'a str, &'a str> {
277    tag(pattern)(input)
278}
279
280fn match_wildcard_single(input: &str) -> IResult<&str, &str> {
281    take_while_m_n(1, 1, is_address_character)(input)
282}
283
284fn match_character_class<'a>(
285    input: &'a str,
286    character_class: &'a CharacterClass,
287) -> IResult<&'a str, &'a str> {
288    if character_class.negated {
289        is_not(character_class.characters.as_str())(input)
290    } else {
291        is_a(character_class.characters.as_str())(input)
292    }
293}
294
295/// Sequentially try all tags from choice element until one matches or return an error
296/// Example choice element: '{foo,bar}'
297/// It will get parsed into a vector containing the strings "foo" and "bar", which are then matched
298fn match_choice<'a>(input: &'a str, choices: &[String]) -> IResult<&'a str, &'a str> {
299    for choice in choices {
300        if let Ok((i, o)) = tag::<_, _, nom::error::Error<&str>>(choice.as_str())(input) {
301            return Ok((i, o));
302        }
303    }
304    Err(nom::Err::Error(nom::error::Error::from_error_kind(
305        input,
306        ErrorKind::Tag,
307    )))
308}
309
310/// Match Wildcard '*' by either consuming the rest of the part, or, if it's not the last component
311/// in the part, by looking ahead and matching the next component
312fn match_wildcard<'a>(
313    input: &'a str,
314    minimum_length: usize,
315    next: Option<&AddressPatternComponent>,
316) -> IResult<&'a str, &'a str> {
317    // If the next component is a '/', there are no more components in the current part and it can be wholly consumed
318    let next = next.filter(|&part| match part {
319        AddressPatternComponent::Tag(s) => s != "/",
320        _ => true,
321    });
322    match next {
323        // No next component, consume all allowed characters until end or next '/'
324        None => verify(take_while1(is_address_character), |s: &str| {
325            s.len() >= minimum_length
326        })(input),
327        // There is another element in this part, so logic gets a bit more complicated
328        Some(component) => {
329            // Wildcards can only match within the current address part, discard the rest
330            let address_part = match input.split_once('/') {
331                Some((p, _)) => p,
332                None => input,
333            };
334
335            // Attempt to find the latest matching occurrence of the next pattern component
336            // This is a greedy wildcard implementation
337            let mut longest: usize = 0;
338            for i in 0..address_part.len() {
339                let (_, substring) = input.split_at(i);
340                let result: IResult<_, _, nom::error::Error<&str>> = match component {
341                    AddressPatternComponent::Tag(s) => match_literally(substring, s.as_str()),
342                    AddressPatternComponent::CharacterClass(cc) => {
343                        match_character_class(substring, cc)
344                    }
345                    AddressPatternComponent::Choice(s) => match_choice(substring, s),
346                    // These two cases are prevented from happening by map_address_pattern_component
347                    AddressPatternComponent::WildcardSingle => {
348                        panic!("Single wildcard ('?') must not follow wildcard ('*')")
349                    }
350                    AddressPatternComponent::Wildcard(_) => {
351                        panic!("Double wildcards must be condensed into one")
352                    }
353                };
354
355                if result.is_ok() {
356                    longest = i
357                }
358            }
359            verify(take(longest), |s: &str| s.len() >= minimum_length)(input)
360        }
361    }
362}
363
364/// Verify that an address is valid
365///
366/// # Examples
367/// ```
368/// use rosc::address::verify_address;
369///
370/// match verify_address("/oscillator/1") {
371///     Ok(()) => println!("Address is valid"),
372///     Err(e) => println!("Address is not valid")
373/// }
374/// ```
375pub fn verify_address(input: &str) -> Result<(), OscError> {
376    match all_consuming::<_, _, nom::error::Error<&str>, _>(many1(pair(
377        tag("/"),
378        take_while1(is_address_character),
379    )))(input)
380    {
381        Ok(_) => Ok(()),
382        Err(_) => Err(OscError::BadAddress("Invalid address".to_string())),
383    }
384}
385
386/// Parse an address pattern's part until the next '/' or the end
387fn address_pattern_part_parser(input: &str) -> IResult<&str, Vec<&str>> {
388    many1::<_, _, nom::error::Error<&str>, _>(alt((
389        take_while1(is_address_character),
390        tag("?"),
391        tag("*"),
392        recognize(pattern_choice),
393        pattern_character_class,
394    )))(input)
395}
396
397/// Verify that an address pattern is valid
398///
399/// # Examples
400/// ```
401/// use rosc::address::verify_address_pattern;
402///
403/// match verify_address_pattern("/oscillator/[0-9]/*") {
404///     Ok(()) => println!("Address is valid"),
405///     Err(e) => println!("Address is not valid")
406/// }
407/// ```
408pub fn verify_address_pattern(input: &str) -> Result<(), OscError> {
409    match all_consuming(many1(
410        // Each part must start with a '/'. This automatically also prevents a trailing '/'
411        pair(tag("/"), address_pattern_part_parser.map(|x| x.concat())),
412    ))(input)
413    {
414        Ok(_) => Ok(()),
415        Err(_) => Err(OscError::BadAddress("Invalid address pattern".to_string())),
416    }
417}