Skip to content
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
37 changes: 17 additions & 20 deletions Cargo.lock

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

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ too_many_arguments = "allow"

[workspace.dependencies]
actix-web = "4.13.0"
aether_factory = { git = "https://github.com/posit-dev/air", package = "air_r_factory", rev = "b8ead5f024da1ddaf118722045bcda430bac1530" }
aether_factory = { git = "https://github.com/posit-dev/air", package = "air_r_factory", rev = "aa552899dece399c7f6234d0741b66c628696b0f" }
aether_lsp_utils = { path = "crates/aether_lsp_utils" }
aether_parser = { git = "https://github.com/posit-dev/air", package = "air_r_parser", rev = "b8ead5f024da1ddaf118722045bcda430bac1530" }
aether_syntax = { git = "https://github.com/posit-dev/air", package = "air_r_syntax", rev = "b8ead5f024da1ddaf118722045bcda430bac1530" }
aether_parser = { git = "https://github.com/posit-dev/air", package = "air_r_parser", rev = "aa552899dece399c7f6234d0741b66c628696b0f" }
aether_syntax = { git = "https://github.com/posit-dev/air", package = "air_r_syntax", rev = "aa552899dece399c7f6234d0741b66c628696b0f" }
aether_path = { path = "crates/aether_path" }
amalthea = { path = "crates/amalthea" }
annotate-snippets = "0.12.16"
Expand Down
74 changes: 16 additions & 58 deletions crates/oak_core/src/syntax_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ use aether_syntax::AnyRSelector;
use aether_syntax::AnyRStatement;
use aether_syntax::RCall;
use aether_syntax::RIdentifier;
use aether_syntax::RStringValue;
use aether_syntax::RSyntaxKind;
use biome_rowan::AstNode;
use biome_rowan::AstSeparatedList;

Expand All @@ -29,57 +27,10 @@ impl RIdentifierExt for RIdentifier {
}
}

pub trait RStringValueExt {
/// Return the string contents without surrounding delimiters.
///
/// TODO(air): Replace with `RStringValue::inner_string_text()` once Air is
/// fully updated.
fn string_text(&self) -> Option<String>;
}

impl RStringValueExt for RStringValue {
fn string_text(&self) -> Option<String> {
let token = self.value().ok()?;
let text = token.text_trimmed();

match token.kind() {
RSyntaxKind::R_DOUBLE_QUOTED_STRING_LITERAL => {
Some(text.strip_prefix('"')?.strip_suffix('"')?.to_string())
},
RSyntaxKind::R_SINGLE_QUOTED_STRING_LITERAL => {
Some(text.strip_prefix('\'')?.strip_suffix('\'')?.to_string())
},
RSyntaxKind::R_RAW_STRING_LITERAL => Some(raw_string_inner(text)?.to_string()),
_ => None,
}
}
}

fn raw_string_inner(text: &str) -> Option<&str> {
let rest = text.strip_prefix(['r', 'R'])?;

let quote = rest.chars().next().filter(|c| matches!(c, '"' | '\''))?;
let rest = &rest[quote.len_utf8()..];

let dashes = rest.len() - rest.trim_start_matches('-').len();
let rest = &rest[dashes..];

let close_bracket = match rest.chars().next()? {
'(' => ')',
'[' => ']',
'{' => '}',
_ => return None,
};
let rest = &rest[1..];

let rest = rest.strip_suffix(quote)?;
let rest = rest.strip_suffix(&"-".repeat(dashes))?;
rest.strip_suffix(close_bracket)
}

