use super::{
accessor::Accessor,
dynamic_snippet::{DynamicPattern, DynamicSnippet},
list_index::ListIndex,
patterns::Pattern,
state::{FilePtr, FileRegistry, State},
};
use crate::{binding::Binding, constant::Constant, context::QueryContext, effects::Effect};
use grit_util::{error::GritResult, AnalysisLogs, ByteRange, CodeRange, Range};
use itertools::Itertools;
use std::{
borrow::Cow,
collections::{BTreeMap, HashMap},
fmt::Debug,
path::Path,
};
pub trait ResolvedPattern<'a, Q: QueryContext>: Clone + Debug + PartialEq {
fn from_binding(binding: Q::Binding<'a>) -> Self;
fn from_constant(constant: Constant) -> Self;
fn from_constant_binding(constant: &'a Constant) -> Self {
Self::from_binding(Binding::from_constant(constant))
}
fn from_file_pointer(file: FilePtr) -> Self;
fn from_files(files: Self) -> Self;
fn from_list_parts(parts: impl Iterator<Item = Self>) -> Self;
fn from_node_binding(node: Q::Node<'a>) -> Self {
Self::from_binding(Binding::from_node(node))
}
fn from_path_binding(path: &'a Path) -> Self {
Self::from_binding(Binding::from_path(path))
}
fn from_range_binding(range: ByteRange, src: &'a str) -> Self {
Self::from_binding(Binding::from_range(range, src))
}
fn from_string(string: String) -> Self;
fn from_resolved_snippet(snippet: ResolvedSnippet<'a, Q>) -> Self;
fn from_dynamic_snippet(
snippet: &'a DynamicSnippet,
state: &mut State<'a, Q>,
context: &'a Q::ExecContext<'a>,
logs: &mut AnalysisLogs,
) -> GritResult<Self>;
fn from_dynamic_pattern(
pattern: &'a DynamicPattern<Q>,
state: &mut State<'a, Q>,
context: &'a Q::ExecContext<'a>,
logs: &mut AnalysisLogs,
) -> GritResult<Self>;
fn from_accessor(
accessor: &'a Accessor<Q>,
state: &mut State<'a, Q>,
context: &'a Q::ExecContext<'a>,
logs: &mut AnalysisLogs,
) -> GritResult<Self>;
fn from_list_index(
index: &'a ListIndex<Q>,
state: &mut State<'a, Q>,
context: &'a Q::ExecContext<'a>,
logs: &mut AnalysisLogs,
) -> GritResult<Self>;
fn from_pattern(
pattern: &'a Pattern<Q>,
state: &mut State<'a, Q>,
context: &'a Q::ExecContext<'a>,
logs: &mut AnalysisLogs,
) -> GritResult<Self>;
fn from_patterns(
patterns: &'a [Option<Pattern<Q>>],
state: &mut State<'a, Q>,
context: &'a Q::ExecContext<'a>,
logs: &mut AnalysisLogs,
) -> GritResult<Vec<Option<Self>>> {
patterns
.iter()
.map(|p| match p {
Some(pattern) => Ok(Some(Self::from_pattern(pattern, state, context, logs)?)),
None => Ok(None),
})
.collect()
}
fn undefined() -> Self {
Self::from_constant(Constant::Undefined)
}
fn extend(
&mut self,
with: Q::ResolvedPattern<'a>,
effects: &mut Vec<Effect<'a, Q>>,
language: &Q::Language<'a>,
) -> GritResult<()>;
fn float(&self, state: &FileRegistry<'a, Q>, language: &Q::Language<'a>) -> GritResult<f64>;
fn get_bindings(&self) -> Option<impl Iterator<Item = Q::Binding<'a>>>;
fn get_file(&self) -> Option<&Q::File<'a>>;
fn get_file_pointers(&self) -> Option<Vec<FilePtr>>;
fn get_files(&self) -> Option<&Self>;
fn get_last_binding(&self) -> Option<&Q::Binding<'a>>;
fn get_list_item_at(&self, index: isize) -> Option<&Self>;
fn get_list_item_at_mut(&mut self, index: isize) -> Option<&mut Self>;
fn get_list_items(&self) -> Option<impl Iterator<Item = &Self>>;
fn get_list_binding_items(&self) -> Option<impl Iterator<Item = Self> + Clone>;
fn get_map(&self) -> Option<&BTreeMap<String, Self>>;
fn get_map_mut(&mut self) -> Option<&mut BTreeMap<String, Self>>;
fn get_snippets(&self) -> Option<impl Iterator<Item = ResolvedSnippet<'a, Q>>>;
fn is_binding(&self) -> bool;
fn is_list(&self) -> bool;
fn is_truthy(&self, state: &mut State<'a, Q>, language: &Q::Language<'a>) -> GritResult<bool>;
fn linearized_text(
&self,
language: &Q::Language<'a>,
effects: &[Effect<'a, Q>],
files: &FileRegistry<'a, Q>,
memo: &mut HashMap<CodeRange, Option<String>>,
should_pad_snippet: bool,
logs: &mut AnalysisLogs,
) -> GritResult<Cow<'a, str>>;
fn matches_undefined(&self) -> bool;
fn matches_false_or_undefined(&self) -> bool;
fn normalize_insert(
&mut self,
binding: &Q::Binding<'a>,
is_first: bool,
language: &Q::Language<'a>,
) -> GritResult<()>;
fn position(&self, language: &Q::Language<'a>) -> Option<Range>;
fn push_binding(&mut self, binding: Q::Binding<'a>) -> GritResult<()>;
fn set_list_item_at_mut(&mut self, index: isize, value: Self) -> GritResult<bool>;
fn text(
&self,
state: &FileRegistry<'a, Q>,
language: &Q::Language<'a>,
) -> GritResult<Cow<'a, str>>;
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResolvedSnippet<'a, Q: QueryContext> {
Text(Cow<'a, str>),
Binding(Q::Binding<'a>),
LazyFn(Box<LazyBuiltIn<'a, Q>>),
}
impl<'a, Q: QueryContext> ResolvedSnippet<'a, Q> {
pub fn from_binding(binding: Q::Binding<'a>) -> ResolvedSnippet<Q> {
Self::Binding(binding)
}
pub fn padding(
&self,
state: &FileRegistry<'a, Q>,
language: &Q::Language<'a>,
) -> GritResult<usize> {
let text = self.text(state, language)?;
let len = text.len();
let trim_len = text.trim_end_matches(' ').len();
Ok(len - trim_len)
}
pub fn text(
&self,
state: &FileRegistry<'a, Q>,
language: &Q::Language<'a>,
) -> GritResult<Cow<'a, str>> {
match self {
ResolvedSnippet::Text(text) => Ok(text.clone()),
ResolvedSnippet::Binding(binding) => {
binding.text(language).map(|c| c.into_owned().into())
}
ResolvedSnippet::LazyFn(lazy) => lazy.text(state, language),
}
}
pub fn linearized_text(
&self,
language: &Q::Language<'a>,
effects: &[Effect<'a, Q>],
files: &FileRegistry<'a, Q>,
memo: &mut HashMap<CodeRange, Option<String>>,
distributed_indent: Option<usize>,
logs: &mut AnalysisLogs,
) -> GritResult<Cow<str>> {
let res = match self {
Self::Text(text) => {
if let Some(indent) = distributed_indent {
Ok(pad_text(text, indent).into())
} else {
Ok(text.clone())
}
}
Self::Binding(binding) => {
binding.linearized_text(language, effects, files, memo, distributed_indent, logs)
}
Self::LazyFn(lazy) => {
lazy.linearized_text(language, effects, files, memo, distributed_indent, logs)
}
};
res
}
pub fn is_truthy(
&self,
state: &mut State<'a, Q>,
language: &Q::Language<'a>,
) -> GritResult<bool> {
let truthiness = match self {
Self::Binding(b) => b.is_truthy(),
Self::Text(t) => !t.is_empty(),
Self::LazyFn(t) => !t.text(&state.files, language)?.is_empty(),
};
Ok(truthiness)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum LazyBuiltIn<'a, Q: QueryContext> {
Join(JoinFn<'a, Q>),
}
impl<'a, Q: QueryContext> LazyBuiltIn<'a, Q> {
fn linearized_text(
&self,
language: &Q::Language<'a>,
effects: &[Effect<'a, Q>],
files: &FileRegistry<'a, Q>,
memo: &mut HashMap<CodeRange, Option<String>>,
distributed_indent: Option<usize>,
logs: &mut AnalysisLogs,
) -> GritResult<Cow<str>> {
match self {
LazyBuiltIn::Join(join) => {
join.linearized_text(language, effects, files, memo, distributed_indent, logs)
}
}
}
pub fn text(
&self,
state: &FileRegistry<'a, Q>,
language: &Q::Language<'a>,
) -> GritResult<Cow<'a, str>> {
match self {
LazyBuiltIn::Join(join) => join.text(state, language),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct JoinFn<'a, Q: QueryContext> {
pub list: Vec<Q::ResolvedPattern<'a>>,
separator: String,
}
impl<'a, Q: QueryContext> JoinFn<'a, Q> {
pub fn from_patterns(
patterns: impl Iterator<Item = Q::ResolvedPattern<'a>>,
separator: String,
) -> Self {
Self {
list: patterns.collect(),
separator,
}
}
fn linearized_text(
&self,
language: &Q::Language<'a>,
effects: &[Effect<'a, Q>],
files: &FileRegistry<'a, Q>,
memo: &mut HashMap<CodeRange, Option<String>>,
distributed_indent: Option<usize>,
logs: &mut AnalysisLogs,
) -> GritResult<Cow<str>> {
let res = self
.list
.iter()
.map(|pattern| {
pattern.linearized_text(
language,
effects,
files,
memo,
distributed_indent.is_some(),
logs,
)
})
.collect::<GritResult<Vec<_>>>()?
.join(&self.separator);
if let Some(padding) = distributed_indent {
Ok(pad_text(&res, padding).into())
} else {
Ok(res.into())
}
}
fn text(
&self,
state: &FileRegistry<'a, Q>,
language: &Q::Language<'a>,
) -> GritResult<Cow<'a, str>> {
Ok(self
.list
.iter()
.map(|pattern| pattern.text(state, language))
.collect::<GritResult<Vec<_>>>()?
.join(&self.separator)
.into())
}
}
fn pad_text(text: &str, padding: usize) -> String {
if text.trim().is_empty() {
text.to_owned()
} else {
let mut res = if text.starts_with('\n') {
"\n".to_owned()
} else {
String::new()
};
res.push_str(&text.lines().join(&format!("\n{}", " ".repeat(padding))));
if text.ends_with('\n') {
res.push('\n')
};
res
}
}
pub trait File<'a, Q: QueryContext> {
fn name(&self, files: &FileRegistry<'a, Q>) -> Q::ResolvedPattern<'a>;
fn absolute_path(
&self,
files: &FileRegistry<'a, Q>,
language: &Q::Language<'a>,
) -> GritResult<Q::ResolvedPattern<'a>>;
fn body(&self, files: &FileRegistry<'a, Q>) -> Q::ResolvedPattern<'a>;
fn binding(&self, files: &FileRegistry<'a, Q>) -> Q::ResolvedPattern<'a>;
}
#[derive(Debug, Clone, PartialEq)]
pub struct ResolvedFile<'a, Q: QueryContext> {
pub name: Q::ResolvedPattern<'a>,
pub body: Q::ResolvedPattern<'a>,
}