Reduce parser dependencies (#9671)

* Reduce parser dependencies

- `enso-parser-syntax-tree-visitor` is now only used when building tests and debug tools.
- Remove `enso-logging` crate and its macros.
- The main bin for `enso-parser` has been moved to a `check_syntax` tool in `enso-parser-debug`.
This commit is contained in:
Kaz Wesley 2024-04-11 00:27:19 -04:00 committed by GitHub
parent 0fcb005ff8
commit 2254dfe9fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 91 additions and 853 deletions

19
Cargo.lock generated
View File

@ -1468,24 +1468,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "enso-logging"
version = "0.3.1"
dependencies = [
"enso-logging-macros",
"web-sys",
]
[[package]]
name = "enso-logging-macros"
version = "0.1.0"
dependencies = [
"Inflector",
"proc-macro2",
"quote",
"syn 2.0.53",
]
[[package]]
name = "enso-macro-utils"
version = "0.2.0"
@ -1610,7 +1592,6 @@ dependencies = [
"boolinator",
"derivative",
"derive_more",
"enso-logging",
"enso-macros",
"enso-reflect",
"enso-zst",

View File

@ -115,8 +115,6 @@ futures = { version = "0.3" }
itertools = { version = "0.12.1" }
lazy_static = { version = "1.4" }
serde_json = { version = "1.0", features = ["raw_value"] }
smallvec = { version = "1.0.0" }
js-sys = { version = "0.3" }
owned_ttf_parser = { version = "0.15.1" }
convert_case = { version = "0.6.0" }
rustybuzz = { version = "0.5.1" }
@ -149,4 +147,3 @@ syn_1 = { package = "syn", version = "1.0", features = [
quote = { version = "1.0.23" }
semver = { version = "1.0.0", features = ["serde"] }
strum = { version = "0.26.2", features = ["derive"] }
thiserror = "1.0.40"

View File

@ -1,24 +0,0 @@
[package]
name = "enso-logging"
version = "0.3.1"
authors = ["Enso Team <contact@luna-lang.org>"]
edition = "2021"
description = "An efficient logger for writing applications in Rust."
readme = "README.md"
homepage = "https://github.com/enso-org/enso/lib/rust/logging"
repository = "https://github.com/enso-org/enso"
license-file = "../../LICENSE"
keywords = ["logging"]
categories = ["development-tools::debugging"]
[lib]
[features]
default = []
[dependencies]
enso-logging-macros = { path = "macros" }
web-sys = { version = "0.3.4", features = ["console"] }
[lints]
workspace = true

View File

@ -1,17 +0,0 @@
[package]
name = "enso-logging-macros"
version = "0.1.0"
edition = "2021"
authors = ["Enso Team <contact@enso.org>"]
[lib]
proc-macro = true
[dependencies]
proc-macro2 = { workspace = true }
quote = { workspace = true }
syn = { workspace = true }
Inflector = "0.11"
[lints]
workspace = true

View File

@ -1,28 +0,0 @@
//! Build script for [`enso_logging_macros`]. This is needed to make cargo aware that
//! the crate depends on the values of environment variables at compile time, and changes to those
//! variables should result in recompiling this crate and its dependents.
// === Non-Standard Linter Configuration ===
#![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
#![warn(trivial_casts)]
#![warn(trivial_numeric_casts)]
#![warn(unsafe_code)]
#![warn(unused_import_braces)]
#![warn(unused_qualifications)]
fn main() {
declare_env_dependence("ENSO_MAX_LOG_LEVEL");
declare_env_dependence("ENSO_UNCOLLAPSED_LOG_LEVEL");
}
/// Make cargo aware that the result of compiling this crate depends on an environment variable.
fn declare_env_dependence(env: &str) {
println!("cargo:rerun-if-env-changed={env}");
// This is a no-op assignment, except it makes cargo aware that the output depends on the env.
let value = std::env::var(env).unwrap_or_default();
println!("cargo:rustc-env={env}={value}");
}

View File

@ -1,580 +0,0 @@
//! Proc macros supporting the implementation of the `enso_logging` library.
// === Features ===
#![feature(anonymous_lifetime_in_impl_trait)]
#![feature(proc_macro_span)]
#![feature(let_chains)]
// === Non-Standard Linter Configuration ===
#![deny(unconditional_recursion)]
#![warn(missing_docs)]
#![warn(trivial_casts)]
use inflector::Inflector;
use quote::quote;
/// The log levels defined in the Console Web API.
/// See: https://console.spec.whatwg.org/#loglevel-severity
const WEB_LOG_LEVELS: &[&str] = &["error", "warn", "info", "debug"];
// ==================================
// === Compile-time configuration ===
// ==================================
const LEVEL_CONFIGURATION_ENV_VARS: LevelConfiguration<&str> = LevelConfiguration {
max_enabled: "ENSO_MAX_LOG_LEVEL",
max_uncollapsed: "ENSO_MAX_UNCOLLAPSED_LOG_LEVEL",
};
// =====================
// === Helper macros ===
// =====================
macro_rules! map_fns {
($Ty:tt, [$($covariant:tt),*]) => { map_fns!($Ty, [$($covariant),*], []); };
($Ty:ident, [$($covariant:ident),*], [$($invariant:ident),*]) => {
impl<T> $Ty<T> {
#[allow(unused)]
fn map<F, U>(self, f: F) -> $Ty<U> where F: Fn(T) -> U {
let Self {
$($covariant,)*
$($invariant,)*
} = self;
$Ty {
$($covariant: f($covariant),)*
$($invariant,)*
}
}
#[allow(unused)]
fn for_each<F>(self, mut f: F) where F: FnMut(T) {
$(f(self.$covariant);)*
}
#[allow(unused)]
fn as_ref(&self) -> $Ty<&T> {
let Self {
$($covariant,)*
$($invariant,)*
} = self;
$Ty {
$($covariant,)*
$($invariant: $invariant.clone(),)*
}
}
}
};
}
// =================
// === Interface ===
// =================
/// Implement a logging API for the spcecified log levels.
#[proc_macro]
pub fn define_log_levels(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
use syn::parse::Parser;
let parser = syn::punctuated::Punctuated::<syn::Ident, syn::Token![,]>::parse_terminated;
let args = parser.parse(ts).unwrap();
let names: Vec<_> = args.into_iter().map(|ident| ident.to_string().to_snake_case()).collect();
let position_in_args = |name| {
names.iter().position(|arg| &name == arg).unwrap_or_else(|| {
let error = "Environment variable value must correspond to a defined log level";
panic!("{error}. Found: {name:?}, expected one of: {names:?}.")
})
};
let level_configuration = LEVEL_CONFIGURATION_ENV_VARS
.map(|var| std::env::var(var).ok().map(position_in_args).unwrap_or_default());
logging_api(names, level_configuration)
}
struct LevelConfiguration<T> {
max_enabled: T,
max_uncollapsed: T,
}
map_fns!(LevelConfiguration, [max_enabled, max_uncollapsed]);
// =====================
// === Top-level API ===
// =====================
fn logging_api(
level_names: impl IntoIterator<Item = String>,
config: LevelConfiguration<usize>,
) -> proc_macro::TokenStream {
let global_logger_ident = ident("GlobalLogger");
let global_logger_path = ident_to_path(global_logger_ident.clone());
let levels = levels(level_names, &config, global_logger_path);
let api: Api = [
span_trait(),
logger_trait(&levels),
span_api(&levels),
event_api(&levels),
global_logger(global_logger_ident, &levels),
]
.into_iter()
.collect();
api.into_library().into()
}
// === Information used to construct level-specific interfaces ===
struct Level {
// Intrinsic properties of a level:
name: String,
enabled: bool,
uncollapsed: bool,
// Identifiers for API cross-references:
trait_methods: LoggerMethods<syn::Ident>,
global_logger_methods: LoggerMethods<syn::Path>,
}
impl Level {
fn new(name: String, enabled: bool, uncollapsed: bool, global_logger_path: &syn::Path) -> Self {
let trait_methods = trait_methods(&name);
let global_logger_methods =
trait_methods.clone().map(|x| qualified(global_logger_path.clone(), x));
Level { global_logger_methods, trait_methods, enabled, uncollapsed, name }
}
}
fn levels(
names: impl IntoIterator<Item = String>,
config: &LevelConfiguration<usize>,
global_logger_path: syn::Path,
) -> Vec<Level> {
let enabled = |i| config.max_enabled >= i;
let uncollapsed = |i| config.max_uncollapsed >= i;
let level = |(i, name)| Level::new(name, enabled(i), uncollapsed(i), &global_logger_path);
names.into_iter().enumerate().map(level).collect()
}
// ==============================
// === Representation of APIs ===
// ==============================
#[derive(Default)]
struct Api {
implementation: proc_macro2::TokenStream,
exports: Vec<Export>,
}
impl Api {
fn extend(&mut self, mut other: Self) {
self.implementation.extend(other.implementation);
self.exports.append(&mut other.exports);
}
fn into_library(self) -> proc_macro2::TokenStream {
let reexport = |name: &syn::Ident| {
quote! { pub use crate::internal::#name; }
};
let prelude: proc_macro2::TokenStream =
self.exports.iter().filter(|&e| e.prelude).map(|e| reexport(&e.ident)).collect();
let exports: proc_macro2::TokenStream =
self.exports.iter().map(|e| reexport(&e.ident)).collect();
let implementation = self.implementation;
quote! {
/// Exports, intended to be used by glob-import.
pub mod prelude {
#prelude
}
#exports
/// Low-level interface, used by macro implementations.
pub mod internal {
#implementation
}
}
}
}
impl FromIterator<Api> for Api {
fn from_iter<T: IntoIterator<Item = Api>>(iter: T) -> Self {
let mut collected: Api = Default::default();
iter.into_iter().for_each(|api| collected.extend(api));
collected
}
}
impl From<proc_macro2::TokenStream> for Api {
fn from(implementation: proc_macro2::TokenStream) -> Self {
Self { implementation, ..Default::default() }
}
}
struct Export {
ident: syn::Ident,
prelude: bool,
}
impl Export {
fn prelude(ident: syn::Ident) -> Self {
Self { ident, prelude: true }
}
}
// =====================
// === LogSpan trait ===
// =====================
fn span_trait() -> Api {
let trait_name = ident("LogSpan");
let implementation = quote! {
/// Identifies a location in the source that may be traced in the logs.
pub trait #trait_name {
/// Log entry into the span, run the given closure, and log exit.
#[inline(always)]
fn in_scope<F, T>(self, f: F) -> T where Self: Sized, F: FnOnce() -> T {
let _scope = self.entered();
f()
}
/// Log entry into the span, and log exit when the returned value is dropped.
#[inline(always)]
fn entered(self) -> Entered<Self> where Self: Sized {
Entered::new(self)
}
#[allow(missing_docs)]
fn _enter(&self);
#[allow(missing_docs)]
fn _exit(&self);
}
/// RAII guard that enters a span when created and exits when dropped.
pub struct Entered<S: #trait_name>(S);
impl<S: #trait_name> Entered<S> {
#[allow(missing_docs)]
pub fn new(span: S) -> Self {
span._enter();
Self(span)
}
}
impl<S: #trait_name> Drop for Entered<S> {
fn drop(&mut self) {
self.0._exit();
}
}
};
let exports = vec![Export::prelude(trait_name)];
Api { implementation, exports }
}
// ====================
// === Logger trait ===
// ====================
fn logger_trait(levels: impl IntoIterator<Item = &Level>) -> Api {
let mut methods = proc_macro2::TokenStream::new();
for level in levels {
level.trait_methods.signatures().for_each(|x| methods.extend(x))
}
(quote! {
/// A type that serves as a destination for logging.
pub trait Logger {
#methods
}
})
.into()
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
struct LoggerMethods<T = proc_macro2::TokenStream> {
emit_fn: T,
enter_fn: T,
exit_fn: T,
}
map_fns!(LoggerMethods, [emit_fn, enter_fn, exit_fn]);
impl LoggerMethods<syn::Ident> {
fn signatures(&self) -> LoggerMethods {
let LoggerMethods { emit_fn, enter_fn, exit_fn } = self;
LoggerMethods {
emit_fn: quote! { #[allow(missing_docs)] fn #emit_fn(span: &str); },
enter_fn: quote! { #[allow(missing_docs)] fn #enter_fn(span: &str); },
exit_fn: quote! { #[allow(missing_docs)] fn #exit_fn(); },
}
}
fn with_bodies(
&self,
bodies: &LoggerMethods<proc_macro2::TokenStream>,
) -> LoggerMethods<proc_macro2::TokenStream> {
let LoggerMethods { emit_fn, enter_fn, exit_fn } = self;
let LoggerMethods { emit_fn: emit_body, enter_fn: enter_body, exit_fn: exit_body } = bodies;
LoggerMethods {
emit_fn: quote! { #[inline] fn #emit_fn(span: &str) { #emit_body } },
enter_fn: quote! { #[inline] fn #enter_fn(span: &str) { #enter_body } },
exit_fn: quote! { #[inline] fn #exit_fn() { #exit_body } },
}
}
}
fn trait_methods(level: &str) -> LoggerMethods<syn::Ident> {
(LoggerMethods {
emit_fn: format!("emit_{level}"),
enter_fn: format!("enter_{level}"),
exit_fn: format!("exit_{level}"),
})
.as_ref()
.map(ident)
}
// =================
// === Event API ===
// =================
fn event_api(levels: impl IntoIterator<Item = &Level>) -> Api {
levels
.into_iter()
.map(|level| event_api_for_level(&level.name, &level.global_logger_methods, level.enabled))
.collect()
}
fn event_api_for_level(level: &str, methods: &LoggerMethods<syn::Path>, enabled: bool) -> Api {
let event_macro = ident(level);
let emit_fn = &methods.emit_fn;
let level_tag = level.to_screaming_snake_case();
let body = if enabled {
quote! {
use $crate::internal::Logger;
$crate::internal::#emit_fn(
&format!(
"[{}] {}:{} {}",
#level_tag,
file!(),
line!(),
format_args!($($args)*)
));
}
} else {
quote! {
let _unused_at_this_log_level = format_args!($($args)*);
}
};
let implementation = quote! {
/// Emit a log message, if the log-level is enabled.
#[macro_export]
macro_rules! #event_macro {
($($args:tt)*) => {{ #body }};
}
};
Api { implementation, ..Default::default() }
}
// ================
// === Span API ===
// ================
fn span_api(levels: impl IntoIterator<Item = &Level>) -> Api {
levels
.into_iter()
.map(|level| span_api_for_level(&level.name, &level.global_logger_methods, level.enabled))
.collect()
}
fn span_api_for_level(level: &str, methods: &LoggerMethods<syn::Path>, enabled: bool) -> Api {
let object_name = ident(level.to_pascal_case());
let macro_name = ident(format!("{level}_span"));
let object_contents = enabled.then_some(quote! { pub String }).unwrap_or_default();
let enter_fn = &methods.enter_fn;
let exit_fn = &methods.exit_fn;
let enter_impl = enabled
.then_some(quote! {
#enter_fn(&self.0);
})
.unwrap_or_default();
let exit_impl = enabled
.then_some(quote! {
#exit_fn();
})
.unwrap_or_default();
let level_tag = level.to_screaming_snake_case();
let creation_body = if enabled {
quote! {
$crate::internal::#object_name(
format!(
"[{}] {}:{} {}",
#level_tag,
file!(),
line!(),
format_args!($($args)*)
)
)
}
} else {
quote! {
let _unused_at_this_log_level = format_args!($($args)*);
$crate::internal::#object_name()
}
};
let implementation = quote! {
/// Refers to a region in the source code that may have associated logging.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct #object_name(#object_contents);
impl LogSpan for #object_name {
#[inline(always)]
fn _enter(&self) {
#enter_impl
}
#[inline(always)]
fn _exit(&self) {
#exit_impl
}
}
/// Create an object that identifies a location in the source code for logging purposes.
#[macro_export]
macro_rules! #macro_name {
($($args:tt)*) => {{
#creation_body
}}
}
};
implementation.into()
}
// =========================
// === Global Logger API ===
// =========================
fn global_logger(logger: syn::Ident, levels: impl IntoIterator<Item = &Level>) -> Api {
let console_logger = ident("NativeConsole");
let web_logger = ident("WebConsole");
let mut web_logger_methods = proc_macro2::TokenStream::new();
let mut console_logger_methods = proc_macro2::TokenStream::new();
let mut web_level = WEB_LOG_LEVELS.iter().copied().fuse();
let mut web = web_level.next().unwrap();
for level in levels {
level
.trait_methods
.with_bodies(&level.enabled.then(console_logger_impl).unwrap_or_default())
.for_each(|x| console_logger_methods.extend(x));
level
.trait_methods
.with_bodies(&level.enabled.then_some(web_logger_impl(web, level)).unwrap_or_default())
.for_each(|x| web_logger_methods.extend(x));
web = web_level.next().unwrap_or(web);
}
(quote! {
#[cfg(target_arch = "wasm32")]
/// The currently-enabled global logger.
pub type #logger = web::#web_logger;
#[cfg(not(target_arch = "wasm32"))]
/// The currently-enabled global logger.
pub type #logger = native::#console_logger;
/// Logging support for wasm environments.
#[cfg(target_arch = "wasm32")]
pub mod web {
use super::*;
/// A [`Logger`] that emits messages to the Console Web API.
pub struct #web_logger;
impl Logger for #web_logger {
#web_logger_methods
}
}
/// Logging support for non-wasm environments.
#[cfg(not(target_arch = "wasm32"))]
pub mod native {
use super::*;
/// A [`Logger`] that emits messages to the native console.
pub struct #console_logger;
thread_local! {
static CONSOLE_INDENT: core::cell::Cell<usize> = core::cell::Cell::new(0);
}
impl Logger for #console_logger {
#console_logger_methods
}
}
})
.into()
}
// =============================
// === Native console logger ===
// =============================
fn console_logger_impl() -> LoggerMethods {
LoggerMethods {
emit_fn: quote! {
let indent = CONSOLE_INDENT.get();
println!("{:indent$}{}", "", span, indent=indent*4);
},
enter_fn: quote! {
let indent = CONSOLE_INDENT.get();
println!("{:indent$}{}", "", span, indent=indent*4);
CONSOLE_INDENT.set(indent + 1);
},
exit_fn: quote! {
let indent = CONSOLE_INDENT.get();
CONSOLE_INDENT.set(indent - 1);
},
}
}
// ======================
// === Web API logger ===
// ======================
fn web_logger_impl(web_level: &str, level: &Level) -> LoggerMethods {
let event_fn = ident(format!("{web_level}_1"));
let group_fn = ident(if level.uncollapsed { "group_1" } else { "group_collapsed_1" });
LoggerMethods {
emit_fn: quote! {
web_sys::console::#event_fn(&span.into());
},
enter_fn: quote! {
web_sys::console::#group_fn(&span.into());
},
exit_fn: quote! {
web_sys::console::group_end();
},
}
}
// ============================
// === Syn-building helpers ===
// ============================
fn qualified(mut path: syn::Path, name: syn::Ident) -> syn::Path {
path.segments.push(path_segment(name));
path
}
fn path_segment(ident: syn::Ident) -> syn::PathSegment {
syn::PathSegment { ident, arguments: Default::default() }
}
fn ident(name: impl AsRef<str>) -> syn::Ident {
syn::Ident::new(name.as_ref(), proc_macro2::Span::call_site())
}
fn ident_to_path(segment: syn::Ident) -> syn::Path {
syn::Path {
leading_colon: Default::default(),
segments: std::iter::once(path_segment(segment)).collect(),
}
}

View File

@ -1,10 +0,0 @@
//! High-performance logging library.
// === Non-Standard Linter Configuration ===
#![deny(unconditional_recursion)]
#![warn(missing_docs)]
#![warn(trivial_casts)]
enso_logging_macros::define_log_levels![Error, Warn, Info, Debug, Trace];

View File

@ -9,11 +9,14 @@ homepage = "https://github.com/enso-org/enso"
repository = "https://github.com/enso-org/enso"
license-file = "../../LICENSE"
[features]
debug = ["dep:enso-parser-syntax-tree-visitor"]
[dependencies]
enso-prelude = { path = "../prelude" }
enso-reflect = { path = "../reflect" }
enso-data-structures = { path = "../data-structures" }
enso-parser-syntax-tree-visitor = { path = "src/syntax/tree/visitor" }
enso-parser-syntax-tree-visitor = { path = "src/syntax/tree/visitor", optional = true }
paste = { version = "1.0" }
serde = { workspace = true }
serde_json = { workspace = true }

View File

@ -10,7 +10,7 @@ repository = "https://github.com/enso-org/enso"
license-file = "../../LICENSE"
[dependencies]
enso-parser = { path = "../" }
enso-parser = { path = "../", features = ["debug"] }
enso-metamodel = { path = "../../metamodel", features = ["rust"] }
enso-metamodel-lexpr = { path = "../../metamodel/lexpr" }
enso-reflect = { path = "../../reflect" }

View File

@ -1,4 +1,6 @@
//! Tests for [`enso_parser`].
//! Parses Enso sources and reports any syntax errors, while performing internal consistency checks.
//! Source files may be specified as command line arguments; if none a provided, source code will be
//! read from standard input.
// === Non-Standard Linter Configuration ===
#![allow(clippy::option_map_unit_fn)]
@ -38,7 +40,7 @@ fn check_file(path: &str, mut code: &str, parser: &mut enso_parser::Parser) {
}
let ast = parser.run(code);
let errors = RefCell::new(vec![]);
ast.map(|tree| {
ast.visit_trees(|tree| {
if let enso_parser::syntax::tree::Variant::Invalid(err) = &*tree.variant {
let error = format!("{}: {}", err.error.message, tree.code());
errors.borrow_mut().push((error, tree.span.clone()));

View File

@ -1702,7 +1702,7 @@ impl Errors {
let ast = parse(code);
expect_tree_representing_code(code, &ast);
let errors = core::cell::Cell::new(Errors::default());
ast.map(|tree| match &*tree.variant {
ast.visit_trees(|tree| match &*tree.variant {
enso_parser::syntax::tree::Variant::Invalid(_) => {
errors.update(|e| Self { invalid_node: true, ..e });
}

View File

@ -18,6 +18,7 @@ serde = { workspace = true }
[dev-dependencies]
enso-metamodel = { path = "../../metamodel", features = ["rust"] }
enso-metamodel-lexpr = { path = "../../metamodel/lexpr" }
enso-parser = { path = "..", features = ["debug"] }
lexpr = "0.2.6"
pretty_assertions = "1.4"

View File

@ -246,9 +246,7 @@ impl<'a, L: Location> Span<'a, L> {
}
Some(_) => break,
None => {
let unexpected_condition = "Internal error: Expected greater indent level.";
self.warn(unexpected_condition);
warn!("{unexpected_condition}");
self.warn("Internal error: Expected greater indent level.");
break;
}
}

View File

@ -48,7 +48,7 @@ fn extract_docs(_filename: &str, mut code: &str) -> Vec<String> {
}
let ast = enso_parser::Parser::new().run(code);
let docs = RefCell::new(vec![]);
ast.map(|tree| match &*tree.variant {
ast.visit_trees(|tree| match &*tree.variant {
enso_parser::syntax::tree::Variant::Documented(doc) => {
docs.borrow_mut().push(doc.documentation.clone());
}

View File

@ -52,7 +52,7 @@ pub extern "system" fn Java_org_enso_syntax2_Parser_parseInput(
if let Some((meta_, code_)) = enso_parser::metadata::parse(input) {
match meta_ {
Ok(meta_) => meta = Some(meta_),
Err(e) => error!("Ignoring invalid metadata: {e}."),
Err(e) => eprintln!("Ignoring invalid metadata: {e}."),
}
code = code_;
}

View File

@ -302,19 +302,16 @@ impl<'s> Resolver<'s> {
if self.macros.len() > self.macro_scope_start() {
let current_macro = self.macros.last_mut().unwrap();
if let Some(subsegments) = current_macro.possible_next_segments.get(repr) {
trace!("Entering next segment of the current macro.");
let mut new_match_tree =
Self::move_to_next_segment(&mut current_macro.matched_macro_def, subsegments);
mem::swap(&mut new_match_tree, &mut current_macro.possible_next_segments);
return Step::StartSegment(token);
} else if let Some(popped) = self.pop_macro_stack_if_reserved(repr) {
trace!("Next token reserved by parent macro. Resolving current macro.");
self.resolve(popped);
return Step::MacroStackPop(token.into());
}
}
if let Some(segments) = root_macro_map.get(repr, context) {
trace!("Starting a new nested macro resolution.");
let mut matched_macro_def = default();
let segments_start = self.segments.len();
let new_macro = PartiallyMatchedMacro {
@ -328,7 +325,6 @@ impl<'s> Resolver<'s> {
self.macros.push(new_macro);
Step::StartSegment(token)
} else {
trace!("Consuming token as current segment body.");
Step::NormalToken(token.into())
}
}

View File

@ -6,6 +6,7 @@ use crate::syntax::*;
use crate::span_builder;
#[cfg(feature = "debug")]
use enso_parser_syntax_tree_visitor::Visitor;
@ -61,7 +62,8 @@ impl<'s> Default for Tree<'s> {
macro_rules! with_ast_definition { ($f:ident ($($args:tt)*)) => { $f! { $($args)*
/// [`Tree`] variants definition. See its docs to learn more.
#[tagged_enum]
#[derive(Clone, Eq, PartialEq, Visitor, Serialize, Reflect, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Clone, Eq, PartialEq, Serialize, Reflect, Deserialize)]
#[allow(clippy::large_enum_variant)] // Inefficient. Will be fixed in #182878443.
#[tagged_enum(apply_attributes_to = "variants")]
#[reflect(inline)]
@ -383,7 +385,8 @@ with_ast_definition!(generate_ast_definition());
// === Invalid ===
/// Error of parsing attached to an [`Tree`] node.
#[derive(Clone, Debug, Eq, PartialEq, Visitor, Serialize, Reflect, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Reflect, Deserialize)]
#[allow(missing_docs)]
#[reflect(transparent)]
#[serde(from = "crate::serialization::Error")]
@ -417,7 +420,8 @@ impl<'s> span::Builder<'s> for Error {
// === Argument blocks ===
/// An argument specification on its own line.
#[derive(Clone, Debug, Eq, PartialEq, Visitor, Serialize, Reflect, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Reflect, Deserialize)]
pub struct ArgumentDefinitionLine<'s> {
/// The token beginning the line.
pub newline: token::Newline<'s>,
@ -435,7 +439,8 @@ impl<'s> span::Builder<'s> for ArgumentDefinitionLine<'s> {
// === Text literals ===
/// A component of a text literal, within the quotation marks.
#[derive(Clone, Debug, Eq, PartialEq, Visitor, Serialize, Reflect, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Reflect, Deserialize)]
pub enum TextElement<'s> {
/// The text content of the literal. If it is multiline, the offset information may contain
/// part of the content, after trimming appropriately.
@ -481,7 +486,8 @@ impl<'s> span::Builder<'s> for TextElement<'s> {
// === Documentation ===
/// A documentation comment.
#[derive(Debug, Clone, Eq, PartialEq, Visitor, Serialize, Reflect, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Reflect, Deserialize)]
pub struct DocComment<'s> {
/// The comment-initiating token.
pub open: token::TextStart<'s>,
@ -522,7 +528,8 @@ impl<'s> span::Builder<'s> for DocComment<'s> {
// === Number literals ===
#[derive(Clone, Debug, Eq, PartialEq, Visitor, Serialize, Reflect, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Reflect, Deserialize)]
#[allow(missing_docs)]
pub struct FractionalDigits<'s> {
/// The dot operator.
@ -541,7 +548,8 @@ impl<'s> span::Builder<'s> for FractionalDigits<'s> {
// === Functions ===
/// A function argument definition.
#[derive(Clone, Debug, Eq, PartialEq, Visitor, Serialize, Reflect, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Reflect, Deserialize)]
pub struct ArgumentDefinition<'s> {
/// Opening parenthesis (outer).
pub open: Option<token::OpenSymbol<'s>>,
@ -576,7 +584,8 @@ impl<'s> span::Builder<'s> for ArgumentDefinition<'s> {
}
/// A default value specification in a function argument definition.
#[derive(Clone, Debug, Eq, PartialEq, Visitor, Serialize, Reflect, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Reflect, Deserialize)]
pub struct ArgumentDefault<'s> {
/// The `=` token.
pub equals: token::Operator<'s>,
@ -591,7 +600,8 @@ impl<'s> span::Builder<'s> for ArgumentDefault<'s> {
}
/// A type ascribed to an argument definition.
#[derive(Clone, Debug, Eq, PartialEq, Visitor, Serialize, Reflect, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Reflect, Deserialize)]
pub struct ArgumentType<'s> {
/// The `:` token.
pub operator: token::Operator<'s>,
@ -607,7 +617,8 @@ impl<'s> span::Builder<'s> for ArgumentType<'s> {
}
/// A function return type specification.
#[derive(Clone, Debug, Eq, PartialEq, Visitor, Serialize, Reflect, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Reflect, Deserialize)]
pub struct ReturnSpecification<'s> {
/// The `->` operator.
pub arrow: token::Operator<'s>,
@ -626,7 +637,8 @@ impl<'s> span::Builder<'s> for ReturnSpecification<'s> {
// === CaseOf ===
/// A line that may contain a case-expression in a case-of expression.
#[derive(Clone, Debug, Default, Eq, PartialEq, Visitor, Serialize, Reflect, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Reflect, Deserialize)]
pub struct CaseLine<'s> {
/// The token beginning the line. This will always be present, unless the first case-expression
/// is on the same line as the initial case-of.
@ -653,7 +665,8 @@ impl<'s> span::Builder<'s> for CaseLine<'s> {
}
/// A case-expression in a case-of expression.
#[derive(Clone, Debug, Default, Eq, PartialEq, Visitor, Serialize, Reflect, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Reflect, Deserialize)]
pub struct Case<'s> {
/// Documentation, if present.
pub documentation: Option<DocComment<'s>>,
@ -692,7 +705,8 @@ impl<'s> span::Builder<'s> for Case<'s> {
pub type OperatorOrError<'s> = Result<token::Operator<'s>, MultipleOperatorError<'s>>;
/// Error indicating multiple operators found next to each other, like `a + * b`.
#[derive(Clone, Debug, Eq, PartialEq, Visitor, Serialize, Reflect, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Reflect, Deserialize)]
#[allow(missing_docs)]
pub struct MultipleOperatorError<'s> {
pub operators: NonEmptyVec<token::Operator<'s>>,
@ -731,7 +745,8 @@ impl<'s> NonEmptyOperatorSequence<'s> for OperatorOrError<'s> {
// === MultiSegmentApp ===
/// A segment of [`MultiSegmentApp`], like `if cond` in the `if cond then ok else fail` expression.
#[derive(Clone, Debug, Eq, PartialEq, Visitor, Serialize, Reflect, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Reflect, Deserialize)]
#[allow(missing_docs)]
pub struct MultiSegmentAppSegment<'s> {
pub header: Token<'s>,
@ -748,7 +763,8 @@ impl<'s> span::Builder<'s> for MultiSegmentAppSegment<'s> {
// === Array and Tuple ===
/// A node following an operator.
#[derive(Clone, Debug, Eq, PartialEq, Visitor, Serialize, Reflect, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Reflect, Deserialize)]
pub struct OperatorDelimitedTree<'s> {
/// The delimiting operator.
pub operator: token::Operator<'s>,
@ -1098,25 +1114,12 @@ impl<'s> From<token::Ident<'s>> for Tree<'s> {
/// could move to it as soon as this error gets resolved:
/// https://github.com/rust-lang/rust/issues/96634.
#[allow(missing_docs)]
pub trait Visitor {
fn before_visiting_children(&mut self) {}
fn after_visiting_children(&mut self) {}
}
/// The visitor trait allowing for [`Tree`] nodes traversal.
#[allow(missing_docs)]
pub trait TreeVisitor<'s, 'a>: Visitor {
fn visit(&mut self, ast: &'a Tree<'s>) -> bool;
}
/// The visitor trait allowing for [`Span`] traversal.
#[allow(missing_docs)]
pub trait SpanVisitor<'s, 'a>: Visitor {
fn visit(&mut self, ast: span::Ref<'s, 'a>) -> bool;
}
#[cfg(feature = "debug")]
pub trait Visitor {}
/// The visitor trait allowing for [`Item`] traversal.
#[allow(missing_docs)]
#[cfg(feature = "debug")]
pub trait ItemVisitor<'s, 'a>: Visitor {
fn visit_item(&mut self, ast: item::Ref<'s, 'a>) -> bool;
}
@ -1129,11 +1132,13 @@ macro_rules! define_visitor {
$visitable:ident
) => {
/// The visitable trait. See documentation of [`define_visitor`] to learn more.
#[cfg(feature = "debug")]
#[allow(missing_docs)]
pub trait $visitable<'s, 'a> {
fn $visit<V: $visitor<'s, 'a>>(&'a self, _visitor: &mut V) {}
}
#[cfg(feature = "debug")]
impl<'s, 'a, T: $visitable<'s, 'a>> $visitable<'s, 'a> for Option<T> {
fn $visit<V: $visitor<'s, 'a>>(&'a self, visitor: &mut V) {
if let Some(elem) = self {
@ -1142,6 +1147,7 @@ macro_rules! define_visitor {
}
}
#[cfg(feature = "debug")]
impl<'s, 'a, T: $visitable<'s, 'a>, E: $visitable<'s, 'a>> $visitable<'s, 'a>
for Result<T, E>
{
@ -1153,12 +1159,14 @@ macro_rules! define_visitor {
}
}
#[cfg(feature = "debug")]
impl<'s, 'a, T: $visitable<'s, 'a>> $visitable<'s, 'a> for Vec<T> {
fn $visit<V: $visitor<'s, 'a>>(&'a self, visitor: &mut V) {
self.iter().map(|t| $visitable::$visit(t, visitor)).for_each(drop);
}
}
#[cfg(feature = "debug")]
impl<'s, 'a, T: $visitable<'s, 'a>> $visitable<'s, 'a> for NonEmptyVec<T> {
fn $visit<V: $visitor<'s, 'a>>(&'a self, visitor: &mut V) {
self.iter().map(|t| $visitable::$visit(t, visitor)).for_each(drop);
@ -1167,33 +1175,14 @@ macro_rules! define_visitor {
};
}
macro_rules! define_visitor_for_tokens {
(
$(#$kind_meta:tt)*
pub enum $kind:ident {
$(
$(#$variant_meta:tt)*
$variant:ident $({$($args:tt)*})?
),* $(,)?
}
) => {
impl<'s, 'a> TreeVisitable<'s, 'a> for token::$kind {}
};
}
define_visitor!(Tree, visit, TreeVisitor, TreeVisitable);
define_visitor!(Span, visit_span, SpanVisitor, SpanVisitable);
define_visitor!(Item, visit_item, ItemVisitor, ItemVisitable);
crate::with_token_definition!(define_visitor_for_tokens());
// === Trait Implementations for Simple Leaf Types ===
macro_rules! spanless_leaf_impls {
($ty:ty) => {
impl<'s, 'a> TreeVisitable<'s, 'a> for $ty {}
impl<'a, 's> SpanVisitable<'s, 'a> for $ty {}
#[cfg(feature = "debug")]
impl<'a, 's> ItemVisitable<'s, 'a> for $ty {}
impl<'s> span::Builder<'s> for $ty {
fn add_to_span(&mut self, span: Span<'s>) -> Span<'s> {
@ -1209,45 +1198,10 @@ spanless_leaf_impls!(VisibleOffset);
spanless_leaf_impls!(Cow<'static, str>);
// === TreeVisitable special cases ===
impl<'s, 'a> TreeVisitable<'s, 'a> for Tree<'s> {
fn visit<V: TreeVisitor<'s, 'a>>(&'a self, visitor: &mut V) {
if visitor.visit(self) {
self.variant.visit(visitor)
}
}
}
impl<'s, 'a, T> TreeVisitable<'s, 'a> for Token<'s, T> {}
// === SpanVisitable special cases ===
impl<'s, 'a> SpanVisitable<'s, 'a> for Tree<'s> {
fn visit_span<V: SpanVisitor<'s, 'a>>(&'a self, visitor: &mut V) {
if visitor.visit(span::Ref {
left_offset: &self.span.left_offset,
code_length: self.span.code_length,
}) {
self.variant.visit_span(visitor)
}
}
}
impl<'a, 's, T> SpanVisitable<'s, 'a> for Token<'s, T> {
fn visit_span<V: SpanVisitor<'s, 'a>>(&'a self, visitor: &mut V) {
let code_length = self.code.length();
visitor.visit(span::Ref { left_offset: &self.left_offset, code_length });
}
}
// === ItemVisitable special cases ===
#[cfg(feature = "debug")]
impl<'s, 'a> ItemVisitable<'s, 'a> for Tree<'s> {
fn visit_item<V: ItemVisitor<'s, 'a>>(&'a self, visitor: &mut V) {
if visitor.visit_item(item::Ref::Tree(self)) {
@ -1256,6 +1210,7 @@ impl<'s, 'a> ItemVisitable<'s, 'a> for Tree<'s> {
}
}
#[cfg(feature = "debug")]
impl<'s: 'a, 'a, T: 'a> ItemVisitable<'s, 'a> for Token<'s, T>
where &'a Token<'s, T>: Into<token::Ref<'s, 'a>>
{
@ -1271,13 +1226,16 @@ where &'a Token<'s, T>: Into<token::Ref<'s, 'a>>
// ==========================
/// A visitor collecting code representation of AST nodes.
#[cfg(feature = "debug")]
#[derive(Debug, Default)]
#[allow(missing_docs)]
struct CodePrinterVisitor {
pub code: String,
}
#[cfg(feature = "debug")]
impl Visitor for CodePrinterVisitor {}
#[cfg(feature = "debug")]
impl<'s, 'a> ItemVisitor<'s, 'a> for CodePrinterVisitor {
fn visit_item(&mut self, item: item::Ref<'s, 'a>) -> bool {
match item {
@ -1291,6 +1249,7 @@ impl<'s, 'a> ItemVisitor<'s, 'a> for CodePrinterVisitor {
}
}
#[cfg(feature = "debug")]
impl<'s> Tree<'s> {
/// Code generator of this AST.
pub fn code(&self) -> String {
@ -1309,35 +1268,11 @@ impl<'s> Tree<'s> {
// =================
// === FnVisitor ===
// =================
/// A visitor allowing running a function on every [`Tree`] node.
#[derive(Debug, Default)]
#[allow(missing_docs)]
pub struct FnVisitor<F>(pub F);
impl<F> Visitor for FnVisitor<F> {}
impl<'s: 'a, 'a, T, F: Fn(&'a Tree<'s>) -> T> TreeVisitor<'s, 'a> for FnVisitor<F> {
fn visit(&mut self, ast: &'a Tree<'s>) -> bool {
(self.0)(ast);
true
}
}
impl<'s> Tree<'s> {
/// Map the provided function over each [`Tree`] node. The function results will be discarded.
pub fn map<T>(&self, f: impl Fn(&Tree<'s>) -> T) {
let mut visitor = FnVisitor(f);
self.visit(&mut visitor);
}
}
// =====================
// === ItemFnVisitor ===
// =====================
#[cfg(feature = "debug")]
impl<'s> Tree<'s> {
/// Apply the provided function to each [`Token`] or [`Tree`] that is a child of the node.
pub fn visit_items<F>(&self, f: F)
@ -1356,4 +1291,24 @@ impl<'s> Tree<'s> {
}
self.variant.visit_item(&mut ItemFnVisitor { f });
}
/// Apply the provided function recursively to each [`Tree`] that is a descendant of the node.
pub fn visit_trees<F>(&self, f: F)
where F: for<'a> FnMut(&'a Tree<'s>) {
struct ItemFnVisitor<F> {
f: F,
}
impl<F> Visitor for ItemFnVisitor<F> {}
impl<'a, 's: 'a, F> ItemVisitor<'s, 'a> for ItemFnVisitor<F>
where F: FnMut(&'a Tree<'s>)
{
fn visit_item(&mut self, item: item::Ref<'s, 'a>) -> bool {
if let item::Ref::Tree(tree) = item {
(self.f)(tree);
}
true
}
}
self.variant.visit_item(&mut ItemFnVisitor { f });
}
}

View File

@ -9,7 +9,8 @@ use crate::syntax::tree::*;
// =============
/// A line of code.
#[derive(Debug, Clone, PartialEq, Eq, Visitor, Reflect, Serialize, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Debug, Clone, PartialEq, Eq, Reflect, Serialize, Deserialize)]
pub struct Line<'s> {
/// Token ending the previous line, if any.
pub newline: token::Newline<'s>,
@ -196,7 +197,8 @@ impl<'s> From<Prefix<'s>> for Tree<'s> {
// ======================
/// The content of a line in an operator block.
#[derive(Debug, Clone, PartialEq, Eq, Visitor, Reflect, Serialize, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Debug, Clone, PartialEq, Eq, Reflect, Serialize, Deserialize)]
pub struct OperatorBlockExpression<'s> {
/// The operator at the beginning of the line.
pub operator: OperatorOrError<'s>,
@ -234,7 +236,8 @@ impl<'s> span::Builder<'s> for OperatorBlockExpression<'s> {
// === Operator block lines ====
/// A line in an operator block.
#[derive(Debug, Clone, PartialEq, Eq, Visitor, Reflect, Serialize, Deserialize)]
#[cfg_attr(feature = "debug", derive(Visitor))]
#[derive(Debug, Clone, PartialEq, Eq, Reflect, Serialize, Deserialize)]
pub struct OperatorLine<'s> {
/// Token ending the previous line, if any.
pub newline: token::Newline<'s>,

View File

@ -32,8 +32,8 @@ use syn::Variant;
/// ======================
use quote::ToTokens;
/// Implements [`TreeVisitable`], [`TreeVisitableMut`], [`SpanVisitable`], and [`SpanVisitableMut`].
/// These traits are defined in the [`crate::ast`] module. Macros in this module hardcode the names
/// Implements [`ItemVisitable`].
/// This trait is defined in the [`crate::ast`] module. Macros in this module hardcode the names
/// of the traits and are not implemented in a generic way because the current Rust implementation
/// does not understand generic definition. See the [`crate::ast`] module to learn more about the
/// design and the Rust compiler issue.
@ -42,8 +42,6 @@ pub fn derive_visitor(input: proc_macro::TokenStream) -> proc_macro::TokenStream
let decl = syn::parse_macro_input!(input as DeriveInput);
let ident = &decl.ident;
let (impl_generics, ty_generics, _inherent_where_clause_opt) = &decl.generics.split_for_impl();
let body = gen_body(quote!(TreeVisitable::visit), &decl.data, false);
let body_span = gen_body(quote!(SpanVisitable::visit_span), &decl.data, false);
let body_item = gen_body(quote!(ItemVisitable::visit_item), &decl.data, false);
let impl_generics_vec: Vec<_> = impl_generics.to_token_stream().into_iter().collect();
@ -61,27 +59,9 @@ pub fn derive_visitor(input: proc_macro::TokenStream) -> proc_macro::TokenStream
let impl_generics = quote!(<#impl_generics 'a>);
let output = quote! {
impl #impl_generics TreeVisitable #impl_generics for #ident #ty_generics {
fn visit<T: TreeVisitor #impl_generics>(&'a self, visitor:&mut T) {
visitor.before_visiting_children();
#body
visitor.after_visiting_children();
}
}
impl #impl_generics SpanVisitable #impl_generics for #ident #ty_generics {
fn visit_span<T: SpanVisitor #impl_generics>(&'a self, visitor:&mut T) {
visitor.before_visiting_children();
#body_span
visitor.after_visiting_children();
}
}
impl #impl_generics ItemVisitable #impl_generics for #ident #ty_generics {
fn visit_item<T: ItemVisitor #impl_generics>(&'a self, visitor:&mut T) {
visitor.before_visiting_children();
#body_item
visitor.after_visiting_children();
}
}
};

View File

@ -16,7 +16,6 @@ publish = true
crate-type = ["rlib"]
[dependencies]
enso-logging = { path = "../logging" }
enso-reflect = { path = "../reflect" }
enso-zst = { path = "../zst" }
boolinator = { workspace = true }

View File

@ -22,21 +22,3 @@ pub use derive_more::*;
pub use enso_reflect::prelude::*;
pub use serde::Deserialize;
pub use serde::Serialize;
// ===============
// === Logging ===
// ===============
pub use enso_logging::debug;
pub use enso_logging::debug_span;
pub use enso_logging::error;
pub use enso_logging::error_span;
pub use enso_logging::info;
pub use enso_logging::info_span;
pub use enso_logging::prelude::*;
pub use enso_logging::trace;
pub use enso_logging::trace_span;
pub use enso_logging::warn;
pub use enso_logging::warn_span;