#[derive(Clone, Debug, PartialEq)]
pub enum DomainValue {
Form(DomainForm),
List(Vec<DomainValue>),
String(String),
Atom(String),
}
#[derive(Clone, Debug, PartialEq)]
pub struct DomainForm {
pub name: String,
pub fields: Vec<(String, DomainValue)>,
pub positional: Vec<DomainValue>,
}
impl DomainForm {
pub fn field(&self, key: &str) -> Option<&DomainValue> {
self.fields
.iter()
.find_map(|(name, value)| (name == key).then_some(value))
}
pub fn atom(&self, key: &str) -> Result<&str, DomainFormError> {
match self.field(key) {
Some(DomainValue::Atom(value)) => Ok(value),
Some(_) => Err(DomainFormError::WrongFieldKind(key.to_owned())),
None => Err(DomainFormError::MissingField(key.to_owned())),
}
}
pub fn string(&self, key: &str) -> Result<&str, DomainFormError> {
match self.field(key) {
Some(DomainValue::String(value)) => Ok(value),
Some(_) => Err(DomainFormError::WrongFieldKind(key.to_owned())),
None => Err(DomainFormError::MissingField(key.to_owned())),
}
}
pub fn list(&self, key: &str) -> Result<&[DomainValue], DomainFormError> {
match self.field(key) {
Some(DomainValue::List(items)) => Ok(items),
Some(_) => Err(DomainFormError::WrongFieldKind(key.to_owned())),
None => Err(DomainFormError::MissingField(key.to_owned())),
}
}
pub fn form(&self, key: &str) -> Result<&DomainForm, DomainFormError> {
match self.field(key) {
Some(DomainValue::Form(value)) => Ok(value),
Some(_) => Err(DomainFormError::WrongFieldKind(key.to_owned())),
None => Err(DomainFormError::MissingField(key.to_owned())),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DomainFormError {
ExpectedForm,
UnexpectedEof,
InvalidToken,
DuplicateField(String),
TrailingInput,
MissingField(String),
WrongFieldKind(String),
}
pub fn parse_domain_form(input: &str) -> Result<DomainForm, DomainFormError> {
let mut parser = Parser { input, index: 0 };
parser.skip_ws();
if !parser.consume_str("#(") {
return Err(DomainFormError::ExpectedForm);
}
let form = parser.parse_form_body()?;
parser.skip_ws();
if parser.index != parser.input.len() {
return Err(DomainFormError::TrailingInput);
}
Ok(form)
}
pub fn format_domain_form(form: &DomainForm) -> String {
let mut out = String::from("#(");
out.push_str(&form.name);
for value in &form.positional {
out.push(' ');
format_value(value, &mut out);
}
for (key, value) in &form.fields {
out.push(' ');
out.push_str(key);
out.push('=');
format_value(value, &mut out);
}
out.push(')');
out
}
fn format_value(value: &DomainValue, out: &mut String) {
match value {
DomainValue::Form(form) => out.push_str(&format_domain_form(form)),
DomainValue::List(items) => {
out.push('[');
for (index, item) in items.iter().enumerate() {
if index > 0 {
out.push(',');
}
format_value(item, out);
}
out.push(']');
}
DomainValue::String(text) => {
out.push('"');
for ch in text.chars() {
if ch == '\\' || ch == '"' {
out.push('\\');
}
out.push(ch);
}
out.push('"');
}
DomainValue::Atom(text) => out.push_str(text),
}
}
struct Parser<'a> {
input: &'a str,
index: usize,
}
impl Parser<'_> {
fn parse_form_body(&mut self) -> Result<DomainForm, DomainFormError> {
let name = self.parse_ident()?;
let mut fields: Vec<(String, DomainValue)> = Vec::new();
let mut positional = Vec::new();
loop {
self.skip_ws();
if self.consume_char(')') {
break;
}
match self.peek_char() {
Some('#') | Some('[') | Some('"') => positional.push(self.parse_value()?),
_ => {
let atom = self.parse_atom()?;
if self.consume_char('=') {
if fields.iter().any(|(key, _)| key == &atom) {
return Err(DomainFormError::DuplicateField(atom));
}
fields.push((atom, self.parse_value()?));
} else {
positional.push(DomainValue::Atom(atom));
}
}
}
}
Ok(DomainForm {
name,
fields,
positional,
})
}
fn parse_value(&mut self) -> Result<DomainValue, DomainFormError> {
self.skip_ws();
if self.consume_str("#(") {
return self.parse_form_body().map(DomainValue::Form);
}
if self.consume_char('[') {
return self.parse_list();
}
if self.peek_char() == Some('"') {
return self.parse_string().map(DomainValue::String);
}
self.parse_atom().map(DomainValue::Atom)
}
fn parse_list(&mut self) -> Result<DomainValue, DomainFormError> {
let mut items = Vec::new();
loop {
self.skip_ws();
if self.consume_char(']') {
break;
}
items.push(self.parse_value()?);
self.skip_ws();
if self.consume_char(',') {
continue;
}
self.expect_char(']')?;
break;
}
Ok(DomainValue::List(items))
}
fn parse_string(&mut self) -> Result<String, DomainFormError> {
self.expect_char('"')?;
let mut out = String::new();
while let Some(ch) = self.next_char() {
match ch {
'"' => return Ok(out),
'\\' => out.push(self.next_char().ok_or(DomainFormError::UnexpectedEof)?),
other => out.push(other),
}
}
Err(DomainFormError::UnexpectedEof)
}
fn parse_atom(&mut self) -> Result<String, DomainFormError> {
let start = self.index;
while let Some(ch) = self.peek_char() {
if ch.is_whitespace() || [',', ')', ']', '='].contains(&ch) {
break;
}
self.index += ch.len_utf8();
}
if self.index == start {
return Err(DomainFormError::UnexpectedEof);
}
Ok(self.input[start..self.index].to_owned())
}
fn parse_ident(&mut self) -> Result<String, DomainFormError> {
let atom = self.parse_atom()?;
if atom
.chars()
.all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '-')
{
Ok(atom)
} else {
Err(DomainFormError::InvalidToken)
}
}
fn expect_char(&mut self, expected: char) -> Result<(), DomainFormError> {
match self.next_char() {
Some(ch) if ch == expected => Ok(()),
Some(_) => Err(DomainFormError::InvalidToken),
None => Err(DomainFormError::UnexpectedEof),
}
}
fn consume_char(&mut self, expected: char) -> bool {
if self.peek_char() == Some(expected) {
self.index += expected.len_utf8();
true
} else {
false
}
}
fn consume_str(&mut self, expected: &str) -> bool {
if self.input[self.index..].starts_with(expected) {
self.index += expected.len();
true
} else {
false
}
}
fn skip_ws(&mut self) {
while let Some(ch) = self.peek_char() {
if ch.is_whitespace() {
self.index += ch.len_utf8();
} else {
break;
}
}
}
fn peek_char(&self) -> Option<char> {
self.input[self.index..].chars().next()
}
fn next_char(&mut self) -> Option<char> {
let ch = self.peek_char()?;
self.index += ch.len_utf8();
Some(ch)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_keyed_form_with_list_and_nested_form() {
let form = parse_domain_form("#(Note dur=4/4 pitch=C4 tags=[a,b] inner=#(Rest dur=1/4))")
.expect("parse");
assert_eq!(form.name, "Note");
assert_eq!(form.atom("dur").unwrap(), "4/4");
assert_eq!(form.atom("pitch").unwrap(), "C4");
assert_eq!(
form.list("tags").unwrap(),
&[
DomainValue::Atom("a".to_owned()),
DomainValue::Atom("b".to_owned())
]
);
assert_eq!(form.form("inner").unwrap().name, "Rest");
}
#[test]
fn parses_positional_values() {
let form = parse_domain_form("#(Chord 60 64 67)").expect("parse");
assert_eq!(form.positional.len(), 3);
assert!(form.fields.is_empty());
}
#[test]
fn round_trips_through_format() {
let source = "#(Note dur=4/4 sym=\"a\\\"b\" pitches=[60,64])";
let form = parse_domain_form(source).expect("parse");
let rendered = format_domain_form(&form);
assert_eq!(parse_domain_form(&rendered).unwrap(), form);
}
#[test]
fn missing_close_paren_is_unexpected_eof() {
assert_eq!(
parse_domain_form("#(Note dur=4/4"),
Err(DomainFormError::UnexpectedEof)
);
}
#[test]
fn duplicate_field_is_rejected() {
assert_eq!(
parse_domain_form("#(Note dur=4/4 dur=1/2)"),
Err(DomainFormError::DuplicateField("dur".to_owned()))
);
}
#[test]
fn trailing_input_is_rejected() {
assert_eq!(
parse_domain_form("#(Note dur=4/4) extra"),
Err(DomainFormError::TrailingInput)
);
}
#[test]
fn non_form_input_is_rejected() {
assert_eq!(
parse_domain_form("Note"),
Err(DomainFormError::ExpectedForm)
);
}
#[test]
fn escaped_quotes_round_trip() {
let form = parse_domain_form("#(S v=\"he said \\\"hi\\\"\")").expect("parse");
assert_eq!(form.string("v").unwrap(), "he said \"hi\"");
}
}