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#[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 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}