Skip to content
Draft
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
13 changes: 6 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion brush-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ async-recursion = "1.1.1"
async-trait = "0.1.89"
brush-parser = { version = "^0.4.0", path = "../brush-parser" }
bon = "3.9.1"
cached = "0.59.0"
cached = "2.0.2"
cfg-if = "1.0.4"
chrono = "0.4.44"
clap = { version = "4.6.0", features = ["derive", "wrap_help"] }
Expand Down
24 changes: 9 additions & 15 deletions brush-core/src/expansion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1467,7 +1467,9 @@ impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> {
let mut transformed_fields = vec![];
for field in expanded_parameter.fields {
let s = String::from(field);
let transformed = self.apply_transform_to(&op, s, came_from_undefined).await?;
let transformed = self
.apply_transform_to(&op, &s, came_from_undefined)
.await?;
transformed_fields.push(WordField::from(transformed));
}

Expand Down Expand Up @@ -2000,21 +2002,19 @@ impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> {
async fn apply_transform_to(
&mut self,
op: &ParameterTransformOp,
s: String,
s: &str,
came_from_undefined: bool,
) -> Result<String, error::Error> {
match op {
brush_parser::word::ParameterTransformOp::PromptExpand => {
prompt::expand_prompt(self.shell, self.params, s).await
}
brush_parser::word::ParameterTransformOp::CapitalizeInitial => {
Ok(to_initial_capitals(s.as_str()))
Ok(to_initial_capitals(s))
}
brush_parser::word::ParameterTransformOp::ExpandEscapeSequences => {
let (result, _) = escape::expand_backslash_escapes(
s.as_str(),
escape::EscapeExpansionMode::AnsiCQuotes,
)?;
let (result, _) =
escape::expand_backslash_escapes(s, escape::EscapeExpansionMode::AnsiCQuotes)?;
Ok(String::from_utf8_lossy(result.as_slice()).into_owned())
}
brush_parser::word::ParameterTransformOp::PossiblyQuoteWithArraysExpanded {
Expand All @@ -2025,20 +2025,14 @@ impl<'a, SE: extensions::ShellExtensions> WordExpander<'a, SE> {
} else {
// TODO(expansion): This isn't right for arrays.
// TODO(expansion): This doesn't honor 'separate_words'
Ok(escape::force_quote(
s.as_str(),
escape::QuoteMode::SingleQuote,
))
Ok(escape::force_quote(s, escape::QuoteMode::SingleQuote))
}
}
brush_parser::word::ParameterTransformOp::Quoted => {
if came_from_undefined {
Ok(String::new())
} else {
Ok(escape::force_quote(
s.as_str(),
escape::QuoteMode::SingleQuote,
))
Ok(escape::force_quote(s, escape::QuoteMode::SingleQuote))
}
}
brush_parser::word::ParameterTransformOp::ToLowerCase => Ok(s.to_lowercase()),
Expand Down
8 changes: 4 additions & 4 deletions brush-core/src/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const VERSION_PATCH: &str = env!("CARGO_PKG_VERSION_PATCH");
pub(crate) async fn expand_prompt(
shell: &mut Shell<impl extensions::ShellExtensions>,
params: &ExecutionParameters,
spec: String,
spec: &str,
) -> Result<String, error::Error> {
// Parse the prompt spec into its pieces.
let prompt_pieces = parse_prompt(spec)?;
Expand Down Expand Up @@ -58,11 +58,11 @@ pub(crate) async fn expand_prompt(
Ok(formatted_prompt)
}

#[cached::proc_macro::cached(size = 64, result = true)]
#[cached::macros::cached(max_size = 64, key = "String", convert = r#"{ spec.to_owned() }"#)]
fn parse_prompt(
spec: String,
spec: &str,
) -> Result<Vec<brush_parser::prompt::PromptPiece>, brush_parser::WordParseError> {
brush_parser::prompt::parse(spec.as_str())
brush_parser::prompt::parse(spec)
}

fn format_prompt_piece(
Expand Down
21 changes: 17 additions & 4 deletions brush-core/src/regex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ use std::cell::RefCell;
use crate::error;
use cached::Cached;

/// Cache mapping a (pattern, case-insensitive, multiline) key to a compiled regex.
type RegexCache = cached::LruCache<(String, bool, bool), fancy_regex::Regex>;

thread_local! {
static REGEX_CACHE: RefCell<cached::SizedCache<(String, bool, bool), fancy_regex::Regex>> =
RefCell::new(cached::SizedCache::with_size(64));
// Wrapped in `Option` so that if cache construction ever fails we gracefully
// degrade to compiling regexes uncached rather than panicking. (With a fixed
// positive `max_size` this always succeeds, but `build()` is fallible.)
static REGEX_CACHE: RefCell<Option<RegexCache>> =
RefCell::new(cached::LruCache::builder().max_size(64).build().ok());
}

/// Represents a piece of a regular expression.
Expand Down Expand Up @@ -103,7 +109,12 @@ pub(crate) fn compile_regex(
// Move regex_str into the key to avoid cloning on cache-hit path.
let key = (regex_str, case_insensitive, multiline);

let cached_regex = REGEX_CACHE.with(|cache| cache.borrow_mut().cache_get(&key).cloned());
let cached_regex = REGEX_CACHE.with(|cache| {
cache
.borrow_mut()
.as_mut()
.and_then(|c| c.cache_get(&key).cloned())
});
if let Some(re) = cached_regex {
return Ok(re);
}
Expand Down Expand Up @@ -133,7 +144,9 @@ pub(crate) fn compile_regex(
drop(regex_str);

REGEX_CACHE.with(|cache| {
cache.borrow_mut().cache_set(key, re.clone());
if let Some(c) = cache.borrow_mut().as_mut() {
c.cache_set(key, re.clone());
}
});

Ok(re)
Expand Down
15 changes: 10 additions & 5 deletions brush-core/src/shell/parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ impl<SE: extensions::ShellExtensions> Shell<SE> {
&self,
s: S,
) -> Result<brush_parser::ast::Program, brush_parser::ParseError> {
parse_string_impl(s.into(), self.parser_options())
let s: String = s.into();
parse_string_impl(&s, &self.parser_options())
}

/// Returns the options that should be used for parsing shell programs; reflects
Expand All @@ -44,12 +45,16 @@ impl<SE: extensions::ShellExtensions> Shell<SE> {
}
}

#[cached::proc_macro::cached(size = 64, result = true)]
#[cached::macros::cached(
max_size = 64,
key = "(String, brush_parser::ParserOptions)",
convert = r#"{ (s.to_owned(), parser_options.to_owned()) }"#
)]
fn parse_string_impl(
s: String,
parser_options: brush_parser::ParserOptions,
s: &str,
parser_options: &brush_parser::ParserOptions,
) -> Result<brush_parser::ast::Program, brush_parser::ParseError> {
let mut parser = create_parser(s.as_bytes(), &parser_options);
let mut parser = create_parser(s.as_bytes(), parser_options);

tracing::debug!(target: trace_categories::PARSE, "Parsing string as program...");
parser.parse_program()
Expand Down
6 changes: 4 additions & 2 deletions brush-core/src/shell/prompts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ impl<SE: extensions::ShellExtensions> Shell<SE> {
let prev_last_result = self.last_exit_status();
let prev_last_pipeline_statuses = self.last_pipeline_statuses.clone();

// Expand it.
// Expand it. We must own the spec here: it borrows `self` (via the
// returned `Cow`), and `expand_prompt` needs `&mut self`.
let prompt_spec = prompt_spec.into_owned();
let params = self.default_exec_params();
let result = prompt::expand_prompt(self, &params, prompt_spec.into_owned()).await;
let result = prompt::expand_prompt(self, &params, &prompt_spec).await;

// Restore the last exit status.
self.last_pipeline_statuses = prev_last_pipeline_statuses;
Expand Down
2 changes: 1 addition & 1 deletion brush-parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ winnow-parser = ["dep:winnow"]
[dependencies]
arbitrary = { version = "1.4.2", optional = true, features = ["derive"] }
bon = "3.9.1"
cached = "0.59.0"
cached = "2.0.2"
indenter = "0.3.4"
miette = { version = "7.6.0", optional = true, default-features = false, features = [
"derive",
Expand Down
8 changes: 4 additions & 4 deletions brush-parser/src/arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ use crate::error;
///
/// * `input` - The arithmetic expression to parse, in string form.
pub fn parse(input: &str) -> Result<ast::ArithmeticExpr, error::WordParseError> {
cacheable_parse(input.to_owned())
cacheable_parse(input)
}

#[cached::proc_macro::cached(size = 64, result = true)]
fn cacheable_parse(input: String) -> Result<ast::ArithmeticExpr, error::WordParseError> {
#[cached::macros::cached(max_size = 64, key = "String", convert = r#"{ input.to_owned() }"#)]
fn cacheable_parse(input: &str) -> Result<ast::ArithmeticExpr, error::WordParseError> {
tracing::debug!(target: "arithmetic", "parsing arithmetic expression: '{input}'");
arithmetic::full_expression(input.as_str())
arithmetic::full_expression(input)
.map_err(|e| error::WordParseError::ArithmeticExpression(e.into()))
}

Expand Down
15 changes: 10 additions & 5 deletions brush-parser/src/tokenizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,15 +491,20 @@ pub fn tokenize_str_with_options(
input: &str,
options: &TokenizerOptions,
) -> Result<Vec<Token>, TokenizerError> {
uncached_tokenize_string(input.to_owned(), options.to_owned())
uncached_tokenize_string(input, options)
}

#[cached::proc_macro::cached(name = "TOKENIZE_CACHE", size = 64, result = true)]
#[cached::macros::cached(
name = "TOKENIZE_CACHE",
max_size = 64,
key = "(String, TokenizerOptions)",
convert = r#"{ (input.to_owned(), options.to_owned()) }"#
)]
fn uncached_tokenize_string(
input: String,
options: TokenizerOptions,
input: &str,
options: &TokenizerOptions,
) -> Result<Vec<Token>, TokenizerError> {
uncached_tokenize_str(input.as_str(), &options)
uncached_tokenize_str(input, options)
}

/// Break the given input shell script string into tokens, returning the tokens.
Expand Down
16 changes: 10 additions & 6 deletions brush-parser/src/word.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,18 +528,22 @@ pub fn parse(
word: &str,
options: &ParserOptions,
) -> Result<Vec<WordPieceWithSource>, error::WordParseError> {
cacheable_parse(word.to_owned(), options.to_owned())
cacheable_parse(word, options)
}

#[cached::proc_macro::cached(size = 64, result = true)]
#[cached::macros::cached(
max_size = 64,
key = "(String, ParserOptions)",
convert = r#"{ (word.to_owned(), options.to_owned()) }"#
)]
fn cacheable_parse(
word: String,
options: ParserOptions,
word: &str,
options: &ParserOptions,
) -> Result<Vec<WordPieceWithSource>, error::WordParseError> {
tracing::debug!(target: "expansion", "Parsing word '{}'", word);

let pieces = expansion_parser::unexpanded_word(word.as_str(), &options)
.map_err(|err| error::WordParseError::Word(word.clone(), err.into()))?;
let pieces = expansion_parser::unexpanded_word(word, options)
.map_err(|err| error::WordParseError::Word(word.to_owned(), err.into()))?;

tracing::debug!(target: "expansion", "Parsed word '{}' => {{{:?}}}", word, pieces);

Expand Down
Loading