Repository: udoprog/genco Branch: main Commit: b48f305532b5 Files: 77 Total size: 342.1 KB Directory structure: gitextract_oks2q_lp/ ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples/ │ ├── c.rs │ ├── csharp.rs │ ├── dart.rs │ ├── go.rs │ ├── java.rs │ ├── js.rs │ ├── kotlin.rs │ ├── nix.rs │ ├── python.rs │ └── rust.rs ├── genco-macros/ │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ └── src/ │ ├── ast.rs │ ├── cursor.rs │ ├── encoder.rs │ ├── fake.rs │ ├── lib.rs │ ├── quote.rs │ ├── quote_fn.rs │ ├── quote_in.rs │ ├── requirements.rs │ ├── static_buffer.rs │ └── string_parser.rs ├── src/ │ ├── fmt/ │ │ ├── config.rs │ │ ├── cursor.rs │ │ ├── fmt_writer.rs │ │ ├── formatter.rs │ │ ├── io_writer.rs │ │ ├── mod.rs │ │ └── vec_writer.rs │ ├── lang/ │ │ ├── c.rs │ │ ├── csharp/ │ │ │ ├── block_comment.rs │ │ │ ├── comment.rs │ │ │ └── mod.rs │ │ ├── dart/ │ │ │ ├── doc_comment.rs │ │ │ └── mod.rs │ │ ├── go.rs │ │ ├── java/ │ │ │ ├── block_comment.rs │ │ │ └── mod.rs │ │ ├── js.rs │ │ ├── kotlin/ │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── nix.rs │ │ ├── python.rs │ │ ├── rust.rs │ │ └── swift.rs │ ├── lib.rs │ ├── macros.rs │ ├── prelude.rs │ └── tokens/ │ ├── display.rs │ ├── format_into.rs │ ├── from_fn.rs │ ├── internal.rs │ ├── item.rs │ ├── item_str.rs │ ├── mod.rs │ ├── quoted.rs │ ├── register.rs │ ├── static_literal.rs │ └── tokens.rs └── tests/ ├── test_indentation_rules.rs ├── test_option.rs ├── test_quote.rs ├── test_quote_in.rs ├── test_quote_simple_expression.rs ├── test_register.rs ├── test_string.rs └── test_token_gen.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: pull_request: {} push: branches: - main schedule: - cron: '44 6 * * 0' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: msrv: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@1.88 - run: cargo build --workspace --lib - run: cargo build --workspace --lib --no-default-features --features alloc test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: rust: [stable, nightly] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{matrix.rust}} - run: cargo build --workspace --all-targets if: matrix.rust == 'stable' - run: cargo test --workspace --all-targets if: matrix.rust == 'nightly' - run: cargo test --workspace --doc if: matrix.rust == 'nightly' clippy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: clippy - run: cargo clippy --workspace --all-features --all-targets -- -D warnings rustfmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - run: cargo fmt --check --all docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - uses: Swatinem/rust-cache@v2 - run: cargo doc --lib --no-deps --document-private-items env: RUSTFLAGS: --cfg doc_cfg RUSTDOCFLAGS: --cfg doc_cfg -D warnings ================================================ FILE: .gitignore ================================================ /target/ **/*.rs.bk Cargo.lock ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] [Unreleased]: https://github.com/udoprog/genco/compare/0.17.3...master ## [0.17.4] ### Changed * Update project documentation. [0.17.4]: https://github.com/udoprog/genco/compare/0.17.3...0.17.4 ## [0.17.3] ### Changed * Fixed badge in project. [0.17.3]: https://github.com/udoprog/genco/compare/0.17.2...0.17.3 ## [0.17.2] ### Added * Added `Copy` and `Clone` implementations for `FromFn` ([#31]). ### Changed * Changed internal syntax of doc tests ([#32]). [#31]: https://github.com/udoprog/genco/issues/31 [#32]: https://github.com/udoprog/genco/issues/32 [0.17.2]: https://github.com/udoprog/genco/compare/0.17.1...0.17.2 ## [0.17.1] ### Changed * Documentation fixes. [0.17.1]: https://github.com/udoprog/genco/compare/0.17.0...0.17.1 ## [0.17.0] ### Added * Added `FormatInto` implementation for `Arguments<'_>` ([#26]). ### Changed * All syntax has been changed from using `#` to `$` ([#27]). * `--cfg genco_nightly` has been deprecated in favor of using byte-span hacks to detect whitespace between tokens on the same column. [#26]: https://github.com/udoprog/genco/issues/26 [#27]: https://github.com/udoprog/genco/issues/27 [0.17.0]: https://github.com/udoprog/genco/compare/0.16.0...0.17.0 ## [0.16.0] ### Changed * Add basic support for using genco to tokenize on stable ([#20]). ## [0.15.1] ### Fixed * Fixed typos in documentation. * Fixed new Clippy lints. ## [0.15.0] ### Fixed * csharp: System must be imported ([#13]). ### Changed * Parse match blocks better by ignoring end condition for nested groups ([#13]). * Match arms containing parenthesis are now whitespace sensitive ([#13]). * Language items are no longer trait objects ([#14]). * Use a singly-linked list to improve how quickly we can iterate over language items in token streams ([#16]). * Pass formatting configuration by reference instead of by value when constructing a formatter ([#17]). ### Added * Patterns are now parsed correctly to support alternatives separated by pipes ([#12]). * Added `quote_fn!` macro and added `FormatInto` to the prelude ([#13]). [#17]: https://github.com/udoprog/genco/issues/17 [#16]: https://github.com/udoprog/genco/issues/16 [#14]: https://github.com/udoprog/genco/issues/14 [#13]: https://github.com/udoprog/genco/issues/13 [#12]: https://github.com/udoprog/genco/issues/12 [#20]: https://github.com/udoprog/genco/issues/20 [0.16.0]: https://github.com/udoprog/genco/compare/0.15.0...0.16.0 [0.15.0]: https://github.com/udoprog/genco/compare/0.14.2...0.15.0 [0.15.1]: https://github.com/udoprog/genco/compare/0.15.0...0.15.1 [0.16.0]: https://github.com/udoprog/genco/compare/0.15.1...0.16.0 ================================================ FILE: Cargo.toml ================================================ [package] name = "genco" version = "0.19.0" authors = ["John-John Tedro "] edition = "2018" rust-version = "1.88" description = "A whitespace-aware quasiquoter for beautiful code generation." documentation = "https://docs.rs/genco" readme = "README.md" homepage = "https://github.com/udoprog/genco" repository = "https://github.com/udoprog/genco" license = "MIT OR Apache-2.0" keywords = ["code-generation", "template"] categories = ["template-engine"] [features] default = ["std", "alloc"] std = [] alloc = [] [dependencies] genco-macros = { path = "./genco-macros", version = "0.19.0" } relative-path = "1.2.0" smallvec = "1.4.0" [dev-dependencies] anyhow = "1.0.31" rand = "0.7.3" [workspace] members = ["genco-macros"] ================================================ FILE: LICENSE-APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: LICENSE-MIT ================================================ Copyright (c) 2017 John-John Tedro Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # genco [github](https://github.com/udoprog/genco) [crates.io](https://crates.io/crates/genco) [docs.rs](https://docs.rs/genco) [build status](https://github.com/udoprog/genco/actions?query=branch%3Amain) A whitespace-aware quasiquoter for beautiful code generation. Central to genco are the [`quote!`] and [`quote_in!`] procedural macros which ease the construction of [token streams]. This project solves the following language-specific concerns: * **Imports** — Generates and groups [import statements] as they are used. So you only import what you use, with no redundancy. We also do our best to [solve namespace conflicts]. * **String Quoting** — genco knows how to [quote strings]. And can even [interpolate] values *into* the quoted string if it's supported by the language. * **Structural Indentation** — The quoter relies on intuitive [whitespace detection] to structurally sort out spacings and indentation. Allowing genco to generate beautiful readable code with minimal effort. This is also a requirement for generating correctly behaving code in languages like Python where [indentation is meaningful]. * **Language Customization** — Building support for new languages is a piece of cake with the help of the [impl_lang!] macro.
To support line changes during [whitespace detection], we depend on span information which was made available in Rust `1.88`. Before that, we rely on a nightly [`proc_macro_span` feature] to work. *Prior to this version of Rust* if you want fully functional whitespace detection you must build and run projects using genco with a `nightly` compiler. This is important for whitespace-sensitive languages like python. You can try the difference between: ```bash cargo run --example rust ``` And: ```bash cargo +nightly run --example rust ``` [`proc_macro_span` feature]: https://github.com/rust-lang/rust/issues/54725
## Supported Languages The following are languages which have built-in support in genco. * [🦀 Rust][rust]
[Example][rust-example] * [☕ Java][java]
[Example][java-example] * [🎼 C#][c#]
[Example][c#-example] * [🐿️ Go][go]
[Example][go-example] * [🎯 Dart][dart]
[Example][dart-example] * [🌐 JavaScript][js]
[Example][js-example] * [🇨 C][c]
[Example][c-example] * [🐍 Python][python]
[Example][python-example] Is your favorite language missing? [Open an issue!] You can run one of the examples by: ```bash cargo +nightly run --example rust ```
## Rust Example The following is a simple program producing Rust code to stdout with custom configuration: ```rust use genco::prelude::*; let hash_map = rust::import("std::collections", "HashMap"); let tokens: rust::Tokens = quote! { fn main() { let mut m = $hash_map::new(); m.insert(1u32, 2u32); } }; println!("{}", tokens.to_file_string()?); ``` This would produce: ```rust,no_test use std::collections::HashMap; fn main() { let mut m = HashMap::new(); m.insert(1u32, 2u32); } ```
[`quote_in!`]: [`quote!`]: [`quoted()`]: [c-example]: [c]: [c#-example]: [c#]: [dart-example]: [dart]: [go-example]: [go]: [impl_lang!]: [import statements]: [indentation is meaningful]: [interpolate]: [java-example]: [java]: [js-example]: [js]: [Open an issue!]: [python-example]: [python]: [quote strings]: [rust-example]: [rust]: [solve namespace conflicts]: [token streams]: [whitespace detection]: ================================================ FILE: examples/c.rs ================================================ use genco::fmt; use genco::prelude::*; fn main() -> anyhow::Result<()> { let printf = &c::include_system("stdio.h", "printf"); let day = "tuesday"; let name = "George"; let tokens = quote! { const char* greet_user() { return $(quoted(format_args!("Hello {name}!"))); } int main() { const char* current_day = $(quoted(day)); $printf("%s\n", current_day); $printf("%s\n", greet_user()); } }; let stdout = std::io::stdout(); let mut w = fmt::IoWriter::new(stdout.lock()); let fmt = fmt::Config::from_lang::(); let config = c::Config::default(); tokens.format_file(&mut w.as_formatter(&fmt), &config)?; Ok(()) } ================================================ FILE: examples/csharp.rs ================================================ use csharp::comment; use genco::fmt; use genco::prelude::*; fn main() -> anyhow::Result<()> { let console = &csharp::import("System", "Console"); let file = &csharp::import("System.IO", "File"); let stream = &csharp::import("System.IO", "Stream"); let soap_formatter = &csharp::import( "System.Runtime.Serialization.Formatters.Soap", "SoapFormatter", ); let simple_object = "TestSimpleObject"; // Note: Comments have to be escaped as raw expressions, since they are // filtered out from procedural macros. let tokens = quote! { public class Test { public static void Main() { $(comment(&["Creates a new TestSimpleObject object."])) $simple_object obj = new $simple_object(); $console.WriteLine("Before serialization the object contains: "); obj.Print(); $(comment(&["Opens a file and serializes the object into it in binary format."])) $stream stream = $file.Open("data.xml", FileMode.Create); $soap_formatter formatter = new $soap_formatter(); $(comment(&["BinaryFormatter formatter = new BinaryFormatter();"])) formatter.Serialize(stream, obj); stream.Close(); $(comment(&["Empties obj."])) obj = null; $(comment(&["Opens file \"data.xml\" and deserializes the object from it."])) stream = $file.Open("data.xml", FileMode.Open); formatter = new $soap_formatter(); $(comment(&["formatter = new BinaryFormatter();"])) obj = ($simple_object)formatter.Deserialize(stream); stream.Close(); $console.WriteLine(""); $console.WriteLine("After deserialization the object contains: "); obj.Print(); } } }; let stdout = std::io::stdout(); let mut w = fmt::IoWriter::new(stdout.lock()); let fmt = fmt::Config::from_lang::().with_indentation(fmt::Indentation::Space(4)); let config = csharp::Config::default(); tokens.format_file(&mut w.as_formatter(&fmt), &config)?; Ok(()) } ================================================ FILE: examples/dart.rs ================================================ use genco::fmt; use genco::prelude::*; fn main() -> anyhow::Result<()> { let hash_map = &dart::import("dart:collection", "HashMap"); let tokens = quote! { print_greeting(String name) { print($[str](Hello $(name))); } $hash_map map() { return new $hash_map(); } }; let stdout = std::io::stdout(); let mut w = fmt::IoWriter::new(stdout.lock()); let fmt = fmt::Config::from_lang::(); let config = dart::Config::default(); tokens.format_file(&mut w.as_formatter(&fmt), &config)?; Ok(()) } ================================================ FILE: examples/go.rs ================================================ use genco::fmt; use genco::prelude::*; fn main() -> anyhow::Result<()> { let println = &go::import("fmt", "Println"); let day = "tuesday"; let name = "George"; let tokens = quote! { func main() { var currentDay string currentDay = $(quoted(day)) $println(currentDay) $println(greetUser()) } func greetUser() string { return $(quoted(format_args!("Hello {name}!"))) } }; let stdout = std::io::stdout(); let mut w = fmt::IoWriter::new(stdout.lock()); let fmt = fmt::Config::from_lang::(); let config = go::Config::default().with_package("main"); tokens.format_file(&mut w.as_formatter(&fmt), &config)?; Ok(()) } ================================================ FILE: examples/java.rs ================================================ use genco::fmt; use genco::prelude::*; fn main() -> anyhow::Result<()> { let car = &java::import("se.tedro", "Car"); let list = &java::import("java.util", "List"); let array_list = &java::import("java.util", "ArrayList"); let tokens = quote! { public class HelloWorld { public static void main(String[] args) { $list<$car> cars = new $array_list<$car>(); cars.add(new $car("Volvo")); cars.add(new $car("Audi")); for ($car car : cars) { System.out.println(car); } } } }; let stdout = std::io::stdout(); let mut w = fmt::IoWriter::new(stdout.lock()); let fmt = fmt::Config::from_lang::().with_newline("\n\r"); let config = java::Config::default().with_package("se.tedro"); tokens.format_file(&mut w.as_formatter(&fmt), &config)?; Ok(()) } ================================================ FILE: examples/js.rs ================================================ use genco::fmt; use genco::prelude::*; fn main() -> anyhow::Result<()> { let react = &js::import("react", "React").into_default(); let display = &js::import("./Display", "Display").into_default(); let button_panel = &js::import("./ButtonPanel", "ButtonPanel").into_default(); let calculate = &js::import("../logic/calculate", "calculate").into_default(); let tokens = quote! { export default class App extends $react.Component { state = { total: null, next: null, operation: null, }; handleClick = buttonName => { this.setState($calculate(this.state, buttonName)); }; render() { return (
<$display value={this.state.next || this.state.total || "0"} /> <$button_panel clickHandler={this.handleClick} />
); } } }; let stdout = std::io::stdout(); let mut w = fmt::IoWriter::new(stdout.lock()); let fmt = fmt::Config::from_lang::(); let config = js::Config::default(); tokens.format_file(&mut w.as_formatter(&fmt), &config)?; Ok(()) } ================================================ FILE: examples/kotlin.rs ================================================ use genco::fmt; use genco::lang::kotlin; use genco::prelude::*; fn main() -> anyhow::Result<()> { let greeter_ty = &kotlin::import("com.example.utils", "Greeter"); let tokens: kotlin::Tokens = quote! { fun main() { val greeter = $greeter_ty("Hello Kotlin from Genco!"); println(greeter.greet()); } }; let stdout = std::io::stdout(); let mut w = fmt::IoWriter::new(stdout.lock()); let fmt_config = fmt::Config::from_lang::() .with_indentation(fmt::Indentation::Space(4)) .with_newline("\n"); let lang_config = kotlin::Config::default().with_package("com.example"); tokens.format_file(&mut w.as_formatter(&fmt_config), &lang_config)?; Ok(()) } ================================================ FILE: examples/nix.rs ================================================ use genco::fmt; use genco::prelude::*; fn main() -> anyhow::Result<()> { let nixpkgs = &nix::inherit("inputs", "nixpkgs"); let pkgs = &nix::variable( "pkgs", quote! { import $nixpkgs { inherit ($nixpkgs) system; config.allowUnfree = true; overlays = []; } }, ); let mk_default = &nix::with("lib", "mkDefault"); let tokens = quote! { { imports = []; environment.systemPackages = with $pkgs; []; networking.useDHCP = $mk_default true; } }; let stdout = std::io::stdout(); let mut w = fmt::IoWriter::new(stdout.lock()); let fmt = fmt::Config::from_lang::(); let config = nix::Config::default(); tokens.format_file(&mut w.as_formatter(&fmt), &config)?; Ok(()) } ================================================ FILE: examples/python.rs ================================================ use genco::fmt; use genco::prelude::*; fn main() -> anyhow::Result<()> { let flask = &python::import("flask", "Flask"); let tokens = quote! { app = $flask(__name__) @app.route('/') def hello(): return "Hello World!" if __name__ == "__main__": app.run() }; let stdout = std::io::stdout(); let mut w = fmt::IoWriter::new(stdout.lock()); let fmt = fmt::Config::from_lang::(); let config = python::Config::default(); tokens.format_file(&mut w.as_formatter(&fmt), &config)?; Ok(()) } ================================================ FILE: examples/rust.rs ================================================ use genco::fmt; use genco::prelude::*; fn main() -> anyhow::Result<()> { // Import the LittleEndian item, without referencing it through the last // module component it is part of. let little_endian = rust::import("byteorder", "LittleEndian"); let big_endian = rust::import("byteorder", "BigEndian").qualified(); // Trait that we need to import to make use of write_u16. let write_bytes_ext = rust::import("byteorder", "WriteBytesExt").with_alias("_"); // Trait that we import since we want to return it. let result = rust::import("anyhow", "Result"); let tokens = quote! { $(register(write_bytes_ext)) fn test() -> $result { let mut data = vec![]; data.write_u16::<$little_endian>(517)?; data.write_u16::<$big_endian>(768)?; println!("{:?}", data); } }; let stdout = std::io::stdout(); let mut w = fmt::IoWriter::new(stdout.lock()); let fmt = fmt::Config::from_lang::().with_indentation(fmt::Indentation::Space(4)); let config = rust::Config::default() // Prettier imports and use. .with_default_import(rust::ImportMode::Qualified); tokens.format_file(&mut w.as_formatter(&fmt), &config)?; Ok(()) } ================================================ FILE: genco-macros/.gitignore ================================================ /target/ **/*.rs.bk Cargo.lock ================================================ FILE: genco-macros/Cargo.toml ================================================ [package] name = "genco-macros" version = "0.19.0" authors = ["John-John Tedro "] edition = "2018" rust-version = "1.88" description = """ A whitespace-aware quasiquoter for beautiful code generation. """ documentation = "https://docs.rs/genco" readme = "README.md" homepage = "https://github.com/udoprog/genco" repository = "https://github.com/udoprog/genco" license = "MIT OR Apache-2.0" keywords = ["code-generation", "template"] categories = ["template-engine"] [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(proc_macro_span, has_proc_macro_span)'] } [dependencies] syn = { version = "2.0.38", features = ["full"] } q = { package = "quote", version = "1.0.3" } proc-macro2 = { version = "1.0.10", features = ["span-locations"] } [lib] proc-macro = true ================================================ FILE: genco-macros/README.md ================================================ # genco-macros [github](https://github.com/udoprog/genco) [crates.io](https://crates.io/crates/genco-macros) [docs.rs](https://docs.rs/genco-macros) [build status](https://github.com/udoprog/genco/actions?query=branch%3Amain) ================================================ FILE: genco-macros/build.rs ================================================ use std::env; use std::process::Command; use std::str; fn main() { println!("cargo:rerun-if-changed=build.rs"); let version = rustc_version().unwrap_or(RustcVersion { minor: u32::MAX, nightly: false, }); if version.nightly && version.minor < 88 { println!("cargo:rustc-cfg=proc_macro_span"); println!("cargo:rustc-cfg=has_proc_macro_span"); } else if version.minor >= 88 { // The relevant parts are stable since 1.88 println!("cargo:rustc-cfg=has_proc_macro_span"); } } struct RustcVersion { minor: u32, nightly: bool, } fn rustc_version() -> Option { let rustc = env::var_os("RUSTC")?; let output = Command::new(rustc).arg("--version").output().ok()?; let version = str::from_utf8(&output.stdout).ok()?; let nightly = version.contains("nightly") || version.contains("dev"); let mut pieces = version.split('.'); if pieces.next()? != "rustc 1" { return None; } let minor = pieces.next()?.parse().ok()?; Some(RustcVersion { minor, nightly }) } ================================================ FILE: genco-macros/src/ast.rs ================================================ use core::fmt; use proc_macro2::{Span, TokenStream, TokenTree}; use syn::Token; use crate::static_buffer::StaticBuffer; /// A single match arm in a match statement. pub(crate) struct MatchArm { pub(crate) attr: Vec, pub(crate) pattern: syn::Pat, pub(crate) condition: Option, pub(crate) block: TokenStream, } /// A delimiter that can be encoded. #[derive(Debug, Clone, Copy)] pub(crate) enum Delimiter { Parenthesis, Brace, Bracket, } impl Delimiter { pub(crate) fn encode_open(self, output: &mut StaticBuffer) { let c = match self { Self::Parenthesis => '(', Self::Brace => '{', Self::Bracket => '[', }; output.push(c); } pub(crate) fn encode_close(self, output: &mut StaticBuffer) { let c = match self { Self::Parenthesis => ')', Self::Brace => '}', Self::Bracket => ']', }; output.push(c); } } pub(crate) enum LiteralName<'a> { /// The literal name as a string. Ident(&'a str), /// The literal name as a character. Char(char), } impl fmt::Display for LiteralName<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { LiteralName::Ident(ident) => ident.fmt(f), LiteralName::Char(c) => write!(f, "{c:?}"), } } } /// The name of an internal fn. pub(crate) enum Name { /// The name is the `const` token. Const(Token![const]), /// Custom name. Ident(String), /// Character name. Char(char), } impl Name { /// Get the name as a string. pub(crate) fn as_literal_name(&self) -> LiteralName<'_> { match self { Name::Const(..) => LiteralName::Ident("const"), Name::Ident(name) => LiteralName::Ident(name.as_str()), Name::Char(c) => LiteralName::Char(*c), } } } impl q::ToTokens for Name { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Name::Const(t) => t.to_tokens(tokens), Name::Ident(name) => name.to_tokens(tokens), Name::Char(c) => c.to_tokens(tokens), } } } #[derive(Debug)] pub(crate) enum ControlKind { Space, Push, Line, } #[derive(Debug)] pub(crate) struct Control { pub(crate) kind: ControlKind, pub(crate) span: Span, } impl Control { /// Construct a control from a string identifier. pub(crate) fn from_char(span: Span, c: char) -> Option { match c { ' ' => Some(Self { kind: ControlKind::Space, span, }), '\n' => Some(Self { kind: ControlKind::Line, span, }), '\r' => Some(Self { kind: ControlKind::Push, span, }), _ => None, } } } /// Items to process from the queue. pub(crate) enum Ast { Tree { tt: TokenTree, }, String { has_eval: bool, stream: TokenStream, }, /// A quoted string. Quoted { s: syn::LitStr, }, /// A literal value embedded in the stream. Literal { string: String, }, DelimiterOpen { delimiter: Delimiter, }, DelimiterClose { delimiter: Delimiter, }, Control { control: Control, }, EvalIdent { ident: syn::Ident, }, /// Something to be evaluated as rust. Eval { expr: syn::Expr, }, /// A bound scope. Scope { binding: Option, content: TokenStream, }, /// A loop repetition. Loop { /// The pattern being bound. pattern: Box, /// Expression being bound to an iterator. expr: Box, /// If a join is specified, this is the token stream used to join. /// It's evaluated in the loop scope. join: Option, /// The inner stream processed. stream: TokenStream, }, Condition { /// Expression being use as a condition. condition: syn::Expr, /// Then branch of the conditional. then_branch: TokenStream, /// Else branch of the conditional. else_branch: Option, }, Let { /// Variable name (or names for a tuple) name: syn::Pat, /// Expression expr: syn::Expr, }, Match { condition: syn::Expr, arms: Vec, }, } ================================================ FILE: genco-macros/src/cursor.rs ================================================ use proc_macro2::Span; use crate::fake::LineColumn; #[derive(Clone, Copy, Debug)] pub(crate) struct Cursor { // Span to use for diagnostics associated with the cursor. pub(crate) span: Span, // The start of the cursor. pub(crate) start: LineColumn, // The end of the cursor. pub(crate) end: LineColumn, } impl Cursor { /// Construt a cursor. pub(crate) fn new(span: Span, start: LineColumn, end: LineColumn) -> Cursor { Self { span, start, end } } /// Calculate the start character for the cursor. pub(crate) fn first_character(self) -> Self { Cursor { span: self.span, start: self.start, end: LineColumn { line: self.start.line, column: self.start.column + 1, }, } } /// Calculate the end character for the cursor. pub(crate) fn last_character(self) -> Self { Cursor { span: self.span, start: LineColumn { line: self.end.line, column: self.end.column.saturating_sub(1), }, end: self.end, } } } ================================================ FILE: genco-macros/src/encoder.rs ================================================ use crate::ast::{Ast, Control, ControlKind, Delimiter, MatchArm}; use crate::cursor::Cursor; use crate::fake::LineColumn; use crate::requirements::Requirements; use crate::static_buffer::StaticBuffer; use crate::Ctxt; use proc_macro2::{Span, TokenStream}; use syn::Result; /// Struct to deal with emitting the necessary spacing. pub(crate) struct Encoder<'a> { /// Context for encoding. cx: &'a Ctxt, /// Use to modify the initial line/column in case something was processed /// before the input was handed off to the quote parser. /// /// See [QuoteInParser]. span_start: Option, /// Override the end span of the quote parser. /// /// This causes whitespace to be emitted at the tail of the expression, /// unless it specifically reached the end of the span. span_end: Option, /// TODO: make private. item_buffer: StaticBuffer<'a>, /// The token stream we are constructing. output: TokenStream, /// Currently stored cursor. last: Option, /// Which column the last line start on. last_start_column: Option, /// Indentation columns. indents: Vec<(usize, Option)>, /// Indicates if the encoder has encountered a string which requires eval /// support in the target language. pub(crate) requirements: Requirements, } impl<'a> Encoder<'a> { pub(crate) fn new( cx: &'a Ctxt, span_start: Option, span_end: Option, ) -> Self { Self { cx, span_start, span_end, item_buffer: StaticBuffer::new(cx), output: TokenStream::new(), last: None, last_start_column: None, indents: Vec::new(), requirements: Requirements::default(), } } /// Encode a single item into the encoder. pub(crate) fn encode(&mut self, cursor: Cursor, ast: Ast) -> Result<()> { self.step(cursor)?; match ast { Ast::Tree { tt, .. } => { self.encode_literal(&tt.to_string()); } Ast::String { has_eval, stream } => { self.requirements.lang_supports_eval |= has_eval; self.encode_string(has_eval, stream); } Ast::Quoted { s } => { self.encode_quoted(s); } Ast::Literal { string } => { self.encode_literal(&string); } Ast::Control { control, .. } => { self.encode_control(control); } Ast::Scope { binding, content, .. } => { self.encode_scope(binding, content); } Ast::EvalIdent { ident } => { self.encode_eval_ident(ident); } Ast::Eval { expr, .. } => { self.encode_eval(expr); } Ast::Loop { pattern, expr, join, stream, .. } => { self.encode_repeat(*pattern, *expr, join, stream); } Ast::DelimiterOpen { delimiter, .. } => { self.encode_open_delimiter(delimiter); } Ast::DelimiterClose { delimiter, .. } => { self.encode_close_delimiter(delimiter); } Ast::Condition { condition, then_branch, else_branch, .. } => { self.encode_condition(condition, then_branch, else_branch); } Ast::Match { condition, arms, .. } => { self.encode_match(condition, arms); } Ast::Let { name, expr } => { self.encode_let(name, expr); } } Ok(()) } /// Finalize and translate into a token stream. pub(crate) fn into_output(mut self) -> Result<(Requirements, TokenStream)> { self.finalize()?; Ok((self.requirements, self.output)) } pub(crate) fn step(&mut self, next: Cursor) -> Result<()> { if let Some(from) = self.from() { // Insert spacing if appropriate. self.tokenize_whitespace(from, next.start, Some(next.span))?; } // Assign the current cursor to the next item. // This will then be used to make future indentation decisions. self.last = Some(next); Ok(()) } pub(crate) fn encode_open_delimiter(&mut self, d: Delimiter) { d.encode_open(&mut self.item_buffer); } pub(crate) fn encode_close_delimiter(&mut self, d: Delimiter) { d.encode_close(&mut self.item_buffer); } pub(crate) fn encode_literal(&mut self, string: &str) { self.item_buffer.push_str(string); } pub(crate) fn encode_string(&mut self, has_eval: bool, stream: TokenStream) { let Ctxt { receiver, module } = self.cx; self.item_buffer.flush(&mut self.output); self.output.extend(q::quote! { #receiver.append(#module::__priv::open_quote(#has_eval)); #stream #receiver.append(#module::__priv::close_quote()); }); } pub(crate) fn encode_quoted(&mut self, s: syn::LitStr) { let Ctxt { receiver, module } = self.cx; self.item_buffer.flush(&mut self.output); self.output.extend(q::quote! { #receiver.append(#module::__priv::open_quote(false)); #receiver.append(#module::__priv::static_(#s)); #receiver.append(#module::__priv::close_quote()); }); } pub(crate) fn encode_control(&mut self, control: Control) { let Ctxt { receiver, .. } = self.cx; self.item_buffer.flush(&mut self.output); match control.kind { ControlKind::Space => { self.output .extend(q::quote_spanned!(control.span => #receiver.space();)); } ControlKind::Push => { self.output .extend(q::quote_spanned!(control.span => #receiver.push();)); } ControlKind::Line => { self.output .extend(q::quote_spanned!(control.span => #receiver.line();)); } } } pub(crate) fn encode_scope(&mut self, binding: Option, content: TokenStream) { let Ctxt { receiver, .. } = self.cx; if binding.is_some() { self.item_buffer.flush(&mut self.output); } let binding = binding.map(|b| q::quote_spanned!(b.span() => let #b = &mut *#receiver;)); self.output.extend(q::quote! {{ #binding #content }}); } /// Encode an evaluation of the given expression. pub(crate) fn encode_eval_ident(&mut self, ident: syn::Ident) { let Ctxt { receiver, .. } = self.cx; self.item_buffer.flush(&mut self.output); self.output.extend(q::quote! { #receiver.append(#ident); }); } /// Encode an evaluation of the given expression. pub(crate) fn encode_eval(&mut self, expr: syn::Expr) { let Ctxt { receiver, .. } = self.cx; self.item_buffer.flush(&mut self.output); self.output.extend(q::quote! { #receiver.append(#expr); }); } pub(crate) fn encode_repeat( &mut self, pattern: syn::Pat, expr: syn::Expr, join: Option, stream: TokenStream, ) { self.item_buffer.flush(&mut self.output); if let Some(join) = join { self.output.extend(q::quote! { { let mut __it = IntoIterator::into_iter(#expr).peekable(); while let Some(#pattern) = __it.next() { #stream if __it.peek().is_some() { #join } } } }); } else { self.output.extend(q::quote! { for #pattern in #expr { #stream } }); } } /// Encode an if statement with an inner stream. pub(crate) fn encode_condition( &mut self, condition: syn::Expr, then_branch: TokenStream, else_branch: Option, ) { self.item_buffer.flush(&mut self.output); let else_branch = else_branch.map(|stream| q::quote!(else { #stream })); self.output.extend(q::quote! { if #condition { #then_branch } #else_branch }); } /// Encode an if statement with an inner stream. pub(crate) fn encode_match(&mut self, condition: syn::Expr, arms: Vec) { self.item_buffer.flush(&mut self.output); let mut stream = TokenStream::new(); for MatchArm { attr, pattern, condition, block, } in arms { let condition = condition.map(|c| q::quote!(if #c)); stream.extend(q::quote!(#(#attr)* #pattern #condition => { #block },)); } let m = q::quote! { match #condition { #stream } }; self.output.extend(m); } /// Encode a let statement pub(crate) fn encode_let(&mut self, name: syn::Pat, expr: syn::Expr) { self.item_buffer.flush(&mut self.output); self.output.extend(q::quote! { let #name = #expr; }) } fn from(&mut self) -> Option { // So we've (potentially) encountered the first ever token, while we // have a spanned start like `quote_in! { out => foo }`, `foo` is now // `next`. // // What we want to do is treat the beginning out `out` as the // indentation position, so we adjust the token. // // But we also want to avoid situations like this: // // ``` // quote_in! { out => // foo // bar // } // ``` // // If we would treat `out` as the start, `foo` would be seen as // unindented. So check if the first encountered token is on the // same line as the binding `out` or not before adjusting them! if let Some(span_start) = self.span_start.take() { self.last_start_column = Some(span_start.column); return Some(span_start); } if let Some(last) = self.last { if self.last_start_column.is_none() { self.last_start_column = Some(last.start.column); } return Some(last.end); } None } /// Finalize the encoder. fn finalize(&mut self) -> Result<()> { let Ctxt { receiver, .. } = self.cx; // evaluate whitespace in case we have an explicit end span. while let Some(to) = self.span_end.take() { if let Some(from) = self.from() { // Insert spacing if appropriate, up until the "fake" end. self.tokenize_whitespace(from, to, None)?; } } self.item_buffer.flush(&mut self.output); while self.indents.pop().is_some() { self.output.extend(q::quote!(#receiver.unindent();)); } Ok(()) } /// If we are in a nightly genco, insert indentation and spacing if /// appropriate in the output token stream. fn tokenize_whitespace( &mut self, from: LineColumn, to: LineColumn, to_span: Option, ) -> Result<()> { let Ctxt { receiver: r, .. } = self.cx; // Do nothing if empty span. if from == to { return Ok(()); } // Insert spacing if we are on the same line, but column has changed. if from.line == to.line { // Same line, but next item doesn't match. if from.column < to.column { self.item_buffer.flush(&mut self.output); self.output.extend(q::quote!(#r.space();)); } return Ok(()); } // Line changed. Determine whether to indent, unindent, or hard break the // line. self.item_buffer.flush(&mut self.output); debug_assert!(from.line < to.line); let line = to.line - from.line > 1; if let Some(last_start_column) = self.last_start_column.take() { if last_start_column < to.column { self.indents.push((last_start_column, to_span)); self.output.extend(q::quote!(#r.indent();)); if line { self.output.extend(q::quote!(#r.line();)); } } else if last_start_column > to.column { while let Some((column, _)) = self.indents.pop() { if column > to.column && !self.indents.is_empty() { self.output.extend(q::quote!(#r.unindent();)); if line { self.output.extend(q::quote!(#r.line();)); } continue; } else if column == to.column { self.output.extend(q::quote!(#r.unindent();)); if line { self.output.extend(q::quote!(#r.line();)); } break; } return Err(indentation_error(to.column, column, to_span)); } } else if line { self.output.extend(q::quote!(#r.line();)); } else { self.output.extend(q::quote!(#r.push();)); } } return Ok(()); fn indentation_error( to_column: usize, from_column: usize, to_span: Option, ) -> syn::Error { let error = if to_column > from_column { let len = to_column.saturating_sub(from_column); format!( "expected {} less {} of indentation", len, if len == 1 { "space" } else { "spaces" } ) } else { let len = from_column.saturating_sub(to_column); format!( "expected {} more {} of indentation", len, if len == 1 { "space" } else { "spaces" } ) }; if let Some(span) = to_span { syn::Error::new(span, error) } else { syn::Error::new(Span::call_site(), error) } } } } ================================================ FILE: genco-macros/src/fake.rs ================================================ use core::cell::{RefCell, RefMut}; use core::fmt::Arguments; use proc_macro2::Span; use crate::cursor::Cursor; /// Error message raised. const ERROR: &str = "Your compiler does not support spans which are required by genco and compat doesn't work, see: https://github.com/rust-lang/rust/issues/54725"; /// Internal line-column abstraction. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) struct LineColumn { /// The line. pub(crate) line: usize, /// The column. pub(crate) column: usize, } impl LineColumn { #[cfg(has_proc_macro_span)] pub(crate) fn start(span: Span) -> Option { let span = span.unwrap().start(); Some(Self { line: span.line(), column: span.column(), }) } #[cfg(has_proc_macro_span)] pub(crate) fn end(span: Span) -> Option { let span = span.unwrap().end(); Some(Self { line: span.line(), column: span.column(), }) } #[cfg(not(has_proc_macro_span))] pub(crate) fn start(_: Span) -> Option { None } #[cfg(not(has_proc_macro_span))] pub(crate) fn end(_: Span) -> Option { None } } #[derive(Default)] pub(crate) struct Buf { buf: RefCell, } impl Buf { /// Format the given arguments and return the associated string. fn format(&self, args: Arguments<'_>) -> RefMut<'_, str> { use std::fmt::Write; let mut buf = self.buf.borrow_mut(); buf.clear(); buf.write_fmt(args).unwrap(); RefMut::map(buf, |buf| buf.as_mut_str()) } /// Construct a cursor from a span. pub(crate) fn cursor(&self, span: Span) -> syn::Result { let start = LineColumn::start(span); let end = LineColumn::end(span); if let (Some(start), Some(end)) = (start, end) { return Ok(Cursor::new(span, start, end)); } // Try compat. let (start, end) = self.find_line_column(span)?; Ok(Cursor::new( span, LineColumn { line: 1, column: start, }, LineColumn { line: 1, column: end, }, )) } /// The start of the given span. pub(crate) fn start(&mut self, span: Span) -> syn::Result { if let Some(start) = LineColumn::start(span) { return Ok(start); } // Try compat. let (column, _) = self.find_line_column(span)?; Ok(LineColumn { line: 1, column }) } /// The start of the given span. pub(crate) fn end(&mut self, span: Span) -> syn::Result { if let Some(end) = LineColumn::end(span) { return Ok(end); } // Try compat. let (_, column) = self.find_line_column(span)?; Ok(LineColumn { line: 1, column }) } /// Join two spans. pub(crate) fn join(&mut self, a: Span, b: Span) -> syn::Result { Ok(Cursor::new( a.join(b).unwrap_or(a), self.start(a)?, self.end(b)?, )) } /// Try to decode line and column information using the debug implementation of /// a `span` which leaks the byte offset of a thing. fn find_line_column(&self, span: Span) -> syn::Result<(usize, usize)> { match self.find_line_column_inner(span) { Some((start, end)) => Ok((start, end)), None => Err(syn::Error::new(span, ERROR)), } } fn find_line_column_inner(&self, span: Span) -> Option<(usize, usize)> { let text = self.format(format_args!("{span:?}")); let start = text.find('(')?; let (start, end) = text .get(start.checked_add(1)?..text.len().checked_sub(1)?)? .split_once("..")?; Some((str::parse(start).ok()?, str::parse(end).ok()?)) } } ================================================ FILE: genco-macros/src/lib.rs ================================================ //! [github](https://github.com/udoprog/genco) //! [crates.io](https://crates.io/crates/genco-macros) //! [docs.rs](https://docs.rs/genco-macros) #![recursion_limit = "256"] #![allow(clippy::type_complexity)] #![cfg_attr(proc_macro_span, feature(proc_macro_span))] extern crate proc_macro; use proc_macro2::Span; use syn::parse::{ParseStream, Parser as _}; struct Ctxt { receiver: syn::Ident, module: syn::Path, } impl Default for Ctxt { fn default() -> Self { let mut module = syn::Path { leading_colon: None, segments: syn::punctuated::Punctuated::default(), }; module .segments .push(syn::Ident::new("genco", Span::call_site()).into()); Self { receiver: syn::Ident::new("__genco_macros_toks", Span::call_site()), module, } } } mod ast; mod cursor; mod encoder; mod fake; mod quote; mod quote_fn; mod quote_in; mod requirements; mod static_buffer; mod string_parser; #[proc_macro] pub fn quote(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let cx = Ctxt::default(); let parser = crate::quote::Quote::new(&cx); let parser = move |stream: ParseStream| parser.parse(stream); let (req, output) = match parser.parse(input) { Ok(data) => data, Err(e) => return proc_macro::TokenStream::from(e.to_compile_error()), }; let check = req.into_check(&cx.receiver); let Ctxt { receiver, module } = &cx; let gen = q::quote! {{ let mut #receiver = #module::tokens::Tokens::new(); { let mut #receiver = &mut #receiver; #output } #check #receiver }}; gen.into() } #[proc_macro] pub fn quote_in(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let quote_in = syn::parse_macro_input!(input as quote_in::QuoteIn); quote_in.stream.into() } #[proc_macro] pub fn quote_fn(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let quote_fn = syn::parse_macro_input!(input as quote_fn::QuoteFn); quote_fn.stream.into() } ================================================ FILE: genco-macros/src/quote.rs ================================================ use proc_macro2::{Punct, Spacing, Span, TokenStream, TokenTree}; use syn::parse::{ParseBuffer, ParseStream}; use syn::spanned::Spanned; use syn::{token, Result, Token}; use crate::ast::{Ast, Control, Delimiter, LiteralName, MatchArm, Name}; use crate::encoder::Encoder; use crate::fake::Buf; use crate::fake::LineColumn; use crate::requirements::Requirements; use crate::string_parser::StringParser; use crate::Ctxt; pub(crate) struct Quote<'a> { /// Context variables. cx: &'a Ctxt, /// Use to modify the initial line/column in case something was processed /// before the input was handed off to the quote parser. /// /// See [QuoteInParser]. span_start: Option, /// Override the end span of the quote parser. /// /// This causes encoder to be emitted at the tail of the expression, /// unless it specifically reached the end of the span. span_end: Option, /// If true, only parse until a comma (`,`) is encountered. until_comma: bool, /// Buffer, buf: Buf, } impl<'a> Quote<'a> { /// Construct a new quote parser. pub(crate) fn new(cx: &'a Ctxt) -> Self { Self { cx, span_start: None, span_end: None, until_comma: false, buf: Buf::default(), } } /// Construct a new quote parser that will only parse until the given token. pub(crate) fn new_until_comma(cx: &'a Ctxt) -> Self { Self { cx, span_start: None, span_end: None, until_comma: true, buf: Buf::default(), } } /// Override the default starting span. pub(crate) fn with_span(mut self, span: Span) -> syn::Result { return Ok(Self { span_start: Some(adjust_start(self.buf.start(span)?)), span_end: Some(adjust_end(self.buf.end(span)?)), ..self }); fn adjust_start(start: LineColumn) -> LineColumn { LineColumn { line: start.line, column: start.column.saturating_add(1), } } fn adjust_end(end: LineColumn) -> LineColumn { LineColumn { line: end.line, column: end.column.saturating_sub(1), } } } /// Parse until end of stream. pub(crate) fn parse(mut self, input: ParseStream) -> Result<(Requirements, TokenStream)> { let mut encoder = Encoder::new(self.cx, self.span_start, self.span_end); self.parse_inner(&mut encoder, input, 0)?; encoder.into_output() } /// Parse `if { } [else { }]`. fn parse_condition(&self, input: ParseStream) -> Result<(Requirements, Ast)> { input.parse::()?; let condition = syn::Expr::parse_without_eager_brace(input)?; if input.peek(Token![=>]) { input.parse::]>()?; let (req, then_branch) = Quote::new(self.cx).parse(input)?; return Ok(( req, Ast::Condition { condition, then_branch, else_branch: None, }, )); } let mut req = Requirements::default(); let content; syn::braced!(content in input); let (r, then_branch) = Quote::new(self.cx).parse(&content)?; req.merge_with(r); let else_branch = if input.peek(Token![else]) { input.parse::()?; let content; syn::braced!(content in input); let (r, else_branch) = Quote::new(self.cx).parse(&content)?; req.merge_with(r); Some(else_branch) } else { None }; Ok(( req, Ast::Condition { condition, then_branch, else_branch, }, )) } /// Parse `for in [join ()] => `. fn parse_loop(&self, input: ParseStream) -> Result<(Requirements, Ast)> { syn::custom_keyword!(join); let mut req = Requirements::default(); input.parse::()?; let pattern = syn::Pat::parse_single(input)?; input.parse::()?; let expr = syn::Expr::parse_without_eager_brace(input)?; let join = if input.peek(join) { input.parse::()?; let content; let paren = syn::parenthesized!(content in input); let (r, join) = Quote::new(self.cx) .with_span(paren.span.span())? .parse(&content)?; req.merge_with(r); Some(join) } else { None }; let content; let input = if input.peek(Token![=>]) { input.parse::]>()?; input } else { syn::braced!(content in input); &content }; let parser = Quote::new(self.cx); let (r, stream) = parser.parse(input)?; req.merge_with(r); let ast = Ast::Loop { pattern: Box::new(pattern), join, expr: Box::new(expr), stream, }; Ok((req, ast)) } fn parse_match(&self, input: ParseStream) -> Result<(Requirements, Ast)> { input.parse::()?; let condition = syn::Expr::parse_without_eager_brace(input)?; let body; syn::braced!(body in input); let mut req = Requirements::default(); let mut arms = Vec::new(); while !body.is_empty() { let attr = input.call(syn::Attribute::parse_outer)?; let pattern = syn::Pat::parse_multi_with_leading_vert(&body)?; let condition = if body.peek(Token![if]) { body.parse::()?; let condition = body.parse::()?; Some(condition) } else { None }; body.parse::]>()?; let (r, block) = if body.peek(token::Brace) { let block; syn::braced!(block in body); let parser = Quote::new(self.cx); parser.parse(&block)? } else if body.peek(token::Paren) { let block; let paren = syn::parenthesized!(block in body); Quote::new(self.cx) .with_span(paren.span.span())? .parse(&block)? } else { let parser = Quote::new_until_comma(self.cx); parser.parse(&body)? }; req.merge_with(r); arms.push(MatchArm { attr, pattern, condition, block, }); if body.peek(Token![,]) { body.parse::()?; } } Ok((req, Ast::Match { condition, arms })) } fn parse_let(&self, input: ParseStream) -> Result<(Requirements, Ast)> { input.parse::()?; let req = Requirements::default(); let name = syn::Pat::parse_single(input)?; input.parse::()?; let expr = syn::Expr::parse_without_eager_brace(input)?; let ast = Ast::Let { name, expr }; Ok((req, ast)) } /// Parse evaluation: `[*] => `. fn parse_scope(&self, input: ParseStream) -> Result { input.parse::()?; let binding = if input.peek(Token![_]) { input.parse::()?; None } else { Some(input.parse()?) }; let content; let content = if input.peek(token::Brace) { syn::braced!(content in input); &content } else { input.parse::]>()?; input }; Ok(Ast::Scope { binding, content: content.parse()?, }) } fn parse_expression(&mut self, encoder: &mut Encoder, input: ParseStream) -> Result<()> { let start = input.parse::()?.span(); // Single identifier without quoting. if !input.peek(token::Paren) { let ident = input.parse::()?; let cursor = self.buf.join(start, ident.span())?; encoder.encode(cursor, Ast::EvalIdent { ident })?; return Ok(()); } let scope; let outer = syn::parenthesized!(scope in input); let cursor = self.buf.join(start, outer.span.span())?; let ast = if scope.peek(Token![if]) { let (req, ast) = self.parse_condition(&scope)?; encoder.requirements.merge_with(req); ast } else if scope.peek(Token![for]) { let (req, ast) = self.parse_loop(&scope)?; encoder.requirements.merge_with(req); ast } else if scope.peek(Token![match]) { let (req, ast) = self.parse_match(&scope)?; encoder.requirements.merge_with(req); ast } else if scope.peek(Token![let]) { let (req, ast) = self.parse_let(&scope)?; encoder.requirements.merge_with(req); ast } else if scope.peek(Token![ref]) { self.parse_scope(&scope)? } else if crate::string_parser::is_lit_str_opt(scope.fork())? { let string = scope.parse::()?.value(); Ast::Literal { string } } else { Ast::Eval { expr: scope.parse()?, } }; encoder.encode(cursor, ast)?; Ok(()) } fn parse_inner( &mut self, encoder: &mut Encoder, input: ParseStream, group_depth: usize, ) -> Result<()> { while !input.is_empty() { if group_depth == 0 && self.until_comma && input.peek(Token![,]) { break; } // Escape sequence. if input.peek(Token![$]) && input.peek2(Token![$]) { let [a] = input.parse::()?.spans; let [b] = input.parse::()?.spans; let cursor = self.buf.join(a, b)?; let mut punct = Punct::new('$', Spacing::Joint); punct.set_span(cursor.span); encoder.encode(cursor, Ast::Tree { tt: punct.into() })?; continue; } if let Some((name, content, [start, end])) = parse_internal_function(input)? { match (name.as_literal_name(), content) { (literal_name @ LiteralName::Ident("str"), None) => { return Err(syn::Error::new( name.span(), format!("Function `{literal_name}` expects content, like: $[{literal_name}]()"), )); } (LiteralName::Ident("str"), Some(content)) => { let parser = StringParser::new(self.cx, &self.buf, end)?; let (options, r, stream) = parser.parse(&content)?; encoder.requirements.merge_with(r); let cursor = self.buf.join(start, end)?; encoder.encode( cursor, Ast::String { has_eval: options.has_eval.get(), stream, }, )?; } (LiteralName::Char(c), content) => { let control = match Control::from_char(name.span(), c) { Some(control) => control, None => { return Err(syn::Error::new(name.span(), format!("Unsupported control {c:?}, expected one of: '\\n', '\r', ' '"))); } }; if let Some(content) = content { return Err(syn::Error::new( content.span(), format!("Control {c:?} does not expect an argument"), )); } let cursor = self.buf.join(start.span(), end.span())?; encoder.encode(cursor, Ast::Control { control })?; } (LiteralName::Ident(string), _) => { return Err(syn::Error::new( name.span(), format!("Unsupported function `{string}`, expected one of: str"), )); } } continue; } let start_expression = input.peek2(token::Paren) || input.peek2(syn::Ident); if input.peek(Token![$]) && start_expression { self.parse_expression(encoder, input)?; continue; } if input.peek(syn::LitStr) { let s = input.parse::()?; let cursor = self.buf.cursor(s.span())?; encoder.encode(cursor, Ast::Quoted { s })?; continue; } // Test for different forms of groups and recurse if necessary. if input.peek(token::Brace) { let content; let braces = syn::braced!(content in input); self.parse_group( encoder, Delimiter::Brace, braces.span.span(), &content, group_depth, )?; continue; } if input.peek(token::Paren) { let content; let braces = syn::parenthesized!(content in input); self.parse_group( encoder, Delimiter::Parenthesis, braces.span.span(), &content, group_depth, )?; continue; } if input.peek(token::Bracket) { let content; let braces = syn::bracketed!(content in input); self.parse_group( encoder, Delimiter::Bracket, braces.span.span(), &content, group_depth, )?; continue; } let tt: TokenTree = input.parse()?; let cursor = self.buf.cursor(tt.span())?; encoder.encode(cursor, Ast::Tree { tt })?; } Ok(()) } fn parse_group( &mut self, encoder: &mut Encoder, delimiter: Delimiter, span: Span, input: ParseStream, group_depth: usize, ) -> Result<()> { let cursor = self.buf.cursor(span)?; encoder.encode(cursor.first_character(), Ast::DelimiterOpen { delimiter })?; self.parse_inner(encoder, input, group_depth + 1)?; encoder.encode(cursor.last_character(), Ast::DelimiterClose { delimiter })?; Ok(()) } } /// Parse an internal function of the form: /// /// ```text /// $[]() /// ``` /// /// The `()` part is optional, and if absent the internal function is /// known as a "control function", like `$[' ']`. pub(crate) fn parse_internal_function<'a>( input: &'a ParseBuffer, ) -> Result>, [Span; 2])>> { // Custom function call. if !(input.peek(Token![$]) && input.peek2(token::Bracket)) { return Ok(None); } let start = input.parse::()?; let function; let brackets = syn::bracketed!(function in input); let name = if function.peek(Token![const]) { Name::Const(function.parse()?) } else if function.peek(syn::LitChar) { let c = function.parse::()?; Name::Char(c.value()) } else { let ident = function.parse::()?; Name::Ident(ident.to_string()) }; if !function.is_empty() { return Err(function.error("expected nothing after function identifier")); } let (content, end) = if input.peek(token::Paren) { let content; let paren = syn::parenthesized!(content in input); (Some(content), paren.span) } else { (None, brackets.span) }; Ok(Some((name, content, [start.span(), end.span()]))) } ================================================ FILE: genco-macros/src/quote_fn.rs ================================================ use proc_macro2::TokenStream; use syn::parse::{Parse, ParseStream}; use syn::Result; use crate::Ctxt; pub(crate) struct QuoteFn { pub(crate) stream: TokenStream, } impl Parse for QuoteFn { fn parse(input: ParseStream) -> Result { let cx = Ctxt::default(); let parser = crate::quote::Quote::new(&cx); let (req, output) = parser.parse(input)?; let check = req.into_check(&cx.receiver); let Ctxt { receiver, module } = &cx; let stream = q::quote! { #module::tokens::from_fn(move |#receiver| { #output #check }) }; Ok(Self { stream }) } } ================================================ FILE: genco-macros/src/quote_in.rs ================================================ use proc_macro2::TokenStream; use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned as _; use syn::{Result, Token}; use crate::Ctxt; pub(crate) struct QuoteIn { pub(crate) stream: TokenStream, } impl Parse for QuoteIn { fn parse(input: ParseStream) -> Result { // Input expression, assign to a variable. let expr = input.parse::()?; input.parse::]>()?; let cx = Ctxt::default(); let parser = crate::quote::Quote::new(&cx); let (req, output) = parser.parse(input)?; let check = req.into_check(&cx.receiver); let Ctxt { receiver, module } = &cx; // Give the assignment its own span to improve diagnostics. let assign_mut = q::quote_spanned! { expr.span() => let #receiver: &mut #module::tokens::Tokens<_> = &mut #expr; }; let stream = q::quote! {{ #assign_mut #output #check }}; Ok(Self { stream }) } } ================================================ FILE: genco-macros/src/requirements.rs ================================================ use proc_macro2::TokenStream; /// Language requirements for token stream. #[derive(Debug, Default, Clone, Copy)] pub(crate) struct Requirements { pub(crate) lang_supports_eval: bool, } impl Requirements { /// Merge this requirements with another. pub fn merge_with(&mut self, other: Self) { self.lang_supports_eval |= other.lang_supports_eval; } /// Generate checks for requirements. pub fn into_check(self, receiver: &syn::Ident) -> TokenStream { let lang_supports_eval = if self.lang_supports_eval { Some(q::quote!(#receiver.lang_supports_eval();)) } else { None }; q::quote! { #lang_supports_eval } } } ================================================ FILE: genco-macros/src/static_buffer.rs ================================================ use proc_macro2::{Span, TokenStream}; use crate::Ctxt; /// Buffer used to resolve static items. pub(crate) struct StaticBuffer<'a> { cx: &'a Ctxt, buffer: String, } impl<'a> StaticBuffer<'a> { /// Construct a new line buffer. pub(crate) fn new(cx: &'a Ctxt) -> Self { Self { cx, buffer: String::new(), } } /// Push the given character to the line buffer. pub(crate) fn push(&mut self, c: char) { self.buffer.push(c); } /// Push the given string to the line buffer. pub(crate) fn push_str(&mut self, s: &str) { self.buffer.push_str(s); } /// Flush the line buffer if necessary. pub(crate) fn flush(&mut self, tokens: &mut TokenStream) { if !self.buffer.is_empty() { let Ctxt { receiver, module } = self.cx; let s = syn::LitStr::new(&self.buffer, Span::call_site()); tokens.extend(q::quote!(#receiver.append(#module::__priv::static_(#s));)); self.buffer.clear(); } } } ================================================ FILE: genco-macros/src/string_parser.rs ================================================ //! Helper to parse quoted strings. use core::cell::{Cell, RefCell}; use core::fmt::Write; use proc_macro2::{Span, TokenStream, TokenTree}; use syn::parse::{ParseBuffer, ParseStream}; use syn::spanned::Spanned; use syn::token; use syn::Result; use crate::ast::LiteralName; use crate::fake::{Buf, LineColumn}; use crate::quote::parse_internal_function; use crate::requirements::Requirements; use crate::Ctxt; /// Options for the parsed string. #[derive(Default)] pub(crate) struct Options { /// If the parsed string has any evaluation statements in it. pub(crate) has_eval: Cell, } fn adjust_start(start: LineColumn) -> LineColumn { LineColumn { line: start.line, column: start.column + 1, } } fn adjust_end(end: LineColumn) -> LineColumn { LineColumn { line: end.line, column: end.column.saturating_sub(1), } } struct Encoder<'a> { cx: &'a Ctxt, span: Span, cursor: Cell>, count: Cell, buf: RefCell, stream: RefCell, pub(crate) options: Options, } impl<'a> Encoder<'a> { pub fn new(cx: &'a Ctxt, cursor: LineColumn, span: Span) -> Self { Self { cx, span, cursor: Cell::new(Some(cursor)), count: Cell::new(0), buf: RefCell::new(String::new()), stream: RefCell::new(TokenStream::new()), options: Options::default(), } } pub(crate) fn finalize(self, end: LineColumn) -> Result<(Options, TokenStream)> { self.flush(Some(end), None)?; Ok((self.options, self.stream.into_inner())) } /// Encode a single character and replace the cursor with the given /// location. pub(crate) fn encode_char(&self, c: char, from: LineColumn, to: LineColumn) -> Result<()> { self.flush_whitespace(Some(from), Some(to))?; self.buf.borrow_mut().push(c); self.cursor.set(Some(to)); Ok(()) } /// Encode a string directly to the static buffer as an optimization. pub(crate) fn encode_str( &self, s: &str, from: LineColumn, to: Option, ) -> Result<()> { self.flush_whitespace(Some(from), to)?; self.buf.borrow_mut().push_str(s); Ok(()) } /// Eval the given identifier. pub(crate) fn eval_ident( &self, ident: &syn::Ident, from: LineColumn, to: Option, ) -> Result<()> { let Ctxt { receiver, module } = self.cx; self.flush(Some(from), to)?; let ident = syn::LitStr::new(&ident.to_string(), ident.span()); self.stream.borrow_mut().extend(q::quote! { #receiver.append(#module::__priv::open_eval()); #receiver.append(#module::__priv::static_(#ident)); #receiver.append(#module::__priv::close_eval()); }); self.options.has_eval.set(true); Ok(()) } /// Eval the given expression. pub(crate) fn eval_stream( &self, expr: TokenStream, from: LineColumn, to: Option, ) -> Result<()> { self.flush(Some(from), to)?; let Ctxt { receiver, module } = self.cx; self.stream.borrow_mut().extend(q::quote! { #receiver.append(#module::__priv::open_eval()); #expr #receiver.append(#module::__priv::close_eval()); }); self.options.has_eval.set(true); Ok(()) } /// Extend the content of the string with the given raw stream. pub(crate) fn raw_expr( &self, expr: &syn::Expr, from: LineColumn, to: Option, ) -> Result<()> { self.flush(Some(from), to)?; let Ctxt { receiver, .. } = self.cx; self.stream.borrow_mut().extend(q::quote! { #receiver.append(#expr); }); Ok(()) } pub(crate) fn extend_tt( &self, tt: &TokenTree, from: LineColumn, to: Option, ) -> Result<()> { self.flush_whitespace(Some(from), to)?; write!(self.buf.borrow_mut(), "{tt}").unwrap(); Ok(()) } /// Flush the outgoing buffer. pub fn flush(&self, from: Option, to: Option) -> Result<()> { let Ctxt { receiver, module } = self.cx; self.flush_whitespace(from, to)?; let lit = { let buf = self.buf.borrow(); if buf.is_empty() { return Ok(()); } syn::LitStr::new(buf.as_str(), self.span) }; self.count.set(self.count.get().wrapping_add(1)); self.stream.borrow_mut().extend(q::quote! { #receiver.append(#module::__priv::static_(#lit)); }); self.buf.borrow_mut().clear(); Ok(()) } /// Flush the outgoing buffer. pub(crate) fn flush_whitespace( &self, from: Option, to: Option, ) -> Result<()> { if let (Some(from), Some(cursor)) = (from, self.cursor.get()) { if cursor.line != from.line { return Err(syn::Error::new( self.span, "string interpolations may not contain line breaks", )); } for _ in 0..from.column.saturating_sub(cursor.column) { self.buf.borrow_mut().push(' '); } } self.cursor.set(to); Ok(()) } } pub struct StringParser<'a> { cx: &'a Ctxt, buf: &'a Buf, start: LineColumn, end: LineColumn, span: Span, } impl<'a> StringParser<'a> { pub(crate) fn new(cx: &'a Ctxt, buf: &'a Buf, span: Span) -> syn::Result { let cursor = buf.cursor(span)?; Ok(Self { cx, buf, // Note: adjusting span since we expect the quoted string to be // withing a block, where the interior span is one character pulled // in in each direction. start: adjust_start(cursor.start), end: adjust_end(cursor.end), span, }) } pub(crate) fn parse(self, input: ParseStream) -> Result<(Options, Requirements, TokenStream)> { let mut requirements = Requirements::default(); let encoder = Encoder::new(self.cx, self.start, self.span); while !input.is_empty() { if input.peek(syn::Token![$]) && input.peek2(syn::Token![$]) { let start = input.parse::()?; let escape = input.parse::()?; let start = self.buf.cursor(start.span())?; let escape = self.buf.cursor(escape.span())?; encoder.encode_char('$', start.start, escape.end)?; continue; } if input.peek(syn::Token![$]) { if let Some((name, content, [start, end])) = parse_internal_function(input)? { match (name.as_literal_name(), content) { (LiteralName::Ident("const"), Some(content)) => { let start = self.buf.cursor(start)?; let end = self.buf.cursor(end)?; // Compile-time string optimization. A single, // enclosed literal string can be added to the // existing static buffer. if is_lit_str_opt(content.fork())? { let s = content.parse::()?; encoder.encode_str(&s.value(), start.start, Some(end.end))?; } else { let expr = content.parse::()?; encoder.raw_expr(&expr, start.start, Some(end.end))?; } } (literal_name, _) => { return Err(syn::Error::new( name.span(), format!( "Unsupported [str] function {literal_name}, expected one of: const" ), )); } } } else { let dollar = input.parse::()?; let [start] = dollar.spans; if !input.peek(token::Paren) { let ident = input.parse::()?; let start = self.buf.cursor(start.span())?; let end = self.buf.cursor(ident.span())?.end; encoder.eval_ident(&ident, start.start, Some(end))?; continue; } let content; let end = syn::parenthesized!(content in input).span; let (req, stream) = crate::quote::Quote::new(self.cx) .with_span(content.span())? .parse(&content)?; requirements.merge_with(req); let start = self.buf.cursor(start.span())?; let end = self.buf.cursor(end.span())?; encoder.eval_stream(stream, start.start, Some(end.end))?; } continue; } let tt = input.parse::()?; let cursor = self.buf.cursor(tt.span())?; encoder.extend_tt(&tt, cursor.start, Some(cursor.end))?; } let (options, stream) = encoder.finalize(self.end)?; Ok((options, requirements, stream)) } } pub(crate) fn is_lit_str_opt(content: ParseBuffer<'_>) -> syn::Result { if content.parse::>()?.is_none() { return Ok(false); } Ok(content.is_empty()) } ================================================ FILE: src/fmt/config.rs ================================================ use crate::lang::Lang; /// Indentation configuration. /// /// ``` /// use genco::prelude::*; /// use genco::fmt; /// /// let tokens: rust::Tokens = quote! { /// fn foo() -> u32 { /// 42u32 /// } /// }; /// /// let mut w = fmt::VecWriter::new(); /// /// let fmt = fmt::Config::from_lang::() /// .with_indentation(fmt::Indentation::Tab); /// let config = rust::Config::default(); /// /// tokens.format_file(&mut w.as_formatter(&fmt), &config)?; /// /// assert_eq! { /// vec![ /// "fn foo() -> u32 {", /// "\t42u32", /// "}", /// ], /// w.into_vec(), /// }; /// # Ok::<_, genco::fmt::Error>(()) /// ``` #[derive(Debug, Clone, Copy)] pub enum Indentation { /// Each indentation is the given number of spaces. Space(usize), /// Each indentation is a tab. Tab, } /// Configuration to use for formatting output. #[derive(Debug, Clone)] pub struct Config { /// Indentation level to use. pub(super) indentation: Indentation, /// What to use as a newline. pub(super) newline: &'static str, } impl Config { /// Construct a new default formatter configuration for the specified /// language. pub fn from_lang() -> Self where L: Lang, { Self { indentation: L::default_indentation(), newline: "\n", } } /// Modify indentation to use. pub fn with_indentation(self, indentation: Indentation) -> Self { Self { indentation, ..self } } /// Set what to use as newline. pub fn with_newline(self, newline: &'static str) -> Self { Self { newline, ..self } } } ================================================ FILE: src/fmt/cursor.rs ================================================ use crate::fmt; use crate::tokens::{Item, Kind}; /// Trait for peeking items. pub(super) trait Parse { type Output: ?Sized; /// Parse the given item into its output. fn parse(item: &Item) -> fmt::Result<&Self::Output>; /// Test if the peek matches the given item. fn peek(item: &Item) -> bool; } /// Peek for a literal. pub(super) struct Literal(()); impl Parse for Literal { type Output = str; #[inline] fn peek(item: &Item) -> bool { matches!(item.kind, Kind::Literal(..)) } #[inline] fn parse(item: &Item) -> fmt::Result<&Self::Output> { match &item.kind { Kind::Literal(s) => Ok(s), _ => Err(core::fmt::Error), } } } /// Peek for an eval marker. pub(super) struct CloseEval(()); impl Parse for CloseEval { type Output = (); #[inline] fn peek(item: &Item) -> bool { matches!(item.kind, Kind::CloseEval) } #[inline] fn parse(item: &Item) -> fmt::Result<&Self::Output> { match &item.kind { Kind::CloseEval => Ok(&()), _ => Err(core::fmt::Error), } } } /// Parser helper. pub(super) struct Cursor<'a, T> { lang: &'a [T], items: &'a [Item], } impl<'a, T> Cursor<'a, T> { /// Construct a new cursor. pub(super) fn new(lang: &'a [T], items: &'a [Item]) -> Self { Self { lang, items } } /// Get a language item by index. pub(super) fn lang(&self, index: usize) -> fmt::Result<&'a T> { self.lang.get(index).ok_or(core::fmt::Error) } /// Get the next item. pub(super) fn next(&mut self) -> Option<&Item> { let (first, rest) = self.items.split_first()?; self.items = rest; Some(first) } #[inline] pub(super) fn peek

(&self) -> bool where P: Parse, { if let Some(item) = self.items.first() { P::peek(item) } else { false } } #[inline] pub(super) fn peek1

(&self) -> bool where P: Parse, { if let Some(item) = self.items.get(1) { P::peek(item) } else { false } } #[inline] pub(super) fn parse

(&mut self) -> fmt::Result<&P::Output> where P: Parse, { let item = self.next().ok_or(core::fmt::Error)?; P::parse(item) } } ================================================ FILE: src/fmt/fmt_writer.rs ================================================ use crate::fmt; /// Helper struct to format a token stream to an underlying writer implementing /// [fmt::Write][std::fmt::Write]. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// use genco::fmt; /// /// let map = rust::import("std::collections", "HashMap"); /// /// let tokens: rust::Tokens = quote! { /// let mut m = $map::new(); /// m.insert(1u32, 2u32); /// }; /// /// // Note: String implements std::fmt::Write /// let mut w = fmt::FmtWriter::new(String::new()); /// /// let fmt = fmt::Config::from_lang::(); /// /// let config = rust::Config::default(); /// // Default format state for Rust. /// let format = rust::Format::default(); /// /// tokens.format(&mut w.as_formatter(&fmt), &config, &format)?; /// /// let string = w.into_inner(); /// /// assert_eq!("let mut m = HashMap::new();\nm.insert(1u32, 2u32);", string); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub struct FmtWriter where W: core::fmt::Write, { writer: W, } impl FmtWriter where W: core::fmt::Write, { /// Construct a new line writer from the underlying writer. pub fn new(writer: W) -> Self { Self { writer } } /// Convert into a formatter. pub fn as_formatter<'a>(&'a mut self, config: &'a fmt::Config) -> fmt::Formatter<'a> { fmt::Formatter::new(self, config) } /// Convert into underlying writer. pub fn into_inner(self) -> W { self.writer } } impl core::fmt::Write for FmtWriter where W: core::fmt::Write, { #[inline(always)] fn write_char(&mut self, c: char) -> core::fmt::Result { self.writer.write_char(c) } #[inline(always)] fn write_str(&mut self, s: &str) -> core::fmt::Result { self.writer.write_str(s) } } impl fmt::Write for FmtWriter where W: core::fmt::Write, { #[inline(always)] fn write_line(&mut self, config: &fmt::Config) -> fmt::Result { self.writer.write_str(config.newline) } } ================================================ FILE: src/fmt/formatter.rs ================================================ use core::mem; use alloc::string::String; use crate::fmt; use crate::fmt::config::{Config, Indentation}; use crate::fmt::cursor::{self, Cursor}; use crate::lang::Lang; use crate::tokens::{Item, Kind}; /// Buffer used as indentation source. static SPACES: &str = " "; static TABS: &str = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; #[derive(Default, Debug, Clone, Copy)] enum Whitespace { #[default] None, Initial, Push, Line, } impl Whitespace { /// Convert into an indentation level. /// /// If we return `None`, no indentation nor lines should be written since we /// are at the initial stage of the file. fn into_indent(self) -> Option { match self { Self::Initial => Some(0), Self::Push => Some(1), Self::Line => Some(2), Self::None => None, } } } /// Token stream formatter. Keeps track of everything we need to know in order /// to enforce genco's indentation and whitespace rules. pub struct Formatter<'a> { write: &'a mut (dyn fmt::Write + 'a), /// Formatter configuration. config: &'a Config, /// How many lines we want to add to the output stream. /// /// This will only be realized if we push non-whitespace. line: Whitespace, /// How many spaces we want to add to the output stream. /// /// This will only be realized if we push non-whitespace, and will be reset /// if a new line is pushed or indentation changes. spaces: usize, /// Current indentation level. indent: i16, } impl<'a> Formatter<'a> { /// Construct a new formatter. pub(crate) fn new(write: &'a mut (dyn fmt::Write + 'a), config: &'a Config) -> Formatter<'a> { Formatter { write, line: Whitespace::Initial, spaces: 0usize, indent: 0i16, config, } } /// Format the given stream of tokens. pub(crate) fn format_items( &mut self, lang: &[L::Item], items: &[Item], config: &L::Config, format: &L::Format, ) -> fmt::Result<()> where L: Lang, { let mut cursor = Cursor::new(lang, items); self.format_cursor::(&mut cursor, config, format, false) } /// Forcibly write a line ending, at the end of a file. /// /// This will also reset any whitespace we have pending. pub(crate) fn write_trailing_line(&mut self) -> fmt::Result { self.line = Whitespace::default(); self.spaces = 0; self.write.write_trailing_line(self.config)?; Ok(()) } /// Write the given string. fn write_str(&mut self, s: &str) -> fmt::Result { if !s.is_empty() { self.flush_whitespace()?; self.write.write_str(s)?; } Ok(()) } fn push(&mut self) { self.line = match self.line { Whitespace::Initial => return, Whitespace::Line => return, _ => Whitespace::Push, }; self.spaces = 0; } /// Push a new line. fn line(&mut self) { self.line = match self.line { Whitespace::Initial => return, _ => Whitespace::Line, }; self.spaces = 0; } /// Push a space. fn space(&mut self) { self.spaces += 1; } /// Increase indentation level. fn indentation(&mut self, n: i16) { self.push(); self.indent += n; } /// Internal function for formatting. fn format_cursor( &mut self, cursor: &mut Cursor<'_, L::Item>, config: &L::Config, format: &L::Format, end_on_close_quote: bool, ) -> fmt::Result where L: Lang, { use crate::lang::LangItem as _; let mut buf = String::new(); let mut stack = smallvec::SmallVec::<[Frame; 4]>::new(); stack.push(Frame::default()); while let (Some(item), Some(head)) = (cursor.next(), stack.last_mut()) { let Frame { in_quote, has_eval, end_on_eval, } = head; match item.kind { Kind::Indentation(0) => (), Kind::Literal(ref literal) => { if *in_quote { L::write_quoted(self, literal)?; } else { self.write_str(literal)?; } } Kind::OpenQuote(e) if !*in_quote => { *has_eval = e; *in_quote = true; L::open_quote(self, config, format, *has_eval)?; } // Warning: slow path which will buffer a string internally. // This is used for expressions like: `$[str](Hello $(quoted(world)))`. // // Evaluating quotes are not supported. Kind::OpenQuote(false) if *in_quote => { self.quoted_quote::(cursor, &mut buf, config, format)?; L::write_quoted(self, &buf)?; buf.clear(); } Kind::CloseQuote if end_on_close_quote => { return Ok(()); } Kind::CloseQuote if *in_quote => { *in_quote = false; L::close_quote(self, config, format, mem::take(has_eval))?; } Kind::Lang(lang) => { cursor.lang(lang)?.format(self, config, format)?; } // whitespace below Kind::Push => { self.push(); } Kind::Line => { self.line(); } Kind::Space => { self.space(); } Kind::Indentation(n) => { self.indentation(n); } Kind::OpenEval if *in_quote => { if cursor.peek::() && cursor.peek1::() { let literal = cursor.parse::()?; L::string_eval_literal(self, config, format, literal)?; cursor.parse::()?; } else { L::start_string_eval(self, config, format)?; stack.push(Frame { in_quote: false, has_eval: false, end_on_eval: true, }); } } // Eval are only allowed within quotes. Kind::CloseEval if *end_on_eval => { L::end_string_eval(self, config, format)?; stack.pop(); } _ => { // Anything else is an illegal state for formatting. return Err(core::fmt::Error); } } } return Ok(()); #[derive(Default, Clone)] struct Frame { in_quote: bool, has_eval: bool, end_on_eval: bool, } } /// Support for evaluating an interior quote and returning it as a string. fn quoted_quote( &mut self, cursor: &mut Cursor<'_, L::Item>, buf: &mut String, config: &L::Config, format: &L::Format, ) -> fmt::Result<()> where L: Lang, { use crate::fmt::FmtWriter; let mut w = FmtWriter::new(buf); let out = &mut Formatter::new(&mut w, self.config); L::open_quote(out, config, format, false)?; out.format_cursor::(cursor, config, format, true)?; L::close_quote(out, config, format, false)?; Ok(()) } // Realize any pending whitespace just prior to writing a non-whitespace // item. fn flush_whitespace(&mut self) -> fmt::Result { let mut spaces = mem::take(&mut self.spaces); if let Some(lines) = mem::take(&mut self.line).into_indent() { for _ in 0..lines { self.write.write_line(self.config)?; } let level = i16::max(self.indent, 0) as usize; match self.config.indentation { Indentation::Space(n) => { spaces += level * n; } Indentation::Tab => { let mut tabs = level; while tabs > 0 { let len = usize::min(tabs, TABS.len()); self.write.write_str(&TABS[0..len])?; tabs -= len; } } } } while spaces > 0 { let len = usize::min(spaces, SPACES.len()); self.write.write_str(&SPACES[0..len])?; spaces -= len; } Ok(()) } } impl core::fmt::Write for Formatter<'_> { fn write_str(&mut self, s: &str) -> fmt::Result { if !s.is_empty() { Formatter::write_str(self, s)?; } Ok(()) } } impl core::fmt::Debug for Formatter<'_> { fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fmt.debug_struct("Formatter") .field("line", &self.line) .field("spaces", &self.spaces) .field("indent", &self.indent) .field("config", self.config) .finish() } } ================================================ FILE: src/fmt/io_writer.rs ================================================ use std::io; use crate::fmt; /// Helper struct to format a token stream to an underlying writer implementing /// [`io::Write`]. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// use genco::fmt; /// /// let map = rust::import("std::collections", "HashMap"); /// /// let tokens: rust::Tokens = quote! { /// let mut m = $map::new(); /// m.insert(1u32, 2u32); /// }; /// /// // Vec implements std::io::Write /// let mut w = fmt::IoWriter::new(Vec::::new()); /// /// let fmt = fmt::Config::from_lang::(); /// let config = rust::Config::default(); /// // Default format state for Rust. /// let format = rust::Format::default(); /// /// tokens.format(&mut w.as_formatter(&fmt), &config, &format)?; /// /// let vector = w.into_inner(); /// let string = std::str::from_utf8(&vector)?; /// /// assert_eq!("let mut m = HashMap::new();\nm.insert(1u32, 2u32);", string); /// # Ok::<_, anyhow::Error>(()) /// ``` pub struct IoWriter where W: io::Write, { writer: W, } impl IoWriter where W: io::Write, { /// Construct a new line writer from the underlying writer. pub fn new(writer: W) -> Self { Self { writer } } /// Convert into a formatter. pub fn as_formatter<'a>(&'a mut self, config: &'a fmt::Config) -> fmt::Formatter<'a> { fmt::Formatter::new(self, config) } /// Convert into the inner writer. pub fn into_inner(self) -> W { self.writer } } impl core::fmt::Write for IoWriter where W: io::Write, { #[inline(always)] fn write_char(&mut self, c: char) -> core::fmt::Result { self.writer .write_all(c.encode_utf8(&mut [0; 4]).as_bytes()) .map_err(|_| core::fmt::Error) } #[inline(always)] fn write_str(&mut self, s: &str) -> core::fmt::Result { self.writer .write_all(s.as_bytes()) .map_err(|_| core::fmt::Error) } } impl fmt::Write for IoWriter where W: io::Write, { #[inline(always)] fn write_line(&mut self, config: &fmt::Config) -> fmt::Result { self.writer .write_all(config.newline.as_bytes()) .map_err(|_| core::fmt::Error) } } ================================================ FILE: src/fmt/mod.rs ================================================ //! Code formatting utilities. //! //! So you have a token stream and it's time to format it into a //! file/string/whatever? You've come to the right place! //! //! Formatting is done through the following utilities: //! //! * [fmt::VecWriter][VecWriter] - To write result into a vector. //! * [fmt::FmtWriter][FmtWriter] - To write the result into something //! implementing [fmt::Write][std::fmt::Write]. //! * [fmt::IoWriter][IoWriter]- To write the result into something implementing //! [io::Write][std::io::Write]. //! //! # Examples //! //! The following is an example, showcasing how you can format directly to //! [stdout]. //! //! [stdout]: std::io::stdout //! //! # Examples //! //! ```rust,no_run //! use genco::prelude::*; //! use genco::fmt; //! //! let map = rust::import("std::collections", "HashMap"); //! //! let tokens: rust::Tokens = quote! { //! let mut m = #map::new(); //! m.insert(1u32, 2u32); //! }; //! //! let stdout = std::io::stdout(); //! let mut w = fmt::IoWriter::new(stdout.lock()); //! //! let fmt = fmt::Config::from_lang::() //! .with_indentation(fmt::Indentation::Space(2)); //! let config = rust::Config::default(); //! //! // Default format state for Rust. //! let format = rust::Format::default(); //! //! tokens.format(&mut w.as_formatter(&fmt), &config, &format)?; //! # Ok::<_, genco::fmt::Error>(()) //! ``` mod config; mod cursor; mod fmt_writer; mod formatter; #[cfg(feature = "std")] mod io_writer; mod vec_writer; pub use self::config::{Config, Indentation}; pub use self::fmt_writer::FmtWriter; pub use self::formatter::Formatter; #[cfg(feature = "std")] pub use self::io_writer::IoWriter; pub use self::vec_writer::VecWriter; /// Result type for the `fmt` module. pub type Result = core::result::Result; /// Error for the `fmt` module. pub type Error = core::fmt::Error; /// Trait that defines a line writer. pub(crate) trait Write: core::fmt::Write { /// Implement for writing a line. fn write_line(&mut self, config: &Config) -> Result; /// Implement for writing the trailing line ending of the file. #[inline] fn write_trailing_line(&mut self, config: &Config) -> Result { self.write_line(config) } } ================================================ FILE: src/fmt/vec_writer.rs ================================================ use alloc::string::String; use alloc::vec::Vec; use crate::fmt; /// Helper struct to format a token stream as a vector of strings. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// use genco::fmt; /// /// let map = rust::import("std::collections", "HashMap"); /// /// let tokens: rust::Tokens = quote! { /// let mut m = $map::new(); /// m.insert(1u32, 2u32); /// }; /// /// // Note: String implements std::fmt::Write /// let mut w = fmt::VecWriter::new(); /// /// let fmt = fmt::Config::from_lang::(); /// /// let config = rust::Config::default(); /// // Default format state for Rust. /// let format = rust::Format::default(); /// /// tokens.format(&mut w.as_formatter(&fmt), &config, &format)?; /// /// let vec = w.into_vec(); /// /// assert_eq!( /// vec![ /// "let mut m = HashMap::new();", /// "m.insert(1u32, 2u32);", /// ], /// vec /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` #[derive(Default)] pub struct VecWriter { line_buffer: String, target: Vec, } impl VecWriter { /// Construct a new writer to a vector. pub fn new() -> Self { Self::default() } /// Convert into a formatter. pub fn as_formatter<'a>(&'a mut self, config: &'a fmt::Config) -> fmt::Formatter<'a> { fmt::Formatter::new(self, config) } /// Convert into a vector. pub fn into_vec(mut self) -> Vec { self.target.push(self.line_buffer); self.target } } impl core::fmt::Write for VecWriter { #[inline(always)] fn write_char(&mut self, c: char) -> core::fmt::Result { self.line_buffer.write_char(c) } #[inline(always)] fn write_str(&mut self, s: &str) -> core::fmt::Result { self.line_buffer.write_str(s) } } impl fmt::Write for VecWriter { #[inline(always)] fn write_line(&mut self, _: &fmt::Config) -> fmt::Result { self.target.push(self.line_buffer.clone()); self.line_buffer.clear(); Ok(()) } // NB: trailing line is ignored for vector writer. fn write_trailing_line(&mut self, _: &fmt::Config) -> fmt::Result { Ok(()) } } ================================================ FILE: src/lang/c.rs ================================================ //! Specialization for C code generation. use core::fmt::Write as _; use alloc::collections::BTreeSet; use crate as genco; use crate::fmt; use crate::quote_in; use crate::tokens::{quoted, ItemStr}; /// Tokens container specialization for C. pub type Tokens = crate::Tokens; impl_lang! { /// Language specialization for C. pub C { type Config = Config; type Format = Format; type Item = Import; fn write_quoted(out: &mut fmt::Formatter<'_>, input: &str) -> fmt::Result { super::c_family_write_quoted(out, input) } fn format_file( tokens: &Tokens, out: &mut fmt::Formatter<'_>, config: &Self::Config, ) -> fmt::Result { let mut header = Tokens::new(); Self::imports(&mut header, tokens); let format = Format::default(); header.format(out, config, &format)?; tokens.format(out, config, &format)?; Ok(()) } } Import(Import) { fn format(&self, out: &mut fmt::Formatter<'_>, _: &Config, _: &Format) -> fmt::Result { out.write_str(&self.item)?; Ok(()) } } } /// The include statement for a C header file such as `#include "foo/bar.h"` or /// `#include `. /// /// Created using the [include()] function. #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub struct Import { /// Path to included file. path: ItemStr, /// Item declared in the included file. item: ItemStr, /// True if the include is specified as a system header using `<>`, false if a local header using `""`. system: bool, } /// Format for C. #[derive(Debug, Default)] pub struct Format {} /// Config data for C. #[derive(Debug, Default)] pub struct Config {} impl C { fn imports(out: &mut Tokens, tokens: &Tokens) { let mut includes = BTreeSet::new(); for include in tokens.iter_lang() { includes.insert((&include.path, include.system)); } if includes.is_empty() { return; } for (file, system_header) in includes { if system_header { quote_in!(*out => #include <$(file)>); } else { quote_in!(*out => #include $(quoted(file))); } out.push(); } out.line(); } } /// Include an item declared in a local C header file such as `#include "foo/bar.h"` /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let fizzbuzz = c::include("foo/bar.h", "fizzbuzz"); /// /// let fizzbuzz_toks = quote! { /// $fizzbuzz /// }; /// /// assert_eq!( /// vec![ /// "#include \"foo/bar.h\"", /// "", /// "fizzbuzz", /// ], /// fizzbuzz_toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn include(path: impl Into, item: impl Into) -> Import { Import { path: path.into(), item: item.into(), system: false, } } /// Include an item declared in a C system header such as `#include `. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let printf = c::include_system("stdio.h", "printf"); /// /// let printf_toks = quote! { /// $printf /// }; /// /// assert_eq!( /// vec![ /// "#include ", /// "", /// "printf", /// ], /// printf_toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn include_system(path: M, item: N) -> Import where M: Into, N: Into, { Import { path: path.into(), item: item.into(), system: true, } } ================================================ FILE: src/lang/csharp/block_comment.rs ================================================ use crate::lang::Csharp; use crate::tokens::{FormatInto, ItemStr}; use crate::Tokens; /// Format a doc comment where each line is preceeded by `///`. /// /// This struct is created by the [block_comment][super::block_comment()] function. pub struct BlockComment(pub(super) T); impl FormatInto for BlockComment where T: IntoIterator, T::Item: Into, { fn format_into(self, tokens: &mut Tokens) { for line in self.0 { tokens.push(); tokens.append(ItemStr::static_("///")); tokens.space(); tokens.append(line.into()); } } } ================================================ FILE: src/lang/csharp/comment.rs ================================================ use crate::lang::Csharp; use crate::tokens; use crate::Tokens; /// Format a doc comment where each line is preceeded by `//`. /// /// This struct is created by the [comment][super::comment()] function. pub struct Comment(pub(super) T); impl tokens::FormatInto for Comment where T: IntoIterator, T::Item: Into, { fn format_into(self, tokens: &mut Tokens) { for line in self.0 { tokens.push(); tokens.append(tokens::static_literal("//")); tokens.space(); tokens.append(line.into()); } } } ================================================ FILE: src/lang/csharp/mod.rs ================================================ //! Specialization for Csharp code generation. //! //! # String Quoting in C# //! //! Since C# uses UTF-16 internally, but literal strings support C-style family //! of escapes. //! //! See [c_family_write_quoted][super::c_family_write_quoted]. //! //! ```rust //! use genco::prelude::*; //! //! let toks: csharp::Tokens = quote!("start π 😊 \n \x7f end"); //! assert_eq!("\"start \\u03c0 \\U0001f60a \\n \\x7f end\"", toks.to_string()?); //! # Ok::<_, genco::fmt::Error>(()) //! ``` mod block_comment; mod comment; use core::fmt::Write as _; use alloc::collections::{BTreeMap, BTreeSet}; use alloc::string::{String, ToString}; use crate as genco; use crate::fmt; use crate::quote_in; use crate::tokens::ItemStr; pub use self::block_comment::BlockComment; pub use self::comment::Comment; /// Tokens container specialization for C#. pub type Tokens = crate::Tokens; impl_lang! { /// Language specialization for C#. pub Csharp { type Config = Config; type Format = Format; type Item = Import; fn write_quoted(out: &mut fmt::Formatter<'_>, input: &str) -> fmt::Result { // From: https://csharpindepth.com/articles/Strings super::c_family_write_quoted(out, input) } fn format_file( tokens: &Tokens, out: &mut fmt::Formatter<'_>, config: &Self::Config, ) -> fmt::Result { let mut file: Tokens = Tokens::new(); let mut format = Format::default(); Self::imports(&mut file, tokens, config, &mut format.imported_names); if let Some(namespace) = &config.namespace { quote_in! { file => namespace $namespace { $tokens } } file.format(out, config, &format)?; } else { file.format(out, config, &format)?; tokens.format(out, config, &format)?; } Ok(()) } } Import(Import) { fn format(&self, out: &mut fmt::Formatter<'_>, config: &Config, format: &Format) -> fmt::Result { { let qualified = self.qualified || is_qualified(config, format, &self.namespace, &self.name); if qualified { out.write_str(&self.namespace)?; out.write_str(SEP)?; } } out.write_str(&self.name)?; return Ok(()); fn is_qualified(config: &Config, format: &Format, namespace: &str, name: &str) -> bool { // Name is in current namespace. No need to qualify. if let Some(config) = &config.namespace { if &**config == namespace { return false; } } if let Some(imported) = format.imported_names.get(name) { // a conflicting name is in the namespace. if imported != namespace { return true; } } false } } } } /// Separator between types and modules in C#. const SEP: &str = "."; /// State using during formatting of C# language items. #[derive(Debug, Default)] pub struct Format { /// Keeping track of names which have been imported, do determine whether /// their use has to be qualified or not. /// /// A missing name means that it has to be used in a qualified manner. imported_names: BTreeMap, } /// Config data for Csharp formatting. #[derive(Debug, Default)] pub struct Config { /// namespace to use. namespace: Option, } impl Config { /// Set the namespace name to build. pub fn with_namespace(self, namespace: N) -> Self where N: Into, { Self { namespace: Some(namespace.into()), } } } /// The import of a C# type `using System.IO;`. /// /// Created through the [import()] function. #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub struct Import { /// namespace of the class. namespace: ItemStr, /// Name of class. name: ItemStr, /// Use as qualified type. qualified: bool, } impl Import { /// Make this type into a qualified type that is always used with a /// namespace. pub fn qualified(self) -> Self { Self { qualified: true, ..self } } } impl Csharp { fn imports( out: &mut Tokens, tokens: &Tokens, config: &Config, imported_names: &mut BTreeMap, ) { let mut modules = BTreeSet::new(); for import in tokens.iter_lang() { modules.insert((&*import.namespace, &*import.name)); } if modules.is_empty() { return; } let mut imported = BTreeSet::new(); for (namespace, name) in modules { if Some(namespace) == config.namespace.as_deref() { continue; } match imported_names.get(name) { // already imported... Some(existing) if existing == namespace => continue, // already imported, as something else... Some(_) => continue, _ => {} } if !imported.contains(namespace) { quote_in!(*out => using $namespace;); out.push(); imported.insert(namespace); } imported_names.insert(name.to_string(), namespace.to_string()); } out.line(); } } /// The import of a C# type `using System.IO;`. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let a = csharp::import("Foo.Bar", "A"); /// let b = csharp::import("Foo.Bar", "B"); /// let ob = csharp::import("Foo.Baz", "B"); /// /// let toks: Tokens = quote! { /// $a /// $b /// $ob /// }; /// /// assert_eq!( /// vec![ /// "using Foo.Bar;", /// "", /// "A", /// "B", /// "Foo.Baz.B", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn import(namespace: P, name: N) -> Import where P: Into, N: Into, { Import { namespace: namespace.into(), name: name.into(), qualified: false, } } /// Format a doc comment where each line is preceeded by `///`. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// use std::iter; /// /// let toks = quote! { /// $(csharp::block_comment(vec!["Foo"])) /// $(csharp::block_comment(iter::empty::<&str>())) /// $(csharp::block_comment(vec!["Bar"])) /// }; /// /// assert_eq!( /// vec![ /// "/// Foo", /// "/// Bar", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn block_comment(comment: T) -> BlockComment where T: IntoIterator, T::Item: Into, { BlockComment(comment) } /// Format a doc comment where each line is preceeded by `//`. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let toks = quote! { /// $(csharp::comment(&["Foo"])) /// $(csharp::comment(&["Bar"])) /// }; /// /// assert_eq!( /// vec![ /// "// Foo", /// "// Bar", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn comment(comment: T) -> Comment where T: IntoIterator, T::Item: Into, { Comment(comment) } ================================================ FILE: src/lang/dart/doc_comment.rs ================================================ use crate::lang::Dart; use crate::tokens; use crate::Tokens; /// Format a doc comment where each line is preceeded by `///`. /// /// This struct is created by the [doc_comment][super::doc_comment()] function. pub struct DocComment(pub(super) T); impl tokens::FormatInto for DocComment where T: IntoIterator, T::Item: Into, { fn format_into(self, tokens: &mut Tokens) { for line in self.0 { tokens.push(); tokens.append(tokens::static_literal("///")); tokens.space(); tokens.append(line.into()); } } } ================================================ FILE: src/lang/dart/mod.rs ================================================ //! Specialization for Dart code generation. //! //! # String Quoting in Dart //! //! Since Java uses UTF-16 internally, string quoting for high unicode //! characters is done through surrogate pairs, as seen with the 😊 below. //! //! ```rust //! use genco::prelude::*; //! //! let toks: dart::Tokens = quote!("start π 😊 \n \x7f ÿ $ \\ end"); //! assert_eq!("\"start π 😊 \\n \\x7f ÿ \\$ \\\\ end\"", toks.to_string()?); //! # Ok::<_, genco::fmt::Error>(()) //! ``` //! //! # String Interpolation in Dart //! //! Strings can be interpolated in Dart, by using the special `$_()` //! escape sequence. //! //! ```rust //! use genco::prelude::*; //! //! let toks: dart::Tokens = quote!($[str]( Hello: $var )); //! assert_eq!("\" Hello: $var \"", toks.to_string()?); //! //! let toks: dart::Tokens = quote!($[str]( Hello: $(a + b) )); //! assert_eq!("\" Hello: ${a + b} \"", toks.to_string()?); //! # Ok::<_, genco::fmt::Error>(()) //! ``` mod doc_comment; pub use self::doc_comment::DocComment; use core::fmt::Write as _; use alloc::collections::BTreeSet; use crate as genco; use crate::fmt; use crate::quote_in; use crate::tokens::{quoted, ItemStr}; const SEP: &str = "."; /// dart:core package. const DART_CORE: &str = "dart:core"; /// Tokens container specialization for Dart. pub type Tokens = crate::Tokens; impl genco::lang::LangSupportsEval for Dart {} impl_lang! { /// Language specialization for Dart. pub Dart { type Config = Config; type Format = Format; type Item = Import; fn string_eval_literal( out: &mut fmt::Formatter<'_>, _config: &Self::Config, _format: &Self::Format, literal: &str, ) -> fmt::Result { write!(out, "${literal}")?; Ok(()) } /// Start a string-interpolated eval. fn start_string_eval( out: &mut fmt::Formatter<'_>, _config: &Self::Config, _format: &Self::Format, ) -> fmt::Result { out.write_str("${")?; Ok(()) } /// End a string interpolated eval. fn end_string_eval( out: &mut fmt::Formatter<'_>, _config: &Self::Config, _format: &Self::Format, ) -> fmt::Result { out.write_char('}')?; Ok(()) } fn write_quoted(out: &mut fmt::Formatter<'_>, input: &str) -> fmt::Result { // Note: Dart is like C escape, but since it supports string // interpolation, `$` also needs to be escaped! for c in input.chars() { match c { // backspace '\u{0008}' => out.write_str("\\b")?, // form feed '\u{0012}' => out.write_str("\\f")?, // new line '\n' => out.write_str("\\n")?, // carriage return '\r' => out.write_str("\\r")?, // horizontal tab '\t' => out.write_str("\\t")?, // vertical tab '\u{0011}' => out.write_str("\\v")?, // Note: only relevant if we were to use single-quoted strings. // '\'' => out.write_str("\\'")?, '"' => out.write_str("\\\"")?, '\\' => out.write_str("\\\\")?, '$' => out.write_str("\\$")?, c if !c.is_control() => out.write_char(c)?, c if (c as u32) < 0x100 => { write!(out, "\\x{:02x}", c as u32)?; } c => { for c in c.encode_utf16(&mut [0u16; 2]) { write!(out, "\\u{c:04x}")?; } } }; } Ok(()) } fn format_file( tokens: &Tokens, out: &mut fmt::Formatter<'_>, config: &Self::Config, ) -> fmt::Result { let mut imports: Tokens = Tokens::new(); Self::imports(&mut imports, tokens, config); let format = Format::default(); imports.format(out, config, &format)?; tokens.format(out, config, &format)?; Ok(()) } } Import(Import) { fn format(&self, out: &mut fmt::Formatter<'_>, _: &Config, _: &Format) -> fmt::Result { if let Some(alias) = &self.alias { out.write_str(alias.as_ref())?; out.write_str(SEP)?; } out.write_str(&self.name)?; Ok(()) } } } /// Format state for Dart. #[derive(Debug, Default)] pub struct Format {} /// Config data for Dart formatting. #[derive(Debug, Default)] pub struct Config {} /// The import of a Dart type `import "dart:math";`. /// /// Created through the [import()] function. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Import { /// Path to import. path: ItemStr, /// Name imported. name: ItemStr, /// Alias of module. alias: Option, } impl Import { /// Add an `as` keyword to the import. pub fn with_alias(self, alias: impl Into) -> Import { Self { alias: Some(alias.into()), ..self } } } impl Dart { /// Resolve all imports. fn imports(out: &mut Tokens, input: &Tokens, _: &Config) { let mut modules = BTreeSet::new(); for import in input.iter_lang() { if &*import.path == DART_CORE { continue; } modules.insert((import.path.clone(), import.alias.clone())); } if modules.is_empty() { return; } for (name, alias) in modules { if let Some(alias) = alias { quote_in!(*out => import $(quoted(name)) as $alias;); } else { quote_in!(*out => import $(quoted(name));); } out.push(); } out.line(); } } /// The import of a Dart type `import "dart:math";`. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let a = dart::import("package:http/http.dart", "A"); /// let b = dart::import("package:http/http.dart", "B"); /// let c = dart::import("package:http/http.dart", "C").with_alias("h2"); /// let d = dart::import("../http.dart", "D"); /// /// let toks = quote! { /// $a /// $b /// $c /// $d /// }; /// /// let expected = vec![ /// "import \"../http.dart\";", /// "import \"package:http/http.dart\";", /// "import \"package:http/http.dart\" as h2;", /// "", /// "A", /// "B", /// "h2.C", /// "D", /// ]; /// /// assert_eq!(expected, toks.to_file_vec()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn import(path: P, name: N) -> Import where P: Into, N: Into, { Import { path: path.into(), alias: None, name: name.into(), } } /// Format a doc comment where each line is preceeded by `///`. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// use std::iter; /// /// let toks = quote! { /// $(dart::doc_comment(vec!["Foo"])) /// $(dart::doc_comment(iter::empty::<&str>())) /// $(dart::doc_comment(vec!["Bar"])) /// }; /// /// assert_eq!( /// vec![ /// "/// Foo", /// "/// Bar", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn doc_comment(comment: T) -> DocComment where T: IntoIterator, T::Item: Into, { DocComment(comment) } ================================================ FILE: src/lang/go.rs ================================================ //! Specialization for Go code generation. //! //! # Examples //! //! Basic example: //! //! ```rust //! use genco::prelude::*; //! //! let toks: js::Tokens = quote! { //! function foo(v) { //! return v + ", World"; //! } //! //! foo("Hello"); //! }; //! //! assert_eq!( //! vec![ //! "function foo(v) {", //! " return v + \", World\";", //! "}", //! "", //! "foo(\"Hello\");", //! ], //! toks.to_file_vec()? //! ); //! # Ok::<_, genco::fmt::Error>(()) //! ``` //! //! String quoting in JavaScript: //! //! ```rust //! use genco::prelude::*; //! //! let toks: go::Tokens = quote!("start π 😊 \n \x7f end"); //! assert_eq!("\"start \\u03c0 \\U0001f60a \\n \\x7f end\"", toks.to_string()?); //! # Ok::<_, genco::fmt::Error>(()) //! ``` use core::fmt::Write as _; use alloc::collections::BTreeSet; use crate as genco; use crate::fmt; use crate::quote_in; use crate::tokens::{quoted, ItemStr}; const MODULE_SEP: &str = "/"; const SEP: &str = "."; /// Tokens container specialization for Go. pub type Tokens = crate::Tokens; impl_lang! { /// Language specialization for Go. pub Go { type Config = Config; type Format = Format; type Item = Import; fn write_quoted(out: &mut fmt::Formatter<'_>, input: &str) -> fmt::Result { // From: https://golang.org/src/strconv/quote.go super::c_family_write_quoted(out, input) } fn format_file( tokens: &Tokens, out: &mut fmt::Formatter<'_>, config: &Self::Config, ) -> fmt::Result { let mut header = Tokens::new(); if let Some(package) = &config.package { quote_in!(header => package $package); header.line(); } Self::imports(&mut header, tokens); let format = Format::default(); header.format(out, config, &format)?; tokens.format(out, config, &format)?; Ok(()) } } Import(Import) { fn format(&self, out: &mut fmt::Formatter<'_>, _: &Config, _: &Format) -> fmt::Result { if let Some(module) = self.module.rsplit(MODULE_SEP).next() { out.write_str(module)?; out.write_str(SEP)?; } out.write_str(&self.name)?; Ok(()) } } } /// The import of a Go type `import "foo/bar"`. /// /// Created using the [import()] function. #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub struct Import { /// Module of the imported name. module: ItemStr, /// Name imported. name: ItemStr, } /// Format for Go. #[derive(Debug, Default)] pub struct Format {} /// Config data for Go. #[derive(Debug, Default)] pub struct Config { package: Option, } impl Config { /// Configure the specified package. pub fn with_package>(self, package: P) -> Self { Self { package: Some(package.into()), } } } impl Go { fn imports(out: &mut Tokens, tokens: &Tokens) { let mut modules = BTreeSet::new(); for import in tokens.iter_lang() { modules.insert(&import.module); } if modules.is_empty() { return; } for module in modules { quote_in!(*out => import $(quoted(module))); out.push(); } out.line(); } } /// The import of a Go type `import "foo/bar"`. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let ty = go::import("foo/bar", "Debug"); /// /// let toks = quote! { /// $ty /// }; /// /// assert_eq!( /// vec![ /// "import \"foo/bar\"", /// "", /// "bar.Debug", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn import(module: M, name: N) -> Import where M: Into, N: Into, { Import { module: module.into(), name: name.into(), } } ================================================ FILE: src/lang/java/block_comment.rs ================================================ use crate::lang::Java; use crate::tokens; use crate::Tokens; /// Format a block comment, starting with `/**`, and ending in `*/`. /// /// This struct is created by the [block_comment][super::block_comment()] function. pub struct BlockComment(pub(super) T); impl tokens::FormatInto for BlockComment where T: IntoIterator, T::Item: Into, { fn format_into(self, tokens: &mut Tokens) { let mut it = self.0.into_iter().peekable(); if it.peek().is_none() { return; } tokens.push(); tokens.append(tokens::static_literal("/**")); tokens.push(); for line in it { tokens.space(); tokens.append(tokens::static_literal("*")); tokens.space(); tokens.append(line.into()); tokens.push(); } tokens.space(); tokens.append("*/"); } } ================================================ FILE: src/lang/java/mod.rs ================================================ //! Specialization for Java code generation. //! //! # String Quoting in Java //! //! Since Java uses UTF-16 internally, string quoting for high unicode //! characters is done through surrogate pairs, as seen with the 😊 below. //! //! ```rust //! use genco::prelude::*; //! //! let toks: java::Tokens = quote!("start π 😊 \n \x7f end"); //! assert_eq!("\"start \\u03c0 \\ud83d\\ude0a \\n \\u007f end\"", toks.to_string()?); //! # Ok::<_, genco::fmt::Error>(()) //! ``` mod block_comment; pub use self::block_comment::BlockComment; use core::fmt::Write as _; use alloc::collections::{BTreeMap, BTreeSet}; use alloc::string::{String, ToString}; use crate as genco; use crate::fmt; use crate::tokens::ItemStr; use crate::{quote, quote_in}; /// Tokens container specialized for Java. pub type Tokens = crate::Tokens; impl_lang! { /// Language specialization for Java. pub Java { type Config = Config; type Format = Format; type Item = Import; fn write_quoted(out: &mut fmt::Formatter<'_>, input: &str) -> fmt::Result { // From: https://docs.oracle.com/javase/tutorial/java/data/characters.html for c in input.chars() { match c { '\t' => out.write_str("\\t")?, '\u{0008}' => out.write_str("\\b")?, '\n' => out.write_str("\\n")?, '\r' => out.write_str("\\r")?, '\u{0014}' => out.write_str("\\f")?, '\'' => out.write_str("\\'")?, '"' => out.write_str("\\\"")?, '\\' => out.write_str("\\\\")?, ' ' => out.write_char(' ')?, c if c.is_ascii() && !c.is_control() => out.write_char(c)?, c => { for c in c.encode_utf16(&mut [0u16; 2]) { write!(out, "\\u{c:04x}")?; } } } } Ok(()) } fn format_file( tokens: &Tokens, out: &mut fmt::Formatter<'_>, config: &Self::Config, ) -> fmt::Result { let mut header = Tokens::new(); if let Some(ref package) = config.package { quote_in!(header => package $package;); header.line(); } let mut format = Format::default(); Self::imports(&mut header, tokens, config, &mut format.imported); header.format(out, config, &format)?; tokens.format(out, config, &format)?; Ok(()) } } Import(Import) { fn format(&self, out: &mut fmt::Formatter<'_>, config: &Config, format: &Format) -> fmt::Result { let file_package = config.package.as_ref().map(|p| p.as_ref()); let imported = format.imported.get(self.name.as_ref()).map(String::as_str); let pkg = Some(self.package.as_ref()); if &*self.package != JAVA_LANG && imported != pkg && file_package != pkg { out.write_str(self.package.as_ref())?; out.write_str(SEP)?; } out.write_str(&self.name)?; Ok(()) } } } const JAVA_LANG: &str = "java.lang"; const SEP: &str = "."; /// Formtat state for Java. #[derive(Debug, Default)] pub struct Format { /// Types which has been imported into the local namespace. imported: BTreeMap, } /// Configuration for Java. #[derive(Debug, Default)] pub struct Config { /// Package to use. package: Option, } impl Config { /// Configure package to use for the file generated. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// use genco::fmt; /// /// let optional = java::import("java.util", "Optional"); /// /// let toks = quote!($optional); /// /// let config = java::Config::default().with_package("java.util"); /// let fmt = fmt::Config::from_lang::(); /// /// let mut w = fmt::VecWriter::new(); /// /// toks.format_file(&mut w.as_formatter(&fmt), &config)?; /// /// assert_eq!( /// vec![ /// "package java.util;", /// "", /// "Optional", /// ], /// w.into_vec(), /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn with_package

(self, package: P) -> Self where P: Into, { Self { package: Some(package.into()), } } } /// The import of a Java type `import java.util.Optional;`. /// /// Created through the [import()] function. #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub struct Import { /// Package of the class. package: ItemStr, /// Name of class. name: ItemStr, } impl Java { fn imports( out: &mut Tokens, tokens: &Tokens, config: &Config, imported: &mut BTreeMap, ) { let mut modules = BTreeSet::new(); let file_package = config.package.as_ref().map(|p| p.as_ref()); for import in tokens.iter_lang() { modules.insert((import.package.clone(), import.name.clone())); } if modules.is_empty() { return; } for (package, name) in modules { if imported.contains_key(&*name) { continue; } if &*package == JAVA_LANG { continue; } if Some(&*package) == file_package { continue; } out.append(quote!(import $(package.clone())$(SEP)$(name.clone());)); out.push(); imported.insert(name.to_string(), package.to_string()); } out.line(); } } /// The import of a Java type `import java.util.Optional;`. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let integer = java::import("java.lang", "Integer"); /// let a = java::import("java.io", "A"); /// /// let toks = quote! { /// $integer /// $a /// }; /// /// assert_eq!( /// vec![ /// "import java.io.A;", /// "", /// "Integer", /// "A", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn import(package: P, name: N) -> Import where P: Into, N: Into, { Import { package: package.into(), name: name.into(), } } /// Format a block comment, starting with `/**`, and ending in `*/`. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// use std::iter; /// /// let toks = quote! { /// $(java::block_comment(vec!["first line", "second line"])) /// $(java::block_comment(iter::empty::<&str>())) /// $(java::block_comment(vec!["third line"])) /// }; /// /// assert_eq!( /// vec![ /// "/**", /// " * first line", /// " * second line", /// " */", /// "/**", /// " * third line", /// " */", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn block_comment(comment: T) -> BlockComment where T: IntoIterator, T::Item: Into, { BlockComment(comment) } ================================================ FILE: src/lang/js.rs ================================================ //! Specialization for JavaScript code generation. //! //! # Examples //! //! Basic example: //! //! ```rust //! use genco::prelude::*; //! //! let toks: js::Tokens = quote! { //! function foo(v) { //! return v + ", World"; //! } //! //! foo("Hello"); //! }; //! //! assert_eq!( //! vec![ //! "function foo(v) {", //! " return v + \", World\";", //! "}", //! "", //! "foo(\"Hello\");", //! ], //! toks.to_file_vec()? //! ); //! # Ok::<_, genco::fmt::Error>(()) //! ``` //! //! # String Quoting in JavaScript //! //! JavaScript uses c-style string quoting, with indefinitely long unicode //! escape sequences. But any non-control character can be embedded directly //! into the string literal (like `"😊"`). //! //! ```rust //! use genco::prelude::*; //! //! let toks: js::Tokens = quote!("start π 😊 \n \x7f ÿ $ \\ end"); //! assert_eq!("\"start π 😊 \\n \\x7f ÿ $ \\\\ end\"", toks.to_string()?); //! //! let toks: js::Tokens = quote!($(quoted("start π 😊 \n \x7f ÿ $ \\ end"))); //! assert_eq!("\"start π 😊 \\n \\x7f ÿ $ \\\\ end\"", toks.to_string()?); //! # Ok::<_, genco::fmt::Error>(()) //! ``` use core::fmt::Write as _; use alloc::collections::{BTreeMap, BTreeSet}; use alloc::string::String; use crate::fmt; use crate::tokens::ItemStr; use relative_path::{RelativePath, RelativePathBuf}; /// Tokens container specialization for Rust. pub type Tokens = crate::Tokens; impl crate::lang::LangSupportsEval for JavaScript {} impl_lang! { /// JavaScript language specialization. pub JavaScript { type Config = Config; type Format = Format; type Item = Import; /// Start a string quote. fn open_quote( out: &mut fmt::Formatter<'_>, _config: &Self::Config, _format: &Self::Format, has_eval: bool, ) -> fmt::Result { if has_eval { out.write_char('`')?; } else { out.write_char('"')?; } Ok(()) } /// End a string quote. fn close_quote( out: &mut fmt::Formatter<'_>, _config: &Self::Config, _format: &Self::Format, has_eval: bool, ) -> fmt::Result { if has_eval { out.write_char('`')?; } else { out.write_char('"')?; } Ok(()) } fn start_string_eval( out: &mut fmt::Formatter<'_>, _config: &Self::Config, _format: &Self::Format, ) -> fmt::Result { out.write_str("${")?; Ok(()) } fn end_string_eval( out: &mut fmt::Formatter<'_>, _config: &Self::Config, _format: &Self::Format, ) -> fmt::Result { out.write_char('}')?; Ok(()) } fn write_quoted(out: &mut fmt::Formatter<'_>, input: &str) -> fmt::Result { // Reference: https://mathiasbynens.be/notes/javascript-escapes for c in input.chars() { match c { // backspace '\u{0008}' => out.write_str("\\b")?, // form feed '\u{0012}' => out.write_str("\\f")?, // new line '\n' => out.write_str("\\n")?, // carriage return '\r' => out.write_str("\\r")?, // horizontal tab '\t' => out.write_str("\\t")?, // vertical tab '\u{0011}' => out.write_str("\\v")?, // null character. '\0' => out.write_str("\\0")?, // Note: only relevant if we were to use single-quoted strings. // '\'' => out.write_str("\\'")?, '"' => out.write_str("\\\"")?, '\\' => out.write_str("\\\\")?, c if !c.is_control() => out.write_char(c)?, c if (c as u32) < 0x100 => { write!(out, "\\x{:02x}", c as u32)?; } c => { write!(out, "\\u{{{:x}}}", c as u32)?; } }; } Ok(()) } fn format_file( tokens: &Tokens, out: &mut fmt::Formatter<'_>, config: &Self::Config, ) -> fmt::Result { let mut imports = Tokens::new(); Self::imports(&mut imports, tokens, config); let format = Format::default(); imports.format(out, config, &format)?; tokens.format(out, config, &format)?; Ok(()) } } Import(Import) { fn format(&self, out: &mut fmt::Formatter<'_>, _: &Config, _: &Format) -> fmt::Result { let name = match self.kind { ImportKind::Named => self.alias.as_ref().unwrap_or(&self.name), _ => &self.name, }; out.write_str(name) } } } /// Format state for JavaScript. #[derive(Debug, Default)] pub struct Format {} /// Configuration for JavaScript. #[derive(Debug, Default)] pub struct Config { module_path: Option, } impl Config { /// Configure the path to the current module being renderer. /// /// This setting will determine what path imports are renderer relative /// towards. So importing a module from `"foo/bar.js"`, and setting this to /// `"foo/baz.js"` will cause the import to be rendered relatively as /// `"../bar.js"`. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// use genco::fmt; /// /// let foo1 = js::import(js::Module::Path("foo/bar.js".into()), "Foo1"); /// let foo2 = js::import(js::Module::Path("foo/bar.js".into()), "Foo2"); /// let react = js::import("react", "React").into_default(); /// /// let toks: js::Tokens = quote! { /// $foo1 /// $foo2 /// $react /// }; /// /// let mut w = fmt::VecWriter::new(); /// /// let config = js::Config::default().with_module_path("foo/baz.js"); /// let fmt = fmt::Config::from_lang::(); /// /// toks.format_file(&mut w.as_formatter(&fmt), &config)?; /// /// assert_eq!( /// vec![ /// "import {Foo1, Foo2} from \"../bar.js\";", /// "import React from \"react\";", /// "", /// "Foo1", /// "Foo2", /// "React" /// ], /// w.into_vec() /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn with_module_path(self, module_path: M) -> Self where M: Into, { Self { module_path: Some(module_path.into()), } } } /// Internal type to determine the kind of import used. #[derive(Debug, Clone, Copy, Hash, PartialOrd, Ord, PartialEq, Eq)] enum ImportKind { Named, Default, Wildcard, } /// The import of a JavaScript type `import {foo} from "module.js"`. /// /// Created through the [import()] function. #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub struct Import { /// The kind of the import. kind: ImportKind, /// Module of the imported name. module: Module, /// Name imported. name: ItemStr, /// Alias of an imported item. /// /// If this is set, you'll get an import like: /// /// ```text /// import { as } from /// ``` alias: Option, } impl Import { /// Change alias of imported item. /// /// This implies that the import is a named import. /// /// If this is set, you'll get an import like: /// /// ```text /// import { as } from /// ``` /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let a = js::import("collections", "vec"); /// let b = js::import("collections", "vec").with_alias("list"); /// /// let toks = quote! { /// $a /// $b /// }; /// /// assert_eq!( /// vec![ /// "import {vec, vec as list} from \"collections\";", /// "", /// "vec", /// "list", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn with_alias(self, alias: N) -> Self where N: Into, { Self { kind: ImportKind::Named, alias: Some(alias.into()), ..self } } /// Convert into a default import. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let default_vec = js::import("collections", "defaultVec").into_default(); /// /// let toks = quote!($default_vec); /// /// assert_eq!( /// vec![ /// "import defaultVec from \"collections\";", /// "", /// "defaultVec", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn into_default(self) -> Self { Self { kind: ImportKind::Default, alias: None, ..self } } /// Convert into a wildcard import. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let all = js::import("collections", "all").into_wildcard(); /// /// let toks = quote!($all); /// /// assert_eq!( /// vec![ /// "import * as all from \"collections\";", /// "", /// "all", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn into_wildcard(self) -> Self { Self { kind: ImportKind::Wildcard, alias: None, ..self } } } /// A module being imported. #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub enum Module { /// A module imported from a specific path. /// /// The path will be relativized according to the module specified in the /// [Config::with_module_path]. Path(RelativePathBuf), /// A globally imported module. Global(ItemStr), } impl<'a> From<&'a str> for Module { fn from(value: &'a str) -> Self { Self::Global(value.into()) } } impl From for Module { fn from(value: String) -> Self { Self::Global(value.into()) } } impl From for Module { fn from(value: ItemStr) -> Self { Self::Global(value) } } impl JavaScript { /// Translate imports into the necessary tokens. fn imports(out: &mut Tokens, tokens: &Tokens, config: &Config) { use crate as genco; use crate::prelude::*; let mut modules = BTreeMap::<&Module, ResolvedModule<'_>>::new(); let mut wildcards = BTreeSet::new(); for import in tokens.iter_lang() { match import.kind { ImportKind::Named => { let module = modules.entry(&import.module).or_default(); module.set.insert(match &import.alias { None => ImportedElement::Plain(&import.name), Some(alias) => ImportedElement::Aliased(&import.name, alias), }); } ImportKind::Default => { let module = modules.entry(&import.module).or_default(); module.default_import = Some(&import.name); } ImportKind::Wildcard => { wildcards.insert((&import.module, &import.name)); } } } if modules.is_empty() && wildcards.is_empty() { return; } for (module, name) in wildcards { out.push(); quote_in! { *out => import * as $name from $(ref t => render_from(t, config.module_path.as_deref(), module)); } } for (name, module) in modules { out.push(); quote_in! { *out => import $(ref tokens => { if let Some(default) = module.default_import { tokens.append(ItemStr::from(default)); if !module.set.is_empty() { tokens.append(","); tokens.space(); } } if !module.set.is_empty() { tokens.append("{"); let mut it = module.set.iter().peekable(); while let Some(el) = it.next() { match *el { ImportedElement::Plain(name) => { tokens.append(name); }, ImportedElement::Aliased(name, alias) => { quote_in!(*tokens => $name as $alias); } } if it.peek().is_some() { tokens.append(","); tokens.space(); } } tokens.append("}"); } }) from $(ref t => render_from(t, config.module_path.as_deref(), name)); }; } out.line(); #[derive(Default)] struct ResolvedModule<'a> { default_import: Option<&'a ItemStr>, set: BTreeSet>, } #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] enum ImportedElement<'a> { Plain(&'a ItemStr), Aliased(&'a ItemStr, &'a ItemStr), } fn render_from(t: &mut js::Tokens, module_path: Option<&RelativePath>, module: &Module) { quote_in! { *t => $(match (module_path, module) { (_, Module::Global(from)) => $(quoted(from)), (None, Module::Path(path)) => $(quoted(path.as_str())), (Some(module_path), Module::Path(path)) => $(quoted(module_path.relative(path).as_str())), }) } } } } /// The import of a JavaScript type `import {foo} from "module.js"`. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let default_vec = js::import("collections", "defaultVec").into_default(); /// let all = js::import("collections", "all").into_wildcard(); /// let vec = js::import("collections", "vec"); /// let vec_as_list = js::import("collections", "vec").with_alias("list"); /// /// let toks = quote! { /// $default_vec /// $all /// $vec /// $vec_as_list /// }; /// /// assert_eq!( /// vec![ /// "import * as all from \"collections\";", /// "import defaultVec, {vec, vec as list} from \"collections\";", /// "", /// "defaultVec", /// "all", /// "vec", /// "list", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn import(module: M, name: N) -> Import where M: Into, N: Into, { Import { kind: ImportKind::Named, module: module.into(), name: name.into(), alias: None, } } ================================================ FILE: src/lang/kotlin/mod.rs ================================================ //! Specialization for Kotlin code generation. //! //! # String Quoting in Kotlin //! //! Since Kotlin runs on the JVM, it also uses UTF-16 internally. String //! quoting for high unicode characters is done through surrogate pairs, as //! seen with the 😊 emoji below. Kotlin also requires escaping `$` characters //! in standard string literals. //! //! ```rust //! use genco::prelude::*; //! //! let toks: kotlin::Tokens = quote!("start π 😊 $var \n end"); //! assert_eq!("\"start \\u03c0 \\ud83d\\ude0a \\$var \\n end\"", toks.to_string()?); //! # Ok::<_, genco::fmt::Error>(()) //! ``` use core::fmt::Write as _; use crate as genco; use crate::fmt; use crate::quote_in; use crate::tokens::ItemStr; use alloc::collections::{BTreeMap, BTreeSet}; use alloc::string::{String, ToString}; /// Tokens container specialized for Kotlin. pub type Tokens = crate::Tokens; // This trait implementation signals to genco that the Kotlin language // supports evaluation constructs like `$(if ...)` in `quote!`. impl genco::lang::LangSupportsEval for Kotlin {} impl_lang! { /// Language specialization for Kotlin. pub Kotlin { type Config = Config; type Format = Format; type Item = Import; fn start_string_eval( out: &mut fmt::Formatter<'_>, _config: &Self::Config, _format: &Self::Format, ) -> fmt::Result { out.write_str("${")?; Ok(()) } fn end_string_eval( out: &mut fmt::Formatter<'_>, _config: &Self::Config, _format: &Self::Format, ) -> fmt::Result { out.write_char('}')?; Ok(()) } fn write_quoted(out: &mut fmt::Formatter<'_>, input: &str) -> fmt::Result { // See: https://kotlinlang.org/docs/basic-types.html#escaped-strings for c in input.chars() { match c { '\t' => out.write_str("\\t")?, '\u{0008}' => out.write_str("\\b")?, // Backspace '\n' => out.write_str("\\n")?, '\r' => out.write_str("\\r")?, '\'' => out.write_str("\\'")?, '"' => out.write_str("\\\"")?, '\\' => out.write_str("\\\\")?, '$' => out.write_str("\\$")?, c if c.is_ascii() && !c.is_control() => out.write_char(c)?, c => { // Encode non-ascii characters as UTF-16 surrogate pairs for unit in c.encode_utf16(&mut [0u16; 2]) { write!(out, "\\u{unit:04x}")?; } } } } Ok(()) } fn format_file( tokens: &Tokens, out: &mut fmt::Formatter<'_>, config: &Self::Config, ) -> fmt::Result { let mut header = Tokens::new(); let mut format = Format::default(); if let Some(ref package) = config.package { // package declarations in Kotlin do not have semicolons quote_in!(header => package $package); header.line(); } Self::imports(&mut header, tokens, config, &mut format.imported); header.format(out, config, &format)?; tokens.format(out, config, &format)?; Ok(()) } } Import(Import) { fn format(&self, out: &mut fmt::Formatter<'_>, config: &Config, format: &Format) -> fmt::Result { let file_package = config.package.as_ref().map(|p| p.as_ref()); let imported = format.imported.get(self.name.as_ref()).map(String::as_str); let current_package = Some(self.package.as_ref()); // Determine if we need to use the fully qualified name (FQN). // Use FQN if the class is not in the current package and has not been imported. // Or if a class with the same name has been imported from a different package. if file_package != current_package && imported != current_package { out.write_str(self.package.as_ref())?; out.write_str(".")?; } out.write_str(&self.name)?; Ok(()) } } } /// Formatting state for Kotlin. #[derive(Debug, Default)] pub struct Format { /// Types which have been imported into the local namespace. /// Maps a simple name to its full package. imported: BTreeMap, } /// Configuration for Kotlin. #[derive(Debug, Default)] pub struct Config { /// Package to use. package: Option, } impl Config { /// Configure package to use for the file generated. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// use genco::fmt; /// /// let list = kotlin::import("kotlin.collections", "List"); /// /// let toks = quote!($list); /// /// let config = kotlin::Config::default().with_package("com.example"); /// let fmt = fmt::Config::from_lang::(); /// /// let mut w = fmt::VecWriter::new(); /// /// toks.format_file(&mut w.as_formatter(&fmt), &config)?; /// /// assert_eq!( /// vec![ /// "package com.example", /// "", /// "import kotlin.collections.List", /// "", /// "List", /// ], /// w.into_vec(), /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn with_package

