From 26a94cc40ef3137b7005664878185a9f1c1e45a8 Mon Sep 17 00:00:00 2001 From: Arend van Beelen jr Date: Fri, 3 May 2024 10:45:31 +0200 Subject: [PATCH] chore: add Grit node compilers (#2689) --- Cargo.lock | 12 +- Cargo.toml | 4 + crates/biome_grit_patterns/Cargo.toml | 8 +- crates/biome_grit_patterns/src/diagnostics.rs | 22 + crates/biome_grit_patterns/src/errors.rs | 36 +- .../biome_grit_patterns/src/grit_binding.rs | 28 +- .../src/grit_code_snippet.rs | 5 +- .../biome_grit_patterns/src/grit_context.rs | 6 +- crates/biome_grit_patterns/src/grit_file.rs | 4 +- crates/biome_grit_patterns/src/grit_node.rs | 29 +- crates/biome_grit_patterns/src/grit_query.rs | 74 +++ ...it_language.rs => grit_target_language.rs} | 5 +- crates/biome_grit_patterns/src/lib.rs | 25 +- crates/biome_grit_patterns/src/pattern.rs | 8 - .../src/pattern_compiler.rs | 228 +++++++++ .../pattern_compiler/accumulate_compiler.rs | 53 ++ .../src/pattern_compiler/add_compiler.rs | 18 + .../src/pattern_compiler/after_compiler.rs | 16 + .../src/pattern_compiler/and_compiler.rs | 47 ++ .../src/pattern_compiler/any_compiler.rs | 47 ++ .../src/pattern_compiler/as_compiler.rs | 77 +++ .../pattern_compiler/assignment_compiler.rs | 59 +++ .../src/pattern_compiler/auto_wrap.rs | 461 ++++++++++++++++++ .../src/pattern_compiler/before_compiler.rs | 16 + .../src/pattern_compiler/bubble_compiler.rs | 56 +++ .../pattern_compiler/compilation_context.rs | 90 ++++ .../pattern_compiler/container_compiler.rs | 31 ++ .../src/pattern_compiler/contains_compiler.rs | 21 + .../src/pattern_compiler/divide_compiler.rs | 18 + .../src/pattern_compiler/equal_compiler.rs | 21 + .../src/pattern_compiler/every_compiler.rs | 17 + .../src/pattern_compiler/if_compiler.rs | 43 ++ .../src/pattern_compiler/includes_compiler.rs | 17 + .../src/pattern_compiler/like_compiler.rs | 22 + .../src/pattern_compiler/limit_compiler.rs | 25 + .../src/pattern_compiler/list_compiler.rs | 45 ++ .../pattern_compiler/list_index_compiler.rs | 43 ++ .../src/pattern_compiler/literal_compiler.rs | 76 +++ .../pattern_compiler/map_accessor_compiler.rs | 37 ++ .../src/pattern_compiler/map_compiler.rs | 47 ++ .../src/pattern_compiler/match_compiler.rs | 40 ++ .../src/pattern_compiler/maybe_compiler.rs | 33 ++ .../src/pattern_compiler/modulo_compiler.rs | 18 + .../src/pattern_compiler/multiply_compiler.rs | 18 + .../src/pattern_compiler/not_compiler.rs | 58 +++ .../src/pattern_compiler/or_compiler.rs | 47 ++ .../pattern_compiler/predicate_compiler.rs | 86 ++++ .../predicate_return_compiler.rs | 16 + .../src/pattern_compiler/rewrite_compiler.rs | 85 ++++ .../pattern_compiler/sequential_compiler.rs | 35 ++ .../src/pattern_compiler/snippet_compiler.rs | 27 + .../src/pattern_compiler/some_compiler.rs | 17 + .../src/pattern_compiler/step_compiler.rs | 109 +++++ .../src/pattern_compiler/subtract_compiler.rs | 18 + .../src/pattern_compiler/variable_compiler.rs | 118 +++++ .../src/pattern_compiler/where_compiler.rs | 21 + .../src/pattern_compiler/within_compiler.rs | 17 + crates/biome_grit_patterns/src/util.rs | 12 + crates/biome_grit_patterns/src/variables.rs | 70 +++ crates/biome_grit_syntax/src/lib.rs | 2 +- crates/biome_grit_syntax/src/syntax_ext.rs | 3 +- crates/biome_grit_syntax/src/syntax_node.rs | 2 +- crates/biome_service/src/diagnostics.rs | 12 +- crates/biome_service/src/workspace/server.rs | 9 +- 64 files changed, 2713 insertions(+), 57 deletions(-) create mode 100644 crates/biome_grit_patterns/src/diagnostics.rs create mode 100644 crates/biome_grit_patterns/src/grit_query.rs rename crates/biome_grit_patterns/src/{grit_language.rs => grit_target_language.rs} (82%) delete mode 100644 crates/biome_grit_patterns/src/pattern.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/accumulate_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/add_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/after_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/and_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/any_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/as_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/assignment_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/auto_wrap.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/before_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/bubble_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/compilation_context.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/container_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/contains_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/divide_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/equal_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/every_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/if_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/includes_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/like_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/limit_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/list_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/list_index_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/literal_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/map_accessor_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/map_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/match_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/maybe_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/modulo_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/multiply_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/not_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/or_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/predicate_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/predicate_return_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/rewrite_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/sequential_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/snippet_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/some_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/step_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/subtract_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/variable_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/where_compiler.rs create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/within_compiler.rs create mode 100644 crates/biome_grit_patterns/src/util.rs create mode 100644 crates/biome_grit_patterns/src/variables.rs diff --git a/Cargo.lock b/Cargo.lock index 25ca7f0f69be..19151a806ff9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -521,9 +521,13 @@ dependencies = [ "anyhow", "biome_console", "biome_diagnostics", + "biome_grit_parser", + "biome_grit_syntax", + "biome_rowan", "grit-pattern-matcher", "grit-util", "im", + "itertools", "serde", ] @@ -1811,9 +1815,9 @@ dependencies = [ [[package]] name = "grit-pattern-matcher" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c0574d27418acbda6c6c10b85d496100e82eebc46b9533bf15f204792f11b8" +checksum = "f7ca790933b1ae99fbb0caf4b90255bbcdc6507b893d5bbd8d0552bbb48018c2" dependencies = [ "anyhow", "elsa", @@ -1826,9 +1830,9 @@ dependencies = [ [[package]] name = "grit-util" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbccf24e380ffb524304fa7b1fbeaa0d021e281dcc931a098e1876f37adb4ef" +checksum = "f6b466e27499052b6206a3fe31eff49c5c55586ea23f9d73b2df41c0de8057ef" dependencies = [ "derive_builder", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 32c1e5ab008e..fd265ef98c5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -194,3 +194,7 @@ opt-level = "s" [profile.release.package.biome_wasm] debug = false opt-level = 3 + +#[patch.crates-io] +#grit-pattern-matcher = { path = "../gritql/crates/grit-pattern-matcher" } +#grit-util = { path = "../gritql/crates/grit-util" } diff --git a/crates/biome_grit_patterns/Cargo.toml b/crates/biome_grit_patterns/Cargo.toml index 8a7077dbc9ef..2590839ad4b0 100644 --- a/crates/biome_grit_patterns/Cargo.toml +++ b/crates/biome_grit_patterns/Cargo.toml @@ -15,9 +15,13 @@ version = "0.0.1" anyhow = { workspace = true } biome_console = { workspace = true } biome_diagnostics = { workspace = true } -grit-pattern-matcher = { version = "0.1" } -grit-util = { version = "0.1" } +biome_grit_parser = { workspace = true } +biome_grit_syntax = { workspace = true } +biome_rowan = { workspace = true } +grit-pattern-matcher = { version = "0.2" } +grit-util = { version = "0.2" } im = { version = "15.1.0" } +itertools = "0.10.5" serde = { workspace = true, features = ["derive"] } [lints] diff --git a/crates/biome_grit_patterns/src/diagnostics.rs b/crates/biome_grit_patterns/src/diagnostics.rs new file mode 100644 index 000000000000..9d15b9c80a5c --- /dev/null +++ b/crates/biome_grit_patterns/src/diagnostics.rs @@ -0,0 +1,22 @@ +use biome_diagnostics::Diagnostic; +use biome_rowan::TextRange; + +#[derive(Debug, Diagnostic)] +#[diagnostic(severity = Warning)] +pub(crate) struct CompilerDiagnostic { + #[message] + #[description] + message: String, + + #[location(span)] + range: TextRange, +} + +impl CompilerDiagnostic { + pub(crate) fn new_warning(message: impl Into, range: TextRange) -> Self { + Self { + message: message.into(), + range, + } + } +} diff --git a/crates/biome_grit_patterns/src/errors.rs b/crates/biome_grit_patterns/src/errors.rs index ac262165042e..2591338ca1ee 100644 --- a/crates/biome_grit_patterns/src/errors.rs +++ b/crates/biome_grit_patterns/src/errors.rs @@ -1,5 +1,6 @@ use biome_diagnostics::serde::Diagnostic as SerializableDiagnostic; use biome_diagnostics::Diagnostic; +use biome_rowan::SyntaxError; use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Diagnostic, Serialize)] @@ -22,11 +23,42 @@ pub struct ParseSnippetError { diagnostics: Vec, } -#[derive(Debug, Deserialize, Diagnostic, Serialize)] -pub enum ParseError { +#[derive(Debug, Deserialize, Serialize)] +pub enum CompileError { /// Indicates the (top-level) pattern could not be parsed. ParsePatternError(ParsePatternError), /// Indicates one of the pattern's snippets could not be parsed. ParseSnippetError(ParseSnippetError), + + /// Used for missing syntax nodes. + MissingSyntaxNode, + + /// If a function or bubble pattern has multiple parameters with the same name. + DuplicateParameters, + + /// Tried to declare or assign a Grit reserved metavariable. + ReservedMetavariable(String), + + /// When an unsupported node kind was discovered during compilation. + UnsupportedKind(u16), + + /// When an unexpected node kind was discovered during compilation. + UnexpectedKind(u16), + + /// A literal value was too large or too small. + LiteralOutOfRange(String), + + /// A pattern is required to compile a Grit query. + MissingPattern, +} + +impl Diagnostic for CompileError {} + +impl From for CompileError { + fn from(error: SyntaxError) -> Self { + match error { + SyntaxError::MissingRequiredChild => Self::MissingSyntaxNode, + } + } } diff --git a/crates/biome_grit_patterns/src/grit_binding.rs b/crates/biome_grit_patterns/src/grit_binding.rs index 5609a1b773ee..407707c62e9f 100644 --- a/crates/biome_grit_patterns/src/grit_binding.rs +++ b/crates/biome_grit_patterns/src/grit_binding.rs @@ -1,6 +1,8 @@ -use crate::{grit_context::GritQueryContext, grit_language::GritLanguage, grit_node::GritNode}; +use crate::{ + grit_context::GritQueryContext, grit_node::GritNode, grit_target_language::GritTargetLanguage, +}; use grit_pattern_matcher::{binding::Binding, constant::Constant}; -use grit_util::{CodeRange, Range}; +use grit_util::{ByteRange, CodeRange, Range}; use std::path::Path; #[derive(Clone, Debug, PartialEq)] @@ -19,7 +21,7 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding { todo!() } - fn from_range(_range: Range, _source: &'a str) -> Self { + fn from_range(_range: ByteRange, _source: &'a str) -> Self { todo!() } @@ -31,19 +33,23 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding { todo!() } - fn position(&self, _language: &GritLanguage) -> Option { + fn position(&self, _language: &GritTargetLanguage) -> Option { todo!() } - fn code_range(&self, _language: &GritLanguage) -> Option { + fn range(&self, _language: &GritTargetLanguage) -> Option { todo!() } - fn is_equivalent_to(&self, _other: &Self, _language: &GritLanguage) -> bool { + fn code_range(&self, _language: &GritTargetLanguage) -> Option { todo!() } - fn is_suppressed(&self, _language: &GritLanguage, _current_name: Option<&str>) -> bool { + fn is_equivalent_to(&self, _other: &Self, _language: &GritTargetLanguage) -> bool { + todo!() + } + + fn is_suppressed(&self, _language: &GritTargetLanguage, _current_name: Option<&str>) -> bool { todo!() } @@ -51,14 +57,14 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding { &self, _text: &str, _is_first: bool, - _language: &GritLanguage, + _language: &GritTargetLanguage, ) -> Option { todo!() } fn linearized_text( &self, - _language: &GritLanguage, + _language: &GritTargetLanguage, _effects: &[grit_pattern_matcher::effects::Effect<'a, GritQueryContext>], _files: &grit_pattern_matcher::pattern::FileRegistry<'a, GritQueryContext>, _memo: &mut std::collections::HashMap>, @@ -68,7 +74,7 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding { todo!() } - fn text(&self, _language: &GritLanguage) -> anyhow::Result> { + fn text(&self, _language: &GritTargetLanguage) -> anyhow::Result> { todo!() } @@ -106,7 +112,7 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding { fn log_empty_field_rewrite_error( &self, - _language: &GritLanguage, + _language: &GritTargetLanguage, _logs: &mut grit_util::AnalysisLogs, ) -> anyhow::Result<()> { todo!() diff --git a/crates/biome_grit_patterns/src/grit_code_snippet.rs b/crates/biome_grit_patterns/src/grit_code_snippet.rs index 4e6241c99583..7294670ddb20 100644 --- a/crates/biome_grit_patterns/src/grit_code_snippet.rs +++ b/crates/biome_grit_patterns/src/grit_code_snippet.rs @@ -7,7 +7,10 @@ use grit_pattern_matcher::pattern::{ use grit_util::AnalysisLogs; #[derive(Clone, Debug)] -pub(crate) struct GritCodeSnippet; +pub(crate) struct GritCodeSnippet { + pub(crate) source: String, + pub(crate) dynamic_snippet: Option>, +} impl CodeSnippet for GritCodeSnippet { fn patterns(&self) -> impl Iterator> { diff --git a/crates/biome_grit_patterns/src/grit_context.rs b/crates/biome_grit_patterns/src/grit_context.rs index 072a66e2c3e9..09ac1a1a8049 100644 --- a/crates/biome_grit_patterns/src/grit_context.rs +++ b/crates/biome_grit_patterns/src/grit_context.rs @@ -1,9 +1,9 @@ use crate::grit_binding::GritBinding; use crate::grit_code_snippet::GritCodeSnippet; use crate::grit_file::GritFile; -use crate::grit_language::GritLanguage; use crate::grit_node::GritNode; use crate::grit_node_patterns::{GritLeafNodePattern, GritNodePattern}; +use crate::grit_target_language::GritTargetLanguage; use crate::grit_tree::GritTree; use crate::resolved_pattern::GritResolvedPattern; use anyhow::Result; @@ -25,7 +25,7 @@ impl QueryContext for GritQueryContext { type Binding<'a> = GritBinding; type CodeSnippet = GritCodeSnippet; type ResolvedPattern<'a> = GritResolvedPattern; - type Language<'a> = GritLanguage; + type Language<'a> = GritTargetLanguage; type File<'a> = GritFile; type Tree = GritTree; } @@ -64,7 +64,7 @@ impl<'a> ExecContext<'a, GritQueryContext> for GritExecContext { todo!() } - fn language(&self) -> &GritLanguage { + fn language(&self) -> &GritTargetLanguage { todo!() } diff --git a/crates/biome_grit_patterns/src/grit_file.rs b/crates/biome_grit_patterns/src/grit_file.rs index 97a19d18a1f5..30a2a9f775a5 100644 --- a/crates/biome_grit_patterns/src/grit_file.rs +++ b/crates/biome_grit_patterns/src/grit_file.rs @@ -1,5 +1,5 @@ use crate::grit_context::GritQueryContext; -use crate::grit_language::GritLanguage; +use crate::grit_target_language::GritTargetLanguage; use crate::resolved_pattern::GritResolvedPattern; use grit_pattern_matcher::pattern::{File, FileRegistry}; @@ -13,7 +13,7 @@ impl<'a> File<'a, GritQueryContext> for GritFile { fn absolute_path( &self, _files: &FileRegistry<'a, GritQueryContext>, - _language: &GritLanguage, + _language: &GritTargetLanguage, ) -> anyhow::Result { todo!() } diff --git a/crates/biome_grit_patterns/src/grit_node.rs b/crates/biome_grit_patterns/src/grit_node.rs index 23998165e2e4..131cf450fe0b 100644 --- a/crates/biome_grit_patterns/src/grit_node.rs +++ b/crates/biome_grit_patterns/src/grit_node.rs @@ -1,8 +1,29 @@ -use grit_util::{AstCursor, AstNode as GritAstNode, CodeRange, Range}; -use std::{borrow::Cow, str::Utf8Error}; +use biome_grit_syntax::GritSyntaxNode; +use grit_util::{AstCursor, AstNode as GritAstNode, ByteRange, CodeRange}; +use std::{borrow::Cow, ops::Deref, str::Utf8Error}; #[derive(Clone, Debug)] -pub(crate) struct GritNode; +pub struct GritNode(GritSyntaxNode); + +impl Deref for GritNode { + type Target = GritSyntaxNode; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for GritNode { + fn from(value: GritSyntaxNode) -> Self { + Self(value) + } +} + +impl From<&GritSyntaxNode> for GritNode { + fn from(value: &GritSyntaxNode) -> Self { + Self(value.clone()) + } +} impl GritAstNode for GritNode { fn ancestors(&self) -> impl Iterator { @@ -37,7 +58,7 @@ impl GritAstNode for GritNode { todo!() } - fn range(&self) -> Range { + fn byte_range(&self) -> ByteRange { todo!() } diff --git a/crates/biome_grit_patterns/src/grit_query.rs b/crates/biome_grit_patterns/src/grit_query.rs new file mode 100644 index 000000000000..ee925b0c4b8a --- /dev/null +++ b/crates/biome_grit_patterns/src/grit_query.rs @@ -0,0 +1,74 @@ +use crate::diagnostics::CompilerDiagnostic; +use crate::grit_context::{GritExecContext, GritQueryContext}; +use crate::grit_target_language::GritTargetLanguage; +use crate::pattern_compiler::PatternCompiler; +use crate::pattern_compiler::{ + compilation_context::CompilationContext, compilation_context::NodeCompilationContext, +}; +use crate::resolved_pattern::GritResolvedPattern; +use crate::variables::{VarRegistry, VariableLocations}; +use crate::CompileError; +use anyhow::Result; +use biome_grit_syntax::{GritRoot, GritRootExt}; +use grit_pattern_matcher::pattern::{Matcher, Pattern, State}; +use std::collections::BTreeMap; + +/// Represents a top-level Grit query. +/// +/// Grit queries provide the +pub struct GritQuery { + pub(crate) pattern: Pattern, + + /// Diagnostics discovered during compilation of the query. + diagnostics: Vec, + + /// All variables discovered during query compilation. + locations: VariableLocations, +} + +impl GritQuery { + pub fn execute(&self) -> Result { + let var_registry = VarRegistry::from_locations(&self.locations); + + let binding = GritResolvedPattern; + let context = GritExecContext; + let mut state = State::new(var_registry.into(), Vec::new()); + let mut logs = Vec::new().into(); + + self.pattern + .execute(&binding, &mut state, &context, &mut logs) + } + + pub fn from_node(root: GritRoot, lang: GritTargetLanguage) -> Result { + let context = CompilationContext::new_anonymous(lang); + + let mut vars_array = Vec::new(); + let mut global_vars = BTreeMap::new(); + let mut diagnostics = Vec::new(); + + // We're not in a local scope yet, so this map is kinda useless. + // It's just there because all node compilers expect one. + let mut vars = BTreeMap::new(); + + let mut node_context = NodeCompilationContext::new( + &context, + &mut vars, + &mut vars_array, + &mut global_vars, + &mut diagnostics, + ); + + let pattern = PatternCompiler::from_node( + &root.pattern().ok_or(CompileError::MissingPattern)?, + &mut node_context, + )?; + + let locations = VariableLocations::new(vars_array); + + Ok(Self { + pattern, + diagnostics, + locations, + }) + } +} diff --git a/crates/biome_grit_patterns/src/grit_language.rs b/crates/biome_grit_patterns/src/grit_target_language.rs similarity index 82% rename from crates/biome_grit_patterns/src/grit_language.rs rename to crates/biome_grit_patterns/src/grit_target_language.rs index 91c6d024ce7e..1654ec377083 100644 --- a/crates/biome_grit_patterns/src/grit_language.rs +++ b/crates/biome_grit_patterns/src/grit_target_language.rs @@ -1,9 +1,10 @@ use crate::grit_node::GritNode; use grit_util::Language; -pub(crate) struct GritLanguage; +#[derive(Clone, Copy)] +pub struct GritTargetLanguage; -impl Language for GritLanguage { +impl Language for GritTargetLanguage { type Node<'a> = GritNode; fn language_name(&self) -> &'static str { diff --git a/crates/biome_grit_patterns/src/lib.rs b/crates/biome_grit_patterns/src/lib.rs index 2e441931252c..c4dbfbcc4995 100644 --- a/crates/biome_grit_patterns/src/lib.rs +++ b/crates/biome_grit_patterns/src/lib.rs @@ -1,16 +1,31 @@ +#![allow(dead_code)] // FIXME: Remove when more stuff is ready +mod diagnostics; mod errors; mod grit_binding; mod grit_code_snippet; mod grit_context; mod grit_file; -mod grit_language; mod grit_node; mod grit_node_patterns; +mod grit_query; +mod grit_target_language; mod grit_tree; -mod parse; -mod pattern; +mod pattern_compiler; mod resolved_pattern; +mod util; +mod variables; pub use errors::*; -pub use parse::parse_pattern; -pub use pattern::GritPattern; +pub use grit_query::GritQuery; +pub use grit_target_language::GritTargetLanguage; + +use biome_grit_parser::parse_grit; + +/// Compiles a Grit pattern from the given source string. +pub fn compile_pattern( + source: &str, + language: GritTargetLanguage, +) -> Result { + let parsed = parse_grit(source); + GritQuery::from_node(parsed.tree(), language) +} diff --git a/crates/biome_grit_patterns/src/pattern.rs b/crates/biome_grit_patterns/src/pattern.rs deleted file mode 100644 index bf99dfe6212d..000000000000 --- a/crates/biome_grit_patterns/src/pattern.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::grit_context::GritQueryContext; -use grit_pattern_matcher::pattern::Pattern; - -pub struct GritPattern { - pub(crate) _pattern: Pattern, - - pub source: String, -} diff --git a/crates/biome_grit_patterns/src/pattern_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler.rs new file mode 100644 index 000000000000..db04676f3b92 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler.rs @@ -0,0 +1,228 @@ +pub(crate) mod compilation_context; + +/// Creates a new scope within the given `context`. +/// +/// This is implemented as a macro instead of method to avoid capturing the +/// entire `context` instance, which would run afoul of the borrow-checking due +/// to its mutable references. +macro_rules! create_scope { + ($context: expr, $local_vars: expr) => {{ + let scope_index = $context.vars_array.len(); + $context.vars_array.push(Vec::new()); + let context = crate::pattern_compiler::NodeCompilationContext { + compilation: $context.compilation, + vars: &mut $local_vars, + vars_array: $context.vars_array, + scope_index, + global_vars: $context.global_vars, + diagnostics: $context.diagnostics, + }; + (scope_index, context) + }}; +} + +mod accumulate_compiler; +mod add_compiler; +mod after_compiler; +mod and_compiler; +mod any_compiler; +mod as_compiler; +mod assignment_compiler; +mod auto_wrap; +mod before_compiler; +mod bubble_compiler; +mod container_compiler; +mod contains_compiler; +mod divide_compiler; +mod equal_compiler; +mod every_compiler; +mod if_compiler; +mod includes_compiler; +mod like_compiler; +mod limit_compiler; +mod list_compiler; +mod list_index_compiler; +mod literal_compiler; +mod map_accessor_compiler; +mod map_compiler; +mod match_compiler; +mod maybe_compiler; +mod modulo_compiler; +mod multiply_compiler; +mod not_compiler; +mod or_compiler; +mod predicate_compiler; +mod predicate_return_compiler; +mod rewrite_compiler; +mod sequential_compiler; +mod snippet_compiler; +mod some_compiler; +mod step_compiler; +mod subtract_compiler; +mod variable_compiler; +mod where_compiler; +mod within_compiler; + +use self::{ + accumulate_compiler::AccumulateCompiler, add_compiler::AddCompiler, + after_compiler::AfterCompiler, and_compiler::AndCompiler, any_compiler::AnyCompiler, + assignment_compiler::AssignmentCompiler, before_compiler::BeforeCompiler, + bubble_compiler::BubbleCompiler, compilation_context::NodeCompilationContext, + contains_compiler::ContainsCompiler, divide_compiler::DivideCompiler, + every_compiler::EveryCompiler, if_compiler::IfCompiler, includes_compiler::IncludesCompiler, + like_compiler::LikeCompiler, limit_compiler::LimitCompiler, + list_index_compiler::ListIndexCompiler, literal_compiler::LiteralCompiler, + map_accessor_compiler::MapAccessorCompiler, maybe_compiler::MaybeCompiler, + modulo_compiler::ModuloCompiler, multiply_compiler::MultiplyCompiler, + not_compiler::NotCompiler, or_compiler::OrCompiler, rewrite_compiler::RewriteCompiler, + sequential_compiler::SequentialCompiler, some_compiler::SomeCompiler, + subtract_compiler::SubtractCompiler, variable_compiler::VariableCompiler, + where_compiler::WhereCompiler, within_compiler::WithinCompiler, +}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::{AnyGritMaybeCurlyPattern, AnyGritPattern, GritSyntaxKind}; +use biome_rowan::AstNode; +use grit_pattern_matcher::pattern::{DynamicPattern, DynamicSnippet, DynamicSnippetPart, Pattern}; + +pub(crate) struct PatternCompiler; + +impl PatternCompiler { + pub(crate) fn from_node( + node: &AnyGritPattern, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + Self::from_node_with_rhs(node, context, false) + } + + pub(crate) fn from_maybe_curly_node( + node: &AnyGritMaybeCurlyPattern, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + match node { + AnyGritMaybeCurlyPattern::AnyGritPattern(pattern) => Self::from_node(pattern, context), + AnyGritMaybeCurlyPattern::GritCurlyPattern(pattern) => { + Self::from_node(&pattern.pattern()?, context) + } + } + } + + fn from_node_with_rhs( + node: &AnyGritPattern, + context: &mut NodeCompilationContext, + is_rhs: bool, + ) -> Result, CompileError> { + match node { + AnyGritPattern::AnyGritLiteral(node) => { + LiteralCompiler::from_node_with_rhs(node, context, is_rhs) + } + AnyGritPattern::GritAddOperation(node) => Ok(Pattern::Add(Box::new( + AddCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritAssignmentAsPattern(node) => Ok(Pattern::Assignment(Box::new( + AssignmentCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritBracketedPattern(node) => { + Self::from_node_with_rhs(&node.pattern()?, context, is_rhs) + } + AnyGritPattern::GritBubble(node) => Ok(Pattern::Bubble(Box::new( + BubbleCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritDivOperation(node) => Ok(Pattern::Divide(Box::new( + DivideCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritDot(_) => { + Ok(Pattern::Dynamic(DynamicPattern::Snippet(DynamicSnippet { + parts: vec![DynamicSnippetPart::String(String::new())], + }))) + } + AnyGritPattern::GritEvery(node) => Ok(Pattern::Every(Box::new( + EveryCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritFiles(node) => Ok(Pattern::Sequential( + SequentialCompiler::from_files_node(node, context)?, + )), + AnyGritPattern::GritLike(node) => Ok(Pattern::Like(Box::new(LikeCompiler::from_node( + node, context, + )?))), + AnyGritPattern::GritListAccessor(node) => Ok(Pattern::ListIndex(Box::new( + ListIndexCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritMapAccessor(node) => Ok(Pattern::Accessor(Box::new( + MapAccessorCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritModOperation(node) => Ok(Pattern::Modulo(Box::new( + ModuloCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritMulOperation(node) => Ok(Pattern::Multiply(Box::new( + MultiplyCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritNodeLike(_) => todo!(), + AnyGritPattern::GritPatternAccumulate(node) => Ok(Pattern::Accumulate(Box::new( + AccumulateCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritPatternAfter(node) => Ok(Pattern::After(Box::new( + AfterCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritPatternAnd(node) => Ok(Pattern::And(Box::new( + AndCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritPatternAny(node) => Ok(Pattern::Any(Box::new( + AnyCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritPatternAs(_) => todo!(), + AnyGritPattern::GritPatternBefore(node) => Ok(Pattern::Before(Box::new( + BeforeCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritPatternContains(node) => Ok(Pattern::Contains(Box::new( + ContainsCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritPatternIfElse(node) => { + Ok(Pattern::If(Box::new(IfCompiler::from_node(node, context)?))) + } + AnyGritPattern::GritPatternIncludes(node) => Ok(Pattern::Includes(Box::new( + IncludesCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritPatternLimit(node) => Ok(Pattern::Limit(Box::new( + LimitCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritPatternMaybe(node) => Ok(Pattern::Maybe(Box::new( + MaybeCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritPatternNot(node) => Ok(Pattern::Not(Box::new( + NotCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritPatternOr(node) => { + Ok(Pattern::Or(Box::new(OrCompiler::from_node(node, context)?))) + } + AnyGritPattern::GritPatternOrElse(node) => { + Err(CompileError::UnsupportedKind(node.syntax().kind().into())) + } + AnyGritPattern::GritPatternWhere(node) => Ok(Pattern::Where(Box::new( + WhereCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritRegexPattern(_) => todo!(), + AnyGritPattern::GritRewrite(node) => Ok(Pattern::Rewrite(Box::new( + RewriteCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritSequential(node) => Ok(Pattern::Sequential( + SequentialCompiler::from_node(node, context)?, + )), + AnyGritPattern::GritSome(node) => Ok(Pattern::Some(Box::new(SomeCompiler::from_node( + node, context, + )?))), + AnyGritPattern::GritSubOperation(node) => Ok(Pattern::Subtract(Box::new( + SubtractCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritUnderscore(_) => Ok(Pattern::Underscore), + AnyGritPattern::GritVariable(node) => Ok(Pattern::Variable( + VariableCompiler::from_node(node, context)?, + )), + AnyGritPattern::GritWithin(node) => Ok(Pattern::Within(Box::new( + WithinCompiler::from_node(node, context)?, + ))), + AnyGritPattern::GritBogusPattern(_) => Err(CompileError::UnexpectedKind( + GritSyntaxKind::GRIT_BOGUS_PATTERN.into(), + )), + } + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/accumulate_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/accumulate_compiler.rs new file mode 100644 index 000000000000..a4e18bb5b461 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/accumulate_compiler.rs @@ -0,0 +1,53 @@ +use super::{ + compilation_context::NodeCompilationContext, variable_compiler::VariableCompiler, + PatternCompiler, +}; +use crate::{grit_code_snippet::GritCodeSnippet, grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::{GritPatternAccumulate, GritPredicateAccumulate}; +use grit_pattern_matcher::pattern::{Accumulate, DynamicPattern, Pattern}; + +pub(crate) struct AccumulateCompiler; + +impl AccumulateCompiler { + pub(crate) fn from_node( + node: &GritPatternAccumulate, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let left = PatternCompiler::from_node(&node.left()?, context)?; + let right = PatternCompiler::from_node_with_rhs(&node.right()?, context, true)?; + let dynamic_right = match &right { + Pattern::Dynamic(r) => Some(r.clone()), + Pattern::CodeSnippet(GritCodeSnippet { + dynamic_snippet: Some(r), + .. + }) => Some(r.clone()), + Pattern::Variable(v) => Some(DynamicPattern::Variable(*v)), + _ => None, + }; + + Ok(Accumulate::new(left, right, dynamic_right)) + } +} + +pub(crate) struct PrAccumulateCompiler; + +impl PrAccumulateCompiler { + pub(crate) fn from_node( + node: &GritPredicateAccumulate, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let left = Pattern::Variable(VariableCompiler::from_node(&node.left()?, context)?); + let right = PatternCompiler::from_node_with_rhs(&node.right()?, context, true)?; + let dynamic_right = match &right { + Pattern::Dynamic(r) => Some(r.clone()), + Pattern::CodeSnippet(GritCodeSnippet { + dynamic_snippet: Some(r), + .. + }) => Some(r.clone()), + Pattern::Variable(v) => Some(DynamicPattern::Variable(*v)), + _ => None, + }; + + Ok(Accumulate::new(left, right, dynamic_right)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/add_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/add_compiler.rs new file mode 100644 index 000000000000..bd2a55d14fd4 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/add_compiler.rs @@ -0,0 +1,18 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritAddOperation; +use grit_pattern_matcher::pattern::Add; + +pub(crate) struct AddCompiler; + +impl AddCompiler { + pub(crate) fn from_node( + node: &GritAddOperation, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let left = PatternCompiler::from_node(&node.left()?, context)?; + let right = PatternCompiler::from_node(&node.right()?, context)?; + + Ok(Add::new(left, right)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/after_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/after_compiler.rs new file mode 100644 index 000000000000..50f1bc327c42 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/after_compiler.rs @@ -0,0 +1,16 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritPatternAfter; +use grit_pattern_matcher::pattern::After; + +pub(crate) struct AfterCompiler; + +impl AfterCompiler { + pub(crate) fn from_node( + node: &GritPatternAfter, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let pattern = PatternCompiler::from_node(&node.pattern()?, context)?; + Ok(After::new(pattern)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/and_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/and_compiler.rs new file mode 100644 index 000000000000..a13137b05299 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/and_compiler.rs @@ -0,0 +1,47 @@ +use super::{ + compilation_context::NodeCompilationContext, predicate_compiler::PredicateCompiler, + PatternCompiler, +}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::{GritPatternAnd, GritPredicateAnd}; +use grit_pattern_matcher::pattern::{And, PrAnd}; + +pub(crate) struct AndCompiler; + +impl AndCompiler { + pub(crate) fn from_node( + node: &GritPatternAnd, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let patterns = node + .patterns() + .into_iter() + .map(|pattern| match pattern { + Ok(pattern) => Ok(PatternCompiler::from_node(&pattern, context)?), + Err(error) => Err(CompileError::from(error)), + }) + .collect::, _>>()?; + + Ok(And::new(patterns)) + } +} + +pub(crate) struct PrAndCompiler; + +impl PrAndCompiler { + pub(crate) fn from_node( + node: &GritPredicateAnd, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let predicates = node + .predicates() + .into_iter() + .map(|predicate| match predicate { + Ok(predicate) => Ok(PredicateCompiler::from_node(&predicate, context)?), + Err(error) => Err(CompileError::from(error)), + }) + .collect::, _>>()?; + + Ok(PrAnd::new(predicates)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/any_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/any_compiler.rs new file mode 100644 index 000000000000..32f4f952c4a4 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/any_compiler.rs @@ -0,0 +1,47 @@ +use super::{ + compilation_context::NodeCompilationContext, predicate_compiler::PredicateCompiler, + PatternCompiler, +}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::{GritPatternAny, GritPredicateAny}; +use grit_pattern_matcher::pattern::{Any, PrAny}; + +pub(crate) struct AnyCompiler; + +impl AnyCompiler { + pub(crate) fn from_node( + node: &GritPatternAny, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let patterns = node + .patterns() + .into_iter() + .map(|pattern| match pattern { + Ok(pattern) => Ok(PatternCompiler::from_node(&pattern, context)?), + Err(error) => Err(CompileError::from(error)), + }) + .collect::, _>>()?; + + Ok(Any::new(patterns)) + } +} + +pub(crate) struct PrAnyCompiler; + +impl PrAnyCompiler { + pub(crate) fn from_node( + node: &GritPredicateAny, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let predicates = node + .predicates() + .into_iter() + .map(|predicate| match predicate { + Ok(predicate) => Ok(PredicateCompiler::from_node(&predicate, context)?), + Err(error) => Err(CompileError::from(error)), + }) + .collect::, _>>()?; + + Ok(PrAny::new(predicates)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/as_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/as_compiler.rs new file mode 100644 index 000000000000..697df4d24a2e --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/as_compiler.rs @@ -0,0 +1,77 @@ +use super::{ + compilation_context::NodeCompilationContext, snippet_compiler::split_snippet, + variable_compiler::VariableCompiler, PatternCompiler, +}; +use crate::{ + diagnostics::CompilerDiagnostic, grit_context::GritQueryContext, grit_node::GritNode, + CompileError, +}; +use biome_grit_syntax::{AnyGritPattern, GritPatternAs, GritSyntaxKind}; +use biome_rowan::AstNode as _; +use grit_pattern_matcher::pattern::{Container, Match, Pattern, Predicate, Where}; +use grit_util::{traverse, AstNode, Language, Order}; + +pub(crate) struct AsCompiler; + +impl AsCompiler { + pub(crate) fn from_node( + node: &GritPatternAs, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let pattern = node.pattern()?; + let variable = node.variable()?; + + let name = variable.value_token()?; + let name = name.text_trimmed(); + + // this just searches the subtree for a variables that share the name. + // could possible lead to some false positives, but more precise solutions + // require much greater changes. + if pattern_repeated_variable(&pattern, name, &context.compilation.lang)? { + context.log(CompilerDiagnostic::new_warning( + format!("It is usually incorrect to redefine a variable {name} using as"), + node.range(), + )); + } + + let pattern = PatternCompiler::from_node(&pattern, context)?; + let variable = VariableCompiler::from_node(&variable, context)?; + Ok(Where::new( + Pattern::Variable(variable), + Predicate::Match(Box::new(Match::new( + Container::Variable(variable), + Some(pattern), + ))), + )) + } +} + +fn pattern_repeated_variable( + pattern: &AnyGritPattern, + name: &str, + lang: &impl Language, +) -> Result { + let node = GritNode::from(pattern.syntax()); + let cursor = traverse(node.walk(), Order::Pre); + Ok(cursor + .filter(|n| { + n.kind() == GritSyntaxKind::GRIT_VARIABLE + || n.kind() == GritSyntaxKind::GRIT_CODE_SNIPPET + }) + .map(|n| { + let s = n.text_trimmed(); + if n.kind() == GritSyntaxKind::GRIT_VARIABLE { + Ok(s == name) + } else { + Ok(is_variables_in_snippet(name, &s.to_string(), lang)) + } + }) + .collect::, CompileError>>()? + .into_iter() + .any(|b| b)) +} + +fn is_variables_in_snippet(name: &str, snippet: &str, lang: &impl Language) -> bool { + let variables = split_snippet(snippet, lang); + variables.iter().any(|v| v.1 == name) +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/assignment_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/assignment_compiler.rs new file mode 100644 index 000000000000..bb91d727bf7b --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/assignment_compiler.rs @@ -0,0 +1,59 @@ +use super::{ + compilation_context::NodeCompilationContext, container_compiler::ContainerCompiler, + PatternCompiler, +}; +use crate::{ + grit_context::GritQueryContext, grit_target_language::GritTargetLanguage, CompileError, +}; +use biome_grit_syntax::{GritAssignmentAsPattern, GritPredicateAssignment}; +use biome_rowan::AstNode; +use grit_pattern_matcher::pattern::{is_reserved_metavariable, Assignment}; +use grit_util::constants::GRIT_METAVARIABLE_PREFIX; + +pub(crate) struct AssignmentCompiler; + +impl AssignmentCompiler { + pub(crate) fn from_node( + node: &GritAssignmentAsPattern, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let container = node.container()?; + let var_text = container.syntax().text_trimmed().to_string(); + if is_reserved_metavariable(&var_text, None::<&GritTargetLanguage>) { + return Err(CompileError::ReservedMetavariable( + var_text + .trim_start_matches(GRIT_METAVARIABLE_PREFIX) + .to_owned(), + )); + } + + let variable = ContainerCompiler::from_node(&container, context)?; + let pattern = PatternCompiler::from_node_with_rhs(&node.pattern()?, context, true)?; + + Ok(Assignment::new(variable, pattern)) + } +} + +pub(crate) struct PrAssignmentCompiler; + +impl PrAssignmentCompiler { + pub(crate) fn from_node( + node: &GritPredicateAssignment, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let container = node.container()?; + let var_text = container.syntax().text_trimmed().to_string(); + if is_reserved_metavariable(&var_text, None::<&GritTargetLanguage>) { + return Err(CompileError::ReservedMetavariable( + var_text + .trim_start_matches(GRIT_METAVARIABLE_PREFIX) + .to_owned(), + )); + } + + let variable = ContainerCompiler::from_node(&container, context)?; + let pattern = PatternCompiler::from_node_with_rhs(&node.pattern()?, context, true)?; + + Ok(Assignment::new(variable, pattern)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/auto_wrap.rs b/crates/biome_grit_patterns/src/pattern_compiler/auto_wrap.rs new file mode 100644 index 000000000000..0bca7097c2c2 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/auto_wrap.rs @@ -0,0 +1,461 @@ +use super::compilation_context::{DefinitionInfo, NodeCompilationContext}; +use crate::CompileError; +use grit_pattern_matcher::constants::{GRIT_RANGE_VAR, MATCH_VAR}; +use grit_pattern_matcher::context::QueryContext; +use grit_pattern_matcher::pattern::{ + And, Bubble, Call, Container, Contains, FilePattern, Includes, Limit, Match, Maybe, Pattern, + PatternDefinition, PrAnd, PrOr, Predicate, Range, Rewrite, Step, StringConstant, Variable, + Where, +}; +use grit_util::FileRange; +use std::collections::BTreeMap; + +pub(super) fn auto_wrap_pattern( + pattern: Pattern, + pattern_definitions: &mut [PatternDefinition], + is_not_multifile: bool, + file_ranges: Option>, + context: &mut NodeCompilationContext, + injected_limit: Option, +) -> Result, CompileError> { + let is_sequential = is_sequential(&pattern, pattern_definitions); + let should_wrap_in_sequential = !is_sequential; + let should_wrap_in_contains = should_autowrap(&pattern, pattern_definitions); + let should_wrap_in_file = should_wrap_in_file(&pattern, pattern_definitions); + let (pattern, extracted_limit) = if should_wrap_in_contains && should_wrap_in_file { + extract_limit_pattern(pattern, pattern_definitions) + } else { + (pattern, None) + }; + let pattern = if is_not_multifile { + let pattern = if let Some(ranges) = file_ranges { + if should_wrap_in_sequential { + wrap_pattern_in_range(GRIT_RANGE_VAR.to_owned(), pattern, ranges, context)? + } else { + pattern + } + } else { + pattern + }; + let first_wrap = if should_wrap_in_contains { + wrap_pattern_in_contains(MATCH_VAR.to_owned(), pattern, context)? + } else { + pattern + }; + let second_wrap = if should_wrap_in_file { + wrap_pattern_in_file(first_wrap)? + } else { + first_wrap + }; + let third_wrap = if let Some(limit) = injected_limit { + // Strip the limit if there is one + let (pattern, _) = extract_limit_pattern(second_wrap, pattern_definitions); + Pattern::Limit(Box::new(Limit::new(pattern, limit))) + } else if let Some(limit) = extracted_limit { + Pattern::Limit(Box::new(Limit::new(second_wrap, limit))) + } else { + second_wrap + }; + wrap_pattern_in_before_and_after_each_file( + third_wrap, + &context.compilation.pattern_definition_info, + )? + } else { + pattern + }; + if should_wrap_in_sequential { + Ok(Pattern::Sequential(vec![Step { pattern }].into())) + } else { + Ok(pattern) + } +} + +fn is_sequential( + pattern: &Pattern, + pattern_definitions: &[PatternDefinition], +) -> bool { + match pattern { + Pattern::Sequential(_) => true, + Pattern::Where(w) => is_sequential(&w.pattern, pattern_definitions), + Pattern::Maybe(m) => is_sequential(&m.pattern, pattern_definitions), + Pattern::Rewrite(r) => is_sequential(&r.left, pattern_definitions), + Pattern::Bubble(b) => is_sequential(&b.pattern_def.pattern, pattern_definitions), + Pattern::Limit(l) => is_sequential(&l.pattern, pattern_definitions), + Pattern::Call(call) => is_sequential( + &pattern_definitions[call.index].pattern, + pattern_definitions, + ), + Pattern::AstNode(_) + | Pattern::List(_) + | Pattern::ListIndex(_) + | Pattern::Map(_) + | Pattern::Accessor(_) + | Pattern::Regex(_) + | Pattern::File(_) + | Pattern::Files(_) + | Pattern::CallBuiltIn(_) + | Pattern::CallFunction(_) + | Pattern::CallForeignFunction(_) + | Pattern::Assignment(_) + | Pattern::Accumulate(_) + | Pattern::And(_) + | Pattern::Or(_) + | Pattern::Any(_) + | Pattern::Not(_) + | Pattern::If(_) + | Pattern::Undefined + | Pattern::Top + | Pattern::Bottom + | Pattern::Underscore + | Pattern::StringConstant(_) + | Pattern::AstLeafNode(_) + | Pattern::IntConstant(_) + | Pattern::FloatConstant(_) + | Pattern::BooleanConstant(_) + | Pattern::Dynamic(_) + | Pattern::CodeSnippet(_) + | Pattern::Variable(_) + | Pattern::Log(_) + | Pattern::Range(_) + | Pattern::Contains(_) + | Pattern::Includes(_) + | Pattern::Within(_) + | Pattern::After(_) + | Pattern::Before(_) + | Pattern::Some(_) + | Pattern::Every(_) + | Pattern::Add(_) + | Pattern::Subtract(_) + | Pattern::Multiply(_) + | Pattern::Divide(_) + | Pattern::Modulo(_) + | Pattern::Like(_) + | Pattern::Dots => false, + } +} + +pub(crate) fn should_autowrap( + pattern: &Pattern, + pattern_definitions: &[PatternDefinition], +) -> bool { + match pattern { + Pattern::Contains(_) => false, + Pattern::File(_) => false, + Pattern::Sequential(_) => false, + Pattern::Where(w) => should_autowrap(&w.pattern, pattern_definitions), + Pattern::Maybe(m) => should_autowrap(&m.pattern, pattern_definitions), + Pattern::Rewrite(r) => should_autowrap(&r.left, pattern_definitions), + Pattern::Bubble(b) => should_autowrap(&b.pattern_def.pattern, pattern_definitions), + Pattern::Limit(l) => should_autowrap(&l.pattern, pattern_definitions), + Pattern::Call(call) => should_autowrap( + &pattern_definitions[call.index].pattern, + pattern_definitions, + ), + Pattern::AstNode(_) + | Pattern::List(_) + | Pattern::ListIndex(_) + | Pattern::Map(_) + | Pattern::Accessor(_) + | Pattern::Regex(_) + | Pattern::Files(_) + | Pattern::CallBuiltIn(_) + | Pattern::CallFunction(_) + | Pattern::CallForeignFunction(_) + | Pattern::Assignment(_) + | Pattern::Accumulate(_) + | Pattern::And(_) + | Pattern::Or(_) + | Pattern::Any(_) + | Pattern::Not(_) + | Pattern::If(_) + | Pattern::Undefined + | Pattern::Top + | Pattern::Bottom + | Pattern::Underscore + | Pattern::StringConstant(_) + | Pattern::AstLeafNode(_) + | Pattern::IntConstant(_) + | Pattern::FloatConstant(_) + | Pattern::BooleanConstant(_) + | Pattern::Dynamic(_) + | Pattern::CodeSnippet(_) + | Pattern::Variable(_) + | Pattern::Log(_) + | Pattern::Range(_) + | Pattern::Includes(_) + | Pattern::Within(_) + | Pattern::After(_) + | Pattern::Before(_) + | Pattern::Some(_) + | Pattern::Every(_) + | Pattern::Add(_) + | Pattern::Subtract(_) + | Pattern::Multiply(_) + | Pattern::Divide(_) + | Pattern::Modulo(_) + | Pattern::Like(_) + | Pattern::Dots => true, + } +} + +fn extract_limit_pattern( + pattern: Pattern, + pattern_definitions: &mut [PatternDefinition], +) -> (Pattern, Option) { + match pattern { + Pattern::Limit(limit) => (limit.pattern, Some(limit.limit)), + Pattern::Where(w) => { + let extracted = extract_limit_pattern(w.pattern, pattern_definitions); + let pattern = Pattern::Where(Box::new(Where::new(extracted.0, w.side_condition))); + (pattern, extracted.1) + } + Pattern::Maybe(m) => { + let extracted = extract_limit_pattern(m.pattern, pattern_definitions); + let pattern = Pattern::Maybe(Box::new(Maybe::new(extracted.0))); + (pattern, extracted.1) + } + Pattern::Rewrite(r) => { + let extracted = extract_limit_pattern(r.left, pattern_definitions); + let pattern = + Pattern::Rewrite(Box::new(Rewrite::new(extracted.0, r.right, r.annotation))); + (pattern, extracted.1) + } + Pattern::Bubble(b) => { + let extracted = extract_limit_pattern(b.pattern_def.pattern, pattern_definitions); + let pattern = Pattern::Bubble(Box::new(Bubble::new( + PatternDefinition::new( + b.pattern_def.name.clone(), + b.pattern_def.scope, + b.pattern_def.params.clone(), + b.pattern_def.local_vars.clone(), + extracted.0, + ), + b.args.into_iter().flatten().collect(), + ))); + (pattern, extracted.1) + } + Pattern::Call(call) => { + let (new_pattern, extracted_limit) = extract_limit_pattern( + pattern_definitions[call.index].pattern.clone(), + pattern_definitions, + ); + pattern_definitions[call.index].pattern = new_pattern; + (Pattern::Call(call), extracted_limit) + } + Pattern::AstNode(_) + | Pattern::File(_) + | Pattern::Contains(_) + | Pattern::Sequential(_) + | Pattern::List(_) + | Pattern::ListIndex(_) + | Pattern::Map(_) + | Pattern::Accessor(_) + | Pattern::Regex(_) + | Pattern::Files(_) + | Pattern::CallBuiltIn(_) + | Pattern::CallFunction(_) + | Pattern::CallForeignFunction(_) + | Pattern::Assignment(_) + | Pattern::Accumulate(_) + | Pattern::And(_) + | Pattern::Or(_) + | Pattern::Any(_) + | Pattern::Not(_) + | Pattern::If(_) + | Pattern::Undefined + | Pattern::Top + | Pattern::Bottom + | Pattern::Underscore + | Pattern::StringConstant(_) + | Pattern::AstLeafNode(_) + | Pattern::IntConstant(_) + | Pattern::FloatConstant(_) + | Pattern::BooleanConstant(_) + | Pattern::Dynamic(_) + | Pattern::CodeSnippet(_) + | Pattern::Variable(_) + | Pattern::Log(_) + | Pattern::Range(_) + | Pattern::Includes(_) + | Pattern::Within(_) + | Pattern::After(_) + | Pattern::Before(_) + | Pattern::Some(_) + | Pattern::Every(_) + | Pattern::Add(_) + | Pattern::Subtract(_) + | Pattern::Multiply(_) + | Pattern::Divide(_) + | Pattern::Modulo(_) + | Pattern::Like(_) + | Pattern::Dots => (pattern, None), + } +} + +fn should_wrap_in_file( + pattern: &Pattern, + pattern_definitions: &[PatternDefinition], +) -> bool { + match pattern { + Pattern::File(_) => false, + Pattern::Files(_) => false, + Pattern::Sequential(_) => false, + Pattern::Where(w) => should_wrap_in_file(&w.pattern, pattern_definitions), + Pattern::Maybe(m) => should_wrap_in_file(&m.pattern, pattern_definitions), + Pattern::Rewrite(r) => should_wrap_in_file(&r.left, pattern_definitions), + Pattern::Bubble(b) => should_wrap_in_file(&b.pattern_def.pattern, pattern_definitions), + Pattern::Limit(l) => should_wrap_in_file(&l.pattern, pattern_definitions), + Pattern::Call(call) => should_wrap_in_file( + &pattern_definitions[call.index].pattern, + pattern_definitions, + ), + Pattern::AstNode(_) + | Pattern::Contains(_) + | Pattern::List(_) + | Pattern::ListIndex(_) + | Pattern::Map(_) + | Pattern::Accessor(_) + | Pattern::Regex(_) + | Pattern::CallBuiltIn(_) + | Pattern::CallFunction(_) + | Pattern::CallForeignFunction(_) + | Pattern::Assignment(_) + | Pattern::Accumulate(_) + | Pattern::And(_) + | Pattern::Or(_) + | Pattern::Any(_) + | Pattern::Not(_) + | Pattern::If(_) + | Pattern::Undefined + | Pattern::Top + | Pattern::Bottom + | Pattern::Underscore + | Pattern::StringConstant(_) + | Pattern::AstLeafNode(_) + | Pattern::IntConstant(_) + | Pattern::FloatConstant(_) + | Pattern::BooleanConstant(_) + | Pattern::Dynamic(_) + | Pattern::CodeSnippet(_) + | Pattern::Variable(_) + | Pattern::Log(_) + | Pattern::Range(_) + | Pattern::Includes(_) + | Pattern::Within(_) + | Pattern::After(_) + | Pattern::Before(_) + | Pattern::Some(_) + | Pattern::Every(_) + | Pattern::Add(_) + | Pattern::Subtract(_) + | Pattern::Multiply(_) + | Pattern::Divide(_) + | Pattern::Modulo(_) + | Pattern::Like(_) + | Pattern::Dots => true, + } +} + +fn wrap_pattern_in_range( + var_name: String, + pattern: Pattern, + ranges: Vec, + context: &mut NodeCompilationContext, +) -> Result, CompileError> { + let var = context.variable_from_name(var_name)?; + let mut predicates = Vec::new(); + for file_range in ranges { + let range = file_range.range.clone(); + let range = Range::from(range); + let range_match = Predicate::Match(Box::new(Match::new( + Container::Variable(var), + Some(Pattern::Range(range)), + ))); + let file_match = Predicate::Match(Box::new(Match::new( + Container::Variable(Variable::file_name()), + Some(Pattern::Includes(Box::new(Includes::new( + Pattern::StringConstant(StringConstant::new( + file_range.file_path.to_string_lossy().to_string(), + )), + )))), + ))); + predicates.push(Predicate::And(Box::new(PrAnd::new(vec![ + file_match, + range_match, + ])))); + } + let pattern = Pattern::Where(Box::new(Where::new( + pattern, + Predicate::Or(Box::new(PrOr::new(predicates))), + ))); + let pattern = Pattern::Where(Box::new(Where::new( + Pattern::Variable(var), + Predicate::Match(Box::new(Match::new( + Container::Variable(var), + Some(pattern), + ))), + ))); + Ok(pattern) +} + +fn wrap_pattern_in_contains( + var_name: String, + pattern: Pattern, + context: &mut NodeCompilationContext, +) -> Result, CompileError> { + let var = context.variable_from_name(var_name)?; + let pattern = Pattern::Where(Box::new(Where::new( + Pattern::Variable(var), + Predicate::Match(Box::new(Match::new( + Container::Variable(var), + Some(pattern), + ))), + ))); + let pattern_definition = PatternDefinition::new( + "".to_string(), + context.scope_index, + vec![], + context.vars.values().copied().collect(), + pattern, + ); + let bubble = Pattern::Bubble(Box::new(Bubble::new(pattern_definition, vec![]))); + Ok(Pattern::Contains(Box::new(Contains::new(bubble, None)))) +} + +fn wrap_pattern_in_file(pattern: Pattern) -> Result, CompileError> { + let pattern = Pattern::File(Box::new(FilePattern::new(Pattern::Top, pattern))); + Ok(pattern) +} + +pub(crate) fn wrap_pattern_in_before_and_after_each_file( + pattern: Pattern, + pattern_definition_info: &BTreeMap, +) -> Result, CompileError> { + let before_each_file = "before_each_file"; + let after_each_file = "after_each_file"; + let mut all_steps = vec![]; + if let Some(DefinitionInfo { + index, + parameters: _, + }) = pattern_definition_info.get(before_each_file) + { + all_steps.push(Pattern::Call(Box::new(Call::new(*index, vec![])))); + } + + all_steps.push(pattern); + if let Some(DefinitionInfo { + index, + parameters: _, + }) = pattern_definition_info.get(after_each_file) + { + all_steps.push(Pattern::Call(Box::new(Call::new(*index, vec![])))); + } + + let final_pattern = if all_steps.len() > 1 { + Pattern::And(Box::new(And::new(all_steps))) + } else { + all_steps.pop().unwrap() + }; + + Ok(final_pattern) +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/before_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/before_compiler.rs new file mode 100644 index 000000000000..8315ba96f8cd --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/before_compiler.rs @@ -0,0 +1,16 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritPatternBefore; +use grit_pattern_matcher::pattern::Before; + +pub(crate) struct BeforeCompiler; + +impl BeforeCompiler { + pub(crate) fn from_node( + node: &GritPatternBefore, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let pattern = PatternCompiler::from_node(&node.pattern()?, context)?; + Ok(Before::new(pattern)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/bubble_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/bubble_compiler.rs new file mode 100644 index 000000000000..844da1befefa --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/bubble_compiler.rs @@ -0,0 +1,56 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, util::TextRangeGritExt, CompileError}; +use biome_grit_syntax::GritBubble; +use biome_rowan::AstNode; +use grit_pattern_matcher::pattern::{Bubble, Pattern, PatternDefinition}; +use itertools::Itertools; +use std::collections::BTreeMap; + +pub(crate) struct BubbleCompiler; + +impl BubbleCompiler { + pub(crate) fn from_node( + node: &GritBubble, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let mut local_vars = BTreeMap::new(); + let (local_scope_index, mut local_context) = create_scope!(context, local_vars); + + // important that this occurs first, as calls assume + // that parameters are registered first + + let parameters: Vec<_> = node + .variables() + .into_iter() + .map(|node| { + let syntax = node.syntax(); + ( + syntax.text_trimmed().to_string(), + syntax.text_trimmed_range().to_byte_range(), + ) + }) + .collect(); + if parameters.iter().unique_by(|n| &n.0).count() != parameters.len() { + return Err(CompileError::DuplicateParameters); + } + + let params = local_context.get_variables(¶meters)?; + + let body = PatternCompiler::from_maybe_curly_node(&node.pattern()?, &mut local_context)?; + + let args = parameters + .into_iter() + .map(|(name, range)| Ok(Pattern::Variable(context.register_variable(name, range)?))) + .collect::, CompileError>>()?; + + let pattern_def = PatternDefinition::new( + "".to_owned(), + local_scope_index, + params, + local_vars.values().copied().collect(), + body, + ); + + Ok(Bubble::new(pattern_def, args)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/compilation_context.rs b/crates/biome_grit_patterns/src/pattern_compiler/compilation_context.rs new file mode 100644 index 000000000000..323637b2dfe4 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/compilation_context.rs @@ -0,0 +1,90 @@ +use biome_rowan::TextRange; +use grit_pattern_matcher::pattern::VariableSourceLocations; + +use crate::{diagnostics::CompilerDiagnostic, grit_target_language::GritTargetLanguage}; +use std::{collections::BTreeMap, path::Path}; + +pub(crate) struct CompilationContext<'a> { + /// Path of the source file being compiled. + pub source_path: Option<&'a Path>, + + /// The target language being matched on. + pub lang: GritTargetLanguage, + + pub pattern_definition_info: BTreeMap, + pub predicate_definition_info: BTreeMap, + pub function_definition_info: BTreeMap, +} + +impl<'a> CompilationContext<'a> { + pub(crate) fn new(source_path: &'a Path, lang: GritTargetLanguage) -> Self { + let mut this = Self::new_anonymous(lang); + this.source_path = Some(source_path); + this + } + + pub(crate) fn new_anonymous(lang: GritTargetLanguage) -> Self { + Self { + source_path: None, + lang, + pattern_definition_info: Default::default(), + predicate_definition_info: Default::default(), + function_definition_info: Default::default(), + } + } +} + +pub(crate) struct NodeCompilationContext<'a> { + pub compilation: &'a CompilationContext<'a>, + + /// Used to lookup local variables in the `vars_array`. + pub vars: &'a mut BTreeMap, + + /// Storage for variable information. + /// + /// The outer vector can be index using `scope_index`, while the individual + /// variables in a scope can be indexed using the indices stored in `vars` + /// and `global_vars`. + pub vars_array: &'a mut Vec>, + + /// Index of the local scope. + /// + /// Corresponds to the index in the outer vector of `vars_array`. + pub scope_index: usize, + + /// Used to lookup global variables in the `vars_array`. + /// + /// Global variables are always at scope 0. + pub global_vars: &'a mut BTreeMap, + + /// Diagnostics discovered during compilation. + pub diagnostics: &'a mut Vec, +} + +impl<'a> NodeCompilationContext<'a> { + pub(crate) fn new( + compilation_context: &'a CompilationContext, + vars: &'a mut BTreeMap, + vars_array: &'a mut Vec>, + global_vars: &'a mut BTreeMap, + diagnostics: &'a mut Vec, + ) -> Self { + Self { + compilation: compilation_context, + vars, + vars_array, + scope_index: 0, + global_vars, + diagnostics, + } + } + + pub(crate) fn log(&mut self, diagnostic: CompilerDiagnostic) { + self.diagnostics.push(diagnostic); + } +} + +pub(crate) struct DefinitionInfo { + pub(crate) index: usize, + pub(crate) parameters: Vec<(String, TextRange)>, +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/container_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/container_compiler.rs new file mode 100644 index 000000000000..7f6693ebfb0f --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/container_compiler.rs @@ -0,0 +1,31 @@ +use super::{ + compilation_context::NodeCompilationContext, list_index_compiler::ListIndexCompiler, + map_accessor_compiler::MapAccessorCompiler, variable_compiler::VariableCompiler, +}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::{AnyGritContainer, GritSyntaxKind}; +use grit_pattern_matcher::pattern::Container; + +pub(crate) struct ContainerCompiler; + +impl ContainerCompiler { + pub(crate) fn from_node( + node: &AnyGritContainer, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + match node { + AnyGritContainer::GritListAccessor(accessor) => Ok(Container::ListIndex(Box::new( + ListIndexCompiler::from_node(accessor, context)?, + ))), + AnyGritContainer::GritMapAccessor(accessor) => Ok(Container::Accessor(Box::new( + MapAccessorCompiler::from_node(accessor, context)?, + ))), + AnyGritContainer::GritVariable(variable) => Ok(Container::Variable( + VariableCompiler::from_node(variable, context)?, + )), + AnyGritContainer::GritBogusContainer(_) => Err(CompileError::UnexpectedKind( + GritSyntaxKind::GRIT_BOGUS_CONTAINER.into(), + )), + } + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/contains_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/contains_compiler.rs new file mode 100644 index 000000000000..375254866b18 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/contains_compiler.rs @@ -0,0 +1,21 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritPatternContains; +use grit_pattern_matcher::pattern::Contains; + +pub(crate) struct ContainsCompiler; + +impl ContainsCompiler { + pub(crate) fn from_node( + node: &GritPatternContains, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let contains = PatternCompiler::from_maybe_curly_node(&node.contains()?, context)?; + let until = node + .until_clause() + .map(|node| PatternCompiler::from_node(&node.until()?, context)) + .transpose()?; + + Ok(Contains::new(contains, until)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/divide_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/divide_compiler.rs new file mode 100644 index 000000000000..04d38a5c3efc --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/divide_compiler.rs @@ -0,0 +1,18 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritDivOperation; +use grit_pattern_matcher::pattern::Divide; + +pub(crate) struct DivideCompiler; + +impl DivideCompiler { + pub(crate) fn from_node( + node: &GritDivOperation, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let left = PatternCompiler::from_node(&node.left()?, context)?; + let right = PatternCompiler::from_node(&node.right()?, context)?; + + Ok(Divide::new(left, right)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/equal_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/equal_compiler.rs new file mode 100644 index 000000000000..cdcfa844f7fd --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/equal_compiler.rs @@ -0,0 +1,21 @@ +use super::{ + compilation_context::NodeCompilationContext, variable_compiler::VariableCompiler, + PatternCompiler, +}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritPredicateEqual; +use grit_pattern_matcher::pattern::Equal; + +pub(crate) struct PrEqualCompiler; + +impl PrEqualCompiler { + pub(crate) fn from_node( + node: &GritPredicateEqual, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let variable = VariableCompiler::from_node(&node.left()?, context)?; + let pattern = PatternCompiler::from_node_with_rhs(&node.right()?, context, true)?; + + Ok(Equal::new(variable, pattern)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/every_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/every_compiler.rs new file mode 100644 index 000000000000..dc7d095d559f --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/every_compiler.rs @@ -0,0 +1,17 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritEvery; +use grit_pattern_matcher::pattern::Every; + +pub(crate) struct EveryCompiler; + +impl EveryCompiler { + pub(crate) fn from_node( + node: &GritEvery, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let pattern = PatternCompiler::from_maybe_curly_node(&node.pattern()?, context)?; + + Ok(Every::new(pattern)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/if_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/if_compiler.rs new file mode 100644 index 000000000000..93d10b42514a --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/if_compiler.rs @@ -0,0 +1,43 @@ +use super::{ + compilation_context::NodeCompilationContext, predicate_compiler::PredicateCompiler, + PatternCompiler, +}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::{GritPatternIfElse, GritPredicateIfElse}; +use grit_pattern_matcher::pattern::{If, PrIf}; + +pub(crate) struct IfCompiler; + +impl IfCompiler { + pub(crate) fn from_node( + node: &GritPatternIfElse, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let if_ = PredicateCompiler::from_node(&node.if_predicate()?, context)?; + let then = PatternCompiler::from_maybe_curly_node(&node.then_pattern()?, context)?; + let else_ = node + .else_clause() + .map(|node| PatternCompiler::from_maybe_curly_node(&node.else_pattern()?, context)) + .transpose()?; + + Ok(If::new(if_, then, else_)) + } +} + +pub(crate) struct PrIfCompiler; + +impl PrIfCompiler { + pub(crate) fn from_node( + node: &GritPredicateIfElse, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let if_ = PredicateCompiler::from_node(&node.if_predicate()?, context)?; + let then = PredicateCompiler::from_node(&node.then_predicate()?, context)?; + let else_ = node + .else_clause() + .map(|node| PredicateCompiler::from_node(&node.else_predicate()?, context)) + .transpose()?; + + Ok(PrIf::new(if_, then, else_)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/includes_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/includes_compiler.rs new file mode 100644 index 000000000000..8dd52c2734dc --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/includes_compiler.rs @@ -0,0 +1,17 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritPatternIncludes; +use grit_pattern_matcher::pattern::Includes; + +pub(crate) struct IncludesCompiler; + +impl IncludesCompiler { + pub(crate) fn from_node( + node: &GritPatternIncludes, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let pattern = PatternCompiler::from_maybe_curly_node(&node.includes()?, context)?; + + Ok(Includes::new(pattern)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/like_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/like_compiler.rs new file mode 100644 index 000000000000..6562db829d28 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/like_compiler.rs @@ -0,0 +1,22 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritLike; +use grit_pattern_matcher::pattern::{FloatConstant, Like, Pattern}; + +pub(crate) struct LikeCompiler; + +impl LikeCompiler { + pub(crate) fn from_node( + node: &GritLike, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let like = PatternCompiler::from_node(&node.example()?, context)?; + let threshold = node + .threshold() + .map(|node| PatternCompiler::from_node(&node.threshold()?, context)) + .transpose()? + .unwrap_or(Pattern::FloatConstant(FloatConstant::new(0.9))); + + Ok(Like::new(like, threshold)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/limit_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/limit_compiler.rs new file mode 100644 index 000000000000..76b8d766a75a --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/limit_compiler.rs @@ -0,0 +1,25 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritPatternLimit; +use grit_pattern_matcher::pattern::Limit; + +pub(crate) struct LimitCompiler; + +impl LimitCompiler { + pub(crate) fn from_node( + node: &GritPatternLimit, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let body = PatternCompiler::from_node(&node.pattern()?, context)?; + let limit = node + .limit()? + .value_token()? + .text_trimmed() + .parse::() + .map_err(|err| { + CompileError::LiteralOutOfRange(format!("Error parsing limit: {err}")) + })?; + + Ok(Limit::new(body, limit)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/list_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/list_compiler.rs new file mode 100644 index 000000000000..1aa9ca65149f --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/list_compiler.rs @@ -0,0 +1,45 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::{AnyGritListPattern, GritList}; +use grit_pattern_matcher::pattern::{List, Pattern}; + +pub(crate) struct ListCompiler; + +impl ListCompiler { + pub(crate) fn from_node( + node: &GritList, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + Self::from_node_with_rhs(node, context, false) + } + + pub(crate) fn from_node_with_rhs( + node: &GritList, + context: &mut NodeCompilationContext, + is_rhs: bool, + ) -> Result, CompileError> { + let patterns = node + .patterns() + .into_iter() + .map(|pattern| match pattern { + Ok(pattern) => Ok(compile_list_pattern(&pattern, context, is_rhs)?), + Err(error) => Err(CompileError::from(error)), + }) + .collect::, _>>()?; + + Ok(List::new(patterns)) + } +} + +fn compile_list_pattern( + node: &AnyGritListPattern, + context: &mut NodeCompilationContext, + is_rhs: bool, +) -> Result, CompileError> { + match node { + AnyGritListPattern::AnyGritPattern(pattern) => { + PatternCompiler::from_node_with_rhs(pattern, context, is_rhs) + } + AnyGritListPattern::GritDotdotdot(_) => Ok(Pattern::Dots), + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/list_index_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/list_index_compiler.rs new file mode 100644 index 000000000000..4b68c6b2f618 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/list_index_compiler.rs @@ -0,0 +1,43 @@ +use super::{ + compilation_context::NodeCompilationContext, container_compiler::ContainerCompiler, + list_compiler::ListCompiler, +}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::{AnyGritListAccessorSubject, AnyGritListIndex, GritListAccessor}; +use grit_pattern_matcher::pattern::{ContainerOrIndex, ListIndex, ListOrContainer}; + +pub(crate) struct ListIndexCompiler; + +impl ListIndexCompiler { + pub(crate) fn from_node( + node: &GritListAccessor, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let list = match node.list()? { + AnyGritListAccessorSubject::AnyGritContainer(container) => { + ListOrContainer::Container(ContainerCompiler::from_node(&container, context)?) + } + AnyGritListAccessorSubject::GritList(list) => { + ListOrContainer::List(ListCompiler::from_node(&list, context)?) + } + }; + + let index = match node.index()? { + AnyGritListIndex::AnyGritContainer(container) => { + ContainerOrIndex::Container(ContainerCompiler::from_node(&container, context)?) + } + AnyGritListIndex::GritIntLiteral(int) => ContainerOrIndex::Index( + int.value_token()?.text_trimmed().parse().map_err(|err| { + CompileError::LiteralOutOfRange(format!("Error parsing list index: {err}")) + })?, + ), + AnyGritListIndex::GritNegativeIntLiteral(int) => ContainerOrIndex::Index( + int.value_token()?.text_trimmed().parse().map_err(|err| { + CompileError::LiteralOutOfRange(format!("Error parsing list index: {err}")) + })?, + ), + }; + + Ok(ListIndex { list, index }) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/literal_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/literal_compiler.rs new file mode 100644 index 000000000000..28b00451ff98 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/literal_compiler.rs @@ -0,0 +1,76 @@ +use super::{ + compilation_context::NodeCompilationContext, list_compiler::ListCompiler, + map_compiler::MapCompiler, +}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::{AnyGritLiteral, GritSyntaxKind}; +use grit_pattern_matcher::pattern::{ + BooleanConstant, FloatConstant, IntConstant, Pattern, StringConstant, +}; + +pub(crate) struct LiteralCompiler; + +impl LiteralCompiler { + pub(crate) fn from_node_with_rhs( + node: &AnyGritLiteral, + context: &mut NodeCompilationContext, + is_rhs: bool, + ) -> Result, CompileError> { + match node { + AnyGritLiteral::GritBooleanLiteral(node) => Ok(Pattern::BooleanConstant( + BooleanConstant::new(node.value()?.text_trimmed() == "true"), + )), + AnyGritLiteral::GritCodeSnippet(_) => todo!(), + AnyGritLiteral::GritDoubleLiteral(node) => Ok(Pattern::FloatConstant( + FloatConstant::new(node.value_token()?.text_trimmed().parse().map_err(|err| { + CompileError::LiteralOutOfRange(format!("Error parsing double: {err}")) + })?), + )), + AnyGritLiteral::GritIntLiteral(node) => Ok(Pattern::IntConstant(IntConstant::new( + node.value_token()?.text_trimmed().parse().map_err(|err| { + CompileError::LiteralOutOfRange(format!("Error parsing integer: {err}")) + })?, + ))), + AnyGritLiteral::GritList(node) => Ok(Pattern::List(Box::new( + ListCompiler::from_node_with_rhs(node, context, is_rhs)?, + ))), + AnyGritLiteral::GritMap(node) => Ok(Pattern::Map(Box::new( + MapCompiler::from_node_with_rhs(node, context, is_rhs)?, + ))), + AnyGritLiteral::GritStringLiteral(node) => { + let token = node.value_token()?; + let text = token.text_trimmed(); + debug_assert!(text.len() >= 2, "Strings must have quotes"); + Ok(Pattern::StringConstant(StringConstant::new(unescape( + &text[1..text.len() - 1], + )))) + } + AnyGritLiteral::GritUndefinedLiteral(_) => Ok(Pattern::Undefined), + AnyGritLiteral::GritBogusLiteral(_) => Err(CompileError::UnexpectedKind( + GritSyntaxKind::GRIT_BOGUS_LITERAL.into(), + )), + } + } +} + +fn unescape(string_literal: &str) -> String { + let mut escaped = false; + let mut value = String::with_capacity(string_literal.len()); + for c in string_literal.chars() { + if escaped { + match c { + 'n' => value.push('\n'), + 'r' => value.push('\r'), + 't' => value.push('\t'), + '\\' => value.push('\\'), + c => value.push(c), + } + } else if c == '\\' { + escaped = true; + } else { + value.push(c); + } + } + + value +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/map_accessor_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/map_accessor_compiler.rs new file mode 100644 index 000000000000..acda036254d9 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/map_accessor_compiler.rs @@ -0,0 +1,37 @@ +use super::{ + compilation_context::NodeCompilationContext, container_compiler::ContainerCompiler, + map_compiler::MapCompiler, variable_compiler::VariableCompiler, +}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::{AnyGritMapAccessorSubject, AnyGritMapKey, GritMapAccessor}; +use biome_rowan::AstNode; +use grit_pattern_matcher::pattern::{Accessor, AccessorKey, AccessorMap}; + +pub(crate) struct MapAccessorCompiler; + +impl MapAccessorCompiler { + pub(crate) fn from_node( + node: &GritMapAccessor, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let map = match node.map()? { + AnyGritMapAccessorSubject::AnyGritContainer(container) => { + AccessorMap::Container(ContainerCompiler::from_node(&container, context)?) + } + AnyGritMapAccessorSubject::GritMap(map) => { + AccessorMap::Map(MapCompiler::from_node(&map, context)?) + } + }; + + let key = match node.key()? { + AnyGritMapKey::GritName(name) => { + AccessorKey::String(name.syntax().text_trimmed().to_string()) + } + AnyGritMapKey::GritVariable(variable) => { + AccessorKey::Variable(VariableCompiler::from_node(&variable, context)?) + } + }; + + Ok(Accessor::new(map, key)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/map_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/map_compiler.rs new file mode 100644 index 000000000000..1a094e9aad4a --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/map_compiler.rs @@ -0,0 +1,47 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::{AnyGritMapElement, GritMap, GritSyntaxKind}; +use biome_rowan::AstNode; +use grit_pattern_matcher::pattern::{GritMap as Map, Pattern}; +use std::collections::BTreeMap; + +pub(crate) struct MapCompiler; + +impl MapCompiler { + pub(crate) fn from_node( + node: &GritMap, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + Self::from_node_with_rhs(node, context, false) + } + + pub(crate) fn from_node_with_rhs( + node: &GritMap, + context: &mut NodeCompilationContext, + is_rhs: bool, + ) -> Result, CompileError> { + let elements = node + .elements() + .into_iter() + .map(|element| compile_map_element(&element?, context, is_rhs)) + .collect::, CompileError>>()?; + Ok(Map::new(elements)) + } +} + +fn compile_map_element( + node: &AnyGritMapElement, + context: &mut NodeCompilationContext, + is_rhs: bool, +) -> Result<(String, Pattern), CompileError> { + match node { + AnyGritMapElement::GritMapElement(element) => { + let key = element.key()?.syntax().text_trimmed().to_string(); + let pattern = PatternCompiler::from_node_with_rhs(&element.value()?, context, is_rhs)?; + Ok((key, pattern)) + } + AnyGritMapElement::GritBogusMapElement(_) => Err(CompileError::UnexpectedKind( + GritSyntaxKind::GRIT_BOGUS_MAP_ELEMENT.into(), + )), + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/match_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/match_compiler.rs new file mode 100644 index 000000000000..b9bc22528950 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/match_compiler.rs @@ -0,0 +1,40 @@ +use super::{ + compilation_context::NodeCompilationContext, container_compiler::ContainerCompiler, + PatternCompiler, +}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::{AnyGritPredicateMatchSubject, GritPredicateMatch}; +use biome_rowan::AstNode; +use grit_pattern_matcher::pattern::{Container, Match}; + +pub(crate) struct PrMatchCompiler; + +impl PrMatchCompiler { + pub(crate) fn from_node( + node: &GritPredicateMatch, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let value = compile_match_subject(&node.left()?, context)?; + let pattern = Some(PatternCompiler::from_node(&node.right()?, context)?); + + Ok(Match::new(value, pattern)) + } +} + +fn compile_match_subject( + node: &AnyGritPredicateMatchSubject, + context: &mut NodeCompilationContext, +) -> Result, CompileError> { + match node { + AnyGritPredicateMatchSubject::AnyGritContainer(node) => { + ContainerCompiler::from_node(node, context) + } + AnyGritPredicateMatchSubject::AnyGritLiteral(literal) => { + // FIXME: The grammar says literals are supported here, but the + // Grit reference compiler doesn't accept them either. + Err(CompileError::UnsupportedKind( + literal.syntax().kind().into(), + )) + } + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/maybe_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/maybe_compiler.rs new file mode 100644 index 000000000000..574b4d81e0b8 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/maybe_compiler.rs @@ -0,0 +1,33 @@ +use super::{ + compilation_context::NodeCompilationContext, predicate_compiler::PredicateCompiler, + PatternCompiler, +}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::{GritPatternMaybe, GritPredicateMaybe}; +use grit_pattern_matcher::pattern::{Maybe, PrMaybe}; + +pub(crate) struct MaybeCompiler; + +impl MaybeCompiler { + pub(crate) fn from_node( + node: &GritPatternMaybe, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let pattern = PatternCompiler::from_maybe_curly_node(&node.pattern()?, context)?; + + Ok(Maybe::new(pattern)) + } +} + +pub(crate) struct PrMaybeCompiler; + +impl PrMaybeCompiler { + pub(crate) fn from_node( + node: &GritPredicateMaybe, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let predicate = PredicateCompiler::from_node(&node.predicate()?, context)?; + + Ok(PrMaybe::new(predicate)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/modulo_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/modulo_compiler.rs new file mode 100644 index 000000000000..2836e464411a --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/modulo_compiler.rs @@ -0,0 +1,18 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritModOperation; +use grit_pattern_matcher::pattern::Modulo; + +pub(crate) struct ModuloCompiler; + +impl ModuloCompiler { + pub(crate) fn from_node( + node: &GritModOperation, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let left = PatternCompiler::from_node(&node.left()?, context)?; + let right = PatternCompiler::from_node(&node.right()?, context)?; + + Ok(Modulo::new(left, right)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/multiply_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/multiply_compiler.rs new file mode 100644 index 000000000000..5c0235c53939 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/multiply_compiler.rs @@ -0,0 +1,18 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritMulOperation; +use grit_pattern_matcher::pattern::Multiply; + +pub(crate) struct MultiplyCompiler; + +impl MultiplyCompiler { + pub(crate) fn from_node( + node: &GritMulOperation, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let left = PatternCompiler::from_node(&node.left()?, context)?; + let right = PatternCompiler::from_node(&node.right()?, context)?; + + Ok(Multiply::new(left, right)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/not_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/not_compiler.rs new file mode 100644 index 000000000000..9129f36f573e --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/not_compiler.rs @@ -0,0 +1,58 @@ +use super::{ + compilation_context::NodeCompilationContext, predicate_compiler::PredicateCompiler, + PatternCompiler, +}; +use crate::{diagnostics::CompilerDiagnostic, grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::{GritPatternNot, GritPredicateNot}; +use biome_rowan::AstNode; +use grit_pattern_matcher::pattern::{Not, Pattern, PatternOrPredicate, PrNot, Predicate}; + +pub(crate) struct NotCompiler; + +impl NotCompiler { + pub(crate) fn from_node( + node: &GritPatternNot, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let pattern = PatternCompiler::from_node(&node.pattern()?, context)?; + if pattern.iter().any(|p| { + matches!( + p, + PatternOrPredicate::Pattern(Pattern::Rewrite(_)) + | PatternOrPredicate::Predicate(Predicate::Rewrite(_)) + ) + }) { + context.log(CompilerDiagnostic::new_warning( + "Rewrites inside of a not will never be applied", + node.syntax().text_range(), + )); + } + + Ok(Not::new(pattern)) + } +} + +pub(crate) struct PrNotCompiler; + +impl PrNotCompiler { + pub(crate) fn from_node( + node: &GritPredicateNot, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let predicate = PredicateCompiler::from_node(&node.predicate()?, context)?; + if predicate.iter().any(|p| { + matches!( + p, + PatternOrPredicate::Pattern(Pattern::Rewrite(_)) + | PatternOrPredicate::Predicate(Predicate::Rewrite(_)) + ) + }) { + context.log(CompilerDiagnostic::new_warning( + "Rewrites inside of a not will never be applied", + node.syntax().text_range(), + )); + } + + Ok(PrNot::new(predicate)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/or_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/or_compiler.rs new file mode 100644 index 000000000000..1dfa235a5b91 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/or_compiler.rs @@ -0,0 +1,47 @@ +use super::{ + compilation_context::NodeCompilationContext, predicate_compiler::PredicateCompiler, + PatternCompiler, +}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::{GritPatternOr, GritPredicateOr}; +use grit_pattern_matcher::pattern::{Or, PrOr}; + +pub(crate) struct OrCompiler; + +impl OrCompiler { + pub(crate) fn from_node( + node: &GritPatternOr, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let patterns = node + .patterns() + .into_iter() + .map(|pattern| match pattern { + Ok(pattern) => Ok(PatternCompiler::from_node(&pattern, context)?), + Err(error) => Err(CompileError::from(error)), + }) + .collect::, _>>()?; + + Ok(Or::new(patterns)) + } +} + +pub(crate) struct PrOrCompiler; + +impl PrOrCompiler { + pub(crate) fn from_node( + node: &GritPredicateOr, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let predicates = node + .predicates() + .into_iter() + .map(|predicate| match predicate { + Ok(predicate) => Ok(PredicateCompiler::from_node(&predicate, context)?), + Err(error) => Err(CompileError::from(error)), + }) + .collect::, _>>()?; + + Ok(PrOr::new(predicates)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/predicate_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/predicate_compiler.rs new file mode 100644 index 000000000000..6555235f903c --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/predicate_compiler.rs @@ -0,0 +1,86 @@ +use super::{ + accumulate_compiler::PrAccumulateCompiler, and_compiler::PrAndCompiler, + any_compiler::PrAnyCompiler, assignment_compiler::PrAssignmentCompiler, + compilation_context::NodeCompilationContext, equal_compiler::PrEqualCompiler, + if_compiler::PrIfCompiler, match_compiler::PrMatchCompiler, maybe_compiler::PrMaybeCompiler, + not_compiler::PrNotCompiler, or_compiler::PrOrCompiler, + predicate_return_compiler::PrReturnCompiler, rewrite_compiler::PrRewriteCompiler, +}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::{AnyGritPredicate, GritSyntaxKind}; +use biome_rowan::AstNode; +use grit_pattern_matcher::pattern::Predicate; + +pub(crate) struct PredicateCompiler; + +impl PredicateCompiler { + pub(crate) fn from_node( + node: &AnyGritPredicate, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + match node { + AnyGritPredicate::GritBooleanLiteral(node) => Ok(match node.value()?.text_trimmed() { + "true" => Predicate::True, + _ => Predicate::False, + }), + AnyGritPredicate::GritBracketedPredicate(node) => { + Self::from_node(&node.predicate()?, context) + } + AnyGritPredicate::GritPredicateAccumulate(node) => Ok(Predicate::Accumulate(Box::new( + PrAccumulateCompiler::from_node(node, context)?, + ))), + AnyGritPredicate::GritPredicateAnd(node) => Ok(Predicate::And(Box::new( + PrAndCompiler::from_node(node, context)?, + ))), + AnyGritPredicate::GritPredicateAny(node) => Ok(Predicate::Any(Box::new( + PrAnyCompiler::from_node(node, context)?, + ))), + AnyGritPredicate::GritPredicateAssignment(node) => Ok(Predicate::Assignment(Box::new( + PrAssignmentCompiler::from_node(node, context)?, + ))), + AnyGritPredicate::GritPredicateCall(_) => todo!(), + AnyGritPredicate::GritPredicateEqual(node) => Ok(Predicate::Equal(Box::new( + PrEqualCompiler::from_node(node, context)?, + ))), + AnyGritPredicate::GritPredicateGreater(node) => { + Err(CompileError::UnsupportedKind(node.syntax().kind().into())) // Not supported by Grit either. + } + AnyGritPredicate::GritPredicateGreaterEqual(node) => { + Err(CompileError::UnsupportedKind(node.syntax().kind().into())) // Not supported by Grit either. + } + AnyGritPredicate::GritPredicateIfElse(node) => Ok(Predicate::If(Box::new( + PrIfCompiler::from_node(node, context)?, + ))), + AnyGritPredicate::GritPredicateLess(node) => { + Err(CompileError::UnsupportedKind(node.syntax().kind().into())) // Not supported by Grit either. + } + AnyGritPredicate::GritPredicateLessEqual(node) => { + Err(CompileError::UnsupportedKind(node.syntax().kind().into())) // Not supported by Grit either. + } + AnyGritPredicate::GritPredicateMatch(node) => Ok(Predicate::Match(Box::new( + PrMatchCompiler::from_node(node, context)?, + ))), + AnyGritPredicate::GritPredicateMaybe(node) => Ok(Predicate::Maybe(Box::new( + PrMaybeCompiler::from_node(node, context)?, + ))), + AnyGritPredicate::GritPredicateNot(node) => Ok(Predicate::Not(Box::new( + PrNotCompiler::from_node(node, context)?, + ))), + AnyGritPredicate::GritPredicateNotEqual(node) => { + Err(CompileError::UnsupportedKind(node.syntax().kind().into())) // Not supported by Grit either. + } + AnyGritPredicate::GritPredicateOr(node) => Ok(Predicate::Or(Box::new( + PrOrCompiler::from_node(node, context)?, + ))), + AnyGritPredicate::GritPredicateReturn(node) => Ok(Predicate::Return(Box::new( + PrReturnCompiler::from_node(node, context)?, + ))), + AnyGritPredicate::GritPredicateRewrite(node) => Ok(Predicate::Rewrite(Box::new( + PrRewriteCompiler::from_node(node, context)?, + ))), + AnyGritPredicate::GritBogusPredicate(_) => Err(CompileError::UnexpectedKind( + GritSyntaxKind::GRIT_BOGUS_PREDICATE.into(), + )), + } + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/predicate_return_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/predicate_return_compiler.rs new file mode 100644 index 000000000000..59b933851bac --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/predicate_return_compiler.rs @@ -0,0 +1,16 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritPredicateReturn; +use grit_pattern_matcher::pattern::PrReturn; + +pub(crate) struct PrReturnCompiler; + +impl PrReturnCompiler { + pub(crate) fn from_node( + node: &GritPredicateReturn, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let pattern = PatternCompiler::from_node_with_rhs(&node.pattern()?, context, true)?; + Ok(PrReturn::new(pattern)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/rewrite_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/rewrite_compiler.rs new file mode 100644 index 000000000000..63516f3c94ab --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/rewrite_compiler.rs @@ -0,0 +1,85 @@ +use super::{ + compilation_context::NodeCompilationContext, variable_compiler::VariableCompiler, + PatternCompiler, +}; +use crate::{ + diagnostics::CompilerDiagnostic, grit_code_snippet::GritCodeSnippet, + grit_context::GritQueryContext, CompileError, +}; +use biome_grit_syntax::{GritPredicateRewrite, GritRewrite, GritSyntaxKind}; +use biome_rowan::AstNode; +use grit_pattern_matcher::pattern::{DynamicPattern, Pattern, Rewrite}; + +pub(crate) struct RewriteCompiler; + +impl RewriteCompiler { + pub(crate) fn from_node( + node: &GritRewrite, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let left = PatternCompiler::from_node(&node.left()?, context)?; + + let right = node.right()?; + let right_syntax_kind = right.syntax().kind(); + let right = PatternCompiler::from_node_with_rhs(&right, context, true)?; + + if let (Pattern::CodeSnippet(left_snippet), Pattern::CodeSnippet(right_snippet)) = + (&left, &right) + { + if left_snippet.source == right_snippet.source { + context.log(CompilerDiagnostic::new_warning( + format!( + "This is rewriting `{}` into the identical string `{}`, will have no effect.", + left_snippet.source, right_snippet.source + ), + node.syntax().text_trimmed_range() + )); + } + } + + let right = to_dynamic_pattern(right, right_syntax_kind)?; + + Ok(Rewrite::new(left, right, None)) + } +} + +pub(crate) struct PrRewriteCompiler; + +impl PrRewriteCompiler { + pub(crate) fn from_node( + node: &GritPredicateRewrite, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let left = Pattern::Variable(VariableCompiler::from_node(&node.left()?, context)?); + + let right = node.right()?; + let right = to_dynamic_pattern( + PatternCompiler::from_node_with_rhs(&right, context, true)?, + right.syntax().kind(), + )?; + + Ok(Rewrite::new(left, right, None)) + } +} + +fn to_dynamic_pattern( + pattern: Pattern, + syntax_kind: GritSyntaxKind, +) -> Result, CompileError> { + let dynamic = match pattern { + Pattern::Dynamic(r) => r, + Pattern::CodeSnippet(GritCodeSnippet { + dynamic_snippet: Some(r), + .. + }) => r, + Pattern::Variable(v) => DynamicPattern::Variable(v), + Pattern::Accessor(a) => DynamicPattern::Accessor(a), + Pattern::ListIndex(a) => DynamicPattern::ListIndex(a), + Pattern::CallBuiltIn(c) => DynamicPattern::CallBuiltIn(*c), + Pattern::CallFunction(c) => DynamicPattern::CallFunction(*c), + Pattern::CallForeignFunction(c) => DynamicPattern::CallForeignFunction(*c), + _ => Err(CompileError::UnexpectedKind(syntax_kind.into()))?, + }; + + Ok(dynamic) +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/sequential_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/sequential_compiler.rs new file mode 100644 index 000000000000..943642becbb1 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/sequential_compiler.rs @@ -0,0 +1,35 @@ +use super::{compilation_context::NodeCompilationContext, step_compiler::StepCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::{GritFiles, GritSequential}; +use grit_pattern_matcher::pattern::{Files, Pattern, Sequential, Some, Step}; + +pub(crate) struct SequentialCompiler; + +impl SequentialCompiler { + pub(crate) fn from_files_node( + node: &GritFiles, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + node.files() + .into_iter() + .map(|pattern| { + let step = StepCompiler::from_node(&pattern?, context)?; + let some = Pattern::Some(Box::new(Some::new(step.pattern))); + let files = Pattern::Files(Box::new(Files::new(some))); + Ok(Step { pattern: files }) + }) + .collect::, CompileError>>() + .map(Into::into) + } + + pub(crate) fn from_node( + node: &GritSequential, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + node.sequential() + .into_iter() + .map(|pattern| StepCompiler::from_node(&pattern?, context)) + .collect::, CompileError>>() + .map(Into::into) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/snippet_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/snippet_compiler.rs new file mode 100644 index 000000000000..1705fd792198 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/snippet_compiler.rs @@ -0,0 +1,27 @@ +use grit_util::{ByteRange, Language}; +use std::borrow::Cow; + +/// Takes a snippet with metavariables and returns a list of ranges and the +/// corresponding metavariables. +/// +/// The ranges are in descending order. +pub fn split_snippet<'a>(snippet: &'a str, lang: &impl Language) -> Vec<(ByteRange, Cow<'a, str>)> { + let mut ranges_and_metavars: Vec<(ByteRange, Cow)> = Vec::new(); + + let variable_regex = lang.metavariable_regex(); + let curly_var_regex = lang.metavariable_bracket_regex(); + + for m in variable_regex.find_iter(snippet) { + ranges_and_metavars.push(((m.start()..m.end()).into(), m.as_str().into())); + } + for m in curly_var_regex.find_iter(snippet) { + let mut metavar: Cow = m.as_str()[2..m.as_str().len() - 1].into(); + metavar.to_mut().insert(0, '$'); + ranges_and_metavars.push(((m.start()..m.end()).into(), metavar)); + } + + // Sort ranges in descending order + ranges_and_metavars.sort_by(|a, b| b.0.start.cmp(&a.0.start)); + + ranges_and_metavars +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/some_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/some_compiler.rs new file mode 100644 index 000000000000..3101eb09ab03 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/some_compiler.rs @@ -0,0 +1,17 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritSome; +use grit_pattern_matcher::pattern::Some; + +pub(crate) struct SomeCompiler; + +impl SomeCompiler { + pub(crate) fn from_node( + node: &GritSome, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let pattern = PatternCompiler::from_maybe_curly_node(&node.pattern()?, context)?; + + Ok(Some::new(pattern)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/step_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/step_compiler.rs new file mode 100644 index 000000000000..58deb8cb41b3 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/step_compiler.rs @@ -0,0 +1,109 @@ +use super::{ + auto_wrap::wrap_pattern_in_before_and_after_each_file, + compilation_context::NodeCompilationContext, PatternCompiler, +}; +use crate::{diagnostics::CompilerDiagnostic, grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::AnyGritPattern; +use biome_rowan::AstNode; +use grit_pattern_matcher::pattern::{Pattern, Step}; + +const SEQUENTIAL_WARNING: &str = "`sequential` matches at the top of the file. \ + If a pattern matched outside of a sequential, but no longer matches, \ + it is likely because naked patterns are automatically wrapped with \ + `contains bubble `"; + +pub(crate) struct StepCompiler; + +impl StepCompiler { + pub(crate) fn from_node( + node: &AnyGritPattern, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let pattern = PatternCompiler::from_node(node, context)?; + match pattern { + Pattern::File(_) + | Pattern::Files(_) + | Pattern::Contains(_) + | Pattern::Includes(_) + | Pattern::Maybe(_) + | Pattern::Call(_) + | Pattern::Where(_) + | Pattern::Bubble(_) => {} + Pattern::And(_) + | Pattern::Or(_) + | Pattern::AstNode(_) + | Pattern::List(_) + | Pattern::ListIndex(_) + | Pattern::Map(_) + | Pattern::Accessor(_) + | Pattern::Regex(_) + | Pattern::Limit(_) + | Pattern::CallBuiltIn(_) + | Pattern::CallFunction(_) + | Pattern::CallForeignFunction(_) + | Pattern::Assignment(_) + | Pattern::Accumulate(_) + | Pattern::Any(_) + | Pattern::Not(_) + | Pattern::If(_) + | Pattern::Undefined + | Pattern::Top + | Pattern::Bottom + | Pattern::Underscore + | Pattern::StringConstant(_) + | Pattern::AstLeafNode(_) + | Pattern::IntConstant(_) + | Pattern::FloatConstant(_) + | Pattern::BooleanConstant(_) + | Pattern::Dynamic(_) + | Pattern::CodeSnippet(_) + | Pattern::Variable(_) + | Pattern::Rewrite(_) + | Pattern::Log(_) + | Pattern::Range(_) + | Pattern::Within(_) + | Pattern::After(_) + | Pattern::Before(_) + | Pattern::Some(_) + | Pattern::Every(_) + | Pattern::Add(_) + | Pattern::Subtract(_) + | Pattern::Multiply(_) + | Pattern::Divide(_) + | Pattern::Modulo(_) + | Pattern::Dots + | Pattern::Like(_) => { + context.log(CompilerDiagnostic::new_warning( + SEQUENTIAL_WARNING, + node.syntax().text_trimmed_range(), + )); + } + Pattern::Sequential(ref s) => { + for step in s.iter() { + if !matches!( + step.pattern, + Pattern::File(_) + | Pattern::Files(_) + | Pattern::Contains(_) + | Pattern::Includes(_) + | Pattern::Maybe(_) + | Pattern::Call(_) + | Pattern::Where(_) + ) { + context.log(CompilerDiagnostic::new_warning( + SEQUENTIAL_WARNING, + node.syntax().text_trimmed_range(), + )); + break; + } + } + } + } + let pattern = wrap_pattern_in_before_and_after_each_file( + pattern, + &context.compilation.pattern_definition_info, + )?; + + Ok(Step::new(pattern)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/subtract_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/subtract_compiler.rs new file mode 100644 index 000000000000..fd5c05b2bd11 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/subtract_compiler.rs @@ -0,0 +1,18 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritSubOperation; +use grit_pattern_matcher::pattern::Subtract; + +pub(crate) struct SubtractCompiler; + +impl SubtractCompiler { + pub(crate) fn from_node( + node: &GritSubOperation, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let left = PatternCompiler::from_node(&node.left()?, context)?; + let right = PatternCompiler::from_node(&node.right()?, context)?; + + Ok(Subtract::new(left, right)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/variable_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/variable_compiler.rs new file mode 100644 index 000000000000..1ba5a08509c3 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/variable_compiler.rs @@ -0,0 +1,118 @@ +use super::compilation_context::NodeCompilationContext; +use crate::util::TextRangeGritExt; +use crate::CompileError; +use biome_grit_syntax::GritVariable; +use biome_rowan::AstNode; +use grit_pattern_matcher::constants::GLOBAL_VARS_SCOPE_INDEX; +use grit_pattern_matcher::pattern::{Variable, VariableSourceLocations}; +use grit_util::ByteRange; +use std::collections::BTreeSet; +use std::path::Path; + +pub(crate) struct VariableCompiler; + +impl VariableCompiler { + pub(crate) fn from_node( + node: &GritVariable, + context: &mut NodeCompilationContext, + ) -> Result { + let name = node.syntax().text_trimmed().to_string(); + let range = node.range().to_byte_range(); + context.register_variable(name, range) + } +} + +impl<'a> NodeCompilationContext<'a> { + pub(super) fn variable_from_name(&mut self, name: String) -> Result { + self.register_variable_with_optional_range(name, None) + } + + pub(super) fn get_variables( + &mut self, + params: &[(String, ByteRange)], + ) -> Result, CompileError> { + params + .iter() + .map(|(name, range)| { + let index = self.register_variable(name.clone(), *range)?; + Ok((name.to_owned(), index)) + }) + .collect() + } + + pub(super) fn register_variable( + &mut self, + name: String, + range: ByteRange, + ) -> Result { + self.register_variable_with_optional_range( + name, + Some(FileLocation { + range, + path: self.compilation.source_path, + }), + ) + } + + fn register_variable_with_optional_range( + &mut self, + name: String, + location: Option, + ) -> Result { + let Self { + vars, + vars_array, + global_vars, + scope_index, + .. + } = self; + + if let Some(i) = vars.get(&name) { + if let Some(FileLocation { range, .. }) = location { + vars_array[*scope_index][*i].locations.insert(range); + } + return Ok(Variable::new(*scope_index, *i)); + } + + if let Some(i) = global_vars.get(&name) { + if let Some(FileLocation { path, range }) = location { + if path.is_none() { + vars_array[GLOBAL_VARS_SCOPE_INDEX][*i] + .locations + .insert(range); + } + } + return Ok(Variable::new(GLOBAL_VARS_SCOPE_INDEX, *i)); + } + let (name_map, scope_index) = if name.starts_with("$GLOBAL_") { + (global_vars, GLOBAL_VARS_SCOPE_INDEX) + } else { + (vars, *scope_index) + }; + let scope = &mut vars_array[scope_index]; + let index = scope.len(); + name_map.insert(name.clone(), index); + + let (locations, path) = if let Some(FileLocation { path, range }) = location { + (BTreeSet::from([range]), path) + } else { + // this currently only comes up with the $match variable which we autowrap, and is not + // usually used by the user, but feels like this could potentially be a source of bugs + (BTreeSet::new(), None) + }; + + scope.push(VariableSourceLocations { + name, + file: path + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_default(), + locations, + }); + Ok(Variable::new(scope_index, index)) + } +} + +struct FileLocation<'a> { + path: Option<&'a Path>, + range: ByteRange, +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/where_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/where_compiler.rs new file mode 100644 index 000000000000..7f49ec26e1f4 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/where_compiler.rs @@ -0,0 +1,21 @@ +use super::{ + compilation_context::NodeCompilationContext, predicate_compiler::PredicateCompiler, + PatternCompiler, +}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritPatternWhere; +use grit_pattern_matcher::pattern::Where; + +pub(crate) struct WhereCompiler; + +impl WhereCompiler { + pub(crate) fn from_node( + node: &GritPatternWhere, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let pattern = PatternCompiler::from_node(&node.pattern()?, context)?; + let side_condition = PredicateCompiler::from_node(&node.side_condition()?, context)?; + + Ok(Where::new(pattern, side_condition)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/within_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/within_compiler.rs new file mode 100644 index 000000000000..289ad8340a54 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/within_compiler.rs @@ -0,0 +1,17 @@ +use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritWithin; +use grit_pattern_matcher::pattern::Within; + +pub(crate) struct WithinCompiler; + +impl WithinCompiler { + pub(crate) fn from_node( + node: &GritWithin, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let pattern = PatternCompiler::from_maybe_curly_node(&node.pattern()?, context)?; + + Ok(Within::new(pattern)) + } +} diff --git a/crates/biome_grit_patterns/src/util.rs b/crates/biome_grit_patterns/src/util.rs new file mode 100644 index 000000000000..0733bbd4bcb1 --- /dev/null +++ b/crates/biome_grit_patterns/src/util.rs @@ -0,0 +1,12 @@ +use biome_rowan::TextRange; +use grit_util::ByteRange; + +pub trait TextRangeGritExt { + fn to_byte_range(&self) -> ByteRange; +} + +impl TextRangeGritExt for TextRange { + fn to_byte_range(&self) -> ByteRange { + ByteRange::new(self.start().into(), self.end().into()) + } +} diff --git a/crates/biome_grit_patterns/src/variables.rs b/crates/biome_grit_patterns/src/variables.rs new file mode 100644 index 000000000000..d6d2f3abf204 --- /dev/null +++ b/crates/biome_grit_patterns/src/variables.rs @@ -0,0 +1,70 @@ +use crate::grit_context::GritQueryContext; +use grit_pattern_matcher::pattern::{VariableContent, VariableSourceLocations}; +use grit_util::VariableBinding; +use im::{vector, Vector}; + +/// List of all variable locations in a query. +/// +/// Variables are stored in a vector of vectors, where the outer vector is used +/// to separate scopes, while the inner vector contains the variables. For each +/// variable, we track the separate locations (plural) where that variable +/// occurs. +#[derive(Debug, Default)] +pub struct VariableLocations(Vec>); + +impl VariableLocations { + pub(crate) fn new(locations: Vec>) -> Self { + Self(locations) + } + + pub(crate) fn compiled_vars(&self) -> Vec { + let mut variables = Vec::new(); + for (i, scope) in self.0.iter().enumerate() { + for (j, var) in scope.iter().enumerate() { + if var.file.is_empty() { + let name = &var.name; + variables.push(VariableBinding { + name: name.to_owned(), + scoped_name: format!("{i}_{j}_{name}"), + ranges: var.locations.iter().copied().collect(), + }); + } + } + } + variables + } +} + +/// Registry containing all variables. +/// +/// Variables are stored here in a three-dimensional vector, where the outer +/// vector is used to separate scopes, the second vector is used to +/// differentiate scope instances across calls, and the inner vector contains +/// the variable contents. +pub(crate) struct VarRegistry<'a>(VarRegistryVector<'a>); + +impl<'a> VarRegistry<'a> { + pub(crate) fn from_locations(locations: &VariableLocations) -> Self { + let vector = locations + .0 + .iter() + .map(|scope| { + vector![scope + .iter() + .map(|s| Box::new(VariableContent::new(s.name.clone()))) + .collect()] + }) + .collect(); + + Self(vector) + } +} + +pub(crate) type VarRegistryVector<'a> = + Vector>>>>; + +impl<'a> From> for VarRegistryVector<'a> { + fn from(value: VarRegistry<'a>) -> Self { + value.0 + } +} diff --git a/crates/biome_grit_syntax/src/lib.rs b/crates/biome_grit_syntax/src/lib.rs index 3e16889ecf63..e59282339f6f 100644 --- a/crates/biome_grit_syntax/src/lib.rs +++ b/crates/biome_grit_syntax/src/lib.rs @@ -7,12 +7,12 @@ mod generated; mod syntax_ext; mod syntax_node; -use biome_rowan::{AstNode, RawSyntaxKind}; pub use biome_rowan::{TextLen, TextRange, TextSize, TokenAtOffset, TriviaPieceKind, WalkEvent}; pub use generated::*; pub use syntax_ext::*; pub use syntax_node::*; +use biome_rowan::{AstNode, RawSyntaxKind}; use GritSyntaxKind::*; impl From for GritSyntaxKind { diff --git a/crates/biome_grit_syntax/src/syntax_ext.rs b/crates/biome_grit_syntax/src/syntax_ext.rs index 9a9aa11a7c8c..8e815bf703f3 100644 --- a/crates/biome_grit_syntax/src/syntax_ext.rs +++ b/crates/biome_grit_syntax/src/syntax_ext.rs @@ -1,6 +1,5 @@ -use biome_rowan::AstNode; - use crate::{AnyGritPattern, GritRoot}; +use biome_rowan::AstNode; pub trait GritRootExt { fn pattern(&self) -> Option; diff --git a/crates/biome_grit_syntax/src/syntax_node.rs b/crates/biome_grit_syntax/src/syntax_node.rs index c4848b893ec2..3697d1283fb4 100644 --- a/crates/biome_grit_syntax/src/syntax_node.rs +++ b/crates/biome_grit_syntax/src/syntax_node.rs @@ -1,4 +1,4 @@ -//! This module defines the Concrete Syntax Tree used by Biome. +//! This module defines the Concrete Syntax Tree for Grit used by Biome. //! //! The tree is entirely lossless, whitespace, comments, and errors are preserved. //! It also provides traversal methods including parent, children, and siblings of nodes. diff --git a/crates/biome_service/src/diagnostics.rs b/crates/biome_service/src/diagnostics.rs index 6f31bc792985..c5763d0c49ad 100644 --- a/crates/biome_service/src/diagnostics.rs +++ b/crates/biome_service/src/diagnostics.rs @@ -7,7 +7,7 @@ use biome_diagnostics::{ }; use biome_formatter::{FormatError, PrintError}; use biome_fs::{BiomePath, FileSystemDiagnostic}; -use biome_grit_patterns::ParseError; +use biome_grit_patterns::CompileError; use biome_js_analyze::utils::rename::RenameError; use biome_js_analyze::RuleError; use serde::{Deserialize, Serialize}; @@ -315,8 +315,8 @@ impl Diagnostic for SourceFileNotSupported { #[derive(Debug, Serialize, Deserialize, Diagnostic)] pub enum SearchError { - /// An invalid patterns was given - ParsePatternError(ParseError), + /// An invalid pattern was given + PatternCompilationError(CompileError), /// No pattern with the given ID InvalidPattern(InvalidPattern), } @@ -415,9 +415,9 @@ impl From for WorkspaceError { } } -impl From for WorkspaceError { - fn from(value: ParseError) -> Self { - Self::SearchError(SearchError::ParsePatternError(value)) +impl From for WorkspaceError { + fn from(value: CompileError) -> Self { + Self::SearchError(SearchError::PatternCompilationError(value)) } } diff --git a/crates/biome_service/src/workspace/server.rs b/crates/biome_service/src/workspace/server.rs index f6d7c03b2b0f..84b2ead3f0be 100644 --- a/crates/biome_service/src/workspace/server.rs +++ b/crates/biome_service/src/workspace/server.rs @@ -22,7 +22,7 @@ use biome_diagnostics::{ }; use biome_formatter::Printed; use biome_fs::{BiomePath, ConfigName}; -use biome_grit_patterns::GritPattern; +use biome_grit_patterns::GritQuery; use biome_json_parser::{parse_json_with_cache, JsonParserOptions}; use biome_json_syntax::JsonFileSource; use biome_parser::AnyParse; @@ -55,7 +55,7 @@ pub(super) struct WorkspaceServer { /// Stores the document sources used across the workspace file_sources: RwLock>, /// Stores patterns to search for. - patterns: DashMap, + patterns: DashMap, } /// The `Workspace` object is long-lived, so we want it to be able to cross @@ -743,7 +743,10 @@ impl Workspace for WorkspaceServer { &self, params: ParsePatternParams, ) -> Result { - let pattern = biome_grit_patterns::parse_pattern(params.pattern)?; + let pattern = biome_grit_patterns::compile_pattern( + ¶ms.pattern, + biome_grit_patterns::GritTargetLanguage, + )?; let pattern_id = PatternId::from("1234"); // TODO: Generate a real ID. self.patterns.insert(pattern_id.clone(), pattern); Ok(ParsePatternResult { pattern_id })