diff --git a/Cargo.lock b/Cargo.lock index abe375c..869c7fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,6 +73,12 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "atomic" version = "0.6.1" @@ -1170,15 +1176,32 @@ dependencies = [ ] [[package]] -name = "pretty-print" -version = "0.1.9" +name = "pretty" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ad516586f2191e7ce412b9b164e61ee6403638aa70e98672b978c1f448e63f" +checksum = "0d22152487193190344590e4f30e219cf3fe140d9e7a3fdb683d82aa2c5f4156" +dependencies = [ + "arrayvec", + "typed-arena", + "unicode-width", +] + +[[package]] +name = "pretty-print" +version = "0.1.8" dependencies = [ "color-ansi", "unicode-segmentation", ] +[[package]] +name = "pretty-test" +version = "0.0.0" +dependencies = [ + "pretty", + "pretty-print", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -1898,6 +1921,12 @@ dependencies = [ "ratatui-widgets", ] +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typenum" version = "1.19.0" diff --git a/Cargo.toml b/Cargo.toml index fe5329e..fbf57f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,8 +8,8 @@ name = "lv" path = "./src/main.rs" [workspace] -members = [".", "./logparse/"] -default-members = [".", "./logparse/"] +members = [".", "./logparse/", "./pretty-print/projects/*"] +default-members = [".", "./logparse/", "./pretty-print/projects/pretty-print"] [dependencies] clap = {version="4.5", features=["derive", "string"]} @@ -26,4 +26,4 @@ regex = "1" crossterm = "*" dumpster = "2.1" logparse = {path = "./logparse/", version="0.2.0"} -pretty-print = "0.1" +pretty-print = {path = "./pretty-print/projects/pretty-print"} diff --git a/pretty-print/.editorconfig b/pretty-print/.editorconfig new file mode 100644 index 0000000..d8612ca --- /dev/null +++ b/pretty-print/.editorconfig @@ -0,0 +1,11 @@ +[*] +charset = utf-8 + + +[*.pest] +indent_style = space +indent_size = 4 + +[*.toml] +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/pretty-print/.github/workflows/rust.yml b/pretty-print/.github/workflows/rust.yml new file mode 100644 index 0000000..568af06 --- /dev/null +++ b/pretty-print/.github/workflows/rust.yml @@ -0,0 +1,27 @@ +name: Rust + +on: + push: + branches: [ master, dev ] + pull_request: + branches: [ master, dev ] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - run: git config --global core.autocrlf false + - uses: actions/checkout@v2 + - name: Rust Nightly + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + components: rustfmt, clippy + - name: Build + run: cargo build --release +# - name: Tests +# run: cargo test --release diff --git a/pretty-print/.gitignore b/pretty-print/.gitignore new file mode 100644 index 0000000..080460e --- /dev/null +++ b/pretty-print/.gitignore @@ -0,0 +1,14 @@ +# OS +.DS_Store/ +thumbs.db +time-travel.* + +# IDE +.vscode/ +.vs/ +.idea/ +*.iml + +# Rust +target/ +Cargo.lock diff --git a/pretty-print/.run/Test All.run.xml b/pretty-print/.run/Test All.run.xml new file mode 100644 index 0000000..facdcbf --- /dev/null +++ b/pretty-print/.run/Test All.run.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/pretty-print/License.md b/pretty-print/License.md new file mode 100644 index 0000000..0afe08d --- /dev/null +++ b/pretty-print/License.md @@ -0,0 +1,356 @@ +Mozilla Public License Version 2.0 +================================== + +### 1. Definitions + +**1.1. “Contributor”** + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +**1.2. “Contributor Version”** + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +**1.3. “Contribution”** + means Covered Software of a particular Contributor. + +**1.4. “Covered Software”** + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +**1.5. “Incompatible With Secondary Licenses”** + means + +* **(a)** that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or +* **(b)** that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +**1.6. “Executable Form”** + means any form of the work other than Source Code Form. + +**1.7. “Larger Work”** + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +**1.8. “License”** + means this document. + +**1.9. “Licensable”** + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +**1.10. “Modifications”** + means any of the following: + +* **(a)** any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or +* **(b)** any new file in Source Code Form that contains any Covered + Software. + +**1.11. “Patent Claims” of a Contributor** + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +**1.12. “Secondary License”** + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +**1.13. “Source Code Form”** + means the form of the work preferred for making modifications. + +**1.14. “You” (or “Your”)** + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, “control” means **(a)** the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or **(b)** ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + + +### 2. License Grants and Conditions + +#### 2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +* **(a)** under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and +* **(b)** under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +#### 2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +#### 2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +* **(a)** for any code that a Contributor has removed from Covered Software; + or +* **(b)** for infringements caused by: **(i)** Your and any other third party's + modifications of Covered Software, or **(ii)** the combination of its + Contributions with other software (except as part of its Contributor + Version); or +* **(c)** under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +#### 2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +#### 2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +#### 2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +#### 2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + + +### 3. Responsibilities + +#### 3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +#### 3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +* **(a)** such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +* **(b)** You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +#### 3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +#### 3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +#### 3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + + +### 4. Inability to Comply Due to Statute or Regulation + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: **(a)** comply with +the terms of this License to the maximum extent possible; and **(b)** +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + + +### 5. Termination + +**5.1.** The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated **(a)** provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and **(b)** on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +**5.2.** If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +**5.3.** In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + + +### 6. Disclaimer of Warranty + +> Covered Software is provided under this License on an “as is” +> basis, without warranty of any kind, either expressed, implied, or +> statutory, including, without limitation, warranties that the +> Covered Software is free of defects, merchantable, fit for a +> particular purpose or non-infringing. The entire risk as to the +> quality and performance of the Covered Software is with You. +> Should any Covered Software prove defective in any respect, You +> (not any Contributor) assume the cost of any necessary servicing, +> repair, or correction. This disclaimer of warranty constitutes an +> essential part of this License. No use of any Covered Software is +> authorized under this License except under this disclaimer. + +### 7. Limitation of Liability + +> Under no circumstances and under no legal theory, whether tort +> (including negligence), contract, or otherwise, shall any +> Contributor, or anyone who distributes Covered Software as +> permitted above, be liable to You for any direct, indirect, +> special, incidental, or consequential damages of any character +> including, without limitation, damages for lost profits, loss of +> goodwill, work stoppage, computer failure or malfunction, or any +> and all other commercial damages or losses, even if such party +> shall have been informed of the possibility of such damages. This +> limitation of liability shall not apply to liability for death or +> personal injury resulting from such party's negligence to the +> extent applicable law prohibits such limitation. Some +> jurisdictions do not allow the exclusion or limitation of +> incidental or consequential damages, so this exclusion and +> limitation may not apply to You. + + +### 8. Litigation + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + + +### 9. Miscellaneous + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + + +### 10. Versions of the License + +#### 10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +#### 10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +#### 10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +#### 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +## Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +## Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + diff --git a/pretty-print/Readme.md b/pretty-print/Readme.md new file mode 100644 index 0000000..ed8ede1 --- /dev/null +++ b/pretty-print/Readme.md @@ -0,0 +1,68 @@ +# Pretty Printer + +This crate defines a +[Wadler-style](http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf) +pretty-printing API. + +Start with the static functions of [PrettyTree](enum.Doc.html). + +## Quick start + +Let's pretty-print simple sexps! We want to pretty print sexps like + +```lisp +(1 2 3) +``` +or, if the line would be too long, like + +```lisp +((1) + (2 3) + (4 5 6)) +``` + +A _simple symbolic expression_ consists of a numeric _atom_ or a nested ordered _list_ of +symbolic expression children. + + + +```rust +use pretty_print::*; +use SExp::*; +enum SExp { + Atom(u32), + List(Vec), +} + +impl SExp { + /// Return a pretty printed format of self. + pub fn to_doc(&self) -> PrettyTree { + match self { + Atom(x) => PrettyTree::text(x.to_string()), + List(xs) => PrettyTree::text("(") + .append(PrettyTree::join(xs.into_iter().map(|x| x.to_doc()), PrettyTree::line_or_space()).nest(1).group()) + .append(PrettyTree::text(")")), + } + } + /// Return a pretty printed format of self. + pub fn to_pretty(&self, width: usize) -> String { + let mut w = Vec::new(); + self.to_doc().render(width, &mut w).unwrap(); + String::from_utf8(w).unwrap() + } +} + +fn main() { + let atom = Atom(5); + assert_eq!("5", atom.to_pretty(10)); + let list = List(vec![Atom(1), Atom(2), Atom(3)]); + assert_eq!("(1 2 3)", list.to_pretty(10)); + assert_eq!( + "\ +(1 + 2 + 3)", + list.to_pretty(5) + ); +} +``` \ No newline at end of file diff --git a/pretty-print/package.json b/pretty-print/package.json new file mode 100644 index 0000000..3dad9e7 --- /dev/null +++ b/pretty-print/package.json @@ -0,0 +1,11 @@ +{ + "private": true, + "scripts": { + "doc": "cargo doc --no-deps --all-features", + "test": "cargo test --release", + "fmt": "cargo fmt --all", + "p": "git push && git push --tags --prune", + "reset": "git reset Head~ --soft", + "u": "cargo upgrade --workspace" + } +} diff --git a/pretty-print/projects/pretty-print/Cargo.toml b/pretty-print/projects/pretty-print/Cargo.toml new file mode 100644 index 0000000..3cfa4ca --- /dev/null +++ b/pretty-print/projects/pretty-print/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "pretty-print" +version = "0.1.8" +authors = ["Aster <192607617@qq.com>"] +description = "pretty print tree" +repository = "https://github.com/oovm/pretty-print" +documentation = "https://docs.rs/pretty-print" +readme = "Readme.md" +license = "MPL-2.0" +edition = "2021" + +[dependencies] +unicode-segmentation = "1.10.1" + +[dependencies.color-ansi] +version = "0.1.0" +#default-features = false +#path = 'C:\Users\Dell\CLionProjects\color-rs\projects\color-ansi' + +[dev-dependencies] + + +[features] +default = ["std"] +std = [] \ No newline at end of file diff --git a/pretty-print/projects/pretty-print/package.json b/pretty-print/projects/pretty-print/package.json new file mode 100644 index 0000000..381d4be --- /dev/null +++ b/pretty-print/projects/pretty-print/package.json @@ -0,0 +1,6 @@ +{ + "private": true, + "scripts": { + "p": "cargo publish --allow-dirty" + } +} diff --git a/pretty-print/projects/pretty-print/readme.md b/pretty-print/projects/pretty-print/readme.md new file mode 100644 index 0000000..271b962 --- /dev/null +++ b/pretty-print/projects/pretty-print/readme.md @@ -0,0 +1,24 @@ +This crate defines a +[Wadler-style](http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf) +pretty-printing API. + +Start with the static functions of [Doc](enum.Doc.html). + +## Quick start + +Let's pretty-print simple sexps! We want to pretty print sexps like + +```lisp +(1 2 3) +``` +or, if the line would be too long, like + +```lisp +((1) + (2 3) + (4 5 6)) +``` + +A _simple symbolic expression_ consists of a numeric _atom_ or a nested ordered _list_ of +symbolic expression children. + diff --git a/pretty-print/projects/pretty-print/src/helpers/affixes.rs b/pretty-print/projects/pretty-print/src/helpers/affixes.rs new file mode 100644 index 0000000..de618b4 --- /dev/null +++ b/pretty-print/projects/pretty-print/src/helpers/affixes.rs @@ -0,0 +1,193 @@ +//! Document formatting of "helpers" such as where some number of prefixes and suffixes would +//! ideally be layed out onto a single line instead of breaking them up into multiple lines. See +//! `BlockDoc` for an example + +use crate::{docs, DocumentTree, DocumentTree}; + +pub struct Affixes<'doc, D, A> +{ + prefix: DocumentTree<'doc, D, A>, + suffix: DocumentTree<'doc, D, A>, + nest: bool, +} + +impl<'a, D> Clone for Affixes<'a, D> +{ + fn clone(&self) -> Self { + Affixes { + prefix: self.prefix.clone(), + suffix: self.suffix.clone(), + nest: self.nest, + } + } +} + +impl<'doc, D, A> Affixes<'doc, D, A> +{ + pub fn new(prefix: DocumentTree<'doc, D, A>, suffix: DocumentTree<'doc, D, A>) -> Self { + Affixes { + prefix, + suffix, + nest: false, + } + } + + pub fn nest(mut self) -> Self { + self.nest = true; + self + } +} + +/// Formats a set of `prefix` and `suffix` documents around a `body` +/// +/// The following document split into the prefixes [\x y ->, \z ->, {], suffixes [nil, nil, }] and +/// body [result: x + y - z] will try to be formatted +/// +/// ```gluon +/// \x y -> \z -> { result: x + y - z } +/// ``` +/// +/// ```gluon +/// \x y -> \z -> { +/// result: x + y - z +/// } +/// ``` +/// +/// ```gluon +/// \x y -> \z -> +/// { +/// result: x + y - z +/// } +/// ``` +/// +/// ```gluon +/// \x y -> +/// \z -> +/// { +/// result: x + y - z +/// } +/// ``` +pub struct BlockDoc<'doc, D, A> + where + D: DocAllocator<'doc, A>, +{ + pub affixes: Vec>, + pub body: DocumentTree<'doc, D, A>, +} + +impl<'doc, D, A> BlockDoc<'doc, D, A> + where + D: DocAllocator<'doc, A>, + D::Doc: Clone, + A: Clone, +{ + pub fn format(self, nest: isize) -> DocumentTree<'doc, D, A> { + let arena = self.body.0; + + let fail_on_multi_line = arena.fail().flat_alt(arena.nil()); + + (1..self.affixes.len() + 1) + .rev() + .map(|split| { + let (before, after) = self.affixes.split_at(split); + let last = before.len() == 1; + docs![ + arena, + docs![ + arena, + arena.concat(before.iter().map(|affixes| affixes.prefix.clone())), + if last { + arena.nil() + } else { + fail_on_multi_line.clone() + } + ] + .group(), + docs![ + arena, + after.iter().rev().cloned().fold( + docs![ + arena, + self.body.clone(), + // If there is no prefix then we must not allow the body to laid out on multiple + // lines without nesting + if !last + && before + .iter() + .all(|affixes| matches!(&*affixes.prefix.1, DocumentTree::Nil)) + { + fail_on_multi_line.clone() + } else { + arena.nil() + }, + ] + .nest(nest) + .append( + arena.concat(after.iter().map(|affixes| affixes.suffix.clone())) + ), + |acc, affixes| { + let mut doc = affixes.prefix.append(acc); + if affixes.nest { + doc = doc.nest(nest); + } + doc.group() + }, + ), + arena.concat(before.iter().map(|affixes| affixes.suffix.clone())), + ] + .group(), + ] + }) + .fold(None::>, |acc, doc| { + Some(match acc { + None => doc, + Some(acc) => acc.union(doc), + }) + }) + .unwrap_or(self.body) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::Arena; + + #[test] + fn format_block() { + let arena = &Arena::<()>::new(); + let mk_doc = || BlockDoc { + affixes: vec![ + Affixes::new(docs![arena, "\\x y ->"], arena.nil()).nest(), + Affixes::new(docs![arena, arena.line(), "\\z ->"], arena.nil()).nest(), + Affixes::new( + docs![arena, arena.line(), "{"], + docs![arena, arena.line(), "}"], + ) + .nest(), + ], + body: docs![arena, arena.line(), "result"], + }; + expect_test::expect![[r#"\x y -> \z -> { result }"#]] + .assert_eq(&mk_doc().format(4).1.pretty(40).to_string()); + expect_test::expect![[r#" +\x y -> \z -> { + result +}"#]] + .assert_eq(&mk_doc().format(4).1.pretty(15).to_string()); + expect_test::expect![[r#" +\x y -> \z -> + { + result + }"#]] + .assert_eq(&mk_doc().format(4).1.pretty(14).to_string()); + expect_test::expect![[r#" +\x y -> + \z -> + { + result + }"#]] + .assert_eq(&mk_doc().format(4).1.pretty(12).to_string()); + } +} diff --git a/pretty-print/projects/pretty-print/src/helpers/hard_block.rs b/pretty-print/projects/pretty-print/src/helpers/hard_block.rs new file mode 100644 index 0000000..a005f98 --- /dev/null +++ b/pretty-print/projects/pretty-print/src/helpers/hard_block.rs @@ -0,0 +1,66 @@ +use super::*; + +/// A soft block is a block that is not required to be on a new line. +/// +/// ```vk +/// {a, b, c} +/// +/// { +/// a, +/// b, +/// } +/// ``` +#[derive(Clone, Debug)] +pub struct HardBlock { + /// The indentation of the soft block + pub indent: usize, + /// The left hand side of the soft block + pub lhs: &'static str, + /// The right hand side of the soft block + pub rhs: &'static str, + /// The joint node of the soft block + pub joint: PrettyTree, +} + +impl HardBlock { + /// Build a new soft block + pub fn new(lhs: &'static str, rhs: &'static str) -> Self { + Self { lhs, rhs, indent: 4, joint: PrettyTree::line_or_space() } + } + /// Build a new soft block with the parentheses syntax + pub fn parentheses() -> Self { + Self::new("(", ")") + } + /// Build a new soft block with the brackets syntax + pub fn brackets() -> Self { + Self::new("[", "]") + } + /// Build a new soft block with the curly braces syntax + pub fn curly_braces() -> Self { + Self::new("{", "}") + } + /// Set the joint node of the soft block + pub fn with_joint(self, joint: PrettyTree) -> Self { + Self { joint, ..self } + } +} + +impl HardBlock { + /// Join a slice of pretty printables with the soft block + pub fn join_slice(&self, slice: &[T], theme: &PrettyProvider) -> PrettyTree { + let mut outer = PrettySequence::new(5); + outer += self.lhs; + outer += PrettyTree::Hardline; + let mut inner = PrettySequence::new(slice.len() * 2); + for (idx, term) in slice.iter().enumerate() { + if idx != 0 { + inner += self.joint.clone(); + } + inner += term.pretty(theme); + } + outer += inner.indent(self.indent); + outer += PrettyTree::Hardline; + outer += self.rhs; + outer.into() + } +} diff --git a/pretty-print/projects/pretty-print/src/helpers/k_and_r_bracket.rs b/pretty-print/projects/pretty-print/src/helpers/k_and_r_bracket.rs new file mode 100644 index 0000000..8bf51fa --- /dev/null +++ b/pretty-print/projects/pretty-print/src/helpers/k_and_r_bracket.rs @@ -0,0 +1,61 @@ +use super::*; +use crate::PrettyBuilder; + +/// `K & R` style brackets +/// +/// ```vk +/// a {} +/// ``` +/// +/// ```vk +/// a { +/// +/// } +/// ``` +#[derive(Copy, Clone, Debug)] +pub struct KAndRBracket { + /// Whether to add a space after the keyword + pub head_space: bool, + /// The left bracket + pub bracket_l: &'static str, + /// The right bracket + pub bracket_r: &'static str, +} + +impl KAndRBracket { + /// Build a bracketed block + pub fn curly_braces() -> Self { + Self { head_space: true, bracket_l: "{", bracket_r: "}" } + } + /// Build a bracketed block + pub fn build<'a, I>( + &self, + items: &[I], + allocator: &'a PrettyProvider, + inline_join: PrettyTree, + block_join: PrettyTree, + ) -> PrettyTree + where + I: PrettyPrint, + { + let mut output = PrettySequence::new(5); + if self.head_space { + output.push(" "); + } + output.push(self.bracket_l); + // inline + let mut inline = PrettySequence::new(3); + inline.push(" "); + inline.push(allocator.join_slice(items, inline_join)); + inline.push(" "); + // block + let mut block = PrettySequence::new(3); + block.push(PrettyTree::Hardline); + block.push(allocator.join_slice(items, block_join).indent(4)); + block.push(PrettyTree::Hardline); + // + output.push(block.flat_alt(inline)); + output.push(self.bracket_r); + output.into() + } +} diff --git a/pretty-print/projects/pretty-print/src/helpers/mod.rs b/pretty-print/projects/pretty-print/src/helpers/mod.rs new file mode 100644 index 0000000..bc9c6fe --- /dev/null +++ b/pretty-print/projects/pretty-print/src/helpers/mod.rs @@ -0,0 +1,13 @@ +#![doc = include_str!("readme.md")] + +use crate::{PrettyBuilder, PrettyPrint, PrettyProvider, PrettyTree}; +use alloc::vec::Vec; +use core::ops::AddAssign; + +mod hard_block; +mod k_and_r_bracket; +mod sequence; +mod soft_block; +// mod affixes; + +pub use self::{hard_block::HardBlock, k_and_r_bracket::KAndRBracket, sequence::PrettySequence, soft_block::SoftBlock}; diff --git a/pretty-print/projects/pretty-print/src/helpers/readme.md b/pretty-print/projects/pretty-print/src/helpers/readme.md new file mode 100644 index 0000000..e69de29 diff --git a/pretty-print/projects/pretty-print/src/helpers/sequence.rs b/pretty-print/projects/pretty-print/src/helpers/sequence.rs new file mode 100644 index 0000000..246eae0 --- /dev/null +++ b/pretty-print/projects/pretty-print/src/helpers/sequence.rs @@ -0,0 +1,60 @@ +use super::*; + +/// The document sequence type. +#[derive(Clone, Debug, Default)] +pub struct PrettySequence { + items: Vec, +} + +impl PrettySequence { + /// Create a new sequence with the given capacity. + pub fn new(capacity: usize) -> Self { + Self { items: Vec::with_capacity(capacity) } + } + /// Create a new sequence with the given capacity. + pub fn push(&mut self, item: T) + where + T: Into, + { + self.items.push(item.into()); + } + /// Create a new sequence with the given capacity. + pub fn extend(&mut self, items: I) + where + I: IntoIterator, + T: Into, + { + self.items.extend(items.into_iter().map(|x| x.into())); + } +} + +impl PrettyBuilder for PrettySequence { + fn flat_alt(self, flat: E) -> PrettyTree + where + E: Into, + { + PrettyTree::from(self).flat_alt(flat) + } + fn indent(self, indent: usize) -> PrettyTree { + PrettyTree::from(self).indent(indent) + } + + fn nest(self, offset: isize) -> PrettyTree { + PrettyTree::from(self).nest(offset) + } +} + +impl AddAssign for PrettySequence +where + T: Into, +{ + fn add_assign(&mut self, rhs: T) { + self.push(rhs); + } +} + +impl From for PrettyTree { + fn from(value: PrettySequence) -> Self { + Self::concat(value.items) + } +} diff --git a/pretty-print/projects/pretty-print/src/helpers/soft_block.rs b/pretty-print/projects/pretty-print/src/helpers/soft_block.rs new file mode 100644 index 0000000..d53f716 --- /dev/null +++ b/pretty-print/projects/pretty-print/src/helpers/soft_block.rs @@ -0,0 +1,72 @@ +use super::*; + +/// A soft block is a block that is not required to be on a new line. +/// +/// ```vk +/// {a, b, c} +/// +/// { +/// a, +/// b, +/// } +/// ``` +#[derive(Clone, Debug)] +pub struct SoftBlock { + /// The indentation of the soft block + pub indent: usize, + /// The left hand side of the soft block + pub lhs: &'static str, + /// The right hand side of the soft block + pub rhs: &'static str, + /// The joint node of the soft block + pub joint: PrettyTree, + /// The tail node of the soft block + pub tail: PrettyTree, +} + +impl SoftBlock { + /// Build a new soft block + pub fn new(lhs: &'static str, rhs: &'static str) -> Self { + Self { lhs, rhs, indent: 4, joint: PrettyTree::line_or_space(), tail: PrettyTree::Nil } + } + /// Build a new soft block with the tuple syntax + pub fn tuple() -> Self { + Self::new("(", ")") + } + /// Build a new soft block with the parentheses syntax + pub fn parentheses() -> Self { + Self::new("(", ")") + } + /// Build a new soft block with the brackets syntax + pub fn brackets() -> Self { + Self::new("[", "]") + } + /// Build a new soft block with the curly braces syntax + pub fn curly_braces() -> Self { + Self::new("{", "}") + } + /// Set the joint node of the soft block + pub fn with_joint(self, joint: PrettyTree) -> Self { + Self { joint, ..self } + } +} + +impl SoftBlock { + /// Join a slice of pretty printables with the soft block + pub fn join_slice(&self, slice: &[T], theme: &PrettyProvider) -> PrettyTree { + let mut outer = PrettySequence::new(5); + outer += self.lhs; + outer += PrettyTree::line_or_space(); + let mut inner = PrettySequence::new(slice.len() * 2); + for (idx, term) in slice.iter().enumerate() { + if idx != 0 { + inner += self.joint.clone(); + } + inner += term.pretty(theme); + } + outer += inner.indent(self.indent); + outer += PrettyTree::line_or_space(); + outer += self.rhs; + outer.into() + } +} diff --git a/pretty-print/projects/pretty-print/src/lib.rs b/pretty-print/projects/pretty-print/src/lib.rs new file mode 100644 index 0000000..616dc58 --- /dev/null +++ b/pretty-print/projects/pretty-print/src/lib.rs @@ -0,0 +1,51 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![deny(missing_debug_implementations, missing_copy_implementations)] +#![warn(missing_docs, rustdoc::missing_crate_level_docs)] +#![doc = include_str!("../readme.md")] +#![doc(html_logo_url = "https://raw.githubusercontent.com/oovm/shape-rs/dev/projects/images/Trapezohedron.svg")] +#![doc(html_favicon_url = "https://raw.githubusercontent.com/oovm/shape-rs/dev/projects/images/Trapezohedron.svg")] + +extern crate alloc; +extern crate core; + +pub mod helpers; +mod providers; +mod render; +mod traits; +mod tree; + +pub use self::render::{ + write_fmt::{BufferWrite, FmtWrite}, + PrettyFormatter, Render, RenderAnnotated, +}; +#[cfg(feature = "std")] +pub use crate::render::write_io::{IoWrite, TerminalWriter}; +pub use crate::{ + providers::PrettyProvider, + traits::{printer::PrettyPrint, PrettyBuilder}, + tree::PrettyTree, +}; +pub use color_ansi::*; + +/// Concatenates a number of documents (or values that can be converted into a document via the +/// `Pretty` trait, like `&str`) +/// +/// ``` +/// use pretty_print::docs; +/// let doc = +/// docs!["let", arena.softline(), "x", arena.softline(), "=", arena.softline(), Some("123"),]; +/// assert_eq!(doc.1.pretty(80).to_string(), "let x = 123"); +/// ``` +#[macro_export] +macro_rules! docs { + ($alloc: expr, $first: expr $(,)?) => { + $crate::Pretty::pretty($first, $alloc) + }; + ($alloc: expr, $first: expr $(, $rest: expr)+ $(,)?) => {{ + let mut doc = $crate::Pretty::pretty($first, $alloc); + $( + doc = doc.append($rest); + )* + doc + }} +} diff --git a/pretty-print/projects/pretty-print/src/providers/mod.rs b/pretty-print/projects/pretty-print/src/providers/mod.rs new file mode 100644 index 0000000..91ea91e --- /dev/null +++ b/pretty-print/projects/pretty-print/src/providers/mod.rs @@ -0,0 +1,236 @@ +use crate::{PrettyPrint, PrettyTree}; +use alloc::{borrow::Cow, rc::Rc}; +use color_ansi::AnsiStyle; +use core::fmt::{Debug, Formatter}; + +/// Represents a pretty-printable tree provider. +pub struct PrettyProvider { + width: usize, + keyword: Rc, + string: Rc, + number: Rc, + macros: Rc, + argument: Rc, + argument_mut: Rc, + local: Rc, + local_mut: Rc, + operator: Rc, + structure: Rc, + variant: Rc, + interface: Rc, +} + +impl Debug for PrettyProvider { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("PrettyProvider").finish() + } +} + +impl PrettyProvider { + /// Creates a new pretty-printable tree provider. + pub fn new(width: usize) -> Self { + Self { + width, + keyword: AnsiStyle::rgb(197, 119, 207).into(), + string: AnsiStyle::rgb(152, 195, 121).into(), + number: AnsiStyle::rgb(206, 153, 100).into(), + macros: AnsiStyle::rgb(87, 182, 194).into(), + argument: AnsiStyle::rgb(239, 112, 117).into(), + argument_mut: AnsiStyle::rgb(239, 112, 117).with_underline().into(), + local: AnsiStyle::rgb(152, 195, 121).into(), + local_mut: AnsiStyle::rgb(152, 195, 121).with_underline().into(), + operator: AnsiStyle::rgb(90, 173, 238).into(), + structure: AnsiStyle::rgb(197, 119, 207).into(), + variant: AnsiStyle::rgb(239, 112, 117).into(), + interface: AnsiStyle::rgb(197, 119, 207).into(), + } + } +} + +impl PrettyProvider { + /// Gets the width of the document. + pub fn get_width(&self) -> usize { + self.width + } + /// Sets the width of the document. + pub fn set_width(&mut self, width: usize) { + self.width = width; + } + /// Gets the width of the document. + pub fn text(&self, text: S) -> PrettyTree + where + S: Into>, + { + PrettyTree::text(text) + } + /// Gets the width of the document. + pub fn custom(&self, text: S, style: Rc) -> PrettyTree + where + S: Into>, + { + PrettyTree::text(text).annotate(style) + } + /// Allocate a document containing the given text. + pub fn keyword(&self, text: S) -> PrettyTree + where + S: Into>, + { + PrettyTree::text(text).annotate(self.keyword.clone()) + } + /// Allocate a document containing the given text. + pub fn identifier(&self, text: S) -> PrettyTree + where + S: Into>, + { + PrettyTree::text(text).annotate(self.operator.clone()) + } + /// Allocate a document containing the given text. + pub fn generic(&self, text: S) -> PrettyTree + where + S: Into>, + { + PrettyTree::text(text).annotate(self.macros.clone()) + } + + /// Allocate a document containing the given text. + pub fn variable(&self, text: S, mutable: bool) -> PrettyTree + where + S: Into>, + { + if mutable { + PrettyTree::text(text).annotate(self.local_mut.clone()) + } + else { + PrettyTree::text(text).annotate(self.local.clone()) + } + } + /// Allocate a document containing the given text. + pub fn argument(&self, text: S, mutable: bool) -> PrettyTree + where + S: Into>, + { + if mutable { + PrettyTree::text(text).annotate(self.argument_mut.clone()) + } + else { + PrettyTree::text(text).annotate(self.argument.clone()) + } + } + /// Allocate a document containing the given text. + pub fn operator(&self, text: S) -> PrettyTree + where + S: Into>, + { + PrettyTree::text(text).annotate(self.operator.clone()) + } + /// Allocate a document containing the given text. + pub fn string(&self, text: S) -> PrettyTree + where + S: Into>, + { + PrettyTree::text(text).annotate(self.string.clone()) + } + /// Allocate a document containing the given text. + pub fn annotation(&self, text: S) -> PrettyTree + where + S: Into>, + { + PrettyTree::text(text).annotate(self.macros.clone()) + } + /// Allocate a document containing the given text. + pub fn number(&self, text: S) -> PrettyTree + where + S: Into>, + { + PrettyTree::text(text).annotate(self.number.clone()) + } + /// Allocate a document containing the given text. + pub fn structure(&self, text: S) -> PrettyTree + where + S: Into>, + { + PrettyTree::text(text).annotate(self.structure.clone()) + } + /// Allocate a document containing the given text. + pub fn variant(&self, text: S) -> PrettyTree + where + S: Into>, + { + PrettyTree::text(text).annotate(self.variant.clone()) + } + + /// Allocate a document containing the given text. + pub fn interface(&self, text: S) -> PrettyTree + where + S: Into>, + { + PrettyTree::text(text).annotate(self.interface.clone()) + } +} + +impl PrettyProvider { + /// Allocate a document containing the given text. + /// + /// # Examples + /// + /// ``` + /// # use pretty_print::PrettyProvider; + /// let theme = PrettyProvider::new(80); + /// theme.join(vec!["a", "b", "c"], ", "); + /// ``` + pub fn join(&self, iter: I, joint: T2) -> PrettyTree + where + I: IntoIterator, + T1: PrettyPrint, + T2: PrettyPrint, + { + PrettyTree::join(iter.into_iter().map(|x| x.pretty(self)), joint.pretty(self)) + } + /// Allocate a document containing the given text. + /// + /// # Examples + /// + /// ``` + /// # use pretty_print::PrettyProvider; + /// let theme = PrettyProvider::new(80); + /// theme.join(&["a", "b", "c"], ", "); + /// ``` + pub fn join_slice(&self, iter: &[I], joint: T) -> PrettyTree + where + I: PrettyPrint, + T: PrettyPrint, + { + PrettyTree::join(iter.iter().map(|s| s.pretty(self)), joint.pretty(self)) + } + /// Allocate a document containing the given text. + /// + /// # Examples + /// + /// ``` + /// # use pretty_print::PrettyProvider; + /// let theme = PrettyProvider::new(80); + /// theme.concat(vec!["1", "2", "3"]); + /// ``` + pub fn concat(&self, iter: I) -> PrettyTree + where + I: IntoIterator, + T: PrettyPrint, + { + PrettyTree::concat(iter.into_iter().map(|x| x.pretty(self))) + } + /// Allocate a document containing the given text. + /// + /// # Examples + /// + /// ``` + /// # use pretty_print::PrettyProvider; + /// let theme = PrettyProvider::new(80); + /// theme.concat_slice(&["1", "2", "3"]); + /// ``` + pub fn concat_slice(&self, iter: &[T]) -> PrettyTree + where + T: PrettyPrint, + { + PrettyTree::concat(iter.iter().map(|s| s.pretty(self))) + } +} diff --git a/pretty-print/projects/pretty-print/src/render/mod.rs b/pretty-print/projects/pretty-print/src/render/mod.rs new file mode 100644 index 0000000..e92e0b2 --- /dev/null +++ b/pretty-print/projects/pretty-print/src/render/mod.rs @@ -0,0 +1,372 @@ +use crate::{BufferWrite, PrettyTree}; +use alloc::{rc::Rc, vec, vec::Vec}; +use color_ansi::AnsiStyle; +use core::fmt::{Debug, Display, Formatter}; + +#[cfg(feature = "std")] +pub mod write_io; + +pub mod write_fmt; + +/// Trait representing the operations necessary to render a document +pub trait Render { + /// The type of the output + type Error; + + /// Write a string to the output + fn write_str(&mut self, s: &str) -> Result; + + /// Write a character to the output + fn write_str_all(&mut self, mut s: &str) -> Result<(), Self::Error> { + while !s.is_empty() { + let count = self.write_str(s)?; + s = &s[count..]; + } + Ok(()) + } + + /// Write a character to the output + fn fail_doc(&self) -> Self::Error; +} + +/// The given text, which must not contain line breaks. +#[derive(Debug)] +pub struct PrettyFormatter<'a> { + tree: &'a PrettyTree, + width: usize, +} + +impl<'a> Display for PrettyFormatter<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + self.tree.render_fmt(self.width, f) + } +} + +impl PrettyTree { + /// Returns a value which implements `std::fmt::Display` + /// + /// ``` + /// use pretty::{BoxDoc, Doc}; + /// let doc = + /// BoxDoc::<()>::group(BoxDoc::text("hello").append(Doc::line()).append(Doc::text("world"))); + /// assert_eq!(format!("{}", doc.pretty(80)), "hello world"); + /// ``` + #[inline] + pub fn pretty(&self, width: usize) -> PrettyFormatter<'_> { + PrettyFormatter { tree: self, width } + } +} + +/// Trait representing the operations necessary to write an annotated document. +pub trait RenderAnnotated: Render { + /// Push an annotation onto the stack + fn push_annotation(&mut self, annotation: Rc) -> Result<(), Self::Error>; + /// Pop an annotation from the stack + fn pop_annotation(&mut self) -> Result<(), Self::Error>; +} + +#[derive(Debug)] +enum Annotation { + Push(Rc), + Pop, +} + +macro_rules! make_spaces { + () => { "" }; + ($s: tt $($t: tt)*) => { concat!(" ", make_spaces!($($t)*)) }; +} + +pub(crate) const SPACES: &str = make_spaces!(,,,,,,,,,,); + +fn append_docs2(ldoc: Rc, rdoc: Rc, mut consumer: impl FnMut(Rc)) -> Rc { + let d = append_docs(rdoc, &mut consumer); + consumer(d); + append_docs(ldoc, &mut consumer) +} + +fn append_docs(mut doc: Rc, consumer: &mut impl FnMut(Rc)) -> Rc { + loop { + // Since appended documents often appear in sequence on the left side we + // gain a slight performance increase by batching these pushes (avoiding + // to push and directly pop `Append` documents) + match doc.as_ref() { + PrettyTree::Append { lhs, rhs } => { + let d = append_docs(rhs.clone(), consumer); + consumer(d); + doc = lhs.clone(); + } + _ => return doc, + } + } +} + +pub fn best(doc: Rc, width: usize, out: &mut W) -> Result<(), W::Error> +where + W: RenderAnnotated, + W: ?Sized, +{ + Best { + pos: 0, + back_cmds: vec![RenderCommand { indent: 0, mode: Mode::Break, node: doc }], + front_cmds: vec![], + annotation_levels: vec![], + width, + } + .best(0, out)?; + + Ok(()) +} + +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +enum Mode { + Break, + Flat, +} + +struct RenderCommand { + indent: usize, + mode: Mode, + node: Rc, +} + +fn write_newline(ind: usize, out: &mut W) -> Result<(), W::Error> +where + W: ?Sized + Render, +{ + out.write_str_all("\n")?; + write_spaces(ind, out) +} + +fn write_spaces(spaces: usize, out: &mut W) -> Result<(), W::Error> +where + W: ?Sized + Render, +{ + let mut inserted = 0; + while inserted < spaces { + let insert = core::cmp::min(SPACES.len(), spaces - inserted); + inserted += out.write_str(&SPACES[..insert])?; + } + + Ok(()) +} + +struct Best { + pos: usize, + back_cmds: Vec, + front_cmds: Vec>, + annotation_levels: Vec, + width: usize, +} + +impl Best { + fn fitting(&mut self, next: Rc, mut pos: usize, ind: usize) -> bool { + let mut bidx = self.back_cmds.len(); + self.front_cmds.clear(); // clear from previous calls from best + self.front_cmds.push(next); + let mut mode = Mode::Flat; + + loop { + let mut doc = match self.front_cmds.pop() { + None => { + if bidx == 0 { + // All commands have been processed + return true; + } else { + bidx -= 1; + mode = Mode::Break; + self.back_cmds[bidx].node.clone() + } + } + Some(cmd) => cmd, + }; + + loop { + match doc.as_ref() { + PrettyTree::Nil => {} + PrettyTree::Append { lhs, rhs } => { + doc = append_docs2(lhs.clone(), rhs.clone(), |send| self.front_cmds.push(send)); + continue; + } + // Newlines inside the group makes it not fit, but those outside lets it + // fit on the current line + PrettyTree::Hardline => return mode == Mode::Break, + PrettyTree::RenderLength { length: len, body: _ } => { + pos += len; + if pos > self.width { + return false; + } + } + PrettyTree::StaticText(str) => { + pos += str.len(); + if pos > self.width { + return false; + } + } + PrettyTree::Text(ref str) => { + pos += str.len(); + if pos > self.width { + return false; + } + } + PrettyTree::MaybeInline { block: flat, inline } => { + doc = match mode { + Mode::Break => flat.clone(), + Mode::Flat => inline.clone(), + }; + continue; + } + + PrettyTree::Column { invoke: function } => { + doc = Rc::new(function(pos)); + continue; + } + PrettyTree::Nesting { invoke: function } => { + doc = Rc::new(function(ind)); + continue; + } + PrettyTree::Nest { space: _, doc: next } + | PrettyTree::Group { items: next } + | PrettyTree::Annotated { style: _, body: next } + | PrettyTree::Union { lhs: _, rhs: next } => { + doc = next.clone(); + continue; + } + PrettyTree::Fail => return false, + } + break; + } + } + } + + fn best(&mut self, top: usize, out: &mut W) -> Result + where + W: RenderAnnotated, + W: ?Sized, + { + let mut fits = true; + + while top < self.back_cmds.len() { + let mut cmd = self.back_cmds.pop().unwrap(); + loop { + let RenderCommand { indent: ind, mode, node } = cmd; + match node.as_ref() { + PrettyTree::Nil => {} + PrettyTree::Append { lhs, rhs } => { + cmd.node = append_docs2(lhs.clone(), rhs.clone(), |send| { + self.back_cmds.push(RenderCommand { indent: ind, mode, node: send }) + }); + continue; + } + PrettyTree::MaybeInline { block, inline } => { + cmd.node = match mode { + Mode::Break => block.clone(), + Mode::Flat => inline.clone(), + }; + continue; + } + PrettyTree::Group { items } => { + match mode { + Mode::Break if self.fitting(items.clone(), self.pos, ind) => { + cmd.mode = Mode::Flat; + } + _ => {} + } + cmd.node = items.clone(); + continue; + } + PrettyTree::Nest { space, doc } => { + // Once https://doc.rust-lang.org/std/primitive.usize.html#method.saturating_add_signed is stable + // this can be replaced + let new_ind = if *space >= 0 { + ind.saturating_add(*space as usize) + } else { + ind.saturating_sub(space.unsigned_abs()) + }; + cmd = RenderCommand { indent: new_ind, mode, node: doc.clone() }; + continue; + } + PrettyTree::Hardline => { + // The next document may have different indentation so we should use it if we can + match self.back_cmds.pop() { + Some(next) => { + write_newline(next.indent, out)?; + self.pos = next.indent; + cmd = next; + continue; + } + None => { + write_newline(ind, out)?; + self.pos = ind; + } + } + } + PrettyTree::RenderLength { length: len, body: doc } => match doc.as_ref() { + PrettyTree::Text(s) => { + out.write_str_all(s)?; + self.pos += len; + fits &= self.pos <= self.width; + } + PrettyTree::StaticText(s) => { + out.write_str_all(s)?; + self.pos += len; + fits &= self.pos <= self.width; + } + _ => unreachable!(), + }, + PrettyTree::Text(ref s) => { + out.write_str_all(s)?; + self.pos += s.len(); + fits &= self.pos <= self.width; + } + PrettyTree::StaticText(s) => { + out.write_str_all(s)?; + self.pos += s.len(); + fits &= self.pos <= self.width; + } + PrettyTree::Annotated { style: color, body: doc } => { + out.push_annotation(color.clone())?; + self.annotation_levels.push(self.back_cmds.len()); + cmd.node = doc.clone(); + continue; + } + PrettyTree::Union { lhs: left, rhs: right } => { + let pos = self.pos; + let annotation_levels = self.annotation_levels.len(); + let bcmds = self.back_cmds.len(); + + self.back_cmds.push(RenderCommand { indent: ind, mode, node: left.clone() }); + + let mut buffer = BufferWrite::new(0); + + match self.best(bcmds, &mut buffer) { + Ok(true) => buffer.render(out)?, + Ok(false) | Err(_) => { + self.pos = pos; + self.back_cmds.truncate(bcmds); + self.annotation_levels.truncate(annotation_levels); + cmd.node = right.clone(); + continue; + } + } + } + PrettyTree::Column { invoke: column } => { + cmd.node = Rc::new(column(self.pos)); + continue; + } + PrettyTree::Nesting { invoke: nesting } => { + cmd.node = Rc::new(nesting(self.pos)); + continue; + } + PrettyTree::Fail => return Err(out.fail_doc()), + } + + break; + } + while self.annotation_levels.last() == Some(&self.back_cmds.len()) { + self.annotation_levels.pop(); + out.pop_annotation()?; + } + } + Ok(fits) + } +} diff --git a/pretty-print/projects/pretty-print/src/render/write_fmt.rs b/pretty-print/projects/pretty-print/src/render/write_fmt.rs new file mode 100644 index 0000000..89868a7 --- /dev/null +++ b/pretty-print/projects/pretty-print/src/render/write_fmt.rs @@ -0,0 +1,121 @@ +use crate::{render::Annotation, Render, RenderAnnotated}; +use alloc::rc::Rc; +use color_ansi::AnsiStyle; +use core::fmt::{Debug, Formatter}; + +/// Writes to something implementing `std::fmt::Write` +pub struct FmtWrite { + upstream: W, +} +/// Represents a terminal writer. +#[derive(Debug)] +pub struct BufferWrite { + buffer: String, + annotations: Vec<(usize, Annotation)>, +} + +impl BufferWrite { + /// Creates a new terminal writer. + pub fn new(capacity: usize) -> Self { + BufferWrite { buffer: String::with_capacity(capacity), annotations: Vec::new() } + } + /// Creates a new terminal writer. + pub fn render(&mut self, render: &mut W) -> Result<(), W::Error> + where + W: RenderAnnotated, + W: ?Sized, + { + let mut start = 0; + for (end, annotation) in &self.annotations { + let s = &self.buffer[start..*end]; + if !s.is_empty() { + render.write_str_all(s)?; + } + start = *end; + match annotation { + Annotation::Push(a) => render.push_annotation(a.clone())?, + Annotation::Pop => render.pop_annotation()?, + } + } + let s = &self.buffer[start..]; + if !s.is_empty() { + render.write_str_all(s)?; + } + Ok(()) + } +} + +impl Render for BufferWrite { + type Error = core::fmt::Error; + + fn write_str(&mut self, s: &str) -> Result { + self.buffer.push_str(s); + Ok(s.len()) + } + + fn write_str_all(&mut self, s: &str) -> Result<(), Self::Error> { + self.buffer.push_str(s); + Ok(()) + } + + fn fail_doc(&self) -> Self::Error { + core::fmt::Error::default() + } +} + +impl RenderAnnotated for FmtWrite +where + W: core::fmt::Write, +{ + fn push_annotation(&mut self, _: Rc) -> Result<(), Self::Error> { + Ok(()) + } + + fn pop_annotation(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl RenderAnnotated for BufferWrite { + fn push_annotation(&mut self, annotation: Rc) -> Result<(), Self::Error> { + self.annotations.push((self.buffer.len(), Annotation::Push(annotation))); + Ok(()) + } + + fn pop_annotation(&mut self) -> Result<(), Self::Error> { + self.annotations.push((self.buffer.len(), Annotation::Pop)); + Ok(()) + } +} + +impl Debug for FmtWrite { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("IoWrite").finish() + } +} + +impl FmtWrite { + /// Create a new `FmtWrite` from something implementing `std::fmt::Write` + pub fn new(upstream: W) -> FmtWrite { + FmtWrite { upstream } + } +} + +impl Render for FmtWrite +where + W: core::fmt::Write, +{ + type Error = core::fmt::Error; + + fn write_str(&mut self, s: &str) -> Result { + self.write_str_all(s).map(|_| s.len()) + } + + fn write_str_all(&mut self, s: &str) -> core::fmt::Result { + self.upstream.write_str(s) + } + + fn fail_doc(&self) -> Self::Error { + core::fmt::Error + } +} diff --git a/pretty-print/projects/pretty-print/src/render/write_io.rs b/pretty-print/projects/pretty-print/src/render/write_io.rs new file mode 100644 index 0000000..e9c25a4 --- /dev/null +++ b/pretty-print/projects/pretty-print/src/render/write_io.rs @@ -0,0 +1,114 @@ +use crate::{Render, RenderAnnotated}; +use alloc::rc::Rc; +use color_ansi::{AnsiAbility, AnsiStyle, AnsiWriter}; +use core::fmt::{Debug, Formatter}; +use std::io::{Error, ErrorKind, Write}; +/// Represents a terminal writer. +pub struct TerminalWriter { + color_stack: Vec>, + upstream: AnsiWriter, +} + +/// Writes to something implementing `std::io::Write` +pub struct IoWrite { + upstream: W, +} + +impl Debug for IoWrite { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("IoWrite").finish() + } +} + +impl IoWrite { + /// Creates a new terminal writer. + pub fn new(upstream: W) -> IoWrite { + IoWrite { upstream } + } +} + +#[cfg(feature = "std")] +impl Render for IoWrite + where + W: std::io::Write, +{ + type Error = std::io::Error; + + fn write_str(&mut self, s: &str) -> std::io::Result { + self.upstream.write(s.as_bytes()) + } + + fn write_str_all(&mut self, s: &str) -> std::io::Result<()> { + self.upstream.write_all(s.as_bytes()) + } + + fn fail_doc(&self) -> Self::Error { + std::io::Error::new(std::io::ErrorKind::Other, "Document failed to render") + } +} + +#[cfg(feature = "std")] +impl RenderAnnotated for IoWrite + where + W: std::io::Write, +{ + fn push_annotation(&mut self, _: Rc) -> Result<(), Self::Error> { + Ok(()) + } + + fn pop_annotation(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl Debug for TerminalWriter { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("TerminalWriter").finish() + } +} + +impl TerminalWriter { + /// Creates a new terminal writer. + pub fn new(upstream: W) -> Self { + TerminalWriter { color_stack: Vec::new(), upstream: AnsiWriter::new(upstream) } + } + /// Creates a new terminal writer with a specific color. + pub fn with_color(mut self, color: AnsiAbility) -> Self { + self.upstream.set_ability(color); + self + } +} + +impl Render for TerminalWriter +where + W: Write, +{ + type Error = Error; + + fn write_str(&mut self, s: &str) -> std::io::Result { + self.upstream.write(s.as_bytes()) + } + + fn write_str_all(&mut self, s: &str) -> std::io::Result<()> { + self.upstream.write_all(s.as_bytes()) + } + + fn fail_doc(&self) -> Self::Error { + Error::new(ErrorKind::Other, "Document failed to render") + } +} + +impl RenderAnnotated for TerminalWriter { + fn push_annotation(&mut self, color: Rc) -> Result<(), Self::Error> { + self.color_stack.push(color.clone()); + self.upstream.set_style(&color) + } + + fn pop_annotation(&mut self) -> Result<(), Self::Error> { + self.color_stack.pop(); + match self.color_stack.last() { + Some(previous) => self.upstream.set_style(previous), + None => self.upstream.reset_style(), + } + } +} diff --git a/pretty-print/projects/pretty-print/src/traits/mod.rs b/pretty-print/projects/pretty-print/src/traits/mod.rs new file mode 100644 index 0000000..2f458b1 --- /dev/null +++ b/pretty-print/projects/pretty-print/src/traits/mod.rs @@ -0,0 +1,34 @@ +use crate::{providers::PrettyProvider, PrettyTree}; +use alloc::string::String; + +pub mod printer; + +/// The `PrettyPrint` trait is implemented by types that can be pretty-printed. +pub trait PrettyBuilder { + /// Acts as `self` when laid out on multiple lines and acts as `that` when laid out on a single line. + /// + /// ``` + /// use pretty::{Arena, DocAllocator}; + /// + /// let arena = Arena::<()>::new(); + /// let body = arena.line().append("x"); + /// let doc = arena + /// .text("let") + /// .append(arena.line()) + /// .append("x") + /// .group() + /// .append(body.clone().flat_alt(arena.line().append("in").append(body))) + /// .group(); + /// + /// assert_eq!(doc.1.pretty(100).to_string(), "let x in x"); + /// assert_eq!(doc.1.pretty(8).to_string(), "let x\nx"); + /// ``` + fn flat_alt(self, inline: E) -> PrettyTree + where + E: Into; + /// Acts as `self` when laid out on a single line and acts as `that` when laid out on multiple lines. + fn indent(self, indent: usize) -> PrettyTree; + + /// Increase the indentation level of this document. + fn nest(self, offset: isize) -> PrettyTree; +} diff --git a/pretty-print/projects/pretty-print/src/traits/printer.rs b/pretty-print/projects/pretty-print/src/traits/printer.rs new file mode 100644 index 0000000..ce40a89 --- /dev/null +++ b/pretty-print/projects/pretty-print/src/traits/printer.rs @@ -0,0 +1,37 @@ +use super::*; + +/// Marker trait for types that can be pretty printed. +pub trait PrettyPrint { + /// Build a pretty tree for this type. + fn pretty(&self, theme: &PrettyProvider) -> PrettyTree; + /// Get a pretty string for this type. + fn pretty_string(&self, theme: &PrettyProvider) -> String { + let mut buffer = String::new(); + if let Err(e) = self.pretty(&theme).render_fmt(theme.get_width(), &mut buffer) { + panic!("Error: {}", e); + } + buffer + } + /// Print a pretty string for this type. + fn pretty_colorful(&self, theme: &PrettyProvider) -> String { + let mut buffer = vec![]; + if let Err(e) = self.pretty(&theme).render_colored(theme.get_width(), &mut buffer) { + panic!("Error: {}", e); + } + match String::from_utf8(buffer) { + Ok(s) => s, + Err(e) => panic!("Error: {}", e), + } + } +} + +impl PrettyPrint for PrettyTree { + fn pretty(&self, _: &PrettyProvider) -> PrettyTree { + self.clone() + } +} +impl PrettyPrint for &'static str { + fn pretty(&self, _: &PrettyProvider) -> PrettyTree { + PrettyTree::StaticText(*self) + } +} diff --git a/pretty-print/projects/pretty-print/src/tree/display.rs b/pretty-print/projects/pretty-print/src/tree/display.rs new file mode 100644 index 0000000..44b2745 --- /dev/null +++ b/pretty-print/projects/pretty-print/src/tree/display.rs @@ -0,0 +1,89 @@ +use super::*; + +impl Default for PrettyTree { + fn default() -> Self { + Self::Nil + } +} + +impl Clone for PrettyTree { + fn clone(&self) -> Self { + match self { + Self::Nil => Self::Nil, + Self::Hardline => Self::Hardline, + Self::Text(s) => Self::Text(s.clone()), + Self::StaticText(s) => Self::StaticText(*s), + Self::Annotated { style: color, body: doc } => Self::Annotated { style: color.clone(), body: doc.clone() }, + Self::Append { lhs, rhs } => Self::Append { lhs: lhs.clone(), rhs: rhs.clone() }, + Self::Group { items } => Self::Group { items: items.clone() }, + Self::MaybeInline { block, inline } => Self::MaybeInline { block: block.clone(), inline: inline.clone() }, + Self::Nest { space, doc } => Self::Nest { space: *space, doc: doc.clone() }, + Self::RenderLength { length: len, body: doc } => Self::RenderLength { length: *len, body: doc.clone() }, + Self::Union { lhs: left, rhs: right } => Self::Union { lhs: left.clone(), rhs: right.clone() }, + Self::Column { invoke: column } => Self::Column { invoke: column.clone() }, + Self::Nesting { invoke: nesting } => Self::Nesting { invoke: nesting.clone() }, + Self::Fail => Self::Fail, + } + } +} + +impl Debug for PrettyTree { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + let is_line = |doc: &PrettyTree| match doc { + PrettyTree::MaybeInline { block: flat, inline: alt } => { + matches!((&**flat, &**alt), (PrettyTree::Hardline, PrettyTree::StaticText(" "))) + } + _ => false, + }; + let is_line_ = |doc: &PrettyTree| match doc { + PrettyTree::MaybeInline { block: flat, inline: alt } => { + matches!((&**flat, &**alt), (PrettyTree::Hardline, PrettyTree::Nil)) + } + _ => false, + }; + match self { + PrettyTree::Nil => f.debug_tuple("Nil").finish(), + PrettyTree::Append { lhs: _, rhs: _ } => { + let mut f = f.debug_list(); + append_docs(self, &mut |doc| { + f.entry(doc); + }); + f.finish() + } + _ if is_line(self) => f.debug_tuple("Line").finish(), + _ if is_line_(self) => f.debug_tuple("Line?").finish(), + PrettyTree::MaybeInline { block, inline } => f.debug_tuple("FlatAlt").field(block).field(inline).finish(), + PrettyTree::Group { items } => { + if is_line(self) { + return f.debug_tuple("SoftLine").finish(); + } + if is_line_(self) { + return f.debug_tuple("SoftLine?").finish(); + } + f.debug_tuple("Group").field(items).finish() + } + PrettyTree::Nest { space, doc } => f.debug_tuple("Nest").field(&space).field(doc).finish(), + PrettyTree::Hardline => f.debug_tuple("Hardline").finish(), + PrettyTree::RenderLength { body: doc, .. } => doc.fmt(f), + PrettyTree::Text(s) => Debug::fmt(s, f), + PrettyTree::StaticText(s) => Debug::fmt(s, f), + PrettyTree::Annotated { style: color, body: doc } => f.debug_tuple("Annotated").field(color).field(doc).finish(), + PrettyTree::Union { lhs: left, rhs: right } => f.debug_tuple("Union").field(left).field(right).finish(), + PrettyTree::Column { .. } => f.debug_tuple("Column(..)").finish(), + PrettyTree::Nesting { .. } => f.debug_tuple("Nesting(..)").finish(), + PrettyTree::Fail => f.debug_tuple("Fail").finish(), + } + } +} + +fn append_docs(mut doc: &PrettyTree, consumer: &mut impl FnMut(&PrettyTree)) { + loop { + match doc { + PrettyTree::Append { lhs, rhs } => { + append_docs(lhs, consumer); + doc = rhs; + } + _ => break consumer(doc), + } + } +} diff --git a/pretty-print/projects/pretty-print/src/tree/into.rs b/pretty-print/projects/pretty-print/src/tree/into.rs new file mode 100644 index 0000000..8afff13 --- /dev/null +++ b/pretty-print/projects/pretty-print/src/tree/into.rs @@ -0,0 +1,50 @@ +use super::*; + +impl Add for PrettyTree +where + T: Into, +{ + type Output = Self; + fn add(self, rhs: T) -> Self::Output { + self.append(rhs.into()) + } +} + +impl AddAssign for PrettyTree +where + T: Into, +{ + fn add_assign(&mut self, rhs: T) { + *self = self.clone().append(rhs.into()); + } +} + +impl From> for PrettyTree +where + Self: From, +{ + fn from(x: Option) -> Self { + match x { + Some(x) => x.into(), + None => Self::Nil, + } + } +} + +impl From<()> for PrettyTree { + fn from(_: ()) -> Self { + Self::Nil + } +} + +impl From<&'static str> for PrettyTree { + fn from(s: &'static str) -> Self { + Self::StaticText(s) + } +} + +impl From for PrettyTree { + fn from(s: String) -> Self { + Self::Text(Rc::from(s)) + } +} diff --git a/pretty-print/projects/pretty-print/src/tree/mod.rs b/pretty-print/projects/pretty-print/src/tree/mod.rs new file mode 100644 index 0000000..77cfbce --- /dev/null +++ b/pretty-print/projects/pretty-print/src/tree/mod.rs @@ -0,0 +1,449 @@ +use crate::{helpers::PrettySequence, render, FmtWrite, PrettyBuilder, RenderAnnotated}; +use alloc::{borrow::Cow, rc::Rc, string::String}; +use color_ansi::AnsiStyle; +use core::{ + fmt::{Debug, Formatter}, + ops::{Add, AddAssign}, +}; +use std::io::Write; +use unicode_segmentation::UnicodeSegmentation; + +mod display; +mod into; + +/// The concrete document type. This type is not meant to be used directly. Instead use the static +/// functions on `Doc` or the methods on an `DocAllocator`. +/// +/// The `T` parameter is used to abstract over pointers to `Doc`. See `RefDoc` and `BoxDoc` for how +/// it is used +pub enum PrettyTree { + /// Nothing to show + Nil, + /// A hard line break + Hardline, + /// A dynamic text document, all newlines are hard line breaks + Text(Rc), + /// A static text document, all newlines are hard line breaks + StaticText(&'static str), + /// A document with ansi styles + Annotated { + /// The style to use for the text + style: Rc, + /// The text to display + body: Rc, + }, + /// Concatenates two documents + Append { + /// The first document + lhs: Rc, + /// The second document + rhs: Rc, + }, + /// Concatenates two documents with a space in between + Group { + /// The first document + items: Rc, + }, + /// Concatenates two documents with a line in between + MaybeInline { + /// The first document + block: Rc, + /// The second document + inline: Rc, + }, + /// Concatenates two documents with a line in between + Nest { + /// The first document + space: isize, + /// The second document + doc: Rc, + }, + /// Stores the length of a string document that is not just ascii + RenderLength { + /// The length of the string + length: usize, + /// The document + body: Rc, + }, + /// Concatenates two documents with a line in between + Union { + /// The first document + lhs: Rc, + /// The second document + rhs: Rc, + }, + /// Concatenates two documents with a line in between + Column { + /// The first document + invoke: Rc Self>, + }, + /// Concatenates two documents with a line in between + Nesting { + /// The first document + invoke: Rc Self>, + }, + /// Concatenates two documents with a line in between + Fail, +} + +#[allow(non_upper_case_globals)] +impl PrettyTree { + /// A hard line break + pub const Space: Self = PrettyTree::StaticText(" "); + /// A line acts like a `\n` but behaves like `space` if it is grouped on a single line. + #[inline] + pub fn line_or_space() -> Self { + Self::Hardline.flat_alt(Self::Space).into() + } + /// A line acts like `\n` but behaves like `nil` if it is grouped on a single line. + #[inline] + pub fn line_or_comma() -> Self { + Self::Hardline.flat_alt(PrettyTree::StaticText(", ")).into() + } + /// Acts like `line` but behaves like `nil` if grouped on a single line + #[inline] + pub fn line_or_nil() -> Self { + Self::Hardline.flat_alt(Self::Nil).into() + } +} + +impl PrettyTree { + /// The given text, which must not contain line breaks. + #[inline] + pub fn text>>(data: U) -> Self { + match data.into() { + Cow::Borrowed(s) => PrettyTree::StaticText(s), + Cow::Owned(s) => PrettyTree::Text(Rc::from(s)), + } + .with_utf8_len() + } +} + +impl PrettyTree { + /// Writes a rendered document to a `std::io::Write` object. + #[inline] + #[cfg(feature = "std")] + pub fn render(&self, width: usize, out: &mut W) -> std::io::Result<()> + where + W: ?Sized + std::io::Write, + { + self.render_raw(width, &mut crate::IoWrite::new(out)) + } + + /// Writes a rendered document to a `std::fmt::Write` object. + #[inline] + pub fn render_fmt(&self, width: usize, out: &mut W) -> core::fmt::Result + where + W: ?Sized + core::fmt::Write, + { + self.render_raw(width, &mut FmtWrite::new(out)) + } + + /// Writes a rendered document to a `RenderAnnotated` object. + #[inline] + pub fn render_raw(&self, width: usize, out: &mut W) -> Result<(), W::Error> + where + W: RenderAnnotated, + W: ?Sized, + { + render::best(Rc::new(self.clone()), width, out) + } +} + +impl PrettyTree { + /// The given text, which must not contain line breaks. + #[inline] + #[cfg(feature = "std")] + pub fn render_colored(&self, width: usize, out: W) -> std::io::Result<()> { + render::best(Rc::new(self.clone()), width, &mut crate::TerminalWriter::new(out)) + } +} + +impl PrettyBuilder for PrettyTree { + /// Acts as `self` when laid out on multiple lines and acts as `that` when laid out on a single line. + /// + /// ``` + /// use pretty::{Arena, DocAllocator}; + /// + /// let arena = Arena::<()>::new(); + /// let body = arena.line().append("x"); + /// let doc = arena + /// .text("let") + /// .append(arena.line()) + /// .append("x") + /// .group() + /// .append(body.clone().flat_alt(arena.line().append("in").append(body))) + /// .group(); + /// + /// assert_eq!(doc.1.pretty(100).to_string(), "let x in x"); + /// assert_eq!(doc.1.pretty(8).to_string(), "let x\nx"); + /// ``` + #[inline] + fn flat_alt(self, flat: E) -> Self + where + E: Into, + { + Self::MaybeInline { block: Rc::new(self), inline: Rc::new(flat.into()) } + } + /// Indents `self` by `adjust` spaces from the current cursor position + /// + /// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr + /// like `RefDoc` or `RcDoc` + /// + /// ```rust + /// use pretty::DocAllocator; + /// + /// let arena = pretty::Arena::<()>::new(); + /// let doc = arena + /// .text("prefix") + /// .append(arena.text(" ")) + /// .append(arena.reflow("The indent function indents these words!").indent(4)); + /// assert_eq!( + /// doc.1.pretty(24).to_string(), + /// " + /// prefix The indent + /// function + /// indents these + /// words!" + /// .trim_start(), + /// ); + /// ``` + #[inline] + fn indent(self, adjust: usize) -> Self { + let spaces = { + use crate::render::SPACES; + let mut doc = PrettyTree::Nil; + let mut remaining = adjust; + while remaining != 0 { + let i = SPACES.len().min(remaining); + remaining -= i; + doc = doc.append(PrettyTree::text(&SPACES[..i])) + } + doc + }; + spaces.append(self).hang(adjust.try_into().unwrap()) + } + + /// Increase the indentation level of this document. + #[inline] + fn nest(self, offset: isize) -> Self { + if let Self::Nil = self { + return self; + } + if offset == 0 { + return self; + } + Self::Nest { space: offset, doc: Rc::new(self) } + } +} + +impl PrettyTree { + fn with_utf8_len(self) -> Self { + let s = match &self { + Self::Text(s) => s.as_ref(), + Self::StaticText(s) => s, + // Doc::SmallText(s) => s, + _ => return self, + }; + if s.is_ascii() { + self + } + else { + let grapheme_len = s.graphemes(true).count(); + Self::RenderLength { length: grapheme_len, body: Rc::new(self) } + } + } + + /// Append the given document after this document. + #[inline] + pub fn append(self, follow: E) -> Self + where + E: Into, + { + let rhs = follow.into(); + match (&self, &rhs) { + (Self::Nil, _) => rhs, + (_, Self::Nil) => self, + _ => Self::Append { lhs: Rc::new(self), rhs: Rc::new(rhs) }, + } + } + /// Allocate a document that intersperses the given separator `S` between the given documents + /// `[A, B, C, ..., Z]`, yielding `[A, S, B, S, C, S, ..., S, Z]`. + /// + /// Compare [the `intersperse` method from the `itertools` crate](https://docs.rs/itertools/0.5.9/itertools/trait.Itertools.html#method.intersperse). + /// + /// NOTE: The separator type, `S` may need to be cloned. Consider using cheaply cloneable ptr + /// like `RefDoc` or `RcDoc` + #[inline] + pub fn join(terms: I, joint: T2) -> PrettyTree + where + I: IntoIterator, + T1: Into, + T2: Into, + { + let joint = joint.into(); + let mut iter = terms.into_iter().map(|s| s.into()); + let mut terms = PrettySequence::new(0); + terms += iter.next().unwrap_or(PrettyTree::Nil); + for term in iter { + terms += joint.clone(); + terms += term; + } + terms.into() + } + /// Allocate a document that intersperses the given separator `S` between the given documents + pub fn concat(docs: I) -> Self + where + I: IntoIterator, + I::Item: Into, + { + let mut head = Self::Nil; + for item in docs.into_iter() { + head += item.into(); + } + head + } + + /// Mark this document as a group. + /// + /// Groups are layed out on a single line if possible. Within a group, all basic documents with + /// several possible layouts are assigned the same layout, that is, they are all layed out + /// horizontally and combined into a one single line, or they are each layed out on their own + /// line. + #[inline] + pub fn group(self) -> Self { + match self { + Self::Group { .. } | Self::Text(_) | Self::StaticText(_) | Self::Nil => self, + _ => Self::Group { items: Rc::new(self) }, + } + } + + /// Mark this document as a comment. + #[inline] + pub fn annotate(self, style: Rc) -> Self { + Self::Annotated { style: style, body: Rc::new(self) } + } + /// Mark this document as a hard line break. + #[inline] + pub fn union(self, other: E) -> Self + where + E: Into, + { + Self::Union { lhs: Rc::new(self), rhs: Rc::new(other.into()) } + } + + /// Lays out `self` so with the nesting level set to the current column + /// + /// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr + /// like `RefDoc` or `RcDoc` + /// + /// ```rust + /// use pretty::{docs, DocAllocator}; + /// + /// let arena = &pretty::Arena::<()>::new(); + /// let doc = docs![ + /// arena, + /// "lorem", + /// " ", + /// arena.intersperse(["ipsum", "dolor"].iter().cloned(), arena.line_()).align(), + /// arena.hardline(), + /// "next", + /// ]; + /// assert_eq!(doc.1.pretty(80).to_string(), "lorem ipsum\n dolor\nnext"); + /// ``` + #[inline] + pub fn align(self) -> Self { + Self::Column { + invoke: Rc::new(move |col| { + let self_ = self.clone(); + Self::Nesting { invoke: Rc::new(move |nest| self_.clone().nest(col as isize - nest as isize)) } + }), + } + } + + /// Lays out `self` with a nesting level set to the current level plus `adjust`. + /// + /// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr + /// like `RefDoc` or `RcDoc` + /// + /// ```rust + /// use pretty::DocAllocator; + /// + /// let arena = pretty::Arena::<()>::new(); + /// let doc = arena + /// .text("prefix") + /// .append(arena.text(" ")) + /// .append(arena.reflow("Indenting these words with nest").hang(4)); + /// assert_eq!( + /// doc.1.pretty(24).to_string(), + /// "prefix Indenting these\n words with\n nest", + /// ); + /// ``` + #[inline] + pub fn hang(self, adjust: isize) -> Self { + self.nest(adjust).align() + } + + /// Lays out `self` and provides the column width of it available to `f` + /// + /// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr + /// like `RefDoc` or `RcDoc` + /// + /// ```rust + /// use pretty::DocAllocator; + /// + /// let arena = pretty::Arena::<()>::new(); + /// let doc = arena + /// .text("prefix ") + /// .append(arena.column(|l| arena.text("| <- column ").append(arena.as_string(l)).into_doc())); + /// assert_eq!(doc.1.pretty(80).to_string(), "prefix | <- column 7"); + /// ``` + #[inline] + pub fn width(self, f: F) -> Self + where + F: Fn(isize) -> Self + Clone + 'static, + { + Self::Column { + invoke: Rc::new(move |start| { + let f = f.clone(); + self.clone() + Self::Column { invoke: Rc::new(move |end| f(end as isize - start as isize)) } + }), + } + } + + /// Puts `self` between `before` and `after` + #[inline] + pub fn enclose(self, before: E, after: F) -> Self + where + E: Into, + F: Into, + { + before.into().append(self).append(after.into()) + } + + /// Puts `self` between `before` and `after` if `cond` is true + pub fn single_quotes(self) -> Self { + self.enclose("'", "'") + } + + /// Puts `self` between `before` and `after` if `cond` is true + pub fn double_quotes(self) -> Self { + self.enclose("\"", "\"") + } + /// Puts `self` between `before` and `after` if `cond` is true + pub fn parens(self) -> Self { + self.enclose("(", ")") + } + /// Puts `self` between `before` and `after` if `cond` is true + pub fn angles(self) -> Self { + self.enclose("<", ">") + } + /// Puts `self` between `before` and `after` if `cond` is true + pub fn braces(self) -> Self { + self.enclose("{", "}") + } + /// Puts `self` between `before` and `after` if `cond` is true + pub fn brackets(self) -> Self { + self.enclose("[", "]") + } +} diff --git a/pretty-print/projects/pretty-print/tests/main.rs b/pretty-print/projects/pretty-print/tests/main.rs new file mode 100644 index 0000000..027c595 --- /dev/null +++ b/pretty-print/projects/pretty-print/tests/main.rs @@ -0,0 +1,6 @@ +#[test] +fn ready() { + println!("it works!") +} + +mod readme; diff --git a/pretty-print/projects/pretty-print/tests/readme.md b/pretty-print/projects/pretty-print/tests/readme.md new file mode 100644 index 0000000..649841b --- /dev/null +++ b/pretty-print/projects/pretty-print/tests/readme.md @@ -0,0 +1,5 @@ +## Tests + +```bash +wee test +``` diff --git a/pretty-print/projects/pretty-print/tests/readme.rs b/pretty-print/projects/pretty-print/tests/readme.rs new file mode 100644 index 0000000..978426e --- /dev/null +++ b/pretty-print/projects/pretty-print/tests/readme.rs @@ -0,0 +1,38 @@ +use pretty_print::*; +use SExp::*; +enum SExp { + Atom(u32), + List(Vec), +} + +impl SExp { + /// Return a pretty printed format of self. + pub fn to_doc(&self) -> PrettyTree { + match self { + Atom(x) => PrettyTree::text(x.to_string()), + List(xs) => PrettyTree::text("(") + .append(PrettyTree::join(xs.into_iter().map(|x| x.to_doc()), PrettyTree::line_or_space()).nest(1).group()) + .append(PrettyTree::text(")")), + } + } + /// Return a pretty printed format of self. + pub fn to_pretty(&self, width: usize) -> String { + let mut w = Vec::new(); + self.to_doc().render(width, &mut w).unwrap(); + String::from_utf8(w).unwrap() + } +} + +fn main() { + let atom = Atom(5); + assert_eq!("5", atom.to_pretty(10)); + let list = List(vec![Atom(1), Atom(2), Atom(3)]); + assert_eq!("(1 2 3)", list.to_pretty(10)); + assert_eq!( + "\ +(1 + 2 + 3)", + list.to_pretty(5) + ); +} diff --git a/pretty-print/projects/pretty-test/Cargo.toml b/pretty-print/projects/pretty-test/Cargo.toml new file mode 100644 index 0000000..e786146 --- /dev/null +++ b/pretty-print/projects/pretty-test/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "pretty-test" +publish = false +version = "0.0.0" +authors = ["Aster <192607617@qq.com>"] +description = "..." +repository = "https://github.com/oovm/sub_projects" +documentation = "https://docs.rs/sub_projects" +readme = "Readme.md" +license = "MPL-2.0" +edition = "2021" +exclude = ["package.json", "tests/**"] + +[dependencies] +pretty = "0.12.1" + +[dependencies.pretty-print] +version = "*" +default-features = false +features = ["std"] +path = "../pretty-print" + +[dev-dependencies] + +[features] +default = [] + +[package.metadata.docs.rs] +all-features = true diff --git a/pretty-print/projects/pretty-test/package.json b/pretty-print/projects/pretty-test/package.json new file mode 100644 index 0000000..381d4be --- /dev/null +++ b/pretty-print/projects/pretty-test/package.json @@ -0,0 +1,6 @@ +{ + "private": true, + "scripts": { + "p": "cargo publish --allow-dirty" + } +} diff --git a/pretty-print/projects/pretty-test/readme.md b/pretty-print/projects/pretty-test/readme.md new file mode 100644 index 0000000..db86903 --- /dev/null +++ b/pretty-print/projects/pretty-test/readme.md @@ -0,0 +1,2 @@ +Title +===== diff --git a/pretty-print/projects/pretty-test/src/errors.rs b/pretty-print/projects/pretty-test/src/errors.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/pretty-print/projects/pretty-test/src/errors.rs @@ -0,0 +1 @@ + diff --git a/pretty-print/projects/pretty-test/src/lib.rs b/pretty-print/projects/pretty-test/src/lib.rs new file mode 100644 index 0000000..5fdf33f --- /dev/null +++ b/pretty-print/projects/pretty-test/src/lib.rs @@ -0,0 +1,5 @@ +#![deny(missing_debug_implementations, missing_copy_implementations)] +#![warn(missing_docs, rustdoc::missing_crate_level_docs)] +#![doc = include_str!("../readme.md")] +#![doc(html_logo_url = "https://raw.githubusercontent.com/oovm/shape-rs/dev/projects/images/Trapezohedron.svg")] +#![doc(html_favicon_url = "https://raw.githubusercontent.com/oovm/shape-rs/dev/projects/images/Trapezohedron.svg")] diff --git a/pretty-print/projects/pretty-test/tests/main.rs b/pretty-print/projects/pretty-test/tests/main.rs new file mode 100644 index 0000000..cd09e47 --- /dev/null +++ b/pretty-print/projects/pretty-test/tests/main.rs @@ -0,0 +1,46 @@ +#![allow(dead_code, unused)] +use pretty_print::{AnsiColor, AnsiStyle, PrettyBuilder, PrettyTree}; +use std::{io::stdout, rc::Rc}; + +#[test] +fn ready() { + println!("it works!") +} + +#[test] +fn box1() { + let doc = PrettyTree::text("hello") + .annotate(Rc::new(AnsiStyle::new(AnsiColor::Red))) + .append(PrettyTree::Hardline) + .append("the") + .append("∷") + .append("world"); + let mut buffer = vec![]; + doc.render_colored(10, &mut buffer).unwrap(); + + println!("{}", String::from_utf8_lossy(&buffer)); +} + +#[test] +fn box2() { + let doc = PrettyTree::join(vec!["hello", " ", " ", "world"], "∷").group(); + let mut buffer = vec![]; + doc.render_colored(10, &mut buffer).unwrap(); + + println!("{}", String::from_utf8_lossy(&buffer)); +} + +#[test] +fn box_doc_inference() { + let doc = PrettyTree::text("test").append(PrettyTree::line_or_space()).append(PrettyTree::text("test")).group(); + + println!("{}", doc.pretty(10)); +} + +#[test] +fn newline_in_text() { + let doc = PrettyTree::text("test") + .append(PrettyTree::line_or_space().append(PrettyTree::text("\"test\n test\"")).nest(4)) + .group(); + println!("{}", doc.pretty(10)); +} diff --git a/pretty-print/projects/pretty-test/tests/readme.md b/pretty-print/projects/pretty-test/tests/readme.md new file mode 100644 index 0000000..649841b --- /dev/null +++ b/pretty-print/projects/pretty-test/tests/readme.md @@ -0,0 +1,5 @@ +## Tests + +```bash +wee test +``` diff --git a/pretty-print/rustfmt.toml b/pretty-print/rustfmt.toml new file mode 100644 index 0000000..0bf0048 --- /dev/null +++ b/pretty-print/rustfmt.toml @@ -0,0 +1,9 @@ +version = "Two" +edition = "2018" +max_width = 128 + +imports_granularity = "Crate" +use_small_heuristics = "Max" +control_brace_style = "ClosingNextLine" +normalize_comments = true +format_code_in_doc_comments = true