From c5279bae7d601cc24bf424c25cf9864451135d22 Mon Sep 17 00:00:00 2001 From: DQ Date: Tue, 18 Apr 2023 20:10:35 +0200 Subject: [PATCH] Add derive macro for `Syntax` (used to be `Language`) (#51) --- CHANGELOG.md | 7 +- Cargo.toml | 75 +------ README.md | 50 +++-- cstree-derive/Cargo.toml | 18 ++ cstree-derive/LICENSE-APACHE | 1 + cstree-derive/LICENSE-MIT | 1 + cstree-derive/README.md | 1 + cstree-derive/src/errors.rs | 56 +++++ cstree-derive/src/lib.rs | 74 +++++++ cstree-derive/src/parsing.rs | 131 ++++++++++++ cstree-derive/src/parsing/attributes.rs | 59 ++++++ cstree-derive/src/symbols.rs | 50 +++++ cstree/Cargo.toml | 79 +++++++ cstree/LICENSE-APACHE | 1 + cstree/LICENSE-MIT | 1 + cstree/README.md | 1 + {benches => cstree/benches}/main.rs | 64 ++---- {examples => cstree/examples}/math.rs | 42 +--- {examples => cstree/examples}/readme.rs | 24 +-- .../examples}/s_expressions.rs | 126 +++++------- {examples => cstree/examples}/salsa.rs | 4 +- {src => cstree/src}/green.rs | 0 {src => cstree/src}/green/builder.rs | 88 ++++---- {src => cstree/src}/green/element.rs | 0 {src => cstree/src}/green/iter.rs | 0 {src => cstree/src}/green/node.rs | 0 {src => cstree/src}/green/token.rs | 0 {src => cstree/src}/interning.rs | 75 +++---- .../src}/interning/default_interner.rs | 0 {src => cstree/src}/interning/lasso_compat.rs | 0 .../interning/lasso_compat/token_interner.rs | 0 .../src}/interning/lasso_compat/traits.rs | 0 {src => cstree/src}/interning/salsa_compat.rs | 4 +- {src => cstree/src}/interning/traits.rs | 0 {src => cstree/src}/lib.rs | 163 ++++++++------- {src => cstree/src}/serde_impls.rs | 90 ++++---- {src => cstree/src}/syntax/element.rs | 68 +++---- {src => cstree/src}/syntax/iter.rs | 36 ++-- {src => cstree/src}/syntax/mod.rs | 26 +-- {src => cstree/src}/syntax/node.rs | 140 ++++++------- {src => cstree/src}/syntax/resolved.rs | 192 +++++++++--------- {src => cstree/src}/syntax/text.rs | 94 +++++---- {src => cstree/src}/syntax/token.rs | 64 +++--- {src => cstree/src}/utility_types.rs | 0 {tests => cstree/tests}/it/basic.rs | 24 +-- cstree/tests/it/main.rs | 80 ++++++++ cstree/tests/it/regressions.rs | 21 ++ {tests => cstree/tests}/it/sendsync.rs | 6 +- {tests => cstree/tests}/it/serde.rs | 4 +- test_suite/Cargo.toml | 16 ++ test_suite/tests/derive.rs | 22 ++ test_suite/tests/ui.rs | 6 + test_suite/tests/ui/repr/missing_repr.rs | 10 + test_suite/tests/ui/repr/missing_repr.stderr | 9 + test_suite/tests/ui/repr/wrong_repr_c.rs | 11 + test_suite/tests/ui/repr/wrong_repr_c.stderr | 10 + test_suite/tests/ui/repr/wrong_repr_u16.rs | 11 + .../tests/ui/repr/wrong_repr_u16.stderr | 10 + test_suite/tests/ui/static_text/empty_expr.rs | 11 + .../tests/ui/static_text/empty_expr.stderr | 11 + .../tests/ui/static_text/missing_text.rs | 11 + .../tests/ui/static_text/missing_text.stderr | 5 + test_suite/tests/ui/static_text/non_expr.rs | 11 + .../tests/ui/static_text/non_expr.stderr | 11 + .../tests/ui/static_text/non_string_expr.rs | 11 + .../ui/static_text/non_string_expr.stderr | 11 + .../tests/ui/static_text/text_assigned.rs | 11 + .../tests/ui/static_text/text_assigned.stderr | 5 + tests/it/main.rs | 77 ------- tests/it/regressions.rs | 38 ---- 70 files changed, 1459 insertions(+), 899 deletions(-) create mode 100644 cstree-derive/Cargo.toml create mode 120000 cstree-derive/LICENSE-APACHE create mode 120000 cstree-derive/LICENSE-MIT create mode 120000 cstree-derive/README.md create mode 100644 cstree-derive/src/errors.rs create mode 100644 cstree-derive/src/lib.rs create mode 100644 cstree-derive/src/parsing.rs create mode 100644 cstree-derive/src/parsing/attributes.rs create mode 100644 cstree-derive/src/symbols.rs create mode 100644 cstree/Cargo.toml create mode 120000 cstree/LICENSE-APACHE create mode 120000 cstree/LICENSE-MIT create mode 120000 cstree/README.md rename {benches => cstree/benches}/main.rs (60%) rename {examples => cstree/examples}/math.rs (80%) rename {examples => cstree/examples}/readme.rs (93%) rename {examples => cstree/examples}/s_expressions.rs (77%) rename {examples => cstree/examples}/salsa.rs (91%) rename {src => cstree/src}/green.rs (100%) rename {src => cstree/src}/green/builder.rs (86%) rename {src => cstree/src}/green/element.rs (100%) rename {src => cstree/src}/green/iter.rs (100%) rename {src => cstree/src}/green/node.rs (100%) rename {src => cstree/src}/green/token.rs (100%) rename {src => cstree/src}/interning.rs (80%) rename {src => cstree/src}/interning/default_interner.rs (100%) rename {src => cstree/src}/interning/lasso_compat.rs (100%) rename {src => cstree/src}/interning/lasso_compat/token_interner.rs (100%) rename {src => cstree/src}/interning/lasso_compat/traits.rs (100%) rename {src => cstree/src}/interning/salsa_compat.rs (98%) rename {src => cstree/src}/interning/traits.rs (100%) rename {src => cstree/src}/lib.rs (85%) rename {src => cstree/src}/serde_impls.rs (73%) rename {src => cstree/src}/syntax/element.rs (86%) rename {src => cstree/src}/syntax/iter.rs (73%) rename {src => cstree/src}/syntax/mod.rs (67%) rename {src => cstree/src}/syntax/node.rs (91%) rename {src => cstree/src}/syntax/resolved.rs (82%) rename {src => cstree/src}/syntax/text.rs (81%) rename {src => cstree/src}/syntax/token.rs (87%) rename {src => cstree/src}/utility_types.rs (100%) rename {tests => cstree/tests}/it/basic.rs (90%) create mode 100644 cstree/tests/it/main.rs create mode 100644 cstree/tests/it/regressions.rs rename {tests => cstree/tests}/it/sendsync.rs (94%) rename {tests => cstree/tests}/it/serde.rs (98%) create mode 100644 test_suite/Cargo.toml create mode 100644 test_suite/tests/derive.rs create mode 100644 test_suite/tests/ui.rs create mode 100644 test_suite/tests/ui/repr/missing_repr.rs create mode 100644 test_suite/tests/ui/repr/missing_repr.stderr create mode 100644 test_suite/tests/ui/repr/wrong_repr_c.rs create mode 100644 test_suite/tests/ui/repr/wrong_repr_c.stderr create mode 100644 test_suite/tests/ui/repr/wrong_repr_u16.rs create mode 100644 test_suite/tests/ui/repr/wrong_repr_u16.stderr create mode 100644 test_suite/tests/ui/static_text/empty_expr.rs create mode 100644 test_suite/tests/ui/static_text/empty_expr.stderr create mode 100644 test_suite/tests/ui/static_text/missing_text.rs create mode 100644 test_suite/tests/ui/static_text/missing_text.stderr create mode 100644 test_suite/tests/ui/static_text/non_expr.rs create mode 100644 test_suite/tests/ui/static_text/non_expr.stderr create mode 100644 test_suite/tests/ui/static_text/non_string_expr.rs create mode 100644 test_suite/tests/ui/static_text/non_string_expr.stderr create mode 100644 test_suite/tests/ui/static_text/text_assigned.rs create mode 100644 test_suite/tests/ui/static_text/text_assigned.stderr delete mode 100644 tests/it/main.rs delete mode 100644 tests/it/regressions.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 29892dd..18832eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,13 @@ ## `v0.12.0` * Documentation has been improved in most areas, together with a switch to a more principled module structure that allows explicitly documenting submodules. + * The `Language` trait has been deprecated in favour of a new `Syntax` trait. `Syntax` provides the same methods that `Language` did before, but is implemented directly on the syntax kind enum instead of an additional type representing the language. + * The supertrait requirements on `PartialOrd`, `Ord`, and `Hash` have been dropped. + * TODO: this allows to optionally provide derive. To enable, add feature flag * The `interning` module has been rewritten. It now provides fuctions for obtaining a default interner (`new_interner` and `new_threaded_interner`) and provides a small, dependency-free interner implementation. * Compatibility with other interners can be enable via feature flags. * **Note** that compatibilty with `lasso` is not enabled by default. Use the `lasso_compat` feature to match the previous default. - * Introduced `Language::static_text` to optimize tokens that always appear with the same text (estimated 10-15% faster tree building when used, depending on the ratio of static to dynamic tokens). + * Introduced `Syntax::static_text` to optimize tokens that always appear with the same text (estimated 10-15% faster tree building when used, depending on the ratio of static to dynamic tokens). * Since `cstree`s are lossless, `GreenNodeBuilder::token` must still be passed the source text even for static tokens. * Internal performance improvements for up to 10% faster tree building by avoiding unnecessary duplication of elements. * Use `NonNull` for the internal representation of `SyntaxNode`, meaning it now benefits from niche optimizations (`Option` is now the same size as `SyntaxNode` itself: the size of a pointer). @@ -14,7 +17,7 @@ * `RawSyntaxKind` has been changed to use a 32-bit index internally, which means existing `Language` implementations and syntax kind `enum`s need to be adjusted to `#[repr(u32)]` and the corresponding conversions. * The crate's export module structure has been reorganized to give different groups of definitions their own submodules. A `cstree::prelude` module is available, containing the most commonly needed types that were previously accessible via `use cstree::*`. Otherwise, the module structure is now as follows: * `cstree` - * `Language` + * `Syntax` * `RawSyntaxKind` * `build` * `GreenNodeBuilder` diff --git a/Cargo.toml b/Cargo.toml index 10397b0..26198c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,12 @@ -[package] +[workspace] +members = [ + "cstree", + "cstree-derive", + "test_suite", +] + +[workspace.package] edition = "2021" -name = "cstree" version = "0.12.0-rc.0" # when updating, also update `#![doc(html_root_url)]` authors = [ "Domenic Quirl ", @@ -10,70 +16,7 @@ description = "Library for generic lossless syntax trees" license = "MIT OR Apache-2.0" repository = "https://github.com/domenicquirl/cstree" readme = "README.md" +rust-version = "1.68" [profile.release] debug = true - -[dependencies] -text-size = "1.1.0" -fxhash = "0.2.1" -parking_lot = "0.12.1" - -# Arc -triomphe = "0.1.7" -sptr = "0.3.2" - -# Default Interner -indexmap = "1.9" - -[dependencies.lasso] -version = "0.6" -features = ["inline-more"] -optional = true - -[dependencies.salsa] -git = "https://github.com/salsa-rs/salsa/" -version = "0.1" -optional = true -package = "salsa-2022" - -[dependencies.serde] -version = "1.0" -optional = true -default-features = false -features = ["derive", "std"] - -[dev-dependencies] -m_lexer = "0.0.4" -serde_json = "1.0" -serde_test = "1.0" -crossbeam-utils = "0.8" -criterion = "0.3" - -[[bench]] -name = "main" -harness = false - -[features] -default = [] -# Implementations of `serde::{De,}Serialize` for CSTrees. -serialize = ["serde", "lasso?/serialize"] -# Interoperability with the `lasso` interning crate. -# When enabled, `cstree`'s default interners will use `lasso` internally, too. -lasso_compat = ["lasso"] -# Additionally provide threadsafe interner types. -# Where applicable (and if the corresponding features are selected), provide compatibility -# implementations for multi-thread interners from other crates. -multi_threaded_interning = ["lasso_compat", "lasso/multi-threaded"] -# Interoperability with the `salsa` framework for incremental computation. -# Use this feature for "Salsa 2022". -# WARNING: This feature is considered unstable! -salsa_2022_compat = ["salsa"] - -[[example]] -name = "salsa" -required-features = ["salsa_2022_compat"] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "doc_cfg"] diff --git a/README.md b/README.md index 44416ea..beffafd 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ concrete syntax trees as its output. We'll talk more about parsing below -- firs to happen to go from input text to a `cstree` syntax tree: 1. Define an enumeration of the types of tokens (like keywords) and nodes (like "an expression") - that you want to have in your syntax and implement `Language` + that you want to have in your syntax and implement `Syntax` 2. Create a `GreenNodeBuilder` and call `start_node`, `token` and `finish_node` from your parser @@ -52,12 +52,12 @@ compound expression. They will, however, be allowed to write nested expressions ### Defining the language First, we need to list the different part of our language's grammar. We can do that using an `enum` with a unit variant for any terminal and non-terminal. -The `enum` needs to be convertible to a `u16`, so we use the `repr` attribute to ensure it uses the correct +The `enum` needs to be convertible to a `u32`, so we use the `repr` attribute to ensure it uses the correct representation. ```rust -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(u16)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] enum SyntaxKind { /* Tokens */ Int, // 42 @@ -71,6 +71,15 @@ enum SyntaxKind { } ``` +For convenience when we're working with generic `cstree` types like `SyntaxNode`, we'll also give a +name to our syntax as a whole and add a type alias for it. +That way, we can match against `SyntaxKind`s using the original name, but use the more informative +`Node` to instantiate `cstree`'s types. + +```rust +type Calculator = SyntaxKind; +``` + Most of these are tokens to lex the input string into, like numbers (`Int`) and operators (`Plus`, `Minus`). We only really need one type of node; expressions. Our syntax tree's root node will have the special kind `Root`, all other nodes will be @@ -78,22 +87,16 @@ expressions containing a sequence of arithmetic operations potentially involving expression nodes. To use our `SyntaxKind`s with `cstree`, we need to tell it how to convert it back to just a number (the -`#[repr(u16)]` that we added) by implementing the `Language` trait. We can also tell `cstree` about tokens that +`#[repr(u32)]` that we added) by implementing the `Syntax` trait. We can also tell `cstree` about tokens that always have the same text through the `static_text` method on the trait. This is useful for the operators and parentheses, but not possible for numbers, since an integer token may be produced from the input `3`, but also from -other numbers like `7` or `12`. We implement `Language` on an empty type, just so we can give it a name. +other numbers like `7` or `12`. We implement `Syntax` on an empty type, just so we can give it a name. ```rust -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Calculator; - -impl Language for Calculator { - // The tokens and nodes we just defined - type Kind = SyntaxKind; - - fn kind_from_raw(raw: RawSyntaxKind) -> Self::Kind { - // This just needs to be the inverse of `kind_to_raw`, but could also - // be an `impl TryFrom for SyntaxKind` or any other conversion. +impl Syntax for Calculator { + fn from_raw(raw: RawSyntaxKind) -> Self { + // This just needs to be the inverse of `into_raw`, but could also + // be an `impl TryFrom for SyntaxKind` or any other conversion. match raw.0 { 0 => SyntaxKind::Int, 1 => SyntaxKind::Plus, @@ -106,12 +109,12 @@ impl Language for Calculator { } } - fn kind_to_raw(kind: Self::Kind) -> RawSyntaxKind { - RawSyntaxKind(kind as u16) + fn ino_raw(self) -> RawSyntaxKind { + RawSyntaxKind(self as u32) } - fn static_text(kind: Self::Kind) -> Option<&'static str> { - match kind { + fn static_text(self) -> Option<&'static str> { + match self { SyntaxKind::Plus => Some("+"), SyntaxKind::Minus => Some("-"), SyntaxKind::LParen => Some("("), @@ -122,7 +125,14 @@ impl Language for Calculator { } ``` +#### Deriving `Syntax` +To save yourself the hassle of defining this conversion (and, perhaps more importantly, continually updating it +while your language's syntax is in flux), `cstree` includes a derive macro for `Syntax` when built with the `derive` +feature. With the macro, the `Syntax` trait implementation above can be replaced by simply adding +`#[derive(Syntax)]` to `SyntaxKind`. + ### Parsing into a green tree + With that out of the way, we can start writing the parser for our expressions. For the purposes of this introduction to `cstree`, I'll assume that there is a lexer that yields the following tokens: diff --git a/cstree-derive/Cargo.toml b/cstree-derive/Cargo.toml new file mode 100644 index 0000000..7f13bc4 --- /dev/null +++ b/cstree-derive/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "cstree_derive" +edition.workspace = true +version.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true +rust-version.workspace = true + +[lib] +name = "cstree_derive" +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.56" +quote = "1.0.26" +syn = { version = "2.0.14" } diff --git a/cstree-derive/LICENSE-APACHE b/cstree-derive/LICENSE-APACHE new file mode 120000 index 0000000..965b606 --- /dev/null +++ b/cstree-derive/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/cstree-derive/LICENSE-MIT b/cstree-derive/LICENSE-MIT new file mode 120000 index 0000000..76219eb --- /dev/null +++ b/cstree-derive/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/cstree-derive/README.md b/cstree-derive/README.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/cstree-derive/README.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/cstree-derive/src/errors.rs b/cstree-derive/src/errors.rs new file mode 100644 index 0000000..4f6f6e8 --- /dev/null +++ b/cstree-derive/src/errors.rs @@ -0,0 +1,56 @@ +use std::{cell::RefCell, fmt, thread}; + +use quote::ToTokens; + +/// Context to collect multiple errors and output them all after parsing in order to not abort +/// immediately on the first error. +/// +/// Ensures that the errors are handled using [`check`](ErrorContext::check) by otherwise panicking +/// on `Drop`. +#[derive(Debug, Default)] +pub(crate) struct ErrorContext { + errors: RefCell>>, +} + +impl ErrorContext { + /// Create a new context. + /// + /// This context contains no errors, but will still trigger a panic if it is not `check`ed. + pub fn new() -> Self { + ErrorContext { + errors: RefCell::new(Some(Vec::new())), + } + } + + /// Add an error to the context that points to `source`. + pub fn error_at(&self, source: S, msg: T) { + self.errors + .borrow_mut() + .as_mut() + .unwrap() + // Transform `ToTokens` here so we don't monomorphize `new_spanned` so much. + .push(syn::Error::new_spanned(source.into_token_stream(), msg)); + } + + /// Add a `syn` parse error directly. + pub fn syn_error(&self, err: syn::Error) { + self.errors.borrow_mut().as_mut().unwrap().push(err); + } + + /// Consume the context, producing a formatted error string if there are errors. + pub fn check(self) -> Result<(), Vec> { + let errors = self.errors.borrow_mut().take().unwrap(); + match errors.len() { + 0 => Ok(()), + _ => Err(errors), + } + } +} + +impl Drop for ErrorContext { + fn drop(&mut self) { + if !thread::panicking() && self.errors.borrow().is_some() { + panic!("forgot to check for errors"); + } + } +} diff --git a/cstree-derive/src/lib.rs b/cstree-derive/src/lib.rs new file mode 100644 index 0000000..6d9d4a5 --- /dev/null +++ b/cstree-derive/src/lib.rs @@ -0,0 +1,74 @@ +use errors::ErrorContext; +use parsing::SyntaxKindEnum; +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned}; +use syn::{parse_macro_input, spanned::Spanned, DeriveInput}; + +mod errors; +mod parsing; +mod symbols; + +use symbols::*; + +#[proc_macro_derive(Syntax, attributes(static_text))] +pub fn language(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + expand_syntax(ast).unwrap_or_else(to_compile_errors).into() +} + +fn expand_syntax(ast: DeriveInput) -> Result> { + let error_handler = ErrorContext::new(); + let Ok(syntax_kind_enum) = SyntaxKindEnum::parse_from_ast(&error_handler, &ast) else { + return Err(error_handler.check().unwrap_err()); + }; + + // Check that the `enum` is `#[repr(u32)]` + match &syntax_kind_enum.repr { + Some(repr) if repr == U32 => (), + Some(_) | None => error_handler.error_at( + syntax_kind_enum.source, + "syntax kind definitions must be `#[repr(u32)]` to derive `Syntax`", + ), + } + + error_handler.check()?; + + let name = &syntax_kind_enum.name; + let variant_count = syntax_kind_enum.variants.len() as u32; + let static_texts = syntax_kind_enum.variants.iter().map(|variant| { + let variant_name = &variant.name; + let static_text = match variant.static_text.as_deref() { + Some(text) => quote!(::core::option::Option::Some(#text)), + None => quote!(::core::option::Option::None), + }; + quote_spanned!(variant.source.span()=> + #name :: #variant_name => #static_text, + ) + }); + let trait_impl = quote_spanned! { syntax_kind_enum.source.span()=> + #[automatically_derived] + impl ::cstree::Syntax for #name { + fn from_raw(raw: ::cstree::RawSyntaxKind) -> Self { + assert!(raw.0 < #variant_count, "Invalid raw syntax kind: {}", raw.0); + // Safety: discriminant is valid by the assert above + unsafe { ::std::mem::transmute::(raw.0) } + } + + fn into_raw(self) -> ::cstree::RawSyntaxKind { + ::cstree::RawSyntaxKind(self as u32) + } + + fn static_text(self) -> ::core::option::Option<&'static str> { + match self { + #( #static_texts )* + } + } + } + }; + Ok(trait_impl) +} + +fn to_compile_errors(errors: Vec) -> proc_macro2::TokenStream { + let compile_errors = errors.iter().map(syn::Error::to_compile_error); + quote!(#(#compile_errors)*) +} diff --git a/cstree-derive/src/parsing.rs b/cstree-derive/src/parsing.rs new file mode 100644 index 0000000..cc53439 --- /dev/null +++ b/cstree-derive/src/parsing.rs @@ -0,0 +1,131 @@ +mod attributes; + +use syn::{punctuated::Punctuated, Token}; + +use crate::{errors::ErrorContext, symbols::*}; + +use self::attributes::Attr; + +/// Convenience for recording errors inside `ErrorContext` instead of the `Err` variant of the `Result`. +pub(crate) type Result = std::result::Result; + +pub(crate) struct SyntaxKindEnum<'i> { + pub(crate) name: syn::Ident, + pub(crate) repr: Option, + pub(crate) variants: Vec>, + pub(crate) source: &'i syn::DeriveInput, +} + +impl<'i> SyntaxKindEnum<'i> { + pub(crate) fn parse_from_ast(error_handler: &ErrorContext, item: &'i syn::DeriveInput) -> Result { + let syn::Data::Enum(data) = &item.data else { + error_handler.error_at(item, "`Syntax` can only be derived on enums"); + return Err(()); + }; + + let name = item.ident.clone(); + + let mut repr = Attr::none(error_handler, REPR); + for repr_attr in item.attrs.iter().filter(|&attr| attr.path().is_ident(&REPR)) { + if let syn::Meta::List(nested) = &repr_attr.meta { + if let Ok(nested) = nested.parse_args_with(Punctuated::::parse_terminated) { + for meta in nested { + if let syn::Meta::Path(path) = meta { + if let Some(ident) = path.get_ident() { + repr.set(repr_attr, ident.clone()); + } + } + } + } + } + } + + let variants = data + .variants + .iter() + .map(|variant| SyntaxKindVariant::parse_from_ast(error_handler, variant)) + .collect(); + + Ok(Self { + name, + repr: repr.get(), + variants, + source: item, + }) + } +} + +pub(crate) struct SyntaxKindVariant<'i> { + pub(crate) name: syn::Ident, + pub(crate) static_text: Option, + pub(crate) source: &'i syn::Variant, +} + +impl<'i> SyntaxKindVariant<'i> { + pub(crate) fn parse_from_ast(error_handler: &ErrorContext, variant: &'i syn::Variant) -> Self { + let name = variant.ident.clone(); + + // Check that `variant` is a unit variant + match &variant.fields { + syn::Fields::Unit => (), + syn::Fields::Named(_) | syn::Fields::Unnamed(_) => { + error_handler.error_at(variant, "syntax kinds with fields are not supported"); + } + } + + // Check that discriminants are unaltered + if variant.discriminant.is_some() { + error_handler.error_at( + variant, + "syntax kinds are not allowed to have custom discriminant values", + ); + } + + let mut static_text = Attr::none(error_handler, STATIC_TEXT); + for text in variant + .attrs + .iter() + .flat_map(|attr| get_static_text(error_handler, attr)) + { + static_text.set(&text, text.value()); + } + Self { + name, + static_text: static_text.get(), + source: variant, + } + } +} + +fn get_static_text(error_handler: &ErrorContext, attr: &syn::Attribute) -> Option { + use syn::Meta::*; + + if attr.path() != STATIC_TEXT { + return None; + } + + match &attr.meta { + List(list) => match list.parse_args() { + Ok(lit) => Some(lit), + Err(e) => { + error_handler.error_at( + list, + "argument to `static_text` must be a string literal: `#[static_text(\"...\")]`", + ); + error_handler.syn_error(e); + None + } + }, + Path(_) => { + error_handler.error_at(attr, "missing text for `static_text`: try `#[static_text(\"...\")]`"); + None + } + NameValue(_) => { + error_handler.error_at( + attr, + "`static_text` takes the text as a function argument: `#[static_text(\"...\")]`", + ); + None + } + } +} diff --git a/cstree-derive/src/parsing/attributes.rs b/cstree-derive/src/parsing/attributes.rs new file mode 100644 index 0000000..b24ef04 --- /dev/null +++ b/cstree-derive/src/parsing/attributes.rs @@ -0,0 +1,59 @@ +#![allow(unused)] + +use super::*; +use proc_macro2::TokenStream; +use quote::ToTokens; + +#[derive(Debug)] +pub(crate) struct Attr<'i, T> { + error_handler: &'i ErrorContext, + name: Symbol, + tokens: TokenStream, + value: Option, +} + +impl<'i, T> Attr<'i, T> { + pub(super) fn none(error_handler: &'i ErrorContext, name: Symbol) -> Self { + Attr { + error_handler, + name, + tokens: TokenStream::new(), + value: None, + } + } + + pub(super) fn set(&mut self, source: S, value: T) { + let tokens = source.into_token_stream(); + + if self.value.is_some() { + self.error_handler + .error_at(tokens, format!("duplicate attribute: `{}`", self.name)); + } else { + self.tokens = tokens; + self.value = Some(value); + } + } + + pub(super) fn set_opt(&mut self, source: S, value: Option) { + if let Some(value) = value { + self.set(source, value); + } + } + + pub(super) fn set_if_none(&mut self, value: T) { + if self.value.is_none() { + self.value = Some(value); + } + } + + pub(super) fn get(self) -> Option { + self.value + } + + pub(super) fn get_with_tokens(self) -> Option<(TokenStream, T)> { + match self.value { + Some(v) => Some((self.tokens, v)), + None => None, + } + } +} diff --git a/cstree-derive/src/symbols.rs b/cstree-derive/src/symbols.rs new file mode 100644 index 0000000..ac2abe0 --- /dev/null +++ b/cstree-derive/src/symbols.rs @@ -0,0 +1,50 @@ +use std::fmt::{self}; +use syn::{Ident, Path}; + +#[derive(Copy, Clone)] +pub struct Symbol(&'static str); + +pub const STATIC_TEXT: Symbol = Symbol("static_text"); +pub const REPR: Symbol = Symbol("repr"); +pub const U32: Symbol = Symbol("u32"); + +impl Symbol { + pub const fn new(text: &'static str) -> Self { + Self(text) + } +} + +impl PartialEq for Ident { + fn eq(&self, word: &Symbol) -> bool { + self == word.0 + } +} + +impl<'a> PartialEq for &'a Ident { + fn eq(&self, word: &Symbol) -> bool { + *self == word.0 + } +} + +impl PartialEq for Path { + fn eq(&self, word: &Symbol) -> bool { + self.is_ident(word.0) + } +} + +impl<'a> PartialEq for &'a Path { + fn eq(&self, word: &Symbol) -> bool { + self.is_ident(word.0) + } +} + +impl fmt::Display for Symbol { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(self.0) + } +} +impl fmt::Debug for Symbol { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.debug_tuple("Symbol").field(&self.0).finish() + } +} diff --git a/cstree/Cargo.toml b/cstree/Cargo.toml new file mode 100644 index 0000000..ff7e7fd --- /dev/null +++ b/cstree/Cargo.toml @@ -0,0 +1,79 @@ +[package] +name = "cstree" +edition.workspace = true +version.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true +rust-version.workspace = true + +[dependencies] +text-size = "1.1.0" +fxhash = "0.2.1" +parking_lot = "0.12.1" + +# Arc +triomphe = "0.1.7" +sptr = "0.3.2" + +# Default Interner +indexmap = "1.9" + +[dependencies.cstree_derive] +path = "../cstree-derive" +optional = true + +[dependencies.lasso] +version = "0.6" +features = ["inline-more"] +optional = true + +[dependencies.salsa] +git = "https://github.com/salsa-rs/salsa/" +version = "0.1" +optional = true +package = "salsa-2022" + +[dependencies.serde] +version = "1.0" +optional = true +default-features = false +features = ["derive", "std"] + +[dev-dependencies] +m_lexer = "0.0.4" +serde_json = "1.0" +serde_test = "1.0" +crossbeam-utils = "0.8" +criterion = "0.3" + +[[bench]] +name = "main" +harness = false + +[features] +default = [] +# Derive macro for `Syntax` +derive = ["dep:cstree_derive"] +# Implementations of `serde::{De,}Serialize` for CSTrees. +serialize = ["serde", "lasso?/serialize"] +# Interoperability with the `lasso` interning crate. +# When enabled, `cstree`'s default interners will use `lasso` internally, too. +lasso_compat = ["lasso"] +# Additionally provide threadsafe interner types. +# Where applicable (and if the corresponding features are selected), provide compatibility +# implementations for multi-thread interners from other crates. +multi_threaded_interning = ["lasso_compat", "lasso/multi-threaded"] +# Interoperability with the `salsa` framework for incremental computation. +# Use this feature for "Salsa 2022". +# WARNING: This feature is considered unstable! +salsa_2022_compat = ["salsa"] + +[[example]] +name = "salsa" +required-features = ["salsa_2022_compat"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "doc_cfg"] diff --git a/cstree/LICENSE-APACHE b/cstree/LICENSE-APACHE new file mode 120000 index 0000000..965b606 --- /dev/null +++ b/cstree/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/cstree/LICENSE-MIT b/cstree/LICENSE-MIT new file mode 120000 index 0000000..76219eb --- /dev/null +++ b/cstree/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/cstree/README.md b/cstree/README.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/cstree/README.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/benches/main.rs b/cstree/benches/main.rs similarity index 60% rename from benches/main.rs rename to cstree/benches/main.rs index 3f95608..924036f 100644 --- a/benches/main.rs +++ b/cstree/benches/main.rs @@ -3,9 +3,8 @@ use cstree::{ build::*, green::GreenNode, interning::{new_interner, Interner}, - Language, RawSyntaxKind, + RawSyntaxKind, Syntax, }; -use std::{fmt, hash::Hash}; #[derive(Debug)] pub enum Element<'s> { @@ -14,37 +13,14 @@ pub enum Element<'s> { Plus, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TestKind { Element { n: u32 }, Plus, } -pub trait Bool: Hash + Ord + fmt::Debug + Copy { - const VALUE: bool; -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct TestLang { - _marker: std::marker::PhantomData, -} - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct NoStaticText; -impl Bool for NoStaticText { - const VALUE: bool = false; -} - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct UseStaticText; -impl Bool for UseStaticText { - const VALUE: bool = true; -} - -impl Language for TestLang { - type Kind = TestKind; - - fn kind_from_raw(raw: RawSyntaxKind) -> Self::Kind { +impl Syntax for TestKind { + fn from_raw(raw: RawSyntaxKind) -> Self { if raw.0 == u32::MAX - 1 { TestKind::Plus } else { @@ -52,40 +28,37 @@ impl Language for TestLang { } } - fn kind_to_raw(kind: Self::Kind) -> RawSyntaxKind { - match kind { + fn into_raw(self) -> RawSyntaxKind { + match self { TestKind::Element { n } => RawSyntaxKind(n), TestKind::Plus => RawSyntaxKind(u32::MAX - 1), } } - fn static_text(kind: Self::Kind) -> Option<&'static str> { - if !::VALUE { - return None; - } - - match kind { + fn static_text(self) -> Option<&'static str> { + match self { TestKind::Plus => Some("+"), TestKind::Element { .. } => None, } } } -pub fn build_tree_with_cache(root: &Element<'_>, cache: &mut NodeCache<'_, I>) -> GreenNode +pub fn build_tree_with_cache(root: &Element<'_>, cache: &mut NodeCache<'_, I>, use_static_text: bool) -> GreenNode where I: Interner, { - let mut builder: GreenNodeBuilder, I> = GreenNodeBuilder::with_cache(cache); - build_recursive(root, &mut builder, 0); + let mut builder: GreenNodeBuilder = GreenNodeBuilder::with_cache(cache); + build_recursive(root, &mut builder, 0, use_static_text); let (node, cache) = builder.finish(); assert!(cache.is_none()); node } -pub fn build_recursive( +pub fn build_recursive( root: &Element<'_>, - builder: &mut GreenNodeBuilder<'_, '_, TestLang, I>, + builder: &mut GreenNodeBuilder<'_, '_, TestKind, I>, mut from: u32, + use_static_text: bool, ) -> u32 where I: Interner, @@ -94,13 +67,16 @@ where Element::Node(children) => { builder.start_node(TestKind::Element { n: from }); for child in children { - from = build_recursive(child, builder, from + 1); + from = build_recursive(child, builder, from + 1, use_static_text); } builder.finish_node(); } Element::Token(text) => { builder.token(TestKind::Element { n: from }, text); } + Element::Plus if use_static_text => { + builder.static_token(TestKind::Plus); + } Element::Plus => { builder.token(TestKind::Plus, "+"); } @@ -132,14 +108,14 @@ pub fn create(c: &mut Criterion) { group.bench_function("with static text", |b| { b.iter(|| { - let tree = build_tree_with_cache::(&tree, &mut cache); + let tree = build_tree_with_cache(&tree, &mut cache, true); black_box(tree); }) }); group.bench_function("without static text", |b| { b.iter(|| { - let tree = build_tree_with_cache::(&tree, &mut cache); + let tree = build_tree_with_cache(&tree, &mut cache, false); black_box(tree); }) }); diff --git a/examples/math.rs b/cstree/examples/math.rs similarity index 80% rename from examples/math.rs rename to cstree/examples/math.rs index 3b00957..ae44a4e 100644 --- a/examples/math.rs +++ b/cstree/examples/math.rs @@ -13,17 +13,21 @@ //! - "+" Token(Add) //! - "4" Token(Number) -use cstree::{build::GreenNodeBuilder, interning::Resolver, util::NodeOrToken}; +use cstree::{build::GreenNodeBuilder, interning::Resolver, util::NodeOrToken, Syntax}; use std::iter::Peekable; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Syntax)] #[repr(u32)] enum SyntaxKind { - Whitespace = 0, + Whitespace, + #[static_text("+")] Add, + #[static_text("-")] Sub, + #[static_text("*")] Mul, + #[static_text("/")] Div, Number, @@ -31,6 +35,7 @@ enum SyntaxKind { Operation, Root, } +type MySyntax = SyntaxKind; use SyntaxKind::*; impl From for cstree::RawSyntaxKind { @@ -39,40 +44,15 @@ impl From for cstree::RawSyntaxKind { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -enum Lang {} -impl cstree::Language for Lang { - type Kind = SyntaxKind; - - fn kind_from_raw(raw: cstree::RawSyntaxKind) -> Self::Kind { - assert!(raw.0 <= Root as u32); - unsafe { std::mem::transmute::(raw.0) } - } - - fn kind_to_raw(kind: Self::Kind) -> cstree::RawSyntaxKind { - kind.into() - } - - fn static_text(kind: Self::Kind) -> Option<&'static str> { - match kind { - Add => Some("+"), - Sub => Some("-"), - Mul => Some("*"), - Div => Some("/"), - _ => None, - } - } -} - -type SyntaxNode = cstree::syntax::SyntaxNode; +type SyntaxNode = cstree::syntax::SyntaxNode; #[allow(unused)] -type SyntaxToken = cstree::syntax::SyntaxToken; +type SyntaxToken = cstree::syntax::SyntaxToken; #[allow(unused)] type SyntaxElement = cstree::util::NodeOrToken; type SyntaxElementRef<'a> = cstree::util::NodeOrToken<&'a SyntaxNode, &'a SyntaxToken>; struct Parser<'input, I: Iterator> { - builder: GreenNodeBuilder<'static, 'static, Lang>, + builder: GreenNodeBuilder<'static, 'static, MySyntax>, iter: Peekable, } impl<'input, I: Iterator> Parser<'input, I> { diff --git a/examples/readme.rs b/cstree/examples/readme.rs similarity index 93% rename from examples/readme.rs rename to cstree/examples/readme.rs index ff0f083..77985a2 100644 --- a/examples/readme.rs +++ b/cstree/examples/readme.rs @@ -6,7 +6,7 @@ use cstree::{ syntax::{ResolvedElementRef, ResolvedNode}, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u32)] pub enum SyntaxKind { /* Tokens */ @@ -19,16 +19,12 @@ pub enum SyntaxKind { Expr, Root, } +type Calculator = SyntaxKind; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Calculator; -impl Language for Calculator { - // The tokens and nodes we just defined - type Kind = SyntaxKind; - - fn kind_from_raw(raw: RawSyntaxKind) -> Self::Kind { - // This just needs to be the inverse of `kind_to_raw`, but could also - // be an `impl TryFrom for SyntaxKind` or any other conversion. +impl Syntax for Calculator { + fn from_raw(raw: RawSyntaxKind) -> Self { + // This just needs to be the inverse of `into_raw`, but could also + // be an `impl TryFrom for SyntaxKind` or any other conversion. match raw.0 { 0 => SyntaxKind::Int, 1 => SyntaxKind::Plus, @@ -41,12 +37,12 @@ impl Language for Calculator { } } - fn kind_to_raw(kind: Self::Kind) -> RawSyntaxKind { - RawSyntaxKind(kind as u32) + fn into_raw(self) -> RawSyntaxKind { + RawSyntaxKind(self as u32) } - fn static_text(kind: Self::Kind) -> Option<&'static str> { - match kind { + fn static_text(self) -> Option<&'static str> { + match self { SyntaxKind::Plus => Some("+"), SyntaxKind::Minus => Some("-"), SyntaxKind::LParen => Some("("), diff --git a/examples/s_expressions.rs b/cstree/examples/s_expressions.rs similarity index 77% rename from examples/s_expressions.rs rename to cstree/examples/s_expressions.rs index 3311150..dcbe7a3 100644 --- a/examples/s_expressions.rs +++ b/cstree/examples/s_expressions.rs @@ -7,12 +7,23 @@ //! You may want to follow the conceptual overview of the design alongside this tutorial: //! https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/dev/syntax.md -/// Let's start with defining all kinds of tokens and composite nodes. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +use std::collections::VecDeque; + +/// Let's start with defining all kinds of syntactical elements that we want to have in our +/// language's grammar. These correspond to tokens and composite nodes in the syntax tree that we +/// want to parse the S-expressions into. +use cstree::Syntax; + +/// Implementing the `Syntax` trait teaches `cstree` to convert our `SyntaxKind`s to its internal +/// types, allowing for a nicer `SyntaxNode` API where "kinds" are values from our `enum` instead of +/// plain `u32` values. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Syntax)] #[repr(u32)] pub enum SyntaxKind { - LParen = 0, // '(' - RParen, // ')' + #[static_text("(")] + LParen, // '(' + #[static_text(")")] + RParen, // ')' Word, // '+', '15' Whitespace, // whitespaces is explicit Error, // as well as errors @@ -22,53 +33,20 @@ pub enum SyntaxKind { Atom, // `+`, `15`, wraps a WORD token Root, // top-level node: a list of s-expressions } -use std::collections::VecDeque; +/// When matching against the kind of the node, `SyntaxKind::Kind` is a fine name to use. +/// For specifying generic arguments like `Node`, we'll use this alias to refer to the +/// syntax as a whole. +type SExprSyntax = SyntaxKind; use SyntaxKind::*; -/// Some boilerplate is needed, as cstree represents kinds as `struct SyntaxKind(u16)` internally, -/// in order to not need the user's `enum SyntaxKind` as a type parameter. -/// -/// First, to easily pass the enum variants into cstree via `.into()`: -impl From for cstree::RawSyntaxKind { - fn from(kind: SyntaxKind) -> Self { - Self(kind as u32) - } -} - -/// Second, implementing the `Language` trait teaches cstree to convert between these two SyntaxKind -/// types, allowing for a nicer SyntaxNode API where "kinds" are values from our `enum SyntaxKind`, -/// instead of plain u16 values. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Lang {} -impl cstree::Language for Lang { - type Kind = SyntaxKind; - - fn kind_from_raw(raw: cstree::RawSyntaxKind) -> Self::Kind { - assert!(raw.0 <= Root as u32); - unsafe { std::mem::transmute::(raw.0) } - } - - fn kind_to_raw(kind: Self::Kind) -> cstree::RawSyntaxKind { - kind.into() - } - - fn static_text(kind: Self::Kind) -> Option<&'static str> { - match kind { - LParen => Some("("), - RParen => Some(")"), - _ => None, - } - } -} - -/// GreenNode is an immutable tree, which caches identical nodes and tokens, but doesn't contain +/// `GreenNode` is an immutable tree, which caches identical nodes and tokens, but doesn't contain /// offsets and parent pointers. -/// cstree also deduplicates the actual source string in addition to the tree nodes, so we will need -/// the Resolver to get the real text back from the interned representation. -use cstree::{green::GreenNode, interning::Resolver, Language}; +/// `cstree` also deduplicates the actual source string in addition to the tree nodes, so we will need +/// the `Resolver` to get the real text back from the interned representation. +use cstree::{green::GreenNode, interning::Resolver}; -/// You can construct GreenNodes by hand, but a builder is helpful for top-down parsers: it maintains +/// You can construct `GreenNode`s by hand, but a builder is helpful for top-down parsers: it maintains /// a stack of currently in-progress nodes. use cstree::build::GreenNodeBuilder; @@ -89,13 +67,13 @@ fn parse(text: &str) -> Parse { /// input tokens, including whitespace. tokens: VecDeque<(SyntaxKind, &'input str)>, /// the in-progress green tree. - builder: GreenNodeBuilder<'static, 'static, Lang>, + builder: GreenNodeBuilder<'static, 'static, SExprSyntax>, /// the list of syntax errors we've accumulated so far. errors: Vec, } /// The outcome of parsing a single S-expression - enum SexpRes { + enum SExprResult { /// An S-expression (i.e. an atom, or a list) was successfully parsed Ok, /// Nothing was parsed, as no significant tokens remained @@ -111,14 +89,14 @@ fn parse(text: &str) -> Parse { // Parse zero or more S-expressions loop { match self.sexp() { - SexpRes::Eof => break, - SexpRes::RParen => { + SExprResult::Eof => break, + SExprResult::RParen => { self.builder.start_node(Error); self.errors.push("unmatched `)`".to_string()); self.bump(); // be sure to advance even in case of an error, so as to not get stuck self.builder.finish_node(); } - SexpRes::Ok => {} + SExprResult::Ok => {} } } // Don't forget to eat *trailing* whitespace @@ -144,28 +122,28 @@ fn parse(text: &str) -> Parse { self.bump(); // '(' loop { match self.sexp() { - SexpRes::Eof => { + SExprResult::Eof => { self.errors.push("expected `)`".to_string()); break; } - SexpRes::RParen => { + SExprResult::RParen => { self.bump(); break; } - SexpRes::Ok => {} + SExprResult::Ok => {} } } // close the list node self.builder.finish_node(); } - fn sexp(&mut self) -> SexpRes { + fn sexp(&mut self) -> SExprResult { // Eat leading whitespace self.skip_ws(); // Either a list, an atom, a closing paren, or an eof. let t = match self.current() { - None => return SexpRes::Eof, - Some(RParen) => return SexpRes::RParen, + None => return SExprResult::Eof, + Some(RParen) => return SExprResult::RParen, Some(t) => t, }; match t { @@ -178,7 +156,7 @@ fn parse(text: &str) -> Parse { Error => self.bump(), _ => unreachable!(), } - SexpRes::Ok + SExprResult::Ok } /// Advance one token, adding it to the current branch of the tree builder. @@ -208,13 +186,13 @@ fn parse(text: &str) -> Parse { } /// To work with the parse results we need a view into the green tree - the syntax tree. -/// It is also immutable, like a GreenNode, but it contains parent pointers, offsets, and has +/// It is also immutable, like a `GreenNode`, but it contains parent pointers, offsets, and has /// identity semantics. -type SyntaxNode = cstree::syntax::SyntaxNode; +type SyntaxNode = cstree::syntax::SyntaxNode; #[allow(unused)] -type SyntaxToken = cstree::syntax::SyntaxToken; +type SyntaxToken = cstree::syntax::SyntaxToken; #[allow(unused)] -type SyntaxElement = cstree::syntax::SyntaxElement; +type SyntaxElement = cstree::syntax::SyntaxElement; impl Parse { fn syntax(&self) -> SyntaxNode { @@ -292,21 +270,21 @@ mod ast { ast_node!(List, List); } -// Sexp is slightly different because it can be both an atom and a list, so let's do it by hand. +// `SExpr` is slightly different because it can be both an atom and a list, so let's do it by hand. #[derive(PartialEq, Eq, Hash)] #[repr(transparent)] -struct Sexp(SyntaxNode); +struct SExpr(SyntaxNode); enum SexpKind { Atom(ast::Atom), List(ast::List), } -impl Sexp { +impl SExpr { fn cast(node: SyntaxNode) -> Option { use ast::*; if Atom::cast(node.clone()).is_some() || List::cast(node.clone()).is_some() { - Some(Sexp(node)) + Some(SExpr(node)) } else { None } @@ -323,8 +301,8 @@ impl Sexp { // Let's enhance AST nodes with ancillary functions and eval. impl ast::Root { - fn sexps(&self) -> impl Iterator + '_ { - self.0.children().cloned().filter_map(Sexp::cast) + fn sexps(&self) -> impl Iterator + '_ { + self.0.children().cloned().filter_map(SExpr::cast) } } @@ -355,7 +333,7 @@ impl ast::Atom { use cstree::util::NodeOrToken; match self.0.green().children().next() { - Some(NodeOrToken::Token(token)) => Lang::static_text(Lang::kind_from_raw(token.kind())) + Some(NodeOrToken::Token(token)) => SExprSyntax::static_text(SExprSyntax::from_raw(token.kind())) .or_else(|| token.text(resolver)) .unwrap(), _ => unreachable!(), @@ -364,8 +342,8 @@ impl ast::Atom { } impl ast::List { - fn sexps(&self) -> impl Iterator + '_ { - self.0.children().cloned().filter_map(Sexp::cast) + fn sexps(&self) -> impl Iterator + '_ { + self.0.children().cloned().filter_map(SExpr::cast) } fn eval(&self, resolver: &impl Resolver) -> Option { @@ -386,7 +364,7 @@ impl ast::List { } } -impl Sexp { +impl SExpr { fn eval(&self, resolver: &impl Resolver) -> Option { match self.kind() { SexpKind::Atom(atom) => atom.eval(resolver), @@ -418,10 +396,10 @@ nan assert_eq!(res, vec![Some(92), Some(92), None, None, Some(92),]) } -/// Split the input string into a flat list of tokens (such as L_PAREN, WORD, and WHITESPACE) +/// Split the input string into a flat list of tokens (such as `LParen`, `Word`, and `Whitespace`) fn lex(text: &str) -> VecDeque<(SyntaxKind, &str)> { fn tok(t: SyntaxKind) -> m_lexer::TokenKind { - m_lexer::TokenKind(cstree::RawSyntaxKind::from(t).0 as u16) + m_lexer::TokenKind(t.into_raw().0 as u16) } fn kind(t: m_lexer::TokenKind) -> SyntaxKind { match t.0 { diff --git a/examples/salsa.rs b/cstree/examples/salsa.rs similarity index 91% rename from examples/salsa.rs rename to cstree/examples/salsa.rs index 110133e..2aeadd6 100644 --- a/examples/salsa.rs +++ b/cstree/examples/salsa.rs @@ -34,7 +34,7 @@ fn main() { let interner = db.as_interner(); let mut shared_interner = &interner; - let mut builder: GreenNodeBuilder = GreenNodeBuilder::with_interner(&mut shared_interner); + let mut builder: GreenNodeBuilder = GreenNodeBuilder::with_interner(&mut shared_interner); let (tree, _no_interner_because_it_was_borrowed) = { builder.start_node(TestSyntaxKind::Plus); builder.token(TestSyntaxKind::Float, "2.05"); @@ -45,6 +45,6 @@ fn main() { builder.finish_node(); builder.finish() }; - let tree: SyntaxNode = SyntaxNode::new_root(tree); + let tree: SyntaxNode = SyntaxNode::new_root(tree); assert_eq!(tree.resolve_text(shared_interner), "2.05 + 7.32"); } diff --git a/src/green.rs b/cstree/src/green.rs similarity index 100% rename from src/green.rs rename to cstree/src/green.rs diff --git a/src/green/builder.rs b/cstree/src/green/builder.rs similarity index 86% rename from src/green/builder.rs rename to cstree/src/green/builder.rs index 48ab437..cf4e967 100644 --- a/src/green/builder.rs +++ b/cstree/src/green/builder.rs @@ -8,7 +8,7 @@ use crate::{ interning::{new_interner, Interner, TokenInterner, TokenKey}, util::NodeOrToken, utility_types::MaybeOwned, - Language, RawSyntaxKind, + RawSyntaxKind, Syntax, }; use super::{node::GreenNodeHead, token::GreenTokenData}; @@ -35,12 +35,12 @@ impl NodeCache<'static> { /// tokens. To re-use an existing interner, see [`with_interner`](NodeCache::with_interner). /// # Examples /// ``` - /// # use cstree::testing::{*, Language as _}; + /// # use cstree::testing::*; /// use cstree::build::NodeCache; /// /// // Build a tree /// let mut cache = NodeCache::new(); - /// let mut builder: GreenNodeBuilder = GreenNodeBuilder::with_cache(&mut cache); + /// let mut builder: GreenNodeBuilder = GreenNodeBuilder::with_cache(&mut cache); /// # builder.start_node(Root); /// # builder.token(Int, "42"); /// # builder.finish_node(); @@ -48,9 +48,9 @@ impl NodeCache<'static> { /// let (tree, _) = builder.finish(); /// /// // Check it out! - /// assert_eq!(tree.kind(), MyLanguage::kind_to_raw(Root)); + /// assert_eq!(tree.kind(), MySyntax::into_raw(Root)); /// let int = tree.children().next().unwrap(); - /// assert_eq!(int.kind(), MyLanguage::kind_to_raw(Int)); + /// assert_eq!(int.kind(), MySyntax::into_raw(Int)); /// ``` pub fn new() -> Self { Self { @@ -75,14 +75,14 @@ where /// (strings) across tokens. /// # Examples /// ``` - /// # use cstree::testing::{*, Language as _}; + /// # use cstree::testing::*; /// # use cstree::interning::*; /// use cstree::build::NodeCache; /// /// // Create the builder from a custom interner /// let mut interner = new_interner(); /// let mut cache = NodeCache::with_interner(&mut interner); - /// let mut builder: GreenNodeBuilder = + /// let mut builder: GreenNodeBuilder = /// GreenNodeBuilder::with_cache(&mut cache); /// /// // Construct the tree @@ -93,9 +93,9 @@ where /// let (tree, _) = builder.finish(); /// /// // Use the tree - /// assert_eq!(tree.kind(), MyLanguage::kind_to_raw(Root)); + /// assert_eq!(tree.kind(), MySyntax::into_raw(Root)); /// let int = tree.children().next().unwrap(); - /// assert_eq!(int.kind(), MyLanguage::kind_to_raw(Int)); + /// assert_eq!(int.kind(), MySyntax::into_raw(Int)); /// assert_eq!(int.as_token().unwrap().text(&interner), Some("42")); /// ``` #[inline] @@ -111,14 +111,14 @@ where /// (strings) across tokens. /// # Examples /// ``` - /// # use cstree::testing::{*, Language as _}; + /// # use cstree::testing::*; /// # use cstree::interning::*; /// use cstree::build::NodeCache; /// /// // Create the builder from a custom interner /// let mut interner = new_interner(); /// let cache = NodeCache::from_interner(interner); - /// let mut builder: GreenNodeBuilder = + /// let mut builder: GreenNodeBuilder = /// GreenNodeBuilder::from_cache(cache); /// /// // Construct the tree @@ -130,9 +130,9 @@ where /// /// // Use the tree /// let interner = cache.unwrap().into_interner().unwrap(); - /// assert_eq!(tree.kind(), MyLanguage::kind_to_raw(Root)); + /// assert_eq!(tree.kind(), MySyntax::into_raw(Root)); /// let int = tree.children().next().unwrap(); - /// assert_eq!(int.kind(), MyLanguage::kind_to_raw(Int)); + /// assert_eq!(int.kind(), MySyntax::into_raw(Int)); /// assert_eq!(int.as_token().unwrap().text(&interner), Some("42")); /// ``` #[inline] @@ -177,9 +177,9 @@ where self.interner.into_owned() } - fn node(&mut self, kind: L::Kind, all_children: &mut Vec, offset: usize) -> GreenNode { + fn node(&mut self, kind: S, all_children: &mut Vec, offset: usize) -> GreenNode { // NOTE: this fn must remove all children starting at `first_child` from `all_children` before returning - let kind = L::kind_to_raw(kind); + let kind = S::into_raw(kind); let mut hasher = FxHasher32::default(); let mut text_len: TextSize = 0.into(); for child in &all_children[offset..] { @@ -229,9 +229,9 @@ where .clone() } - fn token(&mut self, kind: L::Kind, text: Option, len: u32) -> GreenToken { + fn token(&mut self, kind: S, text: Option, len: u32) -> GreenToken { let text_len = TextSize::from(len); - let kind = L::kind_to_raw(kind); + let kind = S::into_raw(kind); let data = GreenTokenData { kind, text, text_len }; self.tokens .entry(data) @@ -253,29 +253,29 @@ pub struct Checkpoint(usize); /// /// # Examples /// ``` -/// # use cstree::testing::{*, Language as _}; +/// # use cstree::testing::*; /// // Build a tree -/// let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); +/// let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); /// builder.start_node(Root); /// builder.token(Int, "42"); /// builder.finish_node(); /// let (tree, cache) = builder.finish(); /// /// // Check it out! -/// assert_eq!(tree.kind(), MyLanguage::kind_to_raw(Root)); +/// assert_eq!(tree.kind(), MySyntax::into_raw(Root)); /// let int = tree.children().next().unwrap(); -/// assert_eq!(int.kind(), MyLanguage::kind_to_raw(Int)); +/// assert_eq!(int.kind(), MySyntax::into_raw(Int)); /// let resolver = cache.unwrap().into_interner().unwrap(); /// assert_eq!(int.as_token().unwrap().text(&resolver), Some("42")); /// ``` #[derive(Debug)] -pub struct GreenNodeBuilder<'cache, 'interner, L: Language, I = TokenInterner> { +pub struct GreenNodeBuilder<'cache, 'interner, S: Syntax, I = TokenInterner> { cache: MaybeOwned<'cache, NodeCache<'interner, I>>, - parents: Vec<(L::Kind, usize)>, + parents: Vec<(S, usize)>, children: Vec, } -impl GreenNodeBuilder<'static, 'static, L> { +impl GreenNodeBuilder<'static, 'static, S> { /// Creates new builder with an empty [`NodeCache`]. pub fn new() -> Self { Self { @@ -286,15 +286,15 @@ impl GreenNodeBuilder<'static, 'static, L> { } } -impl Default for GreenNodeBuilder<'static, 'static, L> { +impl Default for GreenNodeBuilder<'static, 'static, S> { fn default() -> Self { Self::new() } } -impl<'cache, 'interner, L, I> GreenNodeBuilder<'cache, 'interner, L, I> +impl<'cache, 'interner, S, I> GreenNodeBuilder<'cache, 'interner, S, I> where - L: Language, + S: Syntax, I: Interner, { /// Reusing a [`NodeCache`] between multiple builders saves memory, as it allows to structurally @@ -312,11 +312,11 @@ where /// The `cache` given will be returned on [`finish`](GreenNodeBuilder::finish). /// # Examples /// ``` - /// # use cstree::testing::{*, Language as _}; + /// # use cstree::testing::*; /// # use cstree::build::*; /// // Construct a builder from our own cache /// let cache = NodeCache::new(); - /// let mut builder: GreenNodeBuilder = GreenNodeBuilder::from_cache(cache); + /// let mut builder: GreenNodeBuilder = GreenNodeBuilder::from_cache(cache); /// /// // Build a tree /// # builder.start_node(Root); @@ -327,9 +327,9 @@ where /// /// // Use the tree /// let interner = cache.unwrap().into_interner().unwrap(); - /// assert_eq!(tree.kind(), MyLanguage::kind_to_raw(Root)); + /// assert_eq!(tree.kind(), MySyntax::into_raw(Root)); /// let int = tree.children().next().unwrap(); - /// assert_eq!(int.kind(), MyLanguage::kind_to_raw(Int)); + /// assert_eq!(int.kind(), MySyntax::into_raw(Int)); /// assert_eq!(int.as_token().unwrap().text(&interner), Some("42")); /// ``` pub fn from_cache(cache: NodeCache<'interner, I>) -> Self { @@ -377,7 +377,7 @@ where /// # use cstree::testing::*; /// # use cstree::build::*; /// # use cstree::interning::*; - /// let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); + /// let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); /// let interner = builder.interner_mut(); /// let key = interner.get_or_intern("foo"); /// assert_eq!(interner.resolve(key), "foo"); @@ -392,19 +392,19 @@ where /// ## Panics /// In debug mode, if `kind` has static text, this function will verify that `text` matches that text. #[inline] - pub fn token(&mut self, kind: L::Kind, text: &str) { - let token = match L::static_text(kind) { + pub fn token(&mut self, kind: S, text: &str) { + let token = match S::static_text(kind) { Some(static_text) => { debug_assert_eq!( static_text, text, r#"Received `{kind:?}` token which should have text "{static_text}", but "{text}" was given."# ); - self.cache.token::(kind, None, static_text.len() as u32) + self.cache.token::(kind, None, static_text.len() as u32) } None => { let len = text.len() as u32; let text = self.cache.intern(text); - self.cache.token::(kind, Some(text), len) + self.cache.token::(kind, Some(text), len) } }; self.children.push(token.into()); @@ -420,15 +420,15 @@ where /// ## Panics /// If `kind` does not have static text, i.e., `L::static_text(kind)` returns `None`. #[inline] - pub fn static_token(&mut self, kind: L::Kind) { - let static_text = L::static_text(kind).unwrap_or_else(|| panic!("Missing static text for '{kind:?}'")); - let token = self.cache.token::(kind, None, static_text.len() as u32); + pub fn static_token(&mut self, kind: S) { + let static_text = S::static_text(kind).unwrap_or_else(|| panic!("Missing static text for '{kind:?}'")); + let token = self.cache.token::(kind, None, static_text.len() as u32); self.children.push(token.into()); } /// Start new node of the given `kind` and make it current. #[inline] - pub fn start_node(&mut self, kind: L::Kind) { + pub fn start_node(&mut self, kind: S) { let len = self.children.len(); self.parents.push((kind, len)); } @@ -438,7 +438,7 @@ where pub fn finish_node(&mut self) { let (kind, first_child) = self.parents.pop().unwrap(); // NOTE: we rely on the node cache to remove all children starting at `first_child` from `self.children` - let node = self.cache.node::(kind, &mut self.children, first_child); + let node = self.cache.node::(kind, &mut self.children, first_child); self.children.push(node.into()); } @@ -450,13 +450,13 @@ where /// # Examples /// ``` /// # use cstree::testing::*; - /// # use cstree::{build::GreenNodeBuilder, Language}; + /// # use cstree::build::GreenNodeBuilder; /// # struct Parser; /// # impl Parser { /// # fn peek(&self) -> Option { None } /// # fn parse_expr(&mut self) {} /// # } - /// # let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); + /// # let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); /// # let mut parser = Parser; /// let checkpoint = builder.checkpoint(); /// parser.parse_expr(); @@ -475,7 +475,7 @@ where /// Wrap the previous branch marked by [`checkpoint`](GreenNodeBuilder::checkpoint) in a new /// branch and make it current. #[inline] - pub fn start_node_at(&mut self, checkpoint: Checkpoint, kind: L::Kind) { + pub fn start_node_at(&mut self, checkpoint: Checkpoint, kind: S) { let Checkpoint(checkpoint) = checkpoint; assert!( checkpoint <= self.children.len(), diff --git a/src/green/element.rs b/cstree/src/green/element.rs similarity index 100% rename from src/green/element.rs rename to cstree/src/green/element.rs diff --git a/src/green/iter.rs b/cstree/src/green/iter.rs similarity index 100% rename from src/green/iter.rs rename to cstree/src/green/iter.rs diff --git a/src/green/node.rs b/cstree/src/green/node.rs similarity index 100% rename from src/green/node.rs rename to cstree/src/green/node.rs diff --git a/src/green/token.rs b/cstree/src/green/token.rs similarity index 100% rename from src/green/token.rs rename to cstree/src/green/token.rs diff --git a/src/interning.rs b/cstree/src/interning.rs similarity index 80% rename from src/interning.rs rename to cstree/src/interning.rs index d1fd913..a38e055 100644 --- a/src/interning.rs +++ b/cstree/src/interning.rs @@ -5,7 +5,7 @@ //! [`GreenNodeBuilder::token`], which takes the kind of token and a refernce to the text of the token in the source. //! //! Of course, there are tokens whose text will always be the same, such as punctuation (like a semicolon), keywords -//! (like `fn`), or operators (like `<=`). Use [`Language::static_text`] when implementing `Language` to make `cstree` +//! (like `fn`), or operators (like `<=`). Use [`Syntax::static_text`] when implementing `Syntax` to make `cstree` //! aware of such tokens. //! //! There is, however, another category of tokens whose text will appear repeatedly, but for which we cannot know the @@ -59,44 +59,45 @@ //! capabilities with `cstree` as well. Support for this is experimental, and you have to opt in via the //! `salsa_2022_compat` feature. For instructions on how to do this, and whether you actually want to, please refer to //! [the `salsa_compat` module documentation]. -//! -//! ## Multi-threaded interners -//! If you want to use your interner on more than one thread, the interner needs to support interning new text through -//! shared access. With the `multi_threaded_interning` feature, you can get such an interner by calling -//! [`new_threaded_interner`]. The feature also enables support for `ThreadedRodeo`, the multi-threaded interner from -//! `lasso`. -//! -//! **You can pass a reference to that interner to anything that expects an [`Interner`]!** -//! While the interning methods on [`Interner`] require a `&mut self` to also work for single-threaded interners, both -//! [`Resolver`] and [`Interner`] will be implemented for `&interner` if `interner` is multi-threaded: -//! -//! ``` -//! # use cstree::testing::{*, Language as _}; -//! # use cstree::interning::*; -//! -//! let interner = new_threaded_interner(); -//! let mut builder: GreenNodeBuilder = -//! GreenNodeBuilder::from_interner(&interner); -//! -//! # builder.start_node(Root); -//! # builder.token(Int, "42"); -//! # builder.finish_node(); -//! parse(&mut builder, "42"); -//! let (tree, cache) = builder.finish(); -//! -//! // Note that we get a cache and interner back, because we passed an "owned" reference to `from_interner` -//! let used_interner = cache.unwrap().into_interner().unwrap(); -//! assert_eq!(used_interner as *const _, &interner as *const _); -//! -//! let int = tree.children().next().unwrap(); -//! assert_eq!(int.as_token().unwrap().text(&interner), Some("42")); -//! ``` -//! -//! Here, we use `from_interner`, but pass it only a shared reference to "own". Take care to denote the type signature -//! of the `GreenNodeBuilder` appropriately. +#![cfg_attr( + feature = "multi_threaded_interning", + doc = r###" +## Multi-threaded interners + +If you want to use your interner on more than one thread, the interner needs to support interning new text through +shared access. With the `multi_threaded_interning` feature, you can get such an interner by calling +[`new_threaded_interner`]. The feature also enables support for `ThreadedRodeo`, the multi-threaded interner from +`lasso`. + +**You can pass a reference to that interner to anything that expects an [`Interner`]!** +While the interning methods on [`Interner`] require a `&mut self` to also work for single-threaded interners, both +[`Resolver`] and [`Interner`] will be implemented for `&interner` if `interner` is multi-threaded: + +``` +# use cstree::testing::*; +# use cstree::interning::*; +let interner = new_threaded_interner(); +let mut builder: GreenNodeBuilder = + GreenNodeBuilder::from_interner(&interner); +# builder.start_node(Root); +# builder.token(Int, "42"); +# builder.finish_node(); +parse(&mut builder, "42"); +let (tree, cache) = builder.finish(); +// Note that we get a cache and interner back, because we passed an "owned" reference to `from_interner` +let used_interner = cache.unwrap().into_interner().unwrap(); +assert_eq!(used_interner as *const _, &interner as *const _); +let int = tree.children().next().unwrap(); +assert_eq!(int.as_token().unwrap().text(&interner), Some("42")); +``` + +Here, we use `from_interner`, but pass it only a shared reference to "own". Take care to denote the type signature +of the `GreenNodeBuilder` appropriately. +"### +)] //! //! [crate documentation]: crate -//! [`Language::static_text`]: crate::Language::static_text +//! [`Syntax::static_text`]: crate::Syntax::static_text //! [`GreenNodeBuilder::token`]: crate::build::GreenNodeBuilder::token //! [`GreenNodeBuilder::new`]: crate::build::GreenNodeBuilder::new //! [`finish`]: crate::build::GreenNodeBuilder::finish diff --git a/src/interning/default_interner.rs b/cstree/src/interning/default_interner.rs similarity index 100% rename from src/interning/default_interner.rs rename to cstree/src/interning/default_interner.rs diff --git a/src/interning/lasso_compat.rs b/cstree/src/interning/lasso_compat.rs similarity index 100% rename from src/interning/lasso_compat.rs rename to cstree/src/interning/lasso_compat.rs diff --git a/src/interning/lasso_compat/token_interner.rs b/cstree/src/interning/lasso_compat/token_interner.rs similarity index 100% rename from src/interning/lasso_compat/token_interner.rs rename to cstree/src/interning/lasso_compat/token_interner.rs diff --git a/src/interning/lasso_compat/traits.rs b/cstree/src/interning/lasso_compat/traits.rs similarity index 100% rename from src/interning/lasso_compat/traits.rs rename to cstree/src/interning/lasso_compat/traits.rs diff --git a/src/interning/salsa_compat.rs b/cstree/src/interning/salsa_compat.rs similarity index 98% rename from src/interning/salsa_compat.rs rename to cstree/src/interning/salsa_compat.rs index 081c22c..e98c377 100644 --- a/src/interning/salsa_compat.rs +++ b/cstree/src/interning/salsa_compat.rs @@ -55,7 +55,7 @@ //! let db = Database::default(); //! let interner = db.as_interner(); // <-- conversion happens here //! let mut shared_interner = &interner; -//! let mut builder: GreenNodeBuilder = GreenNodeBuilder::with_interner(&mut shared_interner); +//! let mut builder: GreenNodeBuilder = GreenNodeBuilder::with_interner(&mut shared_interner); //! let (tree, _no_interner_because_it_was_borrowed) = { //! builder.start_node(TestSyntaxKind::Plus); //! builder.token(TestSyntaxKind::Float, "2.05"); @@ -66,7 +66,7 @@ //! builder.finish_node(); //! builder.finish() //! }; -//! let tree: SyntaxNode = SyntaxNode::new_root(tree); +//! let tree: SyntaxNode = SyntaxNode::new_root(tree); //! assert_eq!(tree.resolve_text(shared_interner), "2.05 + 7.32"); //! ``` //! diff --git a/src/interning/traits.rs b/cstree/src/interning/traits.rs similarity index 100% rename from src/interning/traits.rs rename to cstree/src/interning/traits.rs diff --git a/src/lib.rs b/cstree/src/lib.rs similarity index 85% rename from src/lib.rs rename to cstree/src/lib.rs index 41d33f2..416ebcb 100644 --- a/src/lib.rs +++ b/cstree/src/lib.rs @@ -40,7 +40,7 @@ //! to happen to go from input text to a `cstree` syntax tree: //! //! 1. Define an enumeration of the types of tokens (like keywords) and nodes (like "an expression") that you want to -//! have in your syntax and implement [`Language`] +//! have in your syntax and implement [`Syntax`] //! //! 2. Create a [`GreenNodeBuilder`](build::GreenNodeBuilder) and call //! [`start_node`](build::GreenNodeBuilder::start_node), [`token`](build::GreenNodeBuilder::token) and @@ -61,8 +61,8 @@ //! The `enum` needs to be convertible to a `u32`, so we use the `repr` attribute to ensure it uses the correct //! representation. //! -//! ```rust,ignore -//! #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +//! ```rust,no_run +//! #[derive(Debug, Clone, Copy, PartialEq, Eq)] //! #[repr(u32)] //! enum SyntaxKind { //! /* Tokens */ @@ -77,6 +77,15 @@ //! } //! ``` //! +//! For convenience when we're working with generic `cstree` types like `SyntaxNode`, we'll also give a name to our +//! syntax as a whole and add a type alias for it. That way, we can match against `SyntaxKind`s using the original name, +//! but use the more informative `Node` to instantiate `cstree`'s types. +//! +//! ```rust,no_run +//! # enum SyntaxKind {} +//! type Calculator = SyntaxKind; +//! ``` +//! //! Most of these are tokens to lex the input string into, like numbers (`Int`) and operators (`Plus`, `Minus`). //! We only really need one type of node; expressions. //! Our syntax tree's root node will have the special kind `Root`, all other nodes will be @@ -84,21 +93,22 @@ //! expression nodes. //! //! To use our `SyntaxKind`s with `cstree`, we need to tell it how to convert it back to just a number (the -//! `#[repr(u16)]` that we added) by implementing the [`Language`] trait. We can also tell `cstree` about tokens that +//! `#[repr(u32)]` that we added) by implementing the [`Syntax`] trait. We can also tell `cstree` about tokens that //! always have the same text through the `static_text` method on the trait. This is useful for the operators and //! parentheses, but not possible for numbers, since an integer token may be produced from the input `3`, but also from -//! other numbers like `7` or `12`. We implement `Language` on an empty type, just so we can give it a name. +//! other numbers like `7` or `12`. //! -//! ```rust,ignore -//! #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -//! pub struct Calculator; -//! impl Language for Calculator { -//! // The tokens and nodes we just defined -//! type Kind = SyntaxKind; +//! ```rust,no_run +//! # #[derive(Debug, Clone, Copy, PartialEq, Eq)] +//! # #[repr(u32)] +//! # enum SyntaxKind { Int, Plus, Minus, LParen, RParen, Expr, Root } +//! # type Calculator = SyntaxKind; +//! # use cstree::{Syntax, RawSyntaxKind}; //! -//! fn kind_from_raw(raw: RawSyntaxKind) -> Self::Kind { -//! // This just needs to be the inverse of `kind_to_raw`, but could also -//! // be an `impl TryFrom for SyntaxKind` or any other conversion. +//! impl Syntax for Calculator { +//! fn from_raw(raw: RawSyntaxKind) -> Self { +//! // This just needs to be the inverse of `into_raw`, but could also +//! // be an `impl TryFrom for SyntaxKind` or any other conversion. //! match raw.0 { //! 0 => SyntaxKind::Int, //! 1 => SyntaxKind::Plus, @@ -111,12 +121,12 @@ //! } //! } //! -//! fn kind_to_raw(kind: Self::Kind) -> RawSyntaxKind { -//! RawSyntaxKind(kind as u32) +//! fn into_raw(self) -> RawSyntaxKind { +//! RawSyntaxKind(self as u32) //! } //! -//! fn static_text(kind: Self::Kind) -> Option<&'static str> { -//! match kind { +//! fn static_text(self) -> Option<&'static str> { +//! match self { //! SyntaxKind::Plus => Some("+"), //! SyntaxKind::Minus => Some("-"), //! SyntaxKind::LParen => Some("("), @@ -127,12 +137,21 @@ //! } //! ``` //! +//! #### Deriving `Syntax` +//! +//! To save yourself the hassle of defining this conversion (and, perhaps more importantly, +//! continually updating it while your language's syntax is in flux), `cstree` includes a derive +//! macro for [`Syntax`](macro@crate::Syntax) when built with the `derive` feature. With the macro, +//! the `Syntax` trait implementation above can be replaced by simply adding `#[derive(Syntax)]` to +//! `SyntaxKind`. +//! //! ### Parsing into a green tree +//! //! With that out of the way, we can start writing the parser for our expressions. //! For the purposes of this introduction to `cstree`, I'll assume that there is a lexer that yields the following //! tokens: //! -//! ```rust,ignore +//! ```rust,no_run //! #[derive(Debug, PartialEq, Eq, Clone, Copy)] //! pub enum Token<'input> { //! // Note that number strings are not yet parsed into actual numbers, @@ -150,7 +169,19 @@ //! A simple lexer that yields such tokens is part of the full `readme` example, but we'll be busy enough with the //! combination of `cstree` and the actual parser, which we define like this: //! -//! ```rust,ignore +//! ```rust,no_run +//! # use std::iter::Peekable; +//! # use cstree::build::GreenNodeBuilder; +//! # struct Lexer<'a> { input: &'a str } +//! # impl<'a> Lexer<'a> { fn new(input: &'a str) -> Self { Self { input } } } +//! # struct Token<'a> { input: &'a str } +//! # impl<'a> Iterator for Lexer<'a> { +//! # type Item = Token<'a>; +//! # fn next(&mut self) -> Option { None } +//! # } +//! # #[derive(Debug, Clone, Copy, PartialEq, Eq, cstree::Syntax)] +//! # #[repr(u32)] enum Calculator { A } +//! //! pub struct Parser<'input> { //! // `Peekable` is a standard library iterator adapter that allows //! // looking ahead at the next item without removing it from the iterator yet @@ -391,7 +422,7 @@ pub mod prelude { build::GreenNodeBuilder, green::{GreenNode, GreenToken}, syntax::{SyntaxElement, SyntaxNode, SyntaxToken}, - Language, RawSyntaxKind, + RawSyntaxKind, Syntax, }; } @@ -415,62 +446,39 @@ pub mod sync { pub use triomphe::Arc; } -/// The `Language` trait is the bridge between the internal `cstree` representation and your +/// A type that represents what items in your language can be. +/// Typically, this is an `enum` with variants such as `Identifier`, `Literal`, ... +/// +/// The `Syntax` trait is the bridge between the internal `cstree` representation and your /// language's types. /// This is essential for providing a [`SyntaxNode`] API that can be used with your types, as in the /// `s_expressions` example: /// /// ``` -/// #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +/// #[derive(Debug, Clone, Copy, PartialEq, Eq, cstree::Syntax)] /// # #[allow(non_camel_case_types)] /// #[repr(u32)] /// enum SyntaxKind { -/// Plus, // `+` -/// Minus, // `-` +/// #[static_text("+")] +/// Plus, // `+` +/// #[static_text("-")] +/// Minus, // `-` /// Integer, // like `15` /// Expression, // combined expression, like `5 + 4 - 3` -/// Whitespace, // whitespaces is explicit -/// #[doc(hidden)] -/// __LAST, -/// } -/// use SyntaxKind::*; -/// -/// #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -/// enum Lang {} -/// -/// impl cstree::Language for Lang { -/// type Kind = SyntaxKind; -/// -/// fn kind_from_raw(raw: cstree::RawSyntaxKind) -> Self::Kind { -/// assert!(raw.0 <= __LAST as u32); -/// unsafe { std::mem::transmute::(raw.0) } -/// } -/// -/// fn kind_to_raw(kind: Self::Kind) -> cstree::RawSyntaxKind { -/// cstree::RawSyntaxKind(kind as u32) -/// } -/// -/// fn static_text(kind: Self::Kind) -> Option<&'static str> { -/// match kind { -/// Plus => Some("+"), -/// Minus => Some("-"), -/// _ => None, -/// } -/// } +/// Whitespace, // whitespace is explicit /// } /// ``` /// +/// `cstree` provides a procedural macro called `cstree_derive` to automatically generate `Syntax` implementations for +/// syntax kind enums if its `derive` feature is enabled. +/// /// [`SyntaxNode`]: crate::syntax::SyntaxNode -pub trait Language: Sized + Clone + Copy + fmt::Debug + Eq + Ord + std::hash::Hash { - /// A type that represents what items in your Language can be. - /// Typically, this is an `enum` with variants such as `Identifier`, `Literal`, ... - type Kind: Sized + Clone + Copy + fmt::Debug; - +pub trait Syntax: Sized + Copy + fmt::Debug + Eq { /// Construct a semantic item kind from the compact representation. - fn kind_from_raw(raw: RawSyntaxKind) -> Self::Kind; + fn from_raw(raw: RawSyntaxKind) -> Self; /// Convert a semantic item kind into a more compact representation. - fn kind_to_raw(kind: Self::Kind) -> RawSyntaxKind; + fn into_raw(self) -> RawSyntaxKind; /// Fixed text for a particular syntax kind. /// Implement for kinds that will only ever represent the same text, such as punctuation (like a @@ -479,16 +487,25 @@ pub trait Language: Sized + Clone + Copy + fmt::Debug + Eq + Ord + std::hash::Ha /// Indicating tokens that have a `static_text` this way allows `cstree` to store them more efficiently, which makes /// it faster to add them to a syntax tree and to look up their text. Since there can often be many occurrences /// of these tokens inside a file, doing so will improve the performance of using `cstree`. - fn static_text(kind: Self::Kind) -> Option<&'static str>; + fn static_text(self) -> Option<&'static str>; } +#[cfg(feature = "derive")] +#[allow(unused_imports)] +#[macro_use] +extern crate cstree_derive; + +#[cfg(feature = "derive")] +/// Derive macro available if `cstree` is built with `features = ["derive"]`. +pub use cstree_derive::Syntax; + #[doc(hidden)] #[allow(unsafe_code, unused)] pub mod testing { pub use crate::prelude::*; - pub fn parse(_b: &mut GreenNodeBuilder, _s: &str) {} + pub fn parse(_b: &mut GreenNodeBuilder, _s: &str) {} - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u32)] #[allow(non_camel_case_types)] pub enum TestSyntaxKind { @@ -501,25 +518,21 @@ pub mod testing { Whitespace, __LAST, } + pub type MySyntax = TestSyntaxKind; pub use TestSyntaxKind::*; - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub enum TestLang {} - pub type MyLanguage = TestLang; - impl Language for TestLang { - type Kind = TestSyntaxKind; - - fn kind_from_raw(raw: RawSyntaxKind) -> Self::Kind { + impl Syntax for TestSyntaxKind { + fn from_raw(raw: RawSyntaxKind) -> Self { assert!(raw.0 <= TestSyntaxKind::__LAST as u32); - unsafe { std::mem::transmute::(raw.0) } + unsafe { std::mem::transmute::(raw.0) } } - fn kind_to_raw(kind: Self::Kind) -> RawSyntaxKind { - RawSyntaxKind(kind as u32) + fn into_raw(self) -> RawSyntaxKind { + RawSyntaxKind(self as u32) } - fn static_text(kind: Self::Kind) -> Option<&'static str> { - match kind { + fn static_text(self) -> Option<&'static str> { + match self { TestSyntaxKind::Plus => Some("+"), _ => None, } diff --git a/src/serde_impls.rs b/cstree/src/serde_impls.rs similarity index 73% rename from src/serde_impls.rs rename to cstree/src/serde_impls.rs index daa8ffb..436ac11 100644 --- a/src/serde_impls.rs +++ b/cstree/src/serde_impls.rs @@ -6,7 +6,7 @@ use crate::{ syntax::{ResolvedNode, SyntaxNode}, traversal::WalkEvent, util::NodeOrToken, - Language, RawSyntaxKind, + RawSyntaxKind, Syntax, }; use serde::{ de::{Error, SeqAccess, Visitor}, @@ -40,7 +40,7 @@ macro_rules! data_list { /// contains a boolean which indicates if this node has a data. If it has one, /// the deserializer should pop the first element from the data list and continue. /// -/// Takes the `Language` (`$l`), `SyntaxNode` (`$node`), `Resolver` (`$resolver`), +/// Takes the `Syntax` (`$l`), `SyntaxNode` (`$node`), `Resolver` (`$resolver`), /// `Serializer` (`$serializer`), and an optional `data_list` which must be a `mut Vec`. macro_rules! gen_serialize { ($l:ident, $node:expr, $resolver:expr, $ser:ident, $($data_list:ident)?) => {{ @@ -56,9 +56,9 @@ macro_rules! gen_serialize { }) .unwrap_or(false);)? - Some(Event::EnterNode($l::kind_to_raw(node.kind()), has_data)) + Some(Event::EnterNode($l::into_raw(node.kind()), has_data)) } - WalkEvent::Enter(NodeOrToken::Token(tok)) => Some(Event::Token($l::kind_to_raw(tok.kind()), tok.resolve_text($resolver))), + WalkEvent::Enter(NodeOrToken::Token(tok)) => Some(Event::Token($l::into_raw(tok.kind()), tok.resolve_text($resolver))), WalkEvent::Leave(NodeOrToken::Node(_)) => Some(Event::LeaveNode), WalkEvent::Leave(NodeOrToken::Token(_)) => None, @@ -87,53 +87,53 @@ enum Event<'text> { } /// Make a `SyntaxNode` serializable but without serializing the data. -pub(crate) struct SerializeWithResolver<'node, 'resolver, L: Language, D: 'static, R: ?Sized> { - pub(crate) node: &'node SyntaxNode, +pub(crate) struct SerializeWithResolver<'node, 'resolver, S: Syntax, D: 'static, R: ?Sized> { + pub(crate) node: &'node SyntaxNode, pub(crate) resolver: &'resolver R, } /// Make a `SyntaxNode` serializable which will include the data for serialization. -pub(crate) struct SerializeWithData<'node, 'resolver, L: Language, D: 'static, R: ?Sized> { - pub(crate) node: &'node SyntaxNode, +pub(crate) struct SerializeWithData<'node, 'resolver, S: Syntax, D: 'static, R: ?Sized> { + pub(crate) node: &'node SyntaxNode, pub(crate) resolver: &'resolver R, } -impl Serialize for SerializeWithData<'_, '_, L, D, R> +impl Serialize for SerializeWithData<'_, '_, S, D, R> where - L: Language, + S: Syntax, R: Resolver + ?Sized, D: Serialize, { - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: Ser) -> Result where - S: serde::Serializer, + Ser: serde::Serializer, { let mut data_list = Vec::new(); - gen_serialize!(L, self.node, self.resolver, serializer, data_list) + gen_serialize!(S, self.node, self.resolver, serializer, data_list) } } -impl Serialize for SerializeWithResolver<'_, '_, L, D, R> +impl Serialize for SerializeWithResolver<'_, '_, S, D, R> where - L: Language, + S: Syntax, R: Resolver + ?Sized, { - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: Ser) -> Result where - S: serde::Serializer, + Ser: serde::Serializer, { - gen_serialize!(L, self.node, self.resolver, serializer,) + gen_serialize!(S, self.node, self.resolver, serializer,) } } -impl Serialize for ResolvedNode +impl Serialize for ResolvedNode where - L: Language, + S: Syntax, D: Serialize, { - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: Ser) -> Result where - S: serde::Serializer, + Ser: serde::Serializer, { let node = SerializeWithResolver { node: self, @@ -143,9 +143,9 @@ where } } -impl<'de, L, D> Deserialize<'de> for ResolvedNode +impl<'de, S, D> Deserialize<'de> for ResolvedNode where - L: Language, + S: Syntax, D: Deserialize<'de>, { // Deserialization is done by walking down the deserialized event stream, @@ -158,20 +158,20 @@ where // we walk down the nodes, check if the bool at `data_list[idx]` is true, // and if so, pop the first element of the data list and attach the data // to the current node. - fn deserialize(deserializer: DE) -> Result + fn deserialize(deserializer: De) -> Result where - DE: serde::Deserializer<'de>, + De: serde::Deserializer<'de>, { - struct EventVisitor { - _marker: PhantomData ResolvedNode>, + struct EventVisitor { + _marker: PhantomData ResolvedNode>, } - impl<'de, L, D> Visitor<'de> for EventVisitor + impl<'de, S, D> Visitor<'de> for EventVisitor where - L: Language, + S: Syntax, D: Deserialize<'de>, { - type Value = (ResolvedNode, VecDeque); + type Value = (ResolvedNode, VecDeque); fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a list of tree events") @@ -181,16 +181,16 @@ where where A: SeqAccess<'de>, { - let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); + let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); let mut data_indices = VecDeque::new(); while let Some(next) = seq.next_element::>()? { match next { Event::EnterNode(kind, has_data) => { - builder.start_node(L::kind_from_raw(kind)); + builder.start_node(S::from_raw(kind)); data_indices.push_back(has_data); } - Event::Token(kind, text) => builder.token(L::kind_from_raw(kind), text), + Event::Token(kind, text) => builder.token(S::from_raw(kind), text), Event::LeaveNode => builder.finish_node(), } } @@ -201,10 +201,10 @@ where } } - struct ProcessedEvents(ResolvedNode, VecDeque); - impl<'de, L, D> Deserialize<'de> for ProcessedEvents + struct ProcessedEvents(ResolvedNode, VecDeque); + impl<'de, S, D> Deserialize<'de> for ProcessedEvents where - L: Language, + S: Syntax, D: Deserialize<'de>, { fn deserialize(deserializer: DE) -> Result @@ -217,20 +217,20 @@ where } let (ProcessedEvents(tree, data_indices), mut data) = - <(ProcessedEvents, VecDeque)>::deserialize(deserializer)?; + <(ProcessedEvents, VecDeque)>::deserialize(deserializer)?; tree.descendants().zip(data_indices).try_for_each(|(node, has_data)| { if has_data { let data = data .pop_front() - .ok_or_else(|| DE::Error::custom("invalid serialized tree"))?; + .ok_or_else(|| De::Error::custom("invalid serialized tree"))?; node.set_data(data); } - >::Ok(()) + >::Ok(()) })?; if !data.is_empty() { - Err(DE::Error::custom( + Err(De::Error::custom( "serialized SyntaxNode contained too many data elements", )) } else { @@ -240,18 +240,18 @@ where } impl Serialize for RawSyntaxKind { - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: Ser) -> Result where - S: serde::Serializer, + Ser: serde::Serializer, { serializer.serialize_u32(self.0) } } impl<'de> Deserialize<'de> for RawSyntaxKind { - fn deserialize(deserializer: D) -> Result + fn deserialize(deserializer: De) -> Result where - D: serde::Deserializer<'de>, + De: serde::Deserializer<'de>, { Ok(Self(u32::deserialize(deserializer)?)) } diff --git a/src/syntax/element.rs b/cstree/src/syntax/element.rs similarity index 86% rename from src/syntax/element.rs rename to cstree/src/syntax/element.rs index e6d74f9..4602f65 100644 --- a/src/syntax/element.rs +++ b/cstree/src/syntax/element.rs @@ -7,25 +7,25 @@ use crate::{ green::GreenElementRef, interning::{Resolver, TokenKey}, util::{NodeOrToken, TokenAtOffset}, - Language, RawSyntaxKind, + RawSyntaxKind, Syntax, }; /// An element of the tree, can be either a node or a token. -pub type SyntaxElement = NodeOrToken, SyntaxToken>; +pub type SyntaxElement = NodeOrToken, SyntaxToken>; -impl From> for SyntaxElement { - fn from(node: SyntaxNode) -> SyntaxElement { +impl From> for SyntaxElement { + fn from(node: SyntaxNode) -> SyntaxElement { NodeOrToken::Node(node) } } -impl From> for SyntaxElement { - fn from(token: SyntaxToken) -> SyntaxElement { +impl From> for SyntaxElement { + fn from(token: SyntaxToken) -> SyntaxElement { NodeOrToken::Token(token) } } -impl SyntaxElement { +impl SyntaxElement { /// Returns this element's [`Display`](fmt::Display) representation as a string. /// /// To avoid allocating for every element, see [`write_display`](type.SyntaxElement.html#method.write_display). @@ -80,22 +80,22 @@ impl SyntaxElement { } /// A reference to an element of the tree, can be either a reference to a node or one to a token. -pub type SyntaxElementRef<'a, L, D = ()> = NodeOrToken<&'a SyntaxNode, &'a SyntaxToken>; +pub type SyntaxElementRef<'a, S, D = ()> = NodeOrToken<&'a SyntaxNode, &'a SyntaxToken>; -impl<'a, L: Language, D> From<&'a SyntaxNode> for SyntaxElementRef<'a, L, D> { - fn from(node: &'a SyntaxNode) -> Self { +impl<'a, S: Syntax, D> From<&'a SyntaxNode> for SyntaxElementRef<'a, S, D> { + fn from(node: &'a SyntaxNode) -> Self { NodeOrToken::Node(node) } } -impl<'a, L: Language, D> From<&'a SyntaxToken> for SyntaxElementRef<'a, L, D> { - fn from(token: &'a SyntaxToken) -> Self { +impl<'a, S: Syntax, D> From<&'a SyntaxToken> for SyntaxElementRef<'a, S, D> { + fn from(token: &'a SyntaxToken) -> Self { NodeOrToken::Token(token) } } -impl<'a, L: Language, D> From<&'a SyntaxElement> for SyntaxElementRef<'a, L, D> { - fn from(element: &'a SyntaxElement) -> Self { +impl<'a, S: Syntax, D> From<&'a SyntaxElement> for SyntaxElementRef<'a, S, D> { + fn from(element: &'a SyntaxElement) -> Self { match element { NodeOrToken::Node(it) => Self::Node(it), NodeOrToken::Token(it) => Self::Token(it), @@ -103,7 +103,7 @@ impl<'a, L: Language, D> From<&'a SyntaxElement> for SyntaxElementRef<'a, } } -impl<'a, L: Language, D> SyntaxElementRef<'a, L, D> { +impl<'a, S: Syntax, D> SyntaxElementRef<'a, S, D> { /// Returns this element's [`Display`](fmt::Display) representation as a string. /// /// To avoid allocating for every element, see [`write_display`](type.SyntaxElementRef.html#method.write_display). @@ -157,14 +157,14 @@ impl<'a, L: Language, D> SyntaxElementRef<'a, L, D> { } } -impl SyntaxElement { +impl SyntaxElement { pub(super) fn new( element: GreenElementRef<'_>, - parent: &SyntaxNode, + parent: &SyntaxNode, index: u32, offset: TextSize, ref_count: *mut AtomicU32, - ) -> SyntaxElement { + ) -> SyntaxElement { match element { NodeOrToken::Node(node) => SyntaxNode::new_child(node, parent, index, offset, ref_count).into(), NodeOrToken::Token(_) => SyntaxToken::new(parent, index, offset).into(), @@ -191,7 +191,7 @@ impl SyntaxElement { /// The kind of this element in terms of your language. #[inline] - pub fn kind(&self) -> L::Kind { + pub fn kind(&self) -> S { match self { NodeOrToken::Node(it) => it.kind(), NodeOrToken::Token(it) => it.kind(), @@ -200,7 +200,7 @@ impl SyntaxElement { /// The parent node of this element, except if this element is the root. #[inline] - pub fn parent(&self) -> Option<&SyntaxNode> { + pub fn parent(&self) -> Option<&SyntaxNode> { match self { NodeOrToken::Node(it) => it.parent(), NodeOrToken::Token(it) => Some(it.parent()), @@ -209,7 +209,7 @@ impl SyntaxElement { /// Returns an iterator along the chain of parents of this node. #[inline] - pub fn ancestors(&self) -> impl Iterator> { + pub fn ancestors(&self) -> impl Iterator> { match self { NodeOrToken::Node(it) => it.ancestors(), NodeOrToken::Token(it) => it.parent().ancestors(), @@ -218,7 +218,7 @@ impl SyntaxElement { /// Return the leftmost token in the subtree of this element. #[inline] - pub fn first_token(&self) -> Option<&SyntaxToken> { + pub fn first_token(&self) -> Option<&SyntaxToken> { match self { NodeOrToken::Node(it) => it.first_token(), NodeOrToken::Token(it) => Some(it), @@ -227,7 +227,7 @@ impl SyntaxElement { /// Return the rightmost token in the subtree of this element. #[inline] - pub fn last_token(&self) -> Option<&SyntaxToken> { + pub fn last_token(&self) -> Option<&SyntaxToken> { match self { NodeOrToken::Node(it) => it.last_token(), NodeOrToken::Token(it) => Some(it), @@ -236,7 +236,7 @@ impl SyntaxElement { /// The tree element to the right of this one, i.e. the next child of this element's parent after this element. #[inline] - pub fn next_sibling_or_token(&self) -> Option> { + pub fn next_sibling_or_token(&self) -> Option> { match self { NodeOrToken::Node(it) => it.next_sibling_or_token(), NodeOrToken::Token(it) => it.next_sibling_or_token(), @@ -245,7 +245,7 @@ impl SyntaxElement { /// The tree element to the left of this one, i.e. the previous child of this element's parent after this element. #[inline] - pub fn prev_sibling_or_token(&self) -> Option> { + pub fn prev_sibling_or_token(&self) -> Option> { match self { NodeOrToken::Node(it) => it.prev_sibling_or_token(), NodeOrToken::Token(it) => it.prev_sibling_or_token(), @@ -253,7 +253,7 @@ impl SyntaxElement { } } -impl<'a, L: Language, D> SyntaxElementRef<'a, L, D> { +impl<'a, S: Syntax, D> SyntaxElementRef<'a, S, D> { /// The range this element covers in the source text, in bytes. #[inline] pub fn text_range(&self) -> TextRange { @@ -274,7 +274,7 @@ impl<'a, L: Language, D> SyntaxElementRef<'a, L, D> { /// The kind of this element in terms of your language. #[inline] - pub fn kind(&self) -> L::Kind { + pub fn kind(&self) -> S { match self { NodeOrToken::Node(it) => it.kind(), NodeOrToken::Token(it) => it.kind(), @@ -283,7 +283,7 @@ impl<'a, L: Language, D> SyntaxElementRef<'a, L, D> { /// The parent node of this element, except if this element is the root. #[inline] - pub fn parent(&self) -> Option<&'a SyntaxNode> { + pub fn parent(&self) -> Option<&'a SyntaxNode> { match self { NodeOrToken::Node(it) => it.parent(), NodeOrToken::Token(it) => Some(it.parent()), @@ -292,7 +292,7 @@ impl<'a, L: Language, D> SyntaxElementRef<'a, L, D> { /// Returns an iterator along the chain of parents of this node. #[inline] - pub fn ancestors(&self) -> impl Iterator> { + pub fn ancestors(&self) -> impl Iterator> { match self { NodeOrToken::Node(it) => it.ancestors(), NodeOrToken::Token(it) => it.parent().ancestors(), @@ -301,7 +301,7 @@ impl<'a, L: Language, D> SyntaxElementRef<'a, L, D> { /// Return the leftmost token in the subtree of this element. #[inline] - pub fn first_token(&self) -> Option<&'a SyntaxToken> { + pub fn first_token(&self) -> Option<&'a SyntaxToken> { match self { NodeOrToken::Node(it) => it.first_token(), NodeOrToken::Token(it) => Some(it), @@ -310,7 +310,7 @@ impl<'a, L: Language, D> SyntaxElementRef<'a, L, D> { /// Return the rightmost token in the subtree of this element. #[inline] - pub fn last_token(&self) -> Option<&'a SyntaxToken> { + pub fn last_token(&self) -> Option<&'a SyntaxToken> { match self { NodeOrToken::Node(it) => it.last_token(), NodeOrToken::Token(it) => Some(it), @@ -319,7 +319,7 @@ impl<'a, L: Language, D> SyntaxElementRef<'a, L, D> { /// The tree element to the right of this one, i.e. the next child of this element's parent after this element. #[inline] - pub fn next_sibling_or_token(&self) -> Option> { + pub fn next_sibling_or_token(&self) -> Option> { match self { NodeOrToken::Node(it) => it.next_sibling_or_token(), NodeOrToken::Token(it) => it.next_sibling_or_token(), @@ -328,7 +328,7 @@ impl<'a, L: Language, D> SyntaxElementRef<'a, L, D> { /// The tree element to the left of this one, i.e. the previous child of this element's parent after this element. #[inline] - pub fn prev_sibling_or_token(&self) -> Option> { + pub fn prev_sibling_or_token(&self) -> Option> { match self { NodeOrToken::Node(it) => it.prev_sibling_or_token(), NodeOrToken::Token(it) => it.prev_sibling_or_token(), @@ -336,7 +336,7 @@ impl<'a, L: Language, D> SyntaxElementRef<'a, L, D> { } #[inline] - pub(super) fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset> { + pub(super) fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset> { assert!(self.text_range().start() <= offset && offset <= self.text_range().end()); match self { NodeOrToken::Token(token) => TokenAtOffset::Single((*token).clone()), diff --git a/src/syntax/iter.rs b/cstree/src/syntax/iter.rs similarity index 73% rename from src/syntax/iter.rs rename to cstree/src/syntax/iter.rs index cb711e9..d742582 100644 --- a/src/syntax/iter.rs +++ b/cstree/src/syntax/iter.rs @@ -7,7 +7,7 @@ use text_size::TextSize; use crate::{ green::{GreenElementRef, GreenNodeChildren}, syntax::{SyntaxElementRef, SyntaxNode}, - Language, + Syntax, }; #[derive(Clone, Debug)] @@ -18,7 +18,7 @@ struct Iter<'n> { } impl<'n> Iter<'n> { - fn new(parent: &'n SyntaxNode) -> Self { + fn new(parent: &'n SyntaxNode) -> Self { let offset = parent.text_range().start(); let green: GreenNodeChildren<'_> = parent.green().children(); Iter { @@ -67,14 +67,14 @@ impl<'n> FusedIterator for Iter<'n> {} /// An iterator over the child nodes of a [`SyntaxNode`]. #[derive(Clone, Debug)] -pub struct SyntaxNodeChildren<'n, L: Language, D: 'static = ()> { +pub struct SyntaxNodeChildren<'n, S: Syntax, D: 'static = ()> { inner: Iter<'n>, - parent: &'n SyntaxNode, + parent: &'n SyntaxNode, } -impl<'n, L: Language, D> SyntaxNodeChildren<'n, L, D> { +impl<'n, S: Syntax, D> SyntaxNodeChildren<'n, S, D> { #[inline] - pub(super) fn new(parent: &'n SyntaxNode) -> Self { + pub(super) fn new(parent: &'n SyntaxNode) -> Self { Self { inner: Iter::new(parent), parent, @@ -82,8 +82,8 @@ impl<'n, L: Language, D> SyntaxNodeChildren<'n, L, D> { } } -impl<'n, L: Language, D> Iterator for SyntaxNodeChildren<'n, L, D> { - type Item = &'n SyntaxNode; +impl<'n, S: Syntax, D> Iterator for SyntaxNodeChildren<'n, S, D> { + type Item = &'n SyntaxNode; #[inline(always)] fn next(&mut self) -> Option { @@ -109,24 +109,24 @@ impl<'n, L: Language, D> Iterator for SyntaxNodeChildren<'n, L, D> { } } -impl<'n, L: Language, D> ExactSizeIterator for SyntaxNodeChildren<'n, L, D> { +impl<'n, S: Syntax, D> ExactSizeIterator for SyntaxNodeChildren<'n, S, D> { #[inline(always)] fn len(&self) -> usize { self.inner.len() } } -impl<'n, L: Language, D> FusedIterator for SyntaxNodeChildren<'n, L, D> {} +impl<'n, S: Syntax, D> FusedIterator for SyntaxNodeChildren<'n, S, D> {} /// An iterator over the children of a [`SyntaxNode`]. #[derive(Clone, Debug)] -pub struct SyntaxElementChildren<'n, L: Language, D: 'static = ()> { +pub struct SyntaxElementChildren<'n, S: Syntax, D: 'static = ()> { inner: Iter<'n>, - parent: &'n SyntaxNode, + parent: &'n SyntaxNode, } -impl<'n, L: Language, D> SyntaxElementChildren<'n, L, D> { +impl<'n, S: Syntax, D> SyntaxElementChildren<'n, S, D> { #[inline] - pub(super) fn new(parent: &'n SyntaxNode) -> Self { + pub(super) fn new(parent: &'n SyntaxNode) -> Self { Self { inner: Iter::new(parent), parent, @@ -134,8 +134,8 @@ impl<'n, L: Language, D> SyntaxElementChildren<'n, L, D> { } } -impl<'n, L: Language, D> Iterator for SyntaxElementChildren<'n, L, D> { - type Item = SyntaxElementRef<'n, L, D>; +impl<'n, S: Syntax, D> Iterator for SyntaxElementChildren<'n, S, D> { + type Item = SyntaxElementRef<'n, S, D>; #[inline(always)] fn next(&mut self) -> Option { @@ -159,10 +159,10 @@ impl<'n, L: Language, D> Iterator for SyntaxElementChildren<'n, L, D> { } } -impl<'n, L: Language, D> ExactSizeIterator for SyntaxElementChildren<'n, L, D> { +impl<'n, S: Syntax, D> ExactSizeIterator for SyntaxElementChildren<'n, S, D> { #[inline(always)] fn len(&self) -> usize { self.inner.len() } } -impl<'n, L: Language, D> FusedIterator for SyntaxElementChildren<'n, L, D> {} +impl<'n, S: Syntax, D> FusedIterator for SyntaxElementChildren<'n, S, D> {} diff --git a/src/syntax/mod.rs b/cstree/src/syntax/mod.rs similarity index 67% rename from src/syntax/mod.rs rename to cstree/src/syntax/mod.rs index e275c04..0607523 100644 --- a/src/syntax/mod.rs +++ b/cstree/src/syntax/mod.rs @@ -22,10 +22,10 @@ pub use text::SyntaxText; // A note on `#[inline]` usage in this module: // In `rowan`, there are two layers of `SyntaxXY`s: the `cursor` layer and the `api` layer. // The `cursor` layer handles all of the actual methods on the tree, while the `api` layer is -// generic over the `Language` of the tree and otherwise forwards its implementation to the `cursor` +// generic over the `Syntax` of the tree and otherwise forwards its implementation to the `cursor` // layer. // Here, we have unified the `cursor` and the `api` layer into the `syntax` layer. -// This means that all of our types here are generic over a `Language`, including the +// This means that all of our types here are generic over a `Syntax`, including the // implementations which, in `rowan`, are part of the `cursor` layer. // Very apparently, this makes the compiler less willing to inline. Almost every "regular use" // method in this file has some kind of `#[inline]` annotation to counteract that. This is _NOT_ @@ -43,15 +43,15 @@ mod tests { #[cfg_attr(miri, ignore)] fn assert_send_sync() { fn f() {} - f::>(); - f::>(); - f::>(); - f::>(); + f::>(); + f::>(); + f::>(); + f::>(); - f::>(); - f::>(); - f::>(); - f::>(); + f::>(); + f::>(); + f::>(); + f::>(); } #[test] @@ -60,10 +60,10 @@ mod tests { fn assert_syntax_sizes() { use std::mem::size_of; - assert_eq!(size_of::>(), size_of::<*const u8>()); + assert_eq!(size_of::>(), size_of::<*const u8>()); // verify niche opt of `NonNull` - assert_eq!(size_of::>>(), size_of::<*const u8>()); + assert_eq!(size_of::>>(), size_of::<*const u8>()); // parent + child index + text len - assert_eq!(size_of::>(), size_of::>() + size_of::() * 2); + assert_eq!(size_of::>(), size_of::>() + size_of::() * 2); } } diff --git a/src/syntax/node.rs b/cstree/src/syntax/node.rs similarity index 91% rename from src/syntax/node.rs rename to cstree/src/syntax/node.rs index b103761..660e4b2 100644 --- a/src/syntax/node.rs +++ b/cstree/src/syntax/node.rs @@ -7,7 +7,7 @@ use crate::{ text::*, traversal::*, util::*, - Language, RawSyntaxKind, + RawSyntaxKind, Syntax, }; use parking_lot::RwLock; use std::{ @@ -29,14 +29,14 @@ use triomphe::Arc; /// individual nodes is relatively cheap. #[derive(Debug)] #[repr(transparent)] -pub struct SyntaxNode { - data: NonNull>, +pub struct SyntaxNode { + data: NonNull>, } -unsafe impl Send for SyntaxNode {} -unsafe impl Sync for SyntaxNode {} +unsafe impl Send for SyntaxNode {} +unsafe impl Sync for SyntaxNode {} -impl SyntaxNode { +impl SyntaxNode { /// Writes this node's [`Debug`](fmt::Debug) representation into the given `target`. /// If `recursive` is `true`, prints the entire subtree rooted in this node. /// Otherwise, only this node's kind and range are written. @@ -120,7 +120,7 @@ impl SyntaxNode { /// Turns this node into a [`ResolvedNode`](crate::syntax::ResolvedNode), but only if there is a resolver associated /// with this tree. #[inline] - pub fn try_resolved(&self) -> Option<&ResolvedNode> { + pub fn try_resolved(&self) -> Option<&ResolvedNode> { // safety: we only coerce if `resolver` exists self.resolver().map(|_| unsafe { ResolvedNode::coerce_ref(self) }) } @@ -129,12 +129,12 @@ impl SyntaxNode { /// # Panics /// If there is no resolver associated with this tree. #[inline] - pub fn resolved(&self) -> &ResolvedNode { + pub fn resolved(&self) -> &ResolvedNode { self.try_resolved().expect("tried to resolve a node without resolver") } } -impl Clone for SyntaxNode { +impl Clone for SyntaxNode { fn clone(&self) -> Self { // safety:: the ref count is only dropped when there are no more external references (see below) // since we are currently cloning such a reference, there is still at least one @@ -144,7 +144,7 @@ impl Clone for SyntaxNode { } } -impl Drop for SyntaxNode { +impl Drop for SyntaxNode { fn drop(&mut self) { // safety:: the ref count is only dropped when there are no more external references (see below) // and all nodes but the root have been dropped. @@ -169,9 +169,9 @@ impl Drop for SyntaxNode { } } -impl SyntaxNode { +impl SyntaxNode { #[inline] - fn data(&self) -> &NodeData { + fn data(&self) -> &NodeData { unsafe { self.data.as_ref() } } @@ -183,7 +183,7 @@ impl SyntaxNode { /// The root of the tree this node belongs to. /// /// If this node is the root, returns `self`. - pub fn root(&self) -> &SyntaxNode { + pub fn root(&self) -> &SyntaxNode { let mut current = self; while let Some(parent) = current.parent() { current = parent; @@ -222,31 +222,31 @@ impl SyntaxNode { } // Identity semantics for hash & eq -impl PartialEq for SyntaxNode { - fn eq(&self, other: &SyntaxNode) -> bool { +impl PartialEq for SyntaxNode { + fn eq(&self, other: &SyntaxNode) -> bool { self.data == other.data } } -impl Eq for SyntaxNode {} +impl Eq for SyntaxNode {} -impl Hash for SyntaxNode { +impl Hash for SyntaxNode { fn hash(&self, state: &mut H) { self.data.hash(state); } } -enum Kind { +enum Kind { Root(GreenNode, Option>>), Child { - parent: SyntaxNode, + parent: SyntaxNode, index: u32, offset: TextSize, }, } -impl Kind { - fn as_child(&self) -> Option<(&SyntaxNode, u32, TextSize)> { +impl Kind { + fn as_child(&self) -> Option<(&SyntaxNode, u32, TextSize)> { match self { Kind::Child { parent, index, offset } => Some((parent, *index, *offset)), _ => None, @@ -254,17 +254,17 @@ impl Kind { } } -pub(super) struct NodeData { - kind: Kind, +pub(super) struct NodeData { + kind: Kind, green: NonNull, ref_count: *mut AtomicU32, data: RwLock>>, - children: Vec>>>, + children: Vec>>>, child_locks: Vec>, } -impl NodeData { - fn new(kind: Kind, green: NonNull, ref_count: *mut AtomicU32, n_children: usize) -> NonNull { +impl NodeData { + fn new(kind: Kind, green: NonNull, ref_count: *mut AtomicU32, n_children: usize) -> NonNull { let mut children = Vec::with_capacity(n_children); let mut child_locks = Vec::with_capacity(n_children); children.extend((0..n_children).map(|_| Default::default())); @@ -282,17 +282,17 @@ impl NodeData { } } -impl SyntaxNode { +impl SyntaxNode { /// Build a new syntax tree on top of a green tree. /// /// # Example /// ``` /// # use cstree::testing::*; - /// # let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); + /// # let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); /// # builder.start_node(Root); /// # builder.finish_node(); /// # let (green_root, _) = builder.finish(); - /// let root: SyntaxNode = SyntaxNode::new_root(green_root); + /// let root: SyntaxNode = SyntaxNode::new_root(green_root); /// assert_eq!(root.kind(), Root); /// ``` #[inline] @@ -300,7 +300,7 @@ impl SyntaxNode { Self::make_new_root(green, None) } - fn new(data: NonNull>) -> Self { + fn new(data: NonNull>) -> Self { Self { data } } @@ -334,7 +334,7 @@ impl SyntaxNode { /// # use cstree::testing::*; /// use cstree::syntax::ResolvedNode; /// - /// let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); + /// let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); /// builder.start_node(Root); /// builder.token(Identifier, "content"); /// builder.finish_node(); @@ -344,11 +344,11 @@ impl SyntaxNode { /// // This created a new interner and cache for us owned by the builder, /// // and `finish` always returns these. /// let interner = cache.unwrap().into_interner().unwrap(); - /// let root: ResolvedNode = SyntaxNode::new_root_with_resolver(green, interner); + /// let root: ResolvedNode = SyntaxNode::new_root_with_resolver(green, interner); /// assert_eq!(root.text(), "content"); /// ``` #[inline] - pub fn new_root_with_resolver(green: GreenNode, resolver: impl Resolver + 'static) -> ResolvedNode { + pub fn new_root_with_resolver(green: GreenNode, resolver: impl Resolver + 'static) -> ResolvedNode { let ptr: StdArc> = StdArc::new(resolver); ResolvedNode { syntax: SyntaxNode::make_new_root(green, Some(ptr)), @@ -412,7 +412,7 @@ impl SyntaxNode { } #[inline] - fn read(&self, index: usize) -> Option> { + fn read(&self, index: usize) -> Option> { // safety: children are pre-allocated and indices are determined internally let _read = unsafe { self.data().child_locks.get_unchecked(index).read() }; // safety: mutable accesses to the slot only occur below and have to take the lock @@ -420,7 +420,7 @@ impl SyntaxNode { slot.as_ref().map(|elem| elem.into()) } - fn try_write(&self, index: usize, elem: SyntaxElement) { + fn try_write(&self, index: usize, elem: SyntaxElement) { // safety: children are pre-allocated and indices are determined internally let _write = unsafe { self.data().child_locks.get_unchecked(index).write() }; // safety: we are the only writer and there are no readers as evidenced by the write lock @@ -469,7 +469,7 @@ impl SyntaxNode { node: &GreenNode, index: usize, offset: TextSize, - ) -> SyntaxElementRef<'_, L, D> { + ) -> SyntaxElementRef<'_, S, D> { if let Some(elem) = self.read(index) { debug_assert_eq!(elem.text_range().start(), offset); return elem; @@ -487,7 +487,7 @@ impl SyntaxNode { element: GreenElementRef<'_>, index: usize, offset: TextSize, - ) -> SyntaxElementRef<'_, L, D> { + ) -> SyntaxElementRef<'_, S, D> { if let Some(elem) = self.read(index) { debug_assert_eq!(elem.text_range().start(), offset); return elem; @@ -529,8 +529,8 @@ impl SyntaxNode { /// The kind of this node in terms of your language. #[inline] - pub fn kind(&self) -> L::Kind { - L::kind_from_raw(self.syntax_kind()) + pub fn kind(&self) -> S { + S::from_raw(self.syntax_kind()) } /// The range this node covers in the source text, in bytes. @@ -547,7 +547,7 @@ impl SyntaxNode { /// by this node, i.e. the combined text of all token leafs of the subtree originating in this /// node. #[inline] - pub fn resolve_text<'n, 'i, I>(&'n self, resolver: &'i I) -> SyntaxText<'n, 'i, I, L, D> + pub fn resolve_text<'n, 'i, I>(&'n self, resolver: &'i I) -> SyntaxText<'n, 'i, I, S, D> where I: Resolver + ?Sized, { @@ -562,7 +562,7 @@ impl SyntaxNode { /// The parent node of this node, except if this node is the root. #[inline] - pub fn parent(&self) -> Option<&SyntaxNode> { + pub fn parent(&self) -> Option<&SyntaxNode> { match &self.data().kind { Kind::Root(_, _) => None, Kind::Child { parent, .. } => Some(parent), @@ -585,7 +585,7 @@ impl SyntaxNode { /// Returns an iterator along the chain of parents of this node. #[inline] - pub fn ancestors(&self) -> impl Iterator> { + pub fn ancestors(&self) -> impl Iterator> { iter::successors(Some(self), |&node| node.parent()) } @@ -593,13 +593,13 @@ impl SyntaxNode { /// /// If you want to also consider leafs, see [`children_with_tokens`](SyntaxNode::children_with_tokens). #[inline] - pub fn children(&self) -> SyntaxNodeChildren<'_, L, D> { + pub fn children(&self) -> SyntaxNodeChildren<'_, S, D> { SyntaxNodeChildren::new(self) } /// Returns an iterator over child elements of this node, including tokens. #[inline] - pub fn children_with_tokens(&self) -> SyntaxElementChildren<'_, L, D> { + pub fn children_with_tokens(&self) -> SyntaxElementChildren<'_, S, D> { SyntaxElementChildren::new(self) } @@ -608,14 +608,14 @@ impl SyntaxNode { /// If you want to also consider leafs, see [`first_child_or_token`](SyntaxNode::first_child_or_token). #[inline] #[allow(clippy::map_clone)] - pub fn first_child(&self) -> Option<&SyntaxNode> { + pub fn first_child(&self) -> Option<&SyntaxNode> { let (node, (index, offset)) = filter_nodes(self.green().children_from(0, self.text_range().start())).next()?; self.get_or_add_node(node, index, offset).as_node().map(|node| *node) } /// The first child element of this node, if any, including tokens. #[inline] - pub fn first_child_or_token(&self) -> Option> { + pub fn first_child_or_token(&self) -> Option> { let (element, (index, offset)) = self.green().children_from(0, self.text_range().start()).next()?; Some(self.get_or_add_element(element, index, offset)) } @@ -625,7 +625,7 @@ impl SyntaxNode { /// If you want to also consider leafs, see [`last_child_or_token`](SyntaxNode::last_child_or_token). #[inline] #[allow(clippy::map_clone)] - pub fn last_child(&self) -> Option<&SyntaxNode> { + pub fn last_child(&self) -> Option<&SyntaxNode> { let (node, (index, offset)) = filter_nodes( self.green() .children_to(self.green().children().len(), self.text_range().end()), @@ -636,7 +636,7 @@ impl SyntaxNode { /// The last child element of this node, if any, including tokens. #[inline] - pub fn last_child_or_token(&self) -> Option> { + pub fn last_child_or_token(&self) -> Option> { let (element, (index, offset)) = self .green() .children_to(self.green().children().len(), self.text_range().end()) @@ -650,7 +650,7 @@ impl SyntaxNode { /// /// If you want to also consider leafs, see [`next_child_or_token_after`](SyntaxNode::next_child_or_token_after). #[inline] - pub fn next_child_after(&self, n: usize, offset: TextSize) -> Option<&SyntaxNode> { + pub fn next_child_after(&self, n: usize, offset: TextSize) -> Option<&SyntaxNode> { let (node, (index, offset)) = filter_nodes(self.green().children_from(n + 1, offset)).next()?; self.get_or_add_node(node, index, offset).as_node().copied() } @@ -658,7 +658,7 @@ impl SyntaxNode { /// The first child element of this node starting at the (n + 1)-st, if any. /// If this method returns `Some`, the contained node is the (n + 1)-st child of this node. #[inline] - pub fn next_child_or_token_after(&self, n: usize, offset: TextSize) -> Option> { + pub fn next_child_or_token_after(&self, n: usize, offset: TextSize) -> Option> { let (element, (index, offset)) = self.green().children_from(n + 1, offset).next()?; Some(self.get_or_add_element(element, index, offset)) } @@ -669,7 +669,7 @@ impl SyntaxNode { /// /// If you want to also consider leafs, see [`prev_child_or_token_before`](SyntaxNode::prev_child_or_token_before). #[inline] - pub fn prev_child_before(&self, n: usize, offset: TextSize) -> Option<&SyntaxNode> { + pub fn prev_child_before(&self, n: usize, offset: TextSize) -> Option<&SyntaxNode> { let (node, (index, offset)) = filter_nodes(self.green().children_to(n, offset)).next()?; self.get_or_add_node(node, index, offset).as_node().copied() } @@ -677,7 +677,7 @@ impl SyntaxNode { /// The last child node of this node up to the nth, if any. /// If this method returns `Some`, the contained node is the (n - 1)-st child. #[inline] - pub fn prev_child_or_token_before(&self, n: usize, offset: TextSize) -> Option> { + pub fn prev_child_or_token_before(&self, n: usize, offset: TextSize) -> Option> { let (element, (index, offset)) = self.green().children_to(n, offset).next()?; Some(self.get_or_add_element(element, index, offset)) } @@ -686,7 +686,7 @@ impl SyntaxNode { /// /// If you want to also consider leafs, see [`next_sibling_or_token`](SyntaxNode::next_sibling_or_token). #[inline] - pub fn next_sibling(&self) -> Option<&SyntaxNode> { + pub fn next_sibling(&self) -> Option<&SyntaxNode> { let (parent, index, _) = self.data().kind.as_child()?; let (node, (index, offset)) = filter_nodes( @@ -700,7 +700,7 @@ impl SyntaxNode { /// The tree element to the right of this one, i.e. the next child of this node's parent after this node. #[inline] - pub fn next_sibling_or_token(&self) -> Option> { + pub fn next_sibling_or_token(&self) -> Option> { let (parent, index, _) = self.data().kind.as_child()?; let (element, (index, offset)) = parent @@ -714,7 +714,7 @@ impl SyntaxNode { /// /// If you want to also consider leafs, see [`prev_sibling_or_token`](SyntaxNode::prev_sibling_or_token). #[inline] - pub fn prev_sibling(&self) -> Option<&SyntaxNode> { + pub fn prev_sibling(&self) -> Option<&SyntaxNode> { let (parent, index, _) = self.data().kind.as_child()?; let (node, (index, offset)) = @@ -724,7 +724,7 @@ impl SyntaxNode { /// The tree element to the left of this one, i.e. the previous child of this node's parent before this node. #[inline] - pub fn prev_sibling_or_token(&self) -> Option> { + pub fn prev_sibling_or_token(&self) -> Option> { let (parent, index, _) = self.data().kind.as_child()?; let (element, (index, offset)) = parent @@ -736,13 +736,13 @@ impl SyntaxNode { /// Return the leftmost token in the subtree of this node #[inline] - pub fn first_token(&self) -> Option<&SyntaxToken> { + pub fn first_token(&self) -> Option<&SyntaxToken> { self.first_child_or_token()?.first_token() } /// Return the rightmost token in the subtree of this node #[inline] - pub fn last_token(&self) -> Option<&SyntaxToken> { + pub fn last_token(&self) -> Option<&SyntaxToken> { self.last_child_or_token()?.last_token() } @@ -752,7 +752,7 @@ impl SyntaxNode { /// /// If you want to also consider leafs, see [`siblings_with_tokens`](SyntaxNode::siblings_with_tokens). #[inline] - pub fn siblings(&self, direction: Direction) -> impl Iterator> { + pub fn siblings(&self, direction: Direction) -> impl Iterator> { iter::successors(Some(self), move |node| match direction { Direction::Next => node.next_sibling(), Direction::Prev => node.prev_sibling(), @@ -763,8 +763,8 @@ impl SyntaxNode { /// node's parent's children from this node on to the left or the right. /// The first item in the iterator will always be this node. #[inline] - pub fn siblings_with_tokens(&self, direction: Direction) -> impl Iterator> { - let me: SyntaxElementRef<'_, L, D> = self.into(); + pub fn siblings_with_tokens(&self, direction: Direction) -> impl Iterator> { + let me: SyntaxElementRef<'_, S, D> = self.into(); iter::successors(Some(me), move |el| match direction { Direction::Next => el.next_sibling_or_token(), Direction::Prev => el.prev_sibling_or_token(), @@ -775,7 +775,7 @@ impl SyntaxNode { /// /// If you want to also consider leafs, see [`descendants_with_tokens`](SyntaxNode::descendants_with_tokens). #[inline] - pub fn descendants(&self) -> impl Iterator> { + pub fn descendants(&self) -> impl Iterator> { self.preorder().filter_map(|event| match event { WalkEvent::Enter(node) => Some(node), WalkEvent::Leave(_) => None, @@ -784,7 +784,7 @@ impl SyntaxNode { /// Returns an iterator over all elements in the subtree starting at this node, including this node. #[inline] - pub fn descendants_with_tokens(&self) -> impl Iterator> { + pub fn descendants_with_tokens(&self) -> impl Iterator> { self.preorder_with_tokens().filter_map(|event| match event { WalkEvent::Enter(it) => Some(it), WalkEvent::Leave(_) => None, @@ -794,7 +794,7 @@ impl SyntaxNode { /// Traverse the subtree rooted at the current node (including the current /// node) in preorder, excluding tokens. #[inline(always)] - pub fn preorder(&self) -> impl Iterator>> { + pub fn preorder(&self) -> impl Iterator>> { iter::successors(Some(WalkEvent::Enter(self)), move |pos| { let next = match pos { WalkEvent::Enter(node) => match node.first_child() { @@ -818,7 +818,7 @@ impl SyntaxNode { /// Traverse the subtree rooted at the current node (including the current /// node) in preorder, including tokens. #[inline(always)] - pub fn preorder_with_tokens(&self) -> impl Iterator>> { + pub fn preorder_with_tokens(&self) -> impl Iterator>> { let me = self.into(); iter::successors(Some(WalkEvent::Enter(me)), move |pos| { let next = match pos { @@ -845,7 +845,7 @@ impl SyntaxNode { /// Find a token in the subtree corresponding to this node, which covers the offset. /// Precondition: offset must be withing node's range. - pub fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset> { + pub fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset> { // TODO: this could be faster if we first drill-down to node, and only // then switch to token search. We should also replace explicit // recursion with a loop. @@ -883,8 +883,8 @@ impl SyntaxNode { /// contains the range. If the range is empty and is contained in two leaf /// nodes, either one can be returned. Precondition: range must be contained /// withing the current node - pub fn covering_element(&self, range: TextRange) -> SyntaxElementRef<'_, L, D> { - let mut res: SyntaxElementRef<'_, L, D> = self.into(); + pub fn covering_element(&self, range: TextRange) -> SyntaxElementRef<'_, S, D> { + let mut res: SyntaxElementRef<'_, S, D> = self.into(); loop { assert!( res.text_range().contains_range(range), @@ -909,9 +909,9 @@ impl SyntaxNode { } #[cfg(feature = "serialize")] -impl SyntaxNode +impl SyntaxNode where - L: Language, + S: Syntax, { /// Return an anonymous object that can be used to serialize this node, /// including the data and by using an external resolver. diff --git a/src/syntax/resolved.rs b/cstree/src/syntax/resolved.rs similarity index 82% rename from src/syntax/resolved.rs rename to cstree/src/syntax/resolved.rs index 6d80072..5ca6dc7 100644 --- a/src/syntax/resolved.rs +++ b/cstree/src/syntax/resolved.rs @@ -17,7 +17,7 @@ use crate::{ syntax::*, traversal::*, util::*, - Language, RawSyntaxKind, + RawSyntaxKind, Syntax, }; /// Syntax tree node that is guaranteed to belong to a tree that contains an associated @@ -26,24 +26,24 @@ use crate::{ /// [`SyntaxNode`] /// [`SyntaxNode::new_root_with_resolver`] #[repr(transparent)] -pub struct ResolvedNode { - pub(super) syntax: SyntaxNode, +pub struct ResolvedNode { + pub(super) syntax: SyntaxNode, } -impl ResolvedNode { +impl ResolvedNode { /// # Safety: /// `syntax` must belong to a tree that contains an associated inline resolver. - pub(super) unsafe fn coerce_ref(syntax: &SyntaxNode) -> &Self { + pub(super) unsafe fn coerce_ref(syntax: &SyntaxNode) -> &Self { &*(syntax as *const _ as *const Self) } /// Returns this node as a [`SyntaxNode`]. - pub fn syntax(&self) -> &SyntaxNode { + pub fn syntax(&self) -> &SyntaxNode { &self.syntax } } -impl Clone for ResolvedNode { +impl Clone for ResolvedNode { fn clone(&self) -> Self { Self { syntax: self.syntax.clone(), @@ -51,15 +51,15 @@ impl Clone for ResolvedNode { } } -impl Deref for ResolvedNode { - type Target = SyntaxNode; +impl Deref for ResolvedNode { + type Target = SyntaxNode; fn deref(&self) -> &Self::Target { &self.syntax } } -impl DerefMut for ResolvedNode { +impl DerefMut for ResolvedNode { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.syntax } @@ -70,24 +70,24 @@ impl DerefMut for ResolvedNode { /// # See also /// [`SyntaxToken`] #[repr(transparent)] -pub struct ResolvedToken { - syntax: SyntaxToken, +pub struct ResolvedToken { + syntax: SyntaxToken, } -impl ResolvedToken { +impl ResolvedToken { /// # Safety: /// `syntax` must belong to a tree that contains an associated inline resolver. - pub(super) unsafe fn coerce_ref(syntax: &SyntaxToken) -> &Self { + pub(super) unsafe fn coerce_ref(syntax: &SyntaxToken) -> &Self { &*(syntax as *const _ as *const Self) } /// Returns this token as a [`SyntaxToken`]. - pub fn syntax(&self) -> &SyntaxToken { + pub fn syntax(&self) -> &SyntaxToken { &self.syntax } } -impl Clone for ResolvedToken { +impl Clone for ResolvedToken { fn clone(&self) -> Self { Self { syntax: self.syntax.clone(), @@ -95,15 +95,15 @@ impl Clone for ResolvedToken { } } -impl Deref for ResolvedToken { - type Target = SyntaxToken; +impl Deref for ResolvedToken { + type Target = SyntaxToken; fn deref(&self) -> &Self::Target { &self.syntax } } -impl DerefMut for ResolvedToken { +impl DerefMut for ResolvedToken { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.syntax } @@ -113,21 +113,21 @@ impl DerefMut for ResolvedToken { /// [`Resolver`](lasso::Resolver), can be either a node or a token. /// # See also /// [`SyntaxElement`](crate::syntax::SyntaxElement) -pub type ResolvedElement = NodeOrToken, ResolvedToken>; +pub type ResolvedElement = NodeOrToken, ResolvedToken>; -impl From> for ResolvedElement { - fn from(node: ResolvedNode) -> ResolvedElement { +impl From> for ResolvedElement { + fn from(node: ResolvedNode) -> ResolvedElement { NodeOrToken::Node(node) } } -impl From> for ResolvedElement { - fn from(token: ResolvedToken) -> ResolvedElement { +impl From> for ResolvedElement { + fn from(token: ResolvedToken) -> ResolvedElement { NodeOrToken::Token(token) } } -impl ResolvedElement { +impl ResolvedElement { #[allow(missing_docs)] pub fn display(&self, resolver: &impl Resolver) -> String { match self { @@ -141,12 +141,12 @@ impl ResolvedElement { /// associated [`Resolver`](lasso::Resolver), can be either a reference to a node or one to a token. /// # See also /// [`SyntaxElementRef`] -pub type ResolvedElementRef<'a, L, D = ()> = NodeOrToken<&'a ResolvedNode, &'a ResolvedToken>; +pub type ResolvedElementRef<'a, S, D = ()> = NodeOrToken<&'a ResolvedNode, &'a ResolvedToken>; -impl<'a, L: Language, D> ResolvedElementRef<'a, L, D> { +impl<'a, S: Syntax, D> ResolvedElementRef<'a, S, D> { /// # Safety: /// `syntax` must belong to a tree that contains an associated inline resolver. - pub(super) unsafe fn coerce_ref(syntax: SyntaxElementRef<'a, L, D>) -> Self { + pub(super) unsafe fn coerce_ref(syntax: SyntaxElementRef<'a, S, D>) -> Self { match syntax { NodeOrToken::Node(node) => Self::Node(ResolvedNode::coerce_ref(node)), NodeOrToken::Token(token) => Self::Token(ResolvedToken::coerce_ref(token)), @@ -154,20 +154,20 @@ impl<'a, L: Language, D> ResolvedElementRef<'a, L, D> { } } -impl<'a, L: Language, D> From<&'a ResolvedNode> for ResolvedElementRef<'a, L, D> { - fn from(node: &'a ResolvedNode) -> Self { +impl<'a, S: Syntax, D> From<&'a ResolvedNode> for ResolvedElementRef<'a, S, D> { + fn from(node: &'a ResolvedNode) -> Self { NodeOrToken::Node(node) } } -impl<'a, L: Language, D> From<&'a ResolvedToken> for ResolvedElementRef<'a, L, D> { - fn from(token: &'a ResolvedToken) -> Self { +impl<'a, S: Syntax, D> From<&'a ResolvedToken> for ResolvedElementRef<'a, S, D> { + fn from(token: &'a ResolvedToken) -> Self { NodeOrToken::Token(token) } } -impl<'a, L: Language, D> From<&'a ResolvedElement> for ResolvedElementRef<'a, L, D> { - fn from(element: &'a ResolvedElement) -> Self { +impl<'a, S: Syntax, D> From<&'a ResolvedElement> for ResolvedElementRef<'a, S, D> { + fn from(element: &'a ResolvedElement) -> Self { match element { NodeOrToken::Node(it) => Self::Node(it), NodeOrToken::Token(it) => Self::Token(it), @@ -175,29 +175,29 @@ impl<'a, L: Language, D> From<&'a ResolvedElement> for ResolvedElementRef< } } -impl ResolvedNode { +impl ResolvedNode { /// Uses the resolver associated with this tree to return an efficient representation of all /// source text covered by this node, i.e. the combined text of all token leafs of the subtree /// originating in this node. #[inline] - pub fn text(&self) -> SyntaxText<'_, '_, dyn Resolver, L, D> { + pub fn text(&self) -> SyntaxText<'_, '_, dyn Resolver, S, D> { SyntaxText::new(self, &**self.resolver()) } } -impl fmt::Debug for ResolvedNode { +impl fmt::Debug for ResolvedNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.write_debug(&**self.resolver(), f, f.alternate()) } } -impl fmt::Display for ResolvedNode { +impl fmt::Display for ResolvedNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.write_display(&**self.resolver(), f) } } -impl ResolvedToken { +impl ResolvedToken { /// Uses the resolver associated with this tree to return the source text of this token. #[inline] pub fn text(&self) -> &str { @@ -208,22 +208,22 @@ impl ResolvedToken { } } -impl fmt::Debug for ResolvedToken { +impl fmt::Debug for ResolvedToken { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.write_debug(&**self.resolver(), f) } } -impl fmt::Display for ResolvedToken { +impl fmt::Display for ResolvedToken { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.write_display(&**self.resolver(), f) } } #[cfg(feature = "serialize")] -impl ResolvedNode +impl ResolvedNode where - L: Language, + S: Syntax, { /// Return an anonymous object that can be used to serialize this node, /// including the data for each node. @@ -267,7 +267,7 @@ macro_rules! forward_node { }; } -impl ResolvedNode { +impl ResolvedNode { /// Returns the [`Resolver`] associated with this tree. pub fn resolver(&self) -> &StdArc> { self.syntax.resolver().unwrap() @@ -283,7 +283,7 @@ impl ResolvedNode { /// /// This method mostly exists to allow the convenience of being agnostic over [`SyntaxNode`] vs [`ResolvedNode`]. #[inline] - pub fn try_resolved(&self) -> Option<&ResolvedNode> { + pub fn try_resolved(&self) -> Option<&ResolvedNode> { Some(self) } @@ -291,7 +291,7 @@ impl ResolvedNode { /// /// This method mostly exists to allow the convenience of being agnostic over [`SyntaxNode`] vs [`ResolvedNode`]. #[inline] - pub fn resolved(&self) -> &ResolvedNode { + pub fn resolved(&self) -> &ResolvedNode { self } @@ -299,7 +299,7 @@ impl ResolvedNode { /// /// If this node is the root, returns `self`. #[inline] - pub fn root(&self) -> &SyntaxNode { + pub fn root(&self) -> &SyntaxNode { unsafe { Self::coerce_ref(self.syntax.root()) } } @@ -325,7 +325,7 @@ impl ResolvedNode { /// Returns an iterator over child elements of this node, including tokens. #[inline] - pub fn children_with_tokens(&self) -> impl Iterator> { + pub fn children_with_tokens(&self) -> impl Iterator> { forward_as_elem!(self.syntax.children_with_tokens()) } @@ -333,13 +333,13 @@ impl ResolvedNode { /// /// If you want to also consider leafs, see [`first_child_or_token`](ResolvedNode::first_child_or_token). #[inline] - pub fn first_child(&self) -> Option<&ResolvedNode> { + pub fn first_child(&self) -> Option<&ResolvedNode> { forward!(self.syntax.first_child()) } /// The first child element of this node, if any, including tokens. #[inline] - pub fn first_child_or_token(&self) -> Option> { + pub fn first_child_or_token(&self) -> Option> { forward_as_elem!(self.syntax.first_child_or_token()) } @@ -347,13 +347,13 @@ impl ResolvedNode { /// /// If you want to also consider leafs, see [`last_child_or_token`](ResolvedNode::last_child_or_token). #[inline] - pub fn last_child(&self) -> Option<&ResolvedNode> { + pub fn last_child(&self) -> Option<&ResolvedNode> { forward!(self.syntax.last_child()) } /// The last child element of this node, if any, including tokens. #[inline] - pub fn last_child_or_token(&self) -> Option> { + pub fn last_child_or_token(&self) -> Option> { forward_as_elem!(self.syntax.last_child_or_token()) } @@ -363,14 +363,14 @@ impl ResolvedNode { /// /// If you want to also consider leafs, see [`next_child_or_token_after`](ResolvedNode::next_child_or_token_after). #[inline] - pub fn next_child_after(&self, n: usize, offset: TextSize) -> Option<&ResolvedNode> { + pub fn next_child_after(&self, n: usize, offset: TextSize) -> Option<&ResolvedNode> { forward!(self.syntax.next_child_after(n, offset)) } /// The first child element of this node starting at the (n + 1)-st, if any. /// If this method returns `Some`, the contained node is the (n + 1)-st child of this node. #[inline] - pub fn next_child_or_token_after(&self, n: usize, offset: TextSize) -> Option> { + pub fn next_child_or_token_after(&self, n: usize, offset: TextSize) -> Option> { forward_as_elem!(self.syntax.next_child_or_token_after(n, offset)) } @@ -381,14 +381,14 @@ impl ResolvedNode { /// If you want to also consider leafs, see /// [`prev_child_or_token_before`](ResolvedNode::prev_child_or_token_before). #[inline] - pub fn prev_child_before(&self, n: usize, offset: TextSize) -> Option<&ResolvedNode> { + pub fn prev_child_before(&self, n: usize, offset: TextSize) -> Option<&ResolvedNode> { forward!(self.syntax.prev_child_before(n, offset)) } /// The last child node of this node up to the nth, if any. /// If this method returns `Some`, the contained node is the (n - 1)-st child. #[inline] - pub fn prev_child_or_token_before(&self, n: usize, offset: TextSize) -> Option> { + pub fn prev_child_or_token_before(&self, n: usize, offset: TextSize) -> Option> { forward_as_elem!(self.syntax.prev_child_or_token_before(n, offset)) } @@ -396,13 +396,13 @@ impl ResolvedNode { /// /// If you want to also consider leafs, see [`next_sibling_or_token`](ResolvedNode::next_sibling_or_token). #[inline] - pub fn next_sibling(&self) -> Option<&ResolvedNode> { + pub fn next_sibling(&self) -> Option<&ResolvedNode> { forward!(self.syntax.next_sibling()) } /// The tree element to the right of this one, i.e. the next child of this node's parent after this node. #[inline] - pub fn next_sibling_or_token(&self) -> Option> { + pub fn next_sibling_or_token(&self) -> Option> { forward_as_elem!(self.syntax.next_sibling_or_token()) } @@ -410,25 +410,25 @@ impl ResolvedNode { /// /// If you want to also consider leafs, see [`prev_sibling_or_token`](ResolvedNode::prev_sibling_or_token). #[inline] - pub fn prev_sibling(&self) -> Option<&ResolvedNode> { + pub fn prev_sibling(&self) -> Option<&ResolvedNode> { forward!(self.syntax.prev_sibling()) } /// The tree element to the left of this one, i.e. the previous child of this node's parent before this node. #[inline] - pub fn prev_sibling_or_token(&self) -> Option> { + pub fn prev_sibling_or_token(&self) -> Option> { forward_as_elem!(self.syntax.prev_sibling_or_token()) } /// Return the leftmost token in the subtree of this node #[inline] - pub fn first_token(&self) -> Option<&ResolvedToken> { + pub fn first_token(&self) -> Option<&ResolvedToken> { forward_token!(self.syntax.first_token()) } /// Return the rightmost token in the subtree of this node #[inline] - pub fn last_token(&self) -> Option<&ResolvedToken> { + pub fn last_token(&self) -> Option<&ResolvedToken> { forward_token!(self.syntax.last_token()) } @@ -438,7 +438,7 @@ impl ResolvedNode { /// /// If you want to also consider leafs, see [`siblings_with_tokens`](ResolvedNode::siblings_with_tokens). #[inline] - pub fn siblings(&self, direction: Direction) -> impl Iterator> { + pub fn siblings(&self, direction: Direction) -> impl Iterator> { forward!(self.syntax.siblings(direction)) } @@ -446,7 +446,7 @@ impl ResolvedNode { /// node's parent's children from this node on to the left or the right. /// The first item in the iterator will always be this node. #[inline] - pub fn siblings_with_tokens(&self, direction: Direction) -> impl Iterator> { + pub fn siblings_with_tokens(&self, direction: Direction) -> impl Iterator> { forward_as_elem!(self.syntax.siblings_with_tokens(direction)) } @@ -454,20 +454,20 @@ impl ResolvedNode { /// /// If you want to also consider leafs, see [`descendants_with_tokens`](ResolvedNode::descendants_with_tokens). #[inline] - pub fn descendants(&self) -> impl Iterator> { + pub fn descendants(&self) -> impl Iterator> { forward!(self.syntax.descendants()) } /// Returns an iterator over all elements in the subtree starting at this node, including this node. #[inline] - pub fn descendants_with_tokens(&self) -> impl Iterator> { + pub fn descendants_with_tokens(&self) -> impl Iterator> { forward_as_elem!(self.syntax.descendants_with_tokens()) } /// Traverse the subtree rooted at the current node (including the current /// node) in preorder, excluding tokens. #[inline(always)] - pub fn preorder(&self) -> impl Iterator>> { + pub fn preorder(&self) -> impl Iterator>> { self.syntax .preorder() .map(|event| event.map(|node| unsafe { Self::coerce_ref(node) })) @@ -476,7 +476,7 @@ impl ResolvedNode { /// Traverse the subtree rooted at the current node (including the current /// node) in preorder, including tokens. #[inline(always)] - pub fn preorder_with_tokens(&self) -> impl Iterator>> { + pub fn preorder_with_tokens(&self) -> impl Iterator>> { self.syntax .preorder_with_tokens() .map(|event| event.map(|elem| unsafe { ResolvedElementRef::coerce_ref(elem) })) @@ -484,7 +484,7 @@ impl ResolvedNode { /// Find a token in the subtree corresponding to this node, which covers the offset. /// Precondition: offset must be withing node's range. - pub fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset> { + pub fn token_at_offset(&self, offset: TextSize) -> TokenAtOffset> { self.syntax .token_at_offset(offset) .map(|token| ResolvedToken { syntax: token }) @@ -494,12 +494,12 @@ impl ResolvedNode { /// contains the range. If the range is empty and is contained in two leaf /// nodes, either one can be returned. Precondition: range must be contained /// withing the current node - pub fn covering_element(&self, range: TextRange) -> ResolvedElementRef<'_, L, D> { + pub fn covering_element(&self, range: TextRange) -> ResolvedElementRef<'_, S, D> { unsafe { ResolvedElementRef::coerce_ref(self.syntax.covering_element(range)) } } } -impl ResolvedToken { +impl ResolvedToken { /// Returns the [`Resolver`] associated with this tree. pub fn resolver(&self) -> &StdArc> { self.syntax.resolver().unwrap() @@ -509,7 +509,7 @@ impl ResolvedToken { /// /// This method mostly exists to allow the convenience of being agnostic over [`SyntaxToken`] vs [`ResolvedToken`]. #[inline] - pub fn try_resolved(&self) -> Option<&ResolvedToken> { + pub fn try_resolved(&self) -> Option<&ResolvedToken> { Some(self) } @@ -517,31 +517,31 @@ impl ResolvedToken { /// /// This method mostly exists to allow the convenience of being agnostic over [`SyntaxToken`] vs [`ResolvedToken`]. #[inline] - pub fn resolved(&self) -> &ResolvedToken { + pub fn resolved(&self) -> &ResolvedToken { self } /// The parent node of this token. #[inline] - pub fn parent(&self) -> &ResolvedNode { + pub fn parent(&self) -> &ResolvedNode { unsafe { ResolvedNode::coerce_ref(self.syntax.parent()) } } /// Returns an iterator along the chain of parents of this token. #[inline] - pub fn ancestors(&self) -> impl Iterator> { + pub fn ancestors(&self) -> impl Iterator> { forward_node!(self.syntax.ancestors()) } /// The tree element to the right of this one, i.e. the next child of this token's parent after this token. #[inline] - pub fn next_sibling_or_token(&self) -> Option> { + pub fn next_sibling_or_token(&self) -> Option> { forward_as_elem!(self.syntax.next_sibling_or_token()) } /// The tree element to the left of this one, i.e. the previous child of this token's parent after this token. #[inline] - pub fn prev_sibling_or_token(&self) -> Option> { + pub fn prev_sibling_or_token(&self) -> Option> { forward_as_elem!(self.syntax.prev_sibling_or_token()) } @@ -549,24 +549,24 @@ impl ResolvedToken { /// token's parent's children from this token on to the left or the right. /// The first item in the iterator will always be this token. #[inline] - pub fn siblings_with_tokens(&self, direction: Direction) -> impl Iterator> { + pub fn siblings_with_tokens(&self, direction: Direction) -> impl Iterator> { forward_as_elem!(self.syntax.siblings_with_tokens(direction)) } /// Returns the next token in the tree. /// This is not necessary a direct sibling of this token, but will always be further right in the tree. - pub fn next_token(&self) -> Option<&ResolvedToken> { + pub fn next_token(&self) -> Option<&ResolvedToken> { forward!(self.syntax.next_token()) } /// Returns the previous token in the tree. /// This is not necessary a direct sibling of this token, but will always be further left in the tree. - pub fn prev_token(&self) -> Option<&ResolvedToken> { + pub fn prev_token(&self) -> Option<&ResolvedToken> { forward!(self.syntax.prev_token()) } } -impl ResolvedElement { +impl ResolvedElement { /// The range this element covers in the source text, in bytes. #[inline] pub fn text_range(&self) -> TextRange { @@ -587,7 +587,7 @@ impl ResolvedElement { /// The kind of this element in terms of your language. #[inline] - pub fn kind(&self) -> L::Kind { + pub fn kind(&self) -> S { match self { NodeOrToken::Node(it) => it.kind(), NodeOrToken::Token(it) => it.kind(), @@ -596,7 +596,7 @@ impl ResolvedElement { /// The parent node of this element, except if this element is the root. #[inline] - pub fn parent(&self) -> Option<&ResolvedNode> { + pub fn parent(&self) -> Option<&ResolvedNode> { match self { NodeOrToken::Node(it) => it.parent(), NodeOrToken::Token(it) => Some(it.parent()), @@ -605,7 +605,7 @@ impl ResolvedElement { /// Returns an iterator along the chain of parents of this node. #[inline] - pub fn ancestors(&self) -> impl Iterator> { + pub fn ancestors(&self) -> impl Iterator> { match self { NodeOrToken::Node(it) => it.ancestors(), NodeOrToken::Token(it) => it.parent().ancestors(), @@ -614,7 +614,7 @@ impl ResolvedElement { /// Return the leftmost token in the subtree of this element. #[inline] - pub fn first_token(&self) -> Option<&ResolvedToken> { + pub fn first_token(&self) -> Option<&ResolvedToken> { match self { NodeOrToken::Node(it) => it.first_token(), NodeOrToken::Token(it) => Some(it), @@ -623,7 +623,7 @@ impl ResolvedElement { /// Return the rightmost token in the subtree of this element. #[inline] - pub fn last_token(&self) -> Option<&ResolvedToken> { + pub fn last_token(&self) -> Option<&ResolvedToken> { match self { NodeOrToken::Node(it) => it.last_token(), NodeOrToken::Token(it) => Some(it), @@ -632,7 +632,7 @@ impl ResolvedElement { /// The tree element to the right of this one, i.e. the next child of this element's parent after this element. #[inline] - pub fn next_sibling_or_token(&self) -> Option> { + pub fn next_sibling_or_token(&self) -> Option> { match self { NodeOrToken::Node(it) => it.next_sibling_or_token(), NodeOrToken::Token(it) => it.next_sibling_or_token(), @@ -641,7 +641,7 @@ impl ResolvedElement { /// The tree element to the left of this one, i.e. the previous child of this element's parent after this element. #[inline] - pub fn prev_sibling_or_token(&self) -> Option> { + pub fn prev_sibling_or_token(&self) -> Option> { match self { NodeOrToken::Node(it) => it.prev_sibling_or_token(), NodeOrToken::Token(it) => it.prev_sibling_or_token(), @@ -649,7 +649,7 @@ impl ResolvedElement { } } -impl<'a, L: Language, D> ResolvedElementRef<'a, L, D> { +impl<'a, S: Syntax, D> ResolvedElementRef<'a, S, D> { /// The range this element covers in the source text, in bytes. #[inline] pub fn text_range(&self) -> TextRange { @@ -670,7 +670,7 @@ impl<'a, L: Language, D> ResolvedElementRef<'a, L, D> { /// The kind of this element in terms of your language. #[inline] - pub fn kind(&self) -> L::Kind { + pub fn kind(&self) -> S { match self { NodeOrToken::Node(it) => it.kind(), NodeOrToken::Token(it) => it.kind(), @@ -679,7 +679,7 @@ impl<'a, L: Language, D> ResolvedElementRef<'a, L, D> { /// The parent node of this element, except if this element is the root. #[inline] - pub fn parent(&self) -> Option<&'a ResolvedNode> { + pub fn parent(&self) -> Option<&'a ResolvedNode> { match self { NodeOrToken::Node(it) => it.parent(), NodeOrToken::Token(it) => Some(it.parent()), @@ -688,7 +688,7 @@ impl<'a, L: Language, D> ResolvedElementRef<'a, L, D> { /// Returns an iterator along the chain of parents of this node. #[inline] - pub fn ancestors(&self) -> impl Iterator> { + pub fn ancestors(&self) -> impl Iterator> { match self { NodeOrToken::Node(it) => it.ancestors(), NodeOrToken::Token(it) => it.parent().ancestors(), @@ -697,7 +697,7 @@ impl<'a, L: Language, D> ResolvedElementRef<'a, L, D> { /// Return the leftmost token in the subtree of this element. #[inline] - pub fn first_token(&self) -> Option<&'a ResolvedToken> { + pub fn first_token(&self) -> Option<&'a ResolvedToken> { match self { NodeOrToken::Node(it) => it.first_token(), NodeOrToken::Token(it) => Some(it), @@ -706,7 +706,7 @@ impl<'a, L: Language, D> ResolvedElementRef<'a, L, D> { /// Return the rightmost token in the subtree of this element. #[inline] - pub fn last_token(&self) -> Option<&'a ResolvedToken> { + pub fn last_token(&self) -> Option<&'a ResolvedToken> { match self { NodeOrToken::Node(it) => it.last_token(), NodeOrToken::Token(it) => Some(it), @@ -715,7 +715,7 @@ impl<'a, L: Language, D> ResolvedElementRef<'a, L, D> { /// The tree element to the right of this one, i.e. the next child of this element's parent after this element. #[inline] - pub fn next_sibling_or_token(&self) -> Option> { + pub fn next_sibling_or_token(&self) -> Option> { match self { NodeOrToken::Node(it) => it.next_sibling_or_token(), NodeOrToken::Token(it) => it.next_sibling_or_token(), @@ -724,7 +724,7 @@ impl<'a, L: Language, D> ResolvedElementRef<'a, L, D> { /// The tree element to the left of this one, i.e. the previous child of this element's parent after this element. #[inline] - pub fn prev_sibling_or_token(&self) -> Option> { + pub fn prev_sibling_or_token(&self) -> Option> { match self { NodeOrToken::Node(it) => it.prev_sibling_or_token(), NodeOrToken::Token(it) => it.prev_sibling_or_token(), diff --git a/src/syntax/text.rs b/cstree/src/syntax/text.rs similarity index 81% rename from src/syntax/text.rs rename to cstree/src/syntax/text.rs index c8966ac..96b7bbe 100644 --- a/src/syntax/text.rs +++ b/cstree/src/syntax/text.rs @@ -6,7 +6,7 @@ use crate::{ interning::{Resolver, TokenKey}, syntax::{SyntaxNode, SyntaxToken}, text::{TextRange, TextSize}, - Language, + Syntax, }; /// An efficient representation of the text that is covered by a [`SyntaxNode`], i.e. the combined @@ -21,9 +21,9 @@ use crate::{ /// # use cstree::testing::*; /// # use cstree::syntax::ResolvedNode; /// # -/// fn parse_float_literal(s: &str) -> ResolvedNode { +/// fn parse_float_literal(s: &str) -> ResolvedNode { /// // parsing... -/// # let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); +/// # let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); /// # builder.start_node(Float); /// # builder.token(Float, s); /// # builder.finish_node(); @@ -41,14 +41,14 @@ use crate::{ /// assert_eq!(sub, "748"); /// ``` #[derive(Clone)] -pub struct SyntaxText<'n, 'i, I: ?Sized, L: Language, D: 'static = ()> { - node: &'n SyntaxNode, +pub struct SyntaxText<'n, 'i, I: ?Sized, S: Syntax, D: 'static = ()> { + node: &'n SyntaxNode, range: TextRange, resolver: &'i I, } -impl<'n, 'i, I: Resolver + ?Sized, L: Language, D> SyntaxText<'n, 'i, I, L, D> { - pub(crate) fn new(node: &'n SyntaxNode, resolver: &'i I) -> Self { +impl<'n, 'i, I: Resolver + ?Sized, S: Syntax, D> SyntaxText<'n, 'i, I, S, D> { + pub(crate) fn new(node: &'n SyntaxNode, resolver: &'i I) -> Self { let range = node.text_range(); SyntaxText { node, range, resolver } } @@ -188,7 +188,7 @@ impl<'n, 'i, I: Resolver + ?Sized, L: Language, D> SyntaxText<'n, 'i, self.fold_chunks((), |(), chunk| f(chunk)) } - fn tokens_with_ranges(&self) -> impl Iterator, TextRange)> { + fn tokens_with_ranges(&self) -> impl Iterator, TextRange)> { let text_range = self.range; self.node .descendants_with_tokens() @@ -208,25 +208,25 @@ fn found(res: Result<(), T>) -> Option { } } -impl + ?Sized, L: Language, D> fmt::Debug for SyntaxText<'_, '_, I, L, D> { +impl + ?Sized, S: Syntax, D> fmt::Debug for SyntaxText<'_, '_, I, S, D> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.to_string(), f) } } -impl + ?Sized, L: Language, D> fmt::Display for SyntaxText<'_, '_, I, L, D> { +impl + ?Sized, S: Syntax, D> fmt::Display for SyntaxText<'_, '_, I, S, D> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.try_for_each_chunk(|chunk| fmt::Display::fmt(chunk, f)) } } -impl + ?Sized, L: Language, D> From> for String { - fn from(text: SyntaxText<'_, '_, I, L, D>) -> String { +impl + ?Sized, S: Syntax, D> From> for String { + fn from(text: SyntaxText<'_, '_, I, S, D>) -> String { text.to_string() } } -impl + ?Sized, L: Language, D> PartialEq for SyntaxText<'_, '_, I, L, D> { +impl + ?Sized, S: Syntax, D> PartialEq for SyntaxText<'_, '_, I, S, D> { fn eq(&self, mut rhs: &str) -> bool { self.try_for_each_chunk(|chunk| { if !rhs.starts_with(chunk) { @@ -240,33 +240,33 @@ impl + ?Sized, L: Language, D> PartialEq for SyntaxTe } } -impl + ?Sized, L: Language, D> PartialEq> for str { - fn eq(&self, rhs: &SyntaxText<'_, '_, I, L, D>) -> bool { +impl + ?Sized, S: Syntax, D> PartialEq> for str { + fn eq(&self, rhs: &SyntaxText<'_, '_, I, S, D>) -> bool { rhs == self } } -impl + ?Sized, L: Language, D> PartialEq<&'_ str> for SyntaxText<'_, '_, I, L, D> { +impl + ?Sized, S: Syntax, D> PartialEq<&'_ str> for SyntaxText<'_, '_, I, S, D> { fn eq(&self, rhs: &&str) -> bool { self == *rhs } } -impl + ?Sized, L: Language, D> PartialEq> for &'_ str { - fn eq(&self, rhs: &SyntaxText<'_, '_, I, L, D>) -> bool { +impl + ?Sized, S: Syntax, D> PartialEq> for &'_ str { + fn eq(&self, rhs: &SyntaxText<'_, '_, I, S, D>) -> bool { rhs == self } } -impl<'n1, 'i1, 'n2, 'i2, I1, I2, L1, L2, D1, D2> PartialEq> - for SyntaxText<'n1, 'i1, I1, L1, D1> +impl<'n1, 'i1, 'n2, 'i2, I1, I2, S1, S2, D1, D2> PartialEq> + for SyntaxText<'n1, 'i1, I1, S1, D1> where - L1: Language, - L2: Language, + S1: Syntax, + S2: Syntax, I1: Resolver + ?Sized, I2: Resolver + ?Sized, { - fn eq(&self, other: &SyntaxText<'_, '_, I2, L2, D2>) -> bool { + fn eq(&self, other: &SyntaxText<'_, '_, I2, S2, D2>) -> bool { if self.range.len() != other.range.len() { return false; } @@ -278,21 +278,21 @@ where } } -fn zip_texts<'it1, 'it2, It1, It2, I1, I2, L1, L2, D1, D2>( +fn zip_texts<'it1, 'it2, It1, It2, I1, I2, S1, S2, D1, D2>( xs: &mut It1, ys: &mut It2, resolver_x: &I1, resolver_y: &I2, ) -> Option<()> where - It1: Iterator, TextRange)>, - It2: Iterator, TextRange)>, + It1: Iterator, TextRange)>, + It2: Iterator, TextRange)>, I1: Resolver + ?Sized, I2: Resolver + ?Sized, D1: 'static, D2: 'static, - L1: Language + 'it1, - L2: Language + 'it2, + S1: Syntax + 'it1, + S2: Syntax + 'it2, { let mut x = xs.next()?; let mut y = ys.next()?; @@ -314,7 +314,7 @@ where } } -impl + ?Sized, L: Language, D> Eq for SyntaxText<'_, '_, I, L, D> {} +impl + ?Sized, S: Syntax, D> Eq for SyntaxText<'_, '_, I, S, D> {} mod private { use std::ops; @@ -383,40 +383,38 @@ mod tests { use super::*; - #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] - pub enum TestLang {} - impl Language for TestLang { - type Kind = RawSyntaxKind; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(transparent)] + pub struct SyntaxKind(u32); - fn kind_from_raw(raw: RawSyntaxKind) -> Self::Kind { - raw + impl Syntax for SyntaxKind { + fn from_raw(raw: RawSyntaxKind) -> Self { + Self(raw.0) } - fn kind_to_raw(kind: Self::Kind) -> RawSyntaxKind { - kind + fn into_raw(self) -> RawSyntaxKind { + RawSyntaxKind(self.0) } - fn static_text(kind: Self::Kind) -> Option<&'static str> { - if kind == RawSyntaxKind(1) { - Some("{") - } else if kind == RawSyntaxKind(2) { - Some("}") - } else { - None + fn static_text(self) -> Option<&'static str> { + match self.0 { + 1 => Some("{"), + 2 => Some("}"), + _ => None, } } } - fn build_tree(chunks: &[&str]) -> (SyntaxNode, impl Resolver) { - let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); - builder.start_node(RawSyntaxKind(62)); + fn build_tree(chunks: &[&str]) -> (SyntaxNode, impl Resolver) { + let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); + builder.start_node(SyntaxKind(62)); for &chunk in chunks.iter() { let kind = match chunk { "{" => 1, "}" => 2, _ => 3, }; - builder.token(RawSyntaxKind(kind), chunk); + builder.token(SyntaxKind(kind), chunk); } builder.finish_node(); let (node, cache) = builder.finish(); diff --git a/src/syntax/token.rs b/cstree/src/syntax/token.rs similarity index 87% rename from src/syntax/token.rs rename to cstree/src/syntax/token.rs index 6e27f7d..6fb68e3 100644 --- a/src/syntax/token.rs +++ b/cstree/src/syntax/token.rs @@ -12,18 +12,18 @@ use crate::{ green::{GreenNode, GreenToken}, interning::{Resolver, TokenKey}, traversal::Direction, - Language, RawSyntaxKind, + RawSyntaxKind, Syntax, }; /// Syntax tree token. #[derive(Debug)] -pub struct SyntaxToken { - parent: SyntaxNode, +pub struct SyntaxToken { + parent: SyntaxNode, index: u32, offset: TextSize, } -impl Clone for SyntaxToken { +impl Clone for SyntaxToken { fn clone(&self) -> Self { Self { parent: self.parent.clone(), @@ -33,7 +33,7 @@ impl Clone for SyntaxToken { } } -impl Hash for SyntaxToken { +impl Hash for SyntaxToken { fn hash(&self, state: &mut H) { self.parent.hash(state); self.index.hash(state); @@ -41,15 +41,15 @@ impl Hash for SyntaxToken { } } -impl PartialEq for SyntaxToken { - fn eq(&self, other: &SyntaxToken) -> bool { +impl PartialEq for SyntaxToken { + fn eq(&self, other: &SyntaxToken) -> bool { self.parent == other.parent && self.index == other.index && self.offset == other.offset } } -impl Eq for SyntaxToken {} +impl Eq for SyntaxToken {} -impl SyntaxToken { +impl SyntaxToken { /// Writes this token's [`Debug`](fmt::Debug) representation into the given `target`. pub fn write_debug(&self, resolver: &R, target: &mut impl fmt::Write) -> fmt::Result where @@ -113,7 +113,7 @@ impl SyntaxToken { /// Turns this token into a [`ResolvedToken`](crate::syntax::ResolvedToken), but only if there is a resolver /// associated with this tree. #[inline] - pub fn try_resolved(&self) -> Option<&ResolvedToken> { + pub fn try_resolved(&self) -> Option<&ResolvedToken> { // safety: we only coerce if `resolver` exists self.resolver().map(|_| unsafe { ResolvedToken::coerce_ref(self) }) } @@ -122,13 +122,13 @@ impl SyntaxToken { /// # Panics /// If there is no resolver associated with this tree. #[inline] - pub fn resolved(&self) -> &ResolvedToken { + pub fn resolved(&self) -> &ResolvedToken { self.try_resolved().expect("tried to resolve a node without resolver") } } -impl SyntaxToken { - pub(super) fn new(parent: &SyntaxNode, index: u32, offset: TextSize) -> SyntaxToken { +impl SyntaxToken { + pub(super) fn new(parent: &SyntaxNode, index: u32, offset: TextSize) -> SyntaxToken { Self { parent: parent.clone_uncounted(), index, @@ -164,8 +164,8 @@ impl SyntaxToken { /// The kind of this token in terms of your language. #[inline] - pub fn kind(&self) -> L::Kind { - L::kind_from_raw(self.syntax_kind()) + pub fn kind(&self) -> S { + S::from_raw(self.syntax_kind()) } /// The range this token covers in the source text, in bytes. @@ -187,17 +187,17 @@ impl SyntaxToken { self.static_text().or_else(|| self.green().text(resolver)).unwrap() } - /// If the [syntax kind](Language::Kind) of this token always represents the same text, returns + /// If the [syntax kind](Syntax) of this token always represents the same text, returns /// that text. /// /// # Examples /// If there is a syntax kind `Plus` that represents just the `+` operator and we implement - /// [`Language::static_text`] for it, we can retrieve this text in the resulting syntax tree. + /// [`Syntax::static_text`] for it, we can retrieve this text in the resulting syntax tree. /// /// ``` /// # use cstree::testing::*; /// # use cstree::build::*; - /// let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); + /// let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); /// # builder.start_node(Root); /// # builder.token(Identifier, "x"); /// # builder.token(Whitespace, " "); @@ -206,7 +206,7 @@ impl SyntaxToken { /// # builder.token(Int, "3"); /// # builder.finish_node(); /// let tree = parse(&mut builder, "x + 3"); - /// # let tree: SyntaxNode = SyntaxNode::new_root(builder.finish().0); + /// # let tree: SyntaxNode = SyntaxNode::new_root(builder.finish().0); /// let plus = tree /// .children_with_tokens() /// .nth(2) // `x`, then a space, then `+` @@ -217,7 +217,7 @@ impl SyntaxToken { /// ``` #[inline(always)] pub fn static_text(&self) -> Option<&'static str> { - L::static_text(self.kind()) + S::static_text(self.kind()) } /// Returns `true` if `self` and `other` represent equal source text. @@ -235,7 +235,7 @@ impl SyntaxToken { /// # Examples /// ``` /// # use cstree::testing::*; - /// let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); + /// let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); /// # builder.start_node(Root); /// # builder.token(Identifier, "x"); /// # builder.token(Whitespace, " "); @@ -247,7 +247,7 @@ impl SyntaxToken { /// # builder.token(Int, "3"); /// # builder.finish_node(); /// let tree = parse(&mut builder, "x + x + 3"); - /// # let tree: SyntaxNode = SyntaxNode::new_root(builder.finish().0); + /// # let tree: SyntaxNode = SyntaxNode::new_root(builder.finish().0); /// let mut tokens = tree.children_with_tokens(); /// let tokens = tokens.by_ref(); /// let first_x = tokens.next().unwrap().into_token().unwrap(); @@ -303,14 +303,14 @@ impl SyntaxToken { /// interner, /// type_table: TypeTable{ /* stuff */}, /// }; - /// let mut builder: GreenNodeBuilder = + /// let mut builder: GreenNodeBuilder = /// GreenNodeBuilder::with_interner(&mut state.interner); /// # let input = ""; /// # builder.start_node(Root); /// # builder.token(Identifier, "x"); /// # builder.finish_node(); /// let tree = parse(&mut builder, "x"); - /// # let tree: SyntaxNode = SyntaxNode::new_root(builder.finish().0); + /// # let tree: SyntaxNode = SyntaxNode::new_root(builder.finish().0); /// let type_table = &state.type_table; /// let ident = tree /// .children_with_tokens() @@ -339,26 +339,26 @@ impl SyntaxToken { /// The parent node of this token. #[inline] - pub fn parent(&self) -> &SyntaxNode { + pub fn parent(&self) -> &SyntaxNode { &self.parent } /// Returns an iterator along the chain of parents of this token. #[inline] - pub fn ancestors(&self) -> impl Iterator> { + pub fn ancestors(&self) -> impl Iterator> { self.parent().ancestors() } /// The tree element to the right of this one, i.e. the next child of this token's parent after this token. #[inline] - pub fn next_sibling_or_token(&self) -> Option> { + pub fn next_sibling_or_token(&self) -> Option> { self.parent() .next_child_or_token_after(self.index as usize, self.text_range().end()) } /// The tree element to the left of this one, i.e. the previous child of this token's parent after this token. #[inline] - pub fn prev_sibling_or_token(&self) -> Option> { + pub fn prev_sibling_or_token(&self) -> Option> { self.parent() .prev_child_or_token_before(self.index as usize, self.text_range().start()) } @@ -367,8 +367,8 @@ impl SyntaxToken { /// token's parent's children from this token on to the left or the right. /// The first item in the iterator will always be this token. #[inline] - pub fn siblings_with_tokens(&self, direction: Direction) -> impl Iterator> { - let me: SyntaxElementRef<'_, L, D> = self.into(); + pub fn siblings_with_tokens(&self, direction: Direction) -> impl Iterator> { + let me: SyntaxElementRef<'_, S, D> = self.into(); iter::successors(Some(me), move |el| match direction { Direction::Next => el.next_sibling_or_token(), Direction::Prev => el.prev_sibling_or_token(), @@ -378,7 +378,7 @@ impl SyntaxToken { /// Returns the next token in the tree. /// This is not necessary a direct sibling of this token, but will always be further right in the tree. #[inline] - pub fn next_token(&self) -> Option<&SyntaxToken> { + pub fn next_token(&self) -> Option<&SyntaxToken> { match self.next_sibling_or_token() { Some(element) => element.first_token(), None => self @@ -392,7 +392,7 @@ impl SyntaxToken { /// Returns the previous token in the tree. /// This is not necessary a direct sibling of this token, but will always be further left in the tree. #[inline] - pub fn prev_token(&self) -> Option<&SyntaxToken> { + pub fn prev_token(&self) -> Option<&SyntaxToken> { match self.prev_sibling_or_token() { Some(element) => element.last_token(), None => self diff --git a/src/utility_types.rs b/cstree/src/utility_types.rs similarity index 100% rename from src/utility_types.rs rename to cstree/src/utility_types.rs diff --git a/tests/it/basic.rs b/cstree/tests/it/basic.rs similarity index 90% rename from tests/it/basic.rs rename to cstree/tests/it/basic.rs index 0e528fe..c02fc3b 100644 --- a/tests/it/basic.rs +++ b/cstree/tests/it/basic.rs @@ -7,7 +7,7 @@ use cstree::{ }; fn build_tree(root: &Element<'_>) -> (SyntaxNode, impl Resolver) { - let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); + let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); build_recursive(root, &mut builder, 0); let (node, cache) = builder.finish(); (SyntaxNode::new_root(node), cache.unwrap().into_interner().unwrap()) @@ -36,19 +36,19 @@ fn create() { let tree = two_level_tree(); let (tree, resolver) = build_tree::<()>(&tree); assert_eq!(tree.syntax_kind(), RawSyntaxKind(0)); - assert_eq!(tree.kind(), RawSyntaxKind(0)); + assert_eq!(tree.kind(), SyntaxKind(0)); { let leaf1_0 = tree.children().nth(1).unwrap().children_with_tokens().next().unwrap(); let leaf1_0 = leaf1_0.into_token().unwrap(); assert_eq!(leaf1_0.syntax_kind(), RawSyntaxKind(5)); - assert_eq!(leaf1_0.kind(), RawSyntaxKind(5)); + assert_eq!(leaf1_0.kind(), SyntaxKind(5)); assert_eq!(leaf1_0.resolve_text(&resolver), "1.0"); assert_eq!(leaf1_0.text_range(), TextRange::at(6.into(), 3.into())); } { let node2 = tree.children().nth(2).unwrap(); assert_eq!(node2.syntax_kind(), RawSyntaxKind(6)); - assert_eq!(node2.kind(), RawSyntaxKind(6)); + assert_eq!(node2.kind(), SyntaxKind(6)); assert_eq!(node2.children_with_tokens().count(), 3); assert_eq!(node2.resolve_text(&resolver), "2.02.12.2"); } @@ -58,7 +58,7 @@ fn create() { fn token_text_eq() { let tree = tree_with_eq_tokens(); let (tree, _) = build_tree::<()>(&tree); - assert_eq!(tree.kind(), RawSyntaxKind(0)); + assert_eq!(tree.kind(), SyntaxKind(0)); let leaf0_0 = tree.children().next().unwrap().children_with_tokens().next().unwrap(); let leaf0_0 = leaf0_0.into_token().unwrap(); @@ -150,7 +150,7 @@ fn inline_resolver() { assert_eq!(leaf1_0.text(), "1.0"); assert_eq!(leaf1_0.text_range(), TextRange::at(6.into(), 3.into())); assert_eq!(format!("{}", leaf1_0), leaf1_0.text()); - assert_eq!(format!("{:?}", leaf1_0), "RawSyntaxKind(5)@6..9 \"1.0\""); + assert_eq!(format!("{:?}", leaf1_0), "SyntaxKind(5)@6..9 \"1.0\""); } { let node2 = tree.children().nth(2).unwrap(); @@ -158,13 +158,13 @@ fn inline_resolver() { let resolver = node2.resolver(); assert_eq!(node2.resolve_text(resolver.as_ref()), node2.text()); assert_eq!(format!("{}", node2).as_str(), node2.text()); - assert_eq!(format!("{:?}", node2), "RawSyntaxKind(6)@9..18"); + assert_eq!(format!("{:?}", node2), "SyntaxKind(6)@9..18"); assert_eq!( format!("{:#?}", node2), - r#"RawSyntaxKind(6)@9..18 - RawSyntaxKind(7)@9..12 "2.0" - RawSyntaxKind(8)@12..15 "2.1" - RawSyntaxKind(9)@15..18 "2.2" + r#"SyntaxKind(6)@9..18 + SyntaxKind(7)@9..12 "2.0" + SyntaxKind(8)@12..15 "2.1" + SyntaxKind(9)@15..18 "2.2" "# ); } @@ -182,5 +182,5 @@ fn assert_debug_display() { f::>(); fn dbg() {} - dbg::>(); + dbg::>(); } diff --git a/cstree/tests/it/main.rs b/cstree/tests/it/main.rs new file mode 100644 index 0000000..a952a23 --- /dev/null +++ b/cstree/tests/it/main.rs @@ -0,0 +1,80 @@ +mod basic; +mod regressions; +mod sendsync; +#[cfg(feature = "serialize")] +mod serde; + +use cstree::{ + build::{GreenNodeBuilder, NodeCache}, + green::GreenNode, + interning::Interner, + RawSyntaxKind, Syntax, +}; + +pub type SyntaxNode = cstree::syntax::SyntaxNode; +pub type SyntaxToken = cstree::syntax::SyntaxToken; +pub type SyntaxElement = cstree::syntax::SyntaxElement; +pub type SyntaxElementRef<'a, D = ()> = cstree::syntax::SyntaxElementRef<'a, SyntaxKind, D>; + +pub type ResolvedNode = cstree::syntax::ResolvedNode; +pub type ResolvedToken = cstree::syntax::ResolvedToken; +pub type ResolvedElement = cstree::syntax::ResolvedElement; +pub type ResolvedElementRef<'a, D = ()> = cstree::syntax::ResolvedElementRef<'a, SyntaxKind, D>; + +#[derive(Debug)] +pub enum Element<'s> { + Node(Vec>), + Token(&'s str), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct SyntaxKind(u32); + +impl Syntax for SyntaxKind { + fn from_raw(raw: RawSyntaxKind) -> Self { + Self(raw.0) + } + + fn into_raw(self) -> RawSyntaxKind { + RawSyntaxKind(self.0) + } + + fn static_text(self) -> Option<&'static str> { + None + } +} + +pub fn build_tree_with_cache(root: &Element<'_>, cache: &mut NodeCache<'_, I>) -> GreenNode +where + I: Interner, +{ + let mut builder: GreenNodeBuilder = GreenNodeBuilder::with_cache(cache); + build_recursive(root, &mut builder, 0); + let (node, cache) = builder.finish(); + assert!(cache.is_none()); + node +} + +pub fn build_recursive( + root: &Element<'_>, + builder: &mut GreenNodeBuilder<'_, '_, SyntaxKind, I>, + mut from: u32, +) -> u32 +where + I: Interner, +{ + match root { + Element::Node(children) => { + builder.start_node(SyntaxKind(from)); + for child in children { + from = build_recursive(child, builder, from + 1); + } + builder.finish_node(); + } + Element::Token(text) => { + builder.token(SyntaxKind(from), text); + } + } + from +} diff --git a/cstree/tests/it/regressions.rs b/cstree/tests/it/regressions.rs new file mode 100644 index 0000000..7ceefed --- /dev/null +++ b/cstree/tests/it/regressions.rs @@ -0,0 +1,21 @@ +use cstree::Syntax; + +#[test] +fn empty_tree_arc() { + // this test is not here for the test itself, but to run it through MIRI, who complained about out-of-bound + // `ThinArc` pointers for a root `GreenNode` with no children + + use cstree::{build::GreenNodeBuilder, syntax::SyntaxNode}; + #[derive(Debug, Clone, Copy, PartialEq, Eq, Syntax)] + #[repr(u32)] + enum SyntaxKind { + Root, + } + + let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); + builder.start_node(SyntaxKind::Root); + builder.finish_node(); + let (green, _) = builder.finish(); + let root: SyntaxNode = SyntaxNode::new_root(green); + assert_eq!(root.kind(), SyntaxKind::Root); +} diff --git a/tests/it/sendsync.rs b/cstree/tests/it/sendsync.rs similarity index 94% rename from tests/it/sendsync.rs rename to cstree/tests/it/sendsync.rs index ad501e0..1fc39b2 100644 --- a/tests/it/sendsync.rs +++ b/cstree/tests/it/sendsync.rs @@ -3,7 +3,7 @@ use crossbeam_utils::thread::scope; use std::{thread, time::Duration}; -use super::{build_recursive, Element, ResolvedNode, SyntaxNode, TestLang}; +use super::{build_recursive, Element, ResolvedNode, SyntaxKind, SyntaxNode}; use cstree::build::GreenNodeBuilder; // Excercise the multi-threaded interner when the corresponding feature is enabled. @@ -12,12 +12,12 @@ use cstree::build::GreenNodeBuilder; use cstree::interning::{new_threaded_interner, MultiThreadedTokenInterner}; #[cfg(not(feature = "multi_threaded_interning"))] -fn get_builder() -> GreenNodeBuilder<'static, 'static, TestLang> { +fn get_builder() -> GreenNodeBuilder<'static, 'static, SyntaxKind> { GreenNodeBuilder::new() } #[cfg(feature = "multi_threaded_interning")] -fn get_builder() -> GreenNodeBuilder<'static, 'static, TestLang, MultiThreadedTokenInterner> { +fn get_builder() -> GreenNodeBuilder<'static, 'static, SyntaxKind, MultiThreadedTokenInterner> { let interner = new_threaded_interner(); GreenNodeBuilder::from_interner(interner) } diff --git a/tests/it/serde.rs b/cstree/tests/it/serde.rs similarity index 98% rename from tests/it/serde.rs rename to cstree/tests/it/serde.rs index 7f7e42e..d86910f 100644 --- a/tests/it/serde.rs +++ b/cstree/tests/it/serde.rs @@ -1,6 +1,6 @@ use crate::{build_recursive, build_tree_with_cache, ResolvedNode}; -use super::{Element, SyntaxNode, TestLang}; +use super::{Element, SyntaxKind, SyntaxNode}; use cstree::{ build::{GreenNodeBuilder, NodeCache}, interning::new_interner, @@ -225,7 +225,7 @@ fn three_level_tree() -> Element<'static> { } fn build_tree(root: Element<'_>) -> ResolvedNode { - let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); + let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); build_recursive(&root, &mut builder, 0); let (node, cache) = builder.finish(); SyntaxNode::new_root_with_resolver(node, cache.unwrap().into_interner().unwrap()) diff --git a/test_suite/Cargo.toml b/test_suite/Cargo.toml new file mode 100644 index 0000000..4df41c1 --- /dev/null +++ b/test_suite/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "cstree_test_suite" +publish = false +version = "0.0.0" +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true +rust-version.workspace = true + +[dependencies] +cstree = { path = "../cstree", features = ["derive"] } + +[dev-dependencies] +trybuild = { version = "1.0.80", features = ["diff"] } diff --git a/test_suite/tests/derive.rs b/test_suite/tests/derive.rs new file mode 100644 index 0000000..f5abf4f --- /dev/null +++ b/test_suite/tests/derive.rs @@ -0,0 +1,22 @@ +use cstree::{RawSyntaxKind, Syntax}; + +#[test] +fn basic() { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Syntax)] + #[repr(u32)] + pub enum SyntaxKind { + A, + #[static_text("b")] + B, + } + pub type MySyntax = SyntaxKind; + + assert_eq!(MySyntax::into_raw(SyntaxKind::A), RawSyntaxKind(0)); + assert_eq!(MySyntax::into_raw(SyntaxKind::B), RawSyntaxKind(1)); + + assert_eq!(MySyntax::from_raw(RawSyntaxKind(0)), SyntaxKind::A); + assert_eq!(MySyntax::from_raw(RawSyntaxKind(1)), SyntaxKind::B); + + assert!(MySyntax::static_text(SyntaxKind::A).is_none()); + assert_eq!(MySyntax::static_text(SyntaxKind::B), Some("b")); +} diff --git a/test_suite/tests/ui.rs b/test_suite/tests/ui.rs new file mode 100644 index 0000000..8fe7373 --- /dev/null +++ b/test_suite/tests/ui.rs @@ -0,0 +1,6 @@ +#[test] +#[cfg_attr(miri, ignore)] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/**/*.rs"); +} diff --git a/test_suite/tests/ui/repr/missing_repr.rs b/test_suite/tests/ui/repr/missing_repr.rs new file mode 100644 index 0000000..244af4c --- /dev/null +++ b/test_suite/tests/ui/repr/missing_repr.rs @@ -0,0 +1,10 @@ +use cstree::Syntax; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Syntax)] +pub enum SyntaxKind { + A, + #[static_text("b")] + B, +} + +fn main() {} diff --git a/test_suite/tests/ui/repr/missing_repr.stderr b/test_suite/tests/ui/repr/missing_repr.stderr new file mode 100644 index 0000000..4eb67c9 --- /dev/null +++ b/test_suite/tests/ui/repr/missing_repr.stderr @@ -0,0 +1,9 @@ +error: syntax kind definitions must be `#[repr(u32)]` to derive `Syntax` + --> tests/ui/repr/missing_repr.rs:4:1 + | +4 | / pub enum SyntaxKind { +5 | | A, +6 | | #[static_text("b")] +7 | | B, +8 | | } + | |_^ diff --git a/test_suite/tests/ui/repr/wrong_repr_c.rs b/test_suite/tests/ui/repr/wrong_repr_c.rs new file mode 100644 index 0000000..0ccf969 --- /dev/null +++ b/test_suite/tests/ui/repr/wrong_repr_c.rs @@ -0,0 +1,11 @@ +use cstree::Syntax; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Syntax)] +#[repr(C)] +pub enum SyntaxKind { + A, + #[static_text("b")] + B, +} + +fn main() {} diff --git a/test_suite/tests/ui/repr/wrong_repr_c.stderr b/test_suite/tests/ui/repr/wrong_repr_c.stderr new file mode 100644 index 0000000..2546577 --- /dev/null +++ b/test_suite/tests/ui/repr/wrong_repr_c.stderr @@ -0,0 +1,10 @@ +error: syntax kind definitions must be `#[repr(u32)]` to derive `Syntax` + --> tests/ui/repr/wrong_repr_c.rs:4:1 + | +4 | / #[repr(C)] +5 | | pub enum SyntaxKind { +6 | | A, +7 | | #[static_text("b")] +8 | | B, +9 | | } + | |_^ diff --git a/test_suite/tests/ui/repr/wrong_repr_u16.rs b/test_suite/tests/ui/repr/wrong_repr_u16.rs new file mode 100644 index 0000000..cb7d477 --- /dev/null +++ b/test_suite/tests/ui/repr/wrong_repr_u16.rs @@ -0,0 +1,11 @@ +use cstree::Syntax; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Syntax)] +#[repr(u16)] +pub enum SyntaxKind { + A, + #[static_text("b")] + B, +} + +fn main() {} diff --git a/test_suite/tests/ui/repr/wrong_repr_u16.stderr b/test_suite/tests/ui/repr/wrong_repr_u16.stderr new file mode 100644 index 0000000..cde1733 --- /dev/null +++ b/test_suite/tests/ui/repr/wrong_repr_u16.stderr @@ -0,0 +1,10 @@ +error: syntax kind definitions must be `#[repr(u32)]` to derive `Syntax` + --> tests/ui/repr/wrong_repr_u16.rs:4:1 + | +4 | / #[repr(u16)] +5 | | pub enum SyntaxKind { +6 | | A, +7 | | #[static_text("b")] +8 | | B, +9 | | } + | |_^ diff --git a/test_suite/tests/ui/static_text/empty_expr.rs b/test_suite/tests/ui/static_text/empty_expr.rs new file mode 100644 index 0000000..2846f24 --- /dev/null +++ b/test_suite/tests/ui/static_text/empty_expr.rs @@ -0,0 +1,11 @@ +use cstree::Syntax; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Syntax)] +#[repr(u32)] +pub enum SyntaxKind { + A, + #[static_text()] + B, +} + +fn main() {} diff --git a/test_suite/tests/ui/static_text/empty_expr.stderr b/test_suite/tests/ui/static_text/empty_expr.stderr new file mode 100644 index 0000000..b3f50b4 --- /dev/null +++ b/test_suite/tests/ui/static_text/empty_expr.stderr @@ -0,0 +1,11 @@ +error: argument to `static_text` must be a string literal: `#[static_text("...")]` + --> tests/ui/static_text/empty_expr.rs:7:7 + | +7 | #[static_text()] + | ^^^^^^^^^^^^^ + +error: unexpected end of input, expected string literal + --> tests/ui/static_text/empty_expr.rs:7:19 + | +7 | #[static_text()] + | ^ diff --git a/test_suite/tests/ui/static_text/missing_text.rs b/test_suite/tests/ui/static_text/missing_text.rs new file mode 100644 index 0000000..ebd3d49 --- /dev/null +++ b/test_suite/tests/ui/static_text/missing_text.rs @@ -0,0 +1,11 @@ +use cstree::Syntax; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Syntax)] +#[repr(u32)] +pub enum SyntaxKind { + A, + #[static_text] + B, +} + +fn main() {} diff --git a/test_suite/tests/ui/static_text/missing_text.stderr b/test_suite/tests/ui/static_text/missing_text.stderr new file mode 100644 index 0000000..6f72295 --- /dev/null +++ b/test_suite/tests/ui/static_text/missing_text.stderr @@ -0,0 +1,5 @@ +error: missing text for `static_text`: try `#[static_text("...")]` + --> tests/ui/static_text/missing_text.rs:7:5 + | +7 | #[static_text] + | ^^^^^^^^^^^^^^ diff --git a/test_suite/tests/ui/static_text/non_expr.rs b/test_suite/tests/ui/static_text/non_expr.rs new file mode 100644 index 0000000..098b568 --- /dev/null +++ b/test_suite/tests/ui/static_text/non_expr.rs @@ -0,0 +1,11 @@ +use cstree::Syntax; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Syntax)] +#[repr(u32)] +pub enum SyntaxKind { + A, + #[static_text(SyntaxKind)] + B, +} + +fn main() {} diff --git a/test_suite/tests/ui/static_text/non_expr.stderr b/test_suite/tests/ui/static_text/non_expr.stderr new file mode 100644 index 0000000..a29f282 --- /dev/null +++ b/test_suite/tests/ui/static_text/non_expr.stderr @@ -0,0 +1,11 @@ +error: argument to `static_text` must be a string literal: `#[static_text("...")]` + --> tests/ui/static_text/non_expr.rs:7:7 + | +7 | #[static_text(SyntaxKind)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected string literal + --> tests/ui/static_text/non_expr.rs:7:19 + | +7 | #[static_text(SyntaxKind)] + | ^^^^^^^^^^ diff --git a/test_suite/tests/ui/static_text/non_string_expr.rs b/test_suite/tests/ui/static_text/non_string_expr.rs new file mode 100644 index 0000000..4a69ee3 --- /dev/null +++ b/test_suite/tests/ui/static_text/non_string_expr.rs @@ -0,0 +1,11 @@ +use cstree::Syntax; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Syntax)] +#[repr(u32)] +pub enum SyntaxKind { + A, + #[static_text(foo + 3)] + B, +} + +fn main() {} diff --git a/test_suite/tests/ui/static_text/non_string_expr.stderr b/test_suite/tests/ui/static_text/non_string_expr.stderr new file mode 100644 index 0000000..0c80b6a --- /dev/null +++ b/test_suite/tests/ui/static_text/non_string_expr.stderr @@ -0,0 +1,11 @@ +error: argument to `static_text` must be a string literal: `#[static_text("...")]` + --> tests/ui/static_text/non_string_expr.rs:7:7 + | +7 | #[static_text(foo + 3)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: expected string literal + --> tests/ui/static_text/non_string_expr.rs:7:19 + | +7 | #[static_text(foo + 3)] + | ^^^ diff --git a/test_suite/tests/ui/static_text/text_assigned.rs b/test_suite/tests/ui/static_text/text_assigned.rs new file mode 100644 index 0000000..1ef59eb --- /dev/null +++ b/test_suite/tests/ui/static_text/text_assigned.rs @@ -0,0 +1,11 @@ +use cstree::Syntax; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Syntax)] +#[repr(u32)] +pub enum SyntaxKind { + A, + #[static_text = "b"] + B, +} + +fn main() {} diff --git a/test_suite/tests/ui/static_text/text_assigned.stderr b/test_suite/tests/ui/static_text/text_assigned.stderr new file mode 100644 index 0000000..056806a --- /dev/null +++ b/test_suite/tests/ui/static_text/text_assigned.stderr @@ -0,0 +1,5 @@ +error: `static_text` takes the text as a function argument: `#[static_text("...")]` + --> tests/ui/static_text/text_assigned.rs:7:5 + | +7 | #[static_text = "b"] + | ^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/it/main.rs b/tests/it/main.rs deleted file mode 100644 index 256856f..0000000 --- a/tests/it/main.rs +++ /dev/null @@ -1,77 +0,0 @@ -mod basic; -mod regressions; -mod sendsync; -#[cfg(feature = "serialize")] -mod serde; - -use cstree::{ - build::{GreenNodeBuilder, NodeCache}, - green::GreenNode, - interning::Interner, - Language, RawSyntaxKind, -}; - -pub type SyntaxNode = cstree::syntax::SyntaxNode; -pub type SyntaxToken = cstree::syntax::SyntaxToken; -pub type SyntaxElement = cstree::syntax::SyntaxElement; -pub type SyntaxElementRef<'a, D = ()> = cstree::syntax::SyntaxElementRef<'a, TestLang, D>; - -pub type ResolvedNode = cstree::syntax::ResolvedNode; -pub type ResolvedToken = cstree::syntax::ResolvedToken; -pub type ResolvedElement = cstree::syntax::ResolvedElement; -pub type ResolvedElementRef<'a, D = ()> = cstree::syntax::ResolvedElementRef<'a, TestLang, D>; - -#[derive(Debug)] -pub enum Element<'s> { - Node(Vec>), - Token(&'s str), -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub enum TestLang {} -impl Language for TestLang { - type Kind = RawSyntaxKind; - - fn kind_from_raw(raw: RawSyntaxKind) -> Self::Kind { - raw - } - - fn kind_to_raw(kind: Self::Kind) -> RawSyntaxKind { - kind - } - - fn static_text(_kind: Self::Kind) -> Option<&'static str> { - None - } -} - -pub fn build_tree_with_cache(root: &Element<'_>, cache: &mut NodeCache<'_, I>) -> GreenNode -where - I: Interner, -{ - let mut builder: GreenNodeBuilder = GreenNodeBuilder::with_cache(cache); - build_recursive(root, &mut builder, 0); - let (node, cache) = builder.finish(); - assert!(cache.is_none()); - node -} - -pub fn build_recursive(root: &Element<'_>, builder: &mut GreenNodeBuilder<'_, '_, L, I>, mut from: u32) -> u32 -where - L: Language, - I: Interner, -{ - match root { - Element::Node(children) => { - builder.start_node(RawSyntaxKind(from)); - for child in children { - from = build_recursive(child, builder, from + 1); - } - builder.finish_node(); - } - Element::Token(text) => { - builder.token(RawSyntaxKind(from), text); - } - } - from -} diff --git a/tests/it/regressions.rs b/tests/it/regressions.rs deleted file mode 100644 index d65aebd..0000000 --- a/tests/it/regressions.rs +++ /dev/null @@ -1,38 +0,0 @@ -#[test] -fn empty_tree_arc() { - // this test is not here for the test itself, but to run it through MIRI, who complained about out-of-bound - // `ThinArc` pointers for a root `GreenNode` with no children - - use cstree::{build::GreenNodeBuilder, syntax::SyntaxNode}; - #[allow(non_camel_case_types)] - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] - #[repr(u32)] - enum SyntaxKind { - Root, - } - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] - enum Lang {} - impl cstree::Language for Lang { - // ... - type Kind = SyntaxKind; - - fn kind_from_raw(raw: cstree::RawSyntaxKind) -> Self::Kind { - assert!(raw.0 <= SyntaxKind::Root as u32); - unsafe { std::mem::transmute::(raw.0) } - } - - fn kind_to_raw(kind: Self::Kind) -> cstree::RawSyntaxKind { - cstree::RawSyntaxKind(kind as u32) - } - - fn static_text(_kind: Self::Kind) -> Option<&'static str> { - None - } - } - let mut builder: GreenNodeBuilder = GreenNodeBuilder::new(); - builder.start_node(SyntaxKind::Root); - builder.finish_node(); - let (green, _) = builder.finish(); - let root: SyntaxNode = SyntaxNode::new_root(green); - assert_eq!(root.kind(), SyntaxKind::Root); -}