├── .gitignore ├── .vscode └── settings.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE.md ├── LICENSE-MIT.md ├── README.md ├── crates ├── constructivism_macro │ ├── Cargo.toml │ ├── LICENSE-APACHE.md │ ├── LICENSE-MIT.md │ ├── README.md │ └── src │ │ └── lib.rs ├── constructivism_macro_gen │ ├── Cargo.toml │ ├── LICENSE-APACHE.md │ ├── LICENSE-MIT.md │ ├── README.md │ └── src │ │ ├── constructivism_macro.include │ │ └── lib.rs └── constructivist │ ├── Cargo.toml │ ├── LICENSE-APACHE.md │ ├── LICENSE-MIT.md │ ├── README.md │ └── src │ ├── context.rs │ ├── core.include │ ├── derive.rs │ ├── exts.rs │ ├── genlib.rs │ ├── lib.rs │ ├── proc.rs │ └── throw.rs ├── examples ├── props.rs ├── stranger │ ├── Cargo.toml │ └── src │ │ └── main.rs └── tutorial.rs └── src ├── core.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "genlib", 4 | "getset", 5 | "impls", 6 | "rustfmt" 7 | ] 8 | } -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "constructivism" 7 | version = "0.3.0" 8 | dependencies = [ 9 | "constructivism_macro", 10 | "tuple_utils", 11 | ] 12 | 13 | [[package]] 14 | name = "constructivism_macro" 15 | version = "0.3.0" 16 | dependencies = [ 17 | "constructivist", 18 | "proc-macro2", 19 | "quote", 20 | "syn", 21 | "toml", 22 | ] 23 | 24 | [[package]] 25 | name = "constructivism_macro_gen" 26 | version = "0.3.0" 27 | dependencies = [ 28 | "quote", 29 | "syn", 30 | ] 31 | 32 | [[package]] 33 | name = "constructivist" 34 | version = "0.3.0" 35 | dependencies = [ 36 | "constructivism_macro_gen", 37 | "proc-macro2", 38 | "quote", 39 | "syn", 40 | "toml", 41 | ] 42 | 43 | [[package]] 44 | name = "equivalent" 45 | version = "1.0.1" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 48 | 49 | [[package]] 50 | name = "hashbrown" 51 | version = "0.14.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" 54 | 55 | [[package]] 56 | name = "indexmap" 57 | version = "2.0.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" 60 | dependencies = [ 61 | "equivalent", 62 | "hashbrown", 63 | ] 64 | 65 | [[package]] 66 | name = "memchr" 67 | version = "2.6.3" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" 70 | 71 | [[package]] 72 | name = "proc-macro2" 73 | version = "1.0.67" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" 76 | dependencies = [ 77 | "unicode-ident", 78 | ] 79 | 80 | [[package]] 81 | name = "quote" 82 | version = "1.0.33" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 85 | dependencies = [ 86 | "proc-macro2", 87 | ] 88 | 89 | [[package]] 90 | name = "serde" 91 | version = "1.0.188" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" 94 | dependencies = [ 95 | "serde_derive", 96 | ] 97 | 98 | [[package]] 99 | name = "serde_derive" 100 | version = "1.0.188" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" 103 | dependencies = [ 104 | "proc-macro2", 105 | "quote", 106 | "syn", 107 | ] 108 | 109 | [[package]] 110 | name = "serde_spanned" 111 | version = "0.6.3" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" 114 | dependencies = [ 115 | "serde", 116 | ] 117 | 118 | [[package]] 119 | name = "stranger" 120 | version = "0.1.0" 121 | 122 | [[package]] 123 | name = "syn" 124 | version = "2.0.37" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" 127 | dependencies = [ 128 | "proc-macro2", 129 | "quote", 130 | "unicode-ident", 131 | ] 132 | 133 | [[package]] 134 | name = "toml" 135 | version = "0.7.8" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" 138 | dependencies = [ 139 | "serde", 140 | "serde_spanned", 141 | "toml_datetime", 142 | "toml_edit", 143 | ] 144 | 145 | [[package]] 146 | name = "toml_datetime" 147 | version = "0.6.3" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" 150 | dependencies = [ 151 | "serde", 152 | ] 153 | 154 | [[package]] 155 | name = "toml_edit" 156 | version = "0.19.15" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" 159 | dependencies = [ 160 | "indexmap", 161 | "serde", 162 | "serde_spanned", 163 | "toml_datetime", 164 | "winnow", 165 | ] 166 | 167 | [[package]] 168 | name = "tuple_utils" 169 | version = "0.4.0" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "cffaaf9392ef73cd30828797152476aaa2fa37a17856934fa63d4843f34290e9" 172 | 173 | [[package]] 174 | name = "unicode-ident" 175 | version = "1.0.11" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 178 | 179 | [[package]] 180 | name = "winnow" 181 | version = "0.5.15" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" 184 | dependencies = [ 185 | "memchr", 186 | ] 187 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "constructivism" 3 | version = "0.3.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | readme = "README.md" 7 | description = "Simplify the construction of structured data." 8 | homepage = "https://github.com/polako-rs/constructivism" 9 | repository = "https://github.com/polako-rs/constructivism" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [workspace] 14 | members = [ 15 | "crates/constructivism_macro", 16 | "crates/constructivism_macro", 17 | "crates/constructivism_macro_gen", 18 | 19 | "examples/stranger", 20 | ] 21 | 22 | [dependencies] 23 | constructivism_macro = { path = "crates/constructivism_macro", version = "0.3.0"} 24 | tuple_utils = "0.4.0" 25 | -------------------------------------------------------------------------------- /LICENSE-APACHE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /LICENSE-MIT.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | `constructivism` is a Rust sample-library designed to simplify the construction of structured data by defining and manipulating sequences of Constructs. This README provides an overview of how to use `constructivism` and how it can be inlined into you project using `constructivist` library. 4 | 5 | ## Installation 6 | 7 | To use Constructivism in your Rust project, add it as a dependency in your `Cargo.toml` file: 8 | 9 | ```toml 10 | [dependencies] 11 | constructivism = "0.0.2" 12 | ``` 13 | 14 | Or let the cargo do the stuff: 15 | 16 | ```bash 17 | cargo add constructivism 18 | ``` 19 | 20 | Constructivism can be inlined into you library as for example `your_library_constructivism` within `constructivist` crate. See [instructions](./crates/constructivist). 21 | 22 | ## Guide 23 | 24 | See also [examples/tutorial.rs](examples/tutorial.rs) 25 | 26 | ### Getting Started 27 | 28 | Usually you start with 29 | 30 | ```rust 31 | use constructivism::*; 32 | ``` 33 | 34 | ### Constructs and Sequences 35 | 36 | 1.1. **Constructs**: Constructivism revolves around the concept of Constructs. You can derive construct like this: 37 | 38 | ```rust 39 | #[derive(Construct)] 40 | pub struct Node { 41 | hidden: bool, 42 | position: (f32, f32), 43 | } 44 | ``` 45 | 46 | 1.2 **`construct!`**: You can use the `construct!` macro to create instances of Constructs. Please ***note*** the dots at the beginning of the each param, they are required and you will find this syntax quite useful. 47 | 48 | ```rust 49 | fn create_node() { 50 | let node = construct!(Node { 51 | .position: (10., 10.), 52 | .hidden: true 53 | }); 54 | assert_eq!(node.position.0, 10.); 55 | assert_eq!(node.hidden, true); 56 | } 57 | ``` 58 | 59 | 1.3 **Sequences**: A Construct can be declared only in front of another Construct. `constructivism` comes with only Nothing, () construct. The `Self -> Base` relation called Sequence in `constructivism`. You can omit the Sequence declaration, `Self -> Nothing` used in this case. If you want to derive Construct on the top of another meaningful Construct, you have to specify Sequence directly with `#[construct(/* Sequence */)]` attribute. 60 | 61 | ```rust 62 | #[derive(Construct)] 63 | #[construct(Rect -> Node)] 64 | pub struct Rect { 65 | size: (f32, f32), 66 | } 67 | ``` 68 | 69 | 1.4 **Constructing Sequences**: The Sequence for the Rect in example above becomes `Rect -> Node -> Nothing`. You can `construct!` the entire sequence within a single call: 70 | 71 | ```rust 72 | fn create_sequence() { 73 | let (rect, node /* nothing */) = construct!(Rect { 74 | .hidden, // You can write just `.hidden` instead of `.hidden: true` 75 | .position: (10., 10.), 76 | .size: (10., 10.), 77 | }); 78 | assert_eq!(rect.size.0, 10.); 79 | assert_eq!(node.position.1, 10.); 80 | assert_eq!(node.hidden, false); 81 | } 82 | ``` 83 | 84 | 1.5 **Params**: There are different kind of Params (the things you passing to `construct!(..)`): 85 | - Common: use `Default::default()` if not passed to `construct!(..)` 86 | - Default: use provided value if not passed to `construct!(..)` 87 | - Required: must be passed to `construct!(..)` 88 | - Skip: can't be passed to `construct!(..)`, use Default::default() or provided value 89 | You configure behavior using `#[param]` attribute when deriving: 90 | 91 | 92 | ```rust 93 | #[derive(Construct)] 94 | #[construct(Follow -> Node)] 95 | pub struct Follow { 96 | offset: (f32, f32), // Common, no #[param] 97 | 98 | #[param(required)] // Required 99 | target: Entity, 100 | 101 | #[param(default = Anchor::Center)] // Default 102 | anchor: Anchor, 103 | 104 | #[param(skip)] // Skip with Default::default() 105 | last_computed_distance: f32, 106 | 107 | #[param(skip = FollowState::None)] // Skip with provided value 108 | state: FollowState, 109 | } 110 | 111 | #[derive(PartialEq, Debug, Copy, Clone)] 112 | pub struct Entity; 113 | 114 | pub enum Anchor { 115 | Left, 116 | Center, 117 | Right, 118 | } 119 | 120 | pub enum FollowState { 121 | None, 122 | Initialized(f32) 123 | } 124 | ``` 125 | 126 | 1.6 **Passing params**: When passing params to `construct!(..)` you have to pass all required for Sequence params, or you will get the compilation error. You can omit non-required params. 127 | 128 | ```rust 129 | fn create_elements() { 130 | // omit everything, default param values will be used 131 | let (rect, node, /* nothing */) = construct!(Rect); 132 | assert_eq!(node.hidden, false); 133 | assert_eq!(rect.size.0, 0.); 134 | 135 | // you have to pass target to Follow, the rest can be omitted.. 136 | let (follow, node) = construct!(Follow { 137 | .target: Entity 138 | }); 139 | assert_eq!(follow.offset.0, 0.); 140 | assert_eq!(node.hidden, false); 141 | 142 | // ..or specified: 143 | let (follow, node) = construct!(Follow { 144 | .hidden, 145 | .target: Entity, 146 | .offset: (10., 10.), 147 | 148 | // last_computed_distance param is skipped, uncommenting 149 | // the next line will result in compilation error 150 | // error: no field `last_computed_distance` on type `&follow_construct::Params` 151 | 152 | // .last_computed_distance: 10. 153 | }); 154 | assert_eq!(follow.offset.0, 10.); 155 | assert_eq!(node.hidden, true); 156 | } 157 | ``` 158 | 159 | ### Design and Methods 160 | 161 | 2.1 **Designs and Methods**: Every Construct has its own Design. You can implement methods for a Construct's design: 162 | 163 | ```rust 164 | impl NodeDesign { 165 | pub fn move_to(&self, entity: Entity, position: (f32, f32)) { } 166 | } 167 | 168 | impl RectDesign { 169 | pub fn expand_to(&self, entity: Entity, size: (f32, f32)) { } 170 | } 171 | ``` 172 | 173 | 2.2 **Calling Methods**: You can call methods on a Construct's design. Method resolution follows the sequence order: 174 | 175 | ```rust 176 | fn use_design() { 177 | let rect_entity = Entity; 178 | design!(Rect).expand_to(rect_entity, (10., 10.)); 179 | design!(Rect).move_to(rect_entity, (10., 10.)); // move_to implemented for NodeDesign 180 | } 181 | ``` 182 | 183 | ### Segments 184 | 185 | 3.1 **Segments**: Segments allow you to define and insert segments into a Construct's sequence: 186 | 187 | ```rust 188 | #[derive(Segment)] 189 | pub struct Input { 190 | disabled: bool, 191 | } 192 | 193 | #[derive(Construct)] 194 | #[construct(Button -> Input -> Rect)] 195 | pub struct Button { 196 | pressed: bool 197 | } 198 | ``` 199 | 200 | 3.2 **Sequence with Segments**: The Sequence for Button becomes `Button -> Input -> Rect -> Node -> Nothing`. You can instance the entire sequence of a Construct containing segments within a single `construct!` call: 201 | 202 | ```rust 203 | fn create_button() { 204 | let (button, input, rect, node) = construct!(Button { 205 | .disabled: true 206 | }); 207 | assert_eq!(button.pressed, false); 208 | assert_eq!(input.disabled, true); 209 | assert_eq!(rect.size.0, 100.); 210 | assert_eq!(node.position.0, 0.); 211 | } 212 | ``` 213 | 214 | 3.3 **Segment Design**: Segment has its own Design as well. And the method call resolves within the Sequence order as well. Segment's designs has one generic parameter - the next segment/construct, so you have to respect it when implement Segment's Design: 215 | 216 | ```rust 217 | impl InputDesign { 218 | fn focus(&self, entity: Entity) { 219 | /* do the focus stuff */ 220 | } 221 | } 222 | 223 | fn focus_button() { 224 | let btn = Entity; 225 | design!(Button).focus(btn); 226 | } 227 | ``` 228 | 229 | ### Props 230 | 231 | 4.1 **Props**: By deriving Constructs or Segments you also get the ability to set and get properties on items with respect of Sequence: 232 | 233 | ```rust 234 | fn button_props() { 235 | let (mut button, mut input, mut rect, mut node) = construct!(Button); 236 | 237 | // You can access to props knowing only the top-level Construct 238 | let pos /* Prop */ = prop!(Button.position); 239 | let size /* Prop */ = prop!(Button.size); 240 | let disabled /* Prop */ = prop!(Button.disabled); 241 | let pressed /* Prop4.2 **Expand props**: If you have field with Construct type, you can access this fields props as well: 267 | 268 | ```rust 269 | #[derive(Construct, Default)] 270 | #[construct(Vec2 -> Nothing)] 271 | pub struct Vec2 { 272 | x: f32, 273 | y: f32, 274 | } 275 | 276 | #[derive(Construct)] 277 | #[construct(Node2d -> Nothing)] 278 | pub struct Node2d { 279 | #[prop(construct)] // You have to mark expandable props with #[prop(construct)] 280 | position: Vec2, 281 | } 282 | 283 | fn modify_position_x() { 284 | let mut node = construct!(Node2d); 285 | assert_eq!(node.position.x, 0.); 286 | assert_eq!(node.position.y, 0.); 287 | 288 | let x = prop!(Node2d.position.x); 289 | 290 | x.set(&mut node, 100.); 291 | assert_eq!(node.position.x, 100.); 292 | assert_eq!(node.position.y, 0.); 293 | } 294 | ``` 295 | 296 | ### Custom Constructors 297 | 298 | 5.1 **Custom Constructors**: Sometimes you may want to implement Construct for a foreign type or provide a custom constructor. You can use `derive_construct!` for this purpose: 299 | 300 | ```rust 301 | pub struct ProgressBar { 302 | min: f32, 303 | val: f32, 304 | max: f32, 305 | } 306 | 307 | 308 | impl ProgressBar { 309 | pub fn min(&self) -> f32 { 310 | self.min 311 | } 312 | pub fn set_min(&mut self, min: f32) { 313 | self.min = min; 314 | if self.max < min { 315 | self.max = min; 316 | } 317 | if self.val < min { 318 | self.val = min; 319 | } 320 | } 321 | pub fn max(&self) -> f32 { 322 | self.max 323 | } 324 | pub fn set_max(&mut self, max: f32) { 325 | self.max = max; 326 | if self.min > max { 327 | self.min = max; 328 | } 329 | if self.val > max { 330 | self.val = max; 331 | } 332 | } 333 | pub fn val(&self) -> f32 { 334 | self.val 335 | } 336 | pub fn set_val(&mut self, val: f32) { 337 | self.val = val.max(self.min).min(self.max) 338 | } 339 | } 340 | 341 | derive_construct! { 342 | // Sequence 343 | seq => ProgressBar -> Rect; 344 | 345 | // Constructor, all params with default values 346 | construct => (min: f32 = 0., max: f32 = 1., val: f32 = 0.) -> { 347 | if max < min { 348 | max = min; 349 | } 350 | val = val.min(max).max(min); 351 | Self { min, val, max } 352 | }; 353 | 354 | // Props using getters and setters 355 | props => { 356 | min: f32 = [min, set_min]; 357 | max: f32 = [max, set_max]; 358 | val: f32 = [val, set_val]; 359 | }; 360 | } 361 | ``` 362 | 363 | 5.2 **Using Custom Constructors**: The provided constructor will be called when creating instances: 364 | 365 | ```rust 366 | fn create_progress_bar() { 367 | let (pb, _, _) = construct!(ProgressBar { .val: 100. }); 368 | assert_eq!(pb.min, 0.); 369 | assert_eq!(pb.max, 1.); 370 | assert_eq!(pb.val, 1.); 371 | } 372 | ``` 373 | 374 | 5.3 **Custom Construct Props**: In the example above `derive_construct!` declares props using getters and setters. This setters and getters are called when you use `Prop::get` and `Prop::set` 375 | 376 | ```rust 377 | fn modify_progress_bar() { 378 | let (mut pb, _, _) = construct!(ProgressBar {}); 379 | let min = prop!(ProgressBar.min); 380 | let val = prop!(ProgressBar.val); 381 | let max = prop!(ProgressBar.max); 382 | 383 | assert_eq!(pb.val, 0.); 384 | 385 | val.set(&mut pb, 2.); 386 | assert_eq!(pb.val, 1.0); //because default for max = 1.0 387 | 388 | min.set(&mut pb, 5.); 389 | max.set(&mut pb, 10.); 390 | assert_eq!(pb.min, 5.); 391 | assert_eq!(pb.val, 5.); 392 | assert_eq!(pb.max, 10.); 393 | } 394 | ``` 395 | 396 | 5.4 **Deriving Segments**: You can derive Segments in a similar way: 397 | 398 | ```rust 399 | pub struct Range { 400 | min: f32, 401 | max: f32, 402 | val: f32, 403 | } 404 | derive_segment! { 405 | // use `seg` to provide type you want to derive Segment 406 | seg => Range; 407 | construct => (min: f32 = 0., max: f32 = 1., val: f32 = 0.) -> { 408 | if max < min { 409 | max = min; 410 | } 411 | val = val.min(max).max(min); 412 | Self { min, val, max } 413 | }; 414 | // Props using fields directly 415 | props => { 416 | min: f32 = value; 417 | max: f32 = value; 418 | val: f32 = value; 419 | }; 420 | } 421 | 422 | #[derive(Construct)] 423 | #[construct(Slider -> Range -> Rect)] 424 | pub struct Slider; 425 | 426 | fn create_slider() { 427 | let (slider, range, _, _) = construct!(Slider { 428 | .val: 10. 429 | }); 430 | assert_eq!(range.min, 0.0); 431 | assert_eq!(range.max, 1.0); 432 | assert_eq!(range.val, 1.0); 433 | } 434 | ``` 435 | 436 | 437 | ## Limitations 438 | 439 | - only public structs (or enums with `constructable!`) 440 | - no generics supported yet (looks very possible) 441 | - limited number of params for the whole inheritance tree (default version compiles with 16, tested with 64) 442 | - only static structs/enums (no lifetimes) 443 | 444 | ## Cost 445 | 446 | I didn't perform any stress-tests. It should run pretty fast: there is no heap allocations, only some deref calls per `construct!` per defined prop per depth level. Cold compilation time grows with number of params limit (1.5 mins for 64), but the size of the binary doesn't changes. 447 | 448 | 449 | ## Roadmap 450 | 451 | - [ ] add `#![forbid(missing_docs)]` to the root of each crate 452 | - [ ] docstring bypassing 453 | - [ ] generics 454 | - [ ] union params, so you can pas only one param from group. For example, Range could have `min`, `max`, `abs` and `rel` constructor params, and you can't pass `abs` and `rel` both. 455 | - [ ] nested construct inference (looks like possible): 456 | ```rust 457 | #[derive(Construct, Default)] 458 | pub struct Vec2 { 459 | x: f32, 460 | y: f32 461 | } 462 | #[derive(Construct)] 463 | pub struct Div { 464 | position: Vec2, 465 | size: Vec2, 466 | } 467 | fn step_inference() { 468 | let div = construct!(Div { 469 | position: {{ x: 23., y: 20. }}, 470 | size: {{ x: 23., y: 20. }} 471 | }) 472 | } 473 | ``` 474 | 475 | ## Contributing 476 | 477 | I welcome contributions to Constructivism! If you'd like to contribute or have any questions, please feel free to open an issue or submit a pull request. 478 | 479 | ## License 480 | 481 | The `constructivism` is dual-licensed under either: 482 | 483 | - MIT License ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) 484 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) 485 | 486 | This means you can select the license you prefer! 487 | This dual-licensing approach is the de-facto standard in the Rust ecosystem and there are [very good reasons](https://github.com/bevyengine/bevy/issues/2373) to include both. -------------------------------------------------------------------------------- /crates/constructivism_macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "constructivism_macro" 3 | version = "0.3.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | readme = "README.md" 7 | description = "Simplify the construction of structured data." 8 | homepage = "https://github.com/polako-rs/constructivism/tree/main/crates/constructivism_macro" 9 | repository = "https://github.com/polako-rs/constructivism" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | proc-macro2 = "1.0.66" 18 | quote = "1.0.33" 19 | syn = { version = "2.0.29", features = ["full"]} 20 | toml = "0.7.6" 21 | constructivist = { path = "../constructivist", version = "0.3.0"} 22 | -------------------------------------------------------------------------------- /crates/constructivism_macro/LICENSE-APACHE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /crates/constructivism_macro/LICENSE-MIT.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /crates/constructivism_macro/README.md: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | The crate exports [`constructivism`](https://crates.io/crates/constructivism) derives, `construct!`, `prop!`, `design!`, `derive_construct!` and `derive_segment!` macros. It is inlined into [`constructivist`](https://crates.io/crates/constructivist). 4 | 5 | ## License 6 | 7 | The `constructivism_macro` is dual-licensed under either: 8 | 9 | - MIT License ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) 10 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) 11 | 12 | This means you can select the license you prefer! 13 | This dual-licensing approach is the de-facto standard in the Rust ecosystem and there are [very good reasons](https://github.com/bevyengine/bevy/issues/2373) to include both. -------------------------------------------------------------------------------- /crates/constructivism_macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[proc_macro_derive(Construct, attributes(prop, param, construct))] 2 | pub fn construct_derive(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream { 3 | use ::constructivist::prelude::*; 4 | use ::syn::{parse_macro_input, DeriveInput}; 5 | let input = parse_macro_input!(input as DeriveInput); 6 | let constructable = match DeriveConstruct::from_derive(input) { 7 | Err(e) => return ::proc_macro::TokenStream::from(e.to_compile_error()), 8 | Ok(c) => c, 9 | }; 10 | let stream = match constructable.build(&Context::new("constructivism")) { 11 | Err(e) => return ::proc_macro::TokenStream::from(e.to_compile_error()), 12 | Ok(c) => c, 13 | }; 14 | ::proc_macro::TokenStream::from(stream) 15 | } 16 | 17 | #[proc_macro_derive(Segment, attributes(prop, param))] 18 | pub fn segment_derive(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream { 19 | use ::constructivist::prelude::*; 20 | use ::syn::{parse_macro_input, DeriveInput}; 21 | let input = parse_macro_input!(input as DeriveInput); 22 | type ConstructivismContext = Context; 23 | let ctx = ConstructivismContext::new("constructivism"); 24 | let constructable = match DeriveSegment::from_derive(input) { 25 | Err(e) => return ::proc_macro::TokenStream::from(e.to_compile_error()), 26 | Ok(c) => c, 27 | }; 28 | let stream = match constructable.build(&ctx) { 29 | Err(e) => return ::proc_macro::TokenStream::from(e.to_compile_error()), 30 | Ok(c) => c, 31 | }; 32 | ::proc_macro::TokenStream::from(stream) 33 | } 34 | 35 | #[proc_macro] 36 | pub fn derive_construct(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream { 37 | use ::constructivist::prelude::*; 38 | use ::syn::parse_macro_input; 39 | type ConstructivismContext = Context; 40 | let ctx = ConstructivismContext::new("constructivism"); 41 | let input = parse_macro_input!(input as DeriveConstruct); 42 | let stream = match input.build(&ctx) { 43 | Err(e) => return ::proc_macro::TokenStream::from(e.to_compile_error()), 44 | Ok(c) => c, 45 | }; 46 | ::proc_macro::TokenStream::from(stream) 47 | } 48 | 49 | #[proc_macro] 50 | pub fn derive_segment(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream { 51 | use ::constructivist::prelude::*; 52 | use ::syn::parse_macro_input; 53 | let input = parse_macro_input!(input as DeriveSegment); 54 | type ConstructivismContext = Context; 55 | let ctx = ConstructivismContext::new("constructivism"); 56 | let stream = match input.build(&ctx) { 57 | Err(e) => return ::proc_macro::TokenStream::from(e.to_compile_error()), 58 | Ok(c) => c, 59 | }; 60 | ::proc_macro::TokenStream::from(stream) 61 | } 62 | 63 | #[proc_macro] 64 | pub fn construct(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream { 65 | use ::constructivist::prelude::*; 66 | use ::syn::parse_macro_input; 67 | type ConstructivismValue = syn::Expr; 68 | type ConstructivismContext = Context; 69 | let cst = parse_macro_input!(input as Construct); 70 | ::proc_macro::TokenStream::from( 71 | match ::constructivist::proc::build( 72 | ConstructivismContext::new("constructivism"), 73 | move |ctx| cst.build(ctx), 74 | ) { 75 | Ok(r) => r, 76 | Err(e) => e.to_compile_error(), 77 | }, 78 | ) 79 | // let mut ctx = ConstructivismContext::new("constructivism"); 80 | // ::proc_macro::TokenStream::from(match cst.build(Ref::new(&mut ctx)) { 81 | // Ok(r) => r, 82 | // Err(e) => e.to_compile_error(), 83 | // }) 84 | } 85 | #[proc_macro] 86 | pub fn prop(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream { 87 | use ::constructivist::prelude::*; 88 | use ::syn::parse_macro_input; 89 | let cst = parse_macro_input!(input as Prop); 90 | type ConstructivismContext = Context; 91 | let ctx = ConstructivismContext::new("constructivism"); 92 | ::proc_macro::TokenStream::from(match cst.build(&ctx) { 93 | Ok(r) => r, 94 | Err(e) => e.to_compile_error(), 95 | }) 96 | } 97 | 98 | #[proc_macro] 99 | pub fn implement_constructivism_core( 100 | input: ::proc_macro::TokenStream, 101 | ) -> ::proc_macro::TokenStream { 102 | use ::constructivist::prelude::*; 103 | use ::syn::parse_macro_input; 104 | let limits = parse_macro_input!(input as genlib::ConstructivistLimits); 105 | ::proc_macro::TokenStream::from(genlib::implement_constructivism_core(limits.max_fields)) 106 | } 107 | 108 | #[proc_macro] 109 | pub fn implement_constructivism(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream { 110 | use ::constructivist::prelude::*; 111 | use ::syn::parse_macro_input; 112 | let limits = parse_macro_input!(input as genlib::ConstructivistLimits); 113 | ::proc_macro::TokenStream::from(genlib::implement_constructivism(limits.max_fields)) 114 | } 115 | -------------------------------------------------------------------------------- /crates/constructivism_macro_gen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "constructivism_macro_gen" 3 | version = "0.3.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | readme = "README.md" 7 | description = "Macro helper for constructivist" 8 | homepage = "https://github.com/polako-rs/constructivism/tree/main/crates/constructivism_macro_gen" 9 | repository = "https://github.com/polako-rs/constructivism" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | quote = "1.0.33" 16 | syn = "2.0.37" 17 | -------------------------------------------------------------------------------- /crates/constructivism_macro_gen/LICENSE-APACHE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /crates/constructivism_macro_gen/LICENSE-MIT.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /crates/constructivism_macro_gen/README.md: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | This is the helper macro crate for [`constructivist`](https://crates.io/crates/constructivist). It allows to generate proc-macro for `your_lib_constructivism`. 4 | 5 | ## License 6 | 7 | The `constructivism_macro_gen` is dual-licensed under either: 8 | 9 | - MIT License ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) 10 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) 11 | 12 | This means you can select the license you prefer! 13 | This dual-licensing approach is the de-facto standard in the Rust ecosystem and there are [very good reasons](https://github.com/bevyengine/bevy/issues/2373) to include both. -------------------------------------------------------------------------------- /crates/constructivism_macro_gen/src/constructivism_macro.include: -------------------------------------------------------------------------------- 1 | ../../constructivism_macro/src/lib.rs -------------------------------------------------------------------------------- /crates/constructivism_macro_gen/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use proc_macro::TokenStream; 4 | use quote::quote; 5 | use syn::{self, parse::Parse, parse_macro_input, LitStr, Token, Type}; 6 | 7 | struct ConstructivismSettnigs { 8 | pub domain: String, 9 | pub value_type: String, 10 | pub context_type: String, 11 | } 12 | 13 | impl Parse for ConstructivismSettnigs { 14 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 15 | let domain: LitStr = input.parse()?; 16 | let domain = domain.value(); 17 | let mut value_type = format!("::syn::Expr"); 18 | let mut context_type = format!("Context"); 19 | if input.peek(Token![,]) { 20 | input.parse::()?; 21 | let vt = input.parse::()?; 22 | let vt = quote! { #vt }; 23 | value_type = vt.to_string(); 24 | } 25 | if input.peek(Token![,]) { 26 | input.parse::()?; 27 | let vt = input.parse::()?; 28 | let vt = quote! { #vt }; 29 | context_type = vt.to_string(); 30 | } 31 | Ok(ConstructivismSettnigs { 32 | domain, 33 | value_type, 34 | context_type, 35 | }) 36 | } 37 | } 38 | 39 | #[proc_macro] 40 | pub fn implement_constructivism_macro(input: TokenStream) -> TokenStream { 41 | let settings = parse_macro_input!(input as ConstructivismSettnigs); 42 | // let path = settings.domain; 43 | // let path = format!("\"{path}\""); 44 | let source = include_str!("constructivism_macro.include"); 45 | let exact_domain = format!("\"{}\"", settings.domain); 46 | let source = source.replace("\"constructivism\"", &exact_domain); 47 | let exact_value = format!("type ConstructivismValue = {};", settings.value_type); 48 | let source = source.replace("type ConstructivismValue = syn::Expr;", &exact_value); 49 | let exact_context = format!("type ConstructivismContext = {};", settings.context_type); 50 | let source = source.replace("type ConstructivismContext = Context;", &exact_context); 51 | match TokenStream::from_str(&source) { 52 | Err(e) => { 53 | let e = e.to_string(); 54 | TokenStream::from(quote! { compile_error!("Can't parse constructivism_macro: {}", #e) }) 55 | } 56 | Ok(stream) => stream, 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/constructivist/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "constructivist" 3 | version = "0.3.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | readme = "README.md" 7 | description = "Simplify the construction of structured data." 8 | homepage = "https://github.com/polako-rs/constructivism/tree/main/crates/constructivist" 9 | repository = "https://github.com/polako-rs/constructivism" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [dependencies] 14 | proc-macro2 = "1.0.66" 15 | quote = "1.0.33" 16 | syn = { version = "2.0.29", features = ["full"]} 17 | toml = "0.7.6" 18 | constructivism_macro_gen = { path = "../constructivism_macro_gen", version = "0.3.0" } 19 | -------------------------------------------------------------------------------- /crates/constructivist/LICENSE-APACHE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /crates/constructivist/LICENSE-MIT.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /crates/constructivist/README.md: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | `constructivist` is the way to deliver `constructivism` into your crate. 4 | 5 | ## The Problem 6 | 7 | At some point you will realise that `construtivism` is fucken awesome. And you want to use it in your crate. Let's call this crate `polako`, just for example. And it also could happen that your crate is about to work with another crate (or framework, or engine), let's say it is about to work with `bevy` (for example of course). You will meet one irresistible problem in this case: you can't implement a foreign trait for a foreign type. You can't implement `constructivism::Construct` for `bevy::Sprite` from your thouthend-times-amazing `polako` crate. 8 | 9 | This is how you become a `constructivist`. 10 | 11 | `constructivis`*`t`* allows you to inline the `constructivis`*`m`* into your crate. But. You are supposed to complete some steps. 12 | 13 | ## The Solution 14 | 15 | I assume that you are using workspace and your crates live inside the `crates` folder. There is also required naming convention with a single constraint: if your main crate is called `awesomecrate`, then your constructivism crate have to be called `awesomecrate_constructivism`. You can inspect how the real `polako` inlines constructivism here: [https://github.com/jkb0o/polako/tree/eml](https://github.com/jkb0o/polako/tree/eml) 16 | 17 | #### Step 1: Create your macro crate 18 | 19 | 1. You have to create (or use existed) your own macro crate to implement your version of `constructivism_macro`. This is how you can create your `polako_macro` crate: 20 | 21 | ```bash 22 | # in your favorite terminal: 23 | cd crates 24 | mkdir polako_macro 25 | cd polako_macro 26 | cargo init --lib 27 | cargo add syn proc_macro2 quote constructivist 28 | ``` 29 | 30 | 2. If it is new macro crate, you have to edit `Cargo.toml` of this crate, and make this crate `proc_macro` crate: 31 | ```toml 32 | # crates/polako_macro/Cargo.toml 33 | [lib] 34 | proc-macro = true 35 | ``` 36 | 37 | 3. You have to inline `constructivism_macro` into your crate: 38 | ```rust 39 | // crates/polako_macro/src/lib.rs 40 | implement_constructivism_macro!("polako"); 41 | ``` 42 | 43 | 4. You can use all the power of `constructivism_macro` in your crate. 44 | 45 | #### Step 2: Create your constructivism crate 46 | 47 | 1. You need to inline all traits and implementations of `constructivism` in your crate: 48 | 49 | ```bash 50 | # we are in constructivism_macro dir for now, go back to crates 51 | cd ../ 52 | mkdir polako_constructivism 53 | cargo init --lib 54 | cargo add --path ../polako_macro 55 | 56 | # you most probably want to have your third-parti crate as dependency 57 | cargo add bevy 58 | ``` 59 | 60 | 2. At this point, you can use constructivism derives and proc macros in you crate. It meansyou can implement constructivism now: 61 | 62 | ```rust 63 | // crates/polako_constructivism/src/lib.rs 64 | pub use polako_macro::*; 65 | // 32 is the maximum params limit, see (TODO: link the explanation) 66 | implement_constructivism!(32); 67 | ``` 68 | 69 | #### Step 3: Add bindings to all your needs 70 | 71 | 1. From now, you are working with crate that defines & implements constructivism structs & traits. An this is the point where you can bridge the third-party crate. `bevy` in our example. Add the bridge mod to your crate: 72 | 73 | ```rust 74 | // crates/polako_constructivism/src/lib.rs 75 | pub use polako_macro::*; 76 | implement_constructivism!(32); 77 | 78 | // add bridge mod: 79 | mod bridge 80 | ``` 81 | 82 | 2. Provide implementations for the third-party crate: 83 | 84 | ```rust 85 | // crates/polako_constructivism/src/bridge.rs 86 | use bevy::prelude::*; 87 | use polaco_macro::*; 88 | 89 | derive_construct! { 90 | NodeBundle -> Nothing () { 91 | NodeBundle::default() 92 | } 93 | } 94 | ``` 95 | 96 | #### Step 4: Add constructivism mod to your crate root 97 | 98 | 1. As you already guessed, the name of the root crate in this tutorial is `polako`. So, you HAVE to add `constructivism` mod to your root crate to make it all work everywhere: 99 | 100 | ```rust 101 | // src/lib.rs 102 | pub mod constructivism { 103 | // this is required: 104 | pub use polako_constructivism::*; 105 | pub use polako_macro::*; 106 | 107 | // this is optional (but nice): 108 | pub mod prelude { 109 | pub use polako_constructivism::prelude::*; 110 | pub use polako_macro::*; 111 | } 112 | } 113 | ``` 114 | 115 | #### Step 5: Give the Feedback 116 | 117 | 1. All the stuff you done won't compile in most cases (becouse I've tested only single case). 118 | 2. Go to github and write an [issue](https://github.com/jkb0o/polako/issues) about how life is hard without constructivism and cry about this damn tutorial that just won't work as expected. 119 | 3. (Optional) Find the source of the problem and provide a *beautiful* PR. 120 | 121 | #### Step 6: Overcome the Suffering 122 | 123 | 1. You followed this tutorial and implemented constructivism more then once. 124 | 2. You wonder - why there is no tools that automate all of these steps? 125 | 3. You realize - it is becouse nobody wrote these tools yet. 126 | 4. You implement `cargo-bootstrap-constructivism` (ask me how), provide astonished PR, and make this crate even better. 127 | 128 | ## License (boring) 129 | 130 | The `constructivist` is dual-licensed under either: 131 | 132 | - MIT License ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) 133 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) 134 | 135 | This means you can select the license you prefer! 136 | This dual-licensing approach is the de-facto standard in the Rust ecosystem and there are [very good reasons](https://github.com/bevyengine/bevy/issues/2373) to include both. -------------------------------------------------------------------------------- /crates/constructivist/src/context.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, collections::HashMap}; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::{format_ident, quote}; 5 | 6 | pub struct Context { 7 | pub prefix: &'static str, 8 | cache: RefCell>, 9 | } 10 | 11 | impl Context { 12 | pub fn new(prefix: &'static str) -> Self { 13 | Self { 14 | prefix, 15 | cache: RefCell::new(HashMap::new()), 16 | } 17 | } 18 | 19 | fn cache(&self, key: &'static str, value: TokenStream) -> TokenStream { 20 | self.cache.borrow_mut().insert(key, value.clone()); 21 | value 22 | } 23 | 24 | pub fn constructivism(&self) -> TokenStream { 25 | self.path("constructivism") 26 | } 27 | 28 | pub fn path(&self, name: &'static str) -> TokenStream { 29 | if let Some(cached) = self.cache.borrow().get(name).cloned() { 30 | return cached; 31 | } 32 | let prefix = self.prefix; 33 | let prefix_ident = format_ident!("{prefix}"); 34 | let global = format_ident!("{name}"); 35 | let local = format_ident!("{prefix}_{name}"); 36 | let lib = if name == prefix { 37 | quote! { ::#prefix_ident } 38 | } else { 39 | quote! { ::#prefix_ident::#global } 40 | }; 41 | let Some(manifest_path) = std::env::var_os("CARGO_MANIFEST_DIR") 42 | .map(std::path::PathBuf::from) 43 | .map(|mut path| { 44 | path.push("Cargo.toml"); 45 | path 46 | }) 47 | else { 48 | return self.cache(name, lib); 49 | }; 50 | let Ok(manifest) = std::fs::read_to_string(&manifest_path) else { 51 | return self.cache(name, lib); 52 | }; 53 | let Ok(manifest) = toml::from_str::>(&manifest) else { 54 | return self.cache(name, lib); 55 | }; 56 | 57 | let Some(pkg) = manifest.get("package") else { 58 | return self.cache(name, lib); 59 | }; 60 | let Some(pkg) = pkg.as_table() else { 61 | return self.cache(name, lib); 62 | }; 63 | let Some(pkg) = pkg.get("name") else { 64 | return self.cache(name, lib); 65 | }; 66 | let Some(pkg) = pkg.as_str() else { 67 | return self.cache(name, lib); 68 | }; 69 | if pkg == &format!("{prefix}_{name}") { 70 | self.cache(name, quote! { crate }) 71 | } else if pkg.starts_with(&format!("{prefix}_mod_")) { 72 | self.cache(name, lib) 73 | } else if pkg.starts_with(&format!("{prefix}_")) { 74 | self.cache(name, quote! { ::#local }) 75 | } else { 76 | self.cache(name, lib) 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /crates/constructivist/src/core.include: -------------------------------------------------------------------------------- 1 | ../../../src/core.rs -------------------------------------------------------------------------------- /crates/constructivist/src/derive.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::context::Context; 4 | use crate::exts::*; 5 | use crate::throw; 6 | use proc_macro2::{Span, TokenStream, TokenTree}; 7 | use quote::{format_ident, quote, ToTokens}; 8 | use syn::{ 9 | braced, bracketed, parenthesized, 10 | parse::{Parse, ParseStream}, 11 | parse2, parse_quote, 12 | spanned::Spanned, 13 | Attribute, Data, DeriveInput, Expr, Field, Fields, Ident, Token, Type, 14 | }; 15 | 16 | pub struct Declarations { 17 | span: Span, 18 | decls: HashMap, 19 | } 20 | 21 | impl Parse for Declarations { 22 | fn parse(input: ParseStream) -> syn::Result { 23 | let span = input.span(); 24 | let mut decls = HashMap::new(); 25 | while let Ok(ident) = input.parse::() { 26 | input.parse::]>()?; 27 | let mut stream = quote! {}; 28 | while !input.peek(Token![;]) { 29 | let tt = input.parse::()?; 30 | stream = quote! { #stream #tt }; 31 | } 32 | input.parse::()?; 33 | decls.insert(ident.to_string(), stream); 34 | } 35 | Ok(Declarations { decls, span }) 36 | } 37 | } 38 | 39 | impl Declarations { 40 | pub fn parse_declaration(&self, key: &str) -> syn::Result { 41 | let Some(stream) = self.decls.get(key) else { 42 | throw!(self.span, "Missing {} declaration key", key); 43 | }; 44 | parse2(stream.clone()) 45 | } 46 | pub fn parse_or_default(&self, key: &str) -> syn::Result { 47 | if let Some(stream) = self.decls.get(key) { 48 | parse2(stream.clone()) 49 | } else { 50 | Ok(T::default()) 51 | } 52 | } 53 | } 54 | 55 | pub enum ParamType { 56 | Single(Type), 57 | Union(Vec), 58 | } 59 | impl Parse for ParamType { 60 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 61 | if input.peek(syn::token::Bracket) { 62 | let content; 63 | bracketed!(content in input); 64 | let params = content.parse_terminated(Param::parse, Token![,])?; 65 | Ok(ParamType::Union(params.into_iter().collect())) 66 | } else { 67 | Ok(ParamType::Single(input.parse()?)) 68 | } 69 | } 70 | } 71 | 72 | pub enum ParamKind { 73 | Common, 74 | Required, 75 | Default(Expr), 76 | Skip(Expr), 77 | } 78 | impl Parse for ParamKind { 79 | fn parse(input: ParseStream) -> syn::Result { 80 | let ident: Ident = input.parse()?; 81 | if &ident.to_string() == "required" { 82 | Ok(ParamKind::Required) 83 | } else if &ident.to_string() == "default" { 84 | input.parse::()?; 85 | Ok(ParamKind::Default(input.parse()?)) 86 | } else if &ident.to_string() == "skip" { 87 | if input.peek(Token![=]) { 88 | input.parse::()?; 89 | Ok(ParamKind::Skip(input.parse()?)) 90 | } else { 91 | Ok(ParamKind::Skip(parse_quote!(Default::default()))) 92 | } 93 | } else { 94 | throw!(ident, "Expected required|default|skip"); 95 | } 96 | } 97 | } 98 | 99 | pub struct Param { 100 | pub name: Ident, 101 | pub ty: ParamType, 102 | pub kind: ParamKind, 103 | pub docs: Vec, 104 | } 105 | 106 | impl Parse for Param { 107 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 108 | let docs = Attribute::parse_outer(&input)? 109 | .iter() 110 | .filter(|a| a.path().is_ident("doc")) 111 | .cloned() 112 | .collect(); 113 | let name = input.parse()?; 114 | input.parse::()?; 115 | let ty = input.parse()?; 116 | let kind = if input.peek(Token![=]) { 117 | input.parse::()?; 118 | ParamKind::Default(input.parse()?) 119 | } else { 120 | ParamKind::Required 121 | }; 122 | Ok(Param { 123 | name, 124 | ty, 125 | kind, 126 | docs, 127 | }) 128 | } 129 | } 130 | 131 | impl Param { 132 | pub fn docs(&self) -> TokenStream { 133 | let mut out = quote! {}; 134 | for doc in self.docs.iter() { 135 | out = quote! { #out #doc } 136 | } 137 | out 138 | } 139 | pub fn skip(&self) -> Option<&Expr> { 140 | match self.kind { 141 | ParamKind::Skip(ref expr) => Some(expr), 142 | _ => None, 143 | } 144 | } 145 | } 146 | 147 | pub struct BuildedParams { 148 | // slider_construct::min, slider_construct::max, slider_construct::val, 149 | type_params: TokenStream, 150 | // slider_construct::min(min), slider_construct::max(max), slider_construct::val(val), 151 | type_params_deconstruct: TokenStream, 152 | // min, max, val, 153 | param_values: TokenStream, 154 | impls: TokenStream, 155 | fields: TokenStream, 156 | fields_new: TokenStream, 157 | } 158 | pub trait Params: Sized { 159 | fn from_fields(fields: &syn::Fields, name: &str, alter: &str) -> syn::Result; 160 | fn build(&self, ctx: &Context, ty: &Type, mod_ident: &Ident) -> syn::Result; 161 | } 162 | impl Params for Vec { 163 | fn from_fields(fields: &syn::Fields, name: &str, alter: &str) -> syn::Result { 164 | let mut params = vec![]; 165 | for field in fields.iter() { 166 | let ty = ParamType::Single(field.ty.clone()); 167 | let docs = field 168 | .attrs 169 | .iter() 170 | .filter(|a| a.path().is_ident("doc")) 171 | .cloned() 172 | .collect(); 173 | let Some(name) = field.ident.clone() else { 174 | throw!(field, "#[derive({})] only supports named structs. You can use `{}!` for complex cases.", name, alter); 175 | }; 176 | let kind = field 177 | .attrs 178 | .iter() 179 | .filter(|a| a.path().is_ident("param")) 180 | .map(|a| a.parse_args()) 181 | .last() 182 | .or(Some(Ok(ParamKind::Common))) 183 | .unwrap()?; 184 | params.push(Param { 185 | ty, 186 | name, 187 | kind, 188 | docs, 189 | }); 190 | } 191 | Ok(params) 192 | } 193 | 194 | fn build(&self, ctx: &Context, ty: &Type, mod_ident: &Ident) -> syn::Result { 195 | let lib = ctx.path("constructivism"); 196 | let mut type_params = quote! {}; // slider_construct::min, slider_construct::max, slider_construct::val, 197 | let mut type_params_deconstruct = quote! {}; // slider_construct::min(min), slider_construct::max(max), slider_construct::val(val), 198 | let mut param_values = quote! {}; // min, max, val, 199 | let mut impls = quote! {}; 200 | let mut fields = quote! {}; 201 | let mut fields_new = quote! {}; 202 | for param in self.iter() { 203 | let ParamType::Single(param_ty) = ¶m.ty else { 204 | throw!(ty, "Union params not supported yet."); 205 | }; 206 | let ident = ¶m.name; 207 | let docs = param.docs(); 208 | if let Some(skip) = param.skip() { 209 | param_values = quote! { #param_values #ident: #skip, }; 210 | } else { 211 | param_values = quote! { #param_values #ident, }; 212 | type_params = quote! { #type_params #mod_ident::#ident, }; 213 | type_params_deconstruct = 214 | quote! { #type_params_deconstruct #mod_ident::#ident(mut #ident), }; 215 | fields = quote! { #fields 216 | #[allow(unused_variables)] 217 | #docs 218 | pub #ident: #lib::Param<#ident, #param_ty>, 219 | }; 220 | fields_new = 221 | quote! { #fields_new #ident: #lib::Param(::std::marker::PhantomData), }; 222 | let default = match ¶m.kind { 223 | ParamKind::Default(default) => { 224 | quote! { 225 | impl Default for #ident { 226 | fn default() -> Self { 227 | #ident(#default) 228 | } 229 | } 230 | } 231 | } 232 | ParamKind::Common => { 233 | quote! { 234 | impl Default for #ident { 235 | fn default() -> Self { 236 | #ident(Default::default()) 237 | } 238 | } 239 | } 240 | } 241 | ParamKind::Required => { 242 | quote! {} 243 | } 244 | ParamKind::Skip(skip) => { 245 | throw!(skip, "Unexpected skip param"); 246 | } 247 | }; 248 | impls = quote! { #impls 249 | #default 250 | #[allow(non_camel_case_types)] 251 | pub struct #ident(pub #param_ty); 252 | impl> From for #ident { 253 | fn from(__value__: T) -> Self { 254 | #ident(__value__.into()) 255 | } 256 | } 257 | impl #lib::AsField for #ident { 258 | fn as_field() -> #lib::Field { 259 | #lib::Field::new() 260 | } 261 | } 262 | impl #lib::New<#param_ty> for #ident { 263 | fn new(from: #param_ty) -> #ident { 264 | #ident(from) 265 | } 266 | } 267 | }; 268 | } 269 | } 270 | Ok(BuildedParams { 271 | type_params, 272 | type_params_deconstruct, 273 | param_values, 274 | impls, 275 | fields, 276 | fields_new, 277 | }) 278 | } 279 | } 280 | 281 | pub struct Sequence { 282 | pub this: Type, 283 | pub segments: Vec, 284 | pub next: Type, 285 | } 286 | 287 | impl Parse for Sequence { 288 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 289 | let this = input.parse()?; 290 | input.parse::]>()?; 291 | let mut next = input.parse()?; 292 | let mut segments = vec![]; 293 | while input.peek(Token![->]) { 294 | input.parse::]>()?; 295 | segments.push(next); 296 | next = input.parse()?; 297 | } 298 | Ok(Sequence { 299 | this, 300 | segments, 301 | next, 302 | }) 303 | } 304 | } 305 | 306 | impl Sequence { 307 | pub fn from_derive(input: &DeriveInput) -> syn::Result { 308 | let attrs = input 309 | .attrs 310 | .iter() 311 | .filter(|a| a.path().is_ident("construct")) 312 | .collect::>(); 313 | if attrs.len() == 0 { 314 | let ty = &input.ident; 315 | return Ok(Sequence { 316 | this: parse_quote! { #ty }, 317 | next: parse_quote! { Nothing }, 318 | segments: vec![], 319 | }); 320 | } 321 | if attrs.len() > 1 { 322 | throw!(attrs[1], "Unexpected #[construct(..) attribute"); 323 | } 324 | Ok(attrs[0].parse_args()?) 325 | } 326 | } 327 | 328 | pub struct DeriveSegment { 329 | ty: Type, 330 | params: Vec, 331 | props: Props, 332 | body: Option, 333 | } 334 | 335 | impl Parse for DeriveSegment { 336 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 337 | let decls = input.parse::()?; 338 | let ty = decls.parse_declaration("seg")?; 339 | let constructor: Constructor = decls.parse_declaration("construct")?; 340 | let params = constructor.params; 341 | let body = Some(constructor.expr); 342 | let props = decls.parse_or_default("props")?; 343 | Ok(DeriveSegment { 344 | ty, 345 | params, 346 | body, 347 | props, 348 | }) 349 | } 350 | } 351 | 352 | impl DeriveSegment { 353 | pub fn from_derive(input: DeriveInput) -> syn::Result { 354 | if input.generics.params.len() > 0 { 355 | throw!( 356 | input.ident, 357 | "#[derive(Segment)] doesn't support generics yet." 358 | ); 359 | } 360 | let ty = &input.ident; 361 | let ty = syn::parse2(quote! { #ty })?; 362 | let Data::Struct(input) = input.data else { 363 | throw!(input.ident, "#[derive(Segment)] only supports named structs. You can use `derive_segment!` for complex cases."); 364 | }; 365 | let params = Params::from_fields(&input.fields, "Segment", "derive_segment")?; 366 | let body = None; 367 | let props = Props::from_fields(&input.fields)?; 368 | Ok(DeriveSegment { 369 | ty, 370 | params, 371 | props, 372 | body, 373 | }) 374 | } 375 | 376 | pub fn build(&self, ctx: &Context) -> syn::Result { 377 | let ty = &self.ty; 378 | let lib = ctx.path("constructivism"); 379 | let type_ident = ty.as_ident()?; 380 | let mod_ident = format_ident!( 381 | // input_segment 382 | "{}_segment", 383 | type_ident.to_string().to_lowercase() 384 | ); 385 | let design = format_ident!("{type_ident}Design"); 386 | let BuildedParams { 387 | fields, 388 | fields_new, 389 | impls, 390 | param_values, 391 | type_params, 392 | type_params_deconstruct, 393 | } = self.params.build(ctx, &ty, &mod_ident)?; 394 | let props_getters = self.props.build_lookup_getters(ctx, &ty)?; 395 | let props_setters = self.props.build_lookup_setters(ctx, &ty)?; 396 | let props_descriptors = self.props.build_type_descriptors(ctx, &ty)?; 397 | let getters = self.props.build_getters(ctx)?; 398 | let setters = self.props.build_setters(ctx)?; 399 | let construct = if let Some(expr) = &self.body { 400 | expr.clone() 401 | } else { 402 | syn::parse2(quote! { 403 | Self { #param_values } 404 | }) 405 | .unwrap() 406 | }; 407 | let decls = quote! { 408 | 409 | // fields 410 | 411 | pub struct Params { 412 | #fields 413 | __base__: ::std::marker::PhantomData, 414 | } 415 | impl #lib::Singleton for Params { 416 | fn instance() -> &'static Self { 417 | &Params { 418 | #fields_new 419 | __base__: ::std::marker::PhantomData, 420 | } 421 | } 422 | } 423 | impl std::ops::Deref for Params { 424 | type Target = T; 425 | fn deref(&self) -> &Self::Target { 426 | T::instance() 427 | } 428 | } 429 | 430 | // Props 431 | pub struct TypeReference; 432 | impl #lib::TypeReference for TypeReference { 433 | type Type = #ty; 434 | } 435 | pub struct Props>( 436 | ::std::marker::PhantomData<(M, T)>, 437 | ); 438 | pub struct Getters<'a>(&'a #ty); 439 | pub struct Setters<'a>(&'a mut #ty); 440 | impl<'a> #lib::Getters<'a, #ty> for Getters<'a> { 441 | fn from_ref(from: &'a #ty) -> Self { 442 | Self(from) 443 | } 444 | fn into_value(self) -> #lib::Value<'a, #ty> { 445 | #lib::Value::Ref(&self.0) 446 | } 447 | } 448 | impl<'a> #lib::Setters<'a, #ty> for Setters<'a> { 449 | fn from_mut(from: &'a mut #ty) -> Self { 450 | Self(from) 451 | } 452 | } 453 | impl Props<#lib::Lookup, T> 454 | where T: 455 | #lib::Props<#lib::Lookup> + 456 | #lib::Props<#lib::Get> + 457 | #lib::Props<#lib::Set> + 458 | #lib::Props<#lib::Describe> 459 | { 460 | pub fn getters(&self) -> &'static Props<#lib::Get, T> { 461 | as #lib::Singleton>::instance() 462 | } 463 | #[doc(hidden)] 464 | pub fn setters(&self) -> &'static Props<#lib::Set, T> { 465 | as #lib::Singleton>::instance() 466 | } 467 | #[doc(hidden)] 468 | pub fn descriptors(&self) -> &'static Props<#lib::Describe, T> { 469 | as #lib::Singleton>::instance() 470 | } 471 | } 472 | impl> Props<#lib::Get, T> { 473 | #props_getters 474 | } 475 | #[doc(hidden)] 476 | impl> Props<#lib::Set, T> { 477 | #props_setters 478 | } 479 | impl> Props<#lib::Describe, T> { 480 | #props_descriptors 481 | } 482 | impl<'a> Getters<'a> { 483 | #getters 484 | } 485 | impl<'a> Setters<'a> { 486 | #setters 487 | } 488 | impl + 'static> std::ops::Deref for Props { 489 | type Target = T; 490 | fn deref(&self) -> &Self::Target { 491 | ::instance() 492 | } 493 | } 494 | impl> #lib::Singleton for Props { 495 | fn instance() -> &'static Self { 496 | &Props(::std::marker::PhantomData) 497 | } 498 | } 499 | impl> #lib::Props for Props { } 500 | 501 | 502 | }; 503 | Ok(quote! { 504 | mod #mod_ident { 505 | use super::*; 506 | #decls 507 | #impls 508 | } 509 | impl #lib::ConstructItem for #type_ident { 510 | type Params = ( #type_params ); 511 | type Getters<'a> = #mod_ident::Getters<'a>; 512 | type Setters<'a> = #mod_ident::Setters<'a>; 513 | fn construct_item(params: Self::Params) -> Self { 514 | let (#type_params_deconstruct) = params; 515 | #construct 516 | } 517 | } 518 | impl #lib::Segment for #type_ident { 519 | type Props + 'static> = #mod_ident::Props; 520 | type Params = #mod_ident::Params; 521 | type Design = #design; 522 | } 523 | pub struct #design( 524 | ::std::marker::PhantomData 525 | ); 526 | impl #lib::Singleton for #design { 527 | fn instance() -> &'static Self { 528 | &#design(::std::marker::PhantomData) 529 | } 530 | } 531 | impl std::ops::Deref for #design { 532 | type Target = T; 533 | fn deref(&self) -> &Self::Target { 534 | T::instance() 535 | } 536 | } 537 | }) 538 | } 539 | } 540 | 541 | pub struct Prop { 542 | pub ident: Ident, 543 | pub ty: Type, 544 | pub kind: PropKind, 545 | docs: Vec, 546 | } 547 | 548 | pub enum PropKind { 549 | Value, 550 | Construct, 551 | GetSet(Ident, Ident), 552 | } 553 | 554 | impl Parse for Prop { 555 | fn parse(input: ParseStream) -> syn::Result { 556 | let docs = Attribute::parse_outer(&input)? 557 | .iter() 558 | .filter(|a| a.path().is_ident("doc")) 559 | .cloned() 560 | .collect(); 561 | let ident = input.parse()?; 562 | input.parse::()?; 563 | let ty = input.parse()?; 564 | input.parse::()?; 565 | let kind = if let Ok(ident) = input.parse::() { 566 | if &ident.to_string() == "construct" { 567 | PropKind::Construct 568 | } else if &ident.to_string() == "value" { 569 | PropKind::Value 570 | } else { 571 | throw!(ident, "Expected construct|value|[get,set]."); 572 | } 573 | } else { 574 | let content; 575 | bracketed!(content in input); 576 | let get = content.parse()?; 577 | content.parse::()?; 578 | let set = content.parse()?; 579 | if !content.is_empty() { 580 | throw!(content, "Unexpected input for prop [get, set]."); 581 | } 582 | PropKind::GetSet(get, set) 583 | }; 584 | Ok(Prop { 585 | docs, 586 | ident, 587 | kind, 588 | ty, 589 | }) 590 | } 591 | } 592 | 593 | impl Prop { 594 | pub fn from_field(field: &Field) -> syn::Result { 595 | let ty = field.ty.clone(); 596 | let Some(ident) = field.ident.clone() else { 597 | throw!(field, "Anonymous fields not supported."); 598 | }; 599 | let docs = field 600 | .attrs 601 | .iter() 602 | .filter(|a| a.path().is_ident("doc")) 603 | .cloned() 604 | .collect(); 605 | let mut attrs = field.attrs.iter().filter(|a| a.path().is_ident("prop")); 606 | if let Some(attr) = attrs.next() { 607 | let spec = attr.parse_args_with(PropSpec::parse)?; 608 | if spec.construct() { 609 | Ok(Prop { 610 | ident, 611 | ty, 612 | docs, 613 | kind: PropKind::Construct, 614 | }) 615 | } else { 616 | let (get, set) = spec.getset()?; 617 | Ok(Prop { 618 | ident, 619 | ty, 620 | docs, 621 | kind: PropKind::GetSet(get, set), 622 | }) 623 | } 624 | } else if let Some(ident) = field.ident.clone() { 625 | Ok(Prop { 626 | ty, 627 | ident, 628 | docs, 629 | kind: PropKind::Value, 630 | }) 631 | } else { 632 | throw!( 633 | field, 634 | "#[param(get, set)] is required for unnamed struct fields" 635 | ); 636 | } 637 | } 638 | pub fn docs(&self) -> TokenStream { 639 | let mut out = quote! {}; 640 | for attr in self.docs.iter() { 641 | out = quote! { #out #attr } 642 | } 643 | out 644 | } 645 | pub fn build_getter(&self, ctx: &Context) -> syn::Result { 646 | let lib = ctx.constructivism(); 647 | let ident = &self.ident; 648 | let ty = &self.ty; 649 | let docs = self.docs(); 650 | Ok(match &self.kind { 651 | PropKind::Value => quote! { 652 | #docs 653 | pub fn #ident(self) -> #lib::Value<'a, #ty> { 654 | #lib::Value::Ref(&self.0.#ident) 655 | } 656 | }, 657 | PropKind::Construct => quote! { 658 | #docs 659 | pub fn #ident(self) -> <#ty as #lib::ConstructItem>::Getters<'a> { 660 | <<#ty as #lib::ConstructItem>::Getters<'a> as #lib::Getters<'a, #ty>>::from_ref( 661 | &self.0.#ident 662 | ) 663 | } 664 | }, 665 | PropKind::GetSet(get, _set) => quote! { 666 | #docs 667 | pub fn #ident(self) -> #lib::Value<'a, #ty> { 668 | #lib::Value::Val(self.0.#get()) 669 | } 670 | }, 671 | }) 672 | } 673 | pub fn build_setter(&self, ctx: &Context) -> syn::Result { 674 | let lib = ctx.path("constructivism"); 675 | let ty = &self.ty; 676 | let ident = &self.ident; 677 | Ok(match &self.kind { 678 | PropKind::Value => { 679 | let setter = format_ident!("set_{}", ident); 680 | quote! { 681 | #[doc(hidden)] 682 | pub fn #setter(self, __value__: #ty) { 683 | self.0.#ident = __value__; 684 | } 685 | } 686 | } 687 | PropKind::Construct => { 688 | let setter = format_ident!("set_{}", ident); 689 | quote! { 690 | #[doc(hidden)] 691 | pub fn #ident(self) -> <#ty as #lib::ConstructItem>::Setters<'a> { 692 | <<#ty as #lib::ConstructItem>::Setters<'a> as #lib::Setters<'a, #ty>>::from_mut( 693 | &mut self.0.#ident 694 | ) 695 | } 696 | #[doc(hidden)] 697 | pub fn #setter(self, __value__: #ty) { 698 | self.0.#ident = __value__; 699 | } 700 | } 701 | } 702 | PropKind::GetSet(_get, set) => { 703 | let setter = format_ident!("set_{}", ident); 704 | quote! { 705 | #[doc(hidden)] 706 | pub fn #ident(self, __value__: #ty) { 707 | self.0.#set(__value__); 708 | } 709 | #[doc(hidden)] 710 | pub fn #setter(self, __value__: #ty) { 711 | self.0.#set(__value__); 712 | } 713 | } 714 | } 715 | }) 716 | } 717 | 718 | pub fn build_lookup_getter(&self, ctx: &Context, this: &Type) -> syn::Result { 719 | let lib = ctx.constructivism(); 720 | let ident = &self.ident; 721 | let ty = &self.ty; 722 | let docs = self.docs(); 723 | Ok(match &self.kind { 724 | PropKind::Value => { 725 | quote! { 726 | #docs 727 | pub fn #ident<'a>(&self, __this__: &'a #this) -> #lib::Value<'a, #ty> { 728 | #lib::Value::Ref(&__this__.#ident) 729 | } 730 | } 731 | } 732 | PropKind::Construct => { 733 | quote! { 734 | #docs 735 | pub fn #ident<'a>(&self, __this__: &'a #this) -> <#ty as #lib::ConstructItem>::Getters<'a> { 736 | <<#ty as #lib::ConstructItem>::Getters<'a> as #lib::Getters<'a, #ty>>::from_ref( 737 | &__this__.#ident 738 | ) 739 | } 740 | } 741 | } 742 | PropKind::GetSet(get, _set) => { 743 | quote! { 744 | #docs 745 | pub fn #ident<'a>(&self, __this__: &'a #this) -> #lib::Value<'a, #ty> { 746 | #lib::Value::Val(__this__.#get()) 747 | } 748 | } 749 | } 750 | }) 751 | } 752 | 753 | pub fn build_lookup_setter(&self, ctx: &Context, this: &Type) -> syn::Result { 754 | let lib = ctx.constructivism(); 755 | let ident = &self.ident; 756 | let ty = &self.ty; 757 | Ok(match &self.kind { 758 | PropKind::Value => { 759 | let setter = format_ident!("set_{}", ident); 760 | quote! { 761 | #[doc(hidden)] 762 | pub fn #ident(&self, __this__: &mut #this, __value__: #ty) { 763 | __this__.#ident = __value__; 764 | } 765 | #[doc(hidden)] 766 | pub fn #setter(&self, __this__: &mut #this, __value__: #ty) { 767 | __this__.#ident = __value__; 768 | } 769 | } 770 | } 771 | PropKind::Construct => { 772 | let setter = format_ident!("set_{}", ident); 773 | quote! { 774 | #[doc(hidden)] 775 | pub fn #ident<'a>(&self, __this__: &'a mut #this) -> <#ty as #lib::ConstructItem>::Setters<'a> { 776 | <<#ty as #lib::ConstructItem>::Setters<'a> as #lib::Setters<'a, #ty>>::from_mut( 777 | &mut __this__.#ident 778 | ) 779 | } 780 | #[doc(hidden)] 781 | pub fn #setter(&self, __this__: &mut #this, __value__: #ty) { 782 | __this__.#ident = __value__; 783 | } 784 | } 785 | } 786 | PropKind::GetSet(_get, set) => { 787 | let setter = format_ident!("set_{}", ident); 788 | quote! { 789 | #[doc(hidden)] 790 | pub fn #ident(&self, __this__: &mut #this, __value__: #ty) { 791 | __this__.#set(__value__); 792 | } 793 | #[doc(hidden)] 794 | pub fn #setter(&self, __this__: &mut #this, __value__: #ty) { 795 | __this__.#set(__value__); 796 | } 797 | } 798 | } 799 | }) 800 | } 801 | 802 | pub fn build_type_descriptor(&self, _ctx: &Context, _this: &Type) -> syn::Result { 803 | let ident = &self.ident; 804 | Ok(quote! { 805 | pub fn #ident(&self) -> &'static TypeReference { 806 | &TypeReference 807 | } 808 | }) 809 | } 810 | } 811 | 812 | pub struct PropSpec(pub Vec); 813 | 814 | impl Parse for PropSpec { 815 | fn parse(input: ParseStream) -> syn::Result { 816 | Ok(PropSpec( 817 | input 818 | .parse_terminated(Ident::parse, Token![,])? 819 | .into_iter() 820 | .collect(), 821 | )) 822 | } 823 | } 824 | impl PropSpec { 825 | pub fn skip(&self) -> bool { 826 | self.0.len() == 1 && &self.0[0].to_string() == "skip" 827 | } 828 | pub fn construct(&self) -> bool { 829 | self.0.len() == 1 && &self.0[0].to_string() == "construct" 830 | } 831 | pub fn getset(&self) -> syn::Result<(Ident, Ident)> { 832 | if self.0.len() == 2 { 833 | Ok((self.0[0].clone(), self.0[1].clone())) 834 | } else { 835 | throw!(self.0[0], "Expected #[prop(getter, setter)]"); 836 | } 837 | } 838 | } 839 | 840 | #[derive(Default)] 841 | pub struct Props(Vec); 842 | impl Parse for Props { 843 | fn parse(input: ParseStream) -> syn::Result { 844 | let content; 845 | braced!(content in input); 846 | Ok(Props( 847 | content 848 | .parse_terminated(Prop::parse, Token![;])? 849 | .into_iter() 850 | .collect(), 851 | )) 852 | } 853 | } 854 | impl Props { 855 | pub fn from_fields(fields: &Fields) -> syn::Result { 856 | let mut props = vec![]; 857 | for field in fields.iter() { 858 | if let Some(attr) = field.attrs.iter().find(|a| a.path().is_ident("prop")) { 859 | // attr.meta.require_list()?. 860 | // attr.meta.to_token_stream() 861 | // Punctuated::::parse_terminated(attr.meta.to_token_stream())?; 862 | // attr.parse_args::>()?; 863 | let skip = attr.parse_args_with(PropSpec::parse)?.skip(); 864 | if skip { 865 | continue; 866 | } 867 | } 868 | props.push(Prop::from_field(field)?) 869 | } 870 | Ok(Props(props)) 871 | } 872 | pub fn build_getters(&self, ctx: &Context) -> syn::Result { 873 | let mut out = quote! {}; 874 | for prop in self.iter() { 875 | let getter = prop.build_getter(ctx)?; 876 | out = quote! { #out #getter } 877 | } 878 | Ok(out) 879 | } 880 | pub fn build_setters(&self, ctx: &Context) -> syn::Result { 881 | let mut out = quote! {}; 882 | for prop in self.iter() { 883 | let setter = prop.build_setter(ctx)?; 884 | out = quote! { #out #setter } 885 | } 886 | Ok(out) 887 | } 888 | 889 | pub fn build_lookup_getters(&self, ctx: &Context, this: &Type) -> syn::Result { 890 | let mut out = quote! {}; 891 | for prop in self.iter() { 892 | let getter = prop.build_lookup_getter(ctx, this)?; 893 | out = quote! { #out #getter } 894 | } 895 | Ok(out) 896 | } 897 | 898 | pub fn build_lookup_setters(&self, ctx: &Context, this: &Type) -> syn::Result { 899 | let mut out = quote! {}; 900 | for prop in self.iter() { 901 | let setter = prop.build_lookup_setter(ctx, this)?; 902 | out = quote! { #out #setter } 903 | } 904 | Ok(out) 905 | } 906 | 907 | pub fn build_type_descriptors(&self, ctx: &Context, this: &Type) -> syn::Result { 908 | let mut out = quote! {}; 909 | for prop in self.iter() { 910 | let descriptor = prop.build_type_descriptor(ctx, this)?; 911 | out = quote! { #out #descriptor }; 912 | } 913 | Ok(out) 914 | } 915 | } 916 | impl std::ops::Deref for Props { 917 | type Target = Vec; 918 | fn deref(&self) -> &Self::Target { 919 | &self.0 920 | } 921 | } 922 | impl std::ops::DerefMut for Props { 923 | fn deref_mut(&mut self) -> &mut Self::Target { 924 | &mut self.0 925 | } 926 | } 927 | 928 | pub struct Constructor { 929 | params: Vec, 930 | expr: Expr, 931 | } 932 | 933 | impl Parse for Constructor { 934 | fn parse(input: ParseStream) -> syn::Result { 935 | let content; 936 | parenthesized!(content in input); 937 | let params = content.parse_terminated(Param::parse, Token![,])?; 938 | let params = params.into_iter().collect(); 939 | input.parse::]>()?; 940 | let expr = input.parse()?; 941 | Ok(Constructor { params, expr }) 942 | } 943 | } 944 | 945 | pub struct DeriveConstruct { 946 | pub ty: Type, 947 | pub sequence: Sequence, 948 | pub params: Vec, 949 | pub props: Props, 950 | pub body: Option, 951 | } 952 | 953 | impl Parse for DeriveConstruct { 954 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 955 | let decls = input.parse::()?; 956 | let sequence: Sequence = decls.parse_declaration("seq")?; 957 | let ty = sequence.this.clone(); 958 | let constructor: Constructor = decls.parse_declaration("construct")?; 959 | let params = constructor.params; 960 | let body = Some(constructor.expr); 961 | let props = decls.parse_or_default("props")?; 962 | Ok(DeriveConstruct { 963 | ty, 964 | params, 965 | body, 966 | sequence, 967 | props, 968 | }) 969 | } 970 | } 971 | 972 | impl DeriveConstruct { 973 | pub fn from_derive(input: DeriveInput) -> syn::Result { 974 | if input.generics.params.len() > 0 { 975 | throw!( 976 | input.ident, 977 | "#[derive(Construct)] doesn't support generics yet." 978 | ); 979 | } 980 | let ident = input.ident.clone(); // Slider 981 | let ty = syn::parse2(quote! { #ident }).unwrap(); 982 | let sequence = Sequence::from_derive(&input)?; 983 | let Data::Struct(input) = input.data else { 984 | throw!(input.ident, "#[derive(Construct)] only supports named structs. You can use `derive_construct!` for complex cases."); 985 | }; 986 | let params = Params::from_fields(&input.fields, "Construct", "derive_construct")?; 987 | let props = Props::from_fields(&input.fields)?; 988 | let body = None; 989 | Ok(DeriveConstruct { 990 | ty, 991 | sequence, 992 | params, 993 | props, 994 | body, 995 | }) 996 | } 997 | 998 | pub fn mod_ident(&self) -> syn::Result { 999 | let type_ident = self.ty.as_ident()?; 1000 | Ok(format_ident!( 1001 | "{}_construct", 1002 | type_ident.to_string().to_lowercase() 1003 | )) 1004 | } 1005 | 1006 | pub fn design_ident(&self) -> syn::Result { 1007 | let type_ident = self.ty.as_ident()?; 1008 | Ok(format_ident!("{}Design", type_ident.to_string())) 1009 | } 1010 | 1011 | pub fn build(&self, ctx: &Context) -> syn::Result { 1012 | let ty = &self.ty; 1013 | let lib = ctx.path("constructivism"); 1014 | let type_ident = ty.as_ident()?; 1015 | let mod_ident = self.mod_ident()?; 1016 | let design = self.design_ident()?; 1017 | let mut deref_design; 1018 | let BuildedParams { 1019 | fields, 1020 | fields_new, 1021 | impls, 1022 | param_values, 1023 | type_params, 1024 | type_params_deconstruct, 1025 | } = self.params.build(ctx, &ty, &mod_ident)?; 1026 | let props_getters = self.props.build_lookup_getters(ctx, &ty)?; 1027 | let props_setters = self.props.build_lookup_setters(ctx, &ty)?; 1028 | let props_descriptors = self.props.build_type_descriptors(ctx, &ty)?; 1029 | let getters = self.props.build_getters(ctx)?; 1030 | let setters = self.props.build_setters(ctx)?; 1031 | let decls = { 1032 | let base = &self.sequence.next; 1033 | let base = if !base.is_nothing() { 1034 | quote! { #base } 1035 | } else { 1036 | quote! { () } 1037 | }; 1038 | let mut deref_fields = quote! { <#base as #lib::Construct>::Params }; 1039 | let mut deref_props = quote! { <#base as #lib::Construct>::Props }; 1040 | deref_design = quote! { <#base as #lib::Construct>::Design }; 1041 | for segment in self.sequence.segments.iter() { 1042 | deref_fields = quote! { <#segment as #lib::Segment>::Params<#deref_fields> }; 1043 | deref_design = quote! { <#segment as #lib::Segment>::Design<#deref_design> }; 1044 | deref_props = quote! { <#segment as #lib::Segment>::Props }; 1045 | } 1046 | 1047 | quote! { 1048 | // Params 1049 | pub struct Params { 1050 | #fields 1051 | } 1052 | impl #lib::Singleton for Params { 1053 | fn instance() -> &'static Self { 1054 | &Params { 1055 | #fields_new 1056 | } 1057 | } 1058 | } 1059 | impl ::std::ops::Deref for Params { 1060 | type Target = #deref_fields; 1061 | fn deref(&self) -> &Self::Target { 1062 | <#deref_fields as #lib::Singleton>::instance() 1063 | } 1064 | } 1065 | 1066 | // Props 1067 | pub struct Props(::std::marker::PhantomData); 1068 | pub struct Getters<'a>(&'a #ty); 1069 | pub struct Setters<'a>(&'a mut #ty); 1070 | pub struct TypeReference; 1071 | impl #lib::TypeReference for TypeReference { 1072 | type Type = #ty; 1073 | } 1074 | impl<'a> #lib::Getters<'a, #ty> for Getters<'a> { 1075 | fn from_ref(from: &'a #ty) -> Self { 1076 | Self(from) 1077 | } 1078 | fn into_value(self) -> #lib::Value<'a, #ty> { 1079 | #lib::Value::Ref(&self.0) 1080 | } 1081 | } 1082 | impl<'a> #lib::Setters<'a, #ty> for Setters<'a> { 1083 | fn from_mut(from: &'a mut #ty) -> Self { 1084 | Self(from) 1085 | } 1086 | } 1087 | impl Props<#lib::Lookup> { 1088 | pub fn getters(&self) -> &'static Props<#lib::Get> { 1089 | as #lib::Singleton>::instance() 1090 | } 1091 | #[doc(hidden)] 1092 | pub fn setters(&self) -> &'static Props<#lib::Set> { 1093 | as #lib::Singleton>::instance() 1094 | } 1095 | #[doc(hidden)] 1096 | pub fn descriptors(&self) -> &'static Props<#lib::Describe> { 1097 | as #lib::Singleton>::instance() 1098 | } 1099 | } 1100 | impl Props<#lib::Describe> { 1101 | #props_descriptors 1102 | } 1103 | impl Props<#lib::Get> { 1104 | #props_getters 1105 | } 1106 | #[doc(hidden)] 1107 | impl Props<#lib::Set> { 1108 | #props_setters 1109 | } 1110 | impl<'a> Getters<'a> { 1111 | #getters 1112 | } 1113 | impl<'a> Setters<'a> { 1114 | #setters 1115 | } 1116 | impl std::ops::Deref for Props { 1117 | type Target = #deref_props; 1118 | fn deref(&self) -> &Self::Target { 1119 | <#deref_props as #lib::Singleton>::instance() 1120 | } 1121 | } 1122 | impl #lib::Singleton for Props { 1123 | fn instance() -> &'static Self { 1124 | &Props(::std::marker::PhantomData) 1125 | } 1126 | } 1127 | impl #lib::Props for Props { } 1128 | } 1129 | }; 1130 | let derive = { 1131 | if self.sequence.this.to_token_stream().to_string() != ty.to_token_stream().to_string() 1132 | { 1133 | throw!( 1134 | self.sequence.this, 1135 | "Sequence head doesn't match struct name" 1136 | ); 1137 | } 1138 | let this = &self.sequence.this; 1139 | let base = &self.sequence.next; 1140 | let base = if !base.is_nothing() { 1141 | quote! { #base } 1142 | } else { 1143 | quote! { () } 1144 | }; 1145 | 1146 | let mut mixed_params = quote! {}; 1147 | let mut expanded_params = quote! { ::ExpandedParams }; 1148 | let mut base_sequence = quote! { ::NestedSequence }; 1149 | let mut deconstruct = quote! {}; 1150 | let mut construct = quote! { ::construct(rest) }; 1151 | for segment in self.sequence.segments.iter().rev() { 1152 | let segment_params = 1153 | format_ident!("{}_params", segment.as_ident()?.to_string().to_lowercase()); 1154 | if mixed_params.is_empty() { 1155 | mixed_params = quote! { <#segment as #lib::ConstructItem>::Params, }; 1156 | deconstruct = quote! { #segment_params }; 1157 | } else { 1158 | mixed_params = quote! { #lib::Mix<<#segment as #lib::ConstructItem>::Params, #mixed_params> }; 1159 | deconstruct = quote! { (#segment_params, #deconstruct) }; 1160 | } 1161 | expanded_params = quote! { #lib::Mix<<#segment as #lib::ConstructItem>::Params, #expanded_params> }; 1162 | construct = quote! { ( <#segment as #lib::ConstructItem>::construct_item(#segment_params), #construct ) }; 1163 | base_sequence = quote! { (#segment, #base_sequence) }; 1164 | } 1165 | let mixed_params = if mixed_params.is_empty() { 1166 | quote! { (#type_params) } 1167 | } else { 1168 | quote! { #lib::Mix<(#type_params), #mixed_params> } 1169 | }; 1170 | let deconstruct = if deconstruct.is_empty() { 1171 | quote! { self_params } 1172 | } else { 1173 | quote! { (self_params, #deconstruct) } 1174 | }; 1175 | let construct = quote! { 1176 | ( 1177 | ::construct_item(self_params), 1178 | #construct 1179 | ) 1180 | }; 1181 | quote! { 1182 | impl #lib::Construct for #type_ident { 1183 | type Sequence = ::Output; 1184 | type Base = #base; 1185 | type Params = #mod_ident::Params; 1186 | type Props = #mod_ident::Props; 1187 | type Design = #design; 1188 | type MixedParams = (#mixed_params); 1189 | type NestedSequence = (Self, #base_sequence); 1190 | type ExpandedParams = #lib::Mix<(#type_params), #expanded_params>; 1191 | 1192 | fn construct(params: P) -> Self::NestedSequence where P: #lib::ExtractParams< 1193 | I, Self::MixedParams, 1194 | Value = ::Output, 1195 | Rest = <<::ExpandedParams as #lib::Extractable>::Input as #lib::AsParams>::Defined 1196 | > { 1197 | let _: Option<#this> = None; 1198 | let (#deconstruct, rest) = params.extract_params(); 1199 | #construct 1200 | } 1201 | } 1202 | } 1203 | }; 1204 | let construct = if let Some(expr) = &self.body { 1205 | expr.clone() 1206 | } else { 1207 | syn::parse2(quote! { 1208 | Self { #param_values } 1209 | }) 1210 | .unwrap() 1211 | }; 1212 | Ok(quote! { 1213 | mod #mod_ident { 1214 | use super::*; 1215 | #decls 1216 | #impls 1217 | } 1218 | impl #lib::ConstructItem for #type_ident { 1219 | type Params = ( #type_params ); 1220 | type Getters<'a> = #mod_ident::Getters<'a>; 1221 | type Setters<'a> = #mod_ident::Setters<'a>; 1222 | fn construct_item(params: Self::Params) -> Self { 1223 | let (#type_params_deconstruct) = params; 1224 | #construct 1225 | } 1226 | } 1227 | pub struct #design; 1228 | impl #lib::Singleton for #design { 1229 | fn instance() -> &'static Self { 1230 | &#design 1231 | } 1232 | } 1233 | impl ::std::ops::Deref for #design { 1234 | type Target = #deref_design; 1235 | fn deref(&self) -> &Self::Target { 1236 | <#deref_design as #lib::Singleton>::instance() 1237 | } 1238 | } 1239 | #derive 1240 | }) 1241 | } 1242 | } 1243 | -------------------------------------------------------------------------------- /crates/constructivist/src/exts.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Ident; 2 | use quote::{quote, ToTokens}; 3 | use syn::{spanned::Spanned, Type}; 4 | 5 | pub trait TypeExt { 6 | fn as_ident(&self) -> syn::Result; 7 | fn is_nothing(&self) -> bool; 8 | } 9 | impl TypeExt for Type { 10 | fn as_ident(&self) -> syn::Result { 11 | let Type::Path(path) = &self else { 12 | return Err(syn::Error::new( 13 | self.span(), 14 | format!( 15 | "Can't extract ident from type {}", 16 | quote!({#self}).to_string() 17 | ), 18 | )); 19 | }; 20 | if path.path.segments.is_empty() { 21 | return Err(syn::Error::new( 22 | self.span(), 23 | format!( 24 | "Can't extract ident from type {}", 25 | quote!({#self}).to_string() 26 | ), 27 | )); 28 | } 29 | Ok(path.path.segments.last().unwrap().ident.clone()) 30 | } 31 | fn is_nothing(&self) -> bool { 32 | &self.into_token_stream().to_string() == "Nothing" 33 | } 34 | } 35 | 36 | pub trait Capitalize { 37 | type Output; 38 | fn capitalize(&self) -> Self::Output; 39 | } 40 | 41 | impl> Capitalize for T { 42 | type Output = String; 43 | fn capitalize(&self) -> Self::Output { 44 | self.as_ref() 45 | .chars() 46 | .enumerate() 47 | .map( 48 | |(idx, ch)| { 49 | if idx > 0 { 50 | ch 51 | } else { 52 | ch.to_ascii_uppercase() 53 | } 54 | }, 55 | ) 56 | .collect() 57 | } 58 | } 59 | 60 | pub trait ToCamelCase { 61 | type Output; 62 | fn to_camel_case(&self) -> Self::Output; 63 | } 64 | 65 | impl<'a> ToCamelCase for &'a str { 66 | type Output = String; 67 | fn to_camel_case(&self) -> Self::Output { 68 | self.split("_").map(|s| s.capitalize()).collect() 69 | } 70 | } 71 | 72 | impl ToCamelCase for String { 73 | type Output = String; 74 | fn to_camel_case(&self) -> Self::Output { 75 | self.split("_").map(|s| s.capitalize()).collect() 76 | } 77 | } 78 | 79 | impl ToCamelCase for Ident { 80 | type Output = Ident; 81 | fn to_camel_case(&self) -> Self::Output { 82 | let s = self.to_string().to_camel_case(); 83 | Ident::new(&s, self.span()) 84 | } 85 | } 86 | 87 | pub trait Suffix { 88 | type Output; 89 | fn suffix>(&self, suffix: S) -> Self::Output; 90 | } 91 | 92 | impl<'a> Suffix for &'a str { 93 | type Output = String; 94 | fn suffix>(&self, suffix: S) -> Self::Output { 95 | format!("{}{}", self, suffix.as_ref()) 96 | } 97 | } 98 | impl Suffix for String { 99 | type Output = String; 100 | fn suffix>(&self, suffix: S) -> Self::Output { 101 | format!("{}{}", self, suffix.as_ref()) 102 | } 103 | } 104 | impl Suffix for Ident { 105 | type Output = Ident; 106 | fn suffix>(&self, suffix: S) -> Self::Output { 107 | Ident::new(&self.to_string().suffix(suffix), self.span()) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /crates/constructivist/src/genlib.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::{format_ident, quote}; 5 | use syn::{parse::Parse, LitInt}; 6 | 7 | pub struct ConstructivistLimits { 8 | pub max_fields: u8, 9 | } 10 | 11 | impl Parse for ConstructivistLimits { 12 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 13 | let max_fields = input.parse::()?; 14 | Ok(ConstructivistLimits { 15 | max_fields: max_fields.base10_parse()?, 16 | }) 17 | } 18 | } 19 | 20 | pub fn implement_constructivism_core(max_size: u8) -> TokenStream { 21 | let extract_field_impls = impl_all_extract_field(max_size); 22 | let add_to_params = impl_all_add_to_params(max_size); 23 | let defined = impl_all_defined(max_size); 24 | let extracts = impl_all_extracts(max_size); 25 | let mixed = impl_all_mixed(max_size); 26 | let as_params = impl_all_as_params(max_size); 27 | let flattern = impl_all_flattern(max_size); 28 | let contains = impl_all_contains(16); 29 | quote! { 30 | #extract_field_impls 31 | #add_to_params 32 | #defined 33 | #extracts 34 | #as_params 35 | #mixed 36 | #flattern 37 | #contains 38 | } 39 | } 40 | 41 | pub fn implement_constructivism(max_size: u8) -> TokenStream { 42 | let source = include_str!("core.include"); 43 | let source = source 44 | .lines() 45 | .filter(|l| !l.contains("@constructivist-no-expose")) 46 | .collect::>() 47 | .join("\n"); 48 | let Ok(core) = TokenStream::from_str(source.as_str()) else { 49 | return quote! { compile_error! ("Coudn't parse constructivism::core")}; 50 | }; 51 | quote! { 52 | #core 53 | implement_constructivism_core!(#max_size); 54 | } 55 | } 56 | 57 | fn impl_all_extract_field(max_size: u8) -> TokenStream { 58 | let mut out = quote! {}; 59 | for size in 1..max_size + 1 { 60 | for idx in 0..size { 61 | let impl_extract = impl_extract_field(idx, size); 62 | out = quote! { #out #impl_extract } 63 | } 64 | } 65 | out 66 | } 67 | fn impl_all_add_to_params(max_size: u8) -> TokenStream { 68 | let mut out = quote! {}; 69 | for size in 1..max_size + 1 { 70 | for idx in 0..size { 71 | let impl_add_to_params = impl_add_to_params(idx, size); 72 | out = quote! { #out #impl_add_to_params } 73 | } 74 | } 75 | out 76 | } 77 | fn impl_all_defined(max_size: u8) -> TokenStream { 78 | let mut out = quote! {}; 79 | for size in 1..max_size + 1 { 80 | let defined = impl_defined(size); 81 | out = quote! { #out #defined } 82 | } 83 | out 84 | } 85 | fn impl_all_extracts(max_size: u8) -> TokenStream { 86 | let mut out = quote! {}; 87 | for size in 1..max_size + 1 { 88 | let extractable = impl_extractable(size); 89 | out = quote! { #out #extractable }; 90 | for defined in 0..size + 1 { 91 | let extract = impl_extract(defined, size); 92 | out = quote! { #out #extract }; 93 | } 94 | } 95 | out 96 | } 97 | fn impl_all_mixed(max_size: u8) -> TokenStream { 98 | let mut out = quote! {}; 99 | for size in 1..max_size + 1 { 100 | for left in 0..size + 1 { 101 | let right = size - left; 102 | let mixed = impl_mixed(left, right); 103 | out = quote! { #out #mixed }; 104 | } 105 | } 106 | out 107 | } 108 | /// ```ignore 109 | /// impl AsParams for (D<0, T0>, D<1, T1>) { 110 | /// type Undefined = (U<0, T0>, U<1, T1>); 111 | /// fn as_params() -> Params { 112 | /// Params(( 113 | /// U::<0, T0>(PhantomData), 114 | /// U::<1, T1>(PhantomData) 115 | /// )) 116 | /// } 117 | /// } 118 | /// ``` 119 | fn impl_all_as_params(max_size: u8) -> TokenStream { 120 | let mut out = quote! {}; 121 | for size in 0..max_size + 1 { 122 | let mut ts = quote! {}; 123 | let mut ds = quote! {}; 124 | let mut us = quote! {}; 125 | let mut ps = quote! {}; 126 | for i in 0..size { 127 | let ti = format_ident!("T{i}"); 128 | ts = quote! { #ts #ti, }; 129 | ds = quote! { #ds D<#i, #ti>, }; 130 | us = quote! { #us U<#i, #ti>, }; 131 | ps = quote! { #ps U::<#i, #ti>(::std::marker::PhantomData), } 132 | } 133 | out = quote! { #out 134 | impl<#ts> AsParams for (#ds) { 135 | type Undefined = Params<(#us)>; 136 | type Defined = Params<(#ds)>; 137 | fn as_params() -> Self::Undefined { 138 | Params(( #ps )) 139 | } 140 | } 141 | } 142 | } 143 | out 144 | } 145 | 146 | fn impl_all_flattern(max_depth: u8) -> TokenStream { 147 | let mut out = quote! {}; 148 | for depth in 1..max_depth + 1 { 149 | let mut ts = quote! {}; 150 | let mut cstr = quote! {}; 151 | let mut ns = quote! { () }; 152 | let mut vs = quote! {}; 153 | let mut dcs = quote! { _ }; 154 | for i in 0..depth { 155 | let ti = format_ident!("T{i}"); 156 | let pi = format_ident!("p{i}"); 157 | let tr = format_ident!("T{}", depth - i - 1); 158 | let pr = format_ident!("p{}", depth - i - 1); 159 | cstr = quote! { #cstr #ti: ConstructItem, }; 160 | ts = if i < depth - 1 { 161 | quote! { #ts #ti, } 162 | } else { 163 | quote! { #ts #ti } 164 | }; 165 | vs = if i < depth - 1 { 166 | quote! { #vs #pi, } 167 | } else { 168 | quote! { #vs #pi } 169 | }; 170 | // ts = quote! { #ts #ti, }; 171 | ns = quote! { (#tr, #ns) }; 172 | dcs = quote! { (#pr, #dcs) }; 173 | } 174 | out = quote! { #out 175 | impl<#cstr> Flattern for #ns { 176 | type Output = (#ts); 177 | fn flattern(self) -> Self::Output { 178 | let #dcs = self; 179 | ( #vs ) 180 | } 181 | } 182 | } 183 | } 184 | out 185 | } 186 | 187 | fn impl_all_contains(max_size: u8) -> TokenStream { 188 | let mut out = quote! {}; 189 | for size in 1..max_size + 1 { 190 | let contains = impl_contains(size); 191 | out = quote! { #out #contains } 192 | } 193 | out 194 | } 195 | 196 | /// Generates single ExtractField trait implementation. 197 | /// `impl_extract_field(1, 3) will generate this: 198 | /// ```ignore 199 | /// impl, A2> ExtractField, T1> for Params<(A0, A1, A2)> { 200 | /// fn field(&self, _: &Field) -> F<1, T1> { 201 | /// F::<1, T1>(PhantomData) 202 | /// } 203 | /// } 204 | /// ``` 205 | fn impl_extract_field(idx: u8, size: u8) -> TokenStream { 206 | let ti = format_ident!("T{idx}"); 207 | let fi = quote! { F<#idx, #ti> }; 208 | let mut gin = quote! {}; 209 | let mut gout = quote! {}; 210 | for i in 0..size { 211 | let ai = format_ident!("A{i}"); 212 | if i == idx { 213 | gin = quote! { #gin #ai: A<#i, #ti>, } 214 | } else { 215 | gin = quote! { #gin #ai,} 216 | } 217 | gout = quote! { #gout #ai, }; 218 | } 219 | 220 | quote! { 221 | impl<#ti, #gin> ExtractField<#fi, #ti> for Params<(#gout)> { 222 | fn field(&self, _: &Field<#ti>) -> #fi { 223 | F::<#idx, #ti>(PhantomData) 224 | } 225 | } 226 | } 227 | } 228 | 229 | /// Generates single std::ops::Add implementation for Params of size `size` 230 | /// and param at `idx` position. `impl_add_to_params(1, 4)` will generate this: 231 | /// ```ignore 232 | // #gin #pundef 233 | /// impl std::ops::Add> for Params<(A0, U<1, T1>, A2, A3)> { 234 | // #pout 235 | /// type Output = Params<(A0, D<1, T1>, A2, A3)>; 236 | /// fn add(self, rhs: D<1, T1>) -> Self::Output { 237 | // #dcs 238 | /// let (p0, _, p2, p3) = self.0; 239 | // #vls 240 | /// Params((p0, rhs, p2, p3)) 241 | /// } 242 | /// } 243 | // #gin #pdef 244 | /// impl std::ops::Add> for Params<(A0, D<1, T1>, A2, A3)> { 245 | // #pout 246 | /// type Output = ParamConflict; 247 | /// fn add(self, _: D<1, T1>) -> Self::Output { 248 | /// ParamConflict::new() 249 | /// } 250 | /// } 251 | /// ``` 252 | fn impl_add_to_params(idx: u8, size: u8) -> TokenStream { 253 | let ti = format_ident!("T{idx}"); 254 | let di = quote! { D<#idx, #ti> }; 255 | let ui = quote! { U<#idx, #ti> }; 256 | let mut gin = quote! {}; 257 | let mut pundef = quote! {}; 258 | let mut pdef = quote! {}; 259 | let mut pout = quote! {}; 260 | let mut dcs = quote! {}; 261 | let mut vls = quote! {}; 262 | for i in 0..size { 263 | if i == idx { 264 | pundef = quote! { #pundef #ui, }; 265 | pdef = quote! { #pdef #di, }; 266 | pout = quote! { #pout #di, }; 267 | dcs = quote! { #dcs _, }; 268 | vls = quote! { #vls rhs, }; 269 | } else { 270 | let ai = format_ident!("A{i}"); 271 | let pi = format_ident!("p{i}"); 272 | gin = quote! { #gin #ai, }; 273 | pundef = quote! { #pundef #ai, }; 274 | pdef = quote! { #pdef #ai, }; 275 | 276 | pout = quote! { #pout #ai, }; 277 | dcs = quote! { #dcs #pi, }; 278 | vls = quote! { #vls #pi, }; 279 | } 280 | } 281 | quote! { 282 | impl<#ti, #gin> std::ops::Add<#di> for Params<(#pundef)> { 283 | type Output = Params<(#pout)>; 284 | fn add(self, rhs: #di) -> Self::Output { 285 | let (#dcs) = self.0; 286 | Params((#vls)) 287 | } 288 | } 289 | 290 | impl<#ti, #gin> std::ops::Add<#di> for Params<(#pdef)> { 291 | type Output = ParamConflict<#ti>; 292 | fn add(self, _: #di) -> Self::Output { 293 | ParamConflict::new() 294 | } 295 | } 296 | } 297 | } 298 | 299 | /// ```ignore 300 | /// impl Extractable for (T0, T1) { 301 | /// type Input = (D<0, T0>, D<1, T1>); 302 | /// type Output = (T0, T1); 303 | /// fn extract(input: Self::Input) -> Self::Output { 304 | /// let (p0, p1) = input; 305 | /// (p0.0, p1.0) 306 | /// } 307 | /// } 308 | /// impl> ExtractParams<2, E> for Params<(T0, T1, T2, T3)> 309 | /// where 310 | /// T2: Shift<0>, 311 | /// T3: Shift<1>, 312 | /// { 313 | /// type Value = E::Output; 314 | /// type Rest = Params<(T2::Target, T3::Target)>; 315 | /// fn extract_params(self) -> (Self::Value, Self::Rest) { 316 | /// let (p0, p1, p2, p3) = self.0; 317 | /// ( 318 | /// E::extract((p0, p1)), 319 | /// Params((p2.shift(), p3.shift())) 320 | /// ) 321 | /// } 322 | /// } 323 | /// ``` 324 | fn impl_extractable(size: u8) -> TokenStream { 325 | let mut ein = quote! {}; 326 | let mut edef = quote! {}; 327 | let mut eout = quote! {}; 328 | let mut dcstr = quote! {}; 329 | 330 | for i in 0..size { 331 | let ti = format_ident!("T{i}"); 332 | let pi = format_ident!("p{i}"); 333 | ein = quote! { #ein #ti, }; 334 | edef = quote! { #edef D<#i, #ti>, }; 335 | dcstr = quote! { #dcstr #pi, }; 336 | eout = quote! { #eout #pi.0, }; 337 | } 338 | quote! { 339 | impl<#ein> Extractable for (#ein) { 340 | type Input = (#edef); 341 | type Output = (#ein); 342 | fn extract(input: Self::Input) -> Self::Output { 343 | let (#dcstr) = input; 344 | (#eout) 345 | } 346 | } 347 | } 348 | } 349 | fn impl_extract(defined: u8, size: u8) -> TokenStream { 350 | let mut ein = quote! {}; 351 | let mut pin = quote! {}; 352 | let mut pfor = quote! {}; 353 | let mut pcstr = quote! {}; 354 | let mut trest = quote! {}; 355 | let mut pdcstr = quote! {}; 356 | let mut pout = quote! {}; 357 | let mut pparams = quote! {}; 358 | 359 | for i in 0..size { 360 | let ti = format_ident!("T{i}"); 361 | let pi = format_ident!("p{i}"); 362 | if i < defined { 363 | ein = quote! { #ein #ti, }; 364 | pout = quote! { #pout #pi, } 365 | } else { 366 | let j = i - defined; 367 | pcstr = quote! { #pcstr #ti: Shift<#j>, }; 368 | trest = quote! { #trest #ti::Target, }; 369 | pparams = quote! { #pparams #pi.shift(), }; 370 | } 371 | pin = quote! { #pin #ti, }; 372 | pfor = quote! { #pfor #ti, }; 373 | pdcstr = quote! { #pdcstr #pi, }; 374 | } 375 | quote! { 376 | impl<#pin E: Extractable> ExtractParams<#defined, E> for Params<(#pin)> 377 | where #pcstr 378 | { 379 | type Value = E::Output; 380 | type Rest = Params<(#trest)>; 381 | fn extract_params(self) -> (Self::Value, Self::Rest) { 382 | let (#pdcstr) = self.0; 383 | ( 384 | E::extract((#pout)), 385 | Params((#pparams)) 386 | ) 387 | } 388 | } 389 | } 390 | } 391 | 392 | // impl Params<(T0, T1, T2)> { 393 | // pub fn defined(self) -> Params<(D<0, T0::Value>, D<1, T1::Value>, D<2, T2::Value>)> { 394 | // let (p0,p1,p2) = self.0; 395 | // Params(( 396 | // D::<0, _>(p0.extract_value()), 397 | // D::<1, _>(p1.extract_value()), 398 | // D::<2, _>(p2.extract_value()), 399 | // )) 400 | // } 401 | // } 402 | fn impl_defined(size: u8) -> TokenStream { 403 | let mut gin = quote! {}; 404 | let mut gout = quote! {}; 405 | let mut pout = quote! {}; 406 | let mut dcstr = quote! {}; 407 | let mut vals = quote! {}; 408 | for i in 0..size { 409 | let ti = format_ident!("T{i}"); 410 | let pi = format_ident!("p{i}"); 411 | gin = quote! { #gin #ti: ExtractValue, }; 412 | gout = quote! { #gout #ti, }; 413 | pout = quote! { #pout D<#i, #ti::Value>, }; 414 | dcstr = quote! { #dcstr #pi, }; 415 | vals = quote! { #vals D::<#i, _>(#pi.extract_value()), } 416 | } 417 | quote! { 418 | impl<#gin> Params<(#gout)> { 419 | pub fn defined(self) -> Params<(#pout)> { 420 | let (#dcstr) = self.0; 421 | Params((#vals)) 422 | } 423 | } 424 | } 425 | } 426 | 427 | /// ```ignore 428 | /// impl Mixed<(D<0, R0>, D<1, R1>)> for (D<0, L0>,) { 429 | /// type Output = (D<0, L0>, D<1, R0>, D<2, R1>); 430 | /// fn split(joined: Self::Output) -> (Self, (D<0, R0>, D<1, R1>)) { 431 | /// let (l0, r0, r1) = joined; 432 | /// let r0 = D::<0, _>(r0.0); 433 | /// let r1 = D::<1, _>(r1.0); 434 | /// ((l0,), (r0, r1)) 435 | /// 436 | /// } 437 | /// } 438 | /// ``` 439 | fn impl_mixed(left: u8, right: u8) -> TokenStream { 440 | let mut ls = quote! {}; // L0, 441 | let mut rs = quote! {}; // R0, R1, 442 | let mut dls = quote! {}; // D<0, L0>, 443 | let mut drs = quote! {}; // D<0, R0>, D<1, R1>, 444 | let mut lvs = quote! {}; // l0, 445 | let mut rvs = quote! {}; // r0, r1, 446 | let mut shift = quote! {}; // let r0 = D::<0, _>(r0.0); 447 | // let r1 = D::<1, _>(r1.0); 448 | let mut output = quote! {}; // D<0, L0>, D<1, R0>, D<2, R1> 449 | for i in 0..left.max(right) { 450 | let li = format_ident!("L{i}"); 451 | let ri = format_ident!("R{i}"); 452 | let lv = format_ident!("l{i}"); 453 | let rv = format_ident!("r{i}"); 454 | if i < left { 455 | ls = quote! { #ls #li, }; 456 | dls = quote! { #dls D<#i, #li>, }; 457 | lvs = quote! { #lvs #lv, }; 458 | } 459 | if i < right { 460 | rs = quote! { #rs #ri, }; 461 | drs = quote! { #drs D<#i, #ri>, }; 462 | rvs = quote! { #rvs #rv, }; 463 | shift = quote! { #shift let #rv = D::<#i, _>(#rv.0); } 464 | } 465 | } 466 | for i in 0..left + right { 467 | let ti = if i < left { 468 | format_ident!("L{i}") 469 | } else { 470 | format_ident!("R{}", i - left) 471 | }; 472 | output = quote! { #output D<#i, #ti>, }; 473 | } 474 | quote! { 475 | impl<#ls #rs> Mixed<(#drs)> for (#dls) { 476 | type Output = (#output); 477 | fn split(joined: Self::Output) -> (Self, (#drs)) { 478 | let (#lvs #rvs) = joined; 479 | #shift 480 | ((#lvs), (#rvs)) 481 | } 482 | } 483 | } 484 | } 485 | // output for impl_contains(4) 486 | // impl Contains<()> for (T0, (T1, (T2, (T3, ())))) { } 487 | // impl Contains<(T3, ())> for (T0, (T1, (T2, (T3, ())))) { } 488 | // impl Contains<(T2, (T3, ()))> for (T0, (T1, (T2, (T3, ())))) { } 489 | // impl Contains<(T1, (T2, (T3, ())))> for (T0, (T1, (T2, (T3, ())))) { } 490 | fn impl_contains(size: u8) -> TokenStream { 491 | let mut out = quote! {}; 492 | let mut tfor = quote! { () }; 493 | let mut tin = quote! {}; 494 | for i in 0..size { 495 | let ti = format_ident!("T{}", size - i - 1); 496 | tfor = quote! { (#ti, #tfor) }; 497 | tin = quote! { #ti, #tin }; 498 | } 499 | for impl_size in 0..size { 500 | let mut cnt = quote! { () }; 501 | for i in 0..impl_size { 502 | let ti = format_ident!("T{}", size - i - 1); 503 | cnt = quote! { (#ti, #cnt) } 504 | } 505 | out = quote! { #out 506 | impl Contains for #tfor { } 507 | } 508 | } 509 | out = quote! { #out 510 | impl<#tin> Contains for #tfor { } 511 | }; 512 | out 513 | } 514 | -------------------------------------------------------------------------------- /crates/constructivist/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod context; 2 | pub mod derive; 3 | pub mod exts; 4 | pub mod genlib; 5 | pub mod proc; 6 | pub mod throw; 7 | 8 | pub mod prelude { 9 | pub use crate::context::Context; 10 | pub use crate::derive::{DeriveConstruct, DeriveSegment}; 11 | pub use crate::genlib; 12 | pub use crate::proc::{Construct, Prop, Ref}; 13 | pub use constructivism_macro_gen::implement_constructivism_macro; 14 | } 15 | -------------------------------------------------------------------------------- /crates/constructivist/src/proc.rs: -------------------------------------------------------------------------------- 1 | use crate::{context::Context, throw}; 2 | use proc_macro2::{Ident, TokenStream}; 3 | use quote::{format_ident, quote}; 4 | use syn::{braced, parenthesized, parse::Parse, spanned::Spanned, token::Brace, Expr, Token, Type}; 5 | 6 | pub trait ContextLike { 7 | fn path(&self, name: &'static str) -> TokenStream; 8 | fn constructivism(&self) -> TokenStream { 9 | self.path("constructivism") 10 | } 11 | } 12 | 13 | pub fn build) -> syn::Result>( 14 | ctx: C, 15 | builder: F, 16 | ) -> syn::Result { 17 | let boxed = Box::new(ctx); 18 | let mem = Box::leak(boxed) as *mut C; 19 | let result = builder(Ref(mem)); 20 | unsafe { 21 | let _ = *Box::from_raw(mem); 22 | } 23 | result 24 | } 25 | 26 | pub struct Ref(*mut C); 27 | 28 | impl Clone for Ref { 29 | fn clone(&self) -> Self { 30 | Ref(self.0.clone()) 31 | } 32 | } 33 | 34 | impl Copy for Ref {} 35 | 36 | impl std::ops::Deref for Ref { 37 | type Target = C; 38 | fn deref(&self) -> &Self::Target { 39 | unsafe { self.0.as_ref().unwrap() } 40 | } 41 | } 42 | 43 | impl std::ops::DerefMut for Ref { 44 | fn deref_mut(&mut self) -> &mut Self::Target { 45 | unsafe { self.0.as_mut().unwrap() } 46 | } 47 | } 48 | 49 | impl ContextLike for Context { 50 | fn path(&self, name: &'static str) -> TokenStream { 51 | self.path(name) 52 | } 53 | } 54 | 55 | pub trait Value: Parse + Clone { 56 | type Context: ContextLike; 57 | fn build(item: &Self, ctx: Ref) -> syn::Result; 58 | fn parse2(stream: TokenStream) -> syn::Result { 59 | syn::parse2::(stream) 60 | } 61 | } 62 | 63 | impl Value for Expr { 64 | type Context = Context; 65 | fn build(item: &Self, _: Ref) -> syn::Result { 66 | Ok(quote! { #item }) 67 | } 68 | } 69 | 70 | #[derive(Clone)] 71 | pub struct Param { 72 | pub ident: Ident, 73 | pub value: V, 74 | } 75 | 76 | impl Parse for Param { 77 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 78 | let mut value = None; 79 | // this is kinda autocomplete 80 | let dot = input.parse::()?; 81 | if input.is_empty() || input.peek(Token![,]) { 82 | let ident = format_ident!("DOT_AUTOCOMPLETE_TOKEN", span = dot.span()); 83 | let value = V::parse2(quote! { true })?; 84 | return Ok(Param { ident, value }); 85 | } 86 | let ident: Ident = input.parse()?; 87 | if value.is_none() && input.peek(Token![:]) { 88 | input.parse::()?; 89 | value = Some(V::parse(input)?); 90 | } 91 | if value.is_none() && (input.is_empty() || input.peek(Token![,])) { 92 | value = Some(syn::parse_quote_spanned! { ident.span() => 93 | true 94 | }); 95 | } 96 | if value.is_none() { 97 | throw!(input, "Unexpected param input: {}", input.to_string()); 98 | } 99 | Ok(Param { 100 | ident, 101 | value: value.unwrap(), 102 | }) 103 | } 104 | } 105 | impl Param { 106 | // let param: &$crate::Param<_, _> = &$fields.$f; 107 | // let field = param.field(); 108 | // let value = $params.field(&field).define(param.value($e.into())); 109 | // let $params = $params + value; 110 | pub fn build(&self, ctx: Ref) -> syn::Result { 111 | let ident = &self.ident; 112 | let value = V::build(&self.value, ctx)?; 113 | let lib = ctx.path("constructivism"); 114 | Ok(quote! { 115 | let param: &#lib::Param<_, _> = &fields.#ident; 116 | let field = param.field(); 117 | let value = params.field(&field).define(param.value((#value).into())); 118 | let params = params + value; 119 | }) 120 | } 121 | } 122 | 123 | #[derive(Clone)] 124 | pub struct Params { 125 | pub items: Vec>, 126 | } 127 | 128 | impl Parse for Params { 129 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 130 | let mut items = vec![]; 131 | while !input.is_empty() { 132 | items.push(Param::::parse(input)?); 133 | if input.peek(Token![,]) { 134 | input.parse::()?; 135 | } 136 | } 137 | Ok(Params { items }) 138 | } 139 | } 140 | 141 | impl Params { 142 | pub fn new() -> Self { 143 | Params { items: vec![] } 144 | } 145 | pub fn build(&self, ctx: Ref) -> syn::Result { 146 | let mut out = quote! {}; 147 | for param in self.items.iter() { 148 | let param = param.build(ctx)?; 149 | out = quote! { #out #param } 150 | } 151 | Ok(out) 152 | } 153 | pub fn empty() -> Self { 154 | Params { items: vec![] } 155 | } 156 | pub fn parenthesized(input: syn::parse::ParseStream) -> syn::Result { 157 | let content; 158 | parenthesized!(content in input); 159 | content.parse() 160 | } 161 | 162 | pub fn braced(input: syn::parse::ParseStream) -> syn::Result { 163 | let content; 164 | braced!(content in input); 165 | content.parse() 166 | } 167 | } 168 | 169 | #[derive(Clone)] 170 | pub struct Construct { 171 | pub ty: Option, 172 | pub flattern: bool, 173 | pub params: Params, 174 | } 175 | 176 | impl Parse for Construct { 177 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 178 | let ty = if input.fork().parse::().is_ok() { 179 | Some(input.parse()?) 180 | } else { 181 | None 182 | }; 183 | let mut flattern = true; 184 | if input.peek(Token![*]) { 185 | input.parse::()?; 186 | flattern = false; 187 | } 188 | let params = if input.peek(Brace) { 189 | Params::braced(input)? 190 | } else { 191 | Params::new() 192 | }; 193 | Ok(Construct { 194 | ty, 195 | flattern, 196 | params, 197 | }) 198 | } 199 | } 200 | 201 | // #[macro_export] 202 | // macro_rules! construct { 203 | // ($t:ty { $($rest:tt)* } ) => { 204 | // { 205 | // use $crate::traits::*; 206 | // type Params = <$t as $crate::Construct>::Params; 207 | // let fields = <<$t as $crate::Construct>::Params as $crate::Singleton>::instance(); 208 | // let params = <<$t as $crate::Construct>::ExpandedParams as $crate::Extractable>::as_params(); 209 | // 210 | // // body here, see Param::build(..) 211 | // 212 | // let defined_params = params.defined(); 213 | // <$t as $crate::Construct>::construct(defined_params).flattern() 214 | // } 215 | // }; 216 | // } 217 | impl Construct { 218 | pub fn build(&self, ctx: Ref) -> syn::Result { 219 | let lib = ctx.path("constructivism"); 220 | let ty = &self.ty; 221 | let body = self.params.build(ctx)?; 222 | if let Some(ty) = ty { 223 | let flattern = if self.flattern { 224 | quote! { .flattern() } 225 | } else { 226 | quote! {} 227 | }; 228 | Ok(quote! {{ 229 | use #lib::traits::*; 230 | let fields = <<#ty as #lib::Construct>::Params as #lib::Singleton>::instance(); 231 | let params = <<#ty as #lib::Construct>::ExpandedParams as #lib::Extractable>::as_params(); 232 | #body 233 | let defined_params = params.defined(); 234 | <#ty as #lib::Construct>::construct(defined_params)#flattern 235 | }}) 236 | } else { 237 | Ok(quote! {{ 238 | use #lib::traits::*; 239 | #body 240 | params.defined() 241 | }}) 242 | } 243 | } 244 | } 245 | 246 | pub struct Prop { 247 | pub root: Type, 248 | pub path: Vec, 249 | } 250 | 251 | impl Parse for Prop { 252 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 253 | let root = input.parse()?; 254 | let mut path = vec![]; 255 | while input.peek(Token![.]) { 256 | let dot = input.parse::()?; 257 | if input.is_empty() { 258 | path.push(format_ident!("DOT_AUTOCOMPLETE_TOKEN", span = dot.span())) 259 | } else { 260 | path.push(input.parse()?) 261 | } 262 | } 263 | Ok(Prop { root, path }) 264 | } 265 | } 266 | 267 | impl Prop { 268 | pub fn build(&self, ctx: &Context) -> syn::Result { 269 | let lib = ctx.constructivism(); 270 | let root = &self.root; 271 | let mut get = quote! { <<#root as #lib::Construct>::Props<#lib::Lookup> as #lib::Singleton>::instance().getters() }; 272 | let mut set = quote! { <<#root as #lib::Construct>::Props<#lib::Lookup> as #lib::Singleton>::instance().setters() }; 273 | if self.path.len() == 0 { 274 | throw!(self.root, "Missing property path."); 275 | } 276 | let last = self.path.len() - 1; 277 | 278 | for (idx, part) in self.path.iter().enumerate() { 279 | let setter = format_ident!("set_{}", part); 280 | if idx == 0 { 281 | get = quote! { #get.#part(host) }; 282 | } else { 283 | get = quote! { #get.#part() }; 284 | } 285 | 286 | if idx == 0 && idx == last { 287 | set = quote! { #set.#setter(host, value) }; 288 | } else if idx == last { 289 | set = quote! { #set.#setter(value)}; 290 | } else if idx == 0 { 291 | set = quote! { #set.#part(host) }; 292 | } else { 293 | set = quote! { #set.#part() }; 294 | } 295 | } 296 | get = quote! { #get.into_value() }; 297 | Ok(quote! { 298 | #lib::Prop::new( 299 | |host| #get, 300 | |host, value| #set 301 | ) 302 | }) 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /crates/constructivist/src/throw.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! throw { 3 | ($loc:expr, $msg:literal) => { 4 | return Err(syn::Error::new($loc.span(), $msg)); 5 | }; 6 | ($loc:expr, $msg:literal, $($arg:expr),*) => { 7 | return Err(syn::Error::new($loc.span(), format!($msg, $($arg),*))); 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /examples/props.rs: -------------------------------------------------------------------------------- 1 | use constructivism::*; 2 | 3 | fn main() {} 4 | 5 | #[derive(Default, Construct)] 6 | #[construct(Color -> Nothing)] 7 | pub struct Color { 8 | /// Red channel 9 | r: f32, 10 | /// Green channel 11 | g: f32, 12 | /// Blue channel 13 | b: f32, 14 | /// Alpha channel 15 | #[prop(a, set_a)] 16 | a: f32, 17 | } 18 | 19 | impl Color { 20 | pub fn a(&self) -> f32 { 21 | self.a 22 | } 23 | pub fn set_a(&mut self, a: f32) { 24 | self.a = a 25 | } 26 | } 27 | 28 | #[derive(Construct)] 29 | #[construct(Div -> Nothing)] 30 | pub struct Div { 31 | /// The background color for the Div, transparent by default. 32 | #[prop(construct)] 33 | background: Color, 34 | } 35 | 36 | #[derive(Construct)] 37 | #[construct(Label -> Div)] 38 | pub struct Label { 39 | /// The text coklor, black by default. 40 | #[prop(construct)] 41 | text_color: Color, 42 | } 43 | 44 | impl Label {} 45 | // impl Model { 46 | // fn props(&self) { 47 | // let props = <::Props as Singleton>::instance(); 48 | 49 | // } 50 | // } 51 | 52 | fn _t() { 53 | let _p = <