Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

F2 doc fields #38

Merged
merged 21 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build_and_test.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
(cd rust/ && cargo test) && \
(cd rust/fastsim-core/ && cargo test) && \
(cd rust/fastsim-cli/ && cargo test) && \
pip install -qe ".[dev]" && \
pytest -v python/fastsim/tests/
205 changes: 116 additions & 89 deletions python/fastsim/demos/demo.py

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions rust/fastsim-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "fastsim-core"
version = "0.1.3"
version = "0.1.4"
edition = "2021"
license = "Apache-2.0"
authors = ["NREL/MTES/CIMS/MBAP Group <fastsim@nrel.gov>"]
Expand All @@ -10,7 +10,10 @@ readme = "../../README.md"
repository = "/~https://github.com/NREL/fastsim"

[dependencies]
proc-macros = { package = "fastsim-proc-macros", version = "~0" }
# uncomment next line for any development work in fastsim-proc-macros
proc-macros = { package = "fastsim-proc-macros", path = "./fastsim-proc-macros" }
# comment next line for any development work in fastsim-proc-macros
# proc-macros = { package = "fastsim-proc-macros", version = "0.1.4" }
pyo3 = { workspace = true, features = [
"extension-module",
"anyhow",
Expand Down
2 changes: 1 addition & 1 deletion rust/fastsim-core/fastsim-proc-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
authors = ["NREL/MTES/CIMS/MBAP Group <fastsim@nrel.gov>"]
name = "fastsim-proc-macros"
version = "0.1.3"
version = "0.1.4"
edition = "2021"
license = "Apache-2.0"
readme = "../../../README.md"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
//! Module that implements [super::add_pyo3_api]

#[macro_use]
mod pyo3_api_utils;

use crate::imports::*;
use crate::utilities::*;

pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut ast = syn::parse_macro_input!(item as syn::ItemStruct);
Expand Down Expand Up @@ -37,6 +41,7 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream {
// /~https://github.com/PyO3/pyo3/blob/48690525e19b87818b59f99be83f1e0eb203c7d4/pyo3-macros-backend/src/pyclass.rs#L220

let mut opts = FieldOptions::default();
// attributes to retain, i.e. attributes that are not handled by this macro
let keep: Vec<bool> = field
.attrs
.iter()
Expand Down Expand Up @@ -73,9 +78,14 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream {
})
.collect();
// println!("options {:?}", opts);
let mut iter = keep.iter();
// this drops attrs with api, removing the field attribute from the struct def
field.attrs.retain(|_| *iter.next().unwrap());
// this drops attrs matching `#[pyo3_api(...)]`, removing the field attribute from the struct def
let new_attrs: (Vec<&syn::Attribute>, Vec<bool>) = field
.attrs
.iter()
.zip(keep.iter())
.filter(|(_a, k)| **k)
.unzip();
field.attrs = new_attrs.0.iter().cloned().cloned().collect();

if let syn::Type::Path(type_path) = ftype.clone() {
// println!(
Expand Down Expand Up @@ -284,3 +294,60 @@ pub fn add_pyo3_api(attr: TokenStream, item: TokenStream) -> TokenStream {
final_output.extend::<TokenStream2>(output);
final_output.into()
}

#[derive(Debug, Default, Clone)]
pub struct FieldOptions {
/// if true, getters are not generated for a field
pub skip_get: bool,
/// if true, setters are not generated for a field
pub skip_set: bool,
/// if true, current field is itself a struct with `orphaned` field
pub field_has_orphaned: bool,
}

pub fn impl_getters_and_setters(
type_path: syn::TypePath,
impl_block: &mut TokenStream2,
ident: &proc_macro2::Ident,
opts: FieldOptions,
has_orphaned: bool,
ftype: syn::Type,
) {
let type_str = type_path.into_token_stream().to_string();
match type_str.as_str() {
"Array1 < f64 >" => {
impl_vec_get_set!(opts, ident, impl_block, f64, Pyo3ArrayF64, has_orphaned);
}
"Array1 < u32 >" => {
impl_vec_get_set!(opts, ident, impl_block, u32, Pyo3ArrayU32, has_orphaned);
}
"Array1 < i32 >" => {
impl_vec_get_set!(opts, ident, impl_block, i32, Pyo3ArrayI32, has_orphaned);
}
"Array1 < bool >" => {
impl_vec_get_set!(opts, ident, impl_block, bool, Pyo3ArrayBool, has_orphaned);
}
"Vec < f64 >" => {
impl_vec_get_set!(opts, ident, impl_block, f64, Pyo3VecF64, has_orphaned);
}
_ => match ident.to_string().as_str() {
"orphaned" => {
impl_block.extend::<TokenStream2>(quote! {
#[getter]
pub fn get_orphaned(&self) -> PyResult<bool> {
Ok(self.orphaned)
}
/// Reset the orphaned flag to false.
pub fn reset_orphaned(&mut self) -> PyResult<()> {
self.orphaned = false;
Ok(())
}
})
}
_ => {
impl_get_body!(ftype, ident, impl_block, opts);
impl_set_body!(ftype, ident, impl_block, has_orphaned, opts);
}
},
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
macro_rules! impl_vec_get_set {
($opts: ident, $fident: ident, $impl_block: ident, $contained_type: ty, $wrapper_type: expr, $has_orphaned: expr) => {
if !$opts.skip_get {
let get_name: TokenStream2 = format!("get_{}", $fident).parse().unwrap();
$impl_block.extend::<TokenStream2>(quote! {
#[getter]
pub fn #get_name(&self) -> PyResult<$wrapper_type> {
Ok($wrapper_type::new(self.#$fident.clone()))
}
});
}
if !$opts.skip_set {
let set_name: TokenStream2 = format!("set_{}", $fident).parse().unwrap();
match stringify!($wrapper_type) {
"Pyo3VecF64" => {
if $has_orphaned {
$impl_block.extend(quote! {
#[setter]
pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> PyResult<()> {
if !self.orphaned {
self.#$fident = new_value;
Ok(())
} else {
Err(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR))
}
}
})
} else {
$impl_block.extend(quote! {
#[setter]
pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> PyResult<()> {
self.#$fident = new_value;
Ok(())
}
})
}
}
_ => {
if $has_orphaned {
$impl_block.extend(quote! {
#[setter]
pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> PyResult<()> {
if !self.orphaned {
self.#$fident = Array1::from_vec(new_value);
Ok(())
} else {
Err(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR))
}
}
})
} else {
$impl_block.extend(quote! {
#[setter]
pub fn #set_name(&mut self, new_value: Vec<$contained_type>) -> PyResult<()> {
self.#$fident = Array1::from_vec(new_value);
Ok(())
}
})
}
}
}

}
};
}

/// Generates pyo3 getter methods
///
/// general match arguments:
/// - type: type of variable (e.g. `f64`)
/// - field: struct field
/// - impl_block: TokenStream2
/// - opts: FieldOptions struct instance
macro_rules! impl_get_body {
(
$type: ident, $field: ident, $impl_block: ident, $opts: ident
) => {
if !$opts.skip_get {
let get_name: TokenStream2 = format!("get_{}", $field).parse().unwrap();
let get_block = if $opts.field_has_orphaned {
quote! {
#[getter]
pub fn #get_name(&mut self) -> PyResult<#$type> {
self.#$field.orphaned = true;
Ok(self.#$field.clone())
}
}
} else {
quote! {
#[getter]
pub fn #get_name(&self) -> PyResult<#$type> {
Ok(self.#$field.clone())
}
}
};
$impl_block.extend::<TokenStream2>(get_block)
}
};
}

/// Generates pyo3 setter methods
///
/// general match arguments:
/// - type: type of variable (e.g. `f64`)
/// - field: struct field
/// - impl_block: TokenStream2
/// - has_orphaned: bool, true if struct has `orphaned` field
/// - opts: FieldOptions struct instance

macro_rules! impl_set_body {
( // for generic
$type: ident, $field: ident, $impl_block: ident, $has_orphaned: expr, $opts: ident
) => {
if !$opts.skip_set {
let set_name: TokenStream2 = format!("set_{}", $field).parse().unwrap();
let orphaned_set_block = if $has_orphaned && $opts.field_has_orphaned {
quote! {
if !self.orphaned {
self.#$field = new_value;
self.#$field.orphaned = false;
Ok(())
} else {
Err(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR))
}
}
} else if $has_orphaned {
quote! {
if !self.orphaned {
self.#$field = new_value;
Ok(())
} else {
Err(PyAttributeError::new_err(crate::utils::NESTED_STRUCT_ERR))
}
}
} else {
quote! {
self.#$field = new_value;
Ok(())
}
};

$impl_block.extend::<TokenStream2>(quote! {
#[setter]
pub fn #set_name(&mut self, new_value: #$type) -> PyResult<()> {
#orphaned_set_block
}
});
}
};
}

#[derive(Debug, Default, Clone)]
pub struct FieldOptions {
/// if true, getters are not generated for a field
pub skip_get: bool,
/// if true, setters are not generated for a field
pub skip_set: bool,
/// if true, current field is itself a struct with `orphaned` field
pub field_has_orphaned: bool,
}
2 changes: 2 additions & 0 deletions rust/fastsim-core/fastsim-proc-macros/src/approx_eq_derive.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Module that implements [super::approx_eq_derive]

use crate::imports::*;

pub fn approx_eq_derive(input: TokenStream) -> TokenStream {
Expand Down
Loading