Skip to content

Commit

Permalink
PoC Rust codegen
Browse files Browse the repository at this point in the history
  • Loading branch information
aumetra committed Oct 25, 2024
1 parent 30b0d91 commit 9855d42
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 27 deletions.
153 changes: 150 additions & 3 deletions packages/cw-schema-codegen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ extern crate log;

use clap::{Parser, ValueEnum};
use std::{
fs::File,
borrow::Cow,
fs::{self, File},
io::{self, Write},
path::PathBuf,
};

#[derive(Clone, Copy, Debug, Default, ValueEnum)]
#[derive(Clone, Copy, Debug, Default, PartialEq, ValueEnum)]
pub enum Language {
#[default]
Rust,
Expand Down Expand Up @@ -48,6 +49,52 @@ impl Opts {
}
}

fn expand_node_name<'a>(
schema: &'a cw_schema::SchemaV1,
node: &'a cw_schema::Node,
) -> Cow<'a, str> {
match node.value {
cw_schema::NodeType::Array { items } => {
let items = &schema.definitions[items];
format!("Vec<{}>", expand_node_name(schema, items)).into()
}
cw_schema::NodeType::Float => "f32".into(),
cw_schema::NodeType::Double => "f64".into(),
cw_schema::NodeType::Boolean => "bool".into(),
cw_schema::NodeType::String => "String".into(),
cw_schema::NodeType::Integer { signed, precision } => {
let ty = if signed { "i" } else { "u" };
format!("{ty}{precision}").into()
}
cw_schema::NodeType::Binary => "Vec<u8>".into(),
cw_schema::NodeType::Optional { inner } => {
let inner = &schema.definitions[inner];
format!("Option<{}>", expand_node_name(schema, inner)).into()
}
cw_schema::NodeType::Struct(..) => node.name.as_ref().into(),
cw_schema::NodeType::Tuple { ref items } => {
let items = items
.iter()
.map(|item| expand_node_name(schema, &schema.definitions[*item]))
.collect::<Vec<_>>()
.join(", ");

format!("({})", items).into()
}
cw_schema::NodeType::Enum { .. } => node.name.as_ref().into(),
_ => todo!(),
}
}

fn prepare_docs(desc: Option<&str>) -> Cow<'_, [Cow<'_, str>]> {
desc.map(|desc| {
desc.lines()
.map(|line| line.replace('"', "\\\"").into())
.collect()
})
.unwrap_or(Cow::Borrowed(&[]))
}

fn main() -> anyhow::Result<()> {
simple_logger::SimpleLogger::new()
.without_timestamps()
Expand All @@ -59,11 +106,111 @@ fn main() -> anyhow::Result<()> {
opts.language, opts.file
);

let schema = std::fs::read_to_string(&opts.file)?;
ensure!(opts.file.exists(), "Schema file does not exist");
ensure!(
opts.language == Language::Rust,
"Only Rust code generation is supported at the moment"
);

let schema = fs::read_to_string(&opts.file)?;
let schema: cw_schema::Schema = serde_json::from_str(&schema)?;
let cw_schema::Schema::V1(schema) = schema else {
bail!("Unsupported schema version");
};

let mut output = opts.output()?;

schema.definitions.iter().for_each(|node| {
debug!("Processing node: {node:?}");

match node.value {
cw_schema::NodeType::Struct(ref sty) => {
let structt = cw_schema_codegen::rust::StructTemplate {
name: &node.name,
docs: prepare_docs(node.description.as_deref()),
ty: match sty {
cw_schema::StructType::Unit => cw_schema_codegen::rust::TypeTemplate::Unit,
cw_schema::StructType::Named { ref properties } => {
cw_schema_codegen::rust::TypeTemplate::Named {
fields: properties
.iter()
.map(|(name, prop)| {
let ty = expand_node_name(
&schema,
&schema.definitions[prop.value],
);
cw_schema_codegen::rust::FieldTemplate {
name: Cow::Borrowed(name),
docs: prepare_docs(prop.description.as_deref()),
ty,
}
})
.collect(),
}
}
_ => unreachable!(),
},
};

writeln!(output, "{structt}").unwrap();
}
cw_schema::NodeType::Enum { ref cases, .. } => {
let enumm = cw_schema_codegen::rust::EnumTemplate {
name: &node.name,
docs: prepare_docs(node.description.as_deref()),
variants: cases
.iter()
.map(
|(name, case)| cw_schema_codegen::rust::EnumVariantTemplate {
name,
docs: prepare_docs(case.description.as_deref()),
ty: match case.value {
cw_schema::EnumValue::Unit => {
cw_schema_codegen::rust::TypeTemplate::Unit
}
cw_schema::EnumValue::Tuple { ref items } => {
let items = items
.iter()
.map(|item| {
let node = &schema.definitions[*item];
expand_node_name(&schema, node)
})
.collect();

cw_schema_codegen::rust::TypeTemplate::Tuple(items)
}
cw_schema::EnumValue::Named { ref properties, .. } => {
cw_schema_codegen::rust::TypeTemplate::Named {
fields: properties
.iter()
.map(|(name, prop)| {
let ty = expand_node_name(
&schema,
&schema.definitions[prop.value],
);
cw_schema_codegen::rust::FieldTemplate {
name: Cow::Borrowed(name),
docs: prepare_docs(
prop.description.as_deref(),
),
ty,
}
})
.collect(),
}
}
_ => unreachable!(),
},
},
)
.collect(),
};

writeln!(output, "{enumm}").unwrap();
}
_ => (),
}
});

Ok(())
}
20 changes: 15 additions & 5 deletions packages/cw-schema-codegen/src/rust.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
use askama::Template;
use std::borrow::Cow;

