diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bd8a838..32cb589 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,6 +28,7 @@ jobs: nix develop .#ci -c cargo run --no-default-features --example time nix develop .#ci -c cargo run --no-default-features --example clap nix develop .#ci -c cargo run --no-default-features --features=nesting --example nesting + nix develop .#ci -c cargo run --no-default-features --features=option --example option nix develop .#ci -c cargo test --no-default-features - name: Test with std features @@ -66,3 +67,9 @@ jobs: nix develop .#ci -c cargo run --features=nesting --example nesting nix develop .#ci -c cargo run --features=nesting --example clap nix develop .#ci -c cargo test + + - name: Test in no std + run: | + cd no-std-examples + nix develop .#no-std -c cargo run --features=box --bin no-std-box + nix develop .#no-std -c cargo run --features=option --bin no-std-option diff --git a/.gitignore b/.gitignore index 99412d3..cb76358 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ Cargo.lock .envrc .direnv/ struct-patch/examples +no-std-examples/target diff --git a/Cargo.toml b/Cargo.toml index 3f43d31..ed16547 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "lib", "derive", ] +exclude = [ "no-std-examples" ] [workspace.package] authors = ["Antonio Yang "] diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 3be2b96..c7b6853 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -52,4 +52,3 @@ fn get_lit(attr_name: String, meta: &ParseNestedMeta) -> syn::Result>(); #[cfg(feature = "nesting")] let original_field_name_empty_values = fields .iter() - .filter(|f| !f.retyped && !f.nesting ) + .filter(|f| !f.retyped && !f.nesting) .filter_map(|f| f.empty_value.as_ref()) .collect::>(); @@ -824,11 +823,12 @@ impl Field { EMPTY_VALUE => { // #[patch(empty_value = ...)] if empty_value.is_some() { - return Err(meta - .error("The empty value is already set, we can't defined more than once")); + return Err(meta.error( + "The empty value is already set, we can't defined more than once", + )); } if let Some(lit) = crate::get_lit(path, &meta)? { - empty_value= Some(lit); + empty_value = Some(lit); } else { return Err(meta .error("empty_value needs a clear value to define what is empty")); diff --git a/flake.lock b/flake.lock index bac6063..4235ae2 100644 --- a/flake.lock +++ b/flake.lock @@ -90,11 +90,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1764705007, - "narHash": "sha256-k6RHT5retgMWRyKenUQ+USSBbi3Yx0b029Dv2YRoXD8=", + "lastModified": 1776830795, + "narHash": "sha256-Gg5hJkg5jCRpgqnWrMRsv91BaPSD4i30VIz2VO2ojkI=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d55711bd994f85296d0d6d204a8badc8d1002b6d", + "rev": "f0992d330153d25ef88978c0cbecdb9c707b6f53", "type": "github" }, "original": { @@ -151,11 +151,11 @@ "nixpkgs": "nixpkgs_4" }, "locked": { - "lastModified": 1764729618, - "narHash": "sha256-z4RA80HCWv2los1KD346c+PwNPzMl79qgl7bCVgz8X0=", + "lastModified": 1776827647, + "narHash": "sha256-sYixYhp5V8jCajO8TRorE4fzs7IkL4MZdfLTKgkPQBk=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "52764074a85145d5001bf0aa30cb71936e9ad5b8", + "rev": "40e6ccc06e1245a4837cbbd6bdda64e21cc67379", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 4a05c95..6b56f0a 100644 --- a/flake.nix +++ b/flake.nix @@ -24,15 +24,28 @@ ''; updateDependencyScript = pkgs.writeShellScriptBin "update-dependency" '' dr ./Cargo.toml - if [ -f "Cargo.toml.old" ]; then - rm Cargo.toml.old + + cd no-std-examples + dr ./Cargo.toml + + if [[ -f "Cargo.toml.old" || -f "no-std-examples/Cargo.toml.old" ]]; then + rm -f Cargo.toml.old + rm -f no-std-examples/Cargo.toml.old exit 1 fi ''; in with pkgs; { - devShells = { + devShells = let + noStdRust = rust-bin.stable.latest.default.override { + targets = [ + "thumbv7m-none-eabi" + ]; + extensions = [ "rust-src" "llvm-tools-preview" ]; + }; + in + { default = mkShell { buildInputs = [ rust-bin.stable.latest.minimal @@ -52,6 +65,13 @@ updateDependencyScript ]; }; + + no-std = mkShell { + buildInputs = [ + noStdRust + qemu + ]; + }; }; } ); diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 286a605..f5a99a4 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -34,11 +34,13 @@ merge = [ "struct-patch-derive/merge" ] +alloc = [] std = ["box", "option"] -box = [] +box = ["alloc"] option = [] nesting = [ "struct-patch-derive/nesting" ] none_as_default = ["option"] keep_none = ["option"] + diff --git a/lib/src/box.rs b/lib/src/box.rs new file mode 100644 index 0000000..59465b3 --- /dev/null +++ b/lib/src/box.rs @@ -0,0 +1,61 @@ +#![cfg(feature = "box")] +use crate::Patch; + +extern crate alloc; +use alloc::boxed::Box; + +impl Patch> for T +where + T: Patch

