1use std::collections::HashMap;
2use std::fmt;
3
4use super::score::{
5 Arpeggio, ChannelWriter, DutySweep, Envelope, Instrument, Length, Pitch, Score, ScoreLimits,
6 Vibrato,
7};
8use super::wave::{Duty, NoiseMode, Waveform};
9use crate::{Error, ErrorCode};
10mod chip;
11mod synthwave;
12
13#[derive(Clone, Debug, Eq, PartialEq)]
15pub struct ParseError {
16 pub line: usize,
17 pub column: usize,
18 pub code: ErrorCode,
21 pub message: String,
22}
23impl fmt::Display for ParseError {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 write!(
26 f,
27 "line {}, column {}: {}",
28 self.line, self.column, self.message
29 )
30 }
31}
32impl std::error::Error for ParseError {}
33impl From<ParseError> for Error {
34 fn from(e: ParseError) -> Self {
35 Error::new(e.code, e.to_string())
36 }
37}
38
39#[derive(Clone, Copy, Debug, Default, PartialEq)]
40struct Pos {
41 line: usize,
42 column: usize,
43}
44impl Pos {
45 fn error(self, code: ErrorCode, message: impl Into<String>) -> ParseError {
46 ParseError {
47 line: self.line,
48 column: self.column,
49 code,
50 message: message.into(),
51 }
52 }
53 fn syntax(self, message: impl Into<String>) -> ParseError {
54 self.error(ErrorCode::Syntax, message)
55 }
56 fn invalid(self, message: impl Into<String>) -> ParseError {
57 self.error(ErrorCode::InvalidArgument, message)
58 }
59 fn wrap(self, error: Error) -> ParseError {
60 self.error(error.code, error.message)
61 }
62}
63
64type Parsed<T> = std::result::Result<T, ParseError>;
65
66struct Cursor<'a> {
68 chars: std::iter::Peekable<std::str::CharIndices<'a>>,
69 text: &'a str,
70 line: usize,
71 column: usize,
72}
73impl<'a> Cursor<'a> {
74 fn new(text: &'a str, line: usize, column: usize) -> Self {
75 Self {
76 chars: text.char_indices().peekable(),
77 text,
78 line,
79 column,
80 }
81 }
82 fn pos(&self) -> Pos {
83 Pos {
84 line: self.line,
85 column: self.column,
86 }
87 }
88 fn peek(&mut self) -> Option<char> {
89 self.chars.peek().map(|&(_, c)| c)
90 }
91 fn bump(&mut self) -> Option<char> {
92 let c = self.chars.next().map(|(_, c)| c);
93 if c.is_some() {
94 self.column += 1;
95 }
96 c
97 }
98 fn eat(&mut self, c: char) -> bool {
99 if self.peek() == Some(c) {
100 self.bump();
101 true
102 } else {
103 false
104 }
105 }
106 fn skip_space(&mut self) {
107 while self.peek().is_some_and(char::is_whitespace) {
108 self.bump();
109 }
110 }
111 fn take_while(&mut self, f: impl Fn(char) -> bool) -> &'a str {
112 let start = self.chars.peek().map_or(self.text.len(), |&(i, _)| i);
113 while self.peek().is_some_and(&f) {
114 self.bump();
115 }
116 let end = self.chars.peek().map_or(self.text.len(), |&(i, _)| i);
117 &self.text[start..end]
118 }
119 fn word(&mut self) -> (Pos, &'a str) {
120 self.skip_space();
121 let pos = self.pos();
122 (pos, self.take_while(|c| !c.is_whitespace()))
123 }
124 fn name(&mut self, what: &str) -> Parsed<String> {
125 let pos = self.pos();
126 let name = self.take_while(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-');
127 if name.is_empty() {
128 return Err(pos.syntax(format!("Expected {what} name")));
129 }
130 Ok(name.to_owned())
131 }
132 fn number(&mut self) -> Parsed<Option<u32>> {
133 let pos = self.pos();
134 let digits = self.take_while(|c| c.is_ascii_digit());
135 if digits.is_empty() {
136 return Ok(None);
137 }
138 digits
139 .parse()
140 .map(Some)
141 .map_err(|_| pos.invalid("Number is too large"))
142 }
143 fn required(&mut self, what: &str) -> Parsed<u32> {
144 let pos = self.pos();
145 self.number()?
146 .ok_or_else(|| pos.syntax(format!("Expected {what}")))
147 }
148}
149
150fn ranged(pos: Pos, value: u32, range: std::ops::RangeInclusive<u32>, what: &str) -> Parsed<u32> {
151 if range.contains(&value) {
152 Ok(value)
153 } else {
154 Err(pos.invalid(format!(
155 "{what} must be {}..={}, not {value}",
156 range.start(),
157 range.end()
158 )))
159 }
160}
161
162#[derive(Clone, Copy, Debug)]
163enum LengthKind {
164 Default,
165 Note(u16),
166 Ticks(u32),
167}
168#[derive(Clone, Copy, Debug)]
169struct LengthSpec {
170 kind: LengthKind,
171 dots: u8,
172}
173
174#[derive(Clone, Debug)]
175enum Token {
176 Note(char, i8, LengthSpec, Option<u8>),
177 Chord(Vec<(char, i8, i8)>, LengthSpec, Option<u8>),
178 Rest(LengthSpec),
179 Extend(LengthSpec),
180 TieNext,
181 Octave(u8),
182 Up,
183 Down,
184 DefaultLength(LengthSpec),
185 Volume(u8),
186 Gate(u8),
187 Instrument(String),
188 Call(String),
189 Loop,
190 Open,
191 Break,
192 Close(u32),
193}
194
195fn length_spec(cursor: &mut Cursor) -> Parsed<LengthSpec> {
196 let pos = cursor.pos();
197 let kind = if cursor.eat('%') {
198 LengthKind::Ticks(ranged(
199 pos,
200 cursor.required("ticks after %")?,
201 1..=u32::MAX,
202 "Ticks",
203 )?)
204 } else {
205 match cursor.number()? {
206 Some(n) => LengthKind::Note(ranged(pos, n, 1..=3840, "Note length")? as u16),
207 None => LengthKind::Default,
208 }
209 };
210 let mut dots = 0u8;
211 while cursor.eat('.') {
212 dots = dots.saturating_add(1);
213 }
214 if dots > 0 && matches!(kind, LengthKind::Ticks(_)) {
215 return Err(pos.syntax("A tick length cannot be dotted"));
216 }
217 Ok(LengthSpec { kind, dots })
218}
219
220fn lex_body(cursor: &mut Cursor, out: &mut Vec<(Token, Pos)>) -> Parsed<()> {
221 loop {
222 cursor.skip_space();
223 let pos = cursor.pos();
224 let Some(c) = cursor.bump() else {
225 return Ok(());
226 };
227 let token = match c {
228 'a'..='g' => {
229 let mut accidental = 0i8;
230 loop {
231 if cursor.eat('+') || cursor.eat('#') {
232 accidental = accidental.saturating_add(1);
233 } else if cursor.eat('-') {
234 accidental = accidental.saturating_sub(1);
235 } else {
236 break;
237 }
238 }
239 let length = length_spec(cursor)?;
240 let velocity = if cursor.eat('!') {
241 let velocity_pos = cursor.pos();
242 Some(ranged(
243 velocity_pos,
244 cursor.required("velocity after !")?,
245 0..=127,
246 "Velocity",
247 )? as u8)
248 } else {
249 None
250 };
251 Token::Note(c, accidental, length, velocity)
252 }
253 'r' => Token::Rest(length_spec(cursor)?),
254 '{' => {
255 let mut members = Vec::new();
256 let mut octave = 0i8;
257 loop {
258 cursor.skip_space();
259 match cursor.bump() {
260 Some('}') => break,
261 Some('>') => {
262 octave = octave.saturating_add(1);
263 }
264 Some('<') => {
265 octave = octave.saturating_sub(1);
266 }
267 Some(letter @ 'a'..='g') => {
268 if members.len() == 16 {
269 return Err(pos.error(
270 ErrorCode::LimitExceeded,
271 "At most 16 notes in a chord",
272 ));
273 }
274 let mut accidental = 0i8;
275 loop {
276 if cursor.eat('+') || cursor.eat('#') {
277 accidental = accidental.saturating_add(1);
278 } else if cursor.eat('-') {
279 accidental = accidental.saturating_sub(1);
280 } else {
281 break;
282 }
283 }
284 members.push((letter, accidental, octave));
285 }
286 _ => {
287 return Err(
288 pos.syntax("Chord needs notes/octave shifts and a closing '}'")
289 );
290 }
291 }
292 if !(-9..=9).contains(&octave) {
293 return Err(pos.invalid("Chord octave shift out of range"));
294 }
295 }
296 if members.is_empty() {
297 return Err(pos.invalid("Chord is empty"));
298 }
299 let length = length_spec(cursor)?;
300 let velocity = if cursor.eat('!') {
301 Some(ranged(pos, cursor.required("velocity")?, 0..=127, "Velocity")? as u8)
302 } else {
303 None
304 };
305 Token::Chord(members, length, velocity)
306 }
307 '^' => Token::Extend(length_spec(cursor)?),
308 '&' => Token::TieNext,
309 'o' => Token::Octave(
310 ranged(pos, cursor.required("octave after o")?, 0..=9, "Octave")? as u8,
311 ),
312 '>' => Token::Up,
313 '<' => Token::Down,
314 'l' => {
315 let spec = length_spec(cursor)?;
316 if matches!(spec.kind, LengthKind::Default) {
317 return Err(pos.syntax("Expected a length after l"));
318 }
319 Token::DefaultLength(spec)
320 }
321 'v' => Token::Volume(
322 ranged(pos, cursor.required("volume after v")?, 0..=15, "Volume")? as u8,
323 ),
324 'q' => Token::Gate(ranged(pos, cursor.required("gate after q")?, 1..=8, "Gate")? as u8),
325 '@' => Token::Instrument(cursor.name("an instrument")?),
326 '$' => Token::Call(cursor.name("a pattern")?),
327 'L' => Token::Loop,
328 '[' => Token::Open,
329 '|' => Token::Break,
330 ']' => {
331 let count_pos = cursor.pos();
332 let count = cursor.number()?.unwrap_or(2);
333 Token::Close(ranged(count_pos, count, 1..=65535, "Repeat count")?)
334 }
335 other => return Err(pos.syntax(format!("Unexpected character '{other}'"))),
336 };
337 out.push((token, pos));
338 }
339}
340
341#[derive(Clone, Debug)]
342enum Node {
343 Token(Token, Pos),
344 Repeat {
345 body: Vec<Node>,
346 tail: Vec<Node>,
347 count: u32,
348 pos: Pos,
349 },
350}
351
352fn build_tree(tokens: Vec<(Token, Pos)>) -> Parsed<Vec<Node>> {
353 let mut stack: Vec<(Pos, Vec<Node>, Option<Vec<Node>>)> = Vec::new();
355 let mut current = Vec::new();
356 for (token, pos) in tokens {
357 match token {
358 Token::Open => stack.push((pos, std::mem::take(&mut current), None)),
359 Token::Break => match stack.last_mut() {
360 Some((_, _, body @ None)) => *body = Some(std::mem::take(&mut current)),
361 Some(_) => return Err(pos.syntax("A repeat has only one '|'")),
362 None => return Err(pos.syntax("'|' outside a repeat")),
363 },
364 Token::Close(count) => {
365 let (open, outer, body) = stack
366 .pop()
367 .ok_or_else(|| pos.syntax("']' without a matching '['"))?;
368 let inner = std::mem::replace(&mut current, outer);
369 let (body, tail) = match body {
370 Some(body) => (body, inner),
371 None => (inner, Vec::new()),
372 };
373 current.push(Node::Repeat {
374 body,
375 tail,
376 count,
377 pos: open,
378 });
379 }
380 token => current.push(Node::Token(token, pos)),
381 }
382 }
383 match stack.last() {
384 Some((open, ..)) => Err(open.syntax("'[' is never closed")),
385 None => Ok(current),
386 }
387}
388
389fn parse_instrument(cursor: &mut Cursor) -> Parsed<(String, Instrument, Pos)> {
390 cursor.skip_space();
391 let name_pos = cursor.pos();
392 let name = cursor.name("an instrument")?;
393 let mut instrument = Instrument::default();
394 let (mut wave, mut duties, mut noise) = ("pulse", None, None);
395 let (mut arpeggio, mut arpeggio_step, mut duty_step) = (None, 16, 16);
396 let (mut drum_kind, mut drum_kit) = (None, None);
397 let mut drum_options = Vec::new();
398 let mut melodic_key = None;
399 let mut chip_options = Vec::new();
400 let mut wide_options = Vec::new();
401 let mut chip_wave_key = None;
402 loop {
403 let (pos, word) = cursor.word();
404 if word.is_empty() {
405 break;
406 }
407 let (key, value) = word
408 .split_once('=')
409 .ok_or_else(|| pos.syntax(format!("Expected key=value, found {word}")))?;
410 let value_pos = Pos {
411 column: pos.column + key.chars().count() + 1,
412 ..pos
413 };
414 let numbers = |count: std::ops::RangeInclusive<usize>| -> Parsed<Vec<i64>> {
415 let list: Vec<_> = value.split(',').map(|v| v.trim().parse::<i64>()).collect();
416 match list.into_iter().collect::<std::result::Result<Vec<_>, _>>() {
417 Ok(list) if count.contains(&list.len()) => Ok(list),
418 _ => Err(value_pos.syntax(format!(
419 "{key} needs {} to {} comma-separated integers",
420 count.start(),
421 count.end()
422 ))),
423 }
424 };
425 let to_u32 = |v: i64| -> Parsed<u32> {
426 u32::try_from(v).map_err(|_| value_pos.invalid(format!("{key} is out of range")))
427 };
428 if !matches!(key, "volume" | "drum" | "kit") && !key.starts_with("drum-") {
429 melodic_key = Some(pos);
430 }
431 if matches!(key, "wave" | "duty" | "duty-step" | "noise") {
432 chip_wave_key = Some(pos);
433 }
434 match key {
435 key if synthwave::is_key(key) => {
436 wide_options.push((key.to_owned(), value.to_owned(), value_pos))
437 }
438 key if chip::is_key(key) => {
439 chip_options.push((key.to_owned(), value.to_owned(), value_pos))
440 }
441 "drum" => {
442 drum_kind = Some(match value {
443 "kick" => super::DrumKind::Kick,
444 "snare" => super::DrumKind::Snare,
445 "closed-hat" => super::DrumKind::ClosedHat,
446 "open-hat" => super::DrumKind::OpenHat,
447 "tom" => super::DrumKind::Tom,
448 "clap" => super::DrumKind::Clap,
449 "crash" => super::DrumKind::Crash,
450 _ => return Err(value_pos.syntax("Unknown drum kind")),
451 })
452 }
453 "kit" => {
454 drum_kit = Some(match value {
455 "chiptune" => super::DrumKit::Chiptune,
456 "electronic" => super::DrumKit::Electronic,
457 _ => return Err(value_pos.syntax("Kit must be chiptune or electronic")),
458 })
459 }
460 "drum-pitch" | "drum-tone" | "drum-noise" | "drum-mix" | "drum-click" | "drum-seed"
461 | "drum-sweep" => {
462 let count = if key == "drum-sweep" { 2 } else { 1 };
463 let values = numbers(count..=count)?
464 .into_iter()
465 .map(to_u32)
466 .collect::<Parsed<Vec<_>>>()?;
467 drum_options.push((key.to_owned(), values, value_pos));
468 }
469 "wave" => match value {
470 "pulse" | "triangle" | "saw" | "noise" => wave = value,
471 _ => {
472 return Err(value_pos.syntax(format!(
473 "wave must be pulse, triangle, saw or noise, not {value}"
474 )));
475 }
476 },
477 "duty" => {
478 let list = value
479 .split(',')
480 .map(|d| d.trim().parse::<Duty>())
481 .collect::<crate::Result<Vec<_>>>()
482 .map_err(|e| value_pos.wrap(e))?;
483 duties = Some((list, pos));
484 }
485 "duty-step" => duty_step = to_u32(numbers(1..=1)?[0])?,
486 "noise" => match value {
487 "long" => noise = Some((NoiseMode::Long, pos)),
488 "short" => noise = Some((NoiseMode::Short, pos)),
489 _ => {
490 return Err(
491 value_pos.syntax(format!("noise must be long or short, not {value}"))
492 );
493 }
494 },
495 "adsr" => {
496 let v = numbers(4..=4)?;
497 instrument.envelope = Envelope {
498 attack_ms: to_u32(v[0])?,
499 decay_ms: to_u32(v[1])?,
500 sustain: ranged(value_pos, to_u32(v[2])?, 0..=100, "Sustain")? as u8,
501 release_ms: to_u32(v[3])?,
502 };
503 }
504 "volume" => {
505 instrument.volume =
506 ranged(value_pos, to_u32(numbers(1..=1)?[0])?, 0..=15, "Volume")? as u8;
507 }
508 "slide" => {
509 instrument.slide = i32::try_from(numbers(1..=1)?[0])
510 .map_err(|_| value_pos.invalid("slide is out of range"))?;
511 }
512 "vibrato" => {
513 let v = numbers(2..=3)?;
514 instrument.vibrato = Some(Vibrato {
515 depth_cents: u16::try_from(v[0])
516 .map_err(|_| value_pos.invalid("Vibrato depth is out of range"))?,
517 period_ms: to_u32(v[1])?,
518 delay_ms: to_u32(v.get(2).copied().unwrap_or(0))?,
519 });
520 }
521 "arpeggio" => {
522 let offsets = numbers(1..=16)?
523 .into_iter()
524 .map(|v| {
525 i8::try_from(v)
526 .map_err(|_| value_pos.invalid("Arpeggio offset is out of range"))
527 })
528 .collect::<Parsed<Vec<_>>>()?;
529 arpeggio = Some(offsets);
530 }
531 "arpeggio-step" => arpeggio_step = to_u32(numbers(1..=1)?[0])?,
532 _ => return Err(pos.syntax(format!("Unknown instrument key {key}"))),
533 }
534 }
535 if let Some(kind) = drum_kind {
536 if let Some(pos) = melodic_key {
537 return Err(pos.invalid("Drums cannot use melodic oscillator or modulation keys"));
538 }
539 let mut drum = super::Drum::new(kind, drum_kit.unwrap_or(super::DrumKit::Electronic));
540 for (key, values, pos) in drum_options {
541 let as_u8 = |v| u8::try_from(v).map_err(|_| pos.invalid("Drum value is out of range"));
542 let as_u16 =
543 |v| u16::try_from(v).map_err(|_| pos.invalid("Drum value is out of range"));
544 match key.as_str() {
545 "drum-pitch" => drum.pitch_hz = as_u16(values[0])?,
546 "drum-tone" => drum.tone_ms = values[0],
547 "drum-noise" => drum.noise_ms = values[0],
548 "drum-mix" => drum.noise_percent = as_u8(values[0])?,
549 "drum-click" => drum.click_percent = as_u8(values[0])?,
550 "drum-seed" => drum.seed = values[0],
551 "drum-sweep" => {
552 drum.sweep_cents = as_u16(values[0])?;
553 drum.sweep_ms = values[1];
554 }
555 _ => unreachable!(),
556 }
557 }
558 drum.validate().map_err(|e| name_pos.wrap(e))?;
559 instrument.drum = Some(drum);
560 } else if drum_kit.is_some() || !drum_options.is_empty() {
561 return Err(name_pos.invalid("kit and drum parameters require drum=KIND"));
562 }
563 instrument.waveform = match wave {
564 "pulse" => Waveform::Pulse(match &duties {
565 Some((list, _)) => list[0],
566 None => Duty::Half,
567 }),
568 "triangle" => Waveform::Triangle,
569 "saw" => Waveform::Sawtooth,
570 _ => Waveform::Noise(noise.map_or(NoiseMode::Long, |(m, _)| m)),
571 };
572 if let Some((_, pos)) = noise.filter(|_| wave != "noise") {
573 return Err(pos.invalid("noise needs wave=noise"));
574 }
575 if let Some((list, pos)) = duties {
576 if wave != "pulse" {
577 return Err(pos.invalid("duty needs wave=pulse"));
578 }
579 if list.len() > 1 {
580 instrument.duty_sweep = Some(DutySweep {
581 duties: list[1..].to_vec(),
582 step_ms: duty_step,
583 });
584 }
585 }
586 instrument.arpeggio = arpeggio.map(|offsets| Arpeggio {
587 offsets,
588 step_ms: arpeggio_step,
589 });
590 if !chip_options.is_empty() {
591 chip::apply(chip_options, &mut instrument, chip_wave_key)?;
592 }
593 if !wide_options.is_empty() {
594 synthwave::apply(wide_options, &mut instrument)?;
595 }
596 instrument.validate().map_err(|e| name_pos.wrap(e))?;
597 Ok((name, instrument, name_pos))
598}
599
600struct TextState {
602 octave: i8,
603 length: Length,
604 last_pitch: Option<Pitch>,
605 tie: Option<Pos>,
606}
607
608struct Expander<'a> {
609 patterns: &'a HashMap<String, Vec<Node>>,
610 limits: &'a ScoreLimits,
611 events: usize,
612 calls: Vec<&'a str>,
613}
614impl<'a> Expander<'a> {
615 fn run(
616 &mut self,
617 nodes: &'a [Node],
618 writer: &mut ChannelWriter,
619 state: &mut TextState,
620 depth: usize,
621 ) -> Parsed<()> {
622 for node in nodes {
623 let pos = match node {
624 Node::Token(_, pos) | Node::Repeat { pos, .. } => *pos,
625 };
626 self.spend(pos)?;
627 match node {
628 Node::Repeat {
629 body,
630 tail,
631 count,
632 pos,
633 } => {
634 if depth >= self.limits.max_nesting {
635 return Err(pos.error(
636 ErrorCode::LimitExceeded,
637 "Repeats and patterns are nested too deeply",
638 ));
639 }
640 for i in 0..*count {
641 self.spend(*pos)?;
642 self.run(body, writer, state, depth + 1)?;
643 if i + 1 < *count {
644 self.run(tail, writer, state, depth + 1)?;
645 }
646 }
647 }
648 Node::Token(token, pos) => self.token(token, *pos, writer, state, depth)?,
649 }
650 }
651 Ok(())
652 }
653 fn spend(&mut self, pos: Pos) -> Parsed<()> {
654 if self.events >= self.limits.max_events {
655 return Err(pos.error(ErrorCode::LimitExceeded, "Score exceeds its event limit"));
656 }
657 self.events += 1;
658 Ok(())
659 }
660 fn token(
661 &mut self,
662 token: &'a Token,
663 pos: Pos,
664 writer: &mut ChannelWriter,
665 state: &mut TextState,
666 depth: usize,
667 ) -> Parsed<()> {
668 if let Some(tie) = state.tie {
669 if !matches!(
670 token,
671 Token::Note(..) | Token::Octave(_) | Token::Up | Token::Down
672 ) {
673 return Err(tie.syntax("'&' must be followed by a note"));
674 }
675 }
676 let length = |spec: &LengthSpec, state: &TextState| -> Length {
677 match spec.kind {
678 LengthKind::Ticks(t) => Length::Ticks(t),
679 LengthKind::Note(denominator) => Length::Note {
680 denominator,
681 dots: spec.dots,
682 },
683 LengthKind::Default => (0..spec.dots).fold(state.length, |l, _| l.dotted()),
684 }
685 };
686 let wrap = |e| pos.wrap(e);
687 match token {
688 Token::Chord(members, spec, velocity) => {
689 let pitches = members
690 .iter()
691 .map(|&(letter, accidental, shift)| {
692 Pitch::from_name(letter, accidental, state.octave + shift).map_err(wrap)
693 })
694 .collect::<Parsed<Vec<_>>>()?;
695 writer
696 .chord_with_velocity(&pitches, length(spec, state), velocity.unwrap_or(127))
697 .map_err(wrap)?;
698 state.last_pitch = None;
700 }
701 Token::Note(letter, accidental, spec, velocity) => {
702 let pitch = Pitch::from_name(*letter, *accidental, state.octave).map_err(wrap)?;
703 let len = length(spec, state);
704 if let Some(tie) = state.tie.take() {
705 if velocity.is_some() {
706 return Err(pos.invalid("A tied continuation cannot set velocity"));
707 }
708 if state.last_pitch != Some(pitch) {
709 return Err(tie.syntax("'&' joins notes of different pitch"));
710 }
711 writer.tie(len).map_err(wrap)?;
712 } else {
713 writer
714 .note_with_velocity(pitch, len, velocity.unwrap_or(127))
715 .map_err(wrap)?;
716 }
717 state.last_pitch = Some(pitch);
718 }
719 Token::Rest(spec) => {
720 writer.rest(length(spec, state)).map_err(wrap)?;
721 state.last_pitch = None;
722 }
723 Token::Extend(spec) => {
724 writer.tie(length(spec, state)).map_err(wrap)?;
725 }
726 Token::TieNext => {
727 if state.last_pitch.is_none() {
728 return Err(pos.syntax("'&' must follow a note"));
729 }
730 state.tie = Some(pos);
731 }
732 Token::Octave(o) => state.octave = *o as i8,
733 Token::Up | Token::Down => {
734 let octave = state.octave + if matches!(token, Token::Up) { 1 } else { -1 };
735 if !(0..=9).contains(&octave) {
736 return Err(pos.invalid("Octave must stay within 0..=9"));
737 }
738 state.octave = octave;
739 }
740 Token::DefaultLength(spec) => {
741 let len = length(spec, state);
742 len.ticks(writer.score().ticks_per_quarter())
743 .map_err(wrap)?;
744 state.length = len;
745 }
746 Token::Volume(v) => {
747 writer.volume(*v).map_err(wrap)?;
748 }
749 Token::Gate(g) => {
750 writer.gate(*g).map_err(wrap)?;
751 }
752 Token::Instrument(name) => {
753 let index = writer
754 .score()
755 .instrument_index(name)
756 .ok_or_else(|| pos.syntax(format!("Unknown instrument {name}")))?;
757 writer.instrument(index).map_err(wrap)?;
758 }
759 Token::Loop => {
760 writer.loop_point().map_err(wrap)?;
761 }
762 Token::Call(name) => {
763 let (key, body) = self
764 .patterns
765 .get_key_value(name)
766 .ok_or_else(|| pos.syntax(format!("Unknown pattern {name}")))?;
767 if self.calls.contains(&key.as_str()) {
768 return Err(pos.syntax(format!("Pattern {name} refers to itself")));
769 }
770 if depth >= self.limits.max_nesting {
771 return Err(pos.error(
772 ErrorCode::LimitExceeded,
773 "Repeats and patterns are nested too deeply",
774 ));
775 }
776 self.calls.push(key);
777 self.run(body, writer, state, depth + 1)?;
778 self.calls.pop();
779 }
780 Token::Open | Token::Break | Token::Close(_) => unreachable!(),
781 }
782 Ok(())
783 }
784}
785
786struct Body {
787 name: String,
788 pos: Pos,
789 tokens: Vec<(Token, Pos)>,
790}
791
792fn body_entry(bodies: &mut Vec<Body>, name: String, pos: Pos) -> &mut Body {
793 let index = match bodies.iter().position(|b| b.name == name) {
794 Some(i) => i,
795 None => {
796 bodies.push(Body {
797 name,
798 pos,
799 tokens: Vec::new(),
800 });
801 bodies.len() - 1
802 }
803 };
804 &mut bodies[index]
805}
806
807pub(super) fn parse(text: &str, limits: &ScoreLimits) -> Parsed<Score> {
808 let start = Pos { line: 1, column: 1 };
809 if text.len() > limits.max_text_bytes {
810 return Err(start.error(
811 ErrorCode::LimitExceeded,
812 "Score text exceeds its length limit",
813 ));
814 }
815 let (mut tempo, mut ticks) = (None, None);
816 let mut instruments = Vec::new();
817 let mut patterns: Vec<Body> = Vec::new();
818 let mut channels: Vec<Body> = Vec::new();
819 for (index, raw) in text.lines().enumerate() {
820 let content = raw.split(';').next().unwrap_or("");
821 let mut cursor = Cursor::new(content, index + 1, 1);
822 let (pos, keyword) = cursor.word();
823 match keyword {
824 "" => continue,
825 "tempo" | "ticks" => {
826 cursor.skip_space();
827 let value_pos = cursor.pos();
828 let value = cursor.required(&format!("a number after {keyword}"))?;
829 let slot = if keyword == "tempo" {
830 &mut tempo
831 } else {
832 &mut ticks
833 };
834 if slot.is_some() {
835 return Err(pos.syntax(format!("{keyword} is set twice")));
836 }
837 let range = if keyword == "tempo" { 1..=999 } else { 1..=960 };
838 *slot = Some(ranged(value_pos, value, range, keyword)? as u16);
839 }
840 "instrument" => instruments.push(parse_instrument(&mut cursor)?),
841 "pattern" | "channel" => {
842 cursor.skip_space();
843 let name_pos = cursor.pos();
844 let name = cursor.name(keyword)?;
845 cursor.skip_space();
846 let eq = cursor.pos();
847 if !cursor.eat('=') {
848 return Err(eq.syntax(format!("Expected '=' after the {keyword} name")));
849 }
850 let list = if keyword == "pattern" {
851 &mut patterns
852 } else {
853 &mut channels
854 };
855 lex_body(&mut cursor, &mut body_entry(list, name, name_pos).tokens)?;
856 continue;
857 }
858 other => return Err(pos.syntax(format!("Unknown directive {other}"))),
859 }
860 cursor.skip_space();
861 if cursor.peek().is_some() {
862 return Err(cursor
863 .pos()
864 .syntax("Unexpected text at the end of the line"));
865 }
866 }
867 let mut score = Score::with_limits(tempo.unwrap_or(120), ticks.unwrap_or(48), limits.clone())
868 .map_err(|e| start.wrap(e))?;
869 for (name, instrument, pos) in instruments {
870 score
871 .add_instrument(name, instrument)
872 .map_err(|e| pos.wrap(e))?;
873 }
874 let mut trees = HashMap::new();
875 for body in patterns {
876 trees.insert(body.name, build_tree(body.tokens)?);
877 }
878 let channel_trees = channels
879 .into_iter()
880 .map(|body| Ok((body.name, body.pos, build_tree(body.tokens)?)))
881 .collect::<Parsed<Vec<_>>>()?;
882 let mut expander = Expander {
883 patterns: &trees,
884 limits,
885 events: 0,
886 calls: Vec::new(),
887 };
888 for (name, pos, nodes) in &channel_trees {
889 let mut writer = score.channel(name).map_err(|e| pos.wrap(e))?;
890 let mut state = TextState {
891 octave: 4,
892 length: Length::QUARTER,
893 last_pitch: None,
894 tie: None,
895 };
896 expander.run(nodes, &mut writer, &mut state, 0)?;
897 if let Some(tie) = state.tie {
898 return Err(tie.syntax("'&' must be followed by a note"));
899 }
900 }
901 Ok(score)
902}