Skip to main content

refrain_core/
parser.rs

1//! Hand-rolled S-expression parser for the Refrain DSL.
2//!
3//! Grammar (informal):
4//!
5//! ```text
6//! refrain := "(" "refrain" IDENT stage* ")"
7//! stage   := "(" stage-kind pattern+ ")"
8//! stage-kind := "territorialize" | "deterritorialize" | "reterritorialize"
9//! pattern := ATOM | "(" op-head pattern* ")"
10//! op-head := "note" | "loop" | "dy/dx" | "quotient" | IDENT
11//! ```
12//!
13//! Sequences of patterns inside a stage become `Pattern::Seq`. Unknown
14//! function-like forms become `Op::Call { head, args }` so the parser does
15//! not have to be updated for every future operator.
16
17use std::iter::Peekable;
18
19use crate::ast::{Op, Pattern, Refrain};
20use crate::error::{RefrainError, Result};
21
22#[derive(Debug, Clone, PartialEq)]
23enum Sexp {
24    Atom(String),
25    List(Vec<Sexp>),
26}
27
28pub fn parse(src: &str) -> Result<Refrain> {
29    let tokens = tokenize(src);
30    let mut iter = tokens.into_iter().peekable();
31    let s = parse_sexp(&mut iter)?;
32    if iter.next().is_some() {
33        return Err(RefrainError::Parse(
34            "trailing tokens after top-level form".into(),
35        ));
36    }
37    sexp_to_refrain(&s)
38}
39
40fn tokenize(src: &str) -> Vec<String> {
41    let mut tokens = Vec::new();
42    let mut cur = String::new();
43    let mut in_comment = false;
44    for ch in src.chars() {
45        if in_comment {
46            if ch == '\n' {
47                in_comment = false;
48            }
49            continue;
50        }
51        if ch == ';' {
52            flush(&mut cur, &mut tokens);
53            in_comment = true;
54            continue;
55        }
56        if ch.is_whitespace() {
57            flush(&mut cur, &mut tokens);
58            continue;
59        }
60        if ch == '(' || ch == ')' {
61            flush(&mut cur, &mut tokens);
62            tokens.push(ch.to_string());
63            continue;
64        }
65        cur.push(ch);
66    }
67    flush(&mut cur, &mut tokens);
68    tokens
69}
70
71fn flush(cur: &mut String, out: &mut Vec<String>) {
72    if !cur.is_empty() {
73        out.push(std::mem::take(cur));
74    }
75}
76
77fn parse_sexp<I: Iterator<Item = String>>(iter: &mut Peekable<I>) -> Result<Sexp> {
78    let tok = iter
79        .next()
80        .ok_or_else(|| RefrainError::Parse("unexpected EOF".into()))?;
81    if tok == "(" {
82        let mut items = Vec::new();
83        loop {
84            let peek = iter
85                .peek()
86                .ok_or_else(|| RefrainError::Parse("unclosed list".into()))?;
87            if peek == ")" {
88                iter.next();
89                return Ok(Sexp::List(items));
90            }
91            items.push(parse_sexp(iter)?);
92        }
93    } else if tok == ")" {
94        Err(RefrainError::Parse("unexpected `)` at top".into()))
95    } else {
96        Ok(Sexp::Atom(tok))
97    }
98}
99
100fn sexp_to_refrain(s: &Sexp) -> Result<Refrain> {
101    let items = expect_list(s, "refrain form")?;
102    if items.len() < 2 {
103        return Err(RefrainError::Parse(
104            "refrain form requires `refrain` head and a name".into(),
105        ));
106    }
107    let head = expect_atom(&items[0], "refrain head")?;
108    if head != "refrain" {
109        return Err(RefrainError::Parse(format!(
110            "expected `refrain`, got `{}`",
111            head
112        )));
113    }
114    let name = expect_atom(&items[1], "refrain name")?.to_string();
115    let mut r = Refrain::new(name);
116    for stage in &items[2..] {
117        let stage_items = expect_list(stage, "stage form")?;
118        if stage_items.is_empty() {
119            return Err(RefrainError::Parse("empty stage form".into()));
120        }
121        let kind = expect_atom(&stage_items[0], "stage kind")?;
122        let body = sexp_to_pattern_body(&stage_items[1..])?;
123        match kind {
124            "territorialize" => r.territorialize = Some(body),
125            "deterritorialize" => r.deterritorialize = Some(body),
126            "reterritorialize" => r.reterritorialize = Some(body),
127            other => {
128                return Err(RefrainError::Parse(format!("unknown stage `{}`", other)));
129            }
130        }
131    }
132    Ok(r)
133}
134
135fn sexp_to_pattern_body(items: &[Sexp]) -> Result<Pattern> {
136    match items.len() {
137        0 => Err(RefrainError::Parse("empty stage body".into())),
138        1 => sexp_to_pattern(&items[0]),
139        _ => {
140            let mut pats = Vec::with_capacity(items.len());
141            for i in items {
142                pats.push(sexp_to_pattern(i)?);
143            }
144            Ok(Pattern::Seq(pats))
145        }
146    }
147}
148
149fn sexp_to_pattern(s: &Sexp) -> Result<Pattern> {
150    match s {
151        Sexp::Atom(a) => Ok(Pattern::Op(Op::Sym(a.clone()))),
152        Sexp::List(xs) => {
153            if xs.is_empty() {
154                return Err(RefrainError::Parse("empty pattern list".into()));
155            }
156            let head = expect_atom(&xs[0], "op head")?;
157            match head {
158                "note" => {
159                    if xs.len() != 3 {
160                        return Err(RefrainError::Parse("note expects (note PITCH DUR)".into()));
161                    }
162                    let pitch = expect_atom(&xs[1], "note pitch")?.to_string();
163                    let dur = expect_atom(&xs[2], "note dur")?.to_string();
164                    Ok(Pattern::Op(Op::Note { pitch, dur }))
165                }
166                "loop" => {
167                    if xs.len() != 3 {
168                        return Err(RefrainError::Parse("loop expects (loop COUNT BODY)".into()));
169                    }
170                    let count_atom = expect_atom(&xs[1], "loop count")?;
171                    let count: u32 = count_atom.parse().map_err(|_| {
172                        RefrainError::Parse(format!("loop count `{}` not u32", count_atom))
173                    })?;
174                    let body = Box::new(sexp_to_pattern(&xs[2])?);
175                    Ok(Pattern::Op(Op::Loop { count, body }))
176                }
177                "dy/dx" => {
178                    if xs.len() != 3 {
179                        return Err(RefrainError::Parse("dy/dx expects (dy/dx X T)".into()));
180                    }
181                    let x = expect_atom(&xs[1], "dy/dx x")?.to_string();
182                    let t = expect_atom(&xs[2], "dy/dx t")?.to_string();
183                    Ok(Pattern::Op(Op::Diff { x, t }))
184                }
185                "quotient" => {
186                    let mut rels = Vec::new();
187                    for s in &xs[1..] {
188                        rels.push(expect_atom(s, "quotient rel")?.to_string());
189                    }
190                    Ok(Pattern::Op(Op::Quotient { rels }))
191                }
192                _ => {
193                    let head_owned = head.to_string();
194                    let mut args = Vec::with_capacity(xs.len() - 1);
195                    for s in &xs[1..] {
196                        args.push(sexp_to_pattern(s)?);
197                    }
198                    Ok(Pattern::Op(Op::Call {
199                        head: head_owned,
200                        args,
201                    }))
202                }
203            }
204        }
205    }
206}
207
208fn expect_list<'a>(s: &'a Sexp, what: &str) -> Result<&'a [Sexp]> {
209    match s {
210        Sexp::List(xs) => Ok(xs),
211        Sexp::Atom(_) => Err(RefrainError::Parse(format!("expected list ({})", what))),
212    }
213}
214
215fn expect_atom<'a>(s: &'a Sexp, what: &str) -> Result<&'a str> {
216    match s {
217        Sexp::Atom(a) => Ok(a.as_str()),
218        Sexp::List(_) => Err(RefrainError::Parse(format!("expected atom ({})", what))),
219    }
220}