, +{ + fn apply(&mut self, patch: Box

) { + self.apply(*patch); + } + + fn into_patch(self) -> Box

{ + Box::new(self.into_patch()) + } + + fn into_patch_by_diff(self, previous_struct: Self) -> Box

{ + Box::new(self.into_patch_by_diff(previous_struct)) + } + + fn new_empty_patch() -> Box

{ + Box::new(T::new_empty_patch()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate as struct_patch; + use crate::Patch; + use alloc::string::String; + + #[test] + fn test_patch_box_simple() { + #[derive(Patch, Debug, PartialEq)] + struct Item { + field: u32, + other: String, + } + + let mut item = Item { + field: 1, + other: String::from("hello"), + }; + let patch = Box::new(ItemPatch { + field: None, + other: Some(String::from("bye")), + }); + + item.apply(patch); + assert_eq!( + item, + Item { + field: 1, + other: String::from("bye") + } + ); + } +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 14cba07..61df30e 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -73,19 +73,24 @@ //! item.apply(filler_2); //! assert_eq!(item.maybe_field_int, Some(7)); //! ``` -#![cfg_attr(not(any(test, feature = "box", feature = "option")), no_std)] +#![no_std] + +#[cfg(feature = "alloc")] +extern crate alloc; #[doc(hidden)] pub use struct_patch_derive::Filler; #[doc(hidden)] pub use struct_patch_derive::Patch; -#[cfg(any(feature = "box", feature = "option"))] -pub mod std; +pub mod r#box; +pub mod option; pub mod traits; pub use traits::*; #[cfg(test)] mod tests { + extern crate alloc; + use alloc::string::String; use serde::Deserialize; #[cfg(feature = "merge")] use struct_patch::Merge; @@ -486,7 +491,7 @@ mod tests { b: B, } - let patches = vec![ + let patches = alloc::vec![ APatch { a: Some(1), b: Some(BPatch { diff --git a/lib/src/option.rs b/lib/src/option.rs new file mode 100644 index 0000000..e0250c2 --- /dev/null +++ b/lib/src/option.rs @@ -0,0 +1,283 @@ +#![cfg(feature = "option")] + +#[cfg(feature = "merge")] +use crate::Merge; +use crate::Patch; + +/// Patch implementation for Option +/// This implementation is used to apply a patch to an optional field +/// The default behavior when patching on `None`, will use the `From` trait to convert the patch to +/// the struct type. +/// Else, +/// The feature `none_as_default`, will patch on default instance when patching on `None`. +/// The feature `keep_none` will keep none when patching on none. +#[cfg(all(not(feature = "keep_none"), not(feature = "none_as_default")))] +impl Patch> for Option +where + T: Patch

+ From

, +{ + fn apply(&mut self, patch: Option

) { + if let Some(patch) = patch { + if let Some(self_) = self { + self_.apply(patch); + } else { + *self = Some(patch.into()); + } + } else { + *self = None; + } + } + + fn into_patch(self) -> Option

{ + self.map(|x| x.into_patch()) + } + + fn into_patch_by_diff(self, previous_struct: Self) -> Option

{ + match (self, previous_struct) { + (Some(self_), Some(previous_struct_)) => { + Some(self_.into_patch_by_diff(previous_struct_)) + } + (Some(self_), None) => Some(self_.into_patch()), + (None, _) => None, + } + } + + fn new_empty_patch() -> Option

{ + Some(T::new_empty_patch()) + } +} + +#[cfg(feature = "keep_none")] +impl Patch> for Option +where + T: Patch

, +{ + fn apply(&mut self, patch: Option

) { + if let Some(patch) = patch { + if let Some(self_) = self { + self_.apply(patch); + return; + } + } + *self = None; + } + + fn into_patch(self) -> Option

{ + self.map(|x| x.into_patch()) + } + + fn into_patch_by_diff(self, previous_struct: Self) -> Option

{ + match (self, previous_struct) { + (Some(self_), Some(previous_struct_)) => { + Some(self_.into_patch_by_diff(previous_struct_)) + } + (Some(self_), None) => Some(self_.into_patch()), + (None, _) => None, + } + } + + fn new_empty_patch() -> Option

{ + Some(T::new_empty_patch()) + } +} +#[cfg(feature = "none_as_default")] +impl Patch> for Option +where + T: Patch

+ Default, +{ + fn apply(&mut self, patch: Option

) { + if let Some(patch) = patch { + if let Some(self_) = self { + self_.apply(patch); + } else { + let mut instance = T::default(); + instance.apply(patch); + *self = Some(instance); + } + } else { + *self = None; + } + } + + fn into_patch(self) -> Option

{ + self.map(|x| x.into_patch()) + } + + fn into_patch_by_diff(self, previous_struct: Self) -> Option

{ + match (self, previous_struct) { + (Some(self_), Some(previous_struct_)) => { + Some(self_.into_patch_by_diff(previous_struct_)) + } + (Some(self_), None) => Some(self_.into_patch()), + (None, _) => None, + } + } + + fn new_empty_patch() -> Option

{ + Some(T::new_empty_patch()) + } +} + +#[cfg(feature = "merge")] +impl Merge for Option +where + T: Merge, +{ + fn merge(self, other: Self) -> Self { + if let Some(other) = other { + let mut self_ = self; + if let Some(self_) = self_.take() { + Some(self_.merge(other)) + } else { + Some(other) + } + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + extern crate alloc; + use crate as struct_patch; + use crate::Patch; + use alloc::string::String; + + #[test] + fn test_patch_option() { + #[derive(Patch, Debug, PartialEq)] + struct Item { + field: u32, + other: String, + } + + impl From for Item { + fn from(patch: ItemPatch) -> Self { + Item { + field: patch.field.unwrap_or_default(), + other: patch.other.unwrap_or_default(), + } + } + } + + let mut item = Some(Item { + field: 1, + other: String::from("hello"), + }); + let patch = Some(ItemPatch { + field: None, + other: Some(String::from("bye")), + }); + + item.apply(patch); + assert_eq!( + item, + Some(Item { + field: 1, + other: String::from("bye") + }) + ); + } + + /// Tests for nested optional fields + /// See https://stackoverflow.com/questions/44331037/how-can-i-distinguish-between-a-deserialized-field-that-is-missing-and-one-that + /// and https://github.com/serde-rs/serde/issues/1042 + /// To understand how to manage optional fields in patch with serde + mod nested { + use super::*; + use serde::Deserialize; + use serde::Deserializer; + + #[derive(PartialEq, Debug, Patch, Deserialize)] + #[patch(attribute(derive(PartialEq, Debug, Deserialize)))] + struct B { + c: u32, + d: u32, + } + + #[derive(PartialEq, Debug, Patch, Deserialize)] + #[patch(attribute(derive(PartialEq, Debug, Deserialize)))] + struct A { + #[patch( + name = "Option", + attribute(serde(deserialize_with = "deserialize_optional_field", default)) + )] + b: Option, + } + + impl From for B { + fn from(patch: BPatch) -> Self { + B { + c: patch.c.unwrap_or_default(), + d: patch.d.unwrap_or_default(), + } + } + } + + fn deserialize_optional_field<'de, T, D>( + deserializer: D, + ) -> Result>, D::Error> + where + D: Deserializer<'de>, + T: Deserialize<'de>, + { + Ok(Some(Option::deserialize(deserializer)?)) + } + + #[test] + fn test_optional_nested_present() { + let mut a = A { + b: Some(B { c: 0, d: 0 }), + }; + let data = r#"{ "b": { "c": 1 } }"#; + let patch: APatch = serde_json::from_str(data).unwrap(); + assert_eq!( + patch, + APatch { + b: Some(Some(BPatch { + c: Some(1), + d: None + })) + } + ); + a.apply(patch); + assert_eq!( + a, + A { + b: Some(B { c: 1, d: 0 }) + } + ); + } + + #[test] + fn test_optional_nested_absent() { + let mut a = A { + b: Some(B { c: 0, d: 0 }), + }; + let data = r#"{ }"#; + let patch: APatch = serde_json::from_str(data).unwrap(); + assert_eq!(patch, APatch { b: None }); + a.apply(patch); + assert_eq!( + a, + A { + b: Some(B { c: 0, d: 0 }) + } + ); + } + + #[test] + fn test_optional_nested_null() { + let mut a = A { + b: Some(B { c: 0, d: 0 }), + }; + let data = r#"{ "b": null }"#; + let patch: APatch = serde_json::from_str(data).unwrap(); + assert_eq!(patch, APatch { b: Some(None) }); + a.apply(patch); + assert_eq!(a, A { b: None }); + } + } +} diff --git a/lib/src/std.rs b/lib/src/std.rs deleted file mode 100644 index 10298bf..0000000 --- a/lib/src/std.rs +++ /dev/null @@ -1,342 +0,0 @@ -#[cfg(all(feature = "merge", feature = "option"))] -use crate::Merge; -#[cfg(any(feature = "box", feature = "option"))] -use crate::Patch; -#[cfg(feature = "box")] -use std::boxed::Box; - -#[cfg(feature = "box")] -impl Patch> for T -where - T: Patch

, -{ - fn apply(&mut self, patch: Box

) { - self.apply(*patch); - } - - fn into_patch(self) -> Box

{ - Box::new(self.into_patch()) - } - - fn into_patch_by_diff(self, previous_struct: Self) -> Box

{ - Box::new(self.into_patch_by_diff(previous_struct)) - } - - fn new_empty_patch() -> Box

{ - Box::new(T::new_empty_patch()) - } -} - -#[cfg(feature = "option")] -/// Patch implementation for Option -/// This implementation is used to apply a patch to an optional field -/// The default behavior when patching on `None`, will use the `From` trait to convert the patch to -/// the struct type. -/// Else, -/// The feature `none_as_default`, will patch on default instance when patching on `None`. -/// The feature `keep_none` will keep none when patching on none. -#[cfg(all(not(feature = "keep_none"), not(feature = "none_as_default")))] -impl Patch> for Option -where - T: Patch

+ From

, -{ - fn apply(&mut self, patch: Option

) { - if let Some(patch) = patch { - if let Some(self_) = self { - self_.apply(patch); - } else { - *self = Some(patch.into()); - } - } else { - *self = None; - } - } - - fn into_patch(self) -> Option

{ - self.map(|x| x.into_patch()) - } - - fn into_patch_by_diff(self, previous_struct: Self) -> Option

{ - match (self, previous_struct) { - (Some(self_), Some(previous_struct_)) => { - Some(self_.into_patch_by_diff(previous_struct_)) - } - (Some(self_), None) => Some(self_.into_patch()), - (None, _) => None, - } - } - - fn new_empty_patch() -> Option

{ - Some(T::new_empty_patch()) - } -} -#[cfg(feature = "keep_none")] -impl Patch> for Option -where - T: Patch

, -{ - fn apply(&mut self, patch: Option

) { - if let Some(patch) = patch { - if let Some(self_) = self { - self_.apply(patch); - return; - } - } - *self = None; - } - - fn into_patch(self) -> Option

{ - self.map(|x| x.into_patch()) - } - - fn into_patch_by_diff(self, previous_struct: Self) -> Option

{ - match (self, previous_struct) { - (Some(self_), Some(previous_struct_)) => { - Some(self_.into_patch_by_diff(previous_struct_)) - } - (Some(self_), None) => Some(self_.into_patch()), - (None, _) => None, - } - } - - fn new_empty_patch() -> Option

{ - Some(T::new_empty_patch()) - } -} -#[cfg(feature = "none_as_default")] -impl Patch> for Option -where - T: Patch

+ Default, -{ - fn apply(&mut self, patch: Option

) { - if let Some(patch) = patch { - if let Some(self_) = self { - self_.apply(patch); - } else { - let mut instance = T::default(); - instance.apply(patch); - *self = Some(instance); - } - } else { - *self = None; - } - } - - fn into_patch(self) -> Option

{ - self.map(|x| x.into_patch()) - } - - fn into_patch_by_diff(self, previous_struct: Self) -> Option

{ - match (self, previous_struct) { - (Some(self_), Some(previous_struct_)) => { - Some(self_.into_patch_by_diff(previous_struct_)) - } - (Some(self_), None) => Some(self_.into_patch()), - (None, _) => None, - } - } - - fn new_empty_patch() -> Option

{ - Some(T::new_empty_patch()) - } -} - -#[cfg(all(feature = "option", feature = "merge"))] -impl Merge for Option -where - T: Merge, -{ - fn merge(self, other: Self) -> Self { - if let Some(other) = other { - let mut self_ = self; - if let Some(self_) = self_.take() { - Some(self_.merge(other)) - } else { - Some(other) - } - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use crate as struct_patch; - use crate::Patch; - - // Tests for Patch> implementation - #[cfg(feature = "box")] - mod patch_box { - use super::*; - - #[test] - fn test_patch_box_simple() { - #[derive(Patch, Debug, PartialEq)] - struct Item { - field: u32, - other: String, - } - - let mut item = Item { - field: 1, - other: String::from("hello"), - }; - let patch = Box::new(ItemPatch { - field: None, - other: Some(String::from("bye")), - }); - - item.apply(patch); - assert_eq!( - item, - Item { - field: 1, - other: String::from("bye") - } - ); - } - } - - // Test for Patch> implementation - #[cfg(feature = "option")] - mod patch_option { - use super::*; - - #[test] - fn test_patch_option() { - #[derive(Patch, Debug, PartialEq)] - struct Item { - field: u32, - other: String, - } - - impl From for Item { - fn from(patch: ItemPatch) -> Self { - Item { - field: patch.field.unwrap_or_default(), - other: patch.other.unwrap_or_default(), - } - } - } - - let mut item = Some(Item { - field: 1, - other: String::from("hello"), - }); - let patch = Some(ItemPatch { - field: None, - other: Some(String::from("bye")), - }); - - item.apply(patch); - assert_eq!( - item, - Some(Item { - field: 1, - other: String::from("bye") - }) - ); - } - - /// Tests for nested optional fields - /// See https://stackoverflow.com/questions/44331037/how-can-i-distinguish-between-a-deserialized-field-that-is-missing-and-one-that - /// and https://github.com/serde-rs/serde/issues/1042 - /// To understand how to manage optional fields in patch with serde - mod nested { - use super::*; - use serde::Deserialize; - use serde::Deserializer; - - #[derive(PartialEq, Debug, Patch, Deserialize)] - #[patch(attribute(derive(PartialEq, Debug, Deserialize)))] - struct B { - c: u32, - d: u32, - } - - #[derive(PartialEq, Debug, Patch, Deserialize)] - #[patch(attribute(derive(PartialEq, Debug, Deserialize)))] - struct A { - #[patch( - name = "Option", - attribute(serde(deserialize_with = "deserialize_optional_field", default)) - )] - b: Option, - } - - impl From for B { - fn from(patch: BPatch) -> Self { - B { - c: patch.c.unwrap_or_default(), - d: patch.d.unwrap_or_default(), - } - } - } - - fn deserialize_optional_field<'de, T, D>( - deserializer: D, - ) -> Result>, D::Error> - where - D: Deserializer<'de>, - T: Deserialize<'de>, - { - Ok(Some(Option::deserialize(deserializer)?)) - } - - #[test] - fn test_optional_nested_present() { - let mut a = A { - b: Some(B { c: 0, d: 0 }), - }; - let data = r#"{ "b": { "c": 1 } }"#; - let patch: APatch = serde_json::from_str(data).unwrap(); - assert_eq!( - patch, - APatch { - b: Some(Some(BPatch { - c: Some(1), - d: None - })) - } - ); - a.apply(patch); - assert_eq!( - a, - A { - b: Some(B { c: 1, d: 0 }) - } - ); - } - - #[test] - fn test_optional_nested_absent() { - let mut a = A { - b: Some(B { c: 0, d: 0 }), - }; - let data = r#"{ }"#; - let patch: APatch = serde_json::from_str(data).unwrap(); - assert_eq!(patch, APatch { b: None }); - a.apply(patch); - assert_eq!( - a, - A { - b: Some(B { c: 0, d: 0 }) - } - ); - } - - #[test] - fn test_optional_nested_null() { - let mut a = A { - b: Some(B { c: 0, d: 0 }), - }; - let data = r#"{ "b": null }"#; - let patch: APatch = serde_json::from_str(data).unwrap(); - assert_eq!(patch, APatch { b: Some(None) }); - a.apply(patch); - assert_eq!(a, A { b: None }); - } - } - } -} diff --git a/no-std-examples/.cargo/config.toml b/no-std-examples/.cargo/config.toml new file mode 100644 index 0000000..26988c6 --- /dev/null +++ b/no-std-examples/.cargo/config.toml @@ -0,0 +1,6 @@ +[build] +target = "thumbv7m-none-eabi" + +[target.thumbv7m-none-eabi] +runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" +rustflags = ["-C", "link-arg=-Tlink.x"] diff --git a/no-std-examples/Cargo.toml b/no-std-examples/Cargo.toml new file mode 100644 index 0000000..e5eaa2c --- /dev/null +++ b/no-std-examples/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "no-std-examples" +authors = ["Antonio Yang "] +version = "0.10.5" +edition = "2021" +license = "MIT" + +[dependencies] +struct-patch = { path = "../lib" } +linked_list_allocator = "0.10" +cortex-m-rt = "0.7" +cortex-m-semihosting = "0.5" +panic-semihosting = "0.6" + +[features] +default = [] +box = ["struct-patch/box"] +option = ["struct-patch/option"] + +[[bin]] +name = "no-std-box" +path = "src/box.rs" + +[[bin]] +name = "no-std-option" +path = "src/option.rs" diff --git a/no-std-examples/build.rs b/no-std-examples/build.rs new file mode 100644 index 0000000..990f665 --- /dev/null +++ b/no-std-examples/build.rs @@ -0,0 +1,11 @@ +use std::env; +use std::fs; +use std::path::PathBuf; + +fn main() { + let out = PathBuf::from(env::var("OUT_DIR").unwrap()); + let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + fs::copy(manifest.join("memory.x"), out.join("memory.x")).unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/no-std-examples/memory.x b/no-std-examples/memory.x new file mode 100644 index 0000000..77434d0 --- /dev/null +++ b/no-std-examples/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 256K + RAM : ORIGIN = 0x20000000, LENGTH = 64K +} diff --git a/no-std-examples/src/box.rs b/no-std-examples/src/box.rs new file mode 100644 index 0000000..0cd149f --- /dev/null +++ b/no-std-examples/src/box.rs @@ -0,0 +1,63 @@ +#![no_std] +#![no_main] +#![allow(clippy::empty_loop)] +#![allow(unused_imports)] +#![allow(dead_code)] +extern crate alloc; + +use core::mem::MaybeUninit; +use cortex_m_rt::entry; +use cortex_m_semihosting::debug; +use cortex_m_semihosting::hprintln; +use linked_list_allocator::LockedHeap; +use panic_semihosting as _; + +#[global_allocator] +static ALLOCATOR: LockedHeap = LockedHeap::empty(); + +const HEAP_SIZE: usize = 1024; +static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> = MaybeUninit::uninit(); + +#[entry] +#[cfg(feature = "box")] +fn main() -> ! { + unsafe { + ALLOCATOR + .lock() + .init(core::ptr::addr_of_mut!(HEAP) as *mut u8, HEAP_SIZE); + } + + use struct_patch::Patch; + + #[derive(Patch)] + struct Item { + field: u32, + patched: bool, + } + + let mut item = Item { + field: 1, + patched: false, + }; + let patch = alloc::boxed::Box::new(ItemPatch { + field: None, + patched: Some(true), + }); + + item.apply(patch); + + if item.patched { + hprintln!("struct-patch success"); + debug::exit(debug::EXIT_SUCCESS); + } else { + hprintln!("!! struct-patch failed"); + debug::exit(debug::EXIT_FAILURE); + } + loop {} +} +#[entry] +#[cfg(not(feature = "box"))] +fn main() -> ! { + debug::exit(debug::EXIT_FAILURE); + loop {} +} diff --git a/no-std-examples/src/option.rs b/no-std-examples/src/option.rs new file mode 100644 index 0000000..0655a88 --- /dev/null +++ b/no-std-examples/src/option.rs @@ -0,0 +1,74 @@ +#![no_std] +#![no_main] +#![allow(clippy::empty_loop)] +#![allow(unused_imports)] +#![allow(dead_code)] +extern crate alloc; + +use core::mem::MaybeUninit; +use cortex_m_rt::entry; +use cortex_m_semihosting::debug; +use cortex_m_semihosting::hprintln; +use linked_list_allocator::LockedHeap; +use panic_semihosting as _; + +#[global_allocator] +static ALLOCATOR: LockedHeap = LockedHeap::empty(); + +const HEAP_SIZE: usize = 1024; +static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> = MaybeUninit::uninit(); + +#[entry] +#[cfg(feature = "option")] +fn main() -> ! { + unsafe { + ALLOCATOR + .lock() + .init(core::ptr::addr_of_mut!(HEAP) as *mut u8, HEAP_SIZE); + } + + use struct_patch::Patch; + + #[derive(Patch)] + struct Item { + field: u32, + patched: bool, + } + + impl From for Item { + fn from(patch: ItemPatch) -> Self { + Item { + field: patch.field.unwrap_or_default(), + patched: patch.patched.unwrap_or_default(), + } + } + } + + let mut item = Some(Item { + field: 1, + patched: false, + }); + + let patch = Some(ItemPatch { + field: None, + patched: Some(true), + }); + + item.apply(patch); + + if item.expect("a demo should be correct").patched { + hprintln!("struct-patch success"); + debug::exit(debug::EXIT_SUCCESS); + } else { + hprintln!("!! struct-patch failed"); + debug::exit(debug::EXIT_FAILURE); + } + + loop {} +} +#[entry] +#[cfg(not(feature = "option"))] +fn main() -> ! { + debug::exit(debug::EXIT_FAILURE); + loop {} +}