Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(grit): implement node-like compilers + fixes #3253

Merged
merged 1 commit into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions crates/biome_grit_patterns/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub struct ParseSnippetError {
diagnostics: Vec<SerializableDiagnostic>,
}

// TODO: We definitely need to improve diagnostics.
#[derive(Debug, Deserialize, Serialize)]
pub enum CompileError {
/// Indicates the (top-level) pattern could not be parsed.
Expand Down Expand Up @@ -53,6 +54,9 @@ pub enum CompileError {
/// When an unexpected node kind was discovered during compilation.
UnexpectedKind(u16),

/// When trying to use an unrecognized function or pattern.
UnknownFunctionOrPattern(String),

/// A literal value was too large or too small.
LiteralOutOfRange(String),

Expand All @@ -63,6 +67,12 @@ pub enum CompileError {
/// rewrite.
InvalidBracketedMetavariable,

/// Unexpected function call argument.
FunctionArgument(NodeLikeArgumentError),

/// Unknown function or predicate.
UnknownFunctionOrPredicate(String),

/// Unknown variable.
UnknownVariable(String),
}
Expand All @@ -76,3 +86,33 @@ impl From<SyntaxError> for CompileError {
}
}
}

impl From<NodeLikeArgumentError> for CompileError {
fn from(error: NodeLikeArgumentError) -> Self {
Self::FunctionArgument(error)
}
}

#[derive(Debug, Deserialize, Serialize)]
pub enum NodeLikeArgumentError {
/// Duplicate arguments in invocation.
DuplicateArguments { name: String },
/// Only variables are allowed as arguments.
ExpectedVariable { name: String },
/// When a named argument is missing its name.
MissingArgumentName { name: String, variable: String },
/// Used when too many arguments are specified.
TooManyArguments { name: String, max_args: usize },
/// Unknown argument given in function
UnknownArgument {
name: String,
argument: String,
valid_args: Vec<String>,
},
/// Used when an invalid argument is used in a function call.
UnknownVariable {
name: String,
arg_name: String,
valid_vars: Vec<String>,
},
}
63 changes: 39 additions & 24 deletions crates/biome_grit_patterns/src/grit_binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ use std::{borrow::Cow, collections::HashMap, path::Path};

