├── .editorconfig ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── derive ├── Cargo.toml ├── README.md ├── derive_decode.md ├── derive_decode_scalar.md ├── src │ ├── definition.rs │ ├── kw.rs │ ├── lib.rs │ ├── node.rs │ ├── scalar.rs │ └── variants.rs └── tests │ ├── ast.rs │ ├── extra.rs │ ├── flatten.rs │ ├── normal.rs │ ├── scalar.rs │ ├── tuples.rs │ └── types.rs ├── examples └── simple.rs ├── images └── error.png ├── lib └── Cargo.toml ├── rustfmt.toml ├── src ├── ast.rs ├── containers.rs ├── convert.rs ├── convert_ast.rs ├── decode.rs ├── errors.rs ├── grammar.rs ├── lib.rs ├── span.rs ├── traits.rs └── wrappers.rs └── vagga.yaml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | tab_width = 4 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | 8 | [*.rs] 9 | indent_style = space 10 | indent_size = 4 11 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | on: [pull_request, push] 2 | 3 | name: Tests 4 | 5 | jobs: 6 | build_and_test: 7 | name: Rust tests 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | rust_version: [1.62, stable] 12 | steps: 13 | - uses: actions/checkout@master 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: ${{ matrix.rust_version }} 18 | default: true 19 | 20 | - name: All feature tests 21 | uses: actions-rs/cargo@v1 22 | with: 23 | command: test 24 | args: --workspace --all-features 25 | 26 | - name: Build without features 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: build 30 | args: --workspace --no-default-features 31 | 32 | - name: Normal tests without extra features (but derive is enabled) 33 | uses: actions-rs/cargo@v1 34 | with: 35 | command: test 36 | args: --workspace --no-default-features --features=derive 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vagga 2 | /target 3 | /tmp 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | ".", 4 | "derive", 5 | ] 6 | 7 | [package] 8 | name = "knuffel" 9 | version = "3.2.0" 10 | edition = "2021" 11 | description = """ 12 | Another KDL language implementation 13 | """ 14 | license = "MIT/Apache-2.0" 15 | keywords = ["kdl", "configuration", "parser"] 16 | categories = ["parser-implementations", "config", "encoding"] 17 | homepage = "https://github.com/tailhook/knuffel" 18 | documentation = "https://docs.rs/knuffel" 19 | rust-version = "1.62.0" 20 | readme = "README.md" 21 | 22 | [dependencies] 23 | chumsky = {version="0.9.2", default-features=false} 24 | knuffel-derive = {path="./derive", version= "^3.2.0", optional=true} 25 | base64 = {version="0.21.0", optional=true} 26 | unicode-width = {version="0.1.9", optional=true} 27 | minicbor = {version="0.19.1", optional=true, features=["std", "derive"]} 28 | miette = "5.1.1" 29 | thiserror = "1.0.31" 30 | 31 | [dev-dependencies] 32 | miette = { version="5.1.1", features=["fancy"] } 33 | assert-json-diff = "2.0.1" 34 | serde_json = "1.0" 35 | 36 | [features] 37 | default = ["derive", "base64", "line-numbers"] 38 | derive = ["knuffel-derive"] 39 | line-numbers = ["unicode-width"] 40 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 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 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 The knuffel Developers 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. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A [KDL](https://kdl.dev) file format parser with great error reporting and 2 | convenient derive macros. 3 | 4 | # About KDL 5 | 6 | To give you some background on the KDL format. Here is a small example: 7 | ```kdl 8 | foo 1 key="val" "three" { 9 | bar 10 | (role)baz 1 2 11 | } 12 | ``` 13 | 14 | Here is what are annotations for all the datum as described by the 15 | [specification] and this guide: 16 | ```text 17 | foo 1 "three" key="val" { ╮ 18 | ─┬─ ┬ ───┬─── ────┬──── │ 19 | │ │ │ ╰───── property (can be multiple) │ 20 | │ │ │ │ 21 | │ ╰────┴────────────── arguments │ 22 | │ │ 23 | └── node name ├─ node "foo", with 24 | │ "bar" and "baz" 25 | bar │ being children 26 | (role)baz 1 2 │ 27 | ──┬─ │ 28 | └────── type name for node named "baz" │ 29 | } ╯ 30 | ``` 31 | (note, the order of properties doesn't matter as well as the order of 32 | properties with respect to arguments, so I've moved arguments to have less 33 | intersections for the arrows) 34 | 35 | # Usage 36 | 37 | Most common usage of this library is using `derive` and [parse] function: 38 | ```rust 39 | #[derive(knuffel::Decode)] 40 | enum TopLevelNode { 41 | Route(Route), 42 | Plugin(Plugin), 43 | } 44 | 45 | #[derive(knuffel::Decode)] 46 | struct Route { 47 | #[knuffel(argument)] 48 | path: String, 49 | #[knuffel(children(name="route"))] 50 | subroutes: Vec, 51 | } 52 | 53 | #[derive(knuffel::Decode)] 54 | struct Plugin { 55 | #[knuffel(argument)] 56 | name: String, 57 | #[knuffel(property)] 58 | url: String, 59 | } 60 | 61 | # fn main() -> miette::Result<()> { 62 | let config = knuffel::parse::>("example.kdl", r#" 63 | route "/api" { 64 | route "/api/v1" 65 | } 66 | plugin "http" url="https://example.org/http" 67 | "#)?; 68 | # Ok(()) 69 | # } 70 | ``` 71 | 72 | This parses into a vector of nodes as enums `TopLevelNode`, but you also use some node as a root of the document if there is no properties and arguments declared: 73 | ```rust,ignore 74 | #[derive(knuffel::Decode)] 75 | struct Document { 76 | #[knuffel(child, unwrap(argument))] 77 | version: Option, 78 | #[knuffel(children(name="route"))] 79 | routes: Vec, 80 | #[knuffel(children(name="plugin"))] 81 | plugins: Vec, 82 | } 83 | 84 | let config = parse::("example.kdl", r#" 85 | version "2.0" 86 | route "/api" { 87 | route "/api/v1" 88 | } 89 | plugin "http" url="https://example.org/http" 90 | "#)?; 91 | ``` 92 | 93 | See description of [Decode](derive@Decode) and 94 | [DecodeScalar](derive@DecodeScalar) for the full 95 | reference on allowed attributes and parse modes. 96 | 97 | # Errors 98 | 99 | This crate publishes nice errors, like this: 100 | 101 | 
102 | Screenshot of error. Here is how narratable printer would print the error:
103 | Error: single char expected after `Alt+`
104 |     Diagnostic severity: error
105 | \
106 | Begin snippet for test.kdl starting at line 17, column 1
107 | \
108 | snippet line 17:     }
109 | snippet line 18:     key "Alt+" mode="normal" {
110 |     label starting at line 18, column 10: invalid value
111 | snippet line 19:         move-focus "left"
112 | 113 | 114 | To make them working, [miette]'s "fancy" feature must be enabled in the final 115 | application's `Cargo.toml`: 116 | ```toml 117 | [dependencies] 118 | miette = { version="4.3.0", features=["fancy"] } 119 | ``` 120 | And the error returned from parser should be converted to [miette::Report] and 121 | printed with debugging handler. The most manual way to do that is: 122 | ```rust 123 | # #[derive(knuffel::Decode, Debug)] 124 | # struct Config {} 125 | # let file_name = "1.kdl"; 126 | # let text = ""; 127 | let config = match knuffel::parse::(file_name, text) { 128 | Ok(config) => config, 129 | Err(e) => { 130 | println!("{:?}", miette::Report::new(e)); 131 | std::process::exit(1); 132 | } 133 | }; 134 | ``` 135 | But usually function that returns `miette::Result` is good enough: 136 | ```rust,no_run 137 | # use std::fs; 138 | # #[derive(knuffel::Decode)] 139 | # struct Config {} 140 | use miette::{IntoDiagnostic, Context}; 141 | 142 | fn parse_config(path: &str) -> miette::Result { 143 | let text = fs::read_to_string(path).into_diagnostic() 144 | .wrap_err_with(|| format!("cannot read {:?}", path))?; 145 | Ok(knuffel::parse(path, &text)?) 146 | } 147 | fn main() -> miette::Result<()> { 148 | let config = parse_config("my.kdl")?; 149 | # Ok(()) 150 | } 151 | ``` 152 | 153 | See [miette guide] for other ways of configuring error output. 154 | 155 | # The Name 156 | 157 | KDL is pronounced as cuddle. "Knuffel" means the same as cuddle in Dutch. 158 | 159 | 160 | License 161 | ======= 162 | 163 | Licensed under either of 164 | 165 | * Apache License, Version 2.0, 166 | (./LICENSE-APACHE or ) 167 | * MIT license (./LICENSE-MIT or ) 168 | at your option. 169 | 170 | Contribution 171 | ------------ 172 | 173 | Unless you explicitly state otherwise, any contribution intentionally 174 | submitted for inclusion in the work by you, as defined in the Apache-2.0 175 | license, shall be dual licensed as above, without any additional terms or 176 | conditions. 177 | 178 | 179 | [specification]: https://github.com/kdl-org/kdl/blob/main/SPEC.md 180 | [miette]: https://docs.rs/miette/ 181 | [miette guide]: https://docs.rs/miette/latest/miette/#-handler-options 182 | -------------------------------------------------------------------------------- /derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "knuffel-derive" 3 | version = "3.2.0" 4 | edition = "2021" 5 | description = """ 6 | A derive implementation for knuffel KDL parser 7 | """ 8 | license = "MIT/Apache-2.0" 9 | keywords = ["kdl", "configuration"] 10 | homepage = "https://github.com/tailhook/knuffel" 11 | documentation = "https://docs.rs/knuffel" 12 | readme = "README.md" 13 | 14 | [lib] 15 | proc_macro = true 16 | 17 | [dependencies] 18 | heck = {version="0.4.1", features=["unicode"]} 19 | syn = {version="1.0.81", features=["full", "extra-traits"]} 20 | quote = "1.0.10" 21 | proc-macro2 = "1.0.32" 22 | proc-macro-error = "1.0.4" 23 | 24 | [dev-dependencies] 25 | knuffel = { path=".." } 26 | miette = { version="5.1.1", features=["fancy"] } 27 | -------------------------------------------------------------------------------- /derive/README.md: -------------------------------------------------------------------------------- 1 | This is derive implementation to make working with [KDL](https://kdl.dev) file 2 | format convenient that works with [knuffel] parser library. 3 | 4 | See more in the documentation of [knuffel] library itself. 5 | 6 | [knuffel]: https://docs.rs/knuffel 7 | 8 | 9 | License 10 | ======= 11 | 12 | Licensed under either of 13 | 14 | * Apache License, Version 2.0, 15 | (./LICENSE-APACHE or ) 16 | * MIT license (./LICENSE-MIT or ) 17 | at your option. 18 | 19 | Contribution 20 | ------------ 21 | 22 | Unless you explicitly state otherwise, any contribution intentionally 23 | submitted for inclusion in the work by you, as defined in the Apache-2.0 24 | license, shall be dual licensed as above, without any additional terms or 25 | conditions. 26 | -------------------------------------------------------------------------------- /derive/derive_decode.md: -------------------------------------------------------------------------------- 1 | The derive is the most interesting part of the `knuffel` libary. 2 | 3 | # Overview 4 | 5 | This trait and derive is used to decode a single node of the KDL document. 6 | 7 | There are few things that derive can be implemented for: 8 | 1. Structure with named or unnamed fields. Most of the text here is about this 9 | case. 10 | 2. A single-field [new type] wrapper around such structure `Wrapper(Inner)` 11 | where `Inner` implements `Decode` (this is a tuple struct with single 12 | argument without annotations). 13 | 3. Unit struct 14 | 4. [Enum](#enums), where each variant corresponds to a specific node name 15 | 16 | [new type]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html 17 | 18 | There are three kinds of things can fit structure fields that must be 19 | annotated appropriately: [arguments](#arguments), [properties](#properties) 20 | and [children](#children). Unlike in `serde` and similar projects, 21 | non-annotated fields are not decoded from source and are filled with 22 | [`std::default::Default`]. 23 | 24 | All annotations are enclosed by `#[knuffel(..)]` attribute. 25 | 26 | Both arguments and properties can decode [scalars](#scalars). 27 | 28 | If structure only has `child` and `children` fields (see [below](#children)) it 29 | can be used as a root document (the output of [`knuffel::parse`]). Or root of 30 | the document can be `Vec where T: Decode`. 31 | 32 | [`knuffel::parse`]: fn.parse.html 33 | 34 | Note: node name is usually not used in the structure decoding node, it's 35 | matched either in parent or in an [enum](#enums). 36 | 37 | # Arguments 38 | 39 | Arguments are scalar values that are usually written on the same line with the 40 | node name and are positional, i.e. they parsed and put into structure fields 41 | in order. 42 | 43 | The two Rust attributes to parse arguments are: 44 | * `argument` -- to parse single argument 45 | * `arguments` -- to parse sequence of arguments 46 | 47 | Note: order of the structure fields matter. Fields marked as `arguments` can 48 | be used only once and cannot be followed by `argument`. 49 | 50 | For example, the following node: 51 | ```kdl 52 | node "arg1" true 1 22 333 53 | ``` 54 | ... can be parsed into the following structure: 55 | ```rust 56 | #[derive(knuffel::Decode)] 57 | struct MyNode { 58 | #[knuffel(argument)] 59 | first: String, 60 | #[knuffel(argument)] 61 | second: bool, 62 | #[knuffel(arguments)] 63 | numbers: Vec, 64 | } 65 | ``` 66 | 67 | Arguments can be optional: 68 | ```rust 69 | #[derive(knuffel::Decode)] 70 | struct MyNode { 71 | #[knuffel(argument)] 72 | first: Option, 73 | #[knuffel(argument)] 74 | second: Option, 75 | } 76 | ``` 77 | 78 | In this case attribute may not exists: 79 | ```kdl 80 | node "arg1" // no `second` argument is okay 81 | ``` 82 | Or may be `null`: 83 | ```kdl 84 | node null null 85 | ``` 86 | 87 | Note: due to limitations of the procedural macros in Rust, optional arguments 88 | must use `Option` in this specific notation. Other variations like these: 89 | ``` 90 | use std::option::Option as Opt; 91 | #[derive(knuffel::Decode)] 92 | struct MyNode { 93 | #[knuffel(argument)] 94 | first: ::std::option::Option, 95 | #[knuffel(argument)] 96 | second: Opt, 97 | } 98 | ``` 99 | Do not work (they will always require `null` arguments). 100 | 101 | The field marked as `arguments` can have any type that implements `FromIterator where T: DecodeScalar`. 102 | 103 | See [Scalars](#scalars) and [Common Attributes](#common-attributes) for more 104 | information on decoding of values. 105 | 106 | # Properties 107 | 108 | Properties are scalar values that are usually written on the same line 109 | prepended with name and equals `=` sign. They are parsed regardless of order, 110 | although if the same argument is specified twice the latter value overrides 111 | former. 112 | 113 | The two Rust attributes to parse properties are: 114 | * `property` -- to parse single argument 115 | * `properties` -- to parse sequence of arguments 116 | 117 | Note: order of the structure fields matter. Fields marked as `properties` can 118 | be used only once and cannot be followed by `property`. 119 | 120 | For example, the following node: 121 | ```kdl 122 | node name="arg1" enabled=true a=1 b=2 c=3 123 | ``` 124 | 125 | Can be parsed into the following structure: 126 | ```rust 127 | # use std::collections::HashMap; 128 | #[derive(knuffel::Decode)] 129 | struct MyNode { 130 | #[knuffel(property)] 131 | name: String, 132 | #[knuffel(property)] 133 | enabled: bool, 134 | #[knuffel(properties)] 135 | numbers: HashMap, 136 | } 137 | ``` 138 | 139 | Properties can be optional: 140 | ```rust 141 | #[derive(knuffel::Decode)] 142 | struct MyNode { 143 | #[knuffel(property)] 144 | name: Option, 145 | #[knuffel(property)] 146 | enabled: Option, 147 | } 148 | ``` 149 | 150 | In this case property may not exists or may be set to `null`: 151 | ```kdl 152 | node name=null 153 | ``` 154 | 155 | Note: due to limitations of the procedural macros in Rust, optional properties 156 | must use `Option` in this specific notation. Other variations like this: 157 | ```rust 158 | use std::option::Option as Opt; 159 | #[derive(knuffel::Decode)] 160 | struct MyNode { 161 | #[knuffel(property)] 162 | name: ::std::option::Option, 163 | #[knuffel(property)] 164 | enabled: Opt, 165 | } 166 | ``` 167 | Do not work (they will always require `property=null`). 168 | 169 | By default, field name is renamed to use `kebab-case` in KDL file. So field 170 | defined like this: 171 | ```rust 172 | #[derive(knuffel::Decode)] 173 | struct MyNode { 174 | #[knuffel(property)] 175 | plugin_name: String, 176 | } 177 | ``` 178 | Parses the following: 179 | ```kdl 180 | node plugin-name="my_plugin" 181 | ``` 182 | 183 | To rename a property in the source use `name=`: 184 | ```rust 185 | #[derive(knuffel::Decode)] 186 | struct MyNode { 187 | #[knuffel(property(name="pluginName"))] 188 | name: String, 189 | } 190 | ``` 191 | 192 | The field marked as `properties` can have any type that implements 193 | `FromIterator<(K, V)> where K: FromStr, V: DecodeScalar`. 194 | 195 | See [Scalars](#scalars) and [Common Attributes](#common-attributes) for more 196 | information on decoding of values. 197 | 198 | # Scalars 199 | 200 | There are additional attributes that define how scalar values are parsed: 201 | * `str` -- uses [`FromStr`](std::str::FromStr) trait. 202 | * `bytes` -- decodes binary strings, either by decoding `base64` if the `(base64)` type is specified in the source or by encoding string into `utf-8` if no type is specified. This is required since 203 | * `default` -- described in [Common Attrbites](#common-attributes) section 204 | since it applies to nodes (non-scalar values) too. 205 | 206 | All of them work on [properties](#properties) and [arguments](#arguments). 207 | 208 | ## Parsing Strings 209 | 210 | The `str` marker is very useful for types coming from other libraries that 211 | aren't supported by `knuffel` directly. 212 | 213 | For example: 214 | ```rust 215 | #[derive(knuffel::Decode)] 216 | struct Server { 217 | #[knuffel(property, str)] 218 | listen: std::net::SocketAddr, 219 | } 220 | ``` 221 | This will parse listening addresses that Rust stdlib supports, like this: 222 | ```kdl 223 | server listen="127.0.0.1:8080" 224 | ``` 225 | 226 | ## Parsing Bytes 227 | 228 | Since in Rust sequence of ints and buffer of bytes cannot be distinguished on 229 | the type level, there is a `bytes` marker that can be applied to parse scalar 230 | as byte buffer. 231 | 232 | For example: 233 | ```rust 234 | #[derive(knuffel::Decode)] 235 | struct Response { 236 | #[knuffel(argument, bytes)] 237 | body: Vec, 238 | } 239 | ``` 240 | 241 | The value of `body` can be specified in two ways. Using `base64` string 242 | (this requires `base64` feature enabled which is default): 243 | ```kdl 244 | response (base64)"SGVsbG8gd29ybGQh" 245 | ``` 246 | 247 | While using base64 allows encoding any binary data, strings may also be used 248 | and end up using utf-8 encoded in buffer. So the KDL above is equivalent to 249 | the following: 250 | ```kdl 251 | response "Hello world!" 252 | ``` 253 | 254 | The field don't have to be `Vec`, it may be any type that has 255 | `TryInto>` (and hence also `Into>`) implementation. For 256 | example 257 | [`bstr::BString`](https://docs.rs/bstr/latest/bstr/struct.BString.html) and 258 | [`bytes::Bytes`](https://docs.rs/bytes/latest/bytes/struct.Bytes.html) work too. 259 | 260 | 261 | # Children 262 | 263 | Nodes are fundamental blocks for data hierarchy in KDL. Here are some examples 264 | of nodes: 265 | ```kdl 266 | node1 "x" "y" 267 | (my_type)node2 prop="value" { 268 | node3 1 269 | node4 2 270 | } 271 | ``` 272 | There are four nodes in this example. Nodes typically start with identifier 273 | which is called node name. Similarly to scalars, nodes can be prepended by type 274 | name in parenthesis. The nodes `node3` and `node4` are children nodes with 275 | respect to `node2`. So when `node2` is decoded its `child` and `children` 276 | directives are interpreted to match `node3` and `node4`. 277 | 278 | The two Rust attributes to parse children are: 279 | * `child` -- to parse single child 280 | * `children` -- to parse sequence of children 281 | 282 | For example the follwing KDL: 283 | ```kdl 284 | node { 285 | version 1 286 | plugin "xxx" 287 | datum "yyy" 288 | } 289 | ``` 290 | ... can be parsed by into the following structures: 291 | ```rust 292 | #[derive(knuffel::Decode)] 293 | enum Setting { 294 | Plugin(#[knuffel(argument)] String), 295 | Datum(#[knuffel(argument)] String), 296 | } 297 | #[derive(knuffel::Decode)] 298 | struct Version { 299 | #[knuffel(argument)] 300 | number: u32 301 | } 302 | #[derive(knuffel::Decode)] 303 | struct MyNode { 304 | #[knuffel(child)] 305 | version: Version, 306 | #[knuffel(children)] 307 | settings: Vec 308 | } 309 | ``` 310 | 311 | There is another form of children which is `children(name="something")`, that 312 | allows filtering nodes by name: 313 | ```rust 314 | #[derive(knuffel::Decode)] 315 | struct NamedNode { 316 | #[knuffel(argument)] 317 | name: u32 318 | } 319 | #[derive(knuffel::Decode)] 320 | struct MyNode { 321 | #[knuffel(children(name="plugin"))] 322 | plugins: Vec, 323 | #[knuffel(children(name="datum"))] 324 | data: Vec, 325 | } 326 | ``` 327 | 328 | Note: we use same node type for `plugin` and `datum` nodes. Generally nodes do 329 | not match on the actual node names, it's the job of the parent node to sort 330 | out their children into the right buckets. Also see [Enums](#enums). 331 | 332 | ## Boolean Child Fields 333 | 334 | Sometimes you want to track just the presence of the child in the node. 335 | 336 | For example this document: 337 | ```kdl 338 | plugin "first" { 339 | auto-start 340 | } 341 | plugin "second" 342 | ``` 343 | ... can be parsed into the list of the following structures: 344 | ```rust 345 | #[derive(knuffel::Decode)] 346 | struct Plugin { 347 | #[knuffel(child)] 348 | auto_start: bool, 349 | } 350 | ``` 351 | And in this case `auto-start` node may be omitted without an error even 352 | though it's not wrapped into an `Option`. 353 | 354 | No arguments, properties and children are allowed in the boolean nodes. 355 | 356 | Note: due to limitations of the procedural macros in Rust, boolean children 357 | must use `bool` in this specific notation. If you shadow `bool` type by some 358 | import the results are undefined (knuffel will still think it's bool node, but 359 | it may not work). 360 | 361 | 362 | ## Unwrapping 363 | 364 | The `unwrap` attribute for `child` allows adding extra children in the KDL 365 | document that aren't represented in the final structure, but they play 366 | important role in making document readable. 367 | 368 | It works by transforming the following: 369 | ```rust,ignore 370 | #[derive(knuffel::Decode)] 371 | struct Node { 372 | #[knuffel(child, unwrap(/* attributes */))] 373 | field: String, 374 | } 375 | ``` 376 | ... into something like this: 377 | ``` 378 | #[derive(knuffel::Decode)] 379 | struct TmpChild { 380 | #[knuffel(/* attributes */)] 381 | field: String, 382 | } 383 | #[derive(knuffel::Decode)] 384 | struct Node { 385 | #[knuffel(child)] 386 | field: TmpChild, 387 | } 388 | ``` 389 | ... and then unpacks `TmpChild` to put target type into the field. 390 | 391 | Most of the attributes can be used in place of `/* attributes */`. Including: 392 | 1. `argument` (the most common, see [below](#properties-become-children)) and 393 | `arguments` 394 | 2. `property` (usually in the form of `property(name="other_name")` to 395 | avoid repetitive KDL) and `properties` 396 | 3. `child` and `children` (see example [below](#grouping-things)) 397 | 398 | Following are some nice examples of using `unwrap`. 399 | 400 | ### Properties Become Children 401 | 402 | In nodes with many properties it might be convenient to put them into children instead. 403 | 404 | So instead of this: 405 | ```kdl 406 | plugin name="my-plugin" url="https://example.com" {} 407 | ``` 408 | ... users can write this: 409 | ```kdl 410 | plugin { 411 | name "my-plugin" 412 | url "https://example.com" 413 | } 414 | ``` 415 | 416 | Here is the respective Rust structure: 417 | ```rust 418 | #[derive(knuffel::Decode)] 419 | struct Plugin { 420 | #[knuffel(child, unwrap(argument))] 421 | name: String, 422 | #[knuffel(child, unwrap(argument))] 423 | url: String, 424 | } 425 | ``` 426 | 427 | You can read this like: `name` field parses a child that contains a single 428 | argument of type `String`. 429 | 430 | ### Grouping Things 431 | 432 | Sometimes instead of different kinds of nodes scattered around you may want to 433 | group them. 434 | 435 | So instead of this: 436 | ```kdl 437 | plugin "a" 438 | file "aa" 439 | plugin "b" 440 | file "bb" 441 | ``` 442 | You nave a KDL document like this: 443 | ```kdl 444 | plugins { 445 | plugin "a" 446 | plugin "b" 447 | } 448 | files { 449 | file "aa" 450 | file "bb" 451 | } 452 | ``` 453 | This can be parsed into the following structure: 454 | ```rust 455 | # #[derive(knuffel::Decode)] struct Plugin {} 456 | # #[derive(knuffel::Decode)] struct File {} 457 | #[derive(knuffel::Decode)] 458 | struct Document { 459 | #[knuffel(child, unwrap(children(name="plugin")))] 460 | plugins: Vec, 461 | #[knuffel(child, unwrap(children(name="file")))] 462 | files: Vec, 463 | } 464 | ``` 465 | 466 | You can read this like: `plugins` field parses a child that contains a set of 467 | children named `plugin`. 468 | 469 | 470 | ## Root Document 471 | 472 | Any structure that has only fields marked as `child` and `children` or 473 | unmarked ones, can be used as the root of the document. 474 | 475 | For example, this structure can: 476 | ```rust 477 | # #[derive(knuffel::Decode)] 478 | # struct NamedNode { #[knuffel(argument)] name: u32 } 479 | #[derive(knuffel::Decode)] 480 | struct MyNode { 481 | #[knuffel(child, unwrap(argument))] 482 | version: u32, 483 | #[knuffel(children(name="plugin"))] 484 | plugins: Vec, 485 | #[knuffel(children(name="datum"))] 486 | data: Vec, 487 | } 488 | ``` 489 | On the other hand this one can **not** because it contains a `property`: 490 | ```rust 491 | # #[derive(knuffel::Decode)] 492 | # struct NamedNode { #[knuffel(argument)] name: u32 } 493 | #[derive(knuffel::Decode)] 494 | struct MyNode { 495 | #[knuffel(property)] 496 | version: u32, 497 | #[knuffel(children(name="plugin"))] 498 | plugins: Vec, 499 | #[knuffel(children(name="datum"))] 500 | data: Vec, 501 | } 502 | ``` 503 | Note: attributes in the `unwrap` have no influence on whether structure can be 504 | used to decode document. 505 | 506 | Technically [DecodeChildren](traits/trait.DecodeChildren.html) trait will be 507 | implemented for the structures that can be used as documents. 508 | 509 | 510 | # Common Attributes 511 | 512 | ## Default 513 | 514 | `default` attribute may be applied to any [arguments](#arguments), 515 | [properties](#properties) or [children](#children). 516 | 517 | There are two forms of it. Marker attribute: 518 | ```rust 519 | #[derive(knuffel::Decode)] 520 | struct MyNode { 521 | #[knuffel(property, default)] 522 | first: String, 523 | } 524 | ``` 525 | Which means that `std::default::Default` should be used if field was not 526 | filled otherwise (i.e. no such property encountered). 527 | 528 | Another form is `default=value`: 529 | ```rust 530 | #[derive(knuffel::Decode)] 531 | struct MyNode { 532 | #[knuffel(property, default="unnamed".into())] 533 | name: String, 534 | } 535 | ``` 536 | Any Rust expression can be used in this case. 537 | 538 | Note, for optional properties `Some` should be included in the default value. 539 | And for scalar values their value can be overriden by using `null`. The 540 | definition like this: 541 | ```rust 542 | #[derive(knuffel::Decode)] 543 | struct MyNode { 544 | #[knuffel(property, default=Some("unnamed".into()))] 545 | name: Option, 546 | } 547 | ``` 548 | Parses these two nodes differently: 549 | ```kdl 550 | node name=null 551 | node 552 | ``` 553 | Will yield: 554 | ```rust 555 | # struct MyNode { name: Option } 556 | let _ = vec![ 557 | MyNode { name: None }, 558 | MyNode { name: Some(String::from("unnamed")) }, 559 | ]; 560 | ``` 561 | 562 | # Flatten 563 | 564 | Similarly to `flatten` flag in `serde`, this allows factoring out some 565 | properties or children into another structure. 566 | 567 | For example: 568 | ```rust 569 | #[derive(knuffel::Decode, Default)] 570 | struct Common { 571 | #[knuffel(child, unwrap(argument))] 572 | name: Option, 573 | #[knuffel(child, unwrap(argument))] 574 | description: Option 575 | } 576 | #[derive(knuffel::Decode)] 577 | struct Plugin { 578 | #[knuffel(flatten(child))] 579 | common: Common, 580 | #[knuffel(child, unwrap(argument))] 581 | url: String, 582 | } 583 | ``` 584 | This will parse the following: 585 | ```kdl 586 | plugin { 587 | name "my-plugin" 588 | description "Some example plugin" 589 | url "https://example.org/plugin" 590 | } 591 | ``` 592 | 593 | There are few limitations of the `flatten`: 594 | 1. All fields in target structure must be optional. 595 | 2. The target structure must implement [`Default`](std::default::Default) 596 | 3. Only children an properties can be factored out, not arguments in current 597 | implementation 598 | 4. You must specify which directives can be used in the target structure 599 | (i.e. `flatten(child, children, property, properties)`) and if `children` 600 | or `properties` are forwarded to the target structure, no more children 601 | and property attributes can be used in this structure following the 602 | `flatten` attribute. 603 | 604 | We may lift some of these limitations later. 605 | 606 | Technically [DecodePartial](traits/trait.DecodePartial.html) trait will be 607 | implemented for the strucutures that can be used with the `flatten` attribute. 608 | 609 | 610 | # Special Values 611 | 612 | ## Type Name 613 | 614 | Here is the example of the node with the type name (the name in parens): 615 | ```kdl 616 | (text)document name="New Document" { } 617 | ``` 618 | 619 | By default knuffel doesn't allow type names for nodes as these are quite rare. 620 | 621 | To allow type names on specific node and to have the name stored use 622 | `type_name` attribute: 623 | ```rust 624 | #[derive(knuffel::Decode)] 625 | struct Node { 626 | #[knuffel(type_name)] 627 | type_name: String, 628 | } 629 | ``` 630 | Type name can be optional. 631 | 632 | The field that is a target of `type_name` can be any type that implements 633 | `FromStr`. This might be used to validate node type: 634 | ```rust 635 | pub enum PluginType { 636 | Builtin, 637 | External, 638 | } 639 | 640 | impl std::str::FromStr for PluginType { 641 | type Err = Box; 642 | fn from_str(s: &str) -> Result { 643 | match s { 644 | "builtin" => Ok(PluginType::Builtin), 645 | "external" => Ok(PluginType::External), 646 | _ => Err("Plugin type name must be `builtin` or `external`")?, 647 | } 648 | } 649 | } 650 | 651 | #[derive(knuffel::Decode)] 652 | struct Node { 653 | #[knuffel(type_name)] 654 | type_name: PluginType, 655 | } 656 | ``` 657 | 658 | ## Node Name 659 | 660 | In knuffel, it's common that parent node, document or enum type checks the node name of the node, and node name is not stored or validated in the strucuture. 661 | 662 | But for the cases where you need it, it's possible to store too: 663 | ```rust 664 | #[derive(knuffel::Decode)] 665 | struct Node { 666 | #[knuffel(node_name)] 667 | node_name: String, 668 | } 669 | ``` 670 | 671 | You can use any type that implements `FromStr` to validate node name. Similarly to the example in the [type names](#type-name) section. 672 | 673 | Node name always exists so optional node_name is not supported. 674 | 675 | ## Spans 676 | 677 | The following definition: 678 | ```rust 679 | use knuffel::span::Span; // or LineSpan 680 | 681 | #[derive(knuffel::Decode)] 682 | #[knuffel(span_type=Span)] 683 | struct Node { 684 | #[knuffel(span)] 685 | span: Span, // This can be user type decoded from Span 686 | } 687 | ``` 688 | Puts position of the node in the source code into the `span` field. Span 689 | contains the whole node, starting from parenthesis that enclose type name if 690 | present otherwise node name. Includes node children if exists and semicolon or 691 | newline that ends the node (so includes any whitespace and coments before the 692 | newline if node ends by a newline, but doesn't include anything after 693 | semicolon). 694 | 695 | The span value might be different than one used for parsing. In this case, it 696 | should implement [`DecodeSpan`](traits/trait.DecodeSpan.html) trait. 697 | 698 | Independenly of whether you use custom span type, or built-in one, you have to 699 | specify `span_type` for the decoder, since there is no generic implementation 700 | of the `DecodeSpan` for any type. See [Span Type](#span-type) for more info 701 | 702 | # Enums 703 | 704 | Enums are used to differentiate nodes by name when multiple kinds of nodes are 705 | pushed to a single collection. 706 | 707 | For example, to parse the following list of actions: 708 | ```kdl 709 | create "xxx" 710 | print-string "yyy" line=2 711 | finish 712 | ``` 713 | The following enum might be used: 714 | ```rust 715 | # #[derive(knuffel::Decode)] struct PrintString {} 716 | #[derive(knuffel::Decode)] 717 | enum Action { 718 | Create(#[knuffel(argument)] String), 719 | PrintString(PrintString), 720 | Finish, 721 | #[knuffel(skip)] 722 | InternalAction, 723 | } 724 | ``` 725 | 726 | The following variants supported: 727 | 1. Single element tuple struct without arguments (`PrintString` in example), 728 | which forwards node parsing to the inner element. 729 | 2. Normal `argument`, `arguments`, `properties`, `children` fields (`Create` 730 | example) 731 | 3. Property fields with names `property(name="xxx")` 732 | 4. Unit structs, in this case no arguments, properties and children are 733 | expected in such node 734 | 5. Variant with `skip`, cannot be deserialized and can be in any form 735 | 736 | Enum variant names are matches against node names converted into `kebab-case`. 737 | 738 | # Container Attributes 739 | 740 | ## Span Type 741 | 742 | Usually generated implemenation is for any span type: 743 | ```rust,ignore 744 | impl Decode for MyStruct { 745 | # ... 746 | } 747 | ``` 748 | But if you want to use `span` argument, it's unlikely to be possible to 749 | implement `DecodeSpan` for any type. 750 | 751 | Use use `span_type=` for implemenation of specific type: 752 | ```rust 753 | use knuffel::span::Span; // or LineSpan 754 | 755 | #[derive(knuffel::Decode)] 756 | #[knuffel(span_type=Span)] 757 | struct MyStruct { 758 | #[knuffel(span)] 759 | span: Span, 760 | } 761 | ``` 762 | This will generate implementation like this: 763 | ```rust,ignore 764 | impl Decode for MyStruct { 765 | # ... 766 | } 767 | ``` 768 | 769 | See [Spans](#spans) section for more info about decoding spans. 770 | -------------------------------------------------------------------------------- /derive/derive_decode_scalar.md: -------------------------------------------------------------------------------- 1 | Currently `DecodeScalar` derive is only implemented for enums 2 | 3 | # Enums 4 | 5 | Only enums that contain no data are supported: 6 | ```rust 7 | #[derive(knuffel::DecodeScalar)] 8 | enum Color { 9 | Red, 10 | Blue, 11 | Green, 12 | InfraRed, 13 | } 14 | ``` 15 | 16 | This will match scalar values in `kebab-case`. For example, this node decoder: 17 | ``` 18 | # #[derive(knuffel::DecodeScalar)] 19 | # enum Color { Red, Blue, Green, InfraRed } 20 | #[derive(knuffel::Decode)] 21 | struct Document { 22 | #[knuffel(child, unwrap(arguments))] 23 | all_colors: Vec, 24 | } 25 | ``` 26 | 27 | Can be populated from the following text: 28 | ```kdl 29 | all-colors "red" "blue" "green" "infra-red" 30 | ``` 31 | -------------------------------------------------------------------------------- /derive/src/definition.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::mem; 3 | 4 | use proc_macro2::{TokenStream, Span}; 5 | use proc_macro_error::emit_error; 6 | use quote::quote; 7 | use syn::ext::IdentExt; 8 | use syn::parse::{Parse, ParseStream}; 9 | use syn::punctuated::Punctuated; 10 | use syn::spanned::Spanned; 11 | 12 | use crate::kw; 13 | 14 | pub enum Definition { 15 | UnitStruct(Struct), 16 | TupleStruct(Struct), 17 | NewType(NewType), 18 | Struct(Struct), 19 | Enum(Enum), 20 | } 21 | 22 | pub enum VariantKind { 23 | Unit, 24 | Nested { option: bool }, 25 | Tuple(Struct), 26 | Named(Struct), 27 | } 28 | 29 | pub enum ArgKind { 30 | Value { option: bool }, 31 | } 32 | 33 | #[derive(Debug, Clone)] 34 | pub enum FieldMode { 35 | Argument, 36 | Property { name: Option }, 37 | Arguments, 38 | Properties, 39 | Children { name: Option }, 40 | Child, 41 | Flatten(Flatten), 42 | Span, 43 | NodeName, 44 | TypeName, 45 | } 46 | 47 | pub enum FlattenItem { 48 | Child, 49 | Property, 50 | } 51 | 52 | #[derive(Debug, Clone)] 53 | pub struct Flatten { 54 | child: bool, 55 | property: bool, 56 | } 57 | 58 | #[derive(Debug, Clone)] 59 | pub enum DecodeMode { 60 | Normal, 61 | Str, 62 | Bytes, 63 | } 64 | 65 | #[derive(Debug)] 66 | pub enum Attr { 67 | Skip, 68 | DecodeMode(DecodeMode), 69 | FieldMode(FieldMode), 70 | Unwrap(FieldAttrs), 71 | Default(Option), 72 | SpanType(syn::Type), 73 | } 74 | 75 | #[derive(Debug, Clone)] 76 | pub struct FieldAttrs { 77 | pub mode: Option, 78 | pub decode: Option<(DecodeMode, Span)>, 79 | pub unwrap: Option>, 80 | pub default: Option>, 81 | } 82 | 83 | #[derive(Debug, Clone)] 84 | pub struct VariantAttrs { 85 | pub skip: bool, 86 | } 87 | 88 | 89 | #[derive(Clone)] 90 | pub enum AttrAccess { 91 | Indexed(usize), 92 | Named(syn::Ident), 93 | } 94 | 95 | #[derive(Clone)] 96 | pub struct Field { 97 | pub span: Span, 98 | pub attr: AttrAccess, 99 | pub tmp_name: syn::Ident, 100 | } 101 | 102 | pub struct SpanField { 103 | pub field: Field, 104 | } 105 | 106 | pub struct NodeNameField { 107 | pub field: Field, 108 | } 109 | 110 | pub struct TypeNameField { 111 | pub field: Field, 112 | pub option: bool, 113 | } 114 | 115 | pub struct Arg { 116 | pub field: Field, 117 | pub kind: ArgKind, 118 | pub decode: DecodeMode, 119 | pub default: Option>, 120 | pub option: bool, 121 | } 122 | 123 | pub struct VarArgs { 124 | pub field: Field, 125 | pub decode: DecodeMode, 126 | } 127 | 128 | pub struct Prop { 129 | pub field: Field, 130 | pub name: String, 131 | pub option: bool, 132 | pub decode: DecodeMode, 133 | pub flatten: bool, 134 | pub default: Option>, 135 | } 136 | 137 | pub struct VarProps { 138 | pub field: Field, 139 | pub decode: DecodeMode, 140 | } 141 | 142 | pub enum ChildMode { 143 | Normal, 144 | Flatten, 145 | Multi, 146 | Bool, 147 | } 148 | 149 | pub struct Child { 150 | pub field: Field, 151 | pub name: String, 152 | pub option: bool, 153 | pub mode: ChildMode, 154 | pub unwrap: Option>, 155 | pub default: Option>, 156 | } 157 | 158 | pub struct VarChildren { 159 | pub field: Field, 160 | pub unwrap: Option>, 161 | } 162 | 163 | pub enum ExtraKind { 164 | Auto, 165 | } 166 | 167 | pub struct ExtraField { 168 | pub field: Field, 169 | pub kind: ExtraKind, 170 | pub option: bool, 171 | } 172 | 173 | #[derive(Clone)] 174 | pub struct TraitProps { 175 | pub span_type: Option, 176 | } 177 | 178 | pub struct Struct { 179 | pub ident: syn::Ident, 180 | pub trait_props: TraitProps, 181 | pub generics: syn::Generics, 182 | pub spans: Vec, 183 | pub node_names: Vec, 184 | pub type_names: Vec, 185 | pub arguments: Vec, 186 | pub var_args: Option, 187 | pub properties: Vec, 188 | pub var_props: Option, 189 | pub has_arguments: bool, 190 | pub has_properties: bool, 191 | pub children: Vec, 192 | pub var_children: Option, 193 | pub extra_fields: Vec, 194 | } 195 | 196 | pub struct StructBuilder { 197 | pub ident: syn::Ident, 198 | pub trait_props: TraitProps, 199 | pub generics: syn::Generics, 200 | pub spans: Vec, 201 | pub node_names: Vec, 202 | pub type_names: Vec, 203 | pub arguments: Vec, 204 | pub var_args: Option, 205 | pub properties: Vec, 206 | pub var_props: Option, 207 | pub children: Vec, 208 | pub var_children: Option, 209 | pub extra_fields: Vec, 210 | } 211 | 212 | pub struct NewType { 213 | pub ident: syn::Ident, 214 | pub trait_props: TraitProps, 215 | pub generics: syn::Generics, 216 | pub option: bool, 217 | } 218 | 219 | pub struct Variant { 220 | pub ident: syn::Ident, 221 | pub name: String, 222 | pub kind: VariantKind, 223 | } 224 | 225 | pub struct Enum { 226 | pub ident: syn::Ident, 227 | pub trait_props: TraitProps, 228 | pub generics: syn::Generics, 229 | pub variants: Vec, 230 | } 231 | 232 | impl TraitProps { 233 | fn pick_from(attrs: &mut Vec<(Attr, Span)>) -> TraitProps { 234 | let mut props = TraitProps { 235 | span_type: None, 236 | }; 237 | for attr in mem::replace(attrs, Vec::new()) { 238 | match attr.0 { 239 | Attr::SpanType(ty) => { 240 | props.span_type = Some(ty); 241 | } 242 | _ => attrs.push(attr), 243 | } 244 | } 245 | return props; 246 | } 247 | } 248 | 249 | fn err_pair(s1: &Field, s2: &Field, t1: &str, t2: &str) 250 | -> syn::Error 251 | { 252 | let mut err = syn::Error::new(s1.span, t1); 253 | err.combine(syn::Error::new(s2.span, t2)); 254 | return err; 255 | } 256 | 257 | fn is_option(ty: &syn::Type) -> bool { 258 | matches!(ty, 259 | syn::Type::Path(syn::TypePath { 260 | qself: None, 261 | path: syn::Path { 262 | leading_colon: None, 263 | segments, 264 | }, 265 | }) 266 | if segments.len() == 1 && segments[0].ident == "Option" 267 | ) 268 | } 269 | 270 | fn is_bool(ty: &syn::Type) -> bool { 271 | matches!(ty, 272 | syn::Type::Path(syn::TypePath { qself: None, path }) 273 | if path.is_ident("bool") 274 | ) 275 | } 276 | 277 | impl Variant { 278 | fn new(ident: syn::Ident, _attrs: VariantAttrs, kind: VariantKind) 279 | -> syn::Result 280 | { 281 | let name = heck::ToKebabCase::to_kebab_case(&ident.unraw().to_string()[..]); 282 | Ok(Variant { 283 | ident, 284 | name, 285 | kind, 286 | }) 287 | } 288 | } 289 | 290 | impl Enum { 291 | fn new(ident: syn::Ident, attrs: Vec, 292 | generics: syn::Generics, 293 | src_variants: impl Iterator) 294 | -> syn::Result 295 | { 296 | let mut attrs = parse_attr_list(&attrs); 297 | let trait_props = TraitProps::pick_from(&mut attrs); 298 | if !attrs.is_empty() { 299 | for (_, span) in attrs { 300 | emit_error!(span, "unexpected container attribute"); 301 | } 302 | } 303 | 304 | let mut variants = Vec::new(); 305 | for var in src_variants { 306 | let mut attrs = VariantAttrs::new(); 307 | attrs.update(parse_attr_list(&var.attrs)); 308 | if attrs.skip { 309 | continue; 310 | } 311 | let kind = match var.fields { 312 | syn::Fields::Named(n) => { 313 | Struct::new(var.ident.clone(), 314 | trait_props.clone(), 315 | generics.clone(), 316 | n.named.into_iter()) 317 | .map(VariantKind::Named)? 318 | } 319 | syn::Fields::Unnamed(u) => { 320 | let tup = Struct::new( 321 | var.ident.clone(), 322 | trait_props.clone(), 323 | generics.clone(), 324 | u.unnamed.into_iter(), 325 | )?; 326 | if tup.all_fields().len() == 1 327 | && tup.extra_fields.len() == 1 328 | && matches!(tup.extra_fields[0].kind, ExtraKind::Auto) 329 | { 330 | // Single tuple variant without any defition means 331 | // the first field inside is meant to be full node 332 | // parser. 333 | VariantKind::Nested { 334 | option: tup.extra_fields[0].option, 335 | } 336 | } else { 337 | VariantKind::Tuple(tup) 338 | } 339 | } 340 | syn::Fields::Unit => { 341 | VariantKind::Unit 342 | } 343 | }; 344 | variants.push(Variant::new(var.ident, attrs, kind)?); 345 | } 346 | Ok(Enum { 347 | ident, 348 | trait_props, 349 | generics, 350 | variants, 351 | }) 352 | } 353 | } 354 | 355 | impl StructBuilder { 356 | pub fn new(ident: syn::Ident, 357 | trait_props: TraitProps, 358 | generics: syn::Generics) 359 | -> Self 360 | { 361 | StructBuilder { 362 | ident, 363 | trait_props, 364 | generics, 365 | spans: Vec::new(), 366 | node_names: Vec::new(), 367 | type_names: Vec::new(), 368 | arguments: Vec::new(), 369 | var_args: None::, 370 | properties: Vec::new(), 371 | var_props: None::, 372 | children: Vec::new(), 373 | var_children: None::, 374 | extra_fields: Vec::new(), 375 | } 376 | } 377 | pub fn build(self) -> Struct { 378 | Struct { 379 | ident: self.ident, 380 | trait_props: self.trait_props, 381 | generics: self.generics, 382 | spans: self.spans, 383 | node_names: self.node_names, 384 | type_names: self.type_names, 385 | has_arguments: 386 | !self.arguments.is_empty() || self.var_args.is_some(), 387 | has_properties: 388 | !self.properties.is_empty() || self.var_props.is_some(), 389 | arguments: self.arguments, 390 | var_args: self.var_args, 391 | properties: self.properties, 392 | var_props: self.var_props, 393 | children: self.children, 394 | var_children: self.var_children, 395 | extra_fields: self.extra_fields, 396 | } 397 | } 398 | pub fn add_field(&mut self, field: Field, is_option: bool, is_bool: bool, 399 | attrs: &FieldAttrs) 400 | -> syn::Result<&mut Self> 401 | { 402 | match &attrs.mode { 403 | Some(FieldMode::Argument) => { 404 | if let Some(prev) = &self.var_args { 405 | return Err(err_pair(&field, &prev.field, 406 | "extra `argument` after capture all `arguments`", 407 | "capture all `arguments` is defined here")); 408 | } 409 | self.arguments.push(Arg { 410 | field, 411 | kind: ArgKind::Value { option: is_option }, 412 | decode: attrs.decode.as_ref().map(|(v, _)| v.clone()) 413 | .unwrap_or(DecodeMode::Normal), 414 | default: attrs.default.clone(), 415 | option: is_option, 416 | }); 417 | } 418 | Some(FieldMode::Arguments) => { 419 | if let Some(prev) = &self.var_args { 420 | return Err(err_pair(&field, &prev.field, 421 | "only single `arguments` allowed", 422 | "previous `arguments` is defined here")); 423 | } 424 | self.var_args = Some(VarArgs { 425 | field, 426 | decode: attrs.decode.as_ref().map(|(v, _)| v.clone()) 427 | .unwrap_or(DecodeMode::Normal), 428 | }); 429 | } 430 | Some(FieldMode::Property { name }) => { 431 | if let Some(prev) = &self.var_props { 432 | return Err(err_pair(&field, &prev.field, 433 | "extra `property` after capture all `properties`", 434 | "capture all `properties` is defined here")); 435 | } 436 | let name = match (name, &field.attr) { 437 | (Some(name), _) => name.clone(), 438 | (None, AttrAccess::Named(name)) 439 | => heck::ToKebabCase::to_kebab_case(&name.unraw().to_string()[..]), 440 | (None, AttrAccess::Indexed(_)) => { 441 | return Err(syn::Error::new(field.span, 442 | "property must be named, try \ 443 | `property(name=\"something\")")); 444 | } 445 | }; 446 | self.properties.push(Prop { 447 | field, 448 | name, 449 | option: is_option, 450 | decode: attrs.decode.as_ref().map(|(v, _)| v.clone()) 451 | .unwrap_or(DecodeMode::Normal), 452 | flatten: false, 453 | default: attrs.default.clone(), 454 | }); 455 | } 456 | Some(FieldMode::Properties) => { 457 | if let Some(prev) = &self.var_props { 458 | return Err(err_pair(&field, &prev.field, 459 | "only single `properties` is allowed", 460 | "previous `properties` is defined here")); 461 | } 462 | self.var_props = Some(VarProps { 463 | field, 464 | decode: attrs.decode.as_ref().map(|(v, _)| v.clone()) 465 | .clone().unwrap_or(DecodeMode::Normal), 466 | }); 467 | } 468 | Some(FieldMode::Child) => { 469 | attrs.no_decode("children"); 470 | if let Some(prev) = &self.var_children { 471 | return Err(err_pair(&field, &prev.field, 472 | "extra `child` after capture all `children`", 473 | "capture all `children` is defined here")); 474 | } 475 | let name = match &field.attr { 476 | AttrAccess::Named(n) => { 477 | heck::ToKebabCase::to_kebab_case(&n.unraw().to_string()[..]) 478 | } 479 | AttrAccess::Indexed(_) => { 480 | return Err(syn::Error::new(field.span, 481 | "`child` is not allowed for tuple structs")); 482 | } 483 | }; 484 | self.children.push(Child { 485 | name, 486 | field, 487 | option: is_option, 488 | mode: if attrs.unwrap.is_none() && is_bool { 489 | ChildMode::Bool 490 | } else { 491 | ChildMode::Normal 492 | }, 493 | unwrap: attrs.unwrap.clone(), 494 | default: attrs.default.clone(), 495 | }); 496 | } 497 | Some(FieldMode::Children { name: Some(name) }) => { 498 | attrs.no_decode("children"); 499 | if let Some(prev) = &self.var_children { 500 | return Err(err_pair(&field, &prev.field, 501 | "extra `children(name=` after capture all `children`", 502 | "capture all `children` is defined here")); 503 | } 504 | self.children.push(Child { 505 | name: name.clone(), 506 | field, 507 | option: is_option, 508 | mode: ChildMode::Multi, 509 | unwrap: attrs.unwrap.clone(), 510 | default: attrs.default.clone(), 511 | }); 512 | } 513 | Some(FieldMode::Children { name: None }) => { 514 | attrs.no_decode("children"); 515 | if let Some(prev) = &self.var_children { 516 | return Err(err_pair(&field, &prev.field, 517 | "only single catch all `children` is allowed", 518 | "previous `children` is defined here")); 519 | } 520 | self.var_children = Some(VarChildren { 521 | field, 522 | unwrap: attrs.unwrap.clone(), 523 | }); 524 | } 525 | Some(FieldMode::Flatten(flatten)) => { 526 | if is_option { 527 | return Err(syn::Error::new(field.span, 528 | "optional flatten fields are not supported yet")); 529 | } 530 | attrs.no_decode("children"); 531 | if flatten.property { 532 | if let Some(prev) = &self.var_props { 533 | return Err(err_pair(&field, &prev.field, 534 | "extra `flatten(property)` after \ 535 | capture all `properties`", 536 | "capture all `properties` is defined here")); 537 | } 538 | self.properties.push(Prop { 539 | field: field.clone(), 540 | name: "".into(), // irrelevant 541 | option: is_option, 542 | decode: DecodeMode::Normal, 543 | flatten: true, 544 | default: None, 545 | }); 546 | } 547 | if flatten.child { 548 | if let Some(prev) = &self.var_children { 549 | return Err(err_pair(&field, &prev.field, 550 | "extra `flatten(child)` after \ 551 | capture all `children`", 552 | "capture all `children` is defined here")); 553 | } 554 | self.children.push(Child { 555 | name: "".into(), // unused 556 | field: field.clone(), 557 | option: is_option, 558 | mode: ChildMode::Flatten, 559 | unwrap: None, 560 | default: None, 561 | }); 562 | } 563 | } 564 | Some(FieldMode::Span) => { 565 | attrs.no_decode("span"); 566 | self.spans.push(SpanField { field }); 567 | } 568 | Some(FieldMode::NodeName) => { 569 | attrs.no_decode("node_name"); 570 | self.node_names.push(NodeNameField { field }); 571 | } 572 | Some(FieldMode::TypeName) => { 573 | attrs.no_decode("type_name"); 574 | self.type_names.push(TypeNameField { 575 | field, 576 | option: is_option, 577 | }); 578 | } 579 | None => { 580 | self.extra_fields.push(ExtraField { 581 | field, 582 | kind: ExtraKind::Auto, 583 | option: is_option, 584 | }); 585 | } 586 | } 587 | return Ok(self); 588 | } 589 | } 590 | 591 | impl Struct { 592 | fn new(ident: syn::Ident, trait_props: TraitProps, generics: syn::Generics, 593 | fields: impl Iterator) 594 | -> syn::Result 595 | { 596 | let mut bld = StructBuilder::new(ident, trait_props, generics); 597 | for (idx, fld) in fields.enumerate() { 598 | let mut attrs = FieldAttrs::new(); 599 | attrs.update(parse_attr_list(&fld.attrs)); 600 | let field = Field::new(&fld, idx); 601 | bld.add_field(field, is_option(&fld.ty), is_bool(&fld.ty), &attrs)?; 602 | } 603 | 604 | Ok(bld.build()) 605 | } 606 | pub fn all_fields(&self) -> Vec<&Field> { 607 | let mut res = Vec::new(); 608 | res.extend(self.spans.iter().map(|a| &a.field)); 609 | res.extend(self.node_names.iter().map(|a| &a.field)); 610 | res.extend(self.type_names.iter().map(|a| &a.field)); 611 | res.extend(self.arguments.iter().map(|a| &a.field)); 612 | res.extend(self.var_args.iter().map(|a| &a.field)); 613 | res.extend(self.properties.iter().map(|p| &p.field)); 614 | res.extend(self.var_props.iter().map(|p| &p.field)); 615 | res.extend(self.children.iter().map(|c| &c.field)); 616 | res.extend(self.var_children.iter().map(|c| &c.field)); 617 | res.extend(self.extra_fields.iter().map(|f| &f.field)); 618 | return res; 619 | } 620 | } 621 | 622 | impl Parse for Definition { 623 | fn parse(input: ParseStream) -> syn::Result { 624 | let mut attrs = input.call(syn::Attribute::parse_outer)?; 625 | let ahead = input.fork(); 626 | let _vis: syn::Visibility = ahead.parse()?; 627 | 628 | let lookahead = ahead.lookahead1(); 629 | if lookahead.peek(syn::Token![struct]) { 630 | let item: syn::ItemStruct = input.parse()?; 631 | attrs.extend(item.attrs); 632 | 633 | let mut attrs = parse_attr_list(&attrs); 634 | let trait_props = TraitProps::pick_from(&mut attrs); 635 | if !attrs.is_empty() { 636 | for (_, span) in attrs { 637 | emit_error!(span, "unexpected container attribute"); 638 | } 639 | } 640 | 641 | match item.fields { 642 | syn::Fields::Named(n) => { 643 | Struct::new(item.ident, trait_props, item.generics, 644 | n.named.into_iter()) 645 | .map(Definition::Struct) 646 | } 647 | syn::Fields::Unnamed(u) => { 648 | let tup = Struct::new( 649 | item.ident.clone(), 650 | trait_props.clone(), 651 | item.generics.clone(), 652 | u.unnamed.into_iter(), 653 | )?; 654 | if tup.all_fields().len() == 1 655 | && tup.extra_fields.len() == 1 656 | && matches!(tup.extra_fields[0].kind, ExtraKind::Auto) 657 | { 658 | Ok(Definition::NewType(NewType { 659 | ident: item.ident, 660 | trait_props, 661 | generics: item.generics, 662 | option: tup.extra_fields[0].option, 663 | })) 664 | } else { 665 | Ok(Definition::TupleStruct(tup)) 666 | } 667 | } 668 | syn::Fields::Unit => { 669 | Struct::new(item.ident, trait_props, item.generics, 670 | Vec::new().into_iter()) 671 | .map(Definition::UnitStruct) 672 | } 673 | } 674 | } else if lookahead.peek(syn::Token![enum]) { 675 | let item: syn::ItemEnum = input.parse()?; 676 | attrs.extend(item.attrs); 677 | Enum::new(item.ident, attrs, item.generics, 678 | item.variants.into_iter()) 679 | .map(Definition::Enum) 680 | } else { 681 | Err(lookahead.error()) 682 | } 683 | } 684 | } 685 | 686 | impl FieldAttrs { 687 | fn new() -> FieldAttrs { 688 | FieldAttrs { 689 | mode: None, 690 | decode: None, 691 | unwrap: None, 692 | default: None, 693 | } 694 | } 695 | fn update(&mut self, attrs: impl IntoIterator) { 696 | use Attr::*; 697 | 698 | for (attr, span) in attrs { 699 | match attr { 700 | FieldMode(mode) => { 701 | if self.mode.is_some() { 702 | emit_error!(span, 703 | "only single attribute that defines mode of the \ 704 | field is allowed. Perhaps you mean `unwrap`?"); 705 | } 706 | self.mode = Some(mode); 707 | } 708 | Unwrap(val) => { 709 | if self.unwrap.is_some() { 710 | emit_error!(span, "`unwrap` specified twice"); 711 | } 712 | self.unwrap = Some(Box::new(val)); 713 | } 714 | DecodeMode(mode) => { 715 | if self.decode.is_some() { 716 | emit_error!(span, 717 | "only single attribute that defines parser of the \ 718 | field is allowed"); 719 | } 720 | self.decode = Some((mode, span)); 721 | 722 | } 723 | Default(value) => { 724 | if self.default.is_some() { 725 | emit_error!(span, 726 | "only single default is allowed"); 727 | } 728 | self.default = Some(value); 729 | } 730 | _ => emit_error!(span, 731 | "this attribute is not supported on fields"), 732 | } 733 | } 734 | } 735 | 736 | fn no_decode(&self, element: &str) { 737 | if let Some((mode, span)) = self.decode.as_ref() { 738 | if self.unwrap.is_some() { 739 | emit_error!(span, 740 | "decode modes are not supported on {}", element; 741 | hint= span.clone() => "try putting decode mode \ 742 | into unwrap(.., {})", mode; 743 | ); 744 | } else { 745 | emit_error!(span, 746 | "decode modes are not supported on {}", element 747 | ); 748 | } 749 | } 750 | } 751 | } 752 | 753 | impl VariantAttrs { 754 | fn new() -> VariantAttrs { 755 | VariantAttrs { 756 | skip: false, 757 | } 758 | } 759 | fn update(&mut self, attrs: impl IntoIterator) { 760 | use Attr::*; 761 | 762 | for (attr, span) in attrs { 763 | match attr { 764 | Skip => self.skip = true, 765 | _ => emit_error!(span, "not supported on enum variants"), 766 | } 767 | } 768 | } 769 | } 770 | 771 | fn parse_attr_list(attrs: &[syn::Attribute]) -> Vec<(Attr, Span)> { 772 | let mut all = Vec::new(); 773 | for attr in attrs { 774 | if matches!(attr.style, syn::AttrStyle::Outer) && 775 | attr.path.is_ident("knuffel") 776 | 777 | { 778 | match attr.parse_args_with(parse_attrs) { 779 | Ok(attrs) => all.extend(attrs), 780 | Err(e) => emit_error!(e), 781 | } 782 | } 783 | } 784 | return all; 785 | } 786 | 787 | fn parse_attrs(input: ParseStream) 788 | -> syn::Result> 789 | { 790 | Punctuated::<_, syn::Token![,]>::parse_terminated_with( 791 | input, Attr::parse) 792 | } 793 | 794 | impl Attr { 795 | fn parse(input: ParseStream) -> syn::Result<(Self, Span)> { 796 | let span = input.span(); 797 | Self::_parse(input).map(|a| (a, span)) 798 | } 799 | fn _parse(input: ParseStream) -> syn::Result { 800 | let lookahead = input.lookahead1(); 801 | if lookahead.peek(kw::argument) { 802 | let _kw: kw::argument = input.parse()?; 803 | Ok(Attr::FieldMode(FieldMode::Argument)) 804 | } else if lookahead.peek(kw::arguments) { 805 | let _kw: kw::arguments = input.parse()?; 806 | Ok(Attr::FieldMode(FieldMode::Arguments)) 807 | } else if lookahead.peek(kw::property) { 808 | let _kw: kw::property = input.parse()?; 809 | let mut name = None; 810 | if !input.is_empty() && !input.lookahead1().peek(syn::Token![,]) { 811 | let parens; 812 | syn::parenthesized!(parens in input); 813 | let lookahead = parens.lookahead1(); 814 | if lookahead.peek(kw::name) { 815 | let _kw: kw::name = parens.parse()?; 816 | let _eq: syn::Token![=] = parens.parse()?; 817 | let name_lit: syn::LitStr = parens.parse()?; 818 | name = Some(name_lit.value()); 819 | } else { 820 | return Err(lookahead.error()) 821 | } 822 | } 823 | Ok(Attr::FieldMode(FieldMode::Property { name })) 824 | } else if lookahead.peek(kw::properties) { 825 | let _kw: kw::properties = input.parse()?; 826 | Ok(Attr::FieldMode(FieldMode::Properties)) 827 | } else if lookahead.peek(kw::children) { 828 | let _kw: kw::children = input.parse()?; 829 | let mut name = None; 830 | if !input.is_empty() && !input.lookahead1().peek(syn::Token![,]) { 831 | let parens; 832 | syn::parenthesized!(parens in input); 833 | let lookahead = parens.lookahead1(); 834 | if lookahead.peek(kw::name) { 835 | let _kw: kw::name = parens.parse()?; 836 | let _eq: syn::Token![=] = parens.parse()?; 837 | let name_lit: syn::LitStr = parens.parse()?; 838 | name = Some(name_lit.value()); 839 | } else { 840 | return Err(lookahead.error()) 841 | } 842 | } 843 | Ok(Attr::FieldMode(FieldMode::Children { name })) 844 | } else if lookahead.peek(kw::child) { 845 | let _kw: kw::child = input.parse()?; 846 | Ok(Attr::FieldMode(FieldMode::Child)) 847 | } else if lookahead.peek(kw::unwrap) { 848 | let _kw: kw::unwrap = input.parse()?; 849 | let parens; 850 | syn::parenthesized!(parens in input); 851 | let mut attrs = FieldAttrs::new(); 852 | let chunk = parens.call(parse_attrs)?; 853 | attrs.update(chunk); 854 | Ok(Attr::Unwrap(attrs)) 855 | } else if lookahead.peek(kw::skip) { 856 | let _kw: kw::skip = input.parse()?; 857 | Ok(Attr::Skip) 858 | } else if lookahead.peek(kw::str) { 859 | let _kw: kw::str = input.parse()?; 860 | Ok(Attr::DecodeMode(DecodeMode::Str)) 861 | } else if lookahead.peek(kw::bytes) { 862 | let _kw: kw::bytes = input.parse()?; 863 | Ok(Attr::DecodeMode(DecodeMode::Bytes)) 864 | } else if lookahead.peek(kw::flatten) { 865 | let _kw: kw::flatten = input.parse()?; 866 | let parens; 867 | syn::parenthesized!(parens in input); 868 | let items = Punctuated:::: 869 | parse_terminated(&parens)?; 870 | let mut flatten = Flatten { 871 | child: false, 872 | property: false, 873 | }; 874 | for item in items { 875 | match item { 876 | FlattenItem::Child => flatten.child = true, 877 | FlattenItem::Property => flatten.property = true, 878 | } 879 | } 880 | Ok(Attr::FieldMode(FieldMode::Flatten(flatten))) 881 | } else if lookahead.peek(kw::default) { 882 | let _kw: kw::default = input.parse()?; 883 | if !input.is_empty() && !input.lookahead1().peek(syn::Token![,]) { 884 | let _eq: syn::Token![=] = input.parse()?; 885 | let value: syn::Expr = input.parse()?; 886 | Ok(Attr::Default(Some(value))) 887 | } else { 888 | Ok(Attr::Default(None)) 889 | } 890 | } else if lookahead.peek(kw::span) { 891 | let _kw: kw::span = input.parse()?; 892 | Ok(Attr::FieldMode(FieldMode::Span)) 893 | } else if lookahead.peek(kw::node_name) { 894 | let _kw: kw::node_name = input.parse()?; 895 | Ok(Attr::FieldMode(FieldMode::NodeName)) 896 | } else if lookahead.peek(kw::type_name) { 897 | let _kw: kw::type_name = input.parse()?; 898 | Ok(Attr::FieldMode(FieldMode::TypeName)) 899 | } else if lookahead.peek(kw::span_type) { 900 | let _kw: kw::span_type = input.parse()?; 901 | let _eq: syn::Token![=] = input.parse()?; 902 | let ty: syn::Type = input.parse()?; 903 | Ok(Attr::SpanType(ty)) 904 | } else { 905 | Err(lookahead.error()) 906 | } 907 | } 908 | } 909 | 910 | impl Parse for FlattenItem { 911 | fn parse(input: ParseStream) -> syn::Result { 912 | let lookahead = input.lookahead1(); 913 | if lookahead.peek(kw::child) { 914 | let _kw: kw::child = input.parse()?; 915 | Ok(FlattenItem::Child) 916 | } else if lookahead.peek(kw::property) { 917 | let _kw: kw::property = input.parse()?; 918 | Ok(FlattenItem::Property) 919 | } else { 920 | Err(lookahead.error()) 921 | } 922 | } 923 | } 924 | 925 | impl Field { 926 | pub fn new_named(name: &syn::Ident) -> Field { 927 | Field { 928 | span: name.span(), 929 | attr: AttrAccess::Named(name.clone()), 930 | tmp_name: name.clone(), 931 | } 932 | } 933 | fn new(field: &syn::Field, idx: usize) -> Field { 934 | field.ident.as_ref() 935 | .map(|id| Field { 936 | span: field.span(), 937 | attr: AttrAccess::Named(id.clone()), 938 | tmp_name: id.clone(), 939 | }) 940 | .unwrap_or_else(|| Field { 941 | span: field.span(), 942 | attr: AttrAccess::Indexed(idx), 943 | tmp_name: syn::Ident::new( 944 | &format!("field{}", idx), 945 | Span::mixed_site(), 946 | ), 947 | }) 948 | } 949 | pub fn from_self(&self) -> TokenStream { 950 | match &self.attr { 951 | AttrAccess::Indexed(idx) => quote!(self.#idx), 952 | AttrAccess::Named(name) => quote!(self.#name), 953 | } 954 | } 955 | pub fn is_indexed(&self) -> bool { 956 | matches!(self.attr, AttrAccess::Indexed(_)) 957 | } 958 | pub fn as_index(&self) -> Option { 959 | match &self.attr { 960 | AttrAccess::Indexed(idx) => Some(*idx), 961 | AttrAccess::Named(_) => None, 962 | } 963 | } 964 | pub fn as_assign_pair(&self) -> Option { 965 | match &self.attr { 966 | AttrAccess::Indexed(_) => None, 967 | AttrAccess::Named(n) if n == &self.tmp_name => Some(quote!(#n)), 968 | AttrAccess::Named(n) => { 969 | let tmp_name = &self.tmp_name; 970 | Some(quote!(#n: #tmp_name)) 971 | } 972 | } 973 | } 974 | } 975 | 976 | impl fmt::Display for DecodeMode { 977 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 978 | use DecodeMode::*; 979 | 980 | match self { 981 | Normal => "normal", 982 | Str => "str", 983 | Bytes => "bytes", 984 | }.fmt(f) 985 | } 986 | } 987 | -------------------------------------------------------------------------------- /derive/src/kw.rs: -------------------------------------------------------------------------------- 1 | syn::custom_keyword!(argument); 2 | syn::custom_keyword!(arguments); 3 | syn::custom_keyword!(bytes); 4 | syn::custom_keyword!(child); 5 | syn::custom_keyword!(children); 6 | syn::custom_keyword!(default); 7 | syn::custom_keyword!(flatten); 8 | syn::custom_keyword!(name); 9 | syn::custom_keyword!(node_name); 10 | syn::custom_keyword!(properties); 11 | syn::custom_keyword!(property); 12 | syn::custom_keyword!(skip); 13 | syn::custom_keyword!(span); 14 | syn::custom_keyword!(span_type); 15 | syn::custom_keyword!(str); 16 | syn::custom_keyword!(type_name); 17 | syn::custom_keyword!(unwrap); 18 | -------------------------------------------------------------------------------- /derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | 3 | mod definition; 4 | mod kw; 5 | mod node; 6 | mod scalar; 7 | mod variants; 8 | 9 | use definition::Definition; 10 | use scalar::{Scalar, emit_scalar}; 11 | 12 | 13 | fn emit_decoder(def: &Definition) -> syn::Result { 14 | match def { 15 | Definition::Struct(s) => node::emit_struct(s, true), 16 | Definition::NewType(s) => node::emit_new_type(s), 17 | Definition::TupleStruct(s) => node::emit_struct(s, false), 18 | Definition::UnitStruct(s) => node::emit_struct(s, true), 19 | Definition::Enum(e) => variants::emit_enum(e), 20 | } 21 | } 22 | 23 | #[proc_macro_error::proc_macro_error] 24 | #[proc_macro_derive(Decode, attributes(knuffel))] 25 | #[doc = include_str!("../derive_decode.md")] 26 | pub fn decode_derive(input: proc_macro::TokenStream) 27 | -> proc_macro::TokenStream 28 | { 29 | let item = syn::parse_macro_input!(input as Definition); 30 | match emit_decoder(&item) { 31 | Ok(stream) => stream.into(), 32 | Err(e) => e.to_compile_error().into(), 33 | } 34 | } 35 | 36 | #[proc_macro_error::proc_macro_error] 37 | #[proc_macro_derive(DecodeScalar, attributes(knuffel))] 38 | #[doc = include_str!("../derive_decode_scalar.md")] 39 | pub fn decode_scalar_derive(input: proc_macro::TokenStream) 40 | -> proc_macro::TokenStream 41 | { 42 | let item = syn::parse_macro_input!(input as Scalar); 43 | match emit_scalar(&item) { 44 | Ok(stream) => stream.into(), 45 | Err(e) => e.to_compile_error().into(), 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /derive/src/scalar.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::ext::IdentExt; 4 | use syn::parse::{Parse, ParseStream}; 5 | use syn::spanned::Spanned; 6 | 7 | 8 | pub enum Scalar { 9 | Enum(Enum), 10 | } 11 | 12 | pub struct Enum { 13 | pub ident: syn::Ident, 14 | pub variants: Vec, 15 | } 16 | 17 | pub struct Variant { 18 | pub ident: syn::Ident, 19 | pub name: String, 20 | } 21 | 22 | impl Enum { 23 | fn new(ident: syn::Ident, _attrs: Vec, 24 | src_variants: impl Iterator) 25 | -> syn::Result 26 | { 27 | let mut variants = Vec::new(); 28 | for var in src_variants { 29 | match var.fields { 30 | syn::Fields::Unit => { 31 | let name = heck::ToKebabCase 32 | ::to_kebab_case(&var.ident.unraw().to_string()[..]); 33 | variants.push(Variant { 34 | ident: var.ident, 35 | name, 36 | }); 37 | } 38 | _ => { 39 | return Err(syn::Error::new(var.span(), 40 | "only unit variants are allowed for DecodeScalar")); 41 | } 42 | } 43 | } 44 | Ok(Enum { 45 | ident, 46 | variants, 47 | }) 48 | } 49 | } 50 | 51 | 52 | impl Parse for Scalar { 53 | fn parse(input: ParseStream) -> syn::Result { 54 | let mut attrs = input.call(syn::Attribute::parse_outer)?; 55 | let ahead = input.fork(); 56 | let _vis: syn::Visibility = ahead.parse()?; 57 | 58 | let lookahead = ahead.lookahead1(); 59 | if lookahead.peek(syn::Token![enum]) { 60 | let item: syn::ItemEnum = input.parse()?; 61 | attrs.extend(item.attrs); 62 | Enum::new(item.ident, attrs, 63 | item.variants.into_iter()) 64 | .map(Scalar::Enum) 65 | } else { 66 | Err(lookahead.error()) 67 | } 68 | } 69 | } 70 | 71 | pub fn emit_scalar(s: &Scalar) -> syn::Result { 72 | match s { 73 | Scalar::Enum(e) => { 74 | emit_enum(e) 75 | } 76 | } 77 | } 78 | 79 | 80 | pub fn emit_enum(e: &Enum) -> syn::Result { 81 | let e_name = &e.ident; 82 | let value_err = if e.variants.len() <= 3 { 83 | format!("expected one of {}", 84 | e.variants.iter() 85 | .map(|v| format!("`{}`", v.name.escape_default())) 86 | .collect::>() 87 | .join(", ")) 88 | } else { 89 | format!("expected `{}`, `{}`, or one of {} others", 90 | e.variants[0].name.escape_default(), 91 | e.variants[1].name.escape_default(), 92 | e.variants.len() - 2) 93 | }; 94 | let match_branches = e.variants.iter() 95 | .map(|var| { 96 | let name = &var.name; 97 | let ident = &var.ident; 98 | quote!(#name => Ok(#e_name::#ident)) 99 | }); 100 | Ok(quote! { 101 | impl ::knuffel::DecodeScalar 102 | for #e_name { 103 | fn raw_decode(val: &::knuffel::span::Spanned< 104 | ::knuffel::ast::Literal, S>, 105 | ctx: &mut ::knuffel::decode::Context) 106 | -> Result<#e_name, ::knuffel::errors::DecodeError> 107 | { 108 | match &**val { 109 | ::knuffel::ast::Literal::String(ref s) => { 110 | match &s[..] { 111 | #(#match_branches,)* 112 | _ => { 113 | Err(::knuffel::errors::DecodeError::conversion( 114 | val, #value_err)) 115 | } 116 | } 117 | } 118 | _ => { 119 | Err(::knuffel::errors::DecodeError::scalar_kind( 120 | ::knuffel::decode::Kind::String, 121 | &val, 122 | )) 123 | } 124 | } 125 | } 126 | fn type_check(type_name: &Option<::knuffel::span::Spanned< 127 | ::knuffel::ast::TypeName, S>>, 128 | ctx: &mut ::knuffel::decode::Context) 129 | { 130 | if let Some(typ) = type_name { 131 | ctx.emit_error(::knuffel::errors::DecodeError::TypeName { 132 | span: typ.span().clone(), 133 | found: Some((**typ).clone()), 134 | expected: ::knuffel::errors::ExpectedType::no_type(), 135 | rust_type: stringify!(#e_name), 136 | }); 137 | } 138 | } 139 | } 140 | }) 141 | } 142 | -------------------------------------------------------------------------------- /derive/src/variants.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{TokenStream, Span}; 2 | use quote::quote; 3 | 4 | use crate::definition::{Enum, VariantKind}; 5 | use crate::node; 6 | 7 | 8 | pub(crate) struct Common<'a> { 9 | pub object: &'a Enum, 10 | pub ctx: &'a syn::Ident, 11 | pub span_type: &'a TokenStream, 12 | } 13 | 14 | pub fn emit_enum(e: &Enum) -> syn::Result { 15 | let name = &e.ident; 16 | let node = syn::Ident::new("node", Span::mixed_site()); 17 | let ctx = syn::Ident::new("ctx", Span::mixed_site()); 18 | 19 | let (_, type_gen, _) = e.generics.split_for_impl(); 20 | let mut common_generics = e.generics.clone(); 21 | let span_ty; 22 | if let Some(ty) = e.trait_props.span_type.as_ref() { 23 | span_ty = quote!(#ty); 24 | } else { 25 | if common_generics.params.is_empty() { 26 | common_generics.lt_token = Some(Default::default()); 27 | common_generics.gt_token = Some(Default::default()); 28 | } 29 | common_generics.params.push(syn::parse2(quote!(S)).unwrap()); 30 | span_ty = quote!(S); 31 | common_generics.make_where_clause().predicates.push( 32 | syn::parse2(quote!(S: ::knuffel::traits::ErrorSpan)).unwrap()); 33 | }; 34 | let trait_gen = quote!(<#span_ty>); 35 | let (impl_gen, _, bounds) = common_generics.split_for_impl(); 36 | 37 | let common = Common { 38 | object: e, 39 | ctx: &ctx, 40 | span_type: &span_ty, 41 | }; 42 | 43 | let decode = decode(&common, &node)?; 44 | Ok(quote! { 45 | impl #impl_gen ::knuffel::Decode #trait_gen for #name #type_gen 46 | #bounds 47 | { 48 | fn decode_node(#node: &::knuffel::ast::SpannedNode<#span_ty>, 49 | #ctx: &mut ::knuffel::decode::Context<#span_ty>) 50 | -> Result> 51 | { 52 | #decode 53 | } 54 | } 55 | }) 56 | } 57 | 58 | fn decode(e: &Common, node: &syn::Ident) -> syn::Result { 59 | let ctx = e.ctx; 60 | let mut branches = Vec::with_capacity(e.object.variants.len()); 61 | let enum_name = &e.object.ident; 62 | for var in &e.object.variants { 63 | let name = &var.name; 64 | let variant_name = &var.ident; 65 | match &var.kind { 66 | VariantKind::Unit => { 67 | branches.push(quote! { 68 | #name => { 69 | for arg in &#node.arguments { 70 | #ctx.emit_error( 71 | ::knuffel::errors::DecodeError::unexpected( 72 | &arg.literal, "argument", 73 | "unexpected argument")); 74 | } 75 | for (name, _) in &#node.properties { 76 | #ctx.emit_error( 77 | ::knuffel::errors::DecodeError::unexpected( 78 | name, "property", 79 | format!("unexpected property `{}`", 80 | name.escape_default()))); 81 | } 82 | if let Some(children) = &#node.children { 83 | for child in children.iter() { 84 | #ctx.emit_error( 85 | ::knuffel::errors::DecodeError::unexpected( 86 | child, "node", 87 | format!("unexpected node `{}`", 88 | child.node_name.escape_default()) 89 | )); 90 | } 91 | } 92 | Ok(#enum_name::#variant_name) 93 | } 94 | }); 95 | } 96 | VariantKind::Nested { option: false } => { 97 | branches.push(quote! { 98 | #name => ::knuffel::Decode::decode_node(#node, #ctx) 99 | .map(#enum_name::#variant_name), 100 | }); 101 | } 102 | VariantKind::Nested { option: true } => { 103 | branches.push(quote! { 104 | #name => { 105 | if #node.arguments.len() > 0 || 106 | #node.properties.len() > 0 || 107 | #node.children.is_some() 108 | { 109 | ::knuffel::Decode::decode_node(#node, #ctx) 110 | .map(Some) 111 | .map(#enum_name::#variant_name) 112 | } else { 113 | Ok(#enum_name::#variant_name(None)) 114 | } 115 | } 116 | }); 117 | } 118 | VariantKind::Tuple(s) => { 119 | let common = node::Common { 120 | object: s, 121 | ctx, 122 | span_type: e.span_type, 123 | }; 124 | let decode = node::decode_enum_item( 125 | &common, 126 | quote!(#enum_name::#variant_name), 127 | node, 128 | false, 129 | )?; 130 | branches.push(quote! { 131 | #name => { #decode } 132 | }); 133 | } 134 | VariantKind::Named(_) => todo!(), 135 | } 136 | } 137 | // TODO(tailhook) use strsim to find similar names 138 | let err = if e.object.variants.len() <= 3 { 139 | format!("expected one of {}", 140 | e.object.variants.iter() 141 | .map(|v| format!("`{}`", v.name.escape_default())) 142 | .collect::>() 143 | .join(", ")) 144 | } else { 145 | format!("expected `{}`, `{}`, or one of {} others", 146 | e.object.variants[0].name.escape_default(), 147 | e.object.variants[1].name.escape_default(), 148 | e.object.variants.len() - 2) 149 | }; 150 | Ok(quote! { 151 | match &**#node.node_name { 152 | #(#branches)* 153 | name_str => { 154 | Err(::knuffel::errors::DecodeError::conversion( 155 | &#node.node_name, #err)) 156 | } 157 | } 158 | }) 159 | } 160 | -------------------------------------------------------------------------------- /derive/tests/ast.rs: -------------------------------------------------------------------------------- 1 | use knuffel::span::Span; 2 | use knuffel::traits::Decode; 3 | 4 | #[derive(knuffel_derive::Decode, Debug)] 5 | #[knuffel(span_type=knuffel::span::Span)] 6 | struct AstChildren { 7 | #[knuffel(children)] 8 | children: Vec>, 9 | } 10 | 11 | fn parse>(text: &str) -> T { 12 | let mut nodes: Vec = knuffel::parse("", text).unwrap(); 13 | assert_eq!(nodes.len(), 1); 14 | nodes.remove(0) 15 | } 16 | 17 | #[test] 18 | fn parse_node_span() { 19 | let item = parse::(r#"node {a; b;}"#); 20 | assert_eq!(item.children.len(), 2); 21 | } 22 | -------------------------------------------------------------------------------- /derive/tests/extra.rs: -------------------------------------------------------------------------------- 1 | use knuffel::span::Span; 2 | use knuffel::traits::Decode; 3 | use knuffel::ast::{TypeName, BuiltinType}; 4 | 5 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 6 | struct Child; 7 | 8 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 9 | #[knuffel(span_type=Span)] 10 | struct NodeSpan { 11 | #[knuffel(span)] 12 | span: Span, 13 | #[knuffel(argument)] 14 | name: String, 15 | #[knuffel(children)] 16 | children: Vec, 17 | } 18 | 19 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 20 | struct NodeType { 21 | #[knuffel(type_name)] 22 | type_name: String, 23 | } 24 | 25 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 26 | struct NameAndType { 27 | #[knuffel(node_name)] 28 | node_name: String, 29 | #[knuffel(type_name)] 30 | type_name: Option, 31 | } 32 | 33 | fn parse>(text: &str) -> T { 34 | let mut nodes: Vec = knuffel::parse("", text).unwrap(); 35 | assert_eq!(nodes.len(), 1); 36 | nodes.remove(0) 37 | } 38 | 39 | #[test] 40 | fn parse_node_span() { 41 | assert_eq!(parse::(r#"node "hello""#), 42 | NodeSpan { 43 | span: Span(0, 12), 44 | name: "hello".into(), 45 | children: Vec::new(), 46 | }); 47 | assert_eq!(parse::(r#" node "hello" "#), 48 | NodeSpan { 49 | span: Span(3, 21), 50 | name: "hello".into(), 51 | children: Vec::new(), 52 | }); 53 | assert_eq!(parse::(r#" node "hello"; "#), 54 | NodeSpan { 55 | span: Span(3, 17), 56 | name: "hello".into(), 57 | children: Vec::new(), 58 | }); 59 | assert_eq!(parse::(r#" node "hello" { child; }"#), 60 | NodeSpan { 61 | span: Span(3, 35), 62 | name: "hello".into(), 63 | children: vec![Child], 64 | }); 65 | } 66 | 67 | #[test] 68 | fn parse_node_type() { 69 | assert_eq!(parse::(r#"(unknown)node {}"#), 70 | NodeType { type_name: "unknown".into() }); 71 | } 72 | 73 | #[test] 74 | fn parse_name_and_type() { 75 | assert_eq!(parse::(r#"(u32)nodexxx"#), 76 | NameAndType { 77 | node_name: "nodexxx".into(), 78 | type_name: Some(BuiltinType::U32.into()), 79 | }); 80 | 81 | assert_eq!(parse::(r#"yyynode /-{ }"#), 82 | NameAndType { 83 | node_name: "yyynode".into(), 84 | type_name: None, 85 | }); 86 | } 87 | -------------------------------------------------------------------------------- /derive/tests/flatten.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use miette::Diagnostic; 4 | 5 | use knuffel::{Decode, span::Span}; 6 | use knuffel::traits::DecodeChildren; 7 | 8 | 9 | #[derive(knuffel_derive::Decode, Default, Debug, PartialEq)] 10 | struct Prop1 { 11 | #[knuffel(property)] 12 | label: Option, 13 | } 14 | 15 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 16 | struct FlatProp { 17 | #[knuffel(flatten(property))] 18 | props: Prop1, 19 | } 20 | 21 | #[derive(knuffel_derive::Decode, Default, Debug, PartialEq)] 22 | struct Unwrap { 23 | #[knuffel(child, unwrap(argument))] 24 | label: Option, 25 | } 26 | 27 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 28 | struct FlatChild { 29 | #[knuffel(flatten(child))] 30 | children: Unwrap, 31 | } 32 | 33 | 34 | fn parse>(text: &str) -> T { 35 | let mut nodes: Vec = knuffel::parse("", text).unwrap(); 36 | assert_eq!(nodes.len(), 1); 37 | nodes.remove(0) 38 | } 39 | 40 | fn parse_err+fmt::Debug>(text: &str) -> String { 41 | let err = knuffel::parse::>("", text).unwrap_err(); 42 | err.related().unwrap() 43 | .map(|e| e.to_string()).collect::>() 44 | .join("\n") 45 | } 46 | 47 | fn parse_doc>(text: &str) -> T { 48 | knuffel::parse("", text).unwrap() 49 | } 50 | 51 | fn parse_doc_err+fmt::Debug>(text: &str) -> String { 52 | let err = knuffel::parse::("", text).unwrap_err(); 53 | err.related().unwrap() 54 | .map(|e| e.to_string()).collect::>() 55 | .join("\n") 56 | } 57 | 58 | #[test] 59 | fn parse_flat_prop() { 60 | assert_eq!(parse::(r#"node label="hello""#), 61 | FlatProp { props: Prop1 { label: Some("hello".into()) } } ); 62 | assert_eq!(parse_err::(r#"node something="world""#), 63 | "unexpected property `something`"); 64 | } 65 | 66 | #[test] 67 | fn parse_flat_child() { 68 | assert_eq!(parse_doc::(r#"label "hello""#), 69 | FlatChild { children: Unwrap { label: Some("hello".into()) } } ); 70 | assert_eq!(parse_doc_err::(r#"something "world""#), 71 | "unexpected node `something`"); 72 | } 73 | -------------------------------------------------------------------------------- /derive/tests/normal.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::collections::BTreeMap; 3 | use std::default::Default; 4 | 5 | use miette::Diagnostic; 6 | 7 | use knuffel::{Decode, span::Span}; 8 | use knuffel::traits::DecodeChildren; 9 | 10 | 11 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 12 | struct Arg1 { 13 | #[knuffel(argument)] 14 | name: String, 15 | } 16 | 17 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 18 | struct Arg1RawIdent { 19 | #[knuffel(argument)] 20 | r#type: String, 21 | } 22 | 23 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 24 | struct ArgDef { 25 | #[knuffel(argument, default)] 26 | name: String, 27 | } 28 | 29 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 30 | struct ArgDefValue { 31 | #[knuffel(argument, default="unnamed".into())] 32 | name: String, 33 | } 34 | 35 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 36 | struct ArgDefOptValue { 37 | #[knuffel(argument, default=Some("unnamed".into()))] 38 | name: Option, 39 | } 40 | 41 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 42 | struct OptArg { 43 | #[knuffel(argument)] 44 | name: Option, 45 | } 46 | 47 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 48 | struct Extra { 49 | field: String, 50 | } 51 | 52 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 53 | struct VarArg { 54 | #[knuffel(arguments)] 55 | params: Vec, 56 | } 57 | 58 | #[derive(knuffel_derive::Decode, Debug, PartialEq, Default)] 59 | struct Prop1 { 60 | #[knuffel(property)] 61 | label: String, 62 | } 63 | 64 | #[derive(knuffel_derive::Decode, Debug, PartialEq, Default)] 65 | struct Prop1RawIdent { 66 | #[knuffel(property)] 67 | r#type: String, 68 | } 69 | 70 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 71 | struct PropDef { 72 | #[knuffel(property, default)] 73 | label: String, 74 | } 75 | 76 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 77 | struct PropDefValue { 78 | #[knuffel(property, default="unknown".into())] 79 | label: String, 80 | } 81 | 82 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 83 | struct PropDefOptValue { 84 | #[knuffel(property, default=Some("unknown".into()))] 85 | label: Option, 86 | } 87 | 88 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 89 | struct PropNamed { 90 | #[knuffel(property(name="x"))] 91 | label: String, 92 | } 93 | 94 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 95 | struct OptProp { 96 | #[knuffel(property)] 97 | label: Option, 98 | } 99 | 100 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 101 | struct VarProp { 102 | #[knuffel(properties)] 103 | scores: BTreeMap, 104 | } 105 | 106 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 107 | struct Children { 108 | #[knuffel(children)] 109 | children: Vec, 110 | } 111 | 112 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 113 | struct FilteredChildren { 114 | #[knuffel(children(name="left"))] 115 | left: Vec, 116 | #[knuffel(children(name="right"))] 117 | right: Vec, 118 | } 119 | 120 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 121 | enum Variant { 122 | Arg1(Arg1), 123 | Prop1(Prop1), 124 | #[knuffel(skip)] 125 | #[allow(dead_code)] 126 | Var3(u32), 127 | } 128 | 129 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 130 | struct Child { 131 | #[knuffel(child)] 132 | main: Prop1, 133 | #[knuffel(child)] 134 | extra: Option, 135 | #[knuffel(child)] 136 | flag: bool, 137 | #[knuffel(child)] 138 | унікод: Option, 139 | } 140 | 141 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 142 | struct ChildDef { 143 | #[knuffel(child, default)] 144 | main: Prop1, 145 | } 146 | 147 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 148 | struct ChildDefValue { 149 | #[knuffel(child, default=Prop1 { label: String::from("prop1") })] 150 | main: Prop1, 151 | } 152 | 153 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 154 | struct Unwrap { 155 | #[knuffel(child, unwrap(argument))] 156 | label: String, 157 | } 158 | 159 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 160 | struct UnwrapRawIdent { 161 | #[knuffel(child, unwrap(argument))] 162 | r#type: String, 163 | } 164 | 165 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 166 | struct UnwrapFiltChildren { 167 | #[knuffel(children(name="labels"), unwrap(arguments))] 168 | labels: Vec>, 169 | } 170 | 171 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 172 | struct UnwrapChildren { 173 | #[knuffel(children, unwrap(arguments))] 174 | labels: Vec>, 175 | } 176 | 177 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 178 | struct Parse { 179 | #[knuffel(child, unwrap(argument, str))] 180 | listen: std::net::SocketAddr, 181 | } 182 | 183 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 184 | struct ParseOpt { 185 | #[knuffel(property, str)] 186 | listen: Option, 187 | } 188 | 189 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 190 | struct Bytes { 191 | #[knuffel(child, unwrap(argument, bytes))] 192 | data: Vec, 193 | } 194 | 195 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 196 | struct OptBytes { 197 | #[knuffel(property, bytes)] 198 | data: Option>, 199 | } 200 | 201 | fn parse>(text: &str) -> T { 202 | let mut nodes: Vec = knuffel::parse("", text).unwrap(); 203 | assert_eq!(nodes.len(), 1); 204 | nodes.remove(0) 205 | } 206 | 207 | fn parse_err+fmt::Debug>(text: &str) -> String { 208 | let err = knuffel::parse::>("", text).unwrap_err(); 209 | err.related().unwrap() 210 | .map(|e| e.to_string()).collect::>() 211 | .join("\n") 212 | } 213 | 214 | fn parse_doc>(text: &str) -> T { 215 | knuffel::parse("", text).unwrap() 216 | } 217 | 218 | fn parse_doc_err+fmt::Debug>(text: &str) -> String { 219 | let err = knuffel::parse::("", text).unwrap_err(); 220 | err.related().unwrap() 221 | .map(|e| e.to_string()).collect::>() 222 | .join("\n") 223 | } 224 | 225 | #[test] 226 | fn parse_arg1() { 227 | assert_eq!(parse::(r#"node "hello""#), 228 | Arg1 { name: "hello".into() } ); 229 | assert_eq!(parse_err::(r#"node "hello" "world""#), 230 | "unexpected argument"); 231 | assert_eq!(parse_err::(r#"(some)node "hello""#), 232 | "no type name expected for this node"); 233 | assert_eq!(parse_err::(r#"node"#), 234 | "additional argument `name` is required"); 235 | } 236 | 237 | #[test] 238 | fn parse_arg1_raw_ident() { 239 | assert_eq!(parse::(r#"node "hello""#), 240 | Arg1RawIdent { r#type: "hello".into() } ); 241 | assert_eq!(parse_err::(r#"node "hello" "world""#), 242 | "unexpected argument"); 243 | assert_eq!(parse_err::(r#"(some)node "hello""#), 244 | "no type name expected for this node"); 245 | assert_eq!(parse_err::(r#"node"#), 246 | "additional argument `type` is required"); 247 | } 248 | 249 | 250 | #[test] 251 | fn parse_arg_default() { 252 | assert_eq!(parse::(r#"node "hello""#), 253 | ArgDef { name: "hello".into() } ); 254 | assert_eq!(parse_err::(r#"node "hello" "world""#), 255 | "unexpected argument"); 256 | assert_eq!(parse::(r#"node"#), 257 | ArgDef { name: "".into() } ); 258 | } 259 | 260 | #[test] 261 | fn parse_arg_def_value() { 262 | assert_eq!(parse::(r#"node "hello""#), 263 | ArgDefValue { name: "hello".into() } ); 264 | assert_eq!(parse_err::(r#"node "hello" "world""#), 265 | "unexpected argument"); 266 | assert_eq!(parse::(r#"node"#), 267 | ArgDefValue { name: "unnamed".into() } ); 268 | 269 | assert_eq!(parse::(r#"node "hello""#), 270 | ArgDefOptValue { name: Some("hello".into()) } ); 271 | assert_eq!(parse_err::(r#"node "hello" "world""#), 272 | "unexpected argument"); 273 | assert_eq!(parse::(r#"node"#), 274 | ArgDefOptValue { name: Some("unnamed".into()) } ); 275 | assert_eq!(parse::(r#"node null"#), 276 | ArgDefOptValue { name: None } ); 277 | } 278 | 279 | #[test] 280 | fn parse_opt_arg() { 281 | assert_eq!(parse::(r#"node "hello""#), 282 | OptArg { name: Some("hello".into()) } ); 283 | assert_eq!(parse::(r#"node"#), 284 | OptArg { name: None }); 285 | assert_eq!(parse::(r#"node null"#), 286 | OptArg { name: None }); 287 | } 288 | 289 | #[test] 290 | fn parse_prop() { 291 | assert_eq!(parse::(r#"node label="hello""#), 292 | Prop1 { label: "hello".into() } ); 293 | assert_eq!(parse_err::(r#"node label="hello" y="world""#), 294 | "unexpected property `y`"); 295 | assert_eq!(parse_err::(r#"node"#), 296 | "property `label` is required"); 297 | } 298 | 299 | #[test] 300 | fn parse_prop_raw_ident() { 301 | assert_eq!(parse::(r#"node type="hello""#), 302 | Prop1RawIdent { r#type: "hello".into() } ); 303 | assert_eq!(parse_err::(r#"node type="hello" y="world""#), 304 | "unexpected property `y`"); 305 | assert_eq!(parse_err::(r#"node"#), 306 | "property `type` is required"); 307 | } 308 | 309 | #[test] 310 | fn parse_prop_default() { 311 | assert_eq!(parse::(r#"node label="hello""#), 312 | PropDef { label: "hello".into() } ); 313 | assert_eq!(parse::(r#"node"#), 314 | PropDef { label: "".into() }); 315 | } 316 | 317 | #[test] 318 | fn parse_prop_def_value() { 319 | assert_eq!(parse::(r#"node label="hello""#), 320 | PropDefValue { label: "hello".into() } ); 321 | assert_eq!(parse::(r#"node"#), 322 | PropDefValue { label: "unknown".into() }); 323 | 324 | assert_eq!(parse::(r#"node label="hello""#), 325 | PropDefOptValue { label: Some("hello".into()) } ); 326 | assert_eq!(parse::(r#"node"#), 327 | PropDefOptValue { label: Some("unknown".into()) }); 328 | assert_eq!(parse::(r#"node label=null"#), 329 | PropDefOptValue { label: None }); 330 | } 331 | 332 | #[test] 333 | fn parse_prop_named() { 334 | assert_eq!(parse::(r#"node x="hello""#), 335 | PropNamed { label: "hello".into() } ); 336 | assert_eq!(parse_err::(r#"node label="hello" y="world""#), 337 | "unexpected property `label`"); 338 | assert_eq!(parse_err::(r#"node"#), 339 | "property `x` is required"); 340 | } 341 | 342 | #[test] 343 | fn parse_unwrap() { 344 | assert_eq!(parse::(r#"node { label "hello"; }"#), 345 | Unwrap { label: "hello".into() } ); 346 | assert_eq!(parse_err::(r#"node label="hello""#), 347 | "unexpected property `label`"); 348 | assert_eq!(parse_err::(r#"node"#), 349 | "child node `label` is required"); 350 | assert_eq!(parse_doc::(r#"label "hello""#), 351 | Unwrap { label: "hello".into() } ); 352 | } 353 | 354 | #[test] 355 | fn parse_unwrap_raw_ident() { 356 | assert_eq!(parse::(r#"node { type "hello"; }"#), 357 | UnwrapRawIdent { r#type: "hello".into() } ); 358 | assert_eq!(parse_err::(r#"node type="hello""#), 359 | "unexpected property `type`"); 360 | assert_eq!(parse_err::(r#"node"#), 361 | "child node `type` is required"); 362 | assert_eq!(parse_doc::(r#"type "hello""#), 363 | UnwrapRawIdent { r#type: "hello".into() } ); 364 | } 365 | 366 | #[test] 367 | fn parse_unwrap_filtered_children() { 368 | assert_eq!(parse::( 369 | r#"node { labels "hello" "world"; labels "oh" "my"; }"#), 370 | UnwrapFiltChildren { labels: vec![ 371 | vec!["hello".into(), "world".into()], 372 | vec!["oh".into(), "my".into()], 373 | ]}, 374 | ); 375 | } 376 | 377 | #[test] 378 | fn parse_unwrap_children() { 379 | assert_eq!(parse::( 380 | r#"node { some "hello" "world"; other "oh" "my"; }"#), 381 | UnwrapChildren { labels: vec![ 382 | vec!["hello".into(), "world".into()], 383 | vec!["oh".into(), "my".into()], 384 | ]}, 385 | ); 386 | } 387 | 388 | #[test] 389 | fn parse_opt_prop() { 390 | assert_eq!(parse::(r#"node label="hello""#), 391 | OptProp { label: Some("hello".into()) } ); 392 | assert_eq!(parse::(r#"node"#), 393 | OptProp { label: None } ); 394 | assert_eq!(parse::(r#"node label=null"#), 395 | OptProp { label: None } ); 396 | } 397 | 398 | #[test] 399 | fn parse_var_arg() { 400 | assert_eq!(parse::(r#"sum 1 2 3"#), 401 | VarArg { params: vec![1, 2, 3] } ); 402 | assert_eq!(parse::(r#"sum"#), 403 | VarArg { params: vec![] } ); 404 | } 405 | 406 | #[test] 407 | fn parse_var_prop() { 408 | let mut scores = BTreeMap::new(); 409 | scores.insert("john".into(), 13); 410 | scores.insert("jack".into(), 7); 411 | assert_eq!(parse::(r#"scores john=13 jack=7"#), 412 | VarProp { scores } ); 413 | assert_eq!(parse::(r#"scores"#), 414 | VarProp { scores: BTreeMap::new() } ); 415 | } 416 | 417 | #[test] 418 | fn parse_children() { 419 | assert_eq!(parse::(r#"parent { - "val1"; - "val2"; }"#), 420 | Children { children: vec! [ 421 | Arg1 { name: "val1".into() }, 422 | Arg1 { name: "val2".into() }, 423 | ]} ); 424 | assert_eq!(parse::(r#"parent"#), 425 | Children { children: Vec::new() } ); 426 | 427 | assert_eq!(parse_doc::(r#"- "val1"; - "val2""#), 428 | Children { children: vec! [ 429 | Arg1 { name: "val1".into() }, 430 | Arg1 { name: "val2".into() }, 431 | ]} ); 432 | assert_eq!(parse_doc::(r#""#), 433 | Children { children: Vec::new() } ); 434 | } 435 | 436 | #[test] 437 | fn parse_filtered_children() { 438 | assert_eq!(parse_doc::( 439 | r#"left "v1"; right "v2"; left "v3""#), 440 | FilteredChildren { 441 | left: vec![ 442 | OptArg { name: Some("v1".into()) }, 443 | OptArg { name: Some("v3".into()) }, 444 | ], 445 | right: vec![ 446 | OptArg { name: Some("v2".into()) }, 447 | ] 448 | }); 449 | assert_eq!(parse_doc::(r#"right; left"#), 450 | FilteredChildren { 451 | left: vec![ 452 | OptArg { name: None }, 453 | ], 454 | right: vec![ 455 | OptArg { name: None }, 456 | ] 457 | }); 458 | assert_eq!(parse_doc_err::(r#"some"#), 459 | "unexpected node `some`"); 460 | } 461 | 462 | #[test] 463 | fn parse_child() { 464 | assert_eq!(parse::(r#"parent { main label="val1"; }"#), 465 | Child { 466 | main: Prop1 { label: "val1".into() }, 467 | extra: None, 468 | flag: false, 469 | унікод: None, 470 | }); 471 | assert_eq!(parse::(r#"parent { 472 | main label="primary"; 473 | extra label="replica"; 474 | }"#), 475 | Child { 476 | main: Prop1 { label: "primary".into() }, 477 | extra: Some(Prop1 { label: "replica".into() }), 478 | flag: false, 479 | унікод: None, 480 | }); 481 | assert_eq!(parse_err::(r#"parent { something; }"#), 482 | "unexpected node `something`\n\ 483 | child node `main` is required"); 484 | assert_eq!(parse_err::(r#"parent"#), 485 | "child node `main` is required"); 486 | 487 | assert_eq!(parse_doc::(r#"main label="val1""#), 488 | Child { 489 | main: Prop1 { label: "val1".into() }, 490 | extra: None, 491 | flag: false, 492 | унікод: None, 493 | }); 494 | assert_eq!(parse_doc::(r#" 495 | main label="primary" 496 | extra label="replica" 497 | flag 498 | "#), 499 | Child { 500 | main: Prop1 { label: "primary".into() }, 501 | extra: Some(Prop1 { label: "replica".into() }), 502 | flag: true, 503 | унікод: None, 504 | }); 505 | assert_eq!(parse_doc_err::(r#"something"#), 506 | "unexpected node `something`\n\ 507 | child node `main` is required"); 508 | assert_eq!(parse_doc_err::(r#""#), 509 | "child node `main` is required"); 510 | 511 | assert_eq!(parse_doc::(r#" 512 | main label="primary" 513 | унікод label="працює" 514 | "#), 515 | Child { 516 | main: Prop1 { label: "primary".into() }, 517 | extra: None, 518 | flag: false, 519 | унікод: Some(Prop1 { label: "працює".into() }), 520 | }); 521 | } 522 | 523 | #[test] 524 | fn parse_child_def() { 525 | assert_eq!(parse::(r#"parent { main label="val1"; }"#), 526 | ChildDef { 527 | main: Prop1 { label: "val1".into() }, 528 | }); 529 | assert_eq!(parse::(r#"parent"#), 530 | ChildDef { 531 | main: Prop1 { label: "".into() }, 532 | }); 533 | } 534 | 535 | #[test] 536 | fn parse_child_def_value() { 537 | assert_eq!(parse::(r#"parent { main label="val1"; }"#), 538 | ChildDefValue { 539 | main: Prop1 { label: "val1".into() }, 540 | }); 541 | assert_eq!(parse::(r#"parent"#), 542 | ChildDefValue { 543 | main: Prop1 { label: "prop1".into() }, 544 | }); 545 | } 546 | 547 | #[test] 548 | fn parse_enum() { 549 | assert_eq!(parse::(r#"arg1 "hello""#), 550 | Variant::Arg1(Arg1 { name: "hello".into() })); 551 | assert_eq!(parse::(r#"prop1 label="hello""#), 552 | Variant::Prop1(Prop1 { label: "hello".into() })); 553 | assert_eq!(parse_err::(r#"something"#), 554 | "expected one of `arg1`, `prop1`"); 555 | } 556 | 557 | #[test] 558 | fn parse_str() { 559 | assert_eq!(parse_doc::(r#"listen "127.0.0.1:8080""#), 560 | Parse { listen: "127.0.0.1:8080".parse().unwrap() }); 561 | assert!(parse_doc_err::(r#"listen "2/3""#).contains("invalid")); 562 | 563 | assert_eq!(parse::(r#"server listen="127.0.0.1:8080""#), 564 | ParseOpt { listen: Some("127.0.0.1:8080".parse().unwrap()) }); 565 | assert!(parse_err::(r#"server listen="2/3""#).contains("invalid")); 566 | assert_eq!(parse::(r#"server listen=null"#), 567 | ParseOpt { listen: None }); 568 | } 569 | 570 | #[test] 571 | fn parse_bytes() { 572 | assert_eq!(parse_doc::(r#"data (base64)"aGVsbG8=""#), 573 | Bytes { data: b"hello".to_vec() }); 574 | assert_eq!(parse_doc::(r#"data "world""#), 575 | Bytes { data: b"world".to_vec() }); 576 | assert_eq!(parse_doc_err::(r#"data (base64)"2/3""#), 577 | "Invalid padding"); 578 | 579 | assert_eq!(parse::(r#"node data=(base64)"aGVsbG8=""#), 580 | OptBytes { data: Some(b"hello".to_vec()) }); 581 | assert_eq!(parse::(r#"node data="world""#), 582 | OptBytes { data: Some(b"world".to_vec()) }); 583 | assert_eq!(parse::(r#"node data=null"#), 584 | OptBytes { data: None }); 585 | } 586 | 587 | #[test] 588 | fn parse_extra() { 589 | assert_eq!(parse::(r#"data"#), 590 | Extra { field: "".into() }); 591 | assert_eq!(parse_err::(r#"data x=1"#), 592 | "unexpected property `x`"); 593 | } 594 | -------------------------------------------------------------------------------- /derive/tests/scalar.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use knuffel::{Decode}; 4 | use knuffel::span::Span; 5 | use miette::Diagnostic; 6 | 7 | 8 | #[derive(knuffel::DecodeScalar, Debug, PartialEq)] 9 | enum SomeScalar { 10 | First, 11 | AnotherOption, 12 | } 13 | 14 | #[derive(knuffel::Decode, Debug, PartialEq)] 15 | struct Item { 16 | #[knuffel(argument)] 17 | value: SomeScalar, 18 | } 19 | 20 | 21 | fn parse>(text: &str) -> T { 22 | let mut nodes: Vec = knuffel::parse("", text).unwrap(); 23 | assert_eq!(nodes.len(), 1); 24 | nodes.remove(0) 25 | } 26 | 27 | fn parse_err+fmt::Debug>(text: &str) -> String { 28 | let err = knuffel::parse::>("", text).unwrap_err(); 29 | err.related().unwrap() 30 | .map(|e| e.to_string()).collect::>() 31 | .join("\n") 32 | } 33 | 34 | #[test] 35 | fn parse_some_scalar() { 36 | assert_eq!(parse::(r#"node "first""#), 37 | Item { value: SomeScalar::First } ); 38 | assert_eq!(parse::(r#"node "another-option""#), 39 | Item { value: SomeScalar::AnotherOption } ); 40 | assert_eq!(parse_err::(r#"node "test""#), 41 | "expected one of `first`, `another-option`"); 42 | } 43 | -------------------------------------------------------------------------------- /derive/tests/tuples.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use miette::Diagnostic; 4 | 5 | use knuffel::{Decode, span::Span}; 6 | 7 | 8 | #[derive(Debug, Decode, PartialEq)] 9 | struct Unit; 10 | 11 | #[derive(Debug, Decode, PartialEq)] 12 | struct Arg(#[knuffel(argument)] u32); 13 | 14 | #[derive(Debug, Decode, PartialEq)] 15 | struct Opt(Option); 16 | 17 | #[derive(Debug, Decode, PartialEq)] 18 | struct Extra(#[knuffel(argument)] Option, u32); 19 | 20 | #[derive(Debug, Decode, PartialEq)] 21 | enum Enum { 22 | Unit, 23 | Arg(#[knuffel(argument)] u32), 24 | Opt(Option), 25 | Extra(#[knuffel(argument)] Option, u32), 26 | } 27 | 28 | 29 | fn parse>(text: &str) -> T { 30 | let mut nodes: Vec = knuffel::parse("", text).unwrap(); 31 | assert_eq!(nodes.len(), 1); 32 | nodes.remove(0) 33 | } 34 | 35 | fn parse_err+fmt::Debug>(text: &str) -> String { 36 | let err = knuffel::parse::>("", text).unwrap_err(); 37 | err.related().unwrap() 38 | .map(|e| e.to_string()).collect::>() 39 | .join("\n") 40 | } 41 | 42 | #[test] 43 | fn parse_unit() { 44 | assert_eq!(parse::(r#"node"#), Unit); 45 | assert_eq!(parse_err::(r#"node something="world""#), 46 | "unexpected property `something`"); 47 | } 48 | 49 | #[test] 50 | fn parse_arg() { 51 | assert_eq!(parse::(r#"node 123"#), Arg(123)); 52 | assert_eq!(parse_err::(r#"node something="world""#), 53 | "additional argument is required"); 54 | } 55 | 56 | #[test] 57 | fn parse_extra() { 58 | assert_eq!(parse::(r#"node "123""#), Extra(Some("123".into()), 0)); 59 | assert_eq!(parse::(r#"node"#), Extra(None, 0)); 60 | assert_eq!(parse_err::(r#"node "123" 456"#), 61 | "unexpected argument"); 62 | } 63 | 64 | #[test] 65 | fn parse_opt() { 66 | assert_eq!(parse::(r#"node 123"#), Opt(Some(Arg(123)))); 67 | assert_eq!(parse::(r#"node"#), Opt(None)); 68 | assert_eq!(parse_err::(r#"node something="world""#), 69 | "additional argument is required"); 70 | } 71 | 72 | #[test] 73 | fn parse_enum() { 74 | assert_eq!(parse::(r#"unit"#), Enum::Unit); 75 | assert_eq!(parse::(r#"arg 123"#), Enum::Arg(123)); 76 | assert_eq!(parse::(r#"opt 123"#), Enum::Opt(Some(Arg(123)))); 77 | assert_eq!(parse::(r#"opt"#), Enum::Opt(None)); 78 | assert_eq!(parse::(r#"extra"#), Enum::Extra(None, 0)); 79 | assert_eq!(parse_err::(r#"unit something="world""#), 80 | "unexpected property `something`"); 81 | assert_eq!(parse_err::(r#"other something="world""#), 82 | "expected `unit`, `arg`, or one of 2 others"); 83 | assert_eq!(parse_err::(r#"extra "hello" "world""#), 84 | "unexpected argument"); 85 | } 86 | -------------------------------------------------------------------------------- /derive/tests/types.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use knuffel::{span::Span}; 4 | use knuffel::traits::DecodeChildren; 5 | 6 | 7 | #[derive(knuffel_derive::Decode, Debug, PartialEq)] 8 | struct Scalars { 9 | #[knuffel(child, unwrap(argument))] 10 | str: String, 11 | #[knuffel(child, unwrap(argument))] 12 | u64: u64, 13 | #[knuffel(child, unwrap(argument))] 14 | f64: f64, 15 | #[knuffel(child, unwrap(argument))] 16 | path: PathBuf, 17 | #[knuffel(child, unwrap(argument))] 18 | boolean: bool, 19 | } 20 | 21 | fn parse>(text: &str) -> T { 22 | knuffel::parse("", text).unwrap() 23 | } 24 | 25 | 26 | #[test] 27 | fn parse_enum() { 28 | assert_eq!( 29 | parse::(r#" 30 | str "hello" 31 | u64 1234 32 | f64 16.125e+1 33 | path "/hello/world" 34 | boolean true 35 | "#), 36 | Scalars { 37 | str: "hello".into(), 38 | u64: 1234, 39 | f64: 161.25, 40 | path: PathBuf::from("/hello/world"), 41 | boolean: true, 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use miette::IntoDiagnostic; 4 | 5 | 6 | #[derive(knuffel::Decode, Debug)] 7 | #[allow(dead_code)] 8 | struct Plugin { 9 | #[knuffel(argument)] 10 | name: String, 11 | #[knuffel(property)] 12 | url: String, 13 | #[knuffel(child, unwrap(argument))] 14 | version: String, 15 | } 16 | 17 | #[derive(knuffel::Decode, Debug)] 18 | #[allow(dead_code)] 19 | struct Config { 20 | #[knuffel(child, unwrap(argument))] 21 | version: String, 22 | #[knuffel(children(name="plugin"))] 23 | plugins: Vec, 24 | } 25 | 26 | fn main() -> miette::Result<()> { 27 | let mut buf = String::new(); 28 | println!("Please type KDL document, press Return, Ctrl+D to finish"); 29 | std::io::stdin().read_to_string(&mut buf).into_diagnostic()?; 30 | let cfg: Config = knuffel::parse("", &buf)?; 31 | println!("{:#?}", cfg); 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /images/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tailhook/knuffel/c44c6b0c0f31ea6d1174d5d2ed41064922ea44ca/images/error.png -------------------------------------------------------------------------------- /lib/Cargo.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tailhook/knuffel/c44c6b0c0f31ea6d1174d5d2ed41064922ea44ca/lib/Cargo.toml -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | max_width = 80 3 | disable_all_formatting = true 4 | -------------------------------------------------------------------------------- /src/ast.rs: -------------------------------------------------------------------------------- 1 | //! Structures that represent abstract syntax tree (AST) of the KDL document 2 | //! 3 | //! All of these types are parameterized by the `S` type which is a span type 4 | //! (perhaps implements [`Span`](crate::traits::Span). The idea is that most of 5 | //! the time spans are used for errors (either at parsing time, or at runtime), 6 | //! and original source is somewhere around to show in error snippets. So it's 7 | //! faster to only track byte offsets and calculate line number and column when 8 | //! printing code snippet. So use [`span::Span`](crate::traits::Span). 9 | //! 10 | //! But sometimes you will not have KDL source around, or performance of 11 | //! priting matters (i.e. you log source spans). In that case, span should 12 | //! contain line and column numbers for things, use 13 | //! [`LineSpan`](crate::span::LineSpan) for that. 14 | 15 | use std::collections::BTreeMap; 16 | use std::convert::Infallible; 17 | use std::fmt; 18 | use std::str::FromStr; 19 | 20 | use crate::span::Spanned; 21 | 22 | /// A shortcut for nodes children that includes span of enclosing braces `{..}` 23 | pub type SpannedChildren = Spanned>, S>; 24 | /// KDL names with span information are represented using this type 25 | pub type SpannedName = Spanned, S>; 26 | /// A KDL node with span of the whole node (including children) 27 | pub type SpannedNode = Spanned, S>; 28 | 29 | /// Single node of the KDL document 30 | #[derive(Debug, Clone)] 31 | #[cfg_attr(feature="minicbor", derive(minicbor::Encode, minicbor::Decode))] 32 | pub struct Node { 33 | /// A type name if specified in parenthesis 34 | #[cfg_attr(feature="minicbor", n(0))] 35 | pub type_name: Option>, 36 | /// A node name 37 | #[cfg_attr(feature="minicbor", n(1))] 38 | pub node_name: SpannedName, 39 | /// Positional arguments 40 | #[cfg_attr(feature="minicbor", n(2))] 41 | pub arguments: Vec>, 42 | /// Named properties 43 | #[cfg_attr(feature="minicbor", n(3))] 44 | pub properties: BTreeMap, Value>, 45 | /// Node's children. This field is not none if there are braces `{..}` 46 | #[cfg_attr(feature="minicbor", n(4))] 47 | pub children: Option>, 48 | } 49 | 50 | /// KDL document root 51 | #[derive(Debug, Clone)] 52 | #[cfg_attr(feature="minicbor", derive(minicbor::Encode, minicbor::Decode))] 53 | pub struct Document { 54 | /// Nodes of the document 55 | #[cfg_attr(feature="minicbor", n(0))] 56 | pub nodes: Vec>, 57 | } 58 | 59 | #[derive(Debug, Clone, PartialEq, Eq)] 60 | #[cfg_attr(feature="minicbor", derive(minicbor::Encode, minicbor::Decode))] 61 | #[cfg_attr(feature="minicbor", cbor(index_only))] 62 | pub(crate) enum Radix { 63 | #[cfg_attr(feature="minicbor", n(2))] 64 | Bin, 65 | #[cfg_attr(feature="minicbor", n(16))] 66 | Hex, 67 | #[cfg_attr(feature="minicbor", n(8))] 68 | Oct, 69 | #[cfg_attr(feature="minicbor", n(10))] 70 | Dec, 71 | } 72 | 73 | /// Potentially unlimited size integer value 74 | #[derive(Debug, Clone, PartialEq, Eq)] 75 | #[cfg_attr(feature="minicbor", derive(minicbor::Encode, minicbor::Decode))] 76 | pub struct Integer( 77 | #[cfg_attr(feature="minicbor", n(0))] 78 | pub(crate) Radix, 79 | #[cfg_attr(feature="minicbor", n(1))] 80 | pub(crate) Box, 81 | ); 82 | 83 | /// Potentially unlimited precision decimal value 84 | #[derive(Debug, Clone, PartialEq, Eq)] 85 | #[cfg_attr(feature="minicbor", derive(minicbor::Encode, minicbor::Decode))] 86 | #[cfg_attr(feature="minicbor", cbor(transparent))] 87 | pub struct Decimal( 88 | #[cfg_attr(feature="minicbor", n(0))] 89 | pub(crate) Box, 90 | ); 91 | 92 | /// Possibly typed KDL scalar value 93 | #[derive(Debug, Clone)] 94 | #[cfg_attr(feature="minicbor", derive(minicbor::Encode, minicbor::Decode))] 95 | pub struct Value { 96 | /// A type name if specified in parenthesis 97 | #[cfg_attr(feature="minicbor", n(0))] 98 | pub type_name: Option>, 99 | /// The actual value literal 100 | #[cfg_attr(feature="minicbor", n(1))] 101 | pub literal: Spanned, 102 | } 103 | 104 | /// Type identifier 105 | #[derive(Debug, Clone, PartialEq, Eq)] 106 | pub struct TypeName(TypeNameInner); 107 | 108 | #[derive(Debug, Clone, PartialEq, Eq)] 109 | enum TypeNameInner { 110 | Builtin(BuiltinType), 111 | Custom(Box), 112 | } 113 | 114 | /// Known type identifier described by the KDL specification 115 | #[non_exhaustive] 116 | #[derive(Debug, Clone, PartialEq, Eq)] 117 | pub enum BuiltinType { 118 | /// `u8`: 8-bit unsigned integer type 119 | U8, 120 | /// `i8`: 8-bit signed integer type 121 | I8, 122 | /// `u16`: 16-bit unsigned integer type 123 | U16, 124 | /// `i16`: 16-bit signed integer type 125 | I16, 126 | /// `u32`: 32-bit unsigned integer type 127 | U32, 128 | /// `i32`: 32-bit signed integer type 129 | I32, 130 | /// `u64`: 64-bit unsigned integer type 131 | U64, 132 | /// `i64`: 64-bit signed integer type 133 | I64, 134 | /// `usize`: platform-dependent unsigned integer type 135 | Usize, 136 | /// `isize`: platform-dependent signed integer type 137 | Isize, 138 | /// `f32`: 32-bit floating point number 139 | F32, 140 | /// `f64`: 64-bit floating point number 141 | F64, 142 | /// `base64` denotes binary bytes type encoded using base64 encoding 143 | Base64, 144 | } 145 | 146 | /// Scalar KDL value 147 | #[derive(Debug, Clone, PartialEq, Eq)] 148 | #[cfg_attr(feature="minicbor", derive(minicbor::Encode, minicbor::Decode))] 149 | pub enum Literal { 150 | /// Null value 151 | #[cfg_attr(feature="minicbor", n(0))] 152 | Null, 153 | /// Boolean value 154 | #[cfg_attr(feature="minicbor", n(1))] 155 | Bool( 156 | #[cfg_attr(feature="minicbor", n(0))] 157 | bool 158 | ), 159 | /// Integer value 160 | #[cfg_attr(feature="minicbor", n(2))] 161 | Int( 162 | #[cfg_attr(feature="minicbor", n(0))] 163 | Integer 164 | ), 165 | /// Decimal (or floating point) value 166 | #[cfg_attr(feature="minicbor", n(3))] 167 | Decimal( 168 | #[cfg_attr(feature="minicbor", n(0))] 169 | Decimal 170 | ), 171 | /// String value 172 | #[cfg_attr(feature="minicbor", n(4))] 173 | String( 174 | #[cfg_attr(feature="minicbor", n(0))] 175 | Box 176 | ), 177 | } 178 | 179 | impl Node { 180 | /// Returns node children 181 | pub fn children(&self) 182 | -> impl Iterator, S>> + 183 | ExactSizeIterator 184 | { 185 | self.children.as_ref().map(|c| c.iter()).unwrap_or_else(|| [].iter()) 186 | } 187 | } 188 | 189 | impl BuiltinType { 190 | /// Returns string representation of the builtin type as defined by KDL 191 | /// specification 192 | pub const fn as_str(&self) -> &'static str { 193 | use BuiltinType::*; 194 | match self { 195 | U8 => "u8", 196 | I8 => "i8", 197 | U16 => "u16", 198 | I16 => "i16", 199 | U32 => "u32", 200 | I32 => "i32", 201 | U64 => "u64", 202 | I64 => "i64", 203 | Usize => "usize", 204 | Isize => "isize", 205 | F32 => "f32", 206 | F64 => "f64", 207 | Base64 => "base64", 208 | } 209 | } 210 | /// Returns `TypeName` structure for the builtin type 211 | pub const fn as_type(self) -> TypeName { 212 | TypeName(TypeNameInner::Builtin(self)) 213 | } 214 | } 215 | 216 | impl TypeName { 217 | pub(crate) fn from_string(val: Box) -> TypeName { 218 | use TypeNameInner::*; 219 | 220 | match BuiltinType::from_str(&val[..]) { 221 | Ok(b) => TypeName(Builtin(b)), 222 | _ => TypeName(Custom(val)), 223 | } 224 | } 225 | /// Returns string represenation of the type name 226 | pub fn as_str(&self) -> &str { 227 | match &self.0 { 228 | TypeNameInner::Builtin(t) => t.as_str(), 229 | TypeNameInner::Custom(t) => t.as_ref(), 230 | } 231 | } 232 | /// Returns `BuiltinType` enum for the type if typename matches builtin 233 | /// type 234 | /// 235 | /// Note: checking for `is_none()` is not forward compatible. In future we 236 | /// may add additional builtin type. Always use `as_str` for types that 237 | /// aren't yet builtin. 238 | pub const fn as_builtin(&self) -> Option<&BuiltinType> { 239 | match &self.0 { 240 | TypeNameInner::Builtin(t) => Some(t), 241 | TypeNameInner::Custom(_) => None, 242 | } 243 | } 244 | } 245 | 246 | impl FromStr for BuiltinType { 247 | type Err = (); 248 | fn from_str(s: &str) -> Result { 249 | use BuiltinType::*; 250 | match s { 251 | "u8" => Ok(U8), 252 | "i8" => Ok(I8), 253 | "u16" => Ok(U16), 254 | "i16" => Ok(I16), 255 | "u32" => Ok(U32), 256 | "i32" => Ok(I32), 257 | "u64" => Ok(U64), 258 | "i64" => Ok(I64), 259 | "f32" => Ok(F32), 260 | "f64" => Ok(F64), 261 | "base64" => Ok(Base64), 262 | _ => Err(()) 263 | } 264 | } 265 | } 266 | 267 | impl FromStr for TypeName { 268 | type Err = Infallible; 269 | fn from_str(s: &str) -> Result { 270 | use TypeNameInner::*; 271 | match BuiltinType::from_str(s) { 272 | Ok(b) => Ok(TypeName(Builtin(b))), 273 | Err(()) => Ok(TypeName(Custom(s.into()))), 274 | } 275 | } 276 | } 277 | 278 | impl std::ops::Deref for TypeName { 279 | type Target = str; 280 | fn deref(&self) -> &str { 281 | self.as_str() 282 | } 283 | } 284 | 285 | impl fmt::Display for TypeName { 286 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 287 | self.as_str().fmt(f) 288 | } 289 | } 290 | 291 | impl Into for BuiltinType { 292 | fn into(self) -> TypeName { 293 | self.as_type() 294 | } 295 | } 296 | 297 | #[cfg(feature="minicbor")] 298 | mod cbor { 299 | use super::TypeName; 300 | use minicbor::{Decoder, Encoder}; 301 | use minicbor::encode::Encode; 302 | use minicbor::decode::Decode; 303 | 304 | impl<'d, C> Decode<'d, C> for TypeName { 305 | fn decode(d: &mut Decoder<'d>, _ctx: &mut C) 306 | -> Result 307 | { 308 | d.str().and_then(|s| s.parse().map_err(|e| match e {})) 309 | } 310 | } 311 | impl Encode for TypeName { 312 | fn encode(&self, e: &mut Encoder, ctx: &mut C) 313 | -> Result<(), minicbor::encode::Error> 314 | where W: minicbor::encode::write::Write 315 | { 316 | self.as_str().encode(e, ctx) 317 | } 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /src/containers.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::rc::Rc; 3 | 4 | use crate::ast::{SpannedNode, Literal, Value, TypeName}; 5 | use crate::decode::Context; 6 | use crate::errors::DecodeError; 7 | use crate::span::Spanned; 8 | use crate::traits::{Decode, DecodeChildren, DecodeScalar, DecodePartial}; 9 | use crate::traits::{ErrorSpan, DecodeSpan, Span}; 10 | 11 | 12 | impl> Decode for Box { 13 | fn decode_node(node: &SpannedNode, ctx: &mut Context) 14 | -> Result> 15 | { 16 | Decode::decode_node(node, ctx).map(Box::new) 17 | } 18 | } 19 | 20 | impl> DecodeChildren for Box { 21 | fn decode_children(nodes: &[SpannedNode], ctx: &mut Context) 22 | -> Result> 23 | { 24 | DecodeChildren::decode_children(nodes, ctx).map(Box::new) 25 | } 26 | } 27 | 28 | impl> DecodePartial for Box { 29 | fn insert_child(&mut self, node: &SpannedNode, ctx: &mut Context) 30 | -> Result> 31 | { 32 | (**self).insert_child(node, ctx) 33 | } 34 | fn insert_property(&mut self, 35 | name: &Spanned, S>, value: &Value, 36 | ctx: &mut Context) 37 | -> Result> 38 | { 39 | (**self).insert_property(name, value, ctx) 40 | } 41 | } 42 | 43 | impl> DecodeScalar for Box { 44 | fn type_check(type_name: &Option>, 45 | ctx: &mut Context) { 46 | T::type_check(type_name, ctx) 47 | } 48 | fn raw_decode(value: &Spanned, ctx: &mut Context) 49 | -> Result> 50 | { 51 | DecodeScalar::raw_decode(value, ctx).map(Box::new) 52 | } 53 | } 54 | 55 | impl> Decode for Arc { 56 | fn decode_node(node: &SpannedNode, ctx: &mut Context) 57 | -> Result> 58 | { 59 | Decode::decode_node(node, ctx).map(Arc::new) 60 | } 61 | } 62 | 63 | impl> DecodeChildren for Arc { 64 | fn decode_children(nodes: &[SpannedNode], ctx: &mut Context) 65 | -> Result> 66 | { 67 | DecodeChildren::decode_children(nodes, ctx).map(Arc::new) 68 | } 69 | } 70 | 71 | impl> DecodePartial for Arc { 72 | fn insert_child(&mut self, node: &SpannedNode, ctx: &mut Context) 73 | -> Result> 74 | { 75 | Arc::get_mut(self).expect("no Arc clone yet") 76 | .insert_child(node, ctx) 77 | } 78 | fn insert_property(&mut self, 79 | name: &Spanned, S>, value: &Value, 80 | ctx: &mut Context) 81 | -> Result> 82 | { 83 | Arc::get_mut(self).expect("no Arc clone yet") 84 | .insert_property(name, value, ctx) 85 | } 86 | } 87 | 88 | impl> DecodeScalar for Arc { 89 | fn type_check(type_name: &Option>, 90 | ctx: &mut Context) 91 | { 92 | T::type_check(type_name, ctx) 93 | } 94 | fn raw_decode(value: &Spanned, ctx: &mut Context) 95 | -> Result> 96 | { 97 | DecodeScalar::raw_decode(value, ctx).map(Arc::new) 98 | } 99 | } 100 | 101 | impl> Decode for Rc { 102 | fn decode_node(node: &SpannedNode, ctx: &mut Context) 103 | -> Result> 104 | { 105 | Decode::decode_node(node, ctx).map(Rc::new) 106 | } 107 | } 108 | 109 | impl> DecodeChildren for Rc { 110 | fn decode_children(nodes: &[SpannedNode], ctx: &mut Context) 111 | -> Result> 112 | { 113 | DecodeChildren::decode_children(nodes, ctx).map(Rc::new) 114 | } 115 | } 116 | 117 | impl> DecodePartial for Rc { 118 | fn insert_child(&mut self, node: &SpannedNode, ctx: &mut Context) 119 | -> Result> 120 | { 121 | Rc::get_mut(self).expect("no Rc clone yet") 122 | .insert_child(node, ctx) 123 | } 124 | fn insert_property(&mut self, 125 | name: &Spanned, S>, value: &Value, 126 | ctx: &mut Context) 127 | -> Result> 128 | { 129 | Rc::get_mut(self).expect("no Rc clone yet") 130 | .insert_property(name, value, ctx) 131 | } 132 | } 133 | 134 | impl> DecodeScalar for Rc { 135 | fn type_check(type_name: &Option>, 136 | ctx: &mut Context) 137 | { 138 | T::type_check(type_name, ctx) 139 | } 140 | fn raw_decode(value: &Spanned, ctx: &mut Context) 141 | -> Result> 142 | { 143 | DecodeScalar::raw_decode(value, ctx).map(Rc::new) 144 | } 145 | } 146 | 147 | impl> DecodeChildren for Vec { 148 | fn decode_children(nodes: &[SpannedNode], ctx: &mut Context) 149 | -> Result> 150 | { 151 | let mut result = Vec::with_capacity(nodes.len()); 152 | for node in nodes { 153 | match Decode::decode_node(node, ctx) { 154 | Ok(node) => result.push(node), 155 | Err(e) => ctx.emit_error(e), 156 | } 157 | } 158 | Ok(result) 159 | } 160 | } 161 | 162 | impl> DecodeScalar for Option { 163 | fn type_check(type_name: &Option>, 164 | ctx: &mut Context) { 165 | T::type_check(type_name, ctx) 166 | } 167 | fn raw_decode(value: &Spanned, ctx: &mut Context) 168 | -> Result> 169 | { 170 | match &**value { 171 | Literal::Null => Ok(None), 172 | _ => DecodeScalar::raw_decode(value, ctx).map(Some), 173 | } 174 | } 175 | } 176 | 177 | impl, S, Q> DecodeScalar for Spanned 178 | where S: Span, 179 | Q: DecodeSpan 180 | { 181 | fn type_check(type_name: &Option>, 182 | ctx: &mut Context) 183 | { 184 | T::type_check(type_name, ctx) 185 | } 186 | fn raw_decode(value: &Spanned, ctx: &mut Context) 187 | -> Result> 188 | { 189 | let decoded = T::raw_decode(value, ctx)?; 190 | Ok(Spanned { 191 | span: DecodeSpan::decode_span(&value.span, ctx), 192 | value: decoded, 193 | }) 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/convert.rs: -------------------------------------------------------------------------------- 1 | use std::default::Default; 2 | use std::path::{Path, PathBuf}; 3 | use std::str::FromStr; 4 | use std::sync::Arc; 5 | 6 | use crate::ast::{Literal, Integer, Decimal, Radix, TypeName, BuiltinType}; 7 | use crate::decode::{Context, Kind}; 8 | use crate::errors::{DecodeError, ExpectedType}; 9 | use crate::span::{Spanned}; 10 | use crate::traits::{ErrorSpan, DecodeScalar}; 11 | 12 | 13 | macro_rules! impl_integer { 14 | ($typ: ident, $marker: ident) => { 15 | impl TryFrom<&Integer> for $typ { 16 | type Error = <$typ as FromStr>::Err; 17 | fn try_from(val: &Integer) -> Result<$typ, <$typ as FromStr>::Err> 18 | { 19 | match val.0 { 20 | Radix::Bin => <$typ>::from_str_radix(&val.1, 2), 21 | Radix::Oct => <$typ>::from_str_radix(&val.1, 8), 22 | Radix::Dec => <$typ>::from_str(&val.1), 23 | Radix::Hex => <$typ>::from_str_radix(&val.1, 16), 24 | } 25 | } 26 | } 27 | 28 | impl DecodeScalar for $typ { 29 | fn raw_decode(val: &Spanned, ctx: &mut Context) 30 | -> Result<$typ, DecodeError> 31 | { 32 | match &**val { 33 | Literal::Int(ref value) => { 34 | match value.try_into() { 35 | Ok(val) => Ok(val), 36 | Err(e) => { 37 | ctx.emit_error(DecodeError::conversion(val, e)); 38 | Ok(0) 39 | } 40 | } 41 | } 42 | _ => { 43 | ctx.emit_error(DecodeError::scalar_kind( 44 | Kind::String, val)); 45 | Ok(0) 46 | } 47 | } 48 | } 49 | fn type_check(type_name: &Option>, 50 | ctx: &mut Context) 51 | { 52 | if let Some(typ) = type_name { 53 | if typ.as_builtin() != Some(&BuiltinType::$marker) { 54 | ctx.emit_error(DecodeError::TypeName { 55 | span: typ.span().clone(), 56 | found: Some(typ.value.clone()), 57 | expected: ExpectedType::optional( 58 | BuiltinType::$marker), 59 | rust_type: stringify!($typ), 60 | }); 61 | } 62 | } 63 | } 64 | } 65 | } 66 | } 67 | 68 | impl_integer!(i8, I8); 69 | impl_integer!(u8, U8); 70 | impl_integer!(i16, I16); 71 | impl_integer!(u16, U16); 72 | impl_integer!(i32, I32); 73 | impl_integer!(u32, U32); 74 | impl_integer!(i64, I64); 75 | impl_integer!(u64, U64); 76 | impl_integer!(isize, Isize); 77 | impl_integer!(usize, Usize); 78 | 79 | macro_rules! impl_float { 80 | ($typ: ident, $marker: ident) => { 81 | impl TryFrom<&Decimal> for $typ { 82 | type Error = <$typ as FromStr>::Err; 83 | fn try_from(val: &Decimal) -> Result<$typ, <$typ as FromStr>::Err> 84 | { 85 | <$typ>::from_str(&val.0) 86 | } 87 | } 88 | 89 | impl DecodeScalar for $typ { 90 | fn raw_decode(val: &Spanned, ctx: &mut Context) 91 | -> Result<$typ, DecodeError> 92 | { 93 | match &**val { 94 | Literal::Decimal(ref value) => { 95 | match value.try_into() { 96 | Ok(val) => Ok(val), 97 | Err(e) => { 98 | ctx.emit_error(DecodeError::conversion(val, e)); 99 | Ok(0.0) 100 | } 101 | } 102 | } 103 | _ => { 104 | ctx.emit_error(DecodeError::scalar_kind( 105 | Kind::String, val)); 106 | Ok(0.0) 107 | } 108 | } 109 | } 110 | fn type_check(type_name: &Option>, 111 | ctx: &mut Context) 112 | { 113 | if let Some(typ) = type_name { 114 | if typ.as_builtin() != Some(&BuiltinType::$marker) { 115 | ctx.emit_error(DecodeError::TypeName { 116 | span: typ.span().clone(), 117 | found: Some(typ.value.clone()), 118 | expected: ExpectedType::optional( 119 | BuiltinType::$marker), 120 | rust_type: stringify!($typ), 121 | }); 122 | } 123 | } 124 | } 125 | } 126 | } 127 | } 128 | 129 | impl_float!(f32, F32); 130 | impl_float!(f64, F64); 131 | 132 | impl DecodeScalar for String { 133 | fn raw_decode(val: &Spanned, ctx: &mut Context) 134 | -> Result> 135 | { 136 | match &**val { 137 | Literal::String(ref s) => Ok(s.clone().into()), 138 | _ => { 139 | ctx.emit_error(DecodeError::scalar_kind(Kind::String, val)); 140 | Ok(String::new()) 141 | } 142 | } 143 | } 144 | fn type_check(type_name: &Option>, 145 | ctx: &mut Context) 146 | { 147 | if let Some(typ) = type_name { 148 | ctx.emit_error(DecodeError::TypeName { 149 | span: typ.span().clone(), 150 | found: Some(typ.value.clone()), 151 | expected: ExpectedType::no_type(), 152 | rust_type: "String", 153 | }); 154 | } 155 | } 156 | } 157 | 158 | 159 | impl DecodeScalar for PathBuf { 160 | fn raw_decode(val: &Spanned, ctx: &mut Context) 161 | -> Result> 162 | { 163 | match &**val { 164 | Literal::String(ref s) => Ok(String::from(s.clone()).into()), 165 | _ => { 166 | ctx.emit_error(DecodeError::scalar_kind(Kind::String, val)); 167 | Ok(Default::default()) 168 | } 169 | } 170 | } 171 | fn type_check(type_name: &Option>, 172 | ctx: &mut Context) 173 | { 174 | if let Some(typ) = type_name { 175 | ctx.emit_error(DecodeError::TypeName { 176 | span: typ.span().clone(), 177 | found: Some(typ.value.clone()), 178 | expected: ExpectedType::no_type(), 179 | rust_type: "PathBuf", 180 | }); 181 | } 182 | } 183 | } 184 | 185 | impl DecodeScalar for Arc { 186 | fn raw_decode(val: &Spanned, ctx: &mut Context) 187 | -> Result, DecodeError> 188 | { 189 | match &**val { 190 | Literal::String(ref s) => Ok(PathBuf::from(&(**s)[..]).into()), 191 | _ => { 192 | ctx.emit_error(DecodeError::scalar_kind(Kind::String, val)); 193 | Ok(PathBuf::default().into()) 194 | } 195 | } 196 | } 197 | fn type_check(type_name: &Option>, 198 | ctx: &mut Context) 199 | { 200 | if let Some(typ) = type_name { 201 | ctx.emit_error(DecodeError::TypeName { 202 | span: typ.span().clone(), 203 | found: Some(typ.value.clone()), 204 | expected: ExpectedType::no_type(), 205 | rust_type: "Arc", 206 | }); 207 | } 208 | } 209 | } 210 | 211 | impl DecodeScalar for Arc { 212 | fn raw_decode(val: &Spanned, ctx: &mut Context) 213 | -> Result, DecodeError> 214 | { 215 | match &**val { 216 | Literal::String(ref s) => Ok(s.clone().into()), 217 | _ => { 218 | ctx.emit_error(DecodeError::scalar_kind(Kind::String, val)); 219 | Ok(String::default().into()) 220 | } 221 | } 222 | } 223 | fn type_check(type_name: &Option>, 224 | ctx: &mut Context) 225 | { 226 | if let Some(typ) = type_name { 227 | ctx.emit_error(DecodeError::TypeName { 228 | span: typ.span().clone(), 229 | found: Some(typ.value.clone()), 230 | expected: ExpectedType::no_type(), 231 | rust_type: "Arc", 232 | }); 233 | } 234 | } 235 | } 236 | 237 | impl DecodeScalar for bool { 238 | fn raw_decode(val: &Spanned, ctx: &mut Context) 239 | -> Result> 240 | { 241 | match &**val { 242 | Literal::Bool(value) => Ok(*value), 243 | _ => { 244 | ctx.emit_error(DecodeError::scalar_kind(Kind::Bool, &val)); 245 | Ok(Default::default()) 246 | } 247 | } 248 | } 249 | fn type_check(type_name: &Option>, 250 | ctx: &mut Context) 251 | { 252 | if let Some(typ) = type_name { 253 | ctx.emit_error(DecodeError::TypeName { 254 | span: typ.span().clone(), 255 | found: Some(typ.value.clone()), 256 | expected: ExpectedType::no_type(), 257 | rust_type: "bool", 258 | }); 259 | } 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/convert_ast.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::{Node, SpannedNode, TypeName, Literal, Value}; 2 | use crate::decode::Context; 3 | use crate::errors::DecodeError; 4 | use crate::span::Spanned; 5 | use crate::traits::{Decode, DecodeScalar, DecodeSpan, Span}; 6 | 7 | impl Decode for Node 8 | where S: Span, 9 | T: DecodeSpan 10 | { 11 | fn decode_node(node: &SpannedNode, ctx: &mut Context) 12 | -> Result> 13 | { 14 | Ok(Node { 15 | type_name: node.type_name.as_ref().map(|n| n.clone_as(ctx)), 16 | node_name: node.node_name.clone_as(ctx), 17 | arguments: node.arguments.iter() 18 | .map(|v| DecodeScalar::decode(v, ctx)) 19 | .collect::>()?, 20 | properties: node.properties.iter() 21 | .map(|(k, v)| { 22 | Ok((k.clone_as(ctx), DecodeScalar::decode(v, ctx)?)) 23 | }) 24 | .collect::>()?, 25 | children: node.children.as_ref().map(|sc| { 26 | Ok(Spanned { 27 | span: DecodeSpan::decode_span(&sc.span, ctx), 28 | value: sc.iter() 29 | .map(|node| Ok(Spanned { 30 | span: DecodeSpan::decode_span(&node.span, ctx), 31 | value: Decode::decode_node(node, ctx)?, 32 | })) 33 | .collect::>()?, 34 | }) 35 | }).transpose()?, 36 | }) 37 | } 38 | } 39 | 40 | impl Decode for SpannedNode 41 | where S: Span, 42 | T: DecodeSpan 43 | { 44 | fn decode_node(node: &SpannedNode, ctx: &mut Context) 45 | -> Result> 46 | { 47 | Ok(Spanned { 48 | span: DecodeSpan::decode_span(&node.span, ctx), 49 | value: Decode::decode_node(node, ctx)?, 50 | }) 51 | } 52 | } 53 | 54 | impl DecodeScalar for Value 55 | where S: Span, 56 | T: DecodeSpan 57 | { 58 | fn type_check(_type_name: &Option>, 59 | _ctx: &mut Context) 60 | { 61 | } 62 | fn raw_decode(_value: &Spanned, _ctx: &mut Context) 63 | -> Result> 64 | { 65 | panic!("called `raw_decode` directly on the `Value`"); 66 | } 67 | fn decode(value: &Value, ctx: &mut Context) 68 | -> Result> 69 | { 70 | Ok(Value { 71 | type_name: value.type_name.as_ref().map(|n| n.clone_as(ctx)), 72 | literal: value.literal.clone_as(ctx), 73 | }) 74 | } 75 | } 76 | 77 | impl DecodeScalar for Literal 78 | where S: Span, 79 | { 80 | fn type_check(_type_name: &Option>, 81 | _ctx: &mut Context) 82 | { 83 | } 84 | fn raw_decode(value: &Spanned, _ctx: &mut Context) 85 | -> Result> 86 | { 87 | Ok((**value).clone()) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/decode.rs: -------------------------------------------------------------------------------- 1 | //! Decode support stuff 2 | //! 3 | //! Mostly useful for manual implementation of various `Decode*` traits. 4 | use std::any::{Any, TypeId}; 5 | use std::collections::HashMap; 6 | use std::default::Default; 7 | use std::fmt; 8 | 9 | use crate::ast::{Literal, BuiltinType, Value, SpannedNode}; 10 | use crate::errors::{DecodeError, ExpectedType}; 11 | use crate::traits::{ErrorSpan, Decode}; 12 | 13 | 14 | /// Context is passed through all the decode operations and can be used for: 15 | /// 16 | /// 1. To emit error and proceed (so multiple errors presented to user) 17 | /// 2. To store and retrieve data in decoders of nodes, scalars and spans 18 | #[derive(Debug)] 19 | pub struct Context { 20 | errors: Vec>, 21 | extensions: HashMap>, 22 | } 23 | 24 | /// Scalar value kind 25 | /// 26 | /// Currently used only for error reporting 27 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 28 | pub enum Kind { 29 | /// An unquoted integer value, signed or unsigned. Having no decimal point. 30 | /// Can be of virtually unlimited length. Can be expressed in binary, octal, 31 | /// decimal, or hexadecimal notation. 32 | Int, 33 | /// A number that has either decimal point or exponential part. Can be only 34 | /// in decimal notation. Can represent either decimal or floating value 35 | /// value. No quotes. 36 | Decimal, 37 | /// A string in `"double quotes"` or `r##"raw quotes"##` 38 | String, 39 | /// A boolean value of `true` or `false` 40 | Bool, 41 | /// The null value (usually corresponds to `None` in Rust) 42 | Null, 43 | } 44 | 45 | /// Decodes KDL value as bytes 46 | /// 47 | /// Used internally by `#[knuffel(..., bytes)]` attribute. But can be used 48 | /// manually for implementing [`DecodeScalar`](crate::traits::DecodeScalar). 49 | pub fn bytes(value: &Value, ctx: &mut Context) -> Vec { 50 | if let Some(typ) = &value.type_name { 51 | match typ.as_builtin() { 52 | Some(&BuiltinType::Base64) => { 53 | #[cfg(feature="base64")] { 54 | use base64::{Engine, engine::general_purpose::STANDARD}; 55 | match &*value.literal { 56 | Literal::String(s) => { 57 | match STANDARD.decode(s.as_bytes()) { 58 | Ok(vec) => vec, 59 | Err(e) => { 60 | ctx.emit_error(DecodeError::conversion( 61 | &value.literal, e)); 62 | Default::default() 63 | } 64 | } 65 | } 66 | _ => { 67 | ctx.emit_error(DecodeError::scalar_kind( 68 | Kind::String, &value.literal)); 69 | Default::default() 70 | } 71 | } 72 | } 73 | #[cfg(not(feature="base64"))] { 74 | ctx.emit_error(DecodeError::unsupported( 75 | &value.literal, 76 | "base64 support is not compiled in")); 77 | Default::default() 78 | } 79 | } 80 | _ => { 81 | ctx.emit_error(DecodeError::TypeName { 82 | span: typ.span().clone(), 83 | found: Some(typ.value.clone()), 84 | expected: ExpectedType::optional(BuiltinType::Base64), 85 | rust_type: "bytes", 86 | }); 87 | Default::default() 88 | } 89 | } 90 | } else { 91 | match &*value.literal { 92 | Literal::String(s) => s.as_bytes().to_vec(), 93 | _ => { 94 | ctx.emit_error(DecodeError::scalar_kind( 95 | Kind::String, &value.literal)); 96 | Default::default() 97 | } 98 | } 99 | } 100 | } 101 | 102 | /// Emits error(s) if node is not a flag node 103 | /// 104 | /// Flag node is a node that has no arguments, properties or children. 105 | /// 106 | /// Used internally by `#[knuffel(child)] x: bool,`. But can be used 107 | /// manually for implementing [`DecodeScalar`](crate::traits::DecodeScalar). 108 | pub fn check_flag_node( 109 | node: &SpannedNode, ctx: &mut Context) 110 | { 111 | for arg in &node.arguments { 112 | ctx.emit_error(DecodeError::unexpected( 113 | &arg.literal, "argument", 114 | "unexpected argument")); 115 | } 116 | for (name, _) in &node.properties { 117 | ctx.emit_error(DecodeError::unexpected( 118 | name, "property", 119 | format!("unexpected property `{}`", 120 | name.escape_default()))); 121 | } 122 | if let Some(children) = &node.children { 123 | for child in children.iter() { 124 | ctx.emit_error( 125 | DecodeError::unexpected( 126 | child, "node", 127 | format!("unexpected node `{}`", 128 | child.node_name.escape_default()) 129 | )); 130 | } 131 | } 132 | } 133 | 134 | /// Parse single KDL node from AST 135 | pub fn node(ast: &SpannedNode) -> Result>> 136 | where T: Decode, 137 | S: ErrorSpan, 138 | { 139 | let mut ctx = Context::new(); 140 | match Decode::decode_node(ast, &mut ctx) { 141 | Ok(_) if ctx.has_errors() => { 142 | Err(ctx.into_errors()) 143 | } 144 | Err(e) => { 145 | ctx.emit_error(e); 146 | Err(ctx.into_errors()) 147 | } 148 | Ok(v) => Ok(v) 149 | } 150 | } 151 | 152 | impl Context { 153 | pub(crate) fn new() -> Context { 154 | Context { 155 | errors: Vec::new(), 156 | extensions: HashMap::new(), 157 | } 158 | } 159 | /// Add error 160 | /// 161 | /// This fails decoding operation similarly to just returning error value. 162 | /// But unlike result allows returning some dummy value and allows decoder 163 | /// to proceed so multiple errors are presented to user at the same time. 164 | pub fn emit_error(&mut self, err: impl Into>) { 165 | self.errors.push(err.into()); 166 | } 167 | /// Returns `true` if any errors was emitted into the context 168 | pub fn has_errors(&self) -> bool { 169 | !self.errors.is_empty() 170 | } 171 | pub(crate) fn into_errors(self) -> Vec> { 172 | self.errors 173 | } 174 | /// Set context value 175 | /// 176 | /// These values aren't used by the knuffel itself. But can be used by 177 | /// user-defined decoders to get some value. Each type can have a single but 178 | /// separate value set. So users are encouraged to use [new type idiom 179 | /// ](https://doc.rust-lang.org/rust-by-example/generics/new_types.html) 180 | /// to avoid conflicts with other libraries. 181 | /// 182 | /// It's also discourated to use `set` in the decoder. It's expeced that 183 | /// context will be filled in using 184 | /// [`parse_with_context`](crate::parse_with_context) function. 185 | pub fn set(&mut self, value: T) { 186 | self.extensions.insert(TypeId::of::(), Box::new(value)); 187 | } 188 | /// Get context value 189 | /// 190 | /// Returns a value previously set in context 191 | pub fn get(&self) -> Option<&T> { 192 | self.extensions.get(&TypeId::of::()) 193 | .and_then(|b| b.downcast_ref()) 194 | } 195 | } 196 | 197 | impl fmt::Display for Kind { 198 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 199 | f.write_str(self.as_str()) 200 | } 201 | } 202 | 203 | impl From<&'_ Literal> for Kind { 204 | fn from(lit: &Literal) -> Kind { 205 | use Literal as L; 206 | use Kind as K; 207 | match lit { 208 | L::Int(_) => K::Int, 209 | L::Decimal(_) => K::Decimal, 210 | L::String(_) => K::String, 211 | L::Bool(_) => K::Bool, 212 | L::Null => K::Null, 213 | } 214 | } 215 | } 216 | 217 | impl Kind { 218 | /// Returns the string representation of `Kind` 219 | /// 220 | /// This is currently used in error messages. 221 | pub const fn as_str(&self) -> &'static str { 222 | use Kind::*; 223 | match self { 224 | Int => "integer", 225 | Decimal => "decimal", 226 | String => "string", 227 | Bool => "boolean", 228 | Null => "null", 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | //! Error types for the knuffel library 2 | //! 3 | //! You only need [`Error`](enum@Error) exposed as `knuffel::Error` unless you 4 | //! do manual implementations of any of the `Decode*` traits. 5 | use std::borrow::Cow; 6 | use std::collections::BTreeSet; 7 | use std::fmt::{self, Write}; 8 | 9 | use thiserror::Error; 10 | use miette::{Diagnostic, NamedSource}; 11 | 12 | use crate::ast::{TypeName, Literal, SpannedNode}; 13 | use crate::span::{Spanned}; 14 | use crate::decode::Kind; 15 | use crate::traits::{ErrorSpan, Span}; 16 | 17 | 18 | /// Main error that is returned from KDL parsers 19 | /// 20 | /// Implements [`miette::Diagnostic`] so can be used to print nice error 21 | /// output with code snippets. 22 | /// 23 | /// See [crate documentation](crate#Errors) and [miette} documentation to 24 | /// find out how deal with them. 25 | #[derive(Debug, Diagnostic, Error)] 26 | #[error("error parsing KDL")] 27 | pub struct Error { 28 | #[source_code] 29 | pub(crate) source_code: NamedSource, 30 | #[related] 31 | pub(crate) errors: Vec, 32 | } 33 | 34 | /// An error type that is returned by decoder traits and emitted to the context 35 | /// 36 | /// These are elements of the 37 | #[derive(Debug, Diagnostic, Error)] 38 | #[non_exhaustive] 39 | pub enum DecodeError { 40 | /// Unexpected type name encountered 41 | /// 42 | /// Type names are identifiers and strings in parenthesis before node names 43 | /// or values. 44 | #[error("{} for {}, found {}", expected, rust_type, 45 | found.as_ref().map(|x| x.as_str()).unwrap_or("no type name"))] 46 | #[diagnostic()] 47 | TypeName { 48 | /// Position of the type name 49 | #[label="unexpected type name"] 50 | span: S, 51 | /// Type name contained in the source code 52 | found: Option, 53 | /// Expected type name or type names 54 | expected: ExpectedType, 55 | /// Rust type that is being decoded when error is encountered 56 | rust_type: &'static str, 57 | }, 58 | /// Different scalar kind was encountered than expected 59 | /// 60 | /// This is emitted when integer is used instead of string, and similar. It 61 | /// may also be encountered when `null` is used for non-optional field. 62 | #[diagnostic()] 63 | #[error("expected {} scalar, found {}", expected, found)] 64 | ScalarKind { 65 | /// Position of the unexpected scalar 66 | #[label("unexpected {}", found)] 67 | span: S, 68 | /// Scalar kind (or multiple) expected at this position 69 | expected: ExpectedKind, 70 | /// Kind of scalar that is found 71 | found: Kind, 72 | }, 73 | /// Some required element is missing 74 | /// 75 | /// This is emitted on missing required attributes, properties, or children. 76 | /// (missing type names are emitted using [`DecodeError::TypeName`]) 77 | #[diagnostic()] 78 | #[error("{}", message)] 79 | Missing { 80 | /// Position of the node name of which has missing element 81 | #[label("node starts here")] 82 | span: S, 83 | /// Description of what's missing 84 | message: String, 85 | }, 86 | /// Missing named node at top level 87 | /// 88 | /// This is similar to `Missing` but is only emitted for nodes on the 89 | /// document level. This is separate error because there is no way to show 90 | /// span where missing node is expected (end of input is not very helpful). 91 | #[diagnostic()] 92 | #[error("{}", message)] 93 | MissingNode { 94 | /// Descriptino of what's missing 95 | message: String, 96 | }, 97 | /// Unexpected entity encountered 98 | /// 99 | /// This is emitted for entities (arguments, properties, children) that have 100 | /// to matching structure field to put into, and also for nodes that aren 101 | /// expected to be encountered twice. 102 | #[diagnostic()] 103 | #[error("{}", message)] 104 | Unexpected { 105 | /// Position of the unexpected element 106 | #[label("unexpected {}", kind)] 107 | span: S, 108 | /// Kind of element that was found 109 | kind: &'static str, 110 | /// Description of the error 111 | message: String, 112 | }, 113 | /// Bad scalar conversion 114 | /// 115 | /// This error is emitted when some scalar value of right kind cannot be 116 | /// converted to the Rust value. Including, but not limited to: 117 | /// 1. Integer value out of range 118 | /// 2. `FromStr` returned error for the value parse by 119 | /// `#[knuffel(.., str)]` 120 | #[error("{}", source)] 121 | #[diagnostic()] 122 | Conversion { 123 | /// Position of the scalar that could not be converted 124 | #[label("invalid value")] 125 | span: S, 126 | /// Original error 127 | source: Box, 128 | }, 129 | /// Unsupported value 130 | /// 131 | /// This is currently used to error out on `(base64)` values when `base64` 132 | /// feature is not enabled. 133 | #[error("{}", message)] 134 | #[diagnostic()] 135 | Unsupported { 136 | /// Position of the value that is unsupported 137 | #[label="unsupported value"] 138 | span: S, 139 | /// Description of why the value is not supported 140 | message: Cow<'static, str>, 141 | }, 142 | /// Custom error that can be emitted during decoding 143 | /// 144 | /// This is not used by the knuffel itself. Note most of the time it's 145 | /// better to use [`DecodeError::Conversion`] as that will associate 146 | /// source code span to the error. 147 | #[error(transparent)] 148 | Custom(Box), 149 | } 150 | 151 | #[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] 152 | pub(crate) enum TokenFormat { 153 | Char(char), 154 | Token(&'static str), 155 | Kind(&'static str), 156 | OpenRaw(usize), 157 | CloseRaw(usize), 158 | Eoi, 159 | } 160 | 161 | struct FormatUnexpected<'x>(&'x TokenFormat, &'x BTreeSet); 162 | 163 | #[derive(Debug, Diagnostic, Error)] 164 | pub(crate) enum ParseError { 165 | #[error("{}", FormatUnexpected(found, expected))] 166 | #[diagnostic()] 167 | Unexpected { 168 | label: Option<&'static str>, 169 | #[label("{}", label.unwrap_or("unexpected token"))] 170 | span: S, 171 | found: TokenFormat, 172 | expected: BTreeSet, 173 | }, 174 | #[error("unclosed {} {}", label, opened)] 175 | #[diagnostic()] 176 | Unclosed { 177 | label: &'static str, 178 | #[label="opened here"] 179 | opened_at: S, 180 | opened: TokenFormat, 181 | #[label("expected {}", expected)] 182 | expected_at: S, 183 | expected: TokenFormat, 184 | found: TokenFormat, 185 | }, 186 | #[error("{}", message)] 187 | #[diagnostic()] 188 | Message { 189 | label: Option<&'static str>, 190 | #[label("{}", label.unwrap_or("unexpected token"))] 191 | span: S, 192 | message: String, 193 | }, 194 | #[error("{}", message)] 195 | #[diagnostic(help("{}", help))] 196 | MessageWithHelp { 197 | label: Option<&'static str>, 198 | #[label("{}", label.unwrap_or("unexpected token"))] 199 | span: S, 200 | message: String, 201 | help: &'static str, 202 | }, 203 | } 204 | 205 | 206 | impl From> for TokenFormat { 207 | fn from(chr: Option) -> TokenFormat { 208 | if let Some(chr) = chr { 209 | TokenFormat::Char(chr) 210 | } else { 211 | TokenFormat::Eoi 212 | } 213 | } 214 | } 215 | 216 | impl From for TokenFormat { 217 | fn from(chr: char) -> TokenFormat { 218 | TokenFormat::Char(chr) 219 | } 220 | } 221 | 222 | impl From<&'static str> for TokenFormat { 223 | fn from(s: &'static str) -> TokenFormat { 224 | TokenFormat::Token(s) 225 | } 226 | } 227 | 228 | impl fmt::Display for TokenFormat { 229 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 230 | use TokenFormat::*; 231 | match self { 232 | // do not escape quotes as we use backticks 233 | Char('"') => write!(f, "`\"`"), 234 | Char('\'') => write!(f, "`\'`"), 235 | // also single backslash should not confuse anybody in this context 236 | Char('\\') => write!(f, r"`\`"), 237 | 238 | Char(c) => write!(f, "`{}`", c.escape_default()), 239 | Token(s) => write!(f, "`{}`", s.escape_default()), 240 | Kind(s) => write!(f, "{}", s), 241 | Eoi => write!(f, "end of input"), 242 | OpenRaw(0) => { 243 | f.write_str("`r\"`") 244 | } 245 | OpenRaw(n) => { 246 | f.write_str("`r")?; 247 | for _ in 0..*n { 248 | f.write_char('#')?; 249 | } 250 | f.write_str("\"`") 251 | } 252 | CloseRaw(0) => { 253 | f.write_str("`\"`") 254 | } 255 | CloseRaw(n) => { 256 | f.write_str("`\"")?; 257 | for _ in 0..*n { 258 | f.write_char('#')?; 259 | } 260 | f.write_char('`') 261 | } 262 | } 263 | } 264 | } 265 | 266 | impl fmt::Display for FormatUnexpected<'_> { 267 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 268 | write!(f, "found {}", self.0)?; 269 | let mut iter = self.1.iter(); 270 | if let Some(item) = iter.next() { 271 | write!(f, ", expected {}", item)?; 272 | let back = iter.next_back(); 273 | for item in iter { 274 | write!(f, ", {}", item)?; 275 | } 276 | if let Some(item) = back { 277 | write!(f, " or {}", item)?; 278 | } 279 | } 280 | Ok(()) 281 | } 282 | } 283 | 284 | impl ParseError { 285 | pub(crate) fn with_expected_token(mut self, token: &'static str) -> Self { 286 | use ParseError::*; 287 | match &mut self { 288 | Unexpected { ref mut expected, .. } => { 289 | *expected = [TokenFormat::Token(token)].into_iter().collect(); 290 | } 291 | _ => {}, 292 | } 293 | self 294 | } 295 | pub(crate) fn with_expected_kind(mut self, token: &'static str) -> Self { 296 | use ParseError::*; 297 | match &mut self { 298 | Unexpected { ref mut expected, .. } => { 299 | *expected = [TokenFormat::Kind(token)].into_iter().collect(); 300 | } 301 | _ => {}, 302 | } 303 | self 304 | } 305 | pub(crate) fn with_no_expected(mut self) -> Self { 306 | use ParseError::*; 307 | match &mut self { 308 | Unexpected { ref mut expected, .. } => { 309 | *expected = BTreeSet::new(); 310 | } 311 | _ => {}, 312 | } 313 | self 314 | } 315 | #[allow(dead_code)] 316 | pub(crate) fn map_span(self, f: impl Fn(S) -> T) -> ParseError 317 | where T: ErrorSpan, 318 | { 319 | use ParseError::*; 320 | match self { 321 | Unexpected { label, span, found, expected } 322 | => Unexpected { label, span: f(span), found, expected }, 323 | Unclosed { label, opened_at, opened, expected_at, expected, found } 324 | => Unclosed { label, opened_at: f(opened_at), opened, 325 | expected_at: f(expected_at), expected, found }, 326 | Message { label, span, message } 327 | => Message { label, span: f(span), message }, 328 | MessageWithHelp { label, span, message, help } 329 | => MessageWithHelp { label, span: f(span), message, help }, 330 | } 331 | } 332 | } 333 | 334 | impl chumsky::Error for ParseError { 335 | type Span = S; 336 | type Label = &'static str; 337 | fn expected_input_found(span: Self::Span, expected: Iter, 338 | found: Option) 339 | -> Self 340 | where Iter: IntoIterator> 341 | { 342 | ParseError::Unexpected { 343 | label: None, 344 | span, 345 | found: found.into(), 346 | expected: expected.into_iter().map(Into::into).collect(), 347 | } 348 | } 349 | fn with_label(mut self, new_label: Self::Label) -> Self { 350 | use ParseError::*; 351 | match self { 352 | Unexpected { ref mut label, .. } => *label = Some(new_label), 353 | Unclosed { ref mut label, .. } => *label = new_label, 354 | Message { ref mut label, .. } => *label = Some(new_label), 355 | MessageWithHelp { ref mut label, .. } => *label = Some(new_label), 356 | } 357 | self 358 | } 359 | fn merge(mut self, other: Self) -> Self { 360 | use ParseError::*; 361 | match (&mut self, other) { 362 | (Unclosed { .. }, _) => self, 363 | (_, other@Unclosed { .. }) => other, 364 | (Unexpected { expected: ref mut dest, .. }, 365 | Unexpected { expected, .. }) 366 | => { 367 | dest.extend(expected.into_iter()); 368 | self 369 | } 370 | (_, other) => todo!("{} -> {}", self, other), 371 | } 372 | } 373 | fn unclosed_delimiter( 374 | unclosed_span: Self::Span, 375 | unclosed: char, 376 | span: Self::Span, 377 | expected: char, 378 | found: Option 379 | ) -> Self { 380 | ParseError::Unclosed { 381 | label: "delimited", 382 | opened_at: unclosed_span, 383 | opened: unclosed.into(), 384 | expected_at: span, 385 | expected: expected.into(), 386 | found: found.into(), 387 | } 388 | } 389 | } 390 | 391 | impl DecodeError { 392 | /// Construct [`DecodeError::Conversion`] error 393 | pub fn conversion(span: &Spanned, err: E) -> Self 394 | where E: Into>, 395 | { 396 | DecodeError::Conversion { 397 | span: span.span().clone(), 398 | source: err.into(), 399 | } 400 | } 401 | /// Construct [`DecodeError::ScalarKind`] error 402 | pub fn scalar_kind(expected: Kind, found: &Spanned) -> Self { 403 | DecodeError::ScalarKind { 404 | span: found.span().clone(), 405 | expected: expected.into(), 406 | found: (&found.value).into(), 407 | } 408 | } 409 | /// Construct [`DecodeError::Missing`] error 410 | pub fn missing(node: &SpannedNode, message: impl Into) -> Self { 411 | DecodeError::Missing { 412 | span: node.node_name.span().clone(), 413 | message: message.into(), 414 | } 415 | } 416 | /// Construct [`DecodeError::Unexpected`] error 417 | pub fn unexpected(elem: &Spanned, kind: &'static str, 418 | message: impl Into) 419 | -> Self 420 | { 421 | DecodeError::Unexpected { 422 | span: elem.span().clone(), 423 | kind, 424 | message: message.into(), 425 | } 426 | } 427 | /// Construct [`DecodeError::Unsupported`] error 428 | pub fn unsupported(span: &Spanned, message: M)-> Self 429 | where M: Into>, 430 | { 431 | DecodeError::Unsupported { 432 | span: span.span().clone(), 433 | message: message.into(), 434 | } 435 | } 436 | #[allow(dead_code)] 437 | pub(crate) fn map_span(self, mut f: impl FnMut(S) -> T) 438 | -> DecodeError 439 | where T: ErrorSpan, 440 | { 441 | use DecodeError::*; 442 | match self { 443 | TypeName { span, found, expected, rust_type } 444 | => TypeName { span: f(span), found, expected, rust_type }, 445 | ScalarKind { span, expected, found } 446 | => ScalarKind { span: f(span), expected, found }, 447 | Missing { span, message } 448 | => Missing { span: f(span), message}, 449 | MissingNode { message } 450 | => MissingNode { message }, 451 | Unexpected { span, kind, message } 452 | => Unexpected { span: f(span), kind, message}, 453 | Conversion { span, source } 454 | => Conversion { span: f(span), source }, 455 | Unsupported { span, message } 456 | => Unsupported { span: f(span), message }, 457 | Custom(e) => Custom(e), 458 | } 459 | } 460 | } 461 | 462 | /// Wrapper around expected type that is used in [`DecodeError::TypeName`]. 463 | #[derive(Debug)] 464 | pub struct ExpectedType { 465 | types: Vec, 466 | no_type: bool, 467 | } 468 | 469 | impl ExpectedType { 470 | /// Declare that decoder expects no type (no parens at all) for the value 471 | pub fn no_type() -> Self { 472 | ExpectedType { 473 | types: [].into(), 474 | no_type: true, 475 | } 476 | } 477 | /// Declare the type that has to be attached to the value 478 | pub fn required(ty: impl Into) -> Self { 479 | ExpectedType { 480 | types: vec![ty.into()], 481 | no_type: false, 482 | } 483 | } 484 | /// Declare the type that can be attached to the value 485 | /// 486 | /// But no type is also okay in this case (although, "no type" and specified 487 | /// type can potentially have different meaning). 488 | pub fn optional(ty: impl Into) -> Self { 489 | ExpectedType { 490 | types: vec![ty.into()], 491 | no_type: true, 492 | } 493 | } 494 | } 495 | 496 | impl fmt::Display for ExpectedType { 497 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 498 | if self.types.is_empty() { 499 | write!(f, "no type") 500 | } else { 501 | let mut iter = self.types.iter(); 502 | if let Some(first) = iter.next() { 503 | write!(f, "{}", first)?; 504 | } 505 | let last = if self.no_type { 506 | None 507 | } else { 508 | iter.next_back() 509 | }; 510 | for item in iter { 511 | write!(f, ", {}", item)?; 512 | } 513 | if self.no_type { 514 | write!(f, " or no type")?; 515 | } else if let Some(last) = last { 516 | write!(f, " or {}", last)?; 517 | } 518 | Ok(()) 519 | } 520 | } 521 | } 522 | 523 | 524 | /// Declares kind of value expected for the scalar value 525 | /// 526 | /// Use [`Kind`](crate::decode::Kind) and `.into()` to create this value. 527 | #[derive(Debug)] 528 | pub struct ExpectedKind(Kind); 529 | 530 | impl From for ExpectedKind { 531 | fn from(kind: Kind) -> ExpectedKind { 532 | ExpectedKind(kind) 533 | } 534 | } 535 | 536 | impl fmt::Display for ExpectedKind { 537 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 538 | write!(f, "{}", self.0.as_str()) 539 | } 540 | } 541 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![warn(missing_docs)] 3 | #![warn(missing_debug_implementations)] 4 | 5 | mod containers; 6 | mod convert; 7 | mod convert_ast; 8 | mod grammar; 9 | mod wrappers; 10 | 11 | pub mod ast; 12 | pub mod decode; 13 | pub mod errors; 14 | pub mod span; 15 | pub mod traits; 16 | 17 | #[cfg(feature="derive")] 18 | pub use knuffel_derive::{Decode, DecodeScalar}; 19 | 20 | pub use wrappers::{parse_ast, parse, parse_with_context}; 21 | pub use traits::{Decode, DecodeScalar, DecodeChildren}; 22 | pub use errors::Error; 23 | -------------------------------------------------------------------------------- /src/span.rs: -------------------------------------------------------------------------------- 1 | //! Knuffel supports to kinds of the span for parsing 2 | //! 3 | //! 1. [`Span`] which only tracks byte offset from the start of the source code 4 | //! 2. [`LineSpan`] which also track line numbers 5 | //! 6 | //! This distinction is important during parsing stage as [`Span`] is normally 7 | //! faster. And [`LineSpan`] is still faster than find out line/column number 8 | //! for each span separately, and is also more convenient if you need this 9 | //! information. 10 | //! 11 | //! On the other hand, on the decode stage you can convert your span types into 12 | //! more elaborate thing that includes file name or can refer to the defaults 13 | //! as a separate kind of span. See [`traits::DecodeSpan`]. 14 | use std::fmt; 15 | use std::ops::Range; 16 | 17 | use crate::traits; 18 | use crate::decode::Context; 19 | 20 | /// Reexport of [miette::SourceSpan] trait that we use for parsing 21 | pub use miette::SourceSpan as ErrorSpan; 22 | 23 | /// Wraps the structure to keep source code span, but also dereference to T 24 | #[derive(Clone, Debug)] 25 | #[cfg_attr(feature="minicbor", derive(minicbor::Encode, minicbor::Decode))] 26 | pub struct Spanned { 27 | #[cfg_attr(feature="minicbor", n(0))] 28 | pub(crate) span: S, 29 | #[cfg_attr(feature="minicbor", n(1))] 30 | pub(crate) value: T, 31 | } 32 | 33 | /// Normal byte offset span 34 | #[derive(Clone, Debug, PartialEq, Eq)] 35 | #[cfg_attr(feature="minicbor", derive(minicbor::Encode, minicbor::Decode))] 36 | pub struct Span( 37 | #[cfg_attr(feature="minicbor", n(0))] 38 | pub usize, 39 | #[cfg_attr(feature="minicbor", n(1))] 40 | pub usize, 41 | ); 42 | 43 | /// Line and column position of the datum in the source code 44 | // TODO(tailhook) optimize Eq to check only offset 45 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 46 | #[cfg_attr(feature="minicbor", derive(minicbor::Encode, minicbor::Decode))] 47 | pub struct LinePos { 48 | /// Zero-based byte offset 49 | #[cfg_attr(feature="minicbor", n(0))] 50 | pub offset: usize, 51 | /// Zero-based line number 52 | #[cfg_attr(feature="minicbor", n(1))] 53 | pub line: usize, 54 | /// Zero-based column number 55 | #[cfg_attr(feature="minicbor", n(2))] 56 | pub column: usize, 57 | } 58 | 59 | /// Span with line and column number 60 | #[derive(Clone, Debug, PartialEq, Eq)] 61 | #[cfg_attr(feature="minicbor", derive(minicbor::Encode, minicbor::Decode))] 62 | pub struct LineSpan( 63 | #[cfg_attr(feature="minicbor", n(0))] 64 | pub LinePos, 65 | #[cfg_attr(feature="minicbor", n(1))] 66 | pub LinePos, 67 | ); 68 | 69 | #[allow(missing_debug_implementations)] 70 | mod sealed { 71 | 72 | pub struct OffsetTracker { 73 | pub(crate) offset: usize, 74 | } 75 | 76 | #[cfg(feature="line-numbers")] 77 | pub struct LineTracker { 78 | pub(crate) offset: usize, 79 | pub(crate) caret_return: bool, 80 | pub(crate) line: usize, 81 | pub(crate) column: usize, 82 | } 83 | 84 | } 85 | 86 | 87 | impl Span { 88 | /// Length of the span in bytes 89 | pub fn length(&self) -> usize { 90 | self.1.saturating_sub(self.0) 91 | } 92 | } 93 | 94 | impl Into for Span { 95 | fn into(self) -> ErrorSpan { 96 | (self.0, self.1.saturating_sub(self.0)).into() 97 | } 98 | } 99 | 100 | impl Into for LineSpan { 101 | fn into(self) -> ErrorSpan { 102 | (self.0.offset, self.1.offset.saturating_sub(self.0.offset)).into() 103 | } 104 | } 105 | 106 | impl chumsky::Span for Span { 107 | type Context = (); 108 | type Offset = usize; 109 | fn new(_context: (), range: std::ops::Range) -> Self { 110 | Span(range.start(), range.end()) 111 | } 112 | fn context(&self) -> () { () } 113 | fn start(&self) -> usize { self.0 } 114 | fn end(&self) -> usize { self.1 } 115 | } 116 | impl traits::sealed::SpanTracker for sealed::OffsetTracker { 117 | type Span = Span; 118 | fn next_span(&mut self, c: char) -> Span { 119 | let start = self.offset; 120 | self.offset += c.len_utf8(); 121 | Span(start, self.offset) 122 | } 123 | } 124 | 125 | 126 | impl traits::sealed::Sealed for Span { 127 | type Tracker = sealed::OffsetTracker; 128 | fn at_start(&self, chars: usize) -> Self { 129 | Span(self.0, self.0+chars) 130 | } 131 | 132 | fn at_end(&self) -> Self { 133 | Span(self.1, self.1) 134 | } 135 | 136 | fn before_start(&self, chars: usize) -> Self { 137 | Span(self.0.saturating_sub(chars), self.0) 138 | } 139 | 140 | fn length(&self) -> usize { 141 | self.1.saturating_sub(self.0) 142 | } 143 | 144 | fn stream(text: &str) -> traits::sealed::Stream<'_, Self, Self::Tracker> 145 | where Self: chumsky::Span 146 | { 147 | chumsky::Stream::from_iter( 148 | Span(text.len(), text.len()), 149 | traits::sealed::Map(text.chars(), 150 | sealed::OffsetTracker { offset: 0 }), 151 | ) 152 | } 153 | } 154 | 155 | impl traits::Span for Span {} 156 | 157 | impl chumsky::Span for LineSpan { 158 | type Context = (); 159 | type Offset = LinePos; 160 | fn new(_context: (), range: std::ops::Range) -> Self { 161 | LineSpan(range.start, range.end) 162 | } 163 | fn context(&self) -> () { () } 164 | fn start(&self) -> LinePos { self.0 } 165 | fn end(&self) -> LinePos { self.1 } 166 | } 167 | 168 | #[cfg(feature="line-numbers")] 169 | impl traits::sealed::SpanTracker for sealed::LineTracker { 170 | type Span = LineSpan; 171 | fn next_span(&mut self, c: char) -> LineSpan { 172 | let offset = self.offset; 173 | let line = self.line; 174 | let column = self.column; 175 | self.offset += c.len_utf8(); 176 | match c { 177 | '\n' if self.caret_return => {} 178 | '\r'|'\n'|'\x0C'|'\u{0085}'|'\u{2028}'|'\u{2029}' => { 179 | self.line += 1; 180 | self.column = 0; 181 | } 182 | '\t' => self.column += 8, 183 | c => { 184 | self.column += unicode_width::UnicodeWidthChar::width(c) 185 | .unwrap_or(0); // treat control chars as zero-length 186 | } 187 | } 188 | self.caret_return = c == '\r'; 189 | LineSpan( 190 | LinePos { 191 | line, 192 | column, 193 | offset, 194 | }, 195 | LinePos { 196 | line: self.line, 197 | column: self.column, 198 | offset: self.offset, 199 | }, 200 | ) 201 | } 202 | } 203 | 204 | #[cfg(feature="line-numbers")] 205 | impl traits::sealed::Sealed for LineSpan { 206 | type Tracker = sealed::LineTracker; 207 | /// Note assuming ascii, single-width, non-newline chars here 208 | fn at_start(&self, chars: usize) -> Self { 209 | LineSpan(self.0, LinePos { 210 | offset: self.0.offset + chars, 211 | column: self.0.column + chars, 212 | .. self.0 213 | }) 214 | } 215 | 216 | fn at_end(&self) -> Self { 217 | LineSpan(self.1, self.1) 218 | } 219 | 220 | /// Note assuming ascii, single-width, non-newline chars here 221 | fn before_start(&self, chars: usize) -> Self { 222 | LineSpan(LinePos { 223 | offset: self.0.offset.saturating_sub(chars), 224 | column: self.0.column.saturating_sub(chars), 225 | .. self.0 226 | }, self.0) 227 | } 228 | 229 | fn length(&self) -> usize { 230 | self.1.offset.saturating_sub(self.0.offset) 231 | } 232 | 233 | fn stream(text: &str) -> traits::sealed::Stream<'_, Self, Self::Tracker> 234 | where Self: chumsky::Span 235 | { 236 | let mut caret_return = false; 237 | let mut line = 0; 238 | let mut last_line = text; 239 | let mut iter = text.chars(); 240 | while let Some(c) = iter.next() { 241 | match c { 242 | '\n' if caret_return => {} 243 | '\r'|'\n'|'\x0C'|'\u{0085}'|'\u{2028}'|'\u{2029}' => { 244 | line += 1; 245 | last_line = iter.as_str(); 246 | } 247 | _ => {} 248 | } 249 | caret_return = c == '\r'; 250 | } 251 | let column = unicode_width::UnicodeWidthStr::width(last_line); 252 | let eoi = LinePos { 253 | line, 254 | column, 255 | offset: text.len(), 256 | }; 257 | chumsky::Stream::from_iter( 258 | LineSpan(eoi, eoi), 259 | traits::sealed::Map( 260 | text.chars(), 261 | sealed::LineTracker { 262 | caret_return: false, 263 | offset: 0, 264 | line: 0, 265 | column: 0, 266 | }, 267 | ), 268 | ) 269 | } 270 | } 271 | 272 | #[cfg(feature="line-numbers")] 273 | impl traits::Span for LineSpan {} 274 | 275 | #[cfg(feature="line-numbers")] 276 | impl traits::DecodeSpan for Span { 277 | fn decode_span(span: &LineSpan, _: &mut Context) 278 | -> Self 279 | { 280 | Span(span.0.offset, span.1.offset) 281 | } 282 | } 283 | 284 | impl Spanned { 285 | /// Converts value but keeps the same span attached 286 | pub fn map(self, f: impl FnOnce(T) -> R) -> Spanned { 287 | Spanned { 288 | span: self.span, 289 | value: f(self.value), 290 | } 291 | } 292 | /// Converts span but keeps the same value attached 293 | pub fn map_span(self, f: impl FnOnce(S) -> U) -> Spanned { 294 | Spanned { 295 | span: f(self.span), 296 | value: self.value, 297 | } 298 | } 299 | pub(crate) fn clone_as(&self, ctx: &mut Context) -> Spanned 300 | where U: traits::DecodeSpan, 301 | T: Clone, 302 | S: traits::ErrorSpan, 303 | { 304 | Spanned { 305 | span: traits::DecodeSpan::decode_span(&self.span, ctx), 306 | value: self.value.clone(), 307 | } 308 | } 309 | } 310 | 311 | impl, S> AsRef for Spanned { 312 | fn as_ref(&self) -> &U { 313 | self.value.as_ref() 314 | } 315 | } 316 | 317 | impl, S> AsMut for Spanned { 318 | fn as_mut(&mut self) -> &mut U { 319 | self.value.as_mut() 320 | } 321 | } 322 | 323 | impl std::ops::Deref for Spanned { 324 | type Target = T; 325 | fn deref(&self) -> &T { 326 | &self.value 327 | } 328 | } 329 | 330 | impl std::ops::DerefMut for Spanned { 331 | fn deref_mut(&mut self) -> &mut T { 332 | &mut self.value 333 | } 334 | } 335 | 336 | impl std::borrow::Borrow for Spanned { 337 | fn borrow(&self) -> &T { 338 | self.value.borrow() 339 | } 340 | } 341 | 342 | impl std::borrow::Borrow for Spanned, S> { 343 | fn borrow(&self) -> &T { 344 | self.value.borrow() 345 | } 346 | } 347 | 348 | impl Spanned { 349 | /// Returns the span of the value 350 | pub fn span(&self) -> &S { 351 | &self.span 352 | } 353 | } 354 | 355 | impl> PartialEq for Spanned { 356 | fn eq(&self, other: &Spanned) -> bool { 357 | self.value == other.value 358 | } 359 | } 360 | 361 | impl> PartialOrd for Spanned { 362 | fn partial_cmp(&self, other: &Spanned) 363 | -> Option 364 | { 365 | self.value.partial_cmp(&other.value) 366 | } 367 | } 368 | 369 | impl Ord for Spanned { 370 | fn cmp(&self, other: &Spanned) -> std::cmp::Ordering { 371 | self.value.cmp(&other.value) 372 | } 373 | } 374 | 375 | impl Eq for Spanned {} 376 | 377 | impl std::hash::Hash for Spanned { 378 | fn hash(&self, state: &mut H) 379 | where H: std::hash::Hasher, 380 | { 381 | self.value.hash(state) 382 | } 383 | } 384 | 385 | impl fmt::Display for Span { 386 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 387 | self.0.fmt(f)?; 388 | "..".fmt(f)?; 389 | self.1.fmt(f)?; 390 | Ok(()) 391 | } 392 | } 393 | 394 | impl From> for Span { 395 | fn from(r: Range) -> Span { 396 | Span(r.start, r.end) 397 | } 398 | } 399 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | //! Traits used for the library 2 | //! 3 | //! Most users will never implement these manually. See 4 | //! [`Decode`](derive@crate::Decode)` and 5 | //! [`DecodeScalar`](derive@crate::DecodeScalar) for a 6 | //! documentation of the derives to implement these traits. 7 | use std::fmt; 8 | 9 | use crate::ast::{SpannedNode, Literal, Value, TypeName}; 10 | use crate::span::Spanned; 11 | use crate::errors::DecodeError; 12 | use crate::decode::Context; 13 | 14 | 15 | /// Trait to decode KDL node from the AST 16 | pub trait Decode: Sized { 17 | /// Decodes the node from the ast 18 | fn decode_node(node: &SpannedNode, ctx: &mut Context) 19 | -> Result>; 20 | } 21 | 22 | /// Trait to decode children of the KDL node, mostly used for root document 23 | pub trait DecodeChildren: Sized { 24 | /// Decodes from a list of chidren ASTs 25 | fn decode_children(nodes: &[SpannedNode], ctx: &mut Context) 26 | -> Result>; 27 | } 28 | 29 | /// The trait is implemented for structures that can be used as part of other 30 | /// structs 31 | /// 32 | /// The type of field that `#[knuffel(flatten)]` is used for should implement 33 | /// this trait. It is automatically implemented by `#[derive(knuffel::Decode)]` 34 | /// by structures that have only optional properties and children (no 35 | /// arguments). 36 | pub trait DecodePartial: Sized { 37 | /// The method is called when unknown child is encountered by parent 38 | /// structure 39 | /// 40 | /// Returns `Ok(true)` if the child is "consumed" (i.e. stored in this 41 | /// structure). 42 | fn insert_child(&mut self, node: &SpannedNode, ctx: &mut Context) 43 | -> Result>; 44 | /// The method is called when unknown property is encountered by parent 45 | /// structure 46 | /// 47 | /// Returns `Ok(true)` if the property is "consumed" (i.e. stored in this 48 | /// structure). 49 | fn insert_property(&mut self, 50 | name: &Spanned, S>, value: &Value, 51 | ctx: &mut Context) 52 | -> Result>; 53 | } 54 | 55 | /// The trait that decodes scalar value and checks its type 56 | pub trait DecodeScalar: Sized { 57 | /// Typecheck the value 58 | /// 59 | /// This method can only emit errors to the context in type mismatch case. 60 | /// Errors emitted to the context are considered fatal once the whole data 61 | /// is processed but non fatal when encountered. So even if there is a type 62 | /// in type name we can proceed and try parsing actual value. 63 | fn type_check(type_name: &Option>, 64 | ctx: &mut Context); 65 | /// Decode value without typecheck 66 | /// 67 | /// This can be used by wrappers to parse some know value but use a 68 | /// different typename (kinda emulated subclassing) 69 | fn raw_decode(value: &Spanned, ctx: &mut Context) 70 | -> Result>; 71 | /// Decode the value and typecheck 72 | /// 73 | /// This should not be overriden and uses `type_check` in combination with 74 | /// `raw_decode`. 75 | fn decode(value: &Value, ctx: &mut Context) 76 | -> Result> 77 | { 78 | Self::type_check(&value.type_name, ctx); 79 | Self::raw_decode(&value.literal, ctx) 80 | } 81 | } 82 | 83 | 84 | /// The trait that decodes span into the final structure 85 | pub trait DecodeSpan: Sized { 86 | /// Decode span 87 | /// 88 | /// This method can use some extra data (say file name) from the context. 89 | /// Although, by default context is empty and end users are expected to use 90 | /// [`parse_with_context`](crate::parse_with_context) to add some values. 91 | fn decode_span(span: &S, ctx: &mut Context) -> Self; 92 | } 93 | 94 | impl DecodeSpan for T { 95 | fn decode_span(span: &T, _: &mut Context) -> Self { 96 | span.clone() 97 | } 98 | } 99 | 100 | /// Span must implement this trait to be used in the error messages 101 | /// 102 | /// Custom span types can be used for this unlike for [`Span`] 103 | pub trait ErrorSpan: Into 104 | + Clone + fmt::Debug + Send + Sync + 'static {} 105 | impl ErrorSpan for T 106 | where T: Into, 107 | T: Clone + fmt::Debug + Send + Sync + 'static, 108 | {} 109 | 110 | 111 | /// Span trait used for parsing source code 112 | /// 113 | /// It's sealed because needs some tight interoperation with the parser. Use 114 | /// [`DecodeSpan`] to convert spans whenever needed. 115 | pub trait Span: sealed::Sealed + chumsky::Span + ErrorSpan {} 116 | 117 | #[allow(missing_debug_implementations)] 118 | pub(crate) mod sealed { 119 | pub type Stream<'a, S, T> = chumsky::Stream< 120 | 'a, char, S, Map, T> 121 | >; 122 | 123 | pub struct Map(pub(crate) I, pub(crate) F); 124 | 125 | pub trait SpanTracker { 126 | type Span; 127 | fn next_span(&mut self, c: char) -> Self::Span; 128 | } 129 | 130 | impl Iterator for Map 131 | where I: Iterator, 132 | T: SpanTracker, 133 | { 134 | type Item = (char, T::Span); 135 | fn next(&mut self) -> Option<(char, T::Span)> { 136 | self.0.next().map(|c| (c, self.1.next_span(c))) 137 | } 138 | } 139 | 140 | pub trait Sealed { 141 | type Tracker: SpanTracker; 142 | /// Note assuming ascii, single-width, non-newline chars here 143 | fn at_start(&self, chars: usize) -> Self; 144 | fn at_end(&self) -> Self; 145 | /// Note assuming ascii, single-width, non-newline chars here 146 | fn before_start(&self, chars: usize) -> Self; 147 | fn length(&self) -> usize; 148 | 149 | fn stream(s: &str) -> Stream<'_, Self, Self::Tracker> 150 | where Self: chumsky::Span; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/wrappers.rs: -------------------------------------------------------------------------------- 1 | use chumsky::Parser; 2 | use miette::NamedSource; 3 | 4 | use crate::ast::Document; 5 | use crate::decode::Context; 6 | use crate::errors::Error; 7 | use crate::grammar; 8 | use crate::span::{Span}; 9 | use crate::traits::{self, DecodeChildren}; 10 | 11 | 12 | /// Parse KDL text and return AST 13 | pub fn parse_ast(file_name: &str, text: &str) 14 | -> Result, Error> 15 | { 16 | grammar::document() 17 | .parse(S::stream(text)) 18 | .map_err(|errors| { 19 | Error { 20 | source_code: NamedSource::new(file_name, text.to_string()), 21 | errors: errors.into_iter().map(Into::into).collect(), 22 | } 23 | }) 24 | } 25 | 26 | /// Parse KDL text and decode Rust object 27 | pub fn parse(file_name: &str, text: &str) -> Result 28 | where T: DecodeChildren, 29 | { 30 | parse_with_context(file_name, text, |_| {}) 31 | } 32 | 33 | /// Parse KDL text and decode Rust object providing extra context for the 34 | /// decoder 35 | pub fn parse_with_context(file_name: &str, text: &str, set_ctx: F) 36 | -> Result 37 | where F: FnOnce(&mut Context), 38 | T: DecodeChildren, 39 | S: traits::Span, 40 | { 41 | let ast = parse_ast(file_name, text)?; 42 | 43 | let mut ctx = Context::new(); 44 | set_ctx(&mut ctx); 45 | let errors = match DecodeChildren::decode_children(&ast.nodes, &mut ctx) { 46 | Ok(_) if ctx.has_errors() => { 47 | ctx.into_errors() 48 | } 49 | Err(e) => { 50 | ctx.emit_error(e); 51 | ctx.into_errors() 52 | } 53 | Ok(v) => return Ok(v) 54 | }; 55 | return Err(Error { 56 | source_code: NamedSource::new(file_name, text.to_string()), 57 | errors: errors.into_iter().map(Into::into).collect(), 58 | }); 59 | } 60 | 61 | #[test] 62 | fn normal() { 63 | let doc = parse_ast::("embedded.kdl", r#"node "hello""#).unwrap(); 64 | assert_eq!(doc.nodes.len(), 1); 65 | assert_eq!(&**doc.nodes[0].node_name, "node"); 66 | } 67 | -------------------------------------------------------------------------------- /vagga.yaml: -------------------------------------------------------------------------------- 1 | commands: 2 | 3 | make: !Command 4 | description: Build the library and CLI 5 | container: ubuntu 6 | run: [cargo, build] 7 | 8 | cargo: !Command 9 | description: Run arbitrary cargo command 10 | symlink-name: cargo 11 | container: ubuntu 12 | run: [cargo] 13 | 14 | nightly-build: !Command 15 | description: Run cargo build on nightly 16 | symlink-name: cargo 17 | container: nightly 18 | run: [cargo, build] 19 | environ: 20 | RUSTFLAGS: "-Z macro-backtrace" 21 | 22 | expand: !Command 23 | description: Run cargo expand 24 | symlink-name: cargo 25 | container: nightly 26 | run: [cargo, expand] 27 | 28 | test-rust: !Command 29 | description: Run test suite 30 | container: ubuntu 31 | run: [cargo, test, --workspace] 32 | volumes: 33 | /tmp: !Tmpfs 34 | size: 1Gi 35 | 36 | test-rust-all-features: !Command 37 | description: Run tests with all features enabled 38 | container: ubuntu 39 | run: [cargo, test, --workspace, --all-features] 40 | volumes: 41 | /tmp: !Tmpfs 42 | size: 1Gi 43 | 44 | 45 | test: !Command 46 | description: Run all tests 47 | container: ubuntu 48 | prerequisites: [test-rust, test-rust-all-features] 49 | run: [echo, Ok] 50 | 51 | containers: 52 | 53 | ubuntu: 54 | setup: 55 | - !Ubuntu jammy 56 | - !Install [ca-certificates, git, build-essential, vim, 57 | pkg-config, libssl-dev] 58 | 59 | - !TarInstall 60 | url: "https://static.rust-lang.org/dist/rust-1.68.0-x86_64-unknown-linux-gnu.tar.gz" 61 | script: "./install.sh --prefix=/usr \ 62 | --components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo" 63 | - !TarInstall 64 | url: "https://static.rust-lang.org/dist/rust-std-1.68.0-wasm32-unknown-unknown.tar.gz" 65 | script: "./install.sh --prefix=/usr --components=rust-std-wasm32-unknown-unknown" 66 | - !Sh 'cargo install cargo-release cargo-audit cargo-outdated --root=/usr' 67 | 68 | environ: 69 | HOME: /work/target 70 | LANG: C.UTF-8 71 | RUST_BACKTRACE: 1 72 | 73 | nightly: 74 | setup: 75 | - !Ubuntu focal 76 | - !Install [ca-certificates, git, build-essential, vim] 77 | 78 | - !TarInstall 79 | url: "https://static.rust-lang.org/dist/rust-nightly-x86_64-unknown-linux-gnu.tar.gz" 80 | script: "./install.sh --prefix=/usr \ 81 | --components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo" 82 | - !Sh 'cargo install cargo-expand --root=/usr' 83 | 84 | environ: 85 | HOME: /work/target 86 | RUST_BACKTRACE: 1 87 | --------------------------------------------------------------------------------