diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 57ca05bdc..25a2808b5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 diff --git a/crates/concrete-syntax/src/concrete_syntax.pest b/crates/concrete-syntax/src/concrete_syntax.pest index 839833ccc..573f7febe 100644 --- a/crates/concrete-syntax/src/concrete_syntax.pest +++ b/crates/concrete-syntax/src/concrete_syntax.pest @@ -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) diff --git a/crates/concrete-syntax/src/models/concrete_syntax/interpreter.rs b/crates/concrete-syntax/src/models/concrete_syntax/interpreter.rs index 7f323338c..7faa2f2a9 100644 --- a/crates/concrete-syntax/src/models/concrete_syntax/interpreter.rs +++ b/crates/concrete-syntax/src/models/concrete_syntax/interpreter.rs @@ -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], diff --git a/crates/concrete-syntax/src/models/concrete_syntax/parser.rs b/crates/concrete-syntax/src/models/concrete_syntax/parser.rs index 77f8bae6f..072ee5879 100644 --- a/crates/concrete-syntax/src/models/concrete_syntax/parser.rs +++ b/crates/concrete-syntax/src/models/concrete_syntax/parser.rs @@ -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(); @@ -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); @@ -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 \/ @@ -416,7 +419,3 @@ impl ConcreteSyntax { }) } } - -#[cfg(test)] -#[path = "unit_tests/parser_test.rs"] -mod parser_test; diff --git a/crates/concrete-syntax/src/models/concrete_syntax/resolver.rs b/crates/concrete-syntax/src/models/concrete_syntax/resolver.rs index 764dbab81..836edd584 100644 --- a/crates/concrete-syntax/src/models/concrete_syntax/resolver.rs +++ b/crates/concrete-syntax/src/models/concrete_syntax/resolver.rs @@ -182,7 +182,3 @@ impl ConcreteSyntax { }) } } - -#[cfg(test)] -#[path = "unit_tests/resolver_test.rs"] -mod resolver_test; diff --git a/crates/concrete-syntax/src/models/concrete_syntax/tree_sitter_adapter.rs b/crates/concrete-syntax/src/models/concrete_syntax/tree_sitter_adapter.rs index e92b5d3fd..72faef3d8 100644 --- a/crates/concrete-syntax/src/models/concrete_syntax/tree_sitter_adapter.rs +++ b/crates/concrete-syntax/src/models/concrete_syntax/tree_sitter_adapter.rs @@ -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}; @@ -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")] diff --git a/crates/concrete-syntax/src/models/concrete_syntax/unit_tests/interpreter_test.rs b/crates/concrete-syntax/src/models/concrete_syntax/unit_tests/interpreter_test.rs index cfac0d074..07dcad6e4 100644 --- a/crates/concrete-syntax/src/models/concrete_syntax/unit_tests/interpreter_test.rs +++ b/crates/concrete-syntax/src/models/concrete_syntax/unit_tests/interpreter_test.rs @@ -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, + ); +} diff --git a/crates/concrete-syntax/src/models/concrete_syntax/unit_tests/parser_test.rs b/crates/concrete-syntax/src/models/concrete_syntax/unit_tests/parser_test.rs index b239e4cd2..ba31ce21c 100644 --- a/crates/concrete-syntax/src/models/concrete_syntax/unit_tests/parser_test.rs +++ b/crates/concrete-syntax/src/models/concrete_syntax/unit_tests/parser_test.rs @@ -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:#?}"), + } + } }