use linked_hash_map::LinkedHashMap;
use serde_derive::Serialize;
use std::{collections::HashSet, fmt};
const INDENTION: usize = 4;
const IMPORT_VEC: &str = "use std::vec::Vec;";
const IMPORT_SERDE: &str = "use serde_derive::{Serialize, Deserialize};";
#[derive(Clone, Debug)]
pub enum UnaryOp {
Neg,
Pos,
Inverse,
}
impl UnaryOp {
pub fn to_str(&self) -> &str {
match self {
UnaryOp::Neg => "-",
UnaryOp::Pos => "+",
UnaryOp::Inverse => "~",
}
}
}
#[derive(Clone, Debug)]
pub enum BinaryOp {
Add,
Sub,
Mul,
Div,
Mod,
LShift,
RShift,
Or,
Xor,
And,
}
impl BinaryOp {
pub fn to_str(&self) -> &str {
match self {
BinaryOp::Add => "+",
BinaryOp::Sub => "-",
BinaryOp::Mul => "*",
BinaryOp::Div => "/",
BinaryOp::Mod => "%",
BinaryOp::LShift => "<<",
BinaryOp::RShift => ">>",
BinaryOp::Or => "|",
BinaryOp::Xor => "^",
BinaryOp::And => "&",
}
}
}
#[derive(Clone, Debug)]
pub struct IdlScopedName(pub Vec<String>, pub bool);
impl fmt::Display for IdlScopedName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let is_absolute_path = self.1;
let components = &self.0;
for (idx, comp) in components.iter().enumerate() {
if idx == 0 && !is_absolute_path {
write!(f, "{comp}")?
} else if idx == 0 && is_absolute_path {
write!(f, "crate::{comp}")?
} else {
write!(f, "::{comp}")?
}
}
Ok(())
}
}
#[derive(Clone, Debug, Default)]
pub enum IdlValueExpr {
#[default]
None,
DecLiteral(String),
HexLiteral(String),
OctLiteral(String),
CharLiteral(String),
WideCharLiteral(String),
StringLiteral(String),
WideStringLiteral(String),
BooleanLiteral(bool),
FloatLiteral(
Option<String>,
Option<String>,
Option<String>,
Option<String>,
),
UnaryOp(UnaryOp, Box<IdlValueExpr>),
BinaryOp(BinaryOp, Box<IdlValueExpr>),
Expr(Box<IdlValueExpr>, Box<IdlValueExpr>),
Brace(Box<IdlValueExpr>),
ScopedName(IdlScopedName),
}
impl fmt::Display for IdlValueExpr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let value_expr = match self {
IdlValueExpr::None => "",
IdlValueExpr::DecLiteral(val) => val,
IdlValueExpr::HexLiteral(val) => val,
IdlValueExpr::OctLiteral(val) => val,
IdlValueExpr::CharLiteral(val) => val,
IdlValueExpr::WideCharLiteral(val) => val,
IdlValueExpr::StringLiteral(val) => val,
IdlValueExpr::WideStringLiteral(val) => val,
IdlValueExpr::BooleanLiteral(val) => &val.to_string(),
IdlValueExpr::UnaryOp(op, expr) => &format!("{}{}", op.to_str(), expr),
IdlValueExpr::BinaryOp(op, expr) => &format!("{}{}", op.to_str(), expr),
IdlValueExpr::Expr(expr1, expr2) => &format!("{}{}", expr1, expr2),
IdlValueExpr::Brace(expr) => &format!("({})", expr),
IdlValueExpr::FloatLiteral(integral, fraction, exponent, suffix) => &format!(
"{}.{}e{}{}",
integral.as_ref().unwrap().clone(),
fraction.as_ref().unwrap().clone(),
exponent.as_ref().unwrap().clone(),
suffix.as_ref().unwrap().clone()
),
IdlValueExpr::ScopedName(name) => &name.to_string(),
};
write!(f, "{value_expr}")
}
}
#[derive(Clone, Debug)]
pub struct IdlStructMember {
pub id: String,
pub type_spec: IdlTypeSpec,
}
#[derive(Clone, Debug)]
pub struct IdlSwitchElement {
pub id: String,
pub type_spec: IdlTypeSpec,
}
#[derive(Clone, Debug)]
pub enum IdlSwitchLabel {
Label(IdlValueExpr),
Default,
}
#[derive(Clone, Debug)]
pub struct IdlSwitchCase {
pub labels: Vec<IdlSwitchLabel>,
pub elem_spec: IdlSwitchElement,
}
#[derive(Clone, Debug, Default)]
pub enum IdlTypeSpec {
#[default]
None,
ArrayType(Box<IdlTypeSpec>, Vec<IdlValueExpr>),
SequenceType(Box<IdlTypeSpec>),
StringType(Option<Box<IdlValueExpr>>),
WideStringType(Option<Box<IdlValueExpr>>),
F32Type,
F64Type,
F128Type,
I16Type,
I32Type,
I64Type,
U16Type,
U32Type,
U64Type,
CharType,
WideCharType,
BooleanType,
OctetType,
ScopedName(IdlScopedName),
}
impl fmt::Display for IdlTypeSpec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let value_expr = match self {
IdlTypeSpec::F32Type => Ok("f32".to_string()),
IdlTypeSpec::F64Type => Ok("f64".to_string()),
IdlTypeSpec::F128Type => Ok("f128".to_string()),
IdlTypeSpec::I16Type => Ok("i16".to_string()),
IdlTypeSpec::I32Type => Ok("i32".to_string()),
IdlTypeSpec::I64Type => Ok("i64".to_string()),
IdlTypeSpec::U16Type => Ok("u16".to_string()),
IdlTypeSpec::U32Type => Ok("u32".to_string()),
IdlTypeSpec::U64Type => Ok("u64".to_string()),
IdlTypeSpec::CharType => Ok("char".to_string()),
IdlTypeSpec::WideCharType => Ok("char".to_string()),
IdlTypeSpec::BooleanType => Ok("bool".to_string()),
IdlTypeSpec::OctetType => Ok("u8".to_string()),
IdlTypeSpec::StringType(None) => Ok("String".to_string()),
IdlTypeSpec::WideStringType(None) => Ok("String".to_string()),
IdlTypeSpec::StringType(_) => Ok("String".to_string()),
IdlTypeSpec::WideStringType(_) => Ok("String".to_string()),
IdlTypeSpec::SequenceType(typ_expr) => Ok(format!("Vec<{}>", typ_expr.as_ref())),
IdlTypeSpec::ArrayType(typ_expr, dim_expr_list) => {
let dim_list_str = dim_expr_list
.iter()
.map(|expr| match expr {
IdlValueExpr::DecLiteral(_)
| IdlValueExpr::HexLiteral(_)
| IdlValueExpr::OctLiteral(_)
| IdlValueExpr::Expr(_, _)
| IdlValueExpr::BinaryOp(_, _) => Ok(format!(";{}_usize]", expr)),
IdlValueExpr::ScopedName(_) => Ok(format!(";{} as usize]", expr)),
_ => Err(fmt::Error),
})
.collect::<Result<String, fmt::Error>>()?;
Ok(format!(
"{}{}{dim_list_str}",
"[".repeat(dim_expr_list.len()),
typ_expr
))
}
IdlTypeSpec::ScopedName(name) => Ok(name.to_string()),
_ => unimplemented!(),
}?;
write!(f, "{value_expr}")
}
}
#[derive(Clone, Debug, Default)]
pub enum IdlTypeDclKind {
#[default]
None,
TypeDcl(String, IdlTypeSpec),
StructDcl(String, Vec<IdlStructMember>),
UnionDcl(String, IdlTypeSpec, Vec<IdlSwitchCase>),
EnumDcl(String, Vec<String>),
}
#[derive(Clone, Debug, Default)]
pub struct IdlTypeDcl(pub IdlTypeDclKind);
#[derive(Serialize)]
struct IdlStructField {
name: String,
type_str: String,
directive: String,
}
#[derive(Serialize)]
struct IdlSwitchField {
name: String,
element_id: String,
element_type: String,
}
impl IdlTypeDcl {
pub fn render(
&self,
env: &minijinja::Environment,
level: usize,
) -> Result<String, minijinja::Error> {
match self.0 {
IdlTypeDclKind::TypeDcl(ref id, ref type_spec) => {
let tmpl = env.get_template("typedef.j2")?;
tmpl.render(minijinja::context! {
typedef_name => id,
typedef_type => type_spec.to_string(),
indent_level => level
})
}
IdlTypeDclKind::StructDcl(ref id, ref type_spec) => {
let tmpl = env.get_template("struct.j2")?;
let fields = type_spec
.iter()
.map(|field| {
if let IdlTypeSpec::ArrayType(_, _) = field.type_spec {
IdlStructField {
name: field.id.clone(),
type_str: field.type_spec.to_string(),
directive: "#[serde(with = \"serde_arrays\")]".to_string(),
}
} else {
IdlStructField {
name: field.id.clone(),
type_str: field.type_spec.to_string(),
directive: String::new(),
}
}
})
.collect::<Vec<IdlStructField>>();
tmpl.render(minijinja::context! {
struct_name => id,
fields,
indent_level => level
})
}
IdlTypeDclKind::EnumDcl(ref id, ref enums) => {
let tmpl = env.get_template("enum.j2")?;
tmpl.render(minijinja::context! {
enum_name => id,
variants => enums,
indent_level => level
})
}
IdlTypeDclKind::UnionDcl(ref id, ref _type_spec, ref switch_cases) => {
let tmpl = env.get_template("union_switch.j2")?;
let union_members = switch_cases
.iter()
.flat_map(|case| {
case.labels
.clone()
.iter()
.map(|label| {
let label = match label {
IdlSwitchLabel::Label(label) => label.to_string(),
IdlSwitchLabel::Default => "default".to_owned(),
};
IdlSwitchField {
name: label.to_string(),
element_id: case.elem_spec.id.clone(),
element_type: case.elem_spec.type_spec.to_string(),
}
})
.collect::<Vec<IdlSwitchField>>()
})
.collect::<Vec<IdlSwitchField>>();
tmpl.render(minijinja::context! {
union_name => id,
union_members,
indent_level => level
})
}
IdlTypeDclKind::None => Ok(String::new()),
}
}
}
#[derive(Clone, Default, Debug)]
pub struct IdlConstDcl {
pub id: String,
pub typedcl: IdlTypeSpec,
pub value: IdlValueExpr,
}
impl IdlConstDcl {
pub fn render(
&self,
env: &minijinja::Environment,
level: usize,
) -> Result<String, minijinja::Error> {
let tmpl = env.get_template("const.j2")?;
let type_str = match &self.typedcl {
IdlTypeSpec::StringType(_) | IdlTypeSpec::WideStringType(_) => "&str".to_owned(),
_ => self.typedcl.to_string(),
};
tmpl.render(minijinja::context! {
const_name => self.id,
const_type => type_str,
const_value => self.value.to_string(),
indent_level => level
})
}
}
#[derive(Clone, Default, Debug)]
pub struct IdlModule {
pub id: Option<String>,
pub modules: LinkedHashMap<String, IdlModule>,
pub types: LinkedHashMap<String, IdlTypeDcl>,
pub constants: LinkedHashMap<String, IdlConstDcl>,
}
impl IdlModule {
pub fn new(id: Option<String>) -> IdlModule {
IdlModule {
id,
modules: LinkedHashMap::default(),
types: LinkedHashMap::default(),
constants: LinkedHashMap::default(),
}
}
pub fn render(
&self,
env: &minijinja::Environment,
level: usize,
) -> Result<String, minijinja::Error> {
let mut module_info = String::new();
let add = if self.id.is_some() { 1 } else { 0 };
let mut uses = HashSet::new();
for typ in self.types.values() {
if let IdlTypeDcl(IdlTypeDclKind::TypeDcl(_, IdlTypeSpec::SequenceType(_))) = typ {
uses.insert(IMPORT_VEC);
} else if let IdlTypeDcl(IdlTypeDclKind::StructDcl(_, _)) = typ {
uses.insert(IMPORT_SERDE);
} else if let IdlTypeDcl(IdlTypeDclKind::EnumDcl(_, _)) = typ {
uses.insert(IMPORT_SERDE);
} else if let IdlTypeDcl(IdlTypeDclKind::UnionDcl(_, _, _)) = typ {
uses.insert(IMPORT_SERDE);
}
}
for cnsts in self.constants.values() {
if let IdlTypeSpec::SequenceType(_) = cnsts.typedcl {
uses.insert(IMPORT_VEC);
break;
}
}
for required_use in uses {
let uses = format!(
"{:indent$}{required_use}\n",
"",
indent = (level + add) * INDENTION
);
module_info.push_str(&uses);
}
for typ in self.types.values() {
let rendered = typ.render(env, level + add)?;
module_info.push_str(&rendered);
module_info.push('\n');
}
for module in self.modules.values() {
let rendered = module.render(env, level + add)?;
module_info.push_str(&rendered);
module_info.push('\n');
}
for cnst in self.constants.values() {
let rendered = cnst.render(env, level + add)?;
module_info.push_str(&rendered);
module_info.push('\n');
}
match self.id {
Some(ref id_str) => {
let tmpl = env.get_template("module.j2")?;
tmpl.render(minijinja::context! {
module_name => id_str,
module_information => module_info,
indent_level => level
})
}
None => Ok(module_info),
}
}
}