From fa257d7db2e898a3f7fac19337ea065a6e814a84 Mon Sep 17 00:00:00 2001 From: Branden J Brown Date: Tue, 15 Oct 2024 17:22:50 -0400 Subject: [PATCH] directive: parse and type check definitions --- Cargo.lock | 28 +++++++ Cargo.toml | 1 + src/directive.rs | 190 +++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 196 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b807eb..ad299fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "itoa" version = "1.0.11" @@ -168,6 +174,27 @@ dependencies = [ "serde", ] +[[package]] +name = "snafu" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223891c85e2a29c3fe8fb900c1fae5e69c2e42415e3177752e8718475efa5019" +dependencies = [ + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c3c6b7927ffe7ecaa769ee0e3994da3b8cafc8f444578982c83ecb161af917" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syn" version = "2.0.79" @@ -192,4 +219,5 @@ dependencies = [ "rstest", "serde", "serde_json", + "snafu", ] diff --git a/Cargo.toml b/Cargo.toml index 3137e36..5b20584 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] serde = { version = "1.0.210", features = ["derive"] } serde_json = "1.0.128" +snafu = "0.8.5" [dev-dependencies] rstest = { version = "0.23.0", default-features = false } diff --git a/src/directive.rs b/src/directive.rs index 1a269e8..ae0d634 100644 --- a/src/directive.rs +++ b/src/directive.rs @@ -1,3 +1,13 @@ +use std::collections::BTreeMap; + +use snafu::{ResultExt, Snafu}; + +/// The types and directives in a VijiN project. +pub struct TypeSystem { + pub types: BTreeMap, + pub directives: BTreeMap, +} + /// The definition of a standard directive. /// /// Note that this type is an input to the parser, not an output. @@ -39,6 +49,7 @@ pub struct Param { } /// Types expected by directive arguments. +#[derive(Clone)] pub enum Type { /// Arbitrary text. Text, @@ -60,14 +71,14 @@ mod serial { #[derive(Deserialize)] #[serde(rename_all = "snake_case", deny_unknown_fields)] - struct Document { - types: Vec, - directives: Vec, + pub struct Document { + pub types: Vec, + pub directives: Vec, } #[derive(Deserialize)] #[serde(rename_all = "snake_case", deny_unknown_fields, tag = "kind")] - enum TypeKind { + pub enum TypeKind { Text, Speech, Character, @@ -83,39 +94,172 @@ mod serial { #[derive(Deserialize)] #[serde(rename_all = "snake_case", deny_unknown_fields)] - struct Type { - name: String, - doc: String, - kind: TypeKind, + pub struct Type { + pub name: String, + pub doc: String, + pub kind: TypeKind, } #[derive(Deserialize)] #[serde(rename_all = "snake_case", deny_unknown_fields)] - struct Param { - name: String, - doc: Option, + pub struct Param { + pub name: String, + pub doc: Option, #[serde(rename = "type")] - typ: String, - nuance: Option>, + pub typ: String, + pub nuance: Option>, } #[derive(Deserialize)] #[serde(rename_all = "snake_case", deny_unknown_fields)] - struct Directive { - name: String, - doc: String, - prefix: String, - prefix_param: Option, - infix: Option, - infix_param: Option, - suffix: Option, - suffix_param: Option, - list_param: Option, + pub struct Directive { + pub name: String, + pub doc: Option, + pub prefix: String, + pub prefix_param: Option, + pub infix: Option, + pub infix_param: Option, + pub suffix: Option, + pub suffix_param: Option, + pub list_param: Option, } } +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("invalid directive definition file: {source}"))] + Decode { source: serde_json::Error }, + + #[snafu(display("no type {name} of {position} parameter for {directive}"))] + ParamType { + directive: String, + position: &'static str, + name: String, + }, + + #[snafu(display("{directive} has {position} parameter with no {position}"))] + MissingSyntax { + directive: String, + position: &'static str, + }, + + #[snafu(display("{directive} has illegal nuance on {position}"))] + InvalidNuance { + directive: String, + position: &'static str, + }, +} + static DIRECTIVE_PREDECLARED: &str = include_str!("directive-predeclared.json"); +impl TypeSystem { + pub fn new() -> TypeSystem { + let mut ts = TypeSystem { + types: BTreeMap::new(), + directives: BTreeMap::new(), + }; + ts.parse(DIRECTIVE_PREDECLARED).unwrap(); + ts + } + + 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), + }; + self.types.insert(t.name, p); + } + for d in s.directives { + let prefix_param = d + .prefix_param + .map(|p| self.param(&d.name, "prefix", p)) + .transpose()?; + let infix_param = d + .infix_param + .map(|p| { + if d.suffix.is_none() { + return Err(Error::MissingSyntax { + directive: d.name.clone(), + position: "infix", + }); + } + self.param(&d.name, "infix", p) + }) + .transpose()?; + let suffix_param = d + .suffix_param + .map(|p| { + if d.suffix.is_none() { + return Err(Error::MissingSyntax { + directive: d.name.clone(), + position: "suffix", + }); + } + self.param(&d.name, "suffix", p) + }) + .transpose()?; + let list_param = d + .list_param + .map(|p| self.param(&d.name, "list", p)) + .transpose()?; + let r = Directive { + name: d.name.clone(), + doc: d.doc.unwrap_or_default(), + prefix: d.prefix, + prefix_param, + infix: d.infix, + infix_param, + suffix: d.suffix, + suffix_param, + list_param, + }; + self.directives.insert(d.name, r); + } + Ok(self) + } + + fn param( + &self, + directive: &str, + position: &'static str, + p: serial::Param, + ) -> Result { + let t = match self.types.get(&p.typ) { + None => { + return Err(Error::ParamType { + directive: directive.into(), + position, + name: p.typ, + }) + } + Some(t) => t, + }; + let nuance = if let Some(n) = p.nuance { + if position != "list" { + return Err(Error::InvalidNuance { + directive: directive.into(), + position, + }); + } + Some(Box::new(self.param(directive, "list nuance", *n)?)) + } else { + None + }; + Ok(Param { + name: p.name, + doc: p.doc, + typ: t.clone(), + nuance, + }) + } +} + #[cfg(test)] mod tests { use super::*;