(self, package: P) -> Self where P: Into, { Self { package: Some(package.into()), } } } /// An import of a Kotlin type, like `import kotlin.collections.List`. /// /// Created through the [import()] function. #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub struct Import { /// Package of the class. package: ItemStr, /// Name of the class. name: ItemStr, } impl Kotlin { /// Gathers and writes import statements to the header. fn imports( out: &mut Tokens, tokens: &Tokens, config: &Config, imported: &mut BTreeMap, ) { let mut to_import = BTreeSet::new(); let file_package = config.package.as_ref().map(|p| p.as_ref()); for import in tokens.iter_lang() { // Don't import if the type is in the current package if Some(import.package.as_ref()) == file_package { continue; } // Don't import if a class with the same name is already imported from another package. if let Some(existing_package) = imported.get(import.name.as_ref()) { if existing_package != import.package.as_ref() { continue; } } to_import.insert(import.clone()); } if to_import.is_empty() { return; } for import in to_import { // import statements in Kotlin do not have semicolons quote_in!(*out => import $(&import.package).$(&import.name)); out.push(); imported.insert(import.name.to_string(), import.package.to_string()); } out.line(); } } /// Create a new Kotlin import. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let list = kotlin::import("kotlin.collections", "List"); /// let map = kotlin::import("kotlin.collections", "Map"); /// /// let toks = quote! { /// val a: $list /// val b: $map /// }; /// /// assert_eq!( /// vec![ /// "import kotlin.collections.List", /// "import kotlin.collections.Map", /// "", /// "val a: List", /// "val b: Map", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn import(package: P, name: N) -> Import where P: Into, N: Into, { Import { package: package.into(), name: name.into(), } } ================================================ FILE: src/lang/mod.rs ================================================ //! Language specialization for genco //! //! This module contains sub-modules which provide implementations of the [Lang] //! trait to configure genco for various programming languages. //! //! This module also provides a dummy [Lang] implementation for `()`. //! //! This allows `()` to be used as a quick and dirty way to do formatting, //! usually for examples. //! //! ```rust //! use genco::prelude::*; //! //! let tokens: Tokens = quote!(hello world); //! # Ok::<_, genco::fmt::Error>(()) //! ``` pub mod c; pub mod csharp; pub mod dart; pub mod go; pub mod java; pub mod js; pub mod kotlin; pub mod nix; pub mod python; pub mod rust; pub mod swift; pub use self::c::C; pub use self::csharp::Csharp; pub use self::dart::Dart; pub use self::go::Go; pub use self::java::Java; pub use self::js::JavaScript; pub use self::kotlin::Kotlin; pub use self::nix::Nix; pub use self::python::Python; pub use self::rust::Rust; pub use self::swift::Swift; use core::fmt::Write as _; use crate::fmt; use crate::Tokens; /// Trait to implement for language specialization. /// /// The various language implementations can be found in the [lang][self] /// module. pub trait Lang: Sized { /// Configuration associated with building a formatting element. type Config; /// State being used during formatting. type Format: Default; /// The type used when resolving imports. type Item: LangItem; /// Provide the default indentation. fn default_indentation() -> fmt::Indentation { fmt::Indentation::Space(4) } /// Start a string quote. fn open_quote( out: &mut fmt::Formatter<'_>, _config: &Self::Config, _format: &Self::Format, _has_eval: bool, ) -> fmt::Result { out.write_char('"')?; Ok(()) } /// End a string quote. fn close_quote( out: &mut fmt::Formatter<'_>, _config: &Self::Config, _format: &Self::Format, _has_eval: bool, ) -> fmt::Result { out.write_char('"')?; Ok(()) } /// A simple, single-literal string evaluation. fn string_eval_literal( out: &mut fmt::Formatter<'_>, config: &Self::Config, format: &Self::Format, literal: &str, ) -> fmt::Result { Self::start_string_eval(out, config, format)?; out.write_str(literal)?; Self::end_string_eval(out, config, format)?; Ok(()) } /// Start a string-interpolated eval. fn start_string_eval( _out: &mut fmt::Formatter<'_>, _config: &Self::Config, _format: &Self::Format, ) -> fmt::Result { Ok(()) } /// End a string interpolated eval. fn end_string_eval( _out: &mut fmt::Formatter<'_>, _config: &Self::Config, _format: &Self::Format, ) -> fmt::Result { Ok(()) } /// Performing string quoting according to language convention. fn write_quoted(out: &mut fmt::Formatter<'_>, input: &str) -> fmt::Result { out.write_str(input) } /// Write a file according to the specified language convention. fn format_file( tokens: &Tokens, out: &mut fmt::Formatter<'_>, config: &Self::Config, ) -> fmt::Result { let format = Self::Format::default(); tokens.format(out, config, &format) } } /// Marker trait indicating that a language supports /// [quoted string interpolation]. /// /// [quoted string interpolation]: https://docs.rs/genco/0/genco/macro.quote.html#quoted-string-interpolation pub trait LangSupportsEval: Lang {} /// Dummy implementation for a language. impl Lang for () { type Config = (); type Format = (); type Item = (); } impl LangItem for () where L: Lang, { fn format(&self, _: &mut fmt::Formatter<'_>, _: &L::Config, _: &L::Format) -> fmt::Result { Ok(()) } } /// A type-erased holder for language-specific items. /// /// Carries formatting and coercion functions like [LangItem][LangItem::format] /// to allow language specific processing to work. pub trait LangItem where L: Lang, { /// Format the language item appropriately. fn format( &self, fmt: &mut fmt::Formatter<'_>, config: &L::Config, format: &L::Format, ) -> fmt::Result; } /// Escape the given string according to a C-family escape sequence. /// /// See . /// /// This is one of the more common escape sequences and is provided here so you /// can use it if a language you've implemented requires it. pub fn c_family_write_quoted(out: &mut fmt::Formatter, input: &str) -> fmt::Result { for c in input.chars() { match c { // alert (bell) '\u{0007}' => out.write_str("\\a")?, // backspace '\u{0008}' => out.write_str("\\b")?, // form feed '\u{0012}' => out.write_str("\\f")?, // new line '\n' => out.write_str("\\n")?, // carriage return '\r' => out.write_str("\\r")?, // horizontal tab '\t' => out.write_str("\\t")?, // vertical tab '\u{0011}' => out.write_str("\\v")?, '\'' => out.write_str("\\'")?, '"' => out.write_str("\\\"")?, '\\' => out.write_str("\\\\")?, ' ' => out.write_char(' ')?, c if c.is_ascii() => { if !c.is_control() { out.write_char(c)? } else { write!(out, "\\x{:02x}", c as u32)?; } } c if (c as u32) < 0x10000 => { write!(out, "\\u{:04x}", c as u32)?; } c => { write!(out, "\\U{:08x}", c as u32)?; } }; } Ok(()) } ================================================ FILE: src/lang/nix.rs ================================================ //! Nix use core::fmt::Write as _; use alloc::collections::BTreeSet; use alloc::string::ToString; use crate as genco; use crate::fmt; use crate::quote_in; use crate::tokens::ItemStr; /// Tokens pub type Tokens = crate::Tokens; impl_lang! { /// Nix pub Nix { type Config = Config; type Format = Format; type Item = Import; fn write_quoted(out: &mut fmt::Formatter<'_>, input: &str) -> fmt::Result { super::c_family_write_quoted(out, input) } fn format_file( tokens: &Tokens, out: &mut fmt::Formatter<'_>, config: &Self::Config, ) -> fmt::Result { let mut header = Tokens::new(); if !config.scoped { Self::arguments(&mut header, tokens); } Self::withs(&mut header, tokens); Self::imports(&mut header, tokens); let format = Format::default(); header.format(out, config, &format)?; tokens.format(out, config, &format)?; Ok(()) } } Import(Import) { fn format(&self, out: &mut fmt::Formatter<'_>, _: &Config, _: &Format) -> fmt::Result { match self { Import::Argument(import) => out.write_str(&import.0)?, Import::Inherit(import) => out.write_str(&import.name)?, Import::Variable(import) => out.write_str(&import.name)?, Import::With(import) => out.write_str(&import.name)?, } Ok(()) } } } /// Import #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub enum Import { /// Argument Argument(ImportArgument), /// Inherit Inherit(ImportInherit), /// Variable Variable(ImportVariable), /// With With(ImportWith), } /// ImportArgument #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub struct ImportArgument(ItemStr); /// ImportInherit #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub struct ImportInherit { /// Path path: ItemStr, /// Name name: ItemStr, } /// ImportVariable #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub struct ImportVariable { /// Name name: ItemStr, /// Value value: Tokens, } /// ImportWith #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub struct ImportWith { /// Argument argument: ItemStr, /// Name name: ItemStr, } /// Format #[derive(Debug, Default)] pub struct Format {} /// Nix formatting configuration. #[derive(Debug, Default)] pub struct Config { scoped: bool, } impl Config { /// With scoped pub fn with_scoped(self, scoped: bool) -> Self { Self { scoped } } } impl Nix { fn arguments(out: &mut Tokens, tokens: &Tokens) { let mut arguments = BTreeSet::new(); for imports in tokens.iter_lang() { match imports { Import::Argument(argument) => { arguments.insert(argument.0.to_string()); } Import::Inherit(inherit) => { let argument = inherit.path.split('.').next(); if let Some(a) = argument { arguments.insert(a.to_string()); } } Import::Variable(variable) => { let value = &variable.value; for import in value.iter_lang() { match import { Import::Inherit(inherit) => { let argument = inherit.path.split('.').next(); if let Some(a) = argument { arguments.insert(a.to_string()); } } Import::Argument(argument) => { arguments.insert(argument.0.to_string()); } _ => (), } } } Import::With(with) => { let argument = with.argument.split('.').next(); if let Some(a) = argument { arguments.insert(a.to_string()); } } } } out.append("{"); out.push(); out.indent(); for argument in arguments { quote_in!(*out => $argument,); out.push(); } out.append("..."); out.push(); out.unindent(); out.append("}:"); out.push(); out.line(); } fn withs(out: &mut Tokens, tokens: &Tokens) { let mut withs = BTreeSet::new(); for imports in tokens.iter_lang() { if let Import::With(with) = imports { withs.insert(&with.argument); } } if withs.is_empty() { return; } for name in withs { quote_in!(*out => with $name;); out.push(); } out.line(); } fn imports(out: &mut Tokens, tokens: &Tokens) { let mut inherits = BTreeSet::new(); let mut variables = BTreeSet::new(); for imports in tokens.iter_lang() { match imports { Import::Inherit(inherit) => { inherits.insert((&inherit.path, &inherit.name)); } Import::Variable(variable) => { let value = &variable.value; for import in value.iter_lang() { if let Import::Inherit(inherit) = import { inherits.insert((&inherit.path, &inherit.name)); } } variables.insert((&variable.name, &variable.value)); } _ => (), } } if inherits.is_empty() && variables.is_empty() { return; } out.append("let"); out.push(); out.indent(); for (path, name) in inherits { quote_in!(*out => inherit ($path) $name;); out.push(); } for (name, value) in variables { quote_in!(*out => $name = $value;); out.push(); } out.unindent(); out.append("in"); out.push(); out.line(); } } /// ``` /// use genco::prelude::*; /// /// let cell = nix::argument("cell"); /// /// let toks = quote! { /// $cell /// }; /// /// assert_eq!( /// vec![ /// "{", /// " cell,", /// " ...", /// "}:", /// "", /// "cell", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn argument(name: M) -> Import where M: Into, { Import::Argument(ImportArgument(name.into())) } /// ``` /// use genco::prelude::*; /// /// let nixpkgs = nix::inherit("inputs", "nixpkgs"); /// /// let toks = quote! { /// $nixpkgs /// }; /// /// assert_eq!( /// vec![ /// "{", /// " inputs,", /// " ...", /// "}:", /// "", /// "let", /// " inherit (inputs) nixpkgs;", /// "in", /// "", /// "nixpkgs", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn inherit(path: M, name: N) -> Import where M: Into, N: Into, { Import::Inherit(ImportInherit { path: path.into(), name: name.into(), }) } /// ``` /// use genco::prelude::*; /// /// let nixpkgs = &nix::inherit("inputs", "nixpkgs"); /// /// let pkgs = nix::variable("pkgs", quote! { /// import $nixpkgs { /// inherit ($nixpkgs) system; /// config.allowUnfree = true; /// } /// }); /// /// let toks = quote! { /// $pkgs /// }; /// /// assert_eq!( /// vec![ /// "{", /// " inputs,", /// " ...", /// "}:", /// "", /// "let", /// " inherit (inputs) nixpkgs;", /// " pkgs = import nixpkgs {", /// " inherit (nixpkgs) system;", /// " config.allowUnfree = true;", /// " };", /// "in", /// "", /// "pkgs" /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn variable(name: M, value: N) -> Import where M: Into, N: Into, { Import::Variable(ImportVariable { name: name.into(), value: value.into(), }) } /// ``` /// use genco::prelude::*; /// /// let concat_map = nix::with("inputs.nixpkgs.lib", "concatMap"); /// let list_to_attrs = nix::with("inputs.nixpkgs.lib", "listToAttrs"); /// /// let toks = quote! { /// $list_to_attrs $concat_map /// }; /// /// assert_eq!( /// vec![ /// "{", /// " inputs,", /// " ...", /// "}:", /// "", /// "with inputs.nixpkgs.lib;", /// "", /// "listToAttrs concatMap", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn with(argument: M, name: N) -> Import where M: Into, N: Into, { Import::With(ImportWith { argument: argument.into(), name: name.into(), }) } ================================================ FILE: src/lang/python.rs ================================================ //! Specialization for Python code generation. //! //! # Examples //! //! String quoting in Python: //! //! ```rust //! use genco::prelude::*; //! //! let toks: python::Tokens = quote!("hello \n world"); //! assert_eq!("\"hello \\n world\"", toks.to_string()?); //! //! let toks: python::Tokens = quote!($(quoted("hello \n world"))); //! assert_eq!("\"hello \\n world\"", toks.to_string()?); //! # Ok::<_, genco::fmt::Error>(()) //! ``` use core::fmt::Write as _; use alloc::collections::{BTreeMap, BTreeSet}; use alloc::vec::Vec; use crate as genco; use crate::fmt; use crate::tokens::ItemStr; use crate::{quote, quote_in}; /// Tokens container specialization for Python. pub type Tokens = crate::Tokens; impl_lang! { /// Language specialization for Python. pub Python { type Config = Config; type Format = Format; type Item = Any; fn write_quoted(out: &mut fmt::Formatter<'_>, input: &str) -> fmt::Result { // From: https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals super::c_family_write_quoted(out, input) } fn format_file( tokens: &Tokens, out: &mut fmt::Formatter<'_>, config: &Self::Config, ) -> fmt::Result { let mut imports = Tokens::new(); Self::imports(&mut imports, tokens); let format = Format::default(); imports.format(out, config, &format)?; tokens.format(out, config, &format)?; Ok(()) } } Import(Import) { fn format(&self, out: &mut fmt::Formatter<'_>, _: &Config, _: &Format) -> fmt::Result { if let TypeModule::Qualified { module, alias } = &self.module { out.write_str(alias.as_ref().unwrap_or(module))?; out.write_str(SEP)?; } let name = match &self.alias { Some(alias) => alias, None => &self.name, }; out.write_str(name)?; Ok(()) } } ImportModule(ImportModule) { fn format(&self, out: &mut fmt::Formatter<'_>, _: &Config, _: &Format) -> fmt::Result { let module = match &self.alias { Some(alias) => alias, None => &self.module, }; out.write_str(module)?; Ok(()) } } } /// Formatting state for python. #[derive(Debug, Default)] pub struct Format {} /// Configuration for python. #[derive(Debug, Default)] pub struct Config {} static SEP: &str = "."; #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] enum TypeModule { Unqualified { /// Name of imported module. module: ItemStr, }, Qualified { /// Name of imported module. module: ItemStr, /// Alias of imported module. alias: Option, }, } impl TypeModule { fn qualified(self) -> Self { match self { Self::Unqualified { module } => Self::Qualified { module, alias: None, }, other => other, } } fn with_alias(self, alias: T) -> Self where T: Into, { match self { Self::Qualified { module, .. } | Self::Unqualified { module } => Self::Qualified { module, alias: Some(alias.into()), }, } } } /// The import of a Python name `from module import foo`. /// /// Created through the [import()] function. #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub struct Import { /// Module of the imported name. module: TypeModule, /// The name that was imported. name: ItemStr, /// Alias of the name imported. alias: Option, } impl Import { /// Configure the importe name with the specified alias. /// /// This implised that the import is not qualified. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let toks = quote! { /// $(python::import("collections", "namedtuple").with_alias("nt")) /// }; /// /// assert_eq!( /// vec![ /// "from collections import namedtuple as nt", /// "", /// "nt", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn with_alias(self, alias: T) -> Self where T: Into, { Self { alias: Some(alias.into()), ..self } } /// Indicate that the import is qualified (module prefixed). /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let toks = quote! { /// $(python::import("collections", "namedtuple").qualified()) /// }; /// /// assert_eq!( /// vec![ /// "import collections", /// "", /// "collections.namedtuple", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn qualified(self) -> Self { Self { module: self.module.qualified(), ..self } } /// Configure the imported name with the specified alias. /// /// This implies that the import is qualified. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let toks = quote! { /// $(python::import("collections", "namedtuple").with_module_alias("c")) /// }; /// /// assert_eq!( /// vec![ /// "import collections as c", /// "", /// "c.namedtuple", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn with_module_alias(self, module_alias: T) -> Self where T: Into, { Self { module: self.module.with_alias(module_alias), ..self } } } /// The import of a Python module `import module`. /// /// Created through the [import_module()] function. #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub struct ImportModule { /// Module of the imported name. module: ItemStr, /// Alias of module imported. alias: Option, } impl ImportModule { /// Set alias for imported module. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let toks = quote! { /// $(python::import_module("collections").with_alias("c")) /// }; /// /// assert_eq!( /// vec![ /// "import collections as c", /// "", /// "c", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn with_alias(self, new_alias: N) -> Self where N: Into, { Self { alias: Some(new_alias.into()), ..self } } } impl Python { fn imports(out: &mut Tokens, tokens: &Tokens) { let mut imported_from = BTreeMap::new(); let mut imports = BTreeSet::new(); for import in tokens.iter_lang() { match import.kind() { AnyKind::Import(Import { module, alias, name, }) => match module { TypeModule::Qualified { module, alias } => { imports.insert((module, alias)); } TypeModule::Unqualified { module } => { imported_from .entry(module) .or_insert_with(BTreeSet::new) .insert((name, alias)); } }, AnyKind::ImportModule(ImportModule { module, alias }) => { imports.insert((module, alias)); } } } if imported_from.is_empty() && imports.is_empty() { return; } for (module, imports) in imported_from { out.push(); let imports = imports .into_iter() .map(|(name, alias)| quote!($name$(if let Some(a) = alias => $[' ']as $a))) .collect::>(); if imports.len() == 1 { quote_in! {*out => from $module import $(imports.into_iter().next()) } } else { quote_in! {*out => from $module import $(for i in imports join (, ) => $i) } } } for (module, alias) in imports { out.push(); quote_in! {*out => import $module$(if let Some(a) = alias => $[' ']as $a) } } out.line(); } } /// The import of a Python name `from module import foo`. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let toks = quote! { /// $(python::import("collections", "namedtuple").with_alias("nt")) /// $(python::import("collections", "namedtuple")) /// $(python::import("collections", "namedtuple").qualified()) /// $(python::import("collections", "namedtuple").with_module_alias("c")) /// }; /// /// assert_eq!( /// vec![ /// "from collections import namedtuple, namedtuple as nt", /// "import collections", /// "import collections as c", /// "", /// "nt", /// "namedtuple", /// "collections.namedtuple", /// "c.namedtuple", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn import(module: M, name: N) -> Import where M: Into, N: Into, { Import { module: TypeModule::Unqualified { module: module.into(), }, name: name.into(), alias: None, } } /// The import of a Python module `import module`. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let toks = quote! { /// $(python::import_module("collections")) /// $(python::import_module("collections").with_alias("c")) /// }; /// /// assert_eq!( /// vec![ /// "import collections", /// "import collections as c", /// "", /// "collections", /// "c", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn import_module(module: M) -> ImportModule where M: Into, { ImportModule { module: module.into(), alias: None, } } ================================================ FILE: src/lang/rust.rs ================================================ //! Specialization for Rust code generation. //! //! # Examples //! //! ```rust //! use genco::prelude::*; //! //! let toks: rust::Tokens = quote! { //! fn foo() -> u32 { //! 42 //! } //! }; //! //! assert_eq!( //! vec![ //! "fn foo() -> u32 {", //! " 42", //! "}", //! ], //! toks.to_file_vec()? //! ); //! # Ok::<_, genco::fmt::Error>(()) //! ``` //! //! # String Quoting in Rust //! //! Rust uses UTF-8 internally, string quoting is with the exception of escape //! sequences a one-to-one translation. //! //! ```rust //! use genco::prelude::*; //! //! let toks: rust::Tokens = quote!("start π 😊 \n \x7f ÿ $ end"); //! assert_eq!("\"start π 😊 \\n \\x7f ÿ $ end\"", toks.to_string()?); //! # Ok::<_, genco::fmt::Error>(()) //! ``` use core::fmt::Write as _; use alloc::collections::{BTreeMap, BTreeSet, VecDeque}; use crate::fmt; use crate::tokens::ItemStr; const SEP: &str = "::"; /// Tokens container specialization for Rust. pub type Tokens = crate::Tokens; impl_lang! { /// Language specialization for Rust. pub Rust { type Config = Config; type Format = Format; type Item = Import; fn write_quoted(out: &mut fmt::Formatter<'_>, input: &str) -> fmt::Result { // From: https://doc.rust-lang.org/reference/tokens.html#literals for c in input.chars() { match c { // new line '\n' => out.write_str("\\n")?, // carriage return '\r' => out.write_str("\\r")?, // horizontal tab '\t' => out.write_str("\\t")?, // backslash '\\' => out.write_str("\\\\")?, // null '\0' => out.write_str("\\0")?, // Note: only relevant if we were to use single-quoted strings. // '\'' => out.write_str("\\'")?, '"' => out.write_str("\\\"")?, c if !c.is_control() => out.write_char(c)?, c if (c as u32) < 0x80 => { write!(out, "\\x{:02x}", c as u32)?; } c => { write!(out, "\\u{{{:04x}}}", c as u32)?; } }; } Ok(()) } fn format_file( tokens: &Tokens, out: &mut fmt::Formatter<'_>, config: &Self::Config, ) -> fmt::Result { let mut imports: Tokens = Tokens::new(); Self::imports(&mut imports, config, tokens); let format = Format::default(); imports.format(out, config, &format)?; tokens.format(out, config, &format)?; Ok(()) } } Import(Import) { fn format(&self, out: &mut fmt::Formatter<'_>, config: &Config, _: &Format) -> fmt::Result { match &self.module { Module::Module { import: Some(ImportMode::Direct), .. } => { self.write_direct(out)?; } Module::Module { import: Some(ImportMode::Qualified), module, } => { self.write_prefixed(out, module)?; } Module::Module { import: None, module, } => match &config.default_import { ImportMode::Direct => self.write_direct(out)?, ImportMode::Qualified => self.write_prefixed(out, module)?, }, Module::Aliased { alias: ref module, .. } => { out.write_str(module)?; out.write_str(SEP)?; out.write_str(&self.name)?; } } Ok(()) } } } /// Format state for Rust. #[derive(Debug, Default)] pub struct Format {} /// Language configuration for Rust. #[derive(Debug)] pub struct Config { default_import: ImportMode, } impl Config { /// Configure the default import mode to use. /// /// See [Import] for more details. pub fn with_default_import(self, default_import: ImportMode) -> Self { Self { default_import } } } impl Default for Config { fn default() -> Self { Config { default_import: ImportMode::Direct, } } } /// The import mode to use when generating import statements. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ImportMode { /// Import names without a module prefix. /// /// so for `std::fmt::Debug` it would import `std::fmt::Debug`, and use /// `Debug`. Direct, /// Import qualified names with a module prefix. /// /// so for `std::fmt::Debug` it would import `std::fmt`, and use /// `fmt::Debug`. Qualified, } #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] enum Module { /// Type imported directly from module with the specified mode. Module { import: Option, module: ItemStr, }, /// Prefixed with an alias. Aliased { module: ItemStr, alias: ItemStr }, } impl Module { /// Convert into an aliased import, or keep as same in case that's not /// feasible. fn into_module_aliased(self, alias: A) -> Self where A: Into, { match self { Self::Module { module, .. } => Self::Aliased { module, alias: alias.into(), }, other => other, } } /// Aliasing a type explicitly means you no longer want to import it by /// module. Set the correct import here. fn into_aliased(self) -> Self { match self { Self::Module { module, .. } => Self::Module { import: Some(ImportMode::Direct), module, }, other => other, } } /// Switch to a direct import mode. /// /// See [ImportMode::Direct]. fn direct(self) -> Self { match self { Self::Module { module, .. } => Self::Module { module, import: Some(ImportMode::Direct), }, other => other, } } /// Switch into a qualified import mode. /// /// See [ImportMode::Qualified]. fn qualified(self) -> Self { match self { Self::Module { module, .. } => Self::Module { module, import: Some(ImportMode::Qualified), }, other => other, } } } /// The import of a Rust type `use std::collections::HashMap`. /// /// Created through the [import()] function. #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub struct Import { /// How the type is imported. module: Module, /// Name of type. name: ItemStr, /// Alias to use for the type. alias: Option, } impl Import { /// Alias the given type as it's imported. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let ty = rust::import("std::fmt", "Debug").with_alias("FmtDebug"); /// /// let toks = quote!($ty); /// /// assert_eq!( /// vec![ /// "use std::fmt::Debug as FmtDebug;", /// "", /// "FmtDebug", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn with_alias>(self, alias: A) -> Self { Self { module: self.module.into_aliased(), alias: Some(alias.into()), ..self } } /// Alias the module being imported. /// /// This also implies that the import is [qualified()]. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let ty = rust::import("std::fmt", "Debug").with_module_alias("other"); /// /// let toks = quote!($ty); /// /// assert_eq!( /// vec![ /// "use std::fmt as other;", /// "", /// "other::Debug", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// /// [qualified()]: Self::qualified() pub fn with_module_alias>(self, alias: A) -> Self { Self { module: self.module.into_module_aliased(alias), ..self } } /// Switch to a qualified import mode. /// /// See [ImportMode::Qualified]. /// /// So importing `std::fmt::Debug` will cause the module to be referenced as /// `fmt::Debug` instead of `Debug`. /// /// This is implied if [with_module_alias()][Self::with_module_alias()] is used. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let ty = rust::import("std::fmt", "Debug").qualified(); /// /// let toks = quote!($ty); /// /// assert_eq!( /// vec![ /// "use std::fmt;", /// "", /// "fmt::Debug", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn qualified(self) -> Self { Self { module: self.module.qualified(), ..self } } /// Switch into a direct import mode. /// /// See [ImportMode::Direct]. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let ty = rust::import("std::fmt", "Debug").direct(); /// /// let toks = quote!($ty); /// /// assert_eq!( /// vec![ /// "use std::fmt::Debug;", /// "", /// "Debug", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn direct(self) -> Self { Self { module: self.module.direct(), ..self } } /// Write the direct name of the type. fn write_direct(&self, out: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(alias) = &self.alias { out.write_str(alias) } else { out.write_str(&self.name) } } /// Write the prefixed name of the type. fn write_prefixed(&self, out: &mut fmt::Formatter<'_>, module: &ItemStr) -> fmt::Result { if let Some(module) = module.rsplit(SEP).next() { out.write_str(module)?; out.write_str(SEP)?; } out.write_str(&self.name)?; Ok(()) } } impl Rust { fn imports(out: &mut Tokens, config: &Config, tokens: &Tokens) { use alloc::collections::btree_set; use crate as genco; use crate::quote_in; let mut modules = BTreeMap::<&ItemStr, Import>::new(); let mut queue = VecDeque::new(); for import in tokens.iter_lang() { queue.push_back(import); } while let Some(import) = queue.pop_front() { match &import.module { Module::Module { module, import: Some(ImportMode::Direct), } => { let module = modules.entry(module).or_default(); module.names.insert((&import.name, import.alias.as_ref())); } Module::Module { module, import: Some(ImportMode::Qualified), } => { let module = modules.entry(module).or_default(); module.self_import = true; } Module::Module { module, import: None, } => match config.default_import { ImportMode::Direct => { let module = modules.entry(module).or_default(); module.names.insert((&import.name, import.alias.as_ref())); } ImportMode::Qualified => { let module = modules.entry(module).or_default(); module.self_import = true; } }, Module::Aliased { module, alias } => { let module = modules.entry(module).or_default(); module.self_aliases.insert(alias); } } } let mut has_any = false; for (m, module) in modules { let mut render = module.iter(m); if let Some(first) = render.next() { has_any = true; out.push(); // render as a group if there's more than one thing being // imported. if let Some(second) = render.next() { quote_in! { *out => use $m::{$(ref o => first.render(o); quote_in!(*o => , $(ref o => second.render(o))); for item in render { quote_in!(*o => , $(ref o => item.render(o))); } )}; }; } else { match first { RenderItem::SelfImport => { quote_in!(*out => use $m;); } RenderItem::SelfAlias { alias } => { quote_in!(*out => use $m as $alias;); } RenderItem::Name { name, alias: Some(alias), } => { quote_in!(*out => use $m::$name as $alias;); } RenderItem::Name { name, alias: None } => { quote_in!(*out => use $m::$name;); } } } } } if has_any { out.line(); } return; /// An imported module. #[derive(Debug, Default)] struct Import<'a> { /// If we need the module (e.g. through an alias). self_import: bool, /// Aliases for the own module. self_aliases: BTreeSet<&'a ItemStr>, /// Set of imported names. names: BTreeSet<(&'a ItemStr, Option<&'a ItemStr>)>, } impl<'a> Import<'a> { fn iter(self, module: &'a str) -> ImportedIter<'a> { ImportedIter { module, self_import: self.self_import, self_aliases: self.self_aliases.into_iter(), names: self.names.into_iter(), } } } struct ImportedIter<'a> { module: &'a str, self_import: bool, self_aliases: btree_set::IntoIter<&'a ItemStr>, names: btree_set::IntoIter<(&'a ItemStr, Option<&'a ItemStr>)>, } impl<'a> Iterator for ImportedIter<'a> { type Item = RenderItem<'a>; fn next(&mut self) -> Option { if core::mem::take(&mut self.self_import) { // Only render self-import if it's not a top level module. if self.module.split(SEP).count() > 1 { return Some(RenderItem::SelfImport); } } if let Some(alias) = self.self_aliases.next() { return Some(RenderItem::SelfAlias { alias }); } if let Some((name, alias)) = self.names.next() { return Some(RenderItem::Name { name, alias }); } None } } #[derive(Clone, Copy)] enum RenderItem<'a> { SelfImport, SelfAlias { alias: &'a ItemStr, }, Name { name: &'a ItemStr, alias: Option<&'a ItemStr>, }, } impl RenderItem<'_> { fn render(self, out: &mut Tokens) { match self { Self::SelfImport => { quote_in!(*out => self); } Self::SelfAlias { alias } => { quote_in!(*out => self as $alias); } Self::Name { name, alias: Some(alias), } => { quote_in!(*out => $name as $alias); } Self::Name { name, alias: None } => { quote_in!(*out => $name); } } } } } } /// The import of a Rust type `use std::collections::HashMap`. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let a = rust::import("std::fmt", "Debug").qualified(); /// let b = rust::import("std::fmt", "Debug").with_module_alias("fmt2"); /// let c = rust::import("std::fmt", "Debug"); /// let d = rust::import("std::fmt", "Debug").with_alias("FmtDebug"); /// /// let toks = quote!{ /// $a /// $b /// $c /// $d /// }; /// /// assert_eq!( /// vec![ /// "use std::fmt::{self, self as fmt2, Debug, Debug as FmtDebug};", /// "", /// "fmt::Debug", /// "fmt2::Debug", /// "Debug", /// "FmtDebug", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// /// # Example with an alias /// /// ``` /// use genco::prelude::*; /// /// let ty = rust::import("std::fmt", "Debug").with_alias("FmtDebug"); /// /// let toks = quote!{ /// $ty /// }; /// /// assert_eq!( /// vec![ /// "use std::fmt::Debug as FmtDebug;", /// "", /// "FmtDebug", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// /// # Example with a module alias /// /// ``` /// use genco::prelude::*; /// /// let ty = rust::import("std::fmt", "Debug").with_module_alias("fmt2"); /// /// let toks = quote!{ /// $ty /// }; /// /// assert_eq!( /// vec![ /// "use std::fmt as fmt2;", /// "", /// "fmt2::Debug", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// /// # Example with multiple aliases /// /// ``` /// use genco::prelude::*; /// /// let a = rust::import("std::fmt", "Debug").with_alias("FmtDebug"); /// let b = rust::import("std::fmt", "Debug").with_alias("FmtDebug2"); /// /// let toks = quote!{ /// $a /// $b /// }; /// /// assert_eq!( /// vec![ /// "use std::fmt::{Debug as FmtDebug, Debug as FmtDebug2};", /// "", /// "FmtDebug", /// "FmtDebug2", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn import(module: M, name: N) -> Import where M: Into, N: Into, { Import { module: Module::Module { import: None, module: module.into(), }, name: name.into(), alias: None, } } ================================================ FILE: src/lang/swift.rs ================================================ //! Specialization for Swift code generation. //! //! # String Quoting in Swift //! //! Swift uses UTF-8 internally, string quoting is with the exception of escape //! sequences a one-to-one translation. //! //! ```rust //! use genco::prelude::*; //! //! let toks: swift::Tokens = quote!("start π 😊 \n \x7f ÿ $ end"); //! assert_eq!("\"start π 😊 \\n \\u{7f} ÿ $ end\"", toks.to_string()?); //! # Ok::<_, genco::fmt::Error>(()) //! ``` use core::fmt::Write as _; use alloc::collections::BTreeSet; use crate::fmt; use crate::tokens::ItemStr; /// Tokens container specialization for Rust. pub type Tokens = crate::Tokens; impl_lang! { /// Swift token specialization. pub Swift { type Config = Config; type Format = Format; type Item = Any; fn write_quoted(out: &mut fmt::Formatter<'_>, input: &str) -> fmt::Result { // From: https://docs.swift.org/swift-book/LanguageGuide/StringsAndCharacters.html for c in input.chars() { match c { '\0' => out.write_str("\\0")?, '\\' => out.write_str("\\\\")?, '\t' => out.write_str("\\t")?, '\n' => out.write_str("\\n")?, '\r' => out.write_str("\\r")?, '\'' => out.write_str("\\'")?, '"' => out.write_str("\\\"")?, c if !c.is_control() => out.write_char(c)?, c => { write!(out, "\\u{{{:x}}}", c as u32)?; } }; } Ok(()) } fn format_file( tokens: &Tokens, out: &mut fmt::Formatter<'_>, config: &Self::Config, ) -> fmt::Result { let mut imports = Tokens::new(); Self::imports(&mut imports, tokens); let format = Format::default(); imports.format(out, config, &format)?; tokens.format(out, config, &format)?; Ok(()) } } Import(Import) { fn format(&self, out: &mut fmt::Formatter<'_>, _: &Config, _: &Format) -> fmt::Result { out.write_str(&self.name) } } ImportImplementationOnly(ImportImplementationOnly) { fn format(&self, out: &mut fmt::Formatter<'_>, _: &Config, _: &Format) -> fmt::Result { out.write_str(&self.name) } } } /// Format state for Swift code. #[derive(Debug, Default)] pub struct Format {} /// Configuration for formatting Swift code. #[derive(Debug, Default)] pub struct Config {} /// The import of a Swift type `import UIKit`. /// /// Created through the [import()] function. #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub struct Import { /// Module of the imported name. module: ItemStr, /// Name imported. name: ItemStr, } /// The implementation-only import of a Swift type `@_implementationOnly import UIKit`. /// /// Created through the [import_implementation_only()] function. #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] pub struct ImportImplementationOnly { /// Module of the imported name. module: ItemStr, /// Name imported. name: ItemStr, } /// The type of import statement to use when importing a Swift module. /// - Standard imports that make the module's public API available /// - Implementation-only imports that hide the imported module from clients #[derive(Debug, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] enum ImportType { /// A standard Swift import statement: `import ModuleName` Import, /// An implementation-only import statement: `@_implementationOnly import ModuleName` /// /// This type of import hides the imported module from the public API, /// preventing clients from depending on it transitively. ImportImplementationOnly, } impl Swift { fn imports(out: &mut Tokens, tokens: &Tokens) { use crate as genco; use crate::quote_in; let mut modules = BTreeSet::new(); for import in tokens.iter_lang() { match import.kind() { AnyKind::Import(ref i) => { modules.insert((&i.module, ImportType::Import)); } AnyKind::ImportImplementationOnly(ref i) => { modules.insert((&i.module, ImportType::ImportImplementationOnly)); } } } if !modules.is_empty() { for (module, import_type) in modules { match import_type { ImportType::Import => { quote_in! { *out => $['\r']import $module} } ImportType::ImportImplementationOnly => { quote_in! { *out => $['\r']@_implementationOnly import $module} } } } } out.line(); } } /// The import of a Swift type `import UIKit`. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let toks = quote!($(swift::import("Foo", "Debug"))); /// /// assert_eq!( /// vec![ /// "import Foo", /// "", /// "Debug", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn import(module: M, name: N) -> Import where M: Into, N: Into, { Import { module: module.into(), name: name.into(), } } /// The implementation-only import of a Swift type `@_implementationOnly import UIKit`. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let toks = quote!($(swift::import_implementation_only("Foo", "Debug"))); /// /// assert_eq!( /// vec![ /// "@_implementationOnly import Foo", /// "", /// "Debug", /// ], /// toks.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn import_implementation_only( module: impl Into, name: impl Into, ) -> ImportImplementationOnly { ImportImplementationOnly { module: module.into(), name: name.into(), } } ================================================ FILE: src/lib.rs ================================================ //! [github](https://github.com/udoprog/genco) //! [crates.io](https://crates.io/crates/genco) //! [docs.rs](https://docs.rs/genco) //! //! A whitespace-aware quasiquoter for beautiful code generation. //! //! Central to genco are the [`quote!`] and [`quote_in!`] procedural macros which //! ease the construction of [token streams]. //! //! This project solves the following language-specific concerns: //! //! * **Imports** — Generates and groups [import statements] as they are used. //! So you only import what you use, with no redundancy. We also do our best //! to [solve namespace conflicts]. //! //! * **String Quoting** — genco knows how to [quote strings]. And can even //! [interpolate] values *into* the quoted string if it's supported by the //! language. //! //! * **Structural Indentation** — The quoter relies on intuitive //! [whitespace detection] to structurally sort out spacings and indentation. //! Allowing genco to generate beautiful readable code with minimal effort. //! This is also a requirement for generating correctly behaving code in //! languages like Python where [indentation is meaningful]. //! //! * **Language Customization** — Building support for new languages is a //! piece of cake with the help of the [impl_lang!] macro. //! //!
//! //! To support line changes during [whitespace detection], we depend on span //! information which was made available in Rust `1.88`. Before that, we rely on //! a nightly [`proc_macro_span` feature] to work. //! //! *Prior to this version of Rust* if you want fully functional whitespace //! detection you must build and run projects using genco with a `nightly` //! compiler. This is important for whitespace-sensitive languages like python. //! //! You can try the difference between: //! //! ```bash //! cargo run --example rust //! ``` //! //! And: //! //! ```bash //! cargo +nightly run --example rust //! ``` //! //! [`proc_macro_span` feature]: https://github.com/rust-lang/rust/issues/54725 //! //!
//! //! ## Supported Languages //! //! The following are languages which have built-in support in genco. //! //! * [🦀 Rust][rust]
//! [Example][rust-example] //! //! * [☕ Java][java]
//! [Example][java-example] //! //! * [🎼 C#][c#]
//! [Example][c#-example] //! //! * [🐿️ Go][go]
//! [Example][go-example] //! //! * [🎯 Dart][dart]
//! [Example][dart-example] //! //! * [🌐 JavaScript][js]
//! [Example][js-example] //! //! * [🇨 C][c]
//! [Example][c-example] //! //! * [🐍 Python][python]
//! [Example][python-example] //! //! Is your favorite language missing? [Open an issue!] //! //! You can run one of the examples by: //! //! ```bash //! cargo +nightly run --example rust //! ``` //! //!
//! //! ## Rust Example //! //! The following is a simple program producing Rust code to stdout with custom //! configuration: //! //! ```rust,no_run //! use genco::prelude::*; //! //! let hash_map = rust::import("std::collections", "HashMap"); //! //! let tokens: rust::Tokens = quote! { //! fn main() { //! let mut m = $hash_map::new(); //! m.insert(1u32, 2u32); //! } //! }; //! //! println!("{}", tokens.to_file_string()?); //! # Ok::<_, genco::fmt::Error>(()) //! ``` //! //! This would produce: //! //! ```rust,no_test //! use std::collections::HashMap; //! //! fn main() { //! let mut m = HashMap::new(); //! m.insert(1u32, 2u32); //! } //! ``` //! //!
//! //! [`quote_in!`]: //! [`quote!`]: //! [`quoted()`]: //! [c-example]: //! [c]: //! [c#-example]: //! [c#]: //! [dart-example]: //! [dart]: //! [go-example]: //! [go]: //! [impl_lang!]: //! [import statements]: //! [indentation is meaningful]: //! [interpolate]: //! [java-example]: //! [java]: //! [js-example]: //! [js]: //! [Open an issue!]: //! [python-example]: //! [python]: //! [quote strings]: //! [rust-example]: //! [rust]: //! [solve namespace conflicts]: //! [token streams]: //! [whitespace detection]: #![deny(missing_docs)] #![deny(rustdoc::broken_intra_doc_links)] #![allow(clippy::needless_doctest_main)] #![no_std] #[cfg(feature = "std")] extern crate std; #[cfg(feature = "alloc")] extern crate alloc; #[cfg(not(feature = "alloc"))] compile_error!("genco: The `alloc` feature must be enabled"); /// Whitespace sensitive quasi-quoting. /// /// This and the [`quote_in!`] macro is the thing that this library revolves /// around. /// /// It provides a flexible and intuitive mechanism for efficiently generating /// beautiful code directly inside of Rust. /// /// > Note that this macro **can only detect line changes** if it's built with /// > Rust 1.88 or by using a `nightly` compiler. See the [main genco /// > documentation] for more information. /// /// ``` /// use genco::prelude::*; /// /// let hash_map = &dart::import("dart:collection", "HashMap"); /// /// let tokens: dart::Tokens = quote! { /// print_greeting(String name) { /// print($[str](Hello $(name))); /// } /// /// $hash_map map() { /// return new $hash_map(); /// } /// }; /// /// println!("{}", tokens.to_file_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// /// # Interpolation /// /// Variables are interpolated using `$`, so to include the variable `test`, you /// would write `$test`. Interpolated variables must implement [`FormatInto`]. /// Expressions can be interpolated with `$()`. /// /// > *Note:* The `$` punctuation itself can be escaped by repeating it twice. /// > So `$$` would produce a single `$` token. /// /// ``` /// use genco::prelude::*; /// /// let hash_map = rust::import("std::collections", "HashMap"); /// /// let tokens: rust::Tokens = quote! { /// struct Quoted { /// field: $hash_map, /// } /// }; /// /// assert_eq!( /// vec![ /// "use std::collections::HashMap;", /// "", /// "struct Quoted {", /// " field: HashMap,", /// "}", /// ], /// tokens.to_file_vec()?, /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// ///
/// /// The following is an expression interpolated with `$()`. /// /// ``` /// use genco::prelude::*; /// /// let tokens: genco::Tokens = quote! { /// hello $("world".to_uppercase()) /// }; /// /// assert_eq!("hello WORLD", tokens.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// ///
/// /// Interpolations are evaluated in the same scope as the macro, so you can /// freely make use of Rust operations like the try keyword (`?`) if /// appropriate: /// /// ``` /// use std::error::Error; /// /// use genco::prelude::*; /// /// fn age_fn(age: &str) -> Result> { /// Ok(quote! { /// fn age() { /// println!("You are {} years old!", $(str::parse::(age)?)); /// } /// }) /// } /// ``` /// /// [`FormatInto`]: crate::tokens::FormatInto /// [main genco documentation]: https://docs.rs/genco /// ///
/// /// # Escape Sequences /// /// Because this macro is *whitespace sensitive*, it might sometimes be /// necessary to provide hints of where whitespace should be inserted. /// /// `quote!` trims any trailing and leading whitespace that it sees. So /// `quote!(Hello )` is the same as `quote!(Hello)`. To include a space at the /// end, we can use the special `$[' ']` escape sequence: /// `quote!(Hello$[' '])`. /// /// The available escape sequences are: /// /// * `$[' ']` — Inserts spacing between tokens. This corresponds to the /// [Tokens::space] function. /// /// * `$['\r']` — Inserts a push operation. Push operations makes sure that /// any following tokens are on their own dedicated line. This corresponds to /// the [Tokens::push] function. /// /// * `$['\n']` — Inserts a forced line. Line operations makes sure that any /// following tokens have an empty line separating them. This corresponds to /// the [Tokens::line] function. /// /// ``` /// use genco::prelude::*; /// /// let numbers = 3..=5; /// /// let tokens: Tokens<()> = quote!(foo$['\r']bar$['\n']baz$[' ']biz); /// /// assert_eq!("foo\nbar\n\nbaz biz", tokens.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// ///
/// /// # String Quoting /// /// Literal strings like `"hello"` are automatically quoted for the target /// language according to its [Lang::write_quoted] implementation. /// /// [Lang::write_quoted]: crate::lang::Lang::write_quoted /// /// ``` /// use genco::prelude::*; /// /// let tokens: java::Tokens = quote! { /// "hello world 😊" /// $(quoted("hello world 😊")) /// $("\"hello world 😊\"") /// $[str](hello world $[const]("😊")) /// }; /// /// assert_eq!( /// vec![ /// "\"hello world \\ud83d\\ude0a\"", /// "\"hello world \\ud83d\\ude0a\"", /// "\"hello world 😊\"", /// "\"hello world \\ud83d\\ude0a\"", /// ], /// tokens.to_file_vec()?, /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// /// # Efficient String Quoting /// /// It's worth investigating the different forms of tokens produced by the /// above example. /// /// * The first one is a static *quoted string*. /// * The second one is a boxed *quoted string*, who's content will be copied /// and is stored on the heap. /// * The third one is a static *literal* which bypasses language quoting /// entirely. /// * Finally the fourth one is an interpolated string. They are really neat, /// and will be covered more in the next section. It's worth noting that /// `$("😊")` is used, because 😊 is not a valid identifier in Rust. So this /// example showcases how strings can be directly embedded in an /// interpolation. /// /// Here you can see the items produced by the macro. /// /// ``` /// # use genco::prelude::*; /// # let tokens: rust::Tokens = quote! { /// # "hello world 😊" /// # $(quoted("hello world 😊")) /// # $("\"hello world 😊\"") /// # $[str](hello world $[const]("😊")) /// # }; /// use genco::tokens::{Item, ItemStr}; /// /// assert_eq!( /// vec![ /// Item::open_quote(false), /// Item::literal(ItemStr::static_("hello world 😊")), /// Item::close_quote(), /// Item::push(), /// Item::open_quote(false), /// Item::literal("hello world 😊".into()), /// Item::close_quote(), /// Item::push(), /// Item::literal(ItemStr::static_("\"hello world 😊\"")), /// Item::push(), /// Item::open_quote(false), /// Item::literal(ItemStr::static_("hello world 😊")), /// Item::close_quote() /// ], /// tokens, /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// ///
/// /// # Quoted String Interpolation /// /// Some languages support interpolating values into strings. /// /// Examples of these are: /// /// * JavaScript - With [template literals] `` `Hello ${a}` `` (note the /// backticks). /// * Dart - With [interpolated strings] like `"Hello $a"` or `"Hello ${a + /// b}"`. /// /// The [`quote!`] macro supports this through `$[str]()`. This will /// produce literal strings with the appropriate language-specific quoting and /// string interpolation formats used. /// /// Components of the string are runtime evaluated with the typical variable /// escape sequences `$ident`, `$()`. In order to interpolate the string /// at compile time we can instead make use of `$[const]()` like you can see with the smile below: /// /// ``` /// use genco::prelude::*; /// /// let smile = "😊"; /// /// let t: js::Tokens = quote!($[str](Hello $[const](smile) $world)); /// assert_eq!("`Hello 😊 ${world}`", t.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// /// Interpolated values are specified with `$()`. And `$` itself is /// escaped by repeating it twice through `$$`. The `` section is /// interpreted the same as in the [`quote!`] macro, but is whitespace sensitive. /// This means that `$(foo)` is not the same as `$(foo )` since the latter will /// have a space preserved at the end. /// /// ``` /// use genco::prelude::*; /// /// let smile = "😊"; /// /// let t: dart::Tokens = quote!($[str](Hello $[const](smile) $(world))); /// assert_eq!("\"Hello 😊 $world\"", t.to_string()?); /// /// let t: dart::Tokens = quote!($[str](Hello $[const](smile) $(a + b))); /// assert_eq!("\"Hello 😊 ${a + b}\"", t.to_string()?); /// /// let t: js::Tokens = quote!($[str](Hello $[const](smile) $(world))); /// assert_eq!("`Hello 😊 ${world}`", t.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// ///
/// /// [template literals]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals /// [interpolated strings]: https://medium.com/run-dart/dart-dartlang-introduction-string-interpolation-8ed99174119a /// /// # Control Flow /// /// [`quote!`] provides some limited mechanisms for control flow inside of the /// macro for convenience. The supported mechanisms are: /// /// * [Loops](#loops) - `$(for in [join ()] => )`. /// * [Conditionals](#conditionals) - `$(if => )`. /// * [Match Statements](#match-statements) - `$(match { [ => ,]* })`. /// ///
/// /// # Loops /// /// To repeat a pattern you can use `$(for in { })`, /// where `` is an iterator. /// /// It is also possible to use the more compact `$(for in => /// )` (note the arrow). /// /// `` will be treated as a quoted expression, so anything which works /// during regular quoting will work here as well, with the addition that /// anything defined in `` will be made available to the statement. /// /// ``` /// use genco::prelude::*; /// /// let numbers = 3..=5; /// /// let tokens: Tokens<()> = quote! { /// Your numbers are: $(for n in numbers => $n$[' ']) /// }; /// /// assert_eq!("Your numbers are: 3 4 5", tokens.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// ///
/// /// # Joining Loops /// /// You can add `join ()` to the end of a repetition. /// /// The expression specified in `join ()` is added _between_ each /// element produced by the loop. /// /// > *Note:* The argument to `join` is *whitespace sensitive*, so leading and /// > trailing is preserved. `join (,)` and `join (, )` would therefore produce /// > different results. /// /// ``` /// use genco::prelude::*; /// /// let numbers = 3..=5; /// /// let tokens: Tokens<()> = quote! { /// Your numbers are: $(for n in numbers join (, ) => $n). /// }; /// /// assert_eq!("Your numbers are: 3, 4, 5.", tokens.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// ///
/// /// # Conditionals /// /// You can specify a conditional with `$(if => )` where /// `` is an pattern or expression evaluating to a `bool`, and `` /// is a quoted expressions. /// /// It's also possible to specify a condition with an else branch, by using /// `$(if { } else { })`. `` is also a quoted /// expression. /// /// ``` /// use genco::prelude::*; /// /// fn greeting(hello: bool, name: &str) -> Tokens<()> { /// quote!(Custom Greeting: $(if hello { /// Hello $name /// } else { /// Goodbye $name /// })) /// } /// /// let tokens = greeting(true, "John"); /// assert_eq!("Custom Greeting: Hello John", tokens.to_string()?); /// /// let tokens = greeting(false, "John"); /// assert_eq!("Custom Greeting: Goodbye John", tokens.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// ///
/// /// The `` branch is optional, conditionals which do not have an else /// branch and evaluated to `false` won't produce any tokens: /// /// ``` /// use genco::prelude::*; /// /// fn greeting(hello: bool, name: &str) -> Tokens<()> { /// quote!(Custom Greeting:$(if hello { /// $[' ']Hello $name /// })) /// } /// /// let tokens = greeting(true, "John"); /// assert_eq!("Custom Greeting: Hello John", tokens.to_string()?); /// /// let tokens = greeting(false, "John"); /// assert_eq!("Custom Greeting:", tokens.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// ///
/// /// # Match Statements /// /// You can specify a match expression using `$(match { [ => /// ,]* }`, where `` is an evaluated expression that is match /// against each subsequent ``. If a pattern matches, the arm with the /// matching `` block is evaluated. /// /// ``` /// use genco::prelude::*; /// /// fn greeting(name: &str) -> Tokens<()> { /// quote!(Hello $(match name { /// "John" | "Jane" => $("Random Stranger"), /// other => $other, /// })) /// } /// /// let tokens = greeting("John"); /// assert_eq!("Hello Random Stranger", tokens.to_string()?); /// /// let tokens = greeting("Mio"); /// assert_eq!("Hello Mio", tokens.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// /// If a match arm contains parenthesis (`=> ()`), the expansion will be /// *whitespace sensitive*. Allowing leading and trailing whitespace to be /// preserved: /// /// ``` /// use genco::prelude::*; /// /// fn greeting(name: &str) -> Tokens<()> { /// quote!(Hello$(match name { /// "John" | "Jane" => ( $("Random Stranger")), /// other => ( $other), /// })) /// } /// /// let tokens = greeting("John"); /// assert_eq!("Hello Random Stranger", tokens.to_string()?); /// /// let tokens = greeting("Mio"); /// assert_eq!("Hello Mio", tokens.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// /// The following is an example with more complex matching: /// /// ``` /// use genco::prelude::*; /// /// enum Greeting { /// Named(&'static str), /// Unknown, /// } /// /// fn greeting(name: Greeting) -> Tokens<()> { /// quote!(Hello $(match name { /// Greeting::Named("John") | Greeting::Named("Jane") => $("Random Stranger"), /// Greeting::Named(other) => $other, /// Greeting::Unknown => $("Unknown Person"), /// })) /// } /// /// let tokens = greeting(Greeting::Named("John")); /// assert_eq!("Hello Random Stranger", tokens.to_string()?); /// /// let tokens = greeting(Greeting::Unknown); /// assert_eq!("Hello Unknown Person", tokens.to_string()?); /// /// let tokens = greeting(Greeting::Named("Mio")); /// assert_eq!("Hello Mio", tokens.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// ///
/// /// # Variable assignment /// /// You can use `$(let = )` to define variables with their value. /// This is useful within loops to compute values from iterator items. /// /// ``` /// use genco::prelude::*; /// /// let names = ["A.B", "C.D"]; /// /// let tokens: Tokens<()> = quote! { /// $(for name in names => /// $(let (first, second) = name.split_once('.').unwrap()) /// $first and $second. /// ) /// }; /// assert_eq!("A and B.\nC and D.", tokens.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// /// Variables can also be mutable: /// /// ``` /// use genco::prelude::*; /// let path = "A.B.C.D"; /// /// let tokens: Tokens<()> = quote! { /// $(let mut items = path.split('.')) /// $(if let Some(first) = items.next() => /// First is $first /// ) /// $(if let Some(second) = items.next() => /// Second is $second /// ) /// }; /// /// assert_eq!("First is A\nSecond is B", tokens.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// ///
/// /// # Scopes /// /// You can use `$(ref { })` to gain access to the current /// token stream. This is an alternative to existing control flow operators if /// you want to run some custom code during evaluation which is otherwise not /// supported. This is called a *scope*. /// /// For a more compact variant you can omit the braces with `$(ref => /// )`. /// /// ``` /// use genco::prelude::*; /// /// fn quote_greeting(surname: &str, lastname: Option<&str>) -> rust::Tokens { /// quote! { /// Hello $surname$(ref toks { /// if let Some(lastname) = lastname { /// toks.space(); /// toks.append(lastname); /// } /// }) /// } /// } /// /// assert_eq!("Hello John", quote_greeting("John", None).to_string()?); /// assert_eq!("Hello John Doe", quote_greeting("John", Some("Doe")).to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// ///
/// /// ## Whitespace Detection /// /// The [`quote!`] macro has the following rules for dealing with indentation and /// spacing. /// /// **Spaces** — Two tokens that are separated are spaced. Regardless of how /// many spaces there are between them. This can be controlled manually by /// inserting the [`$[' ']`][escape] escape sequence in the token stream. /// /// ``` /// use genco::prelude::*; /// /// let tokens: rust::Tokens = quote! { /// fn test() { /// println!("Hello... "); /// /// println!("World!"); /// } /// }; /// /// assert_eq!( /// vec![ /// "fn test() {", /// " println!(\"Hello... \");", /// "", /// " println!(\"World!\");", /// "}", /// ], /// tokens.to_file_vec()?, /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// ///
/// /// **Line breaking** — Line breaks are detected by leaving two empty lines /// between two tokens. This can be controlled manually by inserting the /// [`$['\n']`][escape] escape in the token stream. /// /// ``` /// use genco::prelude::*; /// /// let tokens: rust::Tokens = quote! { /// fn test() { /// println!("Hello... "); /// /// /// /// println!("World!"); /// } /// }; /// /// assert_eq!( /// vec![ /// "fn test() {", /// " println!(\"Hello... \");", /// "", /// " println!(\"World!\");", /// "}", /// ], /// tokens.to_file_vec()?, /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// ///
/// /// **Indentation** — Indentation is determined on a row-by-row basis. If a /// column is further in than the one on the preceeding row, it is indented *one /// level* deeper. /// /// If a column starts shallower than a preceeding, non-whitespace only row, it /// will be matched against previously known indentation levels. Failure to /// match a previously known level is an error. /// /// All indentations inserted during the macro will be unrolled at the end of /// it. So any trailing indentations will be matched by unindentations. /// /// ``` /// use genco::prelude::*; /// /// let tokens: rust::Tokens = quote! { /// fn test() { /// println!("Hello... "); /// /// println!("World!"); /// } /// }; /// /// assert_eq!( /// vec![ /// "fn test() {", /// " println!(\"Hello... \");", /// "", /// " println!(\"World!\");", /// "}", /// ], /// tokens.to_file_vec()?, /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// /// Example showcasing an indentation mismatch: /// /// ```,compile_fail /// use genco::prelude::*; /// /// let tokens: rust::Tokens = quote! { /// fn test() { /// println!("Hello... "); /// /// println!("World!"); /// } /// }; /// ``` /// /// ```text /// ---- src\lib.rs - (line 150) stdout ---- /// error: expected 4 less spaces of indentation /// --> src\lib.rs:157:9 /// | /// 10 | println!("World!"); /// | ^^^^^^^ /// ``` /// /// [escape]: #escape-sequences pub use genco_macros::quote; /// Convenience macro for constructing a [`FormatInto`] implementation in-place. /// /// Constructing [`FormatInto`] implementation instead of short lived [token /// streams] can be more beneficial for memory use and performance. /// /// [`FormatInto`]: crate::tokens::FormatInto /// [token streams]: Tokens /// /// # Comparison /// /// In the below example, `f1` and `f2` are equivalent. In here [quote_fn!] /// simply makes it easier to build. /// /// ``` /// use genco::prelude::*; /// use genco::tokens::from_fn; /// /// let f1 = from_fn(move |t| { /// quote_in!{ *t => /// println!("Hello World"); /// } /// }); /// /// let f2 = quote_fn!{ /// println!("Hello World"); /// }; /// /// let tokens: rust::Tokens = quote!{ /// $f1 /// $f2 /// }; /// /// assert_eq!{ /// vec![ /// "println!(\"Hello World\");", /// "println!(\"Hello World\");", /// ], /// tokens.to_file_vec()?, /// }; /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// /// # Examples which borrow /// /// ``` /// use genco::prelude::*; /// /// fn greeting(name: &str) -> impl FormatInto + '_ { /// quote_fn! { /// println!($[str](Hello $[const](name))) /// } /// } /// /// fn advanced_greeting<'a>(first: &'a str, last: &'a str) -> impl FormatInto + 'a { /// quote_fn! { /// println!($[str](Hello $[const](first) $[const](last))) /// } /// } /// /// let tokens = quote! { /// $(greeting("Mio")); /// $(advanced_greeting("Jane", "Doe")); /// }; /// /// assert_eq!{ /// vec![ /// "println!(\"Hello Mio\");", /// "println!(\"Hello Jane Doe\");", /// ], /// tokens.to_file_vec()? /// }; /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub use genco_macros::quote_fn; /// Behaves the same as [`quote!`] while quoting into an existing token stream /// with ` => `. /// /// This macro takes a destination stream followed by an `=>` and the tokens to /// extend that stream with. /// /// Note that it must be possible to borrow `` mutably, so a reference /// like `&mut rust::Tokens` will have to be dereferenced when used with this /// macro. /// /// ``` /// # use genco::prelude::*; /// /// # fn generate() -> rust::Tokens { /// let mut tokens = rust::Tokens::new(); /// quote_in!(tokens => hello world); /// # tokens /// # } /// /// fn generate_into(tokens: &mut rust::Tokens) { /// quote_in! { *tokens => /// hello... /// world! /// }; /// } /// ``` /// /// # Example /// /// ``` /// use genco::prelude::*; /// /// let mut tokens = rust::Tokens::new(); /// /// quote_in! { tokens => /// fn foo() -> u32 { /// 42 /// } /// } /// ``` /// /// # Use with scopes /// /// [`quote_in!`] can be used inside of a [`quote!`] through [a scope]. /// /// ``` /// use genco::prelude::*; /// /// let tokens: rust::Tokens = quote! { /// fn foo(v: bool) -> u32 { /// $(ref out { /// quote_in! { *out => /// if v { /// 1 /// } else { /// 0 /// } /// } /// }) /// } /// }; /// ``` /// /// [a scope]: quote#scopes pub use genco_macros::quote_in; #[macro_use] mod macros; pub mod fmt; pub mod lang; pub mod prelude; pub mod tokens; #[doc(inline)] pub use self::tokens::Tokens; /// Private module used for macros. #[doc(hidden)] pub mod __priv { use crate::lang::Lang; use crate::tokens::{from_fn, FormatInto}; use crate::tokens::{Item, ItemStr}; #[inline] pub const fn static_(string: &'static str) -> Item { Item::static_(string) } #[inline] pub const fn literal(string: ItemStr) -> Item { Item::literal(string) } #[inline] pub const fn indentation(level: i16) -> Item { Item::indentation(level) } #[inline] pub const fn push() -> Item { Item::push() } #[inline] pub const fn line() -> Item { Item::line() } #[inline] pub const fn space() -> Item { Item::space() } #[inline] pub const fn open_quote(is_interpolation: bool) -> Item { Item::open_quote(is_interpolation) } #[inline] pub const fn close_quote() -> Item { Item::close_quote() } #[inline] pub const fn open_eval() -> Item { Item::open_eval() } #[inline] pub const fn close_eval() -> Item { Item::close_eval() } /// Add a language item directly. /// /// This must only be used by the [`impl_lang!`] macro. /// /// [`impl_lang!`]: crate::impl_lang! #[doc(hidden)] #[inline] pub fn item(item: L::Item) -> impl FormatInto where L: Lang, { from_fn(|t| { t.lang_item(item); }) } /// Register a language item directly. /// /// This must only be used by the [`impl_lang!`] macro. /// /// [`impl_lang!`]: crate::impl_lang! #[doc(hidden)] #[inline] pub fn register(item: L::Item) -> impl FormatInto where L: Lang, { from_fn(|t| { t.lang_item_register(item); }) } } ================================================ FILE: src/macros.rs ================================================ //! Macros helpers in genco. /// Macro to implement support for a custom language. /// /// # Examples /// /// ``` /// use genco::fmt; /// use std::fmt::Write as _; /// /// #[derive(Default)] /// struct Config { /// } /// /// #[derive(Default)] /// struct Format { /// } /// /// genco::impl_lang! { /// MyLang { /// type Config = Config; /// type Item = Any; /// type Format = Format; /// /// fn write_quoted(out: &mut fmt::Formatter<'_>, input: &str) -> fmt::Result { /// genco::lang::c_family_write_quoted(out, input) /// } /// /// fn format_file( /// tokens: &Tokens, /// out: &mut fmt::Formatter<'_>, /// config: &Self::Config, /// ) -> fmt::Result { /// use genco::quote_in; /// /// let mut header: Tokens = Tokens::new(); /// let mut any_imports = false; /// /// for import in tokens.iter_lang() { /// any_imports = true; /// /// match import.kind() { /// AnyKind::Import(import) => { /// header.push(); /// quote_in!(header => import $(import.0)); /// } /// AnyKind::ImportDefault(import) => { /// header.push(); /// quote_in!(header => import default $(import.0)); /// } /// } /// } /// /// if any_imports { /// // Add a line as padding in case we have any imports. /// header.line(); /// } /// /// let format = Format::default(); /// header.format(out, config, &format)?; /// tokens.format(out, config, &format)?; /// Ok(()) /// } /// } /// /// Import(Import) { /// fn format(&self, out: &mut fmt::Formatter<'_>, config: &Config, _: &Format) -> fmt::Result { /// out.write_str(self.0)?; /// Ok(()) /// } /// } /// /// ImportDefault(ImportDefault) { /// fn format(&self, out: &mut fmt::Formatter<'_>, config: &Config, _: &Format) -> fmt::Result { /// write!(out, "default:{}", self.0)?; /// Ok(()) /// } /// } /// } /// /// #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] /// struct Import(&'static str); /// /// #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] /// struct ImportDefault(&'static str); /// /// use genco::{quote, Tokens}; /// /// let a = Import("first"); /// let b = ImportDefault("second"); /// /// let t: Tokens = quote! { /// $a /// $b /// }; /// /// assert_eq! { /// vec![ /// "import first", /// "import default second", /// "", /// "first", /// "default:second" /// ], /// t.to_file_vec()? /// }; /// # Ok::<_, genco::fmt::Error>(()) /// ``` #[macro_export] macro_rules! impl_lang { ( $(#[$($meta:meta)*])* $vis:vis $lang:ident { $($lang_item:tt)* } $( $name:ident($ty:ty) { $($ty_lang_item_item:tt)* } )* ) => { $(#[$($meta)*])* #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] $vis struct $lang(()); impl $crate::lang::Lang for $lang { $($lang_item)* } #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] enum AnyKind { $($name($ty),)* } /// A type-erased language item capable of holding any kind. #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] $vis struct Any { kind: AnyKind, } impl core::fmt::Debug for Any { #[inline] fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.kind.fmt(f) } } impl Any { /// Access the kind of the any type. #[allow(unused)] fn kind(&self) -> &AnyKind { &self.kind } } $( impl From<$ty> for Any { #[inline] fn from(lang: $ty) -> Self { Any { kind: AnyKind::$name(lang) } } } )* impl $crate::lang::LangItem<$lang> for Any { #[inline] fn format( &self, out: &mut $crate::fmt::Formatter<'_>, config: &<$lang as $crate::lang::Lang>::Config, format: &<$lang as $crate::lang::Lang>::Format, ) -> $crate::fmt::Result { match self.kind { $(AnyKind::$name(ref lang) => lang.format(out, config, format),)* } } } $( impl $crate::tokens::FormatInto<$lang> for $ty { #[inline] fn format_into(self, tokens: &mut $crate::Tokens<$lang>) { tokens.append($crate::__priv::item::<$lang>( <<$lang as $crate::lang::Lang>::Item as ::core::convert::From<$ty>>::from(self) )); } } impl<'a> $crate::tokens::FormatInto<$lang> for &'a $ty { #[inline] fn format_into(self, tokens: &mut $crate::Tokens<$lang>) { $crate::tokens::FormatInto::<$lang>::format_into(::core::clone::Clone::clone(self), tokens) } } impl $crate::tokens::Register<$lang> for $ty { #[inline] fn register(self, tokens: &mut $crate::Tokens<$lang>) { tokens.append($crate::__priv::register::<$lang>( <<$lang as $crate::lang::Lang>::Item as ::core::convert::From<$ty>>::from(self) )); } } impl<'a> $crate::tokens::Register<$lang> for &'a $ty { #[inline] fn register(self, tokens: &mut $crate::Tokens<$lang>) { $crate::tokens::Register::<$lang>::register(::core::clone::Clone::clone(self), tokens) } } impl $crate::lang::LangItem<$lang> for $ty { $($ty_lang_item_item)* } )* } } ================================================ FILE: src/prelude.rs ================================================ //! Prelude containing typical things to import when using the library. pub use crate::lang::*; pub use crate::tokens::{display, quoted, register, FormatInto}; pub use crate::{quote, quote_fn, quote_in, Tokens}; ================================================ FILE: src/tokens/display.rs ================================================ use core::fmt; use alloc::string::ToString; use crate::lang::Lang; use crate::tokens::FormatInto; use crate::Tokens; /// Function to build a string literal. /// /// This is an alternative to manually implementing [tokens::FormatInto], since /// it can tokenize anything that implements [Display][fmt::Display] /// directly. /// /// On the other hand, things implementing [tokens::FormatInto] have access to the /// full range of the [Tokens] api, allowing it to work more efficiently. /// /// [tokens::FormatInto]: crate::tokens::FormatInto /// [Tokens]: crate::Tokens /// /// # Examples /// /// Example showcasing quoted strings when generating Rust. /// /// ``` /// use genco::prelude::*; /// use std::fmt; /// /// struct Foo(()); /// /// impl fmt::Display for Foo { /// fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { /// write!(fmt, "Foo") /// } /// } /// /// let map = rust::import("std::collections", "HashMap"); /// /// let foo = Foo(()); /// /// let tokens = quote! { /// let mut m = $map::::new(); /// m.insert(0, $(display(&foo))); /// }; /// /// assert_eq!( /// vec![ /// "use std::collections::HashMap;", /// "", /// "let mut m = HashMap::::new();", /// "m.insert(0, Foo);", /// ], /// tokens.to_file_vec()?, /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn display(inner: T) -> Display where T: fmt::Display, { Display { inner } } /// Struct containing a type that implements [Display][fmt::Display] and can be /// tokenized into a stream. /// /// This is constructed with the [display()] function. #[derive(Clone, Copy)] pub struct Display { inner: T, } impl FormatInto for Display where L: Lang, T: fmt::Display, { #[inline] fn format_into(self, tokens: &mut Tokens) { tokens.literal(self.inner.to_string()); } } ================================================ FILE: src/tokens/format_into.rs ================================================ use core::fmt::Arguments; use alloc::borrow::Cow; use alloc::rc::Rc; use alloc::string::{String, ToString}; use alloc::vec::Vec; use crate::lang::Lang; use crate::tokens::{ItemStr, Tokens}; /// Trait for types that can be formatted in-place into a token stream. /// /// Things implementing [`FormatInto`] can be used as arguments for /// [interpolation] in the [`quote!`] macro. /// /// [`from_fn()`] is a helper function which simplifies the task of creating a /// [`FormatInto`] implementation on the fly. The [`quote_fn!`] macro is also a /// specialization of this. /// /// [`from_fn()`]: crate::tokens::from_fn() /// [`quote!`]: crate::quote /// [`quote_fn!`]: crate::quote_fn /// [interpolation]: crate::quote#interpolation /// /// # Examples /// /// ``` /// use genco::quote_in; /// use genco::tokens::{ItemStr, FormatInto, from_fn, static_literal}; /// use genco::lang::Lang; /// /// fn comment(s: impl Into) -> impl FormatInto /// where /// L: Lang /// { /// from_fn(move |tokens| { /// let s = s.into(); /// quote_in!(*tokens => $(static_literal("//")) $s); /// }) /// } /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub trait FormatInto where L: Lang, { /// Convert the type into tokens in-place. fn format_into(self, tokens: &mut Tokens); } /// Formatting a reference to a token stream is exactly the same as extending /// the token stream with a copy of the stream being formatted. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let a: Tokens = quote!(foo bar); /// /// let result = quote!($a baz); /// /// assert_eq!("foo bar baz", result.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` impl FormatInto for Tokens where L: Lang, { #[inline] fn format_into(self, tokens: &mut Self) { tokens.extend_by_owned(self); } } /// Formatting a reference to a token stream is exactly the same as extending /// the token stream with a copy of the stream being formatted. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let a: &Tokens = "e!(foo bar); /// /// let result = quote!($a baz); /// /// assert_eq!("foo bar baz", result.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` impl FormatInto for &Tokens where L: Lang, L::Item: Clone, { #[inline] fn format_into(self, tokens: &mut Tokens) { tokens.extend_by_ref(self); } } /// Formatting a vector of token streams is like formatting each, one after /// another. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let mut vec = Vec::::new(); /// vec.push(quote!(foo)); /// vec.push(quote!($[' ']bar)); /// /// let result = quote!($vec baz); /// /// assert_eq!("foo bar baz", result.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` impl FormatInto for Vec where L: Lang, T: FormatInto, { #[inline] fn format_into(self, tokens: &mut Tokens) { for t in self { tokens.append(t); } } } /// Formatting a reference to a vector of token streams is like formatting each, /// one after another. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let mut vec = Vec::::new(); /// vec.push(quote!(foo)); /// vec.push(quote!($[' ']bar)); /// /// let result = quote!($(&vec) baz); /// /// assert_eq!("foo bar baz", result.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` impl FormatInto for &Vec where L: Lang, T: FormatInto + Clone, { #[inline] fn format_into(self, tokens: &mut Tokens) { for t in self { tokens.append(t.clone()); } } } /// Formatting a slice of token streams is like formatting each, one after /// another. /// /// This will cause each token stream to be cloned into the destination stream. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let vec = vec!["foo", " ", "bar"]; /// let slice = &vec[..]; /// /// let result: Tokens = quote!($slice baz); /// /// assert_eq!("foo bar baz", result.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` impl FormatInto for &[T] where L: Lang, T: Clone + FormatInto, { #[inline] fn format_into(self, tokens: &mut Tokens) { for t in self { tokens.append(t.clone()); } } } /// Formatting borrowed string boxed them on the heap. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let foo = "foo"; /// let bar = "bar"; /// /// let result: Tokens = quote!($foo $bar baz); /// /// assert_eq!("foo bar baz", result.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` impl FormatInto for &str where L: Lang, { #[inline] fn format_into(self, tokens: &mut Tokens) { tokens.literal(self); } } /// Formatting borrowed string boxed them on the heap. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let foo = String::from("foo"); /// let bar = String::from("bar"); /// /// let result: Tokens = quote!($(&foo) $(&bar) baz); /// /// assert_eq!("foo bar baz", result.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` impl FormatInto for &String where L: Lang, { #[inline] fn format_into(self, tokens: &mut Tokens) { tokens.literal(self); } } /// Formatting owned strings takes ownership of the string directly from the /// heap. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let foo = String::from("foo"); /// let bar = String::from("bar"); /// /// let result: Tokens = quote!($foo $bar baz); /// /// assert_eq!("foo bar baz", result.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` impl FormatInto for String where L: Lang, { #[inline] fn format_into(self, tokens: &mut Tokens) { tokens.literal(self); } } /// Refcounted strings are moved into the token stream without copying. /// /// # Examples /// /// ``` /// use std::rc::Rc; /// use genco::prelude::*; /// /// let foo = Rc::new(String::from("foo")); /// let bar = Rc::new(String::from("bar")); /// /// let result: Tokens = quote!($foo $bar baz); /// /// assert_eq!("foo bar baz", result.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` impl FormatInto for Rc where L: Lang, { #[inline] fn format_into(self, tokens: &mut Tokens) { tokens.literal(self); } } /// Refcounted strings are cloned and moved into the token stream without /// copying. /// /// # Examples /// /// ``` /// use std::rc::Rc; /// use genco::prelude::*; /// /// let foo = Rc::new(String::from("foo")); /// let bar = Rc::new(String::from("bar")); /// /// let result: Tokens = quote!($(&foo) $(&bar) baz); /// /// assert_eq!("foo bar baz", result.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` impl FormatInto for &Rc where L: Lang, { #[inline] fn format_into(self, tokens: &mut Tokens) { tokens.literal(self.as_ref()); } } /// Implementation for [`Arguments`] which allows for arbitrary and efficient /// literal formatting. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let name = "John"; /// let result: Tokens = quote!($(format_args!("Hello {name}"))); /// /// assert_eq!("Hello John", result.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` impl FormatInto for Arguments<'_> where L: Lang, { #[inline] fn format_into(self, tokens: &mut Tokens) { if let Some(s) = self.as_str() { tokens.literal(ItemStr::static_(s)); } else { tokens.literal(self.to_string()); } } } /// Optional items are formatted if they are present. /// /// # Examples /// /// ``` /// use std::rc::Rc; /// use genco::prelude::*; /// /// let foo = Some("foo"); /// let bar = Some("bar"); /// let biz = None::<&str>; /// /// let result: Tokens = quote!($foo $bar baz $biz); /// /// assert_eq!("foo bar baz", result.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` impl FormatInto for Option where L: Lang, T: FormatInto, { #[inline] fn format_into(self, tokens: &mut Tokens) { if let Some(inner) = self { inner.format_into(tokens); } } } /// Cow strings are formatted by either borrowing or cloning the string. /// /// # Examples /// /// ``` /// use std::borrow::Cow; /// use genco::prelude::*; /// /// let foo = Cow::::Borrowed("foo"); /// let bar = Cow::::Owned(String::from("bar")); /// /// let result: Tokens = quote!($foo $bar baz); /// /// assert_eq!("foo bar baz", result.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` impl FormatInto for Cow<'_, str> where L: Lang, { #[inline] fn format_into(self, tokens: &mut Tokens) { match self { Cow::Borrowed(b) => tokens.literal(b), Cow::Owned(o) => o.format_into(tokens), } } } /// Cow strings are formatted by either borrowing or cloning the string. /// /// # Examples /// /// ``` /// use std::borrow::Cow; /// use genco::prelude::*; /// /// let foo = &Cow::::Borrowed("foo"); /// let bar = &Cow::::Owned(String::from("bar")); /// /// let result: Tokens = quote!($foo $bar baz); /// /// assert_eq!("foo bar baz", result.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` impl FormatInto for &Cow<'_, str> where L: Lang, { #[inline] fn format_into(self, tokens: &mut Tokens) { match self { Cow::Borrowed(b) => tokens.literal(b), Cow::Owned(o) => o.format_into(tokens), } } } /// Cow slices are formatted by either borrowing or cloning the slice. /// /// # Examples /// /// ``` /// use std::borrow::Cow; /// use genco::prelude::*; /// /// let foo = Cow::<[&str]>::Borrowed(&["foo", "bar"]); /// let bar = Cow::<[&str]>::Owned(vec!["baz"]); /// /// let result: Tokens = quote!($foo $bar biz); /// /// assert_eq!("foobar baz biz", result.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` impl FormatInto for Cow<'_, [T]> where L: Lang, T: FormatInto + Clone, { #[inline] fn format_into(self, tokens: &mut Tokens) { match self { Cow::Borrowed(b) => { for t in b.iter() { tokens.append(t.clone()); } } Cow::Owned(o) => o.format_into(tokens), } } } /// Cow slices are formatted by either borrowing or cloning the slice. /// /// # Examples /// /// ``` /// use std::borrow::Cow; /// use genco::prelude::*; /// /// let foo = &Cow::<[&str]>::Borrowed(&["foo", "bar"]); /// let bar = &Cow::<[&str]>::Owned(vec!["baz"]); /// /// let result: Tokens = quote!($foo $bar biz); /// /// assert_eq!("foobar baz biz", result.to_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` impl FormatInto for &Cow<'_, [T]> where L: Lang, T: FormatInto + Clone, { #[inline] fn format_into(self, tokens: &mut Tokens) { match self { Cow::Borrowed(b) => { for t in b.iter() { tokens.append(t.clone()); } } Cow::Owned(o) => o.format_into(tokens), } } } macro_rules! impl_display { ($($ty:ty),*) => { $( /// Implementation for primitive type. Uses the corresponding /// [Display][std::fmt::Display] implementation for the /// primitive type. impl FormatInto for $ty where L: Lang, { fn format_into(self, tokens: &mut Tokens) { tokens.append(self.to_string()); } } )* }; } impl_display!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, isize, usize); ================================================ FILE: src/tokens/from_fn.rs ================================================ use crate::lang::Lang; use crate::tokens; use crate::Tokens; /// Construct a [`FormatInto`][crate::tokens::FormatInto] implementation from a /// function. /// /// # Examples /// /// ``` /// use genco::{quote, quote_in}; /// use genco::lang::{Lang, Rust}; /// use genco::tokens::{ItemStr, FormatInto, Tokens, from_fn, static_literal}; /// /// fn comment(s: impl Into + Copy) -> impl FormatInto + Copy { /// from_fn(move |tokens| { /// let s = s.into(); /// quote_in!(*tokens => $(static_literal("//")) #s); /// }) /// } /// /// let c = comment("hello world"); /// let _: Tokens = quote!($c $['\n'] $c); /// # Ok::<_, genco::fmt::Error>(()) /// ``` #[inline] pub fn from_fn(f: F) -> FromFn where F: FnOnce(&mut Tokens), L: Lang, { FromFn { f } } /// A captured function used for formatting tokens. /// /// Constructed using [from_fn()] or the [quote_fn!][crate::quote_fn] macro. #[derive(Clone, Copy)] pub struct FromFn { f: F, } impl tokens::FormatInto for FromFn where L: Lang, F: FnOnce(&mut Tokens), { #[inline] fn format_into(self, tokens: &mut Tokens) { (self.f)(tokens); } } ================================================ FILE: src/tokens/internal.rs ================================================ ================================================ FILE: src/tokens/item.rs ================================================ //! A single element use core::fmt; use crate::lang::Lang; use crate::tokens::{FormatInto, ItemStr, Tokens}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) enum Kind { /// A literal item. /// Is added as a raw string to the stream of tokens. Literal(ItemStr), /// A language-specific item. Lang(usize), /// Push a new line unless the current line is empty. Will be flushed on /// indentation changes. Push, /// Push a line. Will be flushed on indentation changes. Line, /// Space between language items. Typically a single space. /// /// Multiple spacings in sequence are collapsed into one. /// A spacing does nothing if at the beginning of a line. Space, /// Manage indentation. /// /// An indentation of 0 has no effect. Indentation(i16), /// Switch to handling input as a quote. OpenQuote(bool), /// Close the last quote. CloseQuote, /// Switch on evaluation. Only valid during string handling. OpenEval, /// Close evaluation. CloseEval, } /// A single item in a stream of tokens. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[non_exhaustive] pub struct Item { pub(crate) kind: Kind, } impl Item { /// Construct a new item based on a kind. #[inline] pub(crate) const fn new(kind: Kind) -> Self { Self { kind } } /// Shorthand for constructing a new static literal item. /// /// # Examples /// /// ``` /// use genco::tokens::{Item, ItemStr}; /// use genco::lang::Rust; /// /// let a = Item::static_("hello"); /// let b = Item::literal("hello".into()); /// /// assert_eq!(a, b); /// ``` #[inline] pub const fn static_(literal: &'static str) -> Self { Self::new(Kind::Literal(ItemStr::static_(literal))) } /// Construct a new push item. /// /// # Examples /// /// ``` /// use genco::tokens::Item; /// use genco::lang::Rust; /// /// let a = Item::push(); /// let b = Item::push(); /// /// assert_eq!(a, b); /// ``` #[inline] pub const fn push() -> Self { Self::new(Kind::Push) } /// Construct a new line item. /// /// # Examples /// /// ``` /// use genco::tokens::Item; /// use genco::lang::Rust; /// /// let a = Item::line(); /// let b = Item::line(); /// /// assert_eq!(a, b); /// ``` #[inline] pub const fn line() -> Self { Self::new(Kind::Line) } /// Construct a new space item. /// /// # Examples /// /// ``` /// use genco::tokens::Item; /// use genco::lang::Rust; /// /// let a = Item::space(); /// let b = Item::space(); /// /// assert_eq!(a, b); /// ``` #[inline] pub const fn space() -> Self { Self::new(Kind::Space) } /// Construct a new indentation item. #[inline] pub(crate) const fn indentation(n: i16) -> Self { Self::new(Kind::Indentation(n)) } /// Construct a quote open. /// /// Switches to handling input as a quote. The argument indicates whether /// the string contains any interpolated values. The string content is /// quoted with the language-specific [quoting method]. /// /// [quoting method]: Lang::open_quote /// /// # Examples /// /// ``` /// use genco::tokens::Item; /// use genco::lang::Rust; /// /// let a = Item::open_quote(true); /// let b = Item::open_quote(false); /// /// assert_eq!(a, a); /// assert_ne!(a, b); /// ``` #[inline] pub const fn open_quote(is_interpolated: bool) -> Self { Self::new(Kind::OpenQuote(is_interpolated)) } /// Construct a quote close. /// /// # Examples /// /// ``` /// use genco::tokens::Item; /// use genco::lang::Rust; /// /// let a = Item::close_quote(); /// let b = Item::close_quote(); /// /// assert_eq!(a, b); /// ``` #[inline] pub const fn close_quote() -> Self { Self::new(Kind::CloseQuote) } /// Construct a new eval open. #[inline] pub(crate) const fn open_eval() -> Self { Self::new(Kind::OpenEval) } /// Construct a new eval close. #[inline] pub(crate) const fn close_eval() -> Self { Self::new(Kind::CloseEval) } /// Construct a new literal item. /// /// # Examples /// /// ``` /// use genco::tokens::{Item, ItemStr}; /// use genco::lang::Rust; /// /// let a = Item::static_("hello"); /// let b = Item::literal("hello".into()); /// /// assert_eq!(a, b); /// ``` #[inline] pub const fn literal(lit: ItemStr) -> Self { Self::new(Kind::Literal(lit)) } /// Construct a lang item with the given index. #[inline] pub(crate) const fn lang(index: usize) -> Item { Item::new(Kind::Lang(index)) } } impl fmt::Debug for Item { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.kind.fmt(f) } } /// Formatting an item is the same as simply adding that item to the token /// stream. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// use genco::tokens::{Item, ItemStr}; /// /// let foo = Item::literal(ItemStr::static_("foo")); /// let bar = Item::literal("bar".into()); /// /// let result: Tokens = quote!($foo $bar baz); /// /// assert_eq!("foo bar baz", result.to_string()?); /// /// assert_eq!{ /// vec![ /// Item::literal(ItemStr::static_("foo")), /// Item::space(), /// Item::literal("bar".into()), /// Item::space(), /// Item::literal(ItemStr::static_("baz")), /// ], /// result, /// }; /// # Ok::<_, genco::fmt::Error>(()) /// ``` impl FormatInto for Item where L: Lang, { #[inline] fn format_into(self, tokens: &mut Tokens) { tokens.item(self); } } ================================================ FILE: src/tokens/item_str.rs ================================================ //! Helper trait to take ownership of strings. use core::fmt; use core::hash::{Hash, Hasher}; use core::ops::Deref; #[cfg(feature = "alloc")] use alloc::borrow::{Cow, ToOwned}; #[cfg(feature = "alloc")] use alloc::boxed::Box; #[cfg(feature = "alloc")] use alloc::rc::Rc; #[cfg(feature = "alloc")] use alloc::string::String; use crate::lang::Lang; use crate::tokens::{FormatInto, Item, Tokens}; /// Internal representation. #[derive(Clone)] enum ItemStrKind { /// A boxed string. #[cfg(feature = "alloc")] Box(Box), /// A static string. Static(&'static str), } /// A managed string that permits immutable borrowing. #[derive(Clone)] pub struct ItemStr { kind: ItemStrKind, } impl ItemStr { /// Create a new ItemStr from the given kind. #[inline] const fn new(kind: ItemStrKind) -> Self { Self { kind } } /// Construct a new string item based on a static string. /// /// # Examples /// /// ``` /// use genco::tokens::ItemStr; /// /// let string = ItemStr::static_("hello world"); /// assert_eq!(&string[..], "hello world"); /// ``` pub const fn static_(s: &'static str) -> Self { Self::new(ItemStrKind::Static(s)) } } /// Convert stringy things. impl FormatInto for ItemStr where L: Lang, { #[inline] fn format_into(self, tokens: &mut Tokens) { tokens.append(Item::literal(self)); } } impl FormatInto for &ItemStr where L: Lang, { #[inline] fn format_into(self, tokens: &mut Tokens) { tokens.append(Item::literal(self.clone())); } } impl AsRef for ItemStr { #[inline] fn as_ref(&self) -> &str { match &self.kind { #[cfg(feature = "alloc")] ItemStrKind::Box(b) => b, ItemStrKind::Static(s) => s, } } } impl Deref for ItemStr { type Target = str; fn deref(&self) -> &str { match &self.kind { #[cfg(feature = "alloc")] ItemStrKind::Box(b) => b, ItemStrKind::Static(s) => s, } } } #[cfg(feature = "alloc")] impl From> for ItemStr { #[inline] fn from(value: Box) -> Self { Self::new(ItemStrKind::Box(value)) } } impl<'a> From<&'a ItemStr> for ItemStr { #[inline] fn from(value: &'a ItemStr) -> Self { value.clone() } } #[cfg(feature = "alloc")] impl<'a> From<&'a String> for ItemStr { #[inline] fn from(value: &'a String) -> Self { value.clone().into() } } #[cfg(feature = "alloc")] impl From for ItemStr { #[inline] fn from(value: String) -> Self { Self::new(ItemStrKind::Box(value.into_boxed_str())) } } #[cfg(feature = "alloc")] impl<'a> From<&'a str> for ItemStr { #[inline] fn from(value: &'a str) -> Self { Self::new(ItemStrKind::Box(value.to_owned().into_boxed_str())) } } #[cfg(feature = "alloc")] impl<'a, 'b> From<&'b &'a str> for ItemStr { #[inline] fn from(value: &'b &'a str) -> Self { Self::new(ItemStrKind::Box((*value).to_owned().into_boxed_str())) } } #[cfg(feature = "alloc")] impl<'a> From> for ItemStr { #[inline] fn from(value: Cow<'a, str>) -> Self { Self::new(ItemStrKind::Box(match value { Cow::Owned(string) => string.into_boxed_str(), Cow::Borrowed(string) => string.to_owned().into_boxed_str(), })) } } #[cfg(feature = "alloc")] impl<'a, 'b> From<&'b Cow<'a, str>> for ItemStr { #[inline] fn from(value: &'b Cow<'a, str>) -> Self { Self::new(ItemStrKind::Box(match value { Cow::Owned(string) => string.clone().into_boxed_str(), Cow::Borrowed(string) => (*string).to_owned().into_boxed_str(), })) } } #[cfg(feature = "alloc")] impl From> for ItemStr { #[inline] fn from(value: Rc) -> Self { Self::new(ItemStrKind::Box((*value).clone().into())) } } impl fmt::Debug for ItemStr { #[inline] fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { self.as_ref().fmt(fmt) } } impl fmt::Display for ItemStr { #[inline] fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { self.as_ref().fmt(fmt) } } impl PartialEq for ItemStr { #[inline] fn eq(&self, other: &Self) -> bool { self.as_ref() == other.as_ref() } } impl Eq for ItemStr {} impl PartialOrd for ItemStr { #[inline] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for ItemStr { #[inline] fn cmp(&self, other: &Self) -> core::cmp::Ordering { self.as_ref().cmp(other.as_ref()) } } impl Hash for ItemStr { #[inline] fn hash(&self, state: &mut H) where H: Hasher, { self.as_ref().hash(state); } } ================================================ FILE: src/tokens/mod.rs ================================================ //! Utilities for working with token streams. //! //! This is typically a module you will use if you intend to provide a manual //! implementation of [`FormatInto`]. //! //! # Examples //! //! ```rust //! use genco::quote_in; //! use genco::tokens::{from_fn, ItemStr, FormatInto, static_literal}; //! use genco::lang::Lang; //! //! /// Format a block comment, starting with `/**`, and ending in `*/`. //! pub fn block_comment(text: I) -> impl FormatInto //! where //! I: IntoIterator, //! I::Item: Into, //! L: Lang, //! { //! from_fn(move |t| { //! let mut it = text.into_iter().peekable(); //! //! if it.peek().is_some() { //! quote_in! { *t => //! $(static_literal("/**")) //! $(for line in it join ($['\r']) { //! $[' ']* $(line.into()) //! }) //! $[' ']$(static_literal("*/")) //! } //! } //! }) //! } //! //! use genco::prelude::*; //! //! let tokens: java::Tokens = quote! { //! $(block_comment(&["This class is used for awesome stuff", "ok?"])) //! public static class Foo { //! } //! }; //! //! assert_eq!( //! vec![ //! "/**", //! " * This class is used for awesome stuff", //! " * ok?", //! " */", //! "public static class Foo {", //! "}" //! ], //! tokens.to_vec()? //! ); //! # Ok::<_, genco::fmt::Error>(()) //! ``` mod display; mod format_into; mod from_fn; mod internal; mod item; mod item_str; mod quoted; mod register; mod static_literal; mod tokens; pub use self::display::{display, Display}; pub use self::format_into::FormatInto; pub use self::from_fn::{from_fn, FromFn}; pub use self::item::Item; pub(crate) use self::item::Kind; pub use self::item_str::ItemStr; pub use self::quoted::{quoted, QuotedFn}; pub use self::register::{register, Register, RegisterFn}; pub use self::static_literal::static_literal; pub use self::tokens::Tokens; ================================================ FILE: src/tokens/quoted.rs ================================================ use crate::lang::Lang; use crate::tokens::{FormatInto, Tokens}; /// Function to provide string quoting. /// /// Note that quoting is applied automatically for literal strings inside of /// the [`quote!`] macro, like: `quote!("hello")`. /// /// This is used to generated quoted strings, in the language of choice. /// /// # Examples /// /// Example showcasing quoted strings when generating Rust. /// /// ``` /// use genco::prelude::*; /// /// let map = rust::import("std::collections", "HashMap"); /// /// let tokens = quote! { /// let mut m = $map::::new(); /// m.insert(0, $(quoted("hello\" world"))); /// }; /// /// assert_eq!( /// vec![ /// "use std::collections::HashMap;", /// "", /// "let mut m = HashMap::::new();", /// "m.insert(0, \"hello\\\" world\");", /// ], /// tokens.to_file_vec()?, /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// /// # Example: A quote inside a quote /// /// Note that this requires extra buffering to occur when formatting the token stream. /// /// ``` /// use genco::prelude::*; /// /// let tokens: python::Tokens = quote!($[str](Hello $[const](quoted("World 😊")))); /// /// assert_eq!( /// "\"Hello \\\"World \\U0001f60a\\\"\"", /// tokens.to_string()?, /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// /// [`quote!`]: crate::quote pub fn quoted(inner: T) -> QuotedFn { QuotedFn { inner } } /// Struct containing a type that is quoted. /// /// This is constructed with the [quoted()] function. #[derive(Clone, Copy, Debug)] pub struct QuotedFn { inner: T, } impl FormatInto for QuotedFn where L: Lang, T: FormatInto, { #[inline] fn format_into(self, t: &mut Tokens) { t.open_quote(false); self.inner.format_into(t); t.close_quote(); } } ================================================ FILE: src/tokens/register.rs ================================================ use crate::lang::Lang; use crate::tokens::FormatInto; use crate::Tokens; /// Function to provide item registration. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let write_bytes_ext = rust::import("byteorder", "WriteBytesExt").with_alias("_"); /// let read_bytes_ext = rust::import("byteorder", "ReadBytesExt").with_alias("_"); /// let cursor = &rust::import("std::io", "Cursor"); /// let big_endian = &rust::import("byteorder", "BigEndian"); /// /// let tokens = quote! { /// $(register((write_bytes_ext, read_bytes_ext))) /// /// let mut wtr = vec![]; /// wtr.write_u16::<$big_endian>(517).unwrap(); /// wtr.write_u16::<$big_endian>(768).unwrap(); /// assert_eq!(wtr, vec![2, 5, 3, 0]); /// /// let mut rdr = $cursor::new(vec![2, 5, 3, 0]); /// assert_eq!(517, rdr.read_u16::<$big_endian>().unwrap()); /// assert_eq!(768, rdr.read_u16::<$big_endian>().unwrap()); /// }; /// /// assert_eq!( /// vec![ /// "use byteorder::{BigEndian, ReadBytesExt as _, WriteBytesExt as _};", /// "use std::io::Cursor;", /// "", /// "let mut wtr = vec![];", /// "wtr.write_u16::(517).unwrap();", /// "wtr.write_u16::(768).unwrap();", /// "assert_eq!(wtr, vec![2, 5, 3, 0]);", /// "", /// "let mut rdr = Cursor::new(vec![2, 5, 3, 0]);", /// "assert_eq!(517, rdr.read_u16::().unwrap());", /// "assert_eq!(768, rdr.read_u16::().unwrap());" /// ], /// tokens.to_file_vec()?, /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn register(inner: T) -> RegisterFn where T: Register, L: Lang, { RegisterFn { inner } } /// Struct containing a type only intended to be registered. /// /// This is constructed with the [register()] function. #[derive(Debug, Clone, Copy)] pub struct RegisterFn { inner: T, } impl FormatInto for RegisterFn where T: Register, L: Lang, { fn format_into(self, t: &mut Tokens) { t.register(self.inner); } } /// Helper trait to convert something into a stream of registrations. /// /// Thanks to this, we can provide a flexible number of arguments to /// [register()], like a tuple. /// /// This is trait is very similar to [`FormatInto`], except that it constrains /// the types that can be "registered" to only language items. An implementation /// of register must not be used as a substitute for [`FormatInto`]. /// /// [register()]: crate::Tokens::register() /// [`FormatInto`]: crate::tokens::FormatInto /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let mut tokens = rust::Tokens::new(); /// /// let hash_map = rust::import("std::collections", "HashMap"); /// let btree_map = rust::import("std::collections", "BTreeMap"); /// /// tokens.register((hash_map, btree_map)); /// /// assert_eq!( /// vec![ /// "use std::collections::{BTreeMap, HashMap};", /// ], /// tokens.to_file_vec()?, /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub trait Register where L: Lang, { /// Convert the type into tokens. fn register(self, tokens: &mut Tokens); } /// Macro to build implementations of `Register` for a tuple. macro_rules! impl_register_tuple { ($($ty:ident, $var:ident),*) => { impl Register for ($($ty,)*) where $($ty: Register,)* L: Lang, { fn register(self, tokens: &mut Tokens) { let ($($var,)*) = self; $(tokens.register($var);)* } } } } impl_register_tuple!(T1, t1); impl_register_tuple!(T1, t1, T2, t2); impl_register_tuple!(T1, t1, T2, t2, T3, t3); impl_register_tuple!(T1, t1, T2, t2, T3, t3, T4, t4); impl_register_tuple!(T1, t1, T2, t2, T3, t3, T4, t4, T5, t5); impl_register_tuple!(T1, t1, T2, t2, T3, t3, T4, t4, T5, t5, T6, t6); impl_register_tuple!(T1, t1, T2, t2, T3, t3, T4, t4, T5, t5, T6, t6, T7, t7); impl_register_tuple!(T1, t1, T2, t2, T3, t3, T4, t4, T5, t5, T6, t6, T7, t7, T8, t8); ================================================ FILE: src/tokens/static_literal.rs ================================================ use crate::lang::Lang; use crate::tokens::{FormatInto, ItemStr}; /// A formatter from a static literal. /// /// Created from the [static_literal()] function. #[derive(Debug, Clone, Copy)] pub struct StaticLiteral { literal: &'static str, } impl FormatInto for StaticLiteral where L: Lang, { #[inline] fn format_into(self, tokens: &mut crate::Tokens) { tokens.literal(ItemStr::static_(self.literal)); } } /// A formatter from a static literal. /// /// This is typically more efficient than using [append()] with a string /// directly, since it can avoid copying the string. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// use genco::tokens; /// /// let mut tokens = Tokens::<()>::new(); /// tokens.append(tokens::static_literal("hello")); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// /// [append()]: crate::Tokens::append() pub fn static_literal(literal: &'static str) -> StaticLiteral { StaticLiteral { literal } } ================================================ FILE: src/tokens/tokens.rs ================================================ //! A set of tokens that make up a single source-file. //! //! ## Example //! //! ```rust //! use genco::prelude::*; //! //! let mut toks = java::Tokens::new(); //! toks.append("foo"); //! ``` #![allow(clippy::module_inception)] use core::cmp::Ordering; use core::hash; use core::slice; use alloc::string::String; use alloc::vec::Vec; use crate::fmt; use crate::lang::{Lang, LangSupportsEval}; use crate::tokens::{FormatInto, Item, ItemStr, Kind, Register}; /// A stream of tokens. /// /// # Structural Guarantees /// /// This stream of tokens provides the following structural guarantees. /// /// * Only one [`space`] occurs in sequence and indicates spacing between /// tokens. /// * Only one [`push`] occurs in sequence and indicates that the next token /// should be spaced onto a new line. /// * A [`line`] is never by a [`push`] since it would have no effect. A line /// ensures an empty line between two tokens. /// /// ``` /// use genco::Tokens; /// use genco::tokens::Item; /// /// let mut tokens = Tokens::<()>::new(); /// /// // The first push token is "overriden" by a line. /// tokens.space(); /// tokens.space(); /// /// assert_eq!(tokens, [Item::space()]); /// /// let mut tokens = Tokens::<()>::new(); /// /// tokens.space(); /// tokens.push(); /// tokens.push(); /// /// assert_eq!(tokens, [Item::push()]); /// /// let mut tokens = Tokens::<()>::new(); /// /// // The first space and push tokens are "overriden" by a line. /// tokens.space(); /// tokens.push(); /// tokens.line(); /// /// assert_eq!(tokens, [Item::line()]); /// ``` /// /// [`space`]: Self::space /// [`push`]: Self::push /// [`line`]: Self::line pub struct Tokens where L: Lang, { items: Vec, lang: Vec, } impl Tokens where L: Lang, { /// Create a new empty stream of tokens. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let tokens = Tokens::<()>::new(); /// /// assert!(tokens.is_empty()); /// ``` pub fn new() -> Self { Tokens { items: Vec::new(), lang: Vec::new(), } } /// Create a new empty stream of tokens with the specified capacity. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let tokens = Tokens::<()>::with_capacity(10); /// /// assert!(tokens.is_empty()); /// ``` pub fn with_capacity(cap: usize) -> Self { Tokens { items: Vec::with_capacity(cap), lang: Vec::new(), } } /// Construct an iterator over the token stream. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// use genco::tokens::{ItemStr, Item}; /// /// let tokens: Tokens<()> = quote!(foo bar baz); /// let mut it = tokens.iter(); /// /// assert_eq!(Item::literal(ItemStr::static_("foo")), it.next().unwrap()); /// assert_eq!(Item::space(), it.next().unwrap()); /// assert_eq!(Item::literal(ItemStr::static_("bar")), it.next().unwrap()); /// assert_eq!(Item::space(), it.next().unwrap()); /// assert_eq!(Item::literal(ItemStr::static_("baz")), it.next().unwrap()); /// assert_eq!(None, it.next()); /// ``` #[inline] pub fn iter(&self) -> Iter<'_, L::Item> { Iter { lang: &self.lang[..], iter: self.items.iter(), } } /// Append the given tokens. /// /// This append function takes anything implementing [`FormatInto`] making /// the argument's behavior customizable. Most primitive types have built-in /// implementations of [`FormatInto`] treating them as raw tokens. /// /// Most notabley, things implementing [`FormatInto`] can be used as /// arguments for [interpolation] in the [`quote!`] macro. /// /// [`quote!`]: crate::quote /// [interpolation]: crate::quote#interpolation /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let mut tokens = Tokens::<()>::new(); /// tokens.append(4u32); /// /// assert_eq!(quote!($(4u32)), tokens); /// ``` pub fn append(&mut self, tokens: T) where T: FormatInto, { tokens.format_into(self) } #[inline] pub(crate) fn item(&mut self, item: Item) { match item.kind { Kind::Push => self.push(), Kind::Line => self.line(), Kind::Space => self.space(), Kind::Indentation(n) => self.indentation(n), Kind::Lang(..) => { /* ignored */ } kind => self.items.push(Item::new(kind)), } } /// Extend with another stream of tokens. /// /// This respects the structural requirements of adding one element at a /// time, like you would get by calling `space`, `push`, or `line`. #[inline] pub(crate) fn extend_by_ref(&mut self, other: &Tokens) where L::Item: Clone, { self.items.reserve(other.items.len()); let base = self.lang.len(); self.lang.extend(other.lang.iter().cloned()); for item in &other.items { match &item.kind { Kind::Push => self.push(), Kind::Line => self.line(), Kind::Space => self.space(), Kind::Indentation(n) => self.indentation(*n), Kind::Lang(lang) => { self.items.push(Item::lang(base.saturating_add(*lang))); } kind => self.items.push(Item::new(kind.clone())), } } } /// Extend with another stream of tokens. /// /// This respects the structural requirements of adding one element at a /// time, like you would get by calling `space`, `push`, or `line`. #[inline] pub(crate) fn extend_by_owned(&mut self, mut other: Tokens) { self.items.reserve(other.items.len()); let base = self.lang.len(); self.lang.append(&mut other.lang); for item in other.items { match item.kind { Kind::Push => self.push(), Kind::Line => self.line(), Kind::Space => self.space(), Kind::Indentation(n) => self.indentation(n), Kind::Lang(lang) => { self.items.push(Item::lang(base.saturating_add(lang))); } kind => self.items.push(Item::new(kind)), } } } /// Iterate over all registered [`Lang`] items. /// /// The order in which the imports are returned is *not* defined. So if you /// need them in some particular order you have to sort them. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let debug = rust::import("std::fmt", "Debug"); /// let ty = rust::import("std::collections", "HashMap"); /// /// let tokens = quote!(foo $ty baz); /// /// for import in tokens.iter_lang() { /// println!("{:?}", import); /// } /// ``` #[inline] pub fn iter_lang(&self) -> IterLang<'_, L> { IterLang { lang: self.lang.iter(), } } /// Add an registered custom element that is _not_ rendered. /// /// Registration can be used to generate imports that do not render a /// visible result. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let write_bytes_ext = rust::import("byteorder", "WriteBytesExt").with_alias("_"); /// /// let tokens = quote!($(register(write_bytes_ext))); /// /// assert_eq!("use byteorder::WriteBytesExt as _;\n", tokens.to_file_string()?); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn register(&mut self, tokens: T) where T: Register, { tokens.register(self); } /// Check if tokens contain no items. /// /// ``` /// use genco::prelude::*; /// /// let tokens: Tokens<()> = quote!(); /// /// assert!(tokens.is_empty()); /// ``` pub fn is_empty(&self) -> bool { self.items.is_empty() } /// Add a single spacing to the token stream. /// /// Note that due to structural guarantees two consequent spaces may not /// follow each other in the same token stream. /// /// A space operation has no effect unless it's followed by a non-whitespace /// token. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let mut tokens = Tokens::<()>::new(); /// /// tokens.space(); /// tokens.append("hello"); /// tokens.space(); /// tokens.space(); // Note: ignored /// tokens.append("world"); /// tokens.space(); /// /// assert_eq!( /// vec![ /// " hello world", /// ], /// tokens.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn space(&mut self) { if let Some(Item { kind: Kind::Space }) = self.items.last() { return; } self.items.push(Item::space()); } /// Add a single push operation. /// /// Push operations ensure that any following tokens are added to their own /// line. /// /// A push has no effect unless it's *preceeded* or *followed* by /// non-whitespace tokens. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let mut tokens = Tokens::<()>::new(); /// /// tokens.push(); /// tokens.append("hello"); /// tokens.push(); /// tokens.append("world"); /// tokens.push(); /// /// assert_eq!( /// vec![ /// "hello", /// "world" /// ], /// tokens.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn push(&mut self) { let item = loop { let Some(item) = self.items.pop() else { break None; }; match &item.kind { // NB: never reconfigure a line into a push. Kind::Line => { self.items.push(item); return; } Kind::Space | Kind::Push => continue, _ => break Some(item), } }; self.items.extend(item); self.items.push(Item::push()); } /// Add a single line operation. /// /// A line ensures that any following tokens have one line of separation /// between them and the preceeding tokens. /// /// A line has no effect unless it's *preceeded* and *followed* by /// non-whitespace tokens. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let mut tokens = Tokens::<()>::new(); /// /// tokens.line(); /// tokens.append("hello"); /// tokens.line(); /// tokens.append("world"); /// tokens.line(); /// /// assert_eq!( /// vec![ /// "hello", /// "", /// "world" /// ], /// tokens.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn line(&mut self) { let item = loop { let Some(item) = self.items.pop() else { break None; }; if matches!(item.kind, Kind::Line | Kind::Push) { continue; } break Some(item); }; self.items.extend(item); self.items.push(Item::line()); } /// Increase the indentation of the token stream. /// /// An indentation is a language-specific operation which adds whitespace to /// the beginning of a line preceeding any non-whitespace tokens. /// /// An indentation has no effect unless it's *followed* by non-whitespace /// tokens. It also acts like a [`push`], in that it will shift any tokens to /// a new line. /// /// [`push`]: Self::push /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let mut tokens = Tokens::<()>::new(); /// /// tokens.indent(); /// tokens.append("hello"); /// tokens.indent(); /// tokens.append("world"); /// tokens.indent(); /// tokens.append("😀"); /// /// assert_eq!( /// vec![ /// " hello", /// " world", /// " 😀", /// ], /// tokens.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn indent(&mut self) { self.indentation(1); } /// Decrease the indentation of the token stream. /// /// An indentation is a language-specific operation which adds whitespace to /// the beginning of a line preceeding any non-whitespace tokens. /// /// An indentation has no effect unless it's *followed* by non-whitespace /// tokens. It also acts like a [`push`], in that it will shift any tokens to /// a new line. /// /// Indentation can never go below zero, and will just be ignored if that /// were to happen. However, negative indentation is stored in the token /// stream, so any negative indentation in place will have to be countered /// before indentation starts again. /// /// [`push`]: Self::push /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let mut tokens = Tokens::<()>::new(); /// /// tokens.indent(); /// tokens.append("hello"); /// tokens.unindent(); /// tokens.append("world"); /// tokens.unindent(); /// tokens.append("😀"); /// tokens.indent(); /// tokens.append("😁"); /// tokens.indent(); /// tokens.append("😂"); /// /// assert_eq!( /// vec![ /// " hello", /// "world", /// "😀", /// "😁", /// " 😂", /// ], /// tokens.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn unindent(&mut self) { self.indentation(-1); } /// Formatting function for token streams that gives full control over the /// formatting environment. /// /// The configurations and `format` arguments will be provided to all /// registered language items as well, and can be used to customize /// formatting through [LangItem::format()]. /// /// The `format` argument is primarily used internally by /// [Lang::format_file] to provide intermediate state that can be affect how /// language items are formatter. So formatting something as a file might /// yield different results than using this raw formatting function. /// /// Available formatters: /// /// * [fmt::VecWriter] - To write result into a vector. /// * [fmt::FmtWriter] - To write the result into something implementing /// [fmt::Write][std::fmt::Write]. /// * [fmt::IoWriter]- To write the result into something implementing /// [io::Write][std::io::Write]. /// /// # Examples /// /// ```,no_run /// use genco::prelude::*; /// use genco::fmt; /// /// let map = rust::import("std::collections", "HashMap"); /// /// let tokens: rust::Tokens = quote! { /// let mut m = $map::new(); /// m.insert(1u32, 2u32); /// }; /// /// let stdout = std::io::stdout(); /// let mut w = fmt::IoWriter::new(stdout.lock()); /// /// let fmt = fmt::Config::from_lang::() /// .with_indentation(fmt::Indentation::Space(2)); /// let mut formatter = w.as_formatter(&fmt); /// let config = rust::Config::default(); /// /// // Default format state for Rust. /// let format = rust::Format::default(); /// /// tokens.format(&mut formatter, &config, &format)?; /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// /// [LangItem::format()]: crate::lang::LangItem::format() pub fn format( &self, out: &mut fmt::Formatter<'_>, config: &L::Config, format: &L::Format, ) -> fmt::Result { out.format_items::(&self.lang, &self.items, config, format) } /// Push a literal item to the stream. #[inline] pub(crate) fn literal(&mut self, lit: impl Into) { self.items.push(Item::literal(lit.into())); } /// Push an open quote item to the stream. #[inline] pub(crate) fn open_quote(&mut self, is_interpolated: bool) { self.items.push(Item::open_quote(is_interpolated)); } /// Push a close quote item to the stream. #[inline] pub(crate) fn close_quote(&mut self) { self.items.push(Item::close_quote()); } /// Add a language item directly. pub(crate) fn lang_item(&mut self, item: L::Item) { self.items.push(Item::lang(self.lang.len())); self.lang.push(item); } /// Register a language item directly. pub(crate) fn lang_item_register(&mut self, item: L::Item) { self.lang.push(item); } /// File formatting function for token streams that gives full control over the /// formatting environment. /// /// File formatting will render preambles like namespace declarations and /// imports. /// /// Available formatters: /// /// * [fmt::VecWriter] - To write result into a vector. /// * [fmt::FmtWriter] - To write the result into something implementing /// [fmt::Write][std::fmt::Write]. /// * [fmt::IoWriter]- To write the result into something implementing /// [io::Write][std::io::Write]. /// /// # Examples /// /// ```,no_run /// use genco::prelude::*; /// use genco::fmt; /// /// let map = rust::import("std::collections", "HashMap"); /// /// let tokens: rust::Tokens = quote! { /// let mut m = $map::new(); /// m.insert(1u32, 2u32); /// }; /// /// let stdout = std::io::stdout(); /// let mut w = fmt::IoWriter::new(stdout.lock()); /// /// let fmt = fmt::Config::from_lang::() /// .with_indentation(fmt::Indentation::Space(2)); /// let mut formatter = w.as_formatter(&fmt); /// let config = rust::Config::default(); /// /// tokens.format_file(&mut formatter, &config)?; /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn format_file(&self, out: &mut fmt::Formatter<'_>, config: &L::Config) -> fmt::Result { L::format_file(self, out, config)?; out.write_trailing_line()?; Ok(()) } /// Internal function to modify the indentation of the token stream. fn indentation(&mut self, mut n: i16) { let item = loop { // flush all whitespace preceeding the indentation change. let Some(item) = self.items.pop() else { break None; }; match &item.kind { Kind::Push => continue, Kind::Space => continue, Kind::Line => continue, Kind::Indentation(u) => n += u, _ => break Some(item), } }; self.items.extend(item); if n != 0 { self.items.push(Item::new(Kind::Indentation(n))); } } } impl Default for Tokens where L: Lang, { fn default() -> Self { Self::new() } } impl Tokens where L: LangSupportsEval, { /// Helper function to determine if the token stream supports evaluation at compile time. #[doc(hidden)] #[inline] pub fn lang_supports_eval(&self) {} } impl Tokens where L: Lang, L::Config: Default, { /// Format the token stream as a file for the given target language to a /// string using the default configuration. /// /// This is a shorthand to using [FmtWriter][fmt::FmtWriter] directly in /// combination with [format][Self::format_file]. /// /// This function will render imports. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// use genco::fmt; /// /// let map = rust::import("std::collections", "HashMap"); /// /// let tokens: rust::Tokens = quote! { /// let mut m = $map::new(); /// m.insert(1u32, 2u32); /// }; /// /// assert_eq!( /// "use std::collections::HashMap;\n\nlet mut m = HashMap::new();\nm.insert(1u32, 2u32);\n", /// tokens.to_file_string()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn to_file_string(&self) -> fmt::Result { let mut w = fmt::FmtWriter::new(String::new()); let fmt = fmt::Config::from_lang::(); let mut formatter = w.as_formatter(&fmt); let config = L::Config::default(); self.format_file(&mut formatter, &config)?; Ok(w.into_inner()) } /// Format only the current token stream as a string using the default /// configuration. /// /// This is a shorthand to using [FmtWriter][fmt::FmtWriter] directly in /// combination with [format][Self::format]. /// /// This function _will not_ render imports. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let map = rust::import("std::collections", "HashMap"); /// /// let tokens: rust::Tokens = quote! { /// let mut m = $map::new(); /// m.insert(1u32, 2u32); /// }; /// /// assert_eq!( /// "let mut m = HashMap::new();\nm.insert(1u32, 2u32);", /// tokens.to_string()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn to_string(&self) -> fmt::Result { let mut w = fmt::FmtWriter::new(String::new()); let fmt = fmt::Config::from_lang::(); let mut formatter = w.as_formatter(&fmt); let config = L::Config::default(); let format = L::Format::default(); self.format(&mut formatter, &config, &format)?; Ok(w.into_inner()) } /// Format tokens into a vector, where each entry equals a line in the /// resulting file using the default configuration. /// /// This is a shorthand to using [VecWriter][fmt::VecWriter] directly in /// combination with [format][Self::format_file]. /// /// This function will render imports. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let map = rust::import("std::collections", "HashMap"); /// /// let tokens: rust::Tokens = quote! { /// let mut m = $map::new(); /// m.insert(1u32, 2u32); /// }; /// /// assert_eq!( /// vec![ /// "use std::collections::HashMap;", /// "", /// "let mut m = HashMap::new();", /// "m.insert(1u32, 2u32);" /// ], /// tokens.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` /// /// # Example with Python indentation /// /// ``` /// use genco::prelude::*; /// /// let tokens: python::Tokens = quote! { /// def foo(): /// pass /// /// def bar(): /// pass /// }; /// /// assert_eq!( /// vec![ /// "def foo():", /// " pass", /// "", /// "def bar():", /// " pass", /// ], /// tokens.to_file_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn to_file_vec(&self) -> fmt::Result> { let mut w = fmt::VecWriter::new(); let fmt = fmt::Config::from_lang::(); let mut formatter = w.as_formatter(&fmt); let config = L::Config::default(); self.format_file(&mut formatter, &config)?; Ok(w.into_vec()) } /// Helper function to format tokens into a vector, where each entry equals /// a line using the default configuration. /// /// This is a shorthand to using [VecWriter][fmt::VecWriter] directly in /// combination with [format][Self::format]. /// /// This function _will not_ render imports. /// /// # Examples /// /// ``` /// use genco::prelude::*; /// /// let map = rust::import("std::collections", "HashMap"); /// /// let tokens: rust::Tokens = quote! { /// let mut m = $map::new(); /// m.insert(1u32, 2u32); /// }; /// /// assert_eq!( /// vec![ /// "let mut m = HashMap::new();", /// "m.insert(1u32, 2u32);" /// ], /// tokens.to_vec()? /// ); /// # Ok::<_, genco::fmt::Error>(()) /// ``` pub fn to_vec(&self) -> fmt::Result> { let mut w = fmt::VecWriter::new(); let fmt = fmt::Config::from_lang::(); let mut formatter = w.as_formatter(&fmt); let config = L::Config::default(); let format = L::Format::default(); self.format(&mut formatter, &config, &format)?; Ok(w.into_vec()) } } impl PartialEq> for Tokens where L: Lang, L::Item: PartialEq, { #[inline] fn eq(&self, other: &Tokens) -> bool { self.items == other.items } } impl PartialEq> for Tokens where L: Lang, { #[inline] fn eq(&self, other: &Vec) -> bool { self.iter().eq(other.iter()) } } impl PartialEq> for Vec where L: Lang, { #[inline] fn eq(&self, other: &Tokens) -> bool { self.iter().eq(other.iter()) } } impl PartialEq<[Item]> for Tokens where L: Lang, { #[inline] fn eq(&self, other: &[Item]) -> bool { self.iter().eq(other) } } impl PartialEq<[Item; N]> for Tokens where L: Lang, { #[inline] fn eq(&self, other: &[Item; N]) -> bool { self == &other[..] } } impl PartialEq> for [Item] where L: Lang, { #[inline] fn eq(&self, other: &Tokens) -> bool { self.iter().eq(other.iter()) } } impl Eq for Tokens where L: Lang, L::Item: Eq, { } impl PartialOrd> for Tokens where L: Lang, L::Item: PartialOrd, { #[inline] fn partial_cmp(&self, other: &Tokens) -> Option { self.items.iter().partial_cmp(other.items.iter()) } } impl Ord for Tokens where L: Lang, L::Item: Ord, { #[inline] fn cmp(&self, other: &Tokens) -> Ordering { self.items.iter().cmp(other.items.iter()) } } /// An item reference, where language items are translated into a reference to /// them. #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ItemRef<'a, T> { kind: ItemRefKind<'a, T>, } impl core::fmt::Debug for ItemRef<'_, T> where T: core::fmt::Debug, { #[inline] fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.kind.fmt(f) } } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] enum ItemRefKind<'a, T> { Lang(&'a T), Item(&'a Item), } impl PartialEq for ItemRef<'_, T> { #[inline] fn eq(&self, other: &Item) -> bool { match self.kind { ItemRefKind::Lang(..) => false, ItemRefKind::Item(item) => *item == *other, } } } impl PartialEq<&Item> for ItemRef<'_, T> { #[inline] fn eq(&self, other: &&Item) -> bool { match self.kind { ItemRefKind::Lang(..) => false, ItemRefKind::Item(item) => *item == **other, } } } impl PartialEq> for Item { #[inline] fn eq(&self, other: &ItemRef<'_, T>) -> bool { *other == *self } } impl PartialEq> for &Item { #[inline] fn eq(&self, other: &ItemRef<'_, T>) -> bool { *other == **self } } /// Iterator over [`Tokens`]. /// /// This is created using [`Tokens::iter()`]. pub struct Iter<'a, T> { lang: &'a [T], iter: slice::Iter<'a, Item>, } impl<'a, T> Iterator for Iter<'a, T> { type Item = ItemRef<'a, T>; #[inline] fn next(&mut self) -> Option { let item = self.iter.next()?; let kind = if let Kind::Lang(n) = item.kind { ItemRefKind::Lang(self.lang.get(n)?) } else { ItemRefKind::Item(item) }; Some(ItemRef { kind }) } #[inline] fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } } impl<'a, L> IntoIterator for &'a Tokens where L: Lang, { type Item = ItemRef<'a, L::Item>; type IntoIter = Iter<'a, L::Item>; #[inline] fn into_iter(self) -> Self::IntoIter { self.iter() } } impl core::fmt::Debug for Tokens where L: Lang, L::Item: core::fmt::Debug, { #[inline] fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_list().entries(&self.items).finish() } } impl Clone for Tokens where L: Lang, L::Item: Clone, { #[inline] fn clone(&self) -> Self { Self { items: self.items.clone(), lang: self.lang.clone(), } } } impl hash::Hash for Tokens where L: Lang, L::Item: hash::Hash, { #[inline] fn hash(&self, state: &mut H) where H: hash::Hasher, { self.items.hash(state); self.lang.hash(state); } } /// An iterator over language-specific imported items. /// /// Constructed using the [`Tokens::iter_lang`] method. pub struct IterLang<'a, L> where L: Lang, { lang: slice::Iter<'a, L::Item>, } impl<'a, L> Iterator for IterLang<'a, L> where L: Lang, { type Item = &'a L::Item; #[inline] fn next(&mut self) -> Option { self.lang.next() } } #[cfg(test)] mod tests { use core::fmt::Write as _; use alloc::string::String; use alloc::vec; use alloc::vec::Vec; use crate as genco; use crate::fmt; use crate::{quote, Tokens}; /// Own little custom language for this test. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] struct Import(u32); impl_lang! { Lang { type Config = (); type Format = (); type Item = Any; } Import(Import) { fn format(&self, out: &mut fmt::Formatter<'_>, _: &(), _: &()) -> fmt::Result { write!(out, "{}", self.0) } } } #[test] fn test_walk_custom() { let toks: Tokens = quote! { 1:1 $(Import(1)) 1:2 bar 2:1 2:2 $(quote!(3:1 3:2)) $(Import(2)) $(String::from("nope")) }; let mut output: Vec<_> = toks.iter_lang().cloned().collect(); output.sort(); let expected: Vec = vec![Import(1).into(), Import(2).into()]; assert_eq!(expected, output); } } ================================================ FILE: tests/test_indentation_rules.rs ================================================ use genco::prelude::*; #[test] fn test_indentation_rules() -> genco::fmt::Result { let rule1: Tokens = quote!(fn test()); let rule2: Tokens = quote! { fn test() { println!("Hello..."); println!("... World!"); } }; let rule3: Tokens = quote! { fn test() { println!("Hello..."); println!("... World!"); } }; assert_eq!("fn test()", rule1.to_string()?); assert_eq!( "fn test() {\n println!(\"Hello...\");\n\n println!(\"... World!\");\n}", rule2.to_string()? ); assert_eq!( "fn test() {\n println!(\"Hello...\");\n println!(\"... World!\");\n}", rule3.to_string()? ); Ok(()) } ================================================ FILE: tests/test_option.rs ================================================ use genco::prelude::*; #[test] fn test_option() -> genco::fmt::Result { let test1 = Some(quote!(println!("{}", $(quoted("one"))))); let test2 = None::; let tokens: rust::Tokens = quote! { fn test_option() -> u32 { $test1 $test2 42 } }; assert_eq!( vec![ "fn test_option() -> u32 {", " println!(\"{}\", \"one\")", "", " 42", "}" ], tokens.to_file_vec()? ); Ok(()) } ================================================ FILE: tests/test_quote.rs ================================================ use genco::prelude::*; #[test] fn test_quote() -> genco::fmt::Result { let test = quoted("one"); let tokens: rust::Tokens = quote! { fn test() -> u32 { println!("{}", $(test)); 42 } }; assert_eq!( "fn test() -> u32 {\n println!(\"{}\", \"one\");\n\n 42\n}", tokens.to_string()? ); let tokens: rust::Tokens = quote! { fn test() -> u32 { println!("{}", $(quoted("two"))); 42 } }; assert_eq!( "fn test() -> u32 {\n println!(\"{}\", \"two\");\n\n 42\n}", tokens.to_string()? ); let tokens: rust::Tokens = quote! { fn test() -> u32 { println!("{}", $$(quoted("two"))); 42 } }; assert_eq!( "fn test() -> u32 {\n println!(\"{}\", $(quoted(\"two\")));\n\n 42\n}", tokens.to_string()? ); Ok(()) } #[test] fn test_tight_quote() -> genco::fmt::Result { let a = "foo"; let b = "bar"; let c = "baz"; let tokens: rust::Tokens = quote!($(a)$(b)$(c)); assert_eq!("foobarbaz", tokens.to_string()?); Ok(()) } #[test] fn test_escape() -> genco::fmt::Result { let tokens: rust::Tokens = quote!($$$$ $$ $$$$ $$$$ $$ $$ $$[test]); assert_eq!("$$ $ $$ $$ $ $ $[test]", tokens.to_string()?); Ok(()) } #[test] fn test_scope() -> genco::fmt::Result { let tokens: rust::Tokens = quote! { // Nested factory. $(ref tokens { quote_in!(*tokens => fn test() -> u32 { 42 }); }) }; assert_eq!("fn test() -> u32 { 42 }", tokens.to_string()?); Ok(()) } ================================================ FILE: tests/test_quote_in.rs ================================================ use genco::prelude::*; /// basic smoketests. #[test] fn test_quote_in() -> genco::fmt::Result { let mut tokens = rust::Tokens::new(); quote_in!(tokens => fn hello() -> u32 { 42 }); assert_eq!("fn hello() -> u32 { 42 }", tokens.to_string()?); Ok(()) } /// quote_in! must expand into a unit expression. #[test] fn test_quote_into_unit() -> genco::fmt::Result { let tokens = &mut go::Tokens::new(); quote_in!(*tokens => uint32); assert_eq!("uint32", tokens.to_string()?); Ok(()) } ================================================ FILE: tests/test_quote_simple_expression.rs ================================================ use genco::prelude::*; #[test] fn test_quote_simple_expression() -> genco::fmt::Result { let tokens: Tokens = quote!(fn $("test")()); assert_eq!("fn test()", tokens.to_string()?); let expr = "e!(test); let tokens: Tokens = quote!(fn $expr()); assert_eq!("fn test()", tokens.to_string()?); let tokens: Tokens = quote!(fn $(expr)()); assert_eq!("fn test()", tokens.to_string()?); // inline macro expansion. let tokens: Tokens = quote!(fn $(quote!(test))()); assert_eq!("fn test()", tokens.to_string()?); Ok(()) } ================================================ FILE: tests/test_register.rs ================================================ use genco::prelude::*; #[test] fn test_register() -> genco::fmt::Result { let import = rust::import("std::iter", "FromIterator").with_alias("_"); let tokens: Tokens = quote! { $(register(import)) // additional lines are ignored! fn test() -> u32 { 42 } }; println!("{tokens:?}"); assert_eq!( vec![ "use std::iter::FromIterator as _;", "", "fn test() -> u32 {", " 42", "}" ], tokens.to_file_vec()? ); Ok(()) } ================================================ FILE: tests/test_string.rs ================================================ use genco::prelude::*; #[test] fn test_quoted() -> genco::fmt::Result { let t: dart::Tokens = quote!($[str](Hello $($(quoted("World"))))); assert_eq!("\"Hello ${\"World\"}\"", t.to_string()?); let t: dart::Tokens = quote!($[str](Hello "World")); assert_eq!("\"Hello \\\"World\\\"\"", t.to_string()?); let t: dart::Tokens = quote!($[str](Hello $(World))); assert_eq!("\"Hello $World\"", t.to_string()?); let t: js::Tokens = quote!($[str](Hello $(World))); assert_eq!("`Hello ${World}`", t.to_string()?); Ok(()) } #[test] fn test_string_in_string_in() -> genco::fmt::Result { let t: dart::Tokens = quote!($[str](Hello $($[str]($($[str](World)))))); assert_eq!("\"Hello ${\"${\"World\"}\"}\"", t.to_string()?); let t: js::Tokens = quote!($[str](Hello $($[str]($($[str](World)))))); assert_eq!("`Hello ${`${\"World\"}`}`", t.to_string()?); Ok(()) } ================================================ FILE: tests/test_token_gen.rs ================================================ //! Test to assert that the tokens generated are equivalent. use genco::fmt; use genco::prelude::*; use genco::tokens::ItemStr; use genco::__priv::{indentation, line, literal, push, space, static_}; #[test] fn test_token_gen() { let tokens: rust::Tokens = quote! { foo bar baz $(ref tokens => quote_in! { *tokens => hello }) out? }; assert_eq! { vec![ static_("foo"), push(), static_("bar"), push(), static_("baz"), indentation(1), static_("hello"), indentation(-1), static_("out?") ], tokens, } } #[test] fn test_iterator_gen() { let tokens: rust::Tokens = quote! { $(ref t => for n in 0..3 { t.push(); t.append(n); }) }; assert_eq! { vec![ push(), literal("0".into()), push(), literal("1".into()), push(), literal("2".into()), ], tokens, }; let tokens: rust::Tokens = quote! { $(ref t { for n in 0..3 { t.push(); t.append(n); } }) }; assert_eq! { vec![ push(), literal("0".into()), push(), literal("1".into()), push(), literal("2".into()), ], tokens, }; } #[test] fn test_tricky_continuation() { let mut output = rust::Tokens::new(); let bar = ItemStr::static_("bar"); quote_in! { output => foo, $(ref output { output.append(&bar); output.append(static_(",")); output.space(); })baz biz }; assert_eq! { output, vec![ static_("foo,"), space(), static_("bar"), static_(","), space(), static_("baz"), push(), static_("biz"), ] }; } #[test] fn test_indentation() { // Bug: Since we carry the span of out, the line after counts as unindented. // // These two should be identical: let mut a = rust::Tokens::new(); quote_in! { a => a b c }; assert_eq! { a, vec![ static_("a"), indentation(1), static_("b"), indentation(-1), static_("c") ] }; let mut b = rust::Tokens::new(); quote_in! { b => a b c }; assert_eq! { b, vec![ static_("a"), indentation(1), static_("b"), indentation(-1), static_("c") ] }; } #[test] fn test_repeat() { let tokens: rust::Tokens = quote! { foo $(for (a, b) in (0..3).zip(3..6) => $a $b) }; assert_eq! { vec![ static_("foo"), space(), literal("0".into()), space(), literal("3".into()), literal("1".into()), space(), literal("4".into()), literal("2".into()), space(), literal("5".into()) ], tokens, }; let tokens: rust::Tokens = quote! { foo $(for (a, b) in (0..3).zip(3..6) { $a $b }) }; assert_eq! { vec![ static_("foo"), space(), literal("0".into()), space(), literal("3".into()), literal("1".into()), space(), literal("4".into()), literal("2".into()), space(), literal("5".into()) ], tokens, }; } #[test] fn test_tight_quote() { let output: rust::Tokens = quote! { You are:$("fine") }; assert_eq! { output, vec![ static_("You"), space(), static_("are:fine"), ] }; } #[test] fn test_tight_repitition() { let output: rust::Tokens = quote! { You are: $(for v in 0..3 join (, ) => $v) }; assert_eq! { output, vec![ static_("You"), space(), static_("are:"), space(), literal("0".into()), static_(","), space(), literal("1".into()), static_(","), space(), literal("2".into()), ] }; } #[test] fn test_if() { let a = true; let b = false; let output: rust::Tokens = quote! { $(if a => foo) $(if a { foo2 }) $(if b { bar }) $(if b => bar2) $(if a => baz) $(if a { baz2 }) $(if b { not_biz } else { biz }) }; assert_eq! { output, vec![ static_("foo"), push(), static_("foo2"), push(), static_("baz"), push(), static_("baz2"), push(), static_("biz"), ] }; } #[test] fn test_match() { enum Alt { A, B, } fn test(alt: Alt) -> rust::Tokens { quote! { $(match alt { Alt::A => a, Alt::B => b }) } } fn test2(alt: Alt) -> rust::Tokens { quote! { $(match alt { Alt::A => { a }, Alt::B => { b } }) } } fn test2_cond(alt: Alt, cond: bool) -> rust::Tokens { quote! { $(match alt { Alt::A if cond => { a }, _ => { b } }) } } assert_eq! { test(Alt::A), vec![static_("a")] }; assert_eq! { test(Alt::B), vec![static_("b")] }; assert_eq! { test2(Alt::A), vec![static_("a")] }; assert_eq! { test2(Alt::B), vec![static_("b")] }; assert_eq! { test2_cond(Alt::A, true), vec![static_("a")] }; assert_eq! { test2_cond(Alt::A, false), vec![static_("b")] }; } #[test] fn test_let() { let tokens: rust::Tokens = quote! { $(let x = 1) $x }; assert_eq! { tokens, vec![space(), literal("1".into())] }; // Tuple binding let tokens: rust::Tokens = quote! { $(let (a, b) = ("c", "d")) $a, $b }; assert_eq! { tokens, vec![ space(), literal("c".into()), static_(","), space(), literal("d".into()) ] }; // Function call in expression let x = "bar"; fn baz(s: &str) -> String { format!("{s}baz") } let tokens: rust::Tokens = quote! { $(let a = baz(x)) $a }; assert_eq! { tokens, vec![space(), literal("barbaz".into())] }; // Complex expression let x = 2; let tokens: rust::Tokens = quote! { $(let even = if x % 2 == 0 { "even" } else { "odd" }) $even }; assert_eq! { tokens, vec![space(), literal("even".into())] }; } #[test] fn test_mutable_let() { let path = "A.B.C.D"; let tokens: Tokens<()> = quote! { $(let mut items = path.split('.')) $(if let Some(first) = items.next() => First is $first ) $(if let Some(second) = items.next() => Second is $second ) }; assert_eq!( tokens, vec![ push(), static_("First"), space(), static_("is"), space(), literal("A".into()), push(), static_("Second"), space(), static_("is"), space(), literal("B".into()) ] ); } #[test] fn test_empty_loop_whitespace() { // Bug: This should generate two commas. But did generate a space following // it! let tokens: rust::Tokens = quote! { $(for _ in 0..3 join(,) =>) }; assert_eq! { tokens, vec![static_(","), static_(",")] }; let tokens: rust::Tokens = quote! { $(for _ in 0..3 join( ,) =>) }; assert_eq! { tokens, vec![space(), static_(","), space(), static_(",")] }; let tokens: rust::Tokens = quote! { $(for _ in 0..3 join(, ) =>) }; assert_eq! { tokens, vec![static_(","), space(), static_(","), space()] }; let tokens: rust::Tokens = quote! { $(for _ in 0..3 join( , ) =>) }; assert_eq! { tokens, vec![space(), static_(","), space(), static_(","), space()] }; } #[test] fn test_indentation_empty() { let tokens: rust::Tokens = quote! { a $(for _ in 0..3 =>) b }; assert_eq! { tokens, vec![ static_("a"), static_("b") ] }; let tokens: rust::Tokens = quote! { a $(if false {}) b }; assert_eq! { tokens, vec![ static_("a"), static_("b") ] }; let tokens: rust::Tokens = quote! { a $(ref _tokens =>) b }; assert_eq! { tokens, vec![ static_("a"), static_("b") ] }; } #[test] fn test_indentation_management() { let tokens: rust::Tokens = quote! { if a: if b: foo else: c }; assert_eq! { vec![ static_("if"), space(), static_("a:"), indentation(1), static_("if"), space(), static_("b:"), indentation(1), static_("foo"), indentation(-2), static_("else:"), indentation(1), static_("c"), indentation(-1) ], tokens, }; let tokens: rust::Tokens = quote! { if a: if b: foo $(if false => bar) $(if true => baz) }; assert_eq! { vec![ static_("if"), space(), static_("a:"), indentation(1), static_("if"), space(), static_("b:"), indentation(1), static_("foo"), indentation(-2), line(), static_("baz"), ], tokens, }; } #[test] fn test_indentation_management2() -> fmt::Result { let tokens: python::Tokens = quote! { def foo(): pass def bar(): pass }; assert_eq! { vec![ static_("def"), space(), static_("foo():"), indentation(1), static_("pass"), indentation(-1), line(), static_("def"), space(), static_("bar():"), indentation(1), static_("pass"), indentation(-1) ], tokens, }; assert_eq!( vec!["def foo():", " pass", "", "def bar():", " pass",], tokens.to_file_vec()? ); Ok(()) } #[test] fn test_lines() -> fmt::Result { let mut tokens: rust::Tokens = quote! { fn foo() { } }; tokens.line(); quote_in! { tokens => $(if false =>) fn bar() { } }; assert_eq! { vec![ static_("fn"), space(), static_("foo()"), space(), static_("{"), push(), static_("}"), line(), static_("fn"), space(), static_("bar()"), space(), static_("{"), push(), static_("}") ], tokens.clone(), }; assert_eq!( vec!["fn foo() {", "}", "", "fn bar() {", "}",], tokens.to_file_vec()? ); Ok(()) }