pub trait AnyRSelectorExt {
/// Uses [RIdentifierExt::name_text()] and [RStringValueExt::string_text()] to report
/// the name without backticks or quotes
/// Uses [RIdentifierExt::name_text()] and
/// [aether_syntax::RStringValue::inner_string_text()] to report the name
/// without backticks or quotes
///
/// Returns [None] for [aether_syntax::AnyRSelector::RDotDotI] and
/// [aether_syntax::AnyRSelector::RDots], which we don't consider standard
Expand All @@ -91,7 +42,7 @@ impl AnyRSelectorExt for AnyRSelector {
fn identifier_text(&self) -> Option<String> {
match self {
AnyRSelector::RIdentifier(node) => Some(node.name_text()),
AnyRSelector::RStringValue(node) => node.string_text(),
AnyRSelector::RStringValue(node) => Some(node.inner_string_text().ok()?.to_string()),
AnyRSelector::RDots(_) | AnyRSelector::RDotDotI(_) => None,
AnyRSelector::RBogusSelector(_) => None,
}
Expand Down Expand Up @@ -166,35 +117,42 @@ mod tests {
#[test]
fn string_double_quoted() {
assert_matches!(parse_single_expr("\"hello\""), AnyRExpression::AnyRValue(AnyRValue::RStringValue(s)) => {
assert_eq!(s.string_text().unwrap(), "hello");
assert_eq!(s.inner_string_text().unwrap().text(), "hello");
});
}

#[test]
fn string_single_quoted() {
assert_matches!(parse_single_expr("'world'"), AnyRExpression::AnyRValue(AnyRValue::RStringValue(s)) => {
assert_eq!(s.string_text().unwrap(), "world");
assert_eq!(s.inner_string_text().unwrap().text(), "world");
});
}

#[test]
fn string_raw() {
assert_matches!(parse_single_expr("r\"(hello)\""), AnyRExpression::AnyRValue(AnyRValue::RStringValue(s)) => {
assert_eq!(s.string_text().unwrap(), "hello");
assert_eq!(s.inner_string_text().unwrap().text(), "hello");
});
}

#[test]
fn string_raw_with_dashes_and_brackets() {
assert_matches!(parse_single_expr("R'--[a)-\"b]--'"), AnyRExpression::AnyRValue(AnyRValue::RStringValue(s)) => {
assert_eq!(s.string_text().unwrap(), "a)-\"b");
assert_eq!(s.inner_string_text().unwrap().text(), "a)-\"b");
});
}

#[test]
fn string_raw_braces() {
assert_matches!(parse_single_expr("r\"{x}\""), AnyRExpression::AnyRValue(AnyRValue::RStringValue(s)) => {
assert_eq!(s.string_text().unwrap(), "x");
assert_eq!(s.inner_string_text().unwrap().text(), "x");
});
}

#[test]
fn string_raw_pipes() {
assert_matches!(parse_single_expr("r\"-|a|-\""), AnyRExpression::AnyRValue(AnyRValue::RStringValue(s)) => {
assert_eq!(s.inner_string_text().unwrap().text(), "a");
});
}
}
5 changes: 3 additions & 2 deletions crates/oak_package_metadata/src/namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use biome_rowan::AstSeparatedList;
use biome_rowan::SyntaxResult;
use oak_core::syntax_ext::AnyRStatementExt;
use oak_core::syntax_ext::RIdentifierExt;
use oak_core::syntax_ext::RStringValueExt;
use stdext::SortedVec;

/// Parsed NAMESPACE file
Expand Down Expand Up @@ -137,7 +136,9 @@ fn collect_arg_identifiers(
fn directive_name(value: &AnyRExpression) -> Option<String> {
match value {
AnyRExpression::RIdentifier(ident) => Some(ident.name_text()),
AnyRExpression::AnyRValue(AnyRValue::RStringValue(string)) => string.string_text(),
AnyRExpression::AnyRValue(AnyRValue::RStringValue(string)) => {
Some(string.inner_string_text().ok()?.to_string())
},
_ => None,
}
}
Expand Down
3 changes: 1 addition & 2 deletions crates/oak_semantic/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ use biome_rowan::AstNode;
use biome_rowan::TextRange;
use biome_rowan::TextSize;
use oak_core::syntax_ext::RIdentifierExt;
use oak_core::syntax_ext::RStringValueExt;
use oak_index_vec::Idx;
use oak_index_vec::IndexVec;
use rustc_hash::FxHashMap;
Expand Down Expand Up @@ -447,7 +446,7 @@ fn assignment_name(target: &AnyRExpression) -> Option<(String, TextRange)> {
},
// `"x" <- 1` is equivalent to `x <- 1` in R
AnyRExpression::AnyRValue(AnyRValue::RStringValue(s)) => {
let name = s.string_text()?;
let name = s.inner_string_text().ok()?.to_string();
let range = s.syntax().text_trimmed_range();
Some((name, range))
},
Expand Down
7 changes: 4 additions & 3 deletions crates/oak_semantic/src/effects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use biome_rowan::TextRange;
// can name the `name_expr` field's type without depending on oak_core directly.
pub use oak_core::range::RangedAstPtr;
use oak_core::syntax_ext::RIdentifierExt;
use oak_core::syntax_ext::RStringValueExt;
use rustc_hash::FxHashMap;

use crate::semantic_index::AmbiguityReason;
Expand Down Expand Up @@ -497,7 +496,7 @@ fn match_named(arg: &RArgument, formals: Formals) -> Option<usize> {
let name = clause.name().ok()?;
let name_text = match &name {
AnyRArgumentName::RIdentifier(ident) => ident.name_text(),
AnyRArgumentName::RStringValue(s) => s.string_text()?,
AnyRArgumentName::RStringValue(s) => s.inner_string_text().ok()?.to_string(),
_ => return None,
};
formals
Expand Down Expand Up @@ -744,7 +743,9 @@ impl AssignHandler for BindingOperatorHandler {
pub fn resolve_quoted_symbol_or_string(value: &AnyRExpression) -> Option<String> {
match value {
AnyRExpression::RIdentifier(ident) => Some(ident.name_text()),
AnyRExpression::AnyRValue(AnyRValue::RStringValue(s)) => s.string_text(),
AnyRExpression::AnyRValue(AnyRValue::RStringValue(s)) => {
Some(s.inner_string_text().ok()?.to_string())
},
_ => None,
}
}
6 changes: 4 additions & 2 deletions crates/oak_semantic/src/effects/value_eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use aether_syntax::RCall;
use biome_rowan::AstNode;
use biome_rowan::TextRange;
use oak_core::syntax_ext::RIdentifierExt;
use oak_core::syntax_ext::RStringValueExt;

use crate::effects::CallContext;
use crate::effects::CalleeOrigin;
Expand Down Expand Up @@ -46,7 +45,10 @@ impl CallContext<'_> {
pub fn resolve_static_value(&mut self, value: &AnyRExpression) -> Option<StaticValue> {
match value {
AnyRExpression::AnyRValue(AnyRValue::RStringValue(s)) => {
Some(StaticValue::Character(vec![s.string_text()?]))
Some(StaticValue::Character(vec![s
.inner_string_text()
.ok()?
.to_string()]))
},
AnyRExpression::RNullExpression(_) => Some(StaticValue::Null),
AnyRExpression::RCall(call) => {
Expand Down
2 changes: 1 addition & 1 deletion crates/oak_semantic/tests/integration/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,7 @@ fn test_source_path_with_quote_and_backslash_renders_valid_r() {
.text;
assert_eq!(text, "source(\"a\\\"b\\\\c.R\")\n");

// `RStringValueExt::string_text()` strips the delimiters and returns the
// `RStringValue::inner_string_text()` strips the delimiters and returns the
// token's raw text, so the path Oak sees keeps the escapes rather than the
// characters R would resolve them to.
let index = index_with_base(&text);
Expand Down
Loading