directive: test parsing builtins

This commit is contained in:
Branden J Brown 2024-10-16 13:14:22 -04:00
parent 3fd74b7fd5
commit ff5261ae60
1 changed files with 165 additions and 35 deletions

View File

@ -12,7 +12,7 @@ pub struct TypeSystem {
/// The definition of a standard directive. /// The definition of a standard directive.
/// ///
/// Note that this type is an input to the parser, not an output. /// Note that this type is an input to the parser, not an output.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq, Default)]
pub struct Directive { pub struct Directive {
/// The provider's name for the directive. /// The provider's name for the directive.
name: String, name: String,
@ -79,28 +79,14 @@ mod serial {
pub directives: Vec<Directive>, pub directives: Vec<Directive>,
} }
#[derive(Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields, tag = "kind")]
pub enum TypeKind {
Text,
Speech,
Character,
Duration,
Asset {
tags: Vec<String>,
},
Enum {
#[serde(rename = "enum")]
enm: Vec<String>,
},
}
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)] #[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct Type { pub struct Type {
pub name: String, pub name: String,
pub doc: String, pub kind: String,
pub kind: TypeKind, pub tags: Option<Vec<String>>,
#[serde(rename = "enum")]
pub enm: Option<Vec<String>>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -133,6 +119,16 @@ pub enum Error {
#[snafu(display("invalid directive definition file: {source}"))] #[snafu(display("invalid directive definition file: {source}"))]
Decode { source: serde_json::Error }, Decode { source: serde_json::Error },
#[snafu(display("type {name} has invalid kind {given}"))]
InvalidKind { name: String, given: String },
#[snafu(display("{kind} type {name} is missing its {field}"))]
IncompleteType {
name: String,
kind: String,
field: &'static str,
},
#[snafu(display("no type {name} of {position} parameter for {directive}"))] #[snafu(display("no type {name} of {position} parameter for {directive}"))]
ParamType { ParamType {
directive: String, directive: String,
@ -168,13 +164,37 @@ impl TypeSystem {
pub fn parse(&mut self, src: &str) -> Result<&mut Self, Error> { pub fn parse(&mut self, src: &str) -> Result<&mut Self, Error> {
let s: serial::Document = serde_json::from_str(src).context(DecodeSnafu)?; let s: serial::Document = serde_json::from_str(src).context(DecodeSnafu)?;
for t in s.types { for t in s.types {
let p = match t.kind { let p = match t.kind.as_str() {
serial::TypeKind::Text => Type::Text, "text" => Type::Text,
serial::TypeKind::Speech => Type::Speech, "speech" => Type::Speech,
serial::TypeKind::Character => Type::Character, "character" => Type::Character,
serial::TypeKind::Duration => Type::Duration, "duration" => Type::Duration,
serial::TypeKind::Asset { tags } => Type::Asset { tags }, "asset" => match t.tags {
serial::TypeKind::Enum { enm } => Type::Enum(enm), None => {
return Err(Error::IncompleteType {
name: t.name,
kind: t.kind,
field: "tags",
});
}
Some(v) => Type::Asset { tags: v },
},
"enum" => match t.enm {
None => {
return Err(Error::IncompleteType {
name: t.name,
kind: t.kind,
field: "enumerants",
});
}
Some(v) => Type::Enum(v),
},
_ => {
return Err(Error::InvalidKind {
name: t.name,
given: t.kind,
});
}
}; };
self.types.insert(t.name, p); self.types.insert(t.name, p);
} }
@ -268,6 +288,13 @@ mod tests {
use super::*; use super::*;
use rstest::rstest; use rstest::rstest;
macro_rules! vec_into {
[] => { Vec::new() };
[$($val:expr),+ $(,)?] =>{
Vec::from([$($val,)+].map(|x| x.into()))
};
}
#[rstest] #[rstest]
fn builtin() { fn builtin() {
let mut s = TypeSystem { let mut s = TypeSystem {
@ -281,21 +308,124 @@ mod tests {
("speech", Type::Speech), ("speech", Type::Speech),
("character", Type::Character), ("character", Type::Character),
("duration", Type::Duration), ("duration", Type::Duration),
("scene", Type::Asset { tags: Vec::from(["scene"].map(String::from)) }), ("scene", Type::Asset { tags: vec_into!["scene"] }),
("background", Type::Asset { tags: Vec::from(["background"].map(String::from)) }), ("background", Type::Asset { tags: vec_into!["background"] }),
("stage position", Type::Enum(Vec::from(["close", "left third", "right third", "left", "center", "right", "far left", "mid left", "mid right", "far right", "far"].map(String::from)))), ("stage position", Type::Enum(vec_into!["close", "left third", "right third", "left", "center", "right", "far left", "mid left", "mid right", "far right", "far"])),
].map(|(k, v)| (k.into(), v))), ].map(|(k, v)| (k.into(), v))),
directives: BTreeMap::from([ directives: BTreeMap::from([
("page", Directive { ("page", Directive {
name: "page".into(), name: "page".into(),
doc: "Start showing the current static speaker, text, &c.\nNormally not used directly; script speech lines call this directive implicitly.".into(), doc: "Start showing the current static speaker, text, &c.\nNormally not used directly; script speech lines call this directive implicitly.".into(),
prefix: "page".into(), prefix: "page".into(),
prefix_param: None, ..Default::default()
infix: None, }),
infix_param: None, ("set static text _", Directive {
suffix: None, name: "set static text _".into(),
suffix_param: None, doc: "Set the current page's text.\nNormally not used directly; script speech lines call this directive implicitly.".into(),
list_param: None, prefix: "set static text".into(),
prefix_param: Some(Param {
name: "text".into(),
doc: Some("Line content.".into()),
typ: Type::Speech,
nuance: None,
}),
..Default::default()
}),
("set static speaker _", Directive {
name: "set static speaker _".into(),
doc: "Set the current page's speaker.\nNormally not used directly; script speech lines call this directive implicitly.".into(),
prefix: "set static speaker".into(),
prefix_param: Some(Param {
name: "name".into(),
doc: Some("Speaker name.".into()),
typ: Type::Text,
nuance: None,
}),
..Default::default()
}),
("characters", Directive {
name: "characters".into(),
doc: "Set the names of characters in the current scene.".into(),
prefix: "characters".into(),
list_param: Some(Param {
name: "characters".into(),
doc: Some("Name of the character to include.".into()),
typ: Type::Text,
nuance: Some(Box::new(Param {
name: "variant".into(),
doc: Some("Variant of this character.".into()),
typ: Type::Text,
nuance: None,
})),
}),
..Default::default()
}),
("scene", Directive {
name: "scene".into(),
doc: "Mark the beginning of a scene.".into(),
prefix: "scene".into(),
..Default::default()
}),
("background", Directive {
name: "background".into(),
doc: "Set the background image.".into(),
prefix: "background".into(),
prefix_param: Some(Param {
name: "asset".into(),
doc: Some("Background image asset.".into()),
typ: Type::Asset { tags: vec_into!["background"] },
nuance: None,
}),
..Default::default()
}),
("enter _ stage _", Directive {
name: "enter _ stage _".into(),
doc: "Bring a character onto the stage at a given position.".into(),
prefix: "enter".into(),
prefix_param: Some(Param {
name: "character".into(),
doc: Some("Character to enter.".into()),
typ: Type::Character,
nuance: None,
}),
suffix: Some("stage".into()),
suffix_param: Some(Param {
name: "position".into(),
doc: Some("Position on the stage where the character enters.".into()),
typ: Type::Enum(vec_into!["close", "left third", "right third", "left", "center", "right", "far left", "mid left", "mid right", "far right", "far"]),
nuance: None,
}),
..Default::default()
}),
("enter _", Directive {
name: "enter _".into(),
doc: "Bring a character onto the stage at a default position.".into(),
prefix: "enter".into(),
prefix_param: Some(Param {
name: "character".into(),
doc: Some("Character to enter.".into()),
typ: Type::Character,
nuance: None,
}),
..Default::default()
}),
("exit _", Directive {
name: "exit _".into(),
doc: "Remove a character from the stage.".into(),
prefix: "exit".into(),
prefix_param: Some(Param {
name: "character".into(),
doc: Some("Character to exit.".into()),
typ: Type::Character,
nuance: None,
}),
..Default::default()
}),
("exeunt", Directive {
name: "exeunt".into(),
doc: "Remove all characters from the stage.".into(),
prefix: "exeunt".into(),
..Default::default()
}), }),
].map(|(k, v)| (k.into(), v))), ].map(|(k, v)| (k.into(), v))),
}; };