#[derive(Clone)]
pub struct EnumVariantTemplate<'a> {
pub name: &'a str,
pub docs: Cow<'a, [Cow<'a, str>]>,
pub ty: TypeTemplate<'a>,
}

#[derive(Template)]
#[template(escape = "none", path = "rust/enum.tpl.rs")]
pub struct EnumTemplate<'a> {
pub name: &'a str,
pub variants: &'a [EnumVariantTemplate<'a>],
pub docs: Cow<'a, [Cow<'a, str>]>,
pub variants: Cow<'a, [EnumVariantTemplate<'a>]>,
}

#[derive(Clone)]
pub struct FieldTemplate<'a> {
pub name: &'a str,
pub ty: &'a str,
pub name: Cow<'a, str>,
pub docs: Cow<'a, [Cow<'a, str>]>,
pub ty: Cow<'a, str>,
}

#[derive(Clone)]
pub enum TypeTemplate<'a> {
Unit,
Tuple(&'a [&'a str]),
Named { fields: &'a [FieldTemplate<'a>] },
Tuple(Cow<'a, [Cow<'a, str>]>),
Named {
fields: Cow<'a, [FieldTemplate<'a>]>,
},
}

#[derive(Template)]
#[template(escape = "none", path = "rust/struct.tpl.rs")]
pub struct StructTemplate<'a> {
pub name: &'a str,
pub docs: Cow<'a, [Cow<'a, str>]>,
pub ty: TypeTemplate<'a>,
}
12 changes: 12 additions & 0 deletions packages/cw-schema-codegen/templates/rust/enum.tpl.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
{% for doc in docs %}
#[doc = "{{ doc }}"]
{% endfor %}

pub enum {{ name }} {
{% for variant in variants %}
{% for doc in variant.docs %}
#[doc = "{{ doc }}"]
{% endfor %}

{{ variant.name }}
{% match variant.ty %}
{% when TypeTemplate::Unit %}
Expand All @@ -12,6 +20,10 @@ pub enum {{ name }} {
{% when TypeTemplate::Named with { fields } %}
{
{% for field in fields %}
{% for doc in field.docs %}
#[doc = "{{ doc }}"]
{% endfor %}

{{ field.name }}: {{ field.ty }},
{% endfor %}
}
Expand Down
8 changes: 8 additions & 0 deletions packages/cw-schema-codegen/templates/rust/struct.tpl.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
{% for doc in docs %}
#[doc = "{{ doc }}"]
{% endfor %}

pub struct {{ name }}

{% match ty %}
Expand All @@ -12,6 +16,10 @@ pub struct {{ name }}
{% when TypeTemplate::Named with { fields } %}
{
{% for field in fields %}
{% for doc in field.docs %}
#[doc = "{{ doc }}"]
{% endfor %}

{{ field.name }}: {{ field.ty }},
{% endfor %}
}
Expand Down
Loading

0 comments on commit 9855d42

Please sign in to comment.