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
7 changes: 2 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@ jobs:
uses: actions/checkout@v4

- name: Install stable toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt
components: rustfmt, clippy

- name: Set up Python 3.9
uses: actions/setup-python@v5
Expand Down
5 changes: 3 additions & 2 deletions crates/concrete-syntax/src/concrete_syntax.pest
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ capture = { (":[" ~ identifier ~ capture_mode? ~ "]") | "@"~identifier } // FIXM
capture_mode = { "+" | "*" | "?"}
identifier = { (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }

// Literal text - single word/token without whitespace
literal_text = { (!( ":[" | "|>" | "@" ) ~ ANY)+ }
// Literal text - single word/token without whitespace, with escape support
literal_text = { (escaped_char | (!( ":[" | "|>" | "@" ) ~ ANY))+ }
escaped_char = { "\\" ~ ("@" | ":" | "\\") }
WHITESPACE = _{ (" " | "\t" | "\r" | "\n")+ }

// Where constraints (extensible for future constraint types)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,8 @@ fn match_at_all_tree_levels(
///
/// 1) capture = text(node1...node2)
/// 2) match_cs_pattern(
/// remaining_elements, // CS elements still to match
/// /* should_match */ true if node3 exists
/// remaining_elements, // CS elements still to match
/// /* should_match */ true if node3 exists
/// ) starting at node3, node4, ...
fn try_match_node_range(
ctx: &mut MatchingContext<'_>, var_name: &str, constraints: &[CsConstraint],
Expand Down
11 changes: 5 additions & 6 deletions crates/concrete-syntax/src/models/concrete_syntax/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ pub enum CaptureMode {
Optional, // :[var?]
}

/// Decode \" \\ \n \t \/ … inside a string literal.
/// Decode \" \\ \n \t \/ \@ \: … inside a string literal.
fn unescape(src: &str) -> String {
let mut out = String::with_capacity(src.len());
let mut chars = src.chars();
Expand All @@ -82,6 +82,8 @@ fn unescape(src: &str) -> String {
Some('n') => out.push('\n'),
Some('t') => out.push('\t'),
Some('/') => out.push('/'),
Some('@') => out.push('@'),
Some(':') => out.push(':'),
Some(other) => {
out.push('\\');
out.push(other);
Expand Down Expand Up @@ -383,7 +385,8 @@ impl ConcreteSyntax {
literal_text => {
// Split the literal text on whitespace, similar to Python's .split()
let text = pair.as_str();
Ok(Self::parse_literal_tokens(text))
let unescaped_text = unescape(text);
Ok(Self::parse_literal_tokens(&unescaped_text))
}
delimited_literal => {
// Same as literal_text but with escape handling for \/
Expand Down Expand Up @@ -416,7 +419,3 @@ impl ConcreteSyntax {
})
}
}

#[cfg(test)]
#[path = "unit_tests/parser_test.rs"]
mod parser_test;
4 changes: 0 additions & 4 deletions crates/concrete-syntax/src/models/concrete_syntax/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,3 @@ impl ConcreteSyntax {
})
}
}

#[cfg(test)]
#[path = "unit_tests/resolver_test.rs"]
mod resolver_test;
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ pub type Node<'a> = NativeNode<'a>;
#[cfg(feature = "native")]
pub type TreeCursor<'a> = NativeCursor<'a>;

#[cfg(not(feature = "native"))]
pub type Node<'a> = WasmNodeWrapper;
#[cfg(not(feature = "native"))]
pub type TreeCursor<'a> = WasmCursorWrapper;

// Re-export raw tree-sitter types with different names to avoid confusion
#[cfg(feature = "native")]
pub use tree_sitter::{Node as RawNode, TreeCursor as RawTreeCursor};
Expand Down Expand Up @@ -287,12 +292,6 @@ impl SyntaxCursor for WasmCursorWrapper {
}
}

// WASM type aliases
#[cfg(feature = "wasm")]
pub type Node<'a> = WasmNodeWrapper;
#[cfg(feature = "wasm")]
pub type TreeCursor<'a> = WasmCursorWrapper;

/// Adapter functions for working with tree-sitter types directly
/// These provide a bridge between the trait-based and direct tree-sitter APIs
#[cfg(feature = "native")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -542,3 +542,25 @@ fn test_contains_constraint_bug_minimal() {
GO,
);
}

#[test]
fn test_escaped_at_symbol() {
run_test(
"var x = @something;",
"var :[name] = \\@something;",
1,
vec![vec![("name", "x")]],
GO,
);
}

#[test]
fn test_escaped_colon_symbol() {
run_test(
"var x = :something;",
"var :[name] = \\:something;",
1,
vec![vec![("name", "x")]],
GO,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -501,4 +501,45 @@ mod tests {
.unwrap_err()
.contains("'root contains' is not supported"));
}

#[test]
fn test_parse_escaped_at_symbol() {
let input = "var :[name] = \\@something;";
let result = ConcreteSyntax::parse(input).unwrap();
let elements = result.pattern.sequence;

// Should have: "var", capture "name", "=", "@something", ";"
assert!(elements.len() >= 4);

// Find the literal with @something
let at_literal = elements
.iter()
.find(|e| matches!(e, CsElement::Literal(text) if text.contains("@")));

match at_literal {
Some(CsElement::Literal(text)) => {
assert_eq!(text, "@something;");
}
_ => panic!("Expected literal with @something, got: {elements:#?}"),
}
}

#[test]
fn test_parse_escaped_colon_symbol() {
let input = "var :[name] = \\:[something]";
let result = ConcreteSyntax::parse(input).unwrap();
let elements = result.pattern.sequence;

// Find the literal with :something
let colon_literal = elements
.iter()
.find(|e| matches!(e, CsElement::Literal(text) if text.contains(":")));

match colon_literal {
Some(CsElement::Literal(text)) => {
assert_eq!(text, ":[something]");
}
_ => panic!("Expected literal with :something, got: {elements:#?}"),
}
}
}