Showing preview only (363K chars total). Download the full file or copy to clipboard to get everything.
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 <udoprog@tedro.se>"]
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
[<img alt="github" src="https://img.shields.io/badge/github-udoprog/genco-8da0cb?style=for-the-badge&logo=github" height="20">](https://github.com/udoprog/genco)
[<img alt="crates.io" src="https://img.shields.io/crates/v/genco.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/genco)
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-genco-66c2a5?style=for-the-badge&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K" height="20">](https://docs.rs/genco)
[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/udoprog/genco/ci.yml?branch=main&style=for-the-badge" height="20">](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.
<br>
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
<br>
## Supported Languages
The following are languages which have built-in support in genco.
* [🦀 <b>Rust</b>][rust]<br>
<small>[Example][rust-example]</small>
* [☕ <b>Java</b>][java]<br>
<small>[Example][java-example]</small>
* [🎼 <b>C#</b>][c#]<br>
<small>[Example][c#-example]</small>
* [🐿️ <b>Go</b>][go]<br>
<small>[Example][go-example]</small>
* [🎯 <b>Dart</b>][dart]<br>
<small>[Example][dart-example]</small>
* [🌐 <b>JavaScript</b>][js]<br>
<small>[Example][js-example]</small>
* [🇨 <b>C</b>][c]<br>
<small>[Example][c-example]</small>
* [🐍 <b>Python</b>][python]<br>
<small>[Example][python-example]</small>
<small>Is your favorite language missing? <b>[Open an issue!]</b></small>
You can run one of the examples by:
```bash
cargo +nightly run --example rust
```
<br>
## 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);
}
```
<br>
[`quote_in!`]: <https://docs.rs/genco/latest/genco/macro.quote_in.html>
[`quote!`]: <https://docs.rs/genco/latest/genco/macro.quote.html>
[`quoted()`]: <https://docs.rs/genco/latest/genco/tokens/fn.quoted.html>
[c-example]: <https://github.com/udoprog/genco/blob/master/examples/c.rs>
[c]: <https://docs.rs/genco/latest/genco/lang/c/index.html>
[c#-example]: <https://github.com/udoprog/genco/blob/master/examples/csharp.rs>
[c#]: <https://docs.rs/genco/latest/genco/lang/csharp/index.html>
[dart-example]: <https://github.com/udoprog/genco/blob/master/examples/dart.rs>
[dart]: <https://docs.rs/genco/latest/genco/lang/dart/index.html>
[go-example]: <https://github.com/udoprog/genco/blob/master/examples/go.rs>
[go]: <https://docs.rs/genco/latest/genco/lang/go/index.html>
[impl_lang!]: <https://docs.rs/genco/latest/genco/macro.impl_lang.html>
[import statements]: <https://docs.rs/genco/latest/genco/macro.quote.html#imports>
[indentation is meaningful]: <https://docs.python.org/3/faq/design.html#why-does-python-use-indentation-for-grouping-of-statements>
[interpolate]: <https://docs.rs/genco/latest/genco/macro.quote.html#quoted-string-interpolation>
[java-example]: <https://github.com/udoprog/genco/blob/master/examples/java.rs>
[java]: <https://docs.rs/genco/latest/genco/lang/java/index.html>
[js-example]: <https://github.com/udoprog/genco/blob/master/examples/js.rs>
[js]: <https://docs.rs/genco/latest/genco/lang/js/index.html>
[Open an issue!]: <https://github.com/udoprog/genco/issues/new>
[python-example]: <https://github.com/udoprog/genco/blob/master/examples/python.rs>
[python]: <https://docs.rs/genco/latest/genco/lang/python/index.html>
[quote strings]: <https://docs.rs/genco/latest/genco/macro.quote.html#string-quoting>
[rust-example]: <https://github.com/udoprog/genco/blob/master/examples/rust.rs>
[rust]: <https://docs.rs/genco/latest/genco/lang/rust/index.html>
[solve namespace conflicts]: <https://docs.rs/genco/latest/genco/lang/csharp/fn.import.html>
[token streams]: <https://docs.rs/genco/latest/genco/tokens/struct.Tokens.html>
[whitespace detection]: <https://docs.rs/genco/latest/genco/macro.quote.html#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::<C>();
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::<Csharp>().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<int, String> map() {
return new $hash_map<int, String>();
}
};
let stdout = std::io::stdout();
let mut w = fmt::IoWriter::new(stdout.lock());
let fmt = fmt::Config::from_lang::<Dart>();
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::<Go>();
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::<Java>().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 (
<div className="component-app">
<$display value={this.state.next || this.state.total || "0"} />
<$button_panel clickHandler={this.handleClick} />
</div>
);
}
}
};
let stdout = std::io::stdout();
let mut w = fmt::IoWriter::new(stdout.lock());
let fmt = fmt::Config::from_lang::<JavaScript>();
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::<kotlin::Kotlin>()
.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::<Nix>();
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::<Python>();
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::<Rust>().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 <udoprog@tedro.se>"]
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
[<img alt="github" src="https://img.shields.io/badge/github-udoprog/genco-8da0cb?style=for-the-badge&logo=github" height="20">](https://github.com/udoprog/genco)
[<img alt="crates.io" src="https://img.shields.io/crates/v/genco-macros.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/genco-macros)
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-genco--macros-66c2a5?style=for-the-badge&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K" height="20">](https://docs.rs/genco-macros)
[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/udoprog/genco/ci.yml?branch=main&style=for-the-badge" height="20">](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<RustcVersion> {
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<syn::Attribute>,
pub(crate) pattern: syn::Pat,
pub(crate) condition: Option<syn::Expr>,
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<Self> {
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<syn::Ident>,
content: TokenStream,
},
/// A loop repetition.
Loop {
/// The pattern being bound.
pattern: Box<syn::Pat>,
/// Expression being bound to an iterator.
expr: Box<syn::Expr>,
/// If a join is specified, this is the token stream used to join.
/// It's evaluated in the loop scope.
join: Option<TokenStream>,
/// 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<TokenStream>,
},
Let {
/// Variable name (or names for a tuple)
name: syn::Pat,
/// Expression
expr: syn::Expr,
},
Match {
condition: syn::Expr,
arms: Vec<MatchArm>,
},
}
================================================
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<LineColumn>,
/// 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<LineColumn>,
/// TODO: make private.
item_buffer: StaticBuffer<'a>,
/// The token stream we are constructing.
output: TokenStream,
/// Currently stored cursor.
last: Option<Cursor>,
/// Which column the last line start on.
last_start_column: Option<usize>,
/// Indentation columns.
indents: Vec<(usize, Option<Span>)>,
/// 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<LineColumn>,
span_end: Option<LineColumn>,
) -> 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<syn::Ident>, 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<TokenStream>,
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<TokenStream>,
) {
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<MatchArm>) {
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<LineColumn> {
// 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<Span>,
) -> 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<Span>,
) -> 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<Self> {
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<Self> {
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<Self> {
None
}
#[cfg(not(has_proc_macro_span))]
pub(crate) fn end(_: Span) -> Option<Self> {
None
}
}
#[derive(Default)]
pub(crate) struct Buf {
buf: RefCell<String>,
}
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<Cursor> {
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<LineColumn> {
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<LineColumn> {
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<Cursor> {
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
================================================
//! [<img alt="github" src="https://img.shields.io/badge/github-udoprog/genco-8da0cb?style=for-the-badge&logo=github" height="20">](https://github.com/udoprog/genco)
//! [<img alt="crates.io" src="https://img.shields.io/crates/v/genco-macros.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/genco-macros)
//! [<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-genco--macros-66c2a5?style=for-the-badge&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K" height="20">](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<LineColumn>,
/// 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<LineColumn>,
/// 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<Self> {
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 <condition> { <quoted> } [else { <quoted> }]`.
fn parse_condition(&self, input: ParseStream) -> Result<(Requirements, Ast)> {
input.parse::<Token![if]>()?;
let condition = syn::Expr::parse_without_eager_brace(input)?;
if input.peek(Token![=>]) {
input.parse::<Token![=>]>()?;
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::<Token![else]>()?;
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 <expr> in <iter> [join (<quoted>)] => <quoted>`.
fn parse_loop(&self, input: ParseStream) -> Result<(Requirements, Ast)> {
syn::custom_keyword!(join);
let mut req = Requirements::default();
input.parse::<Token![for]>()?;
let pattern = syn::Pat::parse_single(input)?;
input.parse::<Token![in]>()?;
let expr = syn::Expr::parse_without_eager_brace(input)?;
let join = if input.peek(join) {
input.parse::<join>()?;
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::<Token![=>]>()?;
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::<Token![match]>()?;
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::<Token![if]>()?;
let condition = body.parse::<syn::Expr>()?;
Some(condition)
} else {
None
};
body.parse::<Token![=>]>()?;
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::<Token![,]>()?;
}
}
Ok((req, Ast::Match { condition, arms }))
}
fn parse_let(&self, input: ParseStream) -> Result<(Requirements, Ast)> {
input.parse::<Token![let]>()?;
let req = Requirements::default();
let name = syn::Pat::parse_single(input)?;
input.parse::<Token![=]>()?;
let expr = syn::Expr::parse_without_eager_brace(input)?;
let ast = Ast::Let { name, expr };
Ok((req, ast))
}
/// Parse evaluation: `[*]<binding> => <expr>`.
fn parse_scope(&self, input: ParseStream) -> Result<Ast> {
input.parse::<Token![ref]>()?;
let binding = if input.peek(Token![_]) {
input.parse::<Token![_]>()?;
None
} else {
Some(input.parse()?)
};
let content;
let content = if input.peek(token::Brace) {
syn::braced!(content in input);
&content
} else {
input.parse::<Token![=>]>()?;
input
};
Ok(Ast::Scope {
binding,
content: content.parse()?,
})
}
fn parse_expression(&mut self, encoder: &mut Encoder, input: ParseStream) -> Result<()> {
let start = input.parse::<Token![$]>()?.span();
// Single identifier without quoting.
if !input.peek(token::Paren) {
let ident = input.parse::<syn::Ident>()?;
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::<syn::LitStr>()?.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::<Token![$]>()?.spans;
let [b] = input.parse::<Token![$]>()?.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}](<content>)"),
));
}
(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::<syn::LitStr>()?;
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
/// $[<name>](<content>)
/// ```
///
/// The `(<content>)` 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<Option<(Name, Option<ParseBuffer<'a>>, [Span; 2])>> {
// Custom function call.
if !(input.peek(Token![$]) && input.peek2(token::Bracket)) {
return Ok(None);
}
let start = input.parse::<Token![$]>()?;
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::<syn::LitChar>()?;
Name::Char(c.value())
} else {
let ident = function.parse::<syn::Ident>()?;
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<Self> {
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<Self> {
// Input expression, assign to a variable.
let expr = input.parse::<syn::Expr>()?;
input.parse::<Token![=>]>()?;
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<bool>,
}
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<Option<LineColumn>>,
count: Cell<usize>,
buf: RefCell<String>,
stream: RefCell<TokenStream>,
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<LineColumn>,
) -> 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<LineColumn>,
) -> 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<LineColumn>,
) -> 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<LineColumn>,
) -> 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<LineColumn>,
) -> 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<LineColumn>, to: Option<LineColumn>) -> 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<LineColumn>,
to: Option<LineColumn>,
) -> 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<Self> {
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::<syn::Token![$]>()?;
let escape = input.parse::<syn::Token![$]>()?;
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::<syn::LitStr>()?;
encoder.encode_str(&s.value(), start.start, Some(end.end))?;
} else {
let expr = content.parse::<syn::Expr>()?;
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::<syn::Token![$]>()?;
let [start] = dollar.spans;
if !input.peek(token::Paren) {
let ident = input.parse::<syn::Ident>()?;
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::<TokenTree>()?;
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<bool> {
if content.parse::<Option<syn::LitStr>>()?.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::<Rust>()
/// .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<L>() -> 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<P>(&self) -> bool
where
P: Parse,
{
if let Some(item) = self.items.first() {
P::peek(item)
} else {
false
}
}
#[inline]
pub(super) fn peek1<P>(&self) -> bool
where
P: Parse,
{
if let Some(item) = self.items.get(1) {
P::peek(item)
} else {
false
}
}
#[inline]
pub(super) fn parse<P>(&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::<Rust>();
///
/// 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<W>
where
W: core::fmt::Write,
{
writer: W,
}
impl<W> FmtWriter<W>
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<W> core::fmt::Write for FmtWriter<W>
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<W> fmt::Write for FmtWriter<W>
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<usize> {
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<L>(
&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::<L>(&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<L>(
&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::<L>(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::Literal>() && cursor.peek1::<cursor::CloseEval>() {
let literal = cursor.parse::<cursor::Literal>()?;
L::string_eval_literal(self, config, format, literal)?;
cursor.parse::<cursor::CloseEval>()?;
} 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<L>(
&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::<L>(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<u8> implements std::io::Write
/// let mut w = fmt::IoWriter::new(Vec::<u8>::new());
///
/// let fmt = fmt::Config::from_lang::<Rust>();
/// 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<W>
where
W: io::Write,
{
writer: W,
}
impl<W> IoWriter<W>
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<W> core::fmt::Write for IoWriter<W>
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<W> fmt::Write for IoWriter<W>
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::<Rust>()
//! .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<T = ()> = core::result::Result<T, core::fmt::Error>;
/// 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::<Rust>();
///
/// 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<String>,
}
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<String> {
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<C>;
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 <stdio.h>`.
///
/// 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<ItemStr>, item: impl Into<ItemStr>) -> Import {
Import {
path: path.into(),
item: item.into(),
system: false,
}
}
/// Include an item declared in a C system header such as `#include <stdio.h>`.
///
/// # Examples
///
/// ```
/// use genco::prelude::*;
///
/// let printf = c::include_system("stdio.h", "printf");
///
/// let printf_toks = quote! {
/// $printf
/// };
///
/// assert_eq!(
/// vec![
/// "#include <stdio.h>",
/// "",
/// "printf",
/// ],
/// printf_toks.to_file_vec()?
/// );
/// # Ok::<_, genco::fmt::Error>(())
/// ```
pub fn include_system<M, N>(path: M, item: N) -> Import
where
M: Into<ItemStr>,
N: Into<ItemStr>,
{
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<T>(pub(super) T);
impl<T> FormatInto<Csharp> for BlockComment<T>
where
T: IntoIterator,
T::Item: Into<ItemStr>,
{
fn format_into(self, tokens: &mut Tokens<Csharp>) {
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<T>(pub(super) T);
impl<T> tokens::FormatInto<Csharp> for Comment<T>
where
T: IntoIterator,
T::Item: Into<tokens::ItemStr>,
{
fn format_into(self, tokens: &mut Tokens<Csharp>) {
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<Csharp>;
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<String, String>,
}
/// Config data for Csharp formatting.
#[derive(Debug, Default)]
pub struct Config {
/// namespace to use.
namespace: Option<ItemStr>,
}
impl Config {
/// Set the namespace name to build.
pub fn with_namespace<N>(self, namespace: N) -> Self
where
N: Into<ItemStr>,
{
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<String, String>,
) {
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<Csharp> = 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<P, N>(namespace: P, name: N) -> Import
where
P: Into<ItemStr>,
N: Into<ItemStr>,
{
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<T>(comment: T) -> BlockComment<T>
where
T: IntoIterator,
T::Item: Into<ItemStr>,
{
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<T>(comment: T) -> Comment<T>
where
T: IntoIterator,
T::Item: Into<ItemStr>,
{
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<T>(pub(super) T);
impl<T> tokens::FormatInto<Dart> for DocComment<T>
where
T: IntoIterator,
T::Item: Into<tokens::ItemStr>,
{
fn format_into(self, tokens: &mut Tokens<Dart>) {
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 `$_(<string>)`
//! 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<Dart>;
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<ItemStr>,
}
impl Import {
/// Add an `as` keyword to the import.
pub fn with_alias(self, alias: impl Into<ItemStr>) -> 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<P, N>(path: P, name: N) -> Import
where
P: Into<ItemStr>,
N: Into<ItemStr>,
{
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<T>(comment: T) -> DocComment<T>
where
T: IntoIterator,
T::Item: Into<ItemStr>,
{
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<Go>;
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<ItemStr>,
}
impl Config {
/// Configure the specified package.
pub fn with_package<P: Into<ItemStr>>(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<M, N>(module: M, name: N) -> Import
where
M: Into<ItemStr>,
N: Into<ItemStr>,
{
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<T>(pub(super) T);
impl<T> tokens::FormatInto<Java> for BlockComment<T>
where
T: IntoIterator,
T::Item: Into<tokens::ItemStr>,
{
fn format_into(self, tokens: &mut Tokens<Java>) {
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<Java>;
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<String, String>,
}
/// Configuration for Java.
#[derive(Debug, Default)]
pub struct Config {
/// Package to use.
package: Option<ItemStr>,
}
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::<Java>();
///
/// 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<P>(self, package: P) -> Self
where
P: Into<ItemStr>,
{
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<String, String>,
) {
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<P, N>(package: P, name: N) -> Import
where
P: Into<ItemStr>,
N: Into<ItemStr>,
{
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<T>(comment: T) -> BlockComment<T>
where
T: IntoIterator,
T::Item: Into<ItemStr>,
{
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<JavaScript>;
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<RelativePathBuf>,
}
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::<JavaScript>();
///
/// 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<M>(self, module_path: M) -> Self
where
M: Into<RelativePathBuf>,
{
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 {<name> as <alias>} from <module>
/// ```
alias: Option<ItemStr>,
}
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 {<name> as <alias>} from <module>
/// ```
///
/// # 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<N>(self, alias: N) -> Self
where
N: Into<ItemStr>,
{
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<String> for Module {
fn from(value: String) -> Self {
Self::Global(value.into())
}
}
impl From<ItemStr> 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<ImportedElement<'a>>,
}
#[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<M, N>(module: M, name: N) -> Import
where
M: Into<Module>,
N: Into<ItemStr>,
{
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<Kotlin>;
// 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<String, String>,
}
/// Configuration for Kotlin.
#[derive(Debug, Default)]
pub struct Config {
/// Package to use.
package: Option<ItemStr>,
}
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::<Kotlin>();
///
/// 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<P>(self, package: P) -> Self
where
P: Into<ItemStr>,
{
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<String, String>,
) {
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<String>
/// val b: $map<String, Int>
/// };
///
/// assert_eq!(
/// vec![
/// "import kotlin.collections.List",
/// "import kotlin.collections.Map",
/// "",
/// "val a: List<String>",
/// "val b: Map<String, Int>",
/// ],
/// toks.to_file_vec()?
/// );
/// # Ok::<_, genco::fmt::Error>(())
/// ```
pub fn import<P, N>(package: P, name: N) -> Import
where
P: Into<ItemStr>,
N: Into<ItemStr>,
{
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<Self>;
/// 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<Self>,
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<L> LangItem<L> 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<L>
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 <https://en.wikipedia.org/wiki/Escape_sequences_in_C>.
///
/// 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<Nix>;
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<M>(name: M) -> Import
where
M: Into<ItemStr>,
{
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<M, N>(path: M, name: N) -> Import
where
M: Into<ItemStr>,
N: Into<ItemStr>,
{
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<M, N>(name: M, value: N) -> Import
where
M: Into<ItemStr>,
N: Into<Tokens>,
{
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<M, N>(argument: M, name: N) -> Import
where
M: Into<ItemStr>,
N: Into<ItemStr>,
{
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<Python>;
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(Impo
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
SYMBOL INDEX (510 symbols across 63 files)
FILE: examples/c.rs
function main (line 4) | fn main() -> anyhow::Result<()> {
FILE: examples/csharp.rs
function main (line 5) | fn main() -> anyhow::Result<()> {
FILE: examples/dart.rs
function main (line 4) | fn main() -> anyhow::Result<()> {
FILE: examples/go.rs
function main (line 4) | fn main() -> anyhow::Result<()> {
FILE: examples/java.rs
function main (line 4) | fn main() -> anyhow::Result<()> {
FILE: examples/js.rs
function main (line 4) | fn main() -> anyhow::Result<()> {
FILE: examples/kotlin.rs
function main (line 5) | fn main() -> anyhow::Result<()> {
FILE: examples/nix.rs
function main (line 4) | fn main() -> anyhow::Result<()> {
FILE: examples/python.rs
function main (line 4) | fn main() -> anyhow::Result<()> {
FILE: examples/rust.rs
function main (line 4) | fn main() -> anyhow::Result<()> {
FILE: genco-macros/build.rs
function main (line 5) | fn main() {
type RustcVersion (line 22) | struct RustcVersion {
function rustc_version (line 27) | fn rustc_version() -> Option<RustcVersion> {
FILE: genco-macros/src/ast.rs
type MatchArm (line 9) | pub(crate) struct MatchArm {
type Delimiter (line 18) | pub(crate) enum Delimiter {
method encode_open (line 25) | pub(crate) fn encode_open(self, output: &mut StaticBuffer) {
method encode_close (line 35) | pub(crate) fn encode_close(self, output: &mut StaticBuffer) {
type LiteralName (line 46) | pub(crate) enum LiteralName<'a> {
function fmt (line 54) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
type Name (line 63) | pub(crate) enum Name {
method as_literal_name (line 74) | pub(crate) fn as_literal_name(&self) -> LiteralName<'_> {
method to_tokens (line 84) | fn to_tokens(&self, tokens: &mut TokenStream) {
type ControlKind (line 94) | pub(crate) enum ControlKind {
type Control (line 101) | pub(crate) struct Control {
method from_char (line 108) | pub(crate) fn from_char(span: Span, c: char) -> Option<Self> {
type Ast (line 128) | pub(crate) enum Ast {
FILE: genco-macros/src/cursor.rs
type Cursor (line 6) | pub(crate) struct Cursor {
method new (line 17) | pub(crate) fn new(span: Span, start: LineColumn, end: LineColumn) -> C...
method first_character (line 22) | pub(crate) fn first_character(self) -> Self {
method last_character (line 34) | pub(crate) fn last_character(self) -> Self {
FILE: genco-macros/src/encoder.rs
type Encoder (line 12) | pub(crate) struct Encoder<'a> {
function new (line 41) | pub(crate) fn new(
function encode (line 60) | pub(crate) fn encode(&mut self, cursor: Cursor, ast: Ast) -> Result<()> {
function into_output (line 128) | pub(crate) fn into_output(mut self) -> Result<(Requirements, TokenStream...
function step (line 133) | pub(crate) fn step(&mut self, next: Cursor) -> Result<()> {
function encode_open_delimiter (line 145) | pub(crate) fn encode_open_delimiter(&mut self, d: Delimiter) {
function encode_close_delimiter (line 149) | pub(crate) fn encode_close_delimiter(&mut self, d: Delimiter) {
function encode_literal (line 153) | pub(crate) fn encode_literal(&mut self, string: &str) {
function encode_string (line 157) | pub(crate) fn encode_string(&mut self, has_eval: bool, stream: TokenStre...
function encode_quoted (line 169) | pub(crate) fn encode_quoted(&mut self, s: syn::LitStr) {
function encode_control (line 181) | pub(crate) fn encode_control(&mut self, control: Control) {
function encode_scope (line 202) | pub(crate) fn encode_scope(&mut self, binding: Option<syn::Ident>, conte...
function encode_eval_ident (line 218) | pub(crate) fn encode_eval_ident(&mut self, ident: syn::Ident) {
function encode_eval (line 228) | pub(crate) fn encode_eval(&mut self, expr: syn::Expr) {
function encode_repeat (line 237) | pub(crate) fn encode_repeat(
function encode_condition (line 270) | pub(crate) fn encode_condition(
function encode_match (line 286) | pub(crate) fn encode_match(&mut self, condition: syn::Expr, arms: Vec<Ma...
function encode_let (line 310) | pub(crate) fn encode_let(&mut self, name: syn::Pat, expr: syn::Expr) {
function from (line 318) | fn from(&mut self) -> Option<LineColumn> {
function finalize (line 355) | fn finalize(&mut self) -> Result<()> {
function tokenize_whitespace (line 377) | fn tokenize_whitespace(
FILE: genco-macros/src/fake.rs
constant ERROR (line 9) | const ERROR: &str = "Your compiler does not support spans which are requ...
type LineColumn (line 13) | pub(crate) struct LineColumn {
method start (line 22) | pub(crate) fn start(span: Span) -> Option<Self> {
method end (line 32) | pub(crate) fn end(span: Span) -> Option<Self> {
method start (line 42) | pub(crate) fn start(_: Span) -> Option<Self> {
method end (line 47) | pub(crate) fn end(_: Span) -> Option<Self> {
type Buf (line 53) | pub(crate) struct Buf {
method format (line 59) | fn format(&self, args: Arguments<'_>) -> RefMut<'_, str> {
method cursor (line 68) | pub(crate) fn cursor(&self, span: Span) -> syn::Result<Cursor> {
method start (line 93) | pub(crate) fn start(&mut self, span: Span) -> syn::Result<LineColumn> {
method end (line 104) | pub(crate) fn end(&mut self, span: Span) -> syn::Result<LineColumn> {
method join (line 115) | pub(crate) fn join(&mut self, a: Span, b: Span) -> syn::Result<Cursor> {
method find_line_column (line 125) | fn find_line_column(&self, span: Span) -> syn::Result<(usize, usize)> {
method find_line_column_inner (line 132) | fn find_line_column_inner(&self, span: Span) -> Option<(usize, usize)> {
FILE: genco-macros/src/lib.rs
type Ctxt (line 14) | struct Ctxt {
method default (line 20) | fn default() -> Self {
function quote (line 49) | pub fn quote(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
function quote_in (line 80) | pub fn quote_in(input: proc_macro::TokenStream) -> proc_macro::TokenStre...
function quote_fn (line 86) | pub fn quote_fn(input: proc_macro::TokenStream) -> proc_macro::TokenStre...
FILE: genco-macros/src/quote.rs
type Quote (line 14) | pub(crate) struct Quote<'a> {
function new (line 35) | pub(crate) fn new(cx: &'a Ctxt) -> Self {
function new_until_comma (line 46) | pub(crate) fn new_until_comma(cx: &'a Ctxt) -> Self {
function with_span (line 57) | pub(crate) fn with_span(mut self, span: Span) -> syn::Result<Self> {
function parse (line 80) | pub(crate) fn parse(mut self, input: ParseStream) -> Result<(Requirement...
function parse_condition (line 87) | fn parse_condition(&self, input: ParseStream) -> Result<(Requirements, A...
function parse_loop (line 138) | fn parse_loop(&self, input: ParseStream) -> Result<(Requirements, Ast)> {
function parse_match (line 188) | fn parse_match(&self, input: ParseStream) -> Result<(Requirements, Ast)> {
function parse_let (line 247) | fn parse_let(&self, input: ParseStream) -> Result<(Requirements, Ast)> {
function parse_scope (line 262) | fn parse_scope(&self, input: ParseStream) -> Result<Ast> {
function parse_expression (line 288) | fn parse_expression(&mut self, encoder: &mut Encoder, input: ParseStream...
function parse_inner (line 336) | fn parse_inner(
function parse_group (line 474) | fn parse_group(
function parse_internal_function (line 502) | pub(crate) fn parse_internal_function<'a>(
FILE: genco-macros/src/quote_fn.rs
type QuoteFn (line 7) | pub(crate) struct QuoteFn {
method parse (line 12) | fn parse(input: ParseStream) -> Result<Self> {
FILE: genco-macros/src/quote_in.rs
type QuoteIn (line 8) | pub(crate) struct QuoteIn {
method parse (line 13) | fn parse(input: ParseStream) -> Result<Self> {
FILE: genco-macros/src/requirements.rs
type Requirements (line 4) | pub(crate) struct Requirements {
method merge_with (line 10) | pub fn merge_with(&mut self, other: Self) {
method into_check (line 15) | pub fn into_check(self, receiver: &syn::Ident) -> TokenStream {
FILE: genco-macros/src/static_buffer.rs
type StaticBuffer (line 6) | pub(crate) struct StaticBuffer<'a> {
function new (line 13) | pub(crate) fn new(cx: &'a Ctxt) -> Self {
function push (line 21) | pub(crate) fn push(&mut self, c: char) {
function push_str (line 26) | pub(crate) fn push_str(&mut self, s: &str) {
function flush (line 31) | pub(crate) fn flush(&mut self, tokens: &mut TokenStream) {
FILE: genco-macros/src/string_parser.rs
type Options (line 20) | pub(crate) struct Options {
function adjust_start (line 25) | fn adjust_start(start: LineColumn) -> LineColumn {
function adjust_end (line 32) | fn adjust_end(end: LineColumn) -> LineColumn {
type Encoder (line 39) | struct Encoder<'a> {
function new (line 50) | pub fn new(cx: &'a Ctxt, cursor: LineColumn, span: Span) -> Self {
function finalize (line 62) | pub(crate) fn finalize(self, end: LineColumn) -> Result<(Options, TokenS...
function encode_char (line 69) | pub(crate) fn encode_char(&self, c: char, from: LineColumn, to: LineColu...
function encode_str (line 77) | pub(crate) fn encode_str(
function eval_ident (line 89) | pub(crate) fn eval_ident(
function eval_stream (line 112) | pub(crate) fn eval_stream(
function raw_expr (line 133) | pub(crate) fn raw_expr(
function extend_tt (line 149) | pub(crate) fn extend_tt(
function flush (line 161) | pub fn flush(&self, from: Option<LineColumn>, to: Option<LineColumn>) ->...
function flush_whitespace (line 187) | pub(crate) fn flush_whitespace(
type StringParser (line 210) | pub struct StringParser<'a> {
function new (line 219) | pub(crate) fn new(cx: &'a Ctxt, buf: &'a Buf, span: Span) -> syn::Result...
function parse (line 234) | pub(crate) fn parse(self, input: ParseStream) -> Result<(Options, Requir...
function is_lit_str_opt (line 312) | pub(crate) fn is_lit_str_opt(content: ParseBuffer<'_>) -> syn::Result<bo...
FILE: src/fmt/config.rs
type Indentation (line 34) | pub enum Indentation {
type Config (line 43) | pub struct Config {
method from_lang (line 53) | pub fn from_lang<L>() -> Self
method with_indentation (line 64) | pub fn with_indentation(self, indentation: Indentation) -> Self {
method with_newline (line 72) | pub fn with_newline(self, newline: &'static str) -> Self {
FILE: src/fmt/cursor.rs
type Parse (line 5) | pub(super) trait Parse {
method parse (line 9) | fn parse(item: &Item) -> fmt::Result<&Self::Output>;
method peek (line 12) | fn peek(item: &Item) -> bool;
type Output (line 19) | type Output = str;
method peek (line 22) | fn peek(item: &Item) -> bool {
method parse (line 27) | fn parse(item: &Item) -> fmt::Result<&Self::Output> {
type Output (line 39) | type Output = ();
method peek (line 42) | fn peek(item: &Item) -> bool {
method parse (line 47) | fn parse(item: &Item) -> fmt::Result<&Self::Output> {
type Literal (line 16) | pub(super) struct Literal(());
type CloseEval (line 36) | pub(super) struct CloseEval(());
type Cursor (line 56) | pub(super) struct Cursor<'a, T> {
function new (line 63) | pub(super) fn new(lang: &'a [T], items: &'a [Item]) -> Self {
function lang (line 68) | pub(super) fn lang(&self, index: usize) -> fmt::Result<&'a T> {
function next (line 73) | pub(super) fn next(&mut self) -> Option<&Item> {
function peek (line 80) | pub(super) fn peek<P>(&self) -> bool
function peek1 (line 92) | pub(super) fn peek1<P>(&self) -> bool
function parse (line 104) | pub(super) fn parse<P>(&mut self) -> fmt::Result<&P::Output>
FILE: src/fmt/fmt_writer.rs
type FmtWriter (line 35) | pub struct FmtWriter<W>
function new (line 47) | pub fn new(writer: W) -> Self {
function as_formatter (line 52) | pub fn as_formatter<'a>(&'a mut self, config: &'a fmt::Config) -> fmt::F...
function into_inner (line 57) | pub fn into_inner(self) -> W {
function write_char (line 67) | fn write_char(&mut self, c: char) -> core::fmt::Result {
function write_str (line 72) | fn write_str(&mut self, s: &str) -> core::fmt::Result {
function write_line (line 82) | fn write_line(&mut self, config: &fmt::Config) -> fmt::Result {
FILE: src/fmt/formatter.rs
type Whitespace (line 18) | enum Whitespace {
method into_indent (line 31) | fn into_indent(self) -> Option<usize> {
type Formatter (line 43) | pub struct Formatter<'a> {
function new (line 62) | pub(crate) fn new(write: &'a mut (dyn fmt::Write + 'a), config: &'a Conf...
function format_items (line 73) | pub(crate) fn format_items<L>(
function write_trailing_line (line 90) | pub(crate) fn write_trailing_line(&mut self) -> fmt::Result {
function write_str (line 98) | fn write_str(&mut self, s: &str) -> fmt::Result {
function push (line 107) | fn push(&mut self) {
function line (line 118) | fn line(&mut self) {
function space (line 128) | fn space(&mut self) {
function indentation (line 133) | fn indentation(&mut self, n: i16) {
function format_cursor (line 139) | fn format_cursor<L>(
function quoted_quote (line 247) | fn quoted_quote<L>(
function flush_whitespace (line 269) | fn flush_whitespace(&mut self) -> fmt::Result {
function write_str (line 306) | fn write_str(&mut self, s: &str) -> fmt::Result {
function fmt (line 316) | fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
FILE: src/fmt/io_writer.rs
type IoWriter (line 37) | pub struct IoWriter<W>
function new (line 49) | pub fn new(writer: W) -> Self {
function as_formatter (line 54) | pub fn as_formatter<'a>(&'a mut self, config: &'a fmt::Config) -> fmt::F...
function into_inner (line 59) | pub fn into_inner(self) -> W {
function write_char (line 69) | fn write_char(&mut self, c: char) -> core::fmt::Result {
function write_str (line 76) | fn write_str(&mut self, s: &str) -> core::fmt::Result {
function write_line (line 88) | fn write_line(&mut self, config: &fmt::Config) -> fmt::Result {
FILE: src/fmt/mod.rs
type Result (line 64) | pub type Result<T = ()> = core::result::Result<T, core::fmt::Error>;
type Error (line 66) | pub type Error = core::fmt::Error;
type Write (line 69) | pub(crate) trait Write: core::fmt::Write {
method write_line (line 71) | fn write_line(&mut self, config: &Config) -> Result;
method write_trailing_line (line 75) | fn write_trailing_line(&mut self, config: &Config) -> Result {
FILE: src/fmt/vec_writer.rs
type VecWriter (line 44) | pub struct VecWriter {
method new (line 51) | pub fn new() -> Self {
method as_formatter (line 56) | pub fn as_formatter<'a>(&'a mut self, config: &'a fmt::Config) -> fmt:...
method into_vec (line 61) | pub fn into_vec(mut self) -> Vec<String> {
method write_char (line 69) | fn write_char(&mut self, c: char) -> core::fmt::Result {
method write_str (line 74) | fn write_str(&mut self, s: &str) -> core::fmt::Result {
method write_line (line 81) | fn write_line(&mut self, _: &fmt::Config) -> fmt::Result {
method write_trailing_line (line 88) | fn write_trailing_line(&mut self, _: &fmt::Config) -> fmt::Result {
FILE: src/lang/c.rs
type Tokens (line 13) | pub type Tokens = crate::Tokens<C>;
type Import (line 54) | pub struct Import {
type Format (line 65) | pub struct Format {}
type Config (line 69) | pub struct Config {}
method imports (line 72) | fn imports(out: &mut Tokens, tokens: &Tokens) {
function include (line 119) | pub fn include(path: impl Into<ItemStr>, item: impl Into<ItemStr>) -> Im...
function include_system (line 150) | pub fn include_system<M, N>(path: M, item: N) -> Import
FILE: src/lang/csharp/block_comment.rs
type BlockComment (line 8) | pub struct BlockComment<T>(pub(super) T);
function format_into (line 15) | fn format_into(self, tokens: &mut Tokens<Csharp>) {
FILE: src/lang/csharp/comment.rs
type Comment (line 8) | pub struct Comment<T>(pub(super) T);
function format_into (line 15) | fn format_into(self, tokens: &mut Tokens<Csharp>) {
FILE: src/lang/csharp/mod.rs
type Tokens (line 35) | pub type Tokens = crate::Tokens<Csharp>;
constant SEP (line 114) | const SEP: &str = ".";
type Format (line 118) | pub struct Format {
type Config (line 128) | pub struct Config {
method with_namespace (line 135) | pub fn with_namespace<N>(self, namespace: N) -> Self
type Import (line 149) | pub struct Import {
method qualified (line 161) | pub fn qualified(self) -> Self {
method imports (line 170) | fn imports(
function import (line 243) | pub fn import<P, N>(namespace: P, name: N) -> Import
function block_comment (line 278) | pub fn block_comment<T>(comment: T) -> BlockComment<T>
function comment (line 307) | pub fn comment<T>(comment: T) -> Comment<T>
FILE: src/lang/dart/doc_comment.rs
type DocComment (line 8) | pub struct DocComment<T>(pub(super) T);
function format_into (line 15) | fn format_into(self, tokens: &mut Tokens<Dart>) {
FILE: src/lang/dart/mod.rs
constant SEP (line 44) | const SEP: &str = ".";
constant DART_CORE (line 46) | const DART_CORE: &str = "dart:core";
type Tokens (line 49) | pub type Tokens = crate::Tokens<Dart>;
type Format (line 157) | pub struct Format {}
type Config (line 161) | pub struct Config {}
type Import (line 167) | pub struct Import {
method with_alias (line 178) | pub fn with_alias(self, alias: impl Into<ItemStr>) -> Import {
method imports (line 188) | fn imports(out: &mut Tokens, input: &Tokens, _: &Config) {
function import (line 250) | pub fn import<P, N>(path: P, name: N) -> Import
function doc_comment (line 285) | pub fn doc_comment<T>(comment: T) -> DocComment<T>
FILE: src/lang/go.rs
constant MODULE_SEP (line 50) | const MODULE_SEP: &str = "/";
constant SEP (line 51) | const SEP: &str = ".";
type Tokens (line 54) | pub type Tokens = crate::Tokens<Go>;
type Import (line 105) | pub struct Import {
type Format (line 114) | pub struct Format {}
type Config (line 118) | pub struct Config {
method with_package (line 124) | pub fn with_package<P: Into<ItemStr>>(self, package: P) -> Self {
method imports (line 132) | fn imports(out: &mut Tokens, tokens: &Tokens) {
function import (line 175) | pub fn import<M, N>(module: M, name: N) -> Import
FILE: src/lang/java/block_comment.rs
type BlockComment (line 8) | pub struct BlockComment<T>(pub(super) T);
function format_into (line 15) | fn format_into(self, tokens: &mut Tokens<Java>) {
FILE: src/lang/java/mod.rs
type Tokens (line 30) | pub type Tokens = crate::Tokens<Java>;
constant JAVA_LANG (line 102) | const JAVA_LANG: &str = "java.lang";
constant SEP (line 103) | const SEP: &str = ".";
type Format (line 107) | pub struct Format {
type Config (line 114) | pub struct Config {
method with_package (line 149) | pub fn with_package<P>(self, package: P) -> Self
type Import (line 163) | pub struct Import {
method imports (line 171) | fn imports(
function import (line 238) | pub fn import<P, N>(package: P, name: N) -> Import
function block_comment (line 277) | pub fn block_comment<T>(comment: T) -> BlockComment<T>
FILE: src/lang/js.rs
type Tokens (line 59) | pub type Tokens = crate::Tokens<JavaScript>;
type Format (line 184) | pub struct Format {}
type Config (line 188) | pub struct Config {
method with_module_path (line 236) | pub fn with_module_path<M>(self, module_path: M) -> Self
type ImportKind (line 248) | enum ImportKind {
type Import (line 258) | pub struct Import {
method with_alias (line 310) | pub fn with_alias<N>(self, alias: N) -> Self
method into_default (line 342) | pub fn into_default(self) -> Self {
method into_wildcard (line 371) | pub fn into_wildcard(self) -> Self {
type Module (line 382) | pub enum Module {
method from (line 393) | fn from(value: &'a str) -> Self {
method from (line 399) | fn from(value: String) -> Self {
method from (line 405) | fn from(value: ItemStr) -> Self {
method imports (line 412) | fn imports(out: &mut Tokens, tokens: &Tokens, config: &Config) {
function import (line 549) | pub fn import<M, N>(module: M, name: N) -> Import
FILE: src/lang/kotlin/mod.rs
type Tokens (line 28) | pub type Tokens = crate::Tokens<Kotlin>;
type Format (line 127) | pub struct Format {
type Config (line 135) | pub struct Config {
method with_package (line 172) | pub fn with_package<P>(self, package: P) -> Self
type Import (line 186) | pub struct Import {
method imports (line 195) | fn imports(
function import (line 262) | pub fn import<P, N>(package: P, name: N) -> Import
FILE: src/lang/mod.rs
type Lang (line 52) | pub trait Lang: Sized {
method default_indentation (line 61) | fn default_indentation() -> fmt::Indentation {
method open_quote (line 66) | fn open_quote(
method close_quote (line 77) | fn close_quote(
method string_eval_literal (line 88) | fn string_eval_literal(
method start_string_eval (line 101) | fn start_string_eval(
method end_string_eval (line 110) | fn end_string_eval(
method write_quoted (line 119) | fn write_quoted(out: &mut fmt::Formatter<'_>, input: &str) -> fmt::Res...
method format_file (line 124) | fn format_file(
type Config (line 142) | type Config = ();
type Format (line 143) | type Format = ();
type Item (line 144) | type Item = ();
type LangSupportsEval (line 138) | pub trait LangSupportsEval: Lang {}
function format (line 151) | fn format(&self, _: &mut fmt::Formatter<'_>, _: &L::Config, _: &L::Forma...
type LangItem (line 160) | pub trait LangItem<L>
method format (line 165) | fn format(
function c_family_write_quoted (line 179) | pub fn c_family_write_quoted(out: &mut fmt::Formatter, input: &str) -> f...
FILE: src/lang/nix.rs
type Tokens (line 14) | pub type Tokens = crate::Tokens<Nix>;
type Import (line 61) | pub enum Import {
type ImportArgument (line 74) | pub struct ImportArgument(ItemStr);
type ImportInherit (line 78) | pub struct ImportInherit {
type ImportVariable (line 87) | pub struct ImportVariable {
type ImportWith (line 96) | pub struct ImportWith {
type Format (line 105) | pub struct Format {}
type Config (line 109) | pub struct Config {
method with_scoped (line 115) | pub fn with_scoped(self, scoped: bool) -> Self {
method arguments (line 121) | fn arguments(out: &mut Tokens, tokens: &Tokens) {
method withs (line 180) | fn withs(out: &mut Tokens, tokens: &Tokens) {
method imports (line 201) | fn imports(out: &mut Tokens, tokens: &Tokens) {
function argument (line 271) | pub fn argument<M>(name: M) -> Import
function inherit (line 304) | pub fn inherit<M, N>(path: M, name: N) -> Import
function variable (line 352) | pub fn variable<M, N>(name: M, value: N) -> Import
function with (line 388) | pub fn with<M, N>(argument: M, name: N) -> Import
FILE: src/lang/python.rs
type Tokens (line 29) | pub type Tokens = crate::Tokens<Python>;
type Format (line 89) | pub struct Format {}
type Config (line 92) | pub struct Config {}
type TypeModule (line 97) | enum TypeModule {
method qualified (line 111) | fn qualified(self) -> Self {
method with_alias (line 121) | fn with_alias<T>(self, alias: T) -> Self
type Import (line 138) | pub struct Import {
method with_alias (line 171) | pub fn with_alias<T>(self, alias: T) -> Self
method qualified (line 202) | pub fn qualified(self) -> Self {
method with_module_alias (line 232) | pub fn with_module_alias<T>(self, module_alias: T) -> Self
type ImportModule (line 247) | pub struct ImportModule {
method with_alias (line 277) | pub fn with_alias<N>(self, new_alias: N) -> Self
method imports (line 289) | fn imports(out: &mut Tokens, tokens: &Tokens) {
function import (line 380) | pub fn import<M, N>(module: M, name: N) -> Import
function import_module (line 418) | pub fn import_module<M>(module: M) -> ImportModule
FILE: src/lang/rust.rs
constant SEP (line 45) | const SEP: &str = "::";
type Tokens (line 48) | pub type Tokens = crate::Tokens<Rust>;
type Format (line 141) | pub struct Format {}
type Config (line 145) | pub struct Config {
method with_default_import (line 153) | pub fn with_default_import(self, default_import: ImportMode) -> Self {
method default (line 159) | fn default() -> Self {
type ImportMode (line 168) | pub enum ImportMode {
type Module (line 182) | enum Module {
method into_module_aliased (line 195) | fn into_module_aliased<A>(self, alias: A) -> Self
method into_aliased (line 210) | fn into_aliased(self) -> Self {
method direct (line 223) | fn direct(self) -> Self {
method qualified (line 236) | fn qualified(self) -> Self {
type Import (line 251) | pub struct Import {
method with_alias (line 282) | pub fn with_alias<A: Into<ItemStr>>(self, alias: A) -> Self {
method with_module_alias (line 315) | pub fn with_module_alias<A: Into<ItemStr>>(self, alias: A) -> Self {
method qualified (line 350) | pub fn qualified(self) -> Self {
method direct (line 380) | pub fn direct(self) -> Self {
method write_direct (line 388) | fn write_direct(&self, out: &mut fmt::Formatter<'_>) -> fmt::Result {
method write_prefixed (line 397) | fn write_prefixed(&self, out: &mut fmt::Formatter<'_>, module: &ItemSt...
method imports (line 409) | fn imports(out: &mut Tokens, config: &Config, tokens: &Tokens) {
function import (line 698) | pub fn import<M, N>(module: M, name: N) -> Import
FILE: src/lang/swift.rs
type Tokens (line 24) | pub type Tokens = crate::Tokens<Swift>;
type Format (line 84) | pub struct Format {}
type Config (line 88) | pub struct Config {}
type Import (line 94) | pub struct Import {
type ImportImplementationOnly (line 105) | pub struct ImportImplementationOnly {
type ImportType (line 116) | enum ImportType {
method imports (line 127) | fn imports(out: &mut Tokens, tokens: &Tokens) {
function import (line 180) | pub fn import<M, N>(module: M, name: N) -> Import
function import_implementation_only (line 210) | pub fn import_implementation_only(
FILE: src/lib.rs
function static_ (line 1000) | pub const fn static_(string: &'static str) -> Item {
function literal (line 1005) | pub const fn literal(string: ItemStr) -> Item {
function indentation (line 1010) | pub const fn indentation(level: i16) -> Item {
function push (line 1015) | pub const fn push() -> Item {
function line (line 1020) | pub const fn line() -> Item {
function space (line 1025) | pub const fn space() -> Item {
function open_quote (line 1030) | pub const fn open_quote(is_interpolation: bool) -> Item {
function close_quote (line 1035) | pub const fn close_quote() -> Item {
function open_eval (line 1040) | pub const fn open_eval() -> Item {
function close_eval (line 1045) | pub const fn close_eval() -> Item {
function item (line 1056) | pub fn item<L>(item: L::Item) -> impl FormatInto<L>
function register (line 1072) | pub fn register<L>(item: L::Item) -> impl FormatInto<L>
FILE: src/tokens/display.rs
function display (line 57) | pub fn display<T>(inner: T) -> Display<T>
type Display (line 69) | pub struct Display<T> {
function format_into (line 79) | fn format_into(self, tokens: &mut Tokens<L>) {
FILE: src/tokens/format_into.rs
type FormatInto (line 43) | pub trait FormatInto<L>
method format_into (line 48) | fn format_into(self, tokens: &mut Tokens<L>);
function format_into (line 71) | fn format_into(self, tokens: &mut Self) {
function format_into (line 97) | fn format_into(self, tokens: &mut Tokens<L>) {
function format_into (line 125) | fn format_into(self, tokens: &mut Tokens<L>) {
function format_into (line 155) | fn format_into(self, tokens: &mut Tokens<L>) {
function format_into (line 186) | fn format_into(self, tokens: &mut Tokens<L>) {
function format_into (line 213) | fn format_into(self, tokens: &mut Tokens<L>) {
function format_into (line 238) | fn format_into(self, tokens: &mut Tokens<L>) {
method format_into (line 264) | fn format_into(self, tokens: &mut Tokens<L>) {
function format_into (line 290) | fn format_into(self, tokens: &mut Tokens<L>) {
function format_into (line 317) | fn format_into(self, tokens: &mut Tokens<L>) {
function format_into (line 341) | fn format_into(self, tokens: &mut Tokens<L>) {
function format_into (line 373) | fn format_into(self, tokens: &mut Tokens<L>) {
function format_into (line 401) | fn format_into(self, tokens: &mut Tokens<L>) {
function format_into (line 430) | fn format_into(self, tokens: &mut Tokens<L>) {
function format_into (line 460) | fn format_into(self, tokens: &mut Tokens<L>) {
function format_into (line 494) | fn format_into(self, tokens: &mut Tokens<L>) {
FILE: src/tokens/from_fn.rs
function from_fn (line 27) | pub fn from_fn<F, L>(f: F) -> FromFn<F>
type FromFn (line 39) | pub struct FromFn<F> {
function format_into (line 49) | fn format_into(self, tokens: &mut Tokens<L>) {
FILE: src/tokens/item.rs
type Kind (line 9) | pub(crate) enum Kind {
type Item (line 42) | pub struct Item {
method new (line 49) | pub(crate) const fn new(kind: Kind) -> Self {
method static_ (line 67) | pub const fn static_(literal: &'static str) -> Self {
method push (line 85) | pub const fn push() -> Self {
method line (line 103) | pub const fn line() -> Self {
method space (line 121) | pub const fn space() -> Self {
method indentation (line 127) | pub(crate) const fn indentation(n: i16) -> Self {
method open_quote (line 152) | pub const fn open_quote(is_interpolated: bool) -> Self {
method close_quote (line 170) | pub const fn close_quote() -> Self {
method open_eval (line 176) | pub(crate) const fn open_eval() -> Self {
method close_eval (line 182) | pub(crate) const fn close_eval() -> Self {
method literal (line 200) | pub const fn literal(lit: ItemStr) -> Self {
method lang (line 206) | pub(crate) const fn lang(index: usize) -> Item {
method fmt (line 213) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
method format_into (line 251) | fn format_into(self, tokens: &mut Tokens<L>) {
FILE: src/tokens/item_str.rs
type ItemStrKind (line 21) | enum ItemStrKind {
type ItemStr (line 31) | pub struct ItemStr {
method new (line 38) | const fn new(kind: ItemStrKind) -> Self {
method static_ (line 52) | pub const fn static_(s: &'static str) -> Self {
method format_into (line 63) | fn format_into(self, tokens: &mut Tokens<L>) {
method as_ref (line 80) | fn as_ref(&self) -> &str {
method from (line 104) | fn from(value: Box<str>) -> Self {
method from (line 111) | fn from(value: &'a ItemStr) -> Self {
method from (line 119) | fn from(value: &'a String) -> Self {
method from (line 127) | fn from(value: String) -> Self {
method from (line 135) | fn from(value: &'a str) -> Self {
method from (line 143) | fn from(value: &'b &'a str) -> Self {
method from (line 151) | fn from(value: Cow<'a, str>) -> Self {
method from (line 162) | fn from(value: &'b Cow<'a, str>) -> Self {
method from (line 173) | fn from(value: Rc<String>) -> Self {
method fmt (line 180) | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
method fmt (line 187) | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
function format_into (line 73) | fn format_into(self, tokens: &mut Tokens<L>) {
type Target (line 90) | type Target = str;
method deref (line 92) | fn deref(&self) -> &str {
method eq (line 194) | fn eq(&self, other: &Self) -> bool {
method partial_cmp (line 203) | fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
method cmp (line 210) | fn cmp(&self, other: &Self) -> core::cmp::Ordering {
method hash (line 217) | fn hash<H>(&self, state: &mut H)
FILE: src/tokens/quoted.rs
function quoted (line 54) | pub fn quoted<T>(inner: T) -> QuotedFn<T> {
type QuotedFn (line 62) | pub struct QuotedFn<T> {
function format_into (line 72) | fn format_into(self, t: &mut Tokens<L>) {
FILE: src/tokens/register.rs
function register (line 48) | pub fn register<T, L>(inner: T) -> RegisterFn<T>
type RegisterFn (line 60) | pub struct RegisterFn<T> {
function format_into (line 69) | fn format_into(self, t: &mut Tokens<L>) {
type Register (line 106) | pub trait Register<L>
method register (line 111) | fn register(self, tokens: &mut Tokens<L>);
FILE: src/tokens/static_literal.rs
type StaticLiteral (line 8) | pub struct StaticLiteral {
method format_into (line 17) | fn format_into(self, tokens: &mut crate::Tokens<L>) {
function static_literal (line 39) | pub fn static_literal(literal: &'static str) -> StaticLiteral {
FILE: src/tokens/tokens.rs
type Tokens (line 70) | pub struct Tokens<L = ()>
function new (line 93) | pub fn new() -> Self {
function with_capacity (line 111) | pub fn with_capacity(cap: usize) -> Self {
function iter (line 137) | pub fn iter(&self) -> Iter<'_, L::Item> {
function append (line 166) | pub fn append<T>(&mut self, tokens: T)
function item (line 174) | pub(crate) fn item(&mut self, item: Item) {
function extend_by_ref (line 190) | pub(crate) fn extend_by_ref(&mut self, other: &Tokens<L>)
function extend_by_owned (line 217) | pub(crate) fn extend_by_owned(&mut self, mut other: Tokens<L>) {
function iter_lang (line 256) | pub fn iter_lang(&self) -> IterLang<'_, L> {
function register (line 279) | pub fn register<T>(&mut self, tokens: T)
function is_empty (line 295) | pub fn is_empty(&self) -> bool {
function space (line 329) | pub fn space(&mut self) {
function push (line 367) | pub fn push(&mut self) {
function line (line 419) | pub fn line(&mut self) {
function indent (line 471) | pub fn indent(&mut self) {
function unindent (line 521) | pub fn unindent(&mut self) {
function format (line 574) | pub fn format(
function literal (line 585) | pub(crate) fn literal(&mut self, lit: impl Into<ItemStr>) {
function open_quote (line 591) | pub(crate) fn open_quote(&mut self, is_interpolated: bool) {
function close_quote (line 597) | pub(crate) fn close_quote(&mut self) {
function lang_item (line 602) | pub(crate) fn lang_item(&mut self, item: L::Item) {
function lang_item_register (line 608) | pub(crate) fn lang_item_register(&mut self, item: L::Item) {
function format_file (line 650) | pub fn format_file(&self, out: &mut fmt::Formatter<'_>, config: &L::Conf...
function indentation (line 657) | fn indentation(&mut self, mut n: i16) {
method default (line 685) | fn default() -> Self {
function lang_supports_eval (line 697) | pub fn lang_supports_eval(&self) {}
function to_file_string (line 732) | pub fn to_file_string(&self) -> fmt::Result<String> {
function to_string (line 767) | pub fn to_string(&self) -> fmt::Result<String> {
function to_file_vec (line 834) | pub fn to_file_vec(&self) -> fmt::Result<Vec<String>> {
function to_vec (line 872) | pub fn to_vec(&self) -> fmt::Result<Vec<String>> {
function eq (line 889) | fn eq(&self, other: &Tokens<L>) -> bool {
function eq (line 899) | fn eq(&self, other: &Vec<Item>) -> bool {
function eq (line 909) | fn eq(&self, other: &Tokens<L>) -> bool {
function eq (line 919) | fn eq(&self, other: &[Item]) -> bool {
function eq (line 929) | fn eq(&self, other: &[Item; N]) -> bool {
function eq (line 939) | fn eq(&self, other: &Tokens<L>) -> bool {
function partial_cmp (line 957) | fn partial_cmp(&self, other: &Tokens<L>) -> Option<Ordering> {
method cmp (line 968) | fn cmp(&self, other: &Tokens<L>) -> Ordering {
type ItemRef (line 976) | pub struct ItemRef<'a, T> {
function fmt (line 985) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
type ItemRefKind (line 991) | enum ItemRefKind<'a, T> {
function eq (line 998) | fn eq(&self, other: &Item) -> bool {
function eq (line 1008) | fn eq(&self, other: &&Item) -> bool {
method eq (line 1018) | fn eq(&self, other: &ItemRef<'_, T>) -> bool {
function eq (line 1025) | fn eq(&self, other: &ItemRef<'_, T>) -> bool {
type Iter (line 1033) | pub struct Iter<'a, T> {
type Item (line 1039) | type Item = ItemRef<'a, T>;
method next (line 1042) | fn next(&mut self) -> Option<Self::Item> {
method size_hint (line 1055) | fn size_hint(&self) -> (usize, Option<usize>) {
type Item (line 1064) | type Item = ItemRef<'a, L::Item>;
type IntoIter (line 1065) | type IntoIter = Iter<'a, L::Item>;
method into_iter (line 1068) | fn into_iter(self) -> Self::IntoIter {
function fmt (line 1079) | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
method clone (line 1090) | fn clone(&self) -> Self {
function hash (line 1104) | fn hash<H>(&self, state: &mut H)
type IterLang (line 1116) | pub struct IterLang<'a, L>
type Item (line 1127) | type Item = &'a L::Item;
method next (line 1130) | fn next(&mut self) -> Option<Self::Item> {
type Import (line 1149) | struct Import(u32);
function test_walk_custom (line 1166) | fn test_walk_custom() {
FILE: tests/test_indentation_rules.rs
function test_indentation_rules (line 4) | fn test_indentation_rules() -> genco::fmt::Result {
FILE: tests/test_option.rs
function test_option (line 4) | fn test_option() -> genco::fmt::Result {
FILE: tests/test_quote.rs
function test_quote (line 4) | fn test_quote() -> genco::fmt::Result {
function test_tight_quote (line 50) | fn test_tight_quote() -> genco::fmt::Result {
function test_escape (line 60) | fn test_escape() -> genco::fmt::Result {
function test_scope (line 68) | fn test_scope() -> genco::fmt::Result {
FILE: tests/test_quote_in.rs
function test_quote_in (line 5) | fn test_quote_in() -> genco::fmt::Result {
function test_quote_into_unit (line 14) | fn test_quote_into_unit() -> genco::fmt::Result {
FILE: tests/test_quote_simple_expression.rs
function test_quote_simple_expression (line 4) | fn test_quote_simple_expression() -> genco::fmt::Result {
FILE: tests/test_register.rs
function test_register (line 4) | fn test_register() -> genco::fmt::Result {
FILE: tests/test_string.rs
function test_quoted (line 4) | fn test_quoted() -> genco::fmt::Result {
function test_string_in_string_in (line 20) | fn test_string_in_string_in() -> genco::fmt::Result {
FILE: tests/test_token_gen.rs
function test_token_gen (line 10) | fn test_token_gen() {
function test_iterator_gen (line 36) | fn test_iterator_gen() {
function test_tricky_continuation (line 79) | fn test_tricky_continuation() {
function test_indentation (line 110) | fn test_indentation() {
function test_repeat (line 156) | fn test_repeat() {
function test_tight_quote (line 201) | fn test_tight_quote() {
function test_tight_repitition (line 217) | fn test_tight_repitition() {
function test_if (line 241) | fn test_if() {
function test_match (line 272) | fn test_match() {
function test_let (line 328) | fn test_let() {
function test_mutable_let (line 380) | fn test_mutable_let() {
function test_empty_loop_whitespace (line 413) | fn test_empty_loop_whitespace() {
function test_indentation_empty (line 454) | fn test_indentation_empty() {
function test_indentation_management (line 499) | fn test_indentation_management() {
function test_indentation_management2 (line 558) | fn test_indentation_management2() -> fmt::Result {
function test_lines (line 595) | fn test_lines() -> fmt::Result {
Condensed preview — 77 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (370K chars).
[
{
"path": ".github/workflows/ci.yml",
"chars": 1701,
"preview": "name: CI\n\non:\n pull_request: {}\n push:\n branches:\n - main\n schedule:\n - cron: '44 6 * * 0'\n\nconcurrency:\n "
},
{
"path": ".gitignore",
"chars": 31,
"preview": "/target/\n**/*.rs.bk\nCargo.lock\n"
},
{
"path": "CHANGELOG.md",
"chars": 2908,
"preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
},
{
"path": "Cargo.toml",
"chars": 746,
"preview": "[package]\nname = \"genco\"\nversion = \"0.19.0\"\nauthors = [\"John-John Tedro <udoprog@tedro.se>\"]\nedition = \"2018\"\nrust-versi"
},
{
"path": "LICENSE-APACHE",
"chars": 11358,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "LICENSE-MIT",
"chars": 1059,
"preview": "Copyright (c) 2017 John-John Tedro\n\nPermission is hereby granted, free of charge, to any\nperson obtaining a copy of this"
},
{
"path": "README.md",
"chars": 6883,
"preview": "# genco\n\n[<img alt=\"github\" src=\"https://img.shields.io/badge/github-udoprog/genco-8da0cb?style=for-the-badge&logo=githu"
},
{
"path": "examples/c.rs",
"chars": 742,
"preview": "use genco::fmt;\nuse genco::prelude::*;\n\nfn main() -> anyhow::Result<()> {\n let printf = &c::include_system(\"stdio.h\","
},
{
"path": "examples/csharp.rs",
"chars": 2245,
"preview": "use csharp::comment;\nuse genco::fmt;\nuse genco::prelude::*;\n\nfn main() -> anyhow::Result<()> {\n let console = &csharp"
},
{
"path": "examples/dart.rs",
"chars": 615,
"preview": "use genco::fmt;\nuse genco::prelude::*;\n\nfn main() -> anyhow::Result<()> {\n let hash_map = &dart::import(\"dart:collect"
},
{
"path": "examples/go.rs",
"chars": 757,
"preview": "use genco::fmt;\nuse genco::prelude::*;\n\nfn main() -> anyhow::Result<()> {\n let println = &go::import(\"fmt\", \"Println\""
},
{
"path": "examples/java.rs",
"chars": 934,
"preview": "use genco::fmt;\nuse genco::prelude::*;\n\nfn main() -> anyhow::Result<()> {\n let car = &java::import(\"se.tedro\", \"Car\")"
},
{
"path": "examples/js.rs",
"chars": 1309,
"preview": "use genco::fmt;\nuse genco::prelude::*;\n\nfn main() -> anyhow::Result<()> {\n let react = &js::import(\"react\", \"React\")."
},
{
"path": "examples/kotlin.rs",
"chars": 757,
"preview": "use genco::fmt;\nuse genco::lang::kotlin;\nuse genco::prelude::*;\n\nfn main() -> anyhow::Result<()> {\n let greeter_ty = "
},
{
"path": "examples/nix.rs",
"chars": 864,
"preview": "use genco::fmt;\nuse genco::prelude::*;\n\nfn main() -> anyhow::Result<()> {\n let nixpkgs = &nix::inherit(\"inputs\", \"nix"
},
{
"path": "examples/python.rs",
"chars": 586,
"preview": "use genco::fmt;\nuse genco::prelude::*;\n\nfn main() -> anyhow::Result<()> {\n let flask = &python::import(\"flask\", \"Flas"
},
{
"path": "examples/rust.rs",
"chars": 1267,
"preview": "use genco::fmt;\nuse genco::prelude::*;\n\nfn main() -> anyhow::Result<()> {\n // Import the LittleEndian item, without r"
},
{
"path": "genco-macros/.gitignore",
"chars": 31,
"preview": "/target/\n**/*.rs.bk\nCargo.lock\n"
},
{
"path": "genco-macros/Cargo.toml",
"chars": 798,
"preview": "[package]\nname = \"genco-macros\"\nversion = \"0.19.0\"\nauthors = [\"John-John Tedro <udoprog@tedro.se>\"]\nedition = \"2018\"\nrus"
},
{
"path": "genco-macros/README.md",
"chars": 1721,
"preview": "# genco-macros\n\n[<img alt=\"github\" src=\"https://img.shields.io/badge/github-udoprog/genco-8da0cb?style=for-the-badge&log"
},
{
"path": "genco-macros/build.rs",
"chars": 1087,
"preview": "use std::env;\nuse std::process::Command;\nuse std::str;\n\nfn main() {\n println!(\"cargo:rerun-if-changed=build.rs\");\n\n "
},
{
"path": "genco-macros/src/ast.rs",
"chars": 4578,
"preview": "use core::fmt;\n\nuse proc_macro2::{Span, TokenStream, TokenTree};\nuse syn::Token;\n\nuse crate::static_buffer::StaticBuffer"
},
{
"path": "genco-macros/src/cursor.rs",
"chars": 1160,
"preview": "use proc_macro2::Span;\n\nuse crate::fake::LineColumn;\n\n#[derive(Clone, Copy, Debug)]\npub(crate) struct Cursor {\n // Sp"
},
{
"path": "genco-macros/src/encoder.rs",
"chars": 14911,
"preview": "use crate::ast::{Ast, Control, ControlKind, Delimiter, MatchArm};\nuse crate::cursor::Cursor;\nuse crate::fake::LineColumn"
},
{
"path": "genco-macros/src/fake.rs",
"chars": 3936,
"preview": "use core::cell::{RefCell, RefMut};\nuse core::fmt::Arguments;\n\nuse proc_macro2::Span;\n\nuse crate::cursor::Cursor;\n\n/// Er"
},
{
"path": "genco-macros/src/lib.rs",
"chars": 3472,
"preview": "//! [<img alt=\"github\" src=\"https://img.shields.io/badge/github-udoprog/genco-8da0cb?style=for-the-badge&logo=github\" he"
},
{
"path": "genco-macros/src/quote.rs",
"chars": 16862,
"preview": "use proc_macro2::{Punct, Spacing, Span, TokenStream, TokenTree};\nuse syn::parse::{ParseBuffer, ParseStream};\nuse syn::sp"
},
{
"path": "genco-macros/src/quote_fn.rs",
"chars": 681,
"preview": "use proc_macro2::TokenStream;\nuse syn::parse::{Parse, ParseStream};\nuse syn::Result;\n\nuse crate::Ctxt;\n\npub(crate) struc"
},
{
"path": "genco-macros/src/quote_in.rs",
"chars": 1020,
"preview": "use proc_macro2::TokenStream;\nuse syn::parse::{Parse, ParseStream};\nuse syn::spanned::Spanned as _;\nuse syn::{Result, To"
},
{
"path": "genco-macros/src/requirements.rs",
"chars": 721,
"preview": "use proc_macro2::TokenStream;\n/// Language requirements for token stream.\n#[derive(Debug, Default, Clone, Copy)]\npub(cra"
},
{
"path": "genco-macros/src/static_buffer.rs",
"chars": 1050,
"preview": "use proc_macro2::{Span, TokenStream};\n\nuse crate::Ctxt;\n\n/// Buffer used to resolve static items.\npub(crate) struct Stat"
},
{
"path": "genco-macros/src/string_parser.rs",
"chars": 10017,
"preview": "//! Helper to parse quoted strings.\n\nuse core::cell::{Cell, RefCell};\nuse core::fmt::Write;\n\nuse proc_macro2::{Span, Tok"
},
{
"path": "src/fmt/config.rs",
"chars": 1683,
"preview": "use crate::lang::Lang;\n\n/// Indentation configuration.\n///\n/// ```\n/// use genco::prelude::*;\n/// use genco::fmt;\n///\n//"
},
{
"path": "src/fmt/cursor.rs",
"chars": 2385,
"preview": "use crate::fmt;\nuse crate::tokens::{Item, Kind};\n\n/// Trait for peeking items.\npub(super) trait Parse {\n type Output:"
},
{
"path": "src/fmt/fmt_writer.rs",
"chars": 1975,
"preview": "use crate::fmt;\n\n/// Helper struct to format a token stream to an underlying writer implementing\n/// [fmt::Write][std::f"
},
{
"path": "src/fmt/formatter.rs",
"chars": 9745,
"preview": "use core::mem;\n\nuse alloc::string::String;\n\nuse crate::fmt;\nuse crate::fmt::config::{Config, Indentation};\nuse crate::fm"
},
{
"path": "src/fmt/io_writer.rs",
"chars": 2201,
"preview": "use std::io;\n\nuse crate::fmt;\n\n/// Helper struct to format a token stream to an underlying writer implementing\n/// [`io:"
},
{
"path": "src/fmt/mod.rs",
"chars": 2243,
"preview": "//! Code formatting utilities.\n//!\n//! So you have a token stream and it's time to format it into a\n//! file/string/what"
},
{
"path": "src/fmt/vec_writer.rs",
"chars": 2156,
"preview": "use alloc::string::String;\nuse alloc::vec::Vec;\n\nuse crate::fmt;\n\n/// Helper struct to format a token stream as a vector"
},
{
"path": "src/lang/c.rs",
"chars": 3719,
"preview": "//! Specialization for C code generation.\n\nuse core::fmt::Write as _;\n\nuse alloc::collections::BTreeSet;\n\nuse crate as g"
},
{
"path": "src/lang/csharp/block_comment.rs",
"chars": 636,
"preview": "use crate::lang::Csharp;\nuse crate::tokens::{FormatInto, ItemStr};\nuse crate::Tokens;\n\n/// Format a doc comment where ea"
},
{
"path": "src/lang/csharp/comment.rs",
"chars": 611,
"preview": "use crate::lang::Csharp;\nuse crate::tokens;\nuse crate::Tokens;\n\n/// Format a doc comment where each line is preceeded by"
},
{
"path": "src/lang/csharp/mod.rs",
"chars": 7659,
"preview": "//! Specialization for Csharp code generation.\n//!\n//! # String Quoting in C#\n//!\n//! Since C# uses UTF-16 internally, b"
},
{
"path": "src/lang/dart/doc_comment.rs",
"chars": 621,
"preview": "use crate::lang::Dart;\nuse crate::tokens;\nuse crate::Tokens;\n\n/// Format a doc comment where each line is preceeded by `"
},
{
"path": "src/lang/dart/mod.rs",
"chars": 7735,
"preview": "//! Specialization for Dart code generation.\n//!\n//! # String Quoting in Dart\n//!\n//! Since Java uses UTF-16 internally,"
},
{
"path": "src/lang/go.rs",
"chars": 4065,
"preview": "//! Specialization for Go code generation.\n//!\n//! # Examples\n//!\n//! Basic example:\n//!\n//! ```rust\n//! use genco::prel"
},
{
"path": "src/lang/java/block_comment.rs",
"chars": 928,
"preview": "use crate::lang::Java;\nuse crate::tokens;\nuse crate::Tokens;\n\n/// Format a block comment, starting with `/**`, and endin"
},
{
"path": "src/lang/java/mod.rs",
"chars": 7326,
"preview": "//! Specialization for Java code generation.\n//!\n//! # String Quoting in Java\n//!\n//! Since Java uses UTF-16 internally,"
},
{
"path": "src/lang/js.rs",
"chars": 15673,
"preview": "//! Specialization for JavaScript code generation.\n//!\n//! # Examples\n//!\n//! Basic example:\n//!\n//! ```rust\n//! use gen"
},
{
"path": "src/lang/kotlin/mod.rs",
"chars": 8148,
"preview": "//! Specialization for Kotlin code generation.\n//!\n//! # String Quoting in Kotlin\n//!\n//! Since Kotlin runs on the JVM, "
},
{
"path": "src/lang/mod.rs",
"chars": 5879,
"preview": "//! Language specialization for genco\n//!\n//! This module contains sub-modules which provide implementations of the [Lan"
},
{
"path": "src/lang/nix.rs",
"chars": 9499,
"preview": "//! Nix\n\nuse core::fmt::Write as _;\n\nuse alloc::collections::BTreeSet;\nuse alloc::string::ToString;\n\nuse crate as genco;"
},
{
"path": "src/lang/python.rs",
"chars": 10747,
"preview": "//! Specialization for Python code generation.\n//!\n//! # Examples\n//!\n//! String quoting in Python:\n//!\n//! ```rust\n//! "
},
{
"path": "src/lang/rust.rs",
"chars": 19571,
"preview": "//! Specialization for Rust code generation.\n//!\n//! # Examples\n//!\n//! ```rust\n//! use genco::prelude::*;\n//!\n//! let t"
},
{
"path": "src/lang/swift.rs",
"chars": 6152,
"preview": "//! Specialization for Swift code generation.\n//!\n//! # String Quoting in Swift\n//!\n//! Swift uses UTF-8 internally, str"
},
{
"path": "src/lib.rs",
"chars": 32063,
"preview": "//! [<img alt=\"github\" src=\"https://img.shields.io/badge/github-udoprog/genco-8da0cb?style=for-the-badge&logo=github\" he"
},
{
"path": "src/macros.rs",
"chars": 6465,
"preview": "//! Macros helpers in genco.\n\n/// Macro to implement support for a custom language.\n///\n/// # Examples\n///\n/// ```\n/// u"
},
{
"path": "src/prelude.rs",
"chars": 213,
"preview": "//! Prelude containing typical things to import when using the library.\n\npub use crate::lang::*;\npub use crate::tokens::"
},
{
"path": "src/tokens/display.rs",
"chars": 1902,
"preview": "use core::fmt;\n\nuse alloc::string::ToString;\n\nuse crate::lang::Lang;\nuse crate::tokens::FormatInto;\nuse crate::Tokens;\n\n"
},
{
"path": "src/tokens/format_into.rs",
"chars": 11862,
"preview": "use core::fmt::Arguments;\n\nuse alloc::borrow::Cow;\nuse alloc::rc::Rc;\nuse alloc::string::{String, ToString};\nuse alloc::"
},
{
"path": "src/tokens/from_fn.rs",
"chars": 1205,
"preview": "use crate::lang::Lang;\nuse crate::tokens;\nuse crate::Tokens;\n\n/// Construct a [`FormatInto`][crate::tokens::FormatInto] "
},
{
"path": "src/tokens/internal.rs",
"chars": 1,
"preview": "\n"
},
{
"path": "src/tokens/item.rs",
"chars": 5981,
"preview": "//! A single element\n\nuse core::fmt;\n\nuse crate::lang::Lang;\nuse crate::tokens::{FormatInto, ItemStr, Tokens};\n\n#[derive"
},
{
"path": "src/tokens/item_str.rs",
"chars": 4898,
"preview": "//! Helper trait to take ownership of strings.\n\nuse core::fmt;\nuse core::hash::{Hash, Hasher};\nuse core::ops::Deref;\n\n#["
},
{
"path": "src/tokens/mod.rs",
"chars": 2005,
"preview": "//! Utilities for working with token streams.\n//!\n//! This is typically a module you will use if you intend to provide a"
},
{
"path": "src/tokens/quoted.rs",
"chars": 1849,
"preview": "use crate::lang::Lang;\nuse crate::tokens::{FormatInto, Tokens};\n\n/// Function to provide string quoting.\n///\n/// Note th"
},
{
"path": "src/tokens/register.rs",
"chars": 4098,
"preview": "use crate::lang::Lang;\nuse crate::tokens::FormatInto;\nuse crate::Tokens;\n\n/// Function to provide item registration.\n///"
},
{
"path": "src/tokens/static_literal.rs",
"chars": 977,
"preview": "use crate::lang::Lang;\nuse crate::tokens::{FormatInto, ItemStr};\n\n/// A formatter from a static literal.\n///\n/// Created"
},
{
"path": "src/tokens/tokens.rs",
"chars": 30978,
"preview": "//! A set of tokens that make up a single source-file.\n//!\n//! ## Example\n//!\n//! ```rust\n//! use genco::prelude::*;\n//!"
},
{
"path": "tests/test_indentation_rules.rs",
"chars": 773,
"preview": "use genco::prelude::*;\n\n#[test]\nfn test_indentation_rules() -> genco::fmt::Result {\n let rule1: Tokens<Rust> = quote!"
},
{
"path": "tests/test_option.rs",
"chars": 554,
"preview": "use genco::prelude::*;\n\n#[test]\nfn test_option() -> genco::fmt::Result {\n let test1 = Some(quote!(println!(\"{}\", $(qu"
},
{
"path": "tests/test_quote.rs",
"chars": 1664,
"preview": "use genco::prelude::*;\n\n#[test]\nfn test_quote() -> genco::fmt::Result {\n let test = quoted(\"one\");\n\n let tokens: r"
},
{
"path": "tests/test_quote_in.rs",
"chars": 512,
"preview": "use genco::prelude::*;\n\n/// basic smoketests.\n#[test]\nfn test_quote_in() -> genco::fmt::Result {\n let mut tokens = ru"
},
{
"path": "tests/test_quote_simple_expression.rs",
"chars": 588,
"preview": "use genco::prelude::*;\n\n#[test]\nfn test_quote_simple_expression() -> genco::fmt::Result {\n let tokens: Tokens<Rust> ="
},
{
"path": "tests/test_register.rs",
"chars": 580,
"preview": "use genco::prelude::*;\n\n#[test]\nfn test_register() -> genco::fmt::Result {\n let import = rust::import(\"std::iter\", \"F"
},
{
"path": "tests/test_string.rs",
"chars": 907,
"preview": "use genco::prelude::*;\n\n#[test]\nfn test_quoted() -> genco::fmt::Result {\n let t: dart::Tokens = quote!($[str](Hello $"
},
{
"path": "tests/test_token_gen.rs",
"chars": 12035,
"preview": "//! Test to assert that the tokens generated are equivalent.\n\nuse genco::fmt;\nuse genco::prelude::*;\nuse genco::tokens::"
}
]
About this extraction
This page contains the full source code of the udoprog/genco GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 77 files (342.1 KB), approximately 88.1k tokens, and a symbol index with 510 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.