directive: parse and type check definitions

This commit is contained in:
Branden J Brown 2024-10-15 17:22:50 -04:00
parent fff67316bb
commit fa257d7db2
3 changed files with 196 additions and 23 deletions

28
Cargo.lock generated
View File

@ -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",
]

View File

@ -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 }

View File

@ -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<String, Type>,
pub directives: BTreeMap<String, Directive>,
}
/// 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<Type>,
directives: Vec<Directive>,
pub struct Document {
pub types: Vec<Type>,
pub directives: Vec<Directive>,
}
#[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<String>,
pub struct Param {
pub name: String,
pub doc: Option<String>,
#[serde(rename = "type")]
typ: String,
nuance: Option<Box<Param>>,
pub typ: String,
pub nuance: Option<Box<Param>>,
}
#[derive(Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
struct Directive {
name: String,
doc: String,
prefix: String,
prefix_param: Option<Param>,
infix: Option<String>,
infix_param: Option<Param>,
suffix: Option<String>,
suffix_param: Option<Param>,
list_param: Option<Param>,
pub struct Directive {
pub name: String,
pub doc: Option<String>,
pub prefix: String,
pub prefix_param: Option<Param>,
pub infix: Option<String>,
pub infix_param: Option<Param>,
pub suffix: Option<String>,
pub suffix_param: Option<Param>,
pub list_param: Option<Param>,
}
}
#[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<Param, Error> {
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::*;