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}