From ff5261ae6020141027f258c031841a35e0b31c5a Mon Sep 17 00:00:00 2001 From: Branden J Brown Date: Wed, 16 Oct 2024 13:14:22 -0400 Subject: [PATCH] directive: test parsing builtins --- src/directive.rs | 200 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 165 insertions(+), 35 deletions(-) diff --git a/src/directive.rs b/src/directive.rs index 672f67e..1097f20 100644 --- a/src/directive.rs +++ b/src/directive.rs @@ -12,7 +12,7 @@ pub struct TypeSystem { /// The definition of a standard directive. /// /// 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 { /// The provider's name for the directive. name: String, @@ -79,28 +79,14 @@ mod serial { pub directives: Vec, } - #[derive(Deserialize)] - #[serde(rename_all = "snake_case", deny_unknown_fields, tag = "kind")] - pub enum TypeKind { - Text, - Speech, - Character, - Duration, - Asset { - tags: Vec, - }, - Enum { - #[serde(rename = "enum")] - enm: Vec, - }, - } - #[derive(Deserialize)] #[serde(rename_all = "snake_case", deny_unknown_fields)] pub struct Type { pub name: String, - pub doc: String, - pub kind: TypeKind, + pub kind: String, + pub tags: Option>, + #[serde(rename = "enum")] + pub enm: Option>, } #[derive(Deserialize)] @@ -133,6 +119,16 @@ pub enum Error { #[snafu(display("invalid directive definition file: {source}"))] 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}"))] ParamType { directive: String, @@ -168,13 +164,37 @@ impl TypeSystem { pub fn parse(&mut self, src: &str) -> Result<&mut Self, Error> { let s: serial::Document = serde_json::from_str(src).context(DecodeSnafu)?; for t in s.types { - let p = match t.kind { - serial::TypeKind::Text => Type::Text, - serial::TypeKind::Speech => Type::Speech, - serial::TypeKind::Character => Type::Character, - serial::TypeKind::Duration => Type::Duration, - serial::TypeKind::Asset { tags } => Type::Asset { tags }, - serial::TypeKind::Enum { enm } => Type::Enum(enm), + let p = match t.kind.as_str() { + "text" => Type::Text, + "speech" => Type::Speech, + "character" => Type::Character, + "duration" => Type::Duration, + "asset" => match t.tags { + 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); } @@ -268,6 +288,13 @@ mod tests { use super::*; use rstest::rstest; + macro_rules! vec_into { + [] => { Vec::new() }; + [$($val:expr),+ $(,)?] =>{ + Vec::from([$($val,)+].map(|x| x.into())) + }; + } + #[rstest] fn builtin() { let mut s = TypeSystem { @@ -281,21 +308,124 @@ mod tests { ("speech", Type::Speech), ("character", Type::Character), ("duration", Type::Duration), - ("scene", Type::Asset { tags: Vec::from(["scene"].map(String::from)) }), - ("background", Type::Asset { tags: Vec::from(["background"].map(String::from)) }), - ("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)))), + ("scene", Type::Asset { tags: vec_into!["scene"] }), + ("background", Type::Asset { tags: vec_into!["background"] }), + ("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))), directives: BTreeMap::from([ ("page", Directive { name: "page".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_param: None, - infix: None, - infix_param: None, - suffix: None, - suffix_param: None, - list_param: None, + ..Default::default() + }), + ("set static text _", Directive { + name: "set static text _".into(), + doc: "Set the current page's text.\nNormally not used directly; script speech lines call this directive implicitly.".into(), + 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))), };