Skip to main content

audiobox/mixer/events/
parse.rs

1use super::{Choice, Definition, EventBank};
2use crate::mixer::{Attenuation, DistanceModel, Emitter, Fade, Instances, Loop, OverLimit, Play};
3use crate::{Error, ErrorCode};
4use std::collections::BTreeSet;
5use std::fmt;
6use std::str::FromStr;
7
8/// Stable error code and one-based line/character column of an offending token.
9/// Syntax errors use `Syntax`, out-of-range values/unresolved references use
10/// `InvalidArgument`, and text/event/source-entry bounds use `LimitExceeded`.
11#[derive(Clone, Debug, Eq, PartialEq)]
12pub struct ParseError {
13    pub line: usize,
14    pub column: usize,
15    pub code: ErrorCode,
16    pub message: String,
17}
18impl fmt::Display for ParseError {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        write!(
21            f,
22            "line {}, column {}: {}",
23            self.line, self.column, self.message
24        )
25    }
26}
27impl std::error::Error for ParseError {}
28impl From<ParseError> for Error {
29    fn from(e: ParseError) -> Self {
30        Self::new(e.code, e.to_string())
31    }
32}
33#[derive(Clone, Copy, Debug)]
34pub(super) struct Location {
35    line: usize,
36    column: usize,
37}
38impl Location {
39    fn error(self, code: ErrorCode, message: impl Into<String>) -> ParseError {
40        ParseError {
41            line: self.line,
42            column: self.column,
43            code,
44            message: message.into(),
45        }
46    }
47    pub(super) fn invalid(self, message: impl Into<String>) -> ParseError {
48        self.error(ErrorCode::InvalidArgument, message)
49    }
50    fn syntax(self, message: impl Into<String>) -> ParseError {
51        self.error(ErrorCode::Syntax, message)
52    }
53    fn number<T: FromStr>(self, s: &str) -> Result<T, ParseError> {
54        s.parse().map_err(|_| self.syntax("Invalid number"))
55    }
56    fn unsigned(self, s: &str) -> Result<u64, ParseError> {
57        use std::num::IntErrorKind;
58        s.parse::<u64>().map_err(|e| {
59            let negative = s.strip_prefix('-').is_some_and(|digits| {
60                !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit())
61            });
62            if negative
63                || matches!(
64                    e.kind(),
65                    IntErrorKind::PosOverflow | IntErrorKind::NegOverflow
66                )
67            {
68                self.invalid("Integer outside the supported range")
69            } else {
70                self.syntax("Invalid integer")
71            }
72        })
73    }
74    fn float(self, s: &str, min: f32, max: f32) -> Result<f32, ParseError> {
75        let f: f32 = self.number(s)?;
76        if !f.is_finite() || f < min || f > max {
77            return Err(self.invalid("Number outside the supported range"));
78        }
79        Ok(f)
80    }
81    fn vector<const N: usize>(self, s: &str) -> Result<[f32; N], ParseError> {
82        let mut parts = s.split(',');
83        let mut result = [0.0; N];
84        for value in &mut result {
85            *value = self.float(
86                parts
87                    .next()
88                    .ok_or_else(|| self.syntax("Missing vector coordinate"))?,
89                -f32::MAX,
90                f32::MAX,
91            )?;
92        }
93        if parts.next().is_some() {
94            return Err(self.syntax("Too many vector coordinates"));
95        }
96        Ok(result)
97    }
98    fn range(self, s: &str, min: f32, max: f32) -> Result<[f32; 2], ParseError> {
99        let range = if s.contains(',') {
100            self.vector(s)?
101        } else {
102            [self.float(s, min, max)?; 2]
103        };
104        if range[0] < min || range[1] > max || range[0] > range[1] {
105            return Err(self.invalid("Range needs min <= max within supported bounds"));
106        }
107        Ok(range)
108    }
109}
110pub(super) fn name_ok(s: &str) -> bool {
111    !s.is_empty()
112        && s.len() <= 64
113        && s.bytes()
114            .all(|b| b.is_ascii_alphanumeric() || b"_-.".contains(&b))
115}
116pub(super) fn bank(text: &str) -> Result<EventBank, ParseError> {
117    let origin = Location { line: 1, column: 1 };
118    if text.len() > 1024 * 1024 {
119        return Err(origin.error(ErrorCode::LimitExceeded, "Event text exceeds 1 MiB"));
120    }
121    let mut definitions = Vec::new();
122    let mut names = BTreeSet::new();
123    for (i, raw) in text.lines().enumerate() {
124        let line = raw.split('#').next().unwrap_or("");
125        // Advancing the suffix keeps both Unicode whitespace and character columns correct.
126        let mut suffix = line;
127        let mut tokens = Vec::new();
128        let mut column = 1;
129        for token in line.split_whitespace() {
130            let offset = suffix.find(token).expect("token in suffix");
131            let byte = line.len() - suffix.len() + offset;
132            column += suffix[..offset].chars().count();
133            tokens.push((
134                token,
135                Location {
136                    line: i + 1,
137                    column,
138                },
139            ));
140            suffix = &line[byte + token.len()..];
141            column += token.chars().count();
142        }
143        if tokens.is_empty() {
144            continue;
145        }
146        let (head, location) = tokens[0];
147        if head != "event" {
148            return Err(location.syntax("Expected event"));
149        }
150        let &(name, name_location) = tokens
151            .get(1)
152            .ok_or_else(|| location.syntax("Missing event name"))?;
153        if !name_ok(name) || !names.insert(name) {
154            return Err(name_location.syntax("Invalid or duplicate event name"));
155        }
156        if definitions.len() == 1024 {
157            return Err(location.error(
158                ErrorCode::LimitExceeded,
159                "At most 1024 events are supported",
160            ));
161        }
162        let mut d = Definition {
163            name: name.to_owned(),
164            sources: Vec::new(),
165            choice: Choice::Random,
166            gain: [1.0; 2],
167            pitch: [0.0; 2],
168            bus: "master".into(),
169            play: Play {
170                instances: Some(Instances::new(0)),
171                ..Play::default()
172            },
173            source_location: name_location,
174            bus_location: name_location,
175            loop_location: name_location,
176        };
177        let mut seen = BTreeSet::new();
178        let mut emitter = Emitter::default();
179        let mut emitter_location = None;
180        let mut spatial_detail = None;
181        let mut position_location = None;
182        for &(token, at) in &tokens[2..] {
183            let (key, value) = token
184                .split_once('=')
185                .ok_or_else(|| at.syntax("Expected key=value"))?;
186            if value.is_empty() || !seen.insert(key) {
187                return Err(at.syntax("Empty value or repeated property"));
188            }
189            match key {
190                "sources" => {
191                    for source in value.split(',') {
192                        if !name_ok(source) {
193                            return Err(at.syntax("Invalid source name"));
194                        }
195                        if d.sources.len() == 64 {
196                            return Err(at.error(
197                                ErrorCode::LimitExceeded,
198                                "At most 64 source entries per event",
199                            ));
200                        }
201                        d.sources.push(source.to_owned());
202                    }
203                    d.source_location = at;
204                }
205                "choice" => {
206                    d.choice = match value {
207                        "random" => Choice::Random,
208                        "shuffle" => Choice::Shuffle,
209                        "sequence" => Choice::Sequence,
210                        _ => return Err(at.syntax("Expected random, shuffle or sequence")),
211                    }
212                }
213                "gain" => d.gain = at.range(value, 0.0, f32::MAX)?,
214                "pitch" => d.pitch = at.range(value, -48.0, 48.0)?,
215                "bus" => {
216                    if !name_ok(value) {
217                        return Err(at.syntax("Invalid bus name"));
218                    }
219                    d.bus = value.into();
220                    d.bus_location = at;
221                }
222                "priority" => {
223                    let n = at.unsigned(value)?;
224                    d.play.priority =
225                        u8::try_from(n).map_err(|_| at.invalid("Priority must be 0..=255"))?;
226                }
227                "max-instances" => {
228                    let n = at.unsigned(value)?;
229                    d.play.instances.as_mut().unwrap().max = u16::try_from(n)
230                        .map_err(|_| at.invalid("Instance maximum must be 0..=65535"))?;
231                }
232                "min-gap" => {
233                    let gap: f64 = at.number(value)?;
234                    if !gap.is_finite() || !(0.0..=60.0).contains(&gap) {
235                        return Err(at.invalid("Minimum gap must be 0..=60 seconds"));
236                    }
237                    d.play.instances.as_mut().unwrap().min_gap = gap;
238                }
239                "policy" => {
240                    d.play.instances.as_mut().unwrap().policy = match value {
241                        "refuse" => OverLimit::Refuse,
242                        "oldest" => OverLimit::StopOldest,
243                        "quietest" => OverLimit::StopQuietest,
244                        _ => return Err(at.syntax("Expected refuse, oldest or quietest")),
245                    }
246                }
247                "loop" => {
248                    d.play.looping = match value {
249                        "off" => Loop::Off,
250                        "whole" => Loop::Whole,
251                        _ => {
252                            let (a, b) = value
253                                .split_once(',')
254                                .ok_or_else(|| at.syntax("Expected off, whole or start,end"))?;
255                            let start = at.unsigned(a)?;
256                            let end = at.unsigned(b)?;
257                            if start >= end {
258                                return Err(at.invalid("Loop needs start < end"));
259                            }
260                            Loop::Region { start, end }
261                        }
262                    };
263                    d.loop_location = at;
264                }
265                "pan" => d.play.pan = at.float(value, -1.0, 1.0)?,
266                "rate" => d.play.rate = at.float(value, 1.0 / 16.0, 8.0)?,
267                "fade-in" => d.play.fade_in = Fade::linear(at.float(value, 0.0, 60.0)? as f64),
268                "position" => {
269                    d.play.position = Some(at.vector(value)?);
270                    position_location = Some(at);
271                }
272                "emitter" => {
273                    emitter.position = at.vector(value)?;
274                    emitter_location = Some(at);
275                }
276                "velocity" => {
277                    emitter.velocity = at.vector(value)?;
278                    spatial_detail = Some(at);
279                }
280                "attenuation" => {
281                    let (model, rest) = value
282                        .split_once(',')
283                        .ok_or_else(|| at.syntax("Expected model,min,max,rolloff"))?;
284                    let model = match model {
285                        "linear" => DistanceModel::Linear,
286                        "inverse" => DistanceModel::Inverse,
287                        "exponential" => DistanceModel::Exponential,
288                        _ => return Err(at.syntax("Unknown distance model")),
289                    };
290                    let [min_distance, max_distance, rolloff] = at.vector(rest)?;
291                    emitter.attenuation = Attenuation {
292                        model,
293                        min_distance,
294                        max_distance,
295                        rolloff,
296                    };
297                    emitter
298                        .attenuation
299                        .validate()
300                        .map_err(|e| at.invalid(e.message))?;
301                    spatial_detail = Some(at);
302                }
303                _ => return Err(at.syntax(format!("Unknown event property: {key}"))),
304            }
305        }
306        if d.sources.is_empty() {
307            return Err(name_location.syntax("Event needs sources"));
308        }
309        if let Some(at) = emitter_location {
310            if let Some(pos_at) = position_location {
311                return Err(pos_at.invalid("Choose 2D position or a 3D emitter"));
312            }
313            emitter.validate().map_err(|e| at.invalid(e.message))?;
314            d.play.emitter = Some(emitter);
315        } else if let Some(at) = spatial_detail {
316            return Err(at.invalid("Velocity and attenuation require emitter"));
317        }
318        definitions.push(d);
319    }
320    Ok(EventBank { definitions })
321}