#[derive(Clone, Debug, PartialEq)]
pub enum GritBinding<'a> {
/// Bindings to the file with the given path.
File(&'a Path),

/// Binds to a specific node.
Node(GritTargetNode<'a>),

Expand All @@ -34,8 +37,8 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> {
Self::Node(node)
}

fn from_path(_path: &'a Path) -> Self {
todo!()
fn from_path(path: &'a Path) -> Self {
Self::File(path)
}

fn from_range(range: ByteRange, source: &'a str) -> Self {
Expand All @@ -54,7 +57,7 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> {
fn singleton(&self) -> Option<GritTargetNode<'a>> {
match self {
Self::Node(node) => Some(node.clone()),
Self::Range(..) | Self::Empty(..) | Self::Constant(..) => None,
Self::File(..) | Self::Range(..) | Self::Empty(..) | Self::Constant(..) => None,
}
}

Expand All @@ -64,37 +67,37 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> {

fn position(&self, _language: &GritTargetLanguage) -> Option<Range> {
match self {
GritBinding::Node(node) => {
Self::Node(node) => {
let source = SourceFile::new(SourceCode {
text: node.text(),
line_starts: None,
});
source.to_grit_range(node.text_trimmed_range())
}
GritBinding::Range(range, source) => {
Self::Range(range, source) => {
let source = SourceFile::new(SourceCode {
text: source,
line_starts: None,
});
source.to_grit_range(*range)
}
GritBinding::Empty(..) | GritBinding::Constant(_) => None,
Self::File(..) | Self::Empty(..) | Self::Constant(_) => None,
}
}

fn range(&self, _language: &GritTargetLanguage) -> Option<ByteRange> {
match self {
GritBinding::Node(node) => Some(node.byte_range()),
GritBinding::Range(range, _) => Some(range.to_byte_range()),
GritBinding::Empty(..) | GritBinding::Constant(_) => None,
Self::Node(node) => Some(node.byte_range()),
Self::Range(range, _) => Some(range.to_byte_range()),
Self::File(..) | Self::Empty(..) | Self::Constant(_) => None,
}
}

fn code_range(&self, _language: &GritTargetLanguage) -> Option<CodeRange> {
match self {
GritBinding::Node(node) => Some(node.code_range()),
GritBinding::Range(range, source) => Some(range.to_code_range(source)),
GritBinding::Empty(..) | GritBinding::Constant(_) => None,
Self::Node(node) => Some(node.code_range()),
Self::Range(range, source) => Some(range.to_code_range(source)),
Self::File(..) | Self::Empty(..) | Self::Constant(_) => None,
}
}

Expand Down Expand Up @@ -129,31 +132,43 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> {

fn text(&self, _language: &GritTargetLanguage) -> anyhow::Result<Cow<'a, str>> {
match self {
GritBinding::Node(node) => Ok(node.text().into()),
GritBinding::Range(range, source) => {
Self::File(path) => Ok(path.to_string_lossy()),
Self::Node(node) => Ok(node.text().into()),
Self::Range(range, source) => {
Ok((&source[range.start().into()..range.end().into()]).into())
}
GritBinding::Empty(_, _) => Ok("".into()),
GritBinding::Constant(constant) => Ok(constant.to_string().into()),
Self::Empty(_, _) => Ok("".into()),
Self::Constant(constant) => Ok(constant.to_string().into()),
}
}

fn source(&self) -> Option<&'a str> {
todo!()
match self {
Self::Node(node) => Some(node.source()),
Self::Range(_, source) => Some(*source),
Self::Empty(node, _) => Some(node.source()),
Self::File(..) | Self::Constant(..) => None,
}
}

fn as_constant(&self) -> Option<&grit_pattern_matcher::constant::Constant> {
todo!()
fn as_constant(&self) -> Option<&Constant> {
match self {
Self::Constant(constant) => Some(constant),
_ => None,
}
}

fn as_filename(&self) -> Option<&std::path::Path> {
todo!()
fn as_filename(&self) -> Option<&Path> {
match self {
Self::File(path) => Some(path),
_ => None,
}
}

fn as_node(&self) -> Option<GritTargetNode<'a>> {
match self {
GritBinding::Node(node) => Some(node.clone()),
GritBinding::Range(..) | GritBinding::Empty(..) | GritBinding::Constant(_) => None,
Self::Node(node) => Some(node.clone()),
_ => None,
}
}

Expand All @@ -163,7 +178,7 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> {

fn list_items(&self) -> Option<impl Iterator<Item = GritTargetNode<'a>> + Clone> {
match self {
GritBinding::Node(node) if node.is_list() => Some(node.children()),
Self::Node(node) if node.is_list() => Some(node.children()),
_ => None,
}
}
Expand Down
113 changes: 94 additions & 19 deletions crates/biome_grit_patterns/src/grit_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ use crate::grit_target_node::GritTargetNode;
use crate::grit_tree::GritTargetTree;
use anyhow::Result;
use grit_pattern_matcher::context::{ExecContext, QueryContext};
use grit_pattern_matcher::file_owners::FileOwners;
use grit_pattern_matcher::file_owners::{FileOwner, FileOwners};
use grit_pattern_matcher::pattern::{
CallBuiltIn, GritFunctionDefinition, Pattern, PatternDefinition, PredicateDefinition, State,
};
use grit_util::AnalysisLogs;
use grit_util::{AnalysisLogs, FileOrigin};
use path_absolutize::Absolutize;
use std::path::PathBuf;

#[derive(Clone, Debug, PartialEq)]
pub struct GritQueryContext;
Expand All @@ -21,7 +23,7 @@ impl QueryContext for GritQueryContext {
type Node<'a> = GritTargetNode<'a>;
type NodePattern = GritNodePattern;
type LeafNodePattern = GritLeafNodePattern;
type ExecContext<'a> = GritExecContext;
type ExecContext<'a> = GritExecContext<'a>;
type Binding<'a> = GritBinding<'a>;
type CodeSnippet = GritCodeSnippet;
type ResolvedPattern<'a> = GritResolvedPattern<'a>;
Expand All @@ -30,32 +32,47 @@ impl QueryContext for GritQueryContext {
type Tree<'a> = GritTargetTree;
}

#[derive(Debug)]
pub struct GritExecContext {
pub struct GritExecContext<'a> {
lang: GritTargetLanguage,
loadable_files: &'a [GritTargetFile],
files: &'a FileOwners<GritTargetTree>,
functions: Vec<GritFunctionDefinition<GritQueryContext>>,
patterns: Vec<PatternDefinition<GritQueryContext>>,
predicates: Vec<PredicateDefinition<GritQueryContext>>,
}

impl GritExecContext {
pub fn new(lang: GritTargetLanguage) -> Self {
Self { lang }
impl<'a> GritExecContext<'a> {
pub fn new(
lang: GritTargetLanguage,
loadable_files: &'a [GritTargetFile],
files: &'a FileOwners<GritTargetTree>,
) -> Self {
Self {
lang,
loadable_files,
files,
functions: Vec::new(),
patterns: Vec::new(),
predicates: Vec::new(),
}
}
}

impl<'a> ExecContext<'a, GritQueryContext> for GritExecContext {
impl<'a> ExecContext<'a, GritQueryContext> for GritExecContext<'a> {
fn pattern_definitions(&self) -> &[PatternDefinition<GritQueryContext>] {
todo!()
&self.patterns
}

fn predicate_definitions(&self) -> &[PredicateDefinition<GritQueryContext>] {
todo!()
&self.predicates
}

fn function_definitions(&self) -> &[GritFunctionDefinition<GritQueryContext>] {
todo!()
&self.functions
}

fn ignore_limit_pattern(&self) -> bool {
todo!()
false
}

fn call_built_in(
Expand All @@ -65,11 +82,11 @@ impl<'a> ExecContext<'a, GritQueryContext> for GritExecContext {
_state: &mut State<'a, GritQueryContext>,
_logs: &mut AnalysisLogs,
) -> Result<GritResolvedPattern<'a>> {
todo!()
unimplemented!("built-in functions are still TODO")
}

fn files(&self) -> &FileOwners<GritTargetTree> {
todo!()
self.files
}

fn language(&self) -> &GritTargetLanguage {
Expand All @@ -92,10 +109,68 @@ impl<'a> ExecContext<'a, GritQueryContext> for GritExecContext {

fn load_file(
&self,
_file: &<GritQueryContext as QueryContext>::File<'a>,
_state: &mut State<'a, GritQueryContext>,
_logs: &mut AnalysisLogs,
file: &GritFile<'a>,
state: &mut State<'a, GritQueryContext>,
logs: &mut AnalysisLogs,
) -> Result<bool> {
todo!()
match file {
GritFile::Resolved(_) => {
// Assume the file is already loaded
}
GritFile::Ptr(ptr) => {
if state.files.is_loaded(ptr) {
return Ok(true);
}

let index = ptr.file;
let file = &self.loadable_files[index as usize];

// TODO: Verify the workspace's maximum file size.

let file = file_owner_from_matches(&file.path, &file.content, &self.lang, logs)?;
if let Some(file) = file {
self.files.push(file);
state.files.load_file(ptr, self.files.last().unwrap());
}
}
}
Ok(true)
}
}

fn file_owner_from_matches(
name: impl Into<PathBuf>,
source: &str,
language: &GritTargetLanguage,
logs: &mut AnalysisLogs,
) -> Result<Option<FileOwner<GritTargetTree>>> {
let name = name.into();

let Some(tree) = language
.get_parser()
.parse_file(source, Some(&name), logs, FileOrigin::Fresh)
else {
return Ok(None);
};

let absolute_path = name.absolutize()?.to_path_buf();
Ok(Some(FileOwner {
name,
absolute_path,
tree,
matches: Default::default(),
new: false,
}))
}

/// Simple wrapper for target files so that we can avoid doing file I/O inside
/// the Grit engine.
///
/// This should suffice as long as we only do single-file queries, but when we
/// want to support multifile queries, we probably need to implement a solution
/// that can use the Biome workspace.
#[derive(Clone, Debug)]
pub struct GritTargetFile {
pub path: PathBuf,
pub content: String,
